mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-28 00:00:03 -04:00
Merge remote-tracking branch 'upstream/master'
Conflicts: servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java
This commit is contained in:
+1
-2
@@ -66,7 +66,7 @@ public class BrightnessContrastFilter extends RGBImageFilter {
|
|||||||
canFilterIndexColorModel = true;
|
canFilterIndexColorModel = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a precalculated lookup table for performace
|
// Use a pre-calculated lookup table for performance
|
||||||
private final int[] LUT;
|
private final int[] LUT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,7 +149,6 @@ public class BrightnessContrastFilter extends RGBImageFilter {
|
|||||||
*
|
*
|
||||||
* @return the filtered pixel value in the default color space
|
* @return the filtered pixel value in the default color space
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public int filterRGB(int pX, int pY, int pARGB) {
|
public int filterRGB(int pX, int pY, int pARGB) {
|
||||||
// Get color components
|
// Get color components
|
||||||
int r = pARGB >> 16 & 0xFF;
|
int r = pARGB >> 16 & 0xFF;
|
||||||
|
|||||||
+88
-19
@@ -259,11 +259,9 @@ public final class BufferedImageFactory {
|
|||||||
sourceProperties = null;
|
sourceProperties = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processProgress(int mScanline) {
|
private void processProgress(int scanline) {
|
||||||
if (listeners != null) {
|
if (listeners != null) {
|
||||||
int percent = 100 * mScanline / height;
|
int percent = 100 * scanline / height;
|
||||||
|
|
||||||
//System.out.println("Progress: " + percent + "%");
|
|
||||||
|
|
||||||
if (percent > percentageDone) {
|
if (percent > percentageDone) {
|
||||||
percentageDone = percent;
|
percentageDone = percent;
|
||||||
@@ -323,7 +321,7 @@ public final class BufferedImageFactory {
|
|||||||
* pixels. The conversion is done, by masking out the
|
* pixels. The conversion is done, by masking out the
|
||||||
* <em>higher 16 bits</em> of the {@code int}.
|
* <em>higher 16 bits</em> of the {@code int}.
|
||||||
*
|
*
|
||||||
* For eny given {@code int}, the {@code short} value is computed as
|
* For any given {@code int}, the {@code short} value is computed as
|
||||||
* follows:
|
* follows:
|
||||||
* <blockquote>{@code
|
* <blockquote>{@code
|
||||||
* short value = (short) (intValue & 0x0000ffff);
|
* short value = (short) (intValue & 0x0000ffff);
|
||||||
@@ -334,9 +332,11 @@ public final class BufferedImageFactory {
|
|||||||
*/
|
*/
|
||||||
private static short[] toShortPixels(int[] pPixels) {
|
private static short[] toShortPixels(int[] pPixels) {
|
||||||
short[] pixels = new short[pPixels.length];
|
short[] pixels = new short[pPixels.length];
|
||||||
|
|
||||||
for (int i = 0; i < pixels.length; i++) {
|
for (int i = 0; i < pixels.length; i++) {
|
||||||
pixels[i] = (short) (pPixels[i] & 0xffff);
|
pixels[i] = (short) (pPixels[i] & 0xffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -507,24 +507,11 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
|
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
|
||||||
/*if (pModel.getPixelSize() < 8) {
|
|
||||||
// Byte packed
|
|
||||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toBytePackedPixels(pPixels, pModel.getPixelSize()), pOffset, pScanSize);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
else if (pModel.getPixelSize() > 8) {
|
|
||||||
// Byte interleaved
|
|
||||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, toByteInterleavedPixels(pPixels), pOffset, pScanSize);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
//else {
|
|
||||||
// Default, pixelSize == 8, one byte pr pixel
|
|
||||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) {
|
public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) {
|
||||||
if (ImageUtil.getTransferType(pModel) == DataBuffer.TYPE_USHORT) {
|
if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||||
// NOTE: Workaround for limitation in ImageConsumer API
|
// NOTE: Workaround for limitation in ImageConsumer API
|
||||||
// Convert int[] to short[], to be compatible with the ColorModel
|
// Convert int[] to short[], to be compatible with the ColorModel
|
||||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
|
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
|
||||||
@@ -538,4 +525,86 @@ public final class BufferedImageFactory {
|
|||||||
sourceProperties = pProperties;
|
sourceProperties = pProperties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public static void main(String[] args) throws InterruptedException {
|
||||||
|
Image image = Toolkit.getDefaultToolkit().createImage(args[0]);
|
||||||
|
System.err.printf("image: %s (which is %sa buffered image)\n", image, image instanceof BufferedImage ? "" : "not ");
|
||||||
|
|
||||||
|
int warmUpLoops = 500;
|
||||||
|
int testLoops = 100;
|
||||||
|
|
||||||
|
for (int i = 0; i < warmUpLoops; i++) {
|
||||||
|
// Warm up...
|
||||||
|
convertUsingFactory(image);
|
||||||
|
convertUsingPixelGrabber(image);
|
||||||
|
convertUsingPixelGrabberNaive(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage bufferedImage = null;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < testLoops; i++) {
|
||||||
|
bufferedImage = convertUsingFactory(image);
|
||||||
|
}
|
||||||
|
System.err.printf("Conversion time (factory): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage);
|
||||||
|
|
||||||
|
start = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < testLoops; i++) {
|
||||||
|
bufferedImage = convertUsingPixelGrabber(image);
|
||||||
|
}
|
||||||
|
System.err.printf("Conversion time (grabber): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage);
|
||||||
|
|
||||||
|
start = System.currentTimeMillis();
|
||||||
|
for (int i = 0; i < testLoops; i++) {
|
||||||
|
bufferedImage = convertUsingPixelGrabberNaive(image);
|
||||||
|
}
|
||||||
|
System.err.printf("Conversion time (naive g): %f ms (image: %s)\n", (System.currentTimeMillis() - start) / (double) testLoops, bufferedImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BufferedImage convertUsingPixelGrabberNaive(Image image) throws InterruptedException {
|
||||||
|
// NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once
|
||||||
|
if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) {
|
||||||
|
System.err.printf("Could not get image dimensions for image %s\n", image.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = image.getWidth(null);
|
||||||
|
int h = image.getHeight(null);
|
||||||
|
PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB
|
||||||
|
grabber.grabPixels();
|
||||||
|
|
||||||
|
// Following casts are safe, as we force RGB in the pixel grabber
|
||||||
|
int[] pixels = (int[]) grabber.getPixels();
|
||||||
|
|
||||||
|
BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
// bufferedImage.setRGB(0, 0, w, h, pixels, 0, w);
|
||||||
|
bufferedImage.getRaster().setDataElements(0, 0, w, h, pixels);
|
||||||
|
|
||||||
|
return bufferedImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BufferedImage convertUsingPixelGrabber(Image image) throws InterruptedException {
|
||||||
|
// NOTE: It does not matter if we wait for the image or not, the time is about the same as it will only happen once
|
||||||
|
if ((image.getWidth(null) < 0 || image.getHeight(null) < 0) && !ImageUtil.waitForImage(image)) {
|
||||||
|
System.err.printf("Could not get image dimensions for image %s\n", image.getSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = image.getWidth(null);
|
||||||
|
int h = image.getHeight(null);
|
||||||
|
PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, true); // force RGB
|
||||||
|
grabber.grabPixels();
|
||||||
|
|
||||||
|
// Following casts are safe, as we force RGB in the pixel grabber
|
||||||
|
// DirectColorModel cm = (DirectColorModel) grabber.getColorModel();
|
||||||
|
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
||||||
|
int[] pixels = (int[]) grabber.getPixels();
|
||||||
|
|
||||||
|
WritableRaster raster = Raster.createPackedRaster(new DataBufferInt(pixels, pixels.length), w, h, w, cm.getMasks(), null);
|
||||||
|
|
||||||
|
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BufferedImage convertUsingFactory(Image image) {
|
||||||
|
return new BufferedImageFactory(image).getBufferedImage();
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
@@ -53,11 +53,15 @@ public class BufferedImageIcon implements Icon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) {
|
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight) {
|
||||||
|
this(pImage, pWidth, pHeight, pImage.getWidth() == pWidth && pImage.getHeight() == pHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImageIcon(BufferedImage pImage, int pWidth, int pHeight, boolean useFastRendering) {
|
||||||
image = Validate.notNull(pImage, "image");
|
image = Validate.notNull(pImage, "image");
|
||||||
width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d");
|
width = Validate.isTrue(pWidth > 0, pWidth, "width must be positive: %d");
|
||||||
height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d");
|
height = Validate.isTrue(pHeight > 0, pHeight, "height must be positive: %d");
|
||||||
|
|
||||||
fast = image.getWidth() == width && image.getHeight() == height;
|
fast = useFastRendering;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIconHeight() {
|
public int getIconHeight() {
|
||||||
|
|||||||
@@ -292,20 +292,20 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
// When reference for column, add 1 to reference as this buffer is
|
// When reference for column, add 1 to reference as this buffer is
|
||||||
// offset from actual column position by one to allow FS to not check
|
// offset from actual column position by one to allow FS to not check
|
||||||
// left/right edge conditions
|
// left/right edge conditions
|
||||||
int[][] mCurrErr = new int[width + 2][3];
|
int[][] currErr = new int[width + 2][3];
|
||||||
int[][] mNextErr = new int[width + 2][3];
|
int[][] nextErr = new int[width + 2][3];
|
||||||
|
|
||||||
// Random errors in [-1 .. 1] - for first row
|
// Random errors in [-1 .. 1] - for first row
|
||||||
for (int i = 0; i < width + 2; i++) {
|
for (int i = 0; i < width + 2; i++) {
|
||||||
// Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE
|
// Note: This is broken for the strange cases where nextInt returns Integer.MIN_VALUE
|
||||||
/*
|
/*
|
||||||
mCurrErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
currErr[i][0] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
||||||
mCurrErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
currErr[i][1] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
||||||
mCurrErr[i][2] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
currErr[i][2] = (Math.abs(RANDOM.nextInt()) % (FS_SCALE * 2)) - FS_SCALE;
|
||||||
*/
|
*/
|
||||||
mCurrErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
currErr[i][0] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||||
mCurrErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
currErr[i][1] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||||
mCurrErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
currErr[i][2] = RANDOM.nextInt(FS_SCALE * 2) - FS_SCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temp buffers
|
// Temp buffers
|
||||||
@@ -318,10 +318,10 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
// Loop through image data
|
// Loop through image data
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
// Clear out next error rows for colour errors
|
// Clear out next error rows for colour errors
|
||||||
for (int i = mNextErr.length; --i >= 0;) {
|
for (int i = nextErr.length; --i >= 0;) {
|
||||||
mNextErr[i][0] = 0;
|
nextErr[i][0] = 0;
|
||||||
mNextErr[i][1] = 0;
|
nextErr[i][1] = 0;
|
||||||
mNextErr[i][2] = 0;
|
nextErr[i][2] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up start column and limit
|
// Set up start column and limit
|
||||||
@@ -348,7 +348,7 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
// Make a 28.4 FP number, add Error (with fraction),
|
// Make a 28.4 FP number, add Error (with fraction),
|
||||||
// rounding and truncate to int
|
// rounding and truncate to int
|
||||||
inRGB[i] = ((inRGB[i] << 4) + mCurrErr[x + 1][i] + 0x08) >> 4;
|
inRGB[i] = ((inRGB[i] << 4) + currErr[x + 1][i] + 0x08) >> 4;
|
||||||
|
|
||||||
// Clamp
|
// Clamp
|
||||||
if (inRGB[i] > 255) {
|
if (inRGB[i] > 255) {
|
||||||
@@ -384,26 +384,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
if (forward) {
|
if (forward) {
|
||||||
// Row 1 (y)
|
// Row 1 (y)
|
||||||
// Update error in this pixel (x + 1)
|
// Update error in this pixel (x + 1)
|
||||||
mCurrErr[x + 2][0] += diff[0] * 7;
|
currErr[x + 2][0] += diff[0] * 7;
|
||||||
mCurrErr[x + 2][1] += diff[1] * 7;
|
currErr[x + 2][1] += diff[1] * 7;
|
||||||
mCurrErr[x + 2][2] += diff[2] * 7;
|
currErr[x + 2][2] += diff[2] * 7;
|
||||||
|
|
||||||
// Row 2 (y + 1)
|
// Row 2 (y + 1)
|
||||||
// Update error in this pixel (x - 1)
|
// Update error in this pixel (x - 1)
|
||||||
mNextErr[x][0] += diff[0] * 3;
|
nextErr[x][0] += diff[0] * 3;
|
||||||
mNextErr[x][1] += diff[1] * 3;
|
nextErr[x][1] += diff[1] * 3;
|
||||||
mNextErr[x][2] += diff[2] * 3;
|
nextErr[x][2] += diff[2] * 3;
|
||||||
// Update error in this pixel (x)
|
// Update error in this pixel (x)
|
||||||
mNextErr[x + 1][0] += diff[0] * 5;
|
nextErr[x + 1][0] += diff[0] * 5;
|
||||||
mNextErr[x + 1][1] += diff[1] * 5;
|
nextErr[x + 1][1] += diff[1] * 5;
|
||||||
mNextErr[x + 1][2] += diff[2] * 5;
|
nextErr[x + 1][2] += diff[2] * 5;
|
||||||
// Update error in this pixel (x + 1)
|
// Update error in this pixel (x + 1)
|
||||||
// TODO: Consider calculating this using
|
// TODO: Consider calculating this using
|
||||||
// error term = error - sum(error terms 1, 2 and 3)
|
// error term = error - sum(error terms 1, 2 and 3)
|
||||||
// See Computer Graphics (Foley et al.), p. 573
|
// See Computer Graphics (Foley et al.), p. 573
|
||||||
mNextErr[x + 2][0] += diff[0]; // * 1;
|
nextErr[x + 2][0] += diff[0]; // * 1;
|
||||||
mNextErr[x + 2][1] += diff[1]; // * 1;
|
nextErr[x + 2][1] += diff[1]; // * 1;
|
||||||
mNextErr[x + 2][2] += diff[2]; // * 1;
|
nextErr[x + 2][2] += diff[2]; // * 1;
|
||||||
|
|
||||||
// Next
|
// Next
|
||||||
x++;
|
x++;
|
||||||
@@ -417,26 +417,26 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
else {
|
else {
|
||||||
// Row 1 (y)
|
// Row 1 (y)
|
||||||
// Update error in this pixel (x - 1)
|
// Update error in this pixel (x - 1)
|
||||||
mCurrErr[x][0] += diff[0] * 7;
|
currErr[x][0] += diff[0] * 7;
|
||||||
mCurrErr[x][1] += diff[1] * 7;
|
currErr[x][1] += diff[1] * 7;
|
||||||
mCurrErr[x][2] += diff[2] * 7;
|
currErr[x][2] += diff[2] * 7;
|
||||||
|
|
||||||
// Row 2 (y + 1)
|
// Row 2 (y + 1)
|
||||||
// Update error in this pixel (x + 1)
|
// Update error in this pixel (x + 1)
|
||||||
mNextErr[x + 2][0] += diff[0] * 3;
|
nextErr[x + 2][0] += diff[0] * 3;
|
||||||
mNextErr[x + 2][1] += diff[1] * 3;
|
nextErr[x + 2][1] += diff[1] * 3;
|
||||||
mNextErr[x + 2][2] += diff[2] * 3;
|
nextErr[x + 2][2] += diff[2] * 3;
|
||||||
// Update error in this pixel (x)
|
// Update error in this pixel (x)
|
||||||
mNextErr[x + 1][0] += diff[0] * 5;
|
nextErr[x + 1][0] += diff[0] * 5;
|
||||||
mNextErr[x + 1][1] += diff[1] * 5;
|
nextErr[x + 1][1] += diff[1] * 5;
|
||||||
mNextErr[x + 1][2] += diff[2] * 5;
|
nextErr[x + 1][2] += diff[2] * 5;
|
||||||
// Update error in this pixel (x - 1)
|
// Update error in this pixel (x - 1)
|
||||||
// TODO: Consider calculating this using
|
// TODO: Consider calculating this using
|
||||||
// error term = error - sum(error terms 1, 2 and 3)
|
// error term = error - sum(error terms 1, 2 and 3)
|
||||||
// See Computer Graphics (Foley et al.), p. 573
|
// See Computer Graphics (Foley et al.), p. 573
|
||||||
mNextErr[x][0] += diff[0]; // * 1;
|
nextErr[x][0] += diff[0]; // * 1;
|
||||||
mNextErr[x][1] += diff[1]; // * 1;
|
nextErr[x][1] += diff[1]; // * 1;
|
||||||
mNextErr[x][2] += diff[2]; // * 1;
|
nextErr[x][2] += diff[2]; // * 1;
|
||||||
|
|
||||||
// Previous
|
// Previous
|
||||||
x--;
|
x--;
|
||||||
@@ -450,9 +450,9 @@ public class DiffusionDither implements BufferedImageOp, RasterOp {
|
|||||||
|
|
||||||
// Make next error info current for next iteration
|
// Make next error info current for next iteration
|
||||||
int[][] temperr;
|
int[][] temperr;
|
||||||
temperr = mCurrErr;
|
temperr = currErr;
|
||||||
mCurrErr = mNextErr;
|
currErr = nextErr;
|
||||||
mNextErr = temperr;
|
nextErr = temperr;
|
||||||
|
|
||||||
// Toggle direction
|
// Toggle direction
|
||||||
if (alternateScans) {
|
if (alternateScans) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import java.util.Hashtable;
|
|||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haku $
|
* @author last modified by $Author: haku $
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $
|
* @version $Id: common/common-image/src/main/java/com/twelvemonkeys/image/ImageUtil.java#3 $
|
||||||
*/
|
*/
|
||||||
public final class ImageUtil {
|
public final class ImageUtil {
|
||||||
// TODO: Split palette generation out, into ColorModel classes (?)
|
// TODO: Split palette generation out, into ColorModel classes (?)
|
||||||
@@ -175,19 +175,12 @@ public final class ImageUtil {
|
|||||||
|
|
||||||
/** Our static image tracker */
|
/** Our static image tracker */
|
||||||
private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT);
|
private static MediaTracker sTracker = new MediaTracker(NULL_COMPONENT);
|
||||||
//private static Object sTrackerMutex = new Object();
|
|
||||||
|
|
||||||
/** Image id used by the image tracker */
|
|
||||||
//private static int sTrackerId = 0;
|
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
protected static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
|
protected static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
|
||||||
/** */
|
/** */
|
||||||
protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
|
protected static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
|
||||||
|
|
||||||
/** */
|
|
||||||
private static final boolean COLORMODEL_TRANSFERTYPE_SUPPORTED = isColorModelTransferTypeSupported();
|
|
||||||
|
|
||||||
/** */
|
/** */
|
||||||
private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration();
|
private static final GraphicsConfiguration DEFAULT_CONFIGURATION = getDefaultGraphicsConfiguration();
|
||||||
|
|
||||||
@@ -209,22 +202,6 @@ public final class ImageUtil {
|
|||||||
private ImageUtil() {
|
private ImageUtil() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if {@code ColorModel} has a {@code getTransferType} method.
|
|
||||||
*
|
|
||||||
* @return {@code true} if {@code ColorModel} has a
|
|
||||||
* {@code getTransferType} method
|
|
||||||
*/
|
|
||||||
private static boolean isColorModelTransferTypeSupported() {
|
|
||||||
try {
|
|
||||||
ColorModel.getRGBdefault().getTransferType();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Throwable t) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the {@code RenderedImage} to a {@code BufferedImage}.
|
* Converts the {@code RenderedImage} to a {@code BufferedImage}.
|
||||||
* The new image will have the <em>same</em> {@code ColorModel},
|
* The new image will have the <em>same</em> {@code ColorModel},
|
||||||
@@ -382,7 +359,7 @@ public final class ImageUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a copy of the given image. The image will have the same
|
* Creates a copy of the given image. The image will have the same
|
||||||
* colormodel and raster type, but will not share image (pixel) data.
|
* color model and raster type, but will not share image (pixel) data.
|
||||||
*
|
*
|
||||||
* @param pImage the image to clone.
|
* @param pImage the image to clone.
|
||||||
*
|
*
|
||||||
@@ -412,11 +389,11 @@ public final class ImageUtil {
|
|||||||
* <p/>
|
* <p/>
|
||||||
* This method is optimized for the most common cases of {@code ColorModel}
|
* This method is optimized for the most common cases of {@code ColorModel}
|
||||||
* and pixel data combinations. The raster's backing {@code DataBuffer} is
|
* and pixel data combinations. The raster's backing {@code DataBuffer} is
|
||||||
* created directly from the pixel data, as this is faster and with more
|
* created directly from the pixel data, as this is faster and more
|
||||||
* resource-friendly than using
|
* resource-friendly than using
|
||||||
* {@code ColorModel.createCompatibleWritableRaster(w, h)}.
|
* {@code ColorModel.createCompatibleWritableRaster(w, h)}.
|
||||||
* <p/>
|
* <p/>
|
||||||
* For unknown combinations, the method will fallback to using
|
* For uncommon combinations, the method will fallback to using
|
||||||
* {@code ColorModel.createCompatibleWritableRaster(w, h)} and
|
* {@code ColorModel.createCompatibleWritableRaster(w, h)} and
|
||||||
* {@code WritableRaster.setDataElements(w, h, pixels)}
|
* {@code WritableRaster.setDataElements(w, h, pixels)}
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -442,8 +419,8 @@ public final class ImageUtil {
|
|||||||
*/
|
*/
|
||||||
static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) {
|
static WritableRaster createRaster(int pWidth, int pHeight, Object pPixels, ColorModel pColorModel) {
|
||||||
// NOTE: This is optimized code for most common cases.
|
// NOTE: This is optimized code for most common cases.
|
||||||
// We create a DataBuffer with the array from grabber.getPixels()
|
// We create a DataBuffer from the pixel array directly,
|
||||||
// directly, and creating a raster based on the ColorModel.
|
// and creating a raster based on the DataBuffer and ColorModel.
|
||||||
// Creating rasters this way is faster and more resource-friendly, as
|
// Creating rasters this way is faster and more resource-friendly, as
|
||||||
// cm.createCompatibleWritableRaster allocates an
|
// cm.createCompatibleWritableRaster allocates an
|
||||||
// "empty" DataBuffer with a storage array of w*h. This array is
|
// "empty" DataBuffer with a storage array of w*h. This array is
|
||||||
@@ -457,14 +434,12 @@ public final class ImageUtil {
|
|||||||
if (pPixels instanceof int[]) {
|
if (pPixels instanceof int[]) {
|
||||||
int[] data = (int[]) pPixels;
|
int[] data = (int[]) pPixels;
|
||||||
buffer = new DataBufferInt(data, data.length);
|
buffer = new DataBufferInt(data, data.length);
|
||||||
//bands = data.length / (w * h);
|
|
||||||
bands = pColorModel.getNumComponents();
|
bands = pColorModel.getNumComponents();
|
||||||
}
|
}
|
||||||
else if (pPixels instanceof short[]) {
|
else if (pPixels instanceof short[]) {
|
||||||
short[] data = (short[]) pPixels;
|
short[] data = (short[]) pPixels;
|
||||||
buffer = new DataBufferUShort(data, data.length);
|
buffer = new DataBufferUShort(data, data.length);
|
||||||
bands = data.length / (pWidth * pHeight);
|
bands = data.length / (pWidth * pHeight);
|
||||||
//bands = cm.getNumComponents();
|
|
||||||
}
|
}
|
||||||
else if (pPixels instanceof byte[]) {
|
else if (pPixels instanceof byte[]) {
|
||||||
byte[] data = (byte[]) pPixels;
|
byte[] data = (byte[]) pPixels;
|
||||||
@@ -477,47 +452,30 @@ public final class ImageUtil {
|
|||||||
else {
|
else {
|
||||||
bands = data.length / (pWidth * pHeight);
|
bands = data.length / (pWidth * pHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
//bands = pColorModel.getNumComponents();
|
|
||||||
//System.out.println("Pixels: " + data.length + " (" + buffer.getSize() + ")");
|
|
||||||
//System.out.println("w*h*bands: " + (pWidth * pHeight * bands));
|
|
||||||
//System.out.println("Bands: " + bands);
|
|
||||||
//System.out.println("Numcomponents: " + pColorModel.getNumComponents());
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//System.out.println("Fallback!");
|
|
||||||
// Fallback mode, slower & requires more memory, but compatible
|
// Fallback mode, slower & requires more memory, but compatible
|
||||||
bands = -1;
|
bands = -1;
|
||||||
|
|
||||||
// Create raster from colormodel, w and h
|
// Create raster from color model, w and h
|
||||||
raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight);
|
raster = pColorModel.createCompatibleWritableRaster(pWidth, pHeight);
|
||||||
raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions..
|
raster.setDataElements(0, 0, pWidth, pHeight, pPixels); // Note: This is known to throw ClassCastExceptions..
|
||||||
}
|
}
|
||||||
|
|
||||||
//System.out.println("Bands: " + bands);
|
|
||||||
//System.out.println("Pixels: " + pixels.getClass() + " length: " + buffer.getSize());
|
|
||||||
//System.out.println("Needed Raster: " + cm.createCompatibleWritableRaster(1, 1));
|
|
||||||
|
|
||||||
if (raster == null) {
|
if (raster == null) {
|
||||||
//int bits = cm.getPixelSize();
|
|
||||||
//if (bits > 4) {
|
|
||||||
if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) {
|
if (pColorModel instanceof IndexColorModel && isIndexedPacked((IndexColorModel) pColorModel)) {
|
||||||
//System.out.println("Creating packed indexed model");
|
|
||||||
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT);
|
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pColorModel.getPixelSize(), LOCATION_UPPER_LEFT);
|
||||||
}
|
}
|
||||||
else if (pColorModel instanceof PackedColorModel) {
|
else if (pColorModel instanceof PackedColorModel) {
|
||||||
//System.out.println("Creating packed model");
|
|
||||||
PackedColorModel pcm = (PackedColorModel) pColorModel;
|
PackedColorModel pcm = (PackedColorModel) pColorModel;
|
||||||
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT);
|
raster = Raster.createPackedRaster(buffer, pWidth, pHeight, pWidth, pcm.getMasks(), LOCATION_UPPER_LEFT);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//System.out.println("Creating interleaved model");
|
|
||||||
// (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE.
|
// (A)BGR order... For TYPE_3BYTE_BGR/TYPE_4BYTE_ABGR/TYPE_4BYTE_ABGR_PRE.
|
||||||
int[] bandsOffsets = new int[bands];
|
int[] bandsOffsets = new int[bands];
|
||||||
for (int i = 0; i < bands;) {
|
for (int i = 0; i < bands;) {
|
||||||
bandsOffsets[i] = bands - (++i);
|
bandsOffsets[i] = bands - (++i);
|
||||||
}
|
}
|
||||||
//System.out.println("zzz Data array: " + buffer.getSize());
|
|
||||||
|
|
||||||
raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT);
|
raster = Raster.createInterleavedRaster(buffer, pWidth, pHeight, pWidth * bands, bands, bandsOffsets, LOCATION_UPPER_LEFT);
|
||||||
}
|
}
|
||||||
@@ -849,11 +807,13 @@ public final class ImageUtil {
|
|||||||
BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
BufferedImage temp = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) {
|
if (cm instanceof IndexColorModel && pHints == Image.SCALE_SMOOTH) {
|
||||||
|
// TODO: DiffusionDither does not support transparency at the moment, this will create bad results
|
||||||
new DiffusionDither((IndexColorModel) cm).filter(scaled, temp);
|
new DiffusionDither((IndexColorModel) cm).filter(scaled, temp);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
drawOnto(temp, scaled);
|
drawOnto(temp, scaled);
|
||||||
}
|
}
|
||||||
|
|
||||||
scaled = temp;
|
scaled = temp;
|
||||||
//long end = System.currentTimeMillis();
|
//long end = System.currentTimeMillis();
|
||||||
//System.out.println("Time: " + (end - start) + " ms");
|
//System.out.println("Time: " + (end - start) + " ms");
|
||||||
@@ -1140,26 +1100,26 @@ public final class ImageUtil {
|
|||||||
* Sharpens an image using a convolution matrix.
|
* Sharpens an image using a convolution matrix.
|
||||||
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
|
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
|
||||||
* <TABLE border="1" cellspacing="0">
|
* <TABLE border="1" cellspacing="0">
|
||||||
* <TR><TD>0.0</TD><TD>-{@code pAmmount}</TD><TD>0.0</TD></TR>
|
* <TR><TD>0.0</TD><TD>-{@code pAmount}</TD><TD>0.0</TD></TR>
|
||||||
* <TR><TD>-{@code pAmmount}</TD>
|
* <TR><TD>-{@code pAmount}</TD>
|
||||||
* <TD>4.0 * {@code pAmmount} + 1.0</TD>
|
* <TD>4.0 * {@code pAmount} + 1.0</TD>
|
||||||
* <TD>-{@code pAmmount}</TD></TR>
|
* <TD>-{@code pAmount}</TD></TR>
|
||||||
* <TR><TD>0.0</TD><TD>-{@code pAmmount}</TD><TD>0.0</TD></TR>
|
* <TR><TD>0.0</TD><TD>-{@code pAmount}</TD><TD>0.0</TD></TR>
|
||||||
* </TABLE>
|
* </TABLE>
|
||||||
*
|
*
|
||||||
* @param pOriginal the BufferedImage to sharpen
|
* @param pOriginal the BufferedImage to sharpen
|
||||||
* @param pAmmount the ammount of sharpening
|
* @param pAmount the amount of sharpening
|
||||||
*
|
*
|
||||||
* @return a BufferedImage, containing the sharpened image.
|
* @return a BufferedImage, containing the sharpened image.
|
||||||
*/
|
*/
|
||||||
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmmount) {
|
public static BufferedImage sharpen(BufferedImage pOriginal, float pAmount) {
|
||||||
if (pAmmount == 0f) {
|
if (pAmount == 0f) {
|
||||||
return pOriginal;
|
return pOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the convolution matrix
|
// Create the convolution matrix
|
||||||
float[] data = new float[] {
|
float[] data = new float[] {
|
||||||
0.0f, -pAmmount, 0.0f, -pAmmount, 4f * pAmmount + 1f, -pAmmount, 0.0f, -pAmmount, 0.0f
|
0.0f, -pAmount, 0.0f, -pAmount, 4f * pAmount + 1f, -pAmount, 0.0f, -pAmount, 0.0f
|
||||||
};
|
};
|
||||||
|
|
||||||
// Do the filtering
|
// Do the filtering
|
||||||
@@ -1185,7 +1145,7 @@ public final class ImageUtil {
|
|||||||
* Creates a blurred version of the given image.
|
* Creates a blurred version of the given image.
|
||||||
*
|
*
|
||||||
* @param pOriginal the original image
|
* @param pOriginal the original image
|
||||||
* @param pRadius the ammount to blur
|
* @param pRadius the amount to blur
|
||||||
*
|
*
|
||||||
* @return a new {@code BufferedImage} with a blurred version of the given image
|
* @return a new {@code BufferedImage} with a blurred version of the given image
|
||||||
*/
|
*/
|
||||||
@@ -1198,18 +1158,18 @@ public final class ImageUtil {
|
|||||||
// See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation
|
// See: http://en.wikipedia.org/wiki/Gaussian_blur#Implementation
|
||||||
// Also see http://www.jhlabs.com/ip/blurring.html
|
// Also see http://www.jhlabs.com/ip/blurring.html
|
||||||
|
|
||||||
// TODO: Rethink... Fixed ammount and scale matrix instead?
|
// TODO: Rethink... Fixed amount and scale matrix instead?
|
||||||
// pAmmount = 1f - pAmmount;
|
// pAmount = 1f - pAmount;
|
||||||
// float pAmmount = 1f - pRadius;
|
// float pAmount = 1f - pRadius;
|
||||||
//
|
//
|
||||||
// // Normalize ammount
|
// // Normalize amount
|
||||||
// float normAmt = (1f - pAmmount) / 24;
|
// float normAmt = (1f - pAmount) / 24;
|
||||||
//
|
//
|
||||||
// // Create the convolution matrix
|
// // Create the convolution matrix
|
||||||
// float[] data = new float[] {
|
// float[] data = new float[] {
|
||||||
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2,
|
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2,
|
||||||
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
|
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
|
||||||
// normAmt, normAmt * 2, pAmmount, normAmt * 2, normAmt,
|
// normAmt, normAmt * 2, pAmount, normAmt * 2, normAmt,
|
||||||
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
|
// normAmt, normAmt, normAmt * 2, normAmt, normAmt,
|
||||||
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2
|
// normAmt / 2, normAmt, normAmt, normAmt, normAmt / 2
|
||||||
// };
|
// };
|
||||||
@@ -1391,18 +1351,18 @@ public final class ImageUtil {
|
|||||||
* Changes the contrast of the image
|
* Changes the contrast of the image
|
||||||
*
|
*
|
||||||
* @param pOriginal the {@code Image} to change
|
* @param pOriginal the {@code Image} to change
|
||||||
* @param pAmmount the ammount of contrast in the range [-1.0..1.0].
|
* @param pAmount the amount of contrast in the range [-1.0..1.0].
|
||||||
*
|
*
|
||||||
* @return an {@code Image}, containing the contrasted image.
|
* @return an {@code Image}, containing the contrasted image.
|
||||||
*/
|
*/
|
||||||
public static Image contrast(Image pOriginal, float pAmmount) {
|
public static Image contrast(Image pOriginal, float pAmount) {
|
||||||
// No change, return original
|
// No change, return original
|
||||||
if (pAmmount == 0f) {
|
if (pAmount == 0f) {
|
||||||
return pOriginal;
|
return pOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create filter
|
// Create filter
|
||||||
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmmount);
|
RGBImageFilter filter = new BrightnessContrastFilter(0f, pAmount);
|
||||||
|
|
||||||
// Return contrast adjusted image
|
// Return contrast adjusted image
|
||||||
return filter(pOriginal, filter);
|
return filter(pOriginal, filter);
|
||||||
@@ -1413,18 +1373,18 @@ public final class ImageUtil {
|
|||||||
* Changes the brightness of the original image.
|
* Changes the brightness of the original image.
|
||||||
*
|
*
|
||||||
* @param pOriginal the {@code Image} to change
|
* @param pOriginal the {@code Image} to change
|
||||||
* @param pAmmount the ammount of brightness in the range [-2.0..2.0].
|
* @param pAmount the amount of brightness in the range [-2.0..2.0].
|
||||||
*
|
*
|
||||||
* @return an {@code Image}
|
* @return an {@code Image}
|
||||||
*/
|
*/
|
||||||
public static Image brightness(Image pOriginal, float pAmmount) {
|
public static Image brightness(Image pOriginal, float pAmount) {
|
||||||
// No change, return original
|
// No change, return original
|
||||||
if (pAmmount == 0f) {
|
if (pAmount == 0f) {
|
||||||
return pOriginal;
|
return pOriginal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create filter
|
// Create filter
|
||||||
RGBImageFilter filter = new BrightnessContrastFilter(pAmmount, 0f);
|
RGBImageFilter filter = new BrightnessContrastFilter(pAmount, 0f);
|
||||||
|
|
||||||
// Return brightness adjusted image
|
// Return brightness adjusted image
|
||||||
return filter(pOriginal, filter);
|
return filter(pOriginal, filter);
|
||||||
@@ -1465,7 +1425,7 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to use H/W-accellerated code for an image for display purposes.
|
* Tries to use H/W-accelerated code for an image for display purposes.
|
||||||
* Note that transparent parts of the image might be replaced by solid
|
* Note that transparent parts of the image might be replaced by solid
|
||||||
* color. Additional image information not used by the current diplay
|
* color. Additional image information not used by the current diplay
|
||||||
* hardware may be discarded, like extra bith depth etc.
|
* hardware may be discarded, like extra bith depth etc.
|
||||||
@@ -1478,7 +1438,7 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to use H/W-accellerated code for an image for display purposes.
|
* Tries to use H/W-accelerated code for an image for display purposes.
|
||||||
* Note that transparent parts of the image might be replaced by solid
|
* Note that transparent parts of the image might be replaced by solid
|
||||||
* color. Additional image information not used by the current diplay
|
* color. Additional image information not used by the current diplay
|
||||||
* hardware may be discarded, like extra bith depth etc.
|
* hardware may be discarded, like extra bith depth etc.
|
||||||
@@ -1494,7 +1454,7 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to use H/W-accellerated code for an image for display purposes.
|
* Tries to use H/W-accelerated code for an image for display purposes.
|
||||||
* Note that transparent parts of the image will be replaced by solid
|
* Note that transparent parts of the image will be replaced by solid
|
||||||
* color. Additional image information not used by the current diplay
|
* color. Additional image information not used by the current diplay
|
||||||
* hardware may be discarded, like extra bith depth etc.
|
* hardware may be discarded, like extra bith depth etc.
|
||||||
@@ -1784,7 +1744,7 @@ public final class ImageUtil {
|
|||||||
* @param pTimeOut the time to wait, in milliseconds.
|
* @param pTimeOut the time to wait, in milliseconds.
|
||||||
*
|
*
|
||||||
* @return true if the image was loaded successfully, false if an error
|
* @return true if the image was loaded successfully, false if an error
|
||||||
* occured, or the wait was interrupted.
|
* occurred, or the wait was interrupted.
|
||||||
*
|
*
|
||||||
* @see #waitForImages(Image[],long)
|
* @see #waitForImages(Image[],long)
|
||||||
*/
|
*/
|
||||||
@@ -1799,7 +1759,7 @@ public final class ImageUtil {
|
|||||||
* @param pImages an array of Image objects to wait for.
|
* @param pImages an array of Image objects to wait for.
|
||||||
*
|
*
|
||||||
* @return true if the images was loaded successfully, false if an error
|
* @return true if the images was loaded successfully, false if an error
|
||||||
* occured, or the wait was interrupted.
|
* occurred, or the wait was interrupted.
|
||||||
*
|
*
|
||||||
* @see #waitForImages(Image[],long)
|
* @see #waitForImages(Image[],long)
|
||||||
*/
|
*/
|
||||||
@@ -1815,7 +1775,7 @@ public final class ImageUtil {
|
|||||||
* @param pTimeOut the time to wait, in milliseconds
|
* @param pTimeOut the time to wait, in milliseconds
|
||||||
*
|
*
|
||||||
* @return true if the images was loaded successfully, false if an error
|
* @return true if the images was loaded successfully, false if an error
|
||||||
* occured, or the wait was interrupted.
|
* occurred, or the wait was interrupted.
|
||||||
*/
|
*/
|
||||||
public static boolean waitForImages(Image[] pImages, long pTimeOut) {
|
public static boolean waitForImages(Image[] pImages, long pTimeOut) {
|
||||||
// TODO: Need to make sure that we don't wait for the same image many times
|
// TODO: Need to make sure that we don't wait for the same image many times
|
||||||
@@ -1825,13 +1785,6 @@ public final class ImageUtil {
|
|||||||
// Create a local id for use with the mediatracker
|
// Create a local id for use with the mediatracker
|
||||||
int imageId;
|
int imageId;
|
||||||
|
|
||||||
// NOTE: The synchronization throws IllegalMonitorStateException if
|
|
||||||
// using JIT on J2SE 1.2 (tested version Sun JRE 1.2.2_017).
|
|
||||||
// Works perfectly interpreted... Hmmm...
|
|
||||||
//synchronized (sTrackerMutex) {
|
|
||||||
//imageId = ++sTrackerId;
|
|
||||||
//}
|
|
||||||
|
|
||||||
// NOTE: This is very experimental...
|
// NOTE: This is very experimental...
|
||||||
imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages);
|
imageId = pImages.length == 1 ? System.identityHashCode(pImages[0]) : System.identityHashCode(pImages);
|
||||||
|
|
||||||
@@ -1877,7 +1830,7 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests wether the image has any transparent or semi-transparent pixels.
|
* Tests whether the image has any transparent or semi-transparent pixels.
|
||||||
*
|
*
|
||||||
* @param pImage the image
|
* @param pImage the image
|
||||||
* @param pFast if {@code true}, the method tests maximum 10 x 10 pixels,
|
* @param pFast if {@code true}, the method tests maximum 10 x 10 pixels,
|
||||||
@@ -1945,7 +1898,7 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blends two ARGB values half and half, to create a tone inbetween.
|
* Blends two ARGB values half and half, to create a tone in between.
|
||||||
*
|
*
|
||||||
* @param pRGB1 color 1
|
* @param pRGB1 color 1
|
||||||
* @param pRGB2 color 2
|
* @param pRGB2 color 2
|
||||||
@@ -1958,7 +1911,7 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blends two colors half and half, to create a tone inbetween.
|
* Blends two colors half and half, to create a tone in between.
|
||||||
*
|
*
|
||||||
* @param pColor color 1
|
* @param pColor color 1
|
||||||
* @param pOther color 2
|
* @param pOther color 2
|
||||||
@@ -1976,7 +1929,7 @@ public final class ImageUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Blends two colors, controlled by the blendfactor.
|
* Blends two colors, controlled by the blending factor.
|
||||||
* A factor of {@code 0.0} will return the first color,
|
* A factor of {@code 0.0} will return the first color,
|
||||||
* a factor of {@code 1.0} will return the second.
|
* a factor of {@code 1.0} will return the second.
|
||||||
*
|
*
|
||||||
@@ -1998,50 +1951,4 @@ public final class ImageUtil {
|
|||||||
private static int clamp(float f) {
|
private static int clamp(float f) {
|
||||||
return (int) f;
|
return (int) f;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* PixelGrabber subclass that stores any potential properties from an image.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
private static class MyPixelGrabber extends PixelGrabber {
|
|
||||||
private Hashtable mProps = null;
|
|
||||||
|
|
||||||
public MyPixelGrabber(Image pImage) {
|
|
||||||
// Simply grab all pixels, do not convert to default RGB space
|
|
||||||
super(pImage, 0, 0, -1, -1, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default implementation does not store the properties...
|
|
||||||
public void setProperties(Hashtable pProps) {
|
|
||||||
super.setProperties(pProps);
|
|
||||||
mProps = pProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Hashtable getProperties() {
|
|
||||||
return mProps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the transfer type from the given {@code ColorModel}.
|
|
||||||
* <p/>
|
|
||||||
* NOTE: This is a workaround for missing functionality in JDK 1.2.
|
|
||||||
*
|
|
||||||
* @param pModel the color model
|
|
||||||
* @return the transfer type
|
|
||||||
*
|
|
||||||
* @throws NullPointerException if {@code pModel} is {@code null}.
|
|
||||||
*
|
|
||||||
* @see java.awt.image.ColorModel#getTransferType()
|
|
||||||
*/
|
|
||||||
public static int getTransferType(ColorModel pModel) {
|
|
||||||
if (COLORMODEL_TRANSFERTYPE_SUPPORTED) {
|
|
||||||
return pModel.getTransferType();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Stupid workaround
|
|
||||||
// TODO: Create something that performs better
|
|
||||||
return pModel.createCompatibleSampleModel(1, 1).getDataType();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class implements an adaptive pallete generator to reduce images
|
* This class implements an adaptive palette generator to reduce images
|
||||||
* to a variable number of colors.
|
* to a variable number of colors.
|
||||||
* It can also render images into fixed color pallettes.
|
* It can also render images into fixed color pallettes.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -589,7 +589,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Gets an {@code IndexColorModel} from the given image. If the image has an
|
* Gets an {@code IndexColorModel} from the given image. If the image has an
|
||||||
* {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel}
|
* {@code IndexColorModel}, this will be returned. Otherwise, an {@code IndexColorModel}
|
||||||
* is created, using an adaptive pallete.
|
* is created, using an adaptive palette.
|
||||||
*
|
*
|
||||||
* @param pImage the image to get {@code IndexColorModel} from
|
* @param pImage the image to get {@code IndexColorModel} from
|
||||||
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
||||||
@@ -648,7 +648,7 @@ class IndexImage {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@code IndexColorModel} from the given image, using an adaptive
|
* Creates an {@code IndexColorModel} from the given image, using an adaptive
|
||||||
* pallete.
|
* palette.
|
||||||
*
|
*
|
||||||
* @param pImage the image to get {@code IndexColorModel} from
|
* @param pImage the image to get {@code IndexColorModel} from
|
||||||
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
* @param pNumberOfColors the number of colors for the {@code IndexColorModel}
|
||||||
@@ -821,7 +821,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
||||||
* pallete (8 bit) from the color data in the image, and uses default
|
* palette (8 bit) from the color data in the image, and uses default
|
||||||
* dither.
|
* dither.
|
||||||
* <p/>
|
* <p/>
|
||||||
* The image returned is a new image, the input image is not modified.
|
* The image returned is a new image, the input image is not modified.
|
||||||
@@ -865,7 +865,7 @@ class IndexImage {
|
|||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
||||||
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
||||||
* adaptive pallete (8 bit) from the given palette image.
|
* adaptive palette (8 bit) from the given palette image.
|
||||||
* Dithering, transparency and color selection is controlled with the
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -875,7 +875,7 @@ class IndexImage {
|
|||||||
* @param pPalette the Image to read color information from
|
* @param pPalette the Image to read color information from
|
||||||
* @param pMatte the background color, used where the original image was
|
* @param pMatte the background color, used where the original image was
|
||||||
* transparent
|
* transparent
|
||||||
* @param pHints mHints that control output quality and speed.
|
* @param pHints hints that control output quality and speed.
|
||||||
* @return the indexed BufferedImage. The image will be of type
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||||
@@ -900,7 +900,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
||||||
* pallete with the given number of colors.
|
* palette with the given number of colors.
|
||||||
* Dithering, transparency and color selection is controlled with the
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -910,7 +910,7 @@ class IndexImage {
|
|||||||
* @param pNumberOfColors the number of colors for the image
|
* @param pNumberOfColors the number of colors for the image
|
||||||
* @param pMatte the background color, used where the original image was
|
* @param pMatte the background color, used where the original image was
|
||||||
* transparent
|
* transparent
|
||||||
* @param pHints mHints that control output quality and speed.
|
* @param pHints hints that control output quality and speed.
|
||||||
* @return the indexed BufferedImage. The image will be of type
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||||
@@ -947,7 +947,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
||||||
* {@code IndexColorModel}'s pallete.
|
* {@code IndexColorModel}'s palette.
|
||||||
* Dithering, transparency and color selection is controlled with the
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints} parameter.
|
* {@code pHints} parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -1064,7 +1064,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
* {@code TYPE_INT_ARGB}) to an indexed image. Generating an adaptive
|
||||||
* pallete with the given number of colors.
|
* palette with the given number of colors.
|
||||||
* Dithering, transparency and color selection is controlled with the
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -1072,7 +1072,7 @@ class IndexImage {
|
|||||||
*
|
*
|
||||||
* @param pImage the BufferedImage to index
|
* @param pImage the BufferedImage to index
|
||||||
* @param pNumberOfColors the number of colors for the image
|
* @param pNumberOfColors the number of colors for the image
|
||||||
* @param pHints mHints that control output quality and speed.
|
* @param pHints hints that control output quality and speed.
|
||||||
* @return the indexed BufferedImage. The image will be of type
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||||
@@ -1094,7 +1094,7 @@ class IndexImage {
|
|||||||
/**
|
/**
|
||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
* {@code TYPE_INT_ARGB}) to an indexed image. Using the supplied
|
||||||
* {@code IndexColorModel}'s pallete.
|
* {@code IndexColorModel}'s palette.
|
||||||
* Dithering, transparency and color selection is controlled with the
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -1125,7 +1125,7 @@ class IndexImage {
|
|||||||
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
* Converts the input image (must be {@code TYPE_INT_RGB} or
|
||||||
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
* {@code TYPE_INT_ARGB}) to an indexed image. If the palette image
|
||||||
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
* uses an {@code IndexColorModel}, this will be used. Otherwise, generating an
|
||||||
* adaptive pallete (8 bit) from the given palette image.
|
* adaptive palette (8 bit) from the given palette image.
|
||||||
* Dithering, transparency and color selection is controlled with the
|
* Dithering, transparency and color selection is controlled with the
|
||||||
* {@code pHints}parameter.
|
* {@code pHints}parameter.
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -1133,7 +1133,7 @@ class IndexImage {
|
|||||||
*
|
*
|
||||||
* @param pImage the BufferedImage to index
|
* @param pImage the BufferedImage to index
|
||||||
* @param pPalette the Image to read color information from
|
* @param pPalette the Image to read color information from
|
||||||
* @param pHints mHints that control output quality and speed.
|
* @param pHints hints that control output quality and speed.
|
||||||
* @return the indexed BufferedImage. The image will be of type
|
* @return the indexed BufferedImage. The image will be of type
|
||||||
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
* {@code BufferedImage.TYPE_BYTE_INDEXED} or
|
||||||
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
* {@code BufferedImage.TYPE_BYTE_BINARY}, and use an
|
||||||
@@ -1393,7 +1393,7 @@ class IndexImage {
|
|||||||
System.exit(5);
|
System.exit(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create mHints
|
// Create hints
|
||||||
int hints = DITHER_DEFAULT;
|
int hints = DITHER_DEFAULT;
|
||||||
|
|
||||||
if ("DIFFUSION".equalsIgnoreCase(dither)) {
|
if ("DIFFUSION".equalsIgnoreCase(dither)) {
|
||||||
|
|||||||
+10
-11
@@ -30,6 +30,7 @@
|
|||||||
package com.twelvemonkeys.image;
|
package com.twelvemonkeys.image;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.DataBuffer;
|
import java.awt.image.DataBuffer;
|
||||||
@@ -37,7 +38,7 @@ import java.awt.image.IndexColorModel;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A faster implementation of {@code IndexColorModel}, that is backed by an
|
* A faster implementation of {@code IndexColorModel}, that is backed by an
|
||||||
* inverse color-map, for fast lookups.
|
* inverse color-map, for fast look-ups.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author $Author: haku $
|
* @author $Author: haku $
|
||||||
@@ -60,19 +61,17 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
* Creates an {@code InverseColorMapIndexColorModel} from an existing
|
* Creates an {@code InverseColorMapIndexColorModel} from an existing
|
||||||
* {@code IndexColorModel}.
|
* {@code IndexColorModel}.
|
||||||
*
|
*
|
||||||
* @param pColorModel the colormodel to create from
|
* @param pColorModel the color model to create from.
|
||||||
|
* @throws IllegalArgumentException if {@code pColorModel} is {@code null}
|
||||||
*/
|
*/
|
||||||
public InverseColorMapIndexColorModel(IndexColorModel pColorModel) {
|
public InverseColorMapIndexColorModel(final IndexColorModel pColorModel) {
|
||||||
this(pColorModel, getRGBs(pColorModel));
|
this(Validate.notNull(pColorModel, "color model"), getRGBs(pColorModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
|
// NOTE: The pRGBs parameter is used to get around invoking getRGBs two
|
||||||
// times. What is wrong with protected?!
|
// times. What is wrong with protected?!
|
||||||
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
|
private InverseColorMapIndexColorModel(IndexColorModel pColorModel, int[] pRGBs) {
|
||||||
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(),
|
super(pColorModel.getComponentSize()[0], pColorModel.getMapSize(), pRGBs, 0, pColorModel.getTransferType(), pColorModel.getValidPixels());
|
||||||
pRGBs, 0,
|
|
||||||
ImageUtil.getTransferType(pColorModel),
|
|
||||||
pColorModel.getValidPixels());
|
|
||||||
|
|
||||||
rgbs = pRGBs;
|
rgbs = pRGBs;
|
||||||
mapSize = rgbs.length;
|
mapSize = rgbs.length;
|
||||||
@@ -82,11 +81,11 @@ public class InverseColorMapIndexColorModel extends IndexColorModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a defensive copy of the RGB colormap in the given
|
* Creates a defensive copy of the RGB color map in the given
|
||||||
* {@code IndexColorModel}.
|
* {@code IndexColorModel}.
|
||||||
*
|
*
|
||||||
* @param pColorModel the indec colormodel to get RGB values from
|
* @param pColorModel the indexed color model to get RGB values from
|
||||||
* @return the RGB colormap
|
* @return the RGB color map
|
||||||
*/
|
*/
|
||||||
private static int[] getRGBs(IndexColorModel pColorModel) {
|
private static int[] getRGBs(IndexColorModel pColorModel) {
|
||||||
int[] rgb = new int[pColorModel.getMapSize()];
|
int[] rgb = new int[pColorModel.getMapSize()];
|
||||||
|
|||||||
+10
-9
@@ -65,7 +65,8 @@ import java.io.*;
|
|||||||
* @see java.io.DataOutput
|
* @see java.io.DataOutput
|
||||||
*
|
*
|
||||||
* @author Elliotte Rusty Harold
|
* @author Elliotte Rusty Harold
|
||||||
* @version 1.0.3, 28 December 2002
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @version 2
|
||||||
*/
|
*/
|
||||||
public class LittleEndianDataInputStream extends FilterInputStream implements DataInput {
|
public class LittleEndianDataInputStream extends FilterInputStream implements DataInput {
|
||||||
// TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations?
|
// TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations?
|
||||||
@@ -158,7 +159,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
|
return (short) (((byte2 << 24) >>> 16) | (byte1 << 24) >>> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,7 +199,7 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
|
return (char) (((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -221,8 +222,8 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (byte4 << 24) + ((byte3 << 24) >>> 8)
|
return (byte4 << 24) | ((byte3 << 24) >>> 8)
|
||||||
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
|
| ((byte2 << 24) >>> 16) | ((byte1 << 24) >>> 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,10 +249,10 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
|||||||
throw new EOFException();
|
throw new EOFException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (byte8 << 56) + ((byte7 << 56) >>> 8)
|
return (byte8 << 56) | ((byte7 << 56) >>> 8)
|
||||||
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
|
| ((byte6 << 56) >>> 16) | ((byte5 << 56) >>> 24)
|
||||||
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
|
| ((byte4 << 56) >>> 32) | ((byte3 << 56) >>> 40)
|
||||||
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
|
| ((byte2 << 56) >>> 48) | ((byte1 << 56) >>> 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ iff,ilbm=image/x-iff;image/iff
|
|||||||
jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg
|
jpeg,jpg,jpe,jfif=image/jpeg;image/x-jpeg
|
||||||
jpm=image/jpm
|
jpm=image/jpm
|
||||||
png=image/png;image/x-png
|
png=image/png;image/x-png
|
||||||
# NOTE: image/svg-xml is an old reccomendation, should not be used
|
# NOTE: image/svg-xml is an old recommendation, should not be used
|
||||||
svg,svgz=image/svg+xml;image/svg-xml;image/x-svg
|
svg,svgz=image/svg+xml;image/svg-xml;image/x-svg
|
||||||
tga=image/targa;image/x-targa
|
tga=image/targa;image/x-targa
|
||||||
tif,tiff=image/tiff;image/x-tiff
|
tif,tiff=image/tiff;image/x-tiff
|
||||||
|
|||||||
+1
-1
@@ -167,7 +167,7 @@ public abstract class InputStreamAbstractTestCase extends ObjectAbstractTestCase
|
|||||||
input.mark(100); // Should be a no-op
|
input.mark(100); // Should be a no-op
|
||||||
|
|
||||||
int read = input.read();
|
int read = input.read();
|
||||||
assertEquals(0, read);
|
assertTrue(read >= 0);
|
||||||
|
|
||||||
// TODO: According to InputStream#reset, it is allowed to do some
|
// TODO: According to InputStream#reset, it is allowed to do some
|
||||||
// implementation specific reset, and still be correct...
|
// implementation specific reset, and still be correct...
|
||||||
|
|||||||
+208
@@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.io;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LittleEndianDataInputStreamTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: LittleEndianDataInputStreamTest.java,v 1.0 15.02.13 11:04 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class LittleEndianDataInputStreamTest {
|
||||||
|
@Test
|
||||||
|
public void testReadBoolean() throws IOException {
|
||||||
|
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(new byte[] {0, 1, 0x7f, (byte) 0xff}));
|
||||||
|
assertFalse(data.readBoolean());
|
||||||
|
assertTrue(data.readBoolean());
|
||||||
|
assertTrue(data.readBoolean());
|
||||||
|
assertTrue(data.readBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadByte() throws IOException {
|
||||||
|
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x01, (byte) 0x00,
|
||||||
|
(byte) 0xff, (byte) 0xff,
|
||||||
|
(byte) 0x00, (byte) 0x80,
|
||||||
|
(byte) 0xff, (byte) 0x7f,
|
||||||
|
(byte) 0x00, (byte) 0x01,
|
||||||
|
}
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(0, data.readByte());
|
||||||
|
assertEquals(0, data.readByte());
|
||||||
|
assertEquals(1, data.readByte());
|
||||||
|
assertEquals(0, data.readByte());
|
||||||
|
assertEquals(-1, data.readByte());
|
||||||
|
assertEquals(-1, data.readByte());
|
||||||
|
assertEquals(0, data.readByte());
|
||||||
|
assertEquals(Byte.MIN_VALUE, data.readByte());
|
||||||
|
assertEquals(-1, data.readByte());
|
||||||
|
assertEquals(Byte.MAX_VALUE, data.readByte());
|
||||||
|
assertEquals(0, data.readByte());
|
||||||
|
assertEquals(1, data.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadUnsignedByte() throws IOException {
|
||||||
|
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x01, (byte) 0x00,
|
||||||
|
(byte) 0xff, (byte) 0xff,
|
||||||
|
(byte) 0x00, (byte) 0x80,
|
||||||
|
(byte) 0xff, (byte) 0x7f,
|
||||||
|
(byte) 0x00, (byte) 0x01,
|
||||||
|
}
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(0, data.readUnsignedByte());
|
||||||
|
assertEquals(0, data.readUnsignedByte());
|
||||||
|
assertEquals(1, data.readUnsignedByte());
|
||||||
|
assertEquals(0, data.readUnsignedByte());
|
||||||
|
assertEquals(255, data.readUnsignedByte());
|
||||||
|
assertEquals(255, data.readUnsignedByte());
|
||||||
|
assertEquals(0, data.readUnsignedByte());
|
||||||
|
assertEquals(128, data.readUnsignedByte());
|
||||||
|
assertEquals(255, data.readUnsignedByte());
|
||||||
|
assertEquals(Byte.MAX_VALUE, data.readUnsignedByte());
|
||||||
|
assertEquals(0, data.readUnsignedByte());
|
||||||
|
assertEquals(1, data.readUnsignedByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadShort() throws IOException {
|
||||||
|
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x01, (byte) 0x00,
|
||||||
|
(byte) 0xff, (byte) 0xff,
|
||||||
|
(byte) 0x00, (byte) 0x80,
|
||||||
|
(byte) 0xff, (byte) 0x7f,
|
||||||
|
(byte) 0x00, (byte) 0x01,
|
||||||
|
}
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(0, data.readShort());
|
||||||
|
assertEquals(1, data.readShort());
|
||||||
|
assertEquals(-1, data.readShort());
|
||||||
|
assertEquals(Short.MIN_VALUE, data.readShort());
|
||||||
|
assertEquals(Short.MAX_VALUE, data.readShort());
|
||||||
|
assertEquals(256, data.readShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadUnsignedShort() throws IOException {
|
||||||
|
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x01, (byte) 0x00,
|
||||||
|
(byte) 0xff, (byte) 0xff,
|
||||||
|
(byte) 0x00, (byte) 0x80,
|
||||||
|
(byte) 0xff, (byte) 0x7f,
|
||||||
|
(byte) 0x00, (byte) 0x01,
|
||||||
|
}
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(0, data.readUnsignedShort());
|
||||||
|
assertEquals(1, data.readUnsignedShort());
|
||||||
|
assertEquals(Short.MAX_VALUE * 2 + 1, data.readUnsignedShort());
|
||||||
|
assertEquals(Short.MAX_VALUE + 1, data.readUnsignedShort());
|
||||||
|
assertEquals(Short.MAX_VALUE, data.readUnsignedShort());
|
||||||
|
assertEquals(256, data.readUnsignedShort());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadInt() throws IOException {
|
||||||
|
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
|
||||||
|
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
|
||||||
|
(byte) 0xff, (byte) 0x00, (byte) 0xff, (byte) 0x00,
|
||||||
|
(byte) 0x00, (byte) 0xff, (byte) 0x00, (byte) 0xff,
|
||||||
|
(byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca,
|
||||||
|
(byte) 0xca, (byte) 0xfe, (byte) 0xd0, (byte) 0x0d,
|
||||||
|
}
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(0, data.readInt());
|
||||||
|
assertEquals(1, data.readInt());
|
||||||
|
assertEquals(-1, data.readInt());
|
||||||
|
assertEquals(Integer.MIN_VALUE, data.readInt());
|
||||||
|
assertEquals(Integer.MAX_VALUE, data.readInt());
|
||||||
|
assertEquals(16777216, data.readInt());
|
||||||
|
assertEquals(0xff00ff, data.readInt());
|
||||||
|
assertEquals(0xff00ff00, data.readInt());
|
||||||
|
assertEquals(0xCafeBabe, data.readInt());
|
||||||
|
assertEquals(0x0dd0feca, data.readInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadLong() throws IOException {
|
||||||
|
LittleEndianDataInputStream data = new LittleEndianDataInputStream(new ByteArrayInputStream(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
|
||||||
|
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
|
||||||
|
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
|
||||||
|
(byte) 0x0d, (byte) 0xd0, (byte) 0xfe, (byte) 0xca, (byte) 0xbe, (byte) 0xba, (byte) 0xfe, (byte) 0xca,
|
||||||
|
}
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
assertEquals(0, data.readLong());
|
||||||
|
assertEquals(1, data.readLong());
|
||||||
|
assertEquals(-1, data.readLong());
|
||||||
|
assertEquals(Long.MIN_VALUE, data.readLong());
|
||||||
|
assertEquals(Long.MAX_VALUE, data.readLong());
|
||||||
|
assertEquals(72057594037927936L, data.readLong());
|
||||||
|
assertEquals(0xCafeBabeL << 32 | 0xCafeD00dL, data.readLong());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,8 +41,7 @@ import java.util.Arrays;
|
|||||||
/**
|
/**
|
||||||
* A utility class with some useful bean-related functions.
|
* A utility class with some useful bean-related functions.
|
||||||
* <p/>
|
* <p/>
|
||||||
* <em>NOTE: This class is not considered part of the public API and may be
|
* <em>NOTE: This class is not considered part of the public API and may be changed without notice</em>
|
||||||
* changed without notice</em>
|
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haku $
|
* @author last modified by $Author: haku $
|
||||||
@@ -63,7 +62,7 @@ public final class BeanUtil {
|
|||||||
* @param pObject The object to get the property from
|
* @param pObject The object to get the property from
|
||||||
* @param pProperty The name of the property
|
* @param pProperty The name of the property
|
||||||
*
|
*
|
||||||
* @return A string containing the value of the given property, or null
|
* @return A string containing the value of the given property, or {@code null}
|
||||||
* if it can not be found.
|
* if it can not be found.
|
||||||
* @todo Remove System.err's... Create new Exception? Hmm..
|
* @todo Remove System.err's... Create new Exception? Hmm..
|
||||||
*/
|
*/
|
||||||
@@ -77,7 +76,7 @@ public final class BeanUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class objClass = pObject.getClass();
|
Class<?> objClass = pObject.getClass();
|
||||||
|
|
||||||
Object result = pObject;
|
Object result = pObject;
|
||||||
|
|
||||||
@@ -154,9 +153,8 @@ public final class BeanUtil {
|
|||||||
catch (NoSuchMethodException e) {
|
catch (NoSuchMethodException e) {
|
||||||
System.err.print("No method named \"" + methodName + "()\"");
|
System.err.print("No method named \"" + methodName + "()\"");
|
||||||
// The array might be of size 0...
|
// The array might be of size 0...
|
||||||
if (paramClass != null && paramClass.length > 0) {
|
if (paramClass.length > 0 && paramClass[0] != null) {
|
||||||
System.err.print(" with the parameter "
|
System.err.print(" with the parameter " + paramClass[0].getName());
|
||||||
+ paramClass[0].getName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
System.err.println(" in class " + objClass.getName() + "!");
|
System.err.println(" in class " + objClass.getName() + "!");
|
||||||
@@ -177,8 +175,7 @@ public final class BeanUtil {
|
|||||||
result = method.invoke(result, param);
|
result = method.invoke(result, param);
|
||||||
}
|
}
|
||||||
catch (InvocationTargetException e) {
|
catch (InvocationTargetException e) {
|
||||||
System.err.println("property=" + pProperty + " & result="
|
System.err.println("property=" + pProperty + " & result=" + result + " & param=" + Arrays.toString(param));
|
||||||
+ result + " & param=" + Arrays.toString(param));
|
|
||||||
e.getTargetException().printStackTrace();
|
e.getTargetException().printStackTrace();
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
@@ -188,8 +185,7 @@ public final class BeanUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (NullPointerException e) {
|
catch (NullPointerException e) {
|
||||||
System.err.println(objClass.getName() + "." + method.getName()
|
System.err.println(objClass.getName() + "." + method.getName() + "(" + ((paramClass.length > 0 && paramClass[0] != null) ? paramClass[0].getName() : "") + ")");
|
||||||
+ "(" + ((paramClass != null && paramClass.length > 0) ? paramClass[0].getName() : "") + ")");
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -221,10 +217,8 @@ public final class BeanUtil {
|
|||||||
* @throws IllegalAccessException if the caller class has no access to the
|
* @throws IllegalAccessException if the caller class has no access to the
|
||||||
* write method
|
* write method
|
||||||
*/
|
*/
|
||||||
public static void setPropertyValue(Object pObject, String pProperty,
|
public static void setPropertyValue(Object pObject, String pProperty, Object pValue)
|
||||||
Object pValue)
|
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||||
throws NoSuchMethodException, InvocationTargetException,
|
|
||||||
IllegalAccessException {
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// TODO: Support set(Object, Object)/put(Object, Object) methods
|
// TODO: Support set(Object, Object)/put(Object, Object) methods
|
||||||
@@ -255,7 +249,8 @@ public final class BeanUtil {
|
|||||||
method.invoke(obj, params);
|
method.invoke(obj, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues) throws NoSuchMethodException {
|
private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues)
|
||||||
|
throws NoSuchMethodException {
|
||||||
// NOTE: This method assumes pParams.length == 1 && pValues.length == 1
|
// NOTE: This method assumes pParams.length == 1 && pValues.length == 1
|
||||||
|
|
||||||
Method method = null;
|
Method method = null;
|
||||||
@@ -307,10 +302,8 @@ public final class BeanUtil {
|
|||||||
if (method == null) {
|
if (method == null) {
|
||||||
Method[] methods = pObject.getClass().getMethods();
|
Method[] methods = pObject.getClass().getMethods();
|
||||||
for (Method candidate : methods) {
|
for (Method candidate : methods) {
|
||||||
if (Modifier.isPublic(candidate.getModifiers())
|
if (Modifier.isPublic(candidate.getModifiers()) && candidate.getName().equals(pName)
|
||||||
&& candidate.getName().equals(pName)
|
&& candidate.getReturnType() == Void.TYPE && candidate.getParameterTypes().length == 1) {
|
||||||
&& candidate.getReturnType() == Void.TYPE
|
|
||||||
&& candidate.getParameterTypes().length == 1) {
|
|
||||||
// NOTE: Assumes paramTypes.length == 1
|
// NOTE: Assumes paramTypes.length == 1
|
||||||
|
|
||||||
Class type = candidate.getParameterTypes()[0];
|
Class type = candidate.getParameterTypes()[0];
|
||||||
@@ -337,7 +330,7 @@ public final class BeanUtil {
|
|||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object convertValueToType(Object pValue, Class pType) throws ConversionException {
|
private static Object convertValueToType(Object pValue, Class<?> pType) throws ConversionException {
|
||||||
if (pType.isPrimitive()) {
|
if (pType.isPrimitive()) {
|
||||||
if (pType == Boolean.TYPE && pValue instanceof Boolean) {
|
if (pType == Boolean.TYPE && pValue instanceof Boolean) {
|
||||||
return pValue;
|
return pValue;
|
||||||
@@ -395,7 +388,7 @@ public final class BeanUtil {
|
|||||||
* @throws InvocationTargetException if the constructor failed
|
* @throws InvocationTargetException if the constructor failed
|
||||||
*/
|
*/
|
||||||
// TODO: Move to ReflectUtil
|
// TODO: Move to ReflectUtil
|
||||||
public static Object createInstance(Class pClass, Object pParam)
|
public static <T> T createInstance(Class<T> pClass, Object pParam)
|
||||||
throws InvocationTargetException {
|
throws InvocationTargetException {
|
||||||
return createInstance(pClass, new Object[] {pParam});
|
return createInstance(pClass, new Object[] {pParam});
|
||||||
}
|
}
|
||||||
@@ -414,9 +407,9 @@ public final class BeanUtil {
|
|||||||
* @throws InvocationTargetException if the constructor failed
|
* @throws InvocationTargetException if the constructor failed
|
||||||
*/
|
*/
|
||||||
// TODO: Move to ReflectUtil
|
// TODO: Move to ReflectUtil
|
||||||
public static Object createInstance(Class pClass, Object... pParams)
|
public static <T> T createInstance(Class<T> pClass, Object... pParams)
|
||||||
throws InvocationTargetException {
|
throws InvocationTargetException {
|
||||||
Object value;
|
T value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create param and argument arrays
|
// Create param and argument arrays
|
||||||
@@ -429,8 +422,7 @@ public final class BeanUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get constructor
|
// Get constructor
|
||||||
//Constructor constructor = pClass.getDeclaredConstructor(paramTypes);
|
Constructor<T> constructor = pClass.getConstructor(paramTypes);
|
||||||
Constructor constructor = pClass.getConstructor(paramTypes);
|
|
||||||
|
|
||||||
// Invoke and create instance
|
// Invoke and create instance
|
||||||
value = constructor.newInstance(pParams);
|
value = constructor.newInstance(pParams);
|
||||||
@@ -468,12 +460,11 @@ public final class BeanUtil {
|
|||||||
* If the return type of the method is void, null is returned.
|
* If the return type of the method is void, null is returned.
|
||||||
* If the method could not be invoked for any reason, null is returned.
|
* If the method could not be invoked for any reason, null is returned.
|
||||||
*
|
*
|
||||||
* @throws InvocationTargetException if the invocaton failed
|
* @throws InvocationTargetException if the invocation failed
|
||||||
*/
|
*/
|
||||||
// TODO: Move to ReflectUtil
|
// TODO: Move to ReflectUtil
|
||||||
// TODO: Rename to invokeStatic?
|
// TODO: Rename to invokeStatic?
|
||||||
public static Object invokeStaticMethod(Class pClass, String pMethod,
|
public static Object invokeStaticMethod(Class<?> pClass, String pMethod, Object pParam)
|
||||||
Object pParam)
|
|
||||||
throws InvocationTargetException {
|
throws InvocationTargetException {
|
||||||
|
|
||||||
return invokeStaticMethod(pClass, pMethod, new Object[] {pParam});
|
return invokeStaticMethod(pClass, pMethod, new Object[] {pParam});
|
||||||
@@ -492,12 +483,11 @@ public final class BeanUtil {
|
|||||||
* If the return type of the method is void, null is returned.
|
* If the return type of the method is void, null is returned.
|
||||||
* If the method could not be invoked for any reason, null is returned.
|
* If the method could not be invoked for any reason, null is returned.
|
||||||
*
|
*
|
||||||
* @throws InvocationTargetException if the invocaton failed
|
* @throws InvocationTargetException if the invocation failed
|
||||||
*/
|
*/
|
||||||
// TODO: Move to ReflectUtil
|
// TODO: Move to ReflectUtil
|
||||||
// TODO: Rename to invokeStatic?
|
// TODO: Rename to invokeStatic?
|
||||||
public static Object invokeStaticMethod(Class pClass, String pMethod,
|
public static Object invokeStaticMethod(Class<?> pClass, String pMethod, Object... pParams)
|
||||||
Object[] pParams)
|
|
||||||
throws InvocationTargetException {
|
throws InvocationTargetException {
|
||||||
|
|
||||||
Object value = null;
|
Object value = null;
|
||||||
@@ -518,8 +508,7 @@ public final class BeanUtil {
|
|||||||
Method method = pClass.getMethod(pMethod, paramTypes);
|
Method method = pClass.getMethod(pMethod, paramTypes);
|
||||||
|
|
||||||
// Invoke public static method
|
// Invoke public static method
|
||||||
if (Modifier.isPublic(method.getModifiers())
|
if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
|
||||||
&& Modifier.isStatic(method.getModifiers())) {
|
|
||||||
value = method.invoke(null, pParams);
|
value = method.invoke(null, pParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ public abstract class AbstractTokenIterator implements TokenIterator {
|
|||||||
public void remove() {
|
public void remove() {
|
||||||
// TODO: This is not difficult:
|
// TODO: This is not difficult:
|
||||||
// - Convert String to StringBuilder in constructor
|
// - Convert String to StringBuilder in constructor
|
||||||
// - delete(pos, mNext.lenght())
|
// - delete(pos, next.lenght())
|
||||||
// - Add toString() method
|
// - Add toString() method
|
||||||
// BUT: Would it ever be useful? :-)
|
// BUT: Would it ever be useful? :-)
|
||||||
|
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ public class LRUHashMap<K, V> extends LinkedHashMap<K, V> implements ExpiringMap
|
|||||||
*/
|
*/
|
||||||
public void removeLRU() {
|
public void removeLRU() {
|
||||||
int removeCount = (int) Math.max((size() * trimFactor), 1);
|
int removeCount = (int) Math.max((size() * trimFactor), 1);
|
||||||
|
|
||||||
Iterator<Map.Entry<K, V>> entries = entrySet().iterator();
|
Iterator<Map.Entry<K, V>> entries = entrySet().iterator();
|
||||||
while ((removeCount--) > 0 && entries.hasNext()) {
|
while ((removeCount--) > 0 && entries.hasNext()) {
|
||||||
entries.next();
|
entries.next();
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import java.util.Map;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/LRUMap.java#1 $
|
* @version $Id: com/twelvemonkeys/util/LRUMap.java#1 $
|
||||||
*/
|
*/
|
||||||
public class LRUMap<K, V> extends LinkedMap<K, V> implements ExpiringMap<K, V> {
|
public class LRUMap<K, V> extends LinkedMap<K, V> implements ExpiringMap<K, V> {
|
||||||
|
|
||||||
@@ -222,8 +222,9 @@ public class LRUMap<K, V> extends LinkedMap<K, V> implements ExpiringMap<K, V> {
|
|||||||
*/
|
*/
|
||||||
public void removeLRU() {
|
public void removeLRU() {
|
||||||
int removeCount = (int) Math.max((size() * trimFactor), 1);
|
int removeCount = (int) Math.max((size() * trimFactor), 1);
|
||||||
|
|
||||||
while ((removeCount--) > 0) {
|
while ((removeCount--) > 0) {
|
||||||
removeEntry(head.mNext);
|
removeEntry(head.next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,19 +181,19 @@ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Seria
|
|||||||
return "head";
|
return "head";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
head.mPrevious = head.mNext = head;
|
head.previous = head.next = head;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsValue(Object pValue) {
|
public boolean containsValue(Object pValue) {
|
||||||
// Overridden to take advantage of faster iterator
|
// Overridden to take advantage of faster iterator
|
||||||
if (pValue == null) {
|
if (pValue == null) {
|
||||||
for (LinkedEntry e = head.mNext; e != head; e = e.mNext) {
|
for (LinkedEntry e = head.next; e != head; e = e.next) {
|
||||||
if (e.mValue == null) {
|
if (e.mValue == null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (LinkedEntry e = head.mNext; e != head; e = e.mNext) {
|
for (LinkedEntry e = head.next; e != head; e = e.next) {
|
||||||
if (pValue.equals(e.mValue)) {
|
if (pValue.equals(e.mValue)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -215,7 +215,7 @@ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Seria
|
|||||||
}
|
}
|
||||||
|
|
||||||
private abstract class LinkedMapIterator<E> implements Iterator<E> {
|
private abstract class LinkedMapIterator<E> implements Iterator<E> {
|
||||||
LinkedEntry<K, V> mNextEntry = head.mNext;
|
LinkedEntry<K, V> mNextEntry = head.next;
|
||||||
LinkedEntry<K, V> mLastReturned = null;
|
LinkedEntry<K, V> mLastReturned = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,7 +254,7 @@ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Seria
|
|||||||
}
|
}
|
||||||
|
|
||||||
LinkedEntry<K, V> e = mLastReturned = mNextEntry;
|
LinkedEntry<K, V> e = mLastReturned = mNextEntry;
|
||||||
mNextEntry = e.mNext;
|
mNextEntry = e.next;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Seria
|
|||||||
oldValue = null;
|
oldValue = null;
|
||||||
|
|
||||||
// Remove eldest entry if instructed, else grow capacity if appropriate
|
// Remove eldest entry if instructed, else grow capacity if appropriate
|
||||||
LinkedEntry<K, V> eldest = head.mNext;
|
LinkedEntry<K, V> eldest = head.next;
|
||||||
if (removeEldestEntry(eldest)) {
|
if (removeEldestEntry(eldest)) {
|
||||||
removeEntry(eldest);
|
removeEntry(eldest);
|
||||||
}
|
}
|
||||||
@@ -407,13 +407,13 @@ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Seria
|
|||||||
* Linked list implementation of {@code Map.Entry}.
|
* Linked list implementation of {@code Map.Entry}.
|
||||||
*/
|
*/
|
||||||
protected static class LinkedEntry<K, V> extends BasicEntry<K, V> implements Serializable {
|
protected static class LinkedEntry<K, V> extends BasicEntry<K, V> implements Serializable {
|
||||||
LinkedEntry<K, V> mPrevious;
|
LinkedEntry<K, V> previous;
|
||||||
LinkedEntry<K, V> mNext;
|
LinkedEntry<K, V> next;
|
||||||
|
|
||||||
LinkedEntry(K pKey, V pValue, LinkedEntry<K, V> pNext) {
|
LinkedEntry(K pKey, V pValue, LinkedEntry<K, V> pNext) {
|
||||||
super(pKey, pValue);
|
super(pKey, pValue);
|
||||||
|
|
||||||
mNext = pNext;
|
next = pNext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -423,19 +423,19 @@ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Seria
|
|||||||
* @param pExisting the entry to add before
|
* @param pExisting the entry to add before
|
||||||
*/
|
*/
|
||||||
void addBefore(LinkedEntry<K, V> pExisting) {
|
void addBefore(LinkedEntry<K, V> pExisting) {
|
||||||
mNext = pExisting;
|
next = pExisting;
|
||||||
mPrevious = pExisting.mPrevious;
|
previous = pExisting.previous;
|
||||||
|
|
||||||
mPrevious.mNext = this;
|
previous.next = this;
|
||||||
mNext.mPrevious = this;
|
next.previous = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes this entry from the linked list.
|
* Removes this entry from the linked list.
|
||||||
*/
|
*/
|
||||||
void remove() {
|
void remove() {
|
||||||
mPrevious.mNext = mNext;
|
previous.next = next;
|
||||||
mNext.mPrevious = mPrevious;
|
next.previous = previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -456,7 +456,7 @@ public class LinkedMap<K, V> extends AbstractDecoratedMap<K, V> implements Seria
|
|||||||
/**
|
/**
|
||||||
* Removes this entry from the linked list.
|
* Removes this entry from the linked list.
|
||||||
*
|
*
|
||||||
* @param pMap the map to record remoal from
|
* @param pMap the map to record removal from
|
||||||
*/
|
*/
|
||||||
protected void recordRemoval(Map<K, V> pMap) {
|
protected void recordRemoval(Map<K, V> pMap) {
|
||||||
// TODO: Is this REALLY correct?
|
// TODO: Is this REALLY correct?
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The converter (singleton). Converts strings to objects and back.
|
* The converter (singleton). Converts strings to objects and back.
|
||||||
* This is the entrypoint to the converter framework.
|
* This is the entry point to the converter framework.
|
||||||
* <p/>
|
* <p/>
|
||||||
* By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date}
|
* By default, converters for {@link com.twelvemonkeys.util.Time}, {@link Date}
|
||||||
* and {@link Object}
|
* and {@link Object}
|
||||||
@@ -53,17 +53,17 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
// TODO: Get rid of singleton stuff
|
// TODO: Get rid of singleton stuff
|
||||||
// Can probably be a pure static class, but is that a good idea?
|
// Can probably be a pure static class, but is that a good idea?
|
||||||
// Maybe have BeanUtil act as a "proxy", and hide this class alltogheter?
|
// Maybe have BeanUtil act as a "proxy", and hide this class all together?
|
||||||
// TODO: ServiceRegistry for registering 3rd party converters
|
// TODO: ServiceRegistry for registering 3rd party converters
|
||||||
// TODO: URI scheme, for implicit typing? Is that a good idea?
|
// TODO: URI scheme, for implicit typing? Is that a good idea?
|
||||||
// TODO: Array converters?
|
// TODO: Array converters?
|
||||||
public abstract class Converter implements PropertyConverter {
|
public abstract class Converter implements PropertyConverter {
|
||||||
|
|
||||||
/** Our singleton instance */
|
/** Our singleton instance */
|
||||||
protected static Converter sInstance = new ConverterImpl(); // Thread safe & EASY
|
protected static final Converter sInstance = new ConverterImpl(); // Thread safe & EASY
|
||||||
|
|
||||||
/** The conveters Map */
|
/** The converters Map */
|
||||||
protected Map converters = new Hashtable();
|
protected final Map<Class, PropertyConverter> converters = new Hashtable<Class, PropertyConverter>();
|
||||||
|
|
||||||
// Register our predefined converters
|
// Register our predefined converters
|
||||||
static {
|
static {
|
||||||
@@ -115,20 +115,21 @@ public abstract class Converter implements PropertyConverter {
|
|||||||
*
|
*
|
||||||
* @see #unregisterConverter(Class)
|
* @see #unregisterConverter(Class)
|
||||||
*/
|
*/
|
||||||
public static void registerConverter(Class pType, PropertyConverter pConverter) {
|
public static void registerConverter(final Class<?> pType, final PropertyConverter pConverter) {
|
||||||
getInstance().converters.put(pType, pConverter);
|
getInstance().converters.put(pType, pConverter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregisters a converter for a given type. That is, making it unavailable
|
* Un-registers a converter for a given type. That is, making it unavailable
|
||||||
* for the converter framework, and making it (potentially) available for
|
* for the converter framework, and making it (potentially) available for
|
||||||
* garbabe collection.
|
* garbage collection.
|
||||||
*
|
*
|
||||||
* @param pType the (super) type to remove converter for
|
* @param pType the (super) type to remove converter for
|
||||||
*
|
*
|
||||||
* @see #registerConverter(Class,PropertyConverter)
|
* @see #registerConverter(Class,PropertyConverter)
|
||||||
*/
|
*/
|
||||||
public static void unregisterConverter(Class pType) {
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
public static void unregisterConverter(final Class<?> pType) {
|
||||||
getInstance().converters.remove(pType);
|
getInstance().converters.remove(pType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +144,7 @@ public abstract class Converter implements PropertyConverter {
|
|||||||
* @throws ConversionException if the string cannot be converted for any
|
* @throws ConversionException if the string cannot be converted for any
|
||||||
* reason.
|
* reason.
|
||||||
*/
|
*/
|
||||||
public Object toObject(String pString, Class pType)
|
public Object toObject(final String pString, final Class pType) throws ConversionException {
|
||||||
throws ConversionException {
|
|
||||||
return toObject(pString, pType, null);
|
return toObject(pString, pType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ public abstract class Converter implements PropertyConverter {
|
|||||||
* @throws ConversionException if the object cannot be converted to a
|
* @throws ConversionException if the object cannot be converted to a
|
||||||
* string for any reason.
|
* string for any reason.
|
||||||
*/
|
*/
|
||||||
public String toString(Object pObject) throws ConversionException {
|
public String toString(final Object pObject) throws ConversionException {
|
||||||
return toString(pObject, null);
|
return toString(pObject, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+48
-17
@@ -67,9 +67,9 @@ public final class DefaultConverter implements PropertyConverter {
|
|||||||
*
|
*
|
||||||
* @throws ConversionException if the type is null, or if the string cannot
|
* @throws ConversionException if the type is null, or if the string cannot
|
||||||
* be converted into the given type, using a string constructor or static
|
* be converted into the given type, using a string constructor or static
|
||||||
* {@code valueof} method.
|
* {@code valueOf} method.
|
||||||
*/
|
*/
|
||||||
public Object toObject(String pString, final Class pType, String pFormat) throws ConversionException {
|
public Object toObject(final String pString, final Class pType, final String pFormat) throws ConversionException {
|
||||||
if (pString == null) {
|
if (pString == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -87,13 +87,7 @@ public final class DefaultConverter implements PropertyConverter {
|
|||||||
// But what about generic type?! It's erased...
|
// But what about generic type?! It's erased...
|
||||||
|
|
||||||
// Primitive -> wrapper
|
// Primitive -> wrapper
|
||||||
Class type;
|
Class type = unBoxType(pType);
|
||||||
if (pType == Boolean.TYPE) {
|
|
||||||
type = Boolean.class;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
type = pType;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to create instance from <Constructor>(String)
|
// Try to create instance from <Constructor>(String)
|
||||||
@@ -101,13 +95,15 @@ public final class DefaultConverter implements PropertyConverter {
|
|||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
// createInstance failed for some reason
|
// createInstance failed for some reason
|
||||||
|
// Try to invoke the static method valueOf(String)
|
||||||
// Try to invoke the static method valueof(String)
|
|
||||||
value = BeanUtil.invokeStaticMethod(type, "valueOf", pString);
|
value = BeanUtil.invokeStaticMethod(type, "valueOf", pString);
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
// If the value is still null, well, then I cannot help...
|
// If the value is still null, well, then I cannot help...
|
||||||
throw new ConversionException("Could not convert String to " + pType.getName() + ": No constructor " + type.getName() + "(String) or static " + type.getName() + ".valueof(String) method found!");
|
throw new ConversionException(String.format(
|
||||||
|
"Could not convert String to %1$s: No constructor %1$s(String) or static %1$s.valueOf(String) method found!",
|
||||||
|
type.getName()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,12 +112,15 @@ public final class DefaultConverter implements PropertyConverter {
|
|||||||
catch (InvocationTargetException ite) {
|
catch (InvocationTargetException ite) {
|
||||||
throw new ConversionException(ite.getTargetException());
|
throw new ConversionException(ite.getTargetException());
|
||||||
}
|
}
|
||||||
|
catch (ConversionException ce) {
|
||||||
|
throw ce;
|
||||||
|
}
|
||||||
catch (RuntimeException rte) {
|
catch (RuntimeException rte) {
|
||||||
throw new ConversionException(rte);
|
throw new ConversionException(rte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object toArray(String pString, Class pType, String pFormat) {
|
private Object toArray(final String pString, final Class pType, final String pFormat) {
|
||||||
String[] strings = StringUtil.toStringArray(pString, pFormat != null ? pFormat : StringUtil.DELIMITER_STRING);
|
String[] strings = StringUtil.toStringArray(pString, pFormat != null ? pFormat : StringUtil.DELIMITER_STRING);
|
||||||
Class type = pType.getComponentType();
|
Class type = pType.getComponentType();
|
||||||
if (type == String.class) {
|
if (type == String.class) {
|
||||||
@@ -152,10 +151,9 @@ public final class DefaultConverter implements PropertyConverter {
|
|||||||
* @param pObject the object to convert.
|
* @param pObject the object to convert.
|
||||||
* @param pFormat ignored.
|
* @param pFormat ignored.
|
||||||
*
|
*
|
||||||
* @return the string representation of the object, or {@code null} if
|
* @return the string representation of the object, or {@code null} if {@code pObject == null}
|
||||||
* {@code pObject == null}
|
|
||||||
*/
|
*/
|
||||||
public String toString(Object pObject, String pFormat)
|
public String toString(final Object pObject, final String pFormat)
|
||||||
throws ConversionException {
|
throws ConversionException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -170,7 +168,7 @@ public final class DefaultConverter implements PropertyConverter {
|
|||||||
return pFormat == null ? StringUtil.toCSVString(pArray) : StringUtil.toCSVString(pArray, pFormat);
|
return pFormat == null ? StringUtil.toCSVString(pArray) : StringUtil.toCSVString(pArray, pFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object[] toObjectArray(Object pObject) {
|
private Object[] toObjectArray(final Object pObject) {
|
||||||
// TODO: Extract util method for wrapping/unwrapping native arrays?
|
// TODO: Extract util method for wrapping/unwrapping native arrays?
|
||||||
Object[] array;
|
Object[] array;
|
||||||
Class<?> componentType = pObject.getClass().getComponentType();
|
Class<?> componentType = pObject.getClass().getComponentType();
|
||||||
@@ -232,4 +230,37 @@ public final class DefaultConverter implements PropertyConverter {
|
|||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Class<?> unBoxType(final Class<?> pType) {
|
||||||
|
if (pType.isPrimitive()) {
|
||||||
|
if (pType == boolean.class) {
|
||||||
|
return Boolean.class;
|
||||||
|
}
|
||||||
|
if (pType == byte.class) {
|
||||||
|
return Byte.class;
|
||||||
|
}
|
||||||
|
if (pType == char.class) {
|
||||||
|
return Character.class;
|
||||||
|
}
|
||||||
|
if (pType == short.class) {
|
||||||
|
return Short.class;
|
||||||
|
}
|
||||||
|
if (pType == int.class) {
|
||||||
|
return Integer.class;
|
||||||
|
}
|
||||||
|
if (pType == float.class) {
|
||||||
|
return Float.class;
|
||||||
|
}
|
||||||
|
if (pType == long.class) {
|
||||||
|
return Long.class;
|
||||||
|
}
|
||||||
|
if (pType == double.class) {
|
||||||
|
return Double.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unknown type: " + pType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,13 +108,13 @@ public class BeanUtilTestCase extends TestCase {
|
|||||||
assertEquals(0.3, bean.getDoubleValue());
|
assertEquals(0.3, bean.getDoubleValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testConfigureAmbigious1() {
|
public void testConfigureAmbiguous1() {
|
||||||
TestBean bean = new TestBean();
|
TestBean bean = new TestBean();
|
||||||
|
|
||||||
Map<String, String> map = new HashMap<String, String>();
|
Map<String, String> map = new HashMap<String, String>();
|
||||||
|
|
||||||
String value = "one";
|
String value = "one";
|
||||||
map.put("ambigious", value);
|
map.put("ambiguous", value);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BeanUtil.configure(bean, map);
|
BeanUtil.configure(bean, map);
|
||||||
@@ -123,20 +123,20 @@ public class BeanUtilTestCase extends TestCase {
|
|||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNotNull(bean.getAmbigious());
|
assertNotNull(bean.getAmbiguous());
|
||||||
assertEquals("String converted rather than invoking setAmbigiouos(String), ordering not predictable",
|
assertEquals("String converted rather than invoking setAmbiguous(String), ordering not predictable",
|
||||||
"one", bean.getAmbigious());
|
"one", bean.getAmbiguous());
|
||||||
assertSame("String converted rather than invoking setAmbigiouos(String), ordering not predictable",
|
assertSame("String converted rather than invoking setAmbiguous(String), ordering not predictable",
|
||||||
value, bean.getAmbigious());
|
value, bean.getAmbiguous());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testConfigureAmbigious2() {
|
public void testConfigureAmbiguous2() {
|
||||||
TestBean bean = new TestBean();
|
TestBean bean = new TestBean();
|
||||||
|
|
||||||
Map<String, Integer> map = new HashMap<String, Integer>();
|
Map<String, Integer> map = new HashMap<String, Integer>();
|
||||||
|
|
||||||
Integer value = 2;
|
Integer value = 2;
|
||||||
map.put("ambigious", value);
|
map.put("ambiguous", value);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BeanUtil.configure(bean, map);
|
BeanUtil.configure(bean, map);
|
||||||
@@ -145,20 +145,20 @@ public class BeanUtilTestCase extends TestCase {
|
|||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNotNull(bean.getAmbigious());
|
assertNotNull(bean.getAmbiguous());
|
||||||
assertEquals("Integer converted rather than invoking setAmbigiouos(Integer), ordering not predictable",
|
assertEquals("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable",
|
||||||
2, bean.getAmbigious());
|
2, bean.getAmbiguous());
|
||||||
assertSame("Integer converted rather than invoking setAmbigiouos(Integer), ordering not predictable",
|
assertSame("Integer converted rather than invoking setAmbiguous(Integer), ordering not predictable",
|
||||||
value, bean.getAmbigious());
|
value, bean.getAmbiguous());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testConfigureAmbigious3() {
|
public void testConfigureAmbiguous3() {
|
||||||
TestBean bean = new TestBean();
|
TestBean bean = new TestBean();
|
||||||
|
|
||||||
Map<String, Double> map = new HashMap<String, Double>();
|
Map<String, Double> map = new HashMap<String, Double>();
|
||||||
|
|
||||||
Double value = .3;
|
Double value = .3;
|
||||||
map.put("ambigious", value);
|
map.put("ambiguous", value);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BeanUtil.configure(bean, map);
|
BeanUtil.configure(bean, map);
|
||||||
@@ -167,11 +167,11 @@ public class BeanUtilTestCase extends TestCase {
|
|||||||
fail(e.getMessage());
|
fail(e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
assertNotNull(bean.getAmbigious());
|
assertNotNull(bean.getAmbiguous());
|
||||||
assertEquals("Object converted rather than invoking setAmbigious(Object), ordering not predictable",
|
assertEquals("Object converted rather than invoking setAmbiguous(Object), ordering not predictable",
|
||||||
value.getClass(), bean.getAmbigious().getClass());
|
value.getClass(), bean.getAmbiguous().getClass());
|
||||||
assertSame("Object converted rather than invoking setAmbigious(Object), ordering not predictable",
|
assertSame("Object converted rather than invoking setAmbiguous(Object), ordering not predictable",
|
||||||
value, bean.getAmbigious());
|
value, bean.getAmbiguous());
|
||||||
}
|
}
|
||||||
|
|
||||||
static class TestBean {
|
static class TestBean {
|
||||||
@@ -179,7 +179,7 @@ public class BeanUtilTestCase extends TestCase {
|
|||||||
private int intVal;
|
private int intVal;
|
||||||
private Double doubleVal;
|
private Double doubleVal;
|
||||||
|
|
||||||
private Object ambigious;
|
private Object ambiguous;
|
||||||
|
|
||||||
public Double getDoubleValue() {
|
public Double getDoubleValue() {
|
||||||
return doubleVal;
|
return doubleVal;
|
||||||
@@ -193,36 +193,43 @@ public class BeanUtilTestCase extends TestCase {
|
|||||||
return stringVal;
|
return stringVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
public void setStringValue(String pString) {
|
public void setStringValue(String pString) {
|
||||||
stringVal = pString;
|
stringVal = pString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
public void setIntValue(int pInt) {
|
public void setIntValue(int pInt) {
|
||||||
intVal = pInt;
|
intVal = pInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
public void setDoubleValue(Double pDouble) {
|
public void setDoubleValue(Double pDouble) {
|
||||||
doubleVal = pDouble;
|
doubleVal = pDouble;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAmbigious(String pString) {
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
ambigious = pString;
|
public void setAmbiguous(String pString) {
|
||||||
|
ambiguous = pString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAmbigious(Object pObject) {
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
ambigious = pObject;
|
public void setAmbiguous(Object pObject) {
|
||||||
|
ambiguous = pObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAmbigious(Integer pInteger) {
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
ambigious = pInteger;
|
public void setAmbiguous(Integer pInteger) {
|
||||||
|
ambiguous = pInteger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAmbigious(int pInt) {
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
ambigious = (long) pInt; // Just to differentiate...
|
public void setAmbiguous(int pInt) {
|
||||||
|
ambiguous = (long) pInt; // Just to differentiate...
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getAmbigious() {
|
public Object getAmbiguous() {
|
||||||
return ambigious;
|
return ambiguous;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-11
@@ -1,8 +1,14 @@
|
|||||||
package com.twelvemonkeys.util.convert;
|
package com.twelvemonkeys.util.convert;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DefaultConverterTestCase
|
* DefaultConverterTestCase
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -47,23 +53,76 @@ public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase
|
|||||||
|
|
||||||
// Object array test
|
// Object array test
|
||||||
new Conversion("foo, bar", new FooBar[] {new FooBar("foo"), new FooBar("bar")}),
|
new Conversion("foo, bar", new FooBar[] {new FooBar("foo"), new FooBar("bar")}),
|
||||||
new Conversion("/temp, /usr/local/bin", new File[] {new File("/temp"), new File("/usr/local/bin")}),
|
new Conversion("/temp, /usr/local/bin".replace('/', File.separatorChar), new File[] {new File("/temp"), new File("/usr/local/bin")}),
|
||||||
new Conversion("file:/temp, http://java.net/", new URI[] {URI.create("file:/temp"), URI.create("http://java.net/")}),
|
new Conversion("file:/temp, http://java.net/", new URI[] {URI.create("file:/temp"), URI.create("http://java.net/")}),
|
||||||
|
|
||||||
// TODO: More tests
|
// TODO: More tests
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test boolean -> Boolean conversion
|
@Test
|
||||||
|
public void testConvertBooleanPrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertTrue((Boolean) converter.toObject("true", boolean.class, null));
|
||||||
|
assertFalse((Boolean) converter.toObject("FalsE", Boolean.TYPE, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertShortPrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertEquals(1, (short) (Short) converter.toObject("1", short.class, null));
|
||||||
|
assertEquals(-2, (short) (Short) converter.toObject("-2", Short.TYPE, null));
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
public void testConvertIntPrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertEquals(1, (int) (Integer) converter.toObject("1", int.class, null));
|
||||||
|
assertEquals(-2, (int) (Integer) converter.toObject("-2", Integer.TYPE, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertLongPrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertEquals(Long.MAX_VALUE, (long) (Long) converter.toObject("9223372036854775807", long.class, null));
|
||||||
|
assertEquals(-2, (long) (Long) converter.toObject("-2", Long.TYPE, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertBytePrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertEquals(1, (byte) (Byte) converter.toObject("1", byte.class, null));
|
||||||
|
assertEquals(-2, (byte) (Byte) converter.toObject("-2", Byte.TYPE, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertFloatPrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertEquals(1f, (Float) converter.toObject("1.0", float.class, null), 0);
|
||||||
|
assertEquals(-2.3456f, (Float) converter.toObject("-2.3456", Float.TYPE, null), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertDoublePrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertEquals(1d, (Double) converter.toObject("1.0", double.class, null), 0);
|
||||||
|
assertEquals(-2.3456, (Double) converter.toObject("-2.3456", Double.TYPE, null), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore("Known issue. Why would anyone do something like this?")
|
||||||
|
@Test
|
||||||
|
public void testConvertCharPrimitive() {
|
||||||
|
PropertyConverter converter = makePropertyConverter();
|
||||||
|
assertEquals('A', (char) (Character) converter.toObject("A", char.class, null));
|
||||||
|
assertEquals('Z', (char) (Character) converter.toObject("Z", Character.TYPE, null));
|
||||||
|
}
|
||||||
|
|
||||||
public static class FooBar {
|
public static class FooBar {
|
||||||
private final String mBar;
|
private final String bar;
|
||||||
|
|
||||||
public FooBar(String pFoo) {
|
public FooBar(String pFoo) {
|
||||||
if (pFoo == null) {
|
Validate.notNull(pFoo, "foo");
|
||||||
throw new IllegalArgumentException("pFoo == null");
|
|
||||||
}
|
bar = reverse(pFoo);
|
||||||
mBar = reverse(pFoo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String reverse(String pFoo) {
|
private String reverse(String pFoo) {
|
||||||
@@ -77,16 +136,15 @@ public class DefaultConverterTestCase extends PropertyConverterAbstractTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return reverse(mBar);
|
return reverse(bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
return obj == this || (obj instanceof FooBar && ((FooBar) obj).mBar.equals(mBar));
|
return obj == this || (obj != null && obj.getClass() == getClass() && ((FooBar) obj).bar.equals(bar));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return 7 * mBar.hashCode();
|
return 7 * bar.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,10 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class ImageLabel extends JLabel {
|
private static class ImageLabel extends JLabel {
|
||||||
|
static final String ZOOM_IN = "zoom-in";
|
||||||
|
static final String ZOOM_OUT = "zoom-out";
|
||||||
|
static final String ZOOM_ACTUAL = "zoom-actual";
|
||||||
|
|
||||||
Paint backgroundPaint;
|
Paint backgroundPaint;
|
||||||
|
|
||||||
final Paint checkeredBG;
|
final Paint checkeredBG;
|
||||||
@@ -435,9 +439,8 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
|
|
||||||
backgroundPaint = defaultBG != null ? defaultBG : checkeredBG;
|
backgroundPaint = defaultBG != null ? defaultBG : checkeredBG;
|
||||||
|
|
||||||
JPopupMenu popup = createBackgroundPopup();
|
setupActions(pImage);
|
||||||
|
setComponentPopupMenu(createPopupMenu());
|
||||||
setComponentPopupMenu(popup);
|
|
||||||
addMouseListener(new MouseAdapter() {
|
addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent e) {
|
public void mouseClicked(MouseEvent e) {
|
||||||
@@ -448,24 +451,52 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private JPopupMenu createBackgroundPopup() {
|
private void setupActions(final BufferedImage pImage) {
|
||||||
|
// Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always...
|
||||||
|
bindAction(new ZoomAction("Zoom in", pImage, 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));
|
||||||
|
bindAction(new ZoomAction("Zoom out", pImage, .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
|
||||||
|
bindAction(new ZoomAction("Zoom actual", pImage), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindAction(final AbstractAction action, final String key, final KeyStroke... keyStrokes) {
|
||||||
|
for (KeyStroke keyStroke : keyStrokes) {
|
||||||
|
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActionMap().put(key, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPopupMenu createPopupMenu() {
|
||||||
JPopupMenu popup = new JPopupMenu();
|
JPopupMenu popup = new JPopupMenu();
|
||||||
|
|
||||||
|
popup.add(getActionMap().get(ZOOM_ACTUAL));
|
||||||
|
popup.add(getActionMap().get(ZOOM_IN));
|
||||||
|
popup.add(getActionMap().get(ZOOM_OUT));
|
||||||
|
popup.addSeparator();
|
||||||
|
|
||||||
ButtonGroup group = new ButtonGroup();
|
ButtonGroup group = new ButtonGroup();
|
||||||
|
|
||||||
addCheckBoxItem(new ChangeBackgroundAction("Checkered", checkeredBG), popup, group);
|
JMenu background = new JMenu("Background");
|
||||||
popup.addSeparator();
|
popup.add(background);
|
||||||
addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), popup, group);
|
|
||||||
addCheckBoxItem(new ChangeBackgroundAction("Light", Color.LIGHT_GRAY), popup, group);
|
ChangeBackgroundAction checkered = new ChangeBackgroundAction("Checkered", checkeredBG);
|
||||||
addCheckBoxItem(new ChangeBackgroundAction("Gray", Color.GRAY), popup, group);
|
checkered.putValue(Action.SELECTED_KEY, backgroundPaint == checkeredBG);
|
||||||
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), popup, group);
|
addCheckBoxItem(checkered, background, group);
|
||||||
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), popup, group);
|
background.addSeparator();
|
||||||
popup.addSeparator();
|
addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), background, group);
|
||||||
addCheckBoxItem(new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE), popup, group);
|
addCheckBoxItem(new ChangeBackgroundAction("Light", Color.LIGHT_GRAY), background, group);
|
||||||
|
addCheckBoxItem(new ChangeBackgroundAction("Gray", Color.GRAY), background, group);
|
||||||
|
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), background, group);
|
||||||
|
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), background, group);
|
||||||
|
background.addSeparator();
|
||||||
|
ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE);
|
||||||
|
chooseBackgroundAction.putValue(Action.SELECTED_KEY, backgroundPaint == defaultBG);
|
||||||
|
addCheckBoxItem(chooseBackgroundAction, background, group);
|
||||||
|
|
||||||
return popup;
|
return popup;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addCheckBoxItem(final Action pAction, final JPopupMenu pPopup, final ButtonGroup pGroup) {
|
private void addCheckBoxItem(final Action pAction, final JMenu pPopup, final ButtonGroup pGroup) {
|
||||||
JCheckBoxMenuItem item = new JCheckBoxMenuItem(pAction);
|
JCheckBoxMenuItem item = new JCheckBoxMenuItem(pAction);
|
||||||
pGroup.add(item);
|
pGroup.add(item);
|
||||||
pPopup.add(item);
|
pPopup.add(item);
|
||||||
@@ -553,6 +584,34 @@ public abstract class ImageReaderBase extends ImageReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ZoomAction extends AbstractAction {
|
||||||
|
private final BufferedImage image;
|
||||||
|
private final double zoomFactor;
|
||||||
|
|
||||||
|
public ZoomAction(final String name, final BufferedImage image, final double zoomFactor) {
|
||||||
|
super(name);
|
||||||
|
this.image = image;
|
||||||
|
this.zoomFactor = zoomFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoomAction(final String name, final BufferedImage image) {
|
||||||
|
this(name, image, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
if (zoomFactor <= 0) {
|
||||||
|
setIcon(new BufferedImageIcon(image));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Icon current = getIcon();
|
||||||
|
int w = (int) Math.max(Math.min(current.getIconWidth() * zoomFactor, image.getWidth() * 16), image.getWidth() / 16);
|
||||||
|
int h = (int) Math.max(Math.min(current.getIconHeight() * zoomFactor, image.getHeight() * 16), image.getHeight() / 16);
|
||||||
|
|
||||||
|
setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), w > image.getWidth() || h > image.getHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ExitIfNoWindowPresentHandler extends WindowAdapter {
|
private static class ExitIfNoWindowPresentHandler extends WindowAdapter {
|
||||||
|
|||||||
+31
-3
@@ -73,8 +73,10 @@ public final class ColorSpaces {
|
|||||||
|
|
||||||
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
||||||
|
|
||||||
// JDK 7 seems to handle non-perceptual rendering intents gracefully, so we don't need to fiddle with the profiles
|
// OpenJDK 7 seems to handle non-perceptual rendering intents gracefully, so we don't need to fiddle with the profiles.
|
||||||
private final static boolean JDK_HANDLES_RENDERING_INTENTS = SystemUtil.isClassAvailable("java.lang.invoke.CallSite");
|
// However, the later Oracle distribute JDK seems to include the color management code that has the known bugs...
|
||||||
|
private final static boolean JDK_HANDLES_RENDERING_INTENTS =
|
||||||
|
SystemUtil.isClassAvailable("java.lang.invoke.CallSite") && !SystemUtil.isClassAvailable("sun.java2d.cmm.kcms.CMM");
|
||||||
|
|
||||||
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
||||||
|
|
||||||
@@ -171,6 +173,21 @@ public final class ColorSpaces {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
||||||
|
*
|
||||||
|
* @param profile the ICC profile to test. May not be {@code null}.
|
||||||
|
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
||||||
|
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||||
|
*
|
||||||
|
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
||||||
|
*/
|
||||||
|
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||||
|
Validate.notNull(profile, "profile");
|
||||||
|
|
||||||
|
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(profile.getData(ICC_Profile.icSigHead), sRGB.header);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||||
* <p />
|
* <p />
|
||||||
@@ -227,7 +244,7 @@ public final class ColorSpaces {
|
|||||||
|
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
// Fall back to the bundled ClayRGB1998 public domain Adobe RGB 1998 compatible profile,
|
// Fall back to the bundled ClayRGB1998 public domain Adobe RGB 1998 compatible profile,
|
||||||
// identical for all practical purposes
|
// which is identical for all practical purposes
|
||||||
profile = readProfileFromClasspathResource("/profiles/ClayRGB1998.icc");
|
profile = readProfileFromClasspathResource("/profiles/ClayRGB1998.icc");
|
||||||
|
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
@@ -337,15 +354,19 @@ public final class ColorSpaces {
|
|||||||
private static class sRGB {
|
private static class sRGB {
|
||||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData(ICC_Profile.icSigHead);
|
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData(ICC_Profile.icSigHead);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CIEXYZ {
|
private static class CIEXYZ {
|
||||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ).getData(ICC_Profile.icSigHead);
|
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ).getData(ICC_Profile.icSigHead);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PYCC {
|
private static class PYCC {
|
||||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_PYCC).getData(ICC_Profile.icSigHead);
|
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_PYCC).getData(ICC_Profile.icSigHead);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class GRAY {
|
private static class GRAY {
|
||||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_GRAY).getData(ICC_Profile.icSigHead);
|
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_GRAY).getData(ICC_Profile.icSigHead);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LINEAR_RGB {
|
private static class LINEAR_RGB {
|
||||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB).getData(ICC_Profile.icSigHead);
|
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB).getData(ICC_Profile.icSigHead);
|
||||||
}
|
}
|
||||||
@@ -359,7 +380,14 @@ public final class ColorSpaces {
|
|||||||
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os.id());
|
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os.id());
|
||||||
}
|
}
|
||||||
catch (IOException ignore) {
|
catch (IOException ignore) {
|
||||||
|
System.err.printf(
|
||||||
|
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||||
|
ignore.getMessage()
|
||||||
|
);
|
||||||
|
if (DEBUG) {
|
||||||
ignore.printStackTrace();
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
systemDefaults = null;
|
systemDefaults = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2013, 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.
|
||||||
|
#
|
||||||
|
#GENERIC_CMYK=unknown, use built in for now
|
||||||
|
#ADOBE_RGB_1998=unknown, use built in for now
|
||||||
+1
-1
@@ -26,4 +26,4 @@
|
|||||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
#
|
#
|
||||||
GENERIC_CMYK=/C:/Windows/System32/spool/drivers/color/RSWOP.icm
|
GENERIC_CMYK=/C:/Windows/System32/spool/drivers/color/RSWOP.icm
|
||||||
#ADOBE_RGB_1998=use built in for now
|
#ADOBE_RGB_1998=unknown, use built in for now
|
||||||
+20
-2
@@ -139,7 +139,7 @@ public class ColorSpacesTest {
|
|||||||
assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile()));
|
assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.err.println("Not an ICC_ColorSpace: " + cs);
|
System.err.println("WARNING: Not an ICC_ColorSpace: " + cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +163,25 @@ public class ColorSpacesTest {
|
|||||||
assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile()));
|
assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
System.err.println("Not an ICC_ColorSpace: " + cs);
|
System.err.println("Warning: Not an ICC_ColorSpace: " + cs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsCS_sRGBTrue() {
|
||||||
|
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsCS_sRGBFalse() {
|
||||||
|
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||||
|
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||||
|
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||||
|
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testIsCS_sRGBNull() {
|
||||||
|
ColorSpaces.isCS_sRGB(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -54,12 +54,16 @@ final class SipsJP2Reader {
|
|||||||
|
|
||||||
private static final File SIPS_COMMAND = new File("/usr/bin/sips");
|
private static final File SIPS_COMMAND = new File("/usr/bin/sips");
|
||||||
private static final boolean SIPS_EXISTS_AND_EXECUTES = existsAndExecutes(SIPS_COMMAND);
|
private static final boolean SIPS_EXISTS_AND_EXECUTES = existsAndExecutes(SIPS_COMMAND);
|
||||||
|
private static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.icns.debug"));
|
||||||
|
|
||||||
private static boolean existsAndExecutes(final File cmd) {
|
private static boolean existsAndExecutes(final File cmd) {
|
||||||
try {
|
try {
|
||||||
return cmd.exists() && cmd.canExecute();
|
return cmd.exists() && cmd.canExecute();
|
||||||
}
|
}
|
||||||
catch (SecurityException ignore) {
|
catch (SecurityException ignore) {
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2013, 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.
|
||||||
+4
-4
@@ -29,13 +29,13 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AdobeDCT
|
* AdobeDCTSegment
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: AdobeDCT.java,v 1.0 23.04.12 16:55 haraldk Exp$
|
* @version $Id: AdobeDCTSegment.java,v 1.0 23.04.12 16:55 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
class AdobeDCT {
|
class AdobeDCTSegment {
|
||||||
public static final int Unknown = 0;
|
public static final int Unknown = 0;
|
||||||
public static final int YCC = 1;
|
public static final int YCC = 1;
|
||||||
public static final int YCCK = 2;
|
public static final int YCCK = 2;
|
||||||
@@ -45,7 +45,7 @@ class AdobeDCT {
|
|||||||
final int flags1;
|
final int flags1;
|
||||||
final int transform;
|
final int transform;
|
||||||
|
|
||||||
public AdobeDCT(int version, int flags0, int flags1, int transform) {
|
AdobeDCTSegment(int version, int flags0, int flags1, int transform) {
|
||||||
this.version = version; // 100 or 101
|
this.version = version; // 100 or 101
|
||||||
this.flags0 = flags0;
|
this.flags0 = flags0;
|
||||||
this.flags1 = flags1;
|
this.flags1 = flags1;
|
||||||
+1
-1
@@ -147,7 +147,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Entry bitsPerSample = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
|
Entry bitsPerSample = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
|
||||||
Entry samplesPerPixel = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXELS);
|
Entry samplesPerPixel = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
|
||||||
Entry photometricInterpretation = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
Entry photometricInterpretation = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
|
||||||
|
|
||||||
// Required
|
// Required
|
||||||
|
|||||||
+5
-4
@@ -131,11 +131,12 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp {
|
|||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"PointlessArithmeticExpression"})
|
|
||||||
private void convertCMYKToRGB(byte[] cmyk, byte[] rgb) {
|
private void convertCMYKToRGB(byte[] cmyk, byte[] rgb) {
|
||||||
rgb[0] = (byte) (((255 - cmyk[0] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255);
|
// Adapted from http://www.easyrgb.com/index.php?X=MATH
|
||||||
rgb[1] = (byte) (((255 - cmyk[1] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255);
|
final int k = cmyk[3] & 0xFF;
|
||||||
rgb[2] = (byte) (((255 - cmyk[2] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255);
|
rgb[0] = (byte) (255 - (((cmyk[0] & 0xFF) * (255 - k) / 255) + k));
|
||||||
|
rgb[1] = (byte) (255 - (((cmyk[1] & 0xFF) * (255 - k) / 255) + k));
|
||||||
|
rgb[2] = (byte) (255 - (((cmyk[2] & 0xFF) * (255 - k) / 255) + k));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle2D getBounds2D(Raster src) {
|
public Rectangle2D getBounds2D(Raster src) {
|
||||||
|
|||||||
+135
-102
@@ -45,6 +45,7 @@ import com.twelvemonkeys.lang.Validate;
|
|||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
import javax.imageio.event.IIOReadUpdateListener;
|
import javax.imageio.event.IIOReadUpdateListener;
|
||||||
import javax.imageio.event.IIOReadWarningListener;
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -58,8 +59,26 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
|
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
|
||||||
* with support for CMYK/YCCK JPEGs, non-standard color spaces,broken ICC profiles
|
* that adds support and properly handles cases where the JRE version throws exceptions.
|
||||||
* and more.
|
* <p/>
|
||||||
|
* Main features:
|
||||||
|
* <ul>
|
||||||
|
* <li>Support for CMYK JPEGs (converted to RGB by default or as CMYK, using the embedded ICC profile if applicable)</li>
|
||||||
|
* <li>Support for Adobe YCCK JPEGs (converted to RGB by default or as CMYK, using the embedded ICC profile if applicable)</li>
|
||||||
|
* <li>Support for JPEGs containing ICC profiles with interpretation other than 'Perceptual' (profile is assumed to be 'Perceptual' and used)</li>
|
||||||
|
* <li>Support for JPEGs containing ICC profiles with class other than 'Display' (profile is assumed to have class 'Display' and used)</li>
|
||||||
|
* <li>Support for JPEGs containing ICC profiles that are incompatible with stream data (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Support for JPEGs with corrupted ICC profiles (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Support for JPEGs with corrupted {@code ICC_PROFILE} segments (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Support for JPEGs using non-standard color spaces, unsupported by Java 2D (image data is read, profile is ignored)</li>
|
||||||
|
* <li>Issues warnings instead of throwing exceptions in cases of corrupted data where ever the image data can still be read in a reasonable way</li>
|
||||||
|
* </ul>
|
||||||
|
* Thumbnail support:
|
||||||
|
* <ul>
|
||||||
|
* <li>Support for JFIF thumbnails (even if stream contains "inconsistent metadata")</li>
|
||||||
|
* <li>Support for JFXX thumbnails (JPEG, Indexed and RGB)</li>
|
||||||
|
* <li>Support for EXIF thumbnails (JPEG, RGB and YCbCr)</li>
|
||||||
|
* </ul>
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author LUT-based YCbCR conversion by Werner Randelshofer
|
* @author LUT-based YCbCR conversion by Werner Randelshofer
|
||||||
@@ -76,7 +95,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
|
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
|
||||||
|
|
||||||
private static Map<Integer, List<String>> createSegmentIds() {
|
private static Map<Integer, List<String>> createSegmentIds() {
|
||||||
Map<Integer, List<String>> map = new HashMap<Integer, List<String>>();
|
Map<Integer, List<String>> map = new LinkedHashMap<Integer, List<String>>();
|
||||||
|
|
||||||
// JFIF/JFXX APP0 markers
|
// JFIF/JFXX APP0 markers
|
||||||
map.put(JPEG.APP0, JPEGSegmentUtil.ALL_IDS);
|
map.put(JPEG.APP0, JPEGSegmentUtil.ALL_IDS);
|
||||||
@@ -181,7 +200,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
typeList.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
typeList.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||||
|
|
||||||
// We also read and return CMYK if the source image is CMYK/YCCK + original color profile if present
|
// We also read and return CMYK if the source image is CMYK/YCCK + original color profile if present
|
||||||
ICC_Profile profile = getEmbeddedICCProfile();
|
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||||
|
|
||||||
if (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
if (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
@@ -216,8 +235,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||||
ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
|
||||||
// If delegate can determine the spec, we'll just go with that
|
// If delegate can determine the spec, we'll just go with that
|
||||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||||
|
|
||||||
@@ -231,7 +249,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
switch (csType) {
|
switch (csType) {
|
||||||
case CMYK:
|
case CMYK:
|
||||||
// Create based on embedded profile if exists, or create from "Generic CMYK"
|
// Create based on embedded profile if exists, or create from "Generic CMYK"
|
||||||
ICC_Profile profile = getEmbeddedICCProfile();
|
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||||
|
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
return ImageTypeSpecifier.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false);
|
return ImageTypeSpecifier.createInterleaved(ColorSpaces.createColorSpace(profile), new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false);
|
||||||
@@ -267,16 +285,17 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
// Might want to look into the metadata, to see if there's a better way to identify these.
|
// Might want to look into the metadata, to see if there's a better way to identify these.
|
||||||
boolean unsupported = !delegate.getImageTypes(imageIndex).hasNext();
|
boolean unsupported = !delegate.getImageTypes(imageIndex).hasNext();
|
||||||
|
|
||||||
ICC_Profile profile = getEmbeddedICCProfile();
|
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||||
AdobeDCT adobeDCT = getAdobeDCT();
|
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||||
|
|
||||||
// TODO: Probably something bogus here, as ICC profile isn't applied if reading through the delegate any more...
|
// TODO: Probably something bogus here, as ICC profile isn't applied if reading through the delegate any more...
|
||||||
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
||||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||||
if (delegate.canReadRaster() && (
|
if (delegate.canReadRaster() && (
|
||||||
unsupported ||
|
unsupported ||
|
||||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCT.YCCK ||
|
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
|
||||||
profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
profile != null && !ColorSpaces.isCS_sRGB(profile))) {
|
||||||
|
// profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
System.out.println("Reading using raster and extra conversion");
|
System.out.println("Reading using raster and extra conversion");
|
||||||
System.out.println("ICC color profile: " + profile);
|
System.out.println("ICC color profile: " + profile);
|
||||||
@@ -296,8 +315,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int origWidth = getWidth(imageIndex);
|
int origWidth = getWidth(imageIndex);
|
||||||
int origHeight = getHeight(imageIndex);
|
int origHeight = getHeight(imageIndex);
|
||||||
|
|
||||||
AdobeDCT adobeDCT = getAdobeDCT();
|
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||||
SOF startOfFrame = getSOF();
|
SOFSegment startOfFrame = getSOF();
|
||||||
JPEGColorSpace csType = getSourceCSType(adobeDCT, startOfFrame);
|
JPEGColorSpace csType = getSourceCSType(adobeDCT, startOfFrame);
|
||||||
|
|
||||||
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||||
@@ -316,12 +335,12 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else if (intendedCS != null) {
|
else if (intendedCS != null) {
|
||||||
// Handle inconsistencies
|
// Handle inconsistencies
|
||||||
if (startOfFrame.componentsInFrame != intendedCS.getNumComponents()) {
|
if (startOfFrame.componentsInFrame() != intendedCS.getNumComponents()) {
|
||||||
if (startOfFrame.componentsInFrame < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
|
if (startOfFrame.componentsInFrame() < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
|
||||||
processWarningOccurred(String.format(
|
processWarningOccurred(String.format(
|
||||||
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
|
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
|
||||||
"Ignoring Adobe App14 marker, assuming YCbCr/RGB data.",
|
"Ignoring Adobe App14 marker, assuming YCbCr/RGB data.",
|
||||||
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame
|
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame()
|
||||||
));
|
));
|
||||||
|
|
||||||
csType = JPEGColorSpace.YCbCr;
|
csType = JPEGColorSpace.YCbCr;
|
||||||
@@ -332,12 +351,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
"Embedded ICC color profile is incompatible with image data. " +
|
"Embedded ICC color profile is incompatible with image data. " +
|
||||||
"Profile indicates %d components, but SOF%d has %d color components. " +
|
"Profile indicates %d components, but SOF%d has %d color components. " +
|
||||||
"Ignoring ICC profile, assuming source color space %s.",
|
"Ignoring ICC profile, assuming source color space %s.",
|
||||||
intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame, csType
|
intendedCS.getNumComponents(), startOfFrame.marker & 0xf, startOfFrame.componentsInFrame(), csType
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
// NOTE: Avoid using CCOp if same color space, as it's more compatible that way
|
||||||
else if (intendedCS != image.getColorModel().getColorSpace()) {
|
else if (intendedCS != image.getColorModel().getColorSpace()) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("Converting from " + intendedCS + " to " + (image.getColorModel().getColorSpace().isCS_sRGB() ? "sRGB" : image.getColorModel().getColorSpace()));
|
||||||
|
}
|
||||||
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
||||||
}
|
}
|
||||||
// Else, pass through with no conversion
|
// Else, pass through with no conversion
|
||||||
@@ -346,10 +368,20 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||||
|
|
||||||
if (cmykCS instanceof ICC_ColorSpace) {
|
if (cmykCS instanceof ICC_ColorSpace) {
|
||||||
|
processWarningOccurred(
|
||||||
|
"No embedded ICC color profile, defaulting to \"generic\" CMYK ICC profile. " +
|
||||||
|
"Colors may look incorrect."
|
||||||
|
);
|
||||||
|
|
||||||
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
|
convert = new ColorConvertOp(cmykCS, image.getColorModel().getColorSpace(), null);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
|
// ColorConvertOp using non-ICC CS is deadly slow, fall back to fast conversion instead
|
||||||
|
processWarningOccurred(
|
||||||
|
"No embedded ICC color profile, will convert using inaccurate CMYK to RGB conversion. " +
|
||||||
|
"Colors may look incorrect."
|
||||||
|
);
|
||||||
|
|
||||||
convert = new FastCMYKToRGB();
|
convert = new FastCMYKToRGB();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,7 +468,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
static JPEGColorSpace getSourceCSType(AdobeDCT adobeDCT, final SOF startOfFrame) throws IIOException {
|
static JPEGColorSpace getSourceCSType(AdobeDCTSegment adobeDCT, final SOFSegment startOfFrame) throws IIOException {
|
||||||
/*
|
/*
|
||||||
ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html:
|
ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html:
|
||||||
|
|
||||||
@@ -478,11 +510,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
if (adobeDCT != null) {
|
if (adobeDCT != null) {
|
||||||
switch (adobeDCT.getTransform()) {
|
switch (adobeDCT.getTransform()) {
|
||||||
case AdobeDCT.YCC:
|
case AdobeDCTSegment.YCC:
|
||||||
return JPEGColorSpace.YCbCr;
|
return JPEGColorSpace.YCbCr;
|
||||||
case AdobeDCT.YCCK:
|
case AdobeDCTSegment.YCCK:
|
||||||
return JPEGColorSpace.YCCK;
|
return JPEGColorSpace.YCCK;
|
||||||
case AdobeDCT.Unknown:
|
case AdobeDCTSegment.Unknown:
|
||||||
if (startOfFrame.components.length == 1) {
|
if (startOfFrame.components.length == 1) {
|
||||||
return JPEGColorSpace.Gray;
|
return JPEGColorSpace.Gray;
|
||||||
}
|
}
|
||||||
@@ -601,14 +633,24 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
||||||
}
|
}
|
||||||
catch (IOException ignore) {
|
catch (IIOException ignore) {
|
||||||
|
if (DEBUG) {
|
||||||
|
ignore.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException foo) {
|
catch (IllegalArgumentException foo) {
|
||||||
|
if (DEBUG) {
|
||||||
foo.printStackTrace();
|
foo.printStackTrace();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
finally {
|
finally {
|
||||||
imageInput.reset();
|
imageInput.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In case of an exception, avoid NPE when referencing segments later
|
||||||
|
if (segments == null) {
|
||||||
|
segments = Collections.emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<JPEGSegment> getAppSegments(final int marker, final String identifier) throws IOException {
|
private List<JPEGSegment> getAppSegments(final int marker, final String identifier) throws IOException {
|
||||||
@@ -629,7 +671,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return appSegments;
|
return appSegments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SOF getSOF() throws IOException {
|
private SOFSegment getSOF() throws IOException {
|
||||||
for (JPEGSegment segment : segments) {
|
for (JPEGSegment segment : segments) {
|
||||||
if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 ||
|
if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 ||
|
||||||
JPEG.SOF5 >= segment.marker() && segment.marker() <= JPEG.SOF7 ||
|
JPEG.SOF5 >= segment.marker() && segment.marker() <= JPEG.SOF7 ||
|
||||||
@@ -654,7 +696,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
|
components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SOF(segment.marker(), samplePrecision, lines, samplesPerLine, componentsInFrame, components);
|
return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
data.close();
|
data.close();
|
||||||
@@ -665,7 +707,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AdobeDCT getAdobeDCT() throws IOException {
|
private AdobeDCTSegment getAdobeDCT() throws IOException {
|
||||||
// TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers
|
// TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers
|
||||||
List<JPEGSegment> adobe = getAppSegments(JPEG.APP14, "Adobe");
|
List<JPEGSegment> adobe = getAppSegments(JPEG.APP14, "Adobe");
|
||||||
|
|
||||||
@@ -673,7 +715,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
// version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK)
|
// version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK)
|
||||||
DataInputStream stream = new DataInputStream(adobe.get(0).data());
|
DataInputStream stream = new DataInputStream(adobe.get(0).data());
|
||||||
|
|
||||||
return new AdobeDCT(
|
return new AdobeDCTSegment(
|
||||||
stream.readUnsignedByte(),
|
stream.readUnsignedByte(),
|
||||||
stream.readUnsignedShort(),
|
stream.readUnsignedShort(),
|
||||||
stream.readUnsignedShort(),
|
stream.readUnsignedShort(),
|
||||||
@@ -717,10 +759,14 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ICC_Profile getEmbeddedICCProfile() throws IOException {
|
private ICC_Profile getEmbeddedICCProfile(final boolean allowBadIndexes) throws IOException {
|
||||||
// ICC v 1.42 (2006) annex B:
|
// ICC v 1.42 (2006) annex B:
|
||||||
// APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination)
|
// APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination)
|
||||||
// + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments)
|
// + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments)
|
||||||
|
|
||||||
|
// TODO: Allow metadata to contain the wrongly indexed profiles, if readable
|
||||||
|
// NOTE: We ignore any profile with wrong index for reading and image types, just to be on the safe side
|
||||||
|
|
||||||
List<JPEGSegment> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
|
List<JPEGSegment> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
|
||||||
|
|
||||||
if (segments.size() == 1) {
|
if (segments.size() == 1) {
|
||||||
@@ -731,7 +777,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int chunkCount = stream.readUnsignedByte();
|
int chunkCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
if (chunkNumber != 1 && chunkCount != 1) {
|
if (chunkNumber != 1 && chunkCount != 1) {
|
||||||
processWarningOccurred(String.format("Bad number of 'ICC_PROFILE' chunks: %d of %d. Assuming single chunk.", chunkNumber, chunkCount));
|
processWarningOccurred(String.format("Unexpected number of 'ICC_PROFILE' chunks: %d of %d. Ignoring ICC profile.", chunkNumber, chunkCount));
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return readICCProfileSafe(stream);
|
return readICCProfileSafe(stream);
|
||||||
@@ -742,20 +789,28 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
int chunkNumber = stream.readUnsignedByte();
|
int chunkNumber = stream.readUnsignedByte();
|
||||||
int chunkCount = stream.readUnsignedByte();
|
int chunkCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
|
// TODO: Most of the time the ICC profiles are readable and should be obtainable from metadata...
|
||||||
boolean badICC = false;
|
boolean badICC = false;
|
||||||
if (chunkCount != segments.size()) {
|
if (chunkCount != segments.size()) {
|
||||||
// Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0.
|
// Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0.
|
||||||
// Others use count == 1, and all numbers == 1.
|
// Others use count == 1, and all numbers == 1.
|
||||||
// Handle these by issuing warning
|
// Handle these by issuing warning
|
||||||
|
processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk count: %d. Ignoring ICC profile.", chunkCount));
|
||||||
badICC = true;
|
badICC = true;
|
||||||
processWarningOccurred(String.format("Unexpected 'ICC_PROFILE' chunk count: %d. Ignoring count, assuming %d chunks in sequence.", chunkCount, segments.size()));
|
|
||||||
|
if (!allowBadIndexes) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!badICC && chunkNumber < 1) {
|
if (!badICC && chunkNumber < 1) {
|
||||||
// Anything else is just ignored
|
// Anything else is just ignored
|
||||||
processWarningOccurred(String.format("Invalid 'ICC_PROFILE' chunk index: %d. Ignoring ICC profile.", chunkNumber));
|
processWarningOccurred(String.format("Invalid 'ICC_PROFILE' chunk index: %d. Ignoring ICC profile.", chunkNumber));
|
||||||
|
|
||||||
|
if (!allowBadIndexes) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int count = badICC ? segments.size() : chunkCount;
|
int count = badICC ? segments.size() : chunkCount;
|
||||||
InputStream[] streams = new InputStream[count];
|
InputStream[] streams = new InputStream[count];
|
||||||
@@ -910,6 +965,51 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
return thumbnails.get(thumbnailIndex).read();
|
return thumbnails.get(thumbnailIndex).read();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
|
// TODO: Nice try, but no cigar.. getAsTree does not return a "live" view, so any modifications are thrown away
|
||||||
|
IIOMetadata metadata = delegate.getImageMetadata(imageIndex);
|
||||||
|
|
||||||
|
// IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName());
|
||||||
|
// Node jpegVariety = tree.getElementsByTagName("JPEGvariety").item(0);
|
||||||
|
|
||||||
|
// TODO: Allow EXIF (as app1EXIF) in the JPEGvariety (sic) node.
|
||||||
|
// As EXIF is (a subset of) TIFF, (and the EXIF data is a valid TIFF stream) probably use something like:
|
||||||
|
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
||||||
|
/*
|
||||||
|
from: http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
|
||||||
|
|
||||||
|
In future versions of the JPEG metadata format, other varieties of JPEG metadata may be supported (e.g. Exif)
|
||||||
|
by defining other types of nodes which may appear as a child of the JPEGvariety node.
|
||||||
|
|
||||||
|
(Note that an application wishing to interpret Exif metadata given a metadata tree structure in the
|
||||||
|
javax_imageio_jpeg_image_1.0 format must check for an unknown marker segment with a tag indicating an
|
||||||
|
APP1 marker and containing data identifying it as an Exif marker segment. Then it may use application-specific
|
||||||
|
code to interpret the data in the marker segment. If such an application were to encounter a metadata tree
|
||||||
|
formatted according to a future version of the JPEG metadata format, the Exif marker segment might not be
|
||||||
|
unknown in that format - it might be structured as a child node of the JPEGvariety node.
|
||||||
|
|
||||||
|
Thus, it is important for an application to specify which version to use by passing the string identifying
|
||||||
|
the version to the method/constructor used to obtain an IIOMetadata object.)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
|
||||||
|
// app2ICC.setUserObject(getEmbeddedICCProfile());
|
||||||
|
// jpegVariety.getFirstChild().appendChild(app2ICC);
|
||||||
|
|
||||||
|
// new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false);
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IIOMetadata getStreamMetadata() throws IOException {
|
||||||
|
return delegate.getStreamMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
private static void invertCMYK(final Raster raster) {
|
private static void invertCMYK(final Raster raster) {
|
||||||
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||||
|
|
||||||
@@ -1135,73 +1235,6 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SOF {
|
|
||||||
private final int marker;
|
|
||||||
private final int samplePrecision;
|
|
||||||
private final int lines; // height
|
|
||||||
private final int samplesPerLine; // width
|
|
||||||
private final int componentsInFrame;
|
|
||||||
final SOFComponent[] components;
|
|
||||||
|
|
||||||
public SOF(int marker, int samplePrecision, int lines, int samplesPerLine, int componentsInFrame, SOFComponent[] components) {
|
|
||||||
this.marker = marker;
|
|
||||||
this.samplePrecision = samplePrecision;
|
|
||||||
this.lines = lines;
|
|
||||||
this.samplesPerLine = samplesPerLine;
|
|
||||||
this.componentsInFrame = componentsInFrame;
|
|
||||||
this.components = components;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMarker() {
|
|
||||||
return marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSamplePrecision() {
|
|
||||||
return samplePrecision;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getLines() {
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getSamplesPerLine() {
|
|
||||||
return samplesPerLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getComponentsInFrame() {
|
|
||||||
return componentsInFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format(
|
|
||||||
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
|
|
||||||
marker & 0xf, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SOFComponent {
|
|
||||||
final int id;
|
|
||||||
final int hSub;
|
|
||||||
final int vSub;
|
|
||||||
final int qtSel;
|
|
||||||
|
|
||||||
public SOFComponent(int id, int hSub, int vSub, int qtSel) {
|
|
||||||
this.id = id;
|
|
||||||
this.hSub = hSub;
|
|
||||||
this.vSub = vSub;
|
|
||||||
this.qtSel = qtSel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
// Use id either as component number or component name, based on value
|
|
||||||
Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id;
|
|
||||||
return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void showIt(final BufferedImage pImage, final String pTitle) {
|
protected static void showIt(final BufferedImage pImage, final String pTitle) {
|
||||||
ImageReaderBase.showIt(pImage, pTitle);
|
ImageReaderBase.showIt(pImage, pTitle);
|
||||||
}
|
}
|
||||||
@@ -1272,7 +1305,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
// param.setSourceSubsampling(sub, sub, 0, 0);
|
// param.setSourceSubsampling(sub, sub, 0, 0);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
// long start = System.currentTimeMillis();
|
||||||
BufferedImage image = reader.read(0, param);
|
BufferedImage image = reader.read(0, param);
|
||||||
// System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
// System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
// System.err.println("image: " + image);
|
// System.err.println("image: " + image);
|
||||||
@@ -1280,12 +1313,12 @@ public class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||||
|
|
||||||
// int maxW = 1280;
|
int maxW = 1280;
|
||||||
// int maxH = 800;
|
int maxH = 800;
|
||||||
int maxW = 400;
|
// int maxW = 400;
|
||||||
int maxH = 400;
|
// int maxH = 400;
|
||||||
if (image.getWidth() > maxW || image.getHeight() > maxH) {
|
if (image.getWidth() > maxW || image.getHeight() > maxH) {
|
||||||
start = System.currentTimeMillis();
|
// start = System.currentTimeMillis();
|
||||||
float aspect = reader.getAspectRatio(0);
|
float aspect = reader.getAspectRatio(0);
|
||||||
if (aspect >= 1f) {
|
if (aspect >= 1f) {
|
||||||
image = ImageUtil.createResampled(image, maxW, Math.round(maxW / aspect), Image.SCALE_DEFAULT);
|
image = ImageUtil.createResampled(image, maxW, Math.round(maxW / aspect), Image.SCALE_DEFAULT);
|
||||||
|
|||||||
+11
-4
@@ -49,8 +49,9 @@ import static com.twelvemonkeys.lang.Validate.notNull;
|
|||||||
*/
|
*/
|
||||||
final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||||
// TODO: Rewrite JPEGSegment (from metadata) to store stream pos/length, and be able to replay data, and use instead of Segment?
|
// TODO: Rewrite JPEGSegment (from metadata) to store stream pos/length, and be able to replay data, and use instead of Segment?
|
||||||
// TODO: Change order of segments, to make sure APP0/JFIF is always before APP14/Adobe?
|
// TODO: Change order of segments, to make sure APP0/JFIF is always before APP14/Adobe? What about EXIF?
|
||||||
// TODO: Insert fake APP0/JFIF if needed by the reader?
|
// TODO: Insert fake APP0/JFIF if needed by the reader?
|
||||||
|
// TODO: Sort out ICC_PROFILE issues (duplicate sequence numbers etc)?
|
||||||
|
|
||||||
final private ImageInputStream stream;
|
final private ImageInputStream stream;
|
||||||
|
|
||||||
@@ -90,6 +91,12 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
long realPosition = stream.getStreamPosition();
|
long realPosition = stream.getStreamPosition();
|
||||||
int marker = stream.readUnsignedShort();
|
int marker = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
// Skip over 0xff padding between markers
|
||||||
|
while (marker == 0xffff) {
|
||||||
|
realPosition++;
|
||||||
|
marker = 0xff00 | stream.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Refactor to make various segments optional, we probably only want the "Adobe" APP14 segment, 'Exif' APP1 and very few others
|
// TODO: Refactor to make various segments optional, we probably only want the "Adobe" APP14 segment, 'Exif' APP1 and very few others
|
||||||
if (isAppSegmentMarker(marker) && marker != JPEG.APP0 && !(marker == JPEG.APP1 && isAppSegmentWithId("Exif", stream)) && marker != JPEG.APP14) {
|
if (isAppSegmentMarker(marker) && marker != JPEG.APP0 && !(marker == JPEG.APP1 && isAppSegmentWithId("Exif", stream)) && marker != JPEG.APP14) {
|
||||||
int length = stream.readUnsignedShort(); // Length including length field itself
|
int length = stream.readUnsignedShort(); // Length including length field itself
|
||||||
@@ -149,7 +156,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAppSegmentWithId(String segmentId, ImageInputStream stream) throws IOException {
|
private static boolean isAppSegmentWithId(final String segmentId, final ImageInputStream stream) throws IOException {
|
||||||
notNull(segmentId, "segmentId");
|
notNull(segmentId, "segmentId");
|
||||||
|
|
||||||
stream.mark();
|
stream.mark();
|
||||||
@@ -222,7 +229,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(byte[] b, int off, int len) throws IOException {
|
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||||
bitOffset = 0;
|
bitOffset = 0;
|
||||||
|
|
||||||
// NOTE: There is a bug in the JPEGMetadata constructor (JPEGBuffer.loadBuf() method) that expects read to
|
// NOTE: There is a bug in the JPEGMetadata constructor (JPEGBuffer.loadBuf() method) that expects read to
|
||||||
@@ -264,7 +271,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
final long start;
|
final long start;
|
||||||
final long length;
|
final long length;
|
||||||
|
|
||||||
Segment(int marker, long realStart, long start, long length) {
|
Segment(final int marker, final long realStart, final long start, final long length) {
|
||||||
this.marker = marker;
|
this.marker = marker;
|
||||||
this.realStart = realStart;
|
this.realStart = realStart;
|
||||||
this.start = start;
|
this.start = start;
|
||||||
|
|||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.jpeg;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SOFComponent
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: SOFComponent.java,v 1.0 22.04.13 16:40 haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class SOFComponent {
|
||||||
|
final int id;
|
||||||
|
final int hSub;
|
||||||
|
final int vSub;
|
||||||
|
final int qtSel;
|
||||||
|
|
||||||
|
SOFComponent(int id, int hSub, int vSub, int qtSel) {
|
||||||
|
this.id = id;
|
||||||
|
this.hSub = hSub;
|
||||||
|
this.vSub = vSub;
|
||||||
|
this.qtSel = qtSel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
// Use id either as component number or component name, based on value
|
||||||
|
Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id;
|
||||||
|
return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel);
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.jpeg;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SOFSegment
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class SOFSegment {
|
||||||
|
final int marker;
|
||||||
|
final int samplePrecision;
|
||||||
|
final int lines; // height
|
||||||
|
final int samplesPerLine; // width
|
||||||
|
final SOFComponent[] components;
|
||||||
|
|
||||||
|
SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, SOFComponent[] components) {
|
||||||
|
this.marker = marker;
|
||||||
|
this.samplePrecision = samplePrecision;
|
||||||
|
this.lines = lines;
|
||||||
|
this.samplesPerLine = samplesPerLine;
|
||||||
|
this.components = components;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int componentsInFrame() {
|
||||||
|
return components.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format(
|
||||||
|
"SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]",
|
||||||
|
marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-2
@@ -31,10 +31,12 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
|
|||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReadParam;
|
import javax.imageio.ImageReadParam;
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.event.IIOReadWarningListener;
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.spi.IIORegistry;
|
import javax.imageio.spi.IIORegistry;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -75,7 +77,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
new TestData(getClassLoaderResource("/jpeg/gray-sample.jpg"), new Dimension(386, 396)),
|
new TestData(getClassLoaderResource("/jpeg/gray-sample.jpg"), new Dimension(386, 396)),
|
||||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(160, 227)),
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(160, 227)),
|
||||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(2707, 3804)),
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(2707, 3804)),
|
||||||
new TestData(getClassLoaderResource("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg"), new Dimension(640, 480))
|
new TestData(getClassLoaderResource("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg"), new Dimension(640, 480)),
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg"), new Dimension(20, 45))
|
||||||
);
|
);
|
||||||
|
|
||||||
// More test data in specific tests below
|
// More test data in specific tests below
|
||||||
@@ -549,7 +552,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-no-icc.jpg"), new Dimension(100, 100))
|
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-no-icc.jpg"), new Dimension(100, 100))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
for (TestData data : cmykData) {
|
for (TestData data : cmykData) {
|
||||||
reader.setInput(data.getInputStream());
|
reader.setInput(data.getInputStream());
|
||||||
|
|
||||||
@@ -599,4 +601,24 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Test RGBA/YCbCrA handling
|
// TODO: Test RGBA/YCbCrA handling
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadMetadataMaybeNull() throws IOException {
|
||||||
|
// Just test that we can read the metadata without exceptions
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
for (TestData testData : getTestData()) {
|
||||||
|
reader.setInput(testData.getInputStream());
|
||||||
|
|
||||||
|
for (int i = 0; i < reader.getNumImages(true); i++) {
|
||||||
|
try {
|
||||||
|
IIOMetadata metadata = reader.getImageMetadata(i);
|
||||||
|
assertNotNull(String.format("Image metadata null for %s image %s", testData, i), metadata);
|
||||||
|
}
|
||||||
|
catch (IIOException e) {
|
||||||
|
System.err.println(String.format("WARNING: Reading metadata failed for %s image %s: %s", testData, i, e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
@@ -135,4 +135,27 @@ public class JPEGSegmentImageInputStreamTest {
|
|||||||
|
|
||||||
assertEquals(9299l, length); // Sanity check: same as file size
|
assertEquals(9299l, length); // Sanity check: same as file size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadPaddedSegmentsBug() throws IOException {
|
||||||
|
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg")));
|
||||||
|
|
||||||
|
List<JPEGSegment> appSegments = JPEGSegmentUtil.readSegments(stream, JPEGSegmentUtil.APP_SEGMENTS);
|
||||||
|
assertEquals(2, appSegments.size());
|
||||||
|
|
||||||
|
assertEquals(JPEG.APP0, appSegments.get(0).marker());
|
||||||
|
assertEquals("JFIF", appSegments.get(0).identifier());
|
||||||
|
|
||||||
|
assertEquals(JPEG.APP1, appSegments.get(1).marker());
|
||||||
|
assertEquals("Exif", appSegments.get(1).identifier());
|
||||||
|
|
||||||
|
stream.seek(0l);
|
||||||
|
|
||||||
|
long length = 0;
|
||||||
|
while (stream.read() != -1) {
|
||||||
|
length++;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(1079L, length); // Sanity check: same as file size, except padding and the filtered ICC_PROFILE segment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2012, 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.
|
||||||
+1
@@ -35,6 +35,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$
|
* @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
public interface EXIF {
|
public interface EXIF {
|
||||||
// See http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
|
// See http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
|
||||||
int TAG_EXPOSURE_TIME = 33434;
|
int TAG_EXPOSURE_TIME = 33434;
|
||||||
|
|||||||
+23
-1
@@ -88,7 +88,7 @@ final class EXIFEntry extends AbstractEntry {
|
|||||||
return "StripOffsets";
|
return "StripOffsets";
|
||||||
case TIFF.TAG_ORIENTATION:
|
case TIFF.TAG_ORIENTATION:
|
||||||
return "Orientation";
|
return "Orientation";
|
||||||
case TIFF.TAG_SAMPLES_PER_PIXELS:
|
case TIFF.TAG_SAMPLES_PER_PIXEL:
|
||||||
return "SamplesPerPixels";
|
return "SamplesPerPixels";
|
||||||
case TIFF.TAG_ROWS_PER_STRIP:
|
case TIFF.TAG_ROWS_PER_STRIP:
|
||||||
return "RowsPerStrip";
|
return "RowsPerStrip";
|
||||||
@@ -209,6 +209,28 @@ final class EXIFEntry extends AbstractEntry {
|
|||||||
return "PixelYDimension";
|
return "PixelYDimension";
|
||||||
|
|
||||||
// TODO: More field names
|
// TODO: More field names
|
||||||
|
/*
|
||||||
|
default:
|
||||||
|
Class[] classes = new Class[] {TIFF.class, EXIF.class};
|
||||||
|
|
||||||
|
for (Class cl : classes) {
|
||||||
|
Field[] fields = cl.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;
|
return null;
|
||||||
|
|||||||
+13
-1
@@ -35,6 +35,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
public interface TIFF {
|
public interface TIFF {
|
||||||
int TIFF_MAGIC = 42;
|
int TIFF_MAGIC = 42;
|
||||||
|
|
||||||
@@ -98,8 +99,9 @@ public interface TIFF {
|
|||||||
int TAG_BITS_PER_SAMPLE = 258;
|
int TAG_BITS_PER_SAMPLE = 258;
|
||||||
int TAG_COMPRESSION = 259;
|
int TAG_COMPRESSION = 259;
|
||||||
int TAG_PHOTOMETRIC_INTERPRETATION = 262;
|
int TAG_PHOTOMETRIC_INTERPRETATION = 262;
|
||||||
|
int TAG_FILL_ORDER = 266;
|
||||||
int TAG_ORIENTATION = 274;
|
int TAG_ORIENTATION = 274;
|
||||||
int TAG_SAMPLES_PER_PIXELS = 277;
|
int TAG_SAMPLES_PER_PIXEL = 277;
|
||||||
int TAG_PLANAR_CONFIGURATION = 284;
|
int TAG_PLANAR_CONFIGURATION = 284;
|
||||||
int TAG_SAMPLE_FORMAT = 339;
|
int TAG_SAMPLE_FORMAT = 339;
|
||||||
int TAG_YCBCR_SUB_SAMPLING = 530;
|
int TAG_YCBCR_SUB_SAMPLING = 530;
|
||||||
@@ -113,6 +115,7 @@ public interface TIFF {
|
|||||||
int TAG_STRIP_OFFSETS = 273;
|
int TAG_STRIP_OFFSETS = 273;
|
||||||
int TAG_ROWS_PER_STRIP = 278;
|
int TAG_ROWS_PER_STRIP = 278;
|
||||||
int TAG_STRIP_BYTE_COUNTS = 279;
|
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||||
|
// "Old-style" JPEG (still used as EXIF thumbnail)
|
||||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||||
|
|
||||||
@@ -124,6 +127,7 @@ public interface TIFF {
|
|||||||
int TAG_PRIMARY_CHROMATICITIES = 319;
|
int TAG_PRIMARY_CHROMATICITIES = 319;
|
||||||
int TAG_COLOR_MAP = 320;
|
int TAG_COLOR_MAP = 320;
|
||||||
int TAG_EXTRA_SAMPLES = 338;
|
int TAG_EXTRA_SAMPLES = 338;
|
||||||
|
int TAG_TRANSFER_RANGE = 342;
|
||||||
int TAG_YCBCR_COEFFICIENTS = 529;
|
int TAG_YCBCR_COEFFICIENTS = 529;
|
||||||
int TAG_REFERENCE_BLACK_WHITE = 532;
|
int TAG_REFERENCE_BLACK_WHITE = 532;
|
||||||
|
|
||||||
@@ -133,6 +137,7 @@ public interface TIFF {
|
|||||||
int TAG_IMAGE_DESCRIPTION = 270;
|
int TAG_IMAGE_DESCRIPTION = 270;
|
||||||
int TAG_MAKE = 271;
|
int TAG_MAKE = 271;
|
||||||
int TAG_MODEL = 272;
|
int TAG_MODEL = 272;
|
||||||
|
int TAG_PAGE_NUMBER = 297;
|
||||||
int TAG_SOFTWARE = 305;
|
int TAG_SOFTWARE = 305;
|
||||||
int TAG_ARTIST = 315;
|
int TAG_ARTIST = 315;
|
||||||
int TAG_HOST_COMPUTER = 316;
|
int TAG_HOST_COMPUTER = 316;
|
||||||
@@ -161,5 +166,12 @@ public interface TIFF {
|
|||||||
int TAG_TILE_OFFSETS = 324;
|
int TAG_TILE_OFFSETS = 324;
|
||||||
int TAG_TILE_BYTE_COUNTS = 325;
|
int TAG_TILE_BYTE_COUNTS = 325;
|
||||||
|
|
||||||
|
// JPEG
|
||||||
int TAG_JPEG_TABLES = 347;
|
int TAG_JPEG_TABLES = 347;
|
||||||
|
|
||||||
|
// "Old-style" JPEG (Obsolete) DO NOT WRITE!
|
||||||
|
int TAG_OLD_JPEG_PROC = 512;
|
||||||
|
int TAG_OLD_JPEG_Q_TABLES = 519;
|
||||||
|
int TAG_OLD_JPEG_DC_TABLES = 520;
|
||||||
|
int TAG_OLD_JPEG_AC_TABLES = 521;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-3
@@ -40,7 +40,8 @@ public interface JPEG {
|
|||||||
int SOI = 0xFFD8;
|
int SOI = 0xFFD8;
|
||||||
/** End of Image segment marker (EOI). */
|
/** End of Image segment marker (EOI). */
|
||||||
int EOI = 0xFFD9;
|
int EOI = 0xFFD9;
|
||||||
/** Start of Stream segment marker (SOS). */
|
|
||||||
|
/** Start of Scan segment marker (SOS). */
|
||||||
int SOS = 0xFFDA;
|
int SOS = 0xFFDA;
|
||||||
|
|
||||||
/** Define Quantization Tables segment marker (DQT). */
|
/** Define Quantization Tables segment marker (DQT). */
|
||||||
@@ -81,6 +82,10 @@ public interface JPEG {
|
|||||||
int SOF14 = 0xFFCE;
|
int SOF14 = 0xFFCE;
|
||||||
int SOF15 = 0xFFCF;
|
int SOF15 = 0xFFCF;
|
||||||
|
|
||||||
|
// JPEG-LS markers
|
||||||
|
int SOF55 = 0xFFF7; // NOTE: Equal to a normal SOF segment
|
||||||
|
int LSE = 0xFFF8; // JPEG-LS Preset Parameter marker
|
||||||
|
|
||||||
// TODO: Known/Important APPn marker identifiers
|
// TODO: Known/Important APPn marker identifiers
|
||||||
// "JFIF" APP0
|
// "JFIF" APP0
|
||||||
// "JFXX" APP0
|
// "JFXX" APP0
|
||||||
@@ -89,6 +94,6 @@ public interface JPEG {
|
|||||||
// "Adobe" APP14
|
// "Adobe" APP14
|
||||||
|
|
||||||
// Possibly
|
// Possibly
|
||||||
// "http://ns.adobe.com/xap/1.0/" (XMP)
|
// "http://ns.adobe.com/xap/1.0/" (XMP) APP1
|
||||||
// "Photoshop 3.0" (Contains IPTC)
|
// "Photoshop 3.0" (may contain IPTC) APP13
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-4
@@ -90,10 +90,7 @@ public final class JPEGQuality {
|
|||||||
private static int getJPEGQuality(final int[][] quantizationTables) throws IOException {
|
private static int getJPEGQuality(final int[][] quantizationTables) throws IOException {
|
||||||
// System.err.println("tables: " + Arrays.deepToString(tables));
|
// System.err.println("tables: " + Arrays.deepToString(tables));
|
||||||
|
|
||||||
// TODO: Determine lossless JPEG
|
// TODO: Determine lossless JPEG, it's an entirely different algorithm
|
||||||
// if (lossless) {
|
|
||||||
// return 100; // TODO: Sums can be 100... Is lossless not 100?
|
|
||||||
// }
|
|
||||||
|
|
||||||
int qvalue;
|
int qvalue;
|
||||||
|
|
||||||
|
|||||||
+18
-6
@@ -95,7 +95,8 @@ public final class JPEGSegmentUtil {
|
|||||||
|
|
||||||
JPEGSegment segment;
|
JPEGSegment segment;
|
||||||
try {
|
try {
|
||||||
while (!isImageDone(segment = readSegment(stream, segmentIdentifiers))) {
|
do {
|
||||||
|
segment = readSegment(stream, segmentIdentifiers);
|
||||||
// System.err.println("segment: " + segment);
|
// System.err.println("segment: " + segment);
|
||||||
|
|
||||||
if (isRequested(segment, segmentIdentifiers)) {
|
if (isRequested(segment, segmentIdentifiers)) {
|
||||||
@@ -106,6 +107,7 @@ public final class JPEGSegmentUtil {
|
|||||||
segments.add(segment);
|
segments.add(segment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
while (!isImageDone(segment));
|
||||||
}
|
}
|
||||||
catch (EOFException ignore) {
|
catch (EOFException ignore) {
|
||||||
// Just end here, in case of malformed stream
|
// Just end here, in case of malformed stream
|
||||||
@@ -151,8 +153,18 @@ public final class JPEGSegmentUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static JPEGSegment readSegment(final ImageInputStream stream, Map<Integer, List<String>> segmentIdentifiers) throws IOException {
|
static JPEGSegment readSegment(final ImageInputStream stream, final Map<Integer, List<String>> segmentIdentifiers) throws IOException {
|
||||||
int marker = stream.readUnsignedShort();
|
int marker = stream.readUnsignedShort();
|
||||||
|
|
||||||
|
// Skip over 0xff padding between markers
|
||||||
|
while (marker == 0xffff) {
|
||||||
|
marker = 0xff00 | stream.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((marker >> 8 & 0xff) != 0xff) {
|
||||||
|
throw new IIOException(String.format("Bad marker: %04x", marker));
|
||||||
|
}
|
||||||
|
|
||||||
int length = stream.readUnsignedShort(); // Length including length field itself
|
int length = stream.readUnsignedShort(); // Length including length field itself
|
||||||
|
|
||||||
byte[] data;
|
byte[] data;
|
||||||
@@ -191,7 +203,7 @@ public final class JPEGSegmentUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(Object o) {
|
public boolean contains(final Object o) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,13 +215,13 @@ public final class JPEGSegmentUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> get(Object key) {
|
public List<String> get(final Object key) {
|
||||||
return key instanceof Integer && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null;
|
return key instanceof Integer && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean containsKey(Object key) {
|
public boolean containsKey(final Object key) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +233,7 @@ public final class JPEGSegmentUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> get(Object key) {
|
public List<String> get(final Object key) {
|
||||||
return containsKey(key) ? ALL_IDS : null;
|
return containsKey(key) ? ALL_IDS : null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-2
@@ -151,19 +151,21 @@ public class JPEGSegmentUtilTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testReadAll() throws IOException {
|
public void testReadAll() throws IOException {
|
||||||
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(getData("/jpeg/9788245605525.jpg"), JPEGSegmentUtil.ALL_SEGMENTS);
|
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(getData("/jpeg/9788245605525.jpg"), JPEGSegmentUtil.ALL_SEGMENTS);
|
||||||
assertEquals(6, segments.size());
|
assertEquals(7, segments.size());
|
||||||
|
|
||||||
assertEquals(segments.toString(), JPEG.SOF0, segments.get(3).marker());
|
assertEquals(segments.toString(), JPEG.SOF0, segments.get(3).marker());
|
||||||
assertEquals(segments.toString(), null, segments.get(3).identifier());
|
assertEquals(segments.toString(), null, segments.get(3).identifier());
|
||||||
|
assertEquals(segments.toString(), JPEG.SOS, segments.get(segments.size() - 1).marker());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadAllAlt() throws IOException {
|
public void testReadAllAlt() throws IOException {
|
||||||
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEGSegmentUtil.ALL_SEGMENTS);
|
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(getData("/jpeg/ts_open_300dpi.jpg"), JPEGSegmentUtil.ALL_SEGMENTS);
|
||||||
assertEquals(26, segments.size());
|
assertEquals(27, segments.size());
|
||||||
|
|
||||||
assertEquals(segments.toString(), JPEG.SOF0, segments.get(23).marker());
|
assertEquals(segments.toString(), JPEG.SOF0, segments.get(23).marker());
|
||||||
assertEquals(segments.toString(), null, segments.get(23).identifier());
|
assertEquals(segments.toString(), null, segments.get(23).identifier());
|
||||||
|
assertEquals(segments.toString(), JPEG.SOS, segments.get(segments.size() - 1).marker());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -194,4 +196,17 @@ public class JPEGSegmentUtilTest {
|
|||||||
assertEquals(JPEG.APP14, segments.get(21).marker());
|
assertEquals(JPEG.APP14, segments.get(21).marker());
|
||||||
assertEquals("Adobe", segments.get(21).identifier());
|
assertEquals("Adobe", segments.get(21).identifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadPaddedSegments() throws IOException {
|
||||||
|
List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(getData("/jpeg/jfif-padded-segments.jpg"), JPEGSegmentUtil.APP_SEGMENTS);
|
||||||
|
assertEquals(3, segments.size());
|
||||||
|
|
||||||
|
assertEquals(JPEG.APP0, segments.get(0).marker());
|
||||||
|
assertEquals("JFIF", segments.get(0).identifier());
|
||||||
|
assertEquals(JPEG.APP2, segments.get(1).marker());
|
||||||
|
assertEquals("ICC_PROFILE", segments.get(1).identifier());
|
||||||
|
assertEquals(JPEG.APP1, segments.get(2).marker());
|
||||||
|
assertEquals("Exif", segments.get(2).identifier());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2013, 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.
|
||||||
+452
@@ -0,0 +1,452 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2012, 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.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 last modified by $Author: haraldk$
|
||||||
|
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class CCITTFaxDecoderStream extends FilterInputStream {
|
||||||
|
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
|
||||||
|
|
||||||
|
private final int columns;
|
||||||
|
private final byte[] decodedRow;
|
||||||
|
|
||||||
|
private int decodedLength;
|
||||||
|
private int decodedPos;
|
||||||
|
|
||||||
|
private int bitBuffer;
|
||||||
|
private int bitBufferLength;
|
||||||
|
|
||||||
|
// Need to take fill order into account (?) (use flip table?)
|
||||||
|
private final int fillOrder;
|
||||||
|
private final int type;
|
||||||
|
|
||||||
|
private final int[] changes;
|
||||||
|
private int changesCount;
|
||||||
|
|
||||||
|
private static final int EOL_CODE = 0x01; // 12 bit
|
||||||
|
|
||||||
|
public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) {
|
||||||
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
|
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||||
|
// We know this is only used for b/w (1 bit)
|
||||||
|
this.decodedRow = new byte[(columns + 7) / 8];
|
||||||
|
this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, type, "Only CCITT Modified Huffman RLE compression (2) supported: %s"); // TODO: Implement group 3 and 4
|
||||||
|
this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2
|
||||||
|
|
||||||
|
this.changes = new int[columns];
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDEA: Would it be faster to keep all bit combos of each length (>=2) that is NOT a code, to find bit length, then look up value in table?
|
||||||
|
// -- If white run, start at 4 bits to determine length, if black, start at 2 bits
|
||||||
|
|
||||||
|
private void fetch() throws IOException {
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
decodedLength = 0;
|
||||||
|
try {
|
||||||
|
decodeRow();
|
||||||
|
}
|
||||||
|
catch (EOFException e) {
|
||||||
|
// TODO: Rewrite to avoid throw/catch for normal flow...
|
||||||
|
if (decodedLength != 0) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ..otherwise, just client code trying to read past the end of stream
|
||||||
|
decodedLength = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decodeRow() throws IOException {
|
||||||
|
resetBuffer();
|
||||||
|
|
||||||
|
boolean literalRun = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (type == TIFFExtension.COMPRESSION_CCITT_T4) {
|
||||||
|
int eol = readBits(12);
|
||||||
|
System.err.println("eol: " + eol);
|
||||||
|
while (eol != EOL_CODE) {
|
||||||
|
eol = readBits(1);
|
||||||
|
System.err.println("eol: " + eol);
|
||||||
|
// throw new IOException("Missing EOL");
|
||||||
|
}
|
||||||
|
|
||||||
|
literalRun = readBits(1) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.err.println("literalRun: " + literalRun);
|
||||||
|
*/
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
if (literalRun) {
|
||||||
|
changesCount = 0;
|
||||||
|
boolean white = true;
|
||||||
|
|
||||||
|
do {
|
||||||
|
int completeRun = 0;
|
||||||
|
|
||||||
|
int run;
|
||||||
|
do {
|
||||||
|
if (white) {
|
||||||
|
run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
completeRun += run;
|
||||||
|
}
|
||||||
|
while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes
|
||||||
|
|
||||||
|
changes[changesCount++] = index + completeRun;
|
||||||
|
|
||||||
|
// System.err.printf("%s run: %d\n", white ? "white" : "black", run);
|
||||||
|
|
||||||
|
// TODO: Optimize with lookup for 0-7 bits?
|
||||||
|
// Fill bits to byte boundary...
|
||||||
|
while (index % 8 != 0 && completeRun-- > 0) {
|
||||||
|
decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...then fill complete bytes to either 0xff or 0x00...
|
||||||
|
if (index % 8 == 0) {
|
||||||
|
final byte value = (byte) (white ? 0xff : 0x00);
|
||||||
|
|
||||||
|
while (completeRun > 7) {
|
||||||
|
decodedRow[index / 8] = value;
|
||||||
|
completeRun -= 8;
|
||||||
|
index += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...finally fill any remaining bits
|
||||||
|
while (completeRun-- > 0) {
|
||||||
|
decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flip color for next run
|
||||||
|
white = !white;
|
||||||
|
}
|
||||||
|
while (index < columns);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// non-literal run
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && index != columns) {
|
||||||
|
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedLength = (index / 8) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int decodeRun(short[][] codes, short[][] runLengths, int minCodeSize) throws IOException {
|
||||||
|
// TODO: Optimize...
|
||||||
|
// Looping and comparing is the most straight-forward, but probably not the most effective way...
|
||||||
|
int code = readBits(minCodeSize);
|
||||||
|
|
||||||
|
for (int bits = 0; bits < codes.length; bits++) {
|
||||||
|
short[] bitCodes = codes[bits];
|
||||||
|
|
||||||
|
for (int i = 0; i < bitCodes.length; i++) {
|
||||||
|
if (bitCodes[i] == code) {
|
||||||
|
// System.err.println("code: " + code);
|
||||||
|
|
||||||
|
// Code found, return matching run length
|
||||||
|
return runLengths[bits][i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No code found, read one more bit and try again
|
||||||
|
code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("Unknown code in Huffman RLE stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetBuffer() {
|
||||||
|
for (int i = 0; i < decodedRow.length; i++) {
|
||||||
|
decodedRow[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitBuffer = 0;
|
||||||
|
bitBufferLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readBits(int bitCount) throws IOException {
|
||||||
|
while (bitBufferLength < bitCount) {
|
||||||
|
int read = in.read();
|
||||||
|
if (read == -1) {
|
||||||
|
throw new EOFException("Unexpected end of Huffman RLE stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
int bits = read & 0xff;
|
||||||
|
bitBuffer = (bitBuffer << 8) | bits;
|
||||||
|
bitBufferLength += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Take fill order into account
|
||||||
|
bitBufferLength -= bitCount;
|
||||||
|
int result = bitBuffer >> bitBufferLength;
|
||||||
|
bitBuffer &= (1 << bitBufferLength) - 1;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedRow[decodedPos++] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int read = Math.min(decodedLength - decodedPos, len);
|
||||||
|
System.arraycopy(decodedRow, decodedPos, b, off, read);
|
||||||
|
decodedPos += read;
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int skipped = (int) Math.min(decodedLength - decodedPos, n);
|
||||||
|
decodedPos += skipped;
|
||||||
|
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
throw new IOException("mark/reset not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
static final short[][] BLACK_CODES = {
|
||||||
|
{ // 2 bits
|
||||||
|
0x2, 0x3,
|
||||||
|
},
|
||||||
|
{ // 3 bits
|
||||||
|
0x2, 0x3,
|
||||||
|
},
|
||||||
|
{ // 4 bits
|
||||||
|
0x2, 0x3,
|
||||||
|
},
|
||||||
|
{ // 5 bits
|
||||||
|
0x3,
|
||||||
|
},
|
||||||
|
{ // 6 bits
|
||||||
|
0x4, 0x5,
|
||||||
|
},
|
||||||
|
{ // 7 bits
|
||||||
|
0x4, 0x5, 0x7,
|
||||||
|
},
|
||||||
|
{ // 8 bits
|
||||||
|
0x4, 0x7,
|
||||||
|
},
|
||||||
|
{ // 9 bits
|
||||||
|
0x18,
|
||||||
|
},
|
||||||
|
{ // 10 bits
|
||||||
|
0x17, 0x18, 0x37, 0x8, 0xf,
|
||||||
|
},
|
||||||
|
{ // 11 bits
|
||||||
|
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
|
||||||
|
},
|
||||||
|
{ // 12 bits
|
||||||
|
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,
|
||||||
|
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
|
||||||
|
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
|
||||||
|
},
|
||||||
|
{ // 13 bits
|
||||||
|
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
|
||||||
|
0x74, 0x75, 0x76, 0x77,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static final short[][] BLACK_RUN_LENGTHS = {
|
||||||
|
{ // 2 bits
|
||||||
|
3, 2,
|
||||||
|
},
|
||||||
|
{ // 3 bits
|
||||||
|
1, 4,
|
||||||
|
},
|
||||||
|
{ // 4 bits
|
||||||
|
6, 5,
|
||||||
|
},
|
||||||
|
{ // 5 bits
|
||||||
|
7,
|
||||||
|
},
|
||||||
|
{ // 6 bits
|
||||||
|
9, 8,
|
||||||
|
},
|
||||||
|
{ // 7 bits
|
||||||
|
10, 11, 12,
|
||||||
|
},
|
||||||
|
{ // 8 bits
|
||||||
|
13, 14,
|
||||||
|
},
|
||||||
|
{ // 9 bits
|
||||||
|
15,
|
||||||
|
},
|
||||||
|
{ // 10 bits
|
||||||
|
16, 17, 0, 18, 64,
|
||||||
|
},
|
||||||
|
{ // 11 bits
|
||||||
|
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
|
||||||
|
},
|
||||||
|
{ // 12 bits
|
||||||
|
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, 27, 28, 29, 34, 35,
|
||||||
|
36, 37, 38, 39, 42, 43,
|
||||||
|
},
|
||||||
|
{ // 13 bits
|
||||||
|
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960,
|
||||||
|
1024, 1088, 1152, 1216,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final short[][] WHITE_CODES = {
|
||||||
|
{ // 4 bits
|
||||||
|
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
|
||||||
|
},
|
||||||
|
{ // 5 bits
|
||||||
|
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
|
||||||
|
},
|
||||||
|
{ // 6 bits
|
||||||
|
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
|
||||||
|
},
|
||||||
|
{ // 7 bits
|
||||||
|
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
|
||||||
|
},
|
||||||
|
{ // 8 bits
|
||||||
|
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, 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
|
||||||
|
},
|
||||||
|
{ // 9 bits
|
||||||
|
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
|
||||||
|
},
|
||||||
|
{ // 10 bits
|
||||||
|
},
|
||||||
|
{ // 11 bits
|
||||||
|
0x8, 0xc, 0xd,
|
||||||
|
},
|
||||||
|
{ // 12 bits
|
||||||
|
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static final short[][] WHITE_RUN_LENGTHS = {
|
||||||
|
{ // 4 bits
|
||||||
|
2, 3, 4, 5, 6, 7,
|
||||||
|
},
|
||||||
|
{ // 5 bits
|
||||||
|
128, 8, 9, 64, 10, 11,
|
||||||
|
},
|
||||||
|
{ // 6 bits
|
||||||
|
192, 1664, 16, 17, 13, 14, 15, 1, 12,
|
||||||
|
},
|
||||||
|
{ // 7 bits
|
||||||
|
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
|
||||||
|
},
|
||||||
|
{ // 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, 59, 60, 46, 49, 50, 51,
|
||||||
|
52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
|
||||||
|
},
|
||||||
|
{ // 9 bits
|
||||||
|
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
|
||||||
|
},
|
||||||
|
{ // 10 bits
|
||||||
|
},
|
||||||
|
{ // 11 bits
|
||||||
|
1792, 1856, 1920,
|
||||||
|
},
|
||||||
|
{ // 12 bits
|
||||||
|
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
+340
@@ -0,0 +1,340 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A decoder for data converted using "horizontal differencing predictor".
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class HorizontalDeDifferencingStream extends FilterInputStream {
|
||||||
|
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
|
||||||
|
|
||||||
|
private final int columns;
|
||||||
|
// NOTE: PlanarConfiguration == 2 may be treated as samplesPerPixel == 1
|
||||||
|
private final int samplesPerPixel;
|
||||||
|
private final int bitsPerSample;
|
||||||
|
private final ByteOrder byteOrder;
|
||||||
|
|
||||||
|
int decodedLength;
|
||||||
|
int decodedPos;
|
||||||
|
|
||||||
|
private final byte[] buffer;
|
||||||
|
|
||||||
|
public HorizontalDeDifferencingStream(final InputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) {
|
||||||
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
|
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
|
||||||
|
this.samplesPerPixel = Validate.isTrue(bitsPerSample >= 8 || samplesPerPixel == 1, samplesPerPixel, "Unsupported samples per pixel for < 8 bit samples: %s");
|
||||||
|
this.bitsPerSample = Validate.isTrue(isValidBPS(bitsPerSample), bitsPerSample, "Unsupported bits per sample value: %s");
|
||||||
|
this.byteOrder = byteOrder;
|
||||||
|
|
||||||
|
buffer = new byte[(columns * samplesPerPixel * bitsPerSample + 7) / 8];
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidBPS(final int bitsPerSample) {
|
||||||
|
switch (bitsPerSample) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
case 8:
|
||||||
|
case 16:
|
||||||
|
case 32:
|
||||||
|
case 64:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetch() throws IOException {
|
||||||
|
int pos = 0;
|
||||||
|
int read;
|
||||||
|
|
||||||
|
// This *SHOULD* read an entire row of pixels (or nothing at all) into the buffer, otherwise we will throw EOFException below
|
||||||
|
while (pos < buffer.length && (read = in.read(buffer, pos, buffer.length - pos)) > 0) {
|
||||||
|
pos += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos > 0) {
|
||||||
|
if (buffer.length > pos) {
|
||||||
|
throw new EOFException("Unexpected end of stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeRow();
|
||||||
|
|
||||||
|
decodedLength = buffer.length;
|
||||||
|
decodedPos = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
decodedLength = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decodeRow() throws EOFException {
|
||||||
|
// Un-apply horizontal predictor
|
||||||
|
int sample = 0;
|
||||||
|
byte temp;
|
||||||
|
|
||||||
|
switch (bitsPerSample) {
|
||||||
|
case 1:
|
||||||
|
for (int b = 0; b < (columns + 7) / 8; b++) {
|
||||||
|
sample += (buffer[b] >> 7) & 0x1;
|
||||||
|
temp = (byte) ((sample << 7) & 0x80);
|
||||||
|
sample += (buffer[b] >> 6) & 0x1;
|
||||||
|
temp |= (byte) ((sample << 6) & 0x40);
|
||||||
|
sample += (buffer[b] >> 5) & 0x1;
|
||||||
|
temp |= (byte) ((sample << 5) & 0x20);
|
||||||
|
sample += (buffer[b] >> 4) & 0x1;
|
||||||
|
temp |= (byte) ((sample << 4) & 0x10);
|
||||||
|
sample += (buffer[b] >> 3) & 0x1;
|
||||||
|
temp |= (byte) ((sample << 3) & 0x08);
|
||||||
|
sample += (buffer[b] >> 2) & 0x1;
|
||||||
|
temp |= (byte) ((sample << 2) & 0x04);
|
||||||
|
sample += (buffer[b] >> 1) & 0x1;
|
||||||
|
temp |= (byte) ((sample << 1) & 0x02);
|
||||||
|
sample += buffer[b] & 0x1;
|
||||||
|
buffer[b] = (byte) (temp | sample & 0x1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
for (int b = 0; b < (columns + 3) / 4; b++) {
|
||||||
|
sample += (buffer[b] >> 6) & 0x3;
|
||||||
|
temp = (byte) ((sample << 6) & 0xc0);
|
||||||
|
sample += (buffer[b] >> 4) & 0x3;
|
||||||
|
temp |= (byte) ((sample << 4) & 0x30);
|
||||||
|
sample += (buffer[b] >> 2) & 0x3;
|
||||||
|
temp |= (byte) ((sample << 2) & 0x0c);
|
||||||
|
sample += buffer[b] & 0x3;
|
||||||
|
buffer[b] = (byte) (temp | sample & 0x3);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
for (int b = 0; b < (columns + 1) / 2; b++) {
|
||||||
|
sample += (buffer[b] >> 4) & 0xf;
|
||||||
|
temp = (byte) ((sample << 4) & 0xf0);
|
||||||
|
sample += buffer[b] & 0x0f;
|
||||||
|
buffer[b] = (byte) (temp | sample & 0xf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
for (int x = 1; x < columns; x++) {
|
||||||
|
for (int b = 0; b < samplesPerPixel; b++) {
|
||||||
|
int off = x * samplesPerPixel + b;
|
||||||
|
buffer[off] = (byte) (buffer[off - samplesPerPixel] + buffer[off]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
for (int x = 1; x < columns; x++) {
|
||||||
|
for (int b = 0; b < samplesPerPixel; b++) {
|
||||||
|
int off = x * samplesPerPixel + b;
|
||||||
|
putShort(off, asShort(off - samplesPerPixel) + asShort(off));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
for (int x = 1; x < columns; x++) {
|
||||||
|
for (int b = 0; b < samplesPerPixel; b++) {
|
||||||
|
int off = x * samplesPerPixel + b;
|
||||||
|
putInt(off, asInt(off - samplesPerPixel) + asInt(off));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 64:
|
||||||
|
for (int x = 1; x < columns; x++) {
|
||||||
|
for (int b = 0; b < samplesPerPixel; b++) {
|
||||||
|
int off = x * samplesPerPixel + b;
|
||||||
|
putLong(off, asLong(off - samplesPerPixel) + asLong(off));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new AssertionError(String.format("Unsupported bits per sample value: %d", bitsPerSample));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putLong(final int index, final long value) {
|
||||||
|
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||||
|
buffer[index * 8 ] = (byte) ((value >> 56) & 0xff);
|
||||||
|
buffer[index * 8 + 1] = (byte) ((value >> 48) & 0xff);
|
||||||
|
buffer[index * 8 + 2] = (byte) ((value >> 40) & 0xff);
|
||||||
|
buffer[index * 8 + 3] = (byte) ((value >> 32) & 0xff);
|
||||||
|
buffer[index * 8 + 4] = (byte) ((value >> 24) & 0xff);
|
||||||
|
buffer[index * 8 + 5] = (byte) ((value >> 16) & 0xff);
|
||||||
|
buffer[index * 8 + 6] = (byte) ((value >> 8) & 0xff);
|
||||||
|
buffer[index * 8 + 7] = (byte) ((value) & 0xff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer[index * 8 + 7] = (byte) ((value >> 56) & 0xff);
|
||||||
|
buffer[index * 8 + 6] = (byte) ((value >> 48) & 0xff);
|
||||||
|
buffer[index * 8 + 5] = (byte) ((value >> 40) & 0xff);
|
||||||
|
buffer[index * 8 + 4] = (byte) ((value >> 32) & 0xff);
|
||||||
|
buffer[index * 8 + 3] = (byte) ((value >> 24) & 0xff);
|
||||||
|
buffer[index * 8 + 2] = (byte) ((value >> 16) & 0xff);
|
||||||
|
buffer[index * 8 + 1] = (byte) ((value >> 8) & 0xff);
|
||||||
|
buffer[index * 8 ] = (byte) ((value) & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long asLong(final int index) {
|
||||||
|
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||||
|
return (buffer[index * 8 ] & 0xffl) << 56l | (buffer[index * 8 + 1] & 0xffl) << 48l |
|
||||||
|
(buffer[index * 8 + 2] & 0xffl) << 40l | (buffer[index * 8 + 3] & 0xffl) << 32l |
|
||||||
|
(buffer[index * 8 + 4] & 0xffl) << 24 | (buffer[index * 8 + 5] & 0xffl) << 16 |
|
||||||
|
(buffer[index * 8 + 6] & 0xffl) << 8 | buffer[index * 8 + 7] & 0xffl;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (buffer[index * 8 + 7] & 0xffl) << 56l | (buffer[index * 8 + 6] & 0xffl) << 48l |
|
||||||
|
(buffer[index * 8 + 5] & 0xffl) << 40l | (buffer[index * 8 + 4] & 0xffl) << 32l |
|
||||||
|
(buffer[index * 8 + 3] & 0xffl) << 24 | (buffer[index * 8 + 2] & 0xffl) << 16 |
|
||||||
|
(buffer[index * 8 + 1] & 0xffl) << 8 | buffer[index * 8] & 0xffl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putInt(final int index, final int value) {
|
||||||
|
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||||
|
buffer[index * 4 ] = (byte) ((value >> 24) & 0xff);
|
||||||
|
buffer[index * 4 + 1] = (byte) ((value >> 16) & 0xff);
|
||||||
|
buffer[index * 4 + 2] = (byte) ((value >> 8) & 0xff);
|
||||||
|
buffer[index * 4 + 3] = (byte) ((value) & 0xff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer[index * 4 + 3] = (byte) ((value >> 24) & 0xff);
|
||||||
|
buffer[index * 4 + 2] = (byte) ((value >> 16) & 0xff);
|
||||||
|
buffer[index * 4 + 1] = (byte) ((value >> 8) & 0xff);
|
||||||
|
buffer[index * 4 ] = (byte) ((value) & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int asInt(final int index) {
|
||||||
|
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||||
|
return (buffer[index * 4] & 0xff) << 24 | (buffer[index * 4 + 1] & 0xff) << 16 |
|
||||||
|
(buffer[index * 4 + 2] & 0xff) << 8 | buffer[index * 4 + 3] & 0xff;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (buffer[index * 4 + 3] & 0xff) << 24 | (buffer[index * 4 + 2] & 0xff) << 16 |
|
||||||
|
(buffer[index * 4 + 1] & 0xff) << 8 | buffer[index * 4] & 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putShort(final int index, final int value) {
|
||||||
|
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||||
|
buffer[index * 2 ] = (byte) ((value >> 8) & 0xff);
|
||||||
|
buffer[index * 2 + 1] = (byte) ((value) & 0xff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer[index * 2 + 1] = (byte) ((value >> 8) & 0xff);
|
||||||
|
buffer[index * 2 ] = (byte) ((value) & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private short asShort(final int index) {
|
||||||
|
if (byteOrder == ByteOrder.BIG_ENDIAN) {
|
||||||
|
return (short) ((buffer[index * 2] & 0xff) << 8 | buffer[index * 2 + 1] & 0xff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (short) ((buffer[index * 2 + 1] & 0xff) << 8 | buffer[index * 2] & 0xff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[decodedPos++] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int read = Math.min(decodedLength - decodedPos, len);
|
||||||
|
System.arraycopy(buffer, decodedPos, b, off, read);
|
||||||
|
decodedPos += read;
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int skipped = (int) Math.min(decodedLength - decodedPos, n);
|
||||||
|
decodedPos += skipped;
|
||||||
|
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -112,7 +112,7 @@ class JPEGTables {
|
|||||||
|
|
||||||
// Read lengths as short array
|
// Read lengths as short array
|
||||||
short[] lengths = new short[DHT_LENGTH];
|
short[] lengths = new short[DHT_LENGTH];
|
||||||
for (int i = 0, lengthsLength = lengths.length; i < lengthsLength; i++) {
|
for (int i = 0; i < DHT_LENGTH; i++) {
|
||||||
lengths[i] = (short) data.readUnsignedByte();
|
lengths[i] = (short) data.readUnsignedByte();
|
||||||
}
|
}
|
||||||
read += lengths.length;
|
read += lengths.length;
|
||||||
|
|||||||
+179
-124
@@ -33,16 +33,18 @@ import com.twelvemonkeys.io.enc.Decoder;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LZWDecoder
|
* Lempel–Ziv–Welch (LZW) decompression. LZW is a universal loss-less data compression algorithm
|
||||||
|
* created by Abraham Lempel, Jacob Ziv, and Terry Welch.
|
||||||
|
* Inspired by libTiff's LZW decompression.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$
|
* @version $Id: LZWDecoder.java,v 1.0 08.05.12 21:11 haraldk Exp$
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Lempel%E2%80%93Ziv%E2%80%93Welch">LZW (Wikipedia)</a>
|
||||||
*/
|
*/
|
||||||
final class LZWDecoder implements Decoder {
|
abstract class LZWDecoder implements Decoder {
|
||||||
/** Clear: Re-initialize tables. */
|
/** Clear: Re-initialize tables. */
|
||||||
static final int CLEAR_CODE = 256;
|
static final int CLEAR_CODE = 256;
|
||||||
/** End of Information. */
|
/** End of Information. */
|
||||||
@@ -51,44 +53,44 @@ final class LZWDecoder implements Decoder {
|
|||||||
private static final int MIN_BITS = 9;
|
private static final int MIN_BITS = 9;
|
||||||
private static final int MAX_BITS = 12;
|
private static final int MAX_BITS = 12;
|
||||||
|
|
||||||
private final boolean reverseBitOrder;
|
private static final int TABLE_SIZE = 1 << MAX_BITS;
|
||||||
|
|
||||||
private int currentByte = -1;
|
private final boolean compatibilityMode;
|
||||||
private int bitPos;
|
|
||||||
|
|
||||||
// TODO: Consider speeding things up with a "string" type (instead of the inner byte[]),
|
private final String[] table;
|
||||||
// that uses variable size/dynamic allocation, to avoid the excessive array copying?
|
|
||||||
// private final byte[][] table = new byte[4096][0]; // libTiff adds another 1024 "for compatibility"...
|
|
||||||
private final byte[][] table = new byte[4096 + 1024][0]; // libTiff adds another 1024 "for compatibility"...
|
|
||||||
private int tableLength;
|
private int tableLength;
|
||||||
private int bitsPerCode;
|
int bitsPerCode;
|
||||||
private int oldCode = CLEAR_CODE;
|
private int oldCode = CLEAR_CODE;
|
||||||
private int maxCode;
|
private int maxCode;
|
||||||
|
int bitMask;
|
||||||
private int maxString;
|
private int maxString;
|
||||||
private boolean eofReached;
|
boolean eofReached;
|
||||||
|
int nextData;
|
||||||
|
int nextBits;
|
||||||
|
|
||||||
LZWDecoder(final boolean reverseBitOrder) {
|
|
||||||
this.reverseBitOrder = reverseBitOrder;
|
|
||||||
|
|
||||||
|
protected LZWDecoder(final boolean compatibilityMode) {
|
||||||
|
this.compatibilityMode = compatibilityMode;
|
||||||
|
|
||||||
|
table = new String[compatibilityMode ? TABLE_SIZE + 1024 : TABLE_SIZE]; // libTiff adds 1024 "for compatibility"...
|
||||||
|
|
||||||
|
// First 258 entries of table is always fixed
|
||||||
for (int i = 0; i < 256; i++) {
|
for (int i = 0; i < 256; i++) {
|
||||||
table[i] = new byte[] {(byte) i};
|
table[i] = new String((byte) i);
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
LZWDecoder() {
|
private static int bitmaskFor(final int bits) {
|
||||||
this(false);
|
return (1 << bits) - 1;
|
||||||
}
|
|
||||||
|
|
||||||
private int maxCodeFor(final int bits) {
|
|
||||||
return reverseBitOrder ? (1 << bits) - 2 : (1 << bits) - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
tableLength = 258;
|
tableLength = 258;
|
||||||
bitsPerCode = MIN_BITS;
|
bitsPerCode = MIN_BITS;
|
||||||
maxCode = maxCodeFor(bitsPerCode);
|
bitMask = bitmaskFor(bitsPerCode);
|
||||||
|
maxCode = maxCode();
|
||||||
maxString = 1;
|
maxString = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,25 +109,17 @@ final class LZWDecoder implements Decoder {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferPos += writeString(table[code], buffer, bufferPos);
|
bufferPos += table[code].writeTo(buffer, bufferPos);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (code > tableLength + 1 || oldCode >= tableLength) {
|
|
||||||
// TODO: FixMe for old, borked streams
|
|
||||||
System.err.println("code: " + code);
|
|
||||||
System.err.println("oldCode: " + oldCode);
|
|
||||||
System.err.println("tableLength: " + tableLength);
|
|
||||||
throw new DecodeException("Corrupted LZW table");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInTable(code)) {
|
if (isInTable(code)) {
|
||||||
bufferPos += writeString(table[code], buffer, bufferPos);
|
bufferPos += table[code].writeTo(buffer, bufferPos);
|
||||||
addStringToTable(concatenate(table[oldCode], table[code][0]));
|
addStringToTable(table[oldCode].concatenate(table[code].firstChar));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
byte[] outString = concatenate(table[oldCode], table[oldCode][0]);
|
String outString = table[oldCode].concatenate(table[oldCode].firstChar);
|
||||||
|
|
||||||
bufferPos += writeString(outString, buffer, bufferPos);
|
bufferPos += outString.writeTo(buffer, bufferPos);
|
||||||
addStringToTable(outString);
|
addStringToTable(outString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,29 +135,23 @@ final class LZWDecoder implements Decoder {
|
|||||||
return bufferPos;
|
return bufferPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] concatenate(final byte[] string, final byte firstChar) {
|
private void addStringToTable(final String string) throws IOException {
|
||||||
byte[] result = Arrays.copyOf(string, string.length + 1);
|
|
||||||
result[string.length] = firstChar;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addStringToTable(final byte[] string) throws IOException {
|
|
||||||
table[tableLength++] = string;
|
table[tableLength++] = string;
|
||||||
|
|
||||||
if (tableLength >= maxCode) {
|
if (tableLength > maxCode) {
|
||||||
bitsPerCode++;
|
bitsPerCode++;
|
||||||
|
|
||||||
if (bitsPerCode > MAX_BITS) {
|
if (bitsPerCode > MAX_BITS) {
|
||||||
if (reverseBitOrder) {
|
if (compatibilityMode) {
|
||||||
bitsPerCode--;
|
bitsPerCode--;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new DecodeException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
|
throw new DecodeException(java.lang.String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
maxCode = maxCodeFor(bitsPerCode);
|
bitMask = bitmaskFor(bitsPerCode);
|
||||||
|
maxCode = maxCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.length > maxString) {
|
if (string.length > maxString) {
|
||||||
@@ -171,89 +159,14 @@ final class LZWDecoder implements Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int writeString(final byte[] string, final byte[] buffer, final int bufferPos) {
|
protected abstract int maxCode();
|
||||||
if (string.length == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else if (string.length == 1) {
|
|
||||||
buffer[bufferPos] = string[0];
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
System.arraycopy(string, 0, buffer, bufferPos, string.length);
|
|
||||||
|
|
||||||
return string.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isInTable(int code) {
|
private boolean isInTable(int code) {
|
||||||
return code < tableLength;
|
return code < tableLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getNextCode(final InputStream stream) throws IOException {
|
protected abstract int getNextCode(final InputStream stream) throws IOException;
|
||||||
if (eofReached) {
|
|
||||||
return EOI_CODE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bitsToFill = bitsPerCode;
|
|
||||||
int value = 0;
|
|
||||||
|
|
||||||
while (bitsToFill > 0) {
|
|
||||||
int nextBits;
|
|
||||||
if (bitPos == 0) {
|
|
||||||
nextBits = stream.read();
|
|
||||||
|
|
||||||
if (nextBits == -1) {
|
|
||||||
// This is really a bad stream, but should be safe to handle this way, rather than throwing an EOFException.
|
|
||||||
// An EOFException will be thrown by the decoder stream later, if further reading is attempted.
|
|
||||||
eofReached = true;
|
|
||||||
return EOI_CODE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nextBits = currentByte;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bitsFromHere = 8 - bitPos;
|
|
||||||
if (bitsFromHere > bitsToFill) {
|
|
||||||
bitsFromHere = bitsToFill;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reverseBitOrder) {
|
|
||||||
// NOTE: This is a spec violation. However, libTiff reads such files.
|
|
||||||
// TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says:
|
|
||||||
// "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder
|
|
||||||
// is assumed to be 1. The compressed codes are written as bytes (not words) so that the
|
|
||||||
// compressed data will be identical whether it is an ‘II’ or ‘MM’ file."
|
|
||||||
|
|
||||||
// Fill bytes from right-to-left
|
|
||||||
for (int i = 0; i < bitsFromHere; i++) {
|
|
||||||
int destBitPos = bitsPerCode - bitsToFill + i;
|
|
||||||
int srcBitPos = bitPos + i;
|
|
||||||
value |= ((nextBits & (1 << srcBitPos)) >> srcBitPos) << destBitPos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
value |= (nextBits >> 8 - bitPos - bitsFromHere & 0xff >> 8 - bitsFromHere) << bitsToFill - bitsFromHere;
|
|
||||||
}
|
|
||||||
|
|
||||||
bitsToFill -= bitsFromHere;
|
|
||||||
bitPos += bitsFromHere;
|
|
||||||
|
|
||||||
if (bitPos >= 8) {
|
|
||||||
bitPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentByte = nextBits;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == EOI_CODE) {
|
|
||||||
eofReached = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean isOldBitReversedStream(final InputStream stream) throws IOException {
|
static boolean isOldBitReversedStream(final InputStream stream) throws IOException {
|
||||||
stream.mark(2);
|
stream.mark(2);
|
||||||
@@ -267,5 +180,147 @@ final class LZWDecoder implements Decoder {
|
|||||||
stream.reset();
|
stream.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LZWDecoder create(boolean oldBitReversedStream) {
|
||||||
|
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LZWSpecDecoder extends LZWDecoder {
|
||||||
|
|
||||||
|
protected LZWSpecDecoder() {
|
||||||
|
super(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int maxCode() {
|
||||||
|
return bitMask - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final int getNextCode(final InputStream stream) throws IOException {
|
||||||
|
if (eofReached) {
|
||||||
|
return EOI_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int code;
|
||||||
|
int read = stream.read();
|
||||||
|
if (read < 0) {
|
||||||
|
eofReached = true;
|
||||||
|
return EOI_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextData = (nextData << 8) | read;
|
||||||
|
nextBits += 8;
|
||||||
|
|
||||||
|
if (nextBits < bitsPerCode) {
|
||||||
|
read = stream.read();
|
||||||
|
if (read < 0) {
|
||||||
|
eofReached = true;
|
||||||
|
return EOI_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextData = (nextData << 8) | read;
|
||||||
|
nextBits += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
code = ((nextData >> (nextBits - bitsPerCode)) & bitMask);
|
||||||
|
nextBits -= bitsPerCode;
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class LZWCompatibilityDecoder extends LZWDecoder {
|
||||||
|
// NOTE: This is a spec violation. However, libTiff reads such files.
|
||||||
|
// TIFF 6.0 Specification, Section 13: "LZW Compression"/"The Algorithm", page 61, says:
|
||||||
|
// "LZW compression codes are stored into bytes in high-to-low-order fashion, i.e., FillOrder
|
||||||
|
// is assumed to be 1. The compressed codes are written as bytes (not words) so that the
|
||||||
|
// compressed data will be identical whether it is an ‘II’ or ‘MM’ file."
|
||||||
|
|
||||||
|
protected LZWCompatibilityDecoder() {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int maxCode() {
|
||||||
|
return bitMask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final int getNextCode(final InputStream stream) throws IOException {
|
||||||
|
if (eofReached) {
|
||||||
|
return EOI_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int code;
|
||||||
|
int read = stream.read();
|
||||||
|
if (read < 0) {
|
||||||
|
eofReached = true;
|
||||||
|
return EOI_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextData |= read << nextBits;
|
||||||
|
nextBits += 8;
|
||||||
|
|
||||||
|
if (nextBits < bitsPerCode) {
|
||||||
|
read = stream.read();
|
||||||
|
if (read < 0) {
|
||||||
|
eofReached = true;
|
||||||
|
return EOI_CODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextData |= read << nextBits;
|
||||||
|
nextBits += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
code = (nextData & bitMask);
|
||||||
|
nextData >>= bitsPerCode;
|
||||||
|
nextBits -= bitsPerCode;
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class String {
|
||||||
|
final String previous;
|
||||||
|
|
||||||
|
final int length;
|
||||||
|
final byte value;
|
||||||
|
final byte firstChar; // Copied forward for fast access
|
||||||
|
|
||||||
|
public String(final byte code) {
|
||||||
|
this(code, code, 1, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String(final byte value, final byte firstChar, final int length, final String previous) {
|
||||||
|
this.value = value;
|
||||||
|
this.firstChar = firstChar;
|
||||||
|
this.length = length;
|
||||||
|
this.previous = previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String concatenate(final byte firstChar) {
|
||||||
|
return new String(firstChar, this.firstChar, length + 1, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int writeTo(final byte[] buffer, final int offset) {
|
||||||
|
if (length == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (length == 1) {
|
||||||
|
buffer[offset] = value;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
String e = this;
|
||||||
|
|
||||||
|
for (int i = length - 1; i >= 0; i--) {
|
||||||
|
buffer[offset + i] = e.value;
|
||||||
|
e = e.previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
|||||||
*/
|
*/
|
||||||
interface TIFFBaseline {
|
interface TIFFBaseline {
|
||||||
int COMPRESSION_NONE = 1;
|
int COMPRESSION_NONE = 1;
|
||||||
int COMPRESSION_CCITT_HUFFMAN = 2;
|
int COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE = 2;
|
||||||
int COMPRESSION_PACKBITS = 32773;
|
int COMPRESSION_PACKBITS = 32773;
|
||||||
|
|
||||||
int PHOTOMETRIC_WHITE_IS_ZERO = 0;
|
int PHOTOMETRIC_WHITE_IS_ZERO = 0;
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ interface TIFFCustom {
|
|||||||
int COMPRESSION_JBIG = 34661;
|
int COMPRESSION_JBIG = 34661;
|
||||||
int COMPRESSION_SGILOG = 34676;
|
int COMPRESSION_SGILOG = 34676;
|
||||||
int COMPRESSION_SGILOG24 = 34677;
|
int COMPRESSION_SGILOG24 = 34677;
|
||||||
int COMPRESSION_JP2000 = 34712;
|
int COMPRESSION_JPEG2000 = 34712;
|
||||||
|
|
||||||
int PHOTOMETRIC_LOGL = 32844;
|
int PHOTOMETRIC_LOGL = 32844;
|
||||||
int PHOTOMETRIC_LOGLUV = 32845;
|
int PHOTOMETRIC_LOGLUV = 32845;
|
||||||
|
|||||||
+7
@@ -65,4 +65,11 @@ interface TIFFExtension {
|
|||||||
int SAMPLEFORMAT_INT = 2;
|
int SAMPLEFORMAT_INT = 2;
|
||||||
int SAMPLEFORMAT_FP = 3;
|
int SAMPLEFORMAT_FP = 3;
|
||||||
int SAMPLEFORMAT_UNDEFINED = 4;
|
int SAMPLEFORMAT_UNDEFINED = 4;
|
||||||
|
|
||||||
|
int YCBCR_POSITIONING_CENTERED = 1;
|
||||||
|
int YCBCR_POSITIONING_COSITED = 2;
|
||||||
|
|
||||||
|
// "Old-style" JPEG (obsolete)
|
||||||
|
int JPEG_PROC_BASELINE = 1;
|
||||||
|
int JPEG_PROC_LOSSLESS = 14;
|
||||||
}
|
}
|
||||||
|
|||||||
+429
-118
@@ -35,12 +35,15 @@ import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
|||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
|
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||||
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.io.enc.PackBitsDecoder;
|
import com.twelvemonkeys.io.enc.PackBitsDecoder;
|
||||||
@@ -58,9 +61,7 @@ import java.awt.color.ICC_Profile;
|
|||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
@@ -105,28 +106,30 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// TODO: Source region (*tests should be failing*)
|
// TODO: Source region (*tests should be failing*)
|
||||||
// TODO: TIFFImageWriter + Spi
|
// TODO: TIFFImageWriter + Spi
|
||||||
|
|
||||||
|
// TODOs Full BaseLine support:
|
||||||
|
// TODO: Support ExtraSamples (an array, if multiple extra samples!)
|
||||||
|
// (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
|
||||||
|
|
||||||
// TODOs ImageIO advanced functionality:
|
// TODOs ImageIO advanced functionality:
|
||||||
// TODO: Implement readAsRenderedImage to allow tiled renderImage?
|
// TODO: Implement readAsRenderedImage to allow tiled renderImage?
|
||||||
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
||||||
// TODO: Implement readAsRaster directly
|
// TODO: Implement readAsRaster directly
|
||||||
|
// TODO: IIOMetadata (stay close to Sun's TIFF metadata)
|
||||||
// TODOs Full BaseLine support:
|
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
||||||
// TODO: Support ExtraSamples (an array, if multiple extra samples!)
|
|
||||||
// (0: Unspecified (not alpha), 1: Associated Alpha (pre-multiplied), 2: Unassociated Alpha (non-multiplied)
|
|
||||||
// TODO: Support Compression 2 (CCITT Modified Huffman) for bi-level images
|
|
||||||
|
|
||||||
// TODOs Extension support
|
// TODOs Extension support
|
||||||
// TODO: Support PlanarConfiguration 2
|
// TODO: Support PlanarConfiguration 2
|
||||||
// TODO: Support ICCProfile (fully)
|
// TODO: Support ICCProfile (fully)
|
||||||
// TODO: Support Compression 3 & 4 (CCITT T.4 & T.6)
|
// TODO: Support Compression 3 & 4 (CCITT T.4 & T.6)
|
||||||
// TODO: Support Compression 6 ('Old-style' JPEG)
|
|
||||||
// TODO: Support Compression 34712 (JPEG2000)? Depends on JPEG2000 ImageReader
|
// TODO: Support Compression 34712 (JPEG2000)? Depends on JPEG2000 ImageReader
|
||||||
// TODO: Support Compression 34661 (JBIG)? Depends on JBIG ImageReader
|
// TODO: Support Compression 34661 (JBIG)? Depends on JBIG ImageReader
|
||||||
|
|
||||||
// DONE:
|
// DONE:
|
||||||
// Handle SampleFormat (and give up if not == 1)
|
// Handle SampleFormat (and give up if not == 1)
|
||||||
|
// Support Compression 6 ('Old-style' JPEG)
|
||||||
|
// Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images
|
||||||
|
|
||||||
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
|
||||||
|
|
||||||
private CompoundDirectory IFDs;
|
private CompoundDirectory IFDs;
|
||||||
private Directory currentIFD;
|
private Directory currentIFD;
|
||||||
@@ -150,12 +153,12 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
IFDs = (CompoundDirectory) new EXIFReader().read(imageInput); // NOTE: Sets byte order as a side effect
|
IFDs = (CompoundDirectory) new EXIFReader().read(imageInput); // NOTE: Sets byte order as a side effect
|
||||||
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
|
||||||
System.err.printf("ifd[%d]: %s\n", i, IFDs.getDirectory(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
System.err.println("Byte order: " + imageInput.getByteOrder());
|
System.err.println("Byte order: " + imageInput.getByteOrder());
|
||||||
System.err.println("numImages: " + IFDs.directoryCount());
|
System.err.println("Number of images: " + IFDs.directoryCount());
|
||||||
|
|
||||||
|
for (int i = 0; i < IFDs.directoryCount(); i++) {
|
||||||
|
System.err.printf("IFD %d: %s\n", i, IFDs.getDirectory(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +176,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
return IFDs.directoryCount();
|
return IFDs.directoryCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getValueAsIntWithDefault(final int tag, String tagName, Integer defaultValue) throws IIOException {
|
private Number getValueAsNumberWithDefault(final int tag, final String tagName, final Number defaultValue) throws IIOException {
|
||||||
Entry entry = currentIFD.getEntryById(tag);
|
Entry entry = currentIFD.getEntryById(tag);
|
||||||
|
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
@@ -184,7 +187,19 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
throw new IIOException("Missing TIFF tag: " + (tagName != null ? tagName : tag));
|
throw new IIOException("Missing TIFF tag: " + (tagName != null ? tagName : tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ((Number) entry.getValue()).intValue();
|
return (Number) entry.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getValueAsLongWithDefault(final int tag, final String tagName, final Long defaultValue) throws IIOException {
|
||||||
|
return getValueAsNumberWithDefault(tag, tagName, defaultValue).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getValueAsLongWithDefault(final int tag, final Long defaultValue) throws IIOException {
|
||||||
|
return getValueAsLongWithDefault(tag, null, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValueAsIntWithDefault(final int tag, final String tagName, final Integer defaultValue) throws IIOException {
|
||||||
|
return getValueAsNumberWithDefault(tag, tagName, defaultValue).intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getValueAsIntWithDefault(final int tag, Integer defaultValue) throws IIOException {
|
private int getValueAsIntWithDefault(final int tag, Integer defaultValue) throws IIOException {
|
||||||
@@ -216,7 +231,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
getSampleFormat(); // We don't support anything but SAMPLEFORMAT_UINT at the moment, just sanity checking input
|
getSampleFormat(); // We don't support anything but SAMPLEFORMAT_UINT at the moment, just sanity checking input
|
||||||
int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR);
|
int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR);
|
||||||
int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
|
int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
|
||||||
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXELS, 1);
|
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
|
||||||
int bitsPerSample = getBitsPerSample();
|
int bitsPerSample = getBitsPerSample();
|
||||||
int dataType = bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
|
int dataType = bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
|
||||||
|
|
||||||
@@ -252,8 +267,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
||||||
// JPEG reader will handle YCbCr to RGB for us, we'll have to do it ourselves if not JPEG...
|
// JPEG reader will handle YCbCr to RGB for us, otherwise we'll convert while reading
|
||||||
// TODO: Handle YCbCrSubsampling (up-scaler stream, or read data as-is + up-sample (sub-)raster after read? Apply smoothing?)
|
// TODO: Sanity check that we have SamplesPerPixel == 3, BitsPerSample == [8,8,8] and Compression == 1 (none), 5 (LZW), or 6 (JPEG)
|
||||||
case TIFFBaseline.PHOTOMETRIC_RGB:
|
case TIFFBaseline.PHOTOMETRIC_RGB:
|
||||||
// RGB
|
// RGB
|
||||||
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
|
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
|
||||||
@@ -274,23 +289,25 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 4:
|
case 4:
|
||||||
// TODO: Consult ExtraSamples!
|
|
||||||
if (bitsPerSample == 8 || bitsPerSample == 16) {
|
if (bitsPerSample == 8 || bitsPerSample == 16) {
|
||||||
|
// ExtraSamples 0=unspecified, 1=associated (premultiplied), 2=unassociated (TODO: Support unspecified, not alpha)
|
||||||
|
long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
|
||||||
|
|
||||||
switch (planarConfiguration) {
|
switch (planarConfiguration) {
|
||||||
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
if (bitsPerSample == 8 && cs.isCS_sRGB()) {
|
if (bitsPerSample == 8 && cs.isCS_sRGB()) {
|
||||||
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
return ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, false, false);
|
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, extraSamples[0] == 1);
|
||||||
|
|
||||||
case TIFFExtension.PLANARCONFIG_PLANAR:
|
case TIFFExtension.PLANARCONFIG_PLANAR:
|
||||||
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
|
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
// TODO: More samples might be ok, if multiple alpha or unknown samples
|
||||||
default:
|
default:
|
||||||
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
|
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIFF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
|
||||||
}
|
}
|
||||||
case TIFFBaseline.PHOTOMETRIC_PALETTE:
|
case TIFFBaseline.PHOTOMETRIC_PALETTE:
|
||||||
// Palette
|
// Palette
|
||||||
@@ -337,18 +354,29 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
|
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case 5:
|
||||||
|
if (bitsPerSample == 8 || bitsPerSample == 16) {
|
||||||
|
// ExtraSamples 0=unspecified, 1=associated (premultiplied), 2=unassociated (TODO: Support unspecified, not alpha)
|
||||||
|
long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
|
||||||
|
|
||||||
|
switch (planarConfiguration) {
|
||||||
|
case TIFFBaseline.PLANARCONFIG_CHUNKY:
|
||||||
|
return ImageTypeSpecifier.createInterleaved(cs, new int[] {0, 1, 2, 3, 4}, dataType, true, extraSamples[0] == 1);
|
||||||
|
case TIFFExtension.PLANARCONFIG_PLANAR:
|
||||||
|
return ImageTypeSpecifier.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: More samples might be ok, if multiple alpha or unknown samples, consult ExtraSamples
|
// TODO: More samples might be ok, if multiple alpha or unknown samples, consult ExtraSamples
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new IIOException(
|
throw new IIOException(
|
||||||
String.format("Unsupported TIFF SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8 or 4/16): %d/%s", samplesPerPixel, bitsPerSample)
|
String.format("Unsupported TIFF SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8, 4/16, 5/8 or 5/16): %d/%s", samplesPerPixel, bitsPerSample)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case TIFFBaseline.PHOTOMETRIC_MASK:
|
case TIFFBaseline.PHOTOMETRIC_MASK:
|
||||||
// Transparency mask
|
// Transparency mask
|
||||||
|
|
||||||
// TODO: Known extensions
|
|
||||||
throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation);
|
throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation);
|
||||||
default:
|
default:
|
||||||
throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + interpretation);
|
throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + interpretation);
|
||||||
@@ -448,7 +476,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip
|
// NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip
|
||||||
// Strips are top/down, tiles are left/right, top/down
|
// Strips are top/down, tiles are left/right, top/down
|
||||||
int stripTileWidth = width;
|
int stripTileWidth = width;
|
||||||
int stripTileHeight = getValueAsIntWithDefault(TIFF.TAG_ROWS_PER_STRIP, height);
|
long rowsPerStrip = getValueAsLongWithDefault(TIFF.TAG_ROWS_PER_STRIP, (1l << 32) - 1);
|
||||||
|
int stripTileHeight = rowsPerStrip < height ? (int) rowsPerStrip : height;
|
||||||
|
|
||||||
long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false);
|
long[] stripTileOffsets = getValueAsLongArray(TIFF.TAG_TILE_OFFSETS, "TileOffsets", false);
|
||||||
long[] stripTileByteCounts;
|
long[] stripTileByteCounts;
|
||||||
|
|
||||||
@@ -479,9 +509,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
|
WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
|
||||||
int row = 0;
|
int row = 0;
|
||||||
|
|
||||||
// Read data
|
|
||||||
processImageStarted(imageIndex);
|
|
||||||
|
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
// TIFF Baseline
|
// TIFF Baseline
|
||||||
case TIFFBaseline.COMPRESSION_NONE:
|
case TIFFBaseline.COMPRESSION_NONE:
|
||||||
@@ -494,9 +521,70 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// LZW
|
// LZW
|
||||||
case TIFFExtension.COMPRESSION_ZLIB:
|
case TIFFExtension.COMPRESSION_ZLIB:
|
||||||
// 'Adobe-style' Deflate
|
// 'Adobe-style' Deflate
|
||||||
|
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||||
|
// CCITT modified Huffman
|
||||||
|
// Additionally, the specification defines these values as part of the TIFF extensions:
|
||||||
|
// case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
|
// CCITT Group 3 fax encoding
|
||||||
|
// case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
|
// CCITT Group 4 fax encoding
|
||||||
|
|
||||||
|
int[] yCbCrSubsampling = null;
|
||||||
|
int yCbCrPos = 1;
|
||||||
|
double[] yCbCrCoefficients = null;
|
||||||
|
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||||
|
// getRawImageType does the lookup/conversion for these
|
||||||
|
if (raster.getNumBands() != 3) {
|
||||||
|
throw new IIOException("TIFF PhotometricInterpretation YCbCr requires SamplesPerPixel == 3: " + raster.getNumBands());
|
||||||
|
}
|
||||||
|
if (raster.getTransferType() != DataBuffer.TYPE_BYTE) {
|
||||||
|
throw new IIOException("TIFF PhotometricInterpretation YCbCr requires BitsPerSample == [8,8,8]");
|
||||||
|
}
|
||||||
|
|
||||||
|
yCbCrPos = getValueAsIntWithDefault(TIFF.TAG_YCBCR_POSITIONING, TIFFExtension.YCBCR_POSITIONING_CENTERED);
|
||||||
|
if (yCbCrPos != TIFFExtension.YCBCR_POSITIONING_CENTERED && yCbCrPos != TIFFExtension.YCBCR_POSITIONING_COSITED) {
|
||||||
|
processWarningOccurred("Uknown TIFF YCbCrPositioning value, expected 1 or 2: " + yCbCrPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry subSampling = currentIFD.getEntryById(TIFF.TAG_YCBCR_SUB_SAMPLING);
|
||||||
|
|
||||||
|
if (subSampling != null) {
|
||||||
|
try {
|
||||||
|
yCbCrSubsampling = (int[]) subSampling.getValue();
|
||||||
|
}
|
||||||
|
catch (ClassCastException e) {
|
||||||
|
throw new IIOException("Unknown TIFF YCbCrSubSampling value type: " + subSampling.getTypeName(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yCbCrSubsampling.length != 2 ||
|
||||||
|
yCbCrSubsampling[0] != 1 && yCbCrSubsampling[0] != 2 && yCbCrSubsampling[0] != 4 ||
|
||||||
|
yCbCrSubsampling[1] != 1 && yCbCrSubsampling[1] != 2 && yCbCrSubsampling[1] != 4) {
|
||||||
|
throw new IIOException("Bad TIFF YCbCrSubSampling value: " + Arrays.toString(yCbCrSubsampling));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yCbCrSubsampling[0] < yCbCrSubsampling[1]) {
|
||||||
|
processWarningOccurred("TIFF PhotometricInterpretation YCbCr with bad subsampling, expected subHoriz >= subVert: " + Arrays.toString(yCbCrSubsampling));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
yCbCrSubsampling = new int[] {2, 2};
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry coefficients = currentIFD.getEntryById(TIFF.TAG_YCBCR_COEFFICIENTS);
|
||||||
|
if (coefficients != null) {
|
||||||
|
Rational[] value = (Rational[]) coefficients.getValue();
|
||||||
|
yCbCrCoefficients = new double[] {value[0].doubleValue(), value[1].doubleValue(), value[2].doubleValue()};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Default to y CCIR Recommendation 601-1 values
|
||||||
|
yCbCrCoefficients = YCbCrUpsamplerStream.CCIR_601_1_COEFFICIENTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
// TODO: Read only tiles that lies within region
|
// TODO: Read only tiles that lies within region
|
||||||
|
|
||||||
// General uncompressed/compressed reading
|
// General uncompressed/compressed reading
|
||||||
for (int y = 0; y < tilesDown; y++) {
|
for (int y = 0; y < tilesDown; y++) {
|
||||||
int col = 0;
|
int col = 0;
|
||||||
@@ -509,7 +597,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
imageInput.seek(stripTileOffsets[i]);
|
imageInput.seek(stripTileOffsets[i]);
|
||||||
|
|
||||||
DataInput input;
|
DataInput input;
|
||||||
if (compression == TIFFBaseline.COMPRESSION_NONE) {
|
if (compression == TIFFBaseline.COMPRESSION_NONE && interpretation != TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||||
|
// No need for transformation, fast forward
|
||||||
input = imageInput;
|
input = imageInput;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -517,15 +606,21 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
|
? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
|
||||||
: IIOUtil.createStreamAdapter(imageInput);
|
: IIOUtil.createStreamAdapter(imageInput);
|
||||||
|
|
||||||
|
adapter = createDecompressorStream(compression, width, adapter);
|
||||||
|
adapter = createUnpredictorStream(predictor, width, planarConfiguration == 2 ? 1 : raster.getNumBands(), getBitsPerSample(), adapter, imageInput.getByteOrder());
|
||||||
|
|
||||||
|
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||||
|
adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients);
|
||||||
|
}
|
||||||
|
|
||||||
// According to the spec, short/long/etc should follow order of containing stream
|
// According to the spec, short/long/etc should follow order of containing stream
|
||||||
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
|
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
|
||||||
? new DataInputStream(createDecoderInputStream(compression, adapter))
|
? new DataInputStream(adapter)
|
||||||
: new LittleEndianDataInputStream(createDecoderInputStream(compression, adapter));
|
: new LittleEndianDataInputStream(adapter);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a full strip/tile
|
// Read a full strip/tile
|
||||||
readStripTileData(rowRaster, interpretation, predictor, raster, numBands, col, row, colsInTile, rowsInTile, input);
|
readStripTileData(rowRaster, interpretation, raster, col, row, colsInTile, rowsInTile, input);
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
break;
|
break;
|
||||||
@@ -549,6 +644,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
case TIFFExtension.COMPRESSION_JPEG:
|
case TIFFExtension.COMPRESSION_JPEG:
|
||||||
// JPEG ('new-style' JPEG)
|
// JPEG ('new-style' JPEG)
|
||||||
// TODO: Refactor all JPEG reading out to separate JPEG support class?
|
// TODO: Refactor all JPEG reading out to separate JPEG support class?
|
||||||
|
// TODO: Cache the JPEG reader for later use? Remember to reset to avoid resource leaks
|
||||||
|
|
||||||
// TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
|
// TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
|
||||||
ImageReader jpegReader = new JPEGImageReader(getOriginatingProvider());
|
ImageReader jpegReader = new JPEGImageReader(getOriginatingProvider());
|
||||||
@@ -565,6 +661,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// Might have something to do with subsampling?
|
// Might have something to do with subsampling?
|
||||||
// How do we pass the chroma-subsampling parameter from the TIFF structure to the JPEG reader?
|
// How do we pass the chroma-subsampling parameter from the TIFF structure to the JPEG reader?
|
||||||
|
|
||||||
|
// TODO: Consider splicing the TAG_JPEG_TABLES into the streams for each tile, for a
|
||||||
|
// (slightly slower for multiple images, but) more compatible approach..?
|
||||||
|
|
||||||
jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
|
jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
|
||||||
|
|
||||||
// NOTE: This initializes the tables AND MORE secret internal settings for the reader (as if by magic).
|
// NOTE: This initializes the tables AND MORE secret internal settings for the reader (as if by magic).
|
||||||
@@ -620,6 +719,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
// ...and the JPEG reader will probably choke on missing tables...
|
// ...and the JPEG reader will probably choke on missing tables...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
for (int y = 0; y < tilesDown; y++) {
|
for (int y = 0; y < tilesDown; y++) {
|
||||||
int col = 0;
|
int col = 0;
|
||||||
int rowsInTile = Math.min(stripTileHeight, height - row);
|
int rowsInTile = Math.min(stripTileHeight, height - row);
|
||||||
@@ -629,14 +731,14 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
int colsInTile = Math.min(stripTileWidth, width - col);
|
int colsInTile = Math.min(stripTileWidth, width - col);
|
||||||
|
|
||||||
imageInput.seek(stripTileOffsets[i]);
|
imageInput.seek(stripTileOffsets[i]);
|
||||||
SubImageInputStream subStream = new SubImageInputStream(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE);
|
ImageInputStream subStream = new SubImageInputStream(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE);
|
||||||
try {
|
try {
|
||||||
jpegReader.setInput(subStream);
|
jpegReader.setInput(subStream);
|
||||||
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
|
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
|
||||||
jpegParam.setDestinationOffset(new Point(col, row));
|
jpegParam.setDestinationOffset(new Point(col, row));
|
||||||
jpegParam.setDestination(destination);
|
jpegParam.setDestination(destination);
|
||||||
// TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc...
|
// TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc...
|
||||||
// In the latter case we will have to use readAsRaster
|
// In the latter case we will have to use readAsRaster and do color conversion ourselves
|
||||||
jpegReader.read(0, jpegParam);
|
jpegReader.read(0, jpegParam);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
@@ -662,15 +764,202 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TIFFBaseline.COMPRESSION_CCITT_HUFFMAN:
|
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
||||||
// CCITT modified Huffman
|
// JPEG ('old-style' JPEG, later overridden in Technote2)
|
||||||
|
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
|
||||||
|
|
||||||
|
// 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent
|
||||||
|
int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, TIFFExtension.JPEG_PROC_BASELINE);
|
||||||
|
switch (mode) {
|
||||||
|
case TIFFExtension.JPEG_PROC_BASELINE:
|
||||||
|
break; // Supported
|
||||||
|
case TIFFExtension.JPEG_PROC_LOSSLESS:
|
||||||
|
throw new IIOException("Unsupported TIFF JPEGProcessingMode: Lossless (14)");
|
||||||
|
default:
|
||||||
|
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// May use normal tiling??
|
||||||
|
|
||||||
|
// TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
|
||||||
|
jpegReader = new JPEGImageReader(getOriginatingProvider());
|
||||||
|
jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
|
||||||
|
|
||||||
|
// 513/JPEGInterchangeFormat (may be absent...)
|
||||||
|
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
||||||
|
// 514/JPEGInterchangeFormatLength (may be absent...)
|
||||||
|
int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
||||||
|
// TODO: 515/JPEGRestartInterval (may be absent)
|
||||||
|
|
||||||
|
// Currently ignored
|
||||||
|
// 517/JPEGLosslessPredictors
|
||||||
|
// 518/JPEGPointTransforms
|
||||||
|
|
||||||
|
ImageInputStream stream;
|
||||||
|
|
||||||
|
if (jpegOffset != -1) {
|
||||||
|
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
|
||||||
|
|
||||||
|
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null || currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) {
|
||||||
|
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Ignoring JPEG tables. Reading as single tile.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
processWarningOccurred("Old-style JPEG compressed TIFF with JFIF stream encountered. Reading as single tile.");
|
||||||
|
}
|
||||||
|
|
||||||
|
imageInput.seek(jpegOffset);
|
||||||
|
stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE);
|
||||||
|
jpegReader.setInput(stream);
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
try {
|
||||||
|
jpegParam.setSourceRegion(new Rectangle(0, 0, width, height));
|
||||||
|
jpegParam.setDestination(destination);
|
||||||
|
// TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc...
|
||||||
|
// In the latter case we will have to use readAsRaster and do color conversion ourselves
|
||||||
|
jpegReader.read(0, jpegParam);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(100f);
|
||||||
|
|
||||||
|
if (abortRequested()) {
|
||||||
|
processReadAborted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The hard way: Read tables and re-create a full JFIF stream
|
||||||
|
|
||||||
|
processWarningOccurred("Old-style JPEG compressed TIFF without JFIF stream encountered. Attempting to re-create JFIF stream.");
|
||||||
|
|
||||||
|
// 519/JPEGQTables
|
||||||
|
// 520/JPEGDCTables
|
||||||
|
// 521/JPEGACTables
|
||||||
|
|
||||||
|
// These fields were originally intended to point to a list of offsets to the quantization tables, one per
|
||||||
|
// component. Each table consists of 64 BYTES (one for each DCT coefficient in the 8x8 block). The
|
||||||
|
// quantization tables are stored in zigzag order, and are compatible with the quantization tables
|
||||||
|
// usually found in a JPEG stream DQT marker.
|
||||||
|
|
||||||
|
// The original specification strongly recommended that, within the TIFF file, each component be
|
||||||
|
// assigned separate tables, and labelled this field as mandatory whenever the JPEGProc field specifies
|
||||||
|
// a DCT-based process.
|
||||||
|
|
||||||
|
// We've seen old-style JPEG in TIFF files where some or all Table offsets, contained the JPEGQTables,
|
||||||
|
// JPEGDCTables, and JPEGACTables tags are incorrect values beyond EOF. However, these files do always
|
||||||
|
// seem to contain a useful JPEGInterchangeFormat tag. Therefore, we recommend a careful attempt to read
|
||||||
|
// the Tables tags only as a last resort, if no table data is found in a JPEGInterchangeFormat stream.
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: If any of the q/dc/ac tables are equal (or have same offset, even if "spec" violation),
|
||||||
|
// use only the first occurrence, and update selectors in SOF0 and SOS
|
||||||
|
|
||||||
|
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_Q_TABLES, "JPEGQTables", true);
|
||||||
|
byte[][] qTables = new byte[qTablesOffsets.length][(int) (qTablesOffsets[1] - qTablesOffsets[0])]; // TODO: Using the offsets is fragile.. Use fixed length??
|
||||||
|
// byte[][] qTables = new byte[qTablesOffsets.length][64];
|
||||||
|
// System.err.println("qTables: " + qTables[0].length);
|
||||||
|
for (int j = 0; j < qTables.length; j++) {
|
||||||
|
imageInput.seek(qTablesOffsets[j]);
|
||||||
|
imageInput.readFully(qTables[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DC_TABLES, "JPEGDCTables", true);
|
||||||
|
byte[][] dcTables = new byte[dcTablesOffsets.length][(int) (dcTablesOffsets[1] - dcTablesOffsets[0])]; // TODO: Using the offsets is fragile.. Use fixed length??
|
||||||
|
// byte[][] dcTables = new byte[dcTablesOffsets.length][28];
|
||||||
|
// System.err.println("dcTables: " + dcTables[0].length);
|
||||||
|
for (int j = 0; j < dcTables.length; j++) {
|
||||||
|
imageInput.seek(dcTablesOffsets[j]);
|
||||||
|
imageInput.readFully(dcTables[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_AC_TABLES, "JPEGACTables", true);
|
||||||
|
byte[][] acTables = new byte[acTablesOffsets.length][(int) (acTablesOffsets[1] - acTablesOffsets[0])]; // TODO: Using the offsets is fragile.. Use fixed length??
|
||||||
|
// byte[][] acTables = new byte[acTablesOffsets.length][178];
|
||||||
|
// System.err.println("acTables: " + acTables[0].length);
|
||||||
|
for (int j = 0; j < acTables.length; j++) {
|
||||||
|
imageInput.seek(acTablesOffsets[j]);
|
||||||
|
imageInput.readFully(acTables[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
for (int y = 0; y < tilesDown; y++) {
|
||||||
|
int col = 0;
|
||||||
|
int rowsInTile = Math.min(stripTileHeight, height - row);
|
||||||
|
|
||||||
|
for (int x = 0; x < tilesAcross; x++) {
|
||||||
|
int colsInTile = Math.min(stripTileWidth, width - col);
|
||||||
|
int i = y * tilesAcross + x;
|
||||||
|
|
||||||
|
imageInput.seek(stripTileOffsets[i]);
|
||||||
|
stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
||||||
|
Arrays.asList(
|
||||||
|
createJFIFStream(raster, stripTileWidth, stripTileHeight, qTables, dcTables, acTables),
|
||||||
|
IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE),
|
||||||
|
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
||||||
|
)
|
||||||
|
)));
|
||||||
|
|
||||||
|
jpegReader.setInput(stream);
|
||||||
|
|
||||||
|
try {
|
||||||
|
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
|
||||||
|
jpegParam.setDestinationOffset(new Point(col, row));
|
||||||
|
jpegParam.setDestination(destination);
|
||||||
|
// TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc...
|
||||||
|
// In the latter case we will have to use readAsRaster and do color conversion ourselves
|
||||||
|
jpegReader.read(0, jpegParam);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abortRequested()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
col += colsInTile;
|
||||||
|
}
|
||||||
|
|
||||||
|
processImageProgress(100f * row / (float) height);
|
||||||
|
|
||||||
|
if (abortRequested()) {
|
||||||
|
processReadAborted();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
row += rowsInTile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
// Additionally, the specification defines these values as part of the TIFF extensions:
|
// Additionally, the specification defines these values as part of the TIFF extensions:
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
// CCITT Group 3 fax encoding
|
// CCITT Group 3 fax encoding
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
// CCITT Group 4 fax encoding
|
// CCITT Group 4 fax encoding
|
||||||
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
|
||||||
// JPEG ('old-style' JPEG, later overridden in Technote2)
|
// Known, but unsupported compression types
|
||||||
|
case TIFFCustom.COMPRESSION_NEXT:
|
||||||
|
case TIFFCustom.COMPRESSION_CCITTRLEW:
|
||||||
|
case TIFFCustom.COMPRESSION_THUNDERSCAN:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8CTPAD:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8LW:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8MP:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8BL:
|
||||||
|
case TIFFCustom.COMPRESSION_PIXARFILM:
|
||||||
|
case TIFFCustom.COMPRESSION_PIXARLOG:
|
||||||
|
case TIFFCustom.COMPRESSION_DCS:
|
||||||
|
case TIFFCustom.COMPRESSION_JBIG: // Doable with JBIG plugin?
|
||||||
|
case TIFFCustom.COMPRESSION_SGILOG:
|
||||||
|
case TIFFCustom.COMPRESSION_SGILOG24:
|
||||||
|
case TIFFCustom.COMPRESSION_JPEG2000: // Doable with JPEG2000 plugin?
|
||||||
|
|
||||||
throw new IIOException("Unsupported TIFF Compression value: " + compression);
|
throw new IIOException("Unsupported TIFF Compression value: " + compression);
|
||||||
default:
|
default:
|
||||||
@@ -682,13 +971,83 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor,
|
private static InputStream createJFIFStream(WritableRaster raster, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
|
||||||
final WritableRaster raster, final int numBands, final int col, final int startRow,
|
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
||||||
|
2 + 2 + 2 + 6 + 3 * raster.getNumBands() +
|
||||||
|
5 * qTables.length + qTables.length * qTables[0].length +
|
||||||
|
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
||||||
|
5 * acTables.length + acTables.length * acTables[0].length +
|
||||||
|
8 + 2 * raster.getNumBands()
|
||||||
|
);
|
||||||
|
|
||||||
|
DataOutputStream out = new DataOutputStream(stream);
|
||||||
|
|
||||||
|
out.writeShort(JPEG.SOI);
|
||||||
|
out.writeShort(JPEG.SOF0);
|
||||||
|
out.writeShort(2 + 6 + 3 * raster.getNumBands()); // SOF0 len
|
||||||
|
out.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support
|
||||||
|
out.writeShort(stripTileHeight); // height
|
||||||
|
out.writeShort(stripTileWidth); // width
|
||||||
|
out.writeByte(raster.getNumBands()); // Number of components
|
||||||
|
|
||||||
|
for (int comp = 0; comp < raster.getNumBands(); comp++) {
|
||||||
|
out.writeByte(comp); // Component id
|
||||||
|
out.writeByte(comp == 0 ? 0x22 : 0x11); // h/v subsampling TODO: FixMe, consult YCbCrSubsampling
|
||||||
|
out.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < qTables.length; tableIndex++) {
|
||||||
|
byte[] table = qTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DQT);
|
||||||
|
out.writeShort(3 + table.length); // DQT length
|
||||||
|
out.writeByte(tableIndex); // Q table id
|
||||||
|
out.write(table); // Table data
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < dcTables.length; tableIndex++) {
|
||||||
|
byte[] table = dcTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DHT);
|
||||||
|
out.writeShort(3 + table.length); // DHT length
|
||||||
|
out.writeByte(tableIndex); // Huffman table id
|
||||||
|
out.write(table); // Table data
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < acTables.length; tableIndex++) {
|
||||||
|
byte[] table = acTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DHT);
|
||||||
|
out.writeShort(3 + table.length); // DHT length
|
||||||
|
out.writeByte(0x10 + (tableIndex & 0xf)); // Huffman table id
|
||||||
|
out.write(table); // Table data
|
||||||
|
}
|
||||||
|
|
||||||
|
out.writeShort(JPEG.SOS);
|
||||||
|
out.writeShort(6 + 2 * raster.getNumBands()); // SOS length
|
||||||
|
out.writeByte(raster.getNumBands()); // Num comp
|
||||||
|
|
||||||
|
for (int component = 0; component < raster.getNumBands(); component++) {
|
||||||
|
out.writeByte(component); // Comp id
|
||||||
|
out.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown 3 bytes pad... TODO: Figure out what the last 3 bytes are...
|
||||||
|
out.writeByte(0);
|
||||||
|
out.writeByte(0);
|
||||||
|
out.writeByte(0);
|
||||||
|
|
||||||
|
return stream.createInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readStripTileData(final WritableRaster rowRaster, final int interpretation,
|
||||||
|
final WritableRaster raster, final int col, final int startRow,
|
||||||
final int colsInStrip, final int rowsInStrip, final DataInput input)
|
final int colsInStrip, final int rowsInStrip, final DataInput input)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
switch (rowRaster.getTransferType()) {
|
switch (rowRaster.getTransferType()) {
|
||||||
case DataBuffer.TYPE_BYTE:
|
case DataBuffer.TYPE_BYTE:
|
||||||
byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
|
|
||||||
for (int j = 0; j < rowsInStrip; j++) {
|
for (int j = 0; j < rowsInStrip; j++) {
|
||||||
int row = startRow + j;
|
int row = startRow + j;
|
||||||
|
|
||||||
@@ -697,19 +1056,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input.readFully(rowData);
|
input.readFully(rowData);
|
||||||
|
|
||||||
// for (int k = 0; k < rowData.length; k++) {
|
|
||||||
// try {
|
|
||||||
// rowData[k] = input.readByte();
|
|
||||||
// }
|
|
||||||
// catch (IOException e) {
|
|
||||||
// Arrays.fill(rowData, k, rowData.length, (byte) -1);
|
|
||||||
// System.err.printf("Unexpected EOF or bad data at [%d %d]\n", col + k, row);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
unPredict(predictor, colsInStrip, 1, numBands, rowData);
|
|
||||||
normalizeBlack(interpretation, rowData);
|
normalizeBlack(interpretation, rowData);
|
||||||
|
|
||||||
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
||||||
@@ -724,6 +1070,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
case DataBuffer.TYPE_USHORT:
|
case DataBuffer.TYPE_USHORT:
|
||||||
short [] rowDataShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
short [] rowDataShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||||
|
|
||||||
for (int j = 0; j < rowsInStrip; j++) {
|
for (int j = 0; j < rowsInStrip; j++) {
|
||||||
int row = startRow + j;
|
int row = startRow + j;
|
||||||
|
|
||||||
@@ -735,7 +1082,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
rowDataShort[k] = input.readShort();
|
rowDataShort[k] = input.readShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
unPredict(predictor, colsInStrip, 1, numBands, rowDataShort);
|
|
||||||
normalizeBlack(interpretation, rowDataShort);
|
normalizeBlack(interpretation, rowDataShort);
|
||||||
|
|
||||||
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
||||||
@@ -750,6 +1096,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
case DataBuffer.TYPE_INT:
|
case DataBuffer.TYPE_INT:
|
||||||
int [] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
int [] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
||||||
|
|
||||||
for (int j = 0; j < rowsInStrip; j++) {
|
for (int j = 0; j < rowsInStrip; j++) {
|
||||||
int row = startRow + j;
|
int row = startRow + j;
|
||||||
|
|
||||||
@@ -761,7 +1108,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
rowDataInt[k] = input.readInt();
|
rowDataInt[k] = input.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
unPredict(predictor, colsInStrip, 1, numBands, rowDataInt);
|
|
||||||
normalizeBlack(interpretation, rowDataInt);
|
normalizeBlack(interpretation, rowDataInt);
|
||||||
|
|
||||||
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
||||||
@@ -804,75 +1150,40 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("UnusedParameters")
|
private InputStream createDecompressorStream(final int compression, final int width, final InputStream stream) throws IOException {
|
||||||
private void unPredict(final int predictor, int scanLine, int rows, int bands, int[] data) throws IIOException {
|
|
||||||
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
|
|
||||||
switch (predictor) {
|
|
||||||
case TIFFBaseline.PREDICTOR_NONE:
|
|
||||||
break;
|
|
||||||
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
|
|
||||||
// TODO: Implement
|
|
||||||
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
|
|
||||||
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
|
|
||||||
default:
|
|
||||||
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("UnusedParameters")
|
|
||||||
private void unPredict(final int predictor, int scanLine, int rows, int bands, short[] data) throws IIOException {
|
|
||||||
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
|
|
||||||
switch (predictor) {
|
|
||||||
case TIFFBaseline.PREDICTOR_NONE:
|
|
||||||
break;
|
|
||||||
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
|
|
||||||
// TODO: Implement
|
|
||||||
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
|
|
||||||
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
|
|
||||||
default:
|
|
||||||
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unPredict(final int predictor, int scanLine, int rows, final int bands, byte[] data) throws IIOException {
|
|
||||||
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
|
|
||||||
switch (predictor) {
|
|
||||||
case TIFFBaseline.PREDICTOR_NONE:
|
|
||||||
break;
|
|
||||||
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
|
|
||||||
for (int y = 0; y < rows; y++) {
|
|
||||||
for (int x = 1; x < scanLine; x++) {
|
|
||||||
// TODO: For planar data (PlanarConfiguration == 2), treat as bands == 1
|
|
||||||
for (int b = 0; b < bands; b++) {
|
|
||||||
int off = y * scanLine + x;
|
|
||||||
data[off * bands + b] = (byte) (data[(off - 1) * bands + b] + data[off * bands + b]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
|
|
||||||
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
|
|
||||||
default:
|
|
||||||
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private InputStream createDecoderInputStream(final int compression, final InputStream stream) throws IOException {
|
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
|
case TIFFBaseline.COMPRESSION_NONE:
|
||||||
|
return stream;
|
||||||
case TIFFBaseline.COMPRESSION_PACKBITS:
|
case TIFFBaseline.COMPRESSION_PACKBITS:
|
||||||
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
|
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
|
||||||
case TIFFExtension.COMPRESSION_LZW:
|
case TIFFExtension.COMPRESSION_LZW:
|
||||||
return new DecoderStream(stream, new LZWDecoder(LZWDecoder.isOldBitReversedStream(stream)), 1024);
|
return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), 1024);
|
||||||
case TIFFExtension.COMPRESSION_ZLIB:
|
case TIFFExtension.COMPRESSION_ZLIB:
|
||||||
case TIFFExtension.COMPRESSION_DEFLATE:
|
|
||||||
// TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical
|
// TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical
|
||||||
|
case TIFFExtension.COMPRESSION_DEFLATE:
|
||||||
return new InflaterInputStream(stream, new Inflater(), 1024);
|
return new InflaterInputStream(stream, new Inflater(), 1024);
|
||||||
|
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
|
||||||
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
|
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||||
|
return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1));
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputStream createUnpredictorStream(final int predictor, final int width, final int samplesPerPixel, final int bitsPerSample, final InputStream stream, final ByteOrder byteOrder) throws IOException {
|
||||||
|
switch (predictor) {
|
||||||
|
case TIFFBaseline.PREDICTOR_NONE:
|
||||||
|
return stream;
|
||||||
|
case TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING:
|
||||||
|
return new HorizontalDeDifferencingStream(stream, width, samplesPerPixel, bitsPerSample, byteOrder);
|
||||||
|
case TIFFExtension.PREDICTOR_HORIZONTAL_FLOATINGPOINT:
|
||||||
|
throw new IIOException("Unsupported TIFF Predictor value: " + predictor);
|
||||||
|
default:
|
||||||
|
throw new IIOException("Unknown TIFF Predictor value: " + predictor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException {
|
private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException {
|
||||||
Entry entry = currentIFD.getEntryById(tag);
|
Entry entry = currentIFD.getEntryById(tag);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
@@ -893,7 +1204,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
short[] shorts = (short[]) entry.getValue();
|
short[] shorts = (short[]) entry.getValue();
|
||||||
value = new long[shorts.length];
|
value = new long[shorts.length];
|
||||||
|
|
||||||
for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
|
for (int i = 0, length = value.length; i < length; i++) {
|
||||||
value[i] = shorts[i];
|
value[i] = shorts[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -901,7 +1212,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
|||||||
int[] ints = (int[]) entry.getValue();
|
int[] ints = (int[]) entry.getValue();
|
||||||
value = new long[ints.length];
|
value = new long[ints.length];
|
||||||
|
|
||||||
for (int i = 0, stripOffsetsValueLength = value.length; i < stripOffsetsValueLength; i++) {
|
for (int i = 0, length = value.length; i < length; i++) {
|
||||||
value[i] = ints[i];
|
value[i] = ints[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+348
@@ -0,0 +1,348 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input stream that provides on-the-fly conversion and upsampling of TIFF susampled YCbCr samples to (raw) RGB samples.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||||
|
// NOTE: DO NOT MODIFY OR EXPOSE THIS ARRAY OUTSIDE PACKAGE!
|
||||||
|
static final double[] CCIR_601_1_COEFFICIENTS = new double[] {299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0};
|
||||||
|
|
||||||
|
private final int horizChromaSub;
|
||||||
|
private final int vertChromaSub;
|
||||||
|
private final int yCbCrPos;
|
||||||
|
private final int columns;
|
||||||
|
private final double[] coefficients;
|
||||||
|
|
||||||
|
private final int units;
|
||||||
|
private final int unitSize;
|
||||||
|
private final int padding;
|
||||||
|
private final byte[] decodedRows;
|
||||||
|
int decodedLength;
|
||||||
|
int decodedPos;
|
||||||
|
|
||||||
|
private final byte[] buffer;
|
||||||
|
int bufferLength;
|
||||||
|
int bufferPos;
|
||||||
|
|
||||||
|
public YCbCrUpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final double[] coefficients) {
|
||||||
|
super(Validate.notNull(stream, "stream"));
|
||||||
|
|
||||||
|
this.horizChromaSub = chromaSub[0];
|
||||||
|
this.vertChromaSub = chromaSub[1];
|
||||||
|
this.yCbCrPos = yCbCrPos;
|
||||||
|
this.columns = columns;
|
||||||
|
this.coefficients = Arrays.equals(CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients;
|
||||||
|
|
||||||
|
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
|
||||||
|
// For a 4:2 subsampled stream like this:
|
||||||
|
//
|
||||||
|
// Y0 Y1 Y2 Y3 Cb0 Cr0 Y8 Y9 Y10 Y11 Cb1 Cr1
|
||||||
|
// Y4 Y5 Y6 Y7 Y12Y13Y14 Y15
|
||||||
|
//
|
||||||
|
// In the stream, the order is: Y0,Y1,Y2..Y7,Cb0,Cr0, Y8...Y15,Cb1,Cr1, Y16...
|
||||||
|
|
||||||
|
unitSize = horizChromaSub * vertChromaSub + 2;
|
||||||
|
units = (columns + horizChromaSub - 1) / horizChromaSub; // If columns % horizChromasSub != 0...
|
||||||
|
padding = units * horizChromaSub - columns; // ...each coded row will be padded to fill unit
|
||||||
|
decodedRows = new byte[columns * vertChromaSub * 3];
|
||||||
|
buffer = new byte[unitSize * units];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetch() throws IOException {
|
||||||
|
if (bufferPos >= bufferLength) {
|
||||||
|
int pos = 0;
|
||||||
|
int read;
|
||||||
|
|
||||||
|
// This *SHOULD* read an entire row of units into the buffer, otherwise decodeRows will throw EOFException
|
||||||
|
while (pos < buffer.length && (read = in.read(buffer, pos, buffer.length - pos)) > 0) {
|
||||||
|
pos += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferLength = pos;
|
||||||
|
bufferPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bufferLength > 0) {
|
||||||
|
decodeRows();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
decodedLength = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decodeRows() throws EOFException {
|
||||||
|
decodedLength = decodedRows.length;
|
||||||
|
|
||||||
|
for (int u = 0; u < units; u++) {
|
||||||
|
if (bufferPos >= bufferLength) {
|
||||||
|
throw new EOFException("Unexpected end of stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode one unit
|
||||||
|
byte cb = buffer[bufferPos + unitSize - 2];
|
||||||
|
byte cr = buffer[bufferPos + unitSize - 1];
|
||||||
|
|
||||||
|
for (int y = 0; y < vertChromaSub; y++) {
|
||||||
|
for (int x = 0; x < horizChromaSub; x++) {
|
||||||
|
// Skip padding at end of row
|
||||||
|
int column = horizChromaSub * u + x;
|
||||||
|
if (column >= columns) {
|
||||||
|
bufferPos += padding;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pixelOff = 3 * (column + columns * y);
|
||||||
|
|
||||||
|
decodedRows[pixelOff] = buffer[bufferPos++];
|
||||||
|
decodedRows[pixelOff + 1] = cb;
|
||||||
|
decodedRows[pixelOff + 2] = cr;
|
||||||
|
|
||||||
|
// Convert to RGB
|
||||||
|
if (coefficients == null) {
|
||||||
|
YCbCrConverter.convertYCbCr2RGB(decodedRows, decodedRows, pixelOff);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferPos += 2; // Skip CbCr bytes at end of unit
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferPos = bufferLength;
|
||||||
|
decodedPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedRows[decodedPos++] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int read = Math.min(decodedLength - decodedPos, len);
|
||||||
|
System.arraycopy(decodedRows, decodedPos, b, off, read);
|
||||||
|
decodedPos += read;
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) throws IOException {
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decodedPos >= decodedLength) {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (decodedLength < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int skipped = (int) Math.min(decodedLength - decodedPos, n);
|
||||||
|
decodedPos += skipped;
|
||||||
|
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void reset() throws IOException {
|
||||||
|
throw new IOException("mark/reset not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
|
||||||
|
double y = (yCbCr[offset ] & 0xff);
|
||||||
|
double cb = (yCbCr[offset + 1] & 0xff) - 128; // TODO: The -128 part seems bogus... Consult ReferenceBlackWhite??? But default to these values?
|
||||||
|
double cr = (yCbCr[offset + 2] & 0xff) - 128;
|
||||||
|
|
||||||
|
double lumaRed = coefficients[0];
|
||||||
|
double lumaGreen = coefficients[1];
|
||||||
|
double lumaBlue = coefficients[2];
|
||||||
|
|
||||||
|
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
||||||
|
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
|
||||||
|
int green = (int) Math.round((y - lumaRed * (rgb[offset] & 0xff) - lumaBlue * (rgb[offset + 2] & 0xff)) / lumaGreen);
|
||||||
|
|
||||||
|
rgb[offset ] = clamp(red);
|
||||||
|
rgb[offset + 2] = clamp(blue);
|
||||||
|
rgb[offset + 1] = clamp(green);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte clamp(int val) {
|
||||||
|
return (byte) Math.max(0, Math.min(255, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This code is copied from JPEG package, make it "more" public: com.tm.imageio.color package?
|
||||||
|
/**
|
||||||
|
* Static inner class for lazy-loading of conversion tables.
|
||||||
|
*/
|
||||||
|
static final class YCbCrConverter {
|
||||||
|
/** Define tables for YCC->RGB color space conversion. */
|
||||||
|
private final static int SCALEBITS = 16;
|
||||||
|
private final static int MAXJSAMPLE = 255;
|
||||||
|
private final static int CENTERJSAMPLE = 128;
|
||||||
|
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||||
|
|
||||||
|
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes tables for YCC->RGB color space conversion.
|
||||||
|
*/
|
||||||
|
private static void buildYCCtoRGBtable() {
|
||||||
|
if (TIFFImageReader.DEBUG) {
|
||||||
|
System.err.println("Building YCC conversion table");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||||
|
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||||
|
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||||
|
// Cr=>R value is nearest int to 1.40200 * x
|
||||||
|
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cb=>B value is nearest int to 1.77200 * x
|
||||||
|
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||||
|
// Cr=>G value is scaled-up -0.71414 * x
|
||||||
|
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||||
|
// Cb=>G value is scaled-up -0.34414 * x
|
||||||
|
// We also add in ONE_HALF so that need not do it in inner loop
|
||||||
|
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
buildYCCtoRGBtable();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convertYCbCr2RGB(final Raster raster) {
|
||||||
|
final int height = raster.getHeight();
|
||||||
|
final int width = raster.getWidth();
|
||||||
|
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||||
|
|
||||||
|
for (int y = 0; y < height; y++) {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
convertYCbCr2RGB(data, data, (x + y * width) * 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||||
|
int y = yCbCr[offset ] & 0xff;
|
||||||
|
int cr = yCbCr[offset + 2] & 0xff;
|
||||||
|
int cb = yCbCr[offset + 1] & 0xff;
|
||||||
|
|
||||||
|
rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
|
||||||
|
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||||
|
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convertYCCK2CMYK(final Raster raster) {
|
||||||
|
final int height = raster.getHeight();
|
||||||
|
final int width = raster.getWidth();
|
||||||
|
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Inverted
|
||||||
|
int y = 255 - ycck[offset ] & 0xff;
|
||||||
|
int cb = 255 - ycck[offset + 1] & 0xff;
|
||||||
|
int cr = 255 - ycck[offset + 2] & 0xff;
|
||||||
|
int k = 255 - 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) {
|
||||||
|
// return (byte) Math.max(0, Math.min(255, val));
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
<!DOCTYPE com_sun_media_imageio_plugins_tiff_image_1.0 [
|
||||||
|
<!ELEMENT com_sun_media_imageio_plugins_tiff_image_1.0 (TIFFIFD)*>
|
||||||
|
|
||||||
|
<!ELEMENT TIFFIFD (TIFFField | TIFFIFD)*>
|
||||||
|
<!-- An IFD (directory) containing fields -->
|
||||||
|
<!ATTLIST TIFFIFD "tagSets" CDATA #REQUIRED>
|
||||||
|
<!-- Data type: String -->
|
||||||
|
<!ATTLIST TIFFIFD "parentTagNumber" CDATA #IMPLIED>
|
||||||
|
<!-- The tag number of the field pointing to this IFD -->
|
||||||
|
<!-- Data type: Integer -->
|
||||||
|
<!ATTLIST TIFFIFD "parentTagName" CDATA #IMPLIED>
|
||||||
|
<!-- A mnemonic name for the field pointing to this IFD, if known
|
||||||
|
-->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFField (TIFFBytes | TIFFAsciis | TIFFShorts | TIFFSShorts | TIFFLongs | TIFFSLongs | TIFFRationals | TIFFSRationals | TIFFFloats | TIFFDoubles | TIFFUndefined)>
|
||||||
|
<!-- A field containing data -->
|
||||||
|
<!ATTLIST TIFFField "number" CDATA #REQUIRED>
|
||||||
|
<!-- The tag number asociated with the field -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
<!ATTLIST TIFFField "name" CDATA #IMPLIED>
|
||||||
|
<!-- A mnemonic name associated with the field, if known -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFBytes (TIFFByte)*>
|
||||||
|
<!-- A sequence of TIFFByte nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFByte EMPTY>
|
||||||
|
<!-- An integral value between 0 and 255 -->
|
||||||
|
<!ATTLIST TIFFByte "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
<!ATTLIST TIFFByte "description" CDATA #IMPLIED>
|
||||||
|
<!-- A description, if available -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFAsciis (TIFFAscii)*>
|
||||||
|
<!-- A sequence of TIFFAscii nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFAscii EMPTY>
|
||||||
|
<!-- A String value -->
|
||||||
|
<!ATTLIST TIFFAscii "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFShorts (TIFFShort)*>
|
||||||
|
<!-- A sequence of TIFFShort nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFShort EMPTY>
|
||||||
|
<!-- An integral value between 0 and 65535 -->
|
||||||
|
<!ATTLIST TIFFShort "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
<!ATTLIST TIFFShort "description" CDATA #IMPLIED>
|
||||||
|
<!-- A description, if available -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFSShorts (TIFFSShort)*>
|
||||||
|
<!-- A sequence of TIFFSShort nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFSShort EMPTY>
|
||||||
|
<!-- An integral value between -32768 and 32767 -->
|
||||||
|
<!ATTLIST TIFFSShort "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
<!ATTLIST TIFFSShort "description" CDATA #IMPLIED>
|
||||||
|
<!-- A description, if available -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFLongs (TIFFLong)*>
|
||||||
|
<!-- A sequence of TIFFLong nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFLong EMPTY>
|
||||||
|
<!-- An integral value between 0 and 4294967295 -->
|
||||||
|
<!ATTLIST TIFFLong "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
<!ATTLIST TIFFLong "description" CDATA #IMPLIED>
|
||||||
|
<!-- A description, if available -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFSLongs (TIFFSLong)*>
|
||||||
|
<!-- A sequence of TIFFSLong nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFSLong EMPTY>
|
||||||
|
<!-- An integral value between -2147483648 and 2147482647 -->
|
||||||
|
<!ATTLIST TIFFSLong "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
<!ATTLIST TIFFSLong "description" CDATA #IMPLIED>
|
||||||
|
<!-- A description, if available -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFRationals (TIFFRational)*>
|
||||||
|
<!-- A sequence of TIFFRational nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFRational EMPTY>
|
||||||
|
<!-- A rational value consisting of an unsigned numerator and
|
||||||
|
denominator -->
|
||||||
|
<!ATTLIST TIFFRational "value" CDATA #IMPLIED>
|
||||||
|
<!-- The numerator and denominator, separated by a slash -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFSRationals (TIFFSRational)*>
|
||||||
|
<!-- A sequence of TIFFSRational nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFSRational EMPTY>
|
||||||
|
<!-- A rational value consisting of a signed numerator and
|
||||||
|
denominator -->
|
||||||
|
<!ATTLIST TIFFSRational "value" CDATA #IMPLIED>
|
||||||
|
<!-- The numerator and denominator, separated by a slash -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFFloats (TIFFFloat)*>
|
||||||
|
<!-- A sequence of TIFFFloat nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFFloat EMPTY>
|
||||||
|
<!-- A single-precision floating-point value -->
|
||||||
|
<!ATTLIST TIFFFloat "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFDoubles (TIFFDouble)*>
|
||||||
|
<!-- A sequence of TIFFDouble nodes -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFDouble EMPTY>
|
||||||
|
<!-- A double-precision floating-point value -->
|
||||||
|
<!ATTLIST TIFFDouble "value" CDATA #IMPLIED>
|
||||||
|
<!-- The value -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
|
||||||
|
<!ELEMENT TIFFUndefined EMPTY>
|
||||||
|
<!-- Uninterpreted byte data -->
|
||||||
|
<!ATTLIST TIFFUndefined "value" CDATA #IMPLIED>
|
||||||
|
<!-- A list of comma-separated byte values -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
]>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<!DOCTYPE com_sun_media_imageio_plugins_tiff_stream_1.0 [
|
||||||
|
<!ELEMENT com_sun_media_imageio_plugins_tiff_stream_1.0 (ByteOrder)>
|
||||||
|
|
||||||
|
<!ELEMENT ByteOrder EMPTY>
|
||||||
|
<!-- The stream byte order -->
|
||||||
|
<!ATTLIST ByteOrder "value" CDATA #REQUIRED>
|
||||||
|
<!-- One of "BIG_ENDIAN" or "LITTLE_ENDIAN" -->
|
||||||
|
<!-- Data type: String -->
|
||||||
|
]>
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.tiff;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CCITTFaxDecoderStreamTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class CCITTFaxDecoderStreamTest {
|
||||||
|
|
||||||
|
// TODO: Better tests (full A4 width scan lines?)
|
||||||
|
|
||||||
|
// From http://www.mikekohn.net/file_formats/tiff.php
|
||||||
|
static final byte[] DATA_TYPE_2 = {
|
||||||
|
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||||
|
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||||
|
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
|
||||||
|
(byte) 0x7d, (byte) 0xc0, // 01111101 11000000
|
||||||
|
};
|
||||||
|
|
||||||
|
static final byte[] DATA_TYPE_3 = {
|
||||||
|
0x00, 0x01, (byte) 0xc2, 0x70,
|
||||||
|
0x00, 0x01, 0x70,
|
||||||
|
0x01,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
static final byte[] DATA_TYPE_4 = {
|
||||||
|
0x26, (byte) 0xb0, 95, (byte) 0xfa, (byte) 0xc0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Image should be (6 x 4):
|
||||||
|
// 1 1 1 0 1 1 x x
|
||||||
|
// 1 1 1 0 1 1 x x
|
||||||
|
// 1 1 1 0 1 1 x x
|
||||||
|
// 1 1 0 0 1 1 x x
|
||||||
|
BufferedImage image;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);
|
||||||
|
for (int y = 0; y < 4; y++) {
|
||||||
|
for (int x = 0; x < 6; x++) {
|
||||||
|
image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image.setRGB(2, 3, 0xff000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadCountType2() throws IOException {
|
||||||
|
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
int read;
|
||||||
|
while ((read = stream.read()) >= 0) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just make sure we'll have 4 bytes
|
||||||
|
assertEquals(4, count);
|
||||||
|
|
||||||
|
// Verify that we don't return arbitrary values
|
||||||
|
assertEquals(-1, read);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeType2() throws IOException {
|
||||||
|
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
|
||||||
|
|
||||||
|
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
|
byte[] bytes = new byte[imageData.length];
|
||||||
|
new DataInputStream(stream).readFully(bytes);
|
||||||
|
|
||||||
|
// JPanel panel = new JPanel();
|
||||||
|
// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
|
||||||
|
// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
|
||||||
|
// JOptionPane.showConfirmDialog(null, panel);
|
||||||
|
|
||||||
|
assertArrayEquals(imageData, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testDecodeType3() throws IOException {
|
||||||
|
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_3), 6, TIFFExtension.COMPRESSION_CCITT_T4, 1);
|
||||||
|
|
||||||
|
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
|
byte[] bytes = new byte[imageData.length];
|
||||||
|
DataInputStream dataInput = new DataInputStream(stream);
|
||||||
|
|
||||||
|
for (int y = 0; y < image.getHeight(); y++) {
|
||||||
|
System.err.println("y: " + y);
|
||||||
|
dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
// JPanel panel = new JPanel();
|
||||||
|
// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
|
||||||
|
// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
|
||||||
|
// JOptionPane.showConfirmDialog(null, panel);
|
||||||
|
|
||||||
|
assertArrayEquals(imageData, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testDecodeType4() throws IOException {
|
||||||
|
InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_4), 6, TIFFExtension.COMPRESSION_CCITT_T6, 1);
|
||||||
|
|
||||||
|
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
|
||||||
|
byte[] bytes = new byte[imageData.length];
|
||||||
|
DataInputStream dataInput = new DataInputStream(stream);
|
||||||
|
|
||||||
|
for (int y = 0; y < image.getHeight(); y++) {
|
||||||
|
System.err.println("y: " + y);
|
||||||
|
dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
|
||||||
|
}
|
||||||
|
|
||||||
|
// JPanel panel = new JPanel();
|
||||||
|
// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
|
||||||
|
// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
|
||||||
|
// JOptionPane.showConfirmDialog(null, panel);
|
||||||
|
|
||||||
|
assertArrayEquals(imageData, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
+569
@@ -0,0 +1,569 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||||
|
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||||
|
import com.twelvemonkeys.io.LittleEndianDataOutputStream;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HorizontalDeDifferencingStreamTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: HorizontalDeDifferencingStreamTest.java,v 1.0 13.03.13 12:46 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class HorizontalDeDifferencingStreamTest {
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP1BPS() throws IOException {
|
||||||
|
// 1 sample per pixel, 1 bits per sample (mono/indexed)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0x80, 0x00, 0x00,
|
||||||
|
0x71, 0x11, 0x44,
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 24, 1, 1, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0x5e, stream.read());
|
||||||
|
assertEquals(0x1e, stream.read());
|
||||||
|
assertEquals(0x78, stream.read());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP2BPS() throws IOException {
|
||||||
|
// 1 sample per pixel, 2 bits per sample (gray/indexed)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0xc0, 0x00, 0x00, 0x00,
|
||||||
|
0x71, 0x11, 0x44, (byte) 0xcc,
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 16, 1, 2, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0x41, stream.read());
|
||||||
|
assertEquals(0x6b, stream.read());
|
||||||
|
assertEquals(0x05, stream.read());
|
||||||
|
assertEquals(0x0f, stream.read());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP4BPS() throws IOException {
|
||||||
|
// 1 sample per pixel, 4 bits per sample (gray/indexed)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0xf0, 0x00, 0x00, 0x00,
|
||||||
|
0x70, 0x11, 0x44, (byte) 0xcc,
|
||||||
|
0x00, 0x01, 0x10, (byte) 0xe0
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 8, 1, 4, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0x77, stream.read());
|
||||||
|
assertEquals(0x89, stream.read());
|
||||||
|
assertEquals(0xd1, stream.read());
|
||||||
|
assertEquals(0xd9, stream.read());
|
||||||
|
|
||||||
|
// Row 3
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x01, stream.read());
|
||||||
|
assertEquals(0x22, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP8BPS() throws IOException {
|
||||||
|
// 1 sample per pixel, 8 bits per sample (gray/indexed)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0xff, 0, 0, 0,
|
||||||
|
0x7f, 1, 4, -4,
|
||||||
|
0x00, 127, 127, -127
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
|
||||||
|
// Row 3
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0xfe, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadArray1SPP8BPS() throws IOException {
|
||||||
|
// 1 sample per pixel, 8 bits per sample (gray/indexed)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0xff, 0, 0, 0,
|
||||||
|
0x7f, 1, 4, -4,
|
||||||
|
0x00, 127, 127, -127
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
byte[] result = new byte[data.length];
|
||||||
|
new DataInputStream(stream).readFully(result);
|
||||||
|
|
||||||
|
assertArrayEquals(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
|
||||||
|
0x7f, (byte) 0x80, (byte) 0x84, (byte) 0x80,
|
||||||
|
0x00, 0x7f, (byte) 0xfe, 0x7f,
|
||||||
|
},
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read(new byte[16]));
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP32BPS() throws IOException {
|
||||||
|
// 1 sample per pixel, 32 bits per sample (gray)
|
||||||
|
FastByteArrayOutputStream out = new FastByteArrayOutputStream(16);
|
||||||
|
DataOutput dataOut = new DataOutputStream(out);
|
||||||
|
dataOut.writeInt(0x00000000);
|
||||||
|
dataOut.writeInt(305419896);
|
||||||
|
dataOut.writeInt(305419896);
|
||||||
|
dataOut.writeInt(-610839792);
|
||||||
|
|
||||||
|
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.BIG_ENDIAN);
|
||||||
|
DataInput dataIn = new DataInputStream(in);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0, dataIn.readInt());
|
||||||
|
assertEquals(305419896, dataIn.readInt());
|
||||||
|
assertEquals(610839792, dataIn.readInt());
|
||||||
|
assertEquals(0, dataIn.readInt());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP32BPSLittleEndian() throws IOException {
|
||||||
|
// 1 sample per pixel, 32 bits per sample (gray)
|
||||||
|
FastByteArrayOutputStream out = new FastByteArrayOutputStream(16);
|
||||||
|
DataOutput dataOut = new LittleEndianDataOutputStream(out);
|
||||||
|
dataOut.writeInt(0x00000000);
|
||||||
|
dataOut.writeInt(305419896);
|
||||||
|
dataOut.writeInt(305419896);
|
||||||
|
dataOut.writeInt(-610839792);
|
||||||
|
|
||||||
|
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
DataInput dataIn = new LittleEndianDataInputStream(in);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0, dataIn.readInt());
|
||||||
|
assertEquals(305419896, dataIn.readInt());
|
||||||
|
assertEquals(610839792, dataIn.readInt());
|
||||||
|
assertEquals(0, dataIn.readInt());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP64BPS() throws IOException {
|
||||||
|
// 1 sample per pixel, 64 bits per sample (gray)
|
||||||
|
FastByteArrayOutputStream out = new FastByteArrayOutputStream(32);
|
||||||
|
DataOutput dataOut = new DataOutputStream(out);
|
||||||
|
dataOut.writeLong(0x00000000);
|
||||||
|
dataOut.writeLong(81985529216486895L);
|
||||||
|
dataOut.writeLong(81985529216486895L);
|
||||||
|
dataOut.writeLong(-163971058432973790L);
|
||||||
|
|
||||||
|
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.BIG_ENDIAN);
|
||||||
|
DataInput dataIn = new DataInputStream(in);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0, dataIn.readLong());
|
||||||
|
assertEquals(81985529216486895L, dataIn.readLong());
|
||||||
|
assertEquals(163971058432973790L, dataIn.readLong());
|
||||||
|
assertEquals(0, dataIn.readLong());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead1SPP64BPSLittleEndian() throws IOException {
|
||||||
|
// 1 sample per pixel, 64 bits per sample (gray)
|
||||||
|
FastByteArrayOutputStream out = new FastByteArrayOutputStream(32);
|
||||||
|
DataOutput dataOut = new LittleEndianDataOutputStream(out);
|
||||||
|
dataOut.writeLong(0x00000000);
|
||||||
|
dataOut.writeLong(81985529216486895L);
|
||||||
|
dataOut.writeLong(81985529216486895L);
|
||||||
|
dataOut.writeLong(-163971058432973790L);
|
||||||
|
|
||||||
|
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
DataInput dataIn = new LittleEndianDataInputStream(in);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0, dataIn.readLong());
|
||||||
|
assertEquals(81985529216486895L, dataIn.readLong());
|
||||||
|
assertEquals(163971058432973790L, dataIn.readLong());
|
||||||
|
assertEquals(0, dataIn.readLong());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead3SPP8BPS() throws IOException {
|
||||||
|
// 3 samples per pixel, 8 bits per sample (RGB)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0xff, (byte) 0x00, (byte) 0x7f, -1, -1, -1, -4, -4, -4, 4, 4, 4,
|
||||||
|
0x7f, 0x7f, 0x7f, 1, 1, 1, 4, 4, 4, -4, -4, -4,
|
||||||
|
0x00, 0x00, 0x00, 127, -127, 0, -127, 127, 0, 0, 0, 127,
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 3, 8, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0xfe, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0x7e, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0xfa, stream.read());
|
||||||
|
assertEquals(0xfb, stream.read());
|
||||||
|
assertEquals(0x7a, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0xfe, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0x7e, stream.read());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
|
||||||
|
// Row 3
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x81, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead3SPP16BPS() throws IOException {
|
||||||
|
FastByteArrayOutputStream out = new FastByteArrayOutputStream(24);
|
||||||
|
DataOutput dataOut = new DataOutputStream(out);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(-9320);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
dataOut.writeShort(-9320);
|
||||||
|
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
|
||||||
|
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.BIG_ENDIAN);
|
||||||
|
DataInput dataIn = new DataInputStream(in);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(4660, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(4660, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(9320, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(9320, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead3SPP16BPSLittleEndian() throws IOException {
|
||||||
|
FastByteArrayOutputStream out = new FastByteArrayOutputStream(24);
|
||||||
|
DataOutput dataOut = new LittleEndianDataOutputStream(out);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(4660);
|
||||||
|
dataOut.writeShort(-9320);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
dataOut.writeShort(-9320);
|
||||||
|
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(0x0000);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(30292);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
dataOut.writeShort(-60584);
|
||||||
|
|
||||||
|
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.LITTLE_ENDIAN);
|
||||||
|
DataInput dataIn = new LittleEndianDataInputStream(in);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(4660, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(4660, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(9320, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(9320, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(30292, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(60584, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
assertEquals(0, dataIn.readUnsignedShort());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, in.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRead4SPP8BPS() throws IOException {
|
||||||
|
// 4 samples per pixel, 8 bits per sample (RGBA)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
|
||||||
|
0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// Row 1
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x00, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0xfe, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0x7e, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0xfa, stream.read());
|
||||||
|
assertEquals(0xfb, stream.read());
|
||||||
|
assertEquals(0x7a, stream.read());
|
||||||
|
assertEquals(0xfb, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0xfe, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
assertEquals(0x7e, stream.read());
|
||||||
|
assertEquals(0xff, stream.read());
|
||||||
|
|
||||||
|
// Row 2
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
assertEquals(0x7f, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
assertEquals(0x84, stream.read());
|
||||||
|
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
assertEquals(0x80, stream.read());
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadArray4SPP8BPS() throws IOException {
|
||||||
|
// 4 samples per pixel, 8 bits per sample (RGBA)
|
||||||
|
byte[] data = {
|
||||||
|
(byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
|
||||||
|
0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
|
||||||
|
};
|
||||||
|
|
||||||
|
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
byte[] result = new byte[data.length];
|
||||||
|
new DataInputStream(stream).readFully(result);
|
||||||
|
|
||||||
|
assertArrayEquals(
|
||||||
|
new byte[] {
|
||||||
|
(byte) 0xff, 0x00, 0x7f, 0x00,
|
||||||
|
(byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
|
||||||
|
(byte) 0xfa, (byte) 0xfb, 0x7a, (byte) 0xfb,
|
||||||
|
(byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
|
||||||
|
|
||||||
|
0x7f, 0x7f, 0x7f, 0x7f,
|
||||||
|
(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
|
||||||
|
(byte) 0x84, (byte) 0x84, (byte) 0x84, (byte) 0x84,
|
||||||
|
(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
|
||||||
|
},
|
||||||
|
result
|
||||||
|
);
|
||||||
|
|
||||||
|
// EOF
|
||||||
|
assertEquals(-1, stream.read(new byte[16]));
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-13
@@ -30,7 +30,6 @@ import com.twelvemonkeys.io.enc.Decoder;
|
|||||||
import com.twelvemonkeys.io.enc.DecoderAbstractTestCase;
|
import com.twelvemonkeys.io.enc.DecoderAbstractTestCase;
|
||||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||||
import com.twelvemonkeys.io.enc.Encoder;
|
import com.twelvemonkeys.io.enc.Encoder;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -60,24 +59,15 @@ public class LZWDecoderTest extends DecoderAbstractTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testShortBitReversedStream() throws IOException {
|
public void testShortBitReversedStream() throws IOException {
|
||||||
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short.bin"), new LZWDecoder(true), 128);
|
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short.bin"), LZWDecoder.create(true), 128);
|
||||||
InputStream unpacked = new ByteArrayInputStream(new byte[512 * 3 * 5]); // Should be all 0's
|
InputStream unpacked = new ByteArrayInputStream(new byte[512 * 3 * 5]); // Should be all 0's
|
||||||
|
|
||||||
assertSameStreamContents(unpacked, stream);
|
assertSameStreamContents(unpacked, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore("Known issue")
|
|
||||||
@Test
|
|
||||||
public void testShortBitReversedStreamLine45To49() throws IOException {
|
|
||||||
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-short-45-49.bin"), new LZWDecoder(true), 128);
|
|
||||||
InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-short-45-49.bin");
|
|
||||||
|
|
||||||
assertSameStreamContents(unpacked, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLongStream() throws IOException {
|
public void testLongStream() throws IOException {
|
||||||
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), new LZWDecoder(), 1024);
|
InputStream stream = new DecoderStream(getClass().getResourceAsStream("/lzw/lzw-long.bin"), LZWDecoder.create(false), 1024);
|
||||||
InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-long.bin");
|
InputStream unpacked = getClass().getResourceAsStream("/lzw/unpacked-long.bin");
|
||||||
|
|
||||||
assertSameStreamContents(unpacked, stream);
|
assertSameStreamContents(unpacked, stream);
|
||||||
@@ -111,7 +101,7 @@ public class LZWDecoderTest extends DecoderAbstractTestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Decoder createDecoder() {
|
public Decoder createDecoder() {
|
||||||
return new LZWDecoder();
|
return LZWDecoder.create(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+8
-1
@@ -52,10 +52,15 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase<TIFFImageRe
|
|||||||
new TestData(getClassLoaderResource("/tiff/sm_colors_tile.tif"), new Dimension(64, 64)), // RGB, uncompressed, tiled
|
new TestData(getClassLoaderResource("/tiff/sm_colors_tile.tif"), new Dimension(64, 64)), // RGB, uncompressed, tiled
|
||||||
new TestData(getClassLoaderResource("/tiff/sm_colors_pb_tile.tif"), new Dimension(64, 64)), // RGB, PackBits compressed, tiled
|
new TestData(getClassLoaderResource("/tiff/sm_colors_pb_tile.tif"), new Dimension(64, 64)), // RGB, PackBits compressed, tiled
|
||||||
new TestData(getClassLoaderResource("/tiff/galaxy.tif"), new Dimension(965, 965)), // RGB, LZW compressed
|
new TestData(getClassLoaderResource("/tiff/galaxy.tif"), new Dimension(965, 965)), // RGB, LZW compressed
|
||||||
|
new TestData(getClassLoaderResource("/tiff/quad-lzw.tif"), new Dimension(512, 384)), // RGB, Old spec (reversed) LZW compressed, tiled
|
||||||
new TestData(getClassLoaderResource("/tiff/bali.tif"), new Dimension(725, 489)), // Palette-based, LZW compressed
|
new TestData(getClassLoaderResource("/tiff/bali.tif"), new Dimension(725, 489)), // Palette-based, LZW compressed
|
||||||
new TestData(getClassLoaderResource("/tiff/f14.tif"), new Dimension(640, 480)), // Gray, uncompressed
|
new TestData(getClassLoaderResource("/tiff/f14.tif"), new Dimension(640, 480)), // Gray, uncompressed
|
||||||
new TestData(getClassLoaderResource("/tiff/marbles.tif"), new Dimension(1419, 1001)), // RGB, LZW compressed w/predictor
|
new TestData(getClassLoaderResource("/tiff/marbles.tif"), new Dimension(1419, 1001)), // RGB, LZW compressed w/predictor
|
||||||
new TestData(getClassLoaderResource("/tiff/chifley_logo.tif"), new Dimension(591, 177)) // CMYK, uncompressed
|
new TestData(getClassLoaderResource("/tiff/chifley_logo.tif"), new Dimension(591, 177)), // CMYK, uncompressed
|
||||||
|
new TestData(getClassLoaderResource("/tiff/ycbcr-cat.tif"), new Dimension(250, 325)), // YCbCr, LZW compressed
|
||||||
|
new TestData(getClassLoaderResource("/tiff/quad-jpeg.tif"), new Dimension(512, 384)), // YCbCr, JPEG compressed, striped
|
||||||
|
new TestData(getClassLoaderResource("/tiff/smallliz.tif"), new Dimension(160, 160)), // YCbCr, Old-Style JPEG compressed (full JFIF stream)
|
||||||
|
new TestData(getClassLoaderResource("/tiff/zackthecat.tif"), new Dimension(234, 213)) // YCbCr, Old-Style JPEG compressed (tables, no JFIF stream)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,4 +93,6 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase<TIFFImageRe
|
|||||||
protected List<String> getMIMETypes() {
|
protected List<String> getMIMETypes() {
|
||||||
return Arrays.asList("image/tiff");
|
return Arrays.asList("image/tiff");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Test YCbCr colors
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-8
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2012, Harald Kuhr
|
* Copyright (c) 2013, Harald Kuhr
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@@ -28,20 +28,24 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
import com.twelvemonkeys.io.enc.Decoder;
|
import com.twelvemonkeys.io.InputStreamAbstractTestCase;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CCITT Group 3 One-Dimensional (G31D) "No EOLs" Decoder.
|
* YCbCrUpsamplerStreamTest
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: G31DDecoder.java,v 1.0 23.05.12 15:55 haraldk Exp$
|
* @version $Id: YCbCrUpsamplerStreamTest.java,v 1.0 31.01.13 14:35 haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class G31DDecoder implements Decoder {
|
@Ignore
|
||||||
public int decode(final InputStream stream, final byte[] buffer) throws IOException {
|
public class YCbCrUpsamplerStreamTest extends InputStreamAbstractTestCase {
|
||||||
throw new UnsupportedOperationException("Method decode not implemented"); // TODO: Implement
|
// TODO: Implement + add @Ignore for all tests that makes no sense for this class.
|
||||||
|
@Override
|
||||||
|
protected InputStream makeInputStream(byte[] pBytes) {
|
||||||
|
return new YCbCrUpsamplerStream(new ByteArrayInputStream(pBytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, pBytes.length / 4, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -90,6 +90,7 @@
|
|||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-all</artifactId>
|
<artifactId>mockito-all</artifactId>
|
||||||
<version>1.8.5</version>
|
<version>1.8.5</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
+475
-44
@@ -30,6 +30,7 @@ package com.twelvemonkeys.image;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
import com.twelvemonkeys.util.LRUHashMap;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.ImageReadParam;
|
import javax.imageio.ImageReadParam;
|
||||||
@@ -38,17 +39,17 @@ import javax.imageio.ImageTypeSpecifier;
|
|||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBuffer;
|
import java.awt.image.DataBuffer;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.lang.ref.Reference;
|
||||||
import java.util.Random;
|
import java.lang.ref.SoftReference;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.*;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MappedBufferImage
|
* MappedBufferImage
|
||||||
@@ -59,7 +60,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
*/
|
*/
|
||||||
public class MappedBufferImage {
|
public class MappedBufferImage {
|
||||||
private static int threads = Runtime.getRuntime().availableProcessors();
|
private static int threads = Runtime.getRuntime().availableProcessors();
|
||||||
private static ExecutorService executorService = Executors.newFixedThreadPool(threads);
|
private static ExecutorService executorService = Executors.newFixedThreadPool(threads * 4);
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
int argIndex = 0;
|
int argIndex = 0;
|
||||||
@@ -91,8 +92,9 @@ public class MappedBufferImage {
|
|||||||
|
|
||||||
// TODO: Negotiate best layout according to the GraphicsConfiguration.
|
// TODO: Negotiate best layout according to the GraphicsConfiguration.
|
||||||
|
|
||||||
w = reader.getWidth(0);
|
int sub = 1;
|
||||||
h = reader.getHeight(0);
|
w = reader.getWidth(0) / sub;
|
||||||
|
h = reader.getHeight(0) / sub;
|
||||||
|
|
||||||
// GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
|
// GraphicsConfiguration configuration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
|
||||||
// ColorModel cm2 = configuration.getColorModel(cm.getTransparency());
|
// ColorModel cm2 = configuration.getColorModel(cm.getTransparency());
|
||||||
@@ -111,8 +113,11 @@ public class MappedBufferImage {
|
|||||||
|
|
||||||
System.out.println("image = " + image);
|
System.out.println("image = " + image);
|
||||||
|
|
||||||
|
// TODO: Display image while reading
|
||||||
|
|
||||||
ImageReadParam param = reader.getDefaultReadParam();
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
param.setDestination(image);
|
param.setDestination(image);
|
||||||
|
param.setSourceSubsampling(sub, sub, 0, 0);
|
||||||
|
|
||||||
reader.addIIOReadProgressListener(new ConsoleProgressListener());
|
reader.addIIOReadProgressListener(new ConsoleProgressListener());
|
||||||
reader.read(0, param);
|
reader.read(0, param);
|
||||||
@@ -166,7 +171,7 @@ public class MappedBufferImage {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||||
JScrollPane scroll = new JScrollPane(new ImageComponent(image));
|
JScrollPane scroll = new JScrollPane(new ImageComponent(image));
|
||||||
scroll.setBorder(BorderFactory.createEmptyBorder());
|
scroll.setBorder(BorderFactory.createEmptyBorder());
|
||||||
frame.add(scroll);
|
frame.add(scroll);
|
||||||
@@ -184,13 +189,24 @@ public class MappedBufferImage {
|
|||||||
// NOTE: The createCompatibleDestImage takes the byte order/layout into account, unlike the cm.createCompatibleWritableRaster
|
// NOTE: The createCompatibleDestImage takes the byte order/layout into account, unlike the cm.createCompatibleWritableRaster
|
||||||
final BufferedImage output = new ResampleOp(width, height).createCompatibleDestImage(image, null);
|
final BufferedImage output = new ResampleOp(width, height).createCompatibleDestImage(image, null);
|
||||||
|
|
||||||
final int inStep = (int) Math.ceil(image.getHeight() / (double) threads);
|
final int steps = threads * height / 100;
|
||||||
final int outStep = (int) Math.ceil(height / (double) threads);
|
final int inStep = (int) Math.ceil(image.getHeight() / (double) steps);
|
||||||
|
final int outStep = (int) Math.ceil(height / (double) steps);
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(threads);
|
final CountDownLatch latch = new CountDownLatch(steps);
|
||||||
|
|
||||||
|
// System.out.println("Starting image scale on single thread, waiting for execution to complete...");
|
||||||
|
// BufferedImage output = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||||
|
System.out.printf("Started image scale on %d threads, waiting for execution to complete...\n", threads);
|
||||||
|
|
||||||
|
System.out.print("[");
|
||||||
|
final int dotsPerStep = 78 / steps;
|
||||||
|
for (int j = 0; j < 78 - (steps * dotsPerStep); j++) {
|
||||||
|
System.out.print(".");
|
||||||
|
}
|
||||||
|
|
||||||
// Resample image in slices
|
// Resample image in slices
|
||||||
for (int i = 0; i < threads; i++) {
|
for (int i = 0; i < steps; i++) {
|
||||||
final int inY = i * inStep;
|
final int inY = i * inStep;
|
||||||
final int outY = i * outStep;
|
final int outY = i * outStep;
|
||||||
final int inHeight = Math.min(inStep, image.getHeight() - inY);
|
final int inHeight = Math.min(inStep, image.getHeight() - inY);
|
||||||
@@ -200,10 +216,12 @@ public class MappedBufferImage {
|
|||||||
try {
|
try {
|
||||||
BufferedImage in = image.getSubimage(0, inY, image.getWidth(), inHeight);
|
BufferedImage in = image.getSubimage(0, inY, image.getWidth(), inHeight);
|
||||||
BufferedImage out = output.getSubimage(0, outY, width, outHeight);
|
BufferedImage out = output.getSubimage(0, outY, width, outHeight);
|
||||||
new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, out);
|
new ResampleOp(width, outHeight, ResampleOp.FILTER_TRIANGLE).filter(in, out);
|
||||||
// new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).resample(in, out, ResampleOp.createFilter(ResampleOp.FILTER_LANCZOS));
|
// new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, out);
|
||||||
// BufferedImage out = new ResampleOp(width, outHeight, ResampleOp.FILTER_LANCZOS).filter(in, null);
|
|
||||||
// ImageUtil.drawOnto(output.getSubimage(0, outY, width, outHeight), out);
|
for (int j = 0; j < dotsPerStep; j++) {
|
||||||
|
System.out.print(".");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (RuntimeException e) {
|
catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -216,19 +234,17 @@ public class MappedBufferImage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// System.out.println("Starting image scale on single thread, waiting for execution to complete...");
|
|
||||||
// BufferedImage output = new ResampleOp(width, height, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
|
||||||
System.out.printf("Started image scale on %d threads, waiting for execution to complete...%n", threads);
|
|
||||||
|
|
||||||
Boolean done = null;
|
Boolean done = null;
|
||||||
try {
|
try {
|
||||||
done = latch.await(5L, TimeUnit.MINUTES);
|
done = latch.await(5L, TimeUnit.MINUTES);
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignore) {
|
catch (InterruptedException ignore) {
|
||||||
}
|
}
|
||||||
|
System.out.println("]");
|
||||||
|
|
||||||
System.out.printf("%s scaling image in %d ms%n", (done == null ? "Interrupted" : !done ? "Timed out" : "Done"), System.currentTimeMillis() - start);
|
System.out.printf("%s scaling image in %d ms\n", (done == null ? "Interrupted" : !done ? "Timed out" : "Done"), System.currentTimeMillis() - start);
|
||||||
System.out.println("image = " + output);
|
System.out.println("image = " + output);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,10 +374,12 @@ public class MappedBufferImage {
|
|||||||
private static class ImageComponent extends JComponent implements Scrollable {
|
private static class ImageComponent extends JComponent implements Scrollable {
|
||||||
private final BufferedImage image;
|
private final BufferedImage image;
|
||||||
private Paint texture;
|
private Paint texture;
|
||||||
double zoom = 1;
|
private double zoom = 1;
|
||||||
|
|
||||||
public ImageComponent(final BufferedImage image) {
|
public ImageComponent(final BufferedImage image) {
|
||||||
setOpaque(true); // Very important when subclassing JComponent...
|
setOpaque(true); // Very important when sub classing JComponent...
|
||||||
|
setDoubleBuffered(true);
|
||||||
|
|
||||||
this.image = image;
|
this.image = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,6 +388,68 @@ public class MappedBufferImage {
|
|||||||
super.addNotify();
|
super.addNotify();
|
||||||
|
|
||||||
texture = createTexture();
|
texture = createTexture();
|
||||||
|
|
||||||
|
Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
||||||
|
zoom = Math.min(1.0, Math.min(bounds.getWidth() / (double) image.getWidth(), bounds.getHeight() / (double) image.getHeight()));
|
||||||
|
|
||||||
|
// TODO: Take scroll pane into account when zooming (center around center point)
|
||||||
|
AbstractAction zoomIn = new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
System.err.println("ZOOM IN");
|
||||||
|
setZoom(zoom * 2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
addAction(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, getToolkit().getMenuShortcutKeyMask()), zoomIn);
|
||||||
|
addAction(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, getToolkit().getMenuShortcutKeyMask()), zoomIn);
|
||||||
|
addAction(KeyStroke.getKeyStroke(Character.valueOf('+'), 0), zoomIn);
|
||||||
|
addAction(KeyStroke.getKeyStroke(Character.valueOf('+'), getToolkit().getMenuShortcutKeyMask()), zoomIn);
|
||||||
|
AbstractAction zoomOut = new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
System.err.println("ZOOM OUT");
|
||||||
|
setZoom(zoom / 2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addAction(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, getToolkit().getMenuShortcutKeyMask()), zoomOut);
|
||||||
|
addAction(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, getToolkit().getMenuShortcutKeyMask()), zoomOut);
|
||||||
|
addAction(KeyStroke.getKeyStroke(Character.valueOf('-'), 0), zoomOut);
|
||||||
|
addAction(KeyStroke.getKeyStroke(Character.valueOf('-'), getToolkit().getMenuShortcutKeyMask()), zoomOut);
|
||||||
|
AbstractAction zoomFit = new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
System.err.println("ZOOM FIT");
|
||||||
|
// Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
|
||||||
|
Rectangle bounds = getVisibleRect();
|
||||||
|
setZoom(Math.min(1.0, Math.min(bounds.getWidth() / (double) image.getWidth(), bounds.getHeight() / (double) image.getHeight())));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addAction(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, getToolkit().getMenuShortcutKeyMask()), zoomFit);
|
||||||
|
addAction(KeyStroke.getKeyStroke(KeyEvent.VK_9, getToolkit().getMenuShortcutKeyMask()), zoomFit);
|
||||||
|
addAction(KeyStroke.getKeyStroke(KeyEvent.VK_0, getToolkit().getMenuShortcutKeyMask()), new AbstractAction() {
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
System.err.println("ZOOM ACTUAL");
|
||||||
|
setZoom(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setZoom(final double newZoom) {
|
||||||
|
if (newZoom != zoom) {
|
||||||
|
zoom = newZoom;
|
||||||
|
// TODO: Add PCL support for zoom and discard tiles cache based on property change
|
||||||
|
tiles = createTileCache();
|
||||||
|
revalidate();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Point, Tile> createTileCache() {
|
||||||
|
return Collections.synchronizedMap(new SizedLRUMap<Point, Tile>(16 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAction(final KeyStroke keyStroke, final AbstractAction action) {
|
||||||
|
UUID key = UUID.randomUUID();
|
||||||
|
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, key);
|
||||||
|
getActionMap().put(key, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Paint createTexture() {
|
private Paint createTexture() {
|
||||||
@@ -392,10 +472,17 @@ public class MappedBufferImage {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
|
// TODO: Java 7 kills the performance from our custom painting... :-(
|
||||||
|
|
||||||
// TODO: Figure out why mouse wheel/track pad scroll repaints entire component,
|
// TODO: Figure out why mouse wheel/track pad scroll repaints entire component,
|
||||||
// unlike using the scroll bars of the JScrollPane.
|
// unlike using the scroll bars of the JScrollPane.
|
||||||
// Consider creating a custom mouse wheel listener as a workaround.
|
// Consider creating a custom mouse wheel listener as a workaround.
|
||||||
|
|
||||||
|
// TODO: Cache visible rect content in buffered/volatile image (s) + visible rect (+ zoom) to speed up repaints
|
||||||
|
// - Blit the cahced image (possibly translated) (onto itself?)
|
||||||
|
// - Paint only the necessary parts outside the cached image
|
||||||
|
// - Async rendering into cached image
|
||||||
|
|
||||||
// We want to paint only the visible part of the image
|
// We want to paint only the visible part of the image
|
||||||
Rectangle visible = getVisibleRect();
|
Rectangle visible = getVisibleRect();
|
||||||
Rectangle clip = g.getClipBounds();
|
Rectangle clip = g.getClipBounds();
|
||||||
@@ -405,9 +492,28 @@ public class MappedBufferImage {
|
|||||||
g2.setPaint(texture);
|
g2.setPaint(texture);
|
||||||
g2.fillRect(rect.x, rect.y, rect.width, rect.height);
|
g2.fillRect(rect.x, rect.y, rect.width, rect.height);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Center image (might not be the best way to cooperate with the scroll pane)
|
||||||
|
Rectangle imageSize = new Rectangle((int) Math.round(image.getWidth() * zoom), (int) Math.round(image.getHeight() * zoom));
|
||||||
|
if (imageSize.width < getWidth()) {
|
||||||
|
g2.translate((getWidth() - imageSize.width) / 2, 0);
|
||||||
|
}
|
||||||
|
if (imageSize.height < getHeight()) {
|
||||||
|
g2.translate(0, (getHeight() - imageSize.height) / 2);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Zoom
|
||||||
if (zoom != 1) {
|
if (zoom != 1) {
|
||||||
AffineTransform transform = AffineTransform.getScaleInstance(zoom, zoom);
|
// NOTE: This helps mostly when scaling up, or scaling down less than 50%
|
||||||
g2.setTransform(transform);
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||||
|
|
||||||
|
rect = new Rectangle(
|
||||||
|
(int) Math.round(rect.x / zoom), (int) Math.round(rect.y / zoom),
|
||||||
|
(int) Math.round(rect.width / zoom), (int) Math.round(rect.height / zoom)
|
||||||
|
);
|
||||||
|
|
||||||
|
rect = rect.intersection(new Rectangle(image.getWidth(), image.getHeight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
@@ -415,39 +521,308 @@ public class MappedBufferImage {
|
|||||||
System.err.println("repaint: " + (System.currentTimeMillis() - start) + " ms");
|
System.err.println("repaint: " + (System.currentTimeMillis() - start) + " ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void repaintImage(Rectangle rect, Graphics2D g2) {
|
static class Tile {
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
|
||||||
|
private final Reference<BufferedImage> data;
|
||||||
|
private final BufferedImage hardRef;
|
||||||
|
|
||||||
|
Tile(int x, int y, BufferedImage data) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.data = new SoftReference<BufferedImage>(data);
|
||||||
|
|
||||||
|
hardRef = data;
|
||||||
|
|
||||||
|
size = 16 + data.getWidth() * data.getHeight() * data.getRaster().getNumDataElements() * sizeOf(data.getRaster().getTransferType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int sizeOf(final int transferType) {
|
||||||
|
switch (transferType) {
|
||||||
|
case DataBuffer.TYPE_INT:
|
||||||
|
return 4;
|
||||||
|
case DataBuffer.TYPE_SHORT:
|
||||||
|
return 2;
|
||||||
|
case DataBuffer.TYPE_BYTE:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported transfer type: " + transferType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void drawTo(Graphics2D g) {
|
||||||
|
BufferedImage img = data.get();
|
||||||
|
|
||||||
|
if (img != null) {
|
||||||
|
g.drawImage(img, x, y, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// g.setPaint(Color.GREEN);
|
||||||
|
// g.drawString(String.format("[%d, %d]", x, y), x + 20, y + 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
BufferedImage img = data.get();
|
||||||
|
return img != null ? img.getWidth() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
BufferedImage img = data.get();
|
||||||
|
return img != null ? img.getHeight() : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rectangle getRect() {
|
||||||
|
BufferedImage img = data.get();
|
||||||
|
return img != null ? new Rectangle(x, y, img.getWidth(), img.getHeight()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point getLocation() {
|
||||||
|
return new Point(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other == null || getClass() != other.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Tile tile = (Tile) other;
|
||||||
|
|
||||||
|
return x == tile.x && y == tile.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return 997 * x + y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("Tile[%d, %d, %d, %d]", x, y, getWidth(), getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Consider a fixed size (mem) LRUCache instead
|
||||||
|
Map<Point, Tile> tiles = createTileCache();
|
||||||
|
|
||||||
|
private void repaintImage(final Rectangle rect, final Graphics2D g2) {
|
||||||
|
// System.err.println("rect: " + rect);
|
||||||
|
// System.err.println("tiles: " + tiles.size());
|
||||||
|
// TODO: Fix rounding errors
|
||||||
|
// FIx repaint bugs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Paint tiles of the image, to preserve memory
|
// Paint tiles of the image, to preserve memory
|
||||||
int sliceSize = 200;
|
final int tileSize = 200;
|
||||||
|
|
||||||
int slicesW = rect.width / sliceSize;
|
int tilesW = 1 + rect.width / tileSize;
|
||||||
int slicesH = rect.height / sliceSize;
|
int tilesH = 1 + rect.height / tileSize;
|
||||||
|
|
||||||
for (int sliceY = 0; sliceY <= slicesH; sliceY++) {
|
for (int yTile = 0; yTile <= tilesH; yTile++) {
|
||||||
for (int sliceX = 0; sliceX <= slicesW; sliceX++) {
|
for (int xTile = 0; xTile <= tilesW; xTile++) {
|
||||||
int x = rect.x + sliceX * sliceSize;
|
// Image (source) coordinates
|
||||||
int y = rect.y + sliceY * sliceSize;
|
int x = rect.x + xTile * tileSize;
|
||||||
|
int y = rect.y + yTile * tileSize;
|
||||||
|
|
||||||
int w = sliceX == slicesW ? Math.min(sliceSize, rect.x + rect.width - x) : sliceSize;
|
int w = xTile == tilesW ? Math.min(tileSize, rect.x + rect.width - x) : tileSize;
|
||||||
int h = sliceY == slicesH ? Math.min(sliceSize, rect.y + rect.height - y) : sliceSize;
|
int h = yTile == tilesH ? Math.min(tileSize, rect.y + rect.height - y) : tileSize;
|
||||||
|
|
||||||
if (w == 0 || h == 0) {
|
if (w == 0 || h == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// System.err.printf("%04d, %04d, %04d, %04d%n", x, y, w, h);
|
// System.err.printf("%04d, %04d, %04d, %04d%n", x, y, w, h);
|
||||||
BufferedImage img = image.getSubimage(x, y, w, h);
|
|
||||||
g2.drawImage(img, x, y, null);
|
// - Get tile from cache
|
||||||
|
// - If non-null, paint
|
||||||
|
// - If null, request data for later use, with callback, and return
|
||||||
|
// TODO: Could we use ImageProducer/ImageConsumer/ImageObserver interface??
|
||||||
|
|
||||||
|
// Destination (display) coordinates
|
||||||
|
int dstX = (int) Math.round(x * zoom);
|
||||||
|
int dstY = (int) Math.round(y * zoom);
|
||||||
|
int dstW = (int) Math.round(w * zoom);
|
||||||
|
int dstH = (int) Math.round(h * zoom);
|
||||||
|
|
||||||
|
if (dstW == 0 || dstH == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't create overlapping/duplicate tiles...
|
||||||
|
// - Always start tile grid at 0,0
|
||||||
|
// - Always occupy entire tile, unless edge
|
||||||
|
|
||||||
|
// Source (original) coordinates
|
||||||
|
int tileSrcX = x - x % tileSize;
|
||||||
|
int tileSrcY = y - y % tileSize;
|
||||||
|
// final int tileSrcW = Math.min(tileSize, image.getWidth() - tileSrcX);
|
||||||
|
// final int tileSrcH = Math.min(tileSize, image.getHeight() - tileSrcY);
|
||||||
|
|
||||||
|
// Destination (display) coordinates
|
||||||
|
int tileDstX = (int) Math.round(tileSrcX * zoom);
|
||||||
|
int tileDstY = (int) Math.round(tileSrcY * zoom);
|
||||||
|
// final int tileDstW = (int) Math.round(tileSrcW * zoom);
|
||||||
|
// final int tileDstH = (int) Math.round(tileSrcH * zoom);
|
||||||
|
|
||||||
|
List<Point> points = new ArrayList<Point>(4);
|
||||||
|
points.add(new Point(tileDstX, tileDstY));
|
||||||
|
if (tileDstX != dstX) {
|
||||||
|
points.add(new Point(tileDstX + tileSize, tileDstY));
|
||||||
|
}
|
||||||
|
if (tileDstY != dstY) {
|
||||||
|
points.add(new Point(tileDstX, tileDstY + tileSize));
|
||||||
|
}
|
||||||
|
if (tileDstX != dstX && tileDstY != dstY) {
|
||||||
|
points.add(new Point(tileDstX + tileSize, tileDstY + tileSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final Point point : points) {
|
||||||
|
Tile tile = tiles.get(point);
|
||||||
|
|
||||||
|
if (tile != null) {
|
||||||
|
Reference<BufferedImage> img = tile.data;
|
||||||
|
if (img != null) {
|
||||||
|
tile.drawTo(g2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tiles.remove(point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufferedImage img = image.getSubimage(rect.x, rect.y, rect.width, rect.height);
|
// System.err.printf("Tile miss: [%d, %d]\n", dstX, dstY);
|
||||||
// g2.drawImage(img, rect.x, rect.y, null);
|
|
||||||
|
// Dispatch to off-thread worker
|
||||||
|
final Map<Point, Tile> localTiles = tiles;
|
||||||
|
executorService.submit(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
// TODO: Fix rounding issues... Problem is that sometimes the srcW/srcH is 1 pixel off filling the tile...
|
||||||
|
int tileSrcX = (int) Math.round(point.x / zoom);
|
||||||
|
int tileSrcY = (int) Math.round(point.y / zoom);
|
||||||
|
int tileSrcW = Math.min(tileSize, image.getWidth() - tileSrcX);
|
||||||
|
int tileSrcH = Math.min(tileSize, image.getHeight() - tileSrcY);
|
||||||
|
int tileDstW = (int) Math.round(tileSrcW * zoom);
|
||||||
|
int tileDstH = (int) Math.round(tileSrcH * zoom);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// TODO: Consider comparing zoom/local zoom
|
||||||
|
if (localTiles != tiles) {
|
||||||
|
return; // Return early after re-zoom
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localTiles.containsKey(point)) {
|
||||||
|
// System.err.println("Skipping tile, already producing...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test against current view rect, to avoid computing tiles that will be thrown away immediately
|
||||||
|
// TODO: EDT safe?
|
||||||
|
if (!getVisibleRect().intersects(new Rectangle(point.x, point.y, tileDstW, tileDstH))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.err.printf("Creating tile: [%d, %d]\n", tileDstX, tileDstY);
|
||||||
|
|
||||||
|
BufferedImage temp = getGraphicsConfiguration().createCompatibleImage(tileDstW, tileDstH);
|
||||||
|
final Tile tile = new Tile(point.x, point.y, temp);
|
||||||
|
localTiles.put(point, tile);
|
||||||
|
|
||||||
|
Graphics2D graphics = temp.createGraphics();
|
||||||
|
try {
|
||||||
|
Object hint = g2.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
|
||||||
|
|
||||||
|
if (hint != null) {
|
||||||
|
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.scale(zoom, zoom);
|
||||||
|
graphics.drawImage(image.getSubimage(tileSrcX, tileSrcY, tileSrcW, tileSrcH), 0, 0, null);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
repaint(10, tile.x, tile.y, tile.getWidth(), tile.getHeight());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Throwable t) {
|
||||||
|
localTiles.remove(point);
|
||||||
|
System.err.println("Boooo: " + t.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (NullPointerException e) {
|
catch (NullPointerException e) {
|
||||||
// e.printStackTrace();
|
// e.printStackTrace();
|
||||||
// Happens whenever apple.awt.OSXCachingSufraceManager runs out of memory
|
// Happens whenever apple.awt.OSXCachingSurfaceManager runs out of memory
|
||||||
// TODO: Figure out why repaint(x,y,w,h) doesn't work any more..?
|
// TODO: Figure out why repaint(x,y,w,h) doesn't work any more..?
|
||||||
|
System.err.println("Full repaint due to NullPointerException (probably out of memory).");
|
||||||
|
repaint(); // NOTE: Might cause a brief flash while the component is redrawn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void repaintImage0(final Rectangle rect, final Graphics2D g2) {
|
||||||
|
g2.scale(zoom, zoom);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Paint tiles of the image, to preserve memory
|
||||||
|
final int tileSize = 200;
|
||||||
|
|
||||||
|
int tilesW = rect.width / tileSize;
|
||||||
|
int tilesH = rect.height / tileSize;
|
||||||
|
|
||||||
|
for (int yTile = 0; yTile <= tilesH; yTile++) {
|
||||||
|
for (int xTile = 0; xTile <= tilesW; xTile++) {
|
||||||
|
// Image (source) coordinates
|
||||||
|
final int x = rect.x + xTile * tileSize;
|
||||||
|
final int y = rect.y + yTile * tileSize;
|
||||||
|
|
||||||
|
final int w = xTile == tilesW ? Math.min(tileSize, rect.x + rect.width - x) : tileSize;
|
||||||
|
final int h = yTile == tilesH ? Math.min(tileSize, rect.y + rect.height - y) : tileSize;
|
||||||
|
|
||||||
|
if (w == 0 || h == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.err.printf("%04d, %04d, %04d, %04d%n", x, y, w, h);
|
||||||
|
|
||||||
|
BufferedImage img = image.getSubimage(x, y, w, h);
|
||||||
|
g2.drawImage(img, x, y, null);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NullPointerException e) {
|
||||||
|
// e.printStackTrace();
|
||||||
|
// Happens whenever apple.awt.OSXCachingSurfaceManager runs out of memory
|
||||||
|
// TODO: Figure out why repaint(x,y,w,h) doesn't work any more..?
|
||||||
|
System.err.println("Full repaint due to NullPointerException (probably out of memory).");
|
||||||
repaint(); // NOTE: Might cause a brief flash while the component is redrawn
|
repaint(); // NOTE: Might cause a brief flash while the component is redrawn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,12 +851,68 @@ public class MappedBufferImage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean getScrollableTracksViewportWidth() {
|
public boolean getScrollableTracksViewportWidth() {
|
||||||
return false;
|
return getWidth() > getPreferredSize().width;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getScrollableTracksViewportHeight() {
|
public boolean getScrollableTracksViewportHeight() {
|
||||||
|
return getHeight() > getPreferredSize().height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final static class SizedLRUMap<K, V> extends LRUHashMap<K, V> {
|
||||||
|
int currentSize;
|
||||||
|
int maxSize;
|
||||||
|
|
||||||
|
public SizedLRUMap(int pMaxSize) {
|
||||||
|
super(); // Note: super.maxSize doesn't count...
|
||||||
|
maxSize = pMaxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected int sizeOf(final Object pValue) {
|
||||||
|
ImageComponent.Tile cached = (ImageComponent.Tile) pValue;
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cached.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K pKey, V pValue) {
|
||||||
|
currentSize += sizeOf(pValue);
|
||||||
|
|
||||||
|
V old = super.put(pKey, pValue);
|
||||||
|
if (old != null) {
|
||||||
|
currentSize -= sizeOf(old);
|
||||||
|
}
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove(Object pKey) {
|
||||||
|
V old = super.remove(pKey);
|
||||||
|
if (old != null) {
|
||||||
|
currentSize -= sizeOf(old);
|
||||||
|
}
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Map.Entry<K, V> pEldest) {
|
||||||
|
if (maxSize <= currentSize) { // NOTE: maxSize here is mem size
|
||||||
|
removeLRU();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeLRU() {
|
||||||
|
while (maxSize <= currentSize) { // NOTE: maxSize here is mem size
|
||||||
|
super.removeLRU();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PaintDotsTask implements Runnable {
|
private static class PaintDotsTask implements Runnable {
|
||||||
|
|||||||
@@ -28,6 +28,13 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.util;
|
package com.twelvemonkeys.util;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.io.FileUtil;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PersistentMap
|
* PersistentMap
|
||||||
*
|
*
|
||||||
@@ -35,27 +42,293 @@ package com.twelvemonkeys.util;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: PersistentMap.java,v 1.0 May 13, 2009 2:31:29 PM haraldk Exp$
|
* @version $Id: PersistentMap.java,v 1.0 May 13, 2009 2:31:29 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public class PersistentMap {
|
public class PersistentMap<K extends Serializable, V extends Serializable> extends AbstractMap<K, V>{
|
||||||
// TODO: Implement Map
|
public static final FileFilter DIRECTORIES = new FileFilter() {
|
||||||
// TODO: Delta synchronization (db?)
|
public boolean accept(File file) {
|
||||||
|
return file.isDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[All folders]";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final String INDEX = ".index";
|
||||||
|
|
||||||
|
private final File root;
|
||||||
|
private final Map<K, UUID> index = new LinkedHashMap<K, UUID>();
|
||||||
|
|
||||||
|
private boolean mutable = true;
|
||||||
|
|
||||||
|
|
||||||
|
// Idea 2.0:
|
||||||
|
// - Create directory per hashCode
|
||||||
|
// - Create file per object in that directory
|
||||||
|
// - Name file after serialized form of key? Base64?
|
||||||
|
// - Special case for String/Integer/Long etc?
|
||||||
|
// - Or create index file in directory with serialized objects + name (uuid) of file
|
||||||
|
|
||||||
|
// TODO: Consider single index file? Or a few? In root directory instead of each directory
|
||||||
|
// Consider a RAF/FileChannel approach instead of streams - how do we discard portions of a RAF?
|
||||||
|
// - Need to keep track of used/unused parts of file, scan for gaps etc...?
|
||||||
|
// - Need to periodically truncate and re-build the index (always as startup, then at every N puts/removes?)
|
||||||
|
|
||||||
|
/*public */PersistentMap(String id) {
|
||||||
|
this(new File(FileUtil.getTempDirFile(), id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PersistentMap(File root) {
|
||||||
|
this.root = notNull(root);
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
if (!root.exists() && !root.mkdirs()) {
|
||||||
|
throw new IllegalStateException(String.format("'%s' does not exist/could not be created", root.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
else if (!root.isDirectory()) {
|
||||||
|
throw new IllegalStateException(String.format("'%s' exists but is not a directory", root.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.canRead()) {
|
||||||
|
throw new IllegalStateException(String.format("'%s' is not readable", root.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root.canWrite()) {
|
||||||
|
mutable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileUtil.visitFiles(root, DIRECTORIES, new Visitor<File>() {
|
||||||
|
public void visit(File dir) {
|
||||||
|
// - Read .index file
|
||||||
|
// - Add entries to index
|
||||||
|
ObjectInputStream input = null;
|
||||||
|
try {
|
||||||
|
input = new ObjectInputStream(new FileInputStream(new File(dir, INDEX)));
|
||||||
|
while (true) {
|
||||||
|
@SuppressWarnings({"unchecked"})
|
||||||
|
K key = (K) input.readObject();
|
||||||
|
String fileName = (String) input.readObject();
|
||||||
|
index.put(key, UUID.fromString(fileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (EOFException eof) {
|
||||||
|
// break here
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
FileUtil.close(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<K, V>> entrySet() {
|
||||||
|
return new AbstractSet<Entry<K, V>>() {
|
||||||
|
@Override
|
||||||
|
public Iterator<Entry<K, V>> iterator() {
|
||||||
|
return new Iterator<Entry<K, V>>() {
|
||||||
|
Iterator<Entry<K, UUID>> indexIter = index.entrySet().iterator();
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return indexIter.hasNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entry<K, V> next() {
|
||||||
|
return new Entry<K, V>() {
|
||||||
|
final Entry<K, UUID> entry = indexIter.next();
|
||||||
|
|
||||||
|
public K getKey() {
|
||||||
|
return entry.getKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getValue() {
|
||||||
|
K key = entry.getKey();
|
||||||
|
int hash = key != null ? key.hashCode() : 0;
|
||||||
|
return readVal(hash, entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public V setValue(V value) {
|
||||||
|
K key = entry.getKey();
|
||||||
|
int hash = key != null ? key.hashCode() : 0;
|
||||||
|
return writeVal(key, hash, entry.getValue(), value, getValue());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
indexIter.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return index.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return index.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K key, V value) {
|
||||||
|
V oldVal = null;
|
||||||
|
|
||||||
|
UUID uuid = index.get(key);
|
||||||
|
int hash = key != null ? key.hashCode() : 0;
|
||||||
|
|
||||||
|
if (uuid != null) {
|
||||||
|
oldVal = readVal(hash, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeVal(key, hash, uuid, value, oldVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private V writeVal(K key, int hash, UUID uuid, V value, V oldVal) {
|
||||||
|
if (!mutable) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
File bucket = new File(root, hashToFileName(hash));
|
||||||
|
if (!bucket.exists() && !bucket.mkdirs()) {
|
||||||
|
throw new IllegalStateException(String.format("Could not create bucket '%s'", bucket));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uuid == null) {
|
||||||
|
// No uuid means new entry
|
||||||
|
uuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
File idx = new File(bucket, INDEX);
|
||||||
|
|
||||||
|
ObjectOutputStream output = null;
|
||||||
|
try {
|
||||||
|
output = new ObjectOutputStream(new FileOutputStream(idx, true));
|
||||||
|
output.writeObject(key);
|
||||||
|
output.writeObject(uuid.toString());
|
||||||
|
|
||||||
|
index.put(key, uuid);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
FileUtil.close(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File entry = new File(bucket, uuid.toString());
|
||||||
|
if (value != null) {
|
||||||
|
ObjectOutputStream output = null;
|
||||||
|
try {
|
||||||
|
output = new ObjectOutputStream(new FileOutputStream(entry));
|
||||||
|
output.writeObject(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
FileUtil.close(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (entry.exists()) {
|
||||||
|
if (!entry.delete()) {
|
||||||
|
throw new IllegalStateException(String.format("'%s' could not be deleted", entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String hashToFileName(int hash) {
|
||||||
|
return Integer.toString(hash, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
UUID uuid = index.get(key);
|
||||||
|
|
||||||
|
if (uuid != null) {
|
||||||
|
int hash = key != null ? key.hashCode() : 0;
|
||||||
|
return readVal(hash, uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private V readVal(final int hash, final UUID uuid) {
|
||||||
|
File bucket = new File(root, hashToFileName(hash));
|
||||||
|
File entry = new File(bucket, uuid.toString());
|
||||||
|
|
||||||
|
if (entry.exists()) {
|
||||||
|
ObjectInputStream input = null;
|
||||||
|
try {
|
||||||
|
input = new ObjectInputStream(new FileInputStream(entry));
|
||||||
|
//noinspection unchecked
|
||||||
|
return (V) input.readObject();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
FileUtil.close(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove(Object key) {
|
||||||
|
// TODO!!!
|
||||||
|
return super.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should override size, put, get, remove, containsKey and containsValue
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Memory mapped file?
|
||||||
|
Delta sync?
|
||||||
|
|
||||||
Persistent format
|
Persistent format
|
||||||
|
|
||||||
Header
|
Header
|
||||||
File ID 4-8 bytes
|
File ID 4-8 bytes
|
||||||
Size
|
Size (entries)
|
||||||
|
|
||||||
Entry pointer array block
|
PersistentEntry pointer array block (PersistentEntry 0)
|
||||||
Size
|
Size (bytes)
|
||||||
Next entry pointer block address
|
Next entry pointer block address (0 if last)
|
||||||
Entry 1 address
|
PersistentEntry 1 address/offset + key
|
||||||
...
|
...
|
||||||
Entry n address
|
PersistentEntry n address/offset + key
|
||||||
|
|
||||||
Entry 1
|
PersistentEntry 1
|
||||||
|
Size (bytes)?
|
||||||
|
Serialized value or pointer array block
|
||||||
...
|
...
|
||||||
Entry n
|
PersistentEntry n
|
||||||
|
Size (bytes)?
|
||||||
|
Serialized value or pointer array block
|
||||||
|
|
||||||
*/
|
*/
|
||||||
@@ -84,6 +84,7 @@
|
|||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-all</artifactId>
|
<artifactId>mockito-all</artifactId>
|
||||||
<version>1.8.5</version>
|
<version>1.8.5</version>
|
||||||
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.twelvemonkeys.servlet;
|
package com.twelvemonkeys.servlet;
|
||||||
|
|
||||||
import com.twelvemonkeys.util.CollectionUtil;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,88 +9,53 @@ import java.util.*;
|
|||||||
* @author last modified by $Author: haku $
|
* @author last modified by $Author: haku $
|
||||||
* @version $Id: AbstractServletMapAdapter.java#1 $
|
* @version $Id: AbstractServletMapAdapter.java#1 $
|
||||||
*/
|
*/
|
||||||
abstract class AbstractServletMapAdapter extends AbstractMap<String, List<String>> {
|
abstract class AbstractServletMapAdapter<T> extends AbstractMap<String, T> {
|
||||||
// TODO: This map is now a little too lazy.. Should cache entries too (instead?) !
|
// TODO: This map is now a little too lazy.. Should cache entries!
|
||||||
|
private transient Set<Entry<String, T>> entries;
|
||||||
private final static List<String> NULL_LIST = new ArrayList<String>();
|
|
||||||
|
|
||||||
private transient Map<String, List<String>> cache = new HashMap<String, List<String>>();
|
|
||||||
private transient int size = -1;
|
|
||||||
private transient AbstractSet<Entry<String, List<String>>> entries;
|
|
||||||
|
|
||||||
protected abstract Iterator<String> keysImpl();
|
protected abstract Iterator<String> keysImpl();
|
||||||
|
|
||||||
protected abstract Iterator<String> valuesImpl(String pName);
|
protected abstract T valueImpl(String pName);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> get(final Object pKey) {
|
public T get(final Object pKey) {
|
||||||
if (pKey instanceof String) {
|
if (pKey instanceof String) {
|
||||||
return getValues((String) pKey);
|
return valueImpl((String) pKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getValues(final String pName) {
|
|
||||||
List<String> values = cache.get(pName);
|
|
||||||
|
|
||||||
if (values == null) {
|
|
||||||
//noinspection unchecked
|
|
||||||
Iterator<String> headers = valuesImpl(pName);
|
|
||||||
|
|
||||||
if (headers == null) {
|
|
||||||
cache.put(pName, NULL_LIST);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
values = toList(headers);
|
|
||||||
cache.put(pName, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return values == NULL_LIST ? null : values;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<String> toList(final Iterator<String> pValues) {
|
|
||||||
List<String> list = new ArrayList<String>();
|
|
||||||
CollectionUtil.addAll(list, pValues);
|
|
||||||
return Collections.unmodifiableList(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
if (size == -1) {
|
// Avoid creating expensive entry set for computing size
|
||||||
computeSize();
|
int size = 0;
|
||||||
|
|
||||||
|
for (Iterator<String> names = keysImpl(); names.hasNext(); names.next()) {
|
||||||
|
size++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void computeSize() {
|
public Set<Entry<String, T>> entrySet() {
|
||||||
size = 0;
|
|
||||||
|
|
||||||
for (Iterator<String> names = keysImpl(); names.hasNext(); names.next()) {
|
|
||||||
size++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Entry<String, List<String>>> entrySet() {
|
|
||||||
if (entries == null) {
|
if (entries == null) {
|
||||||
entries = new AbstractSet<Entry<String, List<String>>>() {
|
entries = new AbstractSet<Entry<String, T>>() {
|
||||||
public Iterator<Entry<String, List<String>>> iterator() {
|
public Iterator<Entry<String, T>> iterator() {
|
||||||
return new Iterator<Entry<String, List<String>>>() {
|
return new Iterator<Entry<String, T>>() {
|
||||||
Iterator<String> headerNames = keysImpl();
|
Iterator<String> keys = keysImpl();
|
||||||
|
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
return headerNames.hasNext();
|
return keys.hasNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entry<String, List<String>> next() {
|
public Entry<String, T> next() {
|
||||||
// TODO: Replace with cached lookup
|
// TODO: Replace with cached lookup
|
||||||
return new HeaderEntry(headerNames.next());
|
return new HeaderEntry(keys.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void remove() {
|
public void remove() {
|
||||||
throw new UnsupportedOperationException();
|
keys.remove();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -106,34 +69,35 @@ abstract class AbstractServletMapAdapter extends AbstractMap<String, List<String
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HeaderEntry implements Entry<String, List<String>> {
|
private class HeaderEntry implements Entry<String, T> {
|
||||||
String headerName;
|
final String key;
|
||||||
|
|
||||||
public HeaderEntry(String pHeaderName) {
|
public HeaderEntry(final String pKey) {
|
||||||
headerName = pHeaderName;
|
key = pKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getKey() {
|
public String getKey() {
|
||||||
return headerName;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getValue() {
|
public T getValue() {
|
||||||
return get(headerName);
|
return get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> setValue(List<String> pValue) {
|
public T setValue(final T pValue) {
|
||||||
throw new UnsupportedOperationException();
|
// Write-through if supported
|
||||||
|
return put(key, pValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
List<String> value;
|
T value = getValue();
|
||||||
return (headerName == null ? 0 : headerName.hashCode()) ^
|
return (key == null ? 0 : key.hashCode()) ^
|
||||||
((value = getValue()) == null ? 0 : value.hashCode());
|
(value == null ? 0 : value.hashCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object pOther) {
|
public boolean equals(final Object pOther) {
|
||||||
if (pOther == this) {
|
if (pOther == this) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.servlet;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServletAttributesMap
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: ServletAttributesMap.java,v 1.0 01.03.13 10:34 haraldk Exp$
|
||||||
|
*/
|
||||||
|
class ServletAttributesMapAdapter extends AbstractServletMapAdapter<Object> {
|
||||||
|
private final ServletContext context;
|
||||||
|
private final ServletRequest request;
|
||||||
|
|
||||||
|
ServletAttributesMapAdapter(final ServletContext context) {
|
||||||
|
this(notNull(context), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
ServletAttributesMapAdapter(final ServletRequest request) {
|
||||||
|
this(null, notNull(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServletAttributesMapAdapter(final ServletContext context, final ServletRequest request) {
|
||||||
|
this.context = context;
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Enumeration<String> getAttributeNames() {
|
||||||
|
return context != null ? context.getAttributeNames() : request.getAttributeNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object getAttribute(final String name) {
|
||||||
|
return context != null ? context.getAttribute(name) : request.getAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object setAttribute(String name, Object value) {
|
||||||
|
Object oldValue = getAttribute(name);
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
context.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
request.setAttribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object removeAttribute(String name) {
|
||||||
|
Object oldValue = getAttribute(name);
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
context.removeAttribute(name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
request.removeAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return oldValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Iterator<String> keysImpl() {
|
||||||
|
final Enumeration<String> keys = getAttributeNames();
|
||||||
|
return new Iterator<String>() {
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
public boolean hasNext() {
|
||||||
|
return keys.hasMoreElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String next() {
|
||||||
|
key = keys.nextElement();
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove() {
|
||||||
|
// Support removal of attribute through key iterator
|
||||||
|
removeAttribute(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Object valueImpl(String pName) {
|
||||||
|
return getAttribute(pName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object put(String key, Object value) {
|
||||||
|
return setAttribute(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object remove(Object key) {
|
||||||
|
return key instanceof String ? removeAttribute((String) key) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.twelvemonkeys.servlet;
|
package com.twelvemonkeys.servlet;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
|
||||||
import com.twelvemonkeys.util.CollectionUtil;
|
import com.twelvemonkeys.util.CollectionUtil;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.Enumeration;
|
import java.util.*;
|
||||||
import java.util.Iterator;
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServletHeadersMapAdapter
|
* ServletHeadersMapAdapter
|
||||||
@@ -14,24 +14,29 @@ import java.util.Iterator;
|
|||||||
* @author last modified by $Author: haku $
|
* @author last modified by $Author: haku $
|
||||||
* @version $Id: ServletHeadersMapAdapter.java#1 $
|
* @version $Id: ServletHeadersMapAdapter.java#1 $
|
||||||
*/
|
*/
|
||||||
class ServletHeadersMapAdapter extends AbstractServletMapAdapter {
|
class ServletHeadersMapAdapter extends AbstractServletMapAdapter<List<String>> {
|
||||||
|
|
||||||
protected final HttpServletRequest request;
|
protected final HttpServletRequest request;
|
||||||
|
|
||||||
public ServletHeadersMapAdapter(HttpServletRequest pRequest) {
|
public ServletHeadersMapAdapter(final HttpServletRequest pRequest) {
|
||||||
request = Validate.notNull(pRequest, "request");
|
request = notNull(pRequest, "request");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Iterator<String> valuesImpl(String pName) {
|
protected List<String> valueImpl(final String pName) {
|
||||||
//noinspection unchecked
|
@SuppressWarnings("unchecked")
|
||||||
Enumeration<String> headers = request.getHeaders(pName);
|
Enumeration<String> headers = request.getHeaders(pName);
|
||||||
return headers == null ? null : CollectionUtil.iterator(headers);
|
return headers == null ? null : toList(CollectionUtil.iterator(headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> toList(final Iterator<String> pValues) {
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
CollectionUtil.addAll(list, pValues);
|
||||||
|
return Collections.unmodifiableList(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Iterator<String> keysImpl() {
|
protected Iterator<String> keysImpl() {
|
||||||
//noinspection unchecked
|
@SuppressWarnings("unchecked")
|
||||||
Enumeration<String> headerNames = request.getHeaderNames();
|
Enumeration<String> headerNames = request.getHeaderNames();
|
||||||
return headerNames == null ? null : CollectionUtil.iterator(headerNames);
|
return headerNames == null ? null : CollectionUtil.iterator(headerNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.twelvemonkeys.servlet;
|
package com.twelvemonkeys.servlet;
|
||||||
|
|
||||||
import com.twelvemonkeys.lang.Validate;
|
|
||||||
import com.twelvemonkeys.util.CollectionUtil;
|
import com.twelvemonkeys.util.CollectionUtil;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServletParametersMapAdapter
|
* ServletParametersMapAdapter
|
||||||
@@ -14,23 +17,23 @@ import java.util.Iterator;
|
|||||||
* @author last modified by $Author: haku $
|
* @author last modified by $Author: haku $
|
||||||
* @version $Id: ServletParametersMapAdapter.java#1 $
|
* @version $Id: ServletParametersMapAdapter.java#1 $
|
||||||
*/
|
*/
|
||||||
class ServletParametersMapAdapter extends AbstractServletMapAdapter {
|
class ServletParametersMapAdapter extends AbstractServletMapAdapter<List<String>> {
|
||||||
|
// TODO: Be able to piggyback on HttpServletRequest.getParameterMap when available?
|
||||||
|
|
||||||
protected final HttpServletRequest request;
|
protected final ServletRequest request;
|
||||||
|
|
||||||
public ServletParametersMapAdapter(HttpServletRequest pRequest) {
|
public ServletParametersMapAdapter(final ServletRequest pRequest) {
|
||||||
request = Validate.notNull(pRequest, "request");
|
request = notNull(pRequest, "request");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Iterator<String> valuesImpl(String pName) {
|
protected List<String> valueImpl(String pName) {
|
||||||
String[] values = request.getParameterValues(pName);
|
String[] values = request.getParameterValues(pName);
|
||||||
return values == null ? null : CollectionUtil.iterator(values);
|
return values == null ? null : Arrays.asList(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Iterator<String> keysImpl() {
|
protected Iterator<String> keysImpl() {
|
||||||
//noinspection unchecked
|
@SuppressWarnings("unchecked")
|
||||||
Enumeration<String> names = request.getParameterNames();
|
Enumeration<String> names = request.getParameterNames();
|
||||||
return names == null ? null : CollectionUtil.iterator(names);
|
return names == null ? null : CollectionUtil.iterator(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,7 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* Various servlet related helper methods.
|
* Various servlet related helper methods.
|
||||||
*
|
*
|
||||||
* @author Harald Kuhr
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @author Eirik Torske
|
* @author Eirik Torske
|
||||||
* @author last modified by $Author: haku $
|
* @author last modified by $Author: haku $
|
||||||
* @version $Id: ServletUtil.java#3 $
|
* @version $Id: ServletUtil.java#3 $
|
||||||
@@ -544,7 +544,7 @@ public final class ServletUtil {
|
|||||||
/**
|
/**
|
||||||
* Returns a {@code URL} containing the real path for a given virtual
|
* Returns a {@code URL} containing the real path for a given virtual
|
||||||
* path, on URL form.
|
* path, on URL form.
|
||||||
* Note that this mehtod will return {@code null} for all the same reasons
|
* Note that this method will return {@code null} for all the same reasons
|
||||||
* as {@code ServletContext.getRealPath(java.lang.String)} does.
|
* as {@code ServletContext.getRealPath(java.lang.String)} does.
|
||||||
*
|
*
|
||||||
* @param pContext the servlet context
|
* @param pContext the servlet context
|
||||||
@@ -566,7 +566,7 @@ public final class ServletUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the temp directory for the given {@code ServletContext} (webapp).
|
* Gets the temp directory for the given {@code ServletContext} (web app).
|
||||||
*
|
*
|
||||||
* @param pContext the servlet context
|
* @param pContext the servlet context
|
||||||
* @return the temp directory
|
* @return the temp directory
|
||||||
@@ -634,13 +634,30 @@ public final class ServletUtil {
|
|||||||
return new ServletConfigMapAdapter(pContext);
|
return new ServletConfigMapAdapter(pContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO?
|
/**
|
||||||
// public static Map<String, ?> attributesAsMap(final ServletContext pContext) {
|
* Creates an <em>modifiable</em> {@code Map} view of the given
|
||||||
// }
|
* {@code ServletContext}s attributes.
|
||||||
//
|
*
|
||||||
// public static Map<String, ?> attributesAsMap(final ServletRequest pRequest) {
|
* @param pContext the servlet context
|
||||||
// }
|
* @return a {@code Map} view of the attributes
|
||||||
//
|
* @throws IllegalArgumentException if {@code pContext} is {@code null}
|
||||||
|
*/
|
||||||
|
public static Map<String, Object> attributesAsMap(final ServletContext pContext) {
|
||||||
|
return new ServletAttributesMapAdapter(pContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an <em>modifiable</em> {@code Map} view of the given
|
||||||
|
* {@code ServletRequest}s attributes.
|
||||||
|
*
|
||||||
|
* @param pRequest the servlet request
|
||||||
|
* @return a {@code Map} view of the attributes
|
||||||
|
* @throws IllegalArgumentException if {@code pContext} is {@code null}
|
||||||
|
*/
|
||||||
|
public static Map<String, Object> attributesAsMap(final ServletRequest pRequest) {
|
||||||
|
return new ServletAttributesMapAdapter(pRequest);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an unmodifiable {@code Map} view of the given
|
* Creates an unmodifiable {@code Map} view of the given
|
||||||
* {@code HttpServletRequest}s request parameters.
|
* {@code HttpServletRequest}s request parameters.
|
||||||
@@ -649,7 +666,7 @@ public final class ServletUtil {
|
|||||||
* @return a {@code Map} view of the request parameters
|
* @return a {@code Map} view of the request parameters
|
||||||
* @throws IllegalArgumentException if {@code pRequest} is {@code null}
|
* @throws IllegalArgumentException if {@code pRequest} is {@code null}
|
||||||
*/
|
*/
|
||||||
public static Map<String, List<String>> parametersAsMap(final HttpServletRequest pRequest) {
|
public static Map<String, List<String>> parametersAsMap(final ServletRequest pRequest) {
|
||||||
return new ServletParametersMapAdapter(pRequest);
|
return new ServletParametersMapAdapter(pRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1089,13 +1089,13 @@ public class HTTPCache {
|
|||||||
|
|
||||||
// TODO: Extract and make public?
|
// TODO: Extract and make public?
|
||||||
final static class SizedLRUMap<K, V> extends LRUHashMap<K, V> {
|
final static class SizedLRUMap<K, V> extends LRUHashMap<K, V> {
|
||||||
int mSize;
|
int currentSize;
|
||||||
int mMaxSize;
|
int maxSize;
|
||||||
|
|
||||||
public SizedLRUMap(int pMaxSize) {
|
public SizedLRUMap(int pMaxSize) {
|
||||||
//super(true);
|
//super(true);
|
||||||
super(); // Note: super.mMaxSize doesn't count...
|
super(); // Note: super.maxSize doesn't count...
|
||||||
mMaxSize = pMaxSize;
|
maxSize = pMaxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1113,11 +1113,11 @@ public class HTTPCache {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public V put(K pKey, V pValue) {
|
public V put(K pKey, V pValue) {
|
||||||
mSize += sizeOf(pValue);
|
currentSize += sizeOf(pValue);
|
||||||
|
|
||||||
V old = super.put(pKey, pValue);
|
V old = super.put(pKey, pValue);
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
mSize -= sizeOf(old);
|
currentSize -= sizeOf(old);
|
||||||
}
|
}
|
||||||
return old;
|
return old;
|
||||||
}
|
}
|
||||||
@@ -1126,14 +1126,14 @@ public class HTTPCache {
|
|||||||
public V remove(Object pKey) {
|
public V remove(Object pKey) {
|
||||||
V old = super.remove(pKey);
|
V old = super.remove(pKey);
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
mSize -= sizeOf(old);
|
currentSize -= sizeOf(old);
|
||||||
}
|
}
|
||||||
return old;
|
return old;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean removeEldestEntry(Map.Entry<K, V> pEldest) {
|
protected boolean removeEldestEntry(Map.Entry<K, V> pEldest) {
|
||||||
if (mMaxSize <= mSize) { // NOTE: mMaxSize here is mem size
|
if (maxSize <= currentSize) { // NOTE: maxSize here is mem size
|
||||||
removeLRU();
|
removeLRU();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1141,7 +1141,7 @@ public class HTTPCache {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeLRU() {
|
public void removeLRU() {
|
||||||
while (mMaxSize <= mSize) { // NOTE: mMaxSize here is mem size
|
while (maxSize <= currentSize) { // NOTE: maxSize here is mem size
|
||||||
super.removeLRU();
|
super.removeLRU();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Executable
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.servlet;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.util.MapAbstractTestCase;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServletConfigMapAdapterTestCase
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @version $Id: ServletAttributesMapAdapterTestCase.java#1 $
|
||||||
|
*/
|
||||||
|
public class ServletAttributesMapAdapterContextTest extends MapAbstractTestCase {
|
||||||
|
private static final String ATTRIB_VALUE_ETAG = "\"1234567890abcdef\"";
|
||||||
|
private static final Date ATTRIB_VALUE_DATE = new Date();
|
||||||
|
private static final List<Integer> ATTRIB_VALUE_FOO = Arrays.asList(1, 2);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTestSerialization() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowNullKey() {
|
||||||
|
return false; // Makes no sense...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowNullValue() {
|
||||||
|
return false; // Should be allowed, but the tests don't handle the put(foo, null) == remove(foo) semantics
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map makeEmptyMap() {
|
||||||
|
MockServletContextImpl context = mock(MockServletContextImpl.class, Mockito.CALLS_REAL_METHODS);
|
||||||
|
context.attributes = createAttributes(false);
|
||||||
|
|
||||||
|
return new ServletAttributesMapAdapter(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map makeFullMap() {
|
||||||
|
MockServletContextImpl context = mock(MockServletContextImpl.class, Mockito.CALLS_REAL_METHODS);
|
||||||
|
context.attributes = createAttributes(true);
|
||||||
|
|
||||||
|
return new ServletAttributesMapAdapter(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createAttributes(boolean initialValues) {
|
||||||
|
Map<String, Object> map = new ConcurrentHashMap<String, Object>();
|
||||||
|
|
||||||
|
if (initialValues) {
|
||||||
|
String[] sampleKeys = (String[]) getSampleKeys();
|
||||||
|
for (int i = 0; i < sampleKeys.length; i++) {
|
||||||
|
map.put(sampleKeys[i], getSampleValues()[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getSampleKeys() {
|
||||||
|
return new String[] {"Date", "ETag", "X-Foo"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getSampleValues() {
|
||||||
|
return new Object[] {ATTRIB_VALUE_DATE, ATTRIB_VALUE_ETAG, ATTRIB_VALUE_FOO};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getNewSampleValues() {
|
||||||
|
// Needs to be same length but different values
|
||||||
|
return new Object[] {new Date(-1l), "foo/bar", Arrays.asList(2, 3, 4)};
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void testMapPutNullValue() {
|
||||||
|
// Special null semantics
|
||||||
|
resetFull();
|
||||||
|
|
||||||
|
int size = map.size();
|
||||||
|
String key = getClass().getName() + ".someNewKey";
|
||||||
|
map.put(key, null);
|
||||||
|
assertEquals(size, map.size());
|
||||||
|
assertFalse(map.containsKey(key));
|
||||||
|
|
||||||
|
map.put(getSampleKeys()[0], null);
|
||||||
|
assertEquals(size - 1, map.size());
|
||||||
|
assertFalse(map.containsKey(getSampleKeys()[0]));
|
||||||
|
|
||||||
|
map.remove(getSampleKeys()[1]);
|
||||||
|
assertEquals(size - 2, map.size());
|
||||||
|
assertFalse(map.containsKey(getSampleKeys()[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class MockServletContextImpl implements ServletContext {
|
||||||
|
Map<String, Object> attributes;
|
||||||
|
|
||||||
|
public Object getAttribute(String name) {
|
||||||
|
return attributes.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration getAttributeNames() {
|
||||||
|
return Collections.enumeration(attributes.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttribute(String name, Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
attributes.remove(name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attributes.put(name, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
attributes.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+156
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013, 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.servlet;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.util.MapAbstractTestCase;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ServletConfigMapAdapterTestCase
|
||||||
|
* <p/>
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @version $Id: ServletAttributesMapAdapterTestCase.java#1 $
|
||||||
|
*/
|
||||||
|
public class ServletAttributesMapAdapterRequestTest extends MapAbstractTestCase {
|
||||||
|
private static final String ATTRIB_VALUE_ETAG = "\"1234567890abcdef\"";
|
||||||
|
private static final Date ATTRIB_VALUE_DATE = new Date();
|
||||||
|
private static final List<Integer> ATTRIB_VALUE_FOO = Arrays.asList(1, 2);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTestSerialization() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowNullKey() {
|
||||||
|
return false; // Makes no sense...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowNullValue() {
|
||||||
|
return false; // Should be allowed, but the tests don't handle the put(foo, null) == remove(foo) semantics
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map makeEmptyMap() {
|
||||||
|
MockServletRequestImpl request = mock(MockServletRequestImpl.class, Mockito.CALLS_REAL_METHODS);
|
||||||
|
request.attributes = createAttributes(false);
|
||||||
|
|
||||||
|
return new ServletAttributesMapAdapter(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map makeFullMap() {
|
||||||
|
MockServletRequestImpl request = mock(MockServletRequestImpl.class, Mockito.CALLS_REAL_METHODS);
|
||||||
|
request.attributes = createAttributes(true);
|
||||||
|
|
||||||
|
return new ServletAttributesMapAdapter(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> createAttributes(boolean initialValues) {
|
||||||
|
Map<String, Object> map = new ConcurrentHashMap<String, Object>();
|
||||||
|
|
||||||
|
if (initialValues) {
|
||||||
|
String[] sampleKeys = (String[]) getSampleKeys();
|
||||||
|
for (int i = 0; i < sampleKeys.length; i++) {
|
||||||
|
map.put(sampleKeys[i], getSampleValues()[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getSampleKeys() {
|
||||||
|
return new String[] {"Date", "ETag", "X-Foo"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getSampleValues() {
|
||||||
|
return new Object[] {ATTRIB_VALUE_DATE, ATTRIB_VALUE_ETAG, ATTRIB_VALUE_FOO};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] getNewSampleValues() {
|
||||||
|
// Needs to be same length but different values
|
||||||
|
return new Object[] {new Date(-1l), "foo/bar", Arrays.asList(2, 3, 4)};
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void testMapPutNullValue() {
|
||||||
|
// Special null semantics
|
||||||
|
resetFull();
|
||||||
|
|
||||||
|
int size = map.size();
|
||||||
|
String key = getClass().getName() + ".someNewKey";
|
||||||
|
map.put(key, null);
|
||||||
|
assertEquals(size, map.size());
|
||||||
|
assertFalse(map.containsKey(key));
|
||||||
|
|
||||||
|
map.put(getSampleKeys()[0], null);
|
||||||
|
assertEquals(size - 1, map.size());
|
||||||
|
assertFalse(map.containsKey(getSampleKeys()[0]));
|
||||||
|
|
||||||
|
map.remove(getSampleKeys()[1]);
|
||||||
|
assertEquals(size - 2, map.size());
|
||||||
|
assertFalse(map.containsKey(getSampleKeys()[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class MockServletRequestImpl implements ServletRequest {
|
||||||
|
Map<String, Object> attributes;
|
||||||
|
|
||||||
|
public Object getAttribute(String name) {
|
||||||
|
return attributes.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Enumeration getAttributeNames() {
|
||||||
|
return Collections.enumeration(attributes.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttribute(String name, Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
attributes.remove(name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
attributes.put(name, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAttribute(String name) {
|
||||||
|
attributes.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-7
@@ -1,13 +1,15 @@
|
|||||||
package com.twelvemonkeys.servlet;
|
package com.twelvemonkeys.servlet;
|
||||||
|
|
||||||
import com.twelvemonkeys.util.MapAbstractTestCase;
|
import com.twelvemonkeys.util.MapAbstractTestCase;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Suite;
|
||||||
|
|
||||||
import javax.servlet.*;
|
import javax.servlet.*;
|
||||||
import java.util.*;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.io.Serializable;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServletConfigMapAdapterTestCase
|
* ServletConfigMapAdapterTestCase
|
||||||
@@ -16,7 +18,12 @@ import java.net.MalformedURLException;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $
|
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $
|
||||||
*/
|
*/
|
||||||
public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCase {
|
@RunWith(Suite.class)
|
||||||
|
@Suite.SuiteClasses({AbstractServletConfigMapAdapterTest.ServletConfigMapTest.class, AbstractServletConfigMapAdapterTest.FilterConfigMapTest.class, AbstractServletConfigMapAdapterTest.ServletContextMapTest.class})
|
||||||
|
public final class ServletConfigMapAdapterTest {
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class AbstractServletConfigMapAdapterTest extends MapAbstractTestCase {
|
||||||
|
|
||||||
public boolean isPutAddSupported() {
|
public boolean isPutAddSupported() {
|
||||||
return false;
|
return false;
|
||||||
@@ -148,7 +155,7 @@ public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ServletConfigMapTestCase extends ServletConfigMapAdapterTestCase {
|
public static final class ServletConfigMapTest extends AbstractServletConfigMapAdapterTest {
|
||||||
|
|
||||||
public Map makeEmptyMap() {
|
public Map makeEmptyMap() {
|
||||||
ServletConfig config = new TestConfig();
|
ServletConfig config = new TestConfig();
|
||||||
@@ -162,7 +169,7 @@ public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class FilterConfigMapTestCase extends ServletConfigMapAdapterTestCase {
|
public static final class FilterConfigMapTest extends AbstractServletConfigMapAdapterTest {
|
||||||
|
|
||||||
public Map makeEmptyMap() {
|
public Map makeEmptyMap() {
|
||||||
FilterConfig config = new TestConfig();
|
FilterConfig config = new TestConfig();
|
||||||
@@ -176,7 +183,7 @@ public abstract class ServletConfigMapAdapterTestCase extends MapAbstractTestCas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ServletContextMapTestCase extends ServletConfigMapAdapterTestCase {
|
public static final class ServletContextMapTest extends AbstractServletConfigMapAdapterTest {
|
||||||
|
|
||||||
public Map makeEmptyMap() {
|
public Map makeEmptyMap() {
|
||||||
ServletContext config = new TestConfig();
|
ServletContext config = new TestConfig();
|
||||||
+1
-1
@@ -17,7 +17,7 @@ import static org.mockito.Mockito.when;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: ServletHeadersMapAdapterTestCase.java#1 $
|
* @version $Id: ServletHeadersMapAdapterTestCase.java#1 $
|
||||||
*/
|
*/
|
||||||
public class ServletHeadersMapAdapterTestCase extends MapAbstractTestCase {
|
public class ServletHeadersMapAdapterTest extends MapAbstractTestCase {
|
||||||
private static final List<String> HEADER_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
|
private static final List<String> HEADER_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
|
||||||
private static final List<String> HEADER_VALUE_DATE = Arrays.asList(new Date().toString());
|
private static final List<String> HEADER_VALUE_DATE = Arrays.asList(new Date().toString());
|
||||||
private static final List<String> HEADER_VALUE_FOO = Arrays.asList("one", "two");
|
private static final List<String> HEADER_VALUE_FOO = Arrays.asList("one", "two");
|
||||||
+1
-1
@@ -17,7 +17,7 @@ import static org.mockito.Mockito.when;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java#1 $
|
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java#1 $
|
||||||
*/
|
*/
|
||||||
public class ServletParametersMapAdapterTestCase extends MapAbstractTestCase {
|
public class ServletParametersMapAdapterTest extends MapAbstractTestCase {
|
||||||
private static final List<String> PARAM_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
|
private static final List<String> PARAM_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
|
||||||
private static final List<String> PARAM_VALUE_DATE = Arrays.asList(new Date().toString());
|
private static final List<String> PARAM_VALUE_DATE = Arrays.asList(new Date().toString());
|
||||||
private static final List<String> PARAM_VALUE_FOO = Arrays.asList("one", "two");
|
private static final List<String> PARAM_VALUE_FOO = Arrays.asList("one", "two");
|
||||||
Reference in New Issue
Block a user