1 /* GdkPixbufDecoder.java -- Image data decoding object
2 Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu.java.awt.peer.gtk;
41 import gnu.classpath.Configuration;
43 import java.awt.image.BufferedImage;
44 import java.awt.image.ColorModel;
45 import java.awt.image.DirectColorModel;
46 import java.awt.image.ImageConsumer;
47 import java.awt.image.ImageProducer;
48 import java.awt.image.Raster;
49 import java.awt.image.RenderedImage;
50 import java.io.DataInput;
51 import java.io.DataOutput;
52 import java.io.IOException;
53 import java.io.InputStream;
55 import java.util.ArrayList;
56 import java.util.Hashtable;
57 import java.util.Iterator;
58 import java.util.Locale;
59 import java.util.Vector;
61 import javax.imageio.IIOImage;
62 import javax.imageio.ImageReadParam;
63 import javax.imageio.ImageReader;
64 import javax.imageio.ImageTypeSpecifier;
65 import javax.imageio.ImageWriteParam;
66 import javax.imageio.ImageWriter;
67 import javax.imageio.metadata.IIOMetadata;
68 import javax.imageio.spi.IIORegistry;
69 import javax.imageio.spi.ImageReaderSpi;
70 import javax.imageio.spi.ImageWriterSpi;
71 import javax.imageio.stream.ImageInputStream;
72 import javax.imageio.stream.ImageOutputStream;
74 public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder
78 System.loadLibrary("gtkpeer");
84 * Lock that should be held for all gdkpixbuf operations. We don't use
85 * the global gdk_threads_enter/leave functions since gdkpixbuf
86 * operations can be done in parallel to drawing and manipulating gtk
89 static Object pixbufLock = new Object();
91 static native void initStaticState();
92 private final int native_state = GtkGenericPeer.getUniqueInteger ();
94 // initState() has been called, but pumpDone() has not yet been called.
95 private boolean needsClose = false;
97 // the current set of ImageConsumers for this decoder
100 // interface to GdkPixbuf
101 // These native functions should be called with the pixbufLock held.
102 native void initState ();
103 native void pumpBytes (byte[] bytes, int len) throws IOException;
104 native void pumpDone () throws IOException;
105 native void finish (boolean needsClose);
108 * Converts given image to bytes.
109 * Will call the GdkPixbufWriter for each chunk.
111 static native void streamImage(int[] bytes, String format,
112 int width, int height,
113 boolean hasAlpha, GdkPixbufWriter writer);
115 // gdk-pixbuf provids data in RGBA format
116 static final ColorModel cm = new DirectColorModel (32, 0xff000000,
120 public GdkPixbufDecoder (DataInput datainput)
125 public GdkPixbufDecoder (InputStream in)
130 public GdkPixbufDecoder (String filename)
135 public GdkPixbufDecoder (URL url)
140 public GdkPixbufDecoder (byte[] imagedata, int imageoffset, int imagelength)
142 super (imagedata, imageoffset, imagelength);
145 // called back by native side: area_prepared_cb
146 void areaPrepared (int width, int height)
152 for (int i = 0; i < curr.size (); i++)
154 ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
155 ic.setDimensions (width, height);
156 ic.setColorModel (cm);
157 ic.setHints (ImageConsumer.RANDOMPIXELORDER);
161 // called back by native side: area_updated_cb
162 void areaUpdated (int x, int y, int width, int height,
163 int pixels[], int scansize)
168 for (int i = 0; i < curr.size (); i++)
170 ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
171 ic.setPixels (x, y, width, height, cm, pixels, 0, scansize);
175 // called from an async image loader of one sort or another, this method
176 // repeatedly reads bytes from the input stream and passes them through a
177 // GdkPixbufLoader using the native method pumpBytes. pumpBytes in turn
178 // decodes the image data and calls back areaPrepared and areaUpdated on
179 // this object, feeding back decoded pixel blocks, which we pass to each
180 // of the ImageConsumers in the provided Vector.
182 public void produce (Vector v, InputStream is) throws IOException
186 byte bytes[] = new byte[4096];
188 synchronized(pixbufLock)
194 // Note: We don't want the pixbufLock while reading from the InputStream.
195 while ((len = is.read (bytes)) != -1)
197 synchronized(pixbufLock)
199 pumpBytes (bytes, len);
203 synchronized(pixbufLock)
210 for (int i = 0; i < curr.size (); i++)
212 ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
213 ic.imageComplete (ImageConsumer.STATICIMAGEDONE);
219 public void finalize()
221 synchronized(pixbufLock)
228 public static class ImageFormatSpec
231 public boolean writable = false;
232 public ArrayList mimeTypes = new ArrayList();
233 public ArrayList extensions = new ArrayList();
235 public ImageFormatSpec(String name, boolean writable)
238 this.writable = writable;
241 public synchronized void addMimeType(String m)
246 public synchronized void addExtension(String e)
252 static ArrayList imageFormatSpecs;
254 public static ImageFormatSpec registerFormat(String name, boolean writable)
256 ImageFormatSpec ifs = new ImageFormatSpec(name, writable);
257 synchronized(GdkPixbufDecoder.class)
259 if (imageFormatSpecs == null)
260 imageFormatSpecs = new ArrayList();
261 imageFormatSpecs.add(ifs);
266 static String[] getFormatNames(boolean writable)
268 ArrayList names = new ArrayList();
269 synchronized (imageFormatSpecs)
271 Iterator i = imageFormatSpecs.iterator();
274 ImageFormatSpec ifs = (ImageFormatSpec) i.next();
275 if (writable && !ifs.writable)
280 * In order to make the filtering code work, we need to register
281 * this type under every "format name" likely to be used as a synonym.
282 * This generally means "all the extensions people might use".
285 Iterator j = ifs.extensions.iterator();
287 names.add((String) j.next());
290 Object[] objs = names.toArray();
291 String[] strings = new String[objs.length];
292 for (int i = 0; i < objs.length; ++i)
293 strings[i] = (String) objs[i];
297 static String[] getFormatExtensions(boolean writable)
299 ArrayList extensions = new ArrayList();
300 synchronized (imageFormatSpecs)
302 Iterator i = imageFormatSpecs.iterator();
305 ImageFormatSpec ifs = (ImageFormatSpec) i.next();
306 if (writable && !ifs.writable)
308 Iterator j = ifs.extensions.iterator();
310 extensions.add((String) j.next());
313 Object[] objs = extensions.toArray();
314 String[] strings = new String[objs.length];
315 for (int i = 0; i < objs.length; ++i)
316 strings[i] = (String) objs[i];
320 static String[] getFormatMimeTypes(boolean writable)
322 ArrayList mimeTypes = new ArrayList();
323 synchronized (imageFormatSpecs)
325 Iterator i = imageFormatSpecs.iterator();
328 ImageFormatSpec ifs = (ImageFormatSpec) i.next();
329 if (writable && !ifs.writable)
331 Iterator j = ifs.mimeTypes.iterator();
333 mimeTypes.add((String) j.next());
336 Object[] objs = mimeTypes.toArray();
337 String[] strings = new String[objs.length];
338 for (int i = 0; i < objs.length; ++i)
339 strings[i] = (String) objs[i];
344 static String findFormatName(Object ext, boolean needWritable)
349 if (!(ext instanceof String))
350 throw new IllegalArgumentException("extension is not a string");
352 String str = (String) ext;
354 Iterator i = imageFormatSpecs.iterator();
357 ImageFormatSpec ifs = (ImageFormatSpec) i.next();
359 if (needWritable && !ifs.writable)
362 if (ifs.name.equals(str))
365 Iterator j = ifs.extensions.iterator();
368 String extension = (String)j.next();
369 if (extension.equals(str))
373 j = ifs.mimeTypes.iterator();
376 String mimeType = (String)j.next();
377 if (mimeType.equals(str))
381 throw new IllegalArgumentException("unknown extension '" + str + "'");
384 private static GdkPixbufReaderSpi readerSpi;
385 private static GdkPixbufWriterSpi writerSpi;
387 public static synchronized GdkPixbufReaderSpi getReaderSpi()
389 if (readerSpi == null)
390 readerSpi = new GdkPixbufReaderSpi();
394 public static synchronized GdkPixbufWriterSpi getWriterSpi()
396 if (writerSpi == null)
397 writerSpi = new GdkPixbufWriterSpi();
401 public static void registerSpis(IIORegistry reg)
403 reg.registerServiceProvider(getReaderSpi(), ImageReaderSpi.class);
404 reg.registerServiceProvider(getWriterSpi(), ImageWriterSpi.class);
407 public static class GdkPixbufWriterSpi extends ImageWriterSpi
409 public GdkPixbufWriterSpi()
411 super("GdkPixbuf", "2.x",
412 GdkPixbufDecoder.getFormatNames(true),
413 GdkPixbufDecoder.getFormatExtensions(true),
414 GdkPixbufDecoder.getFormatMimeTypes(true),
415 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriter",
416 new Class[] { ImageOutputStream.class },
417 new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReaderSpi" },
418 false, null, null, null, null,
419 false, null, null, null, null);
422 public boolean canEncodeImage(ImageTypeSpecifier ts)
427 public ImageWriter createWriterInstance(Object ext)
429 return new GdkPixbufWriter(this, ext);
432 public String getDescription(java.util.Locale loc)
434 return "GdkPixbuf Writer SPI";
439 public static class GdkPixbufReaderSpi extends ImageReaderSpi
441 public GdkPixbufReaderSpi()
443 super("GdkPixbuf", "2.x",
444 GdkPixbufDecoder.getFormatNames(false),
445 GdkPixbufDecoder.getFormatExtensions(false),
446 GdkPixbufDecoder.getFormatMimeTypes(false),
447 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReader",
448 new Class[] { ImageInputStream.class },
449 new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriterSpi" },
450 false, null, null, null, null,
451 false, null, null, null, null);
454 public boolean canDecodeInput(Object obj)
459 public ImageReader createReaderInstance(Object ext)
461 return new GdkPixbufReader(this, ext);
464 public String getDescription(Locale loc)
466 return "GdkPixbuf Reader SPI";
470 private static class GdkPixbufWriter
471 extends ImageWriter implements Runnable
474 public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi, Object ext)
477 this.ext = findFormatName(ext, true);
480 public IIOMetadata convertImageMetadata (IIOMetadata inData,
481 ImageTypeSpecifier imageType,
482 ImageWriteParam param)
487 public IIOMetadata convertStreamMetadata (IIOMetadata inData,
488 ImageWriteParam param)
493 public IIOMetadata getDefaultImageMetadata (ImageTypeSpecifier imageType,
494 ImageWriteParam param)
499 public IIOMetadata getDefaultStreamMetadata (ImageWriteParam param)
504 public void write (IIOMetadata streamMetadata, IIOImage i, ImageWriteParam param)
507 RenderedImage image = i.getRenderedImage();
508 Raster ras = image.getData();
509 int width = ras.getWidth();
510 int height = ras.getHeight();
511 ColorModel model = image.getColorModel();
512 int[] pixels = CairoGraphics2D.findSimpleIntegerArray (image.getColorModel(), ras);
517 if(model != null && model.hasAlpha())
518 img = CairoSurface.getBufferedImage(width, height);
519 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
520 int[] pix = new int[4];
521 for (int y = 0; y < height; ++y)
522 for (int x = 0; x < width; ++x)
523 img.setRGB(x, y, model.getRGB(ras.getPixel(x, y, pix)));
524 pixels = CairoGraphics2D.findSimpleIntegerArray (img.getColorModel(),
526 model = img.getColorModel();
529 Thread workerThread = new Thread(this, "GdkPixbufWriter");
530 workerThread.start();
531 processImageStarted(1);
532 synchronized(pixbufLock)
534 streamImage(pixels, this.ext, width, height, model.hasAlpha(),
543 while (workerThread.isAlive())
549 catch (InterruptedException ioe)
555 if (exception != null)
558 processImageComplete();
562 * Object marking end of data from native streamImage code.
564 private static final Object DATADONE = new Object();
567 * Holds the data gotten from the native streamImage code.
568 * A worker thread will pull data out.
569 * Needs to be synchronized for access.
570 * The special object DATADONE is added when all data has been delivered.
572 private ArrayList data = new ArrayList();
575 * Holds any IOException thrown by the run method that needs
576 * to be rethrown by the write method.
578 private IOException exception;
580 /** Callback for streamImage native code. **/
581 private void write(byte[] bs)
592 boolean done = false;
597 while (data.isEmpty())
603 catch (InterruptedException ie)
609 Object o = data.remove(0);
614 DataOutput out = (DataOutput) getOutput();
617 out.write((byte[]) o);
619 catch (IOException ioe)
621 // We are only interested in the first exception.
622 if (exception == null)
631 private static class GdkPixbufReader
633 implements ImageConsumer
635 // ImageConsumer parts
636 GdkPixbufDecoder dec;
637 BufferedImage bufferedImage;
638 ColorModel defaultModel;
643 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext)
646 this.ext = findFormatName(ext, false);
649 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext, GdkPixbufDecoder d)
655 public void setDimensions(int w, int h)
657 processImageStarted(1);
662 public void setProperties(Hashtable props) {}
664 public void setColorModel(ColorModel model)
666 defaultModel = model;
669 public void setHints(int flags) {}
671 public void setPixels(int x, int y, int w, int h,
672 ColorModel model, byte[] pixels,
673 int offset, int scansize)
677 public void setPixels(int x, int y, int w, int h,
678 ColorModel model, int[] pixels,
679 int offset, int scansize)
682 model = defaultModel;
684 if (bufferedImage == null)
686 if(model != null && model.hasAlpha())
687 bufferedImage = new BufferedImage (width, height, BufferedImage.TYPE_INT_ARGB);
689 bufferedImage = new BufferedImage (width, height, BufferedImage.TYPE_INT_RGB);
695 pixels2 = new int[pixels.length];
696 for (int yy = 0; yy < h; yy++)
697 for (int xx = 0; xx < w; xx++)
699 int i = yy * scansize + xx;
700 pixels2[i] = model.getRGB (pixels[i]);
706 bufferedImage.setRGB (x, y, w, h, pixels2, offset, scansize);
707 processImageProgress(y / (height == 0 ? 1 : height));
710 public void imageComplete(int status)
712 processImageComplete();
715 public BufferedImage getBufferedImage()
717 if (bufferedImage == null && dec != null)
718 dec.startProduction (this);
719 return bufferedImage;
724 public int getNumImages(boolean allowSearch)
730 public IIOMetadata getImageMetadata(int i)
735 public IIOMetadata getStreamMetadata()
741 public Iterator getImageTypes(int imageIndex)
744 BufferedImage img = getBufferedImage();
745 Vector vec = new Vector();
746 vec.add(new ImageTypeSpecifier(img));
747 return vec.iterator();
750 public int getHeight(int imageIndex)
753 return getBufferedImage().getHeight();
756 public int getWidth(int imageIndex)
759 return getBufferedImage().getWidth();
762 public void setInput(Object input,
763 boolean seekForwardOnly,
764 boolean ignoreMetadata)
766 super.setInput(input, seekForwardOnly, ignoreMetadata);
767 Object get = getInput();
768 if (get instanceof InputStream)
769 dec = new GdkPixbufDecoder((InputStream) get);
770 else if (get instanceof DataInput)
771 dec = new GdkPixbufDecoder((DataInput) get);
773 throw new IllegalArgumentException("input object not supported: "
777 public BufferedImage read(int imageIndex, ImageReadParam param)
780 return getBufferedImage ();