/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library 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. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.awt.image; import java.awt.Graphics; import java.awt.Image; import java.awt.image.ColorModel; import java.awt.image.ImageConsumer; import java.awt.image.ImageObserver; import java.awt.image.ImageProducer; import java.awt.image.IndexColorModel; import java.util.Hashtable; import java.util.LinkedList; import org.jnode.awt.util.AwtUtils; /** * @author epr */ public class JNodeImage extends Image { int width; int height; int availableInfo; Hashtable<String, Object> properties = new Hashtable<String, Object>(); Object pixels; ColorModel colorModel; private boolean productionStarted; private ImageProducer initProducer; /** * Create an empty, transparent image * * @param width * @param height */ public JNodeImage(int width, int height) { this(width, height, ColorModel.getRGBdefault()); } /** * Create an empty, transparent image * * @param width * @param height * @param colorModel */ public JNodeImage(int width, int height, ColorModel colorModel) { this.width = width; this.height = height; this.availableInfo = ImageObserver.ALLBITS | ImageObserver.HEIGHT | ImageObserver.WIDTH | ImageObserver.ALLBITS; this.colorModel = colorModel; } /** * Create a new instance * * @param producer */ public JNodeImage(ImageProducer producer) { this.initProducer = producer; this.colorModel = ColorModel.getRGBdefault(); } /** * @see java.awt.Image#flush() */ public void flush() { // TODO Auto-generated method stub } /** * @return The graphics * @see java.awt.Image#getGraphics() */ public Graphics getGraphics() { return null; } /** * @param observer * @return the height * @see java.awt.Image#getHeight(java.awt.image.ImageObserver) */ public int getHeight(ImageObserver observer) { if ((availableInfo & ImageObserver.HEIGHT) == 0) { prepare(observer); } return height; } /** * @param name * @param observer * @return the property * @see java.awt.Image#getProperty(java.lang.String, java.awt.image.ImageObserver) */ public Object getProperty(String name, ImageObserver observer) { if ((availableInfo & ImageObserver.PROPERTIES) == 0) { prepare(observer); } return properties.get(name); } /** * @return the source * @see java.awt.Image#getSource() */ public ImageProducer getSource() { return new ForwardingProducer(null); } /** * @param observer * @return the width * @see java.awt.Image#getWidth(java.awt.image.ImageObserver) */ public int getWidth(ImageObserver observer) { if ((availableInfo & ImageObserver.WIDTH) == 0) { prepare(observer); } return width; } public int checkImage() { return availableInfo; } /** * Synchronizes the image loading. * * @since PJA2.0 */ public void sync() { loadInitImage(true, null); } public boolean prepare(ImageObserver observer) { if (!productionStarted) { loadInitImage(false, observer); } else if ((availableInfo & ImageObserver.ERROR) != 0) { if (observer != null) { observer.imageUpdate(this, availableInfo, -1, -1, -1, -1); } } else { new ForwardingProducer(observer).startProduction(null); } return (availableInfo & ImageObserver.ALLBITS) != 0; } protected int getWidth() { return width; } protected int getHeight() { return height; } /** * Gets the array used to store the pixels of this image. * * @return an array of <code>int</code> or <code>null</code> if the array * contains <code>byte</code> or if the image was flushed. */ protected int[] getPixels() { Object pixelsArray = getPixelsArray(); return pixelsArray instanceof int[] ? (int[]) pixelsArray : null; } /** * Gets the array used to store the pixels of this image. * * @return an array of <code>int</code> or <code>byte</code>. * @since PJA2.3 */ protected Object getPixelsArray() { if (pixels == null && width >= 0 && height >= 0 && (availableInfo & ImageObserver.ERROR) == 0) { // v2.3 : Added support for 8 bit color images if (colorModel == null || !(colorModel instanceof IndexColorModel)) // Should take into account the size required to store each pixel with model.getPixelSize () // but for the moment only default RGB model is used pixels = new int[width * height]; else pixels = new byte[width * height]; } return pixels; } /** * Gets the color at the point <code>(x,y)</code>. * * @param x the point coordinates. * @param y * @return the color of the point in default RGB model. * @since PJA2.0 */ protected int getPixelColor(int x, int y) { if ((availableInfo & ImageObserver.ERROR) != 0 || pixels == null || x < 0 || x >= width || y < 0 || y >= height) return 0; // v2.3 : Added support for 8 bit color images Object pixelsArray = getPixelsArray(); if (pixelsArray instanceof int[]) return ((int[]) pixelsArray)[x + y * width]; else if (pixelsArray instanceof byte[]) return colorModel.getRGB(((byte[]) pixelsArray)[x + y * width] & 0xFF); else return 0; } /** * Sets the color at the point <code>(x,y)</code>. * * @param x the point coordinates. * @param y * @param ARGB the color of the point in default RGB model. * @since PJA2.0 */ protected void setPixelColor(int x, int y, int ARGB) { if (x >= 0 && x < width && y >= 0 && y < height) { // v2.3 : Added support for 8 bit color images // Get the buffer with getPixelsArray () to ensure that pixels array is available Object pixelsArray = getPixelsArray(); if (pixelsArray instanceof int[]) { ((int[]) pixelsArray)[x + y * width] = ARGB; } else if (pixelsArray instanceof byte[]) { ((byte[]) pixelsArray)[x + y * width] = (byte) AwtUtils.getClosestColorIndex((IndexColorModel) colorModel, ARGB); } } } private synchronized void loadInitImage(boolean wait, final ImageObserver observer) { if (!productionStarted) { final ImageProducer producer = initProducer; // Loads asynchronously initial image if not yet done new Thread() { public void run() { producer.startProduction(new JNodeConsumer(producer, observer)); } } .start(); productionStarted = true; // v1.2 : Moved wait () out of if (!productionStarted) block // because loadInitImage () can called first with parameter wait == false (by prepareImage ()) // and then can be called again with parameter wait == true. In that case, // current thread must be stopped to complete image download. } try { // If image isn't downloaded yet if ((availableInfo & ImageObserver.ERROR) == 0 && (availableInfo & ImageObserver.ALLBITS) == 0) { // Wait the producer notifies us when image is ready if (wait) { wait(); } } } catch (InterruptedException e) { availableInfo = ImageObserver.ABORT | ImageObserver.ERROR; } } /** * ImageProducer implementation. * This producer is used to forward data to ImageConsumer instances and * to inform ImageObserver instances. */ private class ForwardingProducer implements ImageProducer { /** * All consumers */ private final LinkedList<ImageConsumer> consumers = new LinkedList<ImageConsumer>(); /** * The observer, can be null */ private final ImageObserver observer; public ForwardingProducer(ImageObserver observer) { this.observer = observer; } public synchronized void addConsumer(final ImageConsumer ic) { if (ic != null && isConsumer(ic)) { return; } if ((availableInfo & ImageObserver.ERROR) == 0) { // v1.1 : forgot to check ic if (ic != null) { consumers.add(ic); } // Complete image production before drawing in it sync(); } synchronized (JNodeImage.this) { if ((availableInfo & ImageObserver.ERROR) != 0) { if (ic != null) { ic.imageComplete(ImageConsumer.IMAGEERROR); } if (observer != null) { observer.imageUpdate(JNodeImage.this, availableInfo, -1, -1, -1, -1); } } else { if (ic != null) { ic.setDimensions(width, height); ic.setHints( ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME | ImageConsumer.TOPDOWNLEFTRIGHT); ic.setProperties(properties); if (colorModel != null) { ic.setColorModel(colorModel); } if (pixels instanceof int[]) { ic.setPixels(0, 0, width, height, colorModel, (int[]) pixels, 0, width); } else { ic.setPixels(0, 0, width, height, colorModel, (byte[]) pixels, 0, width); } ic.imageComplete(ImageConsumer.STATICIMAGEDONE); } if (observer != null) { observer.imageUpdate(JNodeImage.this, availableInfo, 0, 0, width, height); } } } } public synchronized void removeConsumer(ImageConsumer ic) { consumers.remove(ic); } public synchronized boolean isConsumer(ImageConsumer ic) { return consumers.contains(ic); } public void startProduction(ImageConsumer ic) { addConsumer(ic); } public void requestTopDownLeftRightResend(ImageConsumer ic) { // Useless, already sent in that order } } /** * ImageConsumer implementation */ private class JNodeConsumer implements ImageConsumer { private final ImageProducer producer; private final ImageObserver observer; public JNodeConsumer(ImageProducer producer, ImageObserver observer) { this.producer = producer; this.observer = observer; } public void setDimensions(int width, int height) { JNodeImage.this.width = width; JNodeImage.this.height = height; JNodeImage.this.availableInfo |= ImageObserver.WIDTH | ImageObserver.HEIGHT; if (observer != null) { observer.imageUpdate(JNodeImage.this, availableInfo, 0, 0, width, height); } } public void setHints(int hints) { } @SuppressWarnings("unchecked") public void setProperties(Hashtable props) { JNodeImage.this.properties = props; JNodeImage.this.availableInfo |= ImageObserver.PROPERTIES; if (observer != null) { observer.imageUpdate(JNodeImage.this, availableInfo, 0, 0, width, height); } } public void setColorModel(ColorModel model) { //System.out.println("setColorModel " + model); JNodeImage.this.colorModel = model; } public void setPixels(int x, int y, int width, int height, ColorModel model, byte pixels[], int offset, int scansize) { synchronized (JNodeImage.this) { // v1.1 : check if image not flushed if ((availableInfo & ImageObserver.ERROR) == 0) { final Object pixelsArray = getPixelsArray(); final int[] intPixels; final byte[] bytePixels; int lastARGB = 0; byte lastIndex = (byte) -1; if (pixelsArray instanceof int[]) { intPixels = (int[]) pixelsArray; bytePixels = null; } else { bytePixels = (byte[]) pixelsArray; intPixels = null; } for (int row = 0, destRow = y * JNodeImage.this.width; row < height; row++, destRow += JNodeImage.this.width) { final int rowOff = offset + row * scansize; for (int col = 0; col < width; col++) // v2.3 : Added support for 8 bit color images if (intPixels != null) { // v1.2 : Added & 0xFF to disable sign bit intPixels[destRow + x + col] = model.getRGB(pixels[rowOff + col] & 0xFF); } else if (colorModel == model) { bytePixels[destRow + x + col] = pixels[rowOff + col]; } else { final int ARGB = model.getRGB(pixels[rowOff + col] & 0xFF); if (lastIndex == -1 || lastARGB != ARGB) { // Keep track of the last color lastARGB = ARGB; lastIndex = (byte) AwtUtils.getClosestColorIndex((IndexColorModel) colorModel, lastARGB); } bytePixels[destRow + x + col] = lastIndex; } } JNodeImage.this.availableInfo |= ImageObserver.SOMEBITS; } } } public void setPixels(int x, int y, int width, int height, ColorModel model, int pixels[], int offset, int scansize) { synchronized (JNodeImage.this) { // v1.1 : check if image not flushed if ((availableInfo & ImageObserver.ERROR) == 0) { final Object pixelsArray = getPixelsArray(); final int[] intPixels; final byte[] bytePixels; int lastARGB = 0; byte lastIndex = (byte) -1; if (pixelsArray instanceof int[]) { intPixels = (int[]) pixelsArray; bytePixels = null; } else { bytePixels = (byte[]) pixelsArray; intPixels = null; } for (int row = 0, destRow = y * JNodeImage.this.width; row < height; row++, destRow += JNodeImage.this.width) { final int rowOff = offset + row * scansize; for (int col = 0; col < width; col++) { int ARGB = model == null ? pixels[rowOff + col] : model.getRGB(pixels[rowOff + col]); // v2.3 : Added support for 8 bit color images if (intPixels != null) // If model == null, consider it's the default RGB model intPixels[destRow + x + col] = ARGB; else { if (lastIndex == -1 || lastARGB != ARGB) { // Keep track of the last color lastARGB = ARGB; lastIndex = (byte) AwtUtils.getClosestColorIndex((IndexColorModel) colorModel, lastARGB); } bytePixels[destRow + x + col] = lastIndex; } } } availableInfo |= ImageObserver.SOMEBITS; } } } public void imageComplete(int status) { synchronized (JNodeImage.this) { if (status == IMAGEERROR) { availableInfo = ImageObserver.ERROR; } else if (status == IMAGEABORTED) { availableInfo = ImageObserver.ABORT | ImageObserver.ERROR; } else { availableInfo &= ~ImageObserver.SOMEBITS; if (status == STATICIMAGEDONE) { availableInfo |= ImageObserver.ALLBITS; } else if (status == SINGLEFRAMEDONE) { // This implementation manages only one frame availableInfo |= ImageObserver.ALLBITS /*ImageObserver.FRAMEBITS*/; } } if (status == IMAGEERROR || status == IMAGEABORTED) { pixels = null; } producer.removeConsumer(this); if (observer != null) { if ((availableInfo & ImageObserver.ERROR) != 0) { observer.imageUpdate(JNodeImage.this, availableInfo, -1, -1, -1, -1); } else { observer.imageUpdate(JNodeImage.this, availableInfo, 0, 0, width, height); } } // v1.2 : Moved synchronized to method start JNodeImage.this.notifyAll(); } } } }