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