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 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
;
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 public class GdkPixbufDecoder
extends gnu
.java
.awt
.image
.ImageDecoder
75 System
.loadLibrary("gtkpeer");
81 * Lock that should be held for all gdkpixbuf operations. We don't use
82 * the global gdk_threads_enter/leave functions since gdkpixbuf
83 * operations can be done in parallel to drawing and manipulating gtk
86 static Object pixbufLock
= new Object();
88 static native void initStaticState();
89 private final int native_state
= GtkGenericPeer
.getUniqueInteger ();
91 // initState() has been called, but pumpDone() has not yet been called.
92 private boolean needsClose
= false;
94 // the current set of ImageConsumers for this decoder
97 // interface to GdkPixbuf
98 // These native functions should be called with the pixbufLock held.
99 native void initState ();
100 native void pumpBytes (byte[] bytes
, int len
) throws IOException
;
101 native void pumpDone () throws IOException
;
102 native void finish (boolean needsClose
);
105 * Converts given image to bytes.
106 * Will call the GdkPixbufWriter for each chunk.
108 static native void streamImage(int[] bytes
, String format
,
109 int width
, int height
,
110 boolean hasAlpha
, GdkPixbufWriter writer
);
112 // gdk-pixbuf provids data in RGBA format
113 static final ColorModel cm
= new DirectColorModel (32, 0xff000000,
117 public GdkPixbufDecoder (DataInput datainput
)
122 public GdkPixbufDecoder (InputStream in
)
127 public GdkPixbufDecoder (String filename
)
132 public GdkPixbufDecoder (URL url
)
137 public GdkPixbufDecoder (byte[] imagedata
, int imageoffset
, int imagelength
)
139 super (imagedata
, imageoffset
, imagelength
);
142 // called back by native side: area_prepared_cb
143 void areaPrepared (int width
, int height
)
149 for (int i
= 0; i
< curr
.size (); i
++)
151 ImageConsumer ic
= (ImageConsumer
) curr
.elementAt (i
);
152 ic
.setDimensions (width
, height
);
153 ic
.setColorModel (cm
);
154 ic
.setHints (ImageConsumer
.RANDOMPIXELORDER
);
158 // called back by native side: area_updated_cb
159 void areaUpdated (int x
, int y
, int width
, int height
,
160 int pixels
[], int scansize
)
165 for (int i
= 0; i
< curr
.size (); i
++)
167 ImageConsumer ic
= (ImageConsumer
) curr
.elementAt (i
);
168 ic
.setPixels (x
, y
, width
, height
, cm
, pixels
, 0, scansize
);
172 // called from an async image loader of one sort or another, this method
173 // repeatedly reads bytes from the input stream and passes them through a
174 // GdkPixbufLoader using the native method pumpBytes. pumpBytes in turn
175 // decodes the image data and calls back areaPrepared and areaUpdated on
176 // this object, feeding back decoded pixel blocks, which we pass to each
177 // of the ImageConsumers in the provided Vector.
179 public void produce (Vector v
, InputStream is
) throws IOException
183 byte bytes
[] = new byte[4096];
185 synchronized(pixbufLock
)
191 // Note: We don't want the pixbufLock while reading from the InputStream.
192 while ((len
= is
.read (bytes
)) != -1)
194 synchronized(pixbufLock
)
196 pumpBytes (bytes
, len
);
200 synchronized(pixbufLock
)
207 for (int i
= 0; i
< curr
.size (); i
++)
209 ImageConsumer ic
= (ImageConsumer
) curr
.elementAt (i
);
210 ic
.imageComplete (ImageConsumer
.STATICIMAGEDONE
);
216 public void finalize()
218 synchronized(pixbufLock
)
225 public static class ImageFormatSpec
228 public boolean writable
= false;
229 public ArrayList
<String
> mimeTypes
= new ArrayList
<String
>();
230 public ArrayList
<String
> extensions
= new ArrayList
<String
>();
232 public ImageFormatSpec(String name
, boolean writable
)
235 this.writable
= writable
;
238 public synchronized void addMimeType(String m
)
243 public synchronized void addExtension(String e
)
249 static ArrayList
<ImageFormatSpec
> imageFormatSpecs
;
251 public static ImageFormatSpec
registerFormat(String name
, boolean writable
)
253 ImageFormatSpec ifs
= new ImageFormatSpec(name
, writable
);
254 synchronized(GdkPixbufDecoder
.class)
256 if (imageFormatSpecs
== null)
257 imageFormatSpecs
= new ArrayList
<ImageFormatSpec
>();
258 imageFormatSpecs
.add(ifs
);
263 static String
[] getFormatNames(boolean writable
)
265 ArrayList
<String
> names
= new ArrayList
<String
>();
266 synchronized (imageFormatSpecs
)
268 Iterator
<ImageFormatSpec
> i
= imageFormatSpecs
.iterator();
271 ImageFormatSpec ifs
= i
.next();
272 if (writable
&& !ifs
.writable
)
277 * In order to make the filtering code work, we need to register
278 * this type under every "format name" likely to be used as a synonym.
279 * This generally means "all the extensions people might use".
282 Iterator
<String
> j
= ifs
.extensions
.iterator();
287 return names
.toArray(new String
[names
.size()]);
290 static String
[] getFormatExtensions(boolean writable
)
292 ArrayList
<String
> extensions
= new ArrayList
<String
>();
293 synchronized (imageFormatSpecs
)
295 Iterator
<ImageFormatSpec
> i
= imageFormatSpecs
.iterator();
298 ImageFormatSpec ifs
= i
.next();
299 if (writable
&& !ifs
.writable
)
301 Iterator
<String
> j
= ifs
.extensions
.iterator();
303 extensions
.add(j
.next());
306 return extensions
.toArray(new String
[extensions
.size()]);
309 static String
[] getFormatMimeTypes(boolean writable
)
311 ArrayList
<String
> mimeTypes
= new ArrayList
<String
>();
312 synchronized (imageFormatSpecs
)
314 Iterator
<ImageFormatSpec
> i
= imageFormatSpecs
.iterator();
317 ImageFormatSpec ifs
= i
.next();
318 if (writable
&& !ifs
.writable
)
320 Iterator
<String
> j
= ifs
.mimeTypes
.iterator();
322 mimeTypes
.add(j
.next());
325 return mimeTypes
.toArray(new String
[mimeTypes
.size()]);
329 static String
findFormatName(Object ext
, boolean needWritable
)
334 if (!(ext
instanceof String
))
335 throw new IllegalArgumentException("extension is not a string");
337 String str
= (String
) ext
;
339 Iterator
<ImageFormatSpec
> i
= imageFormatSpecs
.iterator();
342 ImageFormatSpec ifs
= i
.next();
344 if (needWritable
&& !ifs
.writable
)
347 if (ifs
.name
.equals(str
))
350 Iterator
<String
> j
= ifs
.extensions
.iterator();
353 String extension
= j
.next();
354 if (extension
.equals(str
))
358 j
= ifs
.mimeTypes
.iterator();
361 String mimeType
= j
.next();
362 if (mimeType
.equals(str
))
366 throw new IllegalArgumentException("unknown extension '" + str
+ "'");
369 private static GdkPixbufReaderSpi readerSpi
;
370 private static GdkPixbufWriterSpi writerSpi
;
372 public static synchronized GdkPixbufReaderSpi
getReaderSpi()
374 if (readerSpi
== null)
375 readerSpi
= new GdkPixbufReaderSpi();
379 public static synchronized GdkPixbufWriterSpi
getWriterSpi()
381 if (writerSpi
== null)
382 writerSpi
= new GdkPixbufWriterSpi();
386 public static void registerSpis(IIORegistry reg
)
388 reg
.registerServiceProvider(getReaderSpi(), ImageReaderSpi
.class);
389 reg
.registerServiceProvider(getWriterSpi(), ImageWriterSpi
.class);
392 public static class GdkPixbufWriterSpi
extends ImageWriterSpi
394 public GdkPixbufWriterSpi()
396 super("GdkPixbuf", "2.x",
397 GdkPixbufDecoder
.getFormatNames(true),
398 GdkPixbufDecoder
.getFormatExtensions(true),
399 GdkPixbufDecoder
.getFormatMimeTypes(true),
400 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriter",
401 new Class
[] { ImageOutputStream
.class },
402 new String
[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReaderSpi" },
403 false, null, null, null, null,
404 false, null, null, null, null);
407 public boolean canEncodeImage(ImageTypeSpecifier ts
)
412 public ImageWriter
createWriterInstance(Object ext
)
414 return new GdkPixbufWriter(this, ext
);
417 public String
getDescription(java
.util
.Locale loc
)
419 return "GdkPixbuf Writer SPI";
424 public static class GdkPixbufReaderSpi
extends ImageReaderSpi
426 public GdkPixbufReaderSpi()
428 super("GdkPixbuf", "2.x",
429 GdkPixbufDecoder
.getFormatNames(false),
430 GdkPixbufDecoder
.getFormatExtensions(false),
431 GdkPixbufDecoder
.getFormatMimeTypes(false),
432 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReader",
433 new Class
[] { ImageInputStream
.class },
434 new String
[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriterSpi" },
435 false, null, null, null, null,
436 false, null, null, null, null);
439 public boolean canDecodeInput(Object obj
)
444 public ImageReader
createReaderInstance(Object ext
)
446 return new GdkPixbufReader(this, ext
);
449 public String
getDescription(Locale loc
)
451 return "GdkPixbuf Reader SPI";
455 private static class GdkPixbufWriter
456 extends ImageWriter
implements Runnable
459 public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi
, Object ext
)
462 this.ext
= findFormatName(ext
, true);
465 public IIOMetadata
convertImageMetadata (IIOMetadata inData
,
466 ImageTypeSpecifier imageType
,
467 ImageWriteParam param
)
472 public IIOMetadata
convertStreamMetadata (IIOMetadata inData
,
473 ImageWriteParam param
)
478 public IIOMetadata
getDefaultImageMetadata (ImageTypeSpecifier imageType
,
479 ImageWriteParam param
)
484 public IIOMetadata
getDefaultStreamMetadata (ImageWriteParam param
)
489 public void write (IIOMetadata streamMetadata
, IIOImage i
, ImageWriteParam param
)
492 RenderedImage image
= i
.getRenderedImage();
493 Raster ras
= image
.getData();
494 int width
= ras
.getWidth();
495 int height
= ras
.getHeight();
496 ColorModel model
= image
.getColorModel();
497 int[] pixels
= CairoGraphics2D
.findSimpleIntegerArray (image
.getColorModel(), ras
);
502 if(model
!= null && model
.hasAlpha())
503 img
= CairoSurface
.getBufferedImage(width
, height
);
504 img
= new BufferedImage(width
, height
, BufferedImage
.TYPE_INT_RGB
);
505 int[] pix
= new int[4];
506 for (int y
= 0; y
< height
; ++y
)
507 for (int x
= 0; x
< width
; ++x
)
508 img
.setRGB(x
, y
, model
.getRGB(ras
.getPixel(x
, y
, pix
)));
509 pixels
= CairoGraphics2D
.findSimpleIntegerArray (img
.getColorModel(),
511 model
= img
.getColorModel();
514 Thread workerThread
= new Thread(this, "GdkPixbufWriter");
515 workerThread
.start();
516 processImageStarted(1);
517 synchronized(pixbufLock
)
519 streamImage(pixels
, this.ext
, width
, height
, model
.hasAlpha(),
528 while (workerThread
.isAlive())
534 catch (InterruptedException ioe
)
540 if (exception
!= null)
543 processImageComplete();
547 * Object marking end of data from native streamImage code.
549 private static final Object DATADONE
= new Object();
552 * Holds the data gotten from the native streamImage code.
553 * A worker thread will pull data out.
554 * Needs to be synchronized for access.
555 * The special object DATADONE is added when all data has been delivered.
557 private ArrayList
<Object
> data
= new ArrayList
<Object
>();
560 * Holds any IOException thrown by the run method that needs
561 * to be rethrown by the write method.
563 private IOException exception
;
565 /** Callback for streamImage native code. **/
566 private void write(byte[] bs
)
577 boolean done
= false;
582 while (data
.isEmpty())
588 catch (InterruptedException ie
)
594 Object o
= data
.remove(0);
599 DataOutput out
= (DataOutput
) getOutput();
602 out
.write((byte[]) o
);
604 catch (IOException ioe
)
606 // We are only interested in the first exception.
607 if (exception
== null)
616 private static class GdkPixbufReader
618 implements ImageConsumer
620 // ImageConsumer parts
621 GdkPixbufDecoder dec
;
622 BufferedImage bufferedImage
;
623 ColorModel defaultModel
;
628 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi
, Object ext
)
631 this.ext
= findFormatName(ext
, false);
634 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi
, Object ext
,
641 public void setDimensions(int w
, int h
)
643 processImageStarted(1);
648 public void setProperties(Hashtable props
) {}
650 public void setColorModel(ColorModel model
)
652 defaultModel
= model
;
655 public void setHints(int flags
) {}
657 public void setPixels(int x
, int y
, int w
, int h
,
658 ColorModel model
, byte[] pixels
,
659 int offset
, int scansize
)
663 public void setPixels(int x
, int y
, int w
, int h
,
664 ColorModel model
, int[] pixels
,
665 int offset
, int scansize
)
668 model
= defaultModel
;
670 if (bufferedImage
== null)
672 if(model
!= null && model
.hasAlpha())
673 bufferedImage
= new BufferedImage (width
, height
,
674 BufferedImage
.TYPE_INT_ARGB
);
676 bufferedImage
= new BufferedImage (width
, height
,
677 BufferedImage
.TYPE_INT_RGB
);
683 pixels2
= new int[pixels
.length
];
684 for (int yy
= 0; yy
< h
; yy
++)
685 for (int xx
= 0; xx
< w
; xx
++)
687 int i
= yy
* scansize
+ xx
;
688 pixels2
[i
] = model
.getRGB (pixels
[i
]);
694 bufferedImage
.setRGB (x
, y
, w
, h
, pixels2
, offset
, scansize
);
695 processImageProgress(y
/ (height
== 0 ?
1 : height
));
698 public void imageComplete(int status
)
700 processImageComplete();
703 public BufferedImage
getBufferedImage()
705 if (bufferedImage
== null && dec
!= null)
706 dec
.startProduction (this);
707 return bufferedImage
;
712 public int getNumImages(boolean allowSearch
)
718 public IIOMetadata
getImageMetadata(int i
)
723 public IIOMetadata
getStreamMetadata()
729 public Iterator
<ImageTypeSpecifier
> getImageTypes(int imageIndex
)
732 BufferedImage img
= getBufferedImage();
733 Vector
<ImageTypeSpecifier
> vec
= new Vector
<ImageTypeSpecifier
>();
734 vec
.add(new ImageTypeSpecifier(img
));
735 return vec
.iterator();
738 public int getHeight(int imageIndex
)
741 return getBufferedImage().getHeight();
744 public int getWidth(int imageIndex
)
747 return getBufferedImage().getWidth();
750 public void setInput(Object input
,
751 boolean seekForwardOnly
,
752 boolean ignoreMetadata
)
754 super.setInput(input
, seekForwardOnly
, ignoreMetadata
);
755 Object get
= getInput();
756 if (get
instanceof InputStream
)
757 dec
= new GdkPixbufDecoder((InputStream
) get
);
758 else if (get
instanceof DataInput
)
759 dec
= new GdkPixbufDecoder((DataInput
) get
);
761 throw new IllegalArgumentException("input object not supported: "
765 public BufferedImage
read(int imageIndex
, ImageReadParam param
)
768 return getBufferedImage ();