diff --git a/imageio/imageio-xwd/license.txt b/imageio/imageio-xwd/license.txt
new file mode 100755
index 00000000..a7ef43a0
--- /dev/null
+++ b/imageio/imageio-xwd/license.txt
@@ -0,0 +1,27 @@
+Copyright (c) 2020, Harald Kuhr
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/imageio/imageio-xwd/pom.xml b/imageio/imageio-xwd/pom.xml
new file mode 100755
index 00000000..6c903b1b
--- /dev/null
+++ b/imageio/imageio-xwd/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+ com.twelvemonkeys.imageio
+ imageio
+ 3.7-SNAPSHOT
+
+ imageio-xwd
+ TwelveMonkeys :: ImageIO :: XWD plugin
+
+ ImageIO plugin for X11 Window Dump Format (XWD)
+
+
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+ test-jar
+ test
+
+
+
diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/X11.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/X11.java
new file mode 100644
index 00000000..93645547
--- /dev/null
+++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/X11.java
@@ -0,0 +1,20 @@
+package com.twelvemonkeys.imageio.plugins.xwd;
+
+interface X11 {
+// int X10_HEADER_SIZE = 40;
+// int X10_HEADER_VERSION = 0x06;
+
+ int X11_HEADER_SIZE = 100;
+ int X11_HEADER_VERSION = 0x07;
+
+ int PIXMAP_FORMAT_XYBITMAP = 0; // 1 bit format
+ int PIXMAP_FORMAT_XYPIXMAP = 1; // single plane bitmap
+ int PIXMAP_FORMAT_ZPIXMAP = 2; // multiple bitplanes
+
+ int VISUAL_CLASS_STATIC_GRAY = 0;
+ int VISUAL_CLASS_GRAY_SCALE = 1;
+ int VISUAL_CLASS_STATIC_COLOR = 2;
+ int VISUAL_CLASS_PSEUDO_COLOR = 3;
+ int VISUAL_CLASS_TRUE_COLOR = 4;
+ int VISUAL_CLASS_DIRECT_COLOR = 5;
+}
diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageMetadata.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageMetadata.java
new file mode 100644
index 00000000..020ad941
--- /dev/null
+++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageMetadata.java
@@ -0,0 +1,139 @@
+package com.twelvemonkeys.imageio.plugins.xwd;
+
+import com.twelvemonkeys.imageio.AbstractMetadata;
+
+import javax.imageio.metadata.IIOMetadataNode;
+import java.nio.ByteOrder;
+
+import static com.twelvemonkeys.lang.Validate.notNull;
+
+final class XWDImageMetadata extends AbstractMetadata {
+ private final XWDX11Header header;
+
+ XWDImageMetadata(XWDX11Header header) {
+ super(true, null, null, null, null);
+ this.header = notNull(header, "header");
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
+ IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
+ IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
+
+ switch (header.visualClass) {
+ case X11.VISUAL_CLASS_STATIC_GRAY:
+ case X11.VISUAL_CLASS_GRAY_SCALE:
+ colorSpaceType.setAttribute("name", "GRAY");
+ break;
+ default:
+ colorSpaceType.setAttribute("name", "RGB");
+ }
+
+ chroma.appendChild(colorSpaceType);
+
+ // TODO: Depending on visual class OR the presence of color mop!?
+ switch (header.visualClass) {
+ case X11.VISUAL_CLASS_STATIC_COLOR:
+ case X11.VISUAL_CLASS_PSEUDO_COLOR:
+ IIOMetadataNode palette = new IIOMetadataNode("Palette");
+ chroma.appendChild(palette);
+
+ for (int i = 0; i < header.colorMap.getMapSize(); i++) {
+ IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
+ paletteEntry.setAttribute("index", Integer.toString(i));
+
+ paletteEntry.setAttribute("red", Integer.toString(header.colorMap.getRed(i)));
+ paletteEntry.setAttribute("green", Integer.toString(header.colorMap.getGreen(i)));
+ paletteEntry.setAttribute("blue", Integer.toString(header.colorMap.getBlue(i)));
+
+ palette.appendChild(paletteEntry);
+ }
+ break;
+
+ default:
+ // No palette
+ }
+
+
+ return chroma;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
+ IIOMetadataNode node = new IIOMetadataNode("Data");
+
+ IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
+ planarConfiguration.setAttribute("value", "PixelInterleaved");
+ node.appendChild(planarConfiguration);
+
+ IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
+ node.appendChild(sampleFormat);
+
+ switch (header.visualClass) {
+ case X11.VISUAL_CLASS_STATIC_COLOR:
+ case X11.VISUAL_CLASS_PSEUDO_COLOR:
+ sampleFormat.setAttribute("value", "Index");
+ break;
+ default:
+ sampleFormat.setAttribute("value", "UnsignedIntegral");
+ break;
+ }
+
+ IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
+ node.appendChild(bitsPerSample);
+
+ int numComponents = header.numComponents();
+ bitsPerSample.setAttribute("value", createListValue(numComponents, Integer.toString(header.bitsPerPixel / numComponents)));
+
+ // SampleMSB
+ if (header.bitsPerRGB < 8 && header.bitFillOrder == ByteOrder.LITTLE_ENDIAN) {
+ IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
+ node.appendChild(sampleMSB);
+ sampleMSB.setAttribute("value", createListValue(header.numComponents(), "0"));
+ }
+
+ return node;
+ }
+
+
+ @Override
+ protected IIOMetadataNode getStandardDocumentNode() {
+ IIOMetadataNode document = new IIOMetadataNode("Document");
+
+ // The only format we support is the X11 format, and it's version is 7.
+ IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
+ document.appendChild(formatVersion);
+ formatVersion.setAttribute("value", "7");
+
+ return document;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardTextNode() {
+ IIOMetadataNode text = new IIOMetadataNode("Text");
+
+ if (header.windowName != null) {
+ IIOMetadataNode node = new IIOMetadataNode("TextEntry");
+ text.appendChild(node);
+ node.setAttribute("keyword", "DocumentName"); // For TIFF interop. :-)
+ node.setAttribute("value", header.windowName);
+ }
+
+ return text.hasChildNodes() ? text : null;
+ }
+
+ // TODO: Candidate superclass method!
+ private String createListValue(final int itemCount, final String... values) {
+ StringBuilder buffer = new StringBuilder();
+
+ for (int i = 0; i < itemCount; i++) {
+ if (buffer.length() > 0) {
+ buffer.append(' ');
+ }
+
+ buffer.append(values[i % values.length]);
+ }
+
+ return buffer.toString();
+ }
+}
diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReader.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReader.java
new file mode 100644
index 00000000..4826eb0e
--- /dev/null
+++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReader.java
@@ -0,0 +1,225 @@
+package com.twelvemonkeys.imageio.plugins.xwd;
+
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
+
+import javax.imageio.IIOException;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.*;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.Collections;
+import java.util.Iterator;
+
+import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
+
+final class XWDImageReader extends ImageReaderBase {
+ // TODO: This table is also in c.t.i.p.tiff.ReverseInputStream, consider moving to common util?
+ // http://graphics.stanford.edu/~seander/bithacks.html
+ static final byte[] BIT_REVERSE_TABLE = {
+ 0x00, (byte) 0x80, 0x40, (byte) 0xC0, 0x20, (byte) 0xA0, 0x60, (byte) 0xE0, 0x10, (byte) 0x90, 0x50, (byte) 0xD0, 0x30, (byte) 0xB0, 0x70, (byte) 0xF0,
+ 0x08, (byte) 0x88, 0x48, (byte) 0xC8, 0x28, (byte) 0xA8, 0x68, (byte) 0xE8, 0x18, (byte) 0x98, 0x58, (byte) 0xD8, 0x38, (byte) 0xB8, 0x78, (byte) 0xF8,
+ 0x04, (byte) 0x84, 0x44, (byte) 0xC4, 0x24, (byte) 0xA4, 0x64, (byte) 0xE4, 0x14, (byte) 0x94, 0x54, (byte) 0xD4, 0x34, (byte) 0xB4, 0x74, (byte) 0xF4,
+ 0x0C, (byte) 0x8C, 0x4C, (byte) 0xCC, 0x2C, (byte) 0xAC, 0x6C, (byte) 0xEC, 0x1C, (byte) 0x9C, 0x5C, (byte) 0xDC, 0x3C, (byte) 0xBC, 0x7C, (byte) 0xFC,
+ 0x02, (byte) 0x82, 0x42, (byte) 0xC2, 0x22, (byte) 0xA2, 0x62, (byte) 0xE2, 0x12, (byte) 0x92, 0x52, (byte) 0xD2, 0x32, (byte) 0xB2, 0x72, (byte) 0xF2,
+ 0x0A, (byte) 0x8A, 0x4A, (byte) 0xCA, 0x2A, (byte) 0xAA, 0x6A, (byte) 0xEA, 0x1A, (byte) 0x9A, 0x5A, (byte) 0xDA, 0x3A, (byte) 0xBA, 0x7A, (byte) 0xFA,
+ 0x06, (byte) 0x86, 0x46, (byte) 0xC6, 0x26, (byte) 0xA6, 0x66, (byte) 0xE6, 0x16, (byte) 0x96, 0x56, (byte) 0xD6, 0x36, (byte) 0xB6, 0x76, (byte) 0xF6,
+ 0x0E, (byte) 0x8E, 0x4E, (byte) 0xCE, 0x2E, (byte) 0xAE, 0x6E, (byte) 0xEE, 0x1E, (byte) 0x9E, 0x5E, (byte) 0xDE, 0x3E, (byte) 0xBE, 0x7E, (byte) 0xFE,
+ 0x01, (byte) 0x81, 0x41, (byte) 0xC1, 0x21, (byte) 0xA1, 0x61, (byte) 0xE1, 0x11, (byte) 0x91, 0x51, (byte) 0xD1, 0x31, (byte) 0xB1, 0x71, (byte) 0xF1,
+ 0x09, (byte) 0x89, 0x49, (byte) 0xC9, 0x29, (byte) 0xA9, 0x69, (byte) 0xE9, 0x19, (byte) 0x99, 0x59, (byte) 0xD9, 0x39, (byte) 0xB9, 0x79, (byte) 0xF9,
+ 0x05, (byte) 0x85, 0x45, (byte) 0xC5, 0x25, (byte) 0xA5, 0x65, (byte) 0xE5, 0x15, (byte) 0x95, 0x55, (byte) 0xD5, 0x35, (byte) 0xB5, 0x75, (byte) 0xF5,
+ 0x0D, (byte) 0x8D, 0x4D, (byte) 0xCD, 0x2D, (byte) 0xAD, 0x6D, (byte) 0xED, 0x1D, (byte) 0x9D, 0x5D, (byte) 0xDD, 0x3D, (byte) 0xBD, 0x7D, (byte) 0xFD,
+ 0x03, (byte) 0x83, 0x43, (byte) 0xC3, 0x23, (byte) 0xA3, 0x63, (byte) 0xE3, 0x13, (byte) 0x93, 0x53, (byte) 0xD3, 0x33, (byte) 0xB3, 0x73, (byte) 0xF3,
+ 0x0B, (byte) 0x8B, 0x4B, (byte) 0xCB, 0x2B, (byte) 0xAB, 0x6B, (byte) 0xEB, 0x1B, (byte) 0x9B, 0x5B, (byte) 0xDB, 0x3B, (byte) 0xBB, 0x7B, (byte) 0xFB,
+ 0x07, (byte) 0x87, 0x47, (byte) 0xC7, 0x27, (byte) 0xA7, 0x67, (byte) 0xE7, 0x17, (byte) 0x97, 0x57, (byte) 0xD7, 0x37, (byte) 0xB7, 0x77, (byte) 0xF7,
+ 0x0F, (byte) 0x8F, 0x4F, (byte) 0xCF, 0x2F, (byte) 0xAF, 0x6F, (byte) 0xEF, 0x1F, (byte) 0x9F, 0x5F, (byte) 0xDF, 0x3F, (byte) 0xBF, 0x7F, (byte) 0xFF
+ };
+
+ private XWDX11Header header;
+
+ XWDImageReader(ImageReaderSpi provider) {
+ super(provider);
+ }
+
+ @Override
+ public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return new XWDImageMetadata(header);
+ }
+
+ @Override
+ public int getWidth(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.width;
+ }
+
+ @Override
+ public int getHeight(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.height;
+ }
+
+ private void readHeader() throws IOException {
+ assertInput();
+
+ if (header == null) {
+ header = XWDX11Header.read(imageInput);
+ }
+ }
+
+ @Override
+ public Iterator getImageTypes(int imageIndex) throws IOException {
+ ImageTypeSpecifier rawImageType = getRawImageType(imageIndex);
+
+// ArrayList specs = new ArrayList<>();
+ // TODO: Add TYPE_3BYTE_BGR & TYPE_4BYTE_ABGR + TYPE_INT_ types?
+// if (rawImageType )
+
+ return Collections.singletonList(rawImageType).iterator();
+ }
+
+ @Override
+ public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ switch (header.visualClass) {
+ case X11.VISUAL_CLASS_STATIC_GRAY:
+ case X11.VISUAL_CLASS_GRAY_SCALE:
+ return ImageTypeSpecifiers.createGrayscale(header.bitsPerPixel, DataBuffer.TYPE_BYTE, false);
+ case X11.VISUAL_CLASS_STATIC_COLOR:
+ case X11.VISUAL_CLASS_PSEUDO_COLOR:
+ return ImageTypeSpecifiers.createFromIndexColorModel(header.colorMap);
+ case X11.VISUAL_CLASS_TRUE_COLOR:
+ case X11.VISUAL_CLASS_DIRECT_COLOR:
+ int numComponents = header.numComponents();
+ return ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), createBandArray(numComponents, header), DataBuffer.TYPE_BYTE, numComponents > 3, false);
+ default:
+ throw new IIOException(String.format("Unknown visual class: %d", header.visualClass));
+ }
+ }
+
+ private Raster createRowRaster(byte[] row) {
+ DataBuffer databuffer = new DataBufferByte(row, row.length);
+
+ switch (header.visualClass) {
+ case X11.VISUAL_CLASS_TRUE_COLOR:
+ case X11.VISUAL_CLASS_DIRECT_COLOR:
+ return Raster.createInterleavedRaster(databuffer, header.width, 1, header.bytesPerLine, header.bitsPerPixel / 8, createBandArray(header.numComponents(), header), null);
+ default:
+ return Raster.createPackedRaster(databuffer, header.width, 1, header.bitsPerPixel, null);
+ }
+ }
+
+ @Override
+ public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ BufferedImage destination = getDestination(param, getImageTypes(imageIndex), header.width, header.height);
+ WritableRaster raster = destination.getRaster();
+ checkReadParamBandSettings(param, header.numComponents(), raster.getNumBands());
+
+ Rectangle srcRegion = new Rectangle();
+ Rectangle destRegion = new Rectangle();
+ computeRegions(param, header.width, header.height, destination, srcRegion, destRegion);
+
+ byte[] row = new byte[header.bytesPerLine];
+ Raster rowRaster = createRowRaster(row).createChild(srcRegion.x, 0, destRegion.width, 1, 0, 0, null);
+
+ final boolean reverseBits = header.bitsPerPixel < 8 && header.bitFillOrder == ByteOrder.LITTLE_ENDIAN;
+ final boolean clearAlpha = destination.getColorModel().hasAlpha();
+ final int xSub = param == null ? 1 : param.getSourceXSubsampling();
+ final int ySub = param == null ? 1 : param.getSourceYSubsampling();
+
+ imageInput.seek(header.pixelOffset);
+
+ processImageStarted(imageIndex);
+
+ for (int y = 0; y < srcRegion.y + srcRegion.height; y++) {
+ if (y < srcRegion.y || y % ySub != 0) {
+ // Skip row
+ imageInput.skipBytes(row.length);
+ continue;
+ }
+
+ imageInput.readFully(row);
+
+ if (reverseBits) {
+ for (int i = 0; i < row.length; i++) {
+ // TODO: Why do we have to inverse (XOR) as well?
+ row[i] = (byte) ~BIT_REVERSE_TABLE[row[i] & 0xff];
+ }
+ }
+
+ if (clearAlpha) {
+ // If we have alpha, inverse the alpha sample (or set it to opaque?)
+ // TODO: Where's the alpha! First or last depending on byte order?
+ for (int i = 0; i < row.length; i += rowRaster.getNumBands()) {
+ row[i] = (byte) ~row[i];
+ }
+ }
+
+ if (xSub != 1) {
+ // Horizontal subsampling
+ int samplesPerPixel = header.numComponents();
+ subsampleRow(row, srcRegion.x * samplesPerPixel, srcRegion.width, row, srcRegion.x * samplesPerPixel, samplesPerPixel, header.bitsPerRGB, xSub);
+ }
+
+ raster.setDataElements(0, (y - srcRegion.y) / ySub, rowRaster);
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+
+ processImageProgress((y - srcRegion.y) * 100f / srcRegion.height);
+ }
+
+ processImageComplete();
+
+ return destination;
+ }
+
+ private int bytePos(int bitMask, int numBands) {
+ switch (bitMask) {
+ case 0xff:
+ return numBands - 1;
+ case 0xff00:
+ return numBands - 2;
+ case 0xff0000:
+ return numBands - 3;
+ case 0xff000000:
+ return numBands - 4;
+ default:
+ // We only support full byte masks for now
+ throw new IllegalArgumentException(String.format("Unsupported bitmask: 0x%08x", bitMask));
+ }
+ }
+
+ private int[] createBandArray(int numBands, final XWDX11Header header) {
+ int[] offsets = new int[numBands];
+
+ for (int i = 0; i < numBands; i++) {
+ offsets[i] = bytePos(header.masks[i], numBands);
+ }
+
+ return offsets;
+ }
+
+ @Override
+ protected void resetMembers() {
+ header = null;
+ }
+}
diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReaderSpi.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReaderSpi.java
new file mode 100644
index 00000000..9fc7e59e
--- /dev/null
+++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReaderSpi.java
@@ -0,0 +1,40 @@
+package com.twelvemonkeys.imageio.plugins.xwd;
+
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
+
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.util.Locale;
+
+public final class XWDImageReaderSpi extends ImageReaderSpiBase {
+ public XWDImageReaderSpi() {
+ super(new XWDProviderInfo());
+ }
+
+ @Override
+ public boolean canDecodeInput(Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) source;
+
+ stream.mark();
+
+ try {
+ return XWDX11Header.isX11(stream);
+ } finally {
+ stream.reset();
+ }
+ }
+
+ @Override
+ public XWDImageReader createReaderInstance(Object extension) {
+ return new XWDImageReader(this);
+ }
+
+ @Override
+ public String getDescription(Locale locale) {
+ return "X11 Window Dump Format (XWD) reader";
+ }
+}
diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDProviderInfo.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDProviderInfo.java
new file mode 100644
index 00000000..58bbf132
--- /dev/null
+++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDProviderInfo.java
@@ -0,0 +1,28 @@
+package com.twelvemonkeys.imageio.plugins.xwd;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+final class XWDProviderInfo extends ReaderWriterProviderInfo {
+
+ final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.xwd.debug"));
+
+ protected XWDProviderInfo() {
+ super(
+ XWDProviderInfo.class,
+ new String[] {
+ "XWD", "xwd"
+ },
+ new String[] {"xwd",},
+ new String[] {
+ // No official IANA record exists
+ "image/xwd", "image/x-xwd"
+ },
+ "com.twelvemonkeys.imageio.plugins.xwd.XWDImageReader",
+ new String[]{"com.twelvemonkeys.imageio.plugins.xwd.XWDImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDX11Header.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDX11Header.java
new file mode 100644
index 00000000..4dd844df
--- /dev/null
+++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDX11Header.java
@@ -0,0 +1,164 @@
+package com.twelvemonkeys.imageio.plugins.xwd;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.awt.image.DataBuffer;
+import java.awt.image.IndexColorModel;
+import java.io.DataInput;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+final class XWDX11Header {
+
+ final int width;
+ final int height;
+ final ByteOrder byteOrder;
+ final ByteOrder bitFillOrder;
+ final int bitsPerPixel;
+ final int bytesPerLine;
+
+ final int visualClass;
+ final int[] masks;
+ final int bitsPerRGB;
+
+ final IndexColorModel colorMap;
+ final String windowName;
+
+ final long pixelOffset;
+
+ private XWDX11Header(int width, int height, ByteOrder byteOrder, ByteOrder bitFillOrder,
+ int bitsPerPixel, int bytesPerLine, int visualClass,
+ int readMask, int greenMask, int blueMask, int bitsPerRGB,
+ final IndexColorModel colorMap, final String windowName, long pixelOffset) {
+ this.width = width;
+ this.height = height;
+ this.byteOrder = byteOrder;
+ this.bitFillOrder = bitFillOrder;
+ this.bitsPerPixel = bitsPerPixel;
+ this.bytesPerLine = bytesPerLine;
+ this.visualClass = visualClass;
+ this.masks = new int[] {readMask, greenMask, blueMask, ~(readMask | greenMask | blueMask)};
+ this.bitsPerRGB = bitsPerRGB;
+ this.colorMap = colorMap;
+ this.windowName = windowName;
+ this.pixelOffset = pixelOffset;
+ }
+
+ static boolean isX11(final DataInput input) throws IOException {
+ return input.readInt() >= X11.X11_HEADER_SIZE
+ && input.readInt() == X11.X11_HEADER_VERSION;
+ }
+
+ static XWDX11Header read(final ImageInputStream input) throws IOException {
+ input.mark();
+ if (!isX11(input)) {
+ throw new IIOException("Not a valid X11 Window Dump");
+ }
+ input.reset();
+
+ long pos = input.getStreamPosition();
+ int length = input.readInt();
+ input.readInt();
+
+ int format = input.readInt(); // Pixel Format: 0 = 1 bit XYBitmap, 1 = single plane XYPixmap (gray?), 2 = two or more planes ZPixmap
+ int depth = input.readInt(); // Pixel Bit Depth: 1-32 (never seen above 24...)
+
+ int width = input.readInt();
+ int height = input.readInt();
+ int xOffset = input.readInt(); // Rarely used...
+
+ int byteOrder = input.readInt();
+ int unit = input.readInt();
+ int bitOrder = input.readInt(); // Same as TIFF FillOrder...
+ int pad = input.readInt(); // Can be inferred from bytePerLine?
+ int bitsPerPixel = input.readInt();
+ int bytePerLine = input.readInt(); // Scan line
+ int visualClass = input.readInt();
+
+ // TODO: Probably need these masks... Use them for creating color model?
+ int redMask = input.readInt();
+ int greenMask = input.readInt();
+ int blueMask = input.readInt();
+
+ // Size of each color mask in bits", basically bitPerSample
+ // NOTE: This field can't be trusted... :-/
+ int bitsPerRGB = input.readInt();
+ if (bitsPerPixel == 24 && bitsPerRGB == 24) {
+ bitsPerRGB = 8;
+ }
+
+ // Hmmm.. Unclear which of these that matters...
+ // - Can the map be larger than colors used?
+ // - Can numColors be > 0 for non-colormapped types?
+ int numColors = input.readInt();
+ int colorMapEntries = input.readInt();
+
+ // Not useful? Metadata?
+ int windowWidth = input.readInt();
+ int windowHeight = input.readInt();
+ int windowX = input.readInt();
+ int windowY = input.readInt();
+ int windowBorderWidth = input.readInt();
+
+ byte[] windowNameData = new byte[length - X11.X11_HEADER_SIZE];
+ input.readFully(windowNameData);
+ String windowName = windowNameData.length <= 1 ? null : new String(windowNameData, 0, windowNameData.length - 1, StandardCharsets.UTF_8);
+
+ if (XWDProviderInfo.DEBUG) {
+ System.out.println("format = " + format);
+ System.out.println("depth = " + depth);
+ System.out.println("byteOrder = " + byteOrder);
+ System.out.println("unit = " + unit);
+ System.out.println("bitOrder = " + bitOrder);
+ System.out.println("pad = " + pad);
+ System.out.println("bitsPerPixel = " + bitsPerPixel);
+ System.out.println("bytePerLine = " + bytePerLine);
+ System.out.println("visualClass = " + visualClass);
+
+ System.out.printf("redMask = 0x%08x%n", redMask);
+ System.out.printf("greenMask = 0x%08x%n", greenMask);
+ System.out.printf("blueMask = 0x%08x%n", blueMask);
+
+ System.out.println("bitsPerRGB = " + bitsPerRGB);
+
+ System.out.println("numColors = " + numColors);
+ System.out.println("colorMapEntries = " + colorMapEntries);
+ System.out.println("windowName = " + windowName);
+ }
+
+ byte[] colorMap = new byte[12 * colorMapEntries];
+ input.readFully(colorMap);
+
+ return new XWDX11Header(width, height,
+ byteOrder == 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN,
+ bitOrder == 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN,
+ bitsPerPixel, bytePerLine, visualClass, redMask, greenMask, blueMask, bitsPerRGB,
+ createColorMap(bitsPerRGB, colorMap),
+ windowName, pos + length + colorMap.length);
+ }
+
+ private static IndexColorModel createColorMap(int bitDepth, byte[] colorMap) {
+ if (colorMap.length == 0) {
+ return null;
+ }
+
+ int size = colorMap.length / 12;
+ int[] rgb = new int[size];
+
+ for (int i = 0; i < size; i++) {
+// int index = (colorMap[i * 12] & 0xff) << 24 | (colorMap[i * 12 + 1] & 0xff) << 16 | (colorMap[i * 12 + 2] & 0xff) << 8 | (colorMap[i * 12 + 3] & 0xff);
+// System.out.println("index = " + index);
+
+ // We'll just use the high 8 bits, as Java don't really have good support for 16 bit index color model
+ rgb[i] = (colorMap[i * 12 + 4] & 0xff) << 16 | (colorMap[i * 12 + 6] & 0xff) << 8 | (colorMap[i * 12 + 8] & 0xff);
+ }
+
+ return new IndexColorModel(bitDepth, size, rgb, 0, false, -1, DataBuffer.TYPE_BYTE);
+ }
+
+
+ int numComponents() {
+ return bitsPerPixel / bitsPerRGB;
+ }
+}
diff --git a/imageio/imageio-xwd/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-xwd/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..d9d939e7
--- /dev/null
+++ b/imageio/imageio-xwd/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.xwd.XWDImageReaderSpi
diff --git a/imageio/imageio-xwd/src/test/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReaderTest.java b/imageio/imageio-xwd/src/test/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReaderTest.java
new file mode 100644
index 00000000..0168ef9f
--- /dev/null
+++ b/imageio/imageio-xwd/src/test/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReaderTest.java
@@ -0,0 +1,53 @@
+package com.twelvemonkeys.imageio.plugins.xwd;
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
+
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class XWDImageReaderTest extends ImageReaderAbstractTest {
+
+ private final XWDImageReaderSpi provider = new XWDImageReaderSpi();
+
+ @Override
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(getClassLoaderResource("/xwd/input.xwd"), new Dimension(70, 46)),
+ new TestData(getClassLoaderResource("/xwd/brain.xwd"), new Dimension(520, 510)),
+ new TestData(getClassLoaderResource("/xwd/sample_640x426.xwd"), new Dimension(640, 426))
+ );
+ }
+
+ @Override
+ protected ImageReaderSpi createProvider() {
+ return provider;
+ }
+
+ @Override
+ protected Class getReaderClass() {
+ return XWDImageReader.class;
+ }
+
+ @Override
+ protected XWDImageReader createReader() {
+ return provider.createReaderInstance(null);
+ }
+
+ @Override
+ protected List getFormatNames() {
+ return Arrays.asList("xwd", "XWD");
+ }
+
+ @Override
+ protected List getSuffixes() {
+ return Collections.singletonList("xwd");
+ }
+
+ @Override
+ protected List getMIMETypes() {
+ return Arrays.asList("image/xwd", "image/x-xwd");
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-xwd/src/test/resources/xwd/brain.xwd b/imageio/imageio-xwd/src/test/resources/xwd/brain.xwd
new file mode 100644
index 00000000..a082a8bb
Binary files /dev/null and b/imageio/imageio-xwd/src/test/resources/xwd/brain.xwd differ
diff --git a/imageio/imageio-xwd/src/test/resources/xwd/input.xwd b/imageio/imageio-xwd/src/test/resources/xwd/input.xwd
new file mode 100644
index 00000000..13a67c23
Binary files /dev/null and b/imageio/imageio-xwd/src/test/resources/xwd/input.xwd differ
diff --git a/imageio/imageio-xwd/src/test/resources/xwd/sample_640x426.xwd b/imageio/imageio-xwd/src/test/resources/xwd/sample_640x426.xwd
new file mode 100644
index 00000000..da24e768
Binary files /dev/null and b/imageio/imageio-xwd/src/test/resources/xwd/sample_640x426.xwd differ
diff --git a/imageio/pom.xml b/imageio/pom.xml
index 5fd8bd6d..42254be5 100644
--- a/imageio/pom.xml
+++ b/imageio/pom.xml
@@ -44,6 +44,7 @@
imageio-tga
imageio-thumbsdb
imageio-tiff
+ imageio-xwd
imageio-batik