mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 12:35:29 -04:00
Added CRW read
This commit is contained in:
parent
4fd20f7fa0
commit
0498a1a095
@ -0,0 +1,12 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.crw;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRW
|
||||||
|
*
|
||||||
|
* @see <a href="https://sno.phy.queensu.ca/~phil/exiftool/canon_raw.html">The Canon RAW (CRW) File Format</a>
|
||||||
|
*/
|
||||||
|
public interface CRW {
|
||||||
|
int TAG_SLICES = 50752;
|
||||||
|
}
|
@ -0,0 +1,388 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.crw;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRWDecoder
|
||||||
|
*
|
||||||
|
* @see <a href="http://cybercom.net/~dcoffin/dcraw/decompress.c">A simple reference decompressor for CRW files</a>
|
||||||
|
* @author Harald Kuhr (Java port)
|
||||||
|
* @author Dave Coffin (original decompress.c)
|
||||||
|
*/
|
||||||
|
final class CRWDecoder {
|
||||||
|
|
||||||
|
static class Decode {
|
||||||
|
Decode[] branch = new Decode[2];
|
||||||
|
int leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ImageInputStream imageInput;
|
||||||
|
|
||||||
|
private final int height;
|
||||||
|
private final int width;
|
||||||
|
private final int table;
|
||||||
|
|
||||||
|
private Decode[] first_decode = new Decode[32];
|
||||||
|
private Decode[] second_decode = new Decode[512];
|
||||||
|
|
||||||
|
CRWDecoder(ImageInputStream imageInput, int width, int height, int table) {
|
||||||
|
this.imageInput = imageInput;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.table = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code false} if the image starts with compressed data,
|
||||||
|
* {@code true} if it starts with uncompressed low-order bits.
|
||||||
|
* <p>
|
||||||
|
* In Canon compressed data, 0xff is always followed by 0x00.
|
||||||
|
*/
|
||||||
|
private boolean canonHasLowbits() throws IOException {
|
||||||
|
byte[] test = new byte[0x4000]; // TODO: Should probably be (height * width / 4) * enough bytes...
|
||||||
|
|
||||||
|
imageInput.seek(0);
|
||||||
|
imageInput.readFully(test);
|
||||||
|
|
||||||
|
boolean ret = true;
|
||||||
|
// for (int i = 540; i < test.length - 1; i++) {
|
||||||
|
for (int i = 0; i < test.length - 1; i++) { // Note: The original 540 is probably CIFF header length + offset (26 + 514)
|
||||||
|
if ((test[i] & 0xff) == 0xff) {
|
||||||
|
if (test[i + 1] != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
A rough description of Canon's compression algorithm:
|
||||||
|
|
||||||
|
+ Each pixel outputs a 10-bit sample, from 0 to 1023.
|
||||||
|
+ Split the data into blocks of 64 samples each.
|
||||||
|
+ Subtract from each sample the value of the sample two positions
|
||||||
|
to the left, which has the same color filter. From the two
|
||||||
|
leftmost samples in each row, subtract 512.
|
||||||
|
+ For each nonzero sample, make a token consisting of two four-bit
|
||||||
|
numbers. The low nibble is the number of bits required to
|
||||||
|
represent the sample, and the high nibble is the number of
|
||||||
|
zero samples preceding this sample.
|
||||||
|
+ Output this token as a variable-length bitstring using
|
||||||
|
one of three tablesets. Follow it with a fixed-length
|
||||||
|
bitstring containing the sample.
|
||||||
|
|
||||||
|
The "first_decode" table is used for the first sample in each
|
||||||
|
block, and the "second_decode" table is used for the others.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a decode tree according the specification in *source.
|
||||||
|
* The first 16 bytes specify how many codes should be 1-bit, 2-bit
|
||||||
|
* 3-bit, etc. Bytes after that are the leaf values.
|
||||||
|
* <p>
|
||||||
|
* For example, if the source is
|
||||||
|
* <p>
|
||||||
|
* { 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0,
|
||||||
|
* 0x04,0x03,0x05,0x06,0x02,0x07,0x01,0x08,0x09,0x00,0x0a,0x0b,0xff },
|
||||||
|
* <p>
|
||||||
|
* then the code is
|
||||||
|
* <p>
|
||||||
|
* 00 0x04
|
||||||
|
* 010 0x03
|
||||||
|
* 011 0x05
|
||||||
|
* 100 0x06
|
||||||
|
* 101 0x02
|
||||||
|
* 1100 0x07
|
||||||
|
* 1101 0x01
|
||||||
|
* 11100 0x08
|
||||||
|
* 11101 0x09
|
||||||
|
* 11110 0x00
|
||||||
|
* 111110 0x0a
|
||||||
|
* 1111110 0x0b
|
||||||
|
* 1111111 0xff
|
||||||
|
*/
|
||||||
|
private Decode[] free; /* Next unused node */
|
||||||
|
private int freeIndex;
|
||||||
|
private int leaf; /* no. of leaves already added */
|
||||||
|
|
||||||
|
// private void make_decoder(struct decode *dest, const uchar *source, int level)
|
||||||
|
private void make_decoder(Decode[] dest, int destIndex, final byte[] source, int level) {
|
||||||
|
// static struct decode *free; /* Next unused node */
|
||||||
|
// static int leaf; /* no. of leaves already added */
|
||||||
|
int i, next;
|
||||||
|
|
||||||
|
if (level==0) {
|
||||||
|
free = dest;
|
||||||
|
freeIndex = 0;
|
||||||
|
|
||||||
|
leaf = 0;
|
||||||
|
}
|
||||||
|
// free++;
|
||||||
|
freeIndex++;
|
||||||
|
// At what level should the next leaf appear?
|
||||||
|
for (i=next=0; i <= leaf && next < 16; ) {
|
||||||
|
i += (source[next++] & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i > leaf) {
|
||||||
|
if (level < next) { /* Are we there yet? */
|
||||||
|
// dest->branch[0] = free;
|
||||||
|
// make_decoder(free, source, level + 1);
|
||||||
|
dest[destIndex].branch[0] = free[freeIndex];
|
||||||
|
make_decoder(free, freeIndex, source, level + 1);
|
||||||
|
// dest->branch[1] = free;
|
||||||
|
// make_decoder(free, source, level + 1);
|
||||||
|
dest[destIndex].branch[1] = free[freeIndex];
|
||||||
|
make_decoder(free, freeIndex, source, level + 1);
|
||||||
|
} else {
|
||||||
|
// dest->leaf = source[16 + leaf++];
|
||||||
|
dest[destIndex].leaf = (source[16 + leaf++] & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final byte[][] first_tree/*[3][29]*/ = {
|
||||||
|
{ 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0,
|
||||||
|
0x04,0x03,0x05,0x06,0x02,0x07,0x01,0x08,0x09,0x00,0x0a,0x0b, (byte) 0xff},
|
||||||
|
|
||||||
|
{ 0,2,2,3,1,1,1,1,2,0,0,0,0,0,0,0,
|
||||||
|
0x03,0x02,0x04,0x01,0x05,0x00,0x06,0x07,0x09,0x08,0x0a,0x0b, (byte) 0xff},
|
||||||
|
|
||||||
|
{ 0,0,6,3,1,1,2,0,0,0,0,0,0,0,0,0,
|
||||||
|
0x06,0x05,0x07,0x04,0x08,0x03,0x09,0x02,0x00,0x0a,0x01,0x0b, (byte) 0xff},
|
||||||
|
};
|
||||||
|
|
||||||
|
static final byte[][] second_tree/*[3][180]*/ = {
|
||||||
|
{ 0,2,2,2,1,4,2,1,2,5,1,1,0,0,0, (byte) 139,
|
||||||
|
0x03,0x04,0x02,0x05,0x01,0x06,0x07,0x08,
|
||||||
|
0x12,0x13,0x11,0x14,0x09,0x15,0x22,0x00,0x21,0x16,0x0a, (byte) 0xf0,
|
||||||
|
0x23,0x17,0x24,0x31,0x32,0x18,0x19,0x33,0x25,0x41,0x34,0x42,
|
||||||
|
0x35,0x51,0x36,0x37,0x38,0x29,0x79,0x26,0x1a,0x39,0x56,0x57,
|
||||||
|
0x28,0x27,0x52,0x55,0x58,0x43,0x76,0x59,0x77,0x54,0x61, (byte) 0xf9,
|
||||||
|
0x71,0x78,0x75, (byte) 0x96, (byte) 0x97,0x49, (byte) 0xb7,0x53, (byte) 0xd7,0x74, (byte) 0xb6, (byte) 0x98,
|
||||||
|
0x47,0x48, (byte) 0x95,0x69, (byte) 0x99, (byte) 0x91, (byte) 0xfa, (byte) 0xb8,0x68, (byte) 0xb5, (byte) 0xb9, (byte) 0xd6,
|
||||||
|
(byte) 0xf7, (byte) 0xd8,0x67,0x46,0x45, (byte) 0x94,(byte) 0x89,(byte) 0xf8,(byte) 0x81,(byte) 0xd5,(byte) 0xf6,(byte) 0xb4,
|
||||||
|
(byte) 0x88,(byte) 0xb1,0x2a,0x44,0x72,(byte) 0xd9,(byte) 0x87,0x66,(byte) 0xd4,(byte) 0xf5,0x3a,(byte) 0xa7,
|
||||||
|
0x73,(byte) 0xa9,(byte) 0xa8,(byte) 0x86,0x62,(byte) 0xc7,0x65,(byte) 0xc8,(byte) 0xc9,(byte) 0xa1,(byte) 0xf4,(byte) 0xd1,
|
||||||
|
(byte) 0xe9,0x5a,(byte) 0x92,(byte) 0x85,(byte) 0xa6,(byte) 0xe7,(byte) 0x93,(byte) 0xe8,(byte) 0xc1,(byte) 0xc6,0x7a,0x64,
|
||||||
|
(byte) 0xe1,0x4a,0x6a,(byte) 0xe6,(byte) 0xb3,(byte) 0xf1,(byte) 0xd3,(byte) 0xa5,(byte) 0x8a,(byte) 0xb2,(byte) 0x9a,(byte) 0xba,
|
||||||
|
(byte) 0x84,(byte) 0xa4,0x63,(byte) 0xe5,(byte) 0xc5,(byte) 0xf3,(byte) 0xd2,(byte) 0xc4,(byte) 0x82,(byte) 0xaa,(byte) 0xda,(byte) 0xe4,
|
||||||
|
(byte) 0xf2,(byte) 0xca,(byte) 0x83,(byte) 0xa3,(byte) 0xa2,(byte) 0xc3,(byte) 0xea,(byte) 0xc2,(byte) 0xe2,(byte) 0xe3,(byte) 0xff,(byte) 0xff },
|
||||||
|
|
||||||
|
{ 0,2,2,1,4,1,4,1,3,3,1,0,0,0,0, (byte) 140,
|
||||||
|
0x02,0x03,0x01,0x04,0x05,0x12,0x11,0x06,
|
||||||
|
0x13,0x07,0x08,0x14,0x22,0x09,0x21,0x00,0x23,0x15,0x31,0x32,
|
||||||
|
0x0a,0x16,(byte) 0xf0,0x24,0x33,0x41,0x42,0x19,0x17,0x25,0x18,0x51,
|
||||||
|
0x34,0x43,0x52,0x29,0x35,0x61,0x39,0x71,0x62,0x36,0x53,0x26,
|
||||||
|
0x38,0x1a,0x37,(byte) 0x81,0x27,(byte) 0x91,0x79,0x55,0x45,0x28,0x72,0x59,
|
||||||
|
(byte) 0xa1,(byte) 0xb1,0x44,0x69,0x54,0x58,(byte) 0xd1,(byte) 0xfa,0x57,(byte) 0xe1,(byte) 0xf1,(byte) 0xb9,
|
||||||
|
0x49,0x47,0x63,0x6a,(byte) 0xf9,0x56,0x46,(byte) 0xa8,0x2a,0x4a,0x78,(byte) 0x99,
|
||||||
|
0x3a,0x75,0x74,(byte) 0x86,0x65,(byte) 0xc1,0x76,(byte) 0xb6,(byte) 0x96,(byte) 0xd6,(byte) 0x89,(byte) 0x85,
|
||||||
|
(byte) 0xc9,(byte) 0xf5,(byte) 0x95,(byte) 0xb4,(byte) 0xc7,(byte) 0xf7,(byte) 0x8a,(byte) 0x97,(byte) 0xb8,0x73,(byte) 0xb7,(byte) 0xd8,
|
||||||
|
(byte) 0xd9,(byte) 0x87,(byte) 0xa7,0x7a,0x48,(byte) 0x82,(byte) 0x84,(byte) 0xea,(byte) 0xf4,(byte) 0xa6,(byte) 0xc5,0x5a,
|
||||||
|
(byte) 0x94,(byte) 0xa4,(byte) 0xc6,(byte) 0x92,(byte) 0xc3,0x68,(byte) 0xb5,(byte) 0xc8,(byte) 0xe4,(byte) 0xe5,(byte) 0xe6,(byte) 0xe9,
|
||||||
|
(byte) 0xa2,(byte) 0xa3,(byte) 0xe3,(byte) 0xc2,0x66,0x67,(byte) 0x93,(byte) 0xaa,(byte) 0xd4,(byte) 0xd5,(byte) 0xe7,(byte) 0xf8,
|
||||||
|
(byte) 0x88,(byte) 0x9a,(byte) 0xd7,0x77,(byte) 0xc4,0x64,(byte) 0xe2,(byte) 0x98,(byte) 0xa5,(byte) 0xca,(byte) 0xda,(byte) 0xe8,
|
||||||
|
(byte) 0xf3,(byte) 0xf6,(byte) 0xa9,(byte) 0xb2,(byte) 0xb3,(byte) 0xf2,(byte) 0xd2,(byte) 0x83,(byte) 0xba,(byte) 0xd3,(byte) 0xff,(byte) 0xff },
|
||||||
|
|
||||||
|
{ 0,0,6,2,1,3,3,2,5,1,2,2,8,10,0,117,
|
||||||
|
0x04,0x05,0x03,0x06,0x02,0x07,0x01,0x08,
|
||||||
|
0x09,0x12,0x13,0x14,0x11,0x15,0x0a,0x16,0x17,(byte) 0xf0,0x00,0x22,
|
||||||
|
0x21,0x18,0x23,0x19,0x24,0x32,0x31,0x25,0x33,0x38,0x37,0x34,
|
||||||
|
0x35,0x36,0x39,0x79,0x57,0x58,0x59,0x28,0x56,0x78,0x27,0x41,
|
||||||
|
0x29,0x77,0x26,0x42,0x76,(byte) 0x99,0x1a,0x55,(byte) 0x98,(byte) 0x97,(byte) 0xf9,0x48,
|
||||||
|
0x54,(byte) 0x96,(byte) 0x89,0x47,(byte) 0xb7,0x49,(byte) 0xfa,0x75,0x68,(byte) 0xb6,0x67,0x69,
|
||||||
|
(byte) 0xb9,(byte) 0xb8,(byte) 0xd8,0x52,(byte) 0xd7,(byte) 0x88,(byte) 0xb5,0x74,0x51,0x46,(byte) 0xd9,(byte) 0xf8,
|
||||||
|
0x3a,(byte) 0xd6,(byte) 0x87,0x45,0x7a,(byte) 0x95,(byte) 0xd5,(byte) 0xf6,(byte) 0x86,(byte) 0xb4,(byte) 0xa9,(byte) 0x94,
|
||||||
|
0x53,0x2a,(byte) 0xa8,0x43,(byte) 0xf5,(byte) 0xf7,(byte) 0xd4,0x66,(byte) 0xa7,0x5a,0x44,(byte) 0x8a,
|
||||||
|
(byte) 0xc9,(byte) 0xe8,(byte) 0xc8,(byte) 0xe7,(byte) 0x9a,0x6a,0x73,0x4a,0x61,(byte) 0xc7,(byte) 0xf4,(byte) 0xc6,
|
||||||
|
0x65,(byte) 0xe9,0x72,(byte) 0xe6,0x71,(byte) 0x91,(byte) 0x93,(byte) 0xa6,(byte) 0xda,(byte) 0x92,(byte) 0x85,0x62,
|
||||||
|
(byte) 0xf3,(byte) 0xc5,(byte) 0xb2,(byte) 0xa4,(byte) 0x84,(byte) 0xba,0x64,(byte) 0xa5,(byte) 0xb3,(byte) 0xd2,(byte) 0x81,(byte) 0xe5,
|
||||||
|
(byte) 0xd3,(byte) 0xaa,(byte) 0xc4,(byte) 0xca,(byte) 0xf2,(byte) 0xb1,(byte) 0xe4,(byte) 0xd1,(byte) 0x83,0x63,(byte) 0xea,(byte) 0xc3,
|
||||||
|
(byte) 0xe2,(byte) 0x82,(byte) 0xf1,(byte) 0xa3,(byte) 0xc2,(byte) 0xa1,(byte) 0xc1,(byte) 0xe3,(byte) 0xa2,(byte) 0xe1,(byte) 0xff,(byte) 0xff }
|
||||||
|
};
|
||||||
|
|
||||||
|
private void init_tables(int table) {
|
||||||
|
if (table > 2) table = 2;
|
||||||
|
// memset( first_decode, 0, sizeof first_decode);
|
||||||
|
// memset(second_decode, 0, sizeof second_decode);
|
||||||
|
// make_decoder( first_decode, first_tree[table], 0);
|
||||||
|
// make_decoder(second_decode, second_tree[table], 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < first_decode.length; i++) {
|
||||||
|
first_decode[i] = new Decode();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < second_decode.length; i++) {
|
||||||
|
second_decode[i] = new Decode();
|
||||||
|
}
|
||||||
|
|
||||||
|
make_decoder( first_decode, 0, first_tree[table], 0);
|
||||||
|
make_decoder(second_decode, 0, second_tree[table], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#if 0
|
||||||
|
// writebits (int val, int nbits)
|
||||||
|
// {
|
||||||
|
// val <<= 32 - nbits;
|
||||||
|
// while (nbits--) {
|
||||||
|
// putchar(val & 0x80000000 ? '1':'0');
|
||||||
|
// val <<= 1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
getbits(-1) initializes the buffer
|
||||||
|
getbits(n) where 0 <= n <= 25 returns an n-bit integer
|
||||||
|
*/
|
||||||
|
private int bitbuf=0;
|
||||||
|
private int vbits=0;
|
||||||
|
|
||||||
|
int getbits(int nbits) throws IOException {
|
||||||
|
int c;
|
||||||
|
|
||||||
|
if (nbits == 0) return 0;
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (nbits == -1) {
|
||||||
|
ret = bitbuf = vbits = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// ret = bitbuf << (32 - vbits) >> (32 - nbits);
|
||||||
|
ret = bitbuf << (32 - vbits) >>> (32 - nbits);
|
||||||
|
vbits -= nbits;
|
||||||
|
}
|
||||||
|
while (vbits < 25) {
|
||||||
|
// c=fgetc(ifp);
|
||||||
|
c=imageInput.readUnsignedByte();
|
||||||
|
bitbuf = (bitbuf << 8) + c;
|
||||||
|
// if (c == 0xff) fgetc(ifp); /* always extra 00 after ff */
|
||||||
|
if (c == 0xff) {
|
||||||
|
imageInput.readUnsignedByte(); // always extra 00 after ff
|
||||||
|
}
|
||||||
|
vbits += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
short[] decode() throws IOException {
|
||||||
|
short[] result = new short[width * height];
|
||||||
|
|
||||||
|
// struct Decode *decode, *dindex;
|
||||||
|
Decode decode;
|
||||||
|
Decode dindex;
|
||||||
|
int i, j, leaf, len, diff, r;
|
||||||
|
long save;
|
||||||
|
int[] diffbuf = new int[64]; // h * w = 8 * 8 for each compressed block
|
||||||
|
int carry=0, column=0;
|
||||||
|
int[] base = new int[2];
|
||||||
|
// unsigned short outbuf[64];
|
||||||
|
short[] outbuf = new short[64];
|
||||||
|
|
||||||
|
int c;
|
||||||
|
|
||||||
|
boolean lowbits = canonHasLowbits();
|
||||||
|
|
||||||
|
init_tables(table);
|
||||||
|
|
||||||
|
// fseek (ifp, 540 + lowbits*height*width/4, SEEK_SET);
|
||||||
|
// imageInput.seek(540 + (lowbits ? 1 : 0) * height * width / 4);
|
||||||
|
imageInput.seek((lowbits ? 1 : 0) * height * width / 4); // NOTE: The original 540 offset is probably CIFF header length: 26 + DecoderTable[2]: 514
|
||||||
|
getbits(-1); /* Prime the bit buffer */
|
||||||
|
|
||||||
|
while (column < width * height) {
|
||||||
|
// memset(diffbuf,0,sizeof diffbuf);
|
||||||
|
Arrays.fill(diffbuf, 0);
|
||||||
|
|
||||||
|
// decode = first_decode;
|
||||||
|
decode = first_decode[0];
|
||||||
|
for (i = 0; i < 64; i++) {
|
||||||
|
|
||||||
|
// for (dindex=decode; dindex->branch[0]; )
|
||||||
|
// dindex = dindex->branch[getbits(1)];
|
||||||
|
for (dindex = decode; dindex.branch[0] != null; ) {
|
||||||
|
dindex = dindex.branch[getbits(1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// leaf = dindex->leaf;
|
||||||
|
leaf = dindex.leaf;
|
||||||
|
// decode = second_decode;
|
||||||
|
decode = second_decode[0];
|
||||||
|
|
||||||
|
if (leaf == 0 && i != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (leaf == 0xff) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += (leaf >> 4);
|
||||||
|
len = leaf & 15;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
diff = getbits(len);
|
||||||
|
if ((diff & (1 << (len - 1))) == 0) {
|
||||||
|
diff -= (1 << len) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 64) {
|
||||||
|
diffbuf[i] = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Ok ---
|
||||||
|
|
||||||
|
diffbuf[0] += carry;
|
||||||
|
carry = diffbuf[0];
|
||||||
|
|
||||||
|
for (i = 0; i < 64; i++) {
|
||||||
|
if (column++ % width == 0) {
|
||||||
|
base[0] = base[1] = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: The original C code uses unsigned short, so this may overflow differently
|
||||||
|
// outbuf[i] = ( base[i & 1] += diffbuf[i] );
|
||||||
|
|
||||||
|
outbuf[i] = (short) (base[i & 1] += diffbuf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Ok ---
|
||||||
|
|
||||||
|
if (lowbits) {
|
||||||
|
// save = ftell(ifp);
|
||||||
|
save = imageInput.getStreamPosition();
|
||||||
|
// fseek (ifp, (column-64)/4 + 26, SEEK_SET);
|
||||||
|
// imageInput.seek((column - 64) / 4 + 26);
|
||||||
|
imageInput.seek((column - 64) / 4); // Note: The original 26 is CIFF header length (?)
|
||||||
|
for (i = j = 0; j < 64 / 4; j++) {
|
||||||
|
// c = fgetc(ifp);
|
||||||
|
c = imageInput.readUnsignedByte();
|
||||||
|
for (r = 0; r < 8; r += 2) {
|
||||||
|
// TODO: Is this correct? The original C code is on one line, the Java equivalent then throws an AIOOBE...
|
||||||
|
// outbuf[i++] = (outbuf[i] << 2) + ((c >> r) & 3);
|
||||||
|
short sample = (short) ((outbuf[i] << 2) + ((c >> r) & 3));
|
||||||
|
outbuf[i++] = sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fseek (ifp, save, SEEK_SET);
|
||||||
|
imageInput.seek(save);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fwrite(outbuf,2,64,stdout);
|
||||||
|
System.arraycopy(outbuf, 0, result, column - 64, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,294 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014, 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.crw;
|
||||||
|
|
||||||
|
import static java.util.Collections.singletonList;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.crw.ciff.CIFF;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.crw.ciff.CIFFDirectory;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.crw.ciff.CIFFEntry;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.crw.ciff.CIFFReader;
|
||||||
|
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
|
||||||
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canon CRW RAW ImageReader.
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @see <a href="http://cybercom.net/~dcoffin/dcraw/">Decoding raw digital photos in Linux</a>
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author CRW C reference decoder written by Dave Coffin.
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: CRWImageReader.java,v 1.0 07.04.14 21:31 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class CRWImageReader extends ImageReaderBase {
|
||||||
|
// TODO: Avoid duped code from TIFFImageReader, create a ExifRAWBaseImageReader something
|
||||||
|
// TODO: Probably a good idea to move some of the getAsShort/Int/Long/Array to TIFF/EXIF metadata module
|
||||||
|
// TODO: Automatic EXIF rotation, if we find a good way to do that for JPEG/EXIF/TIFF and keeping the metadata sane...
|
||||||
|
|
||||||
|
final static boolean DEBUG = true; //"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.crw.debug"));
|
||||||
|
|
||||||
|
// TODO: Thumbnail may or may not be present
|
||||||
|
|
||||||
|
private CIFFDirectory heap;
|
||||||
|
private boolean hasJPEGPreview;
|
||||||
|
private boolean hasRAWData;
|
||||||
|
|
||||||
|
CRWImageReader(final ImageReaderSpi provider) {
|
||||||
|
super(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetMembers() {
|
||||||
|
heap = null;
|
||||||
|
hasJPEGPreview = false;
|
||||||
|
hasRAWData = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readMetadata() throws IOException {
|
||||||
|
if (imageInput == null) {
|
||||||
|
throw new IllegalStateException("input not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heap == null) {
|
||||||
|
heap = new CIFFReader().read(imageInput);
|
||||||
|
|
||||||
|
hasJPEGPreview = heap.getEntryById(CIFF.TAG_JPEG_PREVIEW) != null;
|
||||||
|
hasRAWData = heap.getEntryById(CIFF.TAG_RAW_DATA) != null;
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("directory: " + heap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumImages(final boolean allowSearch) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
|
||||||
|
return (hasJPEGPreview ? 1 : 0) + (hasRAWData ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumThumbnails(int imageIndex) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
|
// TODO: Count thumbnails!
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readerSupportsThumbnails() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThumbnailWidth(int imageIndex, int thumbnailIndex) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
|
// TODO: Need to get from JPEGImageReader (no ImageWidth tag), this is an ok (but lame) implementation for now
|
||||||
|
return super.getThumbnailWidth(imageIndex, thumbnailIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThumbnailHeight(int imageIndex, int thumbnailIndex) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
|
// TODO: Need to get from JPEGImageReader (no ImageHeight tag), this is an ok (but lame) implementation for now
|
||||||
|
return super.getThumbnailHeight(imageIndex, thumbnailIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage readThumbnail(int imageIndex, int thumbnailIndex) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
|
||||||
|
if (imageIndex != 0) {
|
||||||
|
throw new IndexOutOfBoundsException("No thumbnail for imageIndex: " + imageIndex);
|
||||||
|
}
|
||||||
|
if (thumbnailIndex >= getNumThumbnails(0)) {
|
||||||
|
throw new IndexOutOfBoundsException("thumbnailIndex out of bounds: " + thumbnailIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException("readThumbnail");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
|
||||||
|
if (imageIndex == 0 && hasJPEGPreview) {
|
||||||
|
return getJPEGPreviewDimension().width;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRawImageDimension().width;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
|
||||||
|
if (imageIndex == 0 && hasJPEGPreview) {
|
||||||
|
return getJPEGPreviewDimension().height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getRawImageDimension().height;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dimension getJPEGPreviewDimension() {
|
||||||
|
// TODO: This is incorrect for the G1 sample, which stores the RAW size here...
|
||||||
|
CIFFDirectory imageProperties = heap.getSubDirectory(CIFF.TAG_IMAGE_PROPERTIES);
|
||||||
|
CIFFEntry imageSpec = imageProperties.getEntryById(CIFF.TAG_IMAGE_SPEC);
|
||||||
|
int[] imageSpecValue = (int[]) imageSpec.getValue();
|
||||||
|
|
||||||
|
return new Dimension(imageSpecValue[0], imageSpecValue[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dimension getRawImageDimension() {
|
||||||
|
CIFFDirectory exifInfo = getExifInfo();
|
||||||
|
CIFFEntry sensorInfo = exifInfo.getEntryById(CIFF.TAG_SENSOR_INFO);
|
||||||
|
|
||||||
|
if (sensorInfo != null) {
|
||||||
|
short[] sensorInfoValue = (short[]) sensorInfo.getValue();
|
||||||
|
|
||||||
|
return new Dimension(sensorInfoValue[1], sensorInfoValue[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PowerShot Pro70 et al, don't have a JPEG preview and contains the dimensions in the ImageSpec tag
|
||||||
|
return getJPEGPreviewDimension();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CIFFDirectory getExifInfo() {
|
||||||
|
CIFFDirectory imageProperties = heap.getSubDirectory(CIFF.TAG_IMAGE_PROPERTIES);
|
||||||
|
return imageProperties.getSubDirectory(CIFF.TAG_EXIF_INFORMATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
|
||||||
|
if (imageIndex == 0 && hasJPEGPreview) {
|
||||||
|
BufferedImage image = readJPEGPreview();
|
||||||
|
return singletonList(ImageTypeSpecifier.createFromRenderedImage(image)).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||||
|
readMetadata();
|
||||||
|
|
||||||
|
if (imageIndex == 0 && hasJPEGPreview) {
|
||||||
|
return readJPEGPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
return readRAWData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage readJPEGPreview() throws IOException {
|
||||||
|
CIFFEntry jpegPreview = heap.getEntryById(CIFF.TAG_JPEG_PREVIEW);
|
||||||
|
|
||||||
|
imageInput.seek(jpegPreview.offset());
|
||||||
|
|
||||||
|
return ImageIO.read(new SubImageInputStream(imageInput, jpegPreview.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedImage readRAWData() throws IOException {
|
||||||
|
CIFFDirectory exifInfo = getExifInfo();
|
||||||
|
CIFFEntry decoderTable = exifInfo.getEntryById(CIFF.TAG_DECODER_TABLE);
|
||||||
|
int[] decoderTableValue = decoderTable != null ? (int[]) decoderTable.getValue() : null;
|
||||||
|
int table = decoderTableValue != null ? decoderTableValue[0] : 0;
|
||||||
|
long offset = decoderTableValue != null ? decoderTableValue[2] : 0;
|
||||||
|
|
||||||
|
Dimension size = getRawImageDimension();
|
||||||
|
int width = size.width;
|
||||||
|
int height = size.height;
|
||||||
|
|
||||||
|
// TODO: This is probably not the best way to get the bits/pixel, but seems to be correct (when it's there).
|
||||||
|
// However,
|
||||||
|
CIFFEntry whiteSample = exifInfo.getEntryById(CIFF.TAG_WHITE_SAMPLE);
|
||||||
|
short[] whiteSampleValue = whiteSample != null ? (short[]) whiteSample.getValue() : null;
|
||||||
|
short bitsPerSample = whiteSampleValue != null && whiteSample.length() > 5 ? whiteSampleValue[5] : 12;
|
||||||
|
|
||||||
|
CIFFEntry rawData = heap.getEntryById(CIFF.TAG_RAW_DATA);
|
||||||
|
|
||||||
|
imageInput.seek(rawData.offset() + offset);
|
||||||
|
|
||||||
|
ImageInputStream stream = new BufferedImageInputStream(new SubImageInputStream(imageInput, rawData.length()));
|
||||||
|
|
||||||
|
CRWDecoder decoder = new CRWDecoder(stream, width, height, table);
|
||||||
|
short[] data = decoder.decode();
|
||||||
|
|
||||||
|
DataBuffer dataBuffer = new DataBufferUShort(data, data.length);
|
||||||
|
WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, width, height, width, 1, new int[] {0}, null);
|
||||||
|
|
||||||
|
ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[]{bitsPerSample}, false, false, Transparency.OPAQUE, dataBuffer.getDataType());
|
||||||
|
|
||||||
|
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
CRWImageReader reader = new CRWImageReader(new CRWImageReaderSpi());
|
||||||
|
|
||||||
|
for (String arg : args) {
|
||||||
|
ImageInputStream stream = ImageIO.createImageInputStream(new File(arg));
|
||||||
|
System.err.println("canDecode: " + reader.getOriginatingProvider().canDecodeInput(stream));
|
||||||
|
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
int numImages = reader.getNumImages(true);
|
||||||
|
for (int i = 0; i < numImages; i++) {
|
||||||
|
int numThumbnails = reader.getNumThumbnails(i);
|
||||||
|
for (int n = 0; n < numThumbnails; n++) {
|
||||||
|
showIt(reader.readThumbnail(i, n), arg + " image " + i + " thumbnail " + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
showIt(reader.read(i), arg + " image " + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014, 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.crw;
|
||||||
|
|
||||||
|
import static java.util.Arrays.copyOfRange;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.plugins.crw.ciff.CIFF;
|
||||||
|
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRWImageReaderSpi
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: CRWImageReaderSpi.java,v 1.0 07.04.14 21:26 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class CRWImageReaderSpi extends ImageReaderSpiBase {
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public CRWImageReaderSpi() {
|
||||||
|
super(new CRWProviderInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||||
|
// TODO: This code assumes that any TIFFImageReaderSpi is already installed... It may be installed at a later time. :-(
|
||||||
|
// Make sure we're ordered before any TIFF reader
|
||||||
|
Iterator<ImageReaderSpi> spis = registry.getServiceProviders(ImageReaderSpi.class, new ServiceRegistry.Filter() {
|
||||||
|
@Override
|
||||||
|
public boolean filter(Object provider) {
|
||||||
|
return provider instanceof ImageReaderSpi && isTIFFReaderSpi((ImageReaderSpi) provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTIFFReaderSpi(final ImageReaderSpi provider) {
|
||||||
|
String[] formatNames = provider.getFormatNames();
|
||||||
|
for (String formatName : formatNames) {
|
||||||
|
if (formatName.equalsIgnoreCase("TIFF")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
while (spis.hasNext()) {
|
||||||
|
ImageReaderSpi spi = spis.next();
|
||||||
|
registry.setOrdering(ImageReaderSpi.class, this, spi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canDecodeInput(final Object pSource) throws IOException {
|
||||||
|
if (!(pSource instanceof ImageInputStream)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInputStream stream = (ImageInputStream) pSource;
|
||||||
|
|
||||||
|
stream.mark();
|
||||||
|
try {
|
||||||
|
byte[] bom = new byte[2];
|
||||||
|
stream.readFully(bom);
|
||||||
|
|
||||||
|
ByteOrder originalOrder = stream.getByteOrder();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// CIFF byte order mark II (Intel) or MM (Motorola), just like TIFF
|
||||||
|
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||||
|
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
else if (bom[0] == 'M' && bom[1] == 'M') {
|
||||||
|
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CIFF header is always 26 bytes
|
||||||
|
int size = stream.readInt();
|
||||||
|
if (size != CIFF.HEADER_SIZE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRW uses type HEAP and subtype CCDR
|
||||||
|
byte[] type = new byte[8];
|
||||||
|
stream.readFully(type);
|
||||||
|
|
||||||
|
if (!Arrays.equals(CIFF.TYPE_HEAP, copyOfRange(type, 0, 4))
|
||||||
|
|| !Arrays.equals(CIFF.SUBTYPE_CCDR, copyOfRange(type, 4, 8))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version 1.2
|
||||||
|
return stream.readUnsignedInt() == CIFF.VERSION_1_2;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stream.setByteOrder(originalOrder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stream.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CRWImageReader createReaderInstance(Object extension) {
|
||||||
|
return new CRWImageReader(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(Locale locale) {
|
||||||
|
return "Canon RAW (CRW) format Reader";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.crw;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRWProviderInfo
|
||||||
|
*/
|
||||||
|
final class CRWProviderInfo extends ReaderWriterProviderInfo {
|
||||||
|
CRWProviderInfo() {
|
||||||
|
super(
|
||||||
|
CRWProviderInfo.class,
|
||||||
|
new String[] {"crw", "CRW"},
|
||||||
|
new String[] {"crw"},
|
||||||
|
new String[] {
|
||||||
|
"image/x-canon-raw", // TODO: Look up
|
||||||
|
},
|
||||||
|
"CRWImageReader",
|
||||||
|
new String[] {"CRWImageReaderSpi"},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
null, null,
|
||||||
|
null, null,
|
||||||
|
true,
|
||||||
|
null, null,
|
||||||
|
null, null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018, Oleg Ermolaev
|
||||||
|
* 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.crw;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For CR2 RAW image.
|
||||||
|
*
|
||||||
|
* @author Oleg Ermolaev Date: 05.05.2018 2:04
|
||||||
|
*/
|
||||||
|
final class Slice {
|
||||||
|
private final int firstWidthCount;
|
||||||
|
private final int firstWidth;
|
||||||
|
private final int lastWidth;
|
||||||
|
|
||||||
|
private Slice(int firstWidthCount, int firstWidth, int lastWidth) {
|
||||||
|
this.firstWidthCount = firstWidthCount;
|
||||||
|
this.firstWidth = firstWidth;
|
||||||
|
this.lastWidth = lastWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Slice createSlice(long[] values) throws IIOException {
|
||||||
|
if (values == null || values.length != 3) {
|
||||||
|
throw new IIOException("Unexpected slices array: " + Arrays.toString(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
long firstWidthCount = values[0];
|
||||||
|
long firstWidth = values[1];
|
||||||
|
long lastWidth = values[2];
|
||||||
|
|
||||||
|
if (!(0 < firstWidthCount && firstWidthCount <= Integer.MAX_VALUE) ||
|
||||||
|
!(0 < firstWidth && firstWidth <= Integer.MAX_VALUE) ||
|
||||||
|
!(0 < lastWidth && lastWidth <= Integer.MAX_VALUE) ||
|
||||||
|
firstWidthCount * firstWidth + lastWidth > Integer.MAX_VALUE) {
|
||||||
|
throw new IIOException("Unexpected slices array: " + Arrays.toString(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Slice((int) firstWidthCount, (int) firstWidth, (int) lastWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getWidth() {
|
||||||
|
return firstWidthCount * firstWidth + lastWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] unslice(int[][] data, int componentCount, int height) throws IIOException {
|
||||||
|
final int width = getWidth();
|
||||||
|
final int[] result = new int[width * height];
|
||||||
|
|
||||||
|
for (int componentIndex = 0; componentIndex < componentCount; componentIndex++) {
|
||||||
|
if (result.length != data[componentIndex].length * componentCount) {
|
||||||
|
throw new IIOException(String.format("Invalid array size for component #%d", componentIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int position = 0;
|
||||||
|
int currentWidth = firstWidth / componentCount;
|
||||||
|
|
||||||
|
for (int sliceIndex = 0; sliceIndex < firstWidthCount + 1; ++sliceIndex) {
|
||||||
|
if (sliceIndex == firstWidthCount) {
|
||||||
|
currentWidth = lastWidth / componentCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int sliceOffset = sliceIndex * firstWidth;
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
final int yOffset = y * width;
|
||||||
|
|
||||||
|
for (int x = 0; x < currentWidth; ++x) {
|
||||||
|
final int xOffset = x * componentCount;
|
||||||
|
|
||||||
|
for (int componentIndex = 0; componentIndex < componentCount; componentIndex++) {
|
||||||
|
result[sliceOffset + yOffset + xOffset + componentIndex] = data[componentIndex][position];
|
||||||
|
}
|
||||||
|
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.crw.ciff;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CIFF
|
||||||
|
*
|
||||||
|
* @see <a href="http://xyrion.org/ciff/CIFFspecV1R04.pdf">CIFF: Specification on Image Data File</a>
|
||||||
|
*/
|
||||||
|
public interface CIFF {
|
||||||
|
|
||||||
|
int HEADER_SIZE = 26;
|
||||||
|
int VERSION_1_2 = 0x00010002;
|
||||||
|
|
||||||
|
byte[] TYPE_HEAP = "HEAP".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
|
// According to CIFF spec, other subtypes are "JPGM", "TIFP" and "ARCH".
|
||||||
|
byte[] SUBTYPE_ARCH = "ARCH".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
byte[] SUBTYPE_CCDR = "CCDR".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
byte[] SUBTYPE_JPGM = "JPGM".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
byte[] SUBTYPE_TIFP = "TIFP".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
|
int STORAGE_HEAP = 0x0000; // kStg_InHeapSpace
|
||||||
|
int STORAGE_RECORD = 0x4000; // kStg_InRecordEntry
|
||||||
|
|
||||||
|
int DATA_TYPE_BYTE = 0x0000;
|
||||||
|
int DATA_TYPE_ASCII = 0x0800;
|
||||||
|
int DATA_TYPE_WORD = 0x1000;
|
||||||
|
int DATA_TYPE_DWORD = 0x1800;
|
||||||
|
int DATA_TYPE_UNDEFINED = 0x2000; // kDT_BYTE2
|
||||||
|
|
||||||
|
int DATA_TYPE_HEAP_1 = 0x2800; // kDT_HeapTypeProperty1
|
||||||
|
int DATA_TYPE_HEAP_2 = 0x3000; // kDT_HeapTypeProperty2
|
||||||
|
|
||||||
|
// From Spec
|
||||||
|
int TAG_WILDCARD = 0xffff;
|
||||||
|
int TAG_NULL = 0x0000; // null record
|
||||||
|
int TAG_FREE = 0x0001; // free record
|
||||||
|
int TAG_EX_USED = 0x0002; // special type for implementation purpose
|
||||||
|
|
||||||
|
int TAG_DESCRIPTION = DATA_TYPE_ASCII | 0x0005;
|
||||||
|
int TAG_MODEL_NAME = DATA_TYPE_ASCII | 0x000a;
|
||||||
|
int TAG_FIRMWARE_VERSION = DATA_TYPE_ASCII | 0x000b;
|
||||||
|
int TAG_COMPONENT_VERSION = DATA_TYPE_ASCII | 0x000c;
|
||||||
|
int TAG_ROM_OPERATION_MODE = DATA_TYPE_ASCII | 0x000d;
|
||||||
|
int TAG_OWNER_NAME = DATA_TYPE_ASCII | 0x0010;
|
||||||
|
int TAG_IMAGE_FILE_NAME = DATA_TYPE_ASCII | 0x0016;
|
||||||
|
int TAG_THUMBNAIL_FILE_NAME = DATA_TYPE_ASCII | 0x0017;
|
||||||
|
int TAG_TARGET_IMAGE_TYPE = DATA_TYPE_WORD | 0x000a;
|
||||||
|
int TAG_SR_RELEASE_METHOD = DATA_TYPE_WORD | 0x0010;
|
||||||
|
int TAG_SR_RELEASE_TIMING = DATA_TYPE_WORD | 0x0011;
|
||||||
|
int TAG_RELEASE_SETTING = DATA_TYPE_WORD | 0x0016;
|
||||||
|
int TAG_BODY_SENSITIVITY = DATA_TYPE_WORD | 0x001c;
|
||||||
|
int TAG_IMAGE_FORMAT = DATA_TYPE_DWORD | 0x0003;
|
||||||
|
int TAG_RECORD_ID = DATA_TYPE_DWORD | 0x0004;
|
||||||
|
int TAG_SELF_TIMER_TIME = DATA_TYPE_DWORD | 0x0006;
|
||||||
|
int TAG_SR_TARGET_DISTANCE_SETTING = DATA_TYPE_DWORD | 0x0007;
|
||||||
|
int TAG_BODY_ID = DATA_TYPE_DWORD | 0x000b;
|
||||||
|
int TAG_CAPTURED_TIME = DATA_TYPE_DWORD | 0x000e;
|
||||||
|
int TAG_IMAGE_SPEC = DATA_TYPE_DWORD | 0x0010;
|
||||||
|
int TAG_SR_EF = DATA_TYPE_DWORD | 0x0013;
|
||||||
|
int TAG_MI_EV = DATA_TYPE_DWORD | 0x0014;
|
||||||
|
int TAG_SERIAL_NUMBER = DATA_TYPE_DWORD | 0x0017;
|
||||||
|
int TAG_SR_EXPOSURE = DATA_TYPE_DWORD | 0x0018;
|
||||||
|
int TAG_CAMERA_OBJECT = 0x0007 | DATA_TYPE_HEAP_1;
|
||||||
|
int TAG_SHOOTING_RECORD = 0x0002 | DATA_TYPE_HEAP_2;
|
||||||
|
int TAG_MEASURED_INFO = 0x0003 | DATA_TYPE_HEAP_2;
|
||||||
|
int TAG_CAMERA_SPECIFICATiON = 0x0004 | DATA_TYPE_HEAP_2; // kTC_CameraSpecificaiton
|
||||||
|
|
||||||
|
// From Phil Harvey's ExifTool (https://sno.phy.queensu.ca/~phil/exiftool/canon_raw.html)
|
||||||
|
|
||||||
|
// 0x0006 - 0x300b - 8 -
|
||||||
|
int TAG_CANON_COLOR_INFO1 = DATA_TYPE_BYTE | 0x0032; // - Subdir: 0x300b, Exif: -
|
||||||
|
// 0x0036 - 0x300b ? varies -
|
||||||
|
// 0x003f - 0x300b ? 5120 -
|
||||||
|
// 0x0040 - 0x300b ? 256 -
|
||||||
|
// 0x0041 - 0x300b ? 256 -
|
||||||
|
|
||||||
|
// ASCII Strings
|
||||||
|
// int TAG_CANON_FILE_DESCRIPTION = 0x0805; // - Subdir: 0x2804, Exif: -
|
||||||
|
// int TAG_USER_COMMENT = DATA_TYPE_ASCII | 0x0005; // - Subdir: 0x300a, Exif: -
|
||||||
|
// int TAG_CANON_RAW_MAKE_MODEL = DATA_TYPE_ASCII | 0x080a; // - Subdir: 0x2807, Exif: -
|
||||||
|
// int TAG_CANON_FIRMWARE_VERSION = DATA_TYPE_ASCII | 0x080b; // 32 Firmware version. eg) "Firmware Version 1.1.1" - Subdir: 0x3004, Exif: 0x07
|
||||||
|
// int TAG_COMPONENT_VERSION = 0x080c; // ?
|
||||||
|
// int TAG_ROM_OPERATION_MODE = 0x080d; // 4 eg) The string "USA" for 300D's sold in North America - Subdir: 0x3004, Exif: -
|
||||||
|
// int TAG_OWNER_NAME = 0x0810; // 32 Owner's name. eg) "Phil Harvey" - Subdir: 0x2807, Exif: 0x09
|
||||||
|
int TAG_CANON_IMAGE_TYPE = DATA_TYPE_ASCII | 0x0815; // 32 Type of file. eg) "CRW:EOS DIGITAL REBEL CMOS RAW" - Subdir: 0x2804, Exif: 0x06
|
||||||
|
// int TAG_ORIGINAL_FILE_NAME = 0x0816; // 32 Original file name. eg) "CRW_1834.CRW" - Subdir: 0x300a, Exif: -
|
||||||
|
// int TAG_THUMBNAIL_FILE_NAME = 0x0817; // 32 Thumbnail file name. eg) "CRW_1834.THM" - Subdir: 0x300a, Exif: -
|
||||||
|
|
||||||
|
// SHORT (2-Byte Alignmnt)
|
||||||
|
// int TAG_TARGET_IMAGE_TYPE = 0x100a; // 2 0=real-world subject, 1=written document - Subdir: 0x300a, Exif: -
|
||||||
|
// int TAG_SHUTTER_RELEASE_METHOD = 0x1010; // 2 0=single shot, 1=continuous shooting - Subdir: 0x3002, Exif: -
|
||||||
|
// int TAG_SHUTTER_RELEASE_TIMING = 0x1011; // 2 0=priority on shutter, 1=priority on focus - Subdir: 0x3002, Exif: -
|
||||||
|
// 0x1014 - 0x3002 - 8 -
|
||||||
|
// int TAG_RELEASE_SETTING = 0x1016; // 2 - - Subdir: 0x3002, Exif: -
|
||||||
|
int TAG_BASE_ISO = DATA_TYPE_WORD | 0x101c; // 2 The camera body's base ISO sensitivity - Subdir: 0x3004, Exif: -
|
||||||
|
// 0x1026 - 0x300a - 6 -
|
||||||
|
int TAG_CANON_FLASH_INFO = DATA_TYPE_WORD | 0x1028; // 8 Unknown information, flash related - Subdir: 0x300b, Exif: 0x03
|
||||||
|
int TAG_FOCAL_LENGTH = DATA_TYPE_WORD | 0x1029; // 8 Four 16 bit integers: 0) unknown, 1) focal length in mm, 2-3) sensor width and height in units of 1/1000 inch - Subdir: 0x300b, Exif: 0x02
|
||||||
|
int TAG_CANON_SHOT_INFO = DATA_TYPE_WORD | 0x102a; // varies Data block giving shot information - Subdir: 0x300b, Exif: 0x04
|
||||||
|
int TAG_CANON_COLOR_INFO2 = DATA_TYPE_WORD | 0x102c; // 256 Data block of color information (format unknown) - Subdir: 0x300b, Exif: -
|
||||||
|
int TAG_CANON_CAMERA_SETTINGS = DATA_TYPE_WORD | 0x102d; // varies Data block giving camera settings - Subdir: 0x300b, Exif: 0x01
|
||||||
|
int TAG_WHITE_SAMPLE = DATA_TYPE_WORD | 0x1030; // 102 or 118 White sample information with encrypted 8x8 sample data - Subdir: 0x300b, Exif: -
|
||||||
|
int TAG_SENSOR_INFO = DATA_TYPE_WORD | 0x1031; // 34 Sensor size and resolution information - Subdir: 0x300b, Exif: -
|
||||||
|
int TAG_CANON_CUSTOM_FUNCTIONS = DATA_TYPE_WORD | 0x1033; // varies Data block giving Canon custom settings - Subdir: 0x300b, Exif: 0x0f
|
||||||
|
int TAG_CANON_AF_INFO = DATA_TYPE_WORD | 0x1038; // varies Data block giving AF-specific information - Subdir: 0x300b, Exif: 0x12
|
||||||
|
// 0x1039 0x13 0x300b ? 8 -
|
||||||
|
// 0x103c - 0x300b ? 156 -
|
||||||
|
// 0x107f - 0x300b - varies -
|
||||||
|
int TAG_CANON_FILE_INFO = DATA_TYPE_WORD | 0x1093; // 18 Data block giving file-specific information - Subdir: 0x300b, Exif: 0x93
|
||||||
|
// 0x10a8 0xa8 0x300b ? 20 -
|
||||||
|
int TAG_COLOR_BALANCE = DATA_TYPE_WORD | 0x10a9; // 82 Table of 16-bit integers. The first integer (like many other data blocks) is the number of bytes in the record. This is followed by red, green1, green2 and blue levels for WhiteBalance settings: auto, daylight, shade, cloudy, tungsten, fluorescent, flash, custom and kelvin. The final 4 entries appear to be some sort of baseline red, green1, green2 and blue levels. - Subdir: 0x300b, Exif: 0xa9
|
||||||
|
// 0x10aa 0xaa 0x300b ? 10 -
|
||||||
|
// 0x10ad - 0x300b ? 62 -
|
||||||
|
int TAG_COLOR_TEMPERATURE = DATA_TYPE_WORD | 0x10ae; // 2 16-bit integer giving the color temperature - Subdir: 0x300b, Exif: 0xae
|
||||||
|
// 0x10af - 0x300b ? 2 -
|
||||||
|
int TAG_COLOR_SPACE = DATA_TYPE_WORD | 0x10b4; // 2 16-bit integer specifying the color space (1=sRGB, 2=Adobe RGB, 0xffff=uncalibrated) - Subdir: 0x300b, Exif: 0xb4
|
||||||
|
int TAG_RAW_JPEG_INFO = DATA_TYPE_WORD | 0x10b5; // 10 Data block giving embedded JPG information - Subdir: 0x300b, Exif: 0xb5
|
||||||
|
// 0x10c0 0xc0 0x300b ? 26 -
|
||||||
|
// 0x10c1 0xc1 0x300b ? 26 -
|
||||||
|
// 0x10c2 - 0x300b ? 884 -
|
||||||
|
|
||||||
|
// LONG (4-Byte Alignment)
|
||||||
|
// int TAG_IMAGE_FORMAT = 0x1803; // 8 32-bit integer specifying image format (0x20001 for CRW), followed by 32-bit float giving target compression ratio - Subdir: 0x300a, Exif: -
|
||||||
|
// int TAG_RECORD_ID = 0x1804; // 4 The number of pictures taken since the camera was manufactured - Subdir: 0x300a, Exif: -
|
||||||
|
// 0x1805 - 0x3002 - 8 -
|
||||||
|
// int TAG_SELF_TIMER_TIME = 0x1806; // 4 32-bit integer giving self-timer time in milliseconds - Subdir: 0x3002, Exif: -
|
||||||
|
// int TAG_TARGET_DISTANCE_SETTING = 0x1807; // 4 32-bit float giving target distance in mm - Subdir: 0x3002, Exif: -
|
||||||
|
// int TAG_SERIAL_NUMBER = 0x180b; // 4 The camera body number for EOS models. eg) 00560012345 - Subdir: 0x3004, Exif: 0x0c
|
||||||
|
int TAG_TIME_STAMP = DATA_TYPE_DWORD | 0x180e; // 12 32-bit integer giving the time in seconds when the picture was taken, followed by a 32-bit timezone in seconds - Subdir: 0x300a, Exif: -
|
||||||
|
int TAG_IMAGE_INFO = DATA_TYPE_DWORD | 0x1810; // 28 Data block containing image information, including rotation - Subdir: 0x300a, Exif: -
|
||||||
|
// 0x1812 - 0x3004 - 40 -
|
||||||
|
int TAG_FLASH_INFO = DATA_TYPE_DWORD | 0x1813; // 8 Two 32-bit floats: The flash guide number and the flash threshold - Subdir: 0x3002, Exif: -
|
||||||
|
int TAG_MEASURED_EV = DATA_TYPE_DWORD | 0x1814; // 4 32-bit float giving the measured EV - Subdir: 0x3003, Exif: -
|
||||||
|
int TAG_FILE_NUMBER = DATA_TYPE_DWORD | 0x1817; // 4 32-bit integer giving the number of this file. eg) 1181834 - Subdir: 0x300a, Exif: 0x08
|
||||||
|
// int TAG_EXPOSURE_INFO = DATA_TYPE_DWORD | 0x1818; // 12 Three 32-bit floats: Exposure compensation, Tv, Av - Subdir: 0x3002, Exif: -
|
||||||
|
// 0x1819 - 0x300b - 64 -
|
||||||
|
int TAG_CANON_MODEL_ID = DATA_TYPE_DWORD | 0x1834; // 4 Unsigned 32-bit integer giving unique model ID - Subdir: 0x300b, Exif: 0x10
|
||||||
|
int TAG_DECODER_TABLE = DATA_TYPE_DWORD | 0x1835; // 16 RAW decoder table information - Subdir: 0x300b, Exif: -
|
||||||
|
int TAG_SERIAL_NUMBER_FORMAT = DATA_TYPE_DWORD | 0x183b; // 4 32-bit integer (0x90000000=format 1, 0xa0000000=format 2) - Subdir: 0x300b, Exif: 0x15
|
||||||
|
|
||||||
|
// UNDEFINED (Mixed Data Records)
|
||||||
|
int TAG_RAW_DATA = DATA_TYPE_UNDEFINED | 0x2005; // The raw data itself (the bulk of the CRW file) - Subdir: root
|
||||||
|
int TAG_JPEG_PREVIEW = DATA_TYPE_UNDEFINED | 0x2007; // The embedded JPEG image (2048x1360 pixels for the 300D with Canon firmware) - Subdir: root
|
||||||
|
int TAG_THUMBNAIL = DATA_TYPE_UNDEFINED | 0x2008; // Thumbnail image (JPEG, 160x120 pixels) - Subdir: root
|
||||||
|
|
||||||
|
// SubDirectory Blocks
|
||||||
|
int TAG_IMAGE_DESCRIPTION = DATA_TYPE_HEAP_1 | 0x2804; // The image description subdirectory - Subdir: 0x300a
|
||||||
|
// int TAG_CAMERA_OBJECT = 0x2807; // The camera object subdirectory - Subdir: 0x300a
|
||||||
|
// int TAG_SHOOTING_RECORD = 0x3002; // The shooting record subdirectory - Subdir: 0x300a
|
||||||
|
// int TAG_MEASURED_INFO = 0x3003; // The measured information subdirectory - Subdir: 0x300a
|
||||||
|
int TAG_CAMERA_SPECIFICATION = DATA_TYPE_HEAP_2 | 0x3004; // The camera specification subdirectory - Subdir: 0x2807
|
||||||
|
|
||||||
|
int TAG_IMAGE_PROPERTIES = DATA_TYPE_HEAP_2 | 0x300a; // The main subdirectory containing all meta information - Subdir: root
|
||||||
|
int TAG_EXIF_INFORMATION = DATA_TYPE_HEAP_2 | 0x300b; // The subdirectory containing most of the JPEG/TIFF Exif information - Subdir: 0x300a
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.crw.ciff;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CIFFDirectory
|
||||||
|
*/
|
||||||
|
public final class CIFFDirectory extends AbstractDirectory {
|
||||||
|
|
||||||
|
CIFFDirectory(Collection<CIFFEntry> entries) {
|
||||||
|
super(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CIFFEntry getEntryById(Object identifier) {
|
||||||
|
return (CIFFEntry) super.getEntryById(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CIFFEntry getEntryByFieldName(String fieldName) {
|
||||||
|
return (CIFFEntry) super.getEntryByFieldName(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CIFFDirectory getSubDirectory(int tagId) {
|
||||||
|
CIFFEntry entry = getEntryById(tagId);
|
||||||
|
return entry == null ? null : (CIFFDirectory) entry.getValue();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.crw.ciff;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||||
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CIFFEntry
|
||||||
|
*/
|
||||||
|
public final class CIFFEntry extends AbstractEntry {
|
||||||
|
|
||||||
|
private final long offset;
|
||||||
|
private final long length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a CIFFEntry for "in-record" storage.
|
||||||
|
*
|
||||||
|
* @param identifier
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
CIFFEntry(int identifier, Object value) {
|
||||||
|
super(identifier, value);
|
||||||
|
this.offset = -1;
|
||||||
|
this.length = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a CIFFEntry for "in-heap" storage.
|
||||||
|
*
|
||||||
|
* @param identifier
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
CIFFEntry(int identifier, Object value, long offset, long length) {
|
||||||
|
super(identifier, value);
|
||||||
|
this.offset = offset;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tagId() {
|
||||||
|
return (int) getIdentifier();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isHeapStorage() {
|
||||||
|
return offset != -1 && length != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long offset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long length() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFieldName() {
|
||||||
|
switch ((Integer) getIdentifier()) {
|
||||||
|
case CIFF.TAG_RAW_DATA:
|
||||||
|
return "RawData";
|
||||||
|
case CIFF.TAG_JPEG_PREVIEW:
|
||||||
|
return "JPEGPreview";
|
||||||
|
case CIFF.TAG_THUMBNAIL:
|
||||||
|
return "Thumbnail";
|
||||||
|
case CIFF.TAG_IMAGE_PROPERTIES:
|
||||||
|
return "ImageProperties";
|
||||||
|
case CIFF.TAG_EXIF_INFORMATION:
|
||||||
|
return "ExifInformation";
|
||||||
|
default:
|
||||||
|
Field[] fields = CIFF.class.getFields();
|
||||||
|
|
||||||
|
for (Field field : fields) {
|
||||||
|
try {
|
||||||
|
if (field.getType() == Integer.TYPE && field.getName().startsWith("TAG_")) {
|
||||||
|
if (field.get(null).equals(getIdentifier())) {
|
||||||
|
return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
// Should never happen, but in case, abort
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getNativeIdentifier() {
|
||||||
|
return String.format("0x%04x", (Integer) getIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeName() {
|
||||||
|
int dataType = tagId() & 0x3800;
|
||||||
|
|
||||||
|
switch (dataType) {
|
||||||
|
case CIFF.DATA_TYPE_BYTE:
|
||||||
|
return "BYTE";
|
||||||
|
case CIFF.DATA_TYPE_ASCII:
|
||||||
|
return "ASCII";
|
||||||
|
case CIFF.DATA_TYPE_WORD:
|
||||||
|
return "SHORT";
|
||||||
|
case CIFF.DATA_TYPE_DWORD:
|
||||||
|
return "LONG";
|
||||||
|
case CIFF.DATA_TYPE_UNDEFINED:
|
||||||
|
return "UNDEFINED";
|
||||||
|
case CIFF.DATA_TYPE_HEAP_1:
|
||||||
|
case CIFF.DATA_TYPE_HEAP_2:
|
||||||
|
return super.getTypeName();
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException(String.format("Unsupported data type: 0x%04x", dataType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.crw.ciff;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CIFFReader
|
||||||
|
*/
|
||||||
|
public final class CIFFReader extends MetadataReader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CIFFDirectory read(ImageInputStream input) throws IOException {
|
||||||
|
long start = readCIFFHeader(input);
|
||||||
|
|
||||||
|
// Spec: "No information regarding the length of the heap is given within the actual heap data structure itself"
|
||||||
|
return readHeap(input, start, input.length() - start); // TODO: If length is unknown, we'll have to search for it...
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readCIFFHeader(ImageInputStream input) throws IOException {
|
||||||
|
byte[] bom = new byte[2];
|
||||||
|
input.readFully(bom);
|
||||||
|
|
||||||
|
// CIFF byte order mark II (Intel) or MM (Motorola), just like TIFF
|
||||||
|
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||||
|
input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
}
|
||||||
|
else if (bom[0] == 'M' && bom[1] == 'M') {
|
||||||
|
input.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new IIOException("No CIFF byte order mark found, expected 'II' or 'MM'");
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = input.readInt();
|
||||||
|
if (size != CIFF.HEADER_SIZE) {
|
||||||
|
// TODO: Other sizes?
|
||||||
|
throw new IIOException(String.format("Unexpected CIFF header size, expected %d: %d ", CIFF.HEADER_SIZE, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] typeInfo = new byte[8];
|
||||||
|
input.readFully(typeInfo);
|
||||||
|
|
||||||
|
byte[] type = Arrays.copyOfRange(typeInfo, 0, 4);
|
||||||
|
if (!Arrays.equals(CIFF.TYPE_HEAP, type)) {
|
||||||
|
throw new IIOException(String.format("Unexpected CIFF type, expected 'HEAP': '%s'", new String(type, StandardCharsets.US_ASCII)));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] subtype = Arrays.copyOfRange(typeInfo, 4, 8);
|
||||||
|
if (!(Arrays.equals(CIFF.SUBTYPE_ARCH, subtype) || Arrays.equals(CIFF.SUBTYPE_CCDR, subtype)
|
||||||
|
|| Arrays.equals(CIFF.SUBTYPE_JPGM, subtype) ||Arrays.equals(CIFF.SUBTYPE_TIFP, subtype))) {
|
||||||
|
throw new IIOException(String.format("Unsupported CIFF subtype, expected 'ARCH', 'CCDR', 'JPGM' or 'TIFP': '%s'",
|
||||||
|
new String(subtype, StandardCharsets.US_ASCII)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version 1.2
|
||||||
|
long version = input.readUnsignedInt();
|
||||||
|
if (version != CIFF.VERSION_1_2) {
|
||||||
|
throw new IIOException(String.format("Unsupported CIFF version, expected 1.2: %d.%d", version >> 16, version & 0xffff));
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CIFFDirectory readHeap(ImageInputStream input, long heapOffset, long heapLength) throws IOException {
|
||||||
|
input.seek(heapOffset + heapLength - 4);
|
||||||
|
long offsetTableOffset = input.readUnsignedInt();
|
||||||
|
|
||||||
|
return readOffsetTable(input, heapOffset, offsetTableOffset + heapOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CIFFDirectory readOffsetTable(ImageInputStream input, long heapOffset, long tableOffset) throws IOException {
|
||||||
|
input.seek(tableOffset);
|
||||||
|
|
||||||
|
// DC_UINT16 numRecords;/* the number tblArray elements */
|
||||||
|
// DC_RECORD_ENTRY tblArray[1];/* Array of the record entries */
|
||||||
|
int count = input.readUnsignedShort(); // 0 entries is allowed
|
||||||
|
CIFFEntry[] entries = new CIFFEntry[count];
|
||||||
|
|
||||||
|
// TYPECODE typeCode;/* type code of the record */
|
||||||
|
// DC_UINT32 length;/* record length */
|
||||||
|
// DC_UINT32 offset;/* offset of the record in the heap*/
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
short typeCode = input.readShort();
|
||||||
|
|
||||||
|
long entryLength = input.readUnsignedInt();
|
||||||
|
long entryOffset = input.readUnsignedInt();
|
||||||
|
|
||||||
|
int recordType = typeCode & 0xc000;
|
||||||
|
int dataType = typeCode & 0x3800;
|
||||||
|
int idCode = typeCode & 0x7ff;
|
||||||
|
|
||||||
|
// typeIdCode = dataType + idCode
|
||||||
|
int typeIdCode = typeCode & 0x3fff;
|
||||||
|
|
||||||
|
|
||||||
|
if (recordType == CIFF.STORAGE_RECORD) {
|
||||||
|
Object value = null;
|
||||||
|
// TODO: Even for these records, we need to know the data structure for each tag... :-P
|
||||||
|
switch (dataType) {
|
||||||
|
case CIFF.DATA_TYPE_BYTE:
|
||||||
|
value = (byte) entryLength & 0xff;
|
||||||
|
break;
|
||||||
|
case CIFF.DATA_TYPE_ASCII:
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(8);
|
||||||
|
buffer.putInt((int) entryLength);
|
||||||
|
buffer.putInt((int) entryOffset);
|
||||||
|
|
||||||
|
value = toNullTerminatedStrings(buffer.array());
|
||||||
|
break;
|
||||||
|
case CIFF.DATA_TYPE_WORD:
|
||||||
|
value = (short) entryLength & 0xffff;
|
||||||
|
break;
|
||||||
|
case CIFF.DATA_TYPE_DWORD:
|
||||||
|
value = (int) entryLength;
|
||||||
|
break;
|
||||||
|
case CIFF.DATA_TYPE_UNDEFINED:
|
||||||
|
value = entryLength << 32L | entryOffset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[i] = new CIFFEntry(typeIdCode, value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entries[i] = new CIFFEntry(typeIdCode, new long[] {heapOffset + entryOffset, entryLength}, heapOffset + entryOffset, entryLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill inn sub entries
|
||||||
|
input.mark();
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
CIFFEntry entry = entries[i];
|
||||||
|
int dataType = entry.tagId() & 0x3800;
|
||||||
|
|
||||||
|
Object value;
|
||||||
|
if (entry.isHeapStorage()) {
|
||||||
|
switch (dataType) {
|
||||||
|
case CIFF.DATA_TYPE_HEAP_1:
|
||||||
|
case CIFF.DATA_TYPE_HEAP_2:
|
||||||
|
value = readHeap(input, entry.offset(), entry.length());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CIFF.DATA_TYPE_BYTE:
|
||||||
|
case CIFF.DATA_TYPE_UNDEFINED:
|
||||||
|
case CIFF.DATA_TYPE_ASCII:
|
||||||
|
byte[] bytes = new byte[(int) entry.length()];
|
||||||
|
input.seek(entry.offset());
|
||||||
|
input.readFully(bytes);
|
||||||
|
value = dataType == CIFF.DATA_TYPE_ASCII ? toNullTerminatedStrings(bytes) : bytes;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CIFF.DATA_TYPE_WORD:
|
||||||
|
short[] shorts = new short[(int) (entry.length() / 2)];
|
||||||
|
input.seek(entry.offset());
|
||||||
|
input.readFully(shorts, 0, shorts.length);
|
||||||
|
value = shorts;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CIFF.DATA_TYPE_DWORD:
|
||||||
|
int[] ints = new int[(int) (entry.length() / 4)];
|
||||||
|
input.seek(entry.offset());
|
||||||
|
input.readFully(ints, 0, ints.length);
|
||||||
|
value = ints;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IIOException(String.format("Unsupported data type: 0x%04x", dataType));
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[i] = new CIFFEntry(entry.tagId(), value, entry.offset(), entry.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
input.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CIFFDirectory(asList(entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] toNullTerminatedStrings(byte[] buffer) {
|
||||||
|
int len = buffer.length;
|
||||||
|
|
||||||
|
while (len > 0 && buffer[len - 1] == 0) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(buffer, 0, len, StandardCharsets.US_ASCII).split("\u0000");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
com.twelvemonkeys.imageio.plugins.crw.CRWImageReaderSpi
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2014, 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.crw;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRWImageReaderTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: CRWImageReaderTest.java,v 1.0 07.04.14 21:52 haraldk Exp$
|
||||||
|
*/
|
||||||
|
@Ignore
|
||||||
|
public class CRWImageReaderTest extends ImageReaderAbstractTest<CRWImageReader> {
|
||||||
|
@Override
|
||||||
|
protected List<TestData> getTestData() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new TestData(getClassLoaderResource("/crw/RAW_CANON_G1.CRW"), new Dimension(640, 480)), // Canon G1
|
||||||
|
new TestData(getClassLoaderResource("/crw/RAW_CANON_300D.CRW"), new Dimension(3888, 2592)) // Canon EOS 300D
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ImageReaderSpi createProvider() {
|
||||||
|
return new CRWImageReaderSpi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getFormatNames() {
|
||||||
|
return Arrays.asList("crw");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getSuffixes() {
|
||||||
|
return Arrays.asList("crw");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<String> getMIMETypes() {
|
||||||
|
return Arrays.asList("image/x-canon-raw");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Known issue: Subsampled reading not supported")
|
||||||
|
@Override
|
||||||
|
public void testReadWithSubsampleParamPixels() throws IOException {
|
||||||
|
super.testReadWithSubsampleParamPixels();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Known issue: Source region reading not supported")
|
||||||
|
@Override
|
||||||
|
public void testReadWithSourceRegionParamEqualImage() throws IOException {
|
||||||
|
super.testReadWithSourceRegionParamEqualImage();
|
||||||
|
}
|
||||||
|
}
|
BIN
imageio/imageio-crw/src/test/resources/crw/RAW_CANON_300D.CRW
Normal file
BIN
imageio/imageio-crw/src/test/resources/crw/RAW_CANON_300D.CRW
Normal file
Binary file not shown.
BIN
imageio/imageio-crw/src/test/resources/crw/RAW_CANON_G1.CRW
Normal file
BIN
imageio/imageio-crw/src/test/resources/crw/RAW_CANON_G1.CRW
Normal file
Binary file not shown.
@ -32,6 +32,7 @@
|
|||||||
|
|
||||||
<!-- Stand-alone readers/writers -->
|
<!-- Stand-alone readers/writers -->
|
||||||
<module>imageio-cr2</module>
|
<module>imageio-cr2</module>
|
||||||
|
<module>imageio-crw</module>
|
||||||
<module>imageio-dng</module>
|
<module>imageio-dng</module>
|
||||||
<module>imageio-bmp</module>
|
<module>imageio-bmp</module>
|
||||||
<module>imageio-hdr</module>
|
<module>imageio-hdr</module>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user