Packages

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • none

Classes

Interfaces

Exceptions

  • Overview
  • Package
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6:  * @package Nette
  7:  */
  8: 
  9: 
 10: 
 11: /**
 12:  * Basic manipulation with images.
 13:  *
 14:  * <code>
 15:  * $image = Image::fromFile('nette.jpg');
 16:  * $image->resize(150, 100);
 17:  * $image->sharpen();
 18:  * $image->send();
 19:  * </code>
 20:  *
 21:  * @author     David Grudl
 22:  *
 23:  * @method void alphaBlending(bool $on)
 24:  * @method void antialias(bool $on)
 25:  * @method void arc($x, $y, $w, $h, $start, $end, $color)
 26:  * @method void char(int $font, $x, $y, string $char, $color)
 27:  * @method void charUp(int $font, $x, $y, string $char, $color)
 28:  * @method int colorAllocate($red, $green, $blue)
 29:  * @method int colorAllocateAlpha($red, $green, $blue, $alpha)
 30:  * @method int colorAt($x, $y)
 31:  * @method int colorClosest($red, $green, $blue)
 32:  * @method int colorClosestAlpha($red, $green, $blue, $alpha)
 33:  * @method int colorClosestHWB($red, $green, $blue)
 34:  * @method void colorDeallocate($color)
 35:  * @method int colorExact($red, $green, $blue)
 36:  * @method int colorExactAlpha($red, $green, $blue, $alpha)
 37:  * @method void colorMatch(Image $image2)
 38:  * @method int colorResolve($red, $green, $blue)
 39:  * @method int colorResolveAlpha($red, $green, $blue, $alpha)
 40:  * @method void colorSet($index, $red, $green, $blue)
 41:  * @method array colorsForIndex($index)
 42:  * @method int colorsTotal()
 43:  * @method int colorTransparent($color = NULL)
 44:  * @method void convolution(array $matrix, float $div, float $offset)
 45:  * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH)
 46:  * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
 47:  * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity)
 48:  * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
 49:  * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH)
 50:  * @method void dashedLine($x1, $y1, $x2, $y2, $color)
 51:  * @method void ellipse($cx, $cy, $w, $h, $color)
 52:  * @method void fill($x, $y, $color)
 53:  * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style)
 54:  * @method void filledEllipse($cx, $cy, $w, $h, $color)
 55:  * @method void filledPolygon(array $points, $numPoints, $color)
 56:  * @method void filledRectangle($x1, $y1, $x2, $y2, $color)
 57:  * @method void fillToBorder($x, $y, $border, $color)
 58:  * @method void filter($filtertype)
 59:  * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = NULL)
 60:  * @method void gammaCorrect(float $inputgamma, float $outputgamma)
 61:  * @method int interlace($interlace = NULL)
 62:  * @method bool isTrueColor()
 63:  * @method void layerEffect($effect)
 64:  * @method void line($x1, $y1, $x2, $y2, $color)
 65:  * @method void paletteCopy(Image $source)
 66:  * @method void polygon(array $points, $numPoints, $color)
 67:  * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = NULL, $tightness = NULL, float $angle = NULL, $antialiasSteps = NULL)
 68:  * @method void rectangle($x1, $y1, $x2, $y2, $col)
 69:  * @method Image rotate(float $angle, $backgroundColor)
 70:  * @method void saveAlpha(bool $saveflag)
 71:  * @method void setBrush(Image $brush)
 72:  * @method void setPixel($x, $y, $color)
 73:  * @method void setStyle(array $style)
 74:  * @method void setThickness($thickness)
 75:  * @method void setTile(Image $tile)
 76:  * @method void string($font, $x, $y, string $s, $col)
 77:  * @method void stringUp($font, $x, $y, string $s, $col)
 78:  * @method void trueColorToPalette(bool $dither, $ncolors)
 79:  * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text)
 80:  * @property-read int $width
 81:  * @property-read int $height
 82:  * @property-read resource $imageResource
 83:  * @package Nette
 84:  */
 85: class Image extends Object
 86: {
 87:     /** {@link resize()} only shrinks images */
 88:     const SHRINK_ONLY = 1;
 89: 
 90:     /** {@link resize()} will ignore aspect ratio */
 91:     const STRETCH = 2;
 92: 
 93:     /** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */
 94:     const FIT = 0;
 95: 
 96:     /** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */
 97:     const FILL = 4;
 98: 
 99:     /** {@link resize()} fills given area exactly */
100:     const EXACT = 8;
101: 
102:     /** @int image types {@link send()} */
103:     const JPEG = IMAGETYPE_JPEG,
104:         PNG = IMAGETYPE_PNG,
105:         GIF = IMAGETYPE_GIF;
106: 
107:     const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
108: 
109:     /** @deprecated */
110:     const ENLARGE = 0;
111: 
112:     /** @var resource */
113:     private $image;
114: 
115: 
116:     /**
117:      * Returns RGB color.
118:      * @param  int  red 0..255
119:      * @param  int  green 0..255
120:      * @param  int  blue 0..255
121:      * @param  int  transparency 0..127
122:      * @return array
123:      */
124:     public static function rgb($red, $green, $blue, $transparency = 0)
125:     {
126:         return array(
127:             'red' => max(0, min(255, (int) $red)),
128:             'green' => max(0, min(255, (int) $green)),
129:             'blue' => max(0, min(255, (int) $blue)),
130:             'alpha' => max(0, min(127, (int) $transparency)),
131:         );
132:     }
133: 
134: 
135:     /**
136:      * Opens image from file.
137:      * @param  string
138:      * @param  mixed  detected image format
139:      * @throws NotSupportedException if gd extension is not loaded
140:      * @throws UnknownImageFileException if file not found or file type is not known
141:      * @return Image
142:      */
143:     public static function fromFile($file, & $format = NULL)
144:     {
145:         if (!extension_loaded('gd')) {
146:             throw new NotSupportedException('PHP extension GD is not loaded.');
147:         }
148: 
149:         $info = @getimagesize($file); // @ - files smaller than 12 bytes causes read error
150: 
151:         switch ($format = $info[2]) {
152:             case self::JPEG:
153:                 return new self(imagecreatefromjpeg($file));
154: 
155:             case self::PNG:
156:                 return new self(imagecreatefrompng($file));
157: 
158:             case self::GIF:
159:                 return new self(imagecreatefromgif($file));
160: 
161:             default:
162:                 throw new UnknownImageFileException("Unknown image type or file '$file' not found.");
163:         }
164:     }
165: 
166: 
167:     /**
168:      * Get format from the image stream in the string.
169:      * @param  string
170:      * @return mixed  detected image format
171:      */
172:     public static function getFormatFromString($s)
173:     {
174:         $types = array('image/jpeg' => self::JPEG, 'image/gif' => self::GIF, 'image/png' => self::PNG);
175:         $type = MimeTypeDetector::fromString($s);
176:         return isset($types[$type]) ? $types[$type] : NULL;
177:     }
178: 
179: 
180:     /**
181:      * Create a new image from the image stream in the string.
182:      * @param  string
183:      * @param  mixed  detected image format
184:      * @return Image
185:      */
186:     public static function fromString($s, & $format = NULL)
187:     {
188:         if (!extension_loaded('gd')) {
189:             throw new NotSupportedException('PHP extension GD is not loaded.');
190:         }
191: 
192:         $format = self::getFormatFromString($s);
193: 
194:         return new self(imagecreatefromstring($s));
195:     }
196: 
197: 
198:     /**
199:      * Creates blank image.
200:      * @param  int
201:      * @param  int
202:      * @param  array
203:      * @return Image
204:      */
205:     public static function fromBlank($width, $height, $color = NULL)
206:     {
207:         if (!extension_loaded('gd')) {
208:             throw new NotSupportedException('PHP extension GD is not loaded.');
209:         }
210: 
211:         $width = (int) $width;
212:         $height = (int) $height;
213:         if ($width < 1 || $height < 1) {
214:             throw new InvalidArgumentException('Image width and height must be greater than zero.');
215:         }
216: 
217:         $image = imagecreatetruecolor($width, $height);
218:         if (is_array($color)) {
219:             $color += array('alpha' => 0);
220:             $color = imagecolorallocatealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']);
221:             imagealphablending($image, FALSE);
222:             imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color);
223:             imagealphablending($image, TRUE);
224:         }
225:         return new self($image);
226:     }
227: 
228: 
229:     /**
230:      * Wraps GD image.
231:      * @param  resource
232:      */
233:     public function __construct($image)
234:     {
235:         $this->setImageResource($image);
236:         imagesavealpha($image, TRUE);
237:     }
238: 
239: 
240:     /**
241:      * Returns image width.
242:      * @return int
243:      */
244:     public function getWidth()
245:     {
246:         return imagesx($this->image);
247:     }
248: 
249: 
250:     /**
251:      * Returns image height.
252:      * @return int
253:      */
254:     public function getHeight()
255:     {
256:         return imagesy($this->image);
257:     }
258: 
259: 
260:     /**
261:      * Sets image resource.
262:      * @param  resource
263:      * @return self
264:      */
265:     protected function setImageResource($image)
266:     {
267:         if (!is_resource($image) || get_resource_type($image) !== 'gd') {
268:             throw new InvalidArgumentException('Image is not valid.');
269:         }
270:         $this->image = $image;
271:         return $this;
272:     }
273: 
274: 
275:     /**
276:      * Returns image GD resource.
277:      * @return resource
278:      */
279:     public function getImageResource()
280:     {
281:         return $this->image;
282:     }
283: 
284: 
285:     /**
286:      * Resizes image.
287:      * @param  mixed  width in pixels or percent
288:      * @param  mixed  height in pixels or percent
289:      * @param  int    flags
290:      * @return self
291:      */
292:     public function resize($width, $height, $flags = self::FIT)
293:     {
294:         if ($flags & self::EXACT) {
295:             return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height);
296:         }
297: 
298:         list($newWidth, $newHeight) = self::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags);
299: 
300:         if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize
301:             $newImage = self::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
302:             imagecopyresampled(
303:                 $newImage, $this->getImageResource(),
304:                 0, 0, 0, 0,
305:                 $newWidth, $newHeight, $this->getWidth(), $this->getHeight()
306:             );
307:             $this->image = $newImage;
308:         }
309: 
310:         if ($width < 0 || $height < 0) { // flip is processed in two steps for better quality
311:             $newImage = self::fromBlank($newWidth, $newHeight, self::RGB(0, 0, 0, 127))->getImageResource();
312:             imagecopyresampled(
313:                 $newImage, $this->getImageResource(),
314:                 0, 0, $width < 0 ? $newWidth - 1 : 0, $height < 0 ? $newHeight - 1 : 0,
315:                 $newWidth, $newHeight, $width < 0 ? -$newWidth : $newWidth, $height < 0 ? -$newHeight : $newHeight
316:             );
317:             $this->image = $newImage;
318:         }
319:         return $this;
320:     }
321: 
322: 
323:     /**
324:      * Calculates dimensions of resized image.
325:      * @param  mixed  source width
326:      * @param  mixed  source height
327:      * @param  mixed  width in pixels or percent
328:      * @param  mixed  height in pixels or percent
329:      * @param  int    flags
330:      * @return array
331:      */
332:     public static function calculateSize($srcWidth, $srcHeight, $newWidth, $newHeight, $flags = self::FIT)
333:     {
334:         if (substr($newWidth, -1) === '%') {
335:             $newWidth = round($srcWidth / 100 * abs($newWidth));
336:             $percents = TRUE;
337:         } else {
338:             $newWidth = (int) abs($newWidth);
339:         }
340: 
341:         if (substr($newHeight, -1) === '%') {
342:             $newHeight = round($srcHeight / 100 * abs($newHeight));
343:             $flags |= empty($percents) ? 0 : self::STRETCH;
344:         } else {
345:             $newHeight = (int) abs($newHeight);
346:         }
347: 
348:         if ($flags & self::STRETCH) { // non-proportional
349:             if (empty($newWidth) || empty($newHeight)) {
350:                 throw new InvalidArgumentException('For stretching must be both width and height specified.');
351:             }
352: 
353:             if ($flags & self::SHRINK_ONLY) {
354:                 $newWidth = round($srcWidth * min(1, $newWidth / $srcWidth));
355:                 $newHeight = round($srcHeight * min(1, $newHeight / $srcHeight));
356:             }
357: 
358:         } else {  // proportional
359:             if (empty($newWidth) && empty($newHeight)) {
360:                 throw new InvalidArgumentException('At least width or height must be specified.');
361:             }
362: 
363:             $scale = array();
364:             if ($newWidth > 0) { // fit width
365:                 $scale[] = $newWidth / $srcWidth;
366:             }
367: 
368:             if ($newHeight > 0) { // fit height
369:                 $scale[] = $newHeight / $srcHeight;
370:             }
371: 
372:             if ($flags & self::FILL) {
373:                 $scale = array(max($scale));
374:             }
375: 
376:             if ($flags & self::SHRINK_ONLY) {
377:                 $scale[] = 1;
378:             }
379: 
380:             $scale = min($scale);
381:             $newWidth = round($srcWidth * $scale);
382:             $newHeight = round($srcHeight * $scale);
383:         }
384: 
385:         return array(max((int) $newWidth, 1), max((int) $newHeight, 1));
386:     }
387: 
388: 
389:     /**
390:      * Crops image.
391:      * @param  mixed  x-offset in pixels or percent
392:      * @param  mixed  y-offset in pixels or percent
393:      * @param  mixed  width in pixels or percent
394:      * @param  mixed  height in pixels or percent
395:      * @return self
396:      */
397:     public function crop($left, $top, $width, $height)
398:     {
399:         list($left, $top, $width, $height) = self::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height);
400:         $newImage = self::fromBlank($width, $height, self::RGB(0, 0, 0, 127))->getImageResource();
401:         imagecopy($newImage, $this->getImageResource(), 0, 0, $left, $top, $width, $height);
402:         $this->image = $newImage;
403:         return $this;
404:     }
405: 
406: 
407:     /**
408:      * Calculates dimensions of cutout in image.
409:      * @param  mixed  source width
410:      * @param  mixed  source height
411:      * @param  mixed  x-offset in pixels or percent
412:      * @param  mixed  y-offset in pixels or percent
413:      * @param  mixed  width in pixels or percent
414:      * @param  mixed  height in pixels or percent
415:      * @return array
416:      */
417:     public static function calculateCutout($srcWidth, $srcHeight, $left, $top, $newWidth, $newHeight)
418:     {
419:         if (substr($newWidth, -1) === '%') {
420:             $newWidth = round($srcWidth / 100 * $newWidth);
421:         }
422:         if (substr($newHeight, -1) === '%') {
423:             $newHeight = round($srcHeight / 100 * $newHeight);
424:         }
425:         if (substr($left, -1) === '%') {
426:             $left = round(($srcWidth - $newWidth) / 100 * $left);
427:         }
428:         if (substr($top, -1) === '%') {
429:             $top = round(($srcHeight - $newHeight) / 100 * $top);
430:         }
431:         if ($left < 0) {
432:             $newWidth += $left; $left = 0;
433:         }
434:         if ($top < 0) {
435:             $newHeight += $top; $top = 0;
436:         }
437:         $newWidth = min((int) $newWidth, $srcWidth - $left);
438:         $newHeight = min((int) $newHeight, $srcHeight - $top);
439:         return array($left, $top, $newWidth, $newHeight);
440:     }
441: 
442: 
443:     /**
444:      * Sharpen image.
445:      * @return self
446:      */
447:     public function sharpen()
448:     {
449:         imageconvolution($this->getImageResource(), array( // my magic numbers ;)
450:             array( -1, -1, -1 ),
451:             array( -1, 24, -1 ),
452:             array( -1, -1, -1 ),
453:         ), 16, 0);
454:         return $this;
455:     }
456: 
457: 
458:     /**
459:      * Puts another image into this image.
460:      * @param  Image
461:      * @param  mixed  x-coordinate in pixels or percent
462:      * @param  mixed  y-coordinate in pixels or percent
463:      * @param  int  opacity 0..100
464:      * @return self
465:      */
466:     public function place(Image $image, $left = 0, $top = 0, $opacity = 100)
467:     {
468:         $opacity = max(0, min(100, (int) $opacity));
469: 
470:         if (substr($left, -1) === '%') {
471:             $left = round(($this->getWidth() - $image->getWidth()) / 100 * $left);
472:         }
473: 
474:         if (substr($top, -1) === '%') {
475:             $top = round(($this->getHeight() - $image->getHeight()) / 100 * $top);
476:         }
477: 
478:         if ($opacity === 100) {
479:             imagecopy(
480:                 $this->getImageResource(), $image->getImageResource(),
481:                 $left, $top, 0, 0, $image->getWidth(), $image->getHeight()
482:             );
483: 
484:         } elseif ($opacity <> 0) {
485:             imagecopymerge(
486:                 $this->getImageResource(), $image->getImageResource(),
487:                 $left, $top, 0, 0, $image->getWidth(), $image->getHeight(),
488:                 $opacity
489:             );
490:         }
491:         return $this;
492:     }
493: 
494: 
495:     /**
496:      * Saves image to the file.
497:      * @param  string  filename
498:      * @param  int  quality 0..100 (for JPEG and PNG)
499:      * @param  int  optional image type
500:      * @return bool TRUE on success or FALSE on failure.
501:      */
502:     public function save($file = NULL, $quality = NULL, $type = NULL)
503:     {
504:         if ($type === NULL) {
505:             switch (strtolower(pathinfo($file, PATHINFO_EXTENSION))) {
506:                 case 'jpg':
507:                 case 'jpeg':
508:                     $type = self::JPEG;
509:                     break;
510:                 case 'png':
511:                     $type = self::PNG;
512:                     break;
513:                 case 'gif':
514:                     $type = self::GIF;
515:             }
516:         }
517: 
518:         switch ($type) {
519:             case self::JPEG:
520:                 $quality = $quality === NULL ? 85 : max(0, min(100, (int) $quality));
521:                 return imagejpeg($this->getImageResource(), $file, $quality);
522: 
523:             case self::PNG:
524:                 $quality = $quality === NULL ? 9 : max(0, min(9, (int) $quality));
525:                 return imagepng($this->getImageResource(), $file, $quality);
526: 
527:             case self::GIF:
528:                 return $file === NULL ? imagegif($this->getImageResource()) : imagegif($this->getImageResource(), $file); // PHP bug #44591
529: 
530:             default:
531:                 throw new InvalidArgumentException('Unsupported image type.');
532:         }
533:     }
534: 
535: 
536:     /**
537:      * Outputs image to string.
538:      * @param  int  image type
539:      * @param  int  quality 0..100 (for JPEG and PNG)
540:      * @return string
541:      */
542:     public function toString($type = self::JPEG, $quality = NULL)
543:     {
544:         ob_start();
545:         $this->save(NULL, $quality, $type);
546:         return ob_get_clean();
547:     }
548: 
549: 
550:     /**
551:      * Outputs image to string.
552:      * @return string
553:      */
554:     public function __toString()
555:     {
556:         try {
557:             return $this->toString();
558:         } catch (Exception $e) {
559:             if (func_num_args()) {
560:                 throw $e;
561:             }
562:             trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
563:         }
564:     }
565: 
566: 
567:     /**
568:      * Outputs image to browser.
569:      * @param  int  image type
570:      * @param  int  quality 0..100 (for JPEG and PNG)
571:      * @return bool TRUE on success or FALSE on failure.
572:      */
573:     public function send($type = self::JPEG, $quality = NULL)
574:     {
575:         if ($type !== self::GIF && $type !== self::PNG && $type !== self::JPEG) {
576:             throw new InvalidArgumentException('Unsupported image type.');
577:         }
578:         header('Content-Type: ' . image_type_to_mime_type($type));
579:         return $this->save(NULL, $quality, $type);
580:     }
581: 
582: 
583:     /**
584:      * Call to undefined method.
585:      *
586:      * @param  string  method name
587:      * @param  array   arguments
588:      * @return mixed
589:      * @throws MemberAccessException
590:      */
591:     public function __call($name, $args)
592:     {
593:         $function = 'image' . $name;
594:         if (function_exists($function)) {
595:             foreach ($args as $key => $value) {
596:                 if ($value instanceof self) {
597:                     $args[$key] = $value->getImageResource();
598: 
599:                 } elseif (is_array($value) && isset($value['red'])) { // rgb
600:                     $args[$key] = imagecolorallocatealpha(
601:                         $this->getImageResource(),
602:                         $value['red'], $value['green'], $value['blue'], $value['alpha']
603:                     );
604:                 }
605:             }
606:             array_unshift($args, $this->getImageResource());
607: 
608:             $res = call_user_func_array($function, $args);
609:             return is_resource($res) && get_resource_type($res) === 'gd' ? $this->setImageResource($res) : $res;
610:         }
611: 
612:         return parent::__call($name, $args);
613:     }
614: 
615: }
616: 
617: 
618: /**
619:  * The exception that indicates invalid image file.
620:  * @package Nette
621:  */
622: class UnknownImageFileException extends Exception
623: {
624: }
625: 
Nette Framework 2.0.18 (for PHP 5.2, un-prefixed) API documentation generated by ApiGen 2.8.0