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
);
106 static native void streamImage(int[] bytes
, String format
, int width
, int height
, boolean hasAlpha
, DataOutput sink
);
108 // gdk-pixbuf provids data in RGBA format
109 static final ColorModel cm
= new DirectColorModel (32, 0xff000000,
113 public GdkPixbufDecoder (DataInput datainput
)
118 public GdkPixbufDecoder (InputStream in
)
123 public GdkPixbufDecoder (String filename
)
128 public GdkPixbufDecoder (URL url
)
133 public GdkPixbufDecoder (byte[] imagedata
, int imageoffset
, int imagelength
)
135 super (imagedata
, imageoffset
, imagelength
);
138 // called back by native side: area_prepared_cb
139 void areaPrepared (int width
, int height
)
145 for (int i
= 0; i
< curr
.size (); i
++)
147 ImageConsumer ic
= (ImageConsumer
) curr
.elementAt (i
);
148 ic
.setDimensions (width
, height
);
149 ic
.setColorModel (cm
);
150 ic
.setHints (ImageConsumer
.RANDOMPIXELORDER
);
154 // called back by native side: area_updated_cb
155 void areaUpdated (int x
, int y
, int width
, int height
,
156 int pixels
[], int scansize
)
161 for (int i
= 0; i
< curr
.size (); i
++)
163 ImageConsumer ic
= (ImageConsumer
) curr
.elementAt (i
);
164 ic
.setPixels (x
, y
, width
, height
, cm
, pixels
, 0, scansize
);
168 // called from an async image loader of one sort or another, this method
169 // repeatedly reads bytes from the input stream and passes them through a
170 // GdkPixbufLoader using the native method pumpBytes. pumpBytes in turn
171 // decodes the image data and calls back areaPrepared and areaUpdated on
172 // this object, feeding back decoded pixel blocks, which we pass to each
173 // of the ImageConsumers in the provided Vector.
175 public void produce (Vector v
, InputStream is
) throws IOException
179 byte bytes
[] = new byte[4096];
181 synchronized(pixbufLock
)
187 // Note: We don't want the pixbufLock while reading from the InputStream.
188 while ((len
= is
.read (bytes
)) != -1)
190 synchronized(pixbufLock
)
192 pumpBytes (bytes
, len
);
196 synchronized(pixbufLock
)
203 for (int i
= 0; i
< curr
.size (); i
++)
205 ImageConsumer ic
= (ImageConsumer
) curr
.elementAt (i
);
206 ic
.imageComplete (ImageConsumer
.STATICIMAGEDONE
);
212 public void finalize()
214 synchronized(pixbufLock
)
221 public static class ImageFormatSpec
224 public boolean writable
= false;
225 public ArrayList mimeTypes
= new ArrayList();
226 public ArrayList extensions
= new ArrayList();
228 public ImageFormatSpec(String name
, boolean writable
)
231 this.writable
= writable
;
234 public synchronized void addMimeType(String m
)
239 public synchronized void addExtension(String e
)
245 static ArrayList imageFormatSpecs
;
247 public static ImageFormatSpec
registerFormat(String name
, boolean writable
)
249 ImageFormatSpec ifs
= new ImageFormatSpec(name
, writable
);
250 synchronized(GdkPixbufDecoder
.class)
252 if (imageFormatSpecs
== null)
253 imageFormatSpecs
= new ArrayList();
254 imageFormatSpecs
.add(ifs
);
259 static String
[] getFormatNames(boolean writable
)
261 ArrayList names
= new ArrayList();
262 synchronized (imageFormatSpecs
)
264 Iterator i
= imageFormatSpecs
.iterator();
267 ImageFormatSpec ifs
= (ImageFormatSpec
) i
.next();
268 if (writable
&& !ifs
.writable
)
273 * In order to make the filtering code work, we need to register
274 * this type under every "format name" likely to be used as a synonym.
275 * This generally means "all the extensions people might use".
278 Iterator j
= ifs
.extensions
.iterator();
280 names
.add((String
) j
.next());
283 Object
[] objs
= names
.toArray();
284 String
[] strings
= new String
[objs
.length
];
285 for (int i
= 0; i
< objs
.length
; ++i
)
286 strings
[i
] = (String
) objs
[i
];
290 static String
[] getFormatExtensions(boolean writable
)
292 ArrayList extensions
= new ArrayList();
293 synchronized (imageFormatSpecs
)
295 Iterator i
= imageFormatSpecs
.iterator();
298 ImageFormatSpec ifs
= (ImageFormatSpec
) i
.next();
299 if (writable
&& !ifs
.writable
)
301 Iterator j
= ifs
.extensions
.iterator();
303 extensions
.add((String
) j
.next());
306 Object
[] objs
= extensions
.toArray();
307 String
[] strings
= new String
[objs
.length
];
308 for (int i
= 0; i
< objs
.length
; ++i
)
309 strings
[i
] = (String
) objs
[i
];
313 static String
[] getFormatMimeTypes(boolean writable
)
315 ArrayList mimeTypes
= new ArrayList();
316 synchronized (imageFormatSpecs
)
318 Iterator i
= imageFormatSpecs
.iterator();
321 ImageFormatSpec ifs
= (ImageFormatSpec
) i
.next();
322 if (writable
&& !ifs
.writable
)
324 Iterator j
= ifs
.mimeTypes
.iterator();
326 mimeTypes
.add((String
) j
.next());
329 Object
[] objs
= mimeTypes
.toArray();
330 String
[] strings
= new String
[objs
.length
];
331 for (int i
= 0; i
< objs
.length
; ++i
)
332 strings
[i
] = (String
) objs
[i
];
337 static String
findFormatName(Object ext
, boolean needWritable
)
342 if (!(ext
instanceof String
))
343 throw new IllegalArgumentException("extension is not a string");
345 String str
= (String
) ext
;
347 Iterator i
= imageFormatSpecs
.iterator();
350 ImageFormatSpec ifs
= (ImageFormatSpec
) i
.next();
352 if (needWritable
&& !ifs
.writable
)
355 if (ifs
.name
.equals(str
))
358 Iterator j
= ifs
.extensions
.iterator();
361 String extension
= (String
)j
.next();
362 if (extension
.equals(str
))
366 j
= ifs
.mimeTypes
.iterator();
369 String mimeType
= (String
)j
.next();
370 if (mimeType
.equals(str
))
374 throw new IllegalArgumentException("unknown extension '" + str
+ "'");
377 private static GdkPixbufReaderSpi readerSpi
;
378 private static GdkPixbufWriterSpi writerSpi
;
380 public static synchronized GdkPixbufReaderSpi
getReaderSpi()
382 if (readerSpi
== null)
383 readerSpi
= new GdkPixbufReaderSpi();
387 public static synchronized GdkPixbufWriterSpi
getWriterSpi()
389 if (writerSpi
== null)
390 writerSpi
= new GdkPixbufWriterSpi();
394 public static void registerSpis(IIORegistry reg
)
396 reg
.registerServiceProvider(getReaderSpi(), ImageReaderSpi
.class);
397 reg
.registerServiceProvider(getWriterSpi(), ImageWriterSpi
.class);
400 public static class GdkPixbufWriterSpi
extends ImageWriterSpi
402 public GdkPixbufWriterSpi()
404 super("GdkPixbuf", "2.x",
405 GdkPixbufDecoder
.getFormatNames(true),
406 GdkPixbufDecoder
.getFormatExtensions(true),
407 GdkPixbufDecoder
.getFormatMimeTypes(true),
408 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriter",
409 new Class
[] { ImageOutputStream
.class },
410 new String
[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReaderSpi" },
411 false, null, null, null, null,
412 false, null, null, null, null);
415 public boolean canEncodeImage(ImageTypeSpecifier ts
)
420 public ImageWriter
createWriterInstance(Object ext
)
422 return new GdkPixbufWriter(this, ext
);
425 public String
getDescription(java
.util
.Locale loc
)
427 return "GdkPixbuf Writer SPI";
432 public static class GdkPixbufReaderSpi
extends ImageReaderSpi
434 public GdkPixbufReaderSpi()
436 super("GdkPixbuf", "2.x",
437 GdkPixbufDecoder
.getFormatNames(false),
438 GdkPixbufDecoder
.getFormatExtensions(false),
439 GdkPixbufDecoder
.getFormatMimeTypes(false),
440 "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReader",
441 new Class
[] { ImageInputStream
.class },
442 new String
[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriterSpi" },
443 false, null, null, null, null,
444 false, null, null, null, null);
447 public boolean canDecodeInput(Object obj
)
452 public ImageReader
createReaderInstance(Object ext
)
454 return new GdkPixbufReader(this, ext
);
457 public String
getDescription(Locale loc
)
459 return "GdkPixbuf Reader SPI";
463 private static class GdkPixbufWriter
467 public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi
, Object ext
)
470 this.ext
= findFormatName(ext
, true);
473 public IIOMetadata
convertImageMetadata (IIOMetadata inData
,
474 ImageTypeSpecifier imageType
,
475 ImageWriteParam param
)
480 public IIOMetadata
convertStreamMetadata (IIOMetadata inData
,
481 ImageWriteParam param
)
486 public IIOMetadata
getDefaultImageMetadata (ImageTypeSpecifier imageType
,
487 ImageWriteParam param
)
492 public IIOMetadata
getDefaultStreamMetadata (ImageWriteParam param
)
497 public void write (IIOMetadata streamMetadata
, IIOImage i
, ImageWriteParam param
)
500 RenderedImage image
= i
.getRenderedImage();
501 Raster ras
= image
.getData();
502 int width
= ras
.getWidth();
503 int height
= ras
.getHeight();
504 ColorModel model
= image
.getColorModel();
505 int[] pixels
= GdkGraphics2D
.findSimpleIntegerArray (image
.getColorModel(), ras
);
509 BufferedImage img
= new BufferedImage(width
, height
,
510 (model
!= null && model
.hasAlpha() ?
511 BufferedImage
.TYPE_INT_ARGB
512 : BufferedImage
.TYPE_INT_RGB
));
513 int[] pix
= new int[4];
514 for (int y
= 0; y
< height
; ++y
)
515 for (int x
= 0; x
< width
; ++x
)
516 img
.setRGB(x
, y
, model
.getRGB(ras
.getPixel(x
, y
, pix
)));
517 pixels
= GdkGraphics2D
.findSimpleIntegerArray (img
.getColorModel(),
519 model
= img
.getColorModel();
522 processImageStarted(1);
523 synchronized(pixbufLock
)
525 streamImage(pixels
, this.ext
, width
, height
, model
.hasAlpha(),
526 (DataOutput
) this.getOutput());
528 processImageComplete();
532 private static class GdkPixbufReader
534 implements ImageConsumer
536 // ImageConsumer parts
537 GdkPixbufDecoder dec
;
538 BufferedImage bufferedImage
;
539 ColorModel defaultModel
;
544 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi
, Object ext
)
547 this.ext
= findFormatName(ext
, false);
550 public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi
, Object ext
, GdkPixbufDecoder d
)
556 public void setDimensions(int w
, int h
)
558 processImageStarted(1);
563 public void setProperties(Hashtable props
) {}
565 public void setColorModel(ColorModel model
)
567 defaultModel
= model
;
570 public void setHints(int flags
) {}
572 public void setPixels(int x
, int y
, int w
, int h
,
573 ColorModel model
, byte[] pixels
,
574 int offset
, int scansize
)
578 public void setPixels(int x
, int y
, int w
, int h
,
579 ColorModel model
, int[] pixels
,
580 int offset
, int scansize
)
583 model
= defaultModel
;
585 if (bufferedImage
== null)
587 bufferedImage
= new BufferedImage (width
, height
, (model
!= null && model
.hasAlpha() ?
588 BufferedImage
.TYPE_INT_ARGB
589 : BufferedImage
.TYPE_INT_RGB
));
595 pixels2
= new int[pixels
.length
];
596 for (int yy
= 0; yy
< h
; yy
++)
597 for (int xx
= 0; xx
< w
; xx
++)
599 int i
= yy
* scansize
+ xx
;
600 pixels2
[i
] = model
.getRGB (pixels
[i
]);
606 bufferedImage
.setRGB (x
, y
, w
, h
, pixels2
, offset
, scansize
);
607 processImageProgress(y
/ (height
== 0 ?
1 : height
));
610 public void imageComplete(int status
)
612 processImageComplete();
615 public BufferedImage
getBufferedImage()
617 if (bufferedImage
== null && dec
!= null)
618 dec
.startProduction (this);
619 return bufferedImage
;
624 public int getNumImages(boolean allowSearch
)
630 public IIOMetadata
getImageMetadata(int i
)
635 public IIOMetadata
getStreamMetadata()
641 public Iterator
getImageTypes(int imageIndex
)
644 BufferedImage img
= getBufferedImage();
645 Vector vec
= new Vector();
646 vec
.add(new ImageTypeSpecifier(img
));
647 return vec
.iterator();
650 public int getHeight(int imageIndex
)
653 return getBufferedImage().getHeight();
656 public int getWidth(int imageIndex
)
659 return getBufferedImage().getWidth();
662 public void setInput(Object input
,
663 boolean seekForwardOnly
,
664 boolean ignoreMetadata
)
666 super.setInput(input
, seekForwardOnly
, ignoreMetadata
);
667 Object get
= getInput();
668 if (get
instanceof InputStream
)
669 dec
= new GdkPixbufDecoder((InputStream
) get
);
670 else if (get
instanceof DataInput
)
671 dec
= new GdkPixbufDecoder((DataInput
) get
);
673 throw new IllegalArgumentException("input object not supported: "
677 public BufferedImage
read(int imageIndex
, ImageReadParam param
)
680 return getBufferedImage ();
684 // remaining helper class and static method is a convenience for the Gtk
685 // peers, for loading a BufferedImage in off a disk file without going
686 // through the whole imageio system.
688 public static BufferedImage
createBufferedImage (String filename
)
690 GdkPixbufReader r
= new GdkPixbufReader (getReaderSpi(),
691 "png", // reader auto-detects, doesn't matter
692 new GdkPixbufDecoder (filename
));
693 return r
.getBufferedImage ();
696 public static BufferedImage
createBufferedImage (URL u
)
698 GdkPixbufReader r
= new GdkPixbufReader (getReaderSpi(),
699 "png", // reader auto-detects, doesn't matter
700 new GdkPixbufDecoder (u
));
701 return r
.getBufferedImage ();
704 public static BufferedImage
createBufferedImage (byte[] imagedata
, int imageoffset
,
707 GdkPixbufReader r
= new GdkPixbufReader (getReaderSpi(),
708 "png", // reader auto-detects, doesn't matter
709 new GdkPixbufDecoder (imagedata
,
712 return r
.getBufferedImage ();
715 public static BufferedImage
createBufferedImage (ImageProducer producer
)
717 GdkPixbufReader r
= new GdkPixbufReader (getReaderSpi(), "png" /* ignored */, null);
718 producer
.startProduction(r
);
719 return r
.getBufferedImage ();