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