package nodebox.graphics; import javax.imageio.ImageIO; import javax.management.RuntimeErrorException; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.*; import static nodebox.graphics.MathUtils.clamp; public class Image extends AbstractGrob { private double x, y; private double desiredWidth, desiredHeight; private double alpha = 1; private BufferedImage image; private static BufferedImage blankImage = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY); public static final String BLANK_IMAGE = "__blank"; public Image() { this(new File(BLANK_IMAGE)); } public Image(File file) { if (file == null || file.getPath().equals(BLANK_IMAGE)) { image = blankImage; } else { try { image = ImageIO.read(file); } catch (IOException e) { throw new RuntimeErrorException(null, "Could not read image " + file); } } } public Image(String fname) { this(new File(fname)); } public Image(String fname, double cx, double cy) { this(new File(fname)); this.x = cx; this.y = cy; } public Image(BufferedImage image) { this.image = image; } public Image(Image other) { super(other); this.x = other.x; this.y = other.y; this.desiredWidth = other.desiredWidth; this.desiredHeight = other.desiredHeight; this.alpha = other.alpha; this.image = other.image; } public static Image fromData(byte[] data) { InputStream istream = new BufferedInputStream(new ByteArrayInputStream(data)); try { return new Image(ImageIO.read(istream)); } catch (IOException e) { throw new RuntimeErrorException(null, "Could not read image data."); } } //// Attribute access //// public double getOriginalWidth() { if (image == null) return 0; return image.getWidth(); } public double getOriginalHeight() { if (image == null) return 0; return image.getHeight(); } public double getWidth() { return getOriginalWidth() * getScaleFactor(); } public void setWidth(double width) { this.desiredWidth = width; } public double getHeight() { return getOriginalHeight() * getScaleFactor(); } public void setHeight(double height) { this.desiredHeight = height; } public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } public double getAlpha() { return alpha; } public void setAlpha(double alpha) { this.alpha = alpha; } public BufferedImage getAwtImage() { return image; } public Size getSize() { return new Size(image.getWidth(), image.getHeight()); } //// Transformations //// protected void setupTransform(Graphics2D g) { saveTransform(g); AffineTransform trans = g.getTransform(); trans.concatenate(getTransform().getAffineTransform()); g.setTransform(trans); } //// Grob support //// public boolean isEmpty() { return image == null || image.getWidth() == 0 || image.getHeight() == 0; } public Rect getBounds() { if (image == null) return new Rect(); double factor = getScaleFactor(); double finalWidth = image.getWidth() * factor; double finalHeight = image.getHeight() * factor; return new Rect(x - finalWidth / 2, y - finalHeight / 2, finalWidth, finalHeight); } public double getScaleFactor() { if (desiredWidth != 0 || desiredHeight != 0) { double srcW = image.getWidth(); double srcH = image.getHeight(); if (desiredWidth != 0 && desiredHeight != 0) { // Both width and height were given, constrain to smallest return Math.min(desiredWidth / srcW, desiredHeight / srcH); } else if (desiredWidth != 0) { return desiredWidth / srcW; } else { return desiredHeight / srcH; } } else { return 1; } } public void draw(Graphics2D g) { setupTransform(g); // You can only position an image using an affine transformation. // We use the transformation to translate the image to the specified // position, and scale it according to the given width and height. Transform imageTrans = new Transform(); // Move to the image position. Convert x, y, which are centered coordinates, // to "real" coordinates. double factor = getScaleFactor(); double finalWidth = image.getWidth() * factor; double finalHeight = image.getHeight() * factor; imageTrans.translate(x - finalWidth / 2, y - finalHeight / 2); // Scaling only applies to image that have their desired width and/or height set. // However, getScaleFactor return 1 if height/width are not set, in effect negating // the effect of the scale. imageTrans.scale(getScaleFactor()); double a = clamp(alpha); Composite composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) a); Composite oldComposite = g.getComposite(); g.setComposite(composite); g.drawRenderedImage(image, imageTrans.getAffineTransform()); g.setComposite(oldComposite); restoreTransform(g); } public Image clone() { return new Image(this); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Image)) return false; Image other = (Image) obj; return this.x == other.x && this.y == other.y && this.desiredWidth == other.desiredWidth && this.desiredHeight == other.desiredHeight && this.alpha == other.alpha && this.image.equals(other.image) && super.equals(other); } @Override public String toString() { return "<Image (" + getWidth() + ", " + getHeight() + ")>"; } }