/* * Scriptographer * * This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator * http://scriptographer.org/ * * Copyright (c) 2002-2010, Juerg Lehni * http://scratchdisk.com/ * * All rights reserved. See LICENSE file for details. * * File created on 08.12.2004. */ package com.scriptographer.ai; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Shape; import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.geom.GeneralPath; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.IndexColorModel; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import java.net.URL; import com.scratchdisk.util.IntegerEnumUtils; import com.scratchdisk.util.NetUtils; /** * The Raster item represents an image in an Illustrator document. * * @author lehni */ public class Raster extends Item { // native pointer to an attached data struct: private int data = 0; protected Raster(int handle, int docHandle, boolean created) { super(handle, docHandle, created); } private native int nativeConvert(int type, int width, int height); /** * Creates a raster object * * @param width * @param height * @param type */ public Raster(ColorType type, int width, int height) { super(TYPE_RASTER); nativeConvert(type != null ? type.value : -1, width, height); } /** * An empty Raster image of the given color type * @param type */ public Raster(ColorType type) { this(type, -1, -1); } /** * Creates a raster item from an AWT image. * @param image the AWT image to be converted to a raster item */ public Raster(Image image) { this(getCompatibleType(image), image.getWidth(null), image.getHeight(null)); drawImage(image, 0, 0); } /** * Creates a raster item from an UI image. * @param image the UI image to be converted to a raster item */ public Raster(com.scriptographer.adm.Image image) { // TODO: handle this case directly, without converting back and from // a java BufferedImage, through native code? this(image.getImage()); } /** * Creates an empty raster item. */ public Raster() { this(null, -1, -1); } /** * Creates a raster item from a local image file. * * Sample code: * <code> * var file = new java.io.File('/folder/image.jpg'); * var raster = new Raster(file);</code> * * @param file the image file to be loaded */ public Raster(File file) { this(file, false); } /** * Creates a raster image from a URL. * This blocks until the image is loaded or an error occured. * * Sample code: * <code> * var url = new java.net.URL('http://www.server.com/image.jpg'); * var raster = new Raster(url);</code> * * @param url the URL of the image to load * @throws IOException */ public Raster(URL url) throws IOException { // this(NetUtils.loadImage(url)); // Immediatelly delete the downloaded file afterwards this(NetUtils.loadFile(url, "sg_"), true); } native private static int nativeCreate(File file); private Raster(File file, boolean deleteFile) { super(nativeCreate(file)); if (deleteFile) file.delete(); } public native Matrix getMatrix(); public native void setMatrix(Matrix matrix); /** * The size of the raster in pixels. */ public native Size getSize(); /** * @jshide */ public void setSize(int width, int height) { // changing the size creates a new art handle internally handle = nativeConvert((short) -1, width, height); } public void setSize(com.scriptographer.adm.Size size) { setSize(size.width, size.height); } /** * The width of the raster in pixels. */ public int getWidth() { return (int) getSize().width; } /** * The height of the raster in pixels. */ public int getHeight() { return (int) getSize().height; } /** * Pixels per inch of the raster at it's current size. */ public Size getPpi() { Matrix matrix = getMatrix(); Point orig = new Point(0, 0).transform(matrix); Point u = new Point(1, 0).transform(matrix).subtract(orig); Point v = new Point(0, 1).transform(matrix).subtract(orig); return new Size( 72.0 / u.getLength(), 72.0 / v.getLength() ); } private native int nativeGetType(); /** * The color type of the raster. */ public ColorType getType() { return IntegerEnumUtils.get(ColorType.class, nativeGetType()); } public void setType(ColorType type) { // changing the type creates a new art handle internally handle = nativeConvert(type.value, -1, -1); } /** * The Java2D color model of the raster. * @jshide */ public ColorModel getColorModel() { ColorType type = getType(); ColorModel cm = null; switch (type) { case RGB: case ARGB: cm = new ComponentColorModel(RGBColor.getColorSpace(), type.alpha ? new int[] { 8, 8, 8, 8 } : new int [] { 8, 8, 8 }, type.alpha, false, type.alpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE, DataBuffer.TYPE_BYTE ); break; case CMYK: case ACMYK: cm = new ComponentColorModel(CMYKColor.getColorSpace(), type.alpha ? new int[] { 8, 8, 8, 8, 8 } : new int [] { 8, 8, 8, 8 }, type.alpha, false, type.alpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE, DataBuffer.TYPE_BYTE ); break; case GRAY: case AGRAY: cm = new ComponentColorModel(GrayColor.getColorSpace(), type.alpha ? new int[] { 8, 8 } : new int [] { 8 }, type.alpha, false, type.alpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE, DataBuffer.TYPE_BYTE ); break; case BITMAP: case ABITMAP: // create an IndexColorModel with two colors, black and white: // black is the transparent color in case of an alpha image cm = new IndexColorModel(2, 2, new byte[] { 0, (byte) 255 }, new byte[] { 0, (byte) 255 }, new byte[] { 0, (byte) 255 }, type.alpha ? 0 : -1 ); break; } return cm; } /** * @jshide */ public static ColorType getCompatibleType(Image image) { if (image instanceof BufferedImage) { ColorModel cm = ((BufferedImage) image).getColorModel(); int type = cm.getColorSpace().getType(); boolean alpha = cm.hasAlpha(); if (type == ColorSpace.TYPE_RGB) { return alpha ? ColorType.ARGB : ColorType.RGB; } else if (type == ColorSpace.TYPE_CMYK) { return alpha ? ColorType.ACMYK : ColorType.CMYK; } if (type == ColorSpace.TYPE_GRAY) { return alpha ? ColorType.AGRAY : ColorType.GRAY; } } return null; } /** * @jshide */ public BufferedImage createCompatibleImage(int width, int height) { ColorModel cm = getColorModel(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); return new BufferedImage(cm, raster, false, null); } public BufferedImage getSubImage(int x, int y, int width, int height) { if (width == -1 || height == -1) { Size size = getSize(); if (width == -1) width = (int) size.width; if (height == -1) height = (int) size.height; } BufferedImage img = createCompatibleImage(width, height); Graphics2D g2d = img.createGraphics(); g2d.setColor(java.awt.Color.WHITE); // g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0)); g2d.fillRect(0, 0, width, height); g2d.dispose(); WritableRaster raster = img.getRaster(); byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); nativeGetPixels(data, raster.getNumBands(), x, y, width, height); return img; } public BufferedImage getImage() { return getSubImage(0, 0, -1, -1); } public void drawImage(Image image, int x, int y) { BufferedImage buf; // if image is already a compatible BufferedImage, use it. Otherwise create // a new one: if (image instanceof BufferedImage && getColorModel().isCompatibleSampleModel( ((BufferedImage) image).getSampleModel())) { buf = (BufferedImage) image; } else { buf = createCompatibleImage(image.getWidth(null), image.getHeight(null)); buf.createGraphics().drawImage(image, x, y, null); } WritableRaster raster = buf.getRaster(); byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData(); nativeSetPixels(data, raster.getNumBands(), x, y, buf.getWidth(), buf.getHeight()); } public void setImage(Image image) { setSize(image.getWidth(null), image.getHeight(null)); drawImage(image, 0, 0); } /** * Traces the raster. */ public Tracing trace() { return new Tracing(this); } private Matrix inverse; private int inverseVersion = -1; private Matrix getInverseMatrix() { // Cache the inverse matrix as it might be used often for rastering images if (needsUpdate(inverseVersion)) { inverse = this.getMatrix().invert(); inverseVersion = version; } return inverse; } /** * @jshide */ public Color getAverageColor(Shape shape) { // Rectangle2D rect = shape.getBounds2D(); GeneralPath path; int width = getWidth(); int height = getHeight(); int startX = 0; int startY = 0; if (shape != null) { Matrix inverse = getInverseMatrix(); if (inverse == null) return null; // Create a transformed path. This is faster than // path.clone() / path.transform(at); PathIterator pi = shape.getPathIterator(inverse.toAffineTransform()); path = new GeneralPath(); path.setWindingRule(pi.getWindingRule()); path.append(pi, false); Rectangle2D bounds = path.getBounds2D(); // Fetch the sub image to iterate over and calculate average colors // from // Crop to the maximum size. Rectangle2D.intersect(bounds, new Rectangle2D.Double(startX, startY, width, height), bounds); width = (int) Math.ceil(bounds.getWidth()); height = (int) Math.ceil(bounds.getHeight()); // Are we completely outside the raster? If so, return null if (width <= 0 || height <= 0) return null; startX = (int) Math.floor(bounds.getX()); startY = (int) Math.floor(bounds.getY()); } else { path = null; } BufferedImage img = getSubImage(startX, startY, width, height); // Raster check = new Raster(img); // check.setPosition(rect.getCenterX(), rect.getCenterY()); WritableRaster raster = img.getRaster(); byte[] data = (byte[]) raster.getDataElements(0, 0, null); float components[] = new float[data.length]; for (int i = 0; i < data.length; i++) components[i] = data[i] & 0xff; long total = 1; for (int y = 0; y < height; y++) { for (int x = (y == 0) ? 1 : 0; x < width; x++) { if (path == null || path.contains(x + startX, y + startY)) { data = (byte[]) raster.getDataElements(x, height - 1 - y, data); for (int i = 0; i < data.length; i++) components[i] += data[i] & 0xff; total++; } } } total *= 255; for (int i = 0; i < components.length; i++) components[i] = components[i] / total; // Return colors if (components.length == 4) return new CMYKColor(components); else if (components.length == 3) return new RGBColor(components); else return new GrayColor(components); } /** * The average color of the raster. */ public Color getAverageColor() { return getAverageColor((Shape) null); } /** * {@grouptitle Average Color} * Calculates the average color of the image within the given path. This can * be used for creating raster image effects. * * @param path * @return the average color contained in the area covered by the specified * path. */ public Color getAverageColor(PathItem path) { return getAverageColor(path.toShape()); } /** * Calculates the average color of the image within the given point in the * document. This can be used for creating raster image effects. * * @param point * @return the average color contained in the area described by the specified * point. */ public Color getAverageColor(Point point) { return getAverageColor(new Rectangle(point.subtract(0.5, 0.5), new Size(1, 1))); } /** * Calculates the average color of the image within the given rectangle. * This can be used for creating raster image effects. * * @param rect * @return the average color contained in the area described by the specified * rectangle. */ public Color getAverageColor(Rectangle rect) { return getAverageColor(rect.toRectangle2D()); } /** * {@grouptitle Pixels} * * Gets the color of a pixel in the raster. * @param x * @param y */ public native Color getPixel(int x, int y); /** * Sets the color of a pixel in the raster. * Sample code: * <code> * // Creates an RGB raster of 1px*1px * var raster = new Raster(Color.TYPE_RGB,1,1); * * // Changes the color of the first pixel to red * var redColor = new RGBColor(1,0,0); * raster.setPixel(0,0,redColor)</code> * * @param x * @param y */ public native void setPixel(int x, int y, Color color); /** * Gets the color of a pixel in the raster. * @param x * @param y */ public Color getPixel(Point point) { return getPixel((int) point.x, (int) point.y); } /** * Sets the color of a pixel in the raster. * * Sample code: * <code> * // Creates an RGB raster of 1px*1px * var raster = new Raster(Color.TYPE_RGB,1,1); * * // Changes the color of the first pixel to red * var redColor = new RGBColor(1,0,0); * var point = new Point(0,0); * raster.setPixel(point,redColor)</code> * * @param x * @param y */ public void setPixel(Point point, Color color) { setPixel((int) point.x, (int) point.y, color); } private native void nativeSetPixels(byte[] data, int numComponents, int x, int y, int width, int height); private native void nativeGetPixels(byte[] data, int numComponents, int x, int y, int width, int height); native protected void finalize(); }