/*
* Copyright (c) 1998-2010 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.quercus.lib;
import com.caucho.quercus.QuercusException;
import com.caucho.quercus.annotation.NotNull;
import com.caucho.quercus.annotation.Optional;
import com.caucho.quercus.annotation.ReturnNullAsFalse;
import com.caucho.quercus.env.*;
import com.caucho.quercus.module.AbstractQuercusModule;
import com.caucho.util.L10N;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.WriteStream;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
/**
* PHP image
*/
public class ImageModule extends AbstractQuercusModule {
private static final Logger log = Logger.getLogger(ImageModule.class.getName());
private static final L10N L = new L10N(ImageModule.class);
public static final String GD_VERSION = "2.0.36";
public static final int GD_MAJOR_VERSION = 2;
public static final int GD_MINOR_VERSION = 0;
public static final int GD_RELEASE_VERSION = 36;
public static final int GD_BUNDLED = 0;
public static final String GD_EXTRA_VERSION = "";
public static final long IMG_GIF = 0x1;
public static final long IMG_JPG = 0x2;
public static final long IMG_JPEG = 0x2;
public static final long IMG_PNG = 0x4;
public static final long IMG_WBMP = 0x8;
public static final long IMG_XPM = 0x10;
public static final int IMAGETYPE_GIF = 1;
public static final int IMAGETYPE_JPG = 2;
public static final int IMAGETYPE_JPEG = 2;
public static final int IMAGETYPE_PNG = 3;
public static final int IMAGETYPE_SWF = 4;
public static final int IMAGETYPE_PSD = 5;
public static final int IMAGETYPE_BMP = 6;
public static final int IMAGETYPE_TIFF_II = 7;
public static final int IMAGETYPE_TIFF_MM = 8;
public static final int IMAGETYPE_JPC = 9;
public static final int IMAGETYPE_JP2 = 10;
public static final int IMAGETYPE_JPX = 11;
public static final int IMAGETYPE_JB2 = 12;
public static final int IMAGETYPE_SWC = 13;
public static final int IMAGETYPE_IFF = 14;
public static final int IMAGETYPE_WBMP = 15;
public static final int IMAGETYPE_XBM = 16;
public static final int IMG_COLOR_STYLED = -2;
public static final int IMG_COLOR_BRUSHED = -3;
private static final int PNG_IHDR = pngCode("IHDR");
public static final int IMG_ARC_PIE = 0;
public static final int IMG_ARC_CHORD = 1;
public static final int IMG_ARC_NOFILL = 2;
public static final int IMG_ARC_EDGED = 4;
public static final int IMG_FILTER_NEGATE = 0;
public static final int IMG_FILTER_GRAYSCALE = 1;
public static final int IMG_FILTER_BRIGHTNESS = 2;
public static final int IMG_FILTER_CONTRAST = 3;
public static final int IMG_FILTER_COLORIZE = 4;
public static final int IMG_FILTER_EDGEDETECT = 5;
public static final int IMG_FILTER_EMBOSS = 6;
public static final int IMG_FILTER_GAUSSIAN_BLUR = 7;
public static final int IMG_FILTER_SELECTIVE_BLUR = 8;
public static final int IMG_FILTER_MEAN_REMOVAL = 9;
public static final int IMG_FILTER_SMOOTH = 10;
@Override
public String[] getLoadedExtensions() {
return new String[]{"gd"};
}
/**
* Retrieve information about the currently installed GD library
*/
public static Value gd_info() {
return (new ArrayValueImpl().append(StringValue.create("GD Version"), // ] => 2.0
StringValue.create("2.0")).append(StringValue.create("FreeType Support"), // ] => 1
BooleanValue.TRUE).append(
StringValue.create("FreeType Linkage"), // ] => with freetype
StringValue.create("with freetype")).append(StringValue.create("T1Lib Support"), // ] => 1
BooleanValue.TRUE).append(StringValue.create("GIF Read Support"), // ] => 1
BooleanValue.TRUE).append(StringValue.create("GIF Create Support"), // ] => 1
BooleanValue.TRUE).append(StringValue.create("JPEG Support"), // ] => 1
BooleanValue.TRUE).append(StringValue.create("PNG Support"), // ] => 1
BooleanValue.TRUE).append(StringValue.create("WBMP Support"), // ] => 1
BooleanValue.TRUE).append(StringValue.create("XPM Support"), // ] =>
BooleanValue.FALSE).append(StringValue.create("XBM Support"), // ] =>
BooleanValue.FALSE).append(StringValue.create("JIS-mapped Japanese Font Support"), // ] =>
BooleanValue.FALSE));
}
/**
* Returns the environment value.
*/
public Value getimagesize(Env env,
Path file,
@Optional ArrayValue imageArray) {
if (!file.canRead()) {
return BooleanValue.FALSE;
}
ImageInfo info = new ImageInfo();
ReadStream is = null;
try {
is = file.openRead();
if (!parseImageSize(is, info)) {
return BooleanValue.FALSE;
}
} catch (Exception e) {
log.log(Level.FINE, e.toString(), e);
return BooleanValue.FALSE;
} finally {
is.close();
}
if (imageArray == null) {
imageArray = new ArrayValueImpl();
}
imageArray.put(LongValue.create(info._width));
imageArray.put(LongValue.create(info._height));
imageArray.put(LongValue.create(info._type));
imageArray.put(env.createString("width=\"" + info._width
+ "\" height=\"" + info._height + "\""));
if (info._bits >= 0) {
imageArray.put(env.createString("bits"), LongValue.create(info._bits));
}
if (info._type == IMAGETYPE_JPEG) {
imageArray.put("channels", 3);
}
if (info._mime != null) {
imageArray.put("mime", info._mime);
}
return imageArray;
}
/**
* Get file extension for image type
*/
public static Value image_type_to_extension(int imageType, boolean dot) {
switch (imageType) {
case IMAGETYPE_GIF:
return StringValue.create(dot ? ".gif" : "gif");
case IMAGETYPE_JPG:
return StringValue.create(dot ? ".jpg" : "jpg");
case IMAGETYPE_PNG:
return StringValue.create(dot ? ".png" : "png");
case IMAGETYPE_SWF:
return StringValue.create(dot ? ".swf" : "swf");
case IMAGETYPE_PSD:
return StringValue.create(dot ? ".psd" : "psd");
case IMAGETYPE_BMP:
return StringValue.create(dot ? ".bmp" : "bmp");
case IMAGETYPE_TIFF_II:
return StringValue.create(dot ? ".tiff" : "tiff");
case IMAGETYPE_TIFF_MM:
return StringValue.create(dot ? ".tiff" : "tiff");
case IMAGETYPE_JPC:
return StringValue.create(dot ? ".jpc" : "jpc");
case IMAGETYPE_JP2:
return StringValue.create(dot ? ".jp2" : "jp2");
case IMAGETYPE_JPX:
return StringValue.create(dot ? ".jpf" : "jpf");
case IMAGETYPE_JB2:
return StringValue.create(dot ? ".jb2" : "jb2");
case IMAGETYPE_SWC:
return StringValue.create(dot ? ".swc" : "swc");
case IMAGETYPE_IFF:
return StringValue.create(dot ? ".iff" : "iff");
case IMAGETYPE_WBMP:
return StringValue.create(dot ? ".wbmp" : "wbmp");
case IMAGETYPE_XBM:
return StringValue.create(dot ? ".xbm" : "xbm");
}
throw new QuercusException("unknown imagetype " + imageType);
}
/**
* Get Mime-Type for image-type returned by getimagesize, exif_read_data,
* exif_thumbnail, exif_imagetype
*/
public static Value image_type_to_mime_type(int imageType) {
switch (imageType) {
case IMAGETYPE_GIF:
return StringValue.create("image/gif");
case IMAGETYPE_JPG:
return StringValue.create("image/jpeg");
case IMAGETYPE_PNG:
return StringValue.create("image/png");
case IMAGETYPE_SWF:
return StringValue.create("application/x-shockwave-flash");
case IMAGETYPE_PSD:
return StringValue.create("image/psd");
case IMAGETYPE_BMP:
return StringValue.create("image/bmp");
case IMAGETYPE_TIFF_II:
return StringValue.create("image/tiff");
case IMAGETYPE_TIFF_MM:
return StringValue.create("image/tiff");
case IMAGETYPE_JPC:
return StringValue.create("application/octet-stream");
case IMAGETYPE_JP2:
return StringValue.create("image/jp2");
case IMAGETYPE_JPX:
return StringValue.create("application/octet-stream");
case IMAGETYPE_JB2:
return StringValue.create("application/octet-stream");
case IMAGETYPE_SWC:
return StringValue.create("application/x-shockwave-flash");
case IMAGETYPE_IFF:
return StringValue.create("image/iff");
case IMAGETYPE_WBMP:
return StringValue.create("image/vnd.wap.wbmp");
case IMAGETYPE_XBM:
return StringValue.create("image/xbm");
}
throw new QuercusException("unknown imageType " + imageType);
}
// TODO: image2wbmp
/**
* Returns a copy of the current transform
*/
public static AffineTransform image_get_transform(QuercusImage image) {
if (image == null) {
return null;
}
return image.getGraphics().getTransform();
}
/**
* Returns a copy of the current transform
*/
public static boolean image_set_transform(QuercusImage image,
AffineTransform transform) {
if (image == null) {
return false;
}
image.getGraphics().setTransform(transform);
return true;
}
/**
* Set the blending mode for an image
*/
public static boolean imagealphablending(QuercusImage image,
boolean useAlphaBlending) {
image.getGraphics().setComposite(useAlphaBlending
? AlphaComposite.SrcOver
: AlphaComposite.Src);
return true;
}
/**
* Should antialias functions be used or not
*/
public static boolean imageantialias(QuercusImage image,
boolean useAntiAliasing) {
image.getGraphics().setRenderingHint(RenderingHints.KEY_ANTIALIASING,
useAntiAliasing
? RenderingHints.VALUE_ANTIALIAS_ON
: RenderingHints.VALUE_ANTIALIAS_OFF);
return true;
}
/**
* Draw a partial ellipse
*/
public static boolean imagearc(QuercusImage image,
double cx, double cy,
double width, double height,
double start, double end,
int color) {
Arc2D arc = new Arc2D.Double(cx - width / 2, cy - height / 2,
width, height, -1 * start, -1 * (end - start),
Arc2D.OPEN);
image.stroke(arc, color);
return true;
}
/**
* Draw a character horizontally
*/
public static boolean imagechar(QuercusImage image, int font,
int x, int y, String c, int color) {
Graphics2D g = image.getGraphics();
g.setColor(intToColor(color));
Font awtfont = image.getFont(font);
int height = image.getGraphics().getFontMetrics(awtfont).getAscent();
g.setFont(awtfont);
g.drawString(c.substring(0, 1), x, y + height);
return true;
}
/**
* Draw a character vertically
*/
public static boolean imagecharup(QuercusImage image, int font,
int x, int y, String c, int color) {
Graphics2D g = (Graphics2D) image.getGraphics().create();
g.rotate(-1 * Math.PI / 2);
g.setColor(intToColor(color));
Font awtfont = image.getFont(font);
int height = image.getGraphics().getFontMetrics(awtfont).getAscent();
g.setFont(awtfont);
g.drawString(c.substring(0, 1), -1 * y, x + height);
return true;
}
/**
* Allocate a color for an image
*/
public static long imagecolorallocate(QuercusImage image,
int r, int g, int b) {
if (image != null) {
return image.allocateColor(r, g, b);
} else {
return 0;
}
}
/**
* Allocate a color for an image
*/
public static long imagecolorallocatealpha(QuercusImage image,
int r, int g, int b, int a) {
// PHP's alpha values are inverted and only 7 bits.
int alpha = 0x7f - (a & 0xff);
return ((alpha << 24)
| ((r & 0xff) << 16)
| ((g & 0xff) << 8)
| ((b & 0xff) << 0));
}
/**
* Get the index of the color of a pixel
*/
public static long imagecolorat(QuercusImage image, int x, int y) {
return image.getPixel(x, y);
}
/**
* Get the index of the closest color to the specified color
*/
public static long imagecolorclosest(QuercusImage image, int r, int g, int b) {
return imagecolorallocate(image, r, g, b);
}
/**
* Get the index of the closest color to the specified color + alpha
*/
public static long imagecolorclosestalpha(QuercusImage image,
int r, int g, int b, int a) {
return imagecolorallocatealpha(image, r, g, b, a);
}
/**
* Get the index of the color which has the hue, white and blackness
* nearest to the given color
*/
public static long imagecolorclosesthwb(QuercusImage image,
int r, int g, int b) {
throw new QuercusException("imagecolorclosesthwb is not supported");
}
/**
* De-allocate a color for an image
*/
public static boolean imagecolordeallocate(QuercusImage image, int rgb) {
// no-op
return true;
}
/**
* Get the index of the specified color
*/
public static long imagecolorexact(QuercusImage image, int r, int g, int b) {
return imagecolorallocate(image, r, g, b);
}
/**
* Get the index of the specified color + alpha
*/
public static long imagecolorexactalpha(QuercusImage image,
int r, int g, int b, int a) {
return imagecolorallocatealpha(image, r, g, b, a);
}
/**
* Makes the colors of the palette version of an image more closely
* match the true color version
*/
public static boolean imagecolormatch(QuercusImage image1,
QuercusImage image2) {
// no-op
return true;
}
/**
* Get the index of the specified color or its closest possible alternative
*/
public static long imagecolorresolve(QuercusImage image, int r, int g, int b) {
return imagecolorallocate(image, r, g, b);
}
/**
* Get the index of the specified color + alpha or its closest possible
* alternative
*/
public static long imagecolorresolvealpha(QuercusImage image,
int r, int g, int b, int a) {
return imagecolorallocatealpha(image, r, g, b, a);
}
/**
* Set the color for the specified palette index
*/
public static boolean imagecolorset(QuercusImage image, int index,
int r, int g, int b) {
// no-op since we currently only support true-color, full-alpha channel
return true;
}
/**
* Get the colors for an index
*/
public static ArrayValue imagecolorsforindex(QuercusImage image, int argb) {
ArrayValue arrayValue = new ArrayValueImpl();
arrayValue.put("red", (argb >> 16) & 0xff);
arrayValue.put("green", (argb >> 8) & 0xff);
arrayValue.put("blue", (argb >> 0) & 0xff);
// PHP's alpha is backwards from the rest of the world...
int alpha = 0x7f - ((argb >> 24) & 0xff);
arrayValue.put("alpha", alpha);
return arrayValue;
}
/**
* Find out the number of colors in an image's palette
*/
public static Value imagecolorstotal() {
return LongValue.create(0);
}
/**
* Define a color as transparent
*/
public static long imagecolortransparent(QuercusImage image,
@Optional int color) {
// form that includes the optional argument is a no-op since we
// currently only support true-color, full-alpha channel
return 0xFF000000;
}
/**
* Apply a 3x3 convolution matrix, using coefficient div and offset
*/
public static boolean imageconvolution(QuercusImage image, ArrayValue matrix,
double div, double offset) {
// TODO: implement div and offset
float[] kernelValues = new float[9];
for (int y = 0; y < 3; y++) {
for (int x = 0; x < 3; x++) {
kernelValues[x + y * 3] =
(float) matrix.get(LongValue.create(y)).get(LongValue.create(x)).toDouble();
}
}
ConvolveOp convolveOp = new ConvolveOp(new Kernel(3, 3, kernelValues),
ConvolveOp.EDGE_NO_OP,
null);
BufferedImage bufferedImage =
convolveOp.filter(image._bufferedImage, null);
image._bufferedImage.getGraphics().drawImage(bufferedImage, 1, 0, null);
return true;
}
/**
* Copy part of an image
*/
public static boolean imagecopy(QuercusImage dest, QuercusImage src,
int dx, int dy, int sx, int sy, int w, int h) {
dest.getGraphics().drawImage(src._bufferedImage,
dx, dy, dx + w, dy + h,
sx, sy, sx + w, sy + h, null);
return true;
}
/**
* Copy and merge part of an image
*/
public static boolean imagecopymerge(QuercusImage dest, QuercusImage src,
int dx, int dy, int sx, int sy,
int w, int h, int pct) {
BufferedImage rgba =
new BufferedImage(dest.getWidth(), dest.getHeight(),
BufferedImage.TYPE_INT_ARGB);
rgba.getGraphics().drawImage(src._bufferedImage, 0, 0, null);
BufferedImageOp rescaleOp =
new RescaleOp(new float[]{1, 1, 1, ((float) pct) / 100},
new float[]{0, 0, 0, 0},
null);
BufferedImage rescaledImage =
rescaleOp.filter(rgba, null);
Graphics2D g = (Graphics2D) dest.getGraphics().create();
g.setComposite(AlphaComposite.SrcOver);
g.drawImage(rescaledImage,
dx, dy, dx + w, dy + h,
sx, sy, sx + w, sy + h, null);
return true;
}
/**
* Copy and merge part of an image with gray scale
*/
public static boolean imagecopymergegray(QuercusImage dest, QuercusImage src,
int dx, int dy, int sx, int sy,
int w, int h, int pct) {
BufferedImage rgba =
new BufferedImage(dest.getWidth(), dest.getHeight(),
BufferedImage.TYPE_INT_ARGB);
rgba.getGraphics().drawImage(src._bufferedImage, 0, 0, null);
BufferedImageOp rescaleOp =
new RescaleOp(new float[]{1, 1, 1, ((float) pct) / 100},
new float[]{0, 0, 0, 0},
null);
BufferedImage rescaledImage =
rescaleOp.filter(rgba, null);
ColorConvertOp colorConvertOp =
new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
colorConvertOp.filter(dest._bufferedImage, dest._bufferedImage);
Graphics2D g = (Graphics2D) dest.getGraphics().create();
g.setComposite(AlphaComposite.SrcOver);
g.drawImage(rescaledImage,
dx, dy, dx + w, dy + h,
sx, sy, sx + w, sy + h, null);
return true;
}
/**
* Copy and resize part of an image with resampling
*/
public static boolean imagecopyresampled(QuercusImage dest, QuercusImage src,
int dx, int dy, int sx, int sy,
int dw, int dh, int sw, int sh) {
Graphics2D g = (Graphics2D) dest.getGraphics().create();
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.drawImage(src._bufferedImage,
dx, dy, dx + dw, dy + dh,
sx, sy, sx + sw, sy + sh, null);
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_DEFAULT);
return true;
}
/**
* Copy and resize part of an image
*/
public static boolean imagecopyresized(QuercusImage dest, QuercusImage src,
int dx, int dy, int sx, int sy,
int dw, int dh, int sw, int sh) {
Graphics2D g = (Graphics2D) dest.getGraphics().create();
g.drawImage(src._bufferedImage,
dx, dy, dx + dw, dy + dh,
sx, sy, sx + sw, sy + sh, null);
return true;
}
/**
* Create a new palette based image
*/
public static Value imagecreate(int width, int height) {
QuercusImage image = new QuercusImage(width, height);
image.setToFill(true);
return image;
}
/**
* Create a new image from GD2 file or URL
*/
public static void imagecreatefromgd2(Path file) {
throw new QuercusException(".gd images are not supported");
}
/**
* Create a new image from a given part of GD2 file or URL
*/
public static void imagecreatefromgd2part(Path file,
int srcX, int srcY,
int width, int height) {
throw new QuercusException(".gd images are not supported");
}
/**
* Create a new image from GD file or URL
*/
public static void imagecreatefromgd(Path file) {
throw new QuercusException(".gd images are not supported");
}
/**
* Create a new image from file or URL
*/
public static QuercusImage imagecreatefromgif(Env env, Path filename) {
return new QuercusImage(env, filename);
}
/**
* Create a new image from file or URL
*/
@ReturnNullAsFalse
public static QuercusImage imagecreatefromjpeg(Env env, Path filename) {
try {
return new QuercusImage(env, filename);
} catch (Exception e) {
env.warning(L.l("Can't open {0} as a jpeg image.\n{1}",
filename, e));
log.log(Level.FINE, e.toString(), e);
return null;
}
}
/**
* Create a new image from file or URL
*/
public static QuercusImage imagecreatefrompng(Env env, Path filename) {
return new QuercusImage(env, filename);
}
/**
* Create a new image from the image stream in the string
*/
public static QuercusImage imagecreatefromstring(Env env, InputStream data) {
if (data == null) {
return null;
}
return new QuercusImage(data);
}
/**
* Create a new image from file or URL
*/
public static QuercusImage imagecreatefromwbmp(Env env, Path filename) {
return new QuercusImage(env, filename);
}
/**
* Create a new image from file or URL
*/
public static Value imagecreatefromxbm(Env env, Path filename) {
return new QuercusImage(env, filename);
}
/**
* Create a new image from file or URL
*/
public static QuercusImage imagecreatefromxpm(Env env, Path filename) {
return new QuercusImage(env, filename);
}
/**
* Create a new true color image
*/
public static Value imagecreatetruecolor(int width, int height) {
return new QuercusImage(width, height);
}
/**
* Draw a dashed line
*/
public static boolean imagedashedline(QuercusImage image,
int x1, int y1, int x2, int y2,
int color) {
Graphics2D g = image.getGraphics();
Stroke stroke = g.getStroke();
g.setColor(intToColor(color));
g.setStroke(new BasicStroke(1, BasicStroke.JOIN_ROUND,
BasicStroke.CAP_ROUND, 1,
new float[]{5, 5}, 0));
g.draw(new Line2D.Float(x1, y1, x2, y2));
g.setStroke(stroke);
return true;
}
/**
* Destroy an image
*/
public static boolean imagedestroy(QuercusImage image) {
// no-op
return true;
}
/**
* Draw an ellipse
*/
public static boolean imageellipse(QuercusImage image,
double cx, double cy,
double width, double height,
int color) {
Shape shape = new Ellipse2D.Double(
cx - width / 2, cy - height / 2, width, height);
image.stroke(shape, color);
return true;
}
/**
* Flood fill
*/
public static boolean imagefill(QuercusImage image, int x, int y, int color) {
image.flood(x, y, color);
return true;
}
/**
* Draw a partial ellipse and fill it
*/
public static boolean imagefilledarc(QuercusImage image,
double cx, double cy,
double width, double height,
double start, double end,
int color,
int style) {
int type = Arc2D.PIE;
if ((style & IMG_ARC_CHORD) != 0) {
type = Arc2D.CHORD;
}
Arc2D arc =
new Arc2D.Double(cx - width / 2, cy - height / 2,
width, height, -1 * start,
-1 * (end - start), type);
if ((style & IMG_ARC_NOFILL) == 0) {
image.fill(arc, color);
}
if ((style & IMG_ARC_EDGED) != 0) {
image.stroke(arc, color);
}
return true;
}
/**
* Draw a filled ellipse
*/
public static boolean imagefilledellipse(QuercusImage image,
double cx, double cy,
double width, double height,
int color) {
Ellipse2D ellipse =
new Ellipse2D.Double(cx - width / 2, cy - height / 2, width, height);
image.fill(ellipse, color);
return true;
}
/**
* Draw a filled polygon
*/
public static boolean imagefilledpolygon(Env env,
QuercusImage image,
ArrayValue points,
int numPoints, int color) {
image.fill(arrayToPolygon(env, points, numPoints), color);
return true;
}
/**
* Draw a filled rectangle
*/
public static boolean imagefilledrectangle(QuercusImage image, int x1, int y1,
int x2, int y2, int color) {
image.fill(new Rectangle2D.Float(x1, y1, x2 - x1 + 1, y2 - y1 + 1), color);
return true;
}
/**
* Flood fill to specific color
*/
public static boolean imagefilltoborder(QuercusImage image, int x, int y,
int border, int color) {
image.flood(x, y, color, border);
return true;
}
// Filters /////////////////////////////////////////////////////////
/**
* Applies a filter to an image
*/
public static boolean imagefilter(Env env, QuercusImage image, int filterType,
@Optional int arg1, @Optional int arg2,
@Optional int arg3) {
switch (filterType) {
case IMG_FILTER_NEGATE:
// Reverses all colors of the image.
env.warning(L.l("imagefilter(IMG_FILTER_NEGATE) unimplemented"));
return false;
case IMG_FILTER_GRAYSCALE:
// Converts the image into grayscale.
env.warning(L.l("imagefilter(IMG_FILTER_GRAYSCALE) unimplemented"));
return false;
case IMG_FILTER_BRIGHTNESS:
// Changes brightness of the image. Arg1 sets level of brightness.
env.warning(L.l("imagefilter(IMG_FILTER_BRIGHTNESS) unimplementetd"));
return false;
case IMG_FILTER_CONTRAST:
// Changes contrast of the image. Use arg1 to set level of contrast.
env.warning(L.l("imagefilter(IMG_FILTER_CONTRAST) unimplementetd"));
return false;
case IMG_FILTER_COLORIZE:
// Like IMG_FILTER_GRAYSCALE, except you can specify the color. Use
// arg1, arg2 and arg3 in the form of red, blue, green. The range
// for each color is 0 to 255.
env.warning(L.l("imagefilter(IMG_FILTER_COLORIZE) unimplemented"));
return false;
case IMG_FILTER_EDGEDETECT:
// Uses edge detection to highlight the edges in the image.
env.warning(L.l("imagefilter(IMG_FILTER_EDGEDETECT) unimplemented"));
return false;
case IMG_FILTER_EMBOSS:
// Embosses the image.
env.warning(L.l("imagefilter(IMG_FILTER_EMBOSS) unimplemented"));
return false;
case IMG_FILTER_GAUSSIAN_BLUR:
// Blurs the image using the Gaussian method.
env.warning(L.l("imagefilter(IMG_FILTER_GAUSSIAN_BLUR) "
+ "unimplemented"));
return false;
case IMG_FILTER_SELECTIVE_BLUR:
// Blurs the image.
env.warning(L.l("imagefilter(IMG_FILTER_SELECTIVE_BLUR) "
+ "unimplemented"));
return false;
case IMG_FILTER_MEAN_REMOVAL:
// Uses mean removal to achieve a "sketchy" effect.
env.warning(L.l("imagefilter(IMG_FILTER_MEAN_REMOVAL) "
+ "unimplemented"));
return false;
case IMG_FILTER_SMOOTH:
// Makes the image smoother. Use arg1 to set the level of smoothness.
env.warning(L.l("imagefilter(IMG_FILTER_SMOOTH) unimplemented"));
return false;
default:
throw new QuercusException("unknown filterType in imagefilter()");
}
}
/**
* Get font height.
*
* @param font a font previously loaded with {@link #imageloadfont},
* or 1 -5 for built-in fonts
*/
public static int imagefontheight(int font) {
if (font < 1) {
return 8;
} else if (font == 1) {
return 8;
} else if (font == 2) {
return 13;
} else if (font == 3) {
return 13;
} else if (font == 4) {
return 16;
} else if (font == 5) {
return 15;
} else {
return 15;
}
}
/**
* Get font width.
*
* @param font a font previously loaded with {@link #imageloadfont},
* or 1 -5 for built-in fonts
*/
public static int imagefontwidth(int font) {
if (font < 1) {
return 5;
} else if (font == 1) {
return 5;
} else if (font == 2) {
return 6;
} else if (font == 3) {
return 7;
} else if (font == 4) {
return 8;
} else if (font == 5) {
return 9;
} else {
return 9;
}
}
/**
* draws a true type font image
*/
public static Value imageftbbox(Env env,
double size,
double angle,
StringValue fontFile,
String text,
@Optional ArrayValue extra) {
try {
QuercusImage image = new QuercusImage(100, 100);
Graphics2D g = image.getGraphics();
Font font = image.getTrueTypeFont(env, fontFile);
if (font == null) {
font = image.getFont(1);
}
font = font.deriveFont((float) (size * 96.0 / 72.0));
Font oldFont = g.getFont();
g.setFont(font);
Rectangle2D rect = font.getStringBounds(text, g.getFontRenderContext());
int descent = g.getFontMetrics(font).getDescent();
g.setFont(oldFont);
double x1 = rect.getX();
double y1 = 0;
double x2 = rect.getX() + rect.getWidth();
double y2 = rect.getY() + descent - 1;
ArrayValue bbox = new ArrayValueImpl();
bbox.put(LongValue.create(Math.round(x1)));
bbox.put(LongValue.create(Math.round(y1)));
bbox.put(LongValue.create(Math.round(x2)));
bbox.put(LongValue.create(Math.round(y1)));
bbox.put(LongValue.create(Math.round(x2)));
bbox.put(LongValue.create(Math.round(y2)));
bbox.put(LongValue.create(Math.round(x1)));
bbox.put(LongValue.create(Math.round(y2)));
return bbox;
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
return NullValue.NULL;
}
}
/**
* draws a true type font image
*/
public static Value imagefttext(Env env,
@NotNull QuercusImage image,
double size,
double angle,
int x,
int y,
int color,
StringValue fontFile,
String text,
@Optional ArrayValue extra) {
try {
Graphics2D g = image.getGraphics();
g.setColor(intToColor(color));
Font font = image.getTrueTypeFont(env, fontFile);
if (font == null) {
font = image.getFont(1);
}
double height = size * 96.0 / 72.0;
font = font.deriveFont((float) height);
g.setFont(font);
Object oldAntiAlias = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
AffineTransform oldTransform = g.getTransform();
if (angle != 0) {
g.translate(x, y);
g.rotate(-Math.toRadians(angle));
g.drawString(text, 0, 0);
} else {
g.drawString(text, x, y);
}
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAntiAlias);
g.setTransform(oldTransform);
// TODO: incorrect
ArrayValue value = new ArrayValueImpl();
value = value.append(LongValue.create(x));
value = value.append(LongValue.create(y));
value = value.append(DoubleValue.create(x + text.length() * height));
value = value.append(LongValue.create(y));
value = value.append(DoubleValue.create(x + text.length() * height));
value = value.append(DoubleValue.create(y + height));
value = value.append(DoubleValue.create(x));
value = value.append(DoubleValue.create(y + height));
return value;
} catch (Exception e) {
log.log(Level.WARNING, e.toString(), e);
return NullValue.NULL;
}
}
/**
* Apply a gamma correction to a GD image
*/
public static boolean imagegammacorrect(QuercusImage image,
float gammaBefore, float gammaAfter) {
// this is a no-op in PHP; apparently the GD library dropped
// support for gamma correction between v1.8 and v2.0
return true;
}
/**
* Output GD2 image to browser or file
*/
public static void imagegd2(QuercusImage image, @Optional Path file) {
throw new QuercusException("imagegd2 is not implemented");
}
/**
* Output GD image to browser or file
*/
public static void imagegd(QuercusImage image, @Optional Path file) {
throw new QuercusException("imagegd is not implemented");
}
/**
* Output image to browser or file
*/
public static boolean imagegif(Env env, QuercusImage image,
@Optional Path path) {
try {
if (path != null) {
WriteStream os = path.openWrite();
try {
ImageIO.write(image._bufferedImage, "gif", os);
} finally {
os.close();
}
} else {
ImageIO.write(image._bufferedImage, "gif", env.getOut());
}
return true;
} catch (IOException e) {
log.log(Level.FINE, e.toString(), e);
return false;
}
}
// TODO: imagegrabscreen
// TODO: imagegrabwindow
/**
* Enable or disable interlace
*/
public static boolean imageinterlace(QuercusImage image,
@Optional Boolean enable) {
if (enable != null) {
image.setInterlace(enable);
}
// no-op, can safely ignore (just makes images that load top-down)
return true;
}
/**
* Finds whether an image is a truecolor image
*/
public static boolean imageistruecolor(QuercusImage image) {
return true;
}
/**
* Output image to browser or file
*/
public static boolean imagejpeg(Env env,
QuercusImage image,
@Optional Path path,
@Optional int quality) {
try {
if (path != null) {
WriteStream os = path.openWrite();
try {
ImageIO.write(image._bufferedImage, "jpeg", os);
} finally {
os.close();
}
} else {
ImageIO.write(image._bufferedImage, "jpeg", env.getOut());
}
return true;
} catch (IOException e) {
log.log(Level.FINE, e.toString(), e);
return false;
}
}
/**
* Set the alpha blending flag to use the bundled libgd layering effects
*/
public static boolean imagelayereffect(QuercusImage image, int effect) {
// TODO: there is no documentation for how this function ought to work
// http://us3.php.net/manual/en/function.imagelayereffect.php
return false;
}
/**
* Draw a line
*/
public static boolean imageline(QuercusImage image,
int x1, int y1, int x2, int y2, int color) {
image.stroke(new Line2D.Float(x1, y1, x2, y2), color);
return true;
}
/**
* Load a new font
*/
public static long imageloadfont(Path file) {
throw new QuercusException("imageloadfont() not implemented");
}
/**
* Copy the palette from one image to another
*/
public static boolean imagepalettecopy(QuercusImage source,
QuercusImage dest) {
return true;
}
/**
* Output a PNG image to either the browser or a file
*/
public static boolean imagepng(Env env,
QuercusImage image,
@Optional Path path) {
try {
if (path != null) {
WriteStream os = path.openWrite();
try {
ImageIO.write(image._bufferedImage, "png", os);
} finally {
os.close();
}
} else {
ImageIO.write(image._bufferedImage, "png", env.getOut());
}
return true;
} catch (IOException e) {
log.log(Level.FINE, e.toString(), e);
return false;
}
}
/**
* Draw a polygon
*/
public static boolean imagepolygon(Env env,
QuercusImage image,
ArrayValue points,
int numPoints, int color) {
image.stroke(arrayToPolygon(env, points, numPoints), color);
return true;
}
/**
* Give the bounding box of a text rectangle using PostScript Type1 fonts
*/
public static ArrayValue imagepsbbox(String text, int font, int size,
@Optional int space,
@Optional int tightness,
@Optional float angle) {
throw new QuercusException("imagepsbbox() not implemented");
}
/**
* Make a copy of an already loaded font for further modification
*/
public static int imagepscopyfont(Value fontIndex) {
throw new QuercusException("imagepscopyfont() not implemented");
}
/**
* Change the character encoding vector of a font
*/
public static boolean imagepsencodefont(Value fontIndex, Path encodingFile) {
throw new QuercusException("imagepsencodefont() not implemented");
}
/**
* Extend or condense a font
*/
public static boolean imagepsextendfont(int fontIndex, float extend) {
throw new QuercusException("imagepsextendfont() not implemented");
}
/**
* Free memory used by a PostScript Type 1 font
*/
public static boolean imagepsfreefont(Value fontIndex) {
throw new QuercusException("imagepsfreefont() not implemented");
}
/**
* Load a PostScript Type 1 font from file
*/
public static Value imagepsloadfont(Path fontFile) {
throw new QuercusException("imagepsloadfont() not implemented");
}
/**
* Slant a font
*/
public static boolean imagepsslantfont(Value fontIndex, float slant) {
throw new QuercusException("imagepsslantfont() not implemented");
}
/**
* To draw a text string over an image using PostScript Type1 fonts
*/
public static ArrayValue imagepstext(QuercusImage image,
String text,
Value fontIndex,
int size, int fg, int bg, int x, int y,
@Optional int space,
@Optional int tightness,
@Optional float angle,
@Optional int antialias_steps) {
throw new QuercusException("imagepstext() not implemented");
}
/**
* Draw a rectangle
*/
public static boolean imagerectangle(QuercusImage image, int x1, int y1,
int x2, int y2, int color) {
if (x2 < x1) {
int tmp = x1;
x1 = x2;
x2 = tmp;
}
if (y2 < y1) {
int tmp = y1;
y1 = y2;
y2 = tmp;
}
image.stroke(new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1), color);
return true;
}
/**
* Rotate an image with a given angle
*/
public static boolean imagerotate(QuercusImage image, float angle,
int backgroundColor,
@Optional int ignoreTransparent) {
// this function is broken on most PHP installs: "Note: This
// function is only available if PHP is compiled with the bundled
// version of the GD library."
return false;
}
/**
* Set the flag to save full alpha channel information (as opposed to
* single-color transparency) when saving PNG images
*/
public static boolean imagesavealpha(QuercusImage image, boolean set) {
// no-op since we currently only support true-color, full-alpha channel
return true;
}
/**
* Set the brush image for line drawing
*/
public static boolean imagesetbrush(QuercusImage image, QuercusImage brush) {
image.setBrush(brush);
return true;
}
/**
* Set a single pixel
*/
public static boolean imagesetpixel(QuercusImage image,
int x, int y, int color) {
image.setPixel(x, y, color);
return true;
}
/**
* Set the style for line drawing
*/
public static boolean imagesetstyle(Env env,
QuercusImage image,
ArrayValue style) {
image.setStyle(env, style);
return true;
}
/**
* Set the thickness for line
*/
public static boolean imagesetthickness(QuercusImage image, int thickness) {
image.setThickness(thickness);
return true;
}
// TODO: imagesettile
/**
* Draw a string horizontally
*/
public static boolean imagestring(QuercusImage image, int font,
int x, int y, String s, int color) {
Graphics2D g = image.getGraphics();
g.setColor(intToColor(color));
Font awtfont = image.getFont(font);
int height = image.getGraphics().getFontMetrics(awtfont).getAscent();
g.setFont(awtfont);
g.drawString(s, x, y + height);
return true;
}
/**
* Draw a string vertically
*/
public static boolean imagestringup(QuercusImage image, int font,
int x, int y, String s, int color) {
Graphics2D g = image.getGraphics();
AffineTransform oldTransform = g.getTransform();
g.translate(x, y);
// g.rotate(-1 * Math.PI / 2);
g.rotate(-1 * Math.PI / 2);
g.setColor(intToColor(color));
Font awtfont = image.getFont(font);
int height = image.getGraphics().getFontMetrics(awtfont).getAscent();
g.setFont(awtfont);
g.drawString(s, 0, 0 + height);
g.setTransform(oldTransform);
return true;
}
/**
* Returns the width of the image.
*/
public static int imagesx(@NotNull QuercusImage image) {
if (image == null) {
return 0;
}
return image.getWidth();
}
/**
* Returns the height of the image.
*/
public static int imagesy(@NotNull QuercusImage image) {
if (image == null) {
return 0;
}
return image.getHeight();
}
/**
* general affine transformation
*/
public static boolean image_transform(QuercusImage image,
double m00, double m10,
double m01, double m11,
double m02, double m12) {
if (image == null) {
return false;
}
AffineTransform transform = new AffineTransform(m00, m10, m01, m11, m02, m12);
image.getGraphics().transform(transform);
return true;
}
/**
* scaling transformation
*/
public static boolean image_transform_scale(QuercusImage image,
double sx, double sy) {
if (image == null) {
return false;
}
image.getGraphics().scale(sx, sy);
return true;
}
/**
* shearing transformation
*/
public static boolean image_transform_shear(QuercusImage image,
double shx, double shy) {
if (image == null) {
return false;
}
image.getGraphics().shear(shx, shy);
return true;
}
/**
* translation transformation
*/
public static boolean image_transform_translate(QuercusImage image,
double x, double y) {
if (image == null) {
return false;
}
image.getGraphics().translate(x, y);
return true;
}
/**
* draws a true type font image
*/
public static Value imagettfbbox(Env env,
double size,
double angle,
StringValue fontFile,
String text) {
return imageftbbox(env, size, angle, fontFile, text, null);
}
/**
* draws a true type font image
*/
public static Value imagettftext(Env env,
@NotNull QuercusImage image,
double size,
double angle,
int x,
int y,
int color,
StringValue fontFile,
String text) {
return imagefttext(env, image, size, angle, x, y,
color, fontFile, text, null);
}
/**
* Returns the imagetypes.
*/
public static long imagetypes() {
return IMG_GIF | IMG_JPG | IMG_PNG;
}
/**
* Output image to browser or file
*/
public static void imagewbmp(QuercusImage image,
@Optional Path filename,
@Optional int threshhold) {
throw new QuercusException("not supported");
}
// TODO: imagexbm
/**
* Embe into single tags.
*/
public static boolean iptcembed(String iptcdata, String jpegFileName,
@Optional int spool) {
throw new QuercusException("iptcembed is not [yet] supported");
}
/**
* Convert JPEG image file to WBMP image file
*/
public static void jpeg2wbmp(String jpegFilename,
String wbmpName,
int d_height,
int d_width,
int threshhold) {
throw new QuercusException("not supported");
}
/**
* Convert PNG image file to WBM
*/
public static void png2wbmp(String pngFilename,
String wbmpName,
int d_height,
int d_width,
int threshhold) {
throw new QuercusException("not supported");
}
// Private Helpers ////////////////////////////////////////////////////////
private static Polygon arrayToPolygon(Env env,
ArrayValue points,
int numPoints) {
Polygon polygon = new Polygon();
Iterator<Value> iter = points.getValueIterator(env);
for (int i = 0; i < numPoints; i++) {
int x = iter.next().toInt();
int y = iter.next().toInt();
polygon.addPoint(x, y);
}
return polygon;
}
private static Color intToColor(int argb) {
// don't forget: PHP alpha channel is only 7 bits
int alpha = argb >> 24;
alpha <<= 1;
alpha |= ((alpha & 0x2) >> 1); // copy bit #2 to LSB
return new Color((argb >> 16) & 0xff,
(argb >> 8) & 0xff,
(argb >> 0) & 0xff,
alpha);
}
/**
* Parses the image size from the file.
*/
private static boolean parseImageSize(ReadStream is, ImageInfo info)
throws IOException {
int ch;
ch = is.read();
if (ch == 137) {
// PNG - http://www.libpng.org/pub/png/spec/iso/index-object.html
if (is.read() != 'P'
|| is.read() != 'N'
|| is.read() != 'G'
|| is.read() != '\r'
|| is.read() != '\n'
|| is.read() != 26
|| is.read() != '\n') {
return false;
}
return parsePNGImageSize(is, info);
} else if (ch == 'G') {
// GIF
if (is.read() != 'I'
|| is.read() != 'F'
|| is.read() != '8'
|| ((ch = is.read()) != '7' && ch != '9')
|| is.read() != 'a') {
return false;
}
return parseGIFImageSize(is, info);
} else if (ch == 0xff) {
// JPEG
if (is.read() != 0xd8) {
return false;
}
return parseJPEGImageSize(is, info);
} else {
return false;
}
}
/**
* Parses the image size from the PNG file.
*/
private static boolean parsePNGImageSize(ReadStream is, ImageInfo info)
throws IOException {
int length;
while ((length = readInt(is)) > 0) {
int type = readInt(is);
if (type == PNG_IHDR) {
int width = readInt(is);
int height = readInt(is);
int depth = is.read() & 0xff;
int color = is.read() & 0xff;
int compression = is.read() & 0xff;
int filter = is.read() & 0xff;
int interlace = is.read() & 0xff;
info._width = width;
info._height = height;
info._type = IMAGETYPE_PNG;
info._bits = depth;
info._mime = "image/png";
return true;
} else {
for (int i = 0; i < length; i++) {
if (is.read() < 0) {
return false;
}
}
}
int crc = readInt(is);
}
return false;
}
/**
* Parses the image size from the PNG file.
*/
private static boolean parseGIFImageSize(ReadStream is, ImageInfo info)
throws IOException {
int length;
int width = (is.read() & 0xff) + 256 * (is.read() & 0xff);
int height = (is.read() & 0xff) + 256 * (is.read() & 0xff);
int flags = is.read() & 0xff;
info._width = width;
info._height = height;
info._type = IMAGETYPE_GIF;
info._bits = flags & 0x7;
info._mime = "image/gif";
return true;
}
/**
* Parses the image size from the PNG file.
*/
private static boolean parseJPEGImageSize(ReadStream is, ImageInfo info)
throws IOException {
int ch;
while ((ch = is.read()) == 0xff) {
ch = is.read();
if (ch == 0xff) {
is.unread();
} else if (0xd0 <= ch && ch <= 0xd9) {
// rst
} else if (0x01 == ch) {
// rst
} else if (ch == 0xc0) {
int len = 256 * is.read() + is.read();
int bits = is.read();
int height = 256 * is.read() + is.read();
int width = 256 * is.read() + is.read();
info._width = width;
info._height = height;
info._type = IMAGETYPE_JPEG;
info._bits = bits;
info._mime = "image/jpeg";
return true;
} else {
int len = 256 * is.read() + is.read();
is.skip(len - 2);
}
}
return false;
}
private static int pngCode(String code) {
return ((code.charAt(0) << 24)
| (code.charAt(1) << 16)
| (code.charAt(2) << 8)
| (code.charAt(3)));
}
private static int readInt(ReadStream is)
throws IOException {
return (((is.read() & 0xff) << 24)
| ((is.read() & 0xff) << 16)
| ((is.read() & 0xff) << 8)
| ((is.read() & 0xff)));
}
// Inner Classes ////////////////////////////////////////////////////////
static class ImageInfo {
int _width;
int _height;
int _type;
int _bits;
String _mime;
}
public static class QuercusImage extends ResourceValue {
private HashMap<StringValue, Font> _fontMap = new HashMap<StringValue, Font>();
private Font[] _fontArray = new Font[6];
private int _width;
private int _height;
BufferedImage _bufferedImage;
private Graphics2D _graphics;
private boolean _isInterlace;
private BufferedImage _brush;
private int[] _style;
private int _thickness;
private boolean _isToFill = false;
public QuercusImage(int width, int height) {
_width = width;
_height = height;
_bufferedImage = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
_graphics = (Graphics2D) _bufferedImage.getGraphics();
}
public QuercusImage(InputStream inputStream) {
try {
_bufferedImage = ImageIO.read(inputStream);
_width = _bufferedImage.getWidth(null);
_height = _bufferedImage.getHeight(null);
_graphics = (Graphics2D) _bufferedImage.getGraphics();
} catch (IOException e) {
throw new QuercusException(e);
}
}
public QuercusImage(Env env, Path filename) {
try {
_bufferedImage = ImageIO.read(filename.openRead());
_width = _bufferedImage.getWidth(null);
_height = _bufferedImage.getHeight(null);
_graphics = (Graphics2D) _bufferedImage.getGraphics();
} catch (IOException e) {
throw new QuercusException(e);
}
}
@Override
public String toString() {
return "resource(Image)";
}
public void setInterlace(boolean isInterlace) {
_isInterlace = isInterlace;
}
public boolean isInterlace() {
return _isInterlace;
}
public int getPixel(int x, int y) {
return _bufferedImage.getRGB(x, y);
}
public void setPixel(int x, int y, int color) {
_bufferedImage.setRGB(x, y, color);
}
public Graphics2D getGraphics() {
return _graphics;
}
public Font getFont(int fontIndex) {
if (fontIndex < 0) {
fontIndex = 0;
} else if (fontIndex > 5) {
fontIndex = 5;
}
Font font = _fontArray[fontIndex];
if (font == null) {
switch (fontIndex) {
case 0:
case 1:
font = new Font("sansserif", 0, 8);
break;
case 2:
font = new Font("sansserif", 0, 10);
break;
case 3:
font = new Font("sansserif", 0, 11);
break;
case 4:
font = new Font("sansserif", 0, 12);
break;
default:
font = new Font("sansserif", 0, 14);
break;
}
_fontArray[fontIndex] = font;
}
return font;
}
public Font getTrueTypeFont(Env env, StringValue fontPath)
throws FontFormatException,
IOException {
Font font = _fontMap.get(fontPath);
if (font != null) {
return font;
}
Path path = env.lookupPwd(fontPath);
if (path.canRead()) {
ReadStream is = path.openRead();
try {
font = Font.createFont(Font.TRUETYPE_FONT, is);
} finally {
is.close();
}
_fontMap.put(fontPath, font);
return font;
}
if (fontPath.length() > 0 && fontPath.charAt(0) == '/') {
return null;
}
StringValue gdFontPathKey = env.createString("GDFONTPATH");
StringValue gdFontPath = OptionsModule.getenv(env, gdFontPathKey).toStringValue();
int start = 0;
int len = gdFontPath.length();
while (start < len) {
int i = gdFontPath.indexOf(':', start);
if (i >= 0 && i + 1 < len && gdFontPath.charAt(i + 1) == ';') {
StringValue item = gdFontPath.substring(start, i);
path = env.lookupPwd(item);
start = i + 2;
} else {
StringValue item = gdFontPath.substring(start);
path = env.lookupPwd(item);
start = len;
}
if (path.canRead()) {
ReadStream is = path.openRead();
try {
font = Font.createFont(Font.TRUETYPE_FONT, is);
} finally {
is.close();
}
_fontMap.put(fontPath, font);
return font;
}
}
return null;
}
public int getWidth() {
return _bufferedImage.getWidth(null);
}
public int getHeight() {
return _bufferedImage.getHeight(null);
}
public void fill(Shape shape, int color) {
_graphics.setColor(intToColor(color));
_graphics.fill(shape);
}
public void stroke(Shape shape, int color) {
switch (color) {
case IMG_COLOR_STYLED:
strokeStyled(shape);
break;
case IMG_COLOR_BRUSHED:
strokeBrushed(shape);
break;
default:
_graphics.setColor(intToColor(color));
_graphics.setStroke(new BasicStroke(_thickness));
_graphics.draw(shape);
break;
}
}
private void strokeStyled(Shape shape) {
for (int i = 0; i < _style.length; i++) {
_graphics.setColor(intToColor(_style[i]));
Stroke stroke =
new BasicStroke(_thickness,
BasicStroke.JOIN_ROUND, BasicStroke.CAP_ROUND, 1,
new float[]{1, _style.length - 1},
i);
_graphics.setStroke(stroke);
_graphics.draw(shape);
}
}
private void strokeBrushed(Shape shape) {
// TODO: support "styled brushes" (see imagesetstyle() example on php.net)
Graphics2D g = _graphics;
FlatteningPathIterator fpi =
new FlatteningPathIterator(shape.getPathIterator(g.getTransform()), 1);
float[] floats = new float[6];
fpi.currentSegment(floats);
float last_x = floats[0];
float last_y = floats[1];
while (!fpi.isDone()) {
fpi.currentSegment(floats);
int distance = (int) Math.sqrt(
(floats[0] - last_x) * (floats[0] - last_x)
+ (floats[1] - last_y) * (floats[1] - last_y));
if (distance <= 1) {
distance = 1;
}
for (int i = 1; i <= distance; i++) {
int x = (int) (floats[0] * i + last_x * (distance - i)) / distance;
x -= _brush.getWidth() / 2;
int y = (int) (floats[1] * i + last_y * (distance - i)) / distance;
y -= _brush.getHeight() / 2;
g.drawImage(_brush, x, y, null);
}
last_x = floats[0];
last_y = floats[1];
fpi.next();
}
}
public void setThickness(int thickness) {
_style = null;
_thickness = thickness;
}
public void setStyle(Env env, ArrayValue colors) {
_style = new int[colors.getSize()];
Iterator<Value> iter = colors.getValueIterator(env);
for (int i = 0; i < _style.length; i++) {
_style[i] = iter.next().toInt();
}
}
public void setBrush(QuercusImage image) {
_brush = image._bufferedImage;
}
public BufferedImage getBrush() {
return _brush;
}
public void setToFill(boolean isToFill) {
_isToFill = isToFill;
}
public long allocateColor(int r, int g, int b) {
int color = ((0x7f << 24)
| ((r & 0xff) << 16)
| ((g & 0xff) << 8)
| ((b & 0xff) << 0));
if (_isToFill) {
_isToFill = false;
flood(0, 0, color);
}
return color;
}
public void flood(int x, int y, int color) {
flood(x, y, color, 0, false);
}
public void flood(int x, int y, int color, int border) {
flood(x, y, color, border, true);
}
private void flood(
int startx, int starty, int color, int border, boolean useBorder) {
java.util.Queue<Integer> xq = new LinkedList<Integer>();
java.util.Queue<Integer> yq = new LinkedList<Integer>();
xq.add(startx);
yq.add(starty);
color &= 0x00ffffff;
border &= 0x00ffffff;
int height = getHeight();
while (xq.size() > 0) {
int x = xq.poll();
int y = yq.poll();
int p = (getPixel(x, y) & 0x00ffffff);
if (useBorder ? (p == border || p == color) : (p != 0)) {
continue;
}
setPixel(x, y, color);
for (int i = x - 1; i >= 0; i--) {
p = (getPixel(i, y) & 0x00ffffff);
if (useBorder ? (p == border || p == color) : (p != 0)) {
break;
}
setPixel(i, y, color);
if (y + 1 < height) {
xq.add(i);
yq.add(y + 1);
}
if (y - 1 >= 0) {
xq.add(i);
yq.add(y - 1);
}
}
for (int i = x + 1; i < getWidth(); i++) {
p = (getPixel(i, y) & 0x00ffffff);
if (useBorder ? (p == border || p == color) : (p != 0)) {
break;
}
setPixel(i, y, color);
if (y + 1 < height) {
xq.add(i);
yq.add(y + 1);
}
if (y - 1 >= 0) {
xq.add(i);
yq.add(y - 1);
}
}
if (y + 1 < height) {
xq.add(x);
yq.add(y + 1);
}
if (y - 1 >= 0) {
xq.add(x);
yq.add(y - 1);
}
}
}
}
}