1 /* GdkPixbufDecoder.java -- Image data decoding object
2    Copyright (C) 2003, 2004, 2005  Free Software Foundation, Inc.
3 
4 This file is part of GNU Classpath.
5 
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.
10 
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.
15 
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.
20 
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.
25 
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. */
37 
38 
39 package gnu.java.awt.peer.gtk;
40 
41 import gnu.classpath.Configuration;
42 
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.DataOutput;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.net.URL;
54 import java.util.ArrayList;
55 import java.util.Hashtable;
56 import java.util.Iterator;
57 import java.util.Locale;
58 import java.util.Vector;
59 
60 import javax.imageio.IIOImage;
61 import javax.imageio.ImageReadParam;
62 import javax.imageio.ImageReader;
63 import javax.imageio.ImageTypeSpecifier;
64 import javax.imageio.ImageWriteParam;
65 import javax.imageio.ImageWriter;
66 import javax.imageio.metadata.IIOMetadata;
67 import javax.imageio.spi.IIORegistry;
68 import javax.imageio.spi.ImageReaderSpi;
69 import javax.imageio.spi.ImageWriterSpi;
70 import javax.imageio.stream.ImageInputStream;
71 import javax.imageio.stream.ImageOutputStream;
72 
73 public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder
74 {
75   static
76   {
77     if (Configuration.INIT_LOAD_LIBRARY)
78       {
79         System.loadLibrary("gtkpeer");
80       }
initStaticState()81     initStaticState ();
82   }
83 
initStaticState()84   static native void initStaticState();
85   private final int native_state = GtkGenericPeer.getUniqueInteger ();
86 
87   // initState() has been called, but pumpDone() has not yet been called.
88   private boolean needsClose = false;
89 
90   // the current set of ImageConsumers for this decoder
91   Vector curr;
92 
93   // interface to GdkPixbuf
initState()94   native void initState ();
pumpBytes(byte[] bytes, int len)95   native void pumpBytes (byte[] bytes, int len) throws IOException;
pumpDone()96   native void pumpDone () throws IOException;
finish(boolean needsClose)97   native void finish (boolean needsClose);
streamImage(int[] bytes, String format, int width, int height, boolean hasAlpha, DataOutput sink)98   static native void streamImage(int[] bytes, String format, int width, int height, boolean hasAlpha, DataOutput sink);
99 
100   // gdk-pixbuf provids data in RGBA format
101   static final ColorModel cm = new DirectColorModel (32, 0xff000000,
102                                                      0x00ff0000,
103                                                      0x0000ff00,
104                                                      0x000000ff);
GdkPixbufDecoder(InputStream in)105   public GdkPixbufDecoder (InputStream in)
106   {
107     super (in);
108   }
109 
GdkPixbufDecoder(String filename)110   public GdkPixbufDecoder (String filename)
111   {
112     super (filename);
113   }
114 
GdkPixbufDecoder(URL url)115   public GdkPixbufDecoder (URL url)
116   {
117     super (url);
118   }
119 
GdkPixbufDecoder(byte[] imagedata, int imageoffset, int imagelength)120   public GdkPixbufDecoder (byte[] imagedata, int imageoffset, int imagelength)
121   {
122     super (imagedata, imageoffset, imagelength);
123   }
124 
125   // called back by native side: area_prepared_cb
areaPrepared(int width, int height)126   void areaPrepared (int width, int height)
127   {
128 
129     if (curr == null)
130       return;
131 
132     for (int i = 0; i < curr.size (); i++)
133       {
134         ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
135         ic.setDimensions (width, height);
136         ic.setColorModel (cm);
137         ic.setHints (ImageConsumer.RANDOMPIXELORDER);
138       }
139   }
140 
141   // called back by native side: area_updated_cb
areaUpdated(int x, int y, int width, int height, int pixels[], int scansize)142   void areaUpdated (int x, int y, int width, int height,
143                     int pixels[], int scansize)
144   {
145     if (curr == null)
146       return;
147 
148     for (int i = 0; i < curr.size (); i++)
149       {
150         ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
151         ic.setPixels (x, y, width, height, cm, pixels, 0, scansize);
152       }
153   }
154 
155   // called from an async image loader of one sort or another, this method
156   // repeatedly reads bytes from the input stream and passes them through a
157   // GdkPixbufLoader using the native method pumpBytes. pumpBytes in turn
158   // decodes the image data and calls back areaPrepared and areaUpdated on
159   // this object, feeding back decoded pixel blocks, which we pass to each
160   // of the ImageConsumers in the provided Vector.
161 
produce(Vector v, InputStream is)162   public void produce (Vector v, InputStream is) throws IOException
163   {
164     curr = v;
165 
166     byte bytes[] = new byte[4096];
167     int len = 0;
168     initState();
169     needsClose = true;
170     while ((len = is.read (bytes)) != -1)
171       pumpBytes (bytes, len);
172     pumpDone();
173     needsClose = false;
174 
175     for (int i = 0; i < curr.size (); i++)
176       {
177         ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
178         ic.imageComplete (ImageConsumer.STATICIMAGEDONE);
179       }
180 
181     curr = null;
182   }
183 
finalize()184   public void finalize()
185   {
186     finish(needsClose);
187   }
188 
189 
190   public static class ImageFormatSpec
191   {
192     public String name;
193     public boolean writable = false;
194     public ArrayList mimeTypes = new ArrayList();
195     public ArrayList extensions = new ArrayList();
196 
ImageFormatSpec(String name, boolean writable)197     public ImageFormatSpec(String name, boolean writable)
198     {
199       this.name = name;
200       this.writable = writable;
201     }
202 
addMimeType(String m)203     public synchronized void addMimeType(String m)
204     {
205       mimeTypes.add(m);
206     }
207 
addExtension(String e)208     public synchronized void addExtension(String e)
209     {
210       extensions.add(e);
211     }
212   }
213 
214   static ArrayList imageFormatSpecs;
215 
registerFormat(String name, boolean writable)216   public static ImageFormatSpec registerFormat(String name, boolean writable)
217   {
218     ImageFormatSpec ifs = new ImageFormatSpec(name, writable);
219     synchronized(GdkPixbufDecoder.class)
220       {
221         if (imageFormatSpecs == null)
222           imageFormatSpecs = new ArrayList();
223         imageFormatSpecs.add(ifs);
224       }
225     return ifs;
226   }
227 
getFormatNames(boolean writable)228   static String[] getFormatNames(boolean writable)
229   {
230     ArrayList names = new ArrayList();
231     synchronized (imageFormatSpecs)
232       {
233         Iterator i = imageFormatSpecs.iterator();
234         while (i.hasNext())
235           {
236             ImageFormatSpec ifs = (ImageFormatSpec) i.next();
237             if (writable && !ifs.writable)
238               continue;
239             names.add(ifs.name);
240 
241             /*
242              * In order to make the filtering code work, we need to register
243              * this type under every "format name" likely to be used as a synonym.
244              * This generally means "all the extensions people might use".
245              */
246 
247             Iterator j = ifs.extensions.iterator();
248             while (j.hasNext())
249               names.add((String) j.next());
250           }
251       }
252     Object[] objs = names.toArray();
253     String[] strings = new String[objs.length];
254     for (int i = 0; i < objs.length; ++i)
255       strings[i] = (String) objs[i];
256     return strings;
257   }
258 
getFormatExtensions(boolean writable)259   static String[] getFormatExtensions(boolean writable)
260   {
261     ArrayList extensions = new ArrayList();
262     synchronized (imageFormatSpecs)
263       {
264         Iterator i = imageFormatSpecs.iterator();
265         while (i.hasNext())
266           {
267             ImageFormatSpec ifs = (ImageFormatSpec) i.next();
268             if (writable && !ifs.writable)
269               continue;
270             Iterator j = ifs.extensions.iterator();
271             while (j.hasNext())
272               extensions.add((String) j.next());
273           }
274       }
275     Object[] objs = extensions.toArray();
276     String[] strings = new String[objs.length];
277     for (int i = 0; i < objs.length; ++i)
278       strings[i] = (String) objs[i];
279     return strings;
280   }
281 
getFormatMimeTypes(boolean writable)282   static String[] getFormatMimeTypes(boolean writable)
283   {
284     ArrayList mimeTypes = new ArrayList();
285     synchronized (imageFormatSpecs)
286       {
287         Iterator i = imageFormatSpecs.iterator();
288         while (i.hasNext())
289           {
290             ImageFormatSpec ifs = (ImageFormatSpec) i.next();
291             if (writable && !ifs.writable)
292               continue;
293             Iterator j = ifs.mimeTypes.iterator();
294             while (j.hasNext())
295               mimeTypes.add((String) j.next());
296           }
297       }
298     Object[] objs = mimeTypes.toArray();
299     String[] strings = new String[objs.length];
300     for (int i = 0; i < objs.length; ++i)
301       strings[i] = (String) objs[i];
302     return strings;
303   }
304 
305 
findFormatName(Object ext, boolean needWritable)306   static String findFormatName(Object ext, boolean needWritable)
307   {
308     if (ext == null)
309       return null;
310 
311     if (!(ext instanceof String))
312       throw new IllegalArgumentException("extension is not a string");
313 
314     String str = (String) ext;
315 
316     Iterator i = imageFormatSpecs.iterator();
317     while (i.hasNext())
318       {
319         ImageFormatSpec ifs = (ImageFormatSpec) i.next();
320 
321         if (needWritable && !ifs.writable)
322           continue;
323 
324         if (ifs.name.equals(str))
325           return str;
326 
327         Iterator j = ifs.extensions.iterator();
328         while (j.hasNext())
329           {
330             String extension = (String)j.next();
331             if (extension.equals(str))
332               return ifs.name;
333           }
334 
335         j = ifs.mimeTypes.iterator();
336         while (j.hasNext())
337           {
338             String mimeType = (String)j.next();
339             if (mimeType.equals(str))
340               return ifs.name;
341           }
342       }
343     throw new IllegalArgumentException("unknown extension '" + str + "'");
344   }
345 
346   private static GdkPixbufReaderSpi readerSpi;
347   private static GdkPixbufWriterSpi writerSpi;
348 
getReaderSpi()349   public static synchronized GdkPixbufReaderSpi getReaderSpi()
350   {
351     if (readerSpi == null)
352       readerSpi = new GdkPixbufReaderSpi();
353     return readerSpi;
354   }
355 
getWriterSpi()356   public static synchronized GdkPixbufWriterSpi getWriterSpi()
357   {
358     if (writerSpi == null)
359       writerSpi = new GdkPixbufWriterSpi();
360     return writerSpi;
361   }
362 
registerSpis(IIORegistry reg)363   public static void registerSpis(IIORegistry reg)
364   {
365     reg.registerServiceProvider(getReaderSpi(), ImageReaderSpi.class);
366     reg.registerServiceProvider(getWriterSpi(), ImageWriterSpi.class);
367   }
368 
369   public static class GdkPixbufWriterSpi extends ImageWriterSpi
370   {
GdkPixbufWriterSpi()371     public GdkPixbufWriterSpi()
372     {
373       super("GdkPixbuf", "2.x",
374             GdkPixbufDecoder.getFormatNames(true),
375             GdkPixbufDecoder.getFormatExtensions(true),
376             GdkPixbufDecoder.getFormatMimeTypes(true),
377             "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriter",
378             new Class[] { ImageOutputStream.class },
379             new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReaderSpi" },
380             false, null, null, null, null,
381             false, null, null, null, null);
382     }
383 
canEncodeImage(ImageTypeSpecifier ts)384     public boolean canEncodeImage(ImageTypeSpecifier ts)
385     {
386       return true;
387     }
388 
createWriterInstance(Object ext)389     public ImageWriter createWriterInstance(Object ext)
390     {
391       return new GdkPixbufWriter(this, ext);
392     }
393 
getDescription(java.util.Locale loc)394     public String getDescription(java.util.Locale loc)
395     {
396       return "GdkPixbuf Writer SPI";
397     }
398 
399   }
400 
401   public static class GdkPixbufReaderSpi extends ImageReaderSpi
402   {
GdkPixbufReaderSpi()403     public GdkPixbufReaderSpi()
404     {
405       super("GdkPixbuf", "2.x",
406             GdkPixbufDecoder.getFormatNames(false),
407             GdkPixbufDecoder.getFormatExtensions(false),
408             GdkPixbufDecoder.getFormatMimeTypes(false),
409             "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReader",
410             new Class[] { ImageInputStream.class },
411             new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriterSpi" },
412             false, null, null, null, null,
413             false, null, null, null, null);
414     }
415 
canDecodeInput(Object obj)416     public boolean canDecodeInput(Object obj)
417     {
418       return true;
419     }
420 
createReaderInstance(Object ext)421     public ImageReader createReaderInstance(Object ext)
422     {
423       return new GdkPixbufReader(this, ext);
424     }
425 
getDescription(Locale loc)426     public String getDescription(Locale loc)
427     {
428       return "GdkPixbuf Reader SPI";
429     }
430   }
431 
432   private static class GdkPixbufWriter
433     extends ImageWriter
434   {
435     String ext;
GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi, Object ext)436     public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi, Object ext)
437     {
438       super(ownerSpi);
439       this.ext = findFormatName(ext, true);
440     }
441 
convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param)442     public IIOMetadata convertImageMetadata (IIOMetadata inData,
443                                              ImageTypeSpecifier imageType,
444                                              ImageWriteParam param)
445     {
446       return null;
447     }
448 
convertStreamMetadata(IIOMetadata inData, ImageWriteParam param)449     public IIOMetadata convertStreamMetadata (IIOMetadata inData,
450                                               ImageWriteParam param)
451     {
452       return null;
453     }
454 
getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param)455     public IIOMetadata getDefaultImageMetadata (ImageTypeSpecifier imageType,
456                                                 ImageWriteParam param)
457     {
458       return null;
459     }
460 
getDefaultStreamMetadata(ImageWriteParam param)461     public IIOMetadata getDefaultStreamMetadata (ImageWriteParam param)
462     {
463       return null;
464     }
465 
write(IIOMetadata streamMetadata, IIOImage i, ImageWriteParam param)466   public void write (IIOMetadata streamMetadata, IIOImage i, ImageWriteParam param)
467     throws IOException
468     {
469       RenderedImage image = i.getRenderedImage();
470       Raster ras = image.getData();
471       int width = ras.getWidth();
472       int height = ras.getHeight();
473       ColorModel model = image.getColorModel();
474       int[] pixels = GdkGraphics2D.findSimpleIntegerArray (image.getColorModel(), ras);
475 
476       if (pixels == null)
477         {
478           BufferedImage img = new BufferedImage(width, height,
479                                                 (model != null && model.hasAlpha() ?
480                                                  BufferedImage.TYPE_INT_ARGB
481                                                  : BufferedImage.TYPE_INT_RGB));
482           int[] pix = new int[4];
483           for (int y = 0; y < height; ++y)
484             for (int x = 0; x < width; ++x)
485               img.setRGB(x, y, model.getRGB(ras.getPixel(x, y, pix)));
486           pixels = GdkGraphics2D.findSimpleIntegerArray (img.getColorModel(),
487                                                          img.getRaster());
488           model = img.getColorModel();
489         }
490 
491       processImageStarted(1);
492       streamImage(pixels, this.ext, width, height, model.hasAlpha(),
493                   (DataOutput) this.getOutput());
494       processImageComplete();
495     }
496   }
497 
498   private static class GdkPixbufReader
499     extends ImageReader
500     implements ImageConsumer
501   {
502     // ImageConsumer parts
503     GdkPixbufDecoder dec;
504     BufferedImage bufferedImage;
505     ColorModel defaultModel;
506     int width;
507     int height;
508     String ext;
509 
GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext)510     public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext)
511     {
512       super(ownerSpi);
513       this.ext = findFormatName(ext, false);
514     }
515 
GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext, GdkPixbufDecoder d)516     public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext, GdkPixbufDecoder d)
517     {
518       this(ownerSpi, ext);
519       dec = d;
520     }
521 
setDimensions(int w, int h)522     public void setDimensions(int w, int h)
523     {
524       processImageStarted(1);
525       width = w;
526       height = h;
527     }
528 
setProperties(Hashtable props)529     public void setProperties(Hashtable props) {}
530 
setColorModel(ColorModel model)531     public void setColorModel(ColorModel model)
532     {
533       defaultModel = model;
534     }
535 
setHints(int flags)536     public void setHints(int flags) {}
537 
setPixels(int x, int y, int w, int h, ColorModel model, byte[] pixels, int offset, int scansize)538     public void setPixels(int x, int y, int w, int h,
539                           ColorModel model, byte[] pixels,
540                           int offset, int scansize)
541     {
542     }
543 
setPixels(int x, int y, int w, int h, ColorModel model, int[] pixels, int offset, int scansize)544     public void setPixels(int x, int y, int w, int h,
545                           ColorModel model, int[] pixels,
546                           int offset, int scansize)
547     {
548       if (model == null)
549         model = defaultModel;
550 
551       if (bufferedImage == null)
552         {
553           bufferedImage = new BufferedImage (width, height, (model != null && model.hasAlpha() ?
554                                                              BufferedImage.TYPE_INT_ARGB
555                                                              : BufferedImage.TYPE_INT_RGB));
556         }
557 
558       int pixels2[];
559       if (model != null)
560         {
561           pixels2 = new int[pixels.length];
562           for (int yy = 0; yy < h; yy++)
563             for (int xx = 0; xx < w; xx++)
564               {
565                 int i = yy * scansize + xx;
566                 pixels2[i] = model.getRGB (pixels[i]);
567               }
568         }
569       else
570         pixels2 = pixels;
571 
572       bufferedImage.setRGB (x, y, w, h, pixels2, offset, scansize);
573       processImageProgress(y / (height == 0 ? 1 : height));
574     }
575 
imageComplete(int status)576     public void imageComplete(int status)
577     {
578       processImageComplete();
579     }
580 
getBufferedImage()581     public BufferedImage getBufferedImage()
582     {
583       if (bufferedImage == null && dec != null)
584         dec.startProduction (this);
585       return bufferedImage;
586     }
587 
588     // ImageReader parts
589 
getNumImages(boolean allowSearch)590     public int getNumImages(boolean allowSearch)
591       throws IOException
592     {
593       return 1;
594     }
595 
getImageMetadata(int i)596     public IIOMetadata getImageMetadata(int i)
597     {
598       return null;
599     }
600 
getStreamMetadata()601     public IIOMetadata getStreamMetadata()
602       throws IOException
603     {
604       return null;
605     }
606 
getImageTypes(int imageIndex)607     public Iterator getImageTypes(int imageIndex)
608       throws IOException
609     {
610       BufferedImage img = getBufferedImage();
611       Vector vec = new Vector();
612       vec.add(new ImageTypeSpecifier(img));
613       return vec.iterator();
614     }
615 
getHeight(int imageIndex)616     public int getHeight(int imageIndex)
617       throws IOException
618     {
619       return getBufferedImage().getHeight();
620     }
621 
getWidth(int imageIndex)622     public int getWidth(int imageIndex)
623       throws IOException
624     {
625       return getBufferedImage().getWidth();
626     }
627 
setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata)628     public void setInput(Object input,
629                          boolean seekForwardOnly,
630                          boolean ignoreMetadata)
631     {
632       super.setInput(input, seekForwardOnly, ignoreMetadata);
633       dec = new GdkPixbufDecoder((InputStream) getInput());
634     }
635 
read(int imageIndex, ImageReadParam param)636     public BufferedImage read(int imageIndex, ImageReadParam param)
637       throws IOException
638     {
639       return getBufferedImage ();
640     }
641   }
642 
643   // remaining helper class and static method is a convenience for the Gtk
644   // peers, for loading a BufferedImage in off a disk file without going
645   // through the whole imageio system.
646 
createBufferedImage(String filename)647   public static BufferedImage createBufferedImage (String filename)
648   {
649     GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(),
650                                              "png", // reader auto-detects, doesn't matter
651                                              new GdkPixbufDecoder (filename));
652     return r.getBufferedImage ();
653   }
654 
createBufferedImage(URL u)655   public static BufferedImage createBufferedImage (URL u)
656   {
657     GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(),
658                                              "png", // reader auto-detects, doesn't matter
659                                              new GdkPixbufDecoder (u));
660     return r.getBufferedImage ();
661   }
662 
createBufferedImage(byte[] imagedata, int imageoffset, int imagelength)663   public static BufferedImage createBufferedImage (byte[] imagedata, int imageoffset,
664                                                    int imagelength)
665   {
666     GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(),
667                                              "png", // reader auto-detects, doesn't matter
668                                              new GdkPixbufDecoder (imagedata,
669                                                                    imageoffset,
670                                                                    imagelength));
671     return r.getBufferedImage ();
672   }
673 
createBufferedImage(ImageProducer producer)674   public static BufferedImage createBufferedImage (ImageProducer producer)
675   {
676     GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(), "png" /* ignored */, null);
677     producer.startProduction(r);
678     return r.getBufferedImage ();
679   }
680 
681 }
682