1 29 30 package com.caucho.quercus.lib; 31 32 import com.caucho.quercus.QuercusException; 33 import com.caucho.quercus.QuercusModuleException; 34 import com.caucho.quercus.annotation.NotNull; 35 import com.caucho.quercus.annotation.Optional; 36 import com.caucho.quercus.annotation.ReturnNullAsFalse; 37 import com.caucho.quercus.env.*; 38 import com.caucho.quercus.module.AbstractQuercusModule; 39 import com.caucho.util.L10N; 40 import com.caucho.vfs.Path; 41 import com.caucho.vfs.ReadStream; 42 import com.caucho.vfs.WriteStream; 43 44 import javax.imageio.ImageIO ; 45 import java.awt.*; 46 import java.awt.color.ColorSpace ; 47 import java.awt.geom.Arc2D ; 48 import java.awt.geom.Ellipse2D ; 49 import java.awt.geom.FlatteningPathIterator ; 50 import java.awt.geom.Line2D ; 51 import java.awt.geom.Rectangle2D ; 52 import java.awt.image.BufferedImage ; 53 import java.awt.image.BufferedImageOp ; 54 import java.awt.image.ColorConvertOp ; 55 import java.awt.image.ConvolveOp ; 56 import java.awt.image.Kernel ; 57 import java.awt.image.RescaleOp ; 58 import java.io.IOException ; 59 import java.io.InputStream ; 60 import java.util.LinkedList ; 61 import java.util.logging.Level ; 62 import java.util.logging.Logger ; 63 64 67 public class ImageModule extends AbstractQuercusModule { 68 private static final Logger log 69 = Logger.getLogger(ImageModule.class.getName()); 70 private static final L10N L = new L10N(ImageModule.class); 71 72 public static final long IMG_GIF = 0x1; 73 public static final long IMG_JPG = 0x2; 74 public static final long IMG_JPEG = 0x2; 75 public static final long IMG_PNG = 0x4; 76 public static final long IMG_WBMP = 0x8; 77 public static final long IMG_XPM = 0x10; 78 79 public static final int IMAGETYPE_GIF = 1; 80 public static final int IMAGETYPE_JPG = 2; 81 public static final int IMAGETYPE_JPEG = 2; 82 public static final int IMAGETYPE_PNG = 3; 83 public static final int IMAGETYPE_SWF = 4; 84 public static final int IMAGETYPE_PSD = 5; 85 public static final int IMAGETYPE_BMP = 6; 86 public static final int IMAGETYPE_TIFF_II = 7; 87 public static final int IMAGETYPE_TIFF_MM = 8; 88 public static final int IMAGETYPE_JPC = 9; 89 public static final int IMAGETYPE_JP2 = 10; 90 public static final int IMAGETYPE_JPX = 11; 91 public static final int IMAGETYPE_JB2 = 12; 92 public static final int IMAGETYPE_SWC = 13; 93 public static final int IMAGETYPE_IFF = 14; 94 public static final int IMAGETYPE_WBMP = 15; 95 public static final int IMAGETYPE_XBM = 16; 96 97 public static final int IMG_COLOR_STYLED = -2; 98 public static final int IMG_COLOR_BRUSHED = -3; 99 100 private static final int PNG_IHDR = pngCode("IHDR"); 101 102 public static final int IMG_ARC_PIE = 0; 103 public static final int IMG_ARC_CHORD = 1; 104 public static final int IMG_ARC_NOFILL = 2; 105 public static final int IMG_ARC_EDGED = 4; 106 107 public static final int IMG_FILTER_NEGATE = 0; 108 public static final int IMG_FILTER_GRAYSCALE = 1; 109 public static final int IMG_FILTER_BRIGHTNESS = 2; 110 public static final int IMG_FILTER_CONTRAST = 3; 111 public static final int IMG_FILTER_COLORIZE = 4; 112 public static final int IMG_FILTER_EDGEDETECT = 5; 113 public static final int IMG_FILTER_EMBOSS = 6; 114 public static final int IMG_FILTER_GAUSSIAN_BLUR = 7; 115 public static final int IMG_FILTER_SELECTIVE_BLUR = 8; 116 public static final int IMG_FILTER_MEAN_REMOVAL = 9; 117 public static final int IMG_FILTER_SMOOTH = 10; 118 119 public String []getLoadedExtensions() 120 { 121 return new String [] { "gd" }; 122 } 123 124 127 public Value getimagesize(Env env, 128 Path file, 129 @Optional ArrayValue imageArray) 130 { 131 if (! file.canRead()) 132 return BooleanValue.FALSE; 133 134 ImageInfo info = new ImageInfo(); 135 136 ReadStream is = null; 137 138 try { 139 is = file.openRead(); 140 141 if (! parseImageSize(is, info)) 142 return BooleanValue.FALSE; 143 } catch (Exception e) { 144 log.log(Level.FINE, e.toString(), e); 145 146 return BooleanValue.FALSE; 147 } finally { 148 is.close(); 149 } 150 151 if (imageArray == null) 152 imageArray = new ArrayValueImpl(); 153 154 imageArray.put(new LongValue(info._width)); 155 imageArray.put(new LongValue(info._height)); 156 imageArray.put(new LongValue(info._type)); 157 imageArray.put(new StringValueImpl("width=\"" + info._width + 158 "\" height=\"" + info._height + "\"")); 159 160 if (info._bits >= 0) 161 imageArray.put(new StringValueImpl("bits"), new LongValue(info._bits)); 162 163 if (info._type == IMAGETYPE_JPEG) 164 imageArray.put("channels", 3); 165 166 if (info._mime != null) 167 imageArray.put("mime", info._mime); 168 169 return imageArray; 170 } 171 172 175 private static boolean parseImageSize(ReadStream is, ImageInfo info) 176 throws IOException 177 { 178 int ch; 179 180 ch = is.read(); 181 182 if (ch == 137) { 183 if (is.read() != 'P' || 185 is.read() != 'N' || 186 is.read() != 'G' || 187 is.read() != '\r' || 188 is.read() != '\n' || 189 is.read() != 26 || 190 is.read() != '\n') 191 return false; 192 193 return parsePNGImageSize(is, info); 194 } 195 else if (ch == 'G') { 196 if (is.read() != 'I' || 198 is.read() != 'F' || 199 is.read() != '8' || 200 ((ch = is.read()) != '7' && ch != '9') || 201 is.read() != 'a') 202 return false; 203 204 return parseGIFImageSize(is, info); 205 } 206 else if (ch == 0xff) { 207 if (is.read() != 0xd8) 209 return false; 210 211 return parseJPEGImageSize(is, info); 212 } 213 else 214 return false; 215 } 216 217 220 private static boolean parsePNGImageSize(ReadStream is, ImageInfo info) 221 throws IOException 222 { 223 int length; 224 225 while ((length = readInt(is)) > 0) { 226 int type = readInt(is); 227 228 if (type == PNG_IHDR) { 229 int width = readInt(is); 230 int height = readInt(is); 231 int depth = is.read() & 0xff; 232 int color = is.read() & 0xff; 233 int compression = is.read() & 0xff; 234 int filter = is.read() & 0xff; 235 int interlace = is.read() & 0xff; 236 237 info._width = width; 238 info._height = height; 239 info._type = IMAGETYPE_PNG; 240 241 info._bits = depth; 242 243 info._mime = "image/png"; 244 245 return true; 246 } 247 else { 248 for (int i = 0; i < length; i++) { 249 if (is.read() < 0) 250 return false; 251 } 252 } 253 254 int crc = readInt(is); 255 } 256 257 return false; 258 } 259 260 263 private static boolean parseGIFImageSize(ReadStream is, ImageInfo info) 264 throws IOException 265 { 266 int length; 267 268 int width = (is.read() & 0xff) + 256 * (is.read() & 0xff); 269 int height = (is.read() & 0xff) + 256 * (is.read() & 0xff); 270 271 int flags = is.read() & 0xff; 272 273 info._width = width; 274 info._height = height; 275 info._type = IMAGETYPE_GIF; 276 277 info._bits = flags & 0x7; 278 279 info._mime = "image/gif"; 280 281 return true; 282 } 283 284 287 private static boolean parseJPEGImageSize(ReadStream is, ImageInfo info) 288 throws IOException 289 { 290 int ch; 291 292 while ((ch = is.read()) == 0xff) { 293 ch = is.read(); 294 295 if (ch == 0xff) { 296 is.unread(); 297 } 298 else if (0xd0 <= ch && ch <= 0xd9) { 299 } 301 else if (0x01 == ch) { 302 } 304 else if (ch == 0xc0) { 305 int len = 256 * is.read() + is.read(); 306 307 int bits = is.read(); 308 int height = 256 * is.read() + is.read(); 309 int width = 256 * is.read() + is.read(); 310 311 info._width = width; 312 info._height = height; 313 info._type = IMAGETYPE_JPEG; 314 315 info._bits = bits; 316 317 info._mime = "image/jpeg"; 318 319 return true; 320 } 321 else { 322 int len = 256 * is.read() + is.read(); 323 324 is.skip(len - 2); 325 } 326 } 327 328 329 return false; 330 } 331 332 private static int pngCode(String code) 333 { 334 return ((code.charAt(0) << 24) | 335 (code.charAt(1) << 16) | 336 (code.charAt(2) << 8) | 337 (code.charAt(3))); 338 } 339 340 private static int readInt(ReadStream is) 341 throws IOException 342 { 343 return (((is.read() & 0xff) << 24) | 344 ((is.read() & 0xff) << 16) | 345 ((is.read() & 0xff) << 8) | 346 ((is.read() & 0xff))); 347 } 348 349 352 public static Value gd_info() 353 { 354 return (new ArrayValueImpl() 355 .append(StringValue.create("GD Version"), StringValue.create("2.0 or higher")) 357 .append(StringValue.create("FreeType Support"), BooleanValue.TRUE) 359 .append(StringValue.create("FreeType Linkage"), StringValue.create("with freetype")) 361 .append(StringValue.create("T1Lib Support"), BooleanValue.TRUE) 363 .append(StringValue.create("GIF Read Support"), BooleanValue.TRUE) 365 .append(StringValue.create("GIF Create Support"), BooleanValue.TRUE) 367 .append(StringValue.create("JPG Support"), BooleanValue.TRUE) 369 .append(StringValue.create("PNG Support"), BooleanValue.TRUE) 371 .append(StringValue.create("WBMP Support"), BooleanValue.TRUE) 373 .append(StringValue.create("XPM Support"), BooleanValue.FALSE) 375 .append(StringValue.create("XBM Support"), BooleanValue.FALSE) 377 .append(StringValue.create("JIS-mapped Japanese Font Support"), BooleanValue.FALSE)); 379 } 380 381 384 public static long imagetypes() 385 { 386 return IMG_GIF | IMG_JPG | IMG_PNG; 387 } 388 389 392 public static Value image_type_to_extension(int imageType, boolean dot) 393 { 394 switch(imageType) { 395 case IMAGETYPE_GIF: return StringValue.create(dot ? ".gif" : "gif"); 396 case IMAGETYPE_JPG: return StringValue.create(dot ? ".jpg" : "jpg"); 397 case IMAGETYPE_PNG: return StringValue.create(dot ? ".png" : "png"); 398 case IMAGETYPE_SWF: return StringValue.create(dot ? ".swf" : "swf"); 399 case IMAGETYPE_PSD: return StringValue.create(dot ? ".psd" : "psd"); 400 case IMAGETYPE_BMP: return StringValue.create(dot ? ".bmp" : "bmp"); 401 case IMAGETYPE_TIFF_II: return StringValue.create(dot ? ".tiff" : "tiff"); 402 case IMAGETYPE_TIFF_MM: return StringValue.create(dot ? ".tiff" : "tiff"); 403 case IMAGETYPE_JPC: return StringValue.create(dot ? ".jpc" : "jpc"); 404 case IMAGETYPE_JP2: return StringValue.create(dot ? ".jp2" : "jp2"); 405 case IMAGETYPE_JPX: return StringValue.create(dot ? ".jpf" : "jpf"); 406 case IMAGETYPE_JB2: return StringValue.create(dot ? ".jb2" : "jb2"); 407 case IMAGETYPE_SWC: return StringValue.create(dot ? ".swc" : "swc"); 408 case IMAGETYPE_IFF: return StringValue.create(dot ? ".iff" : "iff"); 409 case IMAGETYPE_WBMP: return StringValue.create(dot ? ".wbmp" : "wbmp"); 410 case IMAGETYPE_XBM: return StringValue.create(dot ? ".xbm" : "xbm"); 411 } 412 throw new QuercusException("unknown imagetype " + imageType); 413 } 414 415 419 public static Value image_type_to_mime_type(int imageType) 420 { 421 switch(imageType) { 422 case IMAGETYPE_GIF: 423 return StringValue.create("image/gif"); 424 case IMAGETYPE_JPG: 425 return StringValue.create("image/jpeg"); 426 case IMAGETYPE_PNG: 427 return StringValue.create("image/png"); 428 case IMAGETYPE_SWF: 429 return StringValue.create("application/x-shockwave-flash"); 430 case IMAGETYPE_PSD: 431 return StringValue.create("image/psd"); 432 case IMAGETYPE_BMP: 433 return StringValue.create("image/bmp"); 434 case IMAGETYPE_TIFF_II: 435 return StringValue.create("image/tiff"); 436 case IMAGETYPE_TIFF_MM: 437 return StringValue.create("image/tiff"); 438 case IMAGETYPE_JPC: 439 return StringValue.create("application/octet-stream"); 440 case IMAGETYPE_JP2: 441 return StringValue.create("image/jp2"); 442 case IMAGETYPE_JPX: 443 return StringValue.create("application/octet-stream"); 444 case IMAGETYPE_JB2: 445 return StringValue.create("application/octet-stream"); 446 case IMAGETYPE_SWC: 447 return StringValue.create("application/x-shockwave-flash"); 448 case IMAGETYPE_IFF: 449 return StringValue.create("image/iff"); 450 case IMAGETYPE_WBMP: 451 return StringValue.create("image/vnd.wap.wbmp"); 452 case IMAGETYPE_XBM: 453 return StringValue.create("image/xbm"); 454 } 455 throw new QuercusException("unknown imageType " + imageType); 456 } 457 458 459 462 public static boolean imagegif(Env env, QuercusImage image) 463 { 464 try { 465 ImageIO.write(image._bufferedImage, "gif", env.getOut()); 466 return true; 467 } 468 catch (IOException e) { 469 throw new QuercusModuleException(e); 470 } 471 } 472 473 476 public static boolean imagepng(Env env, QuercusImage image) 477 { 478 try { 479 ImageIO.write(image._bufferedImage, "png", env.getOut()); 480 return true; 481 } 482 catch (IOException e) { 483 throw new QuercusModuleException(e); 484 } 485 } 486 487 490 public static boolean imagejpeg(Env env, 491 QuercusImage image, 492 @Optional Path path, 493 @Optional int quality) 494 { 495 try { 496 if (path != null) { 497 WriteStream os = path.openWrite(); 498 try { 499 ImageIO.write(image._bufferedImage, "jpeg", os); 500 } finally { 501 os.close(); 502 } 503 } 504 else 505 ImageIO.write(image._bufferedImage, "jpeg", env.getOut()); 506 return true; 507 } 508 catch (IOException e) { 509 log.log(Level.FINE, e.toString(), e); 510 511 return false; 512 } 513 } 514 515 518 public static boolean imagealphablending(QuercusImage image, 519 boolean useAlphaBlending) 520 { 521 image.getGraphics().setComposite(useAlphaBlending 522 ? AlphaComposite.SrcOver 523 : AlphaComposite.Src); 524 return true; 525 } 526 527 530 public static boolean imageantialias(QuercusImage image, 531 boolean useAntiAliasing) 532 { 533 image.getGraphics().setRenderingHint(RenderingHints.KEY_ANTIALIASING, 534 useAntiAliasing 535 ? RenderingHints.VALUE_ANTIALIAS_ON 536 : RenderingHints.VALUE_ANTIALIAS_OFF); 537 return true; 538 } 539 540 543 public static long imagecolorallocate(QuercusImage image, 544 int r, int g, int b) 545 { 546 return (( 0x7f << 24) | 547 ((r & 0xff) << 16) | 548 ((g & 0xff) << 8) | 549 ((b & 0xff) << 0)); 550 } 551 552 555 public static long imagecolorallocatealpha(QuercusImage image, 556 int r, int g, int b, int a) 557 { 558 int alpha = 0x7f - (a & 0xff); 560 return ((alpha << 24) | 561 ((r & 0xff) << 16) | 562 ((g & 0xff) << 8) | 563 ((b & 0xff) << 0) ); 564 } 565 566 569 public static boolean imagecolordeallocate(QuercusImage image, int rgb) 570 { 571 return true; 573 } 574 575 578 public static long imagecolorat(QuercusImage image, int x, int y) 579 { 580 return image.getPixel(x, y); 581 } 582 583 586 public static long imagecolorclosest(QuercusImage image, int r, int g, int b) 587 { 588 return imagecolorallocate(image, r, g, b); 589 } 590 591 594 public static long imagecolorclosestalpha(QuercusImage image, 595 int r, int g, int b, int a) 596 { 597 return imagecolorallocatealpha(image, r, g, b, a); 598 } 599 600 603 public static long imagecolorexact(QuercusImage image, int r, int g, int b) 604 { 605 return imagecolorallocate(image, r, g, b); 606 } 607 608 611 public static long imagecolorexactalpha(QuercusImage image, 612 int r, int g, int b, int a) 613 { 614 return imagecolorallocatealpha(image, r, g, b, a); 615 } 616 617 621 public static boolean imagecolormatch(QuercusImage image1, 622 QuercusImage image2) 623 { 624 return true; 626 } 627 628 631 public static long imagecolorresolve(QuercusImage image, int r, int g, int b) 632 { 633 return imagecolorallocate(image, r, g, b); 634 } 635 636 640 public static long imagecolorresolvealpha(QuercusImage image, 641 int r, int g, int b, int a) 642 { 643 return imagecolorallocatealpha(image, r, g, b, a); 644 } 645 646 649 public static ArrayValue imagecolorsforindex(QuercusImage image, int argb) 650 { 651 ArrayValue arrayValue = new ArrayValueImpl(); 652 arrayValue.put("red", (argb >> 16) & 0xff); 653 arrayValue.put("green", (argb >> 8) & 0xff); 654 arrayValue.put("blue", (argb >> 0) & 0xff); 655 656 int alpha = 0x7f - ((argb >> 24) & 0xff); 658 arrayValue.put("alpha", alpha); 659 return arrayValue; 660 } 661 662 665 public static Value imagecolorstotal() 666 { 667 return LongValue.create(0); 668 } 669 670 673 public static Value imagecreate(int width, int height) 674 { 675 return new QuercusImage(width, height); 676 } 677 678 681 public static QuercusImage imagecreatefromgif(Env env, Path filename) 682 { 683 return new QuercusImage(env, filename); 684 } 685 686 689 @ReturnNullAsFalse 690 public static QuercusImage imagecreatefromjpeg(Env env, Path filename) 691 { 692 try { 693 return new QuercusImage(env, filename); 694 } catch (Exception e) { 695 env.warning(L.l("Can't open {0} as a jpeg image", filename)); 696 log.log(Level.FINE, e.toString(), e); 697 698 return null; 699 } 700 } 701 702 705 public static QuercusImage imagecreatefrompng(Env env, Path filename) 706 { 707 return new QuercusImage(env, filename); 708 } 709 710 713 public static Value imagecreatefromxbm(Env env, Path filename) 714 { 715 return new QuercusImage(env, filename); 716 } 717 718 721 public static QuercusImage imagecreatefromxpm(Env env, Path filename) 722 { 723 return new QuercusImage(env, filename); 724 } 725 726 729 public static QuercusImage imagecreatefromwbmp(Env env, Path filename) 730 { 731 return new QuercusImage(env, filename); 732 } 733 734 737 public static QuercusImage imagecreatefromstring(Env env, InputStream data) 738 { 739 return new QuercusImage(data); 740 } 741 742 745 public static Value imagecreatetruecolor(int width, int height) 746 { 747 return new QuercusImage(width, height); 748 } 749 750 753 public static boolean imagedestroy(QuercusImage image) 754 { 755 return true; 757 } 758 759 762 public static boolean imageistruecolor(QuercusImage image) 763 { 764 return true; 765 } 766 767 770 public static boolean imageinterlace(QuercusImage image, boolean enable) 771 { 772 return true; 774 } 775 776 777 779 782 public static boolean imagesetpixel(QuercusImage image, 783 int x, int y, int color) 784 { 785 image.setPixel(x, y, color); 786 return true; 787 } 788 789 792 public static boolean imageline(QuercusImage image, 793 int x1, int y1, int x2, int y2, int color) 794 { 795 image.stroke(new Line2D.Float (x1, y1, x2, y2), color); 796 return true; 797 } 798 799 802 public static boolean imagedashedline(QuercusImage image, 803 int x1, int y1, int x2, int y2, 804 int color) 805 { 806 Graphics2D g = image.getGraphics(); 807 Stroke stroke = g.getStroke(); 808 g.setColor(intToColor(color)); 809 g.setStroke(new BasicStroke(1, BasicStroke.JOIN_ROUND, 810 BasicStroke.CAP_ROUND, 1, 811 new float[] { 5, 5 }, 0)); 812 g.draw(new Line2D.Float (x1, y1, x2, y2)); 813 g.setStroke(stroke); 814 return true; 815 } 816 817 820 public static boolean imagearc(QuercusImage image, 821 double cx, double cy, 822 double width, double height, 823 double start, double end, 824 int color) 825 { 826 Arc2D arc = new Arc2D.Double (cx-width/2, cy-height/2, 827 width, height, -1 * start, -1 *(end-start), 828 Arc2D.OPEN); 829 image.stroke(arc, color); 830 return true; 831 } 832 833 836 public static boolean imagefilledarc(QuercusImage image, 837 double cx, double cy, 838 double width, double height, 839 double start, double end, 840 int color, 841 int style) 842 { 843 int type = Arc2D.PIE; 844 if ((style & IMG_ARC_CHORD)!=0) type = Arc2D.CHORD; 845 if ((style & IMG_ARC_PIE)!=0) type = Arc2D.PIE; 846 Arc2D arc = 847 new Arc2D.Double (cx-width/2, cy-height/2, 848 width, height, -1 * start, 849 -1 *(end-start), type); 850 if ((style & IMG_ARC_NOFILL)==0) image.fill(arc, color); 851 if ((style & IMG_ARC_EDGED)!=0) image.stroke(arc, color); 852 return true; 853 } 854 855 858 public static boolean imageellipse(QuercusImage image, 859 double cx, double cy, 860 double width, double height, 861 int color) 862 { 863 Shape shape = new Ellipse2D.Double (cx-width/2, cy-height/2, width, height); 864 image.stroke(shape, color); 865 return true; 866 } 867 868 871 public static boolean imagefilledellipse(QuercusImage image, 872 double cx, double cy, 873 double width, double height, 874 int color) 875 { 876 Ellipse2D ellipse = 877 new Ellipse2D.Double (cx-width/2, cy-height/2, width, height); 878 image.fill(ellipse, color); 879 return true; 880 } 881 882 private static Polygon arrayToPolygon(ArrayValue points, int numPoints) 883 { 884 Polygon polygon = new Polygon(); 885 ArrayValue.Entry entry = points.getHead(); 886 for(int i=0; i<numPoints; i++) { 887 int x = entry.getValue().toInt(); 888 entry = entry.getNext(); 889 int y = entry.getValue().toInt(); 890 entry = entry.getNext(); 891 polygon.addPoint(x, y); 892 } 893 return polygon; 894 } 895 896 899 public static boolean imagepolygon(QuercusImage image, ArrayValue points, 900 int numPoints, int color) 901 { 902 image.stroke(arrayToPolygon(points, numPoints), color); 903 return true; 904 } 905 906 909 public static boolean imagefilledpolygon(QuercusImage image, 910 ArrayValue points, 911 int numPoints, int color) 912 { 913 image.fill(arrayToPolygon(points, numPoints), color); 914 return true; 915 } 916 917 920 public static boolean imagerectangle(QuercusImage image, int x1, int y1, 921 int x2, int y2, int color) 922 { 923 if (x2 < x1) { int tmp = x1; x1 = x2; x2 = tmp; } 924 if (y2 < y1) { int tmp = y1; y1 = y2; y2 = tmp; } 925 image.stroke(new Rectangle2D.Float (x1, y1, x2-x1, y2-y1), color); 926 return true; 927 } 928 929 932 public static boolean imagefilledrectangle(QuercusImage image, int x1, int y1, 933 int x2, int y2, int color) 934 { 935 image.fill(new Rectangle2D.Float (x1, y1, x2-x1+1, y2-y1+1), color); 936 return true; 937 } 938 939 940 942 945 public static boolean imagechar(QuercusImage image, int font, 946 int x, int y, String c, int color) 947 { 948 Graphics2D g = image.getGraphics(); 949 g.setColor(intToColor(color)); 950 Font awtfont = image.getFont(font); 951 int height = image.getGraphics().getFontMetrics(awtfont).getAscent(); 952 g.setFont(awtfont); 953 g.drawString(c.substring(0, 1), x, y+height); 954 return true; 955 } 956 957 960 public static boolean imagestring(QuercusImage image, int font, 961 int x, int y, String s, int color) 962 { 963 Graphics2D g = image.getGraphics(); 964 g.setColor(intToColor(color)); 965 Font awtfont = image.getFont(font); 966 int height = image.getGraphics().getFontMetrics(awtfont).getAscent(); 967 g.setFont(awtfont); 968 g.drawString(s, x, y+height); 969 970 return true; 971 } 972 973 976 public static boolean imagecharup(QuercusImage image, int font, 977 int x, int y, String c, int color) 978 { 979 Graphics2D g = (Graphics2D)image.getGraphics().create(); 980 g.rotate(-1 * Math.PI / 2); 981 g.setColor(intToColor(color)); 982 Font awtfont = image.getFont(font); 983 int height = image.getGraphics().getFontMetrics(awtfont).getAscent(); 984 g.setFont(awtfont); 985 g.drawString(c.substring(0, 1), -1 * y, x+height); 986 return true; 987 } 988 989 992 public static int imagesx(@NotNull QuercusImage image) 993 { 994 if (image == null) 995 return 0; 996 997 Graphics2D g = (Graphics2D) image.getGraphics(); 998 999 Rectangle bounds = g.getDeviceConfiguration().getBounds(); 1000 1001 return (int) bounds.getWidth(); 1002 } 1003 1004 1007 public static int imagesy(@NotNull QuercusImage image) 1008 { 1009 if (image == null) 1010 return 0; 1011 1012 Graphics2D g = (Graphics2D) image.getGraphics(); 1013 1014 Rectangle bounds = g.getDeviceConfiguration().getBounds(); 1015 1016 return (int) bounds.getHeight(); 1017 1018 } 1019 1020 1023 public static int imagefontheight(QuercusImage image, int font) 1024 { 1025 return image.getGraphics().getFontMetrics(image.getFont(font)).getHeight(); 1026 } 1027 1028 1031 public static int imagefontwidth(QuercusImage image, int font) 1032 { 1033 return image.getGraphics().getFontMetrics(image.getFont(font)) 1034 .getMaxAdvance(); 1035 } 1036 1037 1038 1040 1043 public static boolean imagecopy(QuercusImage dest, QuercusImage src, 1044 int dx, int dy, int sx, int sy, int w, int h) 1045 { 1046 dest.getGraphics().drawImage(src._bufferedImage, 1047 dx, dy, dx+w, dy+h, 1048 sx, sy, sx+w, sy+h, null); 1049 return true; 1050 } 1051 1052 1055 public static boolean imagecopymerge(QuercusImage dest, QuercusImage src, 1056 int dx, int dy, int sx, int sy, 1057 int w, int h, int pct) 1058 { 1059 BufferedImage rgba = 1060 new BufferedImage (dest.getWidth(), dest.getHeight(), 1061 BufferedImage.TYPE_INT_ARGB); 1062 rgba.getGraphics().drawImage(src._bufferedImage, 0, 0, null); 1063 BufferedImageOp rescaleOp = 1064 new RescaleOp (new float[] { 1, 1, 1, ((float)pct)/100 }, 1065 new float[] { 0, 0, 0, 0 }, 1066 null); 1067 BufferedImage rescaledImage = 1068 rescaleOp.filter(rgba, null); 1069 Graphics2D g = (Graphics2D)dest.getGraphics().create(); 1070 g.setComposite(AlphaComposite.SrcOver); 1071 g.drawImage(rescaledImage, 1072 dx, dy, dx+w, dy+h, 1073 sx, sy, sx+w, sy+h, null); 1074 return true; 1075 } 1076 1077 1080 public static boolean imagecopymergegray(QuercusImage dest, QuercusImage src, 1081 int dx, int dy, int sx, int sy, 1082 int w, int h, int pct) 1083 { 1084 BufferedImage rgba = 1085 new BufferedImage (dest.getWidth(), dest.getHeight(), 1086 BufferedImage.TYPE_INT_ARGB); 1087 rgba.getGraphics().drawImage(src._bufferedImage, 0, 0, null); 1088 BufferedImageOp rescaleOp = 1089 new RescaleOp (new float[] { 1, 1, 1, ((float)pct)/100 }, 1090 new float[] { 0, 0, 0, 0 }, 1091 null); 1092 BufferedImage rescaledImage = 1093 rescaleOp.filter(rgba, null); 1094 1095 ColorConvertOp colorConvertOp = 1096 new ColorConvertOp (ColorSpace.getInstance(ColorSpace.CS_GRAY), null); 1097 colorConvertOp.filter(dest._bufferedImage, dest._bufferedImage); 1098 1099 Graphics2D g = (Graphics2D)dest.getGraphics().create(); 1100 g.setComposite(AlphaComposite.SrcOver); 1101 g.drawImage(rescaledImage, 1102 dx, dy, dx+w, dy+h, 1103 sx, sy, sx+w, sy+h, null); 1104 return true; 1105 } 1106 1107 1110 public static boolean imagecopyresampled(QuercusImage dest, QuercusImage src, 1111 int dx, int dy, int sx, int sy, 1112 int dw, int dh, int sw, int sh) 1113 { 1114 Graphics2D g = (Graphics2D)dest.getGraphics().create(); 1115 g.setRenderingHint(RenderingHints.KEY_RENDERING, 1116 RenderingHints.VALUE_RENDER_QUALITY); 1117 g.drawImage(src._bufferedImage, 1118 dx, dy, dx+dw, dy+dh, 1119 sx, sy, sx+sw, sy+sh, null); 1120 g.setRenderingHint(RenderingHints.KEY_RENDERING, 1121 RenderingHints.VALUE_RENDER_DEFAULT); 1122 return true; 1123 } 1124 1125 1128 public static boolean imagecopyresized(QuercusImage dest, QuercusImage src, 1129 int dx, int dy, int sx, int sy, 1130 int dw, int dh, int sw, int sh) 1131 { 1132 Graphics2D g = (Graphics2D)dest.getGraphics().create(); 1133 g.drawImage(src._bufferedImage, 1134 dx, dy, dx+dw, dy+dh, 1135 sx, sy, sx+sw, sy+sh, null); 1136 return true; 1137 } 1138 1139 1140 1142 1145 public static boolean imagesetbrush(QuercusImage image, QuercusImage brush) 1146 { 1147 image.setBrush(brush); 1148 return true; 1149 } 1150 1151 1154 public static boolean imagesetstyle(QuercusImage image, ArrayValue style) 1155 { 1156 image.setStyle(style); 1157 return true; 1158 } 1159 1160 1163 public static boolean imagesetthickness(QuercusImage image, int thickness) 1164 { 1165 image.setThickness(thickness); 1166 return true; 1167 } 1168 1169 1170 1172 1175 public static boolean imagepalettecopy(QuercusImage source, 1176 QuercusImage dest) 1177 { 1178 return true; 1179 } 1180 1181 1182 1184 1188 public static boolean imagesavealpha(QuercusImage image, boolean set) 1189 { 1190 return true; 1192 } 1193 1194 1197 public static boolean imagecolorset(QuercusImage image, int index, 1198 int r, int g, int b) 1199 { 1200 return true; 1202 } 1203 1204 1207 public static long imagecolortransparent(QuercusImage image, 1208 @Optional int color) 1209 { 1210 return 0xFF000000; 1213 } 1214 1215 1216 1218 1221 public static boolean imagefill(QuercusImage image, int x, int y, int color) 1222 { 1223 image.flood(x, y, color); 1224 return true; 1225 } 1226 1227 1230 public static boolean imagefilltoborder(QuercusImage image, int x, int y, 1231 int border, int color) 1232 { 1233 image.flood(x, y, color, border); 1234 return true; 1235 } 1236 1237 1238 1240 1243 public static boolean imageconvolution(QuercusImage image, ArrayValue matrix, 1244 double div, double offset) 1245 { 1246 float[] kernelValues = new float[9]; 1248 ArrayValue.Entry entry = matrix.getHead(); 1249 for(int y=0; y<3; y++) 1250 for(int x=0; x<3; x++) 1251 { 1252 kernelValues[x+y*3] = 1253 (float)matrix 1254 .get(LongValue.create(y)) 1255 .get(LongValue.create(x)).toDouble(); 1256 } 1257 ConvolveOp convolveOp = 1258 new ConvolveOp (new Kernel (3, 3, kernelValues), 1259 ConvolveOp.EDGE_NO_OP, null); 1260 BufferedImage bufferedImage = 1261 convolveOp.filter(image._bufferedImage, null); 1262 image._bufferedImage.getGraphics().drawImage(bufferedImage, 1, 0, null); 1263 return true; 1264 } 1265 1266 1269 public static boolean imagefilter(Env env, QuercusImage image, int filterType, 1270 @Optional int arg1, @Optional int arg2, 1271 @Optional int arg3) 1272 { 1273 switch(filterType) 1274 { 1275 case IMG_FILTER_NEGATE: 1276 env.warning(L.l("imagefilter(IMG_FILTER_NEGATE) unimplemented")); 1278 return false; 1279 1280 case IMG_FILTER_GRAYSCALE: 1281 env.warning(L.l("imagefilter(IMG_FILTER_GRAYSCALE) unimplemented")); 1283 return false; 1284 1285 case IMG_FILTER_BRIGHTNESS: 1286 env.warning(L.l("imagefilter(IMG_FILTER_BRIGHTNESS) unimplementetd")); 1288 return false; 1289 1290 case IMG_FILTER_CONTRAST: 1291 env.warning(L.l("imagefilter(IMG_FILTER_CONTRAST) unimplementetd")); 1293 return false; 1294 1295 case IMG_FILTER_COLORIZE: 1296 env.warning(L.l("imagefilter(IMG_FILTER_COLORIZE) unimplemented")); 1300 return false; 1301 1302 case IMG_FILTER_EDGEDETECT: 1303 env.warning(L.l("imagefilter(IMG_FILTER_EDGEDETECT) unimplemented")); 1305 return false; 1306 1307 case IMG_FILTER_EMBOSS: 1308 env.warning(L.l("imagefilter(IMG_FILTER_EMBOSS) unimplemented")); 1310 return false; 1311 1312 case IMG_FILTER_GAUSSIAN_BLUR: 1313 env.warning(L.l("imagefilter(IMG_FILTER_GAUSSIAN_BLUR) "+ 1315 "unimplemented")); 1316 return false; 1317 1318 case IMG_FILTER_SELECTIVE_BLUR: 1319 env.warning(L.l("imagefilter(IMG_FILTER_SELECTIVE_BLUR) "+ 1321 "unimplemented")); 1322 return false; 1323 1324 case IMG_FILTER_MEAN_REMOVAL: 1325 env.warning(L.l("imagefilter(IMG_FILTER_MEAN_REMOVAL) "+ 1327 "unimplemented")); 1328 return false; 1329 1330 case IMG_FILTER_SMOOTH: 1331 env.warning(L.l("imagefilter(IMG_FILTER_SMOOTH) unimplemented")); 1333 return false; 1334 1335 default: 1336 throw new QuercusException("unknown filterType in imagefilter()"); 1337 } 1338 } 1339 1340 1341 1343 1346 public static boolean iptcembed(String iptcdata, String jpegFileName, 1347 @Optional int spool) 1348 { 1349 throw new QuercusException("iptcembed is not [yet] supported"); 1350 } 1351 1352 1355 public static boolean imagegammacorrect(QuercusImage image, 1356 float gammaBefore, float gammaAfter) 1357 { 1358 return true; 1361 } 1362 1363 1366 public static boolean imagerotate(QuercusImage image, float angle, 1367 int backgroundColor, 1368 @Optional int ignoreTransparent) 1369 { 1370 return false; 1374 } 1375 1376 1377 1379 1383 public static long imagecolorclosesthwb(QuercusImage image, 1384 int r, int g, int b) 1385 { 1386 throw new QuercusException("imagecolorclosesthwb is not supported"); 1387 } 1388 1389 1392 public static boolean imagelayereffect(QuercusImage image, int effect) 1393 { 1394 return false; 1397 } 1398 1399 1400 1402 1405 public static ArrayValue imageftbbox(float size, float angle, Path fontFile, 1406 String text, 1407 @Optional ArrayValue extraInfo) 1408 { 1409 throw new QuercusException("imageftbbox() not implemented"); 1410 } 1411 1412 1415 public static ArrayValue imagefttext(QuercusImage image, float size, 1416 float angle, int x, int y, int col, 1417 Path fontFile, String text, 1418 ArrayValue extraInfo) 1419 { 1420 throw new QuercusException("imagefttext() not implemented"); 1421 } 1422 1423 1426 public static long imageloadfont(Path file) 1427 { 1428 throw new QuercusException("imageloadfont() not implemented"); 1429 } 1430 1431 1434 public static ArrayValue imagepsbbox(String text, int font, int size, 1435 @Optional int space, 1436 @Optional int tightness, 1437 @Optional float angle) 1438 { 1439 throw new QuercusException("imagepsbbox() not implemented"); 1440 } 1441 1442 1445 public static int imagepscopyfont(Value fontIndex) 1446 { 1447 throw new QuercusException("imagepscopyfont() not implemented"); 1448 } 1449 1450 1453 public static boolean imagepsencodefont(Value fontIndex, Path encodingFile) 1454 { 1455 throw new QuercusException("imagepsencodefont() not implemented"); 1456 } 1457 1458 1461 public static boolean imagepsextendfont(int fontIndex, float extend) 1462 { 1463 throw new QuercusException("imagepsextendfont() not implemented"); 1464 } 1465 1466 1469 public static boolean imagepsfreefont(Value fontIndex) 1470 { 1471 throw new QuercusException("imagepsfreefont() not implemented"); 1472 } 1473 1474 1477 public static Value imagepsloadfont(Path fontFile) 1478 { 1479 throw new QuercusException("imagepsloadfont() not implemented"); 1480 } 1481 1482 1485 public static boolean imagepsslantfont(Value fontIndex, float slant) 1486 { 1487 throw new QuercusException("imagepsslantfont() not implemented"); 1488 } 1489 1490 1493 public static ArrayValue imagepstext(QuercusImage image, 1494 String text, 1495 Value fontIndex, 1496 int size, int fg, int bg, int x, int y, 1497 @Optional int space, 1498 @Optional int tightness, 1499 @Optional float angle, 1500 @Optional int antialias_steps) 1501 { 1502 throw new QuercusException("imagepstext() not implemented"); 1503 } 1504 1505 1506 1508 1511 public static void image2wbmp(QuercusImage image, 1512 @Optional Path filename, 1513 @Optional int threshhold) 1514 { 1515 throw new QuercusException("not supported"); 1516 } 1517 1518 1521 public static void jpeg2wbmp(String jpegFilename, 1522 String wbmpName, 1523 int d_height, 1524 int d_width, 1525 int threshhold) 1526 { 1527 throw new QuercusException("not supported"); 1528 } 1529 1530 1533 public static void png2wbmp(String pngFilename, 1534 String wbmpName, 1535 int d_height, 1536 int d_width, 1537 int threshhold) 1538 { 1539 throw new QuercusException("not supported"); 1540 } 1541 1542 1543 1544 1546 1549 public static void imagecreatefromgd2(Path file) 1550 { 1551 throw new QuercusException(".gd images are not supported"); 1552 } 1553 1554 1557 public static void imagecreatefromgd2part(Path file, 1558 int srcX, int srcY, 1559 int width, int height) 1560 { 1561 throw new QuercusException(".gd images are not supported"); 1562 } 1563 1564 1567 public static void imagecreatefromgd(Path file) 1568 { 1569 throw new QuercusException(".gd images are not supported"); 1570 } 1571 1572 1575 public static void imagegd2(QuercusImage image, @Optional Path file) 1576 { 1577 throw new QuercusException("imagegd2 is not implemented"); 1578 } 1579 1580 1583 public static void imagegd(QuercusImage image, @Optional Path file) 1584 { 1585 throw new QuercusException("imagegd is not implemented"); 1586 } 1587 1588 1589 1591 private static Color intToColor(int argb) 1592 { 1593 int alpha = argb >> 24; 1595 alpha <<= 1; 1596 alpha |= ((alpha & 0x2) >> 1); 1598 return new Color((argb >> 16) & 0xff, 1599 (argb >> 8) & 0xff, 1600 (argb >> 0) & 0xff, 1601 alpha); 1602 } 1603 1604 1606 static class ImageInfo { 1607 int _width; 1608 int _height; 1609 int _type; 1610 1611 int _bits; 1612 1613 String _mime; 1614 } 1615 1616 public static class QuercusImage extends ResourceValue { 1617 private int _width; 1618 private int _height; 1619 BufferedImage _bufferedImage; 1620 private Graphics2D _graphics; 1621 1622 private BufferedImage _brush; 1623 private int[] _style; 1624 private int _thickness; 1625 1626 public QuercusImage(int width, int height) 1627 { 1628 _width = width; 1629 _height = height; 1630 _bufferedImage = 1631 new BufferedImage (width, height, BufferedImage.TYPE_INT_RGB); 1632 _graphics = (Graphics2D)_bufferedImage.getGraphics(); 1633 } 1634 1635 public QuercusImage(InputStream inputStream) 1636 { 1637 try { 1638 _bufferedImage = ImageIO.read(inputStream); 1639 _width = _bufferedImage.getWidth(null); 1640 _height = _bufferedImage.getHeight(null); 1641 _graphics = (Graphics2D)_bufferedImage.getGraphics(); 1642 } 1643 catch (IOException e) { 1644 throw new QuercusException(e); 1645 } 1646 } 1647 1648 public QuercusImage(Env env, Path filename) 1649 { 1650 try { 1651 _bufferedImage = ImageIO.read(filename.openRead()); 1652 _width = _bufferedImage.getWidth(null); 1653 _height = _bufferedImage.getHeight(null); 1654 _graphics = (Graphics2D)_bufferedImage.getGraphics(); 1655 } 1656 catch (IOException e) { 1657 throw new QuercusException(e); 1658 } 1659 } 1660 1661 public String toString() 1662 { 1663 return "resource(Image)"; 1664 } 1665 1666 public int getPixel(int x, int y) 1667 { 1668 return _bufferedImage.getRGB(x, y); 1669 } 1670 1671 public void setPixel(int x, int y, int color) 1672 { 1673 _bufferedImage.setRGB(x, y, color); 1674 } 1675 1676 public Graphics2D getGraphics() 1677 { 1678 return _graphics; 1679 } 1680 1681 public Font getFont(int font) 1682 { 1683 if (font <= 1) 1684 return new Font("sansserif", 0, 8); 1685 switch(font) { 1686 case 2: return new Font("sansserif", 0, 10); 1687 case 3: return new Font("sansserif", 0, 11); 1688 case 4: return new Font("sansserif", 0, 12); 1689 default: 1690 return new Font("sansserif", 0, 14); 1691 } 1692 } 1693 1694 public int getWidth() 1695 { 1696 return _bufferedImage.getWidth(null); 1697 } 1698 1699 public int getHeight() 1700 { 1701 return _bufferedImage.getHeight(null); 1702 } 1703 1704 public void fill(Shape shape, int color) 1705 { 1706 _graphics.setColor(intToColor(color)); 1707 _graphics.fill(shape); 1708 } 1709 1710 public void stroke(Shape shape, int color) 1711 { 1712 switch(color) 1713 { 1714 case IMG_COLOR_STYLED: 1715 strokeStyled(shape); 1716 break; 1717 case IMG_COLOR_BRUSHED: 1718 strokeBrushed(shape); 1719 break; 1720 default: 1721 _graphics.setColor(intToColor(color)); 1722 _graphics.setStroke(new BasicStroke(_thickness)); 1723 _graphics.draw(shape); 1724 break; 1725 } 1726 } 1727 1728 private void strokeStyled(Shape shape) 1729 { 1730 for(int i=0; i<_style.length; i++) 1731 { 1732 _graphics.setColor(intToColor(_style[i])); 1733 Stroke stroke = 1734 new BasicStroke(_thickness, 1735 BasicStroke.JOIN_ROUND, BasicStroke.CAP_ROUND, 1, 1736 new float[] { 1, _style.length-1 }, 1737 i); 1738 _graphics.setStroke(stroke); 1739 _graphics.draw(shape); 1740 } 1741 } 1742 1743 private void strokeBrushed(Shape shape) 1744 { 1745 Graphics2D g = _graphics; 1747 FlatteningPathIterator fpi = 1748 new FlatteningPathIterator (shape.getPathIterator(g.getTransform()), 1); 1749 float[] floats = new float[6]; 1750 fpi.currentSegment(floats); 1751 float last_x = floats[0]; 1752 float last_y = floats[1]; 1753 while(! fpi.isDone()) 1754 { 1755 fpi.currentSegment(floats); 1756 int distance = (int)Math.sqrt((floats[0]-last_x)*(floats[0]-last_x)+ 1757 (floats[1]-last_y)*(floats[1]-last_y)); 1758 if (distance <= 1) distance = 1; 1759 for(int i=1; i<=distance; i++) 1760 { 1761 int x = (int)(floats[0]*i+last_x*(distance-i))/distance; 1762 x -= _brush.getWidth() / 2; 1763 int y = (int)(floats[1]*i+last_y*(distance-i))/distance; 1764 y -= _brush.getHeight() / 2; 1765 g.drawImage(_brush, x, y, null); 1766 } 1767 last_x = floats[0]; 1768 last_y = floats[1]; 1769 fpi.next(); 1770 } 1771 } 1772 1773 public void setThickness(int thickness) 1774 { 1775 _style = null; 1776 _thickness = thickness; 1777 } 1778 1779 public void setStyle(ArrayValue colors) 1780 { 1781 _style = new int[colors.getSize()]; 1782 ArrayValue.Entry e = colors.getHead(); 1783 for(int i=0; i<_style.length; i++) 1784 { 1785 _style[i] = e.getValue().toInt(); 1786 e = e.getNext(); 1787 } 1788 } 1789 1790 public void setBrush(QuercusImage image) 1791 { 1792 _brush = image._bufferedImage; 1793 } 1794 1795 public BufferedImage getBrush() 1796 { 1797 return _brush; 1798 } 1799 1800 public void flood(int x, int y, int color) 1801 { 1802 flood(x, y, color, 0, false); 1803 } 1804 1805 public void flood(int x, int y, int color, int border) 1806 { 1807 flood(x, y, color, border, true); 1808 } 1809 1810 private void flood(int startx, int starty, int color, int border, boolean useBorder) 1811 { 1812 java.util.Queue <Integer > xq = new LinkedList <Integer >(); 1813 java.util.Queue <Integer > yq = new LinkedList <Integer >(); 1814 xq.add(startx); 1815 yq.add(starty); 1816 color &= 0x00ffffff; 1817 border &= 0x00ffffff; 1818 while(xq.size() > 0) 1819 { 1820 int x = xq.poll(); 1821 int y = yq.poll(); 1822 int p = (getPixel(x, y) & 0x00ffffff); 1823 if (useBorder ? (p==border||p==color) : (p != 0)) continue; 1824 setPixel(x, y, color); 1825 for(int i=x-1; i>=0; i--) 1826 { 1827 p = (getPixel(i, y) & 0x00ffffff); 1828 if (useBorder ? (p==border||p==color) : (p!= 0)) break; 1829 setPixel(i, y, color); 1830 xq.add(i); 1831 yq.add(y+1); 1832 xq.add(i); 1833 yq.add(y-1); 1834 } 1835 for(int i=x+1; i<getWidth(); i++) 1836 { 1837 p = (getPixel(i, y) & 0x00ffffff); 1838 if (useBorder ? (p==border||p==color) : (p != 0)) break; 1839 setPixel(i, y, color); 1840 xq.add(i); 1841 yq.add(y+1); 1842 xq.add(i); 1843 yq.add(y-1); 1844 } 1845 xq.add(x); 1846 yq.add(y+1); 1847 xq.add(x); 1848 yq.add(y-1); 1849 } 1850 } 1851 1852 } 1853 1854 1855 1856} 1857 1858 | Popular Tags |