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