mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-10-04 11:26:44 -04:00
@@ -43,6 +43,7 @@ abstract class BitmapDescriptor {
|
|||||||
protected final DIBHeader header;
|
protected final DIBHeader header;
|
||||||
|
|
||||||
protected BufferedImage image;
|
protected BufferedImage image;
|
||||||
|
protected BitmapMask mask;
|
||||||
|
|
||||||
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||||
Validate.notNull(pEntry, "entry");
|
Validate.notNull(pEntry, "entry");
|
||||||
@@ -69,4 +70,17 @@ abstract class BitmapDescriptor {
|
|||||||
protected final int getBitCount() {
|
protected final int getBitCount() {
|
||||||
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
|
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setMask(final BitmapMask mask) {
|
||||||
|
this.mask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean hasMask() {
|
||||||
|
return header.getHeight() == getHeight() * 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,8 +28,6 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
|
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBuffer;
|
import java.awt.image.DataBuffer;
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
@@ -46,8 +44,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
protected final int[] bits;
|
protected final int[] bits;
|
||||||
protected final int[] colors;
|
protected final int[] colors;
|
||||||
|
|
||||||
private BitmapMask mask;
|
|
||||||
|
|
||||||
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||||
super(pEntry, pHeader);
|
super(pEntry, pHeader);
|
||||||
bits = new int[getWidth() * getHeight()];
|
bits = new int[getWidth() * getHeight()];
|
||||||
@@ -65,7 +61,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
// This is slightly obscure, and should probably be moved..
|
// This is slightly obscure, and should probably be moved..
|
||||||
Hashtable<String, Object> properties = null;
|
Hashtable<String, Object> properties = null;
|
||||||
if (entry instanceof DirectoryEntry.CUREntry) {
|
if (entry instanceof DirectoryEntry.CUREntry) {
|
||||||
properties = new Hashtable<String, Object>(1);
|
properties = new Hashtable<>(1);
|
||||||
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +85,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
|
|
||||||
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
|
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
|
||||||
|
|
||||||
//System.out.println("Image: " + image);
|
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,40 +94,40 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
IndexColorModel createColorModel() {
|
IndexColorModel createColorModel() {
|
||||||
// NOTE: This is a hack to make room for transparent pixel for mask
|
// NOTE: This is a hack to make room for transparent pixel for mask
|
||||||
int bits = getBitCount();
|
int bits = getBitCount();
|
||||||
|
|
||||||
int colors = this.colors.length;
|
int colors = this.colors.length;
|
||||||
int trans = -1;
|
int transparent = -1;
|
||||||
|
|
||||||
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
|
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
|
||||||
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
|
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
|
||||||
if (colors > (1 << getBitCount())) {
|
if (colors > (1 << getBitCount())) {
|
||||||
int index = findTransIndexMaybeRemap(this.colors, this.bits);
|
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
||||||
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
// No duplicate found, increase bitcount
|
// No duplicate found, increase bitcount
|
||||||
bits++;
|
bits++;
|
||||||
trans = this.colors.length - 1;
|
transparent = this.colors.length - 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Found a duplicate, use it as trans
|
// Found a duplicate, use it as transparent
|
||||||
trans = index;
|
transparent = index;
|
||||||
colors--;
|
colors--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
||||||
return new InverseColorMapIndexColorModel(
|
return new IndexColorModel(
|
||||||
bits, colors, this.colors, 0, true, trans,
|
bits, colors, this.colors, 0, true, transparent,
|
||||||
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int findTransIndexMaybeRemap(final int[] pColors, final int[] pBits) {
|
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
||||||
// Look for unused colors, to use as transparent
|
// Look for unused colors, to use as transparent
|
||||||
final boolean[] used = new boolean[pColors.length - 1];
|
boolean[] used = new boolean[colors.length - 1];
|
||||||
for (int pBit : pBits) {
|
for (int bit : bits) {
|
||||||
if (!used[pBit]) {
|
if (!used[bit]) {
|
||||||
used[pBit] = true;
|
used[bit] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,38 +138,35 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to find duplicates in colormap, and remap
|
// Try to find duplicates in colormap, and remap
|
||||||
int trans = -1;
|
int transparent = -1;
|
||||||
int duplicate = -1;
|
int duplicate = -1;
|
||||||
for (int i = 0; trans == -1 && i < pColors.length - 1; i++) {
|
for (int i = 0; transparent == -1 && i < colors.length - 1; i++) {
|
||||||
for (int j = i + 1; j < pColors.length - 1; j++) {
|
for (int j = i + 1; j < colors.length - 1; j++) {
|
||||||
if (pColors[i] == pColors[j]) {
|
if (colors[i] == colors[j]) {
|
||||||
trans = j;
|
transparent = j;
|
||||||
duplicate = i;
|
duplicate = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trans != -1) {
|
if (transparent != -1) {
|
||||||
// Remap duplicate
|
// Remap duplicate
|
||||||
for (int i = 0; i < pBits.length; i++) {
|
for (int i = 0; i < bits.length; i++) {
|
||||||
if (pBits[i] == trans) {
|
if (bits[i] == transparent) {
|
||||||
pBits[i] = duplicate;
|
bits[i] = duplicate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return trans;
|
return transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
image = createImageIndexed();
|
image = createImageIndexed();
|
||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMask(final BitmapMask pMask) {
|
|
||||||
mask = pMask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -38,19 +38,19 @@ import java.awt.image.BufferedImage;
|
|||||||
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapMask extends BitmapDescriptor {
|
class BitmapMask extends BitmapDescriptor {
|
||||||
protected final BitmapIndexed mask;
|
protected final BitmapIndexed bitMask;
|
||||||
|
|
||||||
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
||||||
super(pParent, pHeader);
|
super(pParent, pHeader);
|
||||||
mask = new BitmapIndexed(pParent, pHeader);
|
bitMask = new BitmapIndexed(pParent, pHeader);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isTransparent(final int pX, final int pY) {
|
boolean isTransparent(final int pX, final int pY) {
|
||||||
// NOTE: 1: Fully transparent, 0: Opaque...
|
// NOTE: 1: Fully transparent, 0: Opaque...
|
||||||
return mask.bits[pX + pY * getWidth()] != 0;
|
return bitMask.bits[pX + pY * getWidth()] != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
return mask.getImage();
|
return bitMask.getImage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
||||||
@@ -43,6 +45,38 @@ class BitmapRGB extends BitmapDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
|
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
|
||||||
|
// might still have bitmask, but we don't read or use it.
|
||||||
|
if (mask != null) {
|
||||||
|
image = createMaskedImage();
|
||||||
|
mask = null;
|
||||||
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BufferedImage createMaskedImage() {
|
||||||
|
BufferedImage masked = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
|
||||||
|
Graphics2D graphics = masked.createGraphics();
|
||||||
|
try {
|
||||||
|
graphics.drawImage(image, 0, 0, null);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
WritableRaster alphaRaster = masked.getAlphaRaster();
|
||||||
|
|
||||||
|
byte[] trans = {0x0};
|
||||||
|
for (int y = 0; y < getHeight(); y++) {
|
||||||
|
for (int x = 0; x < getWidth(); x++) {
|
||||||
|
if (mask.isTransparent(x, y)) {
|
||||||
|
alphaRaster.setDataElements(x, y, trans);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return masked;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,14 +66,15 @@ import java.util.List;
|
|||||||
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
||||||
// TODO: Support loading icons from DLLs, see
|
// TODO: Support loading icons from DLLs, see
|
||||||
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
||||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
|
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry
|
||||||
|
// (seem impossible as the PNGs are all true color)
|
||||||
abstract class DIBImageReader extends ImageReaderBase {
|
abstract class DIBImageReader extends ImageReaderBase {
|
||||||
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
||||||
private Directory directory;
|
private Directory directory;
|
||||||
|
|
||||||
// TODO: Review these, make sure we don't have a memory leak
|
// TODO: Review these, make sure we don't have a memory leak
|
||||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<DirectoryEntry, DIBHeader>();
|
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
||||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
|
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
||||||
|
|
||||||
private ImageReader pngImageReader;
|
private ImageReader pngImageReader;
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return getImageTypesPNG(entry);
|
return getImageTypesPNG(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
|
List<ImageTypeSpecifier> types = new ArrayList<>();
|
||||||
DIBHeader header = getHeader(entry);
|
DIBHeader header = getHeader(entry);
|
||||||
|
|
||||||
// Use data from header to create specifier
|
// Use data from header to create specifier
|
||||||
@@ -121,10 +122,13 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
|
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
|
||||||
break;
|
break;
|
||||||
case 16:
|
case 16:
|
||||||
|
// TODO: May have mask?!
|
||||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||||
break;
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
specifier = new BitmapRGB(entry, header).hasMask()
|
||||||
|
? ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)
|
||||||
|
: ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||||
break;
|
break;
|
||||||
case 32:
|
case 32:
|
||||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||||
@@ -184,6 +188,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Graphics2D g = destination.createGraphics();
|
Graphics2D g = destination.createGraphics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
g.setComposite(AlphaComposite.Src);
|
g.setComposite(AlphaComposite.Src);
|
||||||
g.drawImage(image, 0, 0, null);
|
g.drawImage(image, 0, 0, null);
|
||||||
@@ -316,6 +321,8 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
descriptors.put(pEntry, descriptor);
|
descriptors.put(pEntry, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
System.err.println("descriptor: " + descriptor);
|
||||||
|
|
||||||
return descriptor.getImage();
|
return descriptor.getImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +342,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||||
readBitmapIndexed1(mask.mask, true);
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
pBitmap.setMask(mask);
|
pBitmap.setMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +377,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: If we are reading the mask, we don't abort or progress
|
// NOTE: If we are reading the mask, we don't abort or report progress
|
||||||
if (!pAsMask) {
|
if (!pAsMask) {
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -455,7 +462,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||||
|
|
||||||
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
||||||
// Will create TYPE_USHORT_555;
|
// Will create TYPE_USHORT_555
|
||||||
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
||||||
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
||||||
WritableRaster raster = Raster.createPackedRaster(
|
WritableRaster raster = Raster.createPackedRaster(
|
||||||
@@ -480,6 +487,8 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Might be mask!?
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
||||||
@@ -494,16 +503,19 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
int scanlineStride = pBitmap.getWidth() * 3;
|
||||||
|
// BMP rows are padded to 4 byte boundary
|
||||||
|
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
||||||
|
|
||||||
WritableRaster raster = Raster.createInterleavedRaster(
|
WritableRaster raster = Raster.createInterleavedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
|
buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
|
||||||
imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
|
|
||||||
|
|
||||||
// TODO: Possibly read padding byte here!
|
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||||
|
int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
|
||||||
|
imageInput.readFully(pixels, offset, scanlineStride);
|
||||||
|
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -512,6 +524,14 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 24 bit icons usually have a bit mask
|
||||||
|
if (pBitmap.hasMask()) {
|
||||||
|
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||||
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
|
|
||||||
|
pBitmap.setMask(mask);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
||||||
@@ -535,6 +555,9 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There might be a mask here as well, but we'll ignore it,
|
||||||
|
// and use the 8 bit alpha channel in the ARGB pixel data
|
||||||
}
|
}
|
||||||
|
|
||||||
private Directory getDirectory() throws IOException {
|
private Directory getDirectory() throws IOException {
|
||||||
|
@@ -39,7 +39,9 @@ public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader>
|
|||||||
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
|
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
|
||||||
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
|
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
|
||||||
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
|
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
|
||||||
)
|
),
|
||||||
|
// Problematic icon that reports 24 bit in the descriptor, but has separate 1 bit ''mask (height 2 x icon height)!
|
||||||
|
new TestData(getClassLoaderResource("/ico/rgb24bitmask.ico"), new Dimension(32, 32))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
imageio/imageio-bmp/src/test/resources/ico/rgb24bitmask.ico
Normal file
BIN
imageio/imageio-bmp/src/test/resources/ico/rgb24bitmask.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
@@ -52,7 +52,7 @@ import java.util.Properties;
|
|||||||
* <p />
|
* <p />
|
||||||
* Color profiles may be configured by placing a property-file
|
* Color profiles may be configured by placing a property-file
|
||||||
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||||
* on the classpath, specifying the full path to the profile.
|
* on the classpath, specifying the full path to the profiles.
|
||||||
* ICC color profiles are probably already present on your system, or
|
* ICC color profiles are probably already present on your system, or
|
||||||
* can be downloaded from
|
* can be downloaded from
|
||||||
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||||
@@ -342,7 +342,7 @@ public final class ColorSpaces {
|
|||||||
try {
|
try {
|
||||||
return ICC_Profile.getInstance(profilePath);
|
return ICC_Profile.getInstance(profilePath);
|
||||||
}
|
}
|
||||||
catch (IOException ignore) {
|
catch (SecurityException | IOException ignore) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
ignore.printStackTrace();
|
ignore.printStackTrace();
|
||||||
}
|
}
|
||||||
|
30
imageio/imageio-hdr/pom.xml
Normal file
30
imageio/imageio-hdr/pom.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?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.2-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>imageio-hdr</artifactId>
|
||||||
|
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||||
|
<description>
|
||||||
|
ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-core</artifactId>
|
||||||
|
<classifier>tests</classifier>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-metadata</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDR.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDR.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
interface HDR {
|
||||||
|
byte[] RADIANCE_MAGIC = new byte[] {'#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E'};
|
||||||
|
byte[] RGBE_MAGIC = new byte[] {'#', '?', 'R', 'G', 'B', 'E'};
|
||||||
|
}
|
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRHeader.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRHeader.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
final class HDRHeader {
|
||||||
|
private static final String KEY_FORMAT = "FORMAT=";
|
||||||
|
private static final String KEY_PRIMARIES = "PRIMARIES=";
|
||||||
|
private static final String KEY_EXPOSURE = "EXPOSURE=";
|
||||||
|
private static final String KEY_GAMMA = "GAMMA=";
|
||||||
|
private static final String KEY_SOFTWARE = "SOFTWARE=";
|
||||||
|
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
|
||||||
|
private String software;
|
||||||
|
|
||||||
|
public static HDRHeader read(final ImageInputStream stream) throws IOException {
|
||||||
|
HDRHeader header = new HDRHeader();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
String line = stream.readLine().trim();
|
||||||
|
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
// This is the last line before the dimensions
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith("#?")) {
|
||||||
|
// Program specifier, don't need that...
|
||||||
|
}
|
||||||
|
else if (line.startsWith("#")) {
|
||||||
|
// Comment (ignore)
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_FORMAT)) {
|
||||||
|
String format = line.substring(KEY_FORMAT.length()).trim();
|
||||||
|
|
||||||
|
if (!format.equals("32-bit_rle_rgbe")) {
|
||||||
|
throw new IIOException("Unsupported format \"" + format + "\"(expected \"32-bit_rle_rgbe\")");
|
||||||
|
}
|
||||||
|
// TODO: Support the 32-bit_rle_xyze format
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_PRIMARIES)) {
|
||||||
|
// TODO: We are going to need these values...
|
||||||
|
// Should contain 8 (RGB + white point) coordinates
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_EXPOSURE)) {
|
||||||
|
// TODO: We are going to need these values...
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_GAMMA)) {
|
||||||
|
// TODO: We are going to need these values...
|
||||||
|
}
|
||||||
|
else if (line.startsWith(KEY_SOFTWARE)) {
|
||||||
|
header.software = line.substring(KEY_SOFTWARE.length()).trim();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// ...ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Proper parsing of width/height and orientation!
|
||||||
|
String dimensionsLine = stream.readLine().trim();
|
||||||
|
String[] dims = dimensionsLine.split("\\s");
|
||||||
|
|
||||||
|
if (dims[0].equals("-Y") && dims[2].equals("+X")) {
|
||||||
|
header.height = Integer.parseInt(dims[1]);
|
||||||
|
header.width = Integer.parseInt(dims[3]);
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IIOException("Unsupported RGBE orientation (expected \"-Y ... +X ...\")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftware() {
|
||||||
|
return software;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.DefaultToneMapper;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
|
||||||
|
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRImageReadParam.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRImageReadParam.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class HDRImageReadParam extends ImageReadParam {
|
||||||
|
static final ToneMapper DEFAULT_TONE_MAPPER = new DefaultToneMapper(.1f);
|
||||||
|
|
||||||
|
private ToneMapper toneMapper = DEFAULT_TONE_MAPPER;
|
||||||
|
|
||||||
|
public ToneMapper getToneMapper() {
|
||||||
|
return toneMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToneMapper(final ToneMapper toneMapper) {
|
||||||
|
this.toneMapper = toneMapper != null ? toneMapper : DEFAULT_TONE_MAPPER;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
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.BufferedImage;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRImageReader.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRImageReader.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class HDRImageReader extends ImageReaderBase {
|
||||||
|
// Specs: http://radsite.lbl.gov/radiance/refer/filefmts.pdf
|
||||||
|
|
||||||
|
private HDRHeader header;
|
||||||
|
|
||||||
|
protected HDRImageReader(final ImageReaderSpi provider) {
|
||||||
|
super(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetMembers() {
|
||||||
|
header = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readHeader() throws IOException {
|
||||||
|
if (header == null) {
|
||||||
|
header = HDRHeader.read(imageInput);
|
||||||
|
|
||||||
|
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
imageInput.seek(imageInput.getFlushedPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return header.getWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return header.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
return Collections.singletonList(ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false)).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
int width = getWidth(imageIndex);
|
||||||
|
int height = getHeight(imageIndex);
|
||||||
|
|
||||||
|
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
|
||||||
|
|
||||||
|
Rectangle srcRegion = new Rectangle();
|
||||||
|
Rectangle destRegion = new Rectangle();
|
||||||
|
computeRegions(param, width, height, destination, srcRegion, destRegion);
|
||||||
|
|
||||||
|
WritableRaster raster = destination.getRaster()
|
||||||
|
.createWritableChild(destRegion.x, destRegion.y, destRegion.width, destRegion.height, 0, 0, null);
|
||||||
|
|
||||||
|
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
||||||
|
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||||
|
|
||||||
|
// Allow pluggable tone mapper via ImageReadParam
|
||||||
|
ToneMapper toneMapper = param instanceof HDRImageReadParam
|
||||||
|
? ((HDRImageReadParam) param).getToneMapper()
|
||||||
|
: HDRImageReadParam.DEFAULT_TONE_MAPPER;
|
||||||
|
|
||||||
|
byte[] rowRGBE = new byte[width * 4];
|
||||||
|
float[] rgb = new float[3];
|
||||||
|
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
// Process one scanline of RGBE data at a time
|
||||||
|
for (int srcY = 0; srcY < height; srcY++) {
|
||||||
|
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
|
||||||
|
if (dstY >= destRegion.height) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
|
||||||
|
|
||||||
|
if (srcY % ySub == 0 && dstY >= destRegion.y) {
|
||||||
|
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
|
||||||
|
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
|
||||||
|
if (dstX >= destRegion.width) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBE.rgbe2float(rgb, rowRGBE, srcX * 4);
|
||||||
|
|
||||||
|
// Map/clamp RGB values into visible range, normally [0...1]
|
||||||
|
toneMapper.map(rgb);
|
||||||
|
|
||||||
|
raster.setDataElements(dstX, dstY, rgb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(srcY * 100f / height);
|
||||||
|
|
||||||
|
if (abortRequested()) {
|
||||||
|
processReadAborted();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canReadRaster() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
int width = getWidth(imageIndex);
|
||||||
|
int height = getHeight(imageIndex);
|
||||||
|
|
||||||
|
Rectangle srcRegion = new Rectangle();
|
||||||
|
Rectangle destRegion = new Rectangle();
|
||||||
|
computeRegions(param, width, height, null, srcRegion, destRegion);
|
||||||
|
destRegion = srcRegion; // We don't really care about destination for raster
|
||||||
|
|
||||||
|
BufferedImage destination = new BufferedImage(srcRegion.width, srcRegion.height, BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
|
WritableRaster raster = destination.getRaster();
|
||||||
|
|
||||||
|
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
||||||
|
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||||
|
|
||||||
|
byte[] rowRGBE = new byte[width * 4];
|
||||||
|
byte[] pixelRGBE = new byte[width];
|
||||||
|
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
// Process one scanline of RGBE data at a time
|
||||||
|
for (int srcY = 0; srcY < height; srcY++) {
|
||||||
|
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
|
||||||
|
if (dstY >= destRegion.height) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
|
||||||
|
|
||||||
|
if (srcY % ySub == 0 && dstY >= destRegion.y) {
|
||||||
|
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
|
||||||
|
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
|
||||||
|
if (dstX >= destRegion.width) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(rowRGBE, srcX * 4, pixelRGBE, 0, 4);
|
||||||
|
raster.setDataElements(dstX, dstY, pixelRGBE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(srcY * 100f / height);
|
||||||
|
|
||||||
|
if (abortRequested()) {
|
||||||
|
processReadAborted();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
return destination.getRaster();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageReadParam getDefaultReadParam() {
|
||||||
|
return new HDRImageReadParam();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return new HDRMetadata(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(final String[] args) throws IOException {
|
||||||
|
File file = new File(args[0]);
|
||||||
|
|
||||||
|
BufferedImage image = ImageIO.read(file);
|
||||||
|
|
||||||
|
showIt(image, file.getName());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRImageReaderSpi.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRImageReaderSpi.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class HDRImageReaderSpi extends ImageReaderSpiBase {
|
||||||
|
public HDRImageReaderSpi() {
|
||||||
|
super(new HDRProviderInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
|
if (!(source instanceof ImageInputStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInputStream stream = (ImageInputStream) source;
|
||||||
|
|
||||||
|
stream.mark();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// NOTE: All images I have found starts with #?RADIANCE (or has no #? line at all),
|
||||||
|
// although some sources claim that #?RGBE is also used.
|
||||||
|
byte[] magic = new byte[HDR.RADIANCE_MAGIC.length];
|
||||||
|
stream.readFully(magic);
|
||||||
|
|
||||||
|
return Arrays.equals(HDR.RADIANCE_MAGIC, magic)
|
||||||
|
|| Arrays.equals(HDR.RGBE_MAGIC, Arrays.copyOf(magic, 6));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stream.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||||
|
return new HDRImageReader(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(final Locale locale) {
|
||||||
|
return "Radiance RGBE High Dynaimc Range (HDR) image reader";
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
|
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
|
||||||
|
final class HDRMetadata extends AbstractMetadata {
|
||||||
|
private final HDRHeader header;
|
||||||
|
|
||||||
|
HDRMetadata(final HDRHeader header) {
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardChromaNode() {
|
||||||
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
|
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||||
|
chroma.appendChild(csType);
|
||||||
|
csType.setAttribute("name", "RGB");
|
||||||
|
// TODO: Support XYZ
|
||||||
|
|
||||||
|
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||||
|
numChannels.setAttribute("value", "3");
|
||||||
|
chroma.appendChild(numChannels);
|
||||||
|
|
||||||
|
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||||
|
blackIsZero.setAttribute("value", "TRUE");
|
||||||
|
chroma.appendChild(blackIsZero);
|
||||||
|
|
||||||
|
return chroma;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No compression
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardCompressionNode() {
|
||||||
|
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||||
|
|
||||||
|
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||||
|
compressionTypeName.setAttribute("value", "RLE");
|
||||||
|
node.appendChild(compressionTypeName);
|
||||||
|
|
||||||
|
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
|
lossless.setAttribute("value", "TRUE");
|
||||||
|
node.appendChild(lossless);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDataNode() {
|
||||||
|
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||||
|
|
||||||
|
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||||
|
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||||
|
node.appendChild(sampleFormat);
|
||||||
|
|
||||||
|
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||||
|
bitsPerSample.setAttribute("value", "8 8 8 8");
|
||||||
|
node.appendChild(bitsPerSample);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDimensionNode() {
|
||||||
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
|
// TODO: Support other orientations
|
||||||
|
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||||
|
imageOrientation.setAttribute("value", "Normal");
|
||||||
|
dimension.appendChild(imageOrientation);
|
||||||
|
|
||||||
|
return dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No document node
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
|
if (header.getSoftware() != null) {
|
||||||
|
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||||
|
|
||||||
|
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
textEntry.setAttribute("keyword", "Software");
|
||||||
|
textEntry.setAttribute("value", header.getSoftware());
|
||||||
|
text.appendChild(textEntry);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No tiling
|
||||||
|
|
||||||
|
// No transparency
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HDRProviderInfo.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: HDRProviderInfo.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
final class HDRProviderInfo extends ReaderWriterProviderInfo {
|
||||||
|
protected HDRProviderInfo() {
|
||||||
|
super(
|
||||||
|
HDRProviderInfo.class,
|
||||||
|
new String[] {"HDR", "hdr", "RGBE", "rgbe"},
|
||||||
|
new String[] {"hdr", "rgbe", "xyze", "pic"},
|
||||||
|
new String[] {"image/vnd.radiance"},
|
||||||
|
"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReader",
|
||||||
|
new String[]{"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi"},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false, null, null, null, null,
|
||||||
|
true, null, null, null, null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,494 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file contains code to read and write four byte rgbe file format
|
||||||
|
* developed by Greg Ward. It handles the conversions between rgbe and
|
||||||
|
* pixels consisting of floats. The data is assumed to be an array of floats.
|
||||||
|
* By default there are three floats per pixel in the order red, green, blue.
|
||||||
|
* (RGBE_DATA_??? values control this.) Only the mimimal header reading and
|
||||||
|
* writing is implemented. Each routine does error checking and will return
|
||||||
|
* a status value as defined below. This code is intended as a skeleton so
|
||||||
|
* feel free to modify it to suit your needs. <P>
|
||||||
|
* <p/>
|
||||||
|
* Ported to Java and restructured by Kenneth Russell. <BR>
|
||||||
|
* posted to http://www.graphics.cornell.edu/~bjw/ <BR>
|
||||||
|
* written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95 <BR>
|
||||||
|
* based on code written by Greg Ward <BR>
|
||||||
|
* <p/>
|
||||||
|
* Source: https://java.net/projects/jogl-demos/sources/svn/content/trunk/src/demos/hdr/RGBE.java
|
||||||
|
*/
|
||||||
|
final class RGBE {
|
||||||
|
// Flags indicating which fields in a Header are valid
|
||||||
|
private static final int VALID_PROGRAMTYPE = 0x01;
|
||||||
|
private static final int VALID_GAMMA = 0x02;
|
||||||
|
private static final int VALID_EXPOSURE = 0x04;
|
||||||
|
|
||||||
|
private static final String gammaString = "GAMMA=";
|
||||||
|
private static final String exposureString = "EXPOSURE=";
|
||||||
|
|
||||||
|
private static final Pattern widthHeightPattern = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
|
||||||
|
|
||||||
|
public static class Header {
|
||||||
|
// Indicates which fields are valid
|
||||||
|
private int valid;
|
||||||
|
|
||||||
|
// Listed at beginning of file to identify it after "#?".
|
||||||
|
// Defaults to "RGBE"
|
||||||
|
private String programType;
|
||||||
|
|
||||||
|
// Image has already been gamma corrected with given gamma.
|
||||||
|
// Defaults to 1.0 (no correction)
|
||||||
|
private float gamma;
|
||||||
|
|
||||||
|
// A value of 1.0 in an image corresponds to <exposure>
|
||||||
|
// watts/steradian/m^2. Defaults to 1.0.
|
||||||
|
private float exposure;
|
||||||
|
|
||||||
|
// Width and height of image
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
|
||||||
|
private Header(int valid,
|
||||||
|
String programType,
|
||||||
|
float gamma,
|
||||||
|
float exposure,
|
||||||
|
int width,
|
||||||
|
int height) {
|
||||||
|
this.valid = valid;
|
||||||
|
this.programType = programType;
|
||||||
|
this.gamma = gamma;
|
||||||
|
this.exposure = exposure;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProgramTypeValid() {
|
||||||
|
return ((valid & VALID_PROGRAMTYPE) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGammaValid() {
|
||||||
|
return ((valid & VALID_GAMMA) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExposureValid() {
|
||||||
|
return ((valid & VALID_EXPOSURE) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProgramType() {
|
||||||
|
return programType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getGamma() {
|
||||||
|
return gamma;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getExposure() {
|
||||||
|
return exposure;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
if (isProgramTypeValid()) {
|
||||||
|
buf.append(" Program type: ");
|
||||||
|
buf.append(getProgramType());
|
||||||
|
}
|
||||||
|
buf.append(" Gamma");
|
||||||
|
if (isGammaValid()) {
|
||||||
|
buf.append(" [valid]");
|
||||||
|
}
|
||||||
|
buf.append(": ");
|
||||||
|
buf.append(getGamma());
|
||||||
|
buf.append(" Exposure");
|
||||||
|
if (isExposureValid()) {
|
||||||
|
buf.append(" [valid]");
|
||||||
|
}
|
||||||
|
buf.append(": ");
|
||||||
|
buf.append(getExposure());
|
||||||
|
buf.append(" Width: ");
|
||||||
|
buf.append(getWidth());
|
||||||
|
buf.append(" Height: ");
|
||||||
|
buf.append(getHeight());
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Header readHeader(final DataInput in) throws IOException {
|
||||||
|
int valid = 0;
|
||||||
|
String programType = null;
|
||||||
|
float gamma = 1.0f;
|
||||||
|
float exposure = 1.0f;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
|
||||||
|
String buf = in.readLine();
|
||||||
|
if (buf == null) {
|
||||||
|
throw new IOException("Unexpected EOF reading magic token");
|
||||||
|
}
|
||||||
|
if (buf.charAt(0) == '#' && buf.charAt(1) == '?') {
|
||||||
|
valid |= VALID_PROGRAMTYPE;
|
||||||
|
programType = buf.substring(2);
|
||||||
|
buf = in.readLine();
|
||||||
|
if (buf == null) {
|
||||||
|
throw new IOException("Unexpected EOF reading line after magic token");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean foundFormat = false;
|
||||||
|
boolean done = false;
|
||||||
|
while (!done) {
|
||||||
|
if (buf.equals("FORMAT=32-bit_rle_rgbe")) {
|
||||||
|
foundFormat = true;
|
||||||
|
}
|
||||||
|
else if (buf.startsWith(gammaString)) {
|
||||||
|
valid |= VALID_GAMMA;
|
||||||
|
gamma = Float.parseFloat(buf.substring(gammaString.length()));
|
||||||
|
}
|
||||||
|
else if (buf.startsWith(exposureString)) {
|
||||||
|
valid |= VALID_EXPOSURE;
|
||||||
|
exposure = Float.parseFloat(buf.substring(exposureString.length()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Matcher m = widthHeightPattern.matcher(buf);
|
||||||
|
if (m.matches()) {
|
||||||
|
width = Integer.parseInt(m.group(2));
|
||||||
|
height = Integer.parseInt(m.group(1));
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
buf = in.readLine();
|
||||||
|
if (buf == null) {
|
||||||
|
throw new IOException("Unexpected EOF reading header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundFormat) {
|
||||||
|
throw new IOException("No FORMAT specifier found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Header(valid, programType, gamma, exposure, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple read routine. Will not correctly handle run length encoding.
|
||||||
|
*/
|
||||||
|
public static void readPixels(DataInput in, float[] data, int numpixels) throws IOException {
|
||||||
|
byte[] rgbe = new byte[4];
|
||||||
|
float[] rgb = new float[3];
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (numpixels-- > 0) {
|
||||||
|
in.readFully(rgbe);
|
||||||
|
|
||||||
|
rgbe2float(rgb, rgbe, 0);
|
||||||
|
|
||||||
|
data[offset++] = rgb[0];
|
||||||
|
data[offset++] = rgb[1];
|
||||||
|
data[offset++] = rgb[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readPixelsRaw(DataInput in, byte[] data, int offset, int numpixels) throws IOException {
|
||||||
|
int numExpected = 4 * numpixels;
|
||||||
|
in.readFully(data, offset, numExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readPixelsRawRLE(DataInput in, byte[] data, int offset,
|
||||||
|
int scanline_width, int num_scanlines) throws IOException {
|
||||||
|
byte[] rgbe = new byte[4];
|
||||||
|
byte[] scanline_buffer = null;
|
||||||
|
int ptr, ptr_end;
|
||||||
|
int count;
|
||||||
|
byte[] buf = new byte[2];
|
||||||
|
|
||||||
|
if ((scanline_width < 8) || (scanline_width > 0x7fff)) {
|
||||||
|
// run length encoding is not allowed so read flat
|
||||||
|
readPixelsRaw(in, data, offset, scanline_width * num_scanlines);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read in each successive scanline
|
||||||
|
while (num_scanlines > 0) {
|
||||||
|
in.readFully(rgbe);
|
||||||
|
|
||||||
|
if ((rgbe[0] != 2) || (rgbe[1] != 2) || ((rgbe[2] & 0x80) != 0)) {
|
||||||
|
// this file is not run length encoded
|
||||||
|
data[offset++] = rgbe[0];
|
||||||
|
data[offset++] = rgbe[1];
|
||||||
|
data[offset++] = rgbe[2];
|
||||||
|
data[offset++] = rgbe[3];
|
||||||
|
readPixelsRaw(in, data, offset, scanline_width * num_scanlines - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) != scanline_width) {
|
||||||
|
throw new IOException("Wrong scanline width " +
|
||||||
|
(((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) +
|
||||||
|
", expected " + scanline_width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanline_buffer == null) {
|
||||||
|
scanline_buffer = new byte[4 * scanline_width];
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = 0;
|
||||||
|
// read each of the four channels for the scanline into the buffer
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
ptr_end = (i + 1) * scanline_width;
|
||||||
|
while (ptr < ptr_end) {
|
||||||
|
in.readFully(buf);
|
||||||
|
|
||||||
|
if ((buf[0] & 0xFF) > 128) {
|
||||||
|
// a run of the same value
|
||||||
|
count = (buf[0] & 0xFF) - 128;
|
||||||
|
if ((count == 0) || (count > ptr_end - ptr)) {
|
||||||
|
throw new IOException("Bad scanline data");
|
||||||
|
}
|
||||||
|
while (count-- > 0) {
|
||||||
|
scanline_buffer[ptr++] = buf[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// a non-run
|
||||||
|
count = buf[0] & 0xFF;
|
||||||
|
if ((count == 0) || (count > ptr_end - ptr)) {
|
||||||
|
throw new IOException("Bad scanline data");
|
||||||
|
}
|
||||||
|
scanline_buffer[ptr++] = buf[1];
|
||||||
|
if (--count > 0) {
|
||||||
|
in.readFully(scanline_buffer, ptr, count);
|
||||||
|
ptr += count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// copy byte data to output
|
||||||
|
for (int i = 0; i < scanline_width; i++) {
|
||||||
|
data[offset++] = scanline_buffer[i];
|
||||||
|
data[offset++] = scanline_buffer[i + scanline_width];
|
||||||
|
data[offset++] = scanline_buffer[i + 2 * scanline_width];
|
||||||
|
data[offset++] = scanline_buffer[i + 3 * scanline_width];
|
||||||
|
}
|
||||||
|
num_scanlines--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard conversion from float pixels to rgbe pixels.
|
||||||
|
*/
|
||||||
|
public static void float2rgbe(byte[] rgbe, float red, float green, float blue) {
|
||||||
|
float v;
|
||||||
|
int e;
|
||||||
|
|
||||||
|
v = red;
|
||||||
|
if (green > v) {
|
||||||
|
v = green;
|
||||||
|
}
|
||||||
|
if (blue > v) {
|
||||||
|
v = blue;
|
||||||
|
}
|
||||||
|
if (v < 1e-32f) {
|
||||||
|
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
FracExp fe = frexp(v);
|
||||||
|
v = (float) (fe.getFraction() * 256.0 / v);
|
||||||
|
rgbe[0] = (byte) (red * v);
|
||||||
|
rgbe[1] = (byte) (green * v);
|
||||||
|
rgbe[2] = (byte) (blue * v);
|
||||||
|
rgbe[3] = (byte) (fe.getExponent() + 128);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard conversion from rgbe to float pixels. Note: Ward uses
|
||||||
|
* ldexp(col+0.5,exp-(128+8)). However we wanted pixels in the
|
||||||
|
* range [0,1] to map back into the range [0,1].
|
||||||
|
*/
|
||||||
|
public static void rgbe2float(float[] rgb, byte[] rgbe, int startRGBEOffset) {
|
||||||
|
float f;
|
||||||
|
|
||||||
|
if (rgbe[startRGBEOffset + 3] != 0) { // nonzero pixel
|
||||||
|
f = (float) ldexp(1.0, (rgbe[startRGBEOffset + 3] & 0xFF) - (128 + 8));
|
||||||
|
rgb[0] = (rgbe[startRGBEOffset + 0] & 0xFF) * f;
|
||||||
|
rgb[1] = (rgbe[startRGBEOffset + 1] & 0xFF) * f;
|
||||||
|
rgb[2] = (rgbe[startRGBEOffset + 2] & 0xFF) * f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rgb[0] = 0;
|
||||||
|
rgb[1] = 0;
|
||||||
|
rgb[2] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double ldexp(double value, int exp) {
|
||||||
|
if (!finite(value) || value == 0.0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
value = scalbn(value, exp);
|
||||||
|
// No good way to indicate errno (want to avoid throwing
|
||||||
|
// exceptions because don't know about stability of calculations)
|
||||||
|
// if(!finite(value)||value==0.0) errno = ERANGE;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Internals only below this point
|
||||||
|
//
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Math routines, some fdlibm-derived
|
||||||
|
//
|
||||||
|
|
||||||
|
static class FracExp {
|
||||||
|
private double fraction;
|
||||||
|
private int exponent;
|
||||||
|
|
||||||
|
public FracExp(double fraction, int exponent) {
|
||||||
|
this.fraction = fraction;
|
||||||
|
this.exponent = exponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFraction() {
|
||||||
|
return fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getExponent() {
|
||||||
|
return exponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final double two54 = 1.80143985094819840000e+16; // 43500000 00000000
|
||||||
|
private static final double twom54 = 5.55111512312578270212e-17; // 0x3C900000 0x00000000
|
||||||
|
private static final double huge = 1.0e+300;
|
||||||
|
private static final double tiny = 1.0e-300;
|
||||||
|
|
||||||
|
private static int hi(double x) {
|
||||||
|
long bits = Double.doubleToRawLongBits(x);
|
||||||
|
return (int) (bits >>> 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int lo(double x) {
|
||||||
|
long bits = Double.doubleToRawLongBits(x);
|
||||||
|
return (int) bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double fromhilo(int hi, int lo) {
|
||||||
|
return Double.longBitsToDouble((((long) hi) << 32) |
|
||||||
|
(((long) lo) & 0xFFFFFFFFL));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FracExp frexp(double x) {
|
||||||
|
int hx = hi(x);
|
||||||
|
int ix = 0x7fffffff & hx;
|
||||||
|
int lx = lo(x);
|
||||||
|
int e = 0;
|
||||||
|
if (ix >= 0x7ff00000 || ((ix | lx) == 0)) {
|
||||||
|
return new FracExp(x, e); // 0,inf,nan
|
||||||
|
}
|
||||||
|
if (ix < 0x00100000) { // subnormal
|
||||||
|
x *= two54;
|
||||||
|
hx = hi(x);
|
||||||
|
ix = hx & 0x7fffffff;
|
||||||
|
e = -54;
|
||||||
|
}
|
||||||
|
e += (ix >> 20) - 1022;
|
||||||
|
hx = (hx & 0x800fffff) | 0x3fe00000;
|
||||||
|
lx = lo(x);
|
||||||
|
return new FracExp(fromhilo(hx, lx), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean finite(double x) {
|
||||||
|
int hx;
|
||||||
|
hx = hi(x);
|
||||||
|
return (((hx & 0x7fffffff) - 0x7ff00000) >> 31) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* copysign(double x, double y) <BR>
|
||||||
|
* copysign(x,y) returns a value with the magnitude of x and
|
||||||
|
* with the sign bit of y.
|
||||||
|
*/
|
||||||
|
private static double copysign(double x, double y) {
|
||||||
|
return fromhilo((hi(x) & 0x7fffffff) | (hi(y) & 0x80000000), lo(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* scalbn (double x, int n) <BR>
|
||||||
|
* scalbn(x,n) returns x* 2**n computed by exponent
|
||||||
|
* manipulation rather than by actually performing an
|
||||||
|
* exponentiation or a multiplication.
|
||||||
|
*/
|
||||||
|
private static double scalbn(double x, int n) {
|
||||||
|
int hx = hi(x);
|
||||||
|
int lx = lo(x);
|
||||||
|
int k = (hx & 0x7ff00000) >> 20; // extract exponent
|
||||||
|
if (k == 0) { // 0 or subnormal x
|
||||||
|
if ((lx | (hx & 0x7fffffff)) == 0) {
|
||||||
|
return x; // +-0
|
||||||
|
}
|
||||||
|
x *= two54;
|
||||||
|
hx = hi(x);
|
||||||
|
k = ((hx & 0x7ff00000) >> 20) - 54;
|
||||||
|
if (n < -50000) {
|
||||||
|
return tiny * x; // underflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (k == 0x7ff) {
|
||||||
|
return x + x; // NaN or Inf
|
||||||
|
}
|
||||||
|
k = k + n;
|
||||||
|
if (k > 0x7fe) {
|
||||||
|
return huge * copysign(huge, x); // overflow
|
||||||
|
}
|
||||||
|
if (k > 0) {
|
||||||
|
// normal result
|
||||||
|
return fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
|
||||||
|
}
|
||||||
|
if (k <= -54) {
|
||||||
|
if (n > 50000) {
|
||||||
|
// in case integer overflow in n+k
|
||||||
|
return huge * copysign(huge, x); // overflow
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return tiny * copysign(tiny, x); // underflow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k += 54; // subnormal result
|
||||||
|
x = fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
|
||||||
|
return x * twom54;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Test harness
|
||||||
|
//
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
for (int i = 0; i < args.length; i++) {
|
||||||
|
try {
|
||||||
|
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(args[i])));
|
||||||
|
Header header = RGBE.readHeader(in);
|
||||||
|
System.err.println("Header for file \"" + args[i] + "\":");
|
||||||
|
System.err.println(" " + header);
|
||||||
|
byte[] data = new byte[header.getWidth() * header.getHeight() * 4];
|
||||||
|
readPixelsRawRLE(in, data, 0, header.getWidth(), header.getHeight());
|
||||||
|
in.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DefaultToneMapper.
|
||||||
|
* <p/>
|
||||||
|
* Normalizes values to range [0...1] using:
|
||||||
|
*
|
||||||
|
* <p><em>V<sub>out</sub> = V<sub>in</sub> / (V<sub>in</sub> + C)</em></p>
|
||||||
|
*
|
||||||
|
* Where <em>C</em> is constant.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: DefaultToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class DefaultToneMapper implements ToneMapper {
|
||||||
|
|
||||||
|
private final float constant;
|
||||||
|
|
||||||
|
public DefaultToneMapper() {
|
||||||
|
this(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultToneMapper(final float constant) {
|
||||||
|
this.constant = constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void map(final float[] rgb) {
|
||||||
|
// Default Vo = Vi / (Vi + 1)
|
||||||
|
for (int i = 0; i < rgb.length; i++) {
|
||||||
|
rgb[i] = rgb[i] / (rgb[i] + constant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GammaToneMapper.
|
||||||
|
* <p/>
|
||||||
|
* Normalizes values to range [0...1] using:
|
||||||
|
*
|
||||||
|
* <p><em>V<sub>out</sub> = A V<sub>in</sub><sup>\u03b3</sup></em></p>
|
||||||
|
*
|
||||||
|
* Where <em>A</em> is constant and <em>\u03b3</em> is the gamma.
|
||||||
|
* Values > 1 are clamped.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: GammaToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class GammaToneMapper implements ToneMapper {
|
||||||
|
|
||||||
|
private final float constant;
|
||||||
|
private final float gamma;
|
||||||
|
|
||||||
|
public GammaToneMapper() {
|
||||||
|
this(0.5f, .25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GammaToneMapper(final float constant, final float gamma) {
|
||||||
|
this.constant = constant;
|
||||||
|
this.gamma = gamma;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void map(final float[] rgb) {
|
||||||
|
// Gamma Vo = A * Vi^y
|
||||||
|
for (int i = 0; i < rgb.length; i++) {
|
||||||
|
rgb[i] = Math.min(1f, (float) (constant * Math.pow(rgb[i], gamma)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NullToneMapper.
|
||||||
|
* <p/>
|
||||||
|
* This {@code ToneMapper} does *not* normalize or clamp values
|
||||||
|
* to range [0...1], but leaves the values as-is.
|
||||||
|
* Useful for applications that implements custom tone mapping.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: NullToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public final class NullToneMapper implements ToneMapper {
|
||||||
|
@Override
|
||||||
|
public void map(float[] rgb) {
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ToneMapper.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: ToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
public interface ToneMapper {
|
||||||
|
void map(float[] rgb);
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
|
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015, 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 "TwelveMonkeys" 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 OWNER 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAImageReaderTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
|
||||||
|
@Override
|
||||||
|
protected List<TestData> getTestData() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new HDRImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<HDRImageReader> getReaderClass() {
|
||||||
|
return HDRImageReader.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HDRImageReader createReader() {
|
||||||
|
return new HDRImageReader(createProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFormatNames() {
|
||||||
|
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getSuffixes() {
|
||||||
|
return Arrays.asList("hdr", "rgbe", "xyze");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getMIMETypes() {
|
||||||
|
return Collections.singletonList(
|
||||||
|
"image/vnd.radiance"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
BIN
imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr
Normal file
BIN
imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr
Normal file
Binary file not shown.
@@ -471,12 +471,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
YCbCrConverter.convertYCbCr2RGB(raster);
|
YCbCrConverter.convertYCbCr2RGB(raster);
|
||||||
}
|
}
|
||||||
else if (csType == JPEGColorSpace.YCCK) {
|
else if (csType == JPEGColorSpace.YCCK) {
|
||||||
YCbCrConverter.convertYCCK2CMYK(raster);
|
// TODO: Need to rethink this (non-) inversion, see #147
|
||||||
|
// TODO: Allow param to specify inversion, or possibly the PDF decode array
|
||||||
// flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
|
// flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
|
||||||
if ((getAdobeDCT().flags0 & 0x8000) != 0) {
|
boolean invert = true;// || (adobeDCT.flags0 & 0x8000) == 0;
|
||||||
/// TODO: Better yet would be to not inverting in the first place, add flag to convertYCCK2CMYK
|
YCbCrConverter.convertYCCK2CMYK(raster, invert);
|
||||||
invertCMYK(raster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (csType == JPEGColorSpace.CMYK) {
|
else if (csType == JPEGColorSpace.CMYK) {
|
||||||
invertCMYK(raster);
|
invertCMYK(raster);
|
||||||
@@ -948,6 +947,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
delegate.abort();
|
delegate.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageReadParam getDefaultReadParam() {
|
||||||
|
return delegate.getDefaultReadParam();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readerSupportsThumbnails() {
|
public boolean readerSupportsThumbnails() {
|
||||||
return true; // We support EXIF, JFIF and JFXX style thumbnails
|
return true; // We support EXIF, JFIF and JFXX style thumbnails
|
||||||
@@ -1176,19 +1180,28 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void convertYCCK2CMYK(final Raster raster) {
|
static void convertYCCK2CMYK(final Raster raster, final boolean invert) {
|
||||||
final int height = raster.getHeight();
|
final int height = raster.getHeight();
|
||||||
final int width = raster.getWidth();
|
final int width = raster.getWidth();
|
||||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
if (invert) {
|
||||||
for (int x = 0; x < width; x++) {
|
for (int y = 0; y < height; y++) {
|
||||||
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
for (int x = 0; x < width; x++) {
|
||||||
|
convertYCCK2CMYKInverted(data, data, (x + y * width) * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
private static void convertYCCK2CMYKInverted(byte[] ycck, byte[] cmyk, int offset) {
|
||||||
// Inverted
|
// Inverted
|
||||||
int y = 255 - ycck[offset ] & 0xff;
|
int y = 255 - ycck[offset ] & 0xff;
|
||||||
int cb = 255 - ycck[offset + 1] & 0xff;
|
int cb = 255 - ycck[offset + 1] & 0xff;
|
||||||
@@ -1205,6 +1218,22 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
||||||
|
int y = ycck[offset ] & 0xff;
|
||||||
|
int cb = ycck[offset + 1] & 0xff;
|
||||||
|
int cr = ycck[offset + 2] & 0xff;
|
||||||
|
int k = ycck[offset + 3] & 0xff;
|
||||||
|
|
||||||
|
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
|
||||||
|
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||||
|
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
|
||||||
|
|
||||||
|
cmyk[offset ] = clamp(cmykC);
|
||||||
|
cmyk[offset + 1] = clamp(cmykM);
|
||||||
|
cmyk[offset + 2] = clamp(cmykY);
|
||||||
|
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||||
|
}
|
||||||
|
|
||||||
private static byte clamp(int val) {
|
private static byte clamp(int val) {
|
||||||
return (byte) Math.max(0, Math.min(255, val));
|
return (byte) Math.max(0, Math.min(255, val));
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,8 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.tga;
|
package com.twelvemonkeys.imageio.plugins.tga;
|
||||||
|
|
||||||
interface TGA {
|
interface TGA {
|
||||||
|
byte[] MAGIC = {'T', 'R', 'U', 'E', 'V', 'I', 'S', 'I', 'O', 'N', '-', 'X', 'F', 'I', 'L', 'E', '.', 0};
|
||||||
|
|
||||||
/** Fixed header size: 18.*/
|
/** Fixed header size: 18.*/
|
||||||
int HEADER_SIZE = 18;
|
int HEADER_SIZE = 18;
|
||||||
|
|
||||||
|
@@ -0,0 +1,187 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tga;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TGAExtensions.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: harald.kuhr$
|
||||||
|
* @version $Id: TGAExtensions.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||||
|
*/
|
||||||
|
final class TGAExtensions {
|
||||||
|
public static final int EXT_AREA_SIZE = 495;
|
||||||
|
|
||||||
|
private String authorName;
|
||||||
|
private String authorComments;
|
||||||
|
|
||||||
|
private Calendar creationDate;
|
||||||
|
private String jobId;
|
||||||
|
|
||||||
|
private String softwareId;
|
||||||
|
private String softwareVersion;
|
||||||
|
|
||||||
|
private int backgroundColor;
|
||||||
|
private double pixelAspectRatio;
|
||||||
|
private double gamma;
|
||||||
|
|
||||||
|
private long colorCorrectionOffset;
|
||||||
|
private long postageStampOffset;
|
||||||
|
private long scanLineOffset;
|
||||||
|
|
||||||
|
private int attributeType;
|
||||||
|
|
||||||
|
private TGAExtensions() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static TGAExtensions read(final ImageInputStream stream) throws IOException {
|
||||||
|
int extSize = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
// Should always be 495 for version 2.0, no newer version exists...
|
||||||
|
if (extSize < EXT_AREA_SIZE) {
|
||||||
|
throw new IIOException(String.format("TGA Extension Area size less than %d: %d", EXT_AREA_SIZE, extSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
TGAExtensions extensions = new TGAExtensions();
|
||||||
|
extensions.authorName = readString(stream, 41);;
|
||||||
|
extensions.authorComments = readString(stream, 324);
|
||||||
|
extensions.creationDate = readDate(stream);
|
||||||
|
extensions.jobId = readString(stream, 41);
|
||||||
|
|
||||||
|
stream.skipBytes(6); // Job time, 3 shorts, hours/minutes/seconds elapsed
|
||||||
|
|
||||||
|
extensions.softwareId = readString(stream, 41);
|
||||||
|
|
||||||
|
// Software version (* 100) short + single byte ASCII (ie. 101 'b' for 1.01b)
|
||||||
|
int softwareVersion = stream.readUnsignedShort();
|
||||||
|
int softwareLetter = stream.readByte();
|
||||||
|
|
||||||
|
extensions.softwareVersion = softwareVersion != 0 && softwareLetter != ' '
|
||||||
|
? String.format("%d.%d%d", softwareVersion / 100, softwareVersion % 100, softwareLetter).trim()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
extensions.backgroundColor = stream.readInt(); // ARGB
|
||||||
|
|
||||||
|
extensions.pixelAspectRatio = readRational(stream);
|
||||||
|
extensions.gamma = readRational(stream);
|
||||||
|
|
||||||
|
extensions.colorCorrectionOffset = stream.readUnsignedInt();
|
||||||
|
extensions.postageStampOffset = stream.readUnsignedInt();
|
||||||
|
extensions.scanLineOffset = stream.readUnsignedInt();
|
||||||
|
|
||||||
|
// Offset 494 specifies Attribute type:
|
||||||
|
// 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)
|
||||||
|
// 1: undefined data in the Alpha field, can be ignored
|
||||||
|
// 2: undefined data in the Alpha field, but should be retained
|
||||||
|
// 3: useful Alpha channel data is present
|
||||||
|
// 4: pre-multiplied Alpha (see description below)
|
||||||
|
// 5 -127: RESERVED
|
||||||
|
// 128-255: Un-assigned
|
||||||
|
extensions.attributeType = stream.readUnsignedByte();
|
||||||
|
|
||||||
|
return extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double readRational(final ImageInputStream stream) throws IOException {
|
||||||
|
int numerator = stream.readUnsignedShort();
|
||||||
|
int denominator = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
return denominator != 0 ? numerator / (double) denominator : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Calendar readDate(final ImageInputStream stream) throws IOException {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.clear();
|
||||||
|
|
||||||
|
int month = stream.readUnsignedShort();
|
||||||
|
int date = stream.readUnsignedShort();
|
||||||
|
int year = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
int hourOfDay = stream.readUnsignedShort();
|
||||||
|
int minute = stream.readUnsignedShort();
|
||||||
|
int second = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
// Unused
|
||||||
|
if (month == 0 && year == 0 && date == 0 && hourOfDay == 0 && minute == 0 && second == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
calendar.set(year, month - 1, date, hourOfDay, minute, second);
|
||||||
|
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readString(final ImageInputStream stream, final int maxLength) throws IOException {
|
||||||
|
byte[] data = new byte[maxLength];
|
||||||
|
stream.readFully(data);
|
||||||
|
|
||||||
|
return asZeroTerminatedASCIIString(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String asZeroTerminatedASCIIString(final byte[] data) {
|
||||||
|
int len = data.length;
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
if (data[i] == 0) {
|
||||||
|
len = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(data, 0, len, StandardCharsets.US_ASCII);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAlpha() {
|
||||||
|
switch (attributeType) {
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlphaPremultiplied() {
|
||||||
|
switch (attributeType) {
|
||||||
|
case 4:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getThumbnailOffset() {
|
||||||
|
return postageStampOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorName() {
|
||||||
|
return authorName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorComments() {
|
||||||
|
return authorComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Calendar getCreationDate() {
|
||||||
|
return creationDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftware() {
|
||||||
|
return softwareId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftwareVersion() {
|
||||||
|
return softwareVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPixelAspectRatio() {
|
||||||
|
return pixelAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBackgroundColor() {
|
||||||
|
return backgroundColor;
|
||||||
|
}
|
||||||
|
}
|
@@ -33,6 +33,7 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
|
|||||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
@@ -51,6 +52,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
// http://www.gamers.org/dEngine/quake3/TGA.txt
|
// http://www.gamers.org/dEngine/quake3/TGA.txt
|
||||||
|
|
||||||
private TGAHeader header;
|
private TGAHeader header;
|
||||||
|
private TGAExtensions extensions;
|
||||||
|
|
||||||
protected TGAImageReader(final ImageReaderSpi provider) {
|
protected TGAImageReader(final ImageReaderSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
@@ -67,6 +70,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
@Override
|
@Override
|
||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
header = null;
|
header = null;
|
||||||
|
extensions = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -89,7 +93,7 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
|
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
|
||||||
|
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
specifiers.add(rawType);
|
specifiers.add(rawType);
|
||||||
@@ -110,19 +114,29 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
return ImageTypeSpecifiers.createFromIndexColorModel(header.getColorMap());
|
return ImageTypeSpecifiers.createFromIndexColorModel(header.getColorMap());
|
||||||
case TGA.IMAGETYPE_MONOCHROME:
|
case TGA.IMAGETYPE_MONOCHROME:
|
||||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||||
return ImageTypeSpecifiers.createGrayscale(1, DataBuffer.TYPE_BYTE);
|
return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
|
||||||
case TGA.IMAGETYPE_TRUECOLOR:
|
case TGA.IMAGETYPE_TRUECOLOR:
|
||||||
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||||
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
|
||||||
|
boolean hasAlpha = header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha();
|
||||||
|
boolean isAlphaPremultiplied = extensions != null && extensions.isAlphaPremultiplied();
|
||||||
|
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 16:
|
case 16:
|
||||||
|
if (hasAlpha) {
|
||||||
|
// USHORT_1555_ARGB...
|
||||||
|
return ImageTypeSpecifiers.createPacked(sRGB, 0x7C00, 0x03E0, 0x001F, 0x8000, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
|
||||||
|
}
|
||||||
|
// Default mask out alpha
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||||
case 24:
|
case 24:
|
||||||
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||||
case 32:
|
case 32:
|
||||||
// 4BYTE_BGRA...
|
// 4BYTE_BGRX...
|
||||||
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false);
|
// Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead,
|
||||||
|
// if hasAlpha is false
|
||||||
|
return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied);
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
|
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
|
||||||
}
|
}
|
||||||
@@ -166,31 +180,32 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
DataInput input;
|
DataInput input;
|
||||||
if (imageType == TGA.IMAGETYPE_COLORMAPPED_RLE || imageType == TGA.IMAGETYPE_TRUECOLOR_RLE || imageType == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
if (imageType == TGA.IMAGETYPE_COLORMAPPED_RLE || imageType == TGA.IMAGETYPE_TRUECOLOR_RLE || imageType == TGA.IMAGETYPE_MONOCHROME_RLE) {
|
||||||
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder(header.getPixelDepth())));
|
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder(header.getPixelDepth())));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
input = imageInput;
|
input = imageInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 8:
|
case 8:
|
||||||
case 24:
|
case 24:
|
||||||
case 32:
|
case 32:
|
||||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
||||||
break;
|
|
||||||
case 16:
|
|
||||||
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
|
||||||
readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
|
||||||
}
|
|
||||||
|
|
||||||
processImageProgress(100f * y / height);
|
|
||||||
|
|
||||||
if (height - 1 - y < srcRegion.y) {
|
|
||||||
break;
|
break;
|
||||||
}
|
case 16:
|
||||||
|
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(100f * y / height);
|
||||||
|
|
||||||
|
if (height - 1 - y < srcRegion.y) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -212,11 +227,11 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
input.readFully(rowDataByte, 0, rowDataByte.length);
|
input.readFully(rowDataByte, 0, rowDataByte.length);
|
||||||
|
|
||||||
if (srcChannel.getNumBands() == 4) {
|
if (srcChannel.getNumBands() == 4 && (header.getAttributeBits() == 0 || extensions != null && !extensions.hasAlpha())) {
|
||||||
invertAlpha(rowDataByte);
|
// Remove the alpha channel (make pixels opaque) if there are no "attribute bits" (alpha bits)
|
||||||
|
removeAlpha32(rowDataByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subsample horizontal
|
// Subsample horizontal
|
||||||
@@ -240,9 +255,9 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void invertAlpha(final byte[] rowDataByte) {
|
private void removeAlpha32(final byte[] rowData) {
|
||||||
for (int i = 3; i < rowDataByte.length; i += 4) {
|
for (int i = 3; i < rowData.length; i += 4) {
|
||||||
rowDataByte[i] = (byte) (0xFF - rowDataByte[i]);
|
rowData[i] = (byte) 0xFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,21 +328,154 @@ public final class TGAImageReader extends ImageReaderBase {
|
|||||||
private void readHeader() throws IOException {
|
private void readHeader() throws IOException {
|
||||||
if (header == null) {
|
if (header == null) {
|
||||||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
// Read header
|
||||||
header = TGAHeader.read(imageInput);
|
header = TGAHeader.read(imageInput);
|
||||||
|
|
||||||
// System.err.println("header: " + header);
|
// System.err.println("header: " + header);
|
||||||
|
|
||||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||||
|
|
||||||
|
// Read footer, if 2.0 format (ends with TRUEVISION-XFILE\0)
|
||||||
|
skipToEnd(imageInput);
|
||||||
|
imageInput.seek(imageInput.getStreamPosition() - 26);
|
||||||
|
|
||||||
|
long extOffset = imageInput.readInt();
|
||||||
|
/*long devOffset = */imageInput.readInt(); // Ignored for now
|
||||||
|
|
||||||
|
byte[] magic = new byte[18];
|
||||||
|
imageInput.readFully(magic);
|
||||||
|
|
||||||
|
if (Arrays.equals(magic, TGA.MAGIC)) {
|
||||||
|
if (extOffset > 0) {
|
||||||
|
imageInput.seek(extOffset);
|
||||||
|
extensions = TGAExtensions.read(imageInput);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageInput.seek(imageInput.getFlushedPosition());
|
imageInput.seek(imageInput.getFlushedPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
// TODO: Candidate util method
|
||||||
|
private static void skipToEnd(final ImageInputStream stream) throws IOException {
|
||||||
|
if (stream.length() > 0) {
|
||||||
|
// Seek to end of file
|
||||||
|
stream.seek(stream.length());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Skip to end
|
||||||
|
long lastGood = stream.getStreamPosition();
|
||||||
|
|
||||||
|
while (stream.read() != -1) {
|
||||||
|
lastGood = stream.getStreamPosition();
|
||||||
|
stream.skipBytes(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.seek(lastGood);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (stream.read() == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Just continue reading to EOF...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumbnail support
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readerSupportsThumbnails() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasThumbnails(final int imageIndex) throws IOException {
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
readHeader();
|
readHeader();
|
||||||
|
|
||||||
return new TGAMetadata(header);
|
return extensions != null && extensions.getThumbnailOffset() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumThumbnails(final int imageIndex) throws IOException {
|
||||||
|
return hasThumbnails(imageIndex) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThumbnailWidth(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
Validate.isTrue(thumbnailIndex >= 0 && thumbnailIndex < getNumThumbnails(imageIndex), "thumbnailIndex >= numThumbnails");
|
||||||
|
|
||||||
|
imageInput.seek(extensions.getThumbnailOffset());
|
||||||
|
|
||||||
|
return imageInput.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThumbnailHeight(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||||
|
getThumbnailWidth(imageIndex, thumbnailIndex); // Laziness...
|
||||||
|
|
||||||
|
return imageInput.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage readThumbnail(final int imageIndex, final int thumbnailIndex) throws IOException {
|
||||||
|
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||||
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
|
|
||||||
|
int width = getThumbnailWidth(imageIndex, thumbnailIndex);
|
||||||
|
int height = getThumbnailHeight(imageIndex, thumbnailIndex);
|
||||||
|
|
||||||
|
// For thumbnail, always read entire image
|
||||||
|
Rectangle srcRegion = new Rectangle(width, height);
|
||||||
|
|
||||||
|
BufferedImage destination = getDestination(null, imageTypes, width, height);
|
||||||
|
WritableRaster destRaster = destination.getRaster();
|
||||||
|
WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
|
||||||
|
|
||||||
|
processThumbnailStarted(imageIndex, thumbnailIndex);
|
||||||
|
|
||||||
|
// Thumbnail is always stored non-compressed, no need for RLE support
|
||||||
|
imageInput.seek(extensions.getThumbnailOffset() + 2);
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
switch (header.getPixelDepth()) {
|
||||||
|
case 8:
|
||||||
|
case 24:
|
||||||
|
case 32:
|
||||||
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowByte(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataByte, destRaster, rowRaster, y);
|
||||||
|
break;
|
||||||
|
case 16:
|
||||||
|
short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
|
readRowUShort(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataUShort, destRaster, rowRaster, y);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
|
||||||
|
}
|
||||||
|
|
||||||
|
processThumbnailProgress(100f * y / height);
|
||||||
|
|
||||||
|
if (height - 1 - y < srcRegion.y) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
processThumbnailComplete();
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata support
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
readHeader();
|
||||||
|
|
||||||
|
return new TGAMetadata(header, extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
@@ -45,7 +45,8 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new TGAProviderInfo());
|
super(new TGAProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean canDecodeInput(final Object source) throws IOException {
|
@Override
|
||||||
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
if (!(source instanceof ImageInputStream)) {
|
if (!(source instanceof ImageInputStream)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
try {
|
try {
|
||||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
// NOTE: The TGA format does not have a magic identifier, so this is guesswork...
|
// NOTE: The original TGA format does not have a magic identifier, so this is guesswork...
|
||||||
// We'll try to match sane values, and hope no other files contains the same sequence.
|
// We'll try to match sane values, and hope no other files contains the same sequence.
|
||||||
|
|
||||||
stream.readUnsignedByte();
|
stream.readUnsignedByte();
|
||||||
@@ -88,11 +89,11 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
|
|
||||||
int colorMapStart = stream.readUnsignedShort();
|
int colorMapStart = stream.readUnsignedShort();
|
||||||
int colorMapSize = stream.readUnsignedShort();
|
int colorMapSize = stream.readUnsignedShort();
|
||||||
int colorMapDetph = stream.readUnsignedByte();
|
int colorMapDepth = stream.readUnsignedByte();
|
||||||
|
|
||||||
if (colorMapSize == 0) {
|
if (colorMapSize == 0) {
|
||||||
// No color map, all 3 fields should be 0
|
// No color map, all 3 fields should be 0
|
||||||
if (colorMapStart!= 0 || colorMapDetph != 0) {
|
if (colorMapStart != 0 || colorMapDepth != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
if (colorMapStart >= colorMapSize) {
|
if (colorMapStart >= colorMapSize) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (colorMapDetph != 15 && colorMapDetph != 16 && colorMapDetph != 24 && colorMapDetph != 32) {
|
if (colorMapDepth != 15 && colorMapDepth != 16 && colorMapDepth != 24 && colorMapDepth != 32) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,6 +135,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
|
|
||||||
// We're pretty sure by now, but there can still be false positives...
|
// We're pretty sure by now, but there can still be false positives...
|
||||||
// For 2.0 format, we could skip to end, and read "TRUEVISION-XFILE.\0" but it would be too slow
|
// For 2.0 format, we could skip to end, and read "TRUEVISION-XFILE.\0" but it would be too slow
|
||||||
|
// unless we are working with a local file (and the file may still be a valid original TGA without it).
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
@@ -31,13 +31,17 @@ package com.twelvemonkeys.imageio.plugins.tga;
|
|||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import java.awt.*;
|
||||||
import java.awt.image.IndexColorModel;
|
import java.awt.image.IndexColorModel;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
final class TGAMetadata extends AbstractMetadata {
|
final class TGAMetadata extends AbstractMetadata {
|
||||||
private final TGAHeader header;
|
private final TGAHeader header;
|
||||||
|
private final TGAExtensions extensions;
|
||||||
|
|
||||||
TGAMetadata(final TGAHeader header) {
|
TGAMetadata(final TGAHeader header, final TGAExtensions extensions) {
|
||||||
this.header = header;
|
this.header = header;
|
||||||
|
this.extensions = extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,6 +49,8 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||||
|
chroma.appendChild(csType);
|
||||||
|
|
||||||
switch (header.getImageType()) {
|
switch (header.getImageType()) {
|
||||||
case TGA.IMAGETYPE_MONOCHROME:
|
case TGA.IMAGETYPE_MONOCHROME:
|
||||||
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
case TGA.IMAGETYPE_MONOCHROME_RLE:
|
||||||
@@ -62,15 +68,22 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
default:
|
default:
|
||||||
csType.setAttribute("name", "Unknown");
|
csType.setAttribute("name", "Unknown");
|
||||||
}
|
}
|
||||||
chroma.appendChild(csType);
|
|
||||||
|
|
||||||
// TODO: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
|
||||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||||
|
chroma.appendChild(numChannels);
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 8:
|
case 8:
|
||||||
case 16:
|
|
||||||
numChannels.setAttribute("value", Integer.toString(1));
|
numChannels.setAttribute("value", Integer.toString(1));
|
||||||
break;
|
break;
|
||||||
|
case 16:
|
||||||
|
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||||
|
numChannels.setAttribute("value", Integer.toString(4));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
numChannels.setAttribute("value", Integer.toString(3));
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
numChannels.setAttribute("value", Integer.toString(3));
|
numChannels.setAttribute("value", Integer.toString(3));
|
||||||
break;
|
break;
|
||||||
@@ -78,11 +91,10 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
numChannels.setAttribute("value", Integer.toString(4));
|
numChannels.setAttribute("value", Integer.toString(4));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
chroma.appendChild(numChannels);
|
|
||||||
|
|
||||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||||
blackIsZero.setAttribute("value", "TRUE");
|
|
||||||
chroma.appendChild(blackIsZero);
|
chroma.appendChild(blackIsZero);
|
||||||
|
blackIsZero.setAttribute("value", "TRUE");
|
||||||
|
|
||||||
// NOTE: TGA files may contain a color map, even if true color...
|
// NOTE: TGA files may contain a color map, even if true color...
|
||||||
// Not sure if this is a good idea to expose to the meta data,
|
// Not sure if this is a good idea to expose to the meta data,
|
||||||
@@ -94,16 +106,26 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||||
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
|
||||||
|
palette.appendChild(paletteEntry);
|
||||||
paletteEntry.setAttribute("index", Integer.toString(i));
|
paletteEntry.setAttribute("index", Integer.toString(i));
|
||||||
|
|
||||||
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||||
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||||
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||||
|
|
||||||
palette.appendChild(paletteEntry);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (extensions != null && extensions.getBackgroundColor() != 0) {
|
||||||
|
Color background = new Color(extensions.getBackgroundColor(), true);
|
||||||
|
|
||||||
|
IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
|
||||||
|
chroma.appendChild(backgroundColor);
|
||||||
|
|
||||||
|
backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
|
||||||
|
backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
|
||||||
|
backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
|
||||||
|
}
|
||||||
|
|
||||||
return chroma;
|
return chroma;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,15 +138,16 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
|
||||||
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
|
||||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||||
|
|
||||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||||
|
node.appendChild(compressionTypeName);
|
||||||
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
|
||||||
? "Uknown" : "RLE";
|
? "Uknown" : "RLE";
|
||||||
compressionTypeName.setAttribute("value", value);
|
compressionTypeName.setAttribute("value", value);
|
||||||
node.appendChild(compressionTypeName);
|
|
||||||
|
|
||||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
lossless.setAttribute("value", "TRUE");
|
|
||||||
node.appendChild(lossless);
|
node.appendChild(lossless);
|
||||||
|
lossless.setAttribute("value", "TRUE");
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
default:
|
default:
|
||||||
@@ -138,10 +161,12 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||||
|
|
||||||
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||||
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
|
||||||
node.appendChild(planarConfiguration);
|
node.appendChild(planarConfiguration);
|
||||||
|
planarConfiguration.setAttribute("value", "PixelInterleaved");
|
||||||
|
|
||||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||||
|
node.appendChild(sampleFormat);
|
||||||
|
|
||||||
switch (header.getImageType()) {
|
switch (header.getImageType()) {
|
||||||
case TGA.IMAGETYPE_COLORMAPPED:
|
case TGA.IMAGETYPE_COLORMAPPED:
|
||||||
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
case TGA.IMAGETYPE_COLORMAPPED_RLE:
|
||||||
@@ -154,13 +179,19 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.appendChild(sampleFormat);
|
|
||||||
|
|
||||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||||
|
node.appendChild(bitsPerSample);
|
||||||
|
|
||||||
switch (header.getPixelDepth()) {
|
switch (header.getPixelDepth()) {
|
||||||
case 8:
|
case 8:
|
||||||
case 16:
|
|
||||||
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
|
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
|
||||||
|
case 16:
|
||||||
|
if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
|
||||||
|
bitsPerSample.setAttribute("value", "5, 5, 5, 1");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bitsPerSample.setAttribute("value", createListValue(3, "5"));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8)));
|
bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8)));
|
||||||
@@ -170,12 +201,6 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.appendChild(bitsPerSample);
|
|
||||||
|
|
||||||
// TODO: Do we need MSB?
|
|
||||||
// IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
|
||||||
// sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
|
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +223,7 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||||
|
dimension.appendChild(imageOrientation);
|
||||||
|
|
||||||
switch (header.getOrigin()) {
|
switch (header.getOrigin()) {
|
||||||
case TGA.ORIGIN_LOWER_LEFT:
|
case TGA.ORIGIN_LOWER_LEFT:
|
||||||
@@ -214,28 +240,64 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dimension.appendChild(imageOrientation);
|
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
|
||||||
|
dimension.appendChild(pixelAspectRatio);
|
||||||
|
pixelAspectRatio.setAttribute("value", extensions != null ? String.valueOf(extensions.getPixelAspectRatio()) : "1.0");
|
||||||
|
|
||||||
return dimension;
|
return dimension;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No document node
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDocumentNode() {
|
||||||
|
IIOMetadataNode document = new IIOMetadataNode("Document");
|
||||||
|
|
||||||
|
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
|
||||||
|
document.appendChild(formatVersion);
|
||||||
|
formatVersion.setAttribute("value", extensions == null ? "1.0" : "2.0");
|
||||||
|
|
||||||
|
// ImageCreationTime from extensions date
|
||||||
|
if (extensions != null && extensions.getCreationDate() != null) {
|
||||||
|
IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime");
|
||||||
|
document.appendChild(imageCreationTime);
|
||||||
|
|
||||||
|
Calendar date = extensions.getCreationDate();
|
||||||
|
|
||||||
|
imageCreationTime.setAttribute("year", String.valueOf(date.get(Calendar.YEAR)));
|
||||||
|
imageCreationTime.setAttribute("month", String.valueOf(date.get(Calendar.MONTH) + 1));
|
||||||
|
imageCreationTime.setAttribute("day", String.valueOf(date.get(Calendar.DAY_OF_MONTH)));
|
||||||
|
imageCreationTime.setAttribute("hour", String.valueOf(date.get(Calendar.HOUR_OF_DAY)));
|
||||||
|
imageCreationTime.setAttribute("minute", String.valueOf(date.get(Calendar.MINUTE)));
|
||||||
|
imageCreationTime.setAttribute("second", String.valueOf(date.get(Calendar.SECOND)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardTextNode() {
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
// TODO: Extra "developer area" and other stuff might go here...
|
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||||
|
|
||||||
|
// NOTE: Names corresponds to equivalent fields in TIFF
|
||||||
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
|
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
|
||||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
appendTextEntry(text, "DocumentName", header.getIdentification());
|
||||||
|
|
||||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
|
||||||
textEntry.setAttribute("keyword", "identification");
|
|
||||||
textEntry.setAttribute("value", header.getIdentification());
|
|
||||||
text.appendChild(textEntry);
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
if (extensions != null) {
|
||||||
|
appendTextEntry(text, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : extensions.getSoftware() + " " + extensions.getSoftwareVersion());
|
||||||
|
appendTextEntry(text, "Artist", extensions.getAuthorName());
|
||||||
|
appendTextEntry(text, "UserComment", extensions.getAuthorComments());
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.hasChildNodes() ? text : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) {
|
||||||
|
if (value != null) {
|
||||||
|
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||||
|
parent.appendChild(textEntry);
|
||||||
|
textEntry.setAttribute("keyword", keyword);
|
||||||
|
textEntry.setAttribute("value", value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No tiling
|
// No tiling
|
||||||
@@ -245,9 +307,23 @@ final class TGAMetadata extends AbstractMetadata {
|
|||||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||||
|
|
||||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||||
alpha.setAttribute("value", header.getPixelDepth() == 32 ? "nonpremultiplied" : "none");
|
|
||||||
transparency.appendChild(alpha);
|
transparency.appendChild(alpha);
|
||||||
|
|
||||||
|
if (extensions != null) {
|
||||||
|
if (extensions.hasAlpha()) {
|
||||||
|
alpha.setAttribute("value", extensions.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alpha.setAttribute("value", "none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (header.getAttributeBits() == 8) {
|
||||||
|
alpha.setAttribute("value", "nonpremultiplied");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
alpha.setAttribute("value", "none");
|
||||||
|
}
|
||||||
|
|
||||||
return transparency;
|
return transparency;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,24 +28,23 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
|
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author <a href="https://github.com/Schmidor">Oliver Schmidtmer</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class CCITTFaxDecoderStream extends FilterInputStream {
|
final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||||
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression",
|
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
|
||||||
// page 43.
|
|
||||||
|
|
||||||
private final int columns;
|
private final int columns;
|
||||||
private final byte[] decodedRow;
|
private final byte[] decodedRow;
|
||||||
@@ -62,8 +61,6 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private int changesReferenceRowCount;
|
private int changesReferenceRowCount;
|
||||||
private int changesCurrentRowCount;
|
private int changesCurrentRowCount;
|
||||||
|
|
||||||
private static final int EOL_CODE = 0x01; // 12 bit
|
|
||||||
|
|
||||||
private boolean optionG32D = false;
|
private boolean optionG32D = false;
|
||||||
|
|
||||||
@SuppressWarnings("unused") // Leading zeros for aligning EOL
|
@SuppressWarnings("unused") // Leading zeros for aligning EOL
|
||||||
@@ -72,29 +69,34 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private boolean optionUncompressed = false;
|
private boolean optionUncompressed = false;
|
||||||
|
|
||||||
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder,
|
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder,
|
||||||
final long options) {
|
final long options) {
|
||||||
super(Validate.notNull(stream, "stream"));
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||||
// We know this is only used for b/w (1 bit)
|
// We know this is only used for b/w (1 bit)
|
||||||
this.decodedRow = new byte[(columns + 7) / 8];
|
this.decodedRow = new byte[(columns + 7) / 8];
|
||||||
this.type = type;
|
this.type = Validate.isTrue(
|
||||||
this.fillOrder = fillOrder;// Validate.isTrue(fillOrder == 1, fillOrder,
|
type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
|
||||||
// "Only fill order 1 supported: %s"); //
|
type == TIFFExtension.COMPRESSION_CCITT_T4 || type == TIFFExtension.COMPRESSION_CCITT_T6,
|
||||||
// TODO: Implement fillOrder == 2
|
type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s"
|
||||||
|
);
|
||||||
|
this.fillOrder = Validate.isTrue(
|
||||||
|
fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT,
|
||||||
|
fillOrder, "Expected fill order 1 or 2: %s"
|
||||||
|
);
|
||||||
|
|
||||||
this.changesReferenceRow = new int[columns];
|
this.changesReferenceRow = new int[columns];
|
||||||
this.changesCurrentRow = new int[columns];
|
this.changesCurrentRow = new int[columns];
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
|
optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
|
||||||
optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
|
optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
|
||||||
optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
|
optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
|
||||||
break;
|
break;
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
|
optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
Validate.isTrue(!optionUncompressed, optionUncompressed,
|
||||||
@@ -107,7 +109,8 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
decodeRow();
|
decodeRow();
|
||||||
} catch (EOFException e) {
|
}
|
||||||
|
catch (EOFException e) {
|
||||||
// TODO: Rewrite to avoid throw/catch for normal flow...
|
// TODO: Rewrite to avoid throw/catch for normal flow...
|
||||||
if (decodedLength != 0) {
|
if (decodedLength != 0) {
|
||||||
throw e;
|
throw e;
|
||||||
@@ -126,16 +129,20 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
boolean white = true;
|
boolean white = true;
|
||||||
changesCurrentRowCount = 0;
|
changesCurrentRowCount = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
int completeRun = 0;
|
int completeRun;
|
||||||
|
|
||||||
if (white) {
|
if (white) {
|
||||||
completeRun = decodeRun(whiteRunTree);
|
completeRun = decodeRun(whiteRunTree);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
completeRun = decodeRun(blackRunTree);
|
completeRun = decodeRun(blackRunTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
index += completeRun;
|
index += completeRun;
|
||||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||||
|
|
||||||
// Flip color for next run
|
// Flip color for next run
|
||||||
white = !white;
|
white = !white;
|
||||||
} while (index < columns);
|
} while (index < columns);
|
||||||
@@ -157,45 +164,53 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
boolean white = true;
|
boolean white = true;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
changesCurrentRowCount = 0;
|
changesCurrentRowCount = 0;
|
||||||
|
|
||||||
mode: while (index < columns) {
|
mode: while (index < columns) {
|
||||||
// read mode
|
// read mode
|
||||||
Node n = codeTree.root;
|
Node n = codeTree.root;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
n = n.walk(readBit());
|
n = n.walk(readBit());
|
||||||
|
|
||||||
if (n == null) {
|
if (n == null) {
|
||||||
continue mode;
|
continue mode;
|
||||||
} else if (n.isLeaf) {
|
}
|
||||||
|
else if (n.isLeaf) {
|
||||||
switch (n.value) {
|
switch (n.value) {
|
||||||
case VALUE_HMODE:
|
case VALUE_HMODE:
|
||||||
int runLength = 0;
|
int runLength;
|
||||||
runLength = decodeRun(white ? whiteRunTree : blackRunTree);
|
|
||||||
index += runLength;
|
|
||||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
|
||||||
|
|
||||||
runLength = decodeRun(white ? blackRunTree : whiteRunTree);
|
runLength = decodeRun(white ? whiteRunTree : blackRunTree);
|
||||||
index += runLength;
|
index += runLength;
|
||||||
changesCurrentRow[changesCurrentRowCount++] = index;
|
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||||
break;
|
|
||||||
case VALUE_PASSMODE:
|
runLength = decodeRun(white ? blackRunTree : whiteRunTree);
|
||||||
index = changesReferenceRow[getNextChangingElement(index, white) + 1];
|
index += runLength;
|
||||||
break;
|
changesCurrentRow[changesCurrentRowCount++] = index;
|
||||||
default:
|
break;
|
||||||
// Vertical mode (-3 to 3)
|
|
||||||
index = changesReferenceRow[getNextChangingElement(index, white)] + n.value;
|
case VALUE_PASSMODE:
|
||||||
changesCurrentRow[changesCurrentRowCount] = index;
|
index = changesReferenceRow[getNextChangingElement(index, white) + 1];
|
||||||
changesCurrentRowCount++;
|
break;
|
||||||
white = !white;
|
|
||||||
break;
|
default:
|
||||||
|
// Vertical mode (-3 to 3)
|
||||||
|
index = changesReferenceRow[getNextChangingElement(index, white)] + n.value;
|
||||||
|
changesCurrentRow[changesCurrentRowCount] = index;
|
||||||
|
changesCurrentRowCount++;
|
||||||
|
white = !white;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
continue mode;
|
continue mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getNextChangingElement(int a0, boolean white) {
|
private int getNextChangingElement(final int a0, final boolean white) {
|
||||||
int start = white ? 0 : 1;
|
int start = white ? 0 : 1;
|
||||||
|
|
||||||
for (int i = start; i < changesReferenceRowCount; i += 2) {
|
for (int i = start; i < changesReferenceRowCount; i += 2) {
|
||||||
if (a0 < changesReferenceRow[i]) {
|
if (a0 < changesReferenceRow[i]) {
|
||||||
return i;
|
return i;
|
||||||
@@ -214,20 +229,24 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
eof: while (true) {
|
eof: while (true) {
|
||||||
// read till next EOL code
|
// read till next EOL code
|
||||||
Node n = eolOnlyTree.root;
|
Node n = eolOnlyTree.root;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
Node tmp = n;
|
|
||||||
n = n.walk(readBit());
|
n = n.walk(readBit());
|
||||||
if (n == null)
|
|
||||||
|
if (n == null) {
|
||||||
continue eof;
|
continue eof;
|
||||||
|
}
|
||||||
|
|
||||||
if (n.isLeaf) {
|
if (n.isLeaf) {
|
||||||
break eof;
|
break eof;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean k = optionG32D ? readBit() : true;
|
|
||||||
if (k) {
|
if (!optionG32D || readBit()) {
|
||||||
decode1D();
|
decode1D();
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
decode2D();
|
decode2D();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,23 +257,27 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
private void decodeRow() throws IOException {
|
private void decodeRow() throws IOException {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||||
decodeRowType2();
|
decodeRowType2();
|
||||||
break;
|
break;
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
decodeRowType4();
|
decodeRowType4();
|
||||||
break;
|
break;
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
decodeRowType6();
|
decodeRowType6();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
boolean white = true;
|
boolean white = true;
|
||||||
|
|
||||||
for (int i = 0; i <= changesCurrentRowCount; i++) {
|
for (int i = 0; i <= changesCurrentRowCount; i++) {
|
||||||
int nextChange = columns;
|
int nextChange = columns;
|
||||||
|
|
||||||
if (i != changesCurrentRowCount) {
|
if (i != changesCurrentRowCount) {
|
||||||
nextChange = changesCurrentRow[i];
|
nextChange = changesCurrentRow[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextChange > columns) {
|
if (nextChange > columns) {
|
||||||
nextChange = columns;
|
nextChange = columns;
|
||||||
}
|
}
|
||||||
@@ -281,6 +304,7 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
if (index % 8 == 0) {
|
if (index % 8 == 0) {
|
||||||
decodedRow[byteIndex] = 0;
|
decodedRow[byteIndex] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
@@ -295,43 +319,42 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
decodedLength = (index + 7) / 8;
|
decodedLength = (index + 7) / 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int decodeRun(Tree tree) throws IOException {
|
private int decodeRun(final Tree tree) throws IOException {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
|
|
||||||
Node n = tree.root;
|
Node n = tree.root;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
boolean bit = readBit();
|
boolean bit = readBit();
|
||||||
n = n.walk(bit);
|
n = n.walk(bit);
|
||||||
if (n == null)
|
|
||||||
|
if (n == null) {
|
||||||
throw new IOException("Unknown code in Huffman RLE stream");
|
throw new IOException("Unknown code in Huffman RLE stream");
|
||||||
|
}
|
||||||
|
|
||||||
if (n.isLeaf) {
|
if (n.isLeaf) {
|
||||||
total += n.value;
|
total += n.value;
|
||||||
if (n.value < 64) {
|
if (n.value < 64) {
|
||||||
return total;
|
return total;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
n = tree.root;
|
n = tree.root;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetBuffer() {
|
private void resetBuffer() throws IOException {
|
||||||
for (int i = 0; i < decodedRow.length; i++) {
|
for (int i = 0; i < decodedRow.length; i++) {
|
||||||
decodedRow[i] = 0;
|
decodedRow[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (bufferPos == -1) {
|
if (bufferPos == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
readBit();
|
||||||
boolean skip = readBit();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,22 +364,29 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
private boolean readBit() throws IOException {
|
private boolean readBit() throws IOException {
|
||||||
if (bufferPos < 0 || bufferPos > 7) {
|
if (bufferPos < 0 || bufferPos > 7) {
|
||||||
buffer = in.read();
|
buffer = in.read();
|
||||||
|
|
||||||
if (buffer == -1) {
|
if (buffer == -1) {
|
||||||
throw new EOFException("Unexpected end of Huffman RLE stream");
|
throw new EOFException("Unexpected end of Huffman RLE stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferPos = 0;
|
bufferPos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isSet;
|
boolean isSet;
|
||||||
|
|
||||||
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
|
||||||
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
isSet = ((buffer >> (bufferPos)) & 1) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferPos++;
|
bufferPos++;
|
||||||
if (bufferPos > 7)
|
|
||||||
|
if (bufferPos > 7) {
|
||||||
bufferPos = -1;
|
bufferPos = -1;
|
||||||
|
}
|
||||||
|
|
||||||
return isSet;
|
return isSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,23 +458,25 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
throw new IOException("mark/reset not supported");
|
throw new IOException("mark/reset not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Node {
|
private static final class Node {
|
||||||
Node left;
|
Node left;
|
||||||
Node right;
|
Node right;
|
||||||
|
|
||||||
int value; // > 63 non term.
|
int value; // > 63 non term.
|
||||||
|
|
||||||
boolean canBeFill = false;
|
boolean canBeFill = false;
|
||||||
boolean isLeaf = false;
|
boolean isLeaf = false;
|
||||||
|
|
||||||
void set(boolean next, Node node) {
|
void set(final boolean next, final Node node) {
|
||||||
if (!next) {
|
if (!next) {
|
||||||
left = node;
|
left = node;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
right = node;
|
right = node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Node walk(boolean next) {
|
Node walk(final boolean next) {
|
||||||
return next ? right : left;
|
return next ? right : left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,51 +486,69 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Tree {
|
private static final class Tree {
|
||||||
Node root = new Node();
|
final Node root = new Node();
|
||||||
|
|
||||||
void fill(int depth, int path, int value) throws IOException {
|
void fill(final int depth, final int path, final int value) throws IOException {
|
||||||
Node current = root;
|
Node current = root;
|
||||||
|
|
||||||
for (int i = 0; i < depth; i++) {
|
for (int i = 0; i < depth; i++) {
|
||||||
int bitPos = depth - 1 - i;
|
int bitPos = depth - 1 - i;
|
||||||
boolean isSet = ((path >> bitPos) & 1) == 1;
|
boolean isSet = ((path >> bitPos) & 1) == 1;
|
||||||
Node next = current.walk(isSet);
|
Node next = current.walk(isSet);
|
||||||
|
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
next = new Node();
|
next = new Node();
|
||||||
|
|
||||||
if (i == depth - 1) {
|
if (i == depth - 1) {
|
||||||
next.value = value;
|
next.value = value;
|
||||||
next.isLeaf = true;
|
next.isLeaf = true;
|
||||||
}
|
}
|
||||||
if (path == 0)
|
|
||||||
|
if (path == 0) {
|
||||||
next.canBeFill = true;
|
next.canBeFill = true;
|
||||||
|
}
|
||||||
|
|
||||||
current.set(isSet, next);
|
current.set(isSet, next);
|
||||||
} else {
|
|
||||||
if (next.isLeaf)
|
|
||||||
throw new IOException("node is leaf, no other following");
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (next.isLeaf) {
|
||||||
|
throw new IOException("node is leaf, no other following");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void fill(int depth, int path, Node node) throws IOException {
|
void fill(final int depth, final int path, final Node node) throws IOException {
|
||||||
Node current = root;
|
Node current = root;
|
||||||
|
|
||||||
for (int i = 0; i < depth; i++) {
|
for (int i = 0; i < depth; i++) {
|
||||||
int bitPos = depth - 1 - i;
|
int bitPos = depth - 1 - i;
|
||||||
boolean isSet = ((path >> bitPos) & 1) == 1;
|
boolean isSet = ((path >> bitPos) & 1) == 1;
|
||||||
Node next = current.walk(isSet);
|
Node next = current.walk(isSet);
|
||||||
|
|
||||||
if (next == null) {
|
if (next == null) {
|
||||||
if (i == depth - 1) {
|
if (i == depth - 1) {
|
||||||
next = node;
|
next = node;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
next = new Node();
|
next = new Node();
|
||||||
}
|
}
|
||||||
if (path == 0)
|
|
||||||
|
if (path == 0) {
|
||||||
next.canBeFill = true;
|
next.canBeFill = true;
|
||||||
|
}
|
||||||
|
|
||||||
current.set(isSet, next);
|
current.set(isSet, next);
|
||||||
} else {
|
|
||||||
if (next.isLeaf)
|
|
||||||
throw new IOException("node is leaf, no other following");
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (next.isLeaf) {
|
||||||
|
throw new IOException("node is leaf, no other following");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -506,105 +556,148 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
|
|
||||||
static final short[][] BLACK_CODES = {
|
static final short[][] BLACK_CODES = {
|
||||||
{ // 2 bits
|
{ // 2 bits
|
||||||
0x2, 0x3, },
|
0x2, 0x3,
|
||||||
|
},
|
||||||
{ // 3 bits
|
{ // 3 bits
|
||||||
0x2, 0x3, },
|
0x2, 0x3,
|
||||||
|
},
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
0x2, 0x3, },
|
0x2, 0x3,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
0x3, },
|
0x3,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
0x4, 0x5, },
|
0x4, 0x5,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
0x4, 0x5, 0x7, },
|
0x4, 0x5, 0x7,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
0x4, 0x7, },
|
0x4, 0x7,
|
||||||
|
},
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
0x18, },
|
0x18,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
0x17, 0x18, 0x37, 0x8, 0xf, },
|
0x17, 0x18, 0x37, 0x8, 0xf,
|
||||||
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd, },
|
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
|
||||||
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
|
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
|
||||||
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
|
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
|
||||||
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb, },
|
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
|
||||||
|
},
|
||||||
{ // 13 bits
|
{ // 13 bits
|
||||||
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
||||||
0x74, 0x75, 0x76, 0x77, } };
|
0x74, 0x75, 0x76, 0x77,
|
||||||
|
}
|
||||||
|
};
|
||||||
static final short[][] BLACK_RUN_LENGTHS = {
|
static final short[][] BLACK_RUN_LENGTHS = {
|
||||||
{ // 2 bits
|
{ // 2 bits
|
||||||
3, 2, },
|
3, 2,
|
||||||
|
},
|
||||||
{ // 3 bits
|
{ // 3 bits
|
||||||
1, 4, },
|
1, 4,
|
||||||
|
},
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
6, 5, },
|
6, 5,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
7, },
|
7,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
9, 8, },
|
9, 8,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
10, 11, 12, },
|
10, 11, 12,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
13, 14, },
|
13, 14,
|
||||||
|
},
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
15, },
|
15,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
16, 17, 0, 18, 64, },
|
16, 17, 0, 18, 64,
|
||||||
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920, },
|
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
|
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
|
||||||
54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
|
54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
|
||||||
27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43, },
|
27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43,
|
||||||
|
},
|
||||||
{ // 13 bits
|
{ // 13 bits
|
||||||
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
|
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
|
||||||
1152, 1216, } };
|
1152, 1216,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static final short[][] WHITE_CODES = {
|
public static final short[][] WHITE_CODES = {
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
0x7, 0x8, 0xb, 0xc, 0xe, 0xf, },
|
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8, },
|
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8, },
|
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc, },
|
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
|
||||||
0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
|
0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
|
||||||
0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb, },
|
0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
|
||||||
|
},
|
||||||
{ // 9 bits
|
{ // 9 bits
|
||||||
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, },
|
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
},
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
0x8, 0xc, 0xd, },
|
0x8, 0xc, 0xd,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, } };
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public static final short[][] WHITE_RUN_LENGTHS = {
|
public static final short[][] WHITE_RUN_LENGTHS = {
|
||||||
{ // 4 bits
|
{ // 4 bits
|
||||||
2, 3, 4, 5, 6, 7, },
|
2, 3, 4, 5, 6, 7,
|
||||||
|
},
|
||||||
{ // 5 bits
|
{ // 5 bits
|
||||||
128, 8, 9, 64, 10, 11, },
|
128, 8, 9, 64, 10, 11,
|
||||||
|
},
|
||||||
{ // 6 bits
|
{ // 6 bits
|
||||||
192, 1664, 16, 17, 13, 14, 15, 1, 12, },
|
192, 1664, 16, 17, 13, 14, 15, 1, 12,
|
||||||
|
},
|
||||||
{ // 7 bits
|
{ // 7 bits
|
||||||
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19, },
|
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
|
||||||
|
},
|
||||||
{ // 8 bits
|
{ // 8 bits
|
||||||
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, 44, 30, 61, 62, 63, 0, 320, 384, 45,
|
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, 44, 30, 61, 62, 63, 0, 320, 384, 45,
|
||||||
59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48, },
|
59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
|
||||||
{ // 9
|
},
|
||||||
// bits
|
{ // 9 bits
|
||||||
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, },
|
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
|
||||||
|
},
|
||||||
{ // 10 bits
|
{ // 10 bits
|
||||||
},
|
},
|
||||||
{ // 11 bits
|
{ // 11 bits
|
||||||
1792, 1856, 1920, },
|
1792, 1856, 1920,
|
||||||
|
},
|
||||||
{ // 12 bits
|
{ // 12 bits
|
||||||
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, } };
|
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
final static Node EOL;
|
final static Node EOL;
|
||||||
final static Node FILL;
|
final static Node FILL;
|
||||||
@@ -631,8 +724,9 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
try {
|
try {
|
||||||
eolOnlyTree.fill(12, 0, FILL);
|
eolOnlyTree.fill(12, 0, FILL);
|
||||||
eolOnlyTree.fill(12, 1, EOL);
|
eolOnlyTree.fill(12, 1, EOL);
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
blackRunTree = new Tree();
|
blackRunTree = new Tree();
|
||||||
@@ -644,9 +738,11 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
blackRunTree.fill(12, 0, FILL);
|
blackRunTree.fill(12, 0, FILL);
|
||||||
blackRunTree.fill(12, 1, EOL);
|
blackRunTree.fill(12, 1, EOL);
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
|
||||||
whiteRunTree = new Tree();
|
whiteRunTree = new Tree();
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < WHITE_CODES.length; i++) {
|
for (int i = 0; i < WHITE_CODES.length; i++) {
|
||||||
@@ -654,10 +750,12 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]);
|
whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
whiteRunTree.fill(12, 0, FILL);
|
whiteRunTree.fill(12, 0, FILL);
|
||||||
whiteRunTree.fill(12, 1, EOL);
|
whiteRunTree.fill(12, 1, EOL);
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
codeTree = new Tree();
|
codeTree = new Tree();
|
||||||
@@ -671,8 +769,9 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
|
|||||||
codeTree.fill(3, 2, -1); // V_L(1)
|
codeTree.fill(3, 2, -1); // V_L(1)
|
||||||
codeTree.fill(6, 2, -2); // V_L(2)
|
codeTree.fill(6, 2, -2); // V_L(2)
|
||||||
codeTree.fill(7, 2, -3); // V_L(3)
|
codeTree.fill(7, 2, -3); // V_L(3)
|
||||||
} catch (Exception e) {
|
}
|
||||||
e.printStackTrace();
|
catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -190,7 +190,6 @@ abstract class LZWDecoder implements Decoder {
|
|||||||
|
|
||||||
public static Decoder create(boolean oldBitReversedStream) {
|
public static Decoder create(boolean oldBitReversedStream) {
|
||||||
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
|
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
|
||||||
// return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWTreeDecoder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class LZWSpecDecoder extends LZWDecoder {
|
static final class LZWSpecDecoder extends LZWDecoder {
|
||||||
|
@@ -62,6 +62,8 @@ interface TIFFExtension {
|
|||||||
int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
|
int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
|
||||||
int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
|
int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
|
||||||
|
|
||||||
|
int FILL_RIGHT_TO_LEFT = 2;
|
||||||
|
|
||||||
int SAMPLEFORMAT_INT = 2;
|
int SAMPLEFORMAT_INT = 2;
|
||||||
int SAMPLEFORMAT_FP = 3;
|
int SAMPLEFORMAT_FP = 3;
|
||||||
int SAMPLEFORMAT_UNDEFINED = 4;
|
int SAMPLEFORMAT_UNDEFINED = 4;
|
||||||
|
@@ -41,7 +41,7 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo {
|
|||||||
protected TIFFProviderInfo() {
|
protected TIFFProviderInfo() {
|
||||||
super(
|
super(
|
||||||
TIFFProviderInfo.class,
|
TIFFProviderInfo.class,
|
||||||
new String[] {"tiff", "TIFF"},
|
new String[] {"tiff", "TIFF", "tif", "TIF"},
|
||||||
new String[] {"tif", "tiff"},
|
new String[] {"tif", "tiff"},
|
||||||
new String[] {
|
new String[] {
|
||||||
"image/tiff", "image/x-tiff"
|
"image/tiff", "image/x-tiff"
|
||||||
|
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
<!-- Stand-alone readers/writers -->
|
<!-- Stand-alone readers/writers -->
|
||||||
<module>imageio-bmp</module>
|
<module>imageio-bmp</module>
|
||||||
|
<module>imageio-hdr</module>
|
||||||
<module>imageio-icns</module>
|
<module>imageio-icns</module>
|
||||||
<module>imageio-iff</module>
|
<module>imageio-iff</module>
|
||||||
<module>imageio-jpeg</module>
|
<module>imageio-jpeg</module>
|
||||||
|
Reference in New Issue
Block a user