mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-04-25 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:
@@ -418,6 +418,10 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
final Paint checkeredBG;
|
||||
@@ -435,9 +439,8 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
|
||||
backgroundPaint = defaultBG != null ? defaultBG : checkeredBG;
|
||||
|
||||
JPopupMenu popup = createBackgroundPopup();
|
||||
|
||||
setComponentPopupMenu(popup);
|
||||
setupActions(pImage);
|
||||
setComponentPopupMenu(createPopupMenu());
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
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();
|
||||
|
||||
popup.add(getActionMap().get(ZOOM_ACTUAL));
|
||||
popup.add(getActionMap().get(ZOOM_IN));
|
||||
popup.add(getActionMap().get(ZOOM_OUT));
|
||||
popup.addSeparator();
|
||||
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Checkered", checkeredBG), popup, group);
|
||||
popup.addSeparator();
|
||||
addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Light", Color.LIGHT_GRAY), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Gray", Color.GRAY), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), popup, group);
|
||||
popup.addSeparator();
|
||||
addCheckBoxItem(new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE), popup, group);
|
||||
JMenu background = new JMenu("Background");
|
||||
popup.add(background);
|
||||
|
||||
ChangeBackgroundAction checkered = new ChangeBackgroundAction("Checkered", checkeredBG);
|
||||
checkered.putValue(Action.SELECTED_KEY, backgroundPaint == checkeredBG);
|
||||
addCheckBoxItem(checkered, background, group);
|
||||
background.addSeparator();
|
||||
addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), background, 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;
|
||||
}
|
||||
|
||||
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);
|
||||
pGroup.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 {
|
||||
|
||||
+32
-4
@@ -73,8 +73,10 @@ public final class ColorSpaces {
|
||||
|
||||
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
|
||||
private final static boolean JDK_HANDLES_RENDERING_INTENTS = SystemUtil.isClassAvailable("java.lang.invoke.CallSite");
|
||||
// OpenJDK 7 seems to handle non-perceptual rendering intents gracefully, so we don't need to fiddle with the profiles.
|
||||
// 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
|
||||
|
||||
@@ -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}.
|
||||
* <p />
|
||||
@@ -227,7 +244,7 @@ public final class ColorSpaces {
|
||||
|
||||
if (profile == null) {
|
||||
// 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");
|
||||
|
||||
if (profile == null) {
|
||||
@@ -337,15 +354,19 @@ public final class ColorSpaces {
|
||||
private static class sRGB {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
|
||||
private static class CIEXYZ {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
|
||||
private static class PYCC {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_PYCC).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
|
||||
private static class GRAY {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_GRAY).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
|
||||
private static class LINEAR_RGB {
|
||||
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());
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
ignore.printStackTrace();
|
||||
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();
|
||||
}
|
||||
|
||||
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.
|
||||
#
|
||||
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()));
|
||||
}
|
||||
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()));
|
||||
}
|
||||
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 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) {
|
||||
try {
|
||||
return cmd.exists() && cmd.canExecute();
|
||||
}
|
||||
catch (SecurityException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* AdobeDCT
|
||||
* AdobeDCTSegment
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 YCC = 1;
|
||||
public static final int YCCK = 2;
|
||||
@@ -45,7 +45,7 @@ class AdobeDCT {
|
||||
final int flags1;
|
||||
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.flags0 = flags0;
|
||||
this.flags1 = flags1;
|
||||
+1
-1
@@ -147,7 +147,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Required
|
||||
|
||||
+5
-4
@@ -131,11 +131,12 @@ class FastCMYKToRGB implements /*BufferedImageOp,*/ RasterOp {
|
||||
return dest;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"PointlessArithmeticExpression"})
|
||||
private void convertCMYKToRGB(byte[] cmyk, byte[] rgb) {
|
||||
rgb[0] = (byte) (((255 - cmyk[0] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255);
|
||||
rgb[1] = (byte) (((255 - cmyk[1] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255);
|
||||
rgb[2] = (byte) (((255 - cmyk[2] & 0xFF) * (255 - cmyk[3] & 0xFF)) / 255);
|
||||
// Adapted from http://www.easyrgb.com/index.php?X=MATH
|
||||
final int k = cmyk[3] & 0xFF;
|
||||
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) {
|
||||
|
||||
+137
-104
@@ -45,6 +45,7 @@ import com.twelvemonkeys.lang.Validate;
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.event.IIOReadUpdateListener;
|
||||
import javax.imageio.event.IIOReadWarningListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
@@ -58,8 +59,26 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader},
|
||||
* with support for CMYK/YCCK JPEGs, non-standard color spaces,broken ICC profiles
|
||||
* and more.
|
||||
* that adds support and properly handles cases where the JRE version throws exceptions.
|
||||
* <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 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 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
|
||||
map.put(JPEG.APP0, JPEGSegmentUtil.ALL_IDS);
|
||||
@@ -181,7 +200,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
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
|
||||
ICC_Profile profile = getEmbeddedICCProfile();
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
|
||||
if (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
||||
if (profile != null) {
|
||||
@@ -216,8 +235,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public
|
||||
ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||
// If delegate can determine the spec, we'll just go with that
|
||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||
|
||||
@@ -231,7 +249,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
switch (csType) {
|
||||
case 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) {
|
||||
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.
|
||||
boolean unsupported = !delegate.getImageTypes(imageIndex).hasNext();
|
||||
|
||||
ICC_Profile profile = getEmbeddedICCProfile();
|
||||
AdobeDCT adobeDCT = getAdobeDCT();
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||
|
||||
// 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)
|
||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||
if (delegate.canReadRaster() && (
|
||||
unsupported ||
|
||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCT.YCCK ||
|
||||
profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
|
||||
profile != null && !ColorSpaces.isCS_sRGB(profile))) {
|
||||
// profile != null && (ColorSpaces.isOffendingColorProfile(profile) || profile.getColorSpaceType() == ColorSpace.TYPE_CMYK))) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Reading using raster and extra conversion");
|
||||
System.out.println("ICC color profile: " + profile);
|
||||
@@ -296,8 +315,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
int origWidth = getWidth(imageIndex);
|
||||
int origHeight = getHeight(imageIndex);
|
||||
|
||||
AdobeDCT adobeDCT = getAdobeDCT();
|
||||
SOF startOfFrame = getSOF();
|
||||
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||
SOFSegment startOfFrame = getSOF();
|
||||
JPEGColorSpace csType = getSourceCSType(adobeDCT, startOfFrame);
|
||||
|
||||
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||
@@ -316,12 +335,12 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else if (intendedCS != null) {
|
||||
// Handle inconsistencies
|
||||
if (startOfFrame.componentsInFrame != intendedCS.getNumComponents()) {
|
||||
if (startOfFrame.componentsInFrame < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
|
||||
if (startOfFrame.componentsInFrame() != intendedCS.getNumComponents()) {
|
||||
if (startOfFrame.componentsInFrame() < 4 && (csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK)) {
|
||||
processWarningOccurred(String.format(
|
||||
"Invalid Adobe App14 marker. Indicates YCCK/CMYK data, but SOF%d has %d color components. " +
|
||||
"Ignoring Adobe App14 marker, assuming YCbCr/RGB data.",
|
||||
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame
|
||||
startOfFrame.marker & 0xf, startOfFrame.componentsInFrame()
|
||||
));
|
||||
|
||||
csType = JPEGColorSpace.YCbCr;
|
||||
@@ -332,12 +351,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
"Embedded ICC color profile is incompatible with image data. " +
|
||||
"Profile indicates %d components, but SOF%d has %d color components. " +
|
||||
"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
|
||||
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);
|
||||
}
|
||||
// Else, pass through with no conversion
|
||||
@@ -346,10 +368,20 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
ColorSpace cmykCS = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@@ -436,7 +468,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
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:
|
||||
|
||||
@@ -478,11 +510,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
if (adobeDCT != null) {
|
||||
switch (adobeDCT.getTransform()) {
|
||||
case AdobeDCT.YCC:
|
||||
case AdobeDCTSegment.YCC:
|
||||
return JPEGColorSpace.YCbCr;
|
||||
case AdobeDCT.YCCK:
|
||||
case AdobeDCTSegment.YCCK:
|
||||
return JPEGColorSpace.YCCK;
|
||||
case AdobeDCT.Unknown:
|
||||
case AdobeDCTSegment.Unknown:
|
||||
if (startOfFrame.components.length == 1) {
|
||||
return JPEGColorSpace.Gray;
|
||||
}
|
||||
@@ -601,14 +633,24 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
catch (IIOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException foo) {
|
||||
foo.printStackTrace();
|
||||
if (DEBUG) {
|
||||
foo.printStackTrace();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
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 {
|
||||
@@ -629,7 +671,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
return appSegments;
|
||||
}
|
||||
|
||||
private SOF getSOF() throws IOException {
|
||||
private SOFSegment getSOF() throws IOException {
|
||||
for (JPEGSegment segment : segments) {
|
||||
if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 ||
|
||||
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);
|
||||
}
|
||||
|
||||
return new SOF(segment.marker(), samplePrecision, lines, samplesPerLine, componentsInFrame, components);
|
||||
return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components);
|
||||
}
|
||||
finally {
|
||||
data.close();
|
||||
@@ -665,7 +707,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
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
|
||||
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)
|
||||
DataInputStream stream = new DataInputStream(adobe.get(0).data());
|
||||
|
||||
return new AdobeDCT(
|
||||
return new AdobeDCTSegment(
|
||||
stream.readUnsignedByte(),
|
||||
stream.readUnsignedShort(),
|
||||
stream.readUnsignedShort(),
|
||||
@@ -717,10 +759,14 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
return data;
|
||||
}
|
||||
|
||||
private ICC_Profile getEmbeddedICCProfile() throws IOException {
|
||||
private ICC_Profile getEmbeddedICCProfile(final boolean allowBadIndexes) throws IOException {
|
||||
// ICC v 1.42 (2006) annex B:
|
||||
// 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)
|
||||
|
||||
// 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");
|
||||
|
||||
if (segments.size() == 1) {
|
||||
@@ -731,7 +777,8 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
int chunkCount = stream.readUnsignedByte();
|
||||
|
||||
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);
|
||||
@@ -742,19 +789,27 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
int chunkNumber = 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;
|
||||
if (chunkCount != segments.size()) {
|
||||
// Some weird JPEGs use 0-based indexes... count == 0 and all numbers == 0.
|
||||
// Others use count == 1, and all numbers == 1.
|
||||
// Handle these by issuing warning
|
||||
processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk count: %d. Ignoring ICC profile.", chunkCount));
|
||||
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) {
|
||||
// Anything else is just ignored
|
||||
processWarningOccurred(String.format("Invalid 'ICC_PROFILE' chunk index: %d. Ignoring ICC profile.", chunkNumber));
|
||||
return null;
|
||||
|
||||
if (!allowBadIndexes) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int count = badICC ? segments.size() : chunkCount;
|
||||
@@ -910,6 +965,51 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
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) {
|
||||
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) {
|
||||
ImageReaderBase.showIt(pImage, pTitle);
|
||||
}
|
||||
@@ -1272,7 +1305,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
// param.setSourceSubsampling(sub, sub, 0, 0);
|
||||
// }
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
// long start = System.currentTimeMillis();
|
||||
BufferedImage image = reader.read(0, param);
|
||||
// System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
// 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);
|
||||
|
||||
// int maxW = 1280;
|
||||
// int maxH = 800;
|
||||
int maxW = 400;
|
||||
int maxH = 400;
|
||||
int maxW = 1280;
|
||||
int maxH = 800;
|
||||
// int maxW = 400;
|
||||
// int maxH = 400;
|
||||
if (image.getWidth() > maxW || image.getHeight() > maxH) {
|
||||
start = System.currentTimeMillis();
|
||||
// start = System.currentTimeMillis();
|
||||
float aspect = reader.getAspectRatio(0);
|
||||
if (aspect >= 1f) {
|
||||
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 {
|
||||
// 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: Sort out ICC_PROFILE issues (duplicate sequence numbers etc)?
|
||||
|
||||
final private ImageInputStream stream;
|
||||
|
||||
@@ -90,6 +91,12 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||
long realPosition = stream.getStreamPosition();
|
||||
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
|
||||
if (isAppSegmentMarker(marker) && marker != JPEG.APP0 && !(marker == JPEG.APP1 && isAppSegmentWithId("Exif", stream)) && marker != JPEG.APP14) {
|
||||
int length = stream.readUnsignedShort(); // Length including length field itself
|
||||
@@ -149,7 +156,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||
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");
|
||||
|
||||
stream.mark();
|
||||
@@ -222,7 +229,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
// 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 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.realStart = realStart;
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
+30
-8
@@ -31,10 +31,12 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.event.IIOReadWarningListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
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/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/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
|
||||
@@ -255,7 +258,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
assertEquals(16, image.getHeight());
|
||||
|
||||
// TODO: Need to test colors!
|
||||
|
||||
|
||||
assertTrue(reader.hasThumbnails(0)); // Should not blow up!
|
||||
}
|
||||
|
||||
@@ -371,7 +374,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
// JFIF with JFXX JPEG encoded thumbnail
|
||||
JPEGImageReader reader = createReader();
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-jfxx-thumbnail-olympus-d320l.jpg")));
|
||||
|
||||
|
||||
assertTrue(reader.hasThumbnails(0));
|
||||
assertEquals(1, reader.getNumThumbnails(0));
|
||||
assertEquals(80, reader.getThumbnailWidth(0, 0));
|
||||
@@ -457,8 +460,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
for (int i = 0; i < strip.getWidth() / 128; i++) {
|
||||
int actualRGB = strip.getRGB(i * 128, 4);
|
||||
assertEquals((actualRGB >> 16) & 0xff, (expectedRGB[i] >> 16) & 0xff, 5);
|
||||
assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5);
|
||||
assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5);
|
||||
assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5);
|
||||
assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,8 +490,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
for (int i = 0; i < thumbnail.getWidth() / 8; i++) {
|
||||
int actualRGB = thumbnail.getRGB(i * 8, 4);
|
||||
assertEquals((actualRGB >> 16) & 0xff, (expectedRGB[i] >> 16) & 0xff, 5);
|
||||
assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5);
|
||||
assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5);
|
||||
assertEquals((actualRGB >> 8) & 0xff, (expectedRGB[i] >> 8) & 0xff, 5);
|
||||
assertEquals((actualRGB) & 0xff, (expectedRGB[i]) & 0xff, 5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +552,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-no-icc.jpg"), new Dimension(100, 100))
|
||||
);
|
||||
|
||||
|
||||
for (TestData data : cmykData) {
|
||||
reader.setInput(data.getInputStream());
|
||||
|
||||
@@ -599,4 +601,24 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@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$
|
||||
* @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public interface EXIF {
|
||||
// See http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
|
||||
int TAG_EXPOSURE_TIME = 33434;
|
||||
|
||||
+23
-1
@@ -88,7 +88,7 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "StripOffsets";
|
||||
case TIFF.TAG_ORIENTATION:
|
||||
return "Orientation";
|
||||
case TIFF.TAG_SAMPLES_PER_PIXELS:
|
||||
case TIFF.TAG_SAMPLES_PER_PIXEL:
|
||||
return "SamplesPerPixels";
|
||||
case TIFF.TAG_ROWS_PER_STRIP:
|
||||
return "RowsPerStrip";
|
||||
@@ -209,6 +209,28 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "PixelYDimension";
|
||||
|
||||
// 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;
|
||||
|
||||
+13
-1
@@ -35,6 +35,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public interface TIFF {
|
||||
int TIFF_MAGIC = 42;
|
||||
|
||||
@@ -98,8 +99,9 @@ public interface TIFF {
|
||||
int TAG_BITS_PER_SAMPLE = 258;
|
||||
int TAG_COMPRESSION = 259;
|
||||
int TAG_PHOTOMETRIC_INTERPRETATION = 262;
|
||||
int TAG_FILL_ORDER = 266;
|
||||
int TAG_ORIENTATION = 274;
|
||||
int TAG_SAMPLES_PER_PIXELS = 277;
|
||||
int TAG_SAMPLES_PER_PIXEL = 277;
|
||||
int TAG_PLANAR_CONFIGURATION = 284;
|
||||
int TAG_SAMPLE_FORMAT = 339;
|
||||
int TAG_YCBCR_SUB_SAMPLING = 530;
|
||||
@@ -113,6 +115,7 @@ public interface TIFF {
|
||||
int TAG_STRIP_OFFSETS = 273;
|
||||
int TAG_ROWS_PER_STRIP = 278;
|
||||
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_LENGTH = 514;
|
||||
|
||||
@@ -124,6 +127,7 @@ public interface TIFF {
|
||||
int TAG_PRIMARY_CHROMATICITIES = 319;
|
||||
int TAG_COLOR_MAP = 320;
|
||||
int TAG_EXTRA_SAMPLES = 338;
|
||||
int TAG_TRANSFER_RANGE = 342;
|
||||
int TAG_YCBCR_COEFFICIENTS = 529;
|
||||
int TAG_REFERENCE_BLACK_WHITE = 532;
|
||||
|
||||
@@ -133,6 +137,7 @@ public interface TIFF {
|
||||
int TAG_IMAGE_DESCRIPTION = 270;
|
||||
int TAG_MAKE = 271;
|
||||
int TAG_MODEL = 272;
|
||||
int TAG_PAGE_NUMBER = 297;
|
||||
int TAG_SOFTWARE = 305;
|
||||
int TAG_ARTIST = 315;
|
||||
int TAG_HOST_COMPUTER = 316;
|
||||
@@ -161,5 +166,12 @@ public interface TIFF {
|
||||
int TAG_TILE_OFFSETS = 324;
|
||||
int TAG_TILE_BYTE_COUNTS = 325;
|
||||
|
||||
// JPEG
|
||||
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;
|
||||
/** End of Image segment marker (EOI). */
|
||||
int EOI = 0xFFD9;
|
||||
/** Start of Stream segment marker (SOS). */
|
||||
|
||||
/** Start of Scan segment marker (SOS). */
|
||||
int SOS = 0xFFDA;
|
||||
|
||||
/** Define Quantization Tables segment marker (DQT). */
|
||||
@@ -81,6 +82,10 @@ public interface JPEG {
|
||||
int SOF14 = 0xFFCE;
|
||||
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
|
||||
// "JFIF" APP0
|
||||
// "JFXX" APP0
|
||||
@@ -89,6 +94,6 @@ public interface JPEG {
|
||||
// "Adobe" APP14
|
||||
|
||||
// Possibly
|
||||
// "http://ns.adobe.com/xap/1.0/" (XMP)
|
||||
// "Photoshop 3.0" (Contains IPTC)
|
||||
// "http://ns.adobe.com/xap/1.0/" (XMP) APP1
|
||||
// "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 {
|
||||
// System.err.println("tables: " + Arrays.deepToString(tables));
|
||||
|
||||
// TODO: Determine lossless JPEG
|
||||
// if (lossless) {
|
||||
// return 100; // TODO: Sums can be 100... Is lossless not 100?
|
||||
// }
|
||||
// TODO: Determine lossless JPEG, it's an entirely different algorithm
|
||||
|
||||
int qvalue;
|
||||
|
||||
|
||||
+18
-6
@@ -95,7 +95,8 @@ public final class JPEGSegmentUtil {
|
||||
|
||||
JPEGSegment segment;
|
||||
try {
|
||||
while (!isImageDone(segment = readSegment(stream, segmentIdentifiers))) {
|
||||
do {
|
||||
segment = readSegment(stream, segmentIdentifiers);
|
||||
// System.err.println("segment: " + segment);
|
||||
|
||||
if (isRequested(segment, segmentIdentifiers)) {
|
||||
@@ -106,6 +107,7 @@ public final class JPEGSegmentUtil {
|
||||
segments.add(segment);
|
||||
}
|
||||
}
|
||||
while (!isImageDone(segment));
|
||||
}
|
||||
catch (EOFException ignore) {
|
||||
// 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();
|
||||
|
||||
// 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
|
||||
|
||||
byte[] data;
|
||||
@@ -191,7 +203,7 @@ public final class JPEGSegmentUtil {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
public boolean contains(final Object o) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -203,13 +215,13 @@ public final class JPEGSegmentUtil {
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
public boolean containsKey(final Object key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -221,7 +233,7 @@ public final class JPEGSegmentUtil {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> get(Object key) {
|
||||
public List<String> get(final Object key) {
|
||||
return containsKey(key) ? ALL_IDS : null;
|
||||
|
||||
}
|
||||
|
||||
+17
-2
@@ -151,19 +151,21 @@ public class JPEGSegmentUtilTest {
|
||||
@Test
|
||||
public void testReadAll() throws IOException {
|
||||
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(), null, segments.get(3).identifier());
|
||||
assertEquals(segments.toString(), JPEG.SOS, segments.get(segments.size() - 1).marker());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadAllAlt() throws IOException {
|
||||
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(), null, segments.get(23).identifier());
|
||||
assertEquals(segments.toString(), JPEG.SOS, segments.get(segments.size() - 1).marker());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -194,4 +196,17 @@ public class JPEGSegmentUtilTest {
|
||||
assertEquals(JPEG.APP14, segments.get(21).marker());
|
||||
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
|
||||
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();
|
||||
}
|
||||
read += lengths.length;
|
||||
|
||||
+179
-124
@@ -33,16 +33,18 @@ import com.twelvemonkeys.io.enc.Decoder;
|
||||
|
||||
import java.io.IOException;
|
||||
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 last modified by $Author: haraldk$
|
||||
* @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. */
|
||||
static final int CLEAR_CODE = 256;
|
||||
/** End of Information. */
|
||||
@@ -51,44 +53,44 @@ final class LZWDecoder implements Decoder {
|
||||
private static final int MIN_BITS = 9;
|
||||
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 int bitPos;
|
||||
private final boolean compatibilityMode;
|
||||
|
||||
// TODO: Consider speeding things up with a "string" type (instead of the inner byte[]),
|
||||
// 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 final String[] table;
|
||||
private int tableLength;
|
||||
private int bitsPerCode;
|
||||
int bitsPerCode;
|
||||
private int oldCode = CLEAR_CODE;
|
||||
private int maxCode;
|
||||
int bitMask;
|
||||
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++) {
|
||||
table[i] = new byte[] {(byte) i};
|
||||
table[i] = new String((byte) i);
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
LZWDecoder() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
private int maxCodeFor(final int bits) {
|
||||
return reverseBitOrder ? (1 << bits) - 2 : (1 << bits) - 1;
|
||||
private static int bitmaskFor(final int bits) {
|
||||
return (1 << bits) - 1;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
tableLength = 258;
|
||||
bitsPerCode = MIN_BITS;
|
||||
maxCode = maxCodeFor(bitsPerCode);
|
||||
bitMask = bitmaskFor(bitsPerCode);
|
||||
maxCode = maxCode();
|
||||
maxString = 1;
|
||||
}
|
||||
|
||||
@@ -107,25 +109,17 @@ final class LZWDecoder implements Decoder {
|
||||
break;
|
||||
}
|
||||
|
||||
bufferPos += writeString(table[code], buffer, bufferPos);
|
||||
bufferPos += table[code].writeTo(buffer, bufferPos);
|
||||
}
|
||||
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)) {
|
||||
bufferPos += writeString(table[code], buffer, bufferPos);
|
||||
addStringToTable(concatenate(table[oldCode], table[code][0]));
|
||||
bufferPos += table[code].writeTo(buffer, bufferPos);
|
||||
addStringToTable(table[oldCode].concatenate(table[code].firstChar));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -141,29 +135,23 @@ final class LZWDecoder implements Decoder {
|
||||
return bufferPos;
|
||||
}
|
||||
|
||||
private static byte[] concatenate(final byte[] string, final byte firstChar) {
|
||||
byte[] result = Arrays.copyOf(string, string.length + 1);
|
||||
result[string.length] = firstChar;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addStringToTable(final byte[] string) throws IOException {
|
||||
private void addStringToTable(final String string) throws IOException {
|
||||
table[tableLength++] = string;
|
||||
|
||||
if (tableLength >= maxCode) {
|
||||
if (tableLength > maxCode) {
|
||||
bitsPerCode++;
|
||||
|
||||
if (bitsPerCode > MAX_BITS) {
|
||||
if (reverseBitOrder) {
|
||||
if (compatibilityMode) {
|
||||
bitsPerCode--;
|
||||
}
|
||||
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) {
|
||||
@@ -171,89 +159,14 @@ final class LZWDecoder implements Decoder {
|
||||
}
|
||||
}
|
||||
|
||||
private static int writeString(final byte[] string, final byte[] buffer, final int bufferPos) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
protected abstract int maxCode();
|
||||
|
||||
private boolean isInTable(int code) {
|
||||
return code < tableLength;
|
||||
}
|
||||
|
||||
private int getNextCode(final InputStream stream) throws IOException {
|
||||
if (eofReached) {
|
||||
return EOI_CODE;
|
||||
}
|
||||
protected abstract int getNextCode(final InputStream stream) throws IOException;
|
||||
|
||||
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 {
|
||||
stream.mark(2);
|
||||
@@ -267,5 +180,147 @@ final class LZWDecoder implements Decoder {
|
||||
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 {
|
||||
int COMPRESSION_NONE = 1;
|
||||
int COMPRESSION_CCITT_HUFFMAN = 2;
|
||||
int COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE = 2;
|
||||
int COMPRESSION_PACKBITS = 32773;
|
||||
|
||||
int PHOTOMETRIC_WHITE_IS_ZERO = 0;
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ interface TIFFCustom {
|
||||
int COMPRESSION_JBIG = 34661;
|
||||
int COMPRESSION_SGILOG = 34676;
|
||||
int COMPRESSION_SGILOG24 = 34677;
|
||||
int COMPRESSION_JP2000 = 34712;
|
||||
int COMPRESSION_JPEG2000 = 34712;
|
||||
|
||||
int PHOTOMETRIC_LOGL = 32844;
|
||||
int PHOTOMETRIC_LOGLUV = 32845;
|
||||
|
||||
+7
@@ -65,4 +65,11 @@ interface TIFFExtension {
|
||||
int SAMPLEFORMAT_INT = 2;
|
||||
int SAMPLEFORMAT_FP = 3;
|
||||
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.Entry;
|
||||
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.jpeg.JPEG;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsDecoder;
|
||||
@@ -58,9 +61,7 @@ import java.awt.color.ICC_Profile;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
@@ -105,28 +106,30 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
// TODO: Source region (*tests should be failing*)
|
||||
// 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:
|
||||
// TODO: Implement readAsRenderedImage to allow tiled renderImage?
|
||||
// For some layouts, we could do reads super-fast with a memory mapped buffer.
|
||||
// TODO: Implement readAsRaster directly
|
||||
|
||||
// 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)
|
||||
// TODO: Support Compression 2 (CCITT Modified Huffman) for bi-level images
|
||||
// TODO: IIOMetadata (stay close to Sun's TIFF metadata)
|
||||
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
|
||||
|
||||
// TODOs Extension support
|
||||
// TODO: Support PlanarConfiguration 2
|
||||
// TODO: Support ICCProfile (fully)
|
||||
// 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 34661 (JBIG)? Depends on JBIG ImageReader
|
||||
|
||||
// DONE:
|
||||
// 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 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
|
||||
|
||||
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("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();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (entry == null) {
|
||||
@@ -184,7 +187,19 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
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 {
|
||||
@@ -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
|
||||
int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR);
|
||||
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 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:
|
||||
// JPEG reader will handle YCbCr to RGB for us, we'll have to do it ourselves if not JPEG...
|
||||
// TODO: Handle YCbCrSubsampling (up-scaler stream, or read data as-is + up-sample (sub-)raster after read? Apply smoothing?)
|
||||
// JPEG reader will handle YCbCr to RGB for us, otherwise we'll convert while reading
|
||||
// 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:
|
||||
// RGB
|
||||
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
|
||||
@@ -274,23 +289,25 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
// TODO: Consult ExtraSamples!
|
||||
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:
|
||||
if (bitsPerSample == 8 && cs.isCS_sRGB()) {
|
||||
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:
|
||||
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
|
||||
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:
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
default:
|
||||
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:
|
||||
// Transparency mask
|
||||
|
||||
// TODO: Known extensions
|
||||
throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation);
|
||||
default:
|
||||
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
|
||||
// Strips are top/down, tiles are left/right, top/down
|
||||
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[] stripTileByteCounts;
|
||||
|
||||
@@ -479,9 +509,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
|
||||
int row = 0;
|
||||
|
||||
// Read data
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
switch (compression) {
|
||||
// TIFF Baseline
|
||||
case TIFFBaseline.COMPRESSION_NONE:
|
||||
@@ -494,9 +521,70 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
// LZW
|
||||
case TIFFExtension.COMPRESSION_ZLIB:
|
||||
// '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
|
||||
|
||||
// General uncompressed/compressed reading
|
||||
for (int y = 0; y < tilesDown; y++) {
|
||||
int col = 0;
|
||||
@@ -509,7 +597,8 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
imageInput.seek(stripTileOffsets[i]);
|
||||
|
||||
DataInput input;
|
||||
if (compression == TIFFBaseline.COMPRESSION_NONE) {
|
||||
if (compression == TIFFBaseline.COMPRESSION_NONE && interpretation != TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||
// No need for transformation, fast forward
|
||||
input = imageInput;
|
||||
}
|
||||
else {
|
||||
@@ -517,15 +606,21 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
? IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts[i])
|
||||
: 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
|
||||
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
|
||||
? new DataInputStream(createDecoderInputStream(compression, adapter))
|
||||
: new LittleEndianDataInputStream(createDecoderInputStream(compression, adapter));
|
||||
|
||||
? new DataInputStream(adapter)
|
||||
: new LittleEndianDataInputStream(adapter);
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
break;
|
||||
@@ -549,6 +644,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
case TIFFExtension.COMPRESSION_JPEG:
|
||||
// JPEG ('new-style' JPEG)
|
||||
// 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
|
||||
ImageReader jpegReader = new JPEGImageReader(getOriginatingProvider());
|
||||
@@ -565,6 +661,9 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
// Might have something to do with subsampling?
|
||||
// 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));
|
||||
|
||||
// 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...
|
||||
}
|
||||
|
||||
// Read data
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
for (int y = 0; y < tilesDown; y++) {
|
||||
int col = 0;
|
||||
int rowsInTile = Math.min(stripTileHeight, height - row);
|
||||
@@ -629,14 +731,14 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
int colsInTile = Math.min(stripTileWidth, width - col);
|
||||
|
||||
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 {
|
||||
jpegReader.setInput(subStream);
|
||||
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
|
||||
// In the latter case we will have to use readAsRaster and do color conversion ourselves
|
||||
jpegReader.read(0, jpegParam);
|
||||
}
|
||||
finally {
|
||||
@@ -662,15 +764,202 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
break;
|
||||
|
||||
case TIFFBaseline.COMPRESSION_CCITT_HUFFMAN:
|
||||
// CCITT modified Huffman
|
||||
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
||||
// 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:
|
||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||
// CCITT Group 3 fax encoding
|
||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
||||
// 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);
|
||||
default:
|
||||
@@ -682,13 +971,83 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
return destination;
|
||||
}
|
||||
|
||||
private void readStripTileData(final WritableRaster rowRaster, final int interpretation, final int predictor,
|
||||
final WritableRaster raster, final int numBands, final int col, final int startRow,
|
||||
private static InputStream createJFIFStream(WritableRaster raster, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
|
||||
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)
|
||||
throws IOException {
|
||||
switch (rowRaster.getTransferType()) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
|
||||
for (int j = 0; j < rowsInStrip; j++) {
|
||||
int row = startRow + j;
|
||||
|
||||
@@ -697,19 +1056,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
||||
@@ -724,6 +1070,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
short [] rowDataShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
|
||||
for (int j = 0; j < rowsInStrip; j++) {
|
||||
int row = startRow + j;
|
||||
|
||||
@@ -735,7 +1082,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
rowDataShort[k] = input.readShort();
|
||||
}
|
||||
|
||||
unPredict(predictor, colsInStrip, 1, numBands, rowDataShort);
|
||||
normalizeBlack(interpretation, rowDataShort);
|
||||
|
||||
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
||||
@@ -750,6 +1096,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
break;
|
||||
case DataBuffer.TYPE_INT:
|
||||
int [] rowDataInt = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
|
||||
|
||||
for (int j = 0; j < rowsInStrip; j++) {
|
||||
int row = startRow + j;
|
||||
|
||||
@@ -761,7 +1108,6 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
rowDataInt[k] = input.readInt();
|
||||
}
|
||||
|
||||
unPredict(predictor, colsInStrip, 1, numBands, rowDataInt);
|
||||
normalizeBlack(interpretation, rowDataInt);
|
||||
|
||||
if (colsInStrip == rowRaster.getWidth() && col + colsInStrip <= raster.getWidth()) {
|
||||
@@ -804,75 +1150,40 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedParameters")
|
||||
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 {
|
||||
private InputStream createDecompressorStream(final int compression, final int width, final InputStream stream) throws IOException {
|
||||
switch (compression) {
|
||||
case TIFFBaseline.COMPRESSION_NONE:
|
||||
return stream;
|
||||
case TIFFBaseline.COMPRESSION_PACKBITS:
|
||||
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
|
||||
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_DEFLATE:
|
||||
// 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);
|
||||
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:
|
||||
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 {
|
||||
Entry entry = currentIFD.getEntryById(tag);
|
||||
if (entry == null) {
|
||||
@@ -893,7 +1204,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
short[] shorts = (short[]) entry.getValue();
|
||||
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];
|
||||
}
|
||||
}
|
||||
@@ -901,7 +1212,7 @@ public class TIFFImageReader extends ImageReaderBase {
|
||||
int[] ints = (int[]) entry.getValue();
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
+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.DecoderStream;
|
||||
import com.twelvemonkeys.io.enc.Encoder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -60,24 +59,15 @@ public class LZWDecoderTest extends DecoderAbstractTestCase {
|
||||
|
||||
@Test
|
||||
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
|
||||
|
||||
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
|
||||
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");
|
||||
|
||||
assertSameStreamContents(unpacked, stream);
|
||||
@@ -111,7 +101,7 @@ public class LZWDecoderTest extends DecoderAbstractTestCase {
|
||||
|
||||
@Override
|
||||
public Decoder createDecoder() {
|
||||
return new LZWDecoder();
|
||||
return LZWDecoder.create(false);
|
||||
}
|
||||
|
||||
@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_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/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/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/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() {
|
||||
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.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -28,20 +28,24 @@
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* CCITT Group 3 One-Dimensional (G31D) "No EOLs" Decoder.
|
||||
* YCbCrUpsamplerStreamTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 {
|
||||
public int decode(final InputStream stream, final byte[] buffer) throws IOException {
|
||||
throw new UnsupportedOperationException("Method decode not implemented"); // TODO: Implement
|
||||
@Ignore
|
||||
public class YCbCrUpsamplerStreamTest extends InputStreamAbstractTestCase {
|
||||
// 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.
+3
-2
@@ -45,7 +45,7 @@
|
||||
<module>imageio-jmagick</module>
|
||||
|
||||
<!-- Test cases for the JRE provided ImageIO plugins -->
|
||||
<module>imageio-reference</module>
|
||||
<module>imageio-reference</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
@@ -90,6 +90,7 @@
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.8.5</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -115,5 +116,5 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
|
||||
Reference in New Issue
Block a user