This is the mail archive of the
java-patches@gcc.gnu.org
mailing list for the Java project.
[gui][patch] implement imageio SPI for gdk-pixbuf
- From: graydon hoare <graydon at redhat dot com>
- To: java-patches at gcc dot gnu dot org
- Date: Tue, 04 Jan 2005 16:16:09 -0500
- Subject: [gui][patch] implement imageio SPI for gdk-pixbuf
hi,
this patch, which I've just committed to java-gui-branch, provides a
javax.imageio SPI for the gdk-pixbuf library, and corrects a variety of
small bugs in the existing imageio implementation. it also adds the
standard static entry points to imageio. so your basic actions like
ImageIO.write(ImageIO.read(new File("foo.jpg"),
"png", new File("bar.png")));
should now work. it can also use URLs and Input/OutputStreams. buffering
is pretty crude and purely in-memory, so you will pay about 2x size of
image worth of memory, temporarily, during a write.
the available input and output types are registered dynamically by
querying modules in gdk-pixbuf. currently, looking at the modules which
came with my copy of gdk-pixbuf, this means you get:
- loading capability for ani, bpm, gif, ico, jpeg, png, pnm, ras, tga,
tiff, wbmp, xbm and xpm
- saving capability for jpeg and png
of course the SPI is not available if you're not using the GTK toolkit;
it is registered dynamically depending on the choice of toolkit. also,
multi-frame i/o and all metadata operations are currently unsupported.
-graydon
2005-01-04 Graydon Hoare <graydon@redhat.com>
* gnu/java/awt/ClasspathToolkit.java
(registerImageIOSpis): New method.
* gnu/java/awt/image/ImageDecoder.java
(imageDecoder): New constructor using InputStream
(startProduction): Handle existing InputStream.
* gnu/java/awt/peer/gtk/GdkGraphics2D.java
(findSimpleIntegerArray): Make public and static.
(updateBufferedImage): Set each pixel, in a loop.
* gnu/java/awt/peer/gtk/GdkPixbufDecoder.java:
Implement ImageIO SPI classes.
(createBufferedImage): Rewrite in terms of SPI classes.
* gnu/java/awt/peer/gtk/GtkToolkit.java
(registerImageIOSpis): New method.
* java/lang/reflect/natMethod.cc
(_Jv_CallAnyMethodA): Borrow a patch from aph, applied to trunk,
which lets JNI call interface methods properly.
* javax/imageio/ImageIO.java
(WriterFormatFilter.filter): Fix copy-and-paste typos.
(WriterMIMETypeFilter.filter): Likewise.
(ImageReaderIterator): Pass extension argument through to SPI.
(getReadersByFilter): Likewise.
(getWritersByFilter): Likewise.
(getImageReadersByFormatName): Likewise.
(getImageReadersByMIMEType): Likewise.
(getImageReadersBySuffix): Likewise.
(getImageWritersByFormatName): Likewise.
(getImageWritersByMIMEType): Likewise.
(getImageWritersBySuffix): Likewise.
(read): Implement.
(write): Implement.
* javax/imageio/ImageReader.java
(progressListeners): Initialize.
(setInput): Implement.
* javax/imageio/ImageWriter.java
(progressListeners): Initialize.
(warningListeners): Likewise.
(warningLocales): Likewise.
(setOutput): Test "isInstance" rather than class equality.
* javax/imageio/spi/IIORegistry.java
(static): Add reader and writer SPIs.
(IIORegistry): Call ClasspathToolkit.registerImageIOSpis.
* jni/gtk-peer/gnu_java_awt_peer_gtk_GdkPixbufDecoder.c
(query_formats): New function.
(save_to_stream): Likewise.
(Java_gnu_java_awt_peer_gtk_GdkPixbufDecoder_streamImage): Likewise.
--- gnu/java/awt/ClasspathToolkit.java 4 Jan 2005 18:45:20 -0000 1.1.18.6
+++ gnu/java/awt/ClasspathToolkit.java 4 Jan 2005 20:47:59 -0000
@@ -62,6 +62,7 @@
import java.util.HashMap;
import java.util.Map;
import java.awt.peer.RobotPeer;
+import javax.imageio.spi.IIORegistry;
/**
@@ -356,6 +357,14 @@
public abstract RobotPeer createRobot (GraphicsDevice screen)
throws AWTException;
+ /**
+ * Used to register ImageIO SPIs provided by the toolkit.
+ */
+
+ public void registerImageIOSpis(IIORegistry reg)
+ {
+ }
+
public abstract boolean nativeQueueEmpty();
public abstract void wakeNativeQueue();
public abstract void iterateNativeQueue(EventQueue locked);
--- gnu/java/awt/image/ImageDecoder.java 24 Jun 2004 05:30:00 -0000 1.3.8.2
+++ gnu/java/awt/image/ImageDecoder.java 4 Jan 2005 20:47:59 -0000
@@ -74,6 +74,11 @@
this.url = url;
}
+ public ImageDecoder (InputStream is)
+ {
+ this.input = is;
+ }
+
public ImageDecoder (byte[] imagedata, int imageoffset, int imagelength)
{
data = imagedata;
@@ -108,17 +113,30 @@
// ImageDecoder constructors so that exceptions cause
// imageComplete to be called with an appropriate error
// status.
- if (url != null)
- input = url.openStream();
- else
- {
- if (filename != null)
- input = new FileInputStream (filename);
- else
- input = new ByteArrayInputStream (data, offset, length);
- }
-
- produce (list, input);
+ if (input == null)
+ {
+ try
+ {
+ if (url != null)
+ input = url.openStream();
+ else
+ {
+ if (filename != null)
+ input = new FileInputStream (filename);
+ else
+ input = new ByteArrayInputStream (data, offset, length);
+ }
+ produce (list, input);
+ }
+ finally
+ {
+ input = null;
+ }
+ }
+ else
+ {
+ produce (list, input);
+ }
}
catch (Exception e)
{
--- gnu/java/awt/peer/gtk/GdkGraphics2D.java 26 Dec 2004 13:07:29 -0000 1.7.2.25
+++ gnu/java/awt/peer/gtk/GdkGraphics2D.java 4 Jan 2005 20:47:59 -0000
@@ -451,7 +451,7 @@
return defaultHints;
}
- private final int[] findSimpleIntegerArray(ColorModel cm, Raster raster)
+ public static final int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
{
if (cm == null || raster == null)
return null;
@@ -494,10 +494,11 @@
{
if (bimage != null && pixelConversionRequired)
{
- bimage.getRaster().setPixels(0, 0,
- bimage.getRaster().getWidth (),
- bimage.getRaster().getHeight (),
- pixelBuffer);
+ int height = bimage.getHeight();
+ int width = bimage.getWidth();
+ for (int y = 0; y < height; ++y)
+ for (int x = 0; x < width; ++x)
+ bimage.setRGB(x, y, pixelBuffer[y*width+height]);
}
}
--- gnu/java/awt/peer/gtk/GdkPixbufDecoder.java 7 Dec 2004 00:30:11 -0000 1.3.10.4
+++ gnu/java/awt/peer/gtk/GdkPixbufDecoder.java 4 Jan 2005 20:47:59 -0000
@@ -42,17 +42,38 @@
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageProducer;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.WritableRaster;
+import java.io.DataOutput;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
import java.util.Vector;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageWriter;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.IIOImage;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+
public class GdkPixbufDecoder extends gnu.java.awt.image.ImageDecoder
{
static
@@ -65,20 +86,27 @@
}
native static void initStaticState ();
private final int native_state = GtkGenericPeer.getUniqueInteger ();
+ private boolean initialized = false;
// the current set of ImageConsumers for this decoder
Vector curr;
// interface to GdkPixbuf
native void initState ();
- native void pumpBytes (byte bytes[], int len);
+ native void pumpBytes (byte[] bytes, int len);
native void finish ();
+ static native void streamImage(int[] bytes, String format, int width, int height, boolean hasAlpha, DataOutput sink);
// gdk-pixbuf provids data in RGBA format
static final ColorModel cm = new DirectColorModel (32, 0xff000000,
0x00ff0000,
0x0000ff00,
0x000000ff);
+ public GdkPixbufDecoder (InputStream in)
+ {
+ super (in);
+ }
+
public GdkPixbufDecoder (String filename)
{
super (filename);
@@ -148,27 +176,345 @@
}
curr = null;
+ finish();
}
- // remaining helper class and static method is a convenience for the Gtk
- // peers, for loading a BufferedImage in off a disk file. one would think
- // this ought to be fairly straightforward, but it does not appear
- // anywhere else I can find.
- private static class BufferedImageBuilder implements ImageConsumer
+ public static class ImageFormatSpec
+ {
+ public String name;
+ public boolean writable = false;
+ public ArrayList mimeTypes = new ArrayList();
+ public ArrayList extensions = new ArrayList();
+
+ public ImageFormatSpec(String name, boolean writable)
+ {
+ this.name = name;
+ this.writable = writable;
+ }
+
+ public synchronized void addMimeType(String m)
+ {
+ mimeTypes.add(m);
+ }
+
+ public synchronized void addExtension(String e)
+ {
+ extensions.add(e);
+ }
+ }
+
+ static ArrayList imageFormatSpecs;
+
+ public static ImageFormatSpec registerFormat(String name, boolean writable)
+ {
+ ImageFormatSpec ifs = new ImageFormatSpec(name, writable);
+ synchronized(GdkPixbufDecoder.class)
+ {
+ if (imageFormatSpecs == null)
+ imageFormatSpecs = new ArrayList();
+ imageFormatSpecs.add(ifs);
+ }
+ return ifs;
+ }
+
+ static String[] getFormatNames(boolean writable)
+ {
+ ArrayList names = new ArrayList();
+ synchronized (imageFormatSpecs)
+ {
+ Iterator i = imageFormatSpecs.iterator();
+ while (i.hasNext())
+ {
+ ImageFormatSpec ifs = (ImageFormatSpec) i.next();
+ if (writable && !ifs.writable)
+ continue;
+ names.add(ifs.name);
+
+ /*
+ * In order to make the filtering code work, we need to register
+ * this type under every "format name" likely to be used as a synonym.
+ * This generally means "all the extensions people might use".
+ */
+
+ Iterator j = ifs.extensions.iterator();
+ while (j.hasNext())
+ names.add((String) j.next());
+ }
+ }
+ Object[] objs = names.toArray();
+ String[] strings = new String[objs.length];
+ for (int i = 0; i < objs.length; ++i)
+ strings[i] = (String) objs[i];
+ return strings;
+ }
+
+ static String[] getFormatExtensions(boolean writable)
+ {
+ ArrayList extensions = new ArrayList();
+ synchronized (imageFormatSpecs)
+ {
+ Iterator i = imageFormatSpecs.iterator();
+ while (i.hasNext())
+ {
+ ImageFormatSpec ifs = (ImageFormatSpec) i.next();
+ if (writable && !ifs.writable)
+ continue;
+ Iterator j = ifs.extensions.iterator();
+ while (j.hasNext())
+ extensions.add((String) j.next());
+ }
+ }
+ Object[] objs = extensions.toArray();
+ String[] strings = new String[objs.length];
+ for (int i = 0; i < objs.length; ++i)
+ strings[i] = (String) objs[i];
+ return strings;
+ }
+
+ static String[] getFormatMimeTypes(boolean writable)
+ {
+ ArrayList mimeTypes = new ArrayList();
+ synchronized (imageFormatSpecs)
+ {
+ Iterator i = imageFormatSpecs.iterator();
+ while (i.hasNext())
+ {
+ ImageFormatSpec ifs = (ImageFormatSpec) i.next();
+ if (writable && !ifs.writable)
+ continue;
+ Iterator j = ifs.mimeTypes.iterator();
+ while (j.hasNext())
+ mimeTypes.add((String) j.next());
+ }
+ }
+ Object[] objs = mimeTypes.toArray();
+ String[] strings = new String[objs.length];
+ for (int i = 0; i < objs.length; ++i)
+ strings[i] = (String) objs[i];
+ return strings;
+ }
+
+
+ static String findFormatName(Object ext, boolean needWritable)
+ {
+ if (ext == null)
+ throw new IllegalArgumentException("extension is null");
+
+ if (!(ext instanceof String))
+ throw new IllegalArgumentException("extension is not a string");
+
+ String str = (String) ext;
+
+ Iterator i = imageFormatSpecs.iterator();
+ while (i.hasNext())
+ {
+ ImageFormatSpec ifs = (ImageFormatSpec) i.next();
+
+ if (needWritable && !ifs.writable)
+ continue;
+
+ if (ifs.name.equals(str))
+ return str;
+
+ Iterator j = ifs.extensions.iterator();
+ while (j.hasNext())
+ {
+ String extension = (String)j.next();
+ if (extension.equals(str))
+ return ifs.name;
+ }
+
+ j = ifs.mimeTypes.iterator();
+ while (j.hasNext())
+ {
+ String mimeType = (String)j.next();
+ if (mimeType.equals(str))
+ return ifs.name;
+ }
+ }
+ throw new IllegalArgumentException("unknown extension '" + str + "'");
+ }
+
+ private static GdkPixbufReaderSpi readerSpi;
+ private static GdkPixbufWriterSpi writerSpi;
+
+ public static synchronized GdkPixbufReaderSpi getReaderSpi()
+ {
+ if (readerSpi == null)
+ readerSpi = new GdkPixbufReaderSpi();
+ return readerSpi;
+ }
+
+ public static synchronized GdkPixbufWriterSpi getWriterSpi()
+ {
+ if (writerSpi == null)
+ writerSpi = new GdkPixbufWriterSpi();
+ return writerSpi;
+ }
+
+ public static void registerSpis(IIORegistry reg)
+ {
+ reg.registerServiceProvider(getReaderSpi(), ImageReaderSpi.class);
+ reg.registerServiceProvider(getWriterSpi(), ImageWriterSpi.class);
+ }
+
+ public static class GdkPixbufWriterSpi extends ImageWriterSpi
+ {
+ public GdkPixbufWriterSpi()
+ {
+ super("GdkPixbuf", "2.x",
+ GdkPixbufDecoder.getFormatNames(true),
+ GdkPixbufDecoder.getFormatExtensions(true),
+ GdkPixbufDecoder.getFormatMimeTypes(true),
+ "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriter",
+ new Class[] { ImageOutputStream.class },
+ new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReaderSpi" },
+ false, null, null, null, null,
+ false, null, null, null, null);
+ }
+
+ public boolean canEncodeImage(ImageTypeSpecifier ts)
+ {
+ return true;
+ }
+
+ public ImageWriter createWriterInstance(Object ext)
+ {
+ return new GdkPixbufWriter(this, ext);
+ }
+
+ public String getDescription(java.util.Locale loc)
+ {
+ return "GdkPixbuf Writer SPI";
+ }
+
+ }
+
+ public static class GdkPixbufReaderSpi extends ImageReaderSpi
+ {
+ public GdkPixbufReaderSpi()
+ {
+ super("GdkPixbuf", "2.x",
+ GdkPixbufDecoder.getFormatNames(false),
+ GdkPixbufDecoder.getFormatExtensions(false),
+ GdkPixbufDecoder.getFormatMimeTypes(false),
+ "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufReader",
+ new Class[] { ImageInputStream.class },
+ new String[] { "gnu.java.awt.peer.gtk.GdkPixbufDecoder$GdkPixbufWriterSpi" },
+ false, null, null, null, null,
+ false, null, null, null, null);
+ }
+
+ public boolean canDecodeInput(Object obj)
+ {
+ return true;
+ }
+
+ public ImageReader createReaderInstance(Object ext)
+ {
+ return new GdkPixbufReader(this, ext);
+ }
+
+ public String getDescription(Locale loc)
+ {
+ return "GdkPixbuf Reader SPI";
+ }
+ }
+
+ private static class GdkPixbufWriter
+ extends ImageWriter
+ {
+ String ext;
+ public GdkPixbufWriter(GdkPixbufWriterSpi ownerSpi, Object ext)
+ {
+ super(ownerSpi);
+ this.ext = findFormatName(ext, true);
+ }
+
+ public IIOMetadata convertImageMetadata (IIOMetadata inData,
+ ImageTypeSpecifier imageType,
+ ImageWriteParam param)
+ {
+ return null;
+ }
+
+ public IIOMetadata convertStreamMetadata (IIOMetadata inData,
+ ImageWriteParam param)
+ {
+ return null;
+ }
+
+ public IIOMetadata getDefaultImageMetadata (ImageTypeSpecifier imageType,
+ ImageWriteParam param)
+ {
+ return null;
+ }
+
+ public IIOMetadata getDefaultStreamMetadata (ImageWriteParam param)
+ {
+ return null;
+ }
+
+ public void write (IIOMetadata streamMetadata, IIOImage i, ImageWriteParam param)
+ throws IOException
+ {
+ RenderedImage image = i.getRenderedImage();
+ Raster ras = image.getData();
+ int width = ras.getWidth();
+ int height = ras.getHeight();
+ ColorModel model = image.getColorModel();
+ int[] pixels = GdkGraphics2D.findSimpleIntegerArray (image.getColorModel(), ras);
+
+ if (pixels == null)
+ {
+ BufferedImage img = new BufferedImage(width, height,
+ (model != null && model.hasAlpha() ?
+ BufferedImage.TYPE_INT_ARGB
+ : BufferedImage.TYPE_INT_RGB));
+ int[] pix = new int[4];
+ for (int y = 0; y < height; ++y)
+ for (int x = 0; x < width; ++x)
+ img.setRGB(x, y, model.getRGB(ras.getPixel(x, y, pix)));
+ pixels = GdkGraphics2D.findSimpleIntegerArray (img.getColorModel(),
+ img.getRaster());
+ model = img.getColorModel();
+ }
+
+ processImageStarted(1);
+ streamImage(pixels, this.ext, width, height, model.hasAlpha(),
+ (DataOutput) this.getOutput());
+ processImageComplete();
+ }
+ }
+
+ private static class GdkPixbufReader
+ extends ImageReader
+ implements ImageConsumer
{
+ // ImageConsumer parts
+ GdkPixbufDecoder dec;
BufferedImage bufferedImage;
ColorModel defaultModel;
int width;
int height;
+ String ext;
+
+ public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext)
+ {
+ super(ownerSpi);
+ this.ext = findFormatName(ext, false);
+ }
- public BufferedImage getBufferedImage()
+ public GdkPixbufReader(GdkPixbufReaderSpi ownerSpi, Object ext, GdkPixbufDecoder d)
{
- return bufferedImage;
+ this(ownerSpi, ext);
+ dec = d;
}
public void setDimensions(int w, int h)
{
+ processImageStarted(1);
width = w;
height = h;
}
@@ -196,9 +542,12 @@
model = defaultModel;
if (bufferedImage == null)
- bufferedImage = new BufferedImage (width, height, (model != null && model.hasAlpha() ?
- BufferedImage.TYPE_INT_ARGB
- : BufferedImage.TYPE_INT_RGB));
+ {
+ bufferedImage = new BufferedImage (width, height, (model != null && model.hasAlpha() ?
+ BufferedImage.TYPE_INT_ARGB
+ : BufferedImage.TYPE_INT_RGB));
+ }
+
int pixels2[];
if (model != null)
{
@@ -214,43 +563,112 @@
pixels2 = pixels;
bufferedImage.setRGB (x, y, w, h, pixels2, offset, scansize);
+ processImageProgress(y / (height == 0 ? 1 : height));
+ }
+
+ public void imageComplete(int status)
+ {
+ processImageComplete();
+ }
+
+ public BufferedImage getBufferedImage()
+ {
+ if (bufferedImage == null && dec != null)
+ dec.startProduction (this);
+ return bufferedImage;
+ }
+
+ // ImageReader parts
+
+ public int getNumImages(boolean allowSearch)
+ throws IOException
+ {
+ return 1;
+ }
+
+ public IIOMetadata getImageMetadata(int i)
+ {
+ return null;
+ }
+
+ public IIOMetadata getStreamMetadata()
+ throws IOException
+ {
+ return null;
+ }
+
+ public Iterator getImageTypes(int imageIndex)
+ throws IOException
+ {
+ BufferedImage img = getBufferedImage();
+ Vector vec = new Vector();
+ vec.add(new ImageTypeSpecifier(img));
+ return vec.iterator();
+ }
+
+ public int getHeight(int imageIndex)
+ throws IOException
+ {
+ return getBufferedImage().getHeight();
+ }
+
+ public int getWidth(int imageIndex)
+ throws IOException
+ {
+ return getBufferedImage().getWidth();
}
- public void imageComplete(int status) {}
+ public void setInput(Object input,
+ boolean seekForwardOnly,
+ boolean ignoreMetadata)
+ {
+ super.setInput(input, seekForwardOnly, ignoreMetadata);
+ dec = new GdkPixbufDecoder((InputStream) getInput());
+ }
+
+ public BufferedImage read(int imageIndex, ImageReadParam param)
+ throws IOException
+ {
+ return getBufferedImage ();
+ }
}
+ // remaining helper class and static method is a convenience for the Gtk
+ // peers, for loading a BufferedImage in off a disk file without going
+ // through the whole imageio system.
+
public static BufferedImage createBufferedImage (String filename)
{
- BufferedImageBuilder bb = new BufferedImageBuilder ();
- GdkPixbufDecoder dec = new GdkPixbufDecoder (filename);
- dec.startProduction (bb);
- return bb.getBufferedImage ();
+ GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(),
+ "png", // reader auto-detects, doesn't matter
+ new GdkPixbufDecoder (filename));
+ return r.getBufferedImage ();
}
public static BufferedImage createBufferedImage (URL u)
{
- BufferedImageBuilder bb = new BufferedImageBuilder ();
- GdkPixbufDecoder dec = new GdkPixbufDecoder (u);
- dec.startProduction (bb);
- return bb.getBufferedImage ();
+ GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(),
+ "png", // reader auto-detects, doesn't matter
+ new GdkPixbufDecoder (u));
+ return r.getBufferedImage ();
}
public static BufferedImage createBufferedImage (byte[] imagedata, int imageoffset,
int imagelength)
{
- BufferedImageBuilder bb = new BufferedImageBuilder ();
- GdkPixbufDecoder dec = new GdkPixbufDecoder (imagedata, imageoffset, imagelength);
- dec.startProduction (bb);
- return bb.getBufferedImage ();
+ GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(),
+ "png", // reader auto-detects, doesn't matter
+ new GdkPixbufDecoder (imagedata,
+ imageoffset,
+ imagelength));
+ return r.getBufferedImage ();
}
public static BufferedImage createBufferedImage (ImageProducer producer)
{
- BufferedImageBuilder bb = new BufferedImageBuilder ();
- producer.startProduction(bb);
- return bb.getBufferedImage ();
+ GdkPixbufReader r = new GdkPixbufReader (getReaderSpi(), "png" /* ignored */, null);
+ producer.startProduction(r);
+ return r.getBufferedImage ();
}
-
-
}
--- gnu/java/awt/peer/gtk/GtkToolkit.java 4 Jan 2005 18:45:21 -0000 1.8.2.16
+++ gnu/java/awt/peer/gtk/GtkToolkit.java 4 Jan 2005 20:47:59 -0000
@@ -649,6 +649,11 @@
return new GdkRobotPeer (screen);
}
+ public void registerImageIOSpis(IIORegistry reg)
+ {
+ GdkPixbufDecoder.registerSpis(reg);
+ }
+
public native boolean nativeQueueEmpty();
public native void wakeNativeQueue();
public native void iterateNativeQueue(EventQueue locked);
--- java/lang/reflect/natMethod.cc 24 Jun 2004 05:30:29 -0000 1.36.10.1
+++ java/lang/reflect/natMethod.cc 4 Jan 2005 20:47:59 -0000
@@ -30,6 +30,7 @@
#include <java/lang/Double.h>
#include <java/lang/IllegalAccessException.h>
#include <java/lang/IllegalArgumentException.h>
+#include <java/lang/IncompatibleClassChangeError.h>
#include <java/lang/NullPointerException.h>
#include <java/lang/ArrayIndexOutOfBoundsException.h>
#include <java/lang/VirtualMachineError.h>
@@ -483,7 +484,26 @@
{
_Jv_VTable *vtable = *(_Jv_VTable **) obj;
if (iface == NULL)
- ncode = vtable->get_method (meth->index);
+ {
+ if (is_jni_call && Modifier::isAbstract (meth->accflags))
+ {
+ // With JNI we don't know if this is an interface call
+ // or a call to an abstract method. Look up the method
+ // by name, the slow way.
+ _Jv_Method *concrete_meth
+ = _Jv_LookupDeclaredMethod (vtable->clas,
+ meth->name,
+ meth->signature);
+ if (concrete_meth == NULL
+ || concrete_meth->ncode == NULL
+ || Modifier::isAbstract(concrete_meth->accflags))
+ throw new java::lang::IncompatibleClassChangeError
+ (_Jv_GetMethodString (vtable->clas, meth->name));
+ ncode = concrete_meth->ncode;
+ }
+ else
+ ncode = vtable->get_method (meth->index);
+ }
else
ncode = _Jv_LookupInterfaceMethodIdx (vtable->clas, iface, meth->index);
}
--- javax/imageio/ImageIO.java 7 Dec 2004 16:54:48 -0000 1.2.4.1
+++ javax/imageio/ImageIO.java 4 Jan 2005 20:48:00 -0000
@@ -38,8 +38,15 @@
package javax.imageio;
+import java.awt.image.RenderedImage;
+import java.awt.image.BufferedImage;
+import java.net.URL;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -48,6 +55,10 @@
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
public final class ImageIO
{
@@ -142,14 +153,14 @@
public boolean filter(Object provider)
{
- if (provider instanceof ImageReaderSpi)
+ if (provider instanceof ImageWriterSpi)
{
- ImageReaderSpi spi = (ImageReaderSpi) provider;
+ ImageWriterSpi spi = (ImageWriterSpi) provider;
String[] formatNames = spi.getFormatNames();
for (int i = formatNames.length - 1; i >= 0; --i)
- if (formatName.equals(formatNames[i]))
- return true;
+ if (formatName.equals(formatNames[i]))
+ return true;
}
return false;
@@ -167,7 +178,7 @@
public boolean filter(Object provider)
{
- if (provider instanceof ImageReaderSpi)
+ if (provider instanceof ImageWriterSpi)
{
ImageWriterSpi spi = (ImageWriterSpi) provider;
String[] mimetypes = spi.getMIMETypes();
@@ -192,7 +203,7 @@
public boolean filter(Object provider)
{
- if (provider instanceof ImageReaderSpi)
+ if (provider instanceof ImageWriterSpi)
{
ImageWriterSpi spi = (ImageWriterSpi) provider;
String[] suffixes = spi.getFileSuffixes();
@@ -209,10 +220,12 @@
private static final class ImageReaderIterator implements Iterator
{
Iterator it;
+ Object readerExtension;
- public ImageReaderIterator(Iterator it)
+ public ImageReaderIterator(Iterator it, Object readerExtension)
{
this.it = it;
+ this.readerExtension = readerExtension;
}
public boolean hasNext()
@@ -224,7 +237,7 @@
{
try
{
- return ((ImageReaderSpi) it.next()).createReaderInstance();
+ return ((ImageReaderSpi) it.next()).createReaderInstance(readerExtension);
}
catch (IOException e)
{
@@ -241,10 +254,12 @@
private static final class ImageWriterIterator implements Iterator
{
Iterator it;
+ Object writerExtension;
- public ImageWriterIterator(Iterator it)
+ public ImageWriterIterator(Iterator it, Object writerExtension)
{
this.it = it;
+ this.writerExtension = writerExtension;
}
public boolean hasNext()
@@ -256,7 +271,7 @@
{
try
{
- return ((ImageWriterSpi) it.next()).createWriterInstance();
+ return ((ImageWriterSpi) it.next()).createWriterInstance(writerExtension);
}
catch (IOException e)
{
@@ -274,12 +289,13 @@
private static boolean useCache = true;
private static Iterator getReadersByFilter(Class type,
- ServiceRegistry.Filter filter)
+ ServiceRegistry.Filter filter,
+ Object readerExtension)
{
try
{
Iterator it = getRegistry().getServiceProviders(type, filter, true);
- return new ImageReaderIterator(it);
+ return new ImageReaderIterator(it, readerExtension);
}
catch (IllegalArgumentException e)
{
@@ -288,12 +304,13 @@
}
private static Iterator getWritersByFilter(Class type,
- ServiceRegistry.Filter filter)
+ ServiceRegistry.Filter filter,
+ Object writerExtension)
{
try
{
Iterator it = getRegistry().getServiceProviders(type, filter, true);
- return new ImageWriterIterator(it);
+ return new ImageWriterIterator(it, writerExtension);
}
catch (IllegalArgumentException e)
{
@@ -312,7 +329,8 @@
throw new IllegalArgumentException("formatName may not be null");
return getReadersByFilter(ImageReaderSpi.class,
- new ReaderFormatFilter(formatName));
+ new ReaderFormatFilter(formatName),
+ formatName);
}
public static Iterator getImageReadersByMIMEType(String MIMEType)
@@ -321,7 +339,8 @@
throw new IllegalArgumentException("MIMEType may not be null");
return getReadersByFilter(ImageReaderSpi.class,
- new ReaderMIMETypeFilter(MIMEType));
+ new ReaderMIMETypeFilter(MIMEType),
+ MIMEType);
}
public static Iterator getImageReadersBySuffix(String fileSuffix)
@@ -330,7 +349,8 @@
throw new IllegalArgumentException("formatName may not be null");
return getReadersByFilter(ImageReaderSpi.class,
- new ReaderSuffixFilter(fileSuffix));
+ new ReaderSuffixFilter(fileSuffix),
+ fileSuffix);
}
public static Iterator getImageWritersByFormatName(String formatName)
@@ -339,7 +359,8 @@
throw new IllegalArgumentException("formatName may not be null");
return getWritersByFilter(ImageWriterSpi.class,
- new WriterFormatFilter(formatName));
+ new WriterFormatFilter(formatName),
+ formatName);
}
public static Iterator getImageWritersByMIMEType(String MIMEType)
@@ -348,7 +369,8 @@
throw new IllegalArgumentException("MIMEType may not be null");
return getWritersByFilter(ImageWriterSpi.class,
- new WriterMIMETypeFilter(MIMEType));
+ new WriterMIMETypeFilter(MIMEType),
+ MIMEType);
}
public static Iterator getImageWritersBySuffix(String fileSuffix)
@@ -357,7 +379,8 @@
throw new IllegalArgumentException("fileSuffix may not be null");
return getWritersByFilter(ImageWriterSpi.class,
- new WriterSuffixFilter(fileSuffix));
+ new WriterSuffixFilter(fileSuffix),
+ fileSuffix);
}
public static String[] getReaderFormatNames()
@@ -496,4 +519,87 @@
{
ImageIO.useCache = useCache;
}
+
+ /*
+ * "Standard" simplified entry points.
+ */
+
+ public static boolean write(RenderedImage im,
+ String formatName,
+ File output)
+ throws IOException
+ {
+ return write(im, formatName, new FileOutputStream(output));
+ }
+
+ public static boolean write(RenderedImage im,
+ String formatName,
+ OutputStream output)
+ throws IOException
+ {
+ return write(im, formatName, new MemoryCacheImageOutputStream(output));
+ }
+
+
+ public static boolean write(RenderedImage im,
+ String formatName,
+ ImageOutputStream output)
+ throws IOException
+ {
+ Iterator writers = getImageWritersByFormatName(formatName);
+ IIOImage img = new IIOImage(im, null, null);
+ while (writers.hasNext())
+ {
+ ImageWriter w = (ImageWriter) writers.next();
+ try
+ {
+ w.setOutput(output);
+ }
+ catch (IllegalArgumentException e)
+ {
+ continue;
+ }
+
+ w.write(null, img, null);
+ output.close();
+ return true;
+ }
+ return false;
+ }
+
+ public static BufferedImage read(ImageInputStream stream)
+ throws IOException
+ {
+ Iterator providers = getRegistry().getServiceProviders(ImageReaderSpi.class, true);
+ while (providers.hasNext())
+ {
+ ImageReaderSpi spi = (ImageReaderSpi) providers.next();
+ if (spi.canDecodeInput(stream))
+ {
+ ImageReader reader = spi.createReaderInstance();
+ reader.setInput(stream);
+ return reader.read(0, null);
+ }
+ }
+ return null;
+ }
+
+ public static BufferedImage read(URL input)
+ throws IOException
+ {
+ return read(input.openStream());
+ }
+
+ public static BufferedImage read(InputStream input)
+ throws IOException
+ {
+ return read(new MemoryCacheImageInputStream(input));
+ }
+
+ public static BufferedImage read(File input)
+ throws IOException
+ {
+ return read(new FileInputStream(input));
+ }
+
}
--- javax/imageio/ImageReader.java 7 Dec 2004 16:54:48 -0000 1.1.16.2
+++ javax/imageio/ImageReader.java 4 Jan 2005 20:48:00 -0000
@@ -51,6 +51,7 @@
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
public abstract class ImageReader
{
@@ -62,7 +63,7 @@
protected Locale locale;
protected int minIndex;
protected ImageReaderSpi originatingProvider;
- protected List progressListeners;
+ protected List progressListeners = new ArrayList();
protected boolean seekForwardOnly;
protected List updateListeners = new ArrayList();
protected List warningListeners = new ArrayList();
@@ -156,6 +157,42 @@
public abstract Iterator getImageTypes(int imageIndex)
throws IOException;
+ public void setInput(Object input,
+ boolean seekForwardOnly,
+ boolean ignoreMetadata)
+ {
+ Class[] okClasses = originatingProvider.getInputTypes();
+ if (okClasses == null)
+ {
+ if (!(input instanceof ImageInputStream))
+ throw new IllegalArgumentException();
+ }
+ else
+ {
+ boolean classOk = false;
+ for (int i = 0; i < okClasses.length; ++i)
+ if (okClasses[i].isInstance(input))
+ classOk = true;
+ if (!classOk)
+ throw new IllegalArgumentException();
+ }
+
+ this.input = input;
+ this.seekForwardOnly = seekForwardOnly;
+ this.ignoreMetadata = ignoreMetadata;
+ this.minIndex = 0;
+ }
+
+ public void setInput(Object in, boolean seekForwardOnly)
+ {
+ setInput(in, seekForwardOnly, false);
+ }
+
+ public void setInput(Object in)
+ {
+ setInput(in, false, false);
+ }
+
public Object getInput()
{
return input;
--- javax/imageio/ImageWriter.java 7 Dec 2004 16:54:48 -0000 1.1.16.2
+++ javax/imageio/ImageWriter.java 4 Jan 2005 20:48:00 -0000
@@ -40,6 +40,7 @@
import java.awt.Dimension;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -59,9 +60,9 @@
protected Locale locale;
protected ImageWriterSpi originatingProvider;
protected Object output;
- protected List progressListeners;
- protected List warningListeners;
- protected List warningLocales;
+ protected List progressListeners = new ArrayList();
+ protected List warningListeners = new ArrayList();
+ protected List warningLocales = new ArrayList();
protected ImageWriter(ImageWriterSpi originatingProvider)
{
@@ -371,11 +372,11 @@
if (originatingProvider != null)
types = originatingProvider.getOutputTypes();
-
+
if (types != null)
for (int i = types.length - 1; i >= 0; --i)
- if (types[i].equals(output.getClass()))
- found = true;
+ if (types[i].isInstance(output))
+ found = true;
if (! found)
throw new IllegalArgumentException("output type not available");
--- javax/imageio/spi/IIORegistry.java 7 Dec 2004 16:54:50 -0000 1.1.12.1
+++ javax/imageio/spi/IIORegistry.java 4 Jan 2005 20:48:00 -0000
@@ -39,7 +39,9 @@
package javax.imageio.spi;
import gnu.classpath.ServiceFactory;
+import gnu.java.awt.ClasspathToolkit;
+import java.awt.Toolkit;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -52,8 +54,8 @@
static
{
- //defaultCategories.add(ImageReaderSpi.class);
- //defaultCategories.add(ImageWriterSpi.class);
+ defaultCategories.add(ImageReaderSpi.class);
+ defaultCategories.add(ImageWriterSpi.class);
defaultCategories.add(ImageTranscoderSpi.class);
defaultCategories.add(ImageInputStreamSpi.class);
defaultCategories.add(ImageOutputStreamSpi.class);
@@ -78,6 +80,8 @@
super(defaultCategories.iterator());
// XXX: Register built-in Spis here.
+
+ ((ClasspathToolkit)Toolkit.getDefaultToolkit()).registerImageIOSpis(this);
registerApplicationClasspathSpis();
}
--- jni/gtk-peer/gnu_java_awt_peer_gtk_GdkPixbufDecoder.c 7 Nov 2004 00:04:27 -0000 1.2.10.6
+++ jni/gtk-peer/gnu_java_awt_peer_gtk_GdkPixbufDecoder.c 4 Jan 2005 20:48:00 -0000
@@ -60,6 +60,8 @@
static jmethodID areaPreparedID;
static jmethodID areaUpdatedID;
+static jmethodID dataOutputWriteID;
+static jmethodID registerFormatID;
static void
area_prepared (GdkPixbufLoader *loader,
@@ -193,10 +195,72 @@
NSA_SET_PB_PTR (env, obj, loader);
}
+static void
+query_formats (JNIEnv *env, jclass clazz)
+{
+ jobject jformat;
+ GSList *formats, *f;
+ GdkPixbufFormat *format;
+ char **ch, *name;
+
+ jclass formatClass;
+ jmethodID addExtensionID;
+ jmethodID addMimeTypeID;
+
+ formatClass = (*env)->FindClass
+ (env, "gnu/java/awt/peer/gtk/GdkPixbufDecoder$ImageFormatSpec");
+
+ g_assert(formatClass != NULL);
+
+ addExtensionID = (*env)->GetMethodID (env, formatClass,
+ "addExtension",
+ "(Ljava/lang/String;)V");
+
+ addMimeTypeID = (*env)->GetMethodID (env, formatClass,
+ "addMimeType",
+ "(Ljava/lang/String;)V");
+
+ formats = gdk_pixbuf_get_formats ();
+
+ for (f = formats; f; f = f->next)
+ {
+ format = (GdkPixbufFormat *) f->data;
+ name = gdk_pixbuf_format_get_name(format);
+
+ jformat = (*env)->CallStaticObjectMethod
+ (env, clazz, registerFormatID,
+ (*env)->NewStringUTF(env, name),
+ (jboolean) gdk_pixbuf_format_is_writable(format));
+
+ g_assert(jformat != NULL);
+
+ ch = gdk_pixbuf_format_get_extensions(format);
+ while (*ch)
+ {
+ (*env)->CallVoidMethod (env, jformat, addExtensionID,
+ (*env)->NewStringUTF(env, *ch));
+ ++ch;
+ }
+
+ ch = gdk_pixbuf_format_get_mime_types(format);
+ while (*ch)
+ {
+ (*env)->CallVoidMethod (env, jformat, addMimeTypeID,
+ (*env)->NewStringUTF(env, *ch));
+ ++ch;
+ }
+ }
+
+ g_slist_free(formats);
+}
+
+
JNIEXPORT void JNICALL
Java_gnu_java_awt_peer_gtk_GdkPixbufDecoder_initStaticState
(JNIEnv *env, jclass clazz)
{
+ jclass dataOutputClass;
+
(*env)->GetJavaVM(env, &vm);
areaPreparedID = (*env)->GetMethodID (env, clazz,
@@ -206,6 +270,20 @@
areaUpdatedID = (*env)->GetMethodID (env, clazz,
"areaUpdated",
"(IIII[II)V");
+
+ registerFormatID = (*env)->GetStaticMethodID
+ (env, clazz,
+ "registerFormat",
+ "(Ljava/lang/String;Z)"
+ "Lgnu/java/awt/peer/gtk/GdkPixbufDecoder$ImageFormatSpec;");
+
+
+ dataOutputClass = (*env)->FindClass(env, "java/io/DataOutput");
+ dataOutputWriteID = (*env)->GetMethodID (env, dataOutputClass,
+ "write", "([B)V");
+
+ query_formats (env, clazz);
+
NSA_PB_INIT (env, clazz);
}
@@ -226,6 +304,115 @@
gdk_threads_leave ();
}
+struct stream_save_request
+{
+ JNIEnv *env;
+ jobject *stream;
+};
+
+static gboolean
+save_to_stream(const gchar *buf,
+ gsize count,
+ GError **error __attribute__((unused)),
+ gpointer data)
+{
+ struct stream_save_request *ssr = (struct stream_save_request *)data;
+
+ jbyteArray jbuf;
+ jbyte *cbuf;
+
+ gdk_threads_leave ();
+ jbuf = (*(ssr->env))->NewByteArray ((ssr->env), count);
+ cbuf = (*(ssr->env))->GetByteArrayElements ((ssr->env), jbuf, NULL);
+ memcpy (cbuf, buf, count);
+ (*(ssr->env))->ReleaseByteArrayElements ((ssr->env), jbuf, cbuf, 0);
+ (*(ssr->env))->CallVoidMethod ((ssr->env), *(ssr->stream),
+ dataOutputWriteID, jbuf);
+ gdk_threads_enter ();
+ return TRUE;
+}
+
+
+JNIEXPORT void JNICALL
+Java_gnu_java_awt_peer_gtk_GdkPixbufDecoder_streamImage
+(JNIEnv *env, jclass clazz __attribute__((unused)),
+ jintArray jarr, jstring jenctype, jint width, jint height,
+ jboolean hasAlpha, jobject stream)
+{
+ GdkPixbuf* pixbuf;
+ jint *ints;
+ guchar a, r, g, b, *pix, *p;
+ GError *err = NULL;
+ const char *enctype;
+ int i;
+
+ struct stream_save_request ssr;
+ ssr.stream = &stream;
+ ssr.env = env;
+
+ ints = (*env)->GetIntArrayElements (env, jarr, NULL);
+ pix = g_malloc(width * height * (hasAlpha ? 4 : 3));
+
+ enctype = (*env)->GetStringUTFChars (env, jenctype, NULL);
+ g_assert(enctype != NULL);
+
+ g_assert (pix != NULL);
+ g_assert (ints != NULL);
+
+ p = pix;
+ for (i = 0; i < width*height; ++i)
+ {
+ /*
+ * Java encodes pixels as integers in a predictable arithmetic order:
+ * 0xAARRGGBB. Since these are jints, JNI has already byte-swapped
+ * them for us if necessary, so they're in "our" endianness, whatever
+ * that is. It uses 4 bytes per pixel whether or not there's an alpha
+ * channel.
+ */
+
+ a = 0xff & (ints[i] >> 24);
+ r = 0xff & (ints[i] >> 16);
+ g = 0xff & (ints[i] >> 8);
+ b = 0xff & ints[i];
+
+ /*
+ * GDK-pixbuf has a very different storage model:
+ *
+ * - A different alpha order (alpha after colors).
+ * - A different packing model (no alpha -> 3-bytes-per-pixel).
+ * - A different "RGB" order (host memory order, not endian-neutral).
+ */
+
+ *p++ = r;
+ *p++ = g;
+ *p++ = b;
+ if (hasAlpha)
+ *p++ = a;
+ }
+
+ gdk_threads_enter ();
+ pixbuf = gdk_pixbuf_new_from_data (pix,
+ GDK_COLORSPACE_RGB,
+ (gboolean) hasAlpha,
+ 8, width, height,
+ width * (hasAlpha ? 4 : 3), /* rowstride */
+ NULL, NULL);
+ g_assert (pixbuf != NULL);
+
+ g_assert(gdk_pixbuf_save_to_callback (pixbuf,
+ &save_to_stream,
+ &ssr,
+ enctype,
+ &err, NULL));
+
+ g_object_unref (pixbuf);
+
+ gdk_threads_leave ();
+ g_free(pix);
+
+ (*env)->ReleaseStringUTFChars (env, jenctype, enctype);
+ (*env)->ReleaseIntArrayElements (env, jarr, ints, 0);
+}
JNIEXPORT void JNICALL
Java_gnu_java_awt_peer_gtk_GdkPixbufDecoder_pumpBytes