Home > Apache, Javascript, PHP > How to stuff 1000 ( or more ) images into a page, without the need for scrollbars

How to stuff 1000 ( or more ) images into a page, without the need for scrollbars

December 26th, 2007

I got this call from a friend, Boris, who’s in a band called geRRIT. He asked me to help him out on an idea he had for getting geRRIT’s songs spread around the world: homeless-songs.

The idea was to send 4000 cd’s to 15 cities around the world, and ask people to copy the cd and give the original to someone else, or leave it at a public spot in town. And while you’re at it, leave a message with picture on a website. Inspired by J.A.L.D (just another link dump) we came to a design that would fit all images that would be uploaded, whether it would be just 10 pictures, or 1000, it had to fit into one page.

So I started some tryouts:

first runner up: using Javascript to resize all images. The major drawback using this technique; bandwidth. If there would be 1000 pictures, each having an original resolution of 1024×768 (which is quite small) and about 300 kb each, the client would have to download 1000×300kb of pictures, a whopping 292 MB worth of data, which will eventually be displayed as tiny thumbnails. A huge waste of bandwidth! Not only bandwidth would be a problem, what about the javascript itself? I’d probably be using getElementsByTagName, or getElementsByClassName, loop through 1000 images, resizing each images. IMHO. a pretty expensive operation, even for a modern computer/browser.

So.. no javascript for resizing, what other options are there?

Second runner up: using GD or Imagick to resize the images on the  fly. Verrrrry expensive, but with the (visually) best results. Each uploaded image would be dynamically resized to fit the grid of images. I’d use an Ajax call after the page is loaded, pass the size of the viewport to a PHP-script which is able to calculate the max-width & height of each image, resizing a copy of the original and send something (xml/json/plain html) back to the client. Well.. I own a server which has a lot of  processing power, but 10 concurrent sessions of resizing-images-script would even bring this Xeon Quad-core to it’s knees (even if i’d cache the resized images for later use). Not an option!

Third runner up:
Use GD/Imagick to resize the original into several resized copies. After a client uploads his/hers image the server would make a certain amount of resized copies. When a client opens the page, the size of the viewport is determined and sent back to the server using an Ajax-call. The server determines the total area and determines how may pixels one image may have, loops through the available sizes, and returns the size that fits the amount of pixels the image may have. I actually build this solution in Ruby on Rails. I mixed PHP and Ruby before (well.. mixed.. just called RoR-pages from a PHP-based webpage using Ajax and some smart proxy-ing), and liked the idea of spreading performance among different
techniques. If the site were to be a HUGE success the actual calculation would become the bottleneck; since RoR is extremely scalable I could setup another (RoR) server to handle the load.
The Proof Of Concept did hold well, even under a bit of stress, however; RoR itself uses quite a lot or resources and I’m not an expert on RoR (yet). I did not want to leave a potentially dangerous script (remember; people are uploading stuff) on a
production-server while i’m not really able to assess the potential security issues.  So; I rewrote the thing in PHP.

Another problem I came across was the aspect-ratio of the uploaded images. The design would be a grid-like solution, all images would have to fill up a certain amount of pixels, leaving no space between them. But; what if an image has a  spect-ratio of 16×10, and the image next to it, say 10×16. One of them has to be resized to match the other height, or width, leaving no space above. And how would I calculate the optimal size of each image? Not knowing the length of each side? So I dug up some old math-books to check out whether I forgot some rule or calculation.. No results.
I did write some code to roughly guess the average size of one image, but the results were quite strange and unpredictive. I came to the conclusion that the only way to get those images aligned and keep a reasonable quality was to crop them to have a common aspect-ratio. Fortunately Imagick has a build-in method to accomplish this: cropThumbnailImage.

OK, So, how did I solve this ?

Pretty easy! I wrote one function which resized one uploaded image into 15 different sizes, store each of those images into a folder named after the size of the image and wrote a function to calculate the optimal size of those images (based on x and y from the ajax-call) to output to the client. I tried to resize the images again using javascript, to get them to fit exactly into the page, but this was too expensive and images didn’t look as crisp as they should be. PHP-Function for resizing an image:

private function getImages($country = '', $city=''){
	if ($country){
		$sqlExtra = " AND country LIKE '" .  mysql_escape_string($country) . "' ";
	}
	if ($city){
		$sqlExtra .= " AND city LIKE '" . mysql_escape_string($city). "'";
	}
	$sql = "SELECT * FROM reactions WHERE `show` = 1 " . $sqlExtra . " ORDER BY created_at DESC";
	$rs = $this->wps->getADO()->execute($sql);
	$count = $rs->recordCount() + 2;

	$totalArea = $this->totalArea();
	$areaPerImage = ($totalArea/$count);
	// determine which image path to use:
	foreach($this->sizes as $size){
		$sizeH = $size + $this->padCorrectionH;
		$sizeW = $size + $this->padCorrectionW;
		$imgArea = ($sizeH*$sizeW);
		if ($imgArea < $areaPerImage){
			$imgPath = $size;
			break;
		}
	}
	// make up the xml for the images in $imgPath;
	$xml ='';
	while (!$rs->EOF){
		if (!$rs->fields['has_image']){
			$xml .='<gerrit_image id="' . $rs->fields['id'] . '" color="#' . $this->getRndColor(). '">';
		}else{
			$xml .='<gerrit_image file="' . $imgPath . '/' . $rs->fields['id'] . '.jpg" id="' . $rs->fields['id'] . '">';
		}
		$xml .='<name>' . $rs->fields['name'] . '</name>';
		$xml .='<city>' . $rs->fields['city'] . '</city>';
		$xml .='<country>' . $rs->fields['country'] . '</country>';
		$xml .='</gerrit_image>';
		$rs->moveNext();
	}

	$xml = '<gerrit_images size="' . $imgPath . '" count="' . ($count-2). '">' . $xml . '</gerrit_images>';
	$this->WPS_DB_ado2dom = new DOMDocument();
	$this->WPS_DB_ado2dom->loadXML($xml);
}

Javascript code : Ajax call to get the images from the server:


function resize(){ // executed at page-loaded-event, and on page-resize.
	var screenSize = Element.getDimensions(document.body);
	var absBottom = screenSize.height;
	var absRight = screenSize.width;
	var width = (absRight - 305 - 30);
 	$('mainContainer').style.width = width + 'px';
 	$('mainContainer').style.height = (absBottom - 60) + 'px';
 	loadDefaultContent(currCountry, currCity);
}

function loadDefaultContent(country, city){
// Use Prototype-Ajax-updater to update the image-container
	if (!country) country = '';
	if (!city) city = '';
	var screenSize = Element.getDimensions($('mainContainer'));
	try{
		new Ajax.Updater(
			'mainContainer',
			gRelRootPath + 'get_images/&amp;city=' + city + '&amp;c=' + country + '&amp;w='+
				(screenSize.width) + '&amp;h=' +(screenSize.height ),
				{
					asynchronous:true,
					evalScripts:true,
					onLoading : waiter,
					onLoaded : unWaiter,
					onComplete :function(transport){ makeSortable(); }
				}
			);
	}catch(e){
	}
	return false;
}

Checkout the result : www.homeless-songs.com

Share this post:
  • Twitter
  • Facebook
  • Digg
  • del.icio.us
  • FriendFeed
  • Technorati
  • Google Bookmarks
  • PDF
  • Print
  • NuJIJ
  • Ping.fm
  • StumbleUpon
  • Symbaloo
  • Hyves

buTTon Apache, Javascript, PHP , , ,

  1. No comments yet.
  1. No trackbacks yet.

Onze tip:SCDB.info – de meest actuele flitspalen in Europa voor uw GPS!