Packages

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • NArrayTools
  • NCallback
  • NComponent
  • NComponentContainer
  • NConfigurator
  • NDateTime53
  • NDebug
  • NEnvironment
  • NFramework
  • NFreezableObject
  • NGenericRecursiveIterator
  • NImage
  • NImageMagick
  • NInstanceFilterIterator
  • NObject
  • NObjectMixin
  • NPaginator
  • NRecursiveComponentIterator
  • NServiceLocator
  • NSmartCachingIterator
  • NString
  • NTools

Interfaces

  • IComponent
  • IComponentContainer
  • IDebuggable
  • IServiceLocator
  • ITranslator

Exceptions

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