1<?php
2/**
3 * image processing functions
4 * @package core
5 * @subpackage functions\functions-image
6 *
7 */
8// force UTF-8 Ø
9// functions-image.php - HEADERS NOT SENT YET!
10
11/**
12 * If in debug mode, prints the given error message and continues; otherwise redirects
13 * to the given error message image and exits; designed for a production gallery.
14 *
15 * Nothing is but logged only if debuglog_error/returnmode mode is enabled
16 *
17 * @param type $errormessage
18 * @param type $errorimg
19 */
20
21/**
22 *
23 * @global string $newfilename
24 * @global string $album
25 * @global string $image
26 * @param string $status_text
27 * @param string $errormessage the error message to print if $_GET['debug'] is set.
28 * @param string $errorimg the filename of the error image to display for production. Defaults to 'err-imagegeneral.png'. Images should be located in /zen/images .
29 * @param string $image
30 * @param string $album
31 * @param string $newfilename
32 */
33function imageError($status_text, $errormessage, $errorimg = 'err-imagegeneral.png', $image = '', $album='', $newfilename = '') {
34	//global $newfilename, $album, $image; // sometime these globals need to be properly named…
35	$debug = isset($_GET['debug']);
36	$debuglog_errors = isset($_GET['returnmode']);
37	if ($debug) {
38		$debugnote = '<strong>' . sprintf(gettext('Zenphoto Image Processing Error: %s'), $errormessage) . '</strong>';
39		$debugnote .= '<br /><br />' . sprintf(gettext('Request URI: [ <code>%s</code> ]'), html_encode(getRequestURI()));
40		$debugnote .= '<br />PHP_SELF: [ <code>' . html_encode($_SERVER['PHP_SELF']) . '</code> ]';
41		$debugnote .= (empty($newfilename) ? '' : '<br />' . sprintf(gettext('Cache: [<code>%s</code>]'), '/' . CACHEFOLDER . '/' . html_encode(sanitize($newfilename, 3))) . ' ');
42		$debugnote .= (empty($image) || empty($album) ? '' : ' <br />' . sprintf(gettext('Image: [<code>%s</code>]'), html_encode(sanitize($album . '/' . $image, 3))) . ' <br />');
43		if($debuglog_errors) {
44			debugLog($debugnote);
45		} else {
46			echo $debugnote;
47		}
48	} else {
49		if (DEBUG_IMAGE_ERR) {
50			trigger_error($errormessage, E_USER_NOTICE);
51		}
52		if(!$debuglog_errors) {
53			header("HTTP/1.0 $status_text");
54			header("Status: $status_text");
55			redirectURL(FULLWEBPATH . '/' . ZENFOLDER . '/images/' . $errorimg);
56		}
57	}
58	exitZP();
59}
60
61/**
62 * Prints debug information from the arguments to i.php.
63 *
64 * @param string $album alubm name
65 * @param string $image image name
66 * @param array $args size/crop arguments
67 * @param string $imgfile the filename of the image
68 */
69function imageDebug($album, $image, $args, $imgfile) {
70	list($size, $width, $height, $cw, $ch, $cx, $cy, $quality, $thumb, $crop) = $args;
71	echo "Album: [ " . $album . " ], Image: [ " . $image . " ]<br /><br />";
72	if (file_exists($imgfile)) {
73		echo "Image filesize: " . filesize($imgfile);
74	} else {
75		echo "Image file not found.";
76	}
77	echo '<br /><br />';
78	echo "<strong>" . gettext("Debug") . " <code>i.php</code> | " . gettext("Arguments:") . "</strong><br />\n\n"
79	?>
80	<ul>
81		<li><?php echo gettext("size ="); ?>   <strong> <?php echo sanitize($size, 3) ?> </strong></li>
82		<li><?php echo gettext("width =") ?>   <strong> <?php echo sanitize($width, 3) ?> </strong></li>
83		<li><?php echo gettext("height =") ?>  <strong> <?php echo sanitize($height, 3) ?> </strong></li>
84		<li><?php echo gettext("cw =") ?>      <strong> <?php echo sanitize($cw, 3) ?> </strong></li>
85		<li><?php echo gettext("ch =") ?>      <strong> <?php echo sanitize($ch, 3) ?> </strong></li>
86		<li><?php echo gettext("cx =") ?>      <strong> <?php echo sanitize($cx, 3) ?> </strong></li>
87		<li><?php echo gettext("cy =") ?>      <strong> <?php echo sanitize($cy, 3) ?> </strong></li>
88		<li><?php echo gettext("quality =") ?> <strong> <?php echo sanitize($quality, 3) ?> </strong></li>
89		<li><?php echo gettext("thumb =") ?>   <strong> <?php echo sanitize($thumb, 3) ?> </strong></li>
90		<li><?php echo gettext("crop =") ?>    <strong> <?php echo sanitize($crop, 3) ?> </strong></li>
91	</ul>
92	<?php
93}
94
95/**
96 * Calculates proprotional width and height
97 * Used internally by cacheImage
98 *
99 * Returns array containing the new width and height
100 *
101 * @param int $size
102 * @param int $width
103 * @param int $height
104 * @param int $w
105 * @param int $h
106 * @param int $thumb
107 * @param int $image_use_side
108 * @param int $dim
109 * @return array
110 */
111function propSizes($size, $width, $height, $w, $h, $thumb, $image_use_side, $dim) {
112	$hprop = round(($h / $w) * $dim);
113	$wprop = round(($w / $h) * $dim);
114	if ($size) {
115		if (((($image_use_side == 'longest')) && $h > $w) || ($image_use_side == 'height') || ($image_use_side == 'shortest' && $h < $w)) {
116			$newh = $dim; // height is the size and width is proportional
117			$neww = $wprop;
118		} else {
119			$neww = $dim; // width is the size and height is proportional
120			$newh = $hprop;
121		}
122	} else { // length and/or width is set, size is NULL (Thumbs work the same as image in this case)
123		if ($height) {
124			$newh = $height; // height is supplied, use it
125		} else {
126			$newh = $hprop; // height not supplied, use the proprotional
127		}
128		if ($width) {
129			$neww = $width; // width is supplied, use it
130		} else {
131			$neww = $wprop; // width is not supplied, use the proportional
132		}
133	}
134	if (DEBUG_IMAGE)
135		debugLog("propSizes(\$size=$size, \$width=$width, \$height=$height, \$w=$w, \$h=$h, \$thumb=$thumb, \$image_use_side=$image_use_side, \$dim=$dim):: \$wprop=$wprop; \$hprop=$hprop; \$neww=$neww; \$newh=$newh");
136	return array($neww, $newh);
137}
138
139/**
140 * iptc_make_tag() function by Thies C. Arntzen
141 * @param $rec
142 * @param $data
143 * @param $value
144 */
145function iptc_make_tag($rec, $data, $value) {
146	$length = strlen($value);
147	$retval = chr(0x1C) . chr($rec) . chr($data);
148	if ($length < 0x8000) {
149		$retval .= chr($length >> 8) . chr($length & 0xFF);
150	} else {
151		$retval .= chr(0x80) . chr(0x04) . chr(($length >> 24) & 0xFF) . chr(($length >> 16) & 0xFF) . chr(($length >> 8) & 0xFF) . chr($length & 0xFF);
152	}
153	return $retval . $value;
154}
155
156/**
157 * Creates the cache folder version of the image, including watermarking
158 *
159 * @param string $newfilename the name of the file when it is in the cache
160 * @param string $imgfile the image name
161 * @param array $args the cropping arguments
162 * @param bool $allow_watermark set to true if image may be watermarked
163 * @param string $theme the current theme
164 * @param string $album the album containing the image
165 */
166function cacheImage($newfilename, $imgfile, $args, $allow_watermark = false, $theme, $album) {
167	global $_zp_gallery;
168	try {
169		@list($size, $width, $height, $cw, $ch, $cx, $cy, $quality, $thumb, $crop, $thumbstandin, $passedWM, $adminrequest, $effects) = $args;
170		// Set the config variables for convenience.
171		if($thumb) {
172			$image_use_side = getOption('thumb_use_side');
173		} else {
174			$image_use_side = getOption('image_use_side');
175		}
176		$upscale = getOption('image_allow_upscale');
177		$allowscale = true;
178		$sharpenthumbs = getOption('thumb_sharpen');
179		$sharpenimages = getOption('image_sharpen');
180		$id = $im = NULL;
181		$watermark_use_image = getAlbumInherited($album, 'watermark', $id);
182		if (empty($watermark_use_image)) {
183			$watermark_use_image = IMAGE_WATERMARK;
184		}
185		if (!$effects) {
186			if ($thumb && getOption('thumb_gray')) {
187				$effects = 'gray';
188			} else if (getOption('image_gray')) {
189				$effects = 'gray';
190			}
191		}
192		$newfile = SERVERCACHE . $newfilename;
193		mkdir_recursive(dirname($newfile), FOLDER_MOD);
194		if (DEBUG_IMAGE)
195			debugLog("cacheImage(\$imgfile=" . basename($imgfile) . ", \$newfilename=$newfilename, \$allow_watermark=$allow_watermark, \$theme=$theme) \$size=$size, \$width=$width, \$height=$height, \$cw=$cw, \$ch=$ch, \$cx=" . (is_null($cx) ? 'NULL' : $cx) . ", \$cy=" . (is_null($cy) ? 'NULL' : $cy) . ", \$quality=$quality, \$thumb=$thumb, \$crop=$crop \$image_use_side=$image_use_side; \$upscale=$upscale);");
196		// Check for the source image.
197		if (!file_exists($imgfile) || !is_readable($imgfile)) {
198			imageError('404 Not Found', sprintf(gettext('Image %s not found or is unreadable.'), filesystemToInternal($imgfile)), 'err-imagenotfound.png');
199		}
200		$rotate = false;
201		if (zp_imageCanRotate()) {
202			$rotate = getImageRotation($imgfile);
203		}
204		$s = getSuffix($imgfile);
205		if (function_exists('exif_thumbnail') && getOption('use_embedded_thumb') && ($s == 'jpg' || $s == 'jpeg')) {
206			$im = exif_thumbnail($imgfile, $tw, $th, $tt);
207			if ($im) {
208				if ($size) {
209					$big_enough = $tw >= $size && $th >= $size;
210				} else {
211					$big_enough = $tw >= $width && $th >= $height;
212				}
213				if ($big_enough) {
214					$im = zp_imageFromString($im);
215					if (DEBUG_IMAGE && $im)
216						debugLog(sprintf(gettext('Using %1$ux%2$u %3$s thumbnail image.'), $tw, $th, image_type_to_mime_type($tt)));
217				} else {
218					$im = false;
219				}
220			} else {
221				$im = false;
222			}
223		}
224		if (!$im) {
225			$im = zp_imageGet($imgfile);
226		}
227		if (!$im) {
228			imageError('404 Not Found', sprintf(gettext('Image %s not renderable (imageGet).'), filesystemToInternal($imgfile)), 'err-failimage.png', $imgfile, $album, $newfilename);
229		}
230		if ($rotate) {
231			if (DEBUG_IMAGE)
232				debugLog("cacheImage:rotate->$rotate");
233			$im = zp_rotateImage($im, $rotate);
234			if (!$im) {
235				imageError('404 Not Found', sprintf(gettext('Image %s not rotatable.'), filesystemToInternal($imgfile)), 'err-failimage.png', $imgfile, $album, $newfilename);
236			}
237		}
238		$w = zp_imageWidth($im);
239		$h = zp_imageHeight($im);
240		// Give the sizing dimension to $dim
241		$ratio_in = '';
242		$ratio_out = '';
243		$crop = ($crop || $cw != 0 || $ch != 0);
244		if (!empty($size)) {
245			$dim = $size;
246			if ($crop) {
247				$dim = $size;
248				if (!$ch)
249					$ch = $size;
250				if (!$cw)
251					$cw = $size;
252				$width = $cw;
253				$height = $ch;
254				$size = false;
255			} else {
256				$width = $height = false;
257			}
258		} else if (!empty($width) && !empty($height)) {
259			$ratio_in = $h / $w;
260			$ratio_out = $height / $width;
261			if ($ratio_in > $ratio_out) { // image is taller than desired, $height is the determining factor
262				$thumb = true;
263				$dim = $width;
264				if (!$ch)
265					$ch = $height;
266			} else { // image is wider than desired, $width is the determining factor
267				$dim = $height;
268				if (!$cw)
269					$cw = $width;
270			}
271		} else if (!empty($width)) {
272			$dim = $width;
273			$size = $height = false;
274		} else if (!empty($height)) {
275			$dim = $height;
276			$size = $width = false;
277		} else {
278			// There's a problem up there somewhere...
279			imageError('404 Not Found', sprintf(gettext('Unknown error processing %s! Please report to the developers at <a href="http://www.zenphoto.org/">www.zenphoto.org</a>'), filesystemToInternal($imgfile)), 'err-imagegeneral.png', $imgfile, $album, $newfilename);
280		}
281
282		$sizes = propSizes($size, $width, $height, $w, $h, $thumb, $image_use_side, $dim);
283		list($neww, $newh) = $sizes;
284
285		if (DEBUG_IMAGE)
286			debugLog("cacheImage:" . basename($imgfile) . ": \$size=$size, \$width=$width, \$height=$height, \$w=$w; \$h=$h; \$cw=$cw, " .
287							"\$ch=$ch, \$cx=$cx, \$cy=$cy, \$quality=$quality, \$thumb=$thumb, \$crop=$crop, \$newh=$newh, \$neww=$neww, \$dim=$dim, " .
288							"\$ratio_in=$ratio_in, \$ratio_out=$ratio_out \$upscale=$upscale \$rotate=$rotate \$effects=$effects");
289
290		if (!$upscale && $newh >= $h && $neww >= $w) { // image is the same size or smaller than the request
291			$neww = $w;
292			$newh = $h;
293			$allowscale = false;
294			if ($crop) {
295				if ($width > $neww) {
296					$width = $neww;
297				}
298				if ($height > $newh) {
299					$height = $newh;
300				}
301			}
302			if (DEBUG_IMAGE)
303				debugLog("cacheImage:no upscale " . basename($imgfile) . ":  \$newh=$newh, \$neww=$neww, \$crop=$crop, \$thumb=$thumb, \$rotate=$rotate, watermark=" . $watermark_use_image);
304		}
305
306		$watermark_image = false;
307		if ($passedWM) {
308			if ($passedWM != NO_WATERMARK) {
309				$watermark_image = getWatermarkPath($passedWM);
310				if (!file_exists($watermark_image)) {
311					$watermark_image = SERVERPATH . '/' . ZENFOLDER . '/images/imageDefault.png';
312				}
313			}
314		} else {
315			if ($allow_watermark) {
316				$watermark_image = $watermark_use_image;
317				if ($watermark_image) {
318					if ($watermark_image != NO_WATERMARK) {
319						$watermark_image = getWatermarkPath($watermark_image);
320						if (!file_exists($watermark_image)) {
321							$watermark_image = SERVERPATH . '/' . ZENFOLDER . '/images/imageDefault.png';
322						}
323					}
324				}
325			}
326		}
327
328		// Crop the image if requested.
329		if ($crop) {
330			if ($cw > $ch) {
331				$ir = $ch / $cw;
332			} else {
333				$ir = $cw / $ch;
334			}
335			if ($size) {
336				$neww = $size;
337				$newh = $ir * $size;
338			} else {
339				$neww = $width;
340				$newh = $height;
341				if ($neww > $newh) {
342					if ($newh === false) {
343						$newh = $ir * $neww;
344					}
345				} else {
346					if ($neww === false) {
347						$neww = $ir * $newh;
348					}
349				}
350			}
351			if (is_null($cx) && is_null($cy)) { // scale crop to max of image
352				// set crop scale factor
353				$cf = 1;
354				if ($cw)
355					$cf = min($cf, $cw / $neww);
356				if ($ch)
357					$cf = min($cf, $ch / $newh);
358				//	set the image area of the crop (use the most image possible, rule of thirds positioning)
359				if (!$cw || $w / $cw * $ch > $h) {
360					$cw = round($h / $ch * $cw * $cf);
361					$ch = round($h * $cf);
362					$cx = round(($w - $cw) / 3);
363				} else {
364					$ch = round($w / $cw * $ch * $cf);
365					$cw = round($w * $cf);
366					$cy = round(($h - $ch) / 3);
367				}
368			} else { // custom crop
369				if (!$cw || $cw > $w)
370					$cw = $w;
371				if (!$ch || $ch > $h)
372					$ch = $h;
373			}
374			// force the crop to be within the image
375			if ($cw + $cx > $w)
376				$cx = $w - $cw;
377			if ($cx < 0) {
378				$cw = $cw + $cx;
379				$cx = 0;
380			}
381			if ($ch + $cy > $h)
382				$cy = $h - $ch;
383			if ($cy < 0) {
384				$ch = $ch + $cy;
385				$cy = 0;
386			}
387			if (DEBUG_IMAGE)
388				debugLog("cacheImage:crop " . basename($imgfile) . ":\$size=$size, \$width=$width, \$height=$height, \$cw=$cw, \$ch=$ch, \$cx=$cx, \$cy=$cy, \$quality=$quality, \$thumb=$thumb, \$crop=$crop, \$rotate=$rotate");
389			switch (getSuffix($newfilename)) {
390				case 'gif':
391					$newim = zp_createImage($neww, $newh, false);
392					$newim = zp_imageResizeTransparent($newim, $neww, $newh);
393					break;
394				case 'png':
395				case 'webp':
396				default:
397					$newim = zp_createImage($neww, $newh);
398					if (in_array(getSuffix($newfilename), array('png', 'webp'))) {
399						$newim = zp_imageResizeAlpha($newim, $neww, $newh);
400					}
401					break;
402			}
403			if (!zp_resampleImage($newim, $im, 0, 0, $cx, $cy, $neww, $newh, $cw, $ch)) {
404					imageError('404 Not Found', sprintf(gettext('Image %s not renderable (resample).'), filesystemToInternal($imgfile)), 'err-failimage.png', $imgfile, $album, $newfilename);
405				}
406		} else {
407			if ($newh >= $h && $neww >= $w && !$rotate && !$effects && !$watermark_image && (!$upscale || $newh == $h && $neww == $w)) {
408				// we can just use the original!
409				if (SYMLINK && @symlink($imgfile, $newfile)) {
410					if (DEBUG_IMAGE)
411						debugLog("cacheImage:symlink original " . basename($imgfile) . ":\$size=$size, \$width=$width, \$height=$height, \$dim=$dim, \$neww=$neww; \$newh=$newh; \$quality=$quality, \$thumb=$thumb, \$crop=$crop, \$rotate=$rotate; \$allowscale=$allowscale;");
412					clearstatcache();
413					return true;
414				} else if (@copy($imgfile, $newfile)) {
415					if (DEBUG_IMAGE)
416						debugLog("cacheImage:copy original " . basename($imgfile) . ":\$size=$size, \$width=$width, \$height=$height, \$dim=$dim, \$neww=$neww; \$newh=$newh; \$quality=$quality, \$thumb=$thumb, \$crop=$crop, \$rotate=$rotate; \$allowscale=$allowscale;");
417					clearstatcache();
418					return true;
419				}
420			}
421			if ($allowscale) {
422				$sizes = propSizes($size, $width, $height, $w, $h, $thumb, $image_use_side, $dim);
423				list($neww, $newh) = $sizes;
424			}
425			if (DEBUG_IMAGE)
426				debugLog("cacheImage:no crop " . basename($imgfile) . ":\$size=$size, \$width=$width, \$height=$height, \$dim=$dim, \$neww=$neww; \$newh=$newh; \$quality=$quality, \$thumb=$thumb, \$crop=$crop, \$rotate=$rotate; \$allowscale=$allowscale;");
427
428			switch (getSuffix($newfilename)) {
429				case 'gif':
430					$newim = zp_createImage($neww, $newh, false);
431					$newim = zp_imageResizeTransparent($newim, $neww, $newh);
432					break;
433				case 'png':
434				case 'webp':
435				default:
436					$newim = zp_createImage($neww, $newh);
437					if (in_array(getSuffix($newfilename), array('png', 'webp'))) {
438						$newim = zp_imageResizeAlpha($newim, $neww, $newh);
439					}
440					break;
441			}
442			if (!zp_resampleImage($newim, $im, 0, 0, 0, 0, $neww, $newh, $w, $h)) {
443				imageError('404 Not Found', sprintf(gettext('Image %s not renderable (resample).'), filesystemToInternal($imgfile)), 'err-failimage.png', $imgfile, $album, $newfilename);
444			}
445			if (($thumb && $sharpenthumbs) || (!$thumb && $sharpenimages)) {
446				if (!zp_imageUnsharpMask($newim, getOption('sharpen_amount'), getOption('sharpen_radius'), getOption('sharpen_threshold'))) {
447					imageError('404 Not Found', sprintf(gettext('Image %s not renderable (unsharp).'), filesystemToInternal($imgfile)), 'err-failimage.png', $imgfile, $album, $newfilename);
448				}
449			}
450		}
451
452		$imgEffects = explode(',', $effects);
453		if (in_array('gray', $imgEffects)) {
454			zp_imageGray($newim);
455		}
456		$newim = addWatermark($newim, $watermark_image, $imgfile);
457
458		// Create the cached file (with lots of compatibility)...
459		@chmod($newfile, 0777);
460		if (zp_imageOutput($newim, getSuffix($newfile), $newfile, $quality)) { //	successful save of cached image
461			if (getOption('EmbedIPTC') && getSuffix($newfilename) == 'jpg' && GRAPHICS_LIBRARY != 'Imagick') { // the embed function works only with JPEG images
462				global $_zp_extra_filetypes; //	because we are doing the require in a function!
463				if (!$_zp_extra_filetypes)
464					$_zp_extra_filetypes = array();
465				require_once(dirname(__FILE__) . '/functions.php'); //	it is ok to increase memory footprint now since the image processing is complete
466				$iptc = array(
467								'1#090'	 => chr(0x1b) . chr(0x25) . chr(0x47), //	character set is UTF-8
468								'2#115'	 => $_zp_gallery->getTitle() //	source
469				);
470				$iptc_data = zp_imageIPTC($imgfile);
471				if ($iptc_data) {
472					$iptc_data = iptcparse($iptc_data);
473					if ($iptc_data)
474						$iptc = array_merge($iptc_data, $iptc);
475				}
476				$imgfile = str_replace(ALBUM_FOLDER_SERVERPATH, '', $imgfile);
477				$imagename = basename($imgfile);
478				$albumname = dirname($imgfile);
479				$image = newImage(newAlbum($albumname), $imagename);
480				$copyright = $image->getCopyright();
481				if (empty($copyright)) {
482					$copyright = getOption('copyright_image_notice');
483				}
484				if (!empty($copyright)) {
485					$iptc['2#116'] = $copyright;
486				}
487				$credit = $image->getCredit();
488				if (!empty($credit)) {
489					$iptc['2#110'] = $credit;
490				}
491				$iptc_result = '';
492				foreach ($iptc as $tag => $string) {
493					$tag_parts = explode('#', $tag);
494					if (is_array($string)) {
495						foreach ($string as $element) {
496							$iptc_result .= iptc_make_tag($tag_parts[0], $tag_parts[1], $element);
497						}
498					} else {
499						$iptc_result .= iptc_make_tag($tag_parts[0], $tag_parts[1], $string);
500					}
501				}
502				$content = iptcembed($iptc_result, $newfile);
503				$fw = fopen($newfile, 'w');
504				fwrite($fw, $content);
505				fclose($fw);
506				clearstatcache();
507			}
508			@chmod($newfile, FILE_MOD);
509			if (DEBUG_IMAGE)
510				debugLog('Finished:' . basename($imgfile));
511		} else {
512			if (DEBUG_IMAGE)
513				debugLog('cacheImage: failed to create ' . $newfile);
514			imageError('404 Not Found', sprintf(gettext('cacheImage: failed to create %s'), $newfile), 'err-failimage.png', $imgfile, $album, $newfilename);
515		}
516		@chmod($newfile, FILE_MOD);
517		zp_imageKill($newim);
518		zp_imageKill($im);
519	} catch (Exception $e) {
520		debugLog('cacheImage(' . $newfilename . ') exception: ' . $e->getMessage());
521		imageError('404 Not Found', sprintf(gettext('cacheImage(%1$s) exception: %2$s'), $newfilename, $e->getMessage()), 'err-failimage.png', $imgfile, $album, $newfilename);
522		return false;
523	}
524	clearstatcache();
525	return true;
526}
527
528/* Determines the rotation of the image looking EXIF information.
529 *
530 * @param string $imgfile the image name
531 * @return false when the image should not be rotated, or the degrees the
532 *         image should be rotated otherwise.
533 *
534 * PHP GD do not support flips so when a flip is needed we make a
535 * rotation that get close to that flip. But I don't think any camera will
536 * fill a flipped value in the tag.
537 */
538function getImageRotation($imgfile) {
539	$rotation = false;
540	$imgfile_db = substr(filesystemToInternal($imgfile), strlen(ALBUM_FOLDER_SERVERPATH));
541	$result = query_single_row('SELECT EXIFOrientation FROM ' . prefix('images') . ' AS i JOIN ' . prefix('albums') . ' as a ON i.albumid = a.id WHERE ' . db_quote($imgfile_db) . ' = CONCAT(a.folder,"/",i.filename)');
542	if (is_null($result)) {
543		//try the file directly as this might be an image not in the database
544		if (in_array(getSuffix($imgfile), array('jpg', 'jpeg', 'tif', 'tiff'))) {
545			$result = exif_read_data($imgfile);
546			if (is_array($result) && array_key_exists('Orientation', $result)) {
547				$rotation = $result['Orientation'];
548			}
549		}
550	} else if (is_array($result) && array_key_exists('EXIFOrientation', $result)) {
551		$splits = preg_split('/!([(0-9)])/', $result['EXIFOrientation']);
552		$rotation = $splits[0];
553	}
554	if ($rotation) {
555		switch ($rotation) {
556			case 1 : return false; // none
557			case 2 : return false; // mirrored
558			case 3 : return 180; // upside-down (not 180 but close)
559			case 4 : return 180; // upside-down mirrored
560			case 5 : return 270; // 90 CW mirrored (not 270 but close)
561			case 6 : return 270; // 90 CCW
562			case 7 : return 90; // 90 CCW mirrored (not 90 but close)
563			case 8 : return 90; // 90 CW
564		}
565	}
566	return false;
567}
568
569/**
570 * Adds a watermark to a resized image. If no watermark is set it just returns the image
571 *
572 * @since ZenphotoCMS 1.5.3 - consolidated from cacheImage() and full-image.php
573 *
574 * @param resource|object $newim GD image resource or Imagick object
575 * @param string $watermark_image The path to the watermark to use
576 * @param string $imgfile Path to the image being processed (optionally for debugging only)
577 * @return resource|object
578 */
579function addWatermark($newim, $watermark_image, $imgfile = null) {
580	if ($watermark_image) {
581		$watermark = zp_imageGet($watermark_image);
582		if (!$watermark) {
583			imageError('404 Not Found', sprintf(gettext('Watermark %s not renderable.'), $watermark_image), 'err-failimage.png');
584		}
585		$offset_h = getOption('watermark_h_offset') / 100;
586		$offset_w = getOption('watermark_w_offset') / 100;
587		$percent = getOption('watermark_scale') / 100;
588		$watermark_width = zp_imageWidth($watermark);
589		$watermark_height = zp_imageHeight($watermark);
590		$imw = zp_imageWidth($newim);
591		$imh = zp_imageHeight($newim);
592		$nw = sqrt(($imw * $imh * $percent) * ($watermark_width / $watermark_height));
593		$nh = $nw * ($watermark_height / $watermark_width);
594		$r = sqrt(($imw * $imh * $percent) / ($watermark_width * $watermark_height));
595		$r = min(1, $r);
596		$nw = round($watermark_width * $r);
597		$nh = round($watermark_height * $r);
598		$watermark_new = false;
599		if ($nw != $watermark_width || $nh != $watermark_height) {
600			$watermark_new = zp_imageResizeAlpha($watermark, $nw, $nh);
601			if (!zp_resampleImage($watermark_new, $watermark, 0, 0, 0, 0, $nw, $nh, $watermark_width, $watermark_height)) {
602				imageError('404 Not Found', sprintf(gettext('Watermark %s not resizeable.'), $watermark_image), 'err-failimage.png');
603			}
604		}
605
606		// If upscaling is not allowed or it did not occur just use the original at least
607		if ($watermark_new === false) {
608			$watermark_new = $watermark;
609		}
610		// Position Overlay in Bottom Right
611		$dest_x = max(0, floor(($imw - $nw) * $offset_w));
612		$dest_y = max(0, floor(($imh - $nh) * $offset_h));
613		if (!is_null($imgfile) && DEBUG_IMAGE) {
614			debugLog("Watermark:" . basename($imgfile) . ": \$offset_h=$offset_h, \$offset_w=$offset_w, \$watermark_height=$watermark_height, \$watermark_width=$watermark_width, \$imw=$imw, \$imh=$imh, \$percent=$percent, \$r=$r, \$nw=$nw, \$nh=$nh, \$dest_x=$dest_x, \$dest_y=$dest_y");
615		}
616		if (!zp_copyCanvas($newim, $watermark_new, $dest_x, $dest_y, 0, 0, $nw, $nh)) {
617			imageError('404 Not Found', sprintf(gettext('Image %s not renderable (copycanvas).'), filesystemToInternal($imgfile)), 'err-failimage.png', $imgfile, $album, $newfilename);
618		}
619		zp_imageKill($watermark);
620		/*
621		 * GD special behaviour:
622		 * If no resizing happened killing $watermark also already kills $watermark_new being the same
623		 */
624		if (GRAPHICS_LIBRARY != 'GD' || (GRAPHICS_LIBRARY == 'GD' && get_resource_type($watermark_new) == 'gd')) {
625			zp_imageKill($watermark_new);
626		}
627	}
628	return $newim;
629}
630