libjava/
[official-gcc.git] / libjava / classpath / gnu / java / awt / peer / gtk / GdkPixbufDecoder.java
blobdbf4a2018c1805d2bc81e332af593914b72e32af
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)
9 any later version.
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
19 02110-1301 USA.
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
24 combination.
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 java.awt.image.BufferedImage;
42 import java.awt.image.ColorModel;
43 import java.awt.image.DirectColorModel;
44 import java.awt.image.ImageConsumer;
45 import java.awt.image.Raster;
46 import java.awt.image.RenderedImage;
47 import java.io.DataInput;
48 import java.io.DataOutput;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.net.URL;
52 import java.util.ArrayList;
53 import java.util.Hashtable;
54 import java.util.Iterator;
55 import java.util.Locale;
56 import java.util.Vector;
58 import javax.imageio.IIOImage;
59 import javax.imageio.ImageReadParam;
60 import javax.imageio.ImageReader;
61 import javax.imageio.ImageTypeSpecifier;
62 import javax.imageio.ImageWriteParam;
63 import javax.imageio.ImageWriter;
64 import javax.imageio.metadata.IIOMetadata;
65 import javax.imageio.spi.IIORegistry;
66 import javax.imageio.spi.ImageReaderSpi;
67 import javax.imageio.spi.ImageWriterSpi;
68 import javax.imageio.stream.ImageInputStream;
69 import javax.imageio.stream.ImageOutputStream;
71 import gnu.classpath.Configuration;
72 import gnu.classpath.Pointer;
74 public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder
76 static
78 if (Configuration.INIT_LOAD_LIBRARY)
80 System.loadLibrary("gtkpeer");
83 initStaticState ();
86 /**
87 * Lock that should be held for all gdkpixbuf operations. We don't use
88 * the global gdk_threads_enter/leave functions since gdkpixbuf
89 * operations can be done in parallel to drawing and manipulating gtk
90 * widgets.
92 static Object pixbufLock = new Object();
94 static native void initStaticState();
95 private final int native_state = GtkGenericPeer.getUniqueInteger ();
97 // initState() has been called, but pumpDone() has not yet been called.
98 private boolean needsClose = false;
100 // the current set of ImageConsumers for this decoder
101 Vector curr;
104 * The pointer to the native pixbuf loader.
106 * This field is manipulated by native code. Don't change or remove
107 * without adjusting the native code.
109 private Pointer nativeDecoder;
111 // interface to GdkPixbuf
112 // These native functions should be called with the pixbufLock held.
113 native void initState ();
114 native void pumpBytes (byte[] bytes, int len) throws IOException;
115 native void pumpDone () throws IOException;
116 native void finish (boolean needsClose);
119 * Converts given image to bytes.
120 * Will call the GdkPixbufWriter for each chunk.
122 static native void streamImage(int[] bytes, String format,
123 int width, int height,
124 boolean hasAlpha, GdkPixbufWriter writer);
126 // gdk-pixbuf provids data in RGBA format
127 static final ColorModel cm = new DirectColorModel (32, 0xff000000,
128 0x00ff0000,
129 0x0000ff00,
130 0x000000ff);
131 public GdkPixbufDecoder (DataInput datainput)
133 super (datainput);
136 public GdkPixbufDecoder (InputStream in)
138 super (in);
141 public GdkPixbufDecoder (String filename)
143 super (filename);
146 public GdkPixbufDecoder (URL url)
148 super (url);
151 public GdkPixbufDecoder (byte[] imagedata, int imageoffset, int imagelength)
153 super (imagedata, imageoffset, imagelength);
156 // called back by native side: area_prepared_cb
157 void areaPrepared (int width, int height)
160 if (curr == null)
161 return;
163 for (int i = 0; i < curr.size (); i++)
165 ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
166 ic.setDimensions (width, height);
167 ic.setColorModel (cm);
168 ic.setHints (ImageConsumer.RANDOMPIXELORDER);
172 // called back by native side: area_updated_cb
173 void areaUpdated (int x, int y, int width, int height,
174 int pixels[], int scansize)
176 if (curr == null)
177 return;
179 for (int i = 0; i < curr.size (); i++)
181 ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
182 ic.setPixels (x, y, width, height, cm, pixels, 0, scansize);
186 // called from an async image loader of one sort or another, this method
187 // repeatedly reads bytes from the input stream and passes them through a
188 // GdkPixbufLoader using the native method pumpBytes. pumpBytes in turn
189 // decodes the image data and calls back areaPrepared and areaUpdated on
190 // this object, feeding back decoded pixel blocks, which we pass to each
191 // of the ImageConsumers in the provided Vector.
193 public void produce (Vector v, InputStream is) throws IOException
195 curr = v;
197 byte bytes[] = new byte[4096];
198 int len = 0;
199 synchronized(pixbufLock)
201 initState();
203 needsClose = true;
205 // Note: We don't want the pixbufLock while reading from the InputStream.
206 while ((len = is.read (bytes)) != -1)
208 synchronized(pixbufLock)
210 pumpBytes (bytes, len);
214 synchronized(pixbufLock)
216 pumpDone();
219 needsClose = false;
221 for (int i = 0; i < curr.size (); i++)
223 ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
224 ic.imageComplete (ImageConsumer.STATICIMAGEDONE);
227 curr = null;
230 public void finalize()
232 synchronized(pixbufLock)
234 finish(needsClose);
239 public static class ImageFormatSpec
241 public String name;
242 public boolean writable = false;
243 public ArrayList<String> mimeTypes = new ArrayList<String>();
244 public ArrayList<String> extensions = new ArrayList<String>();
246 public ImageFormatSpec(String name, boolean writable)
248 this.name = name;
249 this.writable = writable;
252 public synchronized void addMimeType(String m)
254 mimeTypes.add(m);
257 public synchronized void addExtension(String e)
259 extensions.add(e);
263 static ArrayList<ImageFormatSpec> imageFormatSpecs;
265 public static ImageFormatSpec registerFormat(String name, boolean writable)
267 ImageFormatSpec ifs = new ImageFormatSpec(name, writable);
268 synchronized(GdkPixbufDecoder.class)
270 if (imageFormatSpecs == null)
271 imageFormatSpecs = new ArrayList<ImageFormatSpec>();
272 imageFormatSpecs.add(ifs);
274 return ifs;
277 static String[] getFormatNames(boolean writable)
279 ArrayList<String> names = new ArrayList<String>();
280 synchronized (imageFormatSpecs)
282 Iterator<ImageFormatSpec> i = imageFormatSpecs.iterator();
283 while (i.hasNext())
285 ImageFormatSpec ifs = i.next();
286 if (writable && !ifs.writable)
287 continue;
288 names.add(ifs.name);
291 * In order to make the filtering code work, we need to register
292 * this type under every "format name" likely to be used as a synonym.
293 * This generally means "all the extensions people might use".
296 Iterator<String> j = ifs.extensions.iterator();
297 while (j.hasNext())
298 names.add(j.next());
301 return names.toArray(new String[names.size()]);
304 static String[] getFormatExtensions(boolean writable)
306 ArrayList<String> extensions = new ArrayList<String>();
307 synchronized (imageFormatSpecs)
309 Iterator<ImageFormatSpec> i = imageFormatSpecs.iterator();
310 while (i.hasNext())
312 ImageFormatSpec ifs = i.next();
313 if (writable && !ifs.writable)
314 continue;
315 Iterator<String> j = ifs.extensions.iterator();
316 while (j.hasNext())
317 extensions.add(j.next());
320 return extensions.toArray(new String[extensions.size()]);
323 static String[] getFormatMimeTypes(boolean writable)
325 ArrayList<String> mimeTypes = new ArrayList<String>();
326 synchronized (imageFormatSpecs)
328 Iterator<ImageFormatSpec> i = imageFormatSpecs.iterator();
329 while (i.hasNext())
331 ImageFormatSpec ifs = i.next();
332 if (writable && !ifs.writable)
333 continue;
334 Iterator<String> j = ifs.mimeTypes.iterator();
335 while (j.hasNext())
336 mimeTypes.add(j.next());
339 return mimeTypes.toArray(new String[mimeTypes.size()]);
343 static String findFormatName(Object ext, boolean needWritable)
345 if (ext == null)
346 return null;
348 if (!(ext instanceof String))
349 throw new IllegalArgumentException("extension is not a string");
351 String str = (String) ext;
353 Iterator<ImageFormatSpec> i = imageFormatSpecs.iterator();
354 while (i.hasNext())
356 ImageFormatSpec ifs = i.next();
358 if (needWritable && !ifs.writable)
359 continue;
361 if (ifs.name.equals(str))
362 return str;
364 Iterator<String> j = ifs.extensions.iterator();
365 while (j.hasNext())
367 String extension = j.next();
368 if (extension.equals(str))
369 return ifs.name;
372 j = ifs.mimeTypes.iterator();
373 while (j.hasNext())
375 String mimeType = j.next();
376 if (mimeType.equals(str))
377 return ifs.name;
380 throw new IllegalArgumentException("unknown extension '" + str + "'");
383 private static GdkPixbufReaderSpi readerSpi;
384 private static GdkPixbufWriterSpi writerSpi;
386 public static synchronized GdkPixbufReaderSpi getReaderSpi()
388 if (readerSpi == null)
389 readerSpi = new GdkPixbufReaderSpi();
390 return readerSpi;
393 public static synchronized GdkPixbufWriterSpi getWriterSpi()
395 if (writerSpi == null)
396 writerSpi = new GdkPixbufWriterSpi();
397 return writerSpi;
400 public static void registerSpis(IIORegistry reg)
402 reg.registerServiceProvider(getReaderSpi(), ImageReaderSpi.class);
403 reg.registerServiceProvider(getWriterSpi(), ImageWriterSpi.class);
406 public static class GdkPixbufWriterSpi extends ImageWriterSpi
408 public GdkPixbufWriterSpi()
410 super("GdkPixbuf", "2.x",
411 GdkPixbufDecoder.getFormatNames(true),
412 GdkPixbufDecoder.getFormatExtensions(true),
413 GdkPixbufDecoder.getFormatMimeTypes(true),
414 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriter",
415 new Class[] { ImageOutputStream.class },
416 new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReaderSpi" },
417 false, null, null, null, null,
418 false, null, null, null, null);
421 public boolean canEncodeImage(ImageTypeSpecifier ts)
423 return true;
426 public ImageWriter createWriterInstance(Object ext)
428 return new GdkPixbufWriter(this, ext);
431 public String getDescription(java.util.Locale loc)
433 return "GdkPixbuf Writer SPI";
438 public static class GdkPixbufReaderSpi extends ImageReaderSpi
440 public GdkPixbufReaderSpi()
442 super("GdkPixbuf", "2.x",
443 GdkPixbufDecoder.getFormatNames(false),
444 GdkPixbufDecoder.getFormatExtensions(false),
445 GdkPixbufDecoder.getFormatMimeTypes(false),
446 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReader",
447 new Class[] { ImageInputStream.class },
448 new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriterSpi" },
449 false, null, null, null, null,
450 false, null, null, null, null);
453 public boolean canDecodeInput(Object obj)
455 return true;
458 public ImageReader createReaderInstance(Object ext)
460 return new GdkPixbufReader(this, ext);
463 public String getDescription(Locale loc)
465 return "GdkPixbuf Reader SPI";
469 private static class GdkPixbufWriter
470 extends ImageWriter implements Runnable
472 String ext;
473 public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi, Object ext)
475 super(ownerSpi);
476 this.ext = findFormatName(ext, true);
479 public IIOMetadata convertImageMetadata (IIOMetadata inData,
480 ImageTypeSpecifier imageType,
481 ImageWriteParam param)
483 return null;
486 public IIOMetadata convertStreamMetadata (IIOMetadata inData,
487 ImageWriteParam param)
489 return null;
492 public IIOMetadata getDefaultImageMetadata (ImageTypeSpecifier imageType,
493 ImageWriteParam param)
495 return null;
498 public IIOMetadata getDefaultStreamMetadata (ImageWriteParam param)
500 return null;
503 public void write (IIOMetadata streamMetadata, IIOImage i, ImageWriteParam param)
504 throws IOException
506 RenderedImage image = i.getRenderedImage();
507 Raster ras = image.getData();
508 int width = ras.getWidth();
509 int height = ras.getHeight();
510 ColorModel model = image.getColorModel();
511 int[] pixels = CairoGraphics2D.findSimpleIntegerArray (image.getColorModel(), ras);
513 if (pixels == null)
515 BufferedImage img;
516 if(model != null && model.hasAlpha())
517 img = CairoSurface.getBufferedImage(width, height);
518 img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
519 int[] pix = new int[4];
520 for (int y = 0; y < height; ++y)
521 for (int x = 0; x < width; ++x)
522 img.setRGB(x, y, model.getRGB(ras.getPixel(x, y, pix)));
523 pixels = CairoGraphics2D.findSimpleIntegerArray (img.getColorModel(),
524 img.getRaster());
525 model = img.getColorModel();
528 Thread workerThread = new Thread(this, "GdkPixbufWriter");
529 workerThread.start();
530 processImageStarted(1);
531 synchronized(pixbufLock)
533 streamImage(pixels, this.ext, width, height, model.hasAlpha(),
534 this);
536 synchronized(data)
538 data.add(DATADONE);
539 data.notifyAll();
542 while (workerThread.isAlive())
546 workerThread.join();
548 catch (InterruptedException ioe)
550 // Ignored.
554 if (exception != null)
555 throw exception;
557 processImageComplete();
561 * Object marking end of data from native streamImage code.
563 private static final Object DATADONE = new Object();
566 * Holds the data gotten from the native streamImage code.
567 * A worker thread will pull data out.
568 * Needs to be synchronized for access.
569 * The special object DATADONE is added when all data has been delivered.
571 private ArrayList<Object> data = new ArrayList<Object>();
574 * Holds any IOException thrown by the run method that needs
575 * to be rethrown by the write method.
577 private IOException exception;
579 /** Callback for streamImage native code. **/
580 private void write(byte[] bs)
582 synchronized(data)
584 data.add(bs);
585 data.notifyAll();
589 public void run()
591 boolean done = false;
592 while (!done)
594 synchronized(data)
596 while (data.isEmpty())
600 data.wait();
602 catch (InterruptedException ie)
604 /* ignore */
608 Object o = data.remove(0);
609 if (o == DATADONE)
610 done = true;
611 else
613 DataOutput out = (DataOutput) getOutput();
616 out.write((byte[]) o);
618 catch (IOException ioe)
620 // We are only interested in the first exception.
621 if (exception == null)
622 exception = ioe;
630 private static class GdkPixbufReader
631 extends ImageReader
632 implements ImageConsumer
634 // ImageConsumer parts
635 GdkPixbufDecoder dec;
636 BufferedImage bufferedImage;
637 ColorModel defaultModel;
638 int width;
639 int height;
640 String ext;
642 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext)
644 super(ownerSpi);
645 this.ext = findFormatName(ext, false);
648 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext,
649 GdkPixbufDecoder d)
651 this(ownerSpi, ext);
652 dec = d;
655 public void setDimensions(int w, int h)
657 processImageStarted(1);
658 width = w;
659 height = h;
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)
681 if (model == null)
682 model = defaultModel;
684 if (bufferedImage == null)
686 if(model != null && model.hasAlpha())
687 bufferedImage = new BufferedImage (width, height,
688 BufferedImage.TYPE_INT_ARGB);
689 else
690 bufferedImage = new BufferedImage (width, height,
691 BufferedImage.TYPE_INT_RGB);
694 int pixels2[];
695 if (model != null)
697 pixels2 = new int[pixels.length];
698 for (int yy = 0; yy < h; yy++)
699 for (int xx = 0; xx < w; xx++)
701 int i = yy * scansize + xx;
702 pixels2[i] = model.getRGB (pixels[i]);
705 else
706 pixels2 = pixels;
708 bufferedImage.setRGB (x, y, w, h, pixels2, offset, scansize);
709 processImageProgress(y / (height == 0 ? 1 : height));
712 public void imageComplete(int status)
714 processImageComplete();
717 public BufferedImage getBufferedImage()
719 if (bufferedImage == null && dec != null)
720 dec.startProduction (this);
721 return bufferedImage;
724 // ImageReader parts
726 public int getNumImages(boolean allowSearch)
727 throws IOException
729 return 1;
732 public IIOMetadata getImageMetadata(int i)
734 return null;
737 public IIOMetadata getStreamMetadata()
738 throws IOException
740 return null;
743 public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
744 throws IOException
746 BufferedImage img = getBufferedImage();
747 Vector<ImageTypeSpecifier> vec = new Vector<ImageTypeSpecifier>();
748 vec.add(new ImageTypeSpecifier(img));
749 return vec.iterator();
752 public int getHeight(int imageIndex)
753 throws IOException
755 return getBufferedImage().getHeight();
758 public int getWidth(int imageIndex)
759 throws IOException
761 return getBufferedImage().getWidth();
764 public void setInput(Object input,
765 boolean seekForwardOnly,
766 boolean ignoreMetadata)
768 super.setInput(input, seekForwardOnly, ignoreMetadata);
769 Object get = getInput();
770 if (get instanceof InputStream)
771 dec = new GdkPixbufDecoder((InputStream) get);
772 else if (get instanceof DataInput)
773 dec = new GdkPixbufDecoder((DataInput) get);
774 else
775 throw new IllegalArgumentException("input object not supported: "
776 + get);
779 public BufferedImage read(int imageIndex, ImageReadParam param)
780 throws IOException
782 return getBufferedImage ();