Page 1 of 1

php image generating experts

Posted: Fri Nov 21, 2003 6:55 pm
by m3mn0n
Would a Map similar to the one in the pic below:

http://www.r-fate.com/screen2.jpg

be possible to generate in PHP?

Each of those blocks, the trees, the houses, etc needs to be generated based on information in a database. Ive never seen any php image library do something like this before so I'm just wondering if it's possible, and how hard (an estimate) it would be to do so.

Thanks.

Posted: Fri Nov 21, 2003 7:42 pm
by infolock
it may not be as hard as you think, especially since these are blocks. you could make images with trees in it ( along with the half blocks you can see all around it ), and then generate graphing points out of the data you want. each point would be a tree, or house, or whatever you would specify ( wouldn't be much different from making a 3d bar chart in a way ). don't get me wrong, this would be a very long project to work with, but i'm sure it's possible at least :P

Posted: Fri Nov 21, 2003 8:11 pm
by m3mn0n
That's what I was thinking, I could make full blocks and partial blocks for percentages.

I'm having a bit of trouble figuring out the theory behind placing everything where it belongs, maybe it's because of my lack of experience with image generating. But i'm pretty sure i would also need a database grid system so everything gets assigned a grid placement, so each time the map is generated it doesnt randomly put stuff in places. I would imagine that would make the whole mapping idea very realistic if it worked that way. :)

Posted: Sat Nov 22, 2003 9:36 am
by pootergeist
The actual drawing of one image over another is *relatively* childs play.
The awkward part of the programming would be determining a starting x-pos and y-pos coordinate pair (more so if you chose to go a step further and use variable altitudes for the backing terrain)

The trig for determining a horizontal position and vertical from an isometric grid reference is standard enough though, which would return a good baseleft x-y pair - add an offset pair ² and then just run a function to do the overlay (sample below)

² - offset used for larger/smaller higher/taller overlays. You really want to have the top-y and left-x variables passed to the overlay function for ease of coding.

Sample overlay GD function with transparent colour facility

Code: Select all

<?php
class MAPBUILD {
	var $bgimage;		// a pointer to the background image
	
	function MAPBUILD($img='', $colour='CCCCCC', $width=360, $height=240) {
		$this->bgimage = Null;
		if(strlen($img) > 0 && file_exists($img)) {
				// img reference sent - create from that
			$this->giz = getimagesize($img);
			$this->bgimage = ($this->giz[2] < 4) ? ($this->giz[2] < 3) ? ($this->giz[2] < 2) ? ($this->giz[2] < 1) ? Null : imagecreatefromgif($img) : imagecreatefromjpeg($img) : imagecreatefrompng($img) : Null;
		} else {
				// no img reference, create blank image
			$this->bgimage = imagecreatetruecolor($width, $height);
			$bgcolour = imagecolorallocate($this->bgimage, hexdec(substr($colour,0,2)), hexdec(substr($colour,2,2)), hexdec(substr($colour,4,2)));
			imagefill($this->bgimage, 0, 0, $bgcolour);
		}
		return (is_null($this->bgimage)) ? false : true;
	}
	
	function mergeImage($merge_img="", $x_left=0, $y_top=0, $trans_colour="0000FF") {
		$this->mi = $merge_img;
		if(file_exists($this->mi)) {
			$this->xx = $x_left;
			$this->yy = $y_top;
			$this->tc = array(
				'red'=> hexdec(substr($trans_colour,0,2)),
				'green'=> hexdec(substr($trans_colour,2,2)),
				'blue'=> hexdec(substr($trans_colour,4,2))
				);
			$this->md = getimagesize($this->mi);
			$this->mw = $this->md[0];
			$this->mh = $this->md[1];
			$this->mm = ($this->md[2] < 4) ? ($this->md[2] < 3) ? ($this->md[2] < 2) ? imagecreatefromgif($this->mi) : imagecreatefromjpeg($this->mi) : imagecreatefrompng($this->mi) : Null;
				// we now have an overlay image stored in GD format as $this->mm 
				// with width $this->mw and height $this->mh
				// iterate that image a pixel at a time
			for($this->ypo = 0; $this->ypo < $this->mh; $this->ypo++) {
				for($this->xpo = 0; $this->xpo < $this->mw; $this->xpo++) {
					$this->indx_rgb = imagecolorsforindex($this->mm, imagecolorat($this->mm, $this->xpo, $this->ypo));
					if(($this->indx_rgb['red'] == $this->tc['red']) && ($this->indx_rgb['green'] == $this->tc['green']) && ($this->indx_rgb['blue'] == $this->tc['blue'])) {
						// transparent, so ignore
					} else {
						// not transparent colour, so overlay
						$pixel_colour = imagecolorallocate($this->bgimage, $this->indx_rgb['red'], $this->indx_rgb['green'], $this->indx_rgb['blue']);
						imagesetpixel($this->bgimage, $this->xx+$this->xpo, $this->yy+$this->ypo, $pixel_colour);
					}
				}
			}
		imagedestroy($this->mm);
			return true;
		} else {
			return false;
		}
	}
}

/*********** SAMPLE UNDYNAMIC CALL

$a = new MAPBUILD;
$a->mergeImage('./imgs/1.png',50,50);
$a->mergeImage('./imgs/1.png', 150, 120);
$a->mergeImage('./imgs/1.png',250,50);
$a->mergeImage('./imgs/1.png', 10, 190);

header("Content-type: image/jpg");
imagepng($a->bgimage);

***********************************/
?>
The exact protocols you use for storing retrieving information about items and item placement would sort of depend upon what the rest of the application does.

Subnotes:
I used imagesetpixel rather than imagecopymerge as it should be a touch faster and also the imagecopymerge function is fubared in 2.0.10 to 2.0.12 or thereabouts.

You should also note that using ping *.png files for the merge images is best as they can hold the transparent colour without speckling or loss. Try the same with jpegs and you would have some pixels show that were meant as transparent.

Posted: Sat Nov 22, 2003 10:01 am
by pootergeist
Looking back at that - repeated imageMerge calls on the same overlay image *should* really be handled by passing a multi-dim array into the imageMerge function.

You could probably also imagemap the locations while you were assembling the components, thus allowing people to click where they wanted to go and/or displaying alt/title text on mouseover.

You could draw the landscape in a similar fashion (starting at the top and working downwards a grid at a time) and save area background images locally to speed up the compile.

>> off to tinker with some altitude referencing - might have a better working model soon.

Posted: Sun Nov 23, 2003 6:25 am
by pootergeist
Had a further tinker and opted to use nested objects for the overlay images -
As they sit within an associative array of the parent MAPBUILD object, they are only instantiated once. When instantiated they are assembled as an array[x-pos][y-pos] = array(RR, GG, BB, AA); of non transparent pixels.
Each object is then draw over the map by iterating its pixel array - should (and does) speed the process somewhat.

If you wanted to go astep further, I'd advise using the .gd2 file format rather than png (just slightly faster as more native to gd)

Anyway, http://www.teckis.com/testing/overlay/ovre.php uses five nested objects (images) to create the map shown....

Code: Select all

<?php
class MAPBUILD {
	var $images;		// array of images used
	var $path;			// path to images used
	
	function MAPBUILD($img='', $colour='CCCCCC', $width=360, $height=240) {
		$this->flush();
		$this->path = '/home/teckisc/public_html/testing/overlay/imgs/';
		$this->images['background'] = new MAKEIMAGE($this->path.$img.'.png', $colour, $width, $height);
		return $this->images['background'];
	}
	
	function mergeImage($merge_img="", $x_left=0, $y_top=0, $trans_colour="0000FF", $altitude=0, $xplus=0, $yplus=0) {
		if(!isset($this->images[$merge_img])) {
			// image not yet created
			$this->images[$merge_img] = new MAKEIMAGE($this->path.$merge_img.'.png');
			$this->images[$merge_img]->tc = array(
				'red'=> hexdec(substr($trans_colour,0,2)),
				'green'=> hexdec(substr($trans_colour,2,2)),
				'blue'=> hexdec(substr($trans_colour,4,2))
				);
				//
				// we now have an overlay image stored in GD format as $this->images['img_name']->img 
				// with width $this->images['img_name']->w and height $this->images['img_name']->h
				// iterate that image a pixel at a time to build array
				// $this->images['img_name']->pxs[left][top] = array('red','green','blue')
				//
			for($this->ypo = 0; $this->ypo < $this->images[$merge_img]->h; $this->ypo++) {
				for($this->xpo = 0; $this->xpo < $this->images[$merge_img]->w; $this->xpo++) {
					$this->indx_rgb = imagecolorsforindex($this->images[$merge_img]->img, imagecolorat($this->images[$merge_img]->img, $this->xpo, $this->ypo));
					if(
					($this->indx_rgb['red'] != $this->images[$merge_img]->tc['red']) || 
					($this->indx_rgb['green'] != $this->images[$merge_img]->tc['green']) || 
					($this->indx_rgb['blue'] != $this->images[$merge_img]->tc['blue'])) {
						// not transparent colour, so set as array index
						$this->images[$merge_img]->pxs[$this->ypo][$this->xpo] = $this->indx_rgb;
					}
				}
			}
		}
		
		$altitude = (substr($merge_img,0,1) == 'h') ? 7: 0;
		if($this->images[$merge_img] == true) {
			foreach($this->images[$merge_img]->pxs AS $ypos=>$vals) {
				foreach($vals AS $xpos=>$pxc) {
					imagesetpixel($this->images['background']->img, $x_left+$xpos+$xplus, $y_top+$ypos-$yplus-$altitude, imagecolorallocate($this->images['background']->img, $pxc['red'], $pxc['green'], $pxc['blue']));
				}
			}
			
			return true;
		} else {
			return false;
		}
	}

	function destroyAll() {
		foreach($this->images AS $image) {
			$image->destroyImage();
		}
	}
	
	function showMap($savename='') {
		if(strlen($savename) > 0) {
			imagepng($this->images['background']->img, $savename);
		} else {
			header('Content-type: image/png');
			imagepng($this->images['background']->img);
		}
	}
	
	function flush() {
		$this->images = array();
		$this->path = '';
	}
}

class MAKEIMAGE {
	var $img;			// the image itself
	var $w;				// width of image
	var $h;				// height of image
	var $t;				// type of image
	var $tc;			// rgb array of transparent colour for overlays
	var $pxs;			// rgba array of non transparent pixels - array[y-pos][x-pos] = rgba

	function MAKEIMAGE($img='', $colour='CCCCCC', $width=32, $height=22) {
		$this->flush();
		$this->img = Null;
		if(strlen($img) > 0 && file_exists($img)) {
				// img reference sent - create from that
			$this->giz = getimagesize($img);
			$this->w = $this->giz[0];
			$this->h = $this->giz[1];
			$this->t = $this->giz[2];
			$this->img = ($this->t < 4) ? ($this->t < 3) ? ($this->t < 2) ? ($this->t < 1) ? Null : imagecreatefromgif($img) : imagecreatefromjpeg($img) : imagecreatefrompng($img) : Null;
		} else {
				// no img reference, create blank image
			$this->img = imagecreatetruecolor($width, $height);
			$bgcolour = imagecolorallocate($this->img, hexdec(substr($colour,0,2)), hexdec(substr($colour,2,2)), hexdec(substr($colour,4,2)));
			imagefill($this->img, 0, 0, $bgcolour);
			$this->w = $width;
			$this->h = $height;
		}
		return (is_null($this->img)) ? false : true;
	}

	function destroyImage() {
		imagedestroy($this->img);
	}
	
	function flush() {
		$this->h = 0;
		$this->w = 0;
		$this->img = Null;
		$this->t = 3;
		$this->tc = array();
		$this->pxs = array();
	}
}
/*********** SAMPLE UNDYNAMIC CALL */

$landscape = array(
	0 => array('w1', 'w1', 'w1','w1','w1', 'w1', 'w1','g1','h1'),
	1 => array(Null, 'w1', 'w1','w1','w1', 'w1', 'w1','g1','h1'),
	2 => array(Null, Null, 'w1','w1','g1', 'g1', 'g1','g1','h1'),
	3 => array(Null, Null, 'g1','g1','g1', 'g1', 'g1','g1','h2'),
	4 => array(Null, 'g1', 'g1', 'g1', 'g1', 'g1', 'g1','g1','g1'),
	5 => array('g1', 'g1', 'g1', 'g1', 'g1', 'g1', 'g1','g1','g1'),
	6 => array('g1', 'g1', 'g1', Null, Null, Null, 'g1', 'g1', 'g1')
	);
$rows = count($landscape);
$cols = count($landscape[0]);
$mapwidth = (($cols + $rows) * 16) + 4;
$mapheight = (($rows + $cols) *  + 18;

$a = new MAPBUILD('', 'DDDDDD', $mapwidth, $mapheight);

$offsetleft = -14;
$offsettop = 10;
for($rowcnt = 0; $rowcnt < $rows; $rowcnt++) {
	$offsetleft += ($cols / 2)*32;
	for($colcnt = $cols-1; $colcnt > -1; $colcnt--) {
		if($landscape[$rowcnt][$colcnt] != Null) {
			$a->mergeImage($landscape[$rowcnt][$colcnt], $offsetleft, $offsettop);
		}
		$offsettop += 8;
		$offsetleft -= 16;
	}
	$offsettop -= ($cols-1) * 8;
	$offsetleft += 16;
}
$a->mergeImage('library', 146, 43);

$a->showMap();

$a->destroyAll();

// ***********************************/
?>
Quite a bit of work to get that running nice from a database - perhaps

MAPS
`id` AUTO_INCREMENT
`name` VARCHAR
`desc` VARCHAR
`rows` SMALLINT 2
`cols` SMALLINT 2

MAP_ITEMS
`id` AUTO_INCREMENT
`map_id` MEDIUMINT 8
`type` SMALLINT 2 // eg 1=terrain, 2=groundItems, 3=aerial stuff
`image` VARCHAR
`row` SMALLINT 2
`col` SMALLINT 2
`altitude` SMALLINT 2
`x_offset` SMALLINT 2
`y_offset` SMALLINT 2

then you could store the terrain and items in the same table and order by row, col, type ASC.
That would also facilitate referencing any item to a grid rather than full x,y from top left.

Feel free to hack that around any way you want.

Have a major project at work and am moving house to a non-net-connected abode, so will not have much time to domuch more to this coding. Will try to keep an eye on things during my lunchbreaks , so if you meet a brickwall, just holler and wait.

Posted: Mon Nov 24, 2003 1:03 am
by m3mn0n
Thx for that. =)

Anyone know of a tutorial that would explain how to create such a map. I only like to work with code that I fully understand what is going on with it. ;)