Namespaces

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

Classes

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

Interfaces

  • IFreezable

Exceptions

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