OSDN Git Service

2006-06-13 Thomas Fitzsimmons <fitzsim@redhat.com>
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / java / awt / peer / gtk / GdkPixbufDecoder.java
1 /* GdkPixbufDecoder.java -- Image data decoding object
2    Copyright (C) 2003, 2004, 2005, 2006  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.DataInput;
51 import java.io.DataOutput;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.net.URL;
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;
60
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;
73
74 public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder
75 {
76   static 
77   {
78     System.loadLibrary("gtkpeer");
79
80     initStaticState ();
81   }
82   
83   /**
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
87    * widgets.
88    */
89   static Object pixbufLock = new Object();
90
91   static native void initStaticState();
92   private final int native_state = GtkGenericPeer.getUniqueInteger ();
93
94   // initState() has been called, but pumpDone() has not yet been called.
95   private boolean needsClose = false;
96
97   // the current set of ImageConsumers for this decoder
98   Vector curr;
99
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
107   /**
108    * Converts given image to bytes.
109    * Will call the GdkPixbufWriter for each chunk.
110    */
111   static native void streamImage(int[] bytes, String format,
112                                  int width, int height,
113                                  boolean hasAlpha, GdkPixbufWriter writer);
114
115   // gdk-pixbuf provids data in RGBA format
116   static final ColorModel cm = new DirectColorModel (32, 0xff000000, 
117                                                      0x00ff0000, 
118                                                      0x0000ff00, 
119                                                      0x000000ff);
120   public GdkPixbufDecoder (DataInput datainput)
121   {
122     super (datainput);
123   }
124
125   public GdkPixbufDecoder (InputStream in)
126   {
127     super (in);
128   }
129
130   public GdkPixbufDecoder (String filename)
131   {
132     super (filename);
133   }
134   
135   public GdkPixbufDecoder (URL url)
136   {
137     super (url);
138   }
139
140   public GdkPixbufDecoder (byte[] imagedata, int imageoffset, int imagelength)
141   {
142     super (imagedata, imageoffset, imagelength);
143   }
144
145   // called back by native side: area_prepared_cb
146   void areaPrepared (int width, int height)
147   {
148
149     if (curr == null)
150       return;
151
152     for (int i = 0; i < curr.size (); i++)
153       {
154         ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
155         ic.setDimensions (width, height);
156         ic.setColorModel (cm);
157         ic.setHints (ImageConsumer.RANDOMPIXELORDER);
158       }
159   }
160   
161   // called back by native side: area_updated_cb
162   void areaUpdated (int x, int y, int width, int height, 
163                     int pixels[], int scansize)
164   {
165     if (curr == null)
166       return;
167     
168     for (int i = 0; i < curr.size (); i++)
169       {
170         ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
171         ic.setPixels (x, y, width, height, cm, pixels, 0, scansize);
172       }
173   }
174   
175   // called from an async image loader of one sort or another, this method
176   // repeatedly reads bytes from the input stream and passes them through a
177   // GdkPixbufLoader using the native method pumpBytes. pumpBytes in turn
178   // decodes the image data and calls back areaPrepared and areaUpdated on
179   // this object, feeding back decoded pixel blocks, which we pass to each
180   // of the ImageConsumers in the provided Vector.
181
182   public void produce (Vector v, InputStream is) throws IOException
183   {
184     curr = v;
185
186     byte bytes[] = new byte[4096];
187     int len = 0;
188     synchronized(pixbufLock)
189       {
190         initState();
191       }
192     needsClose = true;
193
194     // Note: We don't want the pixbufLock while reading from the InputStream.
195     while ((len = is.read (bytes)) != -1)
196       {
197         synchronized(pixbufLock)
198           {
199             pumpBytes (bytes, len);
200           }
201       }
202
203     synchronized(pixbufLock)
204       {
205         pumpDone();
206       }
207
208     needsClose = false;
209     
210     for (int i = 0; i < curr.size (); i++)
211       {
212         ImageConsumer ic = (ImageConsumer) curr.elementAt (i);
213         ic.imageComplete (ImageConsumer.STATICIMAGEDONE);
214       }
215
216     curr = null;
217   }
218
219   public void finalize()
220   {
221     synchronized(pixbufLock)
222       {
223         finish(needsClose);
224       }
225   }
226
227
228   public static class ImageFormatSpec
229   {
230     public String name;
231     public boolean writable = false;    
232     public ArrayList mimeTypes = new ArrayList();
233     public ArrayList extensions = new ArrayList();
234
235     public ImageFormatSpec(String name, boolean writable)
236     {
237       this.name = name;
238       this.writable = writable;
239     }
240
241     public synchronized void addMimeType(String m)
242     {
243       mimeTypes.add(m);
244     }
245
246     public synchronized void addExtension(String e)
247     {
248       extensions.add(e);
249     }    
250   }
251
252   static ArrayList imageFormatSpecs;
253
254   public static ImageFormatSpec registerFormat(String name, boolean writable) 
255   {
256     ImageFormatSpec ifs = new ImageFormatSpec(name, writable);
257     synchronized(GdkPixbufDecoder.class)
258       {
259         if (imageFormatSpecs == null)
260           imageFormatSpecs = new ArrayList();
261         imageFormatSpecs.add(ifs);
262       }
263     return ifs;
264   }
265
266   static String[] getFormatNames(boolean writable)
267   {
268     ArrayList names = new ArrayList();
269     synchronized (imageFormatSpecs) 
270       {
271         Iterator i = imageFormatSpecs.iterator();
272         while (i.hasNext())
273           {
274             ImageFormatSpec ifs = (ImageFormatSpec) i.next();
275             if (writable && !ifs.writable)
276               continue;
277             names.add(ifs.name);
278
279             /* 
280              * In order to make the filtering code work, we need to register
281              * this type under every "format name" likely to be used as a synonym.
282              * This generally means "all the extensions people might use". 
283              */
284
285             Iterator j = ifs.extensions.iterator();
286             while (j.hasNext())
287               names.add((String) j.next());
288           }
289       }
290     Object[] objs = names.toArray();
291     String[] strings = new String[objs.length];
292     for (int i = 0; i < objs.length; ++i)
293       strings[i] = (String) objs[i];
294     return strings;
295   }
296
297   static String[] getFormatExtensions(boolean writable)
298   {
299     ArrayList extensions = new ArrayList();
300     synchronized (imageFormatSpecs) 
301       {
302         Iterator i = imageFormatSpecs.iterator();
303         while (i.hasNext())
304           {
305             ImageFormatSpec ifs = (ImageFormatSpec) i.next();
306             if (writable && !ifs.writable)
307               continue;
308             Iterator j = ifs.extensions.iterator();
309             while (j.hasNext())
310               extensions.add((String) j.next());
311           }
312       }
313     Object[] objs = extensions.toArray();
314     String[] strings = new String[objs.length];
315     for (int i = 0; i < objs.length; ++i)
316       strings[i] = (String) objs[i];
317     return strings;
318   }
319
320   static String[] getFormatMimeTypes(boolean writable)
321   {
322     ArrayList mimeTypes = new ArrayList();
323     synchronized (imageFormatSpecs) 
324       {
325         Iterator i = imageFormatSpecs.iterator();
326         while (i.hasNext())
327           {
328             ImageFormatSpec ifs = (ImageFormatSpec) i.next();
329             if (writable && !ifs.writable)
330               continue;
331             Iterator j = ifs.mimeTypes.iterator();
332             while (j.hasNext())
333               mimeTypes.add((String) j.next());
334           }
335       }
336     Object[] objs = mimeTypes.toArray();
337     String[] strings = new String[objs.length];
338     for (int i = 0; i < objs.length; ++i)
339       strings[i] = (String) objs[i];
340     return strings;
341   }
342
343   
344   static String findFormatName(Object ext, boolean needWritable)
345   {
346     if (ext == null)
347       return null;
348
349     if (!(ext instanceof String))
350       throw new IllegalArgumentException("extension is not a string");
351
352     String str = (String) ext;
353
354     Iterator i = imageFormatSpecs.iterator();
355     while (i.hasNext())
356       {
357         ImageFormatSpec ifs = (ImageFormatSpec) i.next();
358
359         if (needWritable && !ifs.writable)
360           continue;
361
362         if (ifs.name.equals(str))
363           return str;
364
365         Iterator j = ifs.extensions.iterator(); 
366         while (j.hasNext())
367           {
368             String extension = (String)j.next();
369             if (extension.equals(str))
370               return ifs.name;
371           }
372
373         j = ifs.mimeTypes.iterator(); 
374         while (j.hasNext())
375           {
376             String mimeType = (String)j.next();
377             if (mimeType.equals(str))
378               return ifs.name;
379           }
380       }      
381     throw new IllegalArgumentException("unknown extension '" + str + "'");
382   }
383
384   private static GdkPixbufReaderSpi readerSpi;
385   private static GdkPixbufWriterSpi writerSpi;
386
387   public static synchronized GdkPixbufReaderSpi getReaderSpi()
388   {
389     if (readerSpi == null)
390       readerSpi = new GdkPixbufReaderSpi();
391     return readerSpi;
392   }
393
394   public static synchronized GdkPixbufWriterSpi getWriterSpi()
395   {
396     if (writerSpi == null)
397       writerSpi = new GdkPixbufWriterSpi();
398     return writerSpi;
399   }
400
401   public static void registerSpis(IIORegistry reg) 
402   {
403     reg.registerServiceProvider(getReaderSpi(), ImageReaderSpi.class);
404     reg.registerServiceProvider(getWriterSpi(), ImageWriterSpi.class);
405   }
406
407   public static class GdkPixbufWriterSpi extends ImageWriterSpi
408   {
409     public GdkPixbufWriterSpi() 
410     {      
411       super("GdkPixbuf", "2.x",
412             GdkPixbufDecoder.getFormatNames(true), 
413             GdkPixbufDecoder.getFormatExtensions(true), 
414             GdkPixbufDecoder.getFormatMimeTypes(true),
415             "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriter",
416             new Class[] { ImageOutputStream.class },
417             new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReaderSpi" },
418             false, null, null, null, null,
419             false, null, null, null, null);
420     }
421
422     public boolean canEncodeImage(ImageTypeSpecifier ts)
423     {
424       return true;
425     }
426
427     public ImageWriter createWriterInstance(Object ext)
428     {
429       return new GdkPixbufWriter(this, ext);
430     }
431
432     public String getDescription(java.util.Locale loc)
433     {
434       return "GdkPixbuf Writer SPI";
435     }
436
437   }
438
439   public static class GdkPixbufReaderSpi extends ImageReaderSpi
440   {
441     public GdkPixbufReaderSpi() 
442     { 
443       super("GdkPixbuf", "2.x",
444             GdkPixbufDecoder.getFormatNames(false), 
445             GdkPixbufDecoder.getFormatExtensions(false), 
446             GdkPixbufDecoder.getFormatMimeTypes(false),
447             "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReader",
448             new Class[] { ImageInputStream.class },
449             new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriterSpi" },
450             false, null, null, null, null,
451             false, null, null, null, null);
452     }
453
454     public boolean canDecodeInput(Object obj) 
455     { 
456       return true; 
457     }
458
459     public ImageReader createReaderInstance(Object ext)
460     {
461       return new GdkPixbufReader(this, ext);
462     }
463
464     public String getDescription(Locale loc)
465     {
466       return "GdkPixbuf Reader SPI";
467     }
468   }
469
470   private static class GdkPixbufWriter
471     extends ImageWriter implements Runnable
472   {
473     String ext;
474     public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi, Object ext)
475     {
476       super(ownerSpi);
477       this.ext = findFormatName(ext, true);
478     }
479
480     public IIOMetadata convertImageMetadata (IIOMetadata inData,
481                                              ImageTypeSpecifier imageType,
482                                              ImageWriteParam param)
483     {
484       return null;
485     }
486
487     public IIOMetadata convertStreamMetadata (IIOMetadata inData,
488                                               ImageWriteParam param)
489     {
490       return null;
491     }
492
493     public IIOMetadata getDefaultImageMetadata (ImageTypeSpecifier imageType, 
494                                                 ImageWriteParam param)
495     {
496       return null;
497     }
498
499     public IIOMetadata getDefaultStreamMetadata (ImageWriteParam param)
500     {
501       return null;
502     }
503
504   public void write (IIOMetadata streamMetadata, IIOImage i, ImageWriteParam param)
505     throws IOException
506     {
507       RenderedImage image = i.getRenderedImage();
508       Raster ras = image.getData();
509       int width = ras.getWidth();
510       int height = ras.getHeight();
511       ColorModel model = image.getColorModel();
512       int[] pixels = CairoGraphics2D.findSimpleIntegerArray (image.getColorModel(), ras);
513       
514       if (pixels == null)
515         {
516           BufferedImage img;
517           if(model != null && model.hasAlpha())
518             img = CairoSurface.getBufferedImage(width, height);
519           img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
520           int[] pix = new int[4];
521           for (int y = 0; y < height; ++y)
522             for (int x = 0; x < width; ++x)
523               img.setRGB(x, y, model.getRGB(ras.getPixel(x, y, pix)));
524           pixels = CairoGraphics2D.findSimpleIntegerArray (img.getColorModel(), 
525                                                          img.getRaster());
526           model = img.getColorModel();
527         }
528
529       Thread workerThread = new Thread(this, "GdkPixbufWriter");
530       workerThread.start();
531       processImageStarted(1);
532       synchronized(pixbufLock)
533         {
534           streamImage(pixels, this.ext, width, height, model.hasAlpha(), 
535                       this);
536         }
537       synchronized(data)
538         {
539           data.add(DATADONE);
540           data.notifyAll();
541         }
542
543       while (workerThread.isAlive())
544         {
545           try
546             {
547               workerThread.join();
548             }
549           catch (InterruptedException ioe)
550             {
551               // Ignored.
552             }
553         }
554
555       if (exception != null)
556         throw exception;
557
558       processImageComplete();
559     }    
560
561     /**
562      * Object marking end of data from native streamImage code.
563      */
564     private static final Object DATADONE = new Object();
565
566     /**
567      * Holds the data gotten from the native streamImage code.
568      * A worker thread will pull data out.
569      * Needs to be synchronized for access.
570      * The special object DATADONE is added when all data has been delivered.
571      */
572     private ArrayList data = new ArrayList();
573
574     /**
575      * Holds any IOException thrown by the run method that needs
576      * to be rethrown by the write method.
577      */
578     private IOException exception;
579
580     /** Callback for streamImage native code. **/
581     private void write(byte[] bs)
582     {
583       synchronized(data)
584         {
585           data.add(bs);
586           data.notifyAll();
587         }
588     }
589
590     public void run()
591     {
592       boolean done = false;
593       while (!done)
594         {
595           synchronized(data)
596             {
597               while (data.isEmpty())
598                 {
599                   try
600                     {
601                       data.wait();
602                     }
603                   catch (InterruptedException ie)
604                     {
605                       /* ignore */
606                     }
607                 }
608
609               Object o = data.remove(0);
610               if (o == DATADONE)
611                 done = true;
612               else
613                 {
614                   DataOutput out = (DataOutput) getOutput();
615                   try
616                     {
617                       out.write((byte[]) o);
618                     }
619                   catch (IOException ioe)
620                     {
621                       // We are only interested in the first exception.
622                       if (exception == null)
623                         exception = ioe;
624                     }
625                 }
626             }
627         }
628     }
629   }
630
631   private static class GdkPixbufReader 
632     extends ImageReader
633     implements ImageConsumer
634   {
635     // ImageConsumer parts
636     GdkPixbufDecoder dec;
637     BufferedImage bufferedImage;
638     ColorModel defaultModel;
639     int width;
640     int height;
641     String ext;
642     
643     public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext)
644     {
645       super(ownerSpi);
646       this.ext = findFormatName(ext, false);
647     }
648
649     public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext, GdkPixbufDecoder d)
650     {
651       this(ownerSpi, ext);
652       dec = d;
653     }
654
655     public void setDimensions(int w, int h)
656     {
657       processImageStarted(1);
658       width = w;
659       height = h;
660     }
661     
662     public void setProperties(Hashtable props) {}
663
664     public void setColorModel(ColorModel model) 
665     {
666       defaultModel = model;
667     }
668
669     public void setHints(int flags) {}
670
671     public void setPixels(int x, int y, int w, int h, 
672                           ColorModel model, byte[] pixels, 
673                           int offset, int scansize)
674     {
675     }      
676
677     public void setPixels(int x, int y, int w, int h, 
678                           ColorModel model, int[] pixels, 
679                           int offset, int scansize)
680     {
681       if (model == null)
682         model = defaultModel;
683       
684       if (bufferedImage == null)
685         {
686           if(model != null && model.hasAlpha())
687             bufferedImage = new BufferedImage (width, height, BufferedImage.TYPE_INT_ARGB);
688           else
689             bufferedImage = new BufferedImage (width, height, BufferedImage.TYPE_INT_RGB);
690         }
691
692       int pixels2[];
693       if (model != null)
694         {
695           pixels2 = new int[pixels.length];
696           for (int yy = 0; yy < h; yy++)
697             for (int xx = 0; xx < w; xx++)
698               {
699                 int i = yy * scansize + xx;
700                 pixels2[i] = model.getRGB (pixels[i]);
701               }
702         }
703       else
704         pixels2 = pixels;
705
706       bufferedImage.setRGB (x, y, w, h, pixels2, offset, scansize);
707       processImageProgress(y / (height == 0 ? 1 : height));
708     }
709
710     public void imageComplete(int status) 
711     {
712       processImageComplete();
713     }
714
715     public BufferedImage getBufferedImage()
716     {
717       if (bufferedImage == null && dec != null)
718         dec.startProduction (this);
719       return bufferedImage;
720     }
721
722     // ImageReader parts
723
724     public int getNumImages(boolean allowSearch)
725       throws IOException
726     {
727       return 1;
728     }
729
730     public IIOMetadata getImageMetadata(int i) 
731     {
732       return null;
733     }
734
735     public IIOMetadata getStreamMetadata()
736       throws IOException
737     {
738       return null;
739     }
740
741     public Iterator getImageTypes(int imageIndex)
742       throws IOException
743     {
744       BufferedImage img = getBufferedImage();
745       Vector vec = new Vector();
746       vec.add(new ImageTypeSpecifier(img));
747       return vec.iterator();
748     }
749     
750     public int getHeight(int imageIndex)
751       throws IOException
752     {
753       return getBufferedImage().getHeight();
754     }
755
756     public int getWidth(int imageIndex)
757       throws IOException
758     {
759       return getBufferedImage().getWidth();
760     }
761
762     public void setInput(Object input,
763                          boolean seekForwardOnly,
764                          boolean ignoreMetadata)
765     {
766       super.setInput(input, seekForwardOnly, ignoreMetadata);
767       Object get = getInput();
768       if (get instanceof InputStream)
769         dec = new GdkPixbufDecoder((InputStream) get);
770       else if (get instanceof DataInput)
771         dec = new GdkPixbufDecoder((DataInput) get);
772       else
773         throw new IllegalArgumentException("input object not supported: "
774                                            + get);
775     }
776
777     public BufferedImage read(int imageIndex, ImageReadParam param)
778       throws IOException
779     {
780       return getBufferedImage ();
781     }
782   }
783 }