Treehouse

Image Protection Using PHP, the GD Library, JavaScript, and XHTML

By on  

Warning: The demo for this post may brick your browser.

A while back I posted a MooTools plugin called dwProtector that aimed to make image theft more difficult -- NOT PREVENT IT COMPLETELY -- but make it more difficult for the rookie to average user to download images. Despite being publicly insulted, and just for giggles, I've created another method: read in an image, store each pixel, and provide a method to turn that information into an HTML version of the image.

There are two solutions presented in this post: one by myself and one by MooTools creator Valerio Proietti. Each has its advantages and disadvantages.

The CSS (Both Solutions)

.image			{ position:relative; overflow:hidden; }
.pixel			{ width:1px; height:1px; position:absolute; }
#pixels .pixel { float:left; position:relative; width:1px; height:1px; }

We set up a container and pixel. I needed to add overflow:hidden; because IE6 was having fits.

The PHP: Store Pixels

// settings
$image = 'top-logo-pixel.png';

// get the image size
list($width,$height) = getimagesize($image);
$img = imagecreatefrompng($image);

// data array
$data = array('height'=>$height,'width'=>$width,'pixels'=>array());

// start the height loop
for ($y = 0; $y < $height; $y++){
	for ($x = 0; $x < $width; $x++){
		$color_index = imagecolorat($img, $x, $y);
		$a = ($color_index & 0x7F000000) >> 24;
		$r = ($color_index >> 16) & 0xFF;
		$g = ($color_index >> 8) & 0xFF;
		$b = $color_index & 0xFF;
		if ($a > 0){
			$a = abs(($a / 127) - 1);
			$a = hex(round($a * 255));
		} else {
			$a = '';
		}
		$r = hex($r);
		$g = hex($g);
		$b = hex($b);
		$data['pixels'][] = "$r$g$b$a";
	}
}

// clean up
imagedestroy($img);

/* utility function */
function hex($v){
	$bit = dechex($v);
	if (strlen($bit) == 1) $bit = "0$bit";
	return $bit;
}

The above functionality simply stores the image information in an array. No output to this point.

The PHP: Output Method 1: HTML/DIVs

	/* output as divs */
	$top = $left = 0;
	echo '<div class="image" style="width:',$data['width'],'px;height:',$data['height'],'px">';
	foreach($data['pixels'] as $index=>$pixel) {
		echo '<div class="pixel" style="background:#',substr($pixel,0,6),';top:',$top,'px;left:',$left,'px;"></div>';
		if($index && ($index % $data['width'] == 0)) { $top++; $left = 0; } else { $left++; }
	}
	echo '</div>';

My method is the simpler of the two: for every pixel, output a DIV with a background color equal to the pixel.

The PHP: Output Method 1: JSON -> MooTools -> DIVs

<?php 
	function optimize_data($data){

		$image = array(
			'colors' => array(),
			'data' => array(),
			'height' => $data['height'],
			'width' => $data['width']
		);

		$iterator = array();

		$i = 0;
		$gidx = 0;
		$num = 1;

		foreach($data['pixels'] as $color){
			if (!isset($image['colors'][$color])) $image['colors'][$color] = dechex($i++);

			$di = $image['colors'][$color];

			if ($pdi['c'] == $di){
				$num++;
				$image['data'][$pdi['i']] = $di.'-'.$num;
			} else {
				$num = 1;
				$image['data'][$gidx] = $di;
				$pdi = array('c' => $di, 'i' => $gidx);
				$gidx++;
			}
		}

		$image['data'] = implode($image['data'], ';');
		foreach($image['colors'] as $c => $v) $k[] = $v.':'.$c;
		$image['colors'] = implode($k, ';');

		return json_encode($image);

	}

?>

<script type="text/javascript">
	/* kamicane's version */
	var image = <?php print_r(optimize_data($data)); ?>;
	var colors = image.colors;
	var data = image.data;
	var height = image.height;
	var width = image.width;
	
	colors = colors.split(';');
	var ch = {};
	colors.each(function(c){
		var b = c.split(':');
		ch[b[0]] = b[1];
	});
	
	data = data.split(';');
	var pixels = [];
	
	data.each(function(d){
		d = d.split('-');
		if (d[1] == null) d[1] = 1;
		Number(d[1]).times(function(i){
			pixels.push(ch[d[0]]);
		});
	});
	
	document.addEvent('domready', function(){
		
		var pixelsE = $('pixels');
		
		pixelsE.setStyles({'height': image.height, 'width': image.width });

		pixels.each(function(p){
			var d = new Element('div', {'class': 'pixel'});
			d.style.backgroundColor = '#' + p;
			d.inject(pixelsE);
		});
		
	});
</script>

<div id="pixels"></div>

Valerio chose to take the array of pixels and use a simple compression method to output the pixels as JSON. He then creates MooTools JavaScript code to read the algorithm and output a series of DIVs to create the image.

Sebastian Markbåge's Method

And then there's Sebastian Markbåge, who dropped this bomb: http://blog.calyptus.eu/seb/2009/05/png-parser-in-javascript/. MooTools has some talented contributors, no? Check out Sebastian's Parsing Base64 Encoded Binary PNG Images in JavaScript post! Sebastian's is probably the best solution to output images as HTML.

Before the Panic...

This is merely a proof of concept post -- not a be-all, end-all solution. A simple print-screen will allow you to "capture" the image. These solutions are both a bit inefficient.

And Now...

There's no preventing it...bring the pain.

ydkjs-2.png

Recent Features

  • How to Create a Twitter&nbsp;Card

    One of my favorite social APIs was the Open Graph API adopted by Facebook.  Adding just a few META tags to each page allowed links to my article to be styled and presented the way I wanted them to, giving me a bit of control...

  • CSS&nbsp;Filters

    CSS filter support recently landed within WebKit nightlies. CSS filters provide a method for modifying the rendering of a basic DOM element, image, or video. CSS filters allow for blurring, warping, and modifying the color intensity of elements. Let's have...

Incredible Demos

  • CSS&nbsp;Kwicks

    One of the effects that made me excited about client side and JavaScript was the Kwicks effect.  Take a list of items and react to them accordingly when hovered.  Simple, sweet.  The effect was originally created with JavaScript but come five years later, our...

  • MooTools Overlay&nbsp;Plugin

    Overlays have become a big part of modern websites; we can probably attribute that to the numerous lightboxes that use them. I've found a ton of overlay code snippets out there but none of them satisfy my taste in code. Many of them are...

Discussion

  1. S4l1h

    Nice Work David

  2. error in Method 1: XHTML/DIVs ;)

  3. Yeah, I seemed to keep being a pixel off with that. Not sure how but I didn’t have the time to figure it out. I’ll hide behind the “proof of concept” excuse…

  4. But why? D:

  5. @Simon, why not?

    Nice stuff!

  6. Yeah. Why? You can always crop the image from screen… ;)

    But nice work, anyway…

  7. Simon

    I must say it’s quite impressive even if it couldn’t be used for every image (performance/load-time). Could be used on some images on specific pages.
    But again, very nice work !

  8. Now if I was REALLY bored, I could get the RGB value of each individual pixel, and do a paint by numbers in PS :)

  9. @simon, why could it not be used for all images? Surely the computation could be expelled once, and the RGB array serialized and stored in a DB or text file?

  10. Brianzet

    Very nice – but it still wont protect you from getting a img ripped these days..

  11. nice idea little bit problematic but nice only 1 thing i have to say, its a html error mess
    http://bit.ly/CC2rK

  12. Alexander Lehmann

    This has a major disadvantage since you are converting compressed images into hex dumps, for larger images this will produce quite large files.

  13. I’d bet that if you tried this on a busy site you’d very soon crash the server…very resource intensive for a solution that won’t fully protect your images.

  14. @Adriann: Possibly — it’s important to cache this info.

  15. Иван

    You know, you could optimize the method a little bit — use the left and right || top and bottom borders. You will save up to 3 times the divs. Of course, if the image dimensions are not dividable by 3, you should adjust the last element to be without border(s).

    Also, for the sake of of shortness, you could use A, I, U or any other one letter tag ;)

    Do you want a really nasty way to protect images? On DOM ready make the images invisible, then wait until the load. Put a SPAN with the same features as the image, then make the SPAN with Data URL background equal of the div. You would leave lots of rookies wondering “What the …”

    Off topic: now that I mentioned Data URL, which do you think is smaller and faster — a g-zipped PNG as a regular background, or it’s g-zipped base 64 equivalent in the styles?

  16. Иван

    For the above technique, I forgot to mention, that it does not work in some browsers ;)

  17. This was pretty cool. However, don’t forget print screen.

  18. Иван, a g-zipped PNG should be pretty useless because PNG uses the same algorithm as gzip (DEFLATE). If you save ANY space it probably means that you have some useless meta data in your image (something like “This image was created by Adobe ImageReady” and such). Never GZIP static PNGs.

    About base64 embedding. It depends on the size of the image, if the HTTP and connection overhead exceeds the overhead of base64, then embedding is better. Base64 is roughly 1.4 times the original data, but in this case it could be lessend by the overall GZIP compression of the entire CSS file. But that’s only viable if you combine it with other CSS data. So never include ONLY a single PNG per CSS.

    I guess it could be viable for multiple images, but then you’re better off with an image map.

    As a general rule I try to embed all small graphics with my CSS but never large imagery.

  19. Nice~ thank you for shared..

  20. This obfuscation technique is not real protection. Anybody could get your work. Keep important stuff out of internet :)

  21. Nice technique, but when I created and opened a page with 6 images (all APC cached), the firefox and the whole linux x64 system, on my i7 Xtreme machine, hanged for some seconds.
    Beautiful code up there, but it consumes a lot of CPU, not only from the server, but also from the client.

    PS. I’m really happy none asked yet for a JQuery version :p

  22. Simon

    @Rob Simpkins
    Well like Adriaan said its quite resource intensive and could crash the server (I guess).
    And I don’t know if you tried but I copied the html to try it in local, 555kb juste for this image !! Let’s imagine you’re doing this with an 1024×768 img… The ressources of your computer and of the server would be at their limits !
    But it’s true, it could be cached or stored in a DB (I prefer not to imagine the size of the DB after storing just a hundred of files like this…), reducing the load and performance loss.

  23. @All: Remember that this is simply proof of concept and the compression method would need to be much better to make this feasible.

  24. I find it both amusing and sad that people look at concepts and try to force them into real-world scenarios. What ever happened to proof-of-concepts, theorizing, and merely just doing it to do it, learn it, and become better?

    BAH! Kids today… :-D

  25. Tommix

    This is ABSOLUTELY USELESS. I don’t know any image protection who protects from PrintScreen, or any image capture soft.
    So this is just one another way to slow down your slow script. :)

  26. mgsmus

    @Tommix
    of course pal, everybody knows printscreen thing here but the matter is idea, programming, logic. How can you train up yourself without these ideas?

  27. could you do it in (rows of image length) instead of pixels to decrease load time? Then of course you could spit out the lines backwards with less opacity each time to create a reflection:

    function doReflect(){
          var i;
          var op;
    
      for(i=0; i <= HEIGHT; i++){
                  op = (1- (1/(HEIGHT/i)))/3; 
                        $('rep').innerHTML += "<div class='line' style='opacity: "+op+"; width: "+WIDTH+"px; background-position: 0px "+i+"px' class='imgBlock'></div>";
    } 
    

    Hmm i like that script. Reflects an image quite well.

  28. Patrick

    My memory just spiked 250MB by loading the demo page.

  29. Could please anyone tell me where to put this code in order to use it? I mean at header, at the main body… of my php main file. Thank you for your answer every can be!

  30. Nice good :D but browse load slow and not responding.

  31. Mr U

    Hello David

    Can you please explain were exactly and how I need to install the scripts code into the html code.

    Thanks

    Mr U

  32. Olly Hicks

    I get the feeling there is going to be someone that actually implements this on there site

  33. Dave

    Nice example and idea. This is a very tricky problem to solve, especially given print screen (and cameras for that matter). I thought on this for a year or so and finally found a way that is instant full proof. Not even a photograph can be used to snap and copy the image from the screen. I really like your function here for rendering image manipulations in HTML. (with your method the pixels can actually be given behavior which allows for some cool and crazy possibilities.)

  34. Pix by Pix :) Great idea !

    But it’s too heavy ! Not made for photo galleries ;)

  35. travelsonic

    “I thought on this for a year or so and finally found a way that is instant full proof. Not even a photograph can be used to snap and copy the image from the screen.”

    What, not putting it online in the first place?

    this [the tutorials] is stuff, but:

    1. the optimization thing is important. My browser almost crashed!
    2. The first example I was still able to left click + drag each piece of the “image” – assembling the image is a piece of cake once all the pieces are gathered.

    I feel compelled to try to work around just to see if I know how to do it, and it

  36. oli

    Isn’t there a more fullproof method with php?

  37. I found your website by mere random search, just like you I was looking for the next big gimmick in web image theft prevention. Here’s my take: http://www.eastfist.com/imgprotect. Sure, you could screencap the image it with printscreen, but I tried to make it as convoluted as possible, first notice the watermark. Ultimately, as they say in the photography circles, if you don’t want someone stealing your images, don’t post ‘em.

  38. You could use this method to embed images in newsletters. In this way images will not be blocked by the servers.

  39. how to retrieve more than one group of data from xml and also can we post multiple column of data to xml

  40. ash

    Could you please explain how to use implement this please?

  41. block screenshots and you will be onto something.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!