Skip to the content...

Welcome to the David Walsh Blog. I'm a MooTools, Dojo, jQuery, CSS, and PHP Web Developer located in Madison, Wisconsin, United States. Please contact me if I can make your experience on my website better.

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

35 Responses »

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.

Discussion

  1. s4l1h
    June 11, 2009 @ 8:09 am

    Nice Work David

  2. June 11, 2009 @ 8:25 am

    error in Method 1: XHTML/DIVs ;)

  3. June 11, 2009 @ 8:27 am

    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. June 11, 2009 @ 8:42 am

    But why? D:

  5. June 11, 2009 @ 8:50 am

    @Simon, why not?

    Nice stuff!

  6. June 11, 2009 @ 9:04 am

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

    But nice work, anyway…

  7. simon
    June 11, 2009 @ 9:47 am

    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. June 11, 2009 @ 9:51 am

    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. June 11, 2009 @ 9:53 am

    @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
    June 11, 2009 @ 10:26 am

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

  11. June 11, 2009 @ 10:41 am

    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
    June 11, 2009 @ 10:43 am

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

  13. June 11, 2009 @ 10:50 am

    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. June 11, 2009 @ 11:14 am

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

  15. Иван
    June 11, 2009 @ 1:52 pm

    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. Иван
    June 11, 2009 @ 1:53 pm

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

  17. June 11, 2009 @ 5:16 pm

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

  18. June 11, 2009 @ 6:58 pm

    Иван, 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. June 12, 2009 @ 4:00 am

    Nice~ thank you for shared..

  20. June 12, 2009 @ 5:52 am

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

  21. June 12, 2009 @ 7:37 am

    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
    June 12, 2009 @ 4:09 pm

    @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. June 12, 2009 @ 4:16 pm

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

  24. June 12, 2009 @ 8:13 pm

    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
    June 14, 2009 @ 2:22 pm

    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
    June 15, 2009 @ 2:26 am

    @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. June 15, 2009 @ 7:21 am

    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
    June 15, 2009 @ 8:03 pm

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

  29. June 26, 2009 @ 7:47 am

    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. July 3, 2009 @ 4:37 am

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

  31. mr u
    July 28, 2009 @ 4:43 pm

    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
    January 1, 2010 @ 8:19 am

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

  33. dave
    January 7, 2010 @ 8:37 pm

    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. May 7, 2010 @ 9:00 pm

    Pix by Pix :) Great idea !

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

  35. travelsonic
    August 30, 2010 @ 10:02 am

    “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

Be Heard!

Share your thoughts with fellow developers of all skill levels! I want to hear from you!

Name*:
Email*:
Website:  
Wrap your code with <code> tags, f00!