mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 20:15:28 -04:00
X Window dump support.
This commit is contained in:
parent
021aba1a98
commit
f1810be10a
27
imageio/imageio-xwd/license.txt
Executable file
27
imageio/imageio-xwd/license.txt
Executable file
@ -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.
|
27
imageio/imageio-xwd/pom.xml
Executable file
27
imageio/imageio-xwd/pom.xml
Executable file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-xwd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
|
||||
<description>
|
||||
ImageIO plugin for X11 Window Dump Format (XWD)
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawImageType = getRawImageType(imageIndex);
|
||||
|
||||
// ArrayList<ImageTypeSpecifier> 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;
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.xwd.XWDImageReaderSpi
|
@ -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<XWDImageReader> {
|
||||
|
||||
private final XWDImageReaderSpi provider = new XWDImageReaderSpi();
|
||||
|
||||
@Override
|
||||
protected List<TestData> 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<XWDImageReader> getReaderClass() {
|
||||
return XWDImageReader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected XWDImageReader createReader() {
|
||||
return provider.createReaderInstance(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("xwd", "XWD");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Collections.singletonList("xwd");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/xwd", "image/x-xwd");
|
||||
}
|
||||
}
|
BIN
imageio/imageio-xwd/src/test/resources/xwd/brain.xwd
Normal file
BIN
imageio/imageio-xwd/src/test/resources/xwd/brain.xwd
Normal file
Binary file not shown.
BIN
imageio/imageio-xwd/src/test/resources/xwd/input.xwd
Normal file
BIN
imageio/imageio-xwd/src/test/resources/xwd/input.xwd
Normal file
Binary file not shown.
BIN
imageio/imageio-xwd/src/test/resources/xwd/sample_640x426.xwd
Normal file
BIN
imageio/imageio-xwd/src/test/resources/xwd/sample_640x426.xwd
Normal file
Binary file not shown.
@ -44,6 +44,7 @@
|
||||
<module>imageio-tga</module>
|
||||
<module>imageio-thumbsdb</module>
|
||||
<module>imageio-tiff</module>
|
||||
<module>imageio-xwd</module>
|
||||
|
||||
<!-- Wrappers for 3rd party libs -->
|
||||
<module>imageio-batik</module>
|
||||
|
Loading…
x
Reference in New Issue
Block a user