Packages

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

Classes

  • ArrayHash
  • ArrayList
  • Callback
  • Configurator
  • DateTime53
  • Framework
  • FreezableObject
  • Image
  • Object
  • ObjectMixin

Interfaces

  • IFreezable

Exceptions

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