1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: class Horde_Image_Gd extends Horde_Image_Base
16: {
17:
18: 19: 20: 21: 22:
23: protected $_capabilities = array('resize',
24: 'crop',
25: 'rotate',
26: 'flip',
27: 'mirror',
28: 'grayscale',
29: 'sepia',
30: 'yellowize',
31: 'canvas');
32:
33: 34: 35: 36: 37:
38: protected $_im;
39:
40: 41: 42: 43: 44: 45: 46:
47: public function __construct($params, $context = array())
48: {
49: parent::__construct($params, $context);
50: if (!empty($params['filename'])) {
51: $this->loadFile($params['filename']);
52: } elseif (!empty($params['data'])) {
53: $this->loadString($params['data']);
54: } elseif (!empty($params['width'])) {
55: $this->_im = $this->create($this->_width, $this->_height);
56: $this->call('imageFill', array($this->_im, 0, 0, $this->_allocateColor($this->_background)));
57: }
58: }
59:
60: public function __get($property)
61: {
62: switch ($property) {
63: case '_im':
64: return $this->_im;
65: }
66: }
67:
68: 69: 70:
71: public function display()
72: {
73: $this->headers();
74:
75: return $this->call('image' . $this->_type, array($this->_im));
76: }
77:
78: 79: 80: 81: 82: 83: 84:
85: public function raw($convert = false)
86: {
87: if (!is_resource($this->_im)) {
88: return '';
89: }
90:
91: ob_start();
92: call_user_func('image' . $this->_type, $this->_im);
93: return ob_get_clean();
94: }
95:
96: 97: 98:
99: public function reset()
100: {
101: parent::reset();
102: if (is_resource($this->_im)) {
103: return $this->call('imageDestroy', array($this->_im));
104: }
105:
106: return true;
107: }
108:
109: 110: 111: 112: 113: 114:
115: public function getDimensions()
116: {
117: if (is_resource($this->_im) && $this->_width == 0 && $this->_height ==0) {
118: $this->_width = $this->call('imageSX', array($this->_im));
119: $this->_height = $this->call('imageSY', array($this->_im));
120: return array('width' => $this->_width,
121: 'height' => $this->_height);
122: } else {
123: return array('width' => $this->_width,
124: 'height' => $this->_height);
125: }
126: }
127:
128: 129: 130: 131: 132: 133: 134: 135: 136:
137: private function _allocateColor($name, $alpha = 0)
138: {
139: static $colors = array();
140:
141: if (empty($colors[$name])) {
142: list($r, $g, $b) = self::getRGB($name);
143: $colors[$name] = $this->call('imageColorAllocateAlpha', array($this->_im, $r, $g, $b, $alpha));
144: }
145:
146: return $colors[$name];
147: }
148:
149: 150: 151: 152: 153: 154: 155:
156: private function _getFont($font)
157: {
158: switch ($font) {
159: case 'tiny':
160: return 1;
161:
162: case 'medium':
163: return 3;
164:
165: case 'large':
166: return 4;
167:
168: case 'giant':
169: return 5;
170:
171: case 'small':
172: default:
173: return 2;
174: }
175: }
176:
177: 178: 179: 180: 181: 182: 183:
184: public function loadString($image_data)
185: {
186: $this->_im = $this->call('imageCreateFromString', array($image_data));
187: }
188:
189: 190: 191: 192: 193: 194: 195: 196: 197: 198:
199: public function loadFile($filename)
200: {
201: $info = $this->call('getimagesize', array($filename));
202: if (is_array($info)) {
203: switch ($info[2]) {
204: case 1:
205: if (function_exists('imagecreatefromgif')) {
206: $this->_im = $this->call('imagecreatefromgif', array($filename));
207: }
208: break;
209: case 2:
210: $this->_im = $this->call('imagecreatefromjpeg', array($filename));
211: break;
212: case 3:
213: $this->_im = $this->call('imagecreatefrompng', array($filename));
214: break;
215: case 15:
216: if (function_exists('imagecreatefromgwbmp')) {
217: $this->_im = $this->call('imagecreatefromgwbmp', array($filename));
218: }
219: break;
220: case 16:
221: $this->_im = $this->call('imagecreatefromxbm', array($filename));
222: break;
223: }
224: }
225:
226: if (is_resource($this->_im)) {
227: return true;
228: }
229:
230: $result = parent::loadFile($filename);
231:
232: $this->_im = $this->call('imageCreateFromString', array($this->_data));
233: }
234:
235: 236: 237: 238: 239: 240: 241: 242: 243:
244: public function resize($width, $height, $ratio = true)
245: {
246: 247: 248:
249: if (!$width || !$height || !is_resource($this->_im)) {
250: throw new Horde_Image_Exception('Unable to resize image.');
251: }
252:
253: if ($ratio) {
254: if ($width / $height > $this->call('imageSX', array($this->_im)) / $this->call('imageSY', array($this->_im))) {
255: $width = $height * $this->call('imageSX', array($this->_im)) / $this->call('imageSY', array($this->_im));
256: } else {
257: $height = $width * $this->call('imageSY', array($this->_im)) / $this->call('imageSX', array($this->_im));
258: }
259: }
260:
261: $im = $this->_im;
262: $this->_im = $this->create($width, $height);
263:
264:
265: $this->_width = 0;
266: $this->_height = 0;
267:
268: $this->call('imageFill', array($this->_im, 0, 0, $this->call('imageColorAllocate', array($this->_im, 255, 255, 255))));
269: try {
270: $this->call('imageCopyResampled', array($this->_im, $im, 0, 0, 0, 0, $width, $height, $this->call('imageSX', array($im)), $this->call('imageSY', array($im))));
271: } catch (Horde_Image_Exception $e) {
272: $this->call('imageCopyResized', array($this->_im, $im, 0, 0, 0, 0, $width, $height, $this->call('imageSX', array($im)), $this->call('imageSY', array($im))));
273: }
274: }
275:
276: 277: 278: 279: 280: 281: 282: 283:
284: public function crop($x1, $y1, $x2, $y2)
285: {
286: $im = $this->_im;
287: $this->_im = $this->create($x2 - $x1, $y2 - $y1);
288: $this->_width = 0;
289: $this->_height = 0;
290: $this->call('imageCopy', array($this->_im, $im, 0, 0, $x1, $y1, $x2 - $x1, $y2 - $y1));
291: }
292:
293: 294: 295: 296: 297: 298: 299:
300: public function rotate($angle, $background = 'white')
301: {
302: $background = $this->_allocateColor($background);
303:
304: $this->_width = 0;
305: $this->_height = 0;
306:
307: switch ($angle) {
308: case '90':
309: $x = $this->call('imageSX', array($this->_im));
310: $y = $this->call('imageSY', array($this->_im));
311: $xymax = max($x, $y);
312:
313: $im = $this->create($xymax, $xymax);
314: $im = $this->call('imageRotate', array($im, 270, $background));
315: $this->_im = $im;
316: $im = $this->create($y, $x);
317: if ($x < $y) {
318: $this->call('imageCopy', array($im, $this->_im, 0, 0, 0, 0, $xymax, $xymax));
319: } elseif ($x > $y) {
320: $this->call('imageCopy', array($im, $this->_im, 0, 0, $xymax - $y, $xymax - $x, $xymax, $xymax));
321: }
322: $this->_im = $im;
323: break;
324:
325: default:
326: $this->_im = $this->call('imageRotate', array($this->_im, 360 - $angle, $background));
327: }
328: }
329:
330: 331: 332:
333: public function flip()
334: {
335: $x = $this->call('imageSX', array($this->_im));
336: $y = $this->call('imageSY', array($this->_im));
337:
338: $im = $this->create($x, $y);
339: for ($curY = 0; $curY < $y; $curY++) {
340: $this->call('imageCopy', array($im, $this->_im, 0, $y - ($curY + 1), 0, $curY, $x, 1));
341: }
342:
343: $this->_im = $im;
344: }
345:
346: 347: 348:
349: public function mirror()
350: {
351: $x = $this->call('imageSX', array($this->_im));
352: $y = $this->call('imageSY', array($this->_im));
353:
354: $im = $this->create($x, $y);
355: for ($curX = 0; $curX < $x; $curX++) {
356: $this->call('imageCopy', array($im, $this->_im, $x - ($curX + 1), 0, $curX, 0, 1, $y));
357: }
358:
359: $this->_im = $im;
360: }
361:
362: 363: 364:
365: public function grayscale()
366: {
367: $rateR = .229;
368: $rateG = .587;
369: $rateB = .114;
370: $whiteness = 3;
371: if ($this->call('imageIsTrueColor', array($this->_im)) === true) {
372: $this->call('imageTrueColorToPalette', array($this->_im, true, 256));
373: }
374: $colors = min(256, $this->call('imageColorsTotal', array($this->_im)));
375: for ($x = 0; $x < $colors; $x++) {
376: $src = $this->call('imageColorsForIndex', array($this->_im, $x));
377: $new = min(255, abs($src['red'] * $rateR + $src['green'] * $rateG + $src['blue'] * $rateB) + $whiteness);
378: $this->call('imageColorSet', array($this->_im, $x, $new, $new, $new));
379: }
380: }
381:
382: 383: 384: 385: 386: 387: 388: 389: 390:
391: public function sepia($threshold = 85)
392: {
393: $tintR = 80;
394: $tintG = 43;
395: $tintB = -23;
396: $rateR = .229;
397: $rateG = .587;
398: $rateB = .114;
399: $whiteness = 3;
400:
401: if ($this->call('imageIsTrueColor', array($this->_im)) === true) {
402: $this->call('imageTrueColorToPalette', array($this->_im, true, 256));
403: }
404:
405: $colors = max(256, $this->call('imageColorsTotal', array($this->_im)));
406: for ($x = 0; $x < $colors; $x++) {
407: $src = $this->call('imageColorsForIndex', array($this->_im, $x));
408: $new = min(255, abs($src['red'] * $rateR + $src['green'] * $rateG + $src['blue'] * $rateB) + $whiteness);
409: $r = min(255, $new + $tintR);
410: $g = min(255, $new + $tintG);
411: $b = min(255, $new + $tintB);
412: $this->call('imageColorSet', array($this->_im, $x, $r, $g, $b));
413: }
414: }
415:
416: 417: 418: 419: 420: 421: 422: 423: 424:
425: public function yellowize($intensityY = 50, $intensityB = 3)
426: {
427: if ($this->call('imageIsTrueColor', array($this->_im)) === true) {
428: $this->call('imageTrueColorToPalette', array($this->_im, true, 256));
429: }
430:
431: $colors = max(256, $this->call('imageColorsTotal', array($this->_im)));
432: for ($x = 0; $x < $colors; $x++) {
433: $src = $this->call('imageColorsForIndex', array($this->_im, $x));
434: $r = min($src['red'] + $intensityY, 255);
435: $g = min($src['green'] + $intensityY, 255);
436: $b = max(($r + $g) / max($intensityB, 2), 0);
437: $this->call('imageColorSet', array($this->_im, $x, $r, $g, $b));
438: }
439: }
440:
441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459:
460: public function text($string, $x, $y, $font = 'monospace', $color = 'black', $direction = 0, $fontsize = 'small')
461: {
462: $c = $this->_allocateColor($color);
463: $f = $this->_getFont($fontsize);
464: switch ($direction) {
465: case -90:
466: case 270:
467: $result = $this->call('imageStringUp', array($this->_im, $f, $x, $y, $string, $c));
468: break;
469:
470: case 0:
471: default:
472: $result = $this->call('imageString', array($this->_im, $f, $x, $y, $string, $c));
473: }
474:
475: return $result;
476: }
477:
478: 479: 480: 481: 482: 483: 484: 485: 486:
487: public function circle($x, $y, $r, $color, $fill = null)
488: {
489: $c = $this->_allocateColor($color);
490: if (is_null($fill)) {
491: $result = $this->call('imageEllipse', array($this->_im, $x, $y, $r * 2, $r * 2, $c));
492: } else {
493: if ($fill !== $color) {
494: $fillColor = $this->_allocateColor($fill);
495: $this->call('imageFilledEllipse', array($this->_im, $x, $y, $r * 2, $r * 2, $fillColor));
496: $this->call('imageEllipse', array($this->_im, $x, $y, $r * 2, $r * 2, $c));
497: } else {
498: $this->call('imageFilledEllipse', array($this->_im, $x, $y, $r * 2, $r * 2, $c));
499: }
500: }
501: }
502:
503: 504: 505: 506: 507: 508: 509: 510:
511: public function polygon($verts, $color, $fill = 'none')
512: {
513: $vertices = array();
514: foreach ($verts as $vert) {
515: $vertices[] = $vert['x'];
516: $vertices[] = $vert['y'];
517: }
518:
519: if ($fill != 'none') {
520: $f = $this->_allocateColor($fill);
521: $this->call('imageFilledPolygon', array($this->_im, $vertices, count($verts), $f));
522: }
523:
524: if ($fill == 'none' || $fill != $color) {
525: $c = $this->_allocateColor($color);
526: $this->call('imagePolygon', array($this->_im, $vertices, count($verts), $c));
527: }
528: }
529:
530: 531: 532: 533: 534: 535: 536: 537: 538: 539:
540: public function rectangle($x, $y, $width, $height, $color = 'black', $fill = 'none')
541: {
542: if ($fill != 'none') {
543: $f = $this->_allocateColor($fill);
544: $this->call('imageFilledRectangle', array($this->_im, $x, $y, $x + $width, $y + $height, $f));
545: }
546:
547: if ($fill == 'none' || $fill != $color) {
548: $c = $this->_allocateColor($color);
549: $this->call('imageRectangle', array($this->_im, $x, $y, $x + $width, $y + $height, $c));
550: }
551: }
552:
553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563:
564: public function roundedRectangle($x, $y, $width, $height, $round, $color = 'black', $fill = 'none')
565: {
566: if ($round <= 0) {
567:
568: return $this->rectangle($x, $y, $width, $height, $color, $fill);
569: }
570:
571: $c = $this->_allocateColor($color);
572:
573:
574: $x1 = $x + $round;
575: $y1 = $y + $round;
576:
577: $x2 = $x + $width - $round;
578: $y2 = $y + $round;
579:
580: $x3 = $x + $width - $round;
581: $y3 = $y + $height - $round;
582:
583: $x4 = $x + $round;
584: $y4 = $y + $height - $round;
585:
586: $r = $round * 2;
587:
588:
589: $p1 = Horde_Image::arcPoints($round, 180, 225);
590: $p2 = Horde_Image::arcPoints($round, 225, 270);
591:
592:
593: $p3 = Horde_Image::arcPoints($round, 270, 315);
594: $p4 = Horde_Image::arcPoints($round, 315, 360);
595:
596:
597: $p5 = Horde_Image::arcPoints($round, 0, 45);
598: $p6 = Horde_Image::arcPoints($round, 45, 90);
599:
600:
601: $p7 = Horde_Image::arcPoints($round, 90, 135);
602: $p8 = Horde_Image::arcPoints($round, 135, 180);
603:
604:
605:
606: $this->call('imageArc', array($this->_im, $x1, $y1, $r, $r, 180, 270, $c));
607: $this->call('imageArc', array($this->_im, $x2, $y2, $r, $r, 270, 360, $c));
608: $this->call('imageArc', array($this->_im, $x3, $y3, $r, $r, 0, 90, $c));
609: $this->call('imageArc', array($this->_im, $x4, $y4, $r, $r, 90, 180, $c));
610:
611:
612: $this->call('imageLine', array($this->_im, $x1 + $p2['x2'], $y1 + $p2['y2'], $x2 + $p3['x1'], $y2 + $p3['y1'], $c));
613: $this->call('imageLine', array($this->_im, $x2 + $p4['x2'], $y2 + $p4['y2'], $x3 + $p5['x1'], $y3 + $p5['y1'], $c));
614: $this->call('imageLine', array($this->_im, $x3 + $p6['x2'], $y3 + $p6['y2'], $x4 + $p7['x1'], $y4 + $p7['y1'], $c));
615: $this->call('imageLine', array($this->_im, $x4 + $p8['x2'], $y4 + $p8['y2'], $x1 + $p1['x1'], $y1 + $p1['y1'], $c));
616:
617:
618: if ($fill != 'none') {
619: $f = $this->_allocateColor($fill);
620: $this->call('imageFillToBorder', array($this->_im, $x + ($width / 2), $y + ($height / 2), $c, $f));
621: }
622: }
623:
624: 625: 626: 627: 628: 629: 630: 631: 632: 633:
634: public function line($x1, $y1, $x2, $y2, $color = 'black', $width = 1)
635: {
636: $c = $this->_allocateColor($color);
637:
638:
639: if ($width == 1) {
640: $this->call('imageLine', array($this->_im, $x1, $y1, $x2, $y2, $c));
641: } elseif ($x1 == $x2) {
642:
643:
644: $left = $x1 - floor(($width - 1) / 2);
645: $right = $x1 + floor($width / 2);
646: $this->call('imageFilledRectangle', array($this->_im, $left, $y1, $right, $y2, $c));
647: } elseif ($y1 == $y2) {
648:
649:
650: $top = $y1 - floor($width / 2);
651: $bottom = $y1 + floor(($width - 1) / 2);
652: $this->call('imageFilledRectangle', array($this->_im, $x1, $top, $x2, $bottom, $c));
653: } else {
654:
655:
656:
657:
658: $a = atan2($y1 - $y2, $x2 - $x1);
659: $dx = (sin($a) * $width / 2);
660: $dy = (cos($a) * $width / 2);
661:
662: $verts = array($x2 + $dx, $y2 + $dy, $x2 - $dx, $y2 - $dy, $x1 - $dx, $y1 - $dy, $x1 + $dx, $y1 + $dy);
663: $this->call('imageFilledPolygon', array($this->_im, $verts, count($verts) / 2, $c));
664: }
665: }
666:
667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678:
679: public function dashedLine($x0, $y0, $x1, $y1, $color = 'black', $width = 1, $dash_length = 2, $dash_space = 2)
680: {
681: $c = $this->_allocateColor($color);
682: $w = $this->_allocateColor('white');
683:
684:
685: $style = array();
686: for ($i = 0; $i < $dash_length; $i++) {
687: $style[] = $c;
688: }
689: for ($i = 0; $i < $dash_space; $i++) {
690: $style[] = $w;
691: }
692:
693: $this->call('imageSetStyle', array($this->_im, $style));
694: $this->call('imageSetThickness', array($this->_im, $width));
695: $this->call('imageLine', array($this->_im, $x0, $y0, $x1, $y1, IMG_COLOR_STYLED));
696: }
697:
698: 699: 700: 701: 702: 703: 704: 705: 706:
707: public function polyline($verts, $color, $width = 1)
708: {
709: $first = true;
710: foreach ($verts as $vert) {
711: if (!$first) {
712: $this->line($lastX, $lastY, $vert['x'], $vert['y'], $color, $width);
713: } else {
714: $first = false;
715: }
716: $lastX = $vert['x'];
717: $lastY = $vert['y'];
718: }
719: }
720:
721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731:
732: public function arc($x, $y, $r, $start, $end, $color = 'black', $fill = null)
733: {
734: $c = $this->_allocateColor($color);
735: if (is_null($fill)) {
736: $this->call('imageArc', array($this->_im, $x, $y, $r * 2, $r * 2, $start, $end, $c));
737: } else {
738: if ($fill !== $color) {
739: $f = $this->_allocateColor($fill);
740: $this->call('imageFilledArc', array($this->_im, $x, $y, $r * 2, $r * 2, $start, $end, $f, IMG_ARC_PIE));
741: $this->call('imageFilledArc', array($this->_im, $x, $y, $r * 2, $r * 2, $start, $end, $c, IMG_ARC_EDGED | IMG_ARC_NOFILL));
742: } else {
743: $this->call('imageFilledArc', array($this->_im, $x, $y, $r * 2, $r * 2, $start, $end, $c, IMG_ARC_PIE));
744: }
745: }
746: }
747:
748: 749: 750: 751: 752: 753: 754: 755: 756: 757:
758: public function create($width, $height)
759: {
760: $result = $this->call('imageCreateTrueColor', array($width, $height));
761: if (!is_resource($result)) {
762: throw new Horde_Image_Exception('Could not create image.');
763: }
764:
765: return $result;
766: }
767:
768: 769: 770: 771: 772: 773: 774: 775: 776:
777: public function call($function, $params = null)
778: {
779: unset($php_errormsg);
780: $track = ini_set('track_errors', 1);
781: $result = @call_user_func_array($function, $params);
782: if ($track !== false) {
783: ini_set('track_errors', $track);
784: }
785: if (!empty($php_errormsg)) {
786: $error_msg = $php_errormsg;
787: throw new Horde_Image_Exception($error_msg);
788: }
789: return $result;
790: }
791:
792: 793: 794: 795: 796: 797: 798:
799: public function applyMask($gdimg_mask)
800: {
801: $imgX = round($this->call('imageSX', array($this->_im)));
802: $imgY = round($this->call('imageSY', array($this->_im)));
803: $gdimg_mask_resized = $this->create($imgX, $imgY);
804: $result = $this->call('imageCopyResampled',
805: array($gdimg_mask_resized,
806: $gdimg_mask,
807: 0, 0, 0, 0,
808: $imgX,
809: $imgY,
810: $this->call('imageSX', array($gdimg_mask)),
811: $this->call('imageSY', array($gdimg_mask))));
812:
813: $gdimg_mask_blendtemp = $this->create($imgX, $imgY);
814: $mbtX = $this->call('imageSX', array($gdimg_mask_blendtemp));
815: $mbtY = $this->call('imageSY', array($gdimg_mask_blendtemp));
816:
817: $color_background = $this->call('imageColorAllocate',
818: array($gdimg_mask_blendtemp, 0, 0, 0));
819:
820: $this->call('imageFilledRectangle', array($gdimg_mask_blendtemp,
821: 0,
822: 0,
823: $mbtX,
824: $mbtY,
825: $color_background));
826:
827: $this->call('imageAlphaBlending',
828: array($gdimg_mask_blendtemp, false));
829:
830: $this->call('imageSaveAlpha',
831: array($gdimg_mask_blendtemp, true));
832:
833: for ($x = 0; $x < $imgX; $x++) {
834: for ($y = 0; $y < $imgY; $y++) {
835: $colorat = $this->call('imageColorAt', array($this->_im, $x, $y));
836: $realPixel = $this->call('imageColorsForIndex', array($this->_im, $colorat));
837: $colorat = $this->call('imageColorAt', array($gdimg_mask_resized, $x, $y));
838: $maskPixel = Horde_Image::grayscalePixel($this->call('imageColorsForIndex', array($gdimg_mask_resized, $colorat)));
839: $maskAlpha = 127 - (floor($maskPixel['red'] / 2) * (1 - ($realPixel['alpha'] / 127)));
840: $newcolor = $this->_allocateColorAlpha($gdimg_mask_blendtemp,
841: $realPixel['red'],
842: $realPixel['green'],
843: $realPixel['blue'],
844: intval($maskAlpha));
845: $this->call('imageSetPixel', array($gdimg_mask_blendtemp, $x, $y, $newcolor));
846: }
847: }
848: $this->call('imageAlphaBlending', array($this->_im, false));
849: $this->call('imageSaveAlpha', array($this->_im, true));
850: $this->call('imageCopy', array($this->_im,
851: $gdimg_mask_blendtemp,
852: 0, 0, 0, 0,
853: $mbtX,
854: $mbtY));
855:
856: $this->call('imageDestroy', array($gdimg_mask_blendtemp));
857:
858: $this->call('imageDestroy', array($gdimg_mask_resized));
859:
860: return true;
861: }
862:
863: protected function _allocateColorAlpha($gdimg_hexcolorallocate, $r, $g, $b , $alpha = false)
864: {
865: $result = $this->call('imageColorAllocateAlpha', array($gdimg_hexcolorallocate, $r, $g, $b, intval($alpha)));
866: $result = $this->call('imageColorAllocate', array($gdimg_hexcolorallocate, $r, $g, $b));
867: return $result;
868: }
869:
870: 871: 872: 873: 874: 875: 876:
877: public function getImageAtIndex($index)
878: {
879: if ($index > 0) {
880: throw new Horde_Image_Exception('Image index out of bounds.');
881: }
882: }
883:
884: 885: 886: 887: 888:
889: public function getImagePageCount()
890: {
891: return 1;
892: }
893:
894: }
895: