1: <?php
2: /**
3: ##DOC-SIGNATURE##
4:
5: This file is part of WideImage.
6:
7: WideImage is free software; you can redistribute it and/or modify
8: it under the terms of the GNU Lesser General Public License as published by
9: the Free Software Foundation; either version 2.1 of the License, or
10: (at your option) any later version.
11:
12: WideImage is distributed in the hope that it will be useful,
13: but WITHOUT ANY WARRANTY; without even the implied warranty of
14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15: GNU Lesser General Public License for more details.
16:
17: You should have received a copy of the GNU Lesser General Public License
18: along with WideImage; if not, write to the Free Software
19: Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20:
21: * @package WideImage
22: **/
23:
24: namespace WideImage;
25:
26: use WideImage\Exception\UnknownErrorWhileMappingException;
27: use WideImage\Exception\GDFunctionResultException;
28:
29: /**
30: * Base class for images
31: *
32: * @package WideImage
33: */
34: abstract class Image
35: {
36: /**
37: * Holds the image resource
38: * @var resource
39: */
40: protected $handle = null;
41:
42: /**
43: * Flag that determines if WideImage should call imagedestroy() upon object destruction
44: * @var bool
45: */
46: protected $handleReleased = false;
47:
48: /**
49: * Canvas object
50: * @var \WideImage\Canvas
51: */
52: protected $canvas = null;
53:
54: /**
55: * @var string
56: */
57: protected $sdata = null;
58:
59: /**
60: * The base class constructor
61: *
62: * @param resource $handle Image handle (GD2 resource)
63: */
64: public function __construct($handle)
65: {
66: WideImage::assertValidImageHandle($handle);
67: $this->handle = $handle;
68: }
69:
70: /**
71: * Cleanup
72: *
73: * Destroys the handle via \WideImage\Image::destroy() when called by the GC.
74: */
75: public function __destruct()
76: {
77: $this->destroy();
78: }
79:
80: /**
81: * This method destroy the image handle, and releases the image resource.
82: *
83: * After this is called, the object doesn't hold a valid image any more.
84: * No operation should be called after that.
85: */
86: public function destroy()
87: {
88: if ($this->isValid() && !$this->handleReleased) {
89: imagedestroy($this->handle);
90: }
91:
92: $this->handle = null;
93: }
94:
95: /**
96: * Returns the GD image resource
97: *
98: * @return resource GD image resource
99: */
100: public function getHandle()
101: {
102: return $this->handle;
103: }
104:
105: /**
106: * @return bool True, if the image object holds a valid GD image, false otherwise
107: */
108: public function isValid()
109: {
110: return WideImage::isValidImageHandle($this->handle);
111: }
112:
113: /**
114: * Releases the handle
115: */
116: public function releaseHandle()
117: {
118: $this->handleReleased = true;
119: }
120:
121: /**
122: * Saves an image to a file
123: *
124: * The file type is recognized from the $uri. If you save to a GIF8, truecolor images
125: * are automatically converted to palette.
126: *
127: * This method supports additional parameters: quality (for jpeg images) and
128: * compression quality and filters (for png images). See http://www.php.net/imagejpeg and
129: * http://www.php.net/imagepng for details.
130: *
131: * Examples:
132: * <code>
133: * // save to a GIF
134: * $image->saveToFile('image.gif');
135: *
136: * // save to a PNG with compression=7 and no filters
137: * $image->saveToFile('image.png', 7, PNG_NO_FILTER);
138: *
139: * // save to a JPEG with quality=80
140: * $image->saveToFile('image.jpg', 80);
141: *
142: * // save to a JPEG with default quality=100
143: * $image->saveToFile('image.jpg');
144: * </code>
145: *
146: * @param string $uri File location
147: */
148: public function saveToFile($uri)
149: {
150: $mapper = MapperFactory::selectMapper($uri, null);
151: $args = func_get_args();
152: array_unshift($args, $this->getHandle());
153: $res = call_user_func_array(array($mapper, 'save'), $args);
154:
155: if (!$res) {
156: throw new UnknownErrorWhileMappingException(get_class($mapper) . " returned an invalid result while saving to $uri");
157: }
158: }
159:
160: /**
161: * Returns binary string with image data in format specified by $format
162: *
163: * Additional parameters may be passed to the function. See \WideImage\Image::saveToFile() for more details.
164: *
165: * @param string $format The format of the image
166: * @return string The binary image data in specified format
167: */
168: public function asString($format)
169: {
170: ob_start();
171: $args = func_get_args();
172: $args[0] = null;
173: array_unshift($args, $this->getHandle());
174:
175: $mapper = MapperFactory::selectMapper(null, $format);
176: $res = call_user_func_array(array($mapper, 'save'), $args);
177:
178: if (!$res) {
179: throw new UnknownErrorWhileMappingException(get_class($mapper) . " returned an invalid result while writing the image data");
180: }
181:
182: return ob_get_clean();
183: }
184:
185: /**
186: * Output a header to browser.
187: *
188: * @param $name Name of the header
189: * @param $data Data
190: */
191: protected function writeHeader($name, $data)
192: {
193: header($name . ": " . $data);
194: }
195:
196: /**
197: * Outputs the image to browser
198: *
199: * Sets headers Content-length and Content-type, and echoes the image in the specified format.
200: * All other headers (such as Content-disposition) must be added manually.
201: *
202: * Example:
203: * <code>
204: * WideImage::load('image1.png')->resize(100, 100)->output('gif');
205: * </code>
206: *
207: * @param string $format Image format
208: */
209: public function output($format)
210: {
211: $args = func_get_args();
212: $data = call_user_func_array(array($this, 'asString'), $args);
213:
214: $this->writeHeader('Content-length', strlen($data));
215: $this->writeHeader('Content-type', MapperFactory::mimeType($format));
216: echo $data;
217: }
218:
219: /**
220: * @return int Image width
221: */
222: public function getWidth()
223: {
224: return imagesx($this->handle);
225: }
226:
227: /**
228: * @return int Image height
229: */
230: public function getHeight()
231: {
232: return imagesy($this->handle);
233: }
234:
235: /**
236: * Allocate a color by RGB values.
237: *
238: * @param mixed $R Red-component value or an RGB array (with red, green, blue keys)
239: * @param int $G If $R is int, this is the green component
240: * @param int $B If $R is int, this is the blue component
241: * @return int Image color index
242: */
243: public function allocateColor($R, $G = null, $B = null)
244: {
245: if (is_array($R)) {
246: return imageColorAllocate($this->handle, $R['red'], $R['green'], $R['blue']);
247: }
248:
249: return imageColorAllocate($this->handle, $R, $G, $B);
250: }
251:
252: /**
253: * @return bool True if the image is transparent, false otherwise
254: */
255: public function isTransparent()
256: {
257: return $this->getTransparentColor() >= 0;
258: }
259:
260: /**
261: * @return int Transparent color index
262: */
263: public function getTransparentColor()
264: {
265: return imagecolortransparent($this->handle);
266: }
267:
268: /**
269: * Sets the current transparent color index. Only makes sense for palette images (8-bit).
270: *
271: * @param int $color Transparent color index
272: */
273: public function setTransparentColor($color)
274: {
275: return imagecolortransparent($this->handle, $color);
276: }
277:
278: /**
279: * Returns a RGB array of the transparent color or null if none.
280: *
281: * @return mixed Transparent color RGBA array
282: */
283: public function getTransparentColorRGB()
284: {
285: $total = imagecolorstotal($this->handle);
286: $tc = $this->getTransparentColor();
287:
288: if ($tc >= $total && $total > 0) {
289: return null;
290: }
291:
292: return $this->getColorRGB($tc);
293: }
294:
295: /**
296: * Returns a RGBA array for pixel at $x, $y
297: *
298: * @param int $x
299: * @param int $y
300: * @return array RGB array
301: */
302: public function getRGBAt($x, $y)
303: {
304: return $this->getColorRGB($this->getColorAt($x, $y));
305: }
306:
307: /**
308: * Writes a pixel at the designated coordinates
309: *
310: * Takes an associative array of colours and uses getExactColor() to
311: * retrieve the exact index color to write to the image with.
312: *
313: * @param int $x
314: * @param int $y
315: * @param array $color
316: */
317: public function setRGBAt($x, $y, $color)
318: {
319: $this->setColorAt($x, $y, $this->getExactColor($color));
320: }
321:
322: /**
323: * Returns a color's RGB
324: *
325: * @param int $colorIndex Color index
326: * @return mixed RGBA array for a color with index $colorIndex
327: */
328: public function getColorRGB($colorIndex)
329: {
330: return imageColorsForIndex($this->handle, $colorIndex);
331: }
332:
333: /**
334: * Returns an index of the color at $x, $y
335: *
336: * @param int $x
337: * @param int $y
338: * @return int Color index for a pixel at $x, $y
339: */
340: public function getColorAt($x, $y)
341: {
342: return imagecolorat($this->handle, $x, $y);
343: }
344:
345: /**
346: * Set the color index $color to a pixel at $x, $y
347: *
348: * @param int $x
349: * @param int $y
350: * @param int $color Color index
351: */
352: public function setColorAt($x, $y, $color)
353: {
354: return imagesetpixel($this->handle, $x, $y, $color);
355: }
356:
357: /**
358: * Returns closest color index that matches the given RGB value. Uses
359: * PHP's imagecolorclosest()
360: *
361: * @param mixed $R Red or RGBA array
362: * @param int $G Green component (or null if $R is an RGB array)
363: * @param int $B Blue component (or null if $R is an RGB array)
364: * @return int Color index
365: */
366: public function getClosestColor($R, $G = null, $B = null)
367: {
368: if (is_array($R)) {
369: return imagecolorclosest($this->handle, $R['red'], $R['green'], $R['blue']);
370: }
371:
372: return imagecolorclosest($this->handle, $R, $G, $B);
373: }
374:
375: /**
376: * Returns the color index that exactly matches the given RGB value. Uses
377: * PHP's imagecolorexact()
378: *
379: * @param mixed $R Red or RGBA array
380: * @param int $G Green component (or null if $R is an RGB array)
381: * @param int $B Blue component (or null if $R is an RGB array)
382: * @return int Color index
383: */
384: public function getExactColor($R, $G = null, $B = null)
385: {
386: if (is_array($R)) {
387: return imagecolorexact($this->handle, $R['red'], $R['green'], $R['blue']);
388: }
389:
390: return imagecolorexact($this->handle, $R, $G, $B);
391: }
392:
393: /**
394: * Copies transparency information from $sourceImage. Optionally fills
395: * the image with the transparent color at (0, 0).
396: *
397: * @param object $sourceImage
398: * @param bool $fill True if you want to fill the image with transparent color
399: */
400: public function copyTransparencyFrom($sourceImage, $fill = true)
401: {
402: if ($sourceImage->isTransparent()) {
403: $rgba = $sourceImage->getTransparentColorRGB();
404:
405: if ($rgba === null) {
406: return;
407: }
408:
409: if ($this->isTrueColor()) {
410: $rgba['alpha'] = 127;
411: $color = $this->allocateColorAlpha($rgba);
412: } else {
413: $color = $this->allocateColor($rgba);
414: }
415:
416: $this->setTransparentColor($color);
417:
418: if ($fill) {
419: $this->fill(0, 0, $color);
420: }
421: }
422: }
423:
424: /**
425: * Fill the image at ($x, $y) with color index $color
426: *
427: * @param int $x
428: * @param int $y
429: * @param int $color
430: */
431: public function fill($x, $y, $color)
432: {
433: return imagefill($this->handle, $x, $y, $color);
434: }
435:
436: /**
437: * Used internally to create Operation objects
438: *
439: * @param string $name
440: * @return object
441: */
442: protected function getOperation($name)
443: {
444: return OperationFactory::get($name);
445: }
446:
447: /**
448: * Returns the image's mask
449: *
450: * Mask is a greyscale image where the shade defines the alpha channel (black = transparent, white = opaque).
451: *
452: * For opaque images (JPEG), the result will be white. For images with single-color transparency (GIF, 8-bit PNG),
453: * the areas with the transparent color will be black. For images with alpha channel transparenct,
454: * the result will be alpha channel.
455: *
456: * @return \WideImage\Image An image mask
457: **/
458: public function getMask()
459: {
460: return $this->getOperation('GetMask')->execute($this);
461: }
462:
463: /**
464: * Resize the image to given dimensions.
465: *
466: * $width and $height are both smart coordinates. This means that you can pass any of these values in:
467: * - positive or negative integer (100, -20, ...)
468: * - positive or negative percent string (30%, -15%, ...)
469: * - complex coordinate (50% - 20, 15 + 30%, ...)
470: * - null: if one dimension is null, it's calculated proportionally from the other.
471: *
472: * $fit parameter can be set to one of these three values:
473: * - 'inside': resize proportionally and fit the resulting image tightly in the $width x $height box
474: * - 'outside': resize proportionally and fit the resulting image tighly outside the box
475: * - 'fill': resize the image to fill the $width x $height box exactly
476: *
477: * $scale parameter can be:
478: * - 'down': only resize the image if it's larger than the $width x $height box
479: * - 'up': only resize the image if it's smaller than the $width x $height box
480: * - 'any': resize the image
481: *
482: * Example (resize to half-size):
483: * <code>
484: * $smaller = $image->resize('50%');
485: *
486: * $smaller = $image->resize('100', '100', 'inside', 'down');
487: * is the same as
488: * $smaller = $image->resizeDown(100, 100, 'inside');
489: * </code>
490: *
491: * @param mixed $width The new width (smart coordinate), or null.
492: * @param mixed $height The new height (smart coordinate), or null.
493: * @param string $fit 'inside', 'outside', 'fill'
494: * @param string $scale 'down', 'up', 'any'
495: * @return \WideImage\Image The resized image
496: */
497: public function resize($width = null, $height = null, $fit = 'inside', $scale = 'any')
498: {
499: return $this->getOperation('Resize')->execute($this, $width, $height, $fit, $scale);
500: }
501:
502: /**
503: * Same as \WideImage\Image::resize(), but the image is only applied if it is larger then the given dimensions.
504: * Otherwise, the resulting image retains the source's dimensions.
505: *
506: * @param int $width New width, smart coordinate
507: * @param int $height New height, smart coordinate
508: * @param string $fit 'inside', 'outside', 'fill'
509: * @return \WideImage\Image resized image
510: */
511: public function resizeDown($width = null, $height = null, $fit = 'inside')
512: {
513: return $this->resize($width, $height, $fit, 'down');
514: }
515:
516: /**
517: * Same as \WideImage\Image::resize(), but the image is only applied if it is smaller then the given dimensions.
518: * Otherwise, the resulting image retains the source's dimensions.
519: *
520: * @param int $width New width, smart coordinate
521: * @param int $height New height, smart coordinate
522: * @param string $fit 'inside', 'outside', 'fill'
523: * @return \WideImage\Image resized image
524: */
525: public function resizeUp($width = null, $height = null, $fit = 'inside')
526: {
527: return $this->resize($width, $height, $fit, 'up');
528: }
529:
530: /**
531: * Rotate the image for angle $angle clockwise.
532: *
533: * Preserves transparency. Has issues when saving to a BMP.
534: *
535: * @param int $angle Angle in degrees, clock-wise
536: * @param int $bgColor color of the new background
537: * @param bool $ignoreTransparent
538: * @return \WideImage\Image The rotated image
539: */
540: public function rotate($angle, $bgColor = null, $ignoreTransparent = true)
541: {
542: return $this->getOperation('Rotate')->execute($this, $angle, $bgColor, $ignoreTransparent);
543: }
544:
545: /**
546: * This method lays the overlay (watermark) on the image.
547: *
548: * Hint: if the overlay is a truecolor image with alpha channel, you should leave $pct at 100.
549: *
550: * This operation supports alignment notation in coordinates:
551: * <code>
552: * $watermark = WideImage::load('logo.gif');
553: * $base = WideImage::load('picture.jpg');
554: * $result = $base->merge($watermark, "right - 10", "bottom - 10", 50);
555: * // applies a logo aligned to bottom-right corner with a 10 pixel margin
556: * </code>
557: *
558: * @param \WideImage\Image $overlay The overlay image
559: * @param mixed $left Left position of the overlay, smart coordinate
560: * @param mixed $top Top position of the overlay, smart coordinate
561: * @param int $pct The opacity of the overlay
562: * @return \WideImage\Image The merged image
563: */
564: public function merge($overlay, $left = 0, $top = 0, $pct = 100)
565: {
566: return $this->getOperation('Merge')->execute($this, $overlay, $left, $top, $pct);
567: }
568:
569: /**
570: * Resizes the canvas of the image, but doesn't scale the content of the image
571: *
572: * This operation creates an empty canvas with dimensions $width x $height, filled with
573: * background color $bg_color and draws the original image onto it at position [$pos_x, $pos_y].
574: *
575: * Arguments $width, $height, $pos_x and $pos_y are all smart coordinates. $width and $height are
576: * relative to the current image size, $pos_x and $pos_y are relative to the newly calculated
577: * canvas size. This can be confusing, but it makes sense. See the example below.
578: *
579: * The example below loads a 100x150 image and then resizes its canvas to 200% x 100%+20
580: * (which evaluates to 200x170). The image is placed at position [10, center+20], which evaluates to [10, 30].
581: * <code>
582: * $image = WideImage::load('someimage.jpg'); // 100x150
583: * $white = $image->allocateColor(255, 255, 255);
584: * $image->resizeCanvas('200%', '100% + 20', 10, 'center+20', $white);
585: * </code>
586: *
587: * The parameter $merge defines whether the original image should be merged onto the new canvas.
588: * This means it blends transparent color and alpha colors into the background color. If set to false,
589: * the original image is just copied over, preserving the transparency/alpha information.
590: *
591: * You can set the $scale parameter to limit when to resize the canvas. For example, if you want
592: * to resize the canvas only if the image is smaller than the new size, but leave the image intact
593: * if it's larger, set it to 'up'. Likewise, if you want to shrink the canvas, but don't want to
594: * change images that are already smaller, set it to 'down'.
595: *
596: * @param mixed $width Width of the new canvas (smart coordinate, relative to current image width)
597: * @param mixed $height Height of the new canvas (smart coordinate, relative to current image height)
598: * @param mixed $pos_x x-position of the image (smart coordinate, relative to the new width)
599: * @param mixed $pos_y y-position of the image (smart coordinate, relative to the new height)
600: * @param int $bg_color Background color (created with allocateColor or allocateColorAlpha), defaults to null (tries to use a transparent color)
601: * @param string $scale Possible values: 'up' (enlarge only), 'down' (downsize only), 'any' (resize precisely to $width x $height). Defaults to 'any'.
602: * @param bool $merge Merge the original image (flatten alpha channel and transparency) or copy it over (preserve). Defaults to false.
603: * @return \WideImage\Image The resulting image with resized canvas
604: */
605: public function resizeCanvas($width, $height, $pos_x, $pos_y, $bg_color = null, $scale = 'any', $merge = false)
606: {
607: return $this->getOperation('ResizeCanvas')->execute($this, $width, $height, $pos_x, $pos_y, $bg_color, $scale, $merge);
608: }
609:
610: /**
611: * Returns an image with round corners
612: *
613: * You can either set the corners' color or set them transparent.
614: *
615: * Note on $smoothness: 1 means jagged edges, 2 is much better, more than 4 doesn't noticeably improve the quality.
616: * Rendering becomes increasingly slower if you increase smoothness.
617: *
618: * Example:
619: * <code>
620: * $nice = $ugly->roundCorners(20, $ugly->allocateColor(255, 0, 0), 2);
621: * </code>
622: *
623: * Use $corners parameter to specify which corners to draw rounded. Possible values are
624: * WideImage::SIDE_TOP_LEFT, WideImage::SIDE_TOP,
625: * WideImage::SIDE_TOP_RIGHT, WideImage::SIDE_RIGHT,
626: * WideImage::SIDE_BOTTOM_RIGHT, WideImage::SIDE_BOTTOM,
627: * WideImage::SIDE_BOTTOM_LEFT, WideImage::SIDE_LEFT, and WideImage::SIDE_ALL.
628: * You can specify any combination of corners with a + operation, see example below.
629: *
630: * Example:
631: * <code>
632: * $white = $image->allocateColor(255, 255, 255);
633: * $diagonal_corners = $image->roundCorners(15, $white, 2, WideImage::SIDE_TOP_LEFT + WideImage::SIDE_BOTTOM_RIGHT);
634: * $right_corners = $image->roundCorners(15, $white, 2, WideImage::SIDE_RIGHT);
635: * </code>
636: *
637: * @param int $radius Radius of the corners
638: * @param int $color The color of corners. If null, corners are rendered transparent (slower than using a solid color).
639: * @param int $smoothness Specify the level of smoothness. Suggested values from 1 to 4.
640: * @param int $corners Specify which corners to draw (defaults to WideImage::SIDE_ALL = all corners)
641: * @return \WideImage\Image The resulting image with round corners
642: */
643: public function roundCorners($radius, $color = null, $smoothness = 2, $corners = 255)
644: {
645: return $this->getOperation('RoundCorners')->execute($this, $radius, $color, $smoothness, $corners);
646: }
647:
648: /**
649: * Returns an image with applied mask
650: *
651: * A mask is a grayscale image, where the shade determines the alpha channel. Black is fully transparent
652: * and white is fully opaque.
653: *
654: * @param \WideImage\Image $mask The mask image, greyscale
655: * @param mixed $left Left coordinate, smart coordinate
656: * @param mixed $top Top coordinate, smart coordinate
657: * @return \WideImage\Image The resulting image
658: **/
659: public function applyMask($mask, $left = 0, $top = 0)
660: {
661: return $this->getOperation('ApplyMask')->execute($this, $mask, $left, $top);
662: }
663:
664: /**
665: * Applies a filter
666: *
667: * @param int $filter One of the IMG_FILTER_* constants
668: * @param int $arg1
669: * @param int $arg2
670: * @param int $arg3
671: * @param int $arg4
672: * @return \WideImage\Image
673: */
674: public function applyFilter($filter, $arg1 = null, $arg2 = null, $arg3 = null, $arg4 = null)
675: {
676: return $this->getOperation('ApplyFilter')->execute($this, $filter, $arg1, $arg2, $arg3, $arg4);
677: }
678:
679: /**
680: * Applies convolution matrix with imageconvolution()
681: *
682: * @param array $matrix
683: * @param float $div
684: * @param float $offset
685: * @return \WideImage\Image
686: */
687: public function applyConvolution($matrix, $div, $offset)
688: {
689: return $this->getOperation('ApplyConvolution')->execute($this, $matrix, $div, $offset);
690: }
691:
692: /**
693: * Returns a cropped rectangular portion of the image
694: *
695: * If the rectangle specifies area that is out of bounds, it's limited to the current image bounds.
696: *
697: * Examples:
698: * <code>
699: * $cropped = $img->crop(10, 10, 150, 200); // crops a 150x200 rect at (10, 10)
700: * $cropped = $img->crop(-100, -50, 100, 50); // crops a 100x50 rect at the right-bottom of the image
701: * $cropped = $img->crop('25%', '25%', '50%', '50%'); // crops a 50%x50% rect from the center of the image
702: * </code>
703: *
704: * This operation supports alignment notation in left/top coordinates.
705: * Example:
706: * <code>
707: * $cropped = $img->crop("right", "bottom", 100, 200); // crops a 100x200 rect from right bottom
708: * $cropped = $img->crop("center", "middle", 50, 30); // crops a 50x30 from the center of the image
709: * </code>
710: *
711: * @param mixed $left Left-coordinate of the crop rect, smart coordinate
712: * @param mixed $top Top-coordinate of the crop rect, smart coordinate
713: * @param mixed $width Width of the crop rect, smart coordinate
714: * @param mixed $height Height of the crop rect, smart coordinate
715: * @return \WideImage\Image The cropped image
716: **/
717: public function crop($left = 0, $top = 0, $width = '100%', $height = '100%')
718: {
719: return $this->getOperation('Crop')->execute($this, $left, $top, $width, $height);
720: }
721:
722: /**
723: * Performs an auto-crop on the image
724: *
725: * The image is auto-cropped from each of four sides. All sides are
726: * scanned for pixels that differ from $base_color for more than
727: * $rgb_threshold in absolute RGB difference. If more than $pixel_cutoff
728: * differentiating pixels are found, that line is considered to be the crop line for the side.
729: * If the line isn't different enough, the algorithm procedes to the next line
730: * towards the other edge of the image.
731: *
732: * When the crop rectangle is found, it's enlarged by the $margin value on each of the four sides.
733: *
734: * @param int $margin Margin for the crop rectangle, can be negative.
735: * @param int $rgb_threshold RGB difference which still counts as "same color".
736: * @param int $pixel_cutoff How many pixels need to be different to mark a cut line.
737: * @param int $base_color The base color index. If none specified (or null given), left-top pixel is used.
738: * @return \WideImage\Image The cropped image
739: */
740: public function autoCrop($margin = 0, $rgb_threshold = 0, $pixel_cutoff = 1, $base_color = null)
741: {
742: return $this->getOperation('AutoCrop')->execute($this, $margin, $rgb_threshold, $pixel_cutoff, $base_color);
743: }
744:
745: /**
746: * Returns a negative of the image
747: *
748: * This operation differs from calling \WideImage\Image::applyFilter(IMG_FILTER_NEGATIVE), because it's 8-bit and transparency safe.
749: * This means it will return an 8-bit image, if the source image is 8-bit. If that 8-bit image has a palette transparency,
750: * the resulting image will keep transparency.
751: *
752: * @return \WideImage\Image negative of the image
753: */
754: public function asNegative()
755: {
756: return $this->getOperation('AsNegative')->execute($this);
757: }
758:
759: /**
760: * Returns a grayscale copy of the image
761: *
762: * @return \WideImage\Image grayscale copy
763: **/
764: public function asGrayscale()
765: {
766: return $this->getOperation('AsGrayscale')->execute($this);
767: }
768:
769: /**
770: * Returns a mirrored copy of the image
771: *
772: * @return \WideImage\Image Mirrored copy
773: **/
774: public function mirror()
775: {
776: return $this->getOperation('Mirror')->execute($this);
777: }
778:
779: /**
780: * Applies the unsharp filter
781: *
782: * @param float $amount
783: * @param float $radius
784: * @param float $threshold
785: * @return \WideImage\Image Unsharpened copy of the image
786: **/
787: public function unsharp($amount, $radius, $threshold)
788: {
789: return $this->getOperation('Unsharp')->execute($this, $amount, $radius, $threshold);
790: }
791:
792: /**
793: * Returns a flipped (mirrored over horizontal line) copy of the image
794: *
795: * @return \WideImage\Image Flipped copy
796: **/
797: public function flip()
798: {
799: return $this->getOperation('Flip')->execute($this);
800: }
801:
802: /**
803: * Corrects gamma on the image
804: *
805: * @param float $inputGamma
806: * @param float $outputGamma
807: * @return \WideImage\Image Image with corrected gamma
808: **/
809: public function correctGamma($inputGamma, $outputGamma)
810: {
811: return $this->getOperation('CorrectGamma')->execute($this, $inputGamma, $outputGamma);
812: }
813:
814: /**
815: * Adds noise to the image
816: *
817: * @author Tomasz Kapusta
818: *
819: * @param int $amount Number of noise pixels to add
820: * @param string $type Type of noise 'salt&pepper', 'color' or 'mono'
821: * @return \WideImage\Image Image with noise added
822: **/
823: public function addNoise($amount, $type)
824: {
825: return $this->getOperation('AddNoise')->execute($this, $amount, $type);
826: }
827:
828: /**
829: * Used internally to execute operations
830: *
831: * @param string $name
832: * @param array $args
833: * @return \WideImage\Image
834: */
835: public function __call($name, $args)
836: {
837: $op = $this->getOperation($name);
838: array_unshift($args, $this);
839: return call_user_func_array(array($op, 'execute'), $args);
840: }
841:
842: /**
843: * Returns an image in GIF or PNG format
844: *
845: * @return string
846: */
847: public function __toString()
848: {
849: if ($this->isTransparent()) {
850: return $this->asString('gif');
851: }
852:
853: return $this->asString('png');
854: }
855:
856: /**
857: * Returns a copy of the image object
858: *
859: * @return \WideImage\Image The copy
860: **/
861: public function copy()
862: {
863: $dest = $this->doCreate($this->getWidth(), $this->getHeight());
864: $dest->copyTransparencyFrom($this, true);
865: $this->copyTo($dest, 0, 0);
866: return $dest;
867: }
868:
869: /**
870: * Copies this image onto another image
871: *
872: * @param \WideImage\Image $dest
873: * @param int $left
874: * @param int $top
875: **/
876: public function copyTo($dest, $left = 0, $top = 0)
877: {
878: if (!imagecopy($dest->getHandle(), $this->handle, $left, $top, 0, 0, $this->getWidth(), $this->getHeight())) {
879: throw new GDFunctionResultException("imagecopy() returned false");
880: }
881: }
882:
883: /**
884: * Returns the canvas object
885: *
886: * The Canvas object can be used to draw text and shapes on the image
887: *
888: * Examples:
889: * <code>
890: * $img = WideImage::load('pic.jpg);
891: * $canvas = $img->getCanvas();
892: * $canvas->useFont('arial.ttf', 15, $img->allocateColor(200, 220, 255));
893: * $canvas->writeText(10, 50, "Hello world!");
894: *
895: * $canvas->filledRectangle(10, 10, 80, 40, $img->allocateColor(255, 127, 255));
896: * $canvas->line(60, 80, 30, 100, $img->allocateColor(255, 0, 0));
897: * $img->saveToFile('new.png');
898: * </code>
899: *
900: * @return \WideImage\Canvas The Canvas object
901: **/
902: function getCanvas()
903: {
904: if ($this->canvas == null) {
905: $this->canvas = new Canvas($this);
906: }
907:
908: return $this->canvas;
909: }
910:
911: /**
912: * Returns true if the image is true-color, false otherwise
913: *
914: * @return bool
915: **/
916: abstract public function isTrueColor();
917:
918: /**
919: * Returns a true-color copy of the image
920: *
921: * @return \WideImage\TrueColorImage
922: **/
923: abstract public function asTrueColor();
924:
925: /**
926: * Returns a palette copy (8bit) of the image
927: *
928: * @param int $nColors Number of colors in the resulting image, more than 0, less or equal to 255
929: * @param bool $dither Use dithering or not
930: * @param bool $matchPalette Set to true to use imagecolormatch() to match the resulting palette more closely to the original image
931: * @return \WideImage\Image
932: **/
933: abstract public function asPalette($nColors = 255, $dither = null, $matchPalette = true);
934:
935: /**
936: * Retrieve an image with selected channels
937: *
938: * Examples:
939: * <code>
940: * $channels = $img->getChannels('red', 'blue');
941: * $channels = $img->getChannels('alpha', 'green');
942: * $channels = $img->getChannels(array('green', 'blue'));
943: * </code>
944: *
945: * @return \WideImage\Image
946: **/
947: abstract public function getChannels();
948:
949: /**
950: * Returns an image without an alpha channel
951: *
952: * @return \WideImage\Image
953: **/
954: abstract public function copyNoAlpha();
955:
956: /**
957: * Returns an array of serializable protected variables. Called automatically upon serialize().
958: *
959: * @return array
960: */
961: public function __sleep()
962: {
963: $this->sdata = $this->asString('png');
964:
965: return array('sdata', 'handleReleased');
966: }
967:
968: /**
969: * Restores an image from serialization. Called automatically upon unserialize().
970: */
971: public function __wakeup()
972: {
973: $temp_image = WideImage::loadFromString($this->sdata);
974: $temp_image->releaseHandle();
975: $this->handle = $temp_image->handle;
976: $temp_image = null;
977: $this->sdata = null;
978: }
979: }
980: