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 import gnu
.classpath
.Configuration
;
72 import gnu
.classpath
.Pointer
;
74 public class GdkPixbufDecoder
extends gnu
.java
.awt
.image
.ImageDecoder
78 if (Configuration
.INIT_LOAD_LIBRARY
)
80 System
.loadLibrary("gtkpeer");
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
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
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,
131 public GdkPixbufDecoder (DataInput datainput
)
136 public GdkPixbufDecoder (InputStream in
)
141 public GdkPixbufDecoder (String filename
)
146 public GdkPixbufDecoder (URL 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
)
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
)
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
197 byte bytes
[] = new byte[4096];
199 synchronized(pixbufLock
)
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
)
221 for (int i
= 0; i
< curr
.size (); i
++)
223 ImageConsumer ic
= (ImageConsumer
) curr
.elementAt (i
);
224 ic
.imageComplete (ImageConsumer
.STATICIMAGEDONE
);
230 public void finalize()
232 synchronized(pixbufLock
)
239 public static class ImageFormatSpec
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
)
249 this.writable
= writable
;
252 public synchronized void addMimeType(String m
)
257 public synchronized void addExtension(String 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
);
277 static String
[] getFormatNames(boolean writable
)
279 ArrayList
<String
> names
= new ArrayList
<String
>();
280 synchronized (imageFormatSpecs
)
282 Iterator
<ImageFormatSpec
> i
= imageFormatSpecs
.iterator();
285 ImageFormatSpec ifs
= i
.next();
286 if (writable
&& !ifs
.writable
)
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();
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();
312 ImageFormatSpec ifs
= i
.next();
313 if (writable
&& !ifs
.writable
)
315 Iterator
<String
> j
= ifs
.extensions
.iterator();
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();
331 ImageFormatSpec ifs
= i
.next();
332 if (writable
&& !ifs
.writable
)
334 Iterator
<String
> j
= ifs
.mimeTypes
.iterator();
336 mimeTypes
.add(j
.next());
339 return mimeTypes
.toArray(new String
[mimeTypes
.size()]);
343 static String
findFormatName(Object ext
, boolean needWritable
)
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();
356 ImageFormatSpec ifs
= i
.next();
358 if (needWritable
&& !ifs
.writable
)
361 if (ifs
.name
.equals(str
))
364 Iterator
<String
> j
= ifs
.extensions
.iterator();
367 String extension
= j
.next();
368 if (extension
.equals(str
))
372 j
= ifs
.mimeTypes
.iterator();
375 String mimeType
= j
.next();
376 if (mimeType
.equals(str
))
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();
393 public static synchronized GdkPixbufWriterSpi
getWriterSpi()
395 if (writerSpi
== null)
396 writerSpi
= new GdkPixbufWriterSpi();
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
)
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
)
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
473 public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi
, Object ext
)
476 this.ext
= findFormatName(ext
, true);
479 public IIOMetadata
convertImageMetadata (IIOMetadata inData
,
480 ImageTypeSpecifier imageType
,
481 ImageWriteParam param
)
486 public IIOMetadata
convertStreamMetadata (IIOMetadata inData
,
487 ImageWriteParam param
)
492 public IIOMetadata
getDefaultImageMetadata (ImageTypeSpecifier imageType
,
493 ImageWriteParam param
)
498 public IIOMetadata
getDefaultStreamMetadata (ImageWriteParam param
)
503 public void write (IIOMetadata streamMetadata
, IIOImage i
, ImageWriteParam param
)
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
);
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(),
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(),
542 while (workerThread
.isAlive())
548 catch (InterruptedException ioe
)
554 if (exception
!= null)
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
)
591 boolean done
= false;
596 while (data
.isEmpty())
602 catch (InterruptedException ie
)
608 Object o
= data
.remove(0);
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)
630 private static class GdkPixbufReader
632 implements ImageConsumer
634 // ImageConsumer parts
635 GdkPixbufDecoder dec
;
636 BufferedImage bufferedImage
;
637 ColorModel defaultModel
;
642 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi
, Object ext
)
645 this.ext
= findFormatName(ext
, false);
648 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi
, Object ext
,
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
,
688 BufferedImage
.TYPE_INT_ARGB
);
690 bufferedImage
= new BufferedImage (width
, height
,
691 BufferedImage
.TYPE_INT_RGB
);
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
]);
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
;
726 public int getNumImages(boolean allowSearch
)
732 public IIOMetadata
getImageMetadata(int i
)
737 public IIOMetadata
getStreamMetadata()
743 public Iterator
<ImageTypeSpecifier
> getImageTypes(int imageIndex
)
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
)
755 return getBufferedImage().getHeight();
758 public int getWidth(int imageIndex
)
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
);
775 throw new IllegalArgumentException("input object not supported: "
779 public BufferedImage
read(int imageIndex
, ImageReadParam param
)
782 return getBufferedImage ();