/* * 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 29.12.2004. */ package com.scriptographer.adm; import java.awt.Container; import java.awt.MediaTracker; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.awt.image.ImageProducer; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import com.scriptographer.ai.Raster; import com.scriptographer.ui.NativeObject; /** * @author lehni */ public class Image extends NativeObject { // an image can wrap its representation as an icon as well... private int iconHandle = 0; private int width; private int height; private ImageType type; // these variables are set from nativeCreate private int byteWidth; private int bitsPerPixel; private Drawer drawer; public Image(int width, int height, ImageType type) { this.width = width; this.height = height; this.type = type != null ? type : ImageType.RGB; handle = nativeCreate(width, height, type.value); } public Object clone() { Image copy = new Image(width, height, type); copy.nativeSetPixels(handle, byteWidth * height); return copy; } public static Image getImage(Object obj) throws IOException { if (obj instanceof Image) return (Image) obj; else if (obj instanceof String) return new Image((String) obj); else if (obj instanceof File) return new Image((File) obj); else if (obj instanceof URL) return new Image((URL) obj); else return null; } public Image(java.awt.Image image) { BufferedImage buf; if (image instanceof BufferedImage) { buf = (BufferedImage) image; width = buf.getWidth(); height = buf.getHeight(); int imgType = buf.getType(); switch(imgType) { // direct types: case BufferedImage.TYPE_INT_RGB: type = ImageType.RGB; break; case BufferedImage.TYPE_INT_ARGB: case BufferedImage.TYPE_INT_ARGB_PRE: type = ImageType.ARGB; break; // indirect types, copying is needed: case BufferedImage.TYPE_4BYTE_ABGR: case BufferedImage.TYPE_4BYTE_ABGR_PRE: case BufferedImage.TYPE_BYTE_INDEXED: { BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); tmp.createGraphics().drawImage(buf, null, 0, 0); image = tmp; type = ImageType.ARGB; } break; default: { BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); tmp.createGraphics().drawImage(buf, null, 0, 0); buf = tmp; type = ImageType.ARGB; } } } else { width = image.getWidth(null); height = image.getHeight(null); buf = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); buf.getGraphics().drawImage(image, 0, 0, null); type = ImageType.ARGB; } handle = nativeCreate(width, height, type.value); DataBufferInt buffer = (DataBufferInt) buf.getRaster().getDataBuffer(); int data[] = buffer.getData(); nativeSetPixels(data, width, height, byteWidth); } public Image(Raster raster) { // TODO: handle this case directly, without converting back and from // a java BufferedImage, through native code in Raster (the oposite // of the Raster(ui.Image image); this(raster.getImage()); } // TODO: ImageIO (or sun graphics) on OS X have a bug with the headless mode. // some images cannot be used and throw a HeadlessException when used, others // work just fine. The workaround is to rely on the toolkit function. // The resolution is to use helma's image object and generator mechanism here // too: public Image(File file) throws IOException { // this(checkImage(ImageIO.read(file), file)); this(checkImage(waitForImage(Toolkit.getDefaultToolkit().createImage( file.getPath())), file)); } public Image(ImageProducer producer) throws IOException { this(checkImage(waitForImage(Toolkit.getDefaultToolkit().createImage( producer)), producer)); } public Image(URL url) throws IOException { // this(checkImage(ImageIO.read(url), url)); this(checkImage(waitForImage(Toolkit.getDefaultToolkit().createImage( url)), url)); } public Image(String str) throws IOException { this(getURL(str)); } public Image(InputStream in) throws IOException { // this(checkImage(ImageIO.read(in), in)); this(checkImage(waitForImage(Toolkit.getDefaultToolkit().createImage( getBytes(in))), in)); } private static java.awt.Image checkImage(java.awt.Image image, Object srcObj) throws IOException { if (image == null) throw new IOException("The specified image could not be read: " + srcObj); return image; } public static java.awt.Image waitForImage(java.awt.Image image) { MediaTracker mediaTracker = new MediaTracker(new Container()); mediaTracker.addImage(image, 0); try { mediaTracker.waitForID(0); if (image.getWidth(null) == -1 || image.getHeight(null) == -1) image = null; } catch (InterruptedException e) { image = null; } return image; } private static byte[] getBytes(InputStream in) throws IOException { if (in == null) throw new IOException("InputStream is null"); byte bytes[] = new byte[in.available()]; in.read(bytes); return bytes; } private static URL getURL(String str) throws IOException { // the string could either be an url or a local filename, let's try // both: URL url = null; try { url = new URL(str); } catch (MalformedURLException e) { // try the local file now: // url = new URL("file://" + str); url = new File(str).toURI().toURL(); } return url; } public int getCompatibleType() { switch(type) { case ARGB: case ASCREEN: return BufferedImage.TYPE_INT_ARGB; default: return BufferedImage.TYPE_INT_RGB; } } /** * fetches the pixels from the image and creates a BufferedImage from it */ public BufferedImage getImage() { BufferedImage img = new BufferedImage(width, height, getCompatibleType()); DataBufferInt buffer = (DataBufferInt) img.getRaster().getDataBuffer(); int data[] = buffer.getData(); nativeGetPixels(data, width, height, byteWidth); return img; } public void setImage(BufferedImage image) { int imgType = getCompatibleType(); if (image.getType() != imgType || image.getWidth() != width || image.getHeight() != height) { BufferedImage tmp = new BufferedImage(width, height, imgType); tmp.createGraphics().drawImage(image, 0, 0, null); image = tmp; } DataBufferInt buffer = (DataBufferInt) image.getRaster().getDataBuffer(); int data[] = buffer.getData(); nativeSetPixels(data, width, height, byteWidth); } public int getWidth() { return width; } public int getHeight() { return height; } public Size getSize() { return new Size(width, height); } public int getByteWidth() { return byteWidth; } public int getBitsPerPixel() { return bitsPerPixel; } public int getIconHandle() { if (iconHandle == 0) iconHandle = nativeCreateIcon(); return iconHandle; } /* * This is pretty stupid: ADM seems to take care of destruction of attached * pictures itself (for list entries and dialog items). Therefore one * image cannot be attached to more than on item or entry, otherwise it * would be deleted several times. so in order to create an image to attach * it somewhere, a new instance of the image needs to be created everytime, * and an icon needs to be created as well. We don't have to take care of * the disposal, as the item or entry seems to do so. * * TODO: workaround: handle images in the DrawProc, adjust entryTextRects * accordingly and don't use the native setImage methods at all... some more * work with the Tracker would be needed in order to emulate rollover * behavior. But it may be worth geting around the memory consumption of * this dirty hack here... */ public int createIconHandle() { Image img = ((Image) clone()); int handle = img.nativeCreateIcon(); // clear the handle so that nothing happens in finalize or destroy! img.handle = 0; img.iconHandle = 0; return handle; } public Drawer getDrawer() { if (drawer == null) drawer = new Drawer(nativeBeginDrawer(), this, false); return drawer; } public void dispose() { if (handle != 0) { if (drawer != null) { drawer.dispose(); drawer = null; } nativeDestroy(handle, iconHandle); handle = 0; iconHandle = 0; } } public void finalize() { dispose(); } /** * called by Drawer.dispose */ protected void endDrawer() { if (drawer != null) { nativeEndDrawer(); drawer = null; } } private native int nativeCreate(int width, int height, int type); private native void nativeDestroy(int handle, int iconHandle); private native void nativeSetPixels(int[] data, int width, int height, int byteWidth); private native void nativeGetPixels(int[] data, int width, int height, int byteWidth); private native void nativeSetPixels(int handle, int numBytes); private native int nativeCreateIcon(); private native int nativeBeginDrawer(); private native void nativeEndDrawer(); }