https://github.com/pascalleclercq/appengine-awt
diff --git a/src/main/java/com/google/code/appengine/imageio/ImageIO.java b/src/main/java/com/google/code/appengine/imageio/ImageIO.java
index f1b5cd7..68c4533 100644
--- a/src/main/java/com/google/code/appengine/imageio/ImageIO.java
+++ b/src/main/java/com/google/code/appengine/imageio/ImageIO.java
@@ -22,31 +22,20 @@
import org.apache.harmony.luni.util.NotImplementedException;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.Arrays;
import java.util.List;
import java.net.URL;
import org.apache.harmony.x.imageio.internal.nls.Messages;
-import org.apache.sanselan.ImageReadException;
-import org.apache.sanselan.Sanselan;
-import org.apache.sanselan.common.byteSources.ByteSource;
-import org.apache.sanselan.common.byteSources.ByteSourceInputStream;
-import org.apache.sanselan.formats.gif.GifImageParser;
-import org.apache.sanselan.formats.png.PngImageParser;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.Imaging;
import com.google.code.appengine.awt.image.BufferedImage;
import com.google.code.appengine.awt.image.RenderedImage;
-import com.google.code.appengine.imageio.ImageReader;
-import com.google.code.appengine.imageio.ImageTranscoder;
-import com.google.code.appengine.imageio.ImageTypeSpecifier;
-import com.google.code.appengine.imageio.ImageWriter;
import com.google.code.appengine.imageio.spi.*;
import com.google.code.appengine.imageio.stream.ImageInputStream;
import com.google.code.appengine.imageio.stream.ImageOutputStream;
@@ -338,7 +327,7 @@ else if(name.endsWith(".gif")) {
}*/
try {
- return Sanselan.getBufferedImage(input);
+ return Imaging.getBufferedImage(input);
} catch (ImageReadException e) {
// TODO Auto-generated catch block
throw new IOException(e);
@@ -354,7 +343,7 @@ public static BufferedImage read(InputStream input) throws IOException {
}
try {
- return Sanselan.getBufferedImage(input);
+ return Imaging.getBufferedImage(input);
} catch (ImageReadException e) {
// TODO Auto-generated catch block
throw new IOException(e);
diff --git a/src/main/java/com/google/code/appengine/imageio/stream/FileCacheImageInputStream.java b/src/main/java/com/google/code/appengine/imageio/stream/FileCacheImageInputStream.java
index 5b12fd0..92156f1 100644
--- a/src/main/java/com/google/code/appengine/imageio/stream/FileCacheImageInputStream.java
+++ b/src/main/java/com/google/code/appengine/imageio/stream/FileCacheImageInputStream.java
@@ -18,18 +18,24 @@
package com.google.code.appengine.imageio.stream;
-import java.io.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import com.newatlanta.commons.vfs.provider.gae.GaeFileObject;
+import com.newatlanta.commons.vfs.provider.gae.GaeFileSystemManager;
+import com.newatlanta.commons.vfs.provider.gae.GaeRandomAccessContent;
+import com.newatlanta.commons.vfs.provider.gae.GaeVFS;
+import org.apache.commons.vfs.FileType;
+import org.apache.commons.vfs.util.RandomAccessMode;
import org.apache.harmony.x.imageio.internal.nls.Messages;
-import com.google.code.appengine.imageio.stream.FileCacheImageOutputStream;
-import com.google.code.appengine.imageio.stream.ImageInputStreamImpl;
public class FileCacheImageInputStream extends ImageInputStreamImpl {
private InputStream is;
- private File file;
- private RandomAccessFile raf;
+ private GaeFileObject file;
+ private GaeRandomAccessContent rac;
public FileCacheImageInputStream(InputStream stream, File cacheDir) throws IOException {
@@ -38,55 +44,65 @@ public FileCacheImageInputStream(InputStream stream, File cacheDir) throws IOExc
}
is = stream;
- if (cacheDir == null || cacheDir.isDirectory()) {
- file = File.createTempFile(FileCacheImageOutputStream.IIO_TEMP_FILE_PREFIX, null, cacheDir);
- file.deleteOnExit();
+ // Retrieve the file system manager
+ GaeFileSystemManager fsManager = GaeVFS.getManager();
+
+ // Convert cacheDir to a FileObject
+ GaeFileObject cacheDirObj = (cacheDir == null) ? null :
+ (GaeFileObject)fsManager.resolveFile(cacheDir.getPath());
+
+ // if cache dir is null, or if it is a directory
+ if (cacheDirObj == null || cacheDirObj.getType() == FileType.FOLDER) {
+
+ // original code:
+ file = FileObjectUtils.createTempFileObject(fsManager,
+ FileCacheImageOutputStream.IIO_TEMP_FILE_PREFIX, null, cacheDirObj);
} else {
throw new IllegalArgumentException(Messages.getString("imageio.0B"));
}
- raf = new RandomAccessFile(file, "rw");
+ rac = new GaeRandomAccessContent(file, RandomAccessMode.READWRITE);
}
@Override
public int read() throws IOException {
bitOffset = 0;
- if (streamPos >= raf.length()) {
+ if (streamPos >= rac.length()) {
int b = is.read();
if (b < 0) {
return -1;
}
- raf.seek(streamPos++);
- raf.write(b);
+ rac.seek(streamPos++);
+ rac.write(b);
return b;
}
- raf.seek(streamPos++);
- return raf.read();
+ rac.seek(streamPos++);
+ return rac.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
bitOffset = 0;
- if (streamPos >= raf.length()) {
+ if (streamPos >= rac.length()) {
int nBytes = is.read(b, off, len);
if (nBytes < 0) {
return -1;
}
- raf.seek(streamPos);
- raf.write(b, off, nBytes);
+ rac.seek(streamPos);
+ rac.write(b, off, nBytes);
streamPos += nBytes;
return nBytes;
}
- raf.seek(streamPos);
- int nBytes = raf.read(b, off, len);
+ rac.seek(streamPos);
+ int nBytes = rac.read(b, off, len);
streamPos += nBytes;
return nBytes;
}
@@ -109,7 +125,7 @@ public boolean isCachedMemory() {
@Override
public void close() throws IOException {
super.close();
- raf.close();
+ rac.close();
file.delete();
}
}
diff --git a/src/main/java/com/google/code/appengine/imageio/stream/FileObjectUtils.java b/src/main/java/com/google/code/appengine/imageio/stream/FileObjectUtils.java
new file mode 100644
index 0000000..d8bc09c
--- /dev/null
+++ b/src/main/java/com/google/code/appengine/imageio/stream/FileObjectUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.google.code.appengine.imageio.stream;
+
+import com.newatlanta.commons.vfs.provider.gae.GaeFileObject;
+import org.apache.commons.vfs.FileSystemException;
+import org.apache.commons.vfs.FileSystemManager;
+import org.apache.commons.vfs.FileType;
+
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+/**
+ * Utility class for GaeFileObject. Contains methods for things such as creating a temporary file object.
+ */
+public class FileObjectUtils {
+ private FileObjectUtils(){}
+
+ public final static class RandStringGenerator {
+ private static SecureRandom random = new SecureRandom();
+
+ public static String nextString() {
+ return new BigInteger(130, random).toString(32);
+ }
+ }
+
+ public static GaeFileObject createTempFileObject(FileSystemManager fsManager, String prefix, String suffix, GaeFileObject directory) throws FileSystemException {
+ if (suffix == null) suffix = ".tmp";
+ GaeFileObject file;
+
+ if (directory == null || (directory.getType()== FileType.FOLDER)) {
+ String name = RandStringGenerator.nextString();
+ String filename = prefix + name + suffix;
+ file = (GaeFileObject)fsManager.resolveFile(filename);
+ while (file.exists()) {
+ name = RandStringGenerator.nextString();
+ filename = prefix + name + suffix;
+ file = (GaeFileObject)fsManager.resolveFile(filename);
+ }
+ // guaranteed file doesn't exist at this point
+ file.createFile();
+ } else {
+ throw new IllegalArgumentException("directory must be a directory!");
+ }
+
+ return file;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/ColorTools.java b/src/main/java/org/apache/commons/imaging/ColorTools.java
similarity index 57%
rename from src/main/java/org/apache/sanselan/ColorTools.java
rename to src/main/java/org/apache/commons/imaging/ColorTools.java
index 6541cdb..bdd340d 100644
--- a/src/main/java/org/apache/sanselan/ColorTools.java
+++ b/src/main/java/org/apache/commons/imaging/ColorTools.java
@@ -1,259 +1,248 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.sanselan;
-
-import java.io.File;
-import java.io.IOException;
-
-import com.google.code.appengine.awt.RenderingHints;
-import com.google.code.appengine.awt.color.ColorSpace;
-import com.google.code.appengine.awt.color.ICC_ColorSpace;
-import com.google.code.appengine.awt.color.ICC_Profile;
-import com.google.code.appengine.awt.image.BufferedImage;
-import com.google.code.appengine.awt.image.ColorConvertOp;
-import com.google.code.appengine.awt.image.ColorModel;
-import com.google.code.appengine.awt.image.ComponentColorModel;
-import com.google.code.appengine.awt.image.DirectColorModel;
-import com.google.code.appengine.awt.image.ImagingOpException;
-
-
-/**
- * This class is a mess and needs to be cleaned up.
- */
-public class ColorTools {
-
- public BufferedImage correctImage(BufferedImage src, File file)
- throws ImageReadException, IOException {
- ICC_Profile icc = Sanselan.getICCProfile(file);
- if (icc == null)
- return src;
-
- ICC_ColorSpace cs = new ICC_ColorSpace(icc);
-
- BufferedImage dst = convertFromColorSpace(src, cs);
- return dst;
- }
-
- public BufferedImage relabelColorSpace(BufferedImage bi, ICC_Profile profile)
- throws ImagingOpException {
- ICC_ColorSpace cs = new ICC_ColorSpace(profile);
-
- return relabelColorSpace(bi, cs);
- }
-
- public BufferedImage relabelColorSpace(BufferedImage bi, ColorSpace cs)
- throws ImagingOpException {
- // This does not do the conversion. It tries to relabel the
- // BufferedImage
- // with its actual (presumably correct) Colorspace.
- // use this when the image is mislabeled, presumably having been
- // wrongly assumed to be sRGB
-
- ColorModel cm = deriveColorModel(bi, cs);
-
- return relabelColorSpace(bi, cm);
-
- }
-
- public BufferedImage relabelColorSpace(BufferedImage bi, ColorModel cm)
- throws ImagingOpException {
- // This does not do the conversion. It tries to relabel the
- // BufferedImage
- // with its actual (presumably correct) Colorspace.
- // use this when the image is mislabeled, presumably having been
- // wrongly assumed to be sRGB
-
- BufferedImage result = new BufferedImage(cm, bi.getRaster(), false,
- null);
-
- return result;
- }
-
- public ColorModel deriveColorModel(BufferedImage bi, ColorSpace cs)
- throws ImagingOpException {
- // boolean hasAlpha = (bi.getAlphaRaster() != null);
- return deriveColorModel(bi, cs, false);
- }
-
- public ColorModel deriveColorModel(BufferedImage bi, ColorSpace cs,
- boolean force_no_alpha) throws ImagingOpException {
- return deriveColorModel(bi.getColorModel(), cs, force_no_alpha);
- }
-
- public ColorModel deriveColorModel(ColorModel old_cm, ColorSpace cs,
- boolean force_no_alpha) throws ImagingOpException {
-
- if (old_cm instanceof ComponentColorModel) {
- ComponentColorModel ccm = (ComponentColorModel) old_cm;
- // ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
- if (force_no_alpha)
- return new ComponentColorModel(cs, false, false,
- ComponentColorModel.OPAQUE, ccm.getTransferType());
- else
- return new ComponentColorModel(cs, ccm.hasAlpha(), ccm
- .isAlphaPremultiplied(), ccm.getTransparency(), ccm
- .getTransferType());
- } else if (old_cm instanceof DirectColorModel) {
- DirectColorModel dcm = (DirectColorModel) old_cm;
-
- int old_mask = dcm.getRedMask() | dcm.getGreenMask()
- | dcm.getBlueMask() | dcm.getAlphaMask();
-
- int old_bits = count_bits_in_mask(old_mask);
-
- return new DirectColorModel(cs, old_bits, dcm.getRedMask(), dcm
- .getGreenMask(), dcm.getBlueMask(), dcm.getAlphaMask(), dcm
- .isAlphaPremultiplied(), dcm.getTransferType());
- }
- // else if (old_cm instanceof PackedColorModel)
- // {
- // PackedColorModel pcm = (PackedColorModel) old_cm;
- //
- // // int old_mask = dcm.getRedMask() | dcm.getGreenMask()
- // // | dcm.getBlueMask() | dcm.getAlphaMask();
- //
- // int old_masks[] = pcm.getMasks();
- // // System.out.println("old_mask: " + old_mask);
- // int old_bits = count_bits_in_mask(old_masks);
- // // System.out.println("old_bits: " + old_bits);
- //
- // // PackedColorModel(ColorSpace space, int bits, int rmask, int gmask,
- // int bmask, int amask, boolean isAlphaPremultiplied, int trans, int
- // transferType)
- // cm = new PackedColorModel(cs, old_bits, pcm.getMasks(),
- //
- // pcm.isAlphaPremultiplied(), pcm.getTransparency(), pcm
- // .getTransferType());
- // }
-
- throw new ImagingOpException("Could not clone unknown ColorModel Type.");
- }
-
- private int count_bits_in_mask(int i) {
- int count = 0;
- while (i != 0) {
- count += (i & 1);
- // uses the unsigned version of java's right shift operator,
- // so that left hand bits are zeroed.
- i >>>= 1;
- }
- return count;
- }
-
- public BufferedImage convertToColorSpace(BufferedImage bi, ColorSpace to) {
- ColorSpace from = bi.getColorModel().getColorSpace();
-
- RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING,
- RenderingHints.VALUE_RENDER_QUALITY);
- hints.put(RenderingHints.KEY_COLOR_RENDERING,
- RenderingHints.VALUE_COLOR_RENDER_QUALITY);
- hints.put(RenderingHints.KEY_DITHERING,
- RenderingHints.VALUE_DITHER_ENABLE);
-
- ColorConvertOp op = new ColorConvertOp(from, to, hints);
-
- BufferedImage result = op.filter(bi, null);
-
- result = relabelColorSpace(result, to);
-
- return result;
- }
-
- public BufferedImage convertTosRGB(BufferedImage bi) {
- ColorSpace cs_sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
-
- ColorModel srgbCM = ColorModel.getRGBdefault();
- cs_sRGB = srgbCM.getColorSpace();
-
- return convertToColorSpace(bi, cs_sRGB);
- }
-
- protected BufferedImage convertFromColorSpace(BufferedImage bi,
- ColorSpace from) {
- ColorSpace cs_sRGB;
-
- ColorModel srgbCM = ColorModel.getRGBdefault();
- cs_sRGB = srgbCM.getColorSpace();
-
- return convertBetweenColorSpaces(bi, from, cs_sRGB);
-
- }
-
- public BufferedImage convertBetweenICCProfiles(BufferedImage bi,
- ICC_Profile from, ICC_Profile to) {
- ICC_ColorSpace cs_from = new ICC_ColorSpace(from);
- ICC_ColorSpace cs_to = new ICC_ColorSpace(to);
-
- return convertBetweenColorSpaces(bi, cs_from, cs_to);
- }
-
- public BufferedImage convertToICCProfile(BufferedImage bi, ICC_Profile to) {
- ICC_ColorSpace cs_to = new ICC_ColorSpace(to);
-
- return convertToColorSpace(bi, cs_to);
- }
-
- public BufferedImage convertBetweenColorSpacesX2(BufferedImage bi,
- ColorSpace from, ColorSpace to) {
- RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING,
- RenderingHints.VALUE_RENDER_QUALITY);
- hints.put(RenderingHints.KEY_COLOR_RENDERING,
- RenderingHints.VALUE_COLOR_RENDER_QUALITY);
- hints.put(RenderingHints.KEY_DITHERING,
- RenderingHints.VALUE_DITHER_ENABLE);
-
- // bi = relabelColorSpace(bi, cs);
- // dumpColorSpace("\tcs_sRGB", cs_sRGB);
- // dumpColorSpace("\tColorModel.getRGBdefaultc",
- // ColorModel.getRGBdefault().getColorSpace());
-
- bi = relabelColorSpace(bi, from);
- ColorConvertOp op = new ColorConvertOp(from, to, hints);
- bi = op.filter(bi, null);
-
- bi = relabelColorSpace(bi, from);
-
- bi = op.filter(bi, null);
-
- bi = relabelColorSpace(bi, to);
-
- return bi;
-
- }
-
- public BufferedImage convertBetweenColorSpaces(BufferedImage bi,
- ColorSpace from, ColorSpace to) {
- RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING,
- RenderingHints.VALUE_RENDER_QUALITY);
- hints.put(RenderingHints.KEY_COLOR_RENDERING,
- RenderingHints.VALUE_COLOR_RENDER_QUALITY);
- hints.put(RenderingHints.KEY_DITHERING,
- RenderingHints.VALUE_DITHER_ENABLE);
-
- ColorConvertOp op = new ColorConvertOp(from, to, hints);
-
- bi = relabelColorSpace(bi, from);
-
- BufferedImage result = op.filter(bi, null);
-
- result = relabelColorSpace(result, to);
-
- return result;
- }
-
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.imaging;
+
+import com.google.code.appengine.awt.RenderingHints;
+import com.google.code.appengine.awt.Transparency;
+import com.google.code.appengine.awt.color.ColorSpace;
+import com.google.code.appengine.awt.color.ICC_ColorSpace;
+import com.google.code.appengine.awt.color.ICC_Profile;
+import com.google.code.appengine.awt.image.BufferedImage;
+import com.google.code.appengine.awt.image.ColorConvertOp;
+import com.google.code.appengine.awt.image.ColorModel;
+import com.google.code.appengine.awt.image.ComponentColorModel;
+import com.google.code.appengine.awt.image.DirectColorModel;
+import com.google.code.appengine.awt.image.ImagingOpException;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * A selection of tools for evaluating and manipulating color
+ * spaces, color values, etc.
+ * The Javadoc provided in the original code gave the
+ * following notation:
+ *
+ * "This class is a mess and needs to be cleaned up."
+ */
+public class ColorTools {
+
+ public BufferedImage correctImage(final BufferedImage src, final File file)
+ throws ImageReadException, IOException {
+ final ICC_Profile icc = Imaging.getICCProfile(file);
+ if (icc == null) {
+ return src;
+ }
+
+ final ICC_ColorSpace cs = new ICC_ColorSpace(icc);
+
+ return convertFromColorSpace(src, cs);
+ }
+
+ public BufferedImage relabelColorSpace(final BufferedImage bi, final ICC_Profile profile)
+ throws ImagingOpException {
+ final ICC_ColorSpace cs = new ICC_ColorSpace(profile);
+
+ return relabelColorSpace(bi, cs);
+ }
+
+ public BufferedImage relabelColorSpace(final BufferedImage bi, final ColorSpace cs)
+ throws ImagingOpException {
+ // This does not do the conversion. It tries to relabel the
+ // BufferedImage
+ // with its actual (presumably correct) Colorspace.
+ // use this when the image is mislabeled, presumably having been
+ // wrongly assumed to be sRGB
+
+ final ColorModel cm = deriveColorModel(bi, cs);
+
+ return relabelColorSpace(bi, cm);
+
+ }
+
+ public BufferedImage relabelColorSpace(final BufferedImage bi, final ColorModel cm)
+ throws ImagingOpException {
+ // This does not do the conversion. It tries to relabel the
+ // BufferedImage
+ // with its actual (presumably correct) Colorspace.
+ // use this when the image is mislabeled, presumably having been
+ // wrongly assumed to be sRGB
+
+ return new BufferedImage(cm, bi.getRaster(), false, null);
+ }
+
+ public ColorModel deriveColorModel(final BufferedImage bi, final ColorSpace cs)
+ throws ImagingOpException {
+ // boolean hasAlpha = (bi.getAlphaRaster() != null);
+ return deriveColorModel(bi, cs, false);
+ }
+
+ public ColorModel deriveColorModel(final BufferedImage bi, final ColorSpace cs,
+ final boolean forceNoAlpha) throws ImagingOpException {
+ return deriveColorModel(bi.getColorModel(), cs, forceNoAlpha);
+ }
+
+ public ColorModel deriveColorModel(final ColorModel colorModel, final ColorSpace cs,
+ final boolean forceNoAlpha) throws ImagingOpException {
+
+ if (colorModel instanceof ComponentColorModel) {
+ final ComponentColorModel ccm = (ComponentColorModel) colorModel;
+ // ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ if (forceNoAlpha) {
+ return new ComponentColorModel(cs, false, false,
+ Transparency.OPAQUE, ccm.getTransferType());
+ }
+ return new ComponentColorModel(cs, ccm.hasAlpha(),
+ ccm.isAlphaPremultiplied(), ccm.getTransparency(),
+ ccm.getTransferType());
+ } else if (colorModel instanceof DirectColorModel) {
+ final DirectColorModel dcm = (DirectColorModel) colorModel;
+
+ final int oldMask = dcm.getRedMask() | dcm.getGreenMask()
+ | dcm.getBlueMask() | dcm.getAlphaMask();
+
+ final int oldBits = countBitsInMask(oldMask);
+
+ return new DirectColorModel(cs, oldBits, dcm.getRedMask(),
+ dcm.getGreenMask(), dcm.getBlueMask(), dcm.getAlphaMask(),
+ dcm.isAlphaPremultiplied(), dcm.getTransferType());
+ }
+ // else if (old_cm instanceof PackedColorModel)
+ // {
+ // PackedColorModel pcm = (PackedColorModel) old_cm;
+ //
+ // // int old_mask = dcm.getRedMask() | dcm.getGreenMask()
+ // // | dcm.getBlueMask() | dcm.getAlphaMask();
+ //
+ // int old_masks[] = pcm.getMasks();
+ // // System.out.println("old_mask: " + old_mask);
+ // int old_bits = countBitsInMask(old_masks);
+ // // System.out.println("old_bits: " + old_bits);
+ //
+ // // PackedColorModel(ColorSpace space, int bits, int rmask, int gmask,
+ // int bmask, int amask, boolean isAlphaPremultiplied, int trans, int
+ // transferType)
+ // cm = new PackedColorModel(cs, old_bits, pcm.getMasks(),
+ //
+ // pcm.isAlphaPremultiplied(), pcm.getTransparency(), pcm
+ // .getTransferType());
+ // }
+
+ throw new ImagingOpException("Could not clone unknown ColorModel Type.");
+ }
+
+ private int countBitsInMask(int i) {
+ int count = 0;
+ while (i != 0) {
+ count += (i & 1);
+ // uses the unsigned version of java's right shift operator,
+ // so that left hand bits are zeroed.
+ i >>>= 1;
+ }
+ return count;
+ }
+
+ public BufferedImage convertToColorSpace(final BufferedImage bi, final ColorSpace to) {
+ final ColorSpace from = bi.getColorModel().getColorSpace();
+
+ final RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ hints.put(RenderingHints.KEY_COLOR_RENDERING,
+ RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ hints.put(RenderingHints.KEY_DITHERING,
+ RenderingHints.VALUE_DITHER_ENABLE);
+
+ final ColorConvertOp op = new ColorConvertOp(from, to, hints);
+
+ BufferedImage result = op.filter(bi, null);
+
+ result = relabelColorSpace(result, to);
+
+ return result;
+ }
+
+ public BufferedImage convertTosRGB(final BufferedImage bi) {
+ final ColorModel srgbCM = ColorModel.getRGBdefault();
+ return convertToColorSpace(bi, srgbCM.getColorSpace());
+ }
+
+ protected BufferedImage convertFromColorSpace(final BufferedImage bi, final ColorSpace from) {
+ final ColorModel srgbCM = ColorModel.getRGBdefault();
+ return convertBetweenColorSpaces(bi, from, srgbCM.getColorSpace());
+ }
+
+ public BufferedImage convertBetweenICCProfiles(BufferedImage bi, ICC_Profile from, ICC_Profile to) {
+ final ICC_ColorSpace csFrom = new ICC_ColorSpace(from);
+ final ICC_ColorSpace csTo = new ICC_ColorSpace(to);
+
+ return convertBetweenColorSpaces(bi, csFrom, csTo);
+ }
+
+ public BufferedImage convertToICCProfile(final BufferedImage bi, final ICC_Profile to) {
+ final ICC_ColorSpace csTo = new ICC_ColorSpace(to);
+ return convertToColorSpace(bi, csTo);
+ }
+
+ public BufferedImage convertBetweenColorSpacesX2(BufferedImage bi,
+ final ColorSpace from, final ColorSpace to) {
+ final RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ hints.put(RenderingHints.KEY_COLOR_RENDERING,
+ RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ hints.put(RenderingHints.KEY_DITHERING,
+ RenderingHints.VALUE_DITHER_ENABLE);
+
+ // bi = relabelColorSpace(bi, cs);
+ // dumpColorSpace("\tcs_sRGB", cs_sRGB);
+ // dumpColorSpace("\tColorModel.getRGBdefaultc",
+ // ColorModel.getRGBdefault().getColorSpace());
+
+ bi = relabelColorSpace(bi, from);
+ final ColorConvertOp op = new ColorConvertOp(from, to, hints);
+ bi = op.filter(bi, null);
+
+ bi = relabelColorSpace(bi, from);
+
+ bi = op.filter(bi, null);
+
+ bi = relabelColorSpace(bi, to);
+
+ return bi;
+
+ }
+
+ public BufferedImage convertBetweenColorSpaces(BufferedImage bi,
+ final ColorSpace from, final ColorSpace to) {
+ final RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING,
+ RenderingHints.VALUE_RENDER_QUALITY);
+ hints.put(RenderingHints.KEY_COLOR_RENDERING,
+ RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ hints.put(RenderingHints.KEY_DITHERING,
+ RenderingHints.VALUE_DITHER_ENABLE);
+
+ final ColorConvertOp op = new ColorConvertOp(from, to, hints);
+
+ bi = relabelColorSpace(bi, from);
+
+ BufferedImage result = op.filter(bi, null);
+
+ result = relabelColorSpace(result, to);
+
+ return result;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/FormatCompliance.java b/src/main/java/org/apache/commons/imaging/FormatCompliance.java
new file mode 100644
index 0000000..914d4ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/FormatCompliance.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Provides information about the compliance of a specified data
+ * source (byte array, file, etc.) to an image format.
+ */
+public class FormatCompliance {
+ private final boolean failOnError;
+ private final String description;
+ private final List comments = new ArrayList();
+
+ public FormatCompliance(final String description) {
+ this.description = description;
+ failOnError = false;
+ }
+
+ public FormatCompliance(final String description, final boolean failOnError) {
+ this.description = description;
+ this.failOnError = failOnError;
+ }
+
+ public static FormatCompliance getDefault() {
+ return new FormatCompliance("ignore", false);
+ }
+
+ public void addComment(final String comment) throws ImageReadException {
+ comments.add(comment);
+ if (failOnError) {
+ throw new ImageReadException(comment);
+ }
+ }
+
+ public void addComment(final String comment, final int value) throws ImageReadException {
+ addComment(comment + ": " + getValueDescription(value));
+ }
+
+ @Override
+ public String toString() {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+
+ dump(pw);
+
+ return sw.getBuffer().toString();
+ }
+
+ public void dump() {
+ dump(new PrintWriter(new OutputStreamWriter(System.out, Charset.defaultCharset())));
+ }
+
+ public void dump(final PrintWriter pw) {
+ pw.println("Format Compliance: " + description);
+
+ if (comments.isEmpty()) {
+ pw.println("\t" + "No comments.");
+ } else {
+ for (int i = 0; i < comments.size(); i++) {
+ pw.println("\t" + (i + 1) + ": " + comments.get(i));
+ }
+ }
+ pw.println("");
+ pw.flush();
+ }
+
+ private String getValueDescription(final int value) {
+ return value + " (" + Integer.toHexString(value) + ")";
+ }
+
+ public boolean compareBytes(final String name, final byte[] expected, final byte[] actual)
+ throws ImageReadException {
+ if (expected.length != actual.length) {
+ addComment(name + ": " + "Unexpected length: (expected: "
+ + expected.length + ", actual: " + actual.length + ")");
+ return false;
+ }
+ for (int i = 0; i < expected.length; i++) {
+ // System.out.println("expected: "
+ // + getValueDescription(expected[i]) + ", actual: "
+ // + getValueDescription(actual[i]) + ")");
+ if (expected[i] != actual[i]) {
+ addComment(name + ": " + "Unexpected value: (expected: "
+ + getValueDescription(expected[i]) + ", actual: "
+ + getValueDescription(actual[i]) + ")");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean checkBounds(final String name, final int min, final int max, final int actual)
+ throws ImageReadException {
+ if ((actual < min) || (actual > max)) {
+ addComment(name + ": " + "bounds check: " + min + " <= " + actual
+ + " <= " + max + ": false");
+ return false;
+ }
+
+ return true;
+ }
+
+ public boolean compare(final String name, final int valid, final int actual)
+ throws ImageReadException {
+ return compare(name, new int[] { valid, }, actual);
+ }
+
+ public boolean compare(final String name, final int[] valid, final int actual)
+ throws ImageReadException {
+ for (final int element : valid) {
+ if (actual == element) {
+ return true;
+ }
+ }
+
+ final StringBuilder result = new StringBuilder(43);
+ result.append(name);
+ result.append(": Unexpected value: (valid: ");
+ if (valid.length > 1) {
+ result.append('{');
+ }
+ for (int i = 0; i < valid.length; i++) {
+ if (i > 0) {
+ result.append(", ");
+ }
+ result.append(getValueDescription(valid[i]));
+ }
+ if (valid.length > 1) {
+ result.append('}');
+ }
+ result.append(", actual: " + getValueDescription(actual) + ")");
+ addComment(result.toString());
+ return false;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/ImageDump.java b/src/main/java/org/apache/commons/imaging/ImageDump.java
new file mode 100644
index 0000000..4262540
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/ImageDump.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+import com.google.code.appengine.awt.color.ColorSpace;
+import com.google.code.appengine.awt.color.ICC_ColorSpace;
+import com.google.code.appengine.awt.color.ICC_Profile;
+import com.google.code.appengine.awt.image.BufferedImage;
+
+import org.apache.commons.imaging.icc.IccProfileInfo;
+import org.apache.commons.imaging.icc.IccProfileParser;
+
+/**
+ * Used to store metadata and descriptive information extracted from
+ * image files.
+ */
+public class ImageDump {
+ private String colorSpaceTypeToName(final ColorSpace cs) {
+ // System.out.println(prefix + ": " + "type: "
+ // + cs.getType() );
+ switch (cs.getType()) {
+ case ColorSpace.TYPE_CMYK:
+ return "TYPE_CMYK";
+ case ColorSpace.TYPE_RGB:
+ return "TYPE_RGB";
+
+ case ColorSpace.CS_sRGB:
+ return "CS_sRGB";
+ case ColorSpace.CS_GRAY:
+ return "CS_GRAY";
+ case ColorSpace.CS_CIEXYZ:
+ return "CS_CIEXYZ";
+ case ColorSpace.CS_LINEAR_RGB:
+ return "CS_LINEAR_RGB";
+ case ColorSpace.CS_PYCC:
+ return "CS_PYCC";
+ default:
+ return "unknown";
+ }
+ }
+
+ public void dumpColorSpace(final String prefix, final ColorSpace cs) {
+ System.out.println(prefix + ": " + "type: " + cs.getType() + " ("
+ + colorSpaceTypeToName(cs) + ")");
+
+ if (!(cs instanceof ICC_ColorSpace)) {
+ System.out.println(prefix + ": " + "Unknown ColorSpace: "
+ + cs.getClass().getName());
+ return;
+ }
+
+ final ICC_ColorSpace iccColorSpace = (ICC_ColorSpace) cs;
+ final ICC_Profile iccProfile = iccColorSpace.getProfile();
+
+ final byte[] bytes = iccProfile.getData();
+
+ final IccProfileParser parser = new IccProfileParser();
+
+ final IccProfileInfo info = parser.getICCProfileInfo(bytes);
+ info.dump(prefix);
+ }
+
+ public void dump(final BufferedImage src) {
+ dump("", src);
+ }
+
+ public void dump(final String prefix, final BufferedImage src) {
+ System.out.println(prefix + ": " + "dump");
+ dumpColorSpace(prefix, src.getColorModel().getColorSpace());
+ dumpBIProps(prefix, src);
+ }
+
+ public void dumpBIProps(final String prefix, final BufferedImage src) {
+ final String[] keys = src.getPropertyNames();
+ if (keys == null) {
+ System.out.println(prefix + ": no props");
+ return;
+ }
+ for (final String key : keys) {
+ System.out.println(prefix + ": " + key + ": "
+ + src.getProperty(key));
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/common/IImageMetadata.java b/src/main/java/org/apache/commons/imaging/ImageFormat.java
similarity index 67%
rename from src/main/java/org/apache/sanselan/common/IImageMetadata.java
rename to src/main/java/org/apache/commons/imaging/ImageFormat.java
index c907caa..0e0c322 100644
--- a/src/main/java/org/apache/sanselan/common/IImageMetadata.java
+++ b/src/main/java/org/apache/commons/imaging/ImageFormat.java
@@ -1,33 +1,38 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common;
-
-import java.util.ArrayList;
-
-public interface IImageMetadata
-{
- public String toString(String prefix);
-
- public ArrayList getItems();
-
- public interface IImageMetadataItem
- {
- public String toString(String prefix);
-
- public String toString();
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+/**
+ * Simple image format interface.
+ */
+public interface ImageFormat {
+
+ /**
+ * Get the name of this {@link ImageFormat}.
+ *
+ * @return String name
+ */
+ String getName();
+
+ /**
+ * Get the file extension associated with this {@link ImageFormat}.
+ *
+ * @return String extension
+ */
+ String getExtension();
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/ImageFormats.java b/src/main/java/org/apache/commons/imaging/ImageFormats.java
new file mode 100644
index 0000000..4032f52
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/ImageFormats.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+/**
+ * Enum of known image formats.
+ */
+public enum ImageFormats implements ImageFormat {
+ UNKNOWN,
+ BMP,
+ DCX,
+ GIF,
+ ICNS,
+ ICO,
+ JBIG2,
+ JPEG,
+ PAM,
+ PSD,
+ PBM,
+ PGM,
+ PNM,
+ PPM,
+ PCX,
+ PNG,
+ RGBE,
+ TGA,
+ TIFF,
+ WBMP,
+ XBM,
+ XPM;
+
+ public String getName() {
+ return name();
+ }
+
+ public String getExtension() {
+ return name();
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/ImageInfo.java b/src/main/java/org/apache/commons/imaging/ImageInfo.java
similarity index 75%
rename from src/main/java/org/apache/sanselan/ImageInfo.java
rename to src/main/java/org/apache/commons/imaging/ImageInfo.java
index a8724e5..f19ad10 100644
--- a/src/main/java/org/apache/sanselan/ImageInfo.java
+++ b/src/main/java/org/apache/commons/imaging/ImageInfo.java
@@ -1,374 +1,361 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-
-/**
- * ImageInfo represents a collection of basic properties of an image, such as
- * width, height, format, bit depth, etc.
- */
-public class ImageInfo
-{
- private final String formatDetails; // ie version
-
- private final int bitsPerPixel;
- private final ArrayList comments;
-
- private final ImageFormat format;
- private final String formatName;
- private final int height;
- private final String mimeType;
-
- private final int numberOfImages;
- private final int physicalHeightDpi;
- private final float physicalHeightInch;
- private final int physicalWidthDpi;
- private final float physicalWidthInch;
- private final int width;
- private final boolean isProgressive;
- private final boolean isTransparent;
-
- private final boolean usesPalette;
-
- public static final int COLOR_TYPE_BW = 0;
- public static final int COLOR_TYPE_GRAYSCALE = 1;
- public static final int COLOR_TYPE_RGB = 2;
- public static final int COLOR_TYPE_CMYK = 3;
- public static final int COLOR_TYPE_OTHER = -1;
- public static final int COLOR_TYPE_UNKNOWN = -2;
-
- private final int colorType;
-
- public static final String COMPRESSION_ALGORITHM_UNKNOWN = "Unknown";
- public static final String COMPRESSION_ALGORITHM_NONE = "None";
- public static final String COMPRESSION_ALGORITHM_LZW = "LZW";
- public static final String COMPRESSION_ALGORITHM_PACKBITS = "PackBits";
- public static final String COMPRESSION_ALGORITHM_JPEG = "JPEG";
- public static final String COMPRESSION_ALGORITHM_RLE = "RLE: Run-Length Encoding";
- public static final String COMPRESSION_ALGORITHM_PSD = "Photoshop";
- public static final String COMPRESSION_ALGORITHM_PNG_FILTER = "PNG Filter";
- public static final String COMPRESSION_ALGORITHM_CCITT_GROUP_3 = "CCITT Group 3 1-Dimensional Modified Huffman run-length encoding.";
- public static final String COMPRESSION_ALGORITHM_CCITT_GROUP_4 = "CCITT Group 4";
- public static final String COMPRESSION_ALGORITHM_CCITT_1D = "CCITT 1D";
-
- private final String compressionAlgorithm;
-
- public ImageInfo(String formatDetails, int bitsPerPixel,
- ArrayList comments, ImageFormat format, String formatName,
- int height, String mimeType, int numberOfImages,
- int physicalHeightDpi, float physicalHeightInch,
- int physicalWidthDpi, float physicalWidthInch, int width,
- boolean isProgressive, boolean isTransparent, boolean usesPalette,
- int colorType, String compressionAlgorithm)
- {
- this.formatDetails = formatDetails;
-
- this.bitsPerPixel = bitsPerPixel;
- this.comments = comments;
-
- this.format = format;
- this.formatName = formatName;
- this.height = height;
- this.mimeType = mimeType;
-
- this.numberOfImages = numberOfImages;
- this.physicalHeightDpi = physicalHeightDpi;
- this.physicalHeightInch = physicalHeightInch;
- this.physicalWidthDpi = physicalWidthDpi;
- this.physicalWidthInch = physicalWidthInch;
- this.width = width;
- this.isProgressive = isProgressive;
-
- this.isTransparent = isTransparent;
- this.usesPalette = usesPalette;
-
- this.colorType = colorType;
- this.compressionAlgorithm = compressionAlgorithm;
- }
-
- /**
- * Returns the bits per pixel of the image data.
- */
- public int getBitsPerPixel()
- {
- return bitsPerPixel;
- }
-
- /**
- * Returns a list of comments from the image file. This is mostly
- * obsolete.
- */
- public ArrayList getComments()
- {
- return new ArrayList(comments);
- }
-
- /**
- * Returns the image file format, ie. ImageFormat.IMAGE_FORMAT_PNG.
- * Returns ImageFormat.IMAGE_FORMAT_UNKNOWN if format is unknown.
- *
- * @return A constant defined in ImageFormat.
- * @see ImageFormat
- */
- public ImageFormat getFormat()
- {
- return format;
- }
-
- /**
- * Returns a string with the name of the image file format.
- *
- * @see #getFormat()
- */
- public String getFormatName()
- {
- return formatName;
- }
-
- /**
- * Returns the height of the image in pixels.
- *
- * @see #getWidth()
- */
- public int getHeight()
- {
- return height;
- }
-
- /**
- * Returns the MIME type of the image.
- *
- * @see #getFormat()
- */
- public String getMimeType()
- {
- return mimeType;
- }
-
- /**
- * Returns the number of images in the file.
- *
- * Applies mostly to GIF and TIFF; reading PSD/Photoshop layers is not
- * supported, and Jpeg/JFIF EXIF thumbnails are not included in this count.
- */
- public int getNumberOfImages()
- {
- return numberOfImages;
- }
-
- /**
- * Returns horizontal dpi of the image, if available.
- *
- * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
- * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
- * 72).
- *
- * @return returns -1 if not present.
- */
- public int getPhysicalHeightDpi()
- {
- return physicalHeightDpi;
- }
-
- /**
- * Returns physical height of the image in inches, if available.
- *
- * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
- * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
- * 72).
- *
- * @return returns -1 if not present.
- */
- public float getPhysicalHeightInch()
- {
- return physicalHeightInch;
- }
-
- /**
- * Returns vertical dpi of the image, if available.
- *
- * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
- * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
- * 72).
- *
- * @return returns -1 if not present.
- */
- public int getPhysicalWidthDpi()
- {
- return physicalWidthDpi;
- }
-
- /**
- * Returns physical width of the image in inches, if available.
- *
- * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
- * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
- * 72).
- *
- * @return returns -1 if not present.
- */
- public float getPhysicalWidthInch()
- {
- return physicalWidthInch;
- }
-
- /**
- * Returns the width of the image in pixels.
- *
- * @see #getHeight()
- */
- public int getWidth()
- {
- return width;
- }
-
- /**
- * Returns true if the image is progressive or interlaced.
- */
- public boolean isProgressive()
- {
- return isProgressive;
- }
-
- /**
- * Returns the color type of the image, as a constant (ie.
- * ImageFormat.COLOR_TYPE_CMYK).
- *
- * @see #getColorTypeDescription()
- */
- public int getColorType()
- {
- return colorType;
- }
-
- /**
- * Returns a description of the color type of the image.
- *
- * @see #getColorType()
- */
- public String getColorTypeDescription()
- {
- switch (colorType)
- {
- case COLOR_TYPE_BW:
- return "Black and White";
- case COLOR_TYPE_GRAYSCALE:
- return "Grayscale";
- case COLOR_TYPE_RGB:
- return "RGB";
- case COLOR_TYPE_CMYK:
- return "CMYK";
- case COLOR_TYPE_OTHER:
- return "Other";
- case COLOR_TYPE_UNKNOWN:
- return "Unknown";
-
- default:
- return "Unknown";
- }
-
- }
-
- public void dump()
- {
- System.out.print(toString());
- }
-
- public String toString()
- {
- try
- {
- StringWriter sw = new StringWriter();
- PrintWriter pw = new PrintWriter(sw);
-
- toString(pw, "");
- pw.flush();
-
- return sw.toString();
- } catch (Exception e)
- {
- return "Image Data: Error";
- }
- }
-
- public void toString(PrintWriter pw, String prefix)
- throws ImageReadException, IOException
- {
- pw.println("Format Details: " + formatDetails);
-
- pw.println("Bits Per Pixel: " + bitsPerPixel);
- pw.println("Comments: " + comments.size());
- for (int i = 0; i < comments.size(); i++)
- {
- String s = (String) comments.get(i);
- pw.println("\t" + i + ": '" + s + "'");
-
- }
- pw.println("Format: " + format.name);
- pw.println("Format Name: " + formatName);
- pw.println("Compression Algorithm: " + compressionAlgorithm);
- pw.println("Height: " + height);
- pw.println("MimeType: " + mimeType);
- pw.println("Number Of Images: " + numberOfImages);
- pw.println("Physical Height Dpi: " + physicalHeightDpi);
- pw.println("Physical Height Inch: " + physicalHeightInch);
- pw.println("Physical Width Dpi: " + physicalWidthDpi);
- pw.println("Physical Width Inch: " + physicalWidthInch);
- pw.println("Width: " + width);
- pw.println("Is Progressive: " + isProgressive);
- pw.println("Is Transparent: " + isTransparent);
-
- pw.println("Color Type: " + getColorTypeDescription());
- pw.println("Uses Palette: " + usesPalette);
-
- pw.flush();
-
- }
-
- /**
- * Returns a description of the file format, ie. format version.
- */
- public String getFormatDetails() {
- return formatDetails;
- }
-
- /**
- * Returns true if the image has transparency.
- */
- public boolean isTransparent() {
- return isTransparent;
- }
-
- /**
- * Returns true if the image uses a palette.
- */
- public boolean usesPalette() {
- return usesPalette;
- }
-
- /**
- * Returns a description of the compression algorithm, if any.
- */
- public String getCompressionAlgorithm() {
- return compressionAlgorithm;
- }
-
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ImageInfo represents a collection of basic properties of an image, such as
+ * width, height, format, bit depth, etc.
+ */
+public class ImageInfo {
+ private final String formatDetails; // ie version
+
+ private final int bitsPerPixel;
+ private final List comments;
+
+ private final ImageFormat format;
+ private final String formatName;
+ private final int height;
+ private final String mimeType;
+
+ private final int numberOfImages;
+ private final int physicalHeightDpi;
+ private final float physicalHeightInch;
+ private final int physicalWidthDpi;
+ private final float physicalWidthInch;
+ private final int width;
+ private final boolean progressive;
+ private final boolean transparent;
+
+ private final boolean usesPalette;
+
+ public static final int COLOR_TYPE_BW = 0;
+ public static final int COLOR_TYPE_GRAYSCALE = 1;
+ public static final int COLOR_TYPE_RGB = 2;
+ public static final int COLOR_TYPE_CMYK = 3;
+ public static final int COLOR_TYPE_YCbCr = 4;
+ public static final int COLOR_TYPE_YCCK = 5;
+ public static final int COLOR_TYPE_YCC = 6;
+ public static final int COLOR_TYPE_OTHER = -1;
+ public static final int COLOR_TYPE_UNKNOWN = -2;
+
+ private final int colorType;
+
+ public static final String COMPRESSION_ALGORITHM_UNKNOWN = "Unknown";
+ public static final String COMPRESSION_ALGORITHM_NONE = "None";
+ public static final String COMPRESSION_ALGORITHM_LZW = "LZW";
+ public static final String COMPRESSION_ALGORITHM_PACKBITS = "PackBits";
+ public static final String COMPRESSION_ALGORITHM_JPEG = "JPEG";
+ public static final String COMPRESSION_ALGORITHM_RLE = "RLE: Run-Length Encoding";
+ public static final String COMPRESSION_ALGORITHM_PSD = "Photoshop";
+ public static final String COMPRESSION_ALGORITHM_PNG_FILTER = "PNG Filter";
+ public static final String COMPRESSION_ALGORITHM_CCITT_GROUP_3 = "CCITT Group 3 1-Dimensional Modified Huffman run-length encoding.";
+ public static final String COMPRESSION_ALGORITHM_CCITT_GROUP_4 = "CCITT Group 4";
+ public static final String COMPRESSION_ALGORITHM_CCITT_1D = "CCITT 1D";
+
+ private final String compressionAlgorithm;
+
+ public ImageInfo(final String formatDetails, final int bitsPerPixel,
+ final List comments, final ImageFormat format, final String formatName,
+ final int height, final String mimeType, final int numberOfImages,
+ final int physicalHeightDpi, final float physicalHeightInch,
+ final int physicalWidthDpi, final float physicalWidthInch, final int width,
+ final boolean progressive, final boolean transparent, final boolean usesPalette,
+ final int colorType, final String compressionAlgorithm) {
+ this.formatDetails = formatDetails;
+
+ this.bitsPerPixel = bitsPerPixel;
+ this.comments = comments;
+
+ this.format = format;
+ this.formatName = formatName;
+ this.height = height;
+ this.mimeType = mimeType;
+
+ this.numberOfImages = numberOfImages;
+ this.physicalHeightDpi = physicalHeightDpi;
+ this.physicalHeightInch = physicalHeightInch;
+ this.physicalWidthDpi = physicalWidthDpi;
+ this.physicalWidthInch = physicalWidthInch;
+ this.width = width;
+ this.progressive = progressive;
+
+ this.transparent = transparent;
+ this.usesPalette = usesPalette;
+
+ this.colorType = colorType;
+ this.compressionAlgorithm = compressionAlgorithm;
+ }
+
+ /**
+ * Returns the bits per pixel of the image data.
+ */
+ public int getBitsPerPixel() {
+ return bitsPerPixel;
+ }
+
+ /**
+ * Returns a list of comments from the image file.
+ *
+ * This is mostly obsolete.
+ */
+ public List getComments() {
+ return new ArrayList(comments);
+ }
+
+ /**
+ * Returns the image file format, ie. ImageFormat.IMAGE_FORMAT_PNG.
+ *
+ * Returns ImageFormat.IMAGE_FORMAT_UNKNOWN if format is unknown.
+ *
+ * @return A constant defined in ImageFormat.
+ * @see ImageFormats
+ */
+ public ImageFormat getFormat() {
+ return format;
+ }
+
+ /**
+ * Returns a string with the name of the image file format.
+ *
+ * @see #getFormat()
+ */
+ public String getFormatName() {
+ return formatName;
+ }
+
+ /**
+ * Returns the height of the image in pixels.
+ *
+ * @see #getWidth()
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Returns the MIME type of the image.
+ *
+ * @see #getFormat()
+ */
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ /**
+ * Returns the number of images in the file.
+ *
+ * Applies mostly to GIF and TIFF; reading PSD/Photoshop layers is not
+ * supported, and Jpeg/JFIF EXIF thumbnails are not included in this count.
+ */
+ public int getNumberOfImages() {
+ return numberOfImages;
+ }
+
+ /**
+ * Returns horizontal dpi of the image, if available.
+ *
+ * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
+ * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
+ * 72).
+ *
+ * @return returns -1 if not present.
+ */
+ public int getPhysicalHeightDpi() {
+ return physicalHeightDpi;
+ }
+
+ /**
+ * Returns physical height of the image in inches, if available.
+ *
+ * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
+ * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
+ * 72).
+ *
+ * @return returns -1 if not present.
+ */
+ public float getPhysicalHeightInch() {
+ return physicalHeightInch;
+ }
+
+ /**
+ * Returns vertical dpi of the image, if available.
+ *
+ * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
+ * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
+ * 72).
+ *
+ * @return returns -1 if not present.
+ */
+ public int getPhysicalWidthDpi() {
+ return physicalWidthDpi;
+ }
+
+ /**
+ * Returns physical width of the image in inches, if available.
+ *
+ * Applies to TIFF (optional), BMP (always), GIF (constant: 72), Jpeg
+ * (optional), PNG (optional), PNM (constant: 72), PSD/Photoshop (constant:
+ * 72).
+ *
+ * @return returns -1 if not present.
+ */
+ public float getPhysicalWidthInch() {
+ return physicalWidthInch;
+ }
+
+ /**
+ * Returns the width of the image in pixels.
+ *
+ * @see #getHeight()
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Returns true if the image is progressive or interlaced.
+ */
+ public boolean isProgressive() {
+ return progressive;
+ }
+
+ /**
+ * Returns the color type of the image, as a constant (ie.
+ * ImageFormat.COLOR_TYPE_CMYK).
+ *
+ * @see #getColorTypeDescription()
+ */
+ public int getColorType() {
+ return colorType;
+ }
+
+ /**
+ * Returns a description of the color type of the image.
+ *
+ * @see #getColorType()
+ */
+ public String getColorTypeDescription() {
+ switch (colorType) {
+ case COLOR_TYPE_BW:
+ return "Black and White";
+ case COLOR_TYPE_GRAYSCALE:
+ return "Grayscale";
+ case COLOR_TYPE_RGB:
+ return "RGB";
+ case COLOR_TYPE_CMYK:
+ return "CMYK";
+ case COLOR_TYPE_YCbCr:
+ return "YCbCr";
+ case COLOR_TYPE_YCCK:
+ return "YCCK";
+ case COLOR_TYPE_YCC:
+ return "YCC";
+ case COLOR_TYPE_OTHER:
+ return "Other";
+ case COLOR_TYPE_UNKNOWN:
+ return "Unknown";
+
+ default:
+ return "Unknown";
+ }
+
+ }
+
+ public void dump() {
+ System.out.print(toString());
+ }
+
+ @Override
+ public String toString() {
+ try {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+
+ toString(pw, "");
+ pw.flush();
+
+ return sw.toString();
+ } catch (final Exception e) {
+ return "Image Data: Error";
+ }
+ }
+
+ public void toString(final PrintWriter pw, final String prefix) {
+ pw.println("Format Details: " + formatDetails);
+
+ pw.println("Bits Per Pixel: " + bitsPerPixel);
+ pw.println("Comments: " + comments.size());
+ for (int i = 0; i < comments.size(); i++) {
+ final String s = comments.get(i);
+ pw.println("\t" + i + ": '" + s + "'");
+
+ }
+ pw.println("Format: " + format.getName());
+ pw.println("Format Name: " + formatName);
+ pw.println("Compression Algorithm: " + compressionAlgorithm);
+ pw.println("Height: " + height);
+ pw.println("MimeType: " + mimeType);
+ pw.println("Number Of Images: " + numberOfImages);
+ pw.println("Physical Height Dpi: " + physicalHeightDpi);
+ pw.println("Physical Height Inch: " + physicalHeightInch);
+ pw.println("Physical Width Dpi: " + physicalWidthDpi);
+ pw.println("Physical Width Inch: " + physicalWidthInch);
+ pw.println("Width: " + width);
+ pw.println("Is Progressive: " + progressive);
+ pw.println("Is Transparent: " + transparent);
+
+ pw.println("Color Type: " + getColorTypeDescription());
+ pw.println("Uses Palette: " + usesPalette);
+
+ pw.flush();
+
+ }
+
+ /**
+ * Returns a description of the file format, ie. format version.
+ */
+ public String getFormatDetails() {
+ return formatDetails;
+ }
+
+ /**
+ * Returns true if the image has transparency.
+ */
+ public boolean isTransparent() {
+ return transparent;
+ }
+
+ /**
+ * Returns true if the image uses a palette.
+ */
+ public boolean usesPalette() {
+ return usesPalette;
+ }
+
+ /**
+ * Returns a description of the compression algorithm, if any.
+ */
+ public String getCompressionAlgorithm() {
+ return compressionAlgorithm;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/ImageParser.java b/src/main/java/org/apache/commons/imaging/ImageParser.java
new file mode 100644
index 0000000..b157d47
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/ImageParser.java
@@ -0,0 +1,987 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.imaging.common.BinaryFileParser;
+import org.apache.commons.imaging.common.IBufferedImageFactory;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.SimpleBufferedImageFactory;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
+import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
+import org.apache.commons.imaging.formats.bmp.BmpImageParser;
+import org.apache.commons.imaging.formats.dcx.DcxImageParser;
+import org.apache.commons.imaging.formats.gif.GifImageParser;
+import org.apache.commons.imaging.formats.icns.IcnsImageParser;
+import org.apache.commons.imaging.formats.ico.IcoImageParser;
+import org.apache.commons.imaging.formats.jpeg.JpegImageParser;
+import org.apache.commons.imaging.formats.pcx.PcxImageParser;
+import org.apache.commons.imaging.formats.png.PngImageParser;
+import org.apache.commons.imaging.formats.pnm.PnmImageParser;
+import org.apache.commons.imaging.formats.psd.PsdImageParser;
+import org.apache.commons.imaging.formats.rgbe.RgbeImageParser;
+import org.apache.commons.imaging.formats.tiff.TiffImageParser;
+import org.apache.commons.imaging.formats.wbmp.WbmpImageParser;
+import org.apache.commons.imaging.formats.xbm.XbmImageParser;
+import org.apache.commons.imaging.formats.xpm.XpmImageParser;
+
+/**
+ * Provides the abstract base class for all image reading and writing
+ * utilities. ImageParser implementations are expected to extend this
+ * class providing logic for identifying and processing data in their
+ * own specific format. Specific implementations are found
+ * under the com.apache.commons.imaging.formats package.
+ *
+ *
Application Notes
+ *
+ * Format support
+ *
+ * For the most recent information on format support for the
+ * Apache Commons Imaging package, refer to
+ * Format Support
+ * at the main project development web site.
+ *
+ * On the accuracy of this Javadoc
+ *
+ * The original authors of this class did not supply documentation.
+ * The Javadoc for this class is based on inspection of the
+ * source code. In some cases, the purpose and usage for particular
+ * methods was deduced from the source and may not perfectly reflect
+ * the intentions of the original. Therefore, you should not assume
+ * that the documentation is perfect, especially in the more obscure
+ * and specialized areas of implementation.
+ *
+ * The "Map params" argument
+ *
+ * Many of the methods specified by this class accept an argument of
+ * type Map giving a list of parameters to be used when processing an
+ * image. For example, some of the output formats permit the specification
+ * of different kinds of image compression or color models. Some of the
+ * reading methods permit the calling application to require strict
+ * format compliance. In many cases, however, an application will not
+ * require the use of this argument. While some of the ImageParser
+ * implementations check for (and ignore) null arguments for this parameter,
+ * not all of them do (at least not at the time these notes were written).
+ * Therefore, a prudent programmer will always supply an valid, though
+ * empty instance of a Map implementation when calling such methods.
+ * Generally, the java HashMap class is useful for this purpose.
+ *
+ * Additionally, developers creating or enhancing classes derived
+ * from ImageParser are encouraged to include such checks in their code.
+ */
+public abstract class ImageParser extends BinaryFileParser {
+
+ /**
+ * Gets an array of new instances of all image parsers.
+ *
+ * @return A valid array of image parsers
+ */
+ public static ImageParser[] getAllImageParsers() {
+
+ return new ImageParser[]{
+ new BmpImageParser(),
+ new DcxImageParser(),
+ new GifImageParser(),
+ new IcnsImageParser(),
+ new IcoImageParser(),
+ new JpegImageParser(),
+ new PcxImageParser(),
+ new PngImageParser(),
+ new PnmImageParser(),
+ new PsdImageParser(),
+ new RgbeImageParser(),
+ new TiffImageParser(),
+ new WbmpImageParser(),
+ new XbmImageParser(),
+ new XpmImageParser(),
+ // new JBig2ImageParser(),
+ // new TgaImageParser(),
+ };
+ }
+
+ /**
+ * Get image metadata from the specified byte source. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * IImageMetadata object or to throw an ImageReadException if unable
+ * to process the specified byte source.
+ *
+ * @param byteSource A valid byte source.
+ * @return A valid, potentially subject-matter-specific implementation of
+ * the IImageMetadata interface describing the content extracted
+ * from the source content.
+ * @throws ImageReadException In the event that the the ByteSource
+ * content does not conform to the format of the specific parser
+ * implementation.
+ * @throws IOException In the event of unsuccessful data read operation.
+ */
+ public final IImageMetadata getMetadata(final ByteSource byteSource) throws ImageReadException, IOException {
+ return getMetadata(byteSource, null);
+ }
+
+ /**
+ * Get image metadata from the specified byte source. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * IImageMetadata object or to throw an ImageReadException if unable
+ * to process the specified byte source.
+ *
+ *
The params argument provides a mechanism for individual
+ * implementations to pass optional information into the parser.
+ * Not all formats will require this capability. Because the
+ * base class may call this method with a null params argument,
+ * implementations should always include logic
+ * for ignoring null input.
+ *
+ * @param byteSource A valid byte source.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid, potentially subject-matter-specific implementation of
+ * the IImageMetadata interface describing the content extracted
+ * from the source content.
+ * @throws ImageReadException In the event that the the ByteSource
+ * content does not conform to the format of the specific parser
+ * implementation.
+ * @throws IOException In the event of unsuccessful data read operation.
+ */
+ public abstract IImageMetadata getMetadata(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException;
+
+ /**
+ * Get image metadata from the specified array of bytes. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * IImageMetadata object or to throw an ImageReadException if unable
+ * to process the specified data.
+ *
+ * @param bytes A valid array of bytes
+ * @return A valid, potentially subject-matter-specific implementation of
+ * the IImageMetadata interface describing the content extracted
+ * from the source content.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful data read operation.
+ */
+ public final IImageMetadata getMetadata(final byte[] bytes) throws ImageReadException, IOException {
+ return getMetadata(bytes, null);
+ }
+
+ /**
+ * Get image metadata from the specified array of bytes. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * IImageMetadata object or to throw an ImageReadException if unable
+ * to process the specified data.
+ *
+ * The params argument provides a mechanism for individual
+ * implementations to pass optional information into the parser.
+ * Not all formats will require this capability. Because the
+ * base class may call this method with a null params argument,
+ * implementations should always include logic
+ * for ignoring null input.
+ *
+ * @param bytes A valid array of bytes
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid image metadata object describing the content extracted
+ * from the specified content.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful data read operation.
+ */
+ public final IImageMetadata getMetadata(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getMetadata(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Get image metadata from the specified file. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * IImageMetadata object or to throw an ImageReadException if unable
+ * to process the specified data.
+ *
+ * @param file A valid reference to a file.
+ * @return A valid image metadata object describing the content extracted
+ * from the specified content.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful file read or
+ * access operation.
+ */
+ public final IImageMetadata getMetadata(final File file) throws ImageReadException, IOException {
+ return getMetadata(file, null);
+ }
+
+ /**
+ * Get image metadata from the specified file. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * IImageMetadata object or to throw an ImageReadException if unable
+ * to process the specified data.
+ *
+ * The params argument provides a mechanism for individual
+ * implementations to pass optional information into the parser.
+ * Not all formats will require this capability. Because the
+ * base class may call this method with a null params argument,
+ * implementations should always include logic
+ * for ignoring null input.
+ *
+ * @param file A valid reference to a file.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid image metadata object describing the content extracted
+ * from the specified content.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful file read or
+ * access operation.
+ */
+ public final IImageMetadata getMetadata(final File file, final Map params)
+ throws ImageReadException, IOException {
+ if (getDebug()) {
+ System.out.println(getName() + ".getMetadata" + ": "
+ + file.getName());
+ }
+
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ return getMetadata(new ByteSourceFile(file), params);
+ }
+
+ /**
+ * Get image information from the specified ByteSource. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * ImageInfo object or to throw an ImageReadException if unable
+ * to process the specified data.
+ *
+ * The params argument provides a mechanism for individual
+ * implementations to pass optional information into the parser.
+ * Not all formats will require this capability. Because the
+ * base class may call this method with a null params argument,
+ * implementations should always include logic
+ * for ignoring null input.
+ *
+ * @param byteSource A valid ByteSource object
+ * @param params Optional instructions for special-handling or interpretation
+ * of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid image information object describing the content extracted
+ * from the specified data.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful data access operation.
+ */
+ public abstract ImageInfo getImageInfo(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException;
+
+ /**
+ * Get image information from the specified ByteSource. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * ImageInfo object or to throw an ImageReadException if unable
+ * to process the specified data.
+ *
+ * @param byteSource A valid ByteSource object
+ * @return A valid image information object describing the content extracted
+ * from the specified data.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful data
+ * access operation.
+ */
+ public final ImageInfo getImageInfo(final ByteSource byteSource) throws ImageReadException, IOException {
+ return getImageInfo(byteSource, null);
+ }
+
+ /**
+ * Get image information from the specified array of bytes. Format-specific
+ * ImageParser implementations are expected to return a valid
+ * ImageInfo object or to throw an ImageReadException if unable
+ * to process the specified data.
+ * The params argument provides a mechanism for individual
+ * implementations to pass optional information into the parser.
+ * Not all formats will require this capability. Because the
+ * base class may call this method with a null params argument,
+ * implementations should always include logic
+ * for ignoring null input.
+ *
+ * @param bytes A valid array of bytes
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid image information object describing the content extracted
+ * from the specified data.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful data
+ * access operation.
+ */
+ public final ImageInfo getImageInfo(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Get image information from the specified file Format-specific
+ * ImageParser implementations are expected to return a valid
+ * ImageInfo object or to throw an ImageReadException if unable
+ * to process the specified data.
+ * The params argument provides a mechanism for individual
+ * implementations to pass optional information into the parser.
+ * Not all formats will require this capability. Because the
+ * base class may call this method with a null params argument,
+ * implementations should always include logic
+ * for ignoring null input.
+ *
+ * @param file A valid File object
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid image information object describing the content extracted
+ * from the specified data.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful file read or
+ * access operation.
+ */
+ public final ImageInfo getImageInfo(final File file, final Map params)
+ throws ImageReadException, IOException {
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ return getImageInfo(new ByteSourceFile(file), params);
+ }
+
+ /**
+ * Determines the format compliance of the content of the supplied byte
+ * source based on rules provided by a specific implementation.
+ *
+ * @param byteSource A valid instance of ByteSource
+ * @return true if the content is format-compliant; otherwise, false
+ * @throws ImageReadException may be thrown by sub-classes
+ * @throws IOException may be thrown by sub-classes
+ */
+ public FormatCompliance getFormatCompliance(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ /**
+ * Determines the format compliance of the content of the supplied byte
+ * array based on rules provided by a specific implementation.
+ *
+ * @param bytes A valid byte array.
+ * @return A valid FormatCompliance object.
+ * @throws ImageReadException may be thrown by sub-classes
+ * @throws IOException may be thrown by sub-classes
+ */
+ public final FormatCompliance getFormatCompliance(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getFormatCompliance(new ByteSourceArray(bytes));
+ }
+
+ /**
+ * Determines the format compliance of the specified file based on
+ * rules provided by a specific implementation.
+ *
+ * @param file A valid reference to a file.
+ * @return A valid format compliance object.
+ * @throws ImageReadException may be thrown by sub-classes
+ * @throws IOException may be thrown by sub-classes
+ */
+ public final FormatCompliance getFormatCompliance(final File file)
+ throws ImageReadException, IOException {
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ return getFormatCompliance(new ByteSourceFile(file));
+ }
+
+ /**
+ * Gets all images specified by the byte source (some
+ * formats may include multiple images within a single data source).
+ *
+ * @param byteSource A valid instance of ByteSource.
+ * @return A valid (potentially empty) list of BufferedImage objects.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public List getAllBufferedImages(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final BufferedImage bi = getBufferedImage(byteSource, null);
+
+ final List result = new ArrayList();
+
+ result.add(bi);
+
+ return result;
+ }
+
+ /**
+ * Gets all images specified by the byte array (some
+ * formats may include multiple images within a single data source).
+ *
+ * @param bytes A valid byte array
+ * @return A valid (potentially empty) list of BufferedImage objects.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final List getAllBufferedImages(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getAllBufferedImages(new ByteSourceArray(bytes));
+ }
+
+ /**
+ * Gets all images specified by indicated file (some
+ * formats may include multiple images within a single data source).
+ *
+ * @param file A valid reference to a file.
+ * @return A valid (potentially empty) list of BufferedImage objects.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final List getAllBufferedImages(final File file) throws ImageReadException, IOException {
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ return getAllBufferedImages(new ByteSourceFile(file));
+ }
+
+ /**
+ * Gets a buffered image specified by the byte source (for
+ * sources that specify multiple images, choice of which image
+ * is returned is implementation dependent).
+ *
+ * @param byteSource A valid instance of ByteSource
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid instance of BufferedImage.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public abstract BufferedImage getBufferedImage(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException;
+
+ /**
+ * Gets a buffered image specified by the byte array (for
+ * sources that specify multiple images, choice of which image
+ * is returned is implementation dependent).
+ *
+ * @param bytes A valid byte array
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid instance of BufferedImage.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final BufferedImage getBufferedImage(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getBufferedImage(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Gets a buffered image specified by the indicated file (for
+ * sources that specify multiple images, choice of which image
+ * is returned is implementation dependent).
+ *
+ * @param file A valid file reference.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data (null objects are permitted and
+ * must be supported by implementations).
+ * @return A valid instance of BufferedImage.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final BufferedImage getBufferedImage(final File file, final Map params)
+ throws ImageReadException, IOException {
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ return getBufferedImage(new ByteSourceFile(file), params);
+ }
+
+
+ /**
+ * Writes the content of a BufferedImage to the specified output
+ * stream.
+ *
+ * The params argument provides a mechanism for individual
+ * implementations to pass optional information into the parser.
+ * Not all formats will support this capability. Currently,
+ * some of the parsers do not check for null arguments. So in cases
+ * where no optional specifications are supported, application
+ * code should pass in an empty instance of an implementation of
+ * the map interface (i.e. an empty HashMap).
+ *
+ * @param src An image giving the source content for output
+ * @param os A valid output stream for storing the formatted image
+ * @param params A non-null Map implementation supplying optional,
+ * format-specific instructions for output
+ * (such as selections for data compression, color models, etc.)
+ * @throws ImageWriteException In the event that the output format
+ * cannot handle the input image or invalid params are specified.
+ * @throws IOException In the event of an write error from
+ * the output stream.
+ */
+ public void writeImage(final BufferedImage src, final OutputStream os, final Map params)
+ throws ImageWriteException, IOException {
+ os.close(); // we are obligated to close stream.
+
+ throw new ImageWriteException("This image format (" + getName()
+ + ") cannot be written.");
+ }
+
+ /**
+ * Get the size of the image described by the specified byte array.
+ *
+ * @param bytes A valid byte array.
+ * @return A valid instance of Dimension.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final Dimension getImageSize(final byte[] bytes) throws ImageReadException, IOException {
+ return getImageSize(bytes, null);
+ }
+
+ /**
+ * Get the size of the image described by the specified byte array.
+ *
+ * @param bytes A valid byte array.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data.
+ * @return A valid instance of Dimension.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final Dimension getImageSize(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getImageSize(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Get the size of the image described by the specified file.
+ *
+ * @param file A valid reference to a file.
+ * @return A valid instance of Dimension.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final Dimension getImageSize(final File file) throws ImageReadException, IOException {
+ return getImageSize(file, null);
+ }
+
+ /**
+ * Get the size of the image described by the specified file.
+ *
+ * @param file A valid reference to a file.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data.
+ * @return A valid instance of Dimension.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final Dimension getImageSize(final File file, final Map params)
+ throws ImageReadException, IOException {
+
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ return getImageSize(new ByteSourceFile(file), params);
+ }
+
+ /**
+ * Get the size of the image described by the specified ByteSource.
+ *
+ * @param byteSource A valid reference to a ByteSource.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data.
+ * @return A valid instance of Dimension.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public abstract Dimension getImageSize(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException;
+
+ /**
+ * Get a string containing XML-formatted text conforming to the Extensible
+ * Metadata Platform (EXP) standard for representing information about
+ * image content. Not all image formats support EXP infomation and
+ * even for those that do, there is no guarantee that such information
+ * will be present in an image.
+ *
+ * @param byteSource A valid reference to a ByteSource.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data.
+ * @return If XMP metadata is present, a valid string;
+ * if it is not present, a null.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public abstract String getXmpXml(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException;
+
+ /**
+ * Get an array of bytes describing the International Color Consortium (ICC)
+ * specification for the color space of the image contained in the
+ * input byte array. Not all formats support ICC profiles.
+ *
+ * @param bytes A valid array of bytes.
+ * @return If available, a valid array of bytes; otherwise, a null
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final byte[] getICCProfileBytes(final byte[] bytes) throws ImageReadException, IOException {
+ return getICCProfileBytes(bytes, null);
+ }
+
+ /**
+ * Get an array of bytes describing the International Color Consortium (ICC)
+ * specification for the color space of the image contained in the
+ * input byte array. Not all formats support ICC profiles.
+ *
+ * @param bytes A valid array of bytes.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data.
+ * @return If available, a valid array of bytes; otherwise, a null
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final byte[] getICCProfileBytes(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getICCProfileBytes(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Get an array of bytes describing the International Color Consortium (ICC)
+ * specification for the color space of the image contained in the
+ * input file. Not all formats support ICC profiles.
+ *
+ * @param file A valid file reference.
+ * @return If available, a valid array of bytes; otherwise, a null
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final byte[] getICCProfileBytes(final File file) throws ImageReadException, IOException {
+ return getICCProfileBytes(file, null);
+ }
+
+ /**
+ * Get an array of bytes describing the International Color Consortium (ICC)
+ * specification for the color space of the image contained in the
+ * input file. Not all formats support ICC profiles.
+ *
+ * @param file A valid file reference.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data.
+ * @return If available, a valid array of bytes; otherwise, a null
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final byte[] getICCProfileBytes(final File file, final Map params)
+ throws ImageReadException, IOException {
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ if (getDebug()) {
+ System.out.println(getName() + ": " + file.getName());
+ }
+
+ return getICCProfileBytes(new ByteSourceFile(file), params);
+ }
+
+ /**
+ * Get an array of bytes describing the International Color Consortium (ICC)
+ * specification for the color space of the image contained in the
+ * input byteSoruce. Not all formats support ICC profiles.
+ *
+ * @param byteSource A valid ByteSource.
+ * @param params Optional instructions for special-handling or
+ * interpretation of the input data.
+ * @return If available, a valid array of bytes; otherwise, a null
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public abstract byte[] getICCProfileBytes(ByteSource byteSource, Map params)
+ throws ImageReadException, IOException;
+
+ /**
+ * Write the ImageInfo and format-specific information for the image
+ * content of the specified byte array to a string.
+ *
+ * @param bytes A valid array of bytes.
+ * @return A valid string.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final String dumpImageFile(final byte[] bytes) throws ImageReadException, IOException {
+ return dumpImageFile(new ByteSourceArray(bytes));
+ }
+
+
+ /**
+ * Write the ImageInfo and format-specific information for the image
+ * content of the specified file to a string.
+ *
+ * @param file A valid file reference.
+ * @return A valid string.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final String dumpImageFile(final File file) throws ImageReadException, IOException {
+ if (!canAcceptExtension(file)) {
+ return null;
+ }
+
+ if (getDebug()) {
+ System.out.println(getName() + ": " + file.getName());
+ }
+
+ return dumpImageFile(new ByteSourceFile(file));
+ }
+
+ /**
+ * Write the ImageInfo and format-specific information for the image
+ * content of the specified byte source to a string.
+ *
+ * @param byteSource A valid byte source.
+ * @return A valid string.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public final String dumpImageFile(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final StringWriter sw = new StringWriter();
+ final PrintWriter pw = new PrintWriter(sw);
+
+ dumpImageFile(pw, byteSource);
+
+ pw.flush();
+
+ return sw.toString();
+ }
+
+ /**
+ * Write the ImageInfo and format-specific information for the image
+ * content of the specified byte source to a PrintWriter
+ *
+ * @param byteSource A valid byte source.
+ * @return A valid PrintWriter.
+ * @throws ImageReadException In the event that the the specified content
+ * does not conform to the format of the specific
+ * parser implementation.
+ * @throws IOException In the event of unsuccessful read or access operation.
+ */
+ public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ return false;
+ }
+
+
+ /**
+ * Get a descriptive name for the implementation of an ImageParser.
+ *
+ * @return a valid, subject-matter-specific string.
+ */
+ public abstract String getName();
+
+ /**
+ * Get the default extension for the format specified by an implementation
+ * of ImageParser. Some parsers can support more than one extension
+ * (i.e. .JPEG, .JPG; .TIF, .TIFF, etc.).
+ *
+ * @return A valid string.
+ */
+ public abstract String getDefaultExtension();
+
+ /**
+ * Get an array of all accepted extensions
+ *
+ * @return A valid array of one or more elements.
+ */
+ protected abstract String[] getAcceptedExtensions();
+
+ /**
+ * Get an array of ImageFormat objects describing all accepted types
+ *
+ * @return A valid array of one or more elements.
+ */
+ protected abstract ImageFormat[] getAcceptedTypes();
+
+ /**
+ * Indicates whether the ImageParser implementation can accept
+ * the specified format
+ *
+ * @param type An instance of ImageFormat.
+ * @return If the parser can accept the format, true; otherwise, false.
+ */
+ public boolean canAcceptType(final ImageFormat type) {
+ final ImageFormat[] types = getAcceptedTypes();
+
+ for (final ImageFormat type2 : types) {
+ if (type2.equals(type)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Indicates whether the ImageParser implementation can accept
+ * the specified file based on its extension.
+ *
+ * @param file An valid file reference.
+ * @return If the parser can accept the format, true; otherwise, false.
+ */
+ protected final boolean canAcceptExtension(final File file) {
+ return canAcceptExtension(file.getName());
+ }
+
+ /**
+ * Indicates whether the ImageParser implementation can accept
+ * the specified file name based on its extension.
+ *
+ * @param filename An valid string giving a file name or file path.
+ * @return If the parser can accept the format, true; otherwise, false.
+ */
+ protected final boolean canAcceptExtension(final String filename) {
+ final String[] exts = getAcceptedExtensions();
+ if (exts == null) {
+ return true;
+ }
+
+ final int index = filename.lastIndexOf('.');
+ if (index >= 0) {
+ String ext = filename.substring(index);
+ ext = ext.toLowerCase(Locale.ENGLISH);
+
+ for (final String ext2 : exts) {
+ final String ext2Lower = ext2.toLowerCase(Locale.ENGLISH);
+ if (ext2Lower.equals(ext)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get an instance of IBufferedImageFactory based on the presence
+ * of a specification for ImagingConstants..BUFFERED_IMAGE_FACTORY
+ * within the supplied params.
+ *
+ * @param params A valid Map object, or a null.
+ * @return A valid instance of an implementation of a IBufferedImageFactory.
+ */
+ protected IBufferedImageFactory getBufferedImageFactory(final Map params) {
+ if (params == null) {
+ return new SimpleBufferedImageFactory();
+ }
+
+ final IBufferedImageFactory result = (IBufferedImageFactory) params
+ .get(ImagingConstants.BUFFERED_IMAGE_FACTORY);
+
+ if (null != result) {
+ return result;
+ }
+
+ return new SimpleBufferedImageFactory();
+ }
+
+ /**
+ * A utility method to search a params specification and determine
+ * whether it contains the ImagingConstants.PARAM_KEY_STRICT
+ * specification. Intended
+ * for internal use by ImageParser implementations.
+ *
+ * @param params A valid Map object (or a null).
+ * @return If the params specify strict format compliance, true;
+ * otherwise, false.
+ */
+ public static boolean isStrict(final Map params) {
+ if (params == null || !params.containsKey(ImagingConstants.PARAM_KEY_STRICT)) {
+ return false;
+ }
+ return ((Boolean) params.get(ImagingConstants.PARAM_KEY_STRICT)).booleanValue();
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/ImageReadException.java b/src/main/java/org/apache/commons/imaging/ImageReadException.java
new file mode 100644
index 0000000..4fdfeb6
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/ImageReadException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+/**
+ * A custom exception thrown when an ImageParser or other utility
+ * encounters a format-violation, non-supported element, or other
+ * condition that renders image data unaccessible.
+ */
+public class ImageReadException extends ImagingException {
+ private static final long serialVersionUID = -1L;
+
+ public ImageReadException(final String message) {
+ super(message);
+ }
+
+ public ImageReadException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/ImageWriteException.java b/src/main/java/org/apache/commons/imaging/ImageWriteException.java
new file mode 100644
index 0000000..fae0e27
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/ImageWriteException.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+/**
+ * A custom exception thrown when an ImageParser or other utility
+ * encounters a format-violation, non-supported element, or other
+ * condition that renders image data unwritable.
+ */
+public class ImageWriteException extends ImagingException {
+ private static final long serialVersionUID = -1L;
+
+ public ImageWriteException(final String message) {
+ super(message);
+ }
+
+ public ImageWriteException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ public ImageWriteException(final String message, final Object data) {
+ super(message + ": " + data + " (" + getType(data) + ")");
+ }
+
+ private static String getType(final Object value) {
+ if (value == null) {
+ return "null";
+ } else if (value instanceof Object[]) {
+ return "[Object[]: " + ((Object[]) value).length + "]";
+ } else if (value instanceof char[]) {
+ return "[char[]: " + ((char[]) value).length + "]";
+ } else if (value instanceof byte[]) {
+ return "[byte[]: " + ((byte[]) value).length + "]";
+ } else if (value instanceof short[]) {
+ return "[short[]: " + ((short[]) value).length + "]";
+ } else if (value instanceof int[]) {
+ return "[int[]: " + ((int[]) value).length + "]";
+ } else if (value instanceof long[]) {
+ return "[long[]: " + ((long[]) value).length + "]";
+ } else if (value instanceof float[]) {
+ return "[float[]: " + ((float[]) value).length + "]";
+ } else if (value instanceof double[]) {
+ return "[double[]: " + ((double[]) value).length + "]";
+ } else if (value instanceof boolean[]) {
+ return "[boolean[]: " + ((boolean[]) value).length + "]";
+ } else {
+ return value.getClass().getName();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/Imaging.java b/src/main/java/org/apache/commons/imaging/Imaging.java
new file mode 100644
index 0000000..74747e6
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/Imaging.java
@@ -0,0 +1,1504 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.color.ICC_Profile;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.common.bytesource.ByteSourceArray;
+import org.apache.commons.imaging.common.bytesource.ByteSourceFile;
+import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
+import org.apache.commons.imaging.icc.IccProfileInfo;
+import org.apache.commons.imaging.icc.IccProfileParser;
+import org.apache.commons.imaging.util.IoUtils;
+
+import static org.apache.commons.imaging.ImagingConstants.*;
+
+/**
+ * The primary application programming interface (API) to the Imaging library.
+ *
+
+ *
Application Notes
+ * Using this class
+ * Almost all of the Apache Commons Imaging library's core functionality can
+ * be accessed through the methods provided by this class.
+ * The use of the Imaging class is similar to the Java API's ImageIO class,
+ * though Imaging supports formats and options not included in the standard
+ * Java API.
+ * All of methods provided by the Imaging class are declared static.
+ *
The Apache Commons Imaging package is a pure Java implementation.
+ *
Format support
+ * While the Apache Commons Imaging package handles a number of different
+ * graphics formats, support for some formats is not yet complete.
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site.
+ * Optional parameters for image reading and writing
+ * Some of the methods provided by this class accept an optional
+ * params argument that permits the application to specify
+ * elements for special handling. If these specifications are not required by
+ * the application, the params argument may be omitted (as appropriate) or
+ * a null argument may be provided. In image-writing operations, the option
+ * parameters may include options such as data-compression type (if any),
+ * color model, or other format-specific data representations. The parameters
+ * map may also be used to provide EXIF Tags and other metadata to those
+ * formats that support them. In image-reading operations,
+ * the parameters may include information about special handling in reading
+ * the image data.
+ * Optional parameters are specified using a Map object (typically,
+ * a Java HashMap) to specify a set of keys and values for input.
+ * The specification for support keys is provided by the ImagingConstants
+ * interface as well as by format-specific interfaces such as
+ * JpegContants or TiffConstants.
+ *
Example code
+ * See the source of the SampleUsage class and other classes in the
+ * org.apache.commons.imaging.examples package for examples.
+ *
+ * @see org.apache.commons.imaging.examples.SampleUsage
+ * @see Format Support
+ */
+public abstract class Imaging {
+ private static final int[] MAGIC_NUMBERS_GIF = { 0x47, 0x49, };
+ private static final int[] MAGIC_NUMBERS_PNG = { 0x89, 0x50, };
+ private static final int[] MAGIC_NUMBERS_JPEG = { 0xff, 0xd8, };
+ private static final int[] MAGIC_NUMBERS_BMP = { 0x42, 0x4d, };
+ private static final int[] MAGIC_NUMBERS_TIFF_MOTOROLA = { 0x4D, 0x4D, };
+ private static final int[] MAGIC_NUMBERS_TIFF_INTEL = { 0x49, 0x49, };
+ private static final int[] MAGIC_NUMBERS_PAM = { 0x50, 0x37, };
+ private static final int[] MAGIC_NUMBERS_PSD = { 0x38, 0x42, };
+ private static final int[] MAGIC_NUMBERS_PBM_A = { 0x50, 0x31, };
+ private static final int[] MAGIC_NUMBERS_PBM_B = { 0x50, 0x34, };
+ private static final int[] MAGIC_NUMBERS_PGM_A = { 0x50, 0x32, };
+ private static final int[] MAGIC_NUMBERS_PGM_B = { 0x50, 0x35, };
+ private static final int[] MAGIC_NUMBERS_PPM_A = { 0x50, 0x33, };
+ private static final int[] MAGIC_NUMBERS_PPM_B = { 0x50, 0x36, };
+ private static final int[] MAGIC_NUMBERS_JBIG2_1 = { 0x97, 0x4A, };
+ private static final int[] MAGIC_NUMBERS_JBIG2_2 = { 0x42, 0x32, };
+ private static final int[] MAGIC_NUMBERS_ICNS = { 0x69, 0x63, };
+ private static final int[] MAGIC_NUMBERS_DCX = { 0xB1, 0x68, };
+ private static final int[] MAGIC_NUMBERS_RGBE = { 0x23, 0x3F, };
+
+ /**
+ * Attempts to determine if a file contains an image recorded in
+ * a supported graphics format based on its file-name extension
+ * (for example ".jpg", ".gif", ".png", etc.).
+ *
+ * @param file A valid File object providing a reference to
+ * a file that may contain an image.
+ * @return true if the file-name includes a supported image
+ * format file extension; otherwise, false.
+ */
+ public static boolean hasImageFileExtension(final File file) {
+ if (file == null || !file.isFile()) {
+ return false;
+ }
+ return hasImageFileExtension(file.getName());
+ }
+
+ /**
+ * Attempts to determine if a file contains an image recorded in
+ * a supported graphics format based on its file-name extension
+ * (for example ".jpg", ".gif", ".png", etc.).
+ *
+ * @param filename A valid string representing name of file
+ * which may contain an image.
+ * @return true if the filename has an image format file extension.
+ */
+ public static boolean hasImageFileExtension(String filename) {
+ if (filename == null) {
+ return false;
+ }
+
+ filename = filename.toLowerCase(Locale.ENGLISH);
+
+ final ImageParser[] imageParsers = ImageParser.getAllImageParsers();
+ for (final ImageParser imageParser : imageParsers) {
+ final String[] exts = imageParser.getAcceptedExtensions();
+
+ for (final String ext : exts) {
+ if (filename.endsWith(ext.toLowerCase(Locale.ENGLISH))) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Attempts to determine the image format of a file based on its
+ * "magic numbers," the first bytes of the data.
+ * Many graphics format specify identifying byte
+ * values that appear at the beginning of the data file. This method
+ * checks for such identifying elements and returns a ImageFormat
+ * enumeration indicating what it detects. Note that this
+ * method can return "false positives" in cases where non-image files
+ * begin with the specified byte values.
+ *
+ * @param bytes Byte array containing an image file.
+ * @return An ImageFormat, such as ImageFormat.IMAGE_FORMAT_JPEG. Returns
+ * ImageFormat.IMAGE_FORMAT_UNKNOWN if the image type cannot be
+ * determined.
+ */
+ public static ImageFormat guessFormat(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return guessFormat(new ByteSourceArray(bytes));
+ }
+
+ /**
+ * Attempts to determine the image format of a file based on its
+ * "magic numbers," the first bytes of the data.
+ *
Many graphics formats specify identifying byte
+ * values that appear at the beginning of the data file. This method
+ * checks for such identifying elements and returns a ImageFormat
+ * enumeration indicating what it detects. Note that this
+ * method can return "false positives" in cases where non-image files
+ * begin with the specified byte values.
+ *
+ * @param file File containing image data.
+ * @return An ImageFormat, such as ImageFormat.IMAGE_FORMAT_JPEG. Returns
+ * ImageFormat.IMAGE_FORMAT_UNKNOWN if the image type cannot be
+ * determined.
+ */
+ public static ImageFormat guessFormat(final File file) throws ImageReadException,
+ IOException {
+ return guessFormat(new ByteSourceFile(file));
+ }
+
+ private static boolean compareBytePair(final int[] a, final int[] b) {
+ if (a.length != 2 && b.length != 2) {
+ throw new RuntimeException("Invalid Byte Pair.");
+ }
+ return (a[0] == b[0]) && (a[1] == b[1]);
+ }
+
+
+ /**
+ * Attempts to determine the image format of a file based on its
+ * "magic numbers," the first bytes of the data.
+ *
Many graphics formats specify identifying byte
+ * values that appear at the beginning of the data file. This method
+ * checks for such identifying elements and returns a ImageFormat
+ * enumeration indicating what it detects. Note that this
+ * method can return "false positives" in cases where non-image files
+ * begin with the specified byte values.
+ *
+ * @param byteSource a valid ByteSource object potentially supplying
+ * data for an image.
+ * @return An ImageFormat, such as ImageFormat.IMAGE_FORMAT_JPEG. Returns
+ * ImageFormat.IMAGE_FORMAT_UNKNOWN if the image type cannot be
+ * determined.
+ * @throws ImageReadException in the event of an unsuccessful
+ * attempt to read the image data
+ * @throws IOException in the event of an unrecoverable I/O condition.
+ */
+ public static ImageFormat guessFormat(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+
+ if (byteSource == null) {
+ return ImageFormats.UNKNOWN;
+ }
+
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+
+ final int i1 = is.read();
+ final int i2 = is.read();
+ if ((i1 < 0) || (i2 < 0)) {
+ throw new ImageReadException(
+ "Couldn't read magic numbers to guess format.");
+ }
+
+ final int b1 = i1 & 0xff;
+ final int b2 = i2 & 0xff;
+ final int[] bytePair = { b1, b2, };
+
+ if (compareBytePair(MAGIC_NUMBERS_GIF, bytePair)) {
+ canThrow = true;
+ return ImageFormats.GIF;
+ }
+ // else if (b1 == 0x00 && b2 == 0x00) // too similar to TGA
+ // {
+ // return ImageFormat.IMAGE_FORMAT_ICO;
+ // }
+ else if (compareBytePair(MAGIC_NUMBERS_PNG, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PNG;
+ } else if (compareBytePair(MAGIC_NUMBERS_JPEG, bytePair)) {
+ canThrow = true;
+ return ImageFormats.JPEG;
+ } else if (compareBytePair(MAGIC_NUMBERS_BMP, bytePair)) {
+ canThrow = true;
+ return ImageFormats.BMP;
+ } else if (compareBytePair(MAGIC_NUMBERS_TIFF_MOTOROLA, bytePair)) {
+ canThrow = true;
+ return ImageFormats.TIFF;
+ } else if (compareBytePair(MAGIC_NUMBERS_TIFF_INTEL, bytePair)) {
+ canThrow = true;
+ return ImageFormats.TIFF;
+ } else if (compareBytePair(MAGIC_NUMBERS_PSD, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PSD;
+ } else if (compareBytePair(MAGIC_NUMBERS_PAM, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PAM;
+ } else if (compareBytePair(MAGIC_NUMBERS_PBM_A, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PBM;
+ } else if (compareBytePair(MAGIC_NUMBERS_PBM_B, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PBM;
+ } else if (compareBytePair(MAGIC_NUMBERS_PGM_A, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PGM;
+ } else if (compareBytePair(MAGIC_NUMBERS_PGM_B, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PGM;
+ } else if (compareBytePair(MAGIC_NUMBERS_PPM_A, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PPM;
+ } else if (compareBytePair(MAGIC_NUMBERS_PPM_B, bytePair)) {
+ canThrow = true;
+ return ImageFormats.PPM;
+ } else if (compareBytePair(MAGIC_NUMBERS_JBIG2_1, bytePair)) {
+ final int i3 = is.read();
+ final int i4 = is.read();
+ if ((i3 < 0) || (i4 < 0)) {
+ throw new ImageReadException(
+ "Couldn't read magic numbers to guess format.");
+ }
+
+ final int b3 = i3 & 0xff;
+ final int b4 = i4 & 0xff;
+ final int[] bytePair2 = { b3, b4, };
+ if (compareBytePair(MAGIC_NUMBERS_JBIG2_2, bytePair2)) {
+ canThrow = true;
+ return ImageFormats.JBIG2;
+ }
+ } else if (compareBytePair(MAGIC_NUMBERS_ICNS, bytePair)) {
+ canThrow = true;
+ return ImageFormats.ICNS;
+ } else if (compareBytePair(MAGIC_NUMBERS_DCX, bytePair)) {
+ canThrow = true;
+ return ImageFormats.DCX;
+ } else if (compareBytePair(MAGIC_NUMBERS_RGBE, bytePair)) {
+ canThrow = true;
+ return ImageFormats.RGBE;
+ }
+ canThrow = true;
+ return ImageFormats.UNKNOWN;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ /**
+ * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and
+ * TIFF images.
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @return An instance of ICC_Profile or null if the image contains no ICC
+ * profile.
+ */
+ public static ICC_Profile getICCProfile(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getICCProfile(bytes, null);
+ }
+
+ /**
+ * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and
+ * TIFF images.
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of ICC_Profile or null if the image contains no ICC
+ * profile..
+ */
+ public static ICC_Profile getICCProfile(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getICCProfile(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and
+ * TIFF images.
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @return An instance of ICC_Profile or null if the image contains no ICC
+ * profile..
+ */
+ public static ICC_Profile getICCProfile(final InputStream is, final String filename)
+ throws ImageReadException, IOException {
+ return getICCProfile(is, filename, null);
+ }
+
+ /**
+ * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and
+ * TIFF images.
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of ICC_Profile or null if the image contains no ICC
+ * profile..
+ */
+ public static ICC_Profile getICCProfile(final InputStream is, final String filename,
+ final Map params) throws ImageReadException, IOException {
+ return getICCProfile(new ByteSourceInputStream(is, filename), params);
+ }
+
+ /**
+ * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and
+ * TIFF images.
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @return An instance of ICC_Profile or null if the image contains no ICC
+ * profile..
+ */
+ public static ICC_Profile getICCProfile(final File file)
+ throws ImageReadException, IOException {
+ return getICCProfile(file, null);
+ }
+
+ /**
+ * Extracts an ICC Profile (if present) from JPEG, PNG, PSD (Photoshop) and
+ * TIFF images.
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of ICC_Profile or null if the image contains no ICC
+ * profile..
+ */
+ public static ICC_Profile getICCProfile(final File file, final Map params)
+ throws ImageReadException, IOException {
+ return getICCProfile(new ByteSourceFile(file), params);
+ }
+
+ protected static ICC_Profile getICCProfile(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final byte[] bytes = getICCProfileBytes(byteSource, params);
+ if (bytes == null) {
+ return null;
+ }
+
+ final IccProfileParser parser = new IccProfileParser();
+ final IccProfileInfo info = parser.getICCProfileInfo(bytes);
+ if (info == null) {
+ return null;
+ }
+ if (info.issRGB()) {
+ return null;
+ }
+
+ return ICC_Profile.getInstance(bytes);
+ }
+
+ /**
+ * Extracts the raw bytes of an ICC Profile (if present) from JPEG, PNG, PSD
+ * (Photoshop) and TIFF images.
+ *
+ * To parse the result use IccProfileParser or
+ * ICC_Profile.getInstance(bytes).
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @return A byte array.
+ * @see IccProfileParser
+ * @see ICC_Profile
+ */
+ public static byte[] getICCProfileBytes(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getICCProfileBytes(bytes, null);
+ }
+
+ /**
+ * Extracts the raw bytes of an ICC Profile (if present) from JPEG, PNG, PSD
+ * (Photoshop) and TIFF images.
+ *
+ * To parse the result use IccProfileParser or
+ * ICC_Profile.getInstance(bytes).
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return A byte array.
+ * @see IccProfileParser
+ * @see ICC_Profile
+ */
+ public static byte[] getICCProfileBytes(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getICCProfileBytes(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Extracts the raw bytes of an ICC Profile (if present) from JPEG, PNG, PSD
+ * (Photoshop) and TIFF images.
+ *
+ * To parse the result use IccProfileParser or
+ * ICC_Profile.getInstance(bytes).
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @return A byte array.
+ * @see IccProfileParser
+ * @see ICC_Profile
+ */
+ public static byte[] getICCProfileBytes(final File file)
+ throws ImageReadException, IOException {
+ return getICCProfileBytes(file, null);
+ }
+
+ /**
+ * Extracts the raw bytes of an ICC Profile (if present) from JPEG, PNG, PSD
+ * (Photoshop) and TIFF images.
+ *
+ * To parse the result use IccProfileParser or
+ * ICC_Profile.getInstance(bytes).
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return A byte array.
+ * @see IccProfileParser
+ * @see ICC_Profile
+ */
+ public static byte[] getICCProfileBytes(final File file, final Map params)
+ throws ImageReadException, IOException {
+ return getICCProfileBytes(new ByteSourceFile(file), params);
+ }
+
+ private static byte[] getICCProfileBytes(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.getICCProfileBytes(byteSource, params);
+ }
+
+ /**
+ * Parses the "image info" of an image.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param filename
+ * String.
+ * @param bytes
+ * Byte array containing an image file.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final String filename, final byte[] bytes,
+ final Map params) throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceArray(filename, bytes), params);
+ }
+
+ /**
+ * Parses the "image info" of an image.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param filename
+ * String.
+ * @param bytes
+ * Byte array containing an image file.
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final String filename, final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceArray(filename, bytes), null);
+ }
+
+ /**
+ * Parses the "image info" of an image.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final InputStream is, final String filename)
+ throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceInputStream(is, filename), null);
+ }
+
+ /**
+ * Parses the "image info" of an image.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final InputStream is, final String filename,
+ final Map params) throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceInputStream(is, filename), params);
+ }
+
+ /**
+ * Parses the "image info" of an image.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceArray(bytes), null);
+ }
+
+ /**
+ * Parses the "image info" of an image.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Parses the "image info" of an image file.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final File file, final Map params)
+ throws ImageReadException, IOException {
+ return getImageInfo(new ByteSourceFile(file), params);
+ }
+
+ /**
+ * Parses the "image info" of an image file.
+ *
+ * "Image info" is a summary of basic information about the image such as:
+ * width, height, file format, bit depth, color type, etc.
+ *
+ * Not to be confused with "image metadata."
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @return An instance of ImageInfo.
+ * @see ImageInfo
+ */
+ public static ImageInfo getImageInfo(final File file) throws ImageReadException,
+ IOException {
+ return getImageInfo(file, null);
+ }
+
+ private static ImageInfo getImageInfo(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.getImageInfo(byteSource, params);
+ }
+
+ private static ImageParser getImageParser(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final ImageFormat format = guessFormat(byteSource);
+ if (!format.equals(ImageFormats.UNKNOWN)) {
+
+ final ImageParser[] imageParsers = ImageParser.getAllImageParsers();
+
+ for (final ImageParser imageParser : imageParsers) {
+ if (imageParser.canAcceptType(format)) {
+ return imageParser;
+ }
+ }
+ }
+
+ final String filename = byteSource.getFilename();
+ if (filename != null) {
+ final ImageParser[] imageParsers = ImageParser.getAllImageParsers();
+
+ for (final ImageParser imageParser : imageParsers) {
+ if (imageParser.canAcceptExtension(filename)) {
+ return imageParser;
+ }
+ }
+ }
+
+ throw new ImageReadException("Can't parse this format.");
+ }
+
+ /**
+ * Determines the width and height of an image.
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @return The width and height of the image.
+ */
+ public static Dimension getImageSize(final InputStream is, final String filename)
+ throws ImageReadException, IOException {
+ return getImageSize(is, filename, null);
+ }
+
+ /**
+ * Determines the width and height of an image.
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return The width and height of the image.
+ */
+ public static Dimension getImageSize(final InputStream is, final String filename,
+ final Map params) throws ImageReadException, IOException {
+ return getImageSize(new ByteSourceInputStream(is, filename), params);
+ }
+
+ /**
+ * Determines the width and height of an image.
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @return The width and height of the image.
+ */
+ public static Dimension getImageSize(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getImageSize(bytes, null);
+ }
+
+ /**
+ * Determines the width and height of an image.
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return The width and height of the image.
+ */
+ public static Dimension getImageSize(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getImageSize(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Determines the width and height of an image file.
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @return The width and height of the image.
+ */
+ public static Dimension getImageSize(final File file) throws ImageReadException,
+ IOException {
+ return getImageSize(file, null);
+ }
+
+ /**
+ * Determines the width and height of an image file.
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return The width and height of the image.
+ */
+ public static Dimension getImageSize(final File file, final Map params)
+ throws ImageReadException, IOException {
+ return getImageSize(new ByteSourceFile(file), params);
+ }
+
+ public static Dimension getImageSize(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.getImageSize(byteSource, params);
+ }
+
+ /**
+ * Extracts the embedded XML metadata as an XML string.
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public static String getXmpXml(final InputStream is, final String filename)
+ throws ImageReadException, IOException {
+ return getXmpXml(is, filename, null);
+ }
+
+ /**
+ * Extracts the embedded XML metadata as an XML string.
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public static String getXmpXml(final InputStream is, final String filename, final Map params)
+ throws ImageReadException, IOException {
+ return getXmpXml(new ByteSourceInputStream(is, filename), params);
+ }
+
+ /**
+ * Extracts the embedded XML metadata as an XML string.
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public static String getXmpXml(final byte[] bytes) throws ImageReadException,
+ IOException {
+ return getXmpXml(bytes, null);
+ }
+
+ /**
+ * Extracts the embedded XML metadata as an XML string.
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public static String getXmpXml(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getXmpXml(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Extracts the embedded XML metadata as an XML string.
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public static String getXmpXml(final File file) throws ImageReadException,
+ IOException {
+ return getXmpXml(file, null);
+ }
+
+ /**
+ * Extracts the embedded XML metadata as an XML string.
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public static String getXmpXml(final File file, final Map params)
+ throws ImageReadException, IOException {
+ return getXmpXml(new ByteSourceFile(file), params);
+ }
+
+ /**
+ * Extracts the embedded XML metadata as an XML string.
+ *
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ public static String getXmpXml(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.getXmpXml(byteSource, params);
+ }
+
+ /**
+ * Parses the metadata of an image. This metadata depends on the format of
+ * the image.
+ *
+ * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may
+ * contain comments. TIFF files may contain metadata.
+ *
+ * The instance of IImageMetadata returned by getMetadata() should be upcast
+ * (depending on image format).
+ *
+ * Not to be confused with "image info."
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @return An instance of IImageMetadata.
+ * @see IImageMetadata
+ */
+ public static IImageMetadata getMetadata(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getMetadata(bytes, null);
+ }
+
+ /**
+ * Parses the metadata of an image. This metadata depends on the format of
+ * the image.
+ *
+ * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may
+ * contain comments. TIFF files may contain metadata.
+ *
+ * The instance of IImageMetadata returned by getMetadata() should be upcast
+ * (depending on image format).
+ *
+ * Not to be confused with "image info."
+ *
+ *
+ * @param bytes
+ * Byte array containing an image file.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of IImageMetadata.
+ * @see IImageMetadata
+ */
+ public static IImageMetadata getMetadata(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getMetadata(new ByteSourceArray(bytes), params);
+ }
+
+ /**
+ * Parses the metadata of an image file. This metadata depends on the format
+ * of the image.
+ *
+ * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may
+ * contain comments. TIFF files may contain metadata.
+ *
+ * The instance of IImageMetadata returned by getMetadata() should be upcast
+ * (depending on image format).
+ *
+ * Not to be confused with "image info."
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @return An instance of IImageMetadata.
+ * @see IImageMetadata
+ */
+ public static IImageMetadata getMetadata(final InputStream is, final String filename)
+ throws ImageReadException, IOException {
+ return getMetadata(is, filename, null);
+ }
+
+ /**
+ * Parses the metadata of an image file. This metadata depends on the format
+ * of the image.
+ *
+ * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may
+ * contain comments. TIFF files may contain metadata.
+ *
+ * The instance of IImageMetadata returned by getMetadata() should be upcast
+ * (depending on image format).
+ *
+ * Not to be confused with "image info."
+ *
+ *
+ * @param is
+ * InputStream from which to read image data.
+ * @param filename
+ * Filename associated with image data (optional).
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of IImageMetadata.
+ * @see IImageMetadata
+ */
+ public static IImageMetadata getMetadata(final InputStream is, final String filename,
+ final Map params) throws ImageReadException, IOException {
+ return getMetadata(new ByteSourceInputStream(is, filename), params);
+ }
+
+ /**
+ * Parses the metadata of an image file. This metadata depends on the format
+ * of the image.
+ *
+ * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may
+ * contain comments. TIFF files may contain metadata.
+ *
+ * The instance of IImageMetadata returned by getMetadata() should be upcast
+ * (depending on image format).
+ *
+ * Not to be confused with "image info."
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @return An instance of IImageMetadata.
+ * @see IImageMetadata
+ */
+ public static IImageMetadata getMetadata(final File file)
+ throws ImageReadException, IOException {
+ return getMetadata(file, null);
+ }
+
+ /**
+ * Parses the metadata of an image file. This metadata depends on the format
+ * of the image.
+ *
+ * JPEG/JFIF files may contain EXIF and/or IPTC metadata. PNG files may
+ * contain comments. TIFF files may contain metadata.
+ *
+ * The instance of IImageMetadata returned by getMetadata() should be upcast
+ * (depending on image format).
+ *
+ * Not to be confused with "image info."
+ *
+ *
+ * @param file
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return An instance of IImageMetadata.
+ * @see IImageMetadata
+ */
+ public static IImageMetadata getMetadata(final File file, final Map params)
+ throws ImageReadException, IOException {
+ return getMetadata(new ByteSourceFile(file), params);
+ }
+
+ private static IImageMetadata getMetadata(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.getMetadata(byteSource, params);
+ }
+
+ /**
+ * Write the ImageInfo and format-specific information for the image
+ * content of the specified byte array to a string.
+ * @param bytes A valid array of bytes.
+ * @return A valid string.
+ * @throws ImageReadException In the event that the the specified
+ * content does not conform to the format of the specific parser
+ * implementation.
+ * @throws IOException In the event of unsuccessful read or
+ * access operation.
+ */
+ public static String dumpImageFile(final byte[] bytes) throws ImageReadException,
+ IOException {
+ return dumpImageFile(new ByteSourceArray(bytes));
+ }
+
+ /**
+ * Write the ImageInfo and format-specific information for the image
+ * content of the specified file to a string.
+ * @param file A valid file reference.
+ * @return A valid string.
+ * @throws ImageReadException In the event that the the specified
+ * content does not conform to the format of the specific parser
+ * implementation.
+ * @throws IOException In the event of unsuccessful read or
+ * access operation.
+ */
+ public static String dumpImageFile(final File file) throws ImageReadException,
+ IOException {
+ return dumpImageFile(new ByteSourceFile(file));
+ }
+
+ private static String dumpImageFile(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.dumpImageFile(byteSource);
+ }
+
+ /**
+ * Attempts to determine the image format of the specified data and
+ * evaluates its format compliance. This method
+ * returns a FormatCompliance object which includes information
+ * about the data's compliance to a specific format.
+ * @param bytes a valid array of bytes containing image data.
+ * @return if successful, a valid FormatCompliance object.
+ * @throws ImageReadException in the event of unreadable data.
+ * @throws IOException in the event of an unrecoverable I/O condition.
+ */
+ public static FormatCompliance getFormatCompliance(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getFormatCompliance(new ByteSourceArray(bytes));
+ }
+
+ /**
+ * Attempts to determine the image format of the specified data and
+ * evaluates its format compliance. This method
+ * returns a FormatCompliance object which includes information
+ * about the data's compliance to a specific format.
+ * @param file valid file containing image data
+ * @return if successful, a valid FormatCompliance object.
+ * @throws ImageReadException in the event of unreadable data.
+ * @throws IOException in the event of an unrecoverable I/O condition.
+ */
+ public static FormatCompliance getFormatCompliance(final File file)
+ throws ImageReadException, IOException {
+ return getFormatCompliance(new ByteSourceFile(file));
+ }
+
+ private static FormatCompliance getFormatCompliance(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.getFormatCompliance(byteSource);
+ }
+
+ /**
+ * Gets all images specified by the InputStream (some
+ * formats may include multiple images within a single data source).
+ * @param is A valid InputStream
+ * @return A valid (potentially empty) list of BufferedImage objects.
+ * @throws ImageReadException In the event that the the specified
+ * content does not conform to the format of the specific parser
+ * implementation.
+ * @throws IOException In the event of unsuccessful read or
+ * access operation.
+ */
+ public static List getAllBufferedImages(final InputStream is,
+ final String filename) throws ImageReadException, IOException {
+ return getAllBufferedImages(new ByteSourceInputStream(is, filename));
+ }
+
+ /**
+ * Gets all images specified by the byte array (some
+ * formats may include multiple images within a single data source).
+ * @param bytes a valid array of bytes
+ * @return A valid (potentially empty) list of BufferedImage objects.
+ * @throws ImageReadException In the event that the the specified
+ * content does not conform to the format of the specific parser
+ * implementation.
+ * @throws IOException In the event of unsuccessful read or
+ * access operation.
+ */
+ public static List getAllBufferedImages(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getAllBufferedImages(new ByteSourceArray(bytes));
+ }
+
+ /**
+ * Gets all images specified by the file (some
+ * formats may include multiple images within a single data source).
+ * @param file A reference to a valid data file.
+ * @return A valid (potentially empty) list of BufferedImage objects.
+ * @throws ImageReadException In the event that the the specified
+ * content does not conform to the format of the specific parser
+ * implementation.
+ * @throws IOException In the event of unsuccessful read or
+ * access operation.
+ */
+ public static List getAllBufferedImages(final File file)
+ throws ImageReadException, IOException {
+ return getAllBufferedImages(new ByteSourceFile(file));
+ }
+
+
+ private static List getAllBufferedImages(
+ final ByteSource byteSource) throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+
+ return imageParser.getAllBufferedImages(byteSource);
+ }
+
+
+ /**
+ * Reads the first image from an InputStream.
+ *
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param is a valid ImageStream from which to read data.
+ * @return if successful, a valid buffered image
+ * @throws ImageReadException in the event of a processing error
+ * while reading an image (i.e. a format violation, etc.).
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ */
+
+ public static BufferedImage getBufferedImage(final InputStream is)
+ throws ImageReadException, IOException {
+ return getBufferedImage(is, null);
+ }
+
+
+
+ /**
+ * Reads the first image from an InputStream
+ * using data-processing options specified through a parameters
+ * map. Options may be configured using the ImagingContants
+ * interface or the various format-specific implementations provided
+ * by this package.
+ *
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param is a valid ImageStream from which to read data.
+ * @param params an optional parameters map specifying options
+ * @return if successful, a valid buffered image
+ * @throws ImageReadException in the event of a processing error
+ * while reading an image (i.e. a format violation, etc.).
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ */
+ public static BufferedImage getBufferedImage(final InputStream is, final Map params)
+ throws ImageReadException, IOException {
+ String filename = null;
+ if (params != null && params.containsKey(PARAM_KEY_FILENAME)) {
+ filename = (String) params.get(PARAM_KEY_FILENAME);
+ }
+ return getBufferedImage(new ByteSourceInputStream(is, filename), params);
+ }
+
+ /**
+ * Reads the first image from a byte array.
+ *
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param bytes a valid array of bytes from which to read data.
+ * @return if successful, a valid buffered image
+ * @throws ImageReadException in the event of a processing error
+ * while reading an image (i.e. a format violation, etc.).
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ */
+ public static BufferedImage getBufferedImage(final byte[] bytes)
+ throws ImageReadException, IOException {
+ return getBufferedImage(new ByteSourceArray(bytes), null);
+ }
+
+
+ /**
+ * Reads the first image from a byte array
+ * using data-processing options specified through a parameters
+ * map. Options may be configured using the ImagingContants
+ * interface or the various format-specific implementations provided
+ * by this package.
+ *
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param bytes a valid array of bytes from which to read data.
+ * @param params an optional parameters map specifying options.
+ * @return if successful, a valid buffered image
+ * @throws ImageReadException in the event of a processing error
+ * while reading an image (i.e. a format violation, etc.).
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ */
+ public static BufferedImage getBufferedImage(final byte[] bytes, final Map params)
+ throws ImageReadException, IOException {
+ return getBufferedImage(new ByteSourceArray(bytes), params);
+ }
+
+
+
+
+ /**
+ * Reads the first image from a file.
+ *
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param file a valid reference to a file containing image data.
+ * @return if successful, a valid buffered image
+ * @throws ImageReadException in the event of a processing error
+ * while reading an image (i.e. a format violation, etc.).
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ */
+ public static BufferedImage getBufferedImage(final File file)
+ throws ImageReadException, IOException {
+ return getBufferedImage(new ByteSourceFile(file), null);
+ }
+
+
+ /**
+ * Reads the first image from a file
+ * using data-processing options specified through a parameters
+ * map. Options may be configured using the ImagingContants
+ * interface or the various format-specific implementations provided
+ * by this package.
+ *
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param file a valid reference to a file containing image data.
+ * @return if successful, a valid buffered image
+ * @throws ImageReadException in the event of a processing error
+ * while reading an image (i.e. a format violation, etc.).
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ */
+ public static BufferedImage getBufferedImage(final File file, final Map params)
+ throws ImageReadException, IOException {
+ return getBufferedImage(new ByteSourceFile(file), params);
+ }
+
+
+
+ private static BufferedImage getBufferedImage(final ByteSource byteSource,
+ Map params) throws ImageReadException, IOException {
+ final ImageParser imageParser = getImageParser(byteSource);
+ if (null == params) {
+ params = new HashMap();
+ }
+
+ return imageParser.getBufferedImage(byteSource, params);
+ }
+
+ /**
+ * Writes the content of a BufferedImage to a file using the specified
+ * image format. Specifications for storing the file (such as data compression,
+ * color models, metadata tags, etc.) may be specified using an optional
+ * parameters map. These specifications are defined in the ImagingConstants
+ * interface or in various format-specific implementations.
+ *
+ * Image writing is not supported for all graphics formats.
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param src a valid BufferedImage object
+ * @param file the file to which the output image is to be written
+ * @param format the format in which the output image is to be written
+ * @param params an optional parameters map (nulls permitted)
+ * @throws ImageWriteException in the event of a format violation,
+ * unsupported image format, etc.
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ * @see ImagingConstants
+ */
+ public static void writeImage(final BufferedImage src, final File file,
+ final ImageFormat format, final Map params) throws ImageWriteException,
+ IOException {
+ OutputStream os = null;
+ boolean canThrow = false;
+ try {
+ os = new FileOutputStream(file);
+ os = new BufferedOutputStream(os);
+
+ writeImage(src, os, format, params);
+ canThrow = true;
+ } finally {
+ IoUtils.closeQuietly(canThrow, os);
+ }
+ }
+
+
+ /**
+ * Writes the content of a BufferedImage to a byte array using the specified
+ * image format. Specifications for storing the file (such as data compression,
+ * color models, metadata tags, etc.) may be specified using an optional
+ * parameters map. These specifications are defined in the ImagingConstants
+ * interface or in various format-specific implementations.
+ *
+ * Image writing is not supported for all graphics formats.
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param src a valid BufferedImage object
+ * @param format the format in which the output image is to be written
+ * @param params an optional parameters map (nulls permitted)
+ * @return if successful, a valid array of bytes.
+ * @throws ImageWriteException in the event of a format violation,
+ * unsupported image format, etc.
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ * @see ImagingConstants
+ */
+ public static byte[] writeImageToBytes(final BufferedImage src,
+ final ImageFormat format, final Map params) throws ImageWriteException,
+ IOException {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+
+ writeImage(src, os, format, params);
+
+ return os.toByteArray();
+ }
+
+
+ /**
+ * Writes the content of a BufferedImage to an OutputStream using the specified
+ * image format. Specifications for storing the file (such as data compression,
+ * color models, metadata tags, etc.) may be specified using an optional
+ * parameters map. These specifications are defined in the ImagingConstants
+ * interface or in various format-specific implementations.
+ *
+ * Image writing is not supported for all graphics formats.
+ * For the most recent information on support for specific formats, refer to
+ * Format Support
+ * at the main project development web site. While the Apache Commons
+ * Imaging package does not fully support all formats, it can read
+ * image info, metadata and ICC profiles from all image formats that
+ * provide this data.
+ * @param src a valid BufferedImage object
+ * @param os the OutputStream to which the output image is to be written
+ * @param format the format in which the output image is to be written
+ * @param params an optional parameters map (nulls permitted)
+ * @throws ImageWriteException in the event of a format violation,
+ * unsupported image format, etc.
+ * @throws IOException in the event of an unrecoverable I/O exception.
+ * @see ImagingConstants
+ */
+ public static void writeImage(final BufferedImage src, final OutputStream os,
+ final ImageFormat format, Map params) throws ImageWriteException,
+ IOException {
+ final ImageParser[] imageParsers = ImageParser.getAllImageParsers();
+
+ // make sure params are non-null
+ if (params == null) {
+ params = new HashMap();
+ }
+
+ params.put(PARAM_KEY_FORMAT, format);
+
+ ImageParser imageParser = null;
+ for (final ImageParser imageParser2 : imageParsers) {
+ if (imageParser2.canAcceptType(format)) {
+ imageParser = imageParser2;
+ break;
+ }
+ }
+ if (imageParser != null) {
+ imageParser.writeImage(src, os, params);
+ } else {
+ throw new ImageWriteException("Unknown Format: " + format);
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/SanselanConstants.java b/src/main/java/org/apache/commons/imaging/ImagingConstants.java
similarity index 58%
rename from src/main/java/org/apache/sanselan/SanselanConstants.java
rename to src/main/java/org/apache/commons/imaging/ImagingConstants.java
index 6e266c2..abdb696 100644
--- a/src/main/java/org/apache/sanselan/SanselanConstants.java
+++ b/src/main/java/org/apache/commons/imaging/ImagingConstants.java
@@ -1,108 +1,133 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan;
-
-import java.io.InputStream;
-
-import org.apache.sanselan.formats.tiff.constants.TiffConstants;
-
-public interface SanselanConstants
-{
- /**
- * Parameter key. Applies to read and write operations.
- *
- * Valid values: Boolean.TRUE and Boolean.FALSE.
- */
- public static final String PARAM_KEY_VERBOSE = "VERBOSE";
-
- /**
- * Parameter key. Used to hint the filename when reading from a byte array
- * or InputStream. The filename hint can help disambiguate what file the
- * image format.
- *
- * Applies to read operations.
- *
- * Valid values: filename as string
- *
- *
- * @see InputStream
- */
- public static final String PARAM_KEY_FILENAME = "FILENAME";
-
- /**
- * Parameter key. Used in write operations to indicate desired image format.
- *
- * Valid values: Any format defined in ImageFormat, such as
- * ImageFormat.IMAGE_FORMAT_PNG.
- *
- *
- * @see ImageFormat
- */
- public static final String PARAM_KEY_FORMAT = "FORMAT";
-
- /**
- * Parameter key. Used in write operations to indicate desired compression
- * algorithm.
- *
- * Currently only applies to writing TIFF image files.
- *
- * Valid values: TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED,
- * TiffConstants.TIFF_COMPRESSION_LZW,
- * TiffConstants.TIFF_COMPRESSION_PACKBITS.
- *
- *
- * @see TiffConstants
- */
- public static final String PARAM_KEY_COMPRESSION = "COMPRESSION";
-
- public static final String BUFFERED_IMAGE_FACTORY = "BUFFERED_IMAGE_FACTORY";
-
- /**
- * Parameter key. Indicates whether to read embedded thumbnails.
- *
- * Only applies to read EXIF metadata from JPEG/JFIF files.
- *
- * Valid values: Boolean.TRUE and Boolean.FALSE.
- *
- *
- * @see TiffConstants
- */
- public static final String PARAM_KEY_READ_THUMBNAILS = "READ_THUMBNAILS";
-
- /**
- * Parameter key. Indicates whether to throw exceptions when parsing invalid
- * files, or whether to tolerate small problems.
- *
- * Valid values: Boolean.TRUE and Boolean.FALSE. Default value:
- * Boolean.FALSE.
- *
- *
- * @see TiffConstants
- */
- public static final String PARAM_KEY_STRICT = "STRICT";
-
- /**
- * Parameter key.
- *
- * Only used when writing images.
- *
- * Valid values: String of XMP XML.
- *
- */
- public static final String PARAM_KEY_XMP_XML = "XMP_XML";
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+/**
+ * Defines constants that may be used in passing options to
+ * ImageParser read/write implementations, the utility routines
+ * implemented in the Imaging class, and throughout the
+ * Apache Commons Imaging package. Individual ImageParser
+ * implementations may define their own format-specific options.
+ */
+public interface ImagingConstants {
+ /**
+ * Parameter key. Applies to read and write operations.
+ *
+ * Valid values: Boolean.TRUE and Boolean.FALSE.
+ */
+ String PARAM_KEY_VERBOSE = "VERBOSE";
+
+ /**
+ * Parameter key. Used to hint the filename when reading from a byte array
+ * or InputStream. The filename hint can help disambiguate what file the
+ * image format.
+ *
+ * Applies to read operations.
+ *
+ * Valid values: filename as string
+ *
+ *
+ * @see java.io.InputStream
+ */
+ String PARAM_KEY_FILENAME = "FILENAME";
+
+ /**
+ * Parameter key. Used in write operations to indicate desired image format.
+ *
+ * Valid values: Any format defined in ImageFormat, such as
+ * ImageFormat.IMAGE_FORMAT_PNG.
+ *
+ *
+ * @see org.apache.commons.imaging.ImageFormats
+ */
+ String PARAM_KEY_FORMAT = "FORMAT";
+
+ /**
+ * Parameter key. Used in write operations to indicate desired compression
+ * algorithm.
+ *
+ * Currently only applies to writing TIFF image files.
+ *
+ * Valid values: TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED,
+ * TiffConstants.TIFF_COMPRESSION_CCITT_1D,
+ * TiffConstants.TIFF_COMPRESSION_LZW,
+ * TiffConstants.TIFF_COMPRESSION_PACKBITS.
+ *
+ *
+ * @see org.apache.commons.imaging.formats.tiff.constants.TiffConstants
+ */
+ String PARAM_KEY_COMPRESSION = "COMPRESSION";
+
+ String BUFFERED_IMAGE_FACTORY = "BUFFERED_IMAGE_FACTORY";
+
+ /**
+ * Parameter key. Indicates whether to read embedded thumbnails.
+ *
+ * Only applies to read EXIF metadata from JPEG/JFIF files.
+ *
+ * Valid values: Boolean.TRUE and Boolean.FALSE.
+ *
+ *
+ * @see org.apache.commons.imaging.formats.tiff.constants.TiffConstants
+ */
+ String PARAM_KEY_READ_THUMBNAILS = "READ_THUMBNAILS";
+
+ /**
+ * Parameter key. Indicates whether to throw exceptions when parsing invalid
+ * files, or whether to tolerate small problems.
+ *
+ * Valid values: Boolean.TRUE and Boolean.FALSE. Default value:
+ * Boolean.FALSE.
+ *
+ *
+ * @see org.apache.commons.imaging.formats.tiff.constants.TiffConstants
+ */
+ String PARAM_KEY_STRICT = "STRICT";
+
+ /**
+ * Parameter key.
+ *
+ * Only used when writing images.
+ *
+ * Valid values: TiffOutputSet to write into the image's EXIF metadata.
+ *
+ *
+ * @see org.apache.commons.imaging.formats.tiff.write.TiffOutputSet
+ */
+ String PARAM_KEY_EXIF = "EXIF";
+
+ /**
+ * Parameter key.
+ *
+ * Only used when writing images.
+ *
+ * Valid values: String of XMP XML.
+ *
+ */
+ String PARAM_KEY_XMP_XML = "XMP_XML";
+
+ /**
+ * Parameter key. Used in write operations to indicate the desired pixel
+ * density (DPI), and/or aspect ratio.
+ *
+ * Valid values: PixelDensity
+ *
+ *
+ * @see org.apache.commons.imaging.PixelDensity
+ */
+ String PARAM_KEY_PIXEL_DENSITY = "PIXEL_DENSITY";
+}
diff --git a/src/main/java/org/apache/commons/imaging/ImagingException.java b/src/main/java/org/apache/commons/imaging/ImagingException.java
new file mode 100644
index 0000000..1c5f9fb
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/ImagingException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+/**
+ * The base class for implementing custom exceptions in the
+ * Apache Commons Imaging package.
+ */
+public class ImagingException extends Exception {
+ private static final long serialVersionUID = -1L;
+
+ public ImagingException(final String message) {
+ super(message);
+ }
+
+ public ImagingException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/PixelDensity.java b/src/main/java/org/apache/commons/imaging/PixelDensity.java
new file mode 100644
index 0000000..afecd26
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/PixelDensity.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging;
+
+/**
+ * Used to specify pixel density and physical dimensions when reading or
+ * storing image information.
+ */
+public final class PixelDensity {
+ private final double horizontalDensity;
+ private final double verticalDensity;
+ // / One-tenth of a millimetre units.
+ private final int unitLength;
+
+ private PixelDensity(final double horizontalDensity, final double verticalDensity,
+ final int unitLength) {
+ this.horizontalDensity = horizontalDensity;
+ this.verticalDensity = verticalDensity;
+ this.unitLength = unitLength;
+ }
+
+ public static PixelDensity createUnitless(final double x, final double y) {
+ return new PixelDensity(x, y, 0);
+ }
+
+ public static PixelDensity createFromPixelsPerInch(final double x, final double y) {
+ return new PixelDensity(x, y, 254);
+ }
+
+ public static PixelDensity createFromPixelsPerMetre(final double x, final double y) {
+ return new PixelDensity(x, y, 10000);
+ }
+
+ public static PixelDensity createFromPixelsPerCentimetre(final double x, final double y) {
+ return new PixelDensity(x, y, 100);
+ }
+
+ public boolean isUnitless() {
+ return unitLength == 0;
+ }
+
+ public boolean isInInches() {
+ return unitLength == 254;
+ }
+
+ public boolean isInCentimetres() {
+ return unitLength == 100;
+ }
+
+ public boolean isInMetres() {
+ return unitLength == 10000;
+ }
+
+ public double getRawHorizontalDensity() {
+ return horizontalDensity;
+ }
+
+ public double getRawVerticalDensity() {
+ return verticalDensity;
+ }
+
+ public double horizontalDensityInches() {
+ if (isInInches()) {
+ return horizontalDensity;
+ } else {
+ return horizontalDensity * 254 / unitLength;
+ }
+ }
+
+ public double verticalDensityInches() {
+ if (isInInches()) {
+ return verticalDensity;
+ } else {
+ return verticalDensity * 254 / unitLength;
+ }
+ }
+
+ public double horizontalDensityMetres() {
+ if (isInMetres()) {
+ return horizontalDensity;
+ } else {
+ return horizontalDensity * 10000 / unitLength;
+ }
+ }
+
+ public double verticalDensityMetres() {
+ if (isInMetres()) {
+ return verticalDensity;
+ } else {
+ return verticalDensity * 10000 / unitLength;
+ }
+ }
+
+ public double horizontalDensityCentimetres() {
+ if (isInCentimetres()) {
+ return horizontalDensity;
+ } else {
+ return horizontalDensity * 100 / unitLength;
+ }
+ }
+
+ public double verticalDensityCentimetres() {
+ if (isInCentimetres()) {
+ return verticalDensity;
+ } else {
+ return verticalDensity * 100 / unitLength;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorCIELab.java b/src/main/java/org/apache/commons/imaging/color/ColorCieLab.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/color/ColorCIELab.java
rename to src/main/java/org/apache/commons/imaging/color/ColorCieLab.java
index d709ebc..7066735 100644
--- a/src/main/java/org/apache/sanselan/color/ColorCIELab.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorCieLab.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorCIELab
-{
- public final double L, a, b;
-
- public ColorCIELab(double l, double a, double b)
- {
- L = l;
- this.a = a;
- this.b = b;
- }
-
- public final String toString()
- {
- return "{L: " + L + ", a: " + a + ", b: " + b + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorCieLab {
+ public final double L;
+ public final double a;
+ public final double b;
+
+ public ColorCieLab(final double l, final double a, final double b) {
+ L = l;
+ this.a = a;
+ this.b = b;
+ }
+
+ @Override
+ public String toString() {
+ return "{L: " + L + ", a: " + a + ", b: " + b + "}";
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorCIELCH.java b/src/main/java/org/apache/commons/imaging/color/ColorCieLch.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/color/ColorCIELCH.java
rename to src/main/java/org/apache/commons/imaging/color/ColorCieLch.java
index 47bbfcf..3646dc7 100644
--- a/src/main/java/org/apache/sanselan/color/ColorCIELCH.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorCieLch.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorCIELCH
-{
- public final double L, C, H;
-
- public ColorCIELCH(double l, double C, double H)
- {
- L = l;
- this.C = C;
- this.H = H;
- }
-
- public String toString()
- {
- return "{L: " + L + ", C: " + C + ", H: " + H + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorCieLch {
+ public final double L;
+ public final double C;
+ public final double H;
+
+ public ColorCieLch(final double l, final double C, final double H) {
+ L = l;
+ this.C = C;
+ this.H = H;
+ }
+
+ @Override
+ public String toString() {
+ return "{L: " + L + ", C: " + C + ", H: " + H + "}";
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorCIELuv.java b/src/main/java/org/apache/commons/imaging/color/ColorCieLuv.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/color/ColorCIELuv.java
rename to src/main/java/org/apache/commons/imaging/color/ColorCieLuv.java
index 597b93c..dc04f8f 100644
--- a/src/main/java/org/apache/sanselan/color/ColorCIELuv.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorCieLuv.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorCIELuv
-{
- public final double L, u, v;
-
- public ColorCIELuv(double l, double u, double v)
- {
- L = l;
- this.u = u;
- this.v = v;
- }
-
- public String toString()
- {
- return "{L: " + L + ", u: " + u + ", v: " + v + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorCieLuv {
+ public final double L;
+ public final double u;
+ public final double v;
+
+ public ColorCieLuv(final double l, final double u, final double v) {
+ L = l;
+ this.u = u;
+ this.v = v;
+ }
+
+ @Override
+ public String toString() {
+ return "{L: " + L + ", u: " + u + ", v: " + v + "}";
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorCMY.java b/src/main/java/org/apache/commons/imaging/color/ColorCmy.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/color/ColorCMY.java
rename to src/main/java/org/apache/commons/imaging/color/ColorCmy.java
index ca79290..f0a2ebf 100644
--- a/src/main/java/org/apache/sanselan/color/ColorCMY.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorCmy.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorCMY
-{
- public final double C, M, Y;
-
- public ColorCMY(double C, double M, double Y)
- {
- this.C = C;
- this.M = M;
- this.Y = Y;
- }
-
- public final String toString()
- {
- return "{C: " + C + ", M: " + M + ", Y: " + Y + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorCmy {
+ public final double C;
+ public final double M;
+ public final double Y;
+
+ public ColorCmy(final double C, final double M, final double Y) {
+ this.C = C;
+ this.M = M;
+ this.Y = Y;
+ }
+
+ @Override
+ public String toString() {
+ return "{C: " + C + ", M: " + M + ", Y: " + Y + "}";
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorCMYK.java b/src/main/java/org/apache/commons/imaging/color/ColorCmyk.java
similarity index 75%
rename from src/main/java/org/apache/sanselan/color/ColorCMYK.java
rename to src/main/java/org/apache/commons/imaging/color/ColorCmyk.java
index 7b7e208..ebb239d 100644
--- a/src/main/java/org/apache/sanselan/color/ColorCMYK.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorCmyk.java
@@ -1,35 +1,36 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorCMYK
-{
- public final double C, M, Y, K;
-
- public ColorCMYK(double C, double M, double Y, double K)
- {
- this.C = C;
- this.M = M;
- this.Y = Y;
- this.K = K;
- }
-
- public final String toString()
- {
- return "{C: " + C + ", M: " + M + ", Y: " + Y + ", K: " + K + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorCmyk {
+ public final double C;
+ public final double M;
+ public final double Y;
+ public final double K;
+
+ public ColorCmyk(final double C, final double M, final double Y, final double K) {
+ this.C = C;
+ this.M = M;
+ this.Y = Y;
+ this.K = K;
+ }
+
+ @Override
+ public String toString() {
+ return "{C: " + C + ", M: " + M + ", Y: " + Y + ", K: " + K + "}";
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/color/ColorConversions.java b/src/main/java/org/apache/commons/imaging/color/ColorConversions.java
new file mode 100644
index 0000000..608313a
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/color/ColorConversions.java
@@ -0,0 +1,752 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+
+public final class ColorConversions {
+ private static final double REF_X = 95.047; // Observer= 2°, Illuminant= D65
+ private static final double REF_Y = 100.000;
+ private static final double REF_Z = 108.883;
+
+ private ColorConversions() {
+ }
+
+ public static ColorCieLab convertXYZtoCIELab(final ColorXyz xyz) {
+ return convertXYZtoCIELab(xyz.X, xyz.Y, xyz.Z);
+ }
+
+ public static ColorCieLab convertXYZtoCIELab(final double X, final double Y,
+ final double Z) {
+
+ double var_X = X / REF_X; // REF_X = 95.047 Observer= 2°, Illuminant=
+ // D65
+ double var_Y = Y / REF_Y; // REF_Y = 100.000
+ double var_Z = Z / REF_Z; // REF_Z = 108.883
+
+ if (var_X > 0.008856) {
+ var_X = Math.pow(var_X, (1 / 3.0));
+ } else {
+ var_X = (7.787 * var_X) + (16 / 116.0);
+ }
+ if (var_Y > 0.008856) {
+ var_Y = Math.pow(var_Y, 1 / 3.0);
+ } else {
+ var_Y = (7.787 * var_Y) + (16 / 116.0);
+ }
+ if (var_Z > 0.008856) {
+ var_Z = Math.pow(var_Z, 1 / 3.0);
+ } else {
+ var_Z = (7.787 * var_Z) + (16 / 116.0);
+ }
+
+ final double L = (116 * var_Y) - 16;
+ final double a = 500 * (var_X - var_Y);
+ final double b = 200 * (var_Y - var_Z);
+ return new ColorCieLab(L, a, b);
+ }
+
+ public static ColorXyz convertCIELabtoXYZ(final ColorCieLab cielab) {
+ return convertCIELabtoXYZ(cielab.L, cielab.a, cielab.b);
+ }
+
+ public static ColorXyz convertCIELabtoXYZ(final double L, final double a, final double b) {
+ double var_Y = (L + 16) / 116.0;
+ double var_X = a / 500 + var_Y;
+ double var_Z = var_Y - b / 200.0;
+
+ if (Math.pow(var_Y, 3) > 0.008856) {
+ var_Y = Math.pow(var_Y, 3);
+ } else {
+ var_Y = (var_Y - 16 / 116.0) / 7.787;
+ }
+ if (Math.pow(var_X, 3) > 0.008856) {
+ var_X = Math.pow(var_X, 3);
+ } else {
+ var_X = (var_X - 16 / 116.0) / 7.787;
+ }
+ if (Math.pow(var_Z, 3) > 0.008856) {
+ var_Z = Math.pow(var_Z, 3);
+ } else {
+ var_Z = (var_Z - 16 / 116.0) / 7.787;
+ }
+
+ final double X = REF_X * var_X; // REF_X = 95.047 Observer= 2°, Illuminant=
+ // D65
+ final double Y = REF_Y * var_Y; // REF_Y = 100.000
+ final double Z = REF_Z * var_Z; // REF_Z = 108.883
+
+ return new ColorXyz(X, Y, Z);
+ }
+
+ public static ColorHunterLab convertXYZtoHunterLab(final ColorXyz xyz) {
+ return convertXYZtoHunterLab(xyz.X, xyz.Y, xyz.Z);
+ }
+
+ public static ColorHunterLab convertXYZtoHunterLab(final double X,
+ final double Y, final double Z) {
+ final double L = 10 * Math.sqrt(Y);
+ final double a = 17.5 * (((1.02 * X) - Y) / Math.sqrt(Y));
+ final double b = 7 * ((Y - (0.847 * Z)) / Math.sqrt(Y));
+
+ return new ColorHunterLab(L, a, b);
+ }
+
+ public static ColorXyz convertHunterLabtoXYZ(final ColorHunterLab cielab) {
+ return convertHunterLabtoXYZ(cielab.L, cielab.a, cielab.b);
+ }
+
+ public static ColorXyz convertHunterLabtoXYZ(final double L, final double a,
+ final double b) {
+ final double var_Y = L / 10;
+ final double var_X = a / 17.5 * L / 10;
+ final double var_Z = b / 7 * L / 10;
+
+ final double Y = Math.pow(var_Y, 2);
+ final double X = (var_X + Y) / 1.02;
+ final double Z = -(var_Z - Y) / 0.847;
+
+ return new ColorXyz(X, Y, Z);
+ }
+
+ public static int convertXYZtoRGB(final ColorXyz xyz) {
+ return convertXYZtoRGB(xyz.X, xyz.Y, xyz.Z);
+ }
+
+ public static int convertXYZtoRGB(final double X, final double Y, final double Z) {
+ // Observer = 2°, Illuminant = D65
+ final double var_X = X / 100.0; // Where X = 0 ÷ 95.047
+ final double var_Y = Y / 100.0; // Where Y = 0 ÷ 100.000
+ final double var_Z = Z / 100.0; // Where Z = 0 ÷ 108.883
+
+ double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986;
+ double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415;
+ double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570;
+
+ if (var_R > 0.0031308) {
+ var_R = 1.055 * Math.pow(var_R, (1 / 2.4)) - 0.055;
+ } else {
+ var_R = 12.92 * var_R;
+ }
+ if (var_G > 0.0031308) {
+ var_G = 1.055 * Math.pow(var_G, (1 / 2.4)) - 0.055;
+ } else {
+ var_G = 12.92 * var_G;
+ }
+ if (var_B > 0.0031308) {
+ var_B = 1.055 * Math.pow(var_B, (1 / 2.4)) - 0.055;
+ } else {
+ var_B = 12.92 * var_B;
+ }
+
+ final double R = (var_R * 255);
+ final double G = (var_G * 255);
+ final double B = (var_B * 255);
+
+ return convertRGBtoRGB(R, G, B);
+ }
+
+ public static ColorXyz convertRGBtoXYZ(final int rgb) {
+ final int r = 0xff & (rgb >> 16);
+ final int g = 0xff & (rgb >> 8);
+ final int b = 0xff & (rgb >> 0);
+
+ double var_R = r / 255.0; // Where R = 0 ÷ 255
+ double var_G = g / 255.0; // Where G = 0 ÷ 255
+ double var_B = b / 255.0; // Where B = 0 ÷ 255
+
+ if (var_R > 0.04045) {
+ var_R = Math.pow((var_R + 0.055) / 1.055, 2.4);
+ } else {
+ var_R = var_R / 12.92;
+ }
+ if (var_G > 0.04045) {
+ var_G = Math.pow((var_G + 0.055) / 1.055, 2.4);
+ } else {
+ var_G = var_G / 12.92;
+ }
+ if (var_B > 0.04045) {
+ var_B = Math.pow((var_B + 0.055) / 1.055, 2.4);
+ } else {
+ var_B = var_B / 12.92;
+ }
+
+ var_R = var_R * 100;
+ var_G = var_G * 100;
+ var_B = var_B * 100;
+
+ // Observer. = 2°, Illuminant = D65
+ final double X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805;
+ final double Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722;
+ final double Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505;
+
+ return new ColorXyz(X, Y, Z);
+ }
+
+ public static ColorCmy convertRGBtoCMY(final int rgb) {
+ final int R = 0xff & (rgb >> 16);
+ final int G = 0xff & (rgb >> 8);
+ final int B = 0xff & (rgb >> 0);
+
+ // RGB values = 0 ÷ 255
+ // CMY values = 0 ÷ 1
+
+ final double C = 1 - (R / 255.0);
+ final double M = 1 - (G / 255.0);
+ final double Y = 1 - (B / 255.0);
+
+ return new ColorCmy(C, M, Y);
+ }
+
+ public static int convertCMYtoRGB(final ColorCmy cmy) {
+ // From Ghostscript's gdevcdj.c:
+ // * Ghostscript: R = (1.0 - C) * (1.0 - K)
+ // * Adobe: R = 1.0 - min(1.0, C + K)
+ // and similarly for G and B.
+ // This is Ghostscript's formula with K = 0.
+
+ // CMY values = 0 ÷ 1
+ // RGB values = 0 ÷ 255
+
+ final double R = (1 - cmy.C) * 255.0;
+ final double G = (1 - cmy.M) * 255.0;
+ final double B = (1 - cmy.Y) * 255.0;
+
+ return convertRGBtoRGB(R, G, B);
+ }
+
+ public static ColorCmyk convertCMYtoCMYK(final ColorCmy cmy) {
+ // Where CMYK and CMY values = 0 ÷ 1
+
+ double C = cmy.C;
+ double M = cmy.M;
+ double Y = cmy.Y;
+
+ double var_K = 1.0;
+
+ if (C < var_K) {
+ var_K = C;
+ }
+ if (M < var_K) {
+ var_K = M;
+ }
+ if (Y < var_K) {
+ var_K = Y;
+ }
+ if (var_K == 1) { // Black
+ C = 0;
+ M = 0;
+ Y = 0;
+ } else {
+ C = (C - var_K) / (1 - var_K);
+ M = (M - var_K) / (1 - var_K);
+ Y = (Y - var_K) / (1 - var_K);
+ }
+ return new ColorCmyk(C, M, Y, var_K);
+ }
+
+ public static ColorCmy convertCMYKtoCMY(final ColorCmyk cmyk) {
+ return convertCMYKtoCMY(cmyk.C, cmyk.M, cmyk.Y, cmyk.K);
+ }
+
+ public static ColorCmy convertCMYKtoCMY(double C, double M, double Y,
+ final double K) {
+ // Where CMYK and CMY values = 0 ÷ 1
+
+ C = (C * (1 - K) + K);
+ M = (M * (1 - K) + K);
+ Y = (Y * (1 - K) + K);
+
+ return new ColorCmy(C, M, Y);
+ }
+
+ public static int convertCMYKtoRGB(final int c, final int m, final int y, final int k)
+ // throws ImageReadException, IOException
+ {
+ final double C = c / 255.0;
+ final double M = m / 255.0;
+ final double Y = y / 255.0;
+ final double K = k / 255.0;
+
+ return convertCMYtoRGB(convertCMYKtoCMY(C, M, Y, K));
+ }
+
+ public static ColorHsl convertRGBtoHSL(final int rgb) {
+
+ final int R = 0xff & (rgb >> 16);
+ final int G = 0xff & (rgb >> 8);
+ final int B = 0xff & (rgb >> 0);
+
+ final double var_R = (R / 255.0); // Where RGB values = 0 ÷ 255
+ final double var_G = (G / 255.0);
+ final double var_B = (B / 255.0);
+
+ final double var_Min = Math.min(var_R, Math.min(var_G, var_B)); // Min. value
+ // of RGB
+ double var_Max;
+ boolean maxIsR = false;
+ boolean maxIsG = false;
+ if (var_R >= var_G && var_R >= var_B) {
+ var_Max = var_R;
+ maxIsR = true;
+ } else if (var_G > var_B) {
+ var_Max = var_G;
+ maxIsG = true;
+ } else {
+ var_Max = var_B;
+ }
+ final double del_Max = var_Max - var_Min; // Delta RGB value
+
+ final double L = (var_Max + var_Min) / 2.0;
+
+ double H, S;
+ // Debug.debug("del_Max", del_Max);
+ if (del_Max == 0) {
+ // This is a gray, no chroma...
+
+ H = 0; // HSL results = 0 ÷ 1
+ S = 0;
+ } else {
+ // Chromatic data...
+
+ // Debug.debug("L", L);
+
+ if (L < 0.5) {
+ S = del_Max / (var_Max + var_Min);
+ } else {
+ S = del_Max / (2 - var_Max - var_Min);
+ }
+
+ // Debug.debug("S", S);
+
+ final double del_R = (((var_Max - var_R) / 6) + (del_Max / 2)) / del_Max;
+ final double del_G = (((var_Max - var_G) / 6) + (del_Max / 2)) / del_Max;
+ final double del_B = (((var_Max - var_B) / 6) + (del_Max / 2)) / del_Max;
+
+ if (maxIsR) {
+ H = del_B - del_G;
+ } else if (maxIsG) {
+ H = (1 / 3.0) + del_R - del_B;
+ } else {
+ H = (2 / 3.0) + del_G - del_R;
+ }
+
+ // Debug.debug("H1", H);
+
+ if (H < 0) {
+ H += 1;
+ }
+ if (H > 1) {
+ H -= 1;
+ }
+
+ // Debug.debug("H2", H);
+ }
+
+ return new ColorHsl(H, S, L);
+ }
+
+ public static int convertHSLtoRGB(final ColorHsl hsl) {
+ return convertHSLtoRGB(hsl.H, hsl.S, hsl.L);
+ }
+
+ public static int convertHSLtoRGB(final double H, final double S, final double L) {
+ double R, G, B;
+
+ if (S == 0) {
+ // HSL values = 0 ÷ 1
+ R = L * 255; // RGB results = 0 ÷ 255
+ G = L * 255;
+ B = L * 255;
+ } else {
+ double var_2;
+
+ if (L < 0.5) {
+ var_2 = L * (1 + S);
+ } else {
+ var_2 = (L + S) - (S * L);
+ }
+
+ final double var_1 = 2 * L - var_2;
+
+ R = 255 * convertHuetoRGB(var_1, var_2, H + (1 / 3.0));
+ G = 255 * convertHuetoRGB(var_1, var_2, H);
+ B = 255 * convertHuetoRGB(var_1, var_2, H - (1 / 3.0));
+ }
+
+ return convertRGBtoRGB(R, G, B);
+ }
+
+ private static double convertHuetoRGB(final double v1, final double v2, double vH) {
+ if (vH < 0) {
+ vH += 1;
+ }
+ if (vH > 1) {
+ vH -= 1;
+ }
+ if ((6 * vH) < 1) {
+ return (v1 + (v2 - v1) * 6 * vH);
+ }
+ if ((2 * vH) < 1) {
+ return (v2);
+ }
+ if ((3 * vH) < 2) {
+ return (v1 + (v2 - v1) * ((2 / 3.0) - vH) * 6);
+ }
+ return (v1);
+ }
+
+ public static ColorHsv convertRGBtoHSV(final int rgb) {
+ final int R = 0xff & (rgb >> 16);
+ final int G = 0xff & (rgb >> 8);
+ final int B = 0xff & (rgb >> 0);
+
+ final double var_R = (R / 255.0); // RGB values = 0 ÷ 255
+ final double var_G = (G / 255.0);
+ final double var_B = (B / 255.0);
+
+ final double var_Min = Math.min(var_R, Math.min(var_G, var_B)); // Min. value
+ // of RGB
+ boolean maxIsR = false;
+ boolean maxIsG = false;
+ double var_Max;
+ if (var_R >= var_G && var_R >= var_B) {
+ var_Max = var_R;
+ maxIsR = true;
+ } else if (var_G > var_B) {
+ var_Max = var_G;
+ maxIsG = true;
+ } else {
+ var_Max = var_B;
+ }
+ final double del_Max = var_Max - var_Min; // Delta RGB value
+
+ final double V = var_Max;
+
+ double H, S;
+ if (del_Max == 0) {
+ // This is a gray, no chroma...
+ H = 0; // HSV results = 0 ÷ 1
+ S = 0;
+ } else {
+ // Chromatic data...
+ S = del_Max / var_Max;
+
+ final double del_R = (((var_Max - var_R) / 6) + (del_Max / 2)) / del_Max;
+ final double del_G = (((var_Max - var_G) / 6) + (del_Max / 2)) / del_Max;
+ final double del_B = (((var_Max - var_B) / 6) + (del_Max / 2)) / del_Max;
+
+ if (maxIsR) {
+ H = del_B - del_G;
+ } else if (maxIsG) {
+ H = (1 / 3.0) + del_R - del_B;
+ } else {
+ H = (2 / 3.0) + del_G - del_R;
+ }
+
+ if (H < 0) {
+ H += 1;
+ }
+ if (H > 1) {
+ H -= 1;
+ }
+ }
+
+ return new ColorHsv(H, S, V);
+ }
+
+ public static int convertHSVtoRGB(final ColorHsv HSV) {
+ return convertHSVtoRGB(HSV.H, HSV.S, HSV.V);
+ }
+
+ public static int convertHSVtoRGB(final double H, final double S, final double V) {
+ double R, G, B;
+
+ if (S == 0) {
+ // HSV values = 0 ÷ 1
+ R = V * 255;
+ G = V * 255;
+ B = V * 255;
+ } else {
+ double var_h = H * 6;
+ if (var_h == 6) {
+ var_h = 0; // H must be < 1
+ }
+ final double var_i = Math.floor(var_h); // Or ... var_i = floor( var_h )
+ final double var_1 = V * (1 - S);
+ final double var_2 = V * (1 - S * (var_h - var_i));
+ final double var_3 = V * (1 - S * (1 - (var_h - var_i)));
+
+ double var_r, var_g, var_b;
+
+ if (var_i == 0) {
+ var_r = V;
+ var_g = var_3;
+ var_b = var_1;
+ } else if (var_i == 1) {
+ var_r = var_2;
+ var_g = V;
+ var_b = var_1;
+ } else if (var_i == 2) {
+ var_r = var_1;
+ var_g = V;
+ var_b = var_3;
+ } else if (var_i == 3) {
+ var_r = var_1;
+ var_g = var_2;
+ var_b = V;
+ } else if (var_i == 4) {
+ var_r = var_3;
+ var_g = var_1;
+ var_b = V;
+ } else {
+ var_r = V;
+ var_g = var_1;
+ var_b = var_2;
+ }
+
+ R = var_r * 255; // RGB results = 0 ÷ 255
+ G = var_g * 255;
+ B = var_b * 255;
+ }
+
+ return convertRGBtoRGB(R, G, B);
+ }
+
+ public static int convertCMYKtoRGB_Adobe(final int sc, final int sm, final int sy,
+ final int sk) {
+ final int red = 255 - (sc + sk);
+ final int green = 255 - (sm + sk);
+ final int blue = 255 - (sy + sk);
+
+ return convertRGBtoRGB(red, green, blue);
+ }
+
+ private static double cube(final double f) {
+ return f * f * f;
+ }
+
+ private static double square(final double f) {
+ return f * f;
+ }
+
+ public static int convertCIELabtoARGBTest(final int cieL, final int cieA, final int cieB) {
+ double X, Y, Z;
+
+ {
+
+ double var_Y = ((cieL * 100.0 / 255.0) + 16.0) / 116.0;
+ double var_X = cieA / 500.0 + var_Y;
+ double var_Z = var_Y - cieB / 200.0;
+
+ final double var_x_cube = cube(var_X);
+ final double var_y_cube = cube(var_Y);
+ final double var_z_cube = cube(var_Z);
+
+ if (var_y_cube > 0.008856) {
+ var_Y = var_y_cube;
+ } else {
+ var_Y = (var_Y - 16 / 116.0) / 7.787;
+ }
+
+ if (var_x_cube > 0.008856) {
+ var_X = var_x_cube;
+ } else {
+ var_X = (var_X - 16 / 116.0) / 7.787;
+ }
+
+ if (var_z_cube > 0.008856) {
+ var_Z = var_z_cube;
+ } else {
+ var_Z = (var_Z - 16 / 116.0) / 7.787;
+ }
+
+ // double REF_X = 95.047;
+ // double REF_Y = 100.000;
+ // double REF_Z = 108.883;
+
+ X = REF_X * var_X; // REF_X = 95.047 Observer= 2°, Illuminant= D65
+ Y = REF_Y * var_Y; // REF_Y = 100.000
+ Z = REF_Z * var_Z; // REF_Z = 108.883
+
+ }
+
+ double R, G, B;
+ {
+ final double var_X = X / 100; // X = From 0 to REF_X
+ final double var_Y = Y / 100; // Y = From 0 to REF_Y
+ final double var_Z = Z / 100; // Z = From 0 to REF_Y
+
+ double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986;
+ double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415;
+ double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570;
+
+ if (var_R > 0.0031308) {
+ var_R = 1.055 * Math.pow(var_R, (1 / 2.4)) - 0.055;
+ } else {
+ var_R = 12.92 * var_R;
+ }
+ if (var_G > 0.0031308) {
+ var_G = 1.055 * Math.pow(var_G, (1 / 2.4)) - 0.055;
+ } else {
+ var_G = 12.92 * var_G;
+ }
+
+ if (var_B > 0.0031308) {
+ var_B = 1.055 * Math.pow(var_B, (1 / 2.4)) - 0.055;
+ } else {
+ var_B = 12.92 * var_B;
+ }
+
+ R = (var_R * 255);
+ G = (var_G * 255);
+ B = (var_B * 255);
+ }
+
+ return convertRGBtoRGB(R, G, B);
+ }
+
+ private static int convertRGBtoRGB(final double R, final double G, final double B) {
+ int red = (int) Math.round(R);
+ int green = (int) Math.round(G);
+ int blue = (int) Math.round(B);
+
+ red = Math.min(255, Math.max(0, red));
+ green = Math.min(255, Math.max(0, green));
+ blue = Math.min(255, Math.max(0, blue));
+
+ final int alpha = 0xff;
+ final int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+
+ return rgb;
+ }
+
+ private static int convertRGBtoRGB(int red, int green, int blue) {
+ red = Math.min(255, Math.max(0, red));
+ green = Math.min(255, Math.max(0, green));
+ blue = Math.min(255, Math.max(0, blue));
+
+ final int alpha = 0xff;
+ final int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+
+ return rgb;
+ }
+
+ public static ColorCieLch convertCIELabtoCIELCH(final ColorCieLab cielab) {
+ return convertCIELabtoCIELCH(cielab.L, cielab.a, cielab.b);
+ }
+
+ public static ColorCieLch convertCIELabtoCIELCH(final double L, final double a, final double b) {
+ double var_H = Math.atan2(b, a); // Quadrant by signs
+
+ if (var_H > 0) {
+ var_H = (var_H / Math.PI) * 180.0;
+ } else {
+ var_H = 360 - radian_2_degree(Math.abs(var_H));
+ }
+
+ // L = L;
+ final double C = Math.sqrt(square(a) + square(b));
+ final double H = var_H;
+
+ return new ColorCieLch(L, C, H);
+ }
+
+ public static ColorCieLab convertCIELCHtoCIELab(final ColorCieLch cielch) {
+ return convertCIELCHtoCIELab(cielch.L, cielch.C, cielch.H);
+ }
+
+ public static ColorCieLab convertCIELCHtoCIELab(final double L, final double C, final double H) {
+ // Where CIE-H° = 0 ÷ 360°
+
+ // CIE-L* = CIE-L;
+ final double a = Math.cos(degree_2_radian(H)) * C;
+ final double b = Math.sin(degree_2_radian(H)) * C;
+
+ return new ColorCieLab(L, a, b);
+ }
+
+ public static double degree_2_radian(final double degree) {
+ return degree * Math.PI / 180.0;
+ }
+
+ public static double radian_2_degree(final double radian) {
+ return radian * 180.0 / Math.PI;
+ }
+
+ public static ColorCieLuv convertXYZtoCIELuv(final ColorXyz xyz) {
+ return convertXYZtoCIELuv(xyz.X, xyz.Y, xyz.Z);
+ }
+
+ public static ColorCieLuv convertXYZtoCIELuv(final double X, final double Y, final double Z) {
+ // problems here with div by zero
+
+ final double var_U = (4 * X) / (X + (15 * Y) + (3 * Z));
+ final double var_V = (9 * Y) / (X + (15 * Y) + (3 * Z));
+
+ // Debug.debug("var_U", var_U);
+ // Debug.debug("var_V", var_V);
+
+ double var_Y = Y / 100.0;
+ // Debug.debug("var_Y", var_Y);
+
+ if (var_Y > 0.008856) {
+ var_Y = Math.pow(var_Y, (1 / 3.0));
+ } else {
+ var_Y = (7.787 * var_Y) + (16 / 116.0);
+ }
+
+ // Debug.debug("var_Y", var_Y);
+
+ final double ref_U = (4 * REF_X) / (REF_X + (15 * REF_Y) + (3 * REF_Z));
+ final double ref_V = (9 * REF_Y) / (REF_X + (15 * REF_Y) + (3 * REF_Z));
+
+ // Debug.debug("ref_U", ref_U);
+ // Debug.debug("ref_V", ref_V);
+
+ final double L = (116 * var_Y) - 16;
+ final double u = 13 * L * (var_U - ref_U);
+ final double v = 13 * L * (var_V - ref_V);
+
+ return new ColorCieLuv(L, u, v);
+ }
+
+ public static ColorXyz convertCIELuvtoXYZ(final ColorCieLuv cielch) {
+ return convertCIELuvtoXYZ(cielch.L, cielch.u, cielch.v);
+ }
+
+ public static ColorXyz convertCIELuvtoXYZ(final double L, final double u, final double v) {
+ // problems here with div by zero
+
+ double var_Y = (L + 16) / 116;
+ if (Math.pow(var_Y, 3) > 0.008856) {
+ var_Y = Math.pow(var_Y, 3);
+ } else {
+ var_Y = (var_Y - 16 / 116) / 7.787;
+ }
+
+ final double ref_U = (4 * REF_X) / (REF_X + (15 * REF_Y) + (3 * REF_Z));
+ final double ref_V = (9 * REF_Y) / (REF_X + (15 * REF_Y) + (3 * REF_Z));
+ final double var_U = u / (13 * L) + ref_U;
+ final double var_V = v / (13 * L) + ref_V;
+
+ final double Y = var_Y * 100;
+ final double X = -(9 * Y * var_U) / ((var_U - 4) * var_V - var_U * var_V);
+ final double Z = (9 * Y - (15 * var_V * Y) - (var_V * X)) / (3 * var_V);
+
+ return new ColorXyz(X, Y, Z);
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorHSL.java b/src/main/java/org/apache/commons/imaging/color/ColorHsl.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/color/ColorHSL.java
rename to src/main/java/org/apache/commons/imaging/color/ColorHsl.java
index 29bd17c..1c591f2 100644
--- a/src/main/java/org/apache/sanselan/color/ColorHSL.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorHsl.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorHSL
-{
- public final double H, S, L;
-
- public ColorHSL(double h, double s, double v)
- {
- H = h;
- S = s;
- L = v;
- }
-
- public final String toString()
- {
- return "{H: " + H + ", S: " + S + ", L: " + L + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorHsl {
+ public final double H;
+ public final double S;
+ public final double L;
+
+ public ColorHsl(final double h, final double s, final double v) {
+ H = h;
+ S = s;
+ L = v;
+ }
+
+ @Override
+ public String toString() {
+ return "{H: " + H + ", S: " + S + ", L: " + L + "}";
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorHSV.java b/src/main/java/org/apache/commons/imaging/color/ColorHsv.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/color/ColorHSV.java
rename to src/main/java/org/apache/commons/imaging/color/ColorHsv.java
index 4eba4c1..7d2be7c 100644
--- a/src/main/java/org/apache/sanselan/color/ColorHSV.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorHsv.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorHSV
-{
- public final double H, S, V;
-
- public ColorHSV(double h, double s, double v)
- {
- H = h;
- S = s;
- V = v;
- }
-
- public final String toString()
- {
- return "{H: " + H + ", S: " + S + ", V: " + V + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorHsv {
+ public final double H;
+ public final double S;
+ public final double V;
+
+ public ColorHsv(final double h, final double s, final double v) {
+ H = h;
+ S = s;
+ V = v;
+ }
+
+ @Override
+ public String toString() {
+ return "{H: " + H + ", S: " + S + ", V: " + V + "}";
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorHunterLab.java b/src/main/java/org/apache/commons/imaging/color/ColorHunterLab.java
similarity index 76%
rename from src/main/java/org/apache/sanselan/color/ColorHunterLab.java
rename to src/main/java/org/apache/commons/imaging/color/ColorHunterLab.java
index e01fabe..ec1eda2 100644
--- a/src/main/java/org/apache/sanselan/color/ColorHunterLab.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorHunterLab.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorHunterLab
-{
- public final double L, a, b;
-
- public ColorHunterLab(double l, double a, double b)
- {
- L = l;
- this.a = a;
- this.b = b;
- }
-
- public final String toString()
- {
- return "{L: " + L + ", a: " + a + ", b: " + b + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorHunterLab {
+ public final double L;
+ public final double a;
+ public final double b;
+
+ public ColorHunterLab(final double l, final double a, final double b) {
+ L = l;
+ this.a = a;
+ this.b = b;
+ }
+
+ @Override
+ public String toString() {
+ return "{L: " + L + ", a: " + a + ", b: " + b + "}";
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/color/ColorXYZ.java b/src/main/java/org/apache/commons/imaging/color/ColorXyz.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/color/ColorXYZ.java
rename to src/main/java/org/apache/commons/imaging/color/ColorXyz.java
index 4a37d25..ce99bbf 100644
--- a/src/main/java/org/apache/sanselan/color/ColorXYZ.java
+++ b/src/main/java/org/apache/commons/imaging/color/ColorXyz.java
@@ -1,34 +1,34 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.color;
-
-public final class ColorXYZ
-{
- public final double X, Y, Z;
-
- public ColorXYZ(double x, double y, double z)
- {
- X = x;
- Y = y;
- Z = z;
- }
-
- public final String toString()
- {
- return "{X: " + X + ", Y: " + Y + ", Z: " + Z + "}";
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.color;
+
+public final class ColorXyz {
+ public final double X;
+ public final double Y;
+ public final double Z;
+
+ public ColorXyz(final double x, final double y, final double z) {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ @Override
+ public String toString() {
+ return "{X: " + X + ", Y: " + Y + ", Z: " + Z + "}";
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/color/package-info.java b/src/main/java/org/apache/commons/imaging/color/package-info.java
new file mode 100644
index 0000000..0533c51
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/color/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Color spaces and conversions between them.
+ */
+package org.apache.commons.imaging.color;
diff --git a/src/main/java/org/apache/commons/imaging/common/BasicCParser.java b/src/main/java/org/apache/commons/imaging/common/BasicCParser.java
new file mode 100644
index 0000000..2247f31
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/BasicCParser.java
@@ -0,0 +1,379 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * under the License.
+ */
+
+package org.apache.commons.imaging.common;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.util.Map;
+
+import org.apache.commons.imaging.ImageReadException;
+
+/**
+ * A rudimentary preprocessor and parser for the C programming
+ * language.
+ */
+public class BasicCParser {
+ private final PushbackInputStream is;
+
+ public BasicCParser(final ByteArrayInputStream is) {
+ this.is = new PushbackInputStream(is);
+ }
+
+ public String nextToken() throws IOException, ImageReadException {
+ // I don't know how complete the C parsing in an XPM file
+ // is meant to be, this is just the very basics...
+
+ boolean inString = false;
+ boolean inIdentifier = false;
+ boolean hadBackSlash = false;
+ final StringBuilder token = new StringBuilder();
+ for (int c = is.read(); c != -1; c = is.read()) {
+ if (inString) {
+ if (c == '\\') {
+ token.append('\\');
+ hadBackSlash = !hadBackSlash;
+ } else if (c == '"') {
+ token.append('"');
+ if (!hadBackSlash) {
+ return token.toString();
+ }
+ hadBackSlash = false;
+ } else if (c == '\r' || c == '\n') {
+ throw new ImageReadException(
+ "Unterminated string in XPM file");
+ } else {
+ token.append((char) c);
+ hadBackSlash = false;
+ }
+ } else if (inIdentifier) {
+ if (Character.isLetterOrDigit(c) || c == '_') {
+ token.append((char) c);
+ } else {
+ is.unread(c);
+ return token.toString();
+ }
+ } else {
+ if (c == '"') {
+ token.append('"');
+ inString = true;
+ } else if (Character.isLetterOrDigit(c) || c == '_') {
+ token.append((char) c);
+ inIdentifier = true;
+ } else if (c == '{' || c == '}' || c == '[' || c == ']'
+ || c == '*' || c == ';' || c == '=' || c == ',') {
+ token.append((char) c);
+ return token.toString();
+ } else if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
+ // ignore
+ } else {
+ throw new ImageReadException(
+ "Unhandled/invalid character '" + ((char) c)
+ + "' found in XPM file");
+ }
+ }
+ }
+
+ if (inIdentifier) {
+ return token.toString();
+ }
+ if (inString) {
+ throw new ImageReadException("Unterminated string ends XMP file");
+ }
+ return null;
+ }
+
+ public static ByteArrayOutputStream preprocess(final InputStream is,
+ final StringBuilder firstComment, final Map defines)
+ throws IOException, ImageReadException {
+ boolean inSingleQuotes = false;
+ boolean inString = false;
+ boolean inComment = false;
+ boolean inDirective = false;
+ boolean hadSlash = false;
+ boolean hadStar = false;
+ boolean hadBackSlash = false;
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ boolean seenFirstComment = (firstComment == null);
+ final StringBuilder directiveBuffer = new StringBuilder();
+ for (int c = is.read(); c != -1; c = is.read()) {
+ if (inComment) {
+ if (c == '*') {
+ if (hadStar && !seenFirstComment) {
+ firstComment.append('*');
+ }
+ hadStar = true;
+ } else if (c == '/') {
+ if (hadStar) {
+ hadStar = false;
+ inComment = false;
+ seenFirstComment = true;
+ } else {
+ if (!seenFirstComment) {
+ firstComment.append((char) c);
+ }
+ }
+ } else {
+ if (hadStar && !seenFirstComment) {
+ firstComment.append('*');
+ }
+ hadStar = false;
+ if (!seenFirstComment) {
+ firstComment.append((char) c);
+ }
+ }
+ } else if (inSingleQuotes) {
+ if (c == '\\') {
+ if (hadBackSlash) {
+ out.write('\\');
+ out.write('\\');
+ hadBackSlash = false;
+ } else {
+ hadBackSlash = true;
+ }
+ } else if (c == '\'') {
+ if (hadBackSlash) {
+ out.write('\\');
+ hadBackSlash = false;
+ } else {
+ inSingleQuotes = false;
+ }
+ out.write('\'');
+ } else if (c == '\r' || c == '\n') {
+ throw new ImageReadException("Unterminated single quote in file");
+ } else {
+ if (hadBackSlash) {
+ out.write('\\');
+ hadBackSlash = false;
+ }
+ out.write(c);
+ }
+ } else if (inString) {
+ if (c == '\\') {
+ if (hadBackSlash) {
+ out.write('\\');
+ out.write('\\');
+ hadBackSlash = false;
+ } else {
+ hadBackSlash = true;
+ }
+ } else if (c == '"') {
+ if (hadBackSlash) {
+ out.write('\\');
+ hadBackSlash = false;
+ } else {
+ inString = false;
+ }
+ out.write('"');
+ } else if (c == '\r' || c == '\n') {
+ throw new ImageReadException("Unterminated string in file");
+ } else {
+ if (hadBackSlash) {
+ out.write('\\');
+ hadBackSlash = false;
+ }
+ out.write(c);
+ }
+ } else if (inDirective) {
+ if (c == '\r' || c == '\n') {
+ inDirective = false;
+ final String[] tokens = tokenizeRow(directiveBuffer.toString());
+ if (tokens.length < 2 || tokens.length > 3) {
+ throw new ImageReadException("Bad preprocessor directive");
+ }
+ if (!tokens[0].equals("define")) {
+ throw new ImageReadException("Invalid/unsupported "
+ + "preprocessor directive '" + tokens[0] + "'");
+ }
+ defines.put(tokens[1], (tokens.length == 3) ? tokens[2]
+ : null);
+ directiveBuffer.setLength(0);
+ } else {
+ directiveBuffer.append((char) c);
+ }
+ } else {
+ if (c == '/') {
+ if (hadSlash) {
+ out.write('/');
+ }
+ hadSlash = true;
+ } else if (c == '*') {
+ if (hadSlash) {
+ inComment = true;
+ hadSlash = false;
+ } else {
+ out.write(c);
+ }
+ } else if (c == '\'') {
+ if (hadSlash) {
+ out.write('/');
+ }
+ hadSlash = false;
+ out.write(c);
+ inSingleQuotes = true;
+ } else if (c == '"') {
+ if (hadSlash) {
+ out.write('/');
+ }
+ hadSlash = false;
+ out.write(c);
+ inString = true;
+ } else if (c == '#') {
+ if (defines == null) {
+ throw new ImageReadException("Unexpected preprocessor directive");
+ }
+ inDirective = true;
+ } else {
+ if (hadSlash) {
+ out.write('/');
+ }
+ hadSlash = false;
+ out.write(c);
+ // Only whitespace allowed before first comment:
+ if (c != ' ' && c != '\t' && c != '\r' && c != '\n') {
+ seenFirstComment = true;
+ }
+ }
+ }
+ }
+ if (hadSlash) {
+ out.write('/');
+ }
+ if (hadStar) {
+ out.write('*');
+ }
+ if (inString) {
+ throw new ImageReadException("Unterminated string at the end of file");
+ }
+ if (inComment) {
+ throw new ImageReadException("Unterminated comment at the end of file");
+ }
+ return out;
+ }
+
+ public static String[] tokenizeRow(final String row) {
+ final String[] tokens = row.split("[ \t]");
+ int numLiveTokens = 0;
+ for (final String token : tokens) {
+ if (token != null && token.length() > 0) {
+ ++numLiveTokens;
+ }
+ }
+ final String[] liveTokens = new String[numLiveTokens];
+ int next = 0;
+ for (final String token : tokens) {
+ if (token != null && token.length() > 0) {
+ liveTokens[next++] = token;
+ }
+ }
+ return liveTokens;
+ }
+
+ public static void unescapeString(final StringBuilder stringBuilder, final String string)
+ throws ImageReadException {
+ if (string.length() < 2) {
+ throw new ImageReadException("Parsing XPM file failed, "
+ + "string is too short");
+ }
+ if (string.charAt(0) != '"'
+ || string.charAt(string.length() - 1) != '"') {
+ throw new ImageReadException("Parsing XPM file failed, "
+ + "string not surrounded by '\"'");
+ }
+ boolean hadBackSlash = false;
+ for (int i = 1; i < (string.length() - 1); i++) {
+ final char c = string.charAt(i);
+ if (hadBackSlash) {
+ if (c == '\\') {
+ stringBuilder.append('\\');
+ } else if (c == '"') {
+ stringBuilder.append('"');
+ } else if (c == '\'') {
+ stringBuilder.append('\'');
+ } else if (c == 'x') {
+ if (i + 2 >= string.length()) {
+ throw new ImageReadException(
+ "Parsing XPM file failed, "
+ + "hex constant in string too short");
+ }
+ final char hex1 = string.charAt(i + 1);
+ final char hex2 = string.charAt(i + 2);
+ i += 2;
+ int constant;
+ try {
+ constant = Integer.parseInt(Character.toString(hex1) + Character.toString(hex2), 16);
+ } catch (final NumberFormatException nfe) {
+ throw new ImageReadException(
+ "Parsing XPM file failed, "
+ + "hex constant invalid", nfe);
+ }
+ stringBuilder.append((char) constant);
+ } else if (c == '0' || c == '1' || c == '2' || c == '3'
+ || c == '4' || c == '5' || c == '6' || c == '7') {
+ int length = 1;
+ if (i + 1 < string.length() && '0' <= string.charAt(i + 1)
+ && string.charAt(i + 1) <= '7') {
+ ++length;
+ }
+ if (i + 2 < string.length() && '0' <= string.charAt(i + 2)
+ && string.charAt(i + 2) <= '7') {
+ ++length;
+ }
+ int constant = 0;
+ for (int j = 0; j < length; j++) {
+ constant *= 8;
+ constant += (string.charAt(i + j) - '0');
+ }
+ i += length - 1;
+ stringBuilder.append((char) constant);
+ } else if (c == 'a') {
+ stringBuilder.append((char) 0x07);
+ } else if (c == 'b') {
+ stringBuilder.append((char) 0x08);
+ } else if (c == 'f') {
+ stringBuilder.append((char) 0x0c);
+ } else if (c == 'n') {
+ stringBuilder.append((char) 0x0a);
+ } else if (c == 'r') {
+ stringBuilder.append((char) 0x0d);
+ } else if (c == 't') {
+ stringBuilder.append((char) 0x09);
+ } else if (c == 'v') {
+ stringBuilder.append((char) 0x0b);
+ } else {
+ throw new ImageReadException("Parsing XPM file failed, "
+ + "invalid escape sequence");
+ }
+ hadBackSlash = false;
+ } else {
+ if (c == '\\') {
+ hadBackSlash = true;
+ } else if (c == '"') {
+ throw new ImageReadException("Parsing XPM file failed, "
+ + "extra '\"' found in string");
+ } else {
+ stringBuilder.append(c);
+ }
+ }
+ }
+ if (hadBackSlash) {
+ throw new ImageReadException("Parsing XPM file failed, "
+ + "unterminated escape sequence found in string");
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/BinaryConstant.java b/src/main/java/org/apache/commons/imaging/common/BinaryConstant.java
new file mode 100644
index 0000000..414f5cc
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/BinaryConstant.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+public class BinaryConstant implements Cloneable {
+ private final byte[] value;
+
+ public BinaryConstant(final byte[] value) {
+ this.value = value.clone();
+ }
+
+ @Override
+ public BinaryConstant clone() throws CloneNotSupportedException {
+ return (BinaryConstant) super.clone();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof BinaryConstant)) {
+ return false;
+ }
+ final BinaryConstant other = (BinaryConstant) obj;
+ return equals(other.value);
+ }
+
+ public boolean equals(final byte[] bytes) {
+ return Arrays.equals(value, bytes);
+ }
+
+ public boolean equals(final byte[] bytes, final int offset, final int length) {
+ if (value.length != length) {
+ return false;
+ }
+ for (int i = 0; i < length; i++) {
+ if (value[i] != bytes[offset + i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(value);
+ }
+
+ public byte get(final int i) {
+ return value[i];
+ }
+
+ public int size() {
+ return value.length;
+ }
+
+ public byte[] toByteArray() {
+ return value.clone();
+ }
+
+ public void writeTo(final OutputStream os) throws IOException {
+ for (final byte element : value) {
+ os.write(element);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/BinaryFileParser.java b/src/main/java/org/apache/commons/imaging/common/BinaryFileParser.java
new file mode 100644
index 0000000..09c1245
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/BinaryFileParser.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+public class BinaryFileParser {
+ // default byte order for Java, many file formats.
+ private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
+ private boolean debug;
+
+ public BinaryFileParser(final ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
+ }
+
+ /**
+ * Constructs a BinaryFileParser with the default, big-endian, byte order.
+ */
+ public BinaryFileParser() {
+ // as above
+ }
+
+ protected void setByteOrder(final ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
+ }
+
+ public ByteOrder getByteOrder() {
+ return byteOrder;
+ }
+
+ public boolean getDebug() {
+ return debug;
+ }
+
+ public void setDebug(final boolean debug) {
+ this.debug = debug;
+ }
+
+ protected final void debugNumber(final String msg, final int data, final int bytes) {
+ final PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out, Charset.defaultCharset()));
+ debugNumber(pw, msg, data, bytes);
+ pw.flush();
+ }
+
+ protected final void debugNumber(final PrintWriter pw, final String msg, final int data, final int bytes) {
+ pw.print(msg + ": " + data + " (");
+ int byteData = data;
+ for (int i = 0; i < bytes; i++) {
+ if (i > 0) {
+ pw.print(",");
+ }
+ final int singleByte = 0xff & byteData;
+ pw.print((char) singleByte + " [" + singleByte + "]");
+ byteData >>= 8;
+ }
+ pw.println(") [0x" + Integer.toHexString(data) + ", " + Integer.toBinaryString(data) + "]");
+ pw.flush();
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java b/src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java
new file mode 100644
index 0000000..eb9b5b9
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/BinaryFunctions.java
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.RandomAccessFile;
+import java.nio.ByteOrder;
+
+import org.apache.commons.imaging.ImageReadException;
+
+/**
+ * Convenience methods for various binary and I/O operations.
+ */
+public final class BinaryFunctions {
+ private BinaryFunctions() {
+ }
+
+ public static boolean startsWith(final byte[] haystack, final byte[] needle) {
+ if (needle == null) {
+ return false;
+ }
+ if (haystack == null) {
+ return false;
+ }
+ if (needle.length > haystack.length) {
+ return false;
+ }
+
+ for (int i = 0; i < needle.length; i++) {
+ if (needle[i] != haystack[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean startsWith(final byte[] haystack, final BinaryConstant needle) {
+ if ((haystack == null) || (haystack.length < needle.size())) {
+ return false;
+ }
+
+ for (int i = 0; i < needle.size(); i++) {
+ if (haystack[i] != needle.get(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static byte readByte(final String name, final InputStream is, final String exception)
+ throws IOException {
+ final int result = is.read();
+ if ((result < 0)) {
+ throw new IOException(exception);
+ }
+ return (byte) (0xff & result);
+ }
+
+ public static byte[] readBytes(final String name, final InputStream is, final int length)
+ throws IOException {
+ final String exception = name + " could not be read.";
+ return readBytes(name, is, length, exception);
+ }
+
+ public static byte[] readBytes(final String name, final InputStream is, final int length,
+ final String exception) throws IOException {
+ final byte[] result = new byte[length];
+ int read = 0;
+ while (read < length) {
+ final int count = is.read(result, read, length - read);
+ if (count < 0) {
+ throw new IOException(exception + " count: " + count
+ + " read: " + read + " length: " + length);
+ }
+
+ read += count;
+ }
+
+ return result;
+ }
+
+ public static byte[] readBytes(final InputStream is, final int count) throws IOException {
+ return readBytes("", is, count, "Unexpected EOF");
+ }
+
+ public static void readAndVerifyBytes(final InputStream is, final byte[] expected,
+ final String exception) throws ImageReadException, IOException {
+ for (final byte element : expected) {
+ final int data = is.read();
+ final byte b = (byte) (0xff & data);
+
+ if (data < 0) {
+ throw new ImageReadException("Unexpected EOF.");
+ }
+
+ if (b != element) {
+ throw new ImageReadException(exception);
+ }
+ }
+ }
+
+ public static void readAndVerifyBytes(final InputStream is,
+ final BinaryConstant expected, final String exception)
+ throws ImageReadException, IOException {
+ for (int i = 0; i < expected.size(); i++) {
+ final int data = is.read();
+ final byte b = (byte) (0xff & data);
+
+ if (data < 0) {
+ throw new ImageReadException("Unexpected EOF.");
+ }
+
+ if (b != expected.get(i)) {
+ throw new ImageReadException(exception);
+ }
+ }
+ }
+
+ public static void skipBytes(final InputStream is, final long length, final String exception)
+ throws IOException {
+ long total = 0;
+ while (length != total) {
+ final long skipped = is.skip(length - total);
+ if (skipped < 1) {
+ throw new IOException(exception + " (" + skipped + ")");
+ }
+ total += skipped;
+ }
+ }
+
+ public static byte[] remainingBytes(final String name, final byte[] bytes, final int count) {
+ return slice(bytes, count, bytes.length - count);
+ }
+
+ public static byte[] slice(final byte[] bytes, final int start, final int count) {
+ final byte[] result = new byte[count];
+ System.arraycopy(bytes, start, result, 0, count);
+ return result;
+ }
+
+ public static byte[] head(final byte[] bytes, int count) {
+ if (count > bytes.length) {
+ count = bytes.length;
+ }
+ return slice(bytes, 0, count);
+ }
+
+ public static boolean compareBytes(final byte[] a, final int aStart, final byte[] b,
+ final int bStart, final int length) {
+ if (a.length < (aStart + length)) {
+ return false;
+ }
+ if (b.length < (bStart + length)) {
+ return false;
+ }
+
+ for (int i = 0; i < length; i++) {
+ if (a[aStart + i] != b[bStart + i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static int read4Bytes(final String name, final InputStream is,
+ final String exception, final ByteOrder byteOrder) throws IOException {
+ final int byte0 = is.read();
+ final int byte1 = is.read();
+ final int byte2 = is.read();
+ final int byte3 = is.read();
+ if ((byte0 | byte1 | byte2 | byte3) < 0) {
+ throw new IOException(exception);
+ }
+
+ final int result;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ result = (byte0 << 24) | (byte1 << 16)
+ | (byte2 << 8) | (byte3 << 0);
+ } else {
+ result = (byte3 << 24) | (byte2 << 16)
+ | (byte1 << 8) | (byte0 << 0);
+ }
+
+ return result;
+ }
+
+ public static int read3Bytes(final String name, final InputStream is,
+ final String exception, final ByteOrder byteOrder) throws IOException {
+ final int byte0 = is.read();
+ final int byte1 = is.read();
+ final int byte2 = is.read();
+ if ((byte0 | byte1 | byte2) < 0) {
+ throw new IOException(exception);
+ }
+
+ final int result;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ result = (byte0 << 16) | (byte1 << 8)
+ | (byte2 << 0);
+ } else {
+ result = (byte2 << 16) | (byte1 << 8)
+ | (byte0 << 0);
+ }
+
+ return result;
+ }
+
+ public static int read2Bytes(final String name, final InputStream is,
+ final String exception, final ByteOrder byteOrder) throws IOException {
+ final int byte0 = is.read();
+ final int byte1 = is.read();
+ if ((byte0 | byte1) < 0) {
+ throw new IOException(exception);
+ }
+
+ final int result;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ result = (byte0 << 8) | byte1;
+ } else {
+ result = (byte1 << 8) | byte0;
+ }
+
+ return result;
+ }
+
+ public static void printCharQuad(final String msg, final int i) {
+ System.out.println(msg + ": '" + (char) (0xff & (i >> 24))
+ + (char) (0xff & (i >> 16)) + (char) (0xff & (i >> 8))
+ + (char) (0xff & (i >> 0)) + "'");
+
+ }
+
+ public static void printCharQuad(final PrintWriter pw, final String msg, final int i) {
+ pw.println(msg + ": '" + (char) (0xff & (i >> 24))
+ + (char) (0xff & (i >> 16)) + (char) (0xff & (i >> 8))
+ + (char) (0xff & (i >> 0)) + "'");
+
+ }
+
+ public static void printByteBits(final String msg, final byte i) {
+ System.out.println(msg + ": '" + Integer.toBinaryString(0xff & i));
+ }
+
+ public static int charsToQuad(final char c1, final char c2, final char c3, final char c4) {
+ return (((0xff & c1) << 24) | ((0xff & c2) << 16) | ((0xff & c3) << 8) | ((0xff & c4) << 0));
+ }
+
+ public static int findNull(final byte[] src) {
+ return findNull(src, 0);
+ }
+
+ public static int findNull(final byte[] src, final int start) {
+ for (int i = start; i < src.length; i++) {
+ if (src[i] == 0) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public static byte[] getRAFBytes(final RandomAccessFile raf, final long pos,
+ final int length, final String exception) throws IOException {
+ final byte[] result = new byte[length];
+
+ raf.seek(pos);
+
+ int read = 0;
+ while (read < length) {
+ final int count = raf.read(result, read, length - read);
+ if (count < 0) {
+ throw new IOException(exception);
+ }
+
+ read += count;
+ }
+
+ return result;
+
+ }
+
+ public static void skipBytes(final InputStream is, final long length) throws IOException {
+ skipBytes(is, length, "Couldn't skip bytes");
+ }
+
+ public static void copyStreamToStream(final InputStream is, final OutputStream os)
+ throws IOException {
+ final byte[] buffer = new byte[1024];
+ int read;
+ while ((read = is.read(buffer)) > 0) {
+ os.write(buffer, 0, read);
+ }
+ }
+
+ public static byte[] getStreamBytes(final InputStream is) throws IOException {
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ copyStreamToStream(is, os);
+ return os.toByteArray();
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/BinaryOutputStream.java b/src/main/java/org/apache/commons/imaging/common/BinaryOutputStream.java
new file mode 100644
index 0000000..56b3ced
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/BinaryOutputStream.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteOrder;
+
+public class BinaryOutputStream extends OutputStream {
+ private final OutputStream os;
+ // default byte order for Java, many file formats.
+ private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
+ private boolean debug;
+ private int count;
+
+ public final void setDebug(final boolean b) {
+ debug = b;
+ }
+
+ public final boolean getDebug() {
+ return debug;
+ }
+
+ public BinaryOutputStream(final OutputStream os, final ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
+ this.os = os;
+ }
+
+ public BinaryOutputStream(final OutputStream os) {
+ this.os = os;
+ }
+
+ protected void setByteOrder(final ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
+ }
+
+ public ByteOrder getByteOrder() {
+ return byteOrder;
+ }
+
+ @Override
+ public void write(final int i) throws IOException {
+ os.write(i);
+ count++;
+ }
+
+ @Override
+ public final void write(final byte[] bytes) throws IOException {
+ os.write(bytes, 0, bytes.length);
+ count += bytes.length;
+ }
+
+ @Override
+ public final void write(final byte[] bytes, final int offset, final int length) throws IOException {
+ os.write(bytes, offset, length);
+ count += length;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ os.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ os.close();
+ }
+
+ public int getByteCount() {
+ return count;
+ }
+
+ public final void write4Bytes(final int value) throws IOException {
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ write(0xff & (value >> 24));
+ write(0xff & (value >> 16));
+ write(0xff & (value >> 8));
+ write(0xff & value);
+ } else {
+ write(0xff & value);
+ write(0xff & (value >> 8));
+ write(0xff & (value >> 16));
+ write(0xff & (value >> 24));
+ }
+ }
+
+ public final void write3Bytes(final int value) throws IOException {
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ write(0xff & (value >> 16));
+ write(0xff & (value >> 8));
+ write(0xff & value);
+ } else {
+ write(0xff & value);
+ write(0xff & (value >> 8));
+ write(0xff & (value >> 16));
+ }
+ }
+
+ public final void write2Bytes(final int value) throws IOException {
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ write(0xff & (value >> 8));
+ write(0xff & value);
+ } else {
+ write(0xff & value);
+ write(0xff & (value >> 8));
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/ByteConversions.java b/src/main/java/org/apache/commons/imaging/common/ByteConversions.java
new file mode 100644
index 0000000..10b8333
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/ByteConversions.java
@@ -0,0 +1,390 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.nio.ByteOrder;
+
+/**
+ * Convenience methods for converting data types to and from
+ * byte arrays.
+ */
+public final class ByteConversions {
+ private ByteConversions() {
+ }
+
+ public static byte[] toBytes(final short value, final ByteOrder byteOrder) {
+ final byte[] result = new byte[2];
+ toBytes(value, byteOrder, result, 0);
+ return result;
+ }
+
+ public static byte[] toBytes(final short[] values, final ByteOrder byteOrder) {
+ return toBytes(values, 0, values.length, byteOrder);
+ }
+
+ private static byte[] toBytes(final short[] values, final int offset, final int length, final ByteOrder byteOrder) {
+ final byte[] result = new byte[length * 2];
+ for (int i = 0; i < length; i++) {
+ toBytes(values[offset + i], byteOrder, result, i * 2);
+ }
+ return result;
+ }
+
+ private static void toBytes(final short value, final ByteOrder byteOrder, final byte[] result, final int offset) {
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ result[offset + 0] = (byte) (value >> 8);
+ result[offset + 1] = (byte) (value >> 0);
+ } else {
+ result[offset + 1] = (byte) (value >> 8);
+ result[offset + 0] = (byte) (value >> 0);
+ }
+ }
+
+ public static byte[] toBytes(final int value, final ByteOrder byteOrder) {
+ final byte[] result = new byte[4];
+ toBytes(value, byteOrder, result, 0);
+ return result;
+ }
+
+ public static byte[] toBytes(final int[] values, final ByteOrder byteOrder) {
+ return toBytes(values, 0, values.length, byteOrder);
+ }
+
+ private static byte[] toBytes(final int[] values, final int offset, final int length, final ByteOrder byteOrder) {
+ final byte[] result = new byte[length * 4];
+ for (int i = 0; i < length; i++) {
+ toBytes(values[offset + i], byteOrder, result, i * 4);
+ }
+ return result;
+ }
+
+ private static void toBytes(final int value, final ByteOrder byteOrder, final byte[] result, final int offset) {
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ result[offset + 0] = (byte) (value >> 24);
+ result[offset + 1] = (byte) (value >> 16);
+ result[offset + 2] = (byte) (value >> 8);
+ result[offset + 3] = (byte) (value >> 0);
+ } else {
+ result[offset + 3] = (byte) (value >> 24);
+ result[offset + 2] = (byte) (value >> 16);
+ result[offset + 1] = (byte) (value >> 8);
+ result[offset + 0] = (byte) (value >> 0);
+ }
+ }
+
+ public static byte[] toBytes(final float value, final ByteOrder byteOrder) {
+ final byte[] result = new byte[4];
+ toBytes(value, byteOrder, result, 0);
+ return result;
+ }
+
+ public static byte[] toBytes(final float[] values, final ByteOrder byteOrder) {
+ return toBytes(values, 0, values.length, byteOrder);
+ }
+
+ private static byte[] toBytes(final float[] values, final int offset, final int length, final ByteOrder byteOrder) {
+ final byte[] result = new byte[length * 4];
+ for (int i = 0; i < length; i++) {
+ toBytes(values[offset + i], byteOrder, result, i * 4);
+ }
+ return result;
+ }
+
+ private static void toBytes(final float value, final ByteOrder byteOrder, final byte[] result, final int offset) {
+ final int bits = Float.floatToRawIntBits(value);
+ if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
+ result[offset + 0] = (byte) (0xff & (bits >> 0));
+ result[offset + 1] = (byte) (0xff & (bits >> 8));
+ result[offset + 2] = (byte) (0xff & (bits >> 16));
+ result[offset + 3] = (byte) (0xff & (bits >> 24));
+ } else {
+ result[offset + 3] = (byte) (0xff & (bits >> 0));
+ result[offset + 2] = (byte) (0xff & (bits >> 8));
+ result[offset + 1] = (byte) (0xff & (bits >> 16));
+ result[offset + 0] = (byte) (0xff & (bits >> 24));
+ }
+ }
+
+ public static byte[] toBytes(final double value, final ByteOrder byteOrder) {
+ final byte[] result = new byte[8];
+ toBytes(value, byteOrder, result, 0);
+ return result;
+ }
+
+ public static byte[] toBytes(final double[] values, final ByteOrder byteOrder) {
+ return toBytes(values, 0, values.length, byteOrder);
+ }
+
+ private static byte[] toBytes(final double[] values, final int offset,
+ final int length, final ByteOrder byteOrder) {
+ final byte[] result = new byte[length * 8];
+ for (int i = 0; i < length; i++) {
+ toBytes(values[offset + i], byteOrder, result, i * 8);
+ }
+ return result;
+ }
+
+ private static void toBytes(final double value, final ByteOrder byteOrder, final byte[] result, final int offset) {
+ final long bits = Double.doubleToRawLongBits(value);
+ if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
+ result[offset + 0] = (byte) (0xff & (bits >> 0));
+ result[offset + 1] = (byte) (0xff & (bits >> 8));
+ result[offset + 2] = (byte) (0xff & (bits >> 16));
+ result[offset + 3] = (byte) (0xff & (bits >> 24));
+ result[offset + 4] = (byte) (0xff & (bits >> 32));
+ result[offset + 5] = (byte) (0xff & (bits >> 40));
+ result[offset + 6] = (byte) (0xff & (bits >> 48));
+ result[offset + 7] = (byte) (0xff & (bits >> 56));
+ } else {
+ result[offset + 7] = (byte) (0xff & (bits >> 0));
+ result[offset + 6] = (byte) (0xff & (bits >> 8));
+ result[offset + 5] = (byte) (0xff & (bits >> 16));
+ result[offset + 4] = (byte) (0xff & (bits >> 24));
+ result[offset + 3] = (byte) (0xff & (bits >> 32));
+ result[offset + 2] = (byte) (0xff & (bits >> 40));
+ result[offset + 1] = (byte) (0xff & (bits >> 48));
+ result[offset + 0] = (byte) (0xff & (bits >> 56));
+ }
+ }
+
+ public static byte[] toBytes(final RationalNumber value, final ByteOrder byteOrder) {
+ final byte[] result = new byte[8];
+ toBytes(value, byteOrder, result, 0);
+ return result;
+ }
+
+ public static byte[] toBytes(final RationalNumber[] values, final ByteOrder byteOrder) {
+ return toBytes(values, 0, values.length, byteOrder);
+ }
+
+ private static byte[] toBytes(final RationalNumber[] values, final int offset,
+ final int length, final ByteOrder byteOrder) {
+ final byte[] result = new byte[length * 8];
+ for (int i = 0; i < length; i++) {
+ toBytes(values[offset + i], byteOrder, result, i * 8);
+ }
+ return result;
+ }
+
+ private static void toBytes(final RationalNumber value, final ByteOrder byteOrder,
+ final byte[] result, final int offset) {
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ result[offset + 0] = (byte) (value.numerator >> 24);
+ result[offset + 1] = (byte) (value.numerator >> 16);
+ result[offset + 2] = (byte) (value.numerator >> 8);
+ result[offset + 3] = (byte) (value.numerator >> 0);
+ result[offset + 4] = (byte) (value.divisor >> 24);
+ result[offset + 5] = (byte) (value.divisor >> 16);
+ result[offset + 6] = (byte) (value.divisor >> 8);
+ result[offset + 7] = (byte) (value.divisor >> 0);
+ } else {
+ result[offset + 3] = (byte) (value.numerator >> 24);
+ result[offset + 2] = (byte) (value.numerator >> 16);
+ result[offset + 1] = (byte) (value.numerator >> 8);
+ result[offset + 0] = (byte) (value.numerator >> 0);
+ result[offset + 7] = (byte) (value.divisor >> 24);
+ result[offset + 6] = (byte) (value.divisor >> 16);
+ result[offset + 5] = (byte) (value.divisor >> 8);
+ result[offset + 4] = (byte) (value.divisor >> 0);
+ }
+ }
+
+ public static short toShort(final byte[] bytes, final ByteOrder byteOrder) {
+ return toShort(bytes, 0, byteOrder);
+ }
+
+ private static short toShort(final byte[] bytes, final int offset, final ByteOrder byteOrder) {
+ return (short) toUInt16(bytes, offset, byteOrder);
+ }
+
+ public static short[] toShorts(final byte[] bytes, final ByteOrder byteOrder) {
+ return toShorts(bytes, 0, bytes.length, byteOrder);
+ }
+
+ private static short[] toShorts(final byte[] bytes, final int offset,
+ final int length, final ByteOrder byteOrder) {
+ final short[] result = new short[length / 2];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toShort(bytes, offset + 2 * i, byteOrder);
+ }
+ return result;
+ }
+
+ public static int toUInt16(final byte[] bytes, final ByteOrder byteOrder) {
+ return toUInt16(bytes, 0, byteOrder);
+ }
+
+ public static int toUInt16(final byte[] bytes, final int offset, final ByteOrder byteOrder) {
+ final int byte0 = 0xff & bytes[offset + 0];
+ final int byte1 = 0xff & bytes[offset + 1];
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ return ((byte0 << 8) | byte1);
+ } else {
+ return ((byte1 << 8) | byte0);
+ }
+ }
+
+ public static int[] toUInt16s(final byte[] bytes, final ByteOrder byteOrder) {
+ return toUInt16s(bytes, 0, bytes.length, byteOrder);
+ }
+
+ private static int[] toUInt16s(final byte[] bytes, final int offset, final int length,
+ final ByteOrder byteOrder) {
+ final int[] result = new int[length / 2];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toUInt16(bytes, offset + 2 * i, byteOrder);
+ }
+ return result;
+ }
+
+ public static int toInt(final byte[] bytes, final ByteOrder byteOrder) {
+ return toInt(bytes, 0, byteOrder);
+ }
+
+ public static int toInt(final byte[] bytes, final int offset, final ByteOrder byteOrder) {
+ final int byte0 = 0xff & bytes[offset + 0];
+ final int byte1 = 0xff & bytes[offset + 1];
+ final int byte2 = 0xff & bytes[offset + 2];
+ final int byte3 = 0xff & bytes[offset + 3];
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ return (byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3;
+ } else {
+ return (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
+ }
+ }
+
+ public static int[] toInts(final byte[] bytes, final ByteOrder byteOrder) {
+ return toInts(bytes, 0, bytes.length, byteOrder);
+ }
+
+ private static int[] toInts(final byte[] bytes, final int offset, final int length,
+ final ByteOrder byteOrder) {
+ final int[] result = new int[length / 4];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toInt(bytes, offset + 4 * i, byteOrder);
+ }
+ return result;
+ }
+
+ public static float toFloat(final byte[] bytes, final ByteOrder byteOrder) {
+ return toFloat(bytes, 0, byteOrder);
+ }
+
+ private static float toFloat(final byte[] bytes, final int offset, final ByteOrder byteOrder) {
+ final int byte0 = 0xff & bytes[offset + 0];
+ final int byte1 = 0xff & bytes[offset + 1];
+ final int byte2 = 0xff & bytes[offset + 2];
+ final int byte3 = 0xff & bytes[offset + 3];
+ final int bits;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ bits = (byte0 << 24) | (byte1 << 16) | (byte2 << 8) | (byte3 << 0);
+ } else {
+ bits = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | (byte0 << 0);
+ }
+ return Float.intBitsToFloat(bits);
+ }
+
+ public static float[] toFloats(final byte[] bytes, final ByteOrder byteOrder) {
+ return toFloats(bytes, 0, bytes.length, byteOrder);
+ }
+
+ private static float[] toFloats(final byte[] bytes, final int offset,
+ final int length, final ByteOrder byteOrder) {
+ final float[] result = new float[length / 4];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toFloat(bytes, offset + 4 * i, byteOrder);
+ }
+ return result;
+ }
+
+ public static double toDouble(final byte[] bytes, final ByteOrder byteOrder) {
+ return toDouble(bytes, 0, byteOrder);
+ }
+
+ private static double toDouble(final byte[] bytes, final int offset, final ByteOrder byteOrder) {
+ final long byte0 = 0xffL & bytes[offset + 0];
+ final long byte1 = 0xffL & bytes[offset + 1];
+ final long byte2 = 0xffL & bytes[offset + 2];
+ final long byte3 = 0xffL & bytes[offset + 3];
+ final long byte4 = 0xffL & bytes[offset + 4];
+ final long byte5 = 0xffL & bytes[offset + 5];
+ final long byte6 = 0xffL & bytes[offset + 6];
+ final long byte7 = 0xffL & bytes[offset + 7];
+ final long bits;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ bits = (byte0 << 56) | (byte1 << 48) | (byte2 << 40)
+ | (byte3 << 32) | (byte4 << 24) | (byte5 << 16)
+ | (byte6 << 8) | (byte7 << 0);
+ } else {
+ bits = (byte7 << 56) | (byte6 << 48) | (byte5 << 40)
+ | (byte4 << 32) | (byte3 << 24) | (byte2 << 16)
+ | (byte1 << 8) | (byte0 << 0);
+ }
+ return Double.longBitsToDouble(bits);
+ }
+
+ public static double[] toDoubles(final byte[] bytes, final ByteOrder byteOrder) {
+ return toDoubles(bytes, 0, bytes.length, byteOrder);
+ }
+
+ private static double[] toDoubles(final byte[] bytes, final int offset,
+ final int length, final ByteOrder byteOrder) {
+ final double[] result = new double[length / 8];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toDouble(bytes, offset + 8 * i, byteOrder);
+ }
+ return result;
+ }
+
+ public static RationalNumber toRational(final byte[] bytes, final ByteOrder byteOrder) {
+ return toRational(bytes, 0, byteOrder);
+ }
+
+ private static RationalNumber toRational(final byte[] bytes, final int offset, final ByteOrder byteOrder) {
+ final int byte0 = 0xff & bytes[offset + 0];
+ final int byte1 = 0xff & bytes[offset + 1];
+ final int byte2 = 0xff & bytes[offset + 2];
+ final int byte3 = 0xff & bytes[offset + 3];
+ final int byte4 = 0xff & bytes[offset + 4];
+ final int byte5 = 0xff & bytes[offset + 5];
+ final int byte6 = 0xff & bytes[offset + 6];
+ final int byte7 = 0xff & bytes[offset + 7];
+ final int numerator;
+ final int divisor;
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ numerator = (byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3;
+ divisor = (byte4 << 24) | (byte5 << 16) | (byte6 << 8) | byte7;
+ } else {
+ numerator = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;
+ divisor = (byte7 << 24) | (byte6 << 16) | (byte5 << 8) | byte4;
+ }
+ return new RationalNumber(numerator, divisor);
+ }
+
+ public static RationalNumber[] toRationals(final byte[] bytes, final ByteOrder byteOrder) {
+ return toRationals(bytes, 0, bytes.length, byteOrder);
+ }
+
+ private static RationalNumber[] toRationals(final byte[] bytes,
+ final int offset, final int length, final ByteOrder byteOrder) {
+ final RationalNumber[] result = new RationalNumber[length / 8];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = toRational(bytes, offset + 8 * i, byteOrder);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/common/MyByteArrayOutputStream.java b/src/main/java/org/apache/commons/imaging/common/FastByteArrayOutputStream.java
similarity index 64%
rename from src/main/java/org/apache/sanselan/common/MyByteArrayOutputStream.java
rename to src/main/java/org/apache/commons/imaging/common/FastByteArrayOutputStream.java
index ffd8a77..0ab4100 100644
--- a/src/main/java/org/apache/sanselan/common/MyByteArrayOutputStream.java
+++ b/src/main/java/org/apache/commons/imaging/common/FastByteArrayOutputStream.java
@@ -1,59 +1,56 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class MyByteArrayOutputStream extends OutputStream
-// some performace benefit, because not thread safe.
-{
- private final byte bytes[];
-
- public MyByteArrayOutputStream(int length)
- {
- bytes = new byte[length];
- }
-
- private int count = 0;
-
- public void write(int value) throws IOException
- {
- if (count >= bytes.length)
- throw new IOException("Write exceeded expected length (" + count
- + ", " + bytes.length + ")");
-
- bytes[count] = (byte) value;
- count++;
- }
-
- public byte[] toByteArray()
- {
- if (count < bytes.length)
- {
- byte result[] = new byte[count];
- System.arraycopy(bytes, 0, result, 0, count);
- return result;
- }
- return bytes;
- }
-
- public int getBytesWritten()
- {
- return count;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Like ByteArrayOutputStream, but has some performance benefit,
+ * because it's not thread safe.
+ */
+class FastByteArrayOutputStream extends OutputStream {
+ private final byte[] bytes;
+ private int count;
+
+ public FastByteArrayOutputStream(final int length) {
+ bytes = new byte[length];
+ }
+
+ @Override
+ public void write(final int value) throws IOException {
+ if (count >= bytes.length) {
+ throw new IOException("Write exceeded expected length (" + count + ", " + bytes.length + ")");
+ }
+
+ bytes[count] = (byte) value;
+ count++;
+ }
+
+ public byte[] toByteArray() {
+ if (count < bytes.length) {
+ final byte[] result = new byte[count];
+ System.arraycopy(bytes, 0, result, 0, count);
+ return result;
+ }
+ return bytes;
+ }
+
+ public int getBytesWritten() {
+ return count;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/common/IBufferedImageFactory.java b/src/main/java/org/apache/commons/imaging/common/IBufferedImageFactory.java
similarity index 78%
rename from src/main/java/org/apache/sanselan/common/IBufferedImageFactory.java
rename to src/main/java/org/apache/commons/imaging/common/IBufferedImageFactory.java
index 857df69..e6dbc4f 100644
--- a/src/main/java/org/apache/sanselan/common/IBufferedImageFactory.java
+++ b/src/main/java/org/apache/commons/imaging/common/IBufferedImageFactory.java
@@ -1,29 +1,28 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.sanselan.common;
-
-import com.google.code.appengine.awt.image.BufferedImage;
-
-public interface IBufferedImageFactory
-{
- public BufferedImage getColorBufferedImage(int width, int height,
- boolean hasAlpha);
-
- public BufferedImage getGrayscaleBufferedImage(int width, int height,
- boolean hasAlpha);
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.imaging.common;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+
+public interface IBufferedImageFactory {
+ BufferedImage getColorBufferedImage(int width, int height,
+ boolean hasAlpha);
+
+ BufferedImage getGrayscaleBufferedImage(int width, int height,
+ boolean hasAlpha);
+}
diff --git a/src/main/java/org/apache/sanselan/ImageReadException.java b/src/main/java/org/apache/commons/imaging/common/IImageMetadata.java
similarity index 71%
rename from src/main/java/org/apache/sanselan/ImageReadException.java
rename to src/main/java/org/apache/commons/imaging/common/IImageMetadata.java
index 472e424..dc23d9c 100644
--- a/src/main/java/org/apache/sanselan/ImageReadException.java
+++ b/src/main/java/org/apache/commons/imaging/common/IImageMetadata.java
@@ -1,32 +1,31 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan;
-
-public class ImageReadException extends SanselanException
-{
- static final long serialVersionUID = -1L;
-
- public ImageReadException(String s)
- {
- super(s);
- }
-
- public ImageReadException(String s, Exception e)
- {
- super(s, e);
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.util.List;
+
+public interface IImageMetadata {
+ String toString(String prefix);
+
+ List extends IImageMetadataItem> getItems();
+
+ interface IImageMetadataItem {
+ String toString(String prefix);
+
+ String toString();
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java b/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java
new file mode 100644
index 0000000..02e5a2f
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/ImageBuilder.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * Development notes:
+ * This class was introduced to the Apache Commons Imaging library in
+ * order to improve performance in building images. The setRGB method
+ * provided by this class represents a substantial improvement in speed
+ * compared to that of the BufferedImage class that was originally used
+ * in Apache Sanselan.
+ * This increase is attained because ImageBuilder is a highly specialized
+ * class that does not need to perform the general-purpose logic required
+ * for BufferedImage. If you need to modify this class to add new
+ * image formats or functionality, keep in mind that some of its methods
+ * are invoked literally millions of times when building an image.
+ * Since even the introduction of something as small as a single conditional
+ * inside of setRGB could result in a noticeable increase in the
+ * time to read a file, changes should be made with care.
+ * During development, I experimented with inlining the setRGB logic
+ * in some of the code that uses it. This approach did not significantly
+ * improve performance, leading me to speculate that the Java JIT compiler
+ * might have inlined the method at run time. Further investigation
+ * is required.
+ *
+ */
+package org.apache.commons.imaging.common;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+import com.google.code.appengine.awt.image.ColorModel;
+import com.google.code.appengine.awt.image.DataBufferInt;
+import com.google.code.appengine.awt.image.DirectColorModel;
+import com.google.code.appengine.awt.image.Raster;
+import com.google.code.appengine.awt.image.RasterFormatException;
+import com.google.code.appengine.awt.image.WritableRaster;
+import java.util.Properties;
+
+/**
+ * A utility class primary intended for storing data obtained by reading
+ * image files.
+ */
+public class ImageBuilder {
+ private final int[] data;
+ private final int width;
+ private final int height;
+ private final boolean hasAlpha;
+
+ /**
+ * Construct an ImageBuilder instance
+ * @param width the width of the image to be built
+ * @param height the height of the image to be built
+ * @param hasAlpha indicates whether the image has an alpha channel
+ * (the selection of alpha channel does not change the memory
+ * requirements for the ImageBuilder or resulting BufferedImage.
+ */
+ public ImageBuilder(final int width, final int height, final boolean hasAlpha) {
+ if (width <= 0) {
+ throw new RasterFormatException("zero or negative width value");
+ }
+ if (height <= 0) {
+ throw new RasterFormatException("zero or negative height value");
+ }
+
+ data = new int[width * height];
+ this.width = width;
+ this.height = height;
+ this.hasAlpha = hasAlpha;
+ }
+
+ /**
+ * Get the width of the ImageBuilder pixel field
+ * @return a positive integer
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * Get the height of the ImageBuilder pixel field
+ * @return a positive integer
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * Get the RGB or ARGB value for the pixel at the position (x,y)
+ * within the image builder pixel field. For performance reasons
+ * no bounds checking is applied.
+ * @param x the X coordinate of the pixel to be read
+ * @param y the Y coordinate of the pixel to be read
+ * @return the RGB or ARGB pixel value
+ */
+ public int getRGB(final int x, final int y) {
+ final int rowOffset = y * width;
+ return data[rowOffset + x];
+ }
+
+ /**
+ * Set the RGB or ARGB value for the pixel at position (x,y)
+ * within the image builder pixel field. For performance reasons,
+ * no bounds checking is applied.
+ * @param x the X coordinate of the pixel to be set
+ * @param y the Y coordinate of the pixel to be set
+ * @param argb the RGB or ARGB value to be stored.
+ */
+ public void setRGB(final int x, final int y, final int argb) {
+ final int rowOffset = y * width;
+ data[rowOffset + x] = argb;
+ }
+
+ /**
+ * Create a BufferedImage using the data stored in the ImageBuilder.
+ * @return a valid BufferedImage.
+ */
+ public BufferedImage getBufferedImage() {
+ return makeBufferedImage(data, width, height, hasAlpha);
+ }
+
+ /**
+ * Gets a subimage from the ImageBuilder using the specified parameters.
+ * If the parameters specify a rectangular region that is not entirely
+ * contained within the bounds defined by the ImageBuilder, this method will
+ * throw a RasterFormatException. This runtime-exception behavior
+ * is consistent with the behavior of the getSubimage method
+ * provided by BufferdImage.
+ * @param x the X coordinate of the upper-left corner of the
+ * specified rectangular region
+ * @param y the Y coordinate of the upper-left corner of the
+ * specified rectangular region
+ * @param w the width of the specified rectangular region
+ * @param h the height of the specified rectangular region
+ * @return a BufferedImage that constructed from the deta within the
+ * specified rectangular region
+ * @throws RasterFormatException f the specified area is not contained
+ * within this ImageBuilder
+ */
+ public BufferedImage getSubimage(final int x, final int y, final int w, final int h)
+ {
+ if (w <= 0) {
+ throw new RasterFormatException("negative or zero subimage width");
+ }
+ if (h <= 0) {
+ throw new RasterFormatException("negative or zero subimage height");
+ }
+ if (x < 0 || x >= width) {
+ throw new RasterFormatException("subimage x is outside raster");
+ }
+ if (x + w > width) {
+ throw new RasterFormatException(
+ "subimage (x+width) is outside raster");
+ }
+ if (y < 0 || y >= height) {
+ throw new RasterFormatException("subimage y is outside raster");
+ }
+ if (y + h > height) {
+ throw new RasterFormatException(
+ "subimage (y+height) is outside raster");
+ }
+
+
+ // Transcribe the data to an output image array
+ final int[] argb = new int[w * h];
+ int k = 0;
+ for (int iRow = 0; iRow < h; iRow++) {
+ final int dIndex = (iRow + y) * width + x;
+ System.arraycopy(this.data, dIndex, argb, k, w);
+ k += w;
+
+ }
+
+ return makeBufferedImage(argb, w, h, hasAlpha);
+
+ }
+
+ private BufferedImage makeBufferedImage(
+ final int[] argb, final int w, final int h, final boolean useAlpha)
+ {
+ ColorModel colorModel;
+ WritableRaster raster;
+ final DataBufferInt buffer = new DataBufferInt(argb, w * h);
+ if (useAlpha) {
+ colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00,
+ 0x000000ff, 0xff000000);
+ raster = Raster.createPackedRaster(buffer, w, h,
+ w, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff,
+ 0xff000000 }, null);
+ } else {
+ colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00,
+ 0x000000ff);
+ raster = Raster.createPackedRaster(buffer, w, h,
+ w, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
+ null);
+ }
+ return new BufferedImage(colorModel, raster,
+ colorModel.isAlphaPremultiplied(), new Properties());
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/ImageMetadata.java b/src/main/java/org/apache/commons/imaging/common/ImageMetadata.java
new file mode 100644
index 0000000..b9bbd1a
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/ImageMetadata.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ImageMetadata implements IImageMetadata {
+ private static final String NEWLINE = System.getProperty("line.separator");
+ private final List items = new ArrayList();
+
+ public void add(final String keyword, final String text) {
+ add(new Item(keyword, text));
+ }
+
+ public void add(final IImageMetadataItem item) {
+ items.add(item);
+ }
+
+ public List extends IImageMetadataItem> getItems() {
+ return new ArrayList(items);
+ }
+
+ @Override
+ public String toString() {
+ return toString(null);
+ }
+
+ public String toString(String prefix) {
+ if (null == prefix) {
+ prefix = "";
+ }
+
+ final StringBuilder result = new StringBuilder();
+ for (int i = 0; i < items.size(); i++) {
+ if (i > 0) {
+ result.append(NEWLINE);
+ }
+ // if (null != prefix)
+ // result.append(prefix);
+
+ final ImageMetadata.IImageMetadataItem item = items.get(i);
+ result.append(item.toString(prefix + "\t"));
+
+ // Debug.debug("prefix", prefix);
+ // Debug.debug("item", items.get(i));
+ // Debug.debug();
+ }
+ return result.toString();
+ }
+
+ public static class Item implements IImageMetadataItem {
+ private final String keyword;
+ private final String text;
+
+ public Item(final String keyword, final String text) {
+ this.keyword = keyword;
+ this.text = text;
+ }
+
+ public String getKeyword() {
+ return keyword;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ @Override
+ public String toString() {
+ return toString(null);
+ }
+
+ public String toString(final String prefix) {
+ final String result = keyword + ": " + text;
+ if (null != prefix) {
+ return prefix + result;
+ } else {
+ return result;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/PackBits.java b/src/main/java/org/apache/commons/imaging/common/PackBits.java
new file mode 100644
index 0000000..ee0a1aa
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/PackBits.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.util.IoUtils;
+
+public class PackBits {
+
+ public byte[] decompress(final byte[] bytes, final int expected)
+ throws ImageReadException {
+ int total = 0;
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ // Loop until you get the number of unpacked bytes you are expecting:
+ int i = 0;
+ while (total < expected) {
+ // Read the next source byte into n.
+ if (i >= bytes.length) {
+ throw new ImageReadException(
+ "Tiff: Unpack bits source exhausted: " + i
+ + ", done + " + total + ", expected + "
+ + expected);
+ }
+
+ final int n = bytes[i++];
+ if ((n >= 0) && (n <= 127)) {
+ // If n is between 0 and 127 inclusive, copy the next n+1 bytes
+ // literally.
+ final int count = n + 1;
+
+ total += count;
+ for (int j = 0; j < count; j++) {
+ baos.write(bytes[i++]);
+ }
+ } else if ((n >= -127) && (n <= -1)) {
+ // Else if n is between -127 and -1 inclusive, copy the next byte
+ // -n+1 times.
+
+ final int b = bytes[i++];
+ final int count = -n + 1;
+
+ total += count;
+ for (int j = 0; j < count; j++) {
+ baos.write(b);
+ }
+ } else if (n == -128) {
+ // Else if n is -128, noop.
+ throw new ImageReadException("Packbits: " + n);
+ }
+ }
+
+ return baos.toByteArray();
+
+ }
+
+ private int findNextDuplicate(final byte[] bytes, final int start) {
+ // int last = -1;
+ if (start >= bytes.length) {
+ return -1;
+ }
+
+ byte prev = bytes[start];
+
+ for (int i = start + 1; i < bytes.length; i++) {
+ final byte b = bytes[i];
+
+ if (b == prev) {
+ return i - 1;
+ }
+
+ prev = b;
+ }
+
+ return -1;
+ }
+
+ private int findRunLength(final byte[] bytes, final int start) {
+ final byte b = bytes[start];
+
+ int i;
+
+ for (i = start + 1; (i < bytes.length) && (bytes[i] == b); i++) {
+ // do nothing
+ }
+
+ return i - start;
+ }
+
+ public byte[] compress(final byte[] bytes) throws IOException {
+ FastByteArrayOutputStream baos = null;
+ boolean canThrow = false;
+ try {
+ baos = new FastByteArrayOutputStream(
+ bytes.length * 2); // max length 1 extra byte for every 128
+
+ int ptr = 0;
+ while (ptr < bytes.length) {
+ int dup = findNextDuplicate(bytes, ptr);
+
+ if (dup == ptr) {
+ // write run length
+ final int len = findRunLength(bytes, dup);
+ final int actualLen = Math.min(len, 128);
+ baos.write(-(actualLen - 1));
+ baos.write(bytes[ptr]);
+ ptr += actualLen;
+ } else {
+ // write literals
+ int len = dup - ptr;
+
+ if (dup > 0) {
+ final int runlen = findRunLength(bytes, dup);
+ if (runlen < 3) {
+ // may want to discard next run.
+ final int nextptr = ptr + len + runlen;
+ final int nextdup = findNextDuplicate(bytes, nextptr);
+ if (nextdup != nextptr) {
+ // discard 2-byte run
+ dup = nextdup;
+ len = dup - ptr;
+ }
+ }
+ }
+
+ if (dup < 0) {
+ len = bytes.length - ptr;
+ }
+ final int actualLen = Math.min(len, 128);
+
+ baos.write(actualLen - 1);
+ for (int i = 0; i < actualLen; i++) {
+ baos.write(bytes[ptr]);
+ ptr++;
+ }
+ }
+ }
+ final byte[] result = baos.toByteArray();
+ canThrow = true;
+ return result;
+ } finally {
+ IoUtils.closeQuietly(canThrow, baos);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/RationalNumber.java b/src/main/java/org/apache/commons/imaging/common/RationalNumber.java
new file mode 100644
index 0000000..a841a6f
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/RationalNumber.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common;
+
+import java.text.NumberFormat;
+
+/**
+ * Rational number, as used by the TIFF image format.
+ */
+public class RationalNumber extends Number {
+
+ private static final long serialVersionUID = -8412262656468158691L;
+
+ // int-precision tolerance
+ private static final double TOLERANCE = 1E-8;
+
+ public final int numerator;
+ public final int divisor;
+
+ public RationalNumber(final int numerator, final int divisor) {
+ this.numerator = numerator;
+ this.divisor = divisor;
+ }
+
+ static RationalNumber factoryMethod(long n, long d) {
+ // safer than constructor - handles values outside min/max range.
+ // also does some simple finding of common denominators.
+
+ if (n > Integer.MAX_VALUE || n < Integer.MIN_VALUE
+ || d > Integer.MAX_VALUE || d < Integer.MIN_VALUE) {
+ while ((n > Integer.MAX_VALUE || n < Integer.MIN_VALUE
+ || d > Integer.MAX_VALUE || d < Integer.MIN_VALUE)
+ && (Math.abs(n) > 1) && (Math.abs(d) > 1)) {
+ // brutal, inprecise truncation =(
+ // use the sign-preserving right shift operator.
+ n >>= 1;
+ d >>= 1;
+ }
+
+ if (d == 0) {
+ throw new NumberFormatException("Invalid value, numerator: " + n + ", divisor: " + d);
+ }
+ }
+
+ final long gcd = gcd(n, d);
+ d = d / gcd;
+ n = n / gcd;
+
+ return new RationalNumber((int) n, (int) d);
+ }
+
+ /**
+ * Return the greatest common divisor
+ */
+ private static long gcd(final long a, final long b) {
+ if (b == 0) {
+ return a;
+ } else {
+ return gcd(b, a % b);
+ }
+ }
+
+ public RationalNumber negate() {
+ return new RationalNumber(-numerator, divisor);
+ }
+
+ @Override
+ public double doubleValue() {
+ return (double) numerator / (double) divisor;
+ }
+
+ @Override
+ public float floatValue() {
+ return (float) numerator / (float) divisor;
+ }
+
+ @Override
+ public int intValue() {
+ return numerator / divisor;
+ }
+
+ @Override
+ public long longValue() {
+ return (long) numerator / (long) divisor;
+ }
+
+ @Override
+ public String toString() {
+ if (divisor == 0) {
+ return "Invalid rational (" + numerator + "/" + divisor + ")";
+ }
+ final NumberFormat nf = NumberFormat.getInstance();
+
+ if ((numerator % divisor) == 0) {
+ return nf.format(numerator / divisor);
+ }
+ return numerator + "/" + divisor + " (" + nf.format((double) numerator / divisor) + ")";
+ }
+
+ public String toDisplayString() {
+ if ((numerator % divisor) == 0) {
+ return Integer.toString(numerator / divisor);
+ }
+ final NumberFormat nf = NumberFormat.getInstance();
+ nf.setMaximumFractionDigits(3);
+ return nf.format((double) numerator / (double) divisor);
+ }
+
+
+ private static class Option {
+ public final RationalNumber rationalNumber;
+ public final double error;
+
+ private Option(final RationalNumber rationalNumber, final double error) {
+ this.rationalNumber = rationalNumber;
+ this.error = error;
+ }
+
+ public static Option factory(final RationalNumber rationalNumber, final double value) {
+ return new Option(rationalNumber, Math.abs(rationalNumber .doubleValue() - value));
+ }
+
+ @Override
+ public String toString() {
+ return rationalNumber.toString();
+ }
+ }
+
+ /**
+ * Calculate rational number using successive approximations.
+ */
+ public static RationalNumber valueOf(double value) {
+ if (value >= Integer.MAX_VALUE) {
+ return new RationalNumber(Integer.MAX_VALUE, 1);
+ } else if (value <= -Integer.MAX_VALUE) {
+ return new RationalNumber(-Integer.MAX_VALUE, 1);
+ }
+
+ boolean negative = false;
+ if (value < 0) {
+ negative = true;
+ value = Math.abs(value);
+ }
+
+ RationalNumber l;
+ RationalNumber h;
+
+ if (value == 0) {
+ return new RationalNumber(0, 1);
+ } else if (value >= 1) {
+ final int approx = (int) value;
+ if (approx < value) {
+ l = new RationalNumber(approx, 1);
+ h = new RationalNumber(approx + 1, 1);
+ } else {
+ l = new RationalNumber(approx - 1, 1);
+ h = new RationalNumber(approx, 1);
+ }
+ } else {
+ final int approx = (int) (1.0 / value);
+ if ((1.0 / approx) < value) {
+ l = new RationalNumber(1, approx);
+ h = new RationalNumber(1, approx - 1);
+ } else {
+ l = new RationalNumber(1, approx + 1);
+ h = new RationalNumber(1, approx);
+ }
+ }
+ Option low = Option.factory(l, value);
+ Option high = Option.factory(h, value);
+
+ Option bestOption = (low.error < high.error) ? low : high;
+
+ final int maxIterations = 100; // value is quite high, actually.
+ // shouldn't matter.
+ for (int count = 0; bestOption.error > TOLERANCE
+ && count < maxIterations; count++) {
+ final RationalNumber mediant = RationalNumber.factoryMethod(
+ (long) low.rationalNumber.numerator
+ + (long) high.rationalNumber.numerator,
+ (long) low.rationalNumber.divisor
+ + (long) high.rationalNumber.divisor);
+ final Option mediantOption = Option.factory(mediant, value);
+
+ if (value < mediant.doubleValue()) {
+ if (high.error <= mediantOption.error) {
+ break;
+ }
+
+ high = mediantOption;
+ } else {
+ if (low.error <= mediantOption.error) {
+ break;
+ }
+
+ low = mediantOption;
+ }
+
+ if (mediantOption.error < bestOption.error) {
+ bestOption = mediantOption;
+ }
+ }
+
+ return negative ? bestOption.rationalNumber.negate()
+ : bestOption.rationalNumber;
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/common/RgbBufferedImageFactory.java b/src/main/java/org/apache/commons/imaging/common/RgbBufferedImageFactory.java
similarity index 78%
rename from src/main/java/org/apache/sanselan/common/RgbBufferedImageFactory.java
rename to src/main/java/org/apache/commons/imaging/common/RgbBufferedImageFactory.java
index 65a409b..6de4deb 100644
--- a/src/main/java/org/apache/sanselan/common/RgbBufferedImageFactory.java
+++ b/src/main/java/org/apache/commons/imaging/common/RgbBufferedImageFactory.java
@@ -1,38 +1,36 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.sanselan.common;
-
-import com.google.code.appengine.awt.image.BufferedImage;
-
-public class RgbBufferedImageFactory implements IBufferedImageFactory
-{
- public BufferedImage getColorBufferedImage(int width, int height,
- boolean hasAlpha)
- {
- if (hasAlpha)
- return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- }
-
- public BufferedImage getGrayscaleBufferedImage(int width, int height,
- boolean hasAlpha)
- {
- // always use color.
- return getColorBufferedImage(width, height, hasAlpha);
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.imaging.common;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+
+public class RgbBufferedImageFactory implements IBufferedImageFactory {
+ public BufferedImage getColorBufferedImage(final int width, final int height,
+ final boolean hasAlpha) {
+ if (hasAlpha) {
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ }
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ }
+
+ public BufferedImage getGrayscaleBufferedImage(final int width, final int height,
+ final boolean hasAlpha) {
+ // always use color.
+ return getColorBufferedImage(width, height, hasAlpha);
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/common/SimpleBufferedImageFactory.java b/src/main/java/org/apache/commons/imaging/common/SimpleBufferedImageFactory.java
similarity index 77%
rename from src/main/java/org/apache/sanselan/common/SimpleBufferedImageFactory.java
rename to src/main/java/org/apache/commons/imaging/common/SimpleBufferedImageFactory.java
index 6d3356b..ed7de91 100644
--- a/src/main/java/org/apache/sanselan/common/SimpleBufferedImageFactory.java
+++ b/src/main/java/org/apache/commons/imaging/common/SimpleBufferedImageFactory.java
@@ -1,40 +1,39 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.sanselan.common;
-
-import com.google.code.appengine.awt.image.BufferedImage;
-
-public class SimpleBufferedImageFactory implements IBufferedImageFactory
-{
- public BufferedImage getColorBufferedImage(int width, int height,
- boolean hasAlpha)
- {
- if (hasAlpha)
- return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- }
-
- public BufferedImage getGrayscaleBufferedImage(int width, int height,
- boolean hasAlpha)
- {
- if (hasAlpha)
- return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-
- return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.imaging.common;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+
+public class SimpleBufferedImageFactory implements IBufferedImageFactory {
+ public BufferedImage getColorBufferedImage(final int width, final int height,
+ final boolean hasAlpha) {
+ if (hasAlpha) {
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ }
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ }
+
+ public BufferedImage getGrayscaleBufferedImage(final int width, final int height,
+ final boolean hasAlpha) {
+ if (hasAlpha) {
+ return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ }
+
+ return new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/common/byteSources/ByteSource.java b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSource.java
similarity index 52%
rename from src/main/java/org/apache/sanselan/common/byteSources/ByteSource.java
rename to src/main/java/org/apache/commons/imaging/common/bytesource/ByteSource.java
index d00b689..2610652 100644
--- a/src/main/java/org/apache/sanselan/common/byteSources/ByteSource.java
+++ b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSource.java
@@ -1,67 +1,73 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common.byteSources;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.apache.sanselan.common.BinaryFileFunctions;
-
-public abstract class ByteSource extends BinaryFileFunctions
-{
- protected final String filename;
-
- public ByteSource(final String filename)
- {
- this.filename = filename;
- }
-
- public final InputStream getInputStream(int start) throws IOException
- {
- InputStream is = getInputStream();
-
- skipBytes(is, start);
-
- return is;
- }
-
- public abstract InputStream getInputStream() throws IOException;
-
- public abstract byte[] getBlock(int start, int length) throws IOException;
-
- public abstract byte[] getAll() throws IOException;
-
- /*
- * This operation can be VERY expensive; for inputstream
- * byte sources, the entire stream must be drained to
- * determine its length.
- */
- public abstract long getLength() throws IOException;
-
- //
- // public byte[] getAll() throws IOException
- // {
- // return getBlock(0, (int) getLength());
- // }
-
- public abstract String getDescription();
-
- public final String getFilename()
- {
- return filename;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.bytesource;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.imaging.common.BinaryFunctions;
+
+public abstract class ByteSource {
+ protected final String filename;
+
+ public ByteSource(final String filename) {
+ this.filename = filename;
+ }
+
+ public final InputStream getInputStream(final long start) throws IOException {
+ InputStream is = null;
+ boolean succeeded = false;
+ try {
+ is = getInputStream();
+ BinaryFunctions.skipBytes(is, start);
+ succeeded = true;
+ } finally {
+ if (!succeeded && is != null) {
+ is.close();
+ }
+ }
+ return is;
+ }
+
+ public abstract InputStream getInputStream() throws IOException;
+
+ public byte[] getBlock(final int start, final int length) throws IOException {
+ return getBlock(0xFFFFffffL & start, length);
+ }
+
+ public abstract byte[] getBlock(long start, int length) throws IOException;
+
+ public abstract byte[] getAll() throws IOException;
+
+ /*
+ * This operation can be VERY expensive; for inputstream byte sources, the
+ * entire stream must be drained to determine its length.
+ */
+ public abstract long getLength() throws IOException;
+
+ //
+ // public byte[] getAll() throws IOException
+ // {
+ // return getBlock(0, (int) getLength());
+ // }
+
+ public abstract String getDescription();
+
+ public final String getFilename() {
+ return filename;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/common/byteSources/ByteSourceArray.java b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceArray.java
similarity index 66%
rename from src/main/java/org/apache/sanselan/common/byteSources/ByteSourceArray.java
rename to src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceArray.java
index a152d5f..4e0911c 100644
--- a/src/main/java/org/apache/sanselan/common/byteSources/ByteSourceArray.java
+++ b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceArray.java
@@ -1,73 +1,72 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common.byteSources;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class ByteSourceArray extends ByteSource
-{
- private final byte bytes[];
-
- public ByteSourceArray(String filename, byte bytes[])
- {
- super(filename);
- this.bytes = bytes;
- }
-
- public ByteSourceArray(byte bytes[])
- {
- super(null);
- this.bytes = bytes;
- }
-
- public InputStream getInputStream()
- {
- return new ByteArrayInputStream(bytes);
- }
-
- public byte[] getBlock(int start, int length) throws IOException
- {
- // We include a separate check for int overflow.
- if ((start < 0) || (length < 0) || (start + length < 0) || (start + length > bytes.length)) {
- throw new IOException("Could not read block (block start: " + start
- + ", block length: " + length + ", data length: "
- + bytes.length + ").");
- }
-
- byte result[] = new byte[length];
- System.arraycopy(bytes, start, result, 0, length);
- return result;
- }
-
- public long getLength()
- {
- return bytes.length;
- }
-
- public byte[] getAll() throws IOException
- {
- return bytes;
- }
-
- public String getDescription()
- {
- return bytes.length + " byte array";
- }
-
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.bytesource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ByteSourceArray extends ByteSource {
+ private final byte[] bytes;
+
+ public ByteSourceArray(final String filename, final byte[] bytes) {
+ super(filename);
+ this.bytes = bytes;
+ }
+
+ public ByteSourceArray(final byte[] bytes) {
+ super(null);
+ this.bytes = bytes;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public byte[] getBlock(final long startLong, final int length) throws IOException {
+ final int start = (int) startLong;
+ // We include a separate check for int overflow.
+ if ((start < 0) || (length < 0) || (start + length < 0)
+ || (start + length > bytes.length)) {
+ throw new IOException("Could not read block (block start: " + start
+ + ", block length: " + length + ", data length: "
+ + bytes.length + ").");
+ }
+
+ final byte[] result = new byte[length];
+ System.arraycopy(bytes, start, result, 0, length);
+ return result;
+ }
+
+ @Override
+ public long getLength() {
+ return bytes.length;
+ }
+
+ @Override
+ public byte[] getAll() throws IOException {
+ return bytes;
+ }
+
+ @Override
+ public String getDescription() {
+ return bytes.length + " byte array";
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/common/byteSources/ByteSourceFile.java b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceFile.java
similarity index 50%
rename from src/main/java/org/apache/sanselan/common/byteSources/ByteSourceFile.java
rename to src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceFile.java
index 652e45a..a8c5f3c 100644
--- a/src/main/java/org/apache/sanselan/common/byteSources/ByteSourceFile.java
+++ b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceFile.java
@@ -1,123 +1,100 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common.byteSources;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.RandomAccessFile;
-
-import org.apache.sanselan.util.Debug;
-
-public class ByteSourceFile extends ByteSource
-{
- private final File file;
-
- public ByteSourceFile(File file)
- {
- super(file.getName());
- this.file = file;
- }
-
- public InputStream getInputStream() throws IOException
- {
- FileInputStream is = null;
- BufferedInputStream bis = null;
- is = new FileInputStream(file);
- bis = new BufferedInputStream(is);
- return bis;
- }
-
- public byte[] getBlock(int start, int length) throws IOException
- {
-
- RandomAccessFile raf = null;
- try
- {
- raf = new RandomAccessFile(file, "r");
-
- // We include a separate check for int overflow.
- if ((start < 0) || (length < 0) || (start + length < 0) || (start + length > raf.length())) {
- throw new IOException("Could not read block (block start: " + start
- + ", block length: " + length + ", data length: "
- + raf.length() + ").");
- }
-
- return getRAFBytes(raf, start, length,
- "Could not read value from file");
- }
- finally
- {
- try
- {
- if (raf != null) {
- raf.close();
- }
- }
- catch (Exception e)
- {
- Debug.debug(e);
- }
-
- }
- }
-
- public long getLength()
- {
- return file.length();
- }
-
- public byte[] getAll() throws IOException
- {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
-
- InputStream is = null;
- try
- {
- is = new FileInputStream(file);
- is = new BufferedInputStream(is);
- byte buffer[] = new byte[1024];
- int read;
- while ((read = is.read(buffer)) > 0)
- {
- baos.write(buffer, 0, read);
- }
- return baos.toByteArray();
- }
- finally
- {
- try
- {
- if (null != is)
- is.close();
- }
- catch (IOException e)
- {
- // Debug.d
- }
- }
- }
-
- public String getDescription()
- {
- return "File: '" + file.getAbsolutePath() + "'";
- }
-
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.bytesource;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+import org.apache.commons.imaging.common.BinaryFunctions;
+import org.apache.commons.imaging.util.IoUtils;
+
+public class ByteSourceFile extends ByteSource {
+ private final File file;
+
+ public ByteSourceFile(final File file) {
+ super(file.getName());
+ this.file = file;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(file));
+ }
+
+ @Override
+ public byte[] getBlock(final long start, final int length) throws IOException {
+
+ RandomAccessFile raf = null;
+ boolean canThrow = false;
+ try {
+ raf = new RandomAccessFile(file, "r");
+
+ // We include a separate check for int overflow.
+ if ((start < 0) || (length < 0) || (start + length < 0)
+ || (start + length > raf.length())) {
+ throw new IOException("Could not read block (block start: "
+ + start + ", block length: " + length
+ + ", data length: " + raf.length() + ").");
+ }
+
+ final byte[] ret = BinaryFunctions.getRAFBytes(raf, start, length,
+ "Could not read value from file");
+ canThrow = true;
+ return ret;
+ } finally {
+ IoUtils.closeQuietly(canThrow, raf);
+ }
+ }
+
+ @Override
+ public long getLength() {
+ return file.length();
+ }
+
+ @Override
+ public byte[] getAll() throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = new FileInputStream(file);
+ is = new BufferedInputStream(is);
+ final byte[] buffer = new byte[1024];
+ int read;
+ while ((read = is.read(buffer)) > 0) {
+ baos.write(buffer, 0, read);
+ }
+ final byte[] ret = baos.toByteArray();
+ canThrow = true;
+ return ret;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ @Override
+ public String getDescription() {
+ return "File: '" + file.getAbsolutePath() + "'";
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceInputStream.java b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceInputStream.java
new file mode 100644
index 0000000..f317056
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/bytesource/ByteSourceInputStream.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.bytesource;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.imaging.common.BinaryFunctions;
+
+public class ByteSourceInputStream extends ByteSource {
+ private final InputStream is;
+ private CacheBlock cacheHead;
+ private static final int BLOCK_SIZE = 1024;
+ private byte[] readBuffer;
+ private long streamLength = -1;
+
+ public ByteSourceInputStream(final InputStream is, final String filename) {
+ super(filename);
+ this.is = new BufferedInputStream(is);
+ }
+
+ private class CacheBlock {
+ public final byte[] bytes;
+ private CacheBlock next;
+ private boolean triedNext;
+
+ public CacheBlock(final byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ public CacheBlock getNext() throws IOException {
+ if (null != next) {
+ return next;
+ }
+ if (triedNext) {
+ return null;
+ }
+ triedNext = true;
+ next = readBlock();
+ return next;
+ }
+
+ }
+
+ private CacheBlock readBlock() throws IOException {
+ if (null == readBuffer) {
+ readBuffer = new byte[BLOCK_SIZE];
+ }
+
+ final int read = is.read(readBuffer);
+ if (read < 1) {
+ return null;
+ } else if (read < BLOCK_SIZE) {
+ // return a copy.
+ final byte[] result = new byte[read];
+ System.arraycopy(readBuffer, 0, result, 0, read);
+ return new CacheBlock(result);
+ } else {
+ // return current buffer.
+ final byte[] result = readBuffer;
+ readBuffer = null;
+ return new CacheBlock(result);
+ }
+ }
+
+ private CacheBlock getFirstBlock() throws IOException {
+ if (null == cacheHead) {
+ cacheHead = readBlock();
+ }
+ return cacheHead;
+ }
+
+ private class CacheReadingInputStream extends InputStream {
+ private CacheBlock block;
+ private boolean readFirst;
+ private int blockIndex;
+
+ @Override
+ public int read() throws IOException {
+ if (null == block) {
+ if (readFirst) {
+ return -1;
+ }
+ block = getFirstBlock();
+ readFirst = true;
+ }
+
+ if (block != null && blockIndex >= block.bytes.length) {
+ block = block.getNext();
+ blockIndex = 0;
+ }
+
+ if (null == block) {
+ return -1;
+ }
+
+ if (blockIndex >= block.bytes.length) {
+ return -1;
+ }
+
+ return 0xff & block.bytes[blockIndex++];
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ // first section copied verbatim from InputStream
+ if (b == null) {
+ throw new NullPointerException();
+ } else if ((off < 0) || (off > b.length) || (len < 0)
+ || ((off + len) > b.length) || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ // optimized block read
+
+ if (null == block) {
+ if (readFirst) {
+ return -1;
+ }
+ block = getFirstBlock();
+ readFirst = true;
+ }
+
+ if (block != null && blockIndex >= block.bytes.length) {
+ block = block.getNext();
+ blockIndex = 0;
+ }
+
+ if (null == block) {
+ return -1;
+ }
+
+ if (blockIndex >= block.bytes.length) {
+ return -1;
+ }
+
+ final int readSize = Math.min(len, block.bytes.length - blockIndex);
+ System.arraycopy(block.bytes, blockIndex, b, off, readSize);
+ blockIndex += readSize;
+ return readSize;
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+
+ long remaining = n;
+
+ if (n <= 0) {
+ return 0;
+ }
+
+ while (remaining > 0) {
+ // read the first block
+ if (null == block) {
+ if (readFirst) {
+ return -1;
+ }
+ block = getFirstBlock();
+ readFirst = true;
+ }
+
+ // get next block
+ if (block != null && blockIndex >= block.bytes.length) {
+ block = block.getNext();
+ blockIndex = 0;
+ }
+
+ if (null == block) {
+ break;
+ }
+
+ if (blockIndex >= block.bytes.length) {
+ break;
+ }
+
+ final int readSize = Math.min((int) Math.min(BLOCK_SIZE, remaining), block.bytes.length - blockIndex);
+
+ blockIndex += readSize;
+ remaining -= readSize;
+ }
+
+ return n - remaining;
+ }
+
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return new CacheReadingInputStream();
+ }
+
+ @Override
+ public byte[] getBlock(final long blockStart, final int blockLength) throws IOException {
+ // We include a separate check for int overflow.
+ if ((blockStart < 0) || (blockLength < 0)
+ || (blockStart + blockLength < 0)
+ || (blockStart + blockLength > streamLength)) {
+ throw new IOException("Could not read block (block start: "
+ + blockStart + ", block length: " + blockLength
+ + ", data length: " + streamLength + ").");
+ }
+
+ final InputStream cis = getInputStream();
+ BinaryFunctions.skipBytes(cis, blockStart);
+
+ final byte[] bytes = new byte[blockLength];
+ int total = 0;
+ while (true) {
+ final int read = cis.read(bytes, total, bytes.length - total);
+ if (read < 1) {
+ throw new IOException("Could not read block.");
+ }
+ total += read;
+ if (total >= blockLength) {
+ return bytes;
+ }
+ }
+ }
+
+ @Override
+ public long getLength() throws IOException {
+ if (streamLength >= 0) {
+ return streamLength;
+ }
+
+ final InputStream cis = getInputStream();
+ long result = 0;
+ long skipped;
+ while ((skipped = cis.skip(1024)) > 0) {
+ result += skipped;
+ }
+ streamLength = result;
+ return result;
+ }
+
+ @Override
+ public byte[] getAll() throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ CacheBlock block = getFirstBlock();
+ while (block != null) {
+ baos.write(block.bytes);
+ block = block.getNext();
+ }
+ return baos.toByteArray();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Inputstream: '" + filename + "'";
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/bytesource/package-info.java b/src/main/java/org/apache/commons/imaging/common/bytesource/package-info.java
new file mode 100644
index 0000000..f249845
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/bytesource/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Encapsulates sources from which data may be read.
+ */
+package org.apache.commons.imaging.common.bytesource;
+
diff --git a/src/main/java/org/apache/commons/imaging/common/itu_t4/BitArrayOutputStream.java b/src/main/java/org/apache/commons/imaging/common/itu_t4/BitArrayOutputStream.java
new file mode 100644
index 0000000..1f71ea6
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/itu_t4/BitArrayOutputStream.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.itu_t4;
+
+import java.io.OutputStream;
+
+/**
+ * Output stream writing to a byte array, and capable
+ * of writing 1 bit at a time, starting from the most significant bit.
+ */
+class BitArrayOutputStream extends OutputStream {
+ private byte[] buffer;
+ private int bytesWritten;
+ private int cache;
+ private int cacheMask = 0x80;
+
+ public BitArrayOutputStream() {
+ buffer = new byte[16];
+ }
+
+ public BitArrayOutputStream(final int size) {
+ buffer = new byte[size];
+ }
+
+ public int size() {
+ return bytesWritten;
+ }
+
+ public byte[] toByteArray() {
+ flush();
+ if (bytesWritten == buffer.length) {
+ return buffer;
+ }
+ final byte[] out = new byte[bytesWritten];
+ System.arraycopy(buffer, 0, out, 0, bytesWritten);
+ return out;
+ }
+
+ @Override
+ public void close() {
+ flush();
+ }
+
+ @Override
+ public void flush() {
+ if (cacheMask != 0x80) {
+ writeByte(cache);
+ cache = 0;
+ cacheMask = 0x80;
+ }
+ }
+
+ @Override
+ public void write(final int b) {
+ flush();
+ writeByte(b);
+ }
+
+ public void writeBit(final int bit) {
+ if (bit != 0) {
+ cache |= cacheMask;
+ }
+ cacheMask >>>= 1;
+ if (cacheMask == 0) {
+ flush();
+ }
+ }
+
+ public int getBitsAvailableInCurrentByte() {
+ int count = 0;
+ for (int mask = cacheMask; mask != 0; mask >>>= 1) {
+ ++count;
+ }
+ return count;
+ }
+
+ private void writeByte(final int b) {
+ if (bytesWritten >= buffer.length) {
+ final byte[] bigger = new byte[buffer.length * 2];
+ System.arraycopy(buffer, 0, bigger, 0, bytesWritten);
+ buffer = bigger;
+ }
+ buffer[bytesWritten++] = (byte) b;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/common/BitInputStreamFlexible.java b/src/main/java/org/apache/commons/imaging/common/itu_t4/BitInputStreamFlexible.java
similarity index 61%
rename from src/main/java/org/apache/sanselan/common/BitInputStreamFlexible.java
rename to src/main/java/org/apache/commons/imaging/common/itu_t4/BitInputStreamFlexible.java
index 6f85b8b..a321ac2 100644
--- a/src/main/java/org/apache/sanselan/common/BitInputStreamFlexible.java
+++ b/src/main/java/org/apache/commons/imaging/common/itu_t4/BitInputStreamFlexible.java
@@ -1,114 +1,108 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class BitInputStreamFlexible extends InputStream
- implements
- BinaryConstants
-{
- // TODO should be byte order conscious, ie TIFF for reading
- // samples size<8 - shuoldn't that effect their order within byte?
- private final InputStream is;
-
- public BitInputStreamFlexible(InputStream is)
- {
- this.is = is;
- // super(is);
- }
-
- public int read() throws IOException
- {
- if (cacheBitsRemaining > 0)
- throw new IOException("BitInputStream: incomplete bit read");
- return is.read();
- }
-
- private int cache;
- private int cacheBitsRemaining = 0;
- private long bytesRead = 0;
-
- public final int readBits(int count) throws IOException
- {
-
- if (count <= 32) // catch-all
- {
- int result = 0;
- // int done = 0;
-
- if (cacheBitsRemaining > 0)
- {
- if (count >= cacheBitsRemaining)
- {
- result = ((1 << cacheBitsRemaining) - 1) & cache;
- count -= cacheBitsRemaining;
- cacheBitsRemaining = 0;
- }
- else
- {
- // cache >>= count;
- cacheBitsRemaining -= count;
- result = ((1 << count) - 1) & (cache >> cacheBitsRemaining);
- count = 0;
- }
- }
- while (count >= 8)
- {
- cache = is.read();
- if (cache < 0)
- throw new IOException("couldn't read bits");
- System.out.println("cache 1: " + cache + " ("
- + Integer.toHexString(cache) + ", "
- + Integer.toBinaryString(cache) + ")");
- bytesRead++;
- result = (result << 8) | (0xff & cache);
- count -= 8;
- }
- if (count > 0)
- {
- cache = is.read();
- if (cache < 0)
- throw new IOException("couldn't read bits");
- System.out.println("cache 2: " + cache + " ("
- + Integer.toHexString(cache) + ", "
- + Integer.toBinaryString(cache) + ")");
- bytesRead++;
- cacheBitsRemaining = 8 - count;
- result = (result << count)
- | (((1 << count) - 1) & (cache >> cacheBitsRemaining));
- count = 0;
- }
-
- return result;
- }
-
- throw new IOException("BitInputStream: unknown error");
-
- }
-
- public void flushCache()
- {
- cacheBitsRemaining = 0;
- }
-
- public long getBytesRead()
- {
- return bytesRead;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.itu_t4;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Input stream that allows reading up to 32 bits
+ * across byte boundaries in most significant
+ * bit first order.
+ */
+class BitInputStreamFlexible extends InputStream {
+ // TODO should be byte order conscious, ie TIFF for reading
+ // samples size<8 - shuoldn't that effect their order within byte?
+ private final InputStream is;
+ private int cache;
+ private int cacheBitsRemaining;
+ private long bytesRead;
+
+ public BitInputStreamFlexible(final InputStream is) {
+ this.is = is;
+ // super(is);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (cacheBitsRemaining > 0) {
+ throw new IOException("BitInputStream: incomplete bit read");
+ }
+ return is.read();
+ }
+
+ public final int readBits(int count) throws IOException {
+
+ if (count <= 32) {
+ // catch-all
+ int result = 0;
+ // int done = 0;
+
+ if (cacheBitsRemaining > 0) {
+ if (count >= cacheBitsRemaining) {
+ result = ((1 << cacheBitsRemaining) - 1) & cache;
+ count -= cacheBitsRemaining;
+ cacheBitsRemaining = 0;
+ } else {
+ // cache >>= count;
+ cacheBitsRemaining -= count;
+ result = ((1 << count) - 1) & (cache >> cacheBitsRemaining);
+ count = 0;
+ }
+ }
+ while (count >= 8) {
+ cache = is.read();
+ if (cache < 0) {
+ throw new IOException("couldn't read bits");
+ }
+ // System.out.println("cache 1: " + cache + " ("
+ // + Integer.toHexString(cache) + ", "
+ // + Integer.toBinaryString(cache) + ")");
+ bytesRead++;
+ result = (result << 8) | (0xff & cache);
+ count -= 8;
+ }
+ if (count > 0) {
+ cache = is.read();
+ if (cache < 0) {
+ throw new IOException("couldn't read bits");
+ }
+ // System.out.println("cache 2: " + cache + " ("
+ // + Integer.toHexString(cache) + ", "
+ // + Integer.toBinaryString(cache) + ")");
+ bytesRead++;
+ cacheBitsRemaining = 8 - count;
+ result = (result << count)
+ | (((1 << count) - 1) & (cache >> cacheBitsRemaining));
+ count = 0;
+ }
+
+ return result;
+ }
+
+ throw new IOException("BitInputStream: unknown error");
+
+ }
+
+ public void flushCache() {
+ cacheBitsRemaining = 0;
+ }
+
+ public long getBytesRead() {
+ return bytesRead;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/itu_t4/HuffmanTree.java b/src/main/java/org/apache/commons/imaging/common/itu_t4/HuffmanTree.java
new file mode 100644
index 0000000..119263e
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/itu_t4/HuffmanTree.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.itu_t4;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A Huffman tree implemented as 1 array for high locality of reference.
+ */
+class HuffmanTree {
+ private final List> nodes = new ArrayList>();
+
+ private final static class Node {
+ boolean empty = true;
+ T value;
+ }
+
+ public final void insert(final String pattern, final T value) throws HuffmanTreeException {
+ int position = 0;
+ Node node = growAndGetNode(position);
+ if (node.value != null) {
+ throw new HuffmanTreeException("Can't add child to a leaf");
+ }
+ for (int patternPosition = 0; patternPosition < pattern.length(); patternPosition++) {
+ final char nextChar = pattern.charAt(patternPosition);
+ if (nextChar == '0') {
+ position = (position << 1) + 1;
+ } else {
+ position = (position + 1) << 1;
+ }
+ node = growAndGetNode(position);
+ if (node.value != null) {
+ throw new HuffmanTreeException("Can't add child to a leaf");
+ }
+ }
+ node.value = value;
+ }
+
+ private Node growAndGetNode(final int position) {
+ while (position >= nodes.size()) {
+ nodes.add(new Node());
+ }
+ final Node node = nodes.get(position);
+ node.empty = false;
+ return node;
+ }
+
+ public final T decode(final BitInputStreamFlexible bitStream) throws HuffmanTreeException {
+ int position = 0;
+ Node node = nodes.get(0);
+ while (node.value == null) {
+ int nextBit;
+ try {
+ nextBit = bitStream.readBits(1);
+ } catch (final IOException ioEx) {
+ throw new HuffmanTreeException(
+ "Error reading stream for huffman tree", ioEx);
+ }
+ if (nextBit == 0) {
+ position = (position << 1) + 1;
+ } else {
+ position = (position + 1) << 1;
+ }
+ if (position >= nodes.size()) {
+ throw new HuffmanTreeException("Invalid bit pattern");
+ }
+ node = nodes.get(position);
+ if (node.empty) {
+ throw new HuffmanTreeException("Invalid bit pattern");
+ }
+ }
+ return node.value;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/ImageWriteException.java b/src/main/java/org/apache/commons/imaging/common/itu_t4/HuffmanTreeException.java
similarity index 70%
rename from src/main/java/org/apache/sanselan/ImageWriteException.java
rename to src/main/java/org/apache/commons/imaging/common/itu_t4/HuffmanTreeException.java
index 05b0ccd..ded4a5b 100644
--- a/src/main/java/org/apache/sanselan/ImageWriteException.java
+++ b/src/main/java/org/apache/commons/imaging/common/itu_t4/HuffmanTreeException.java
@@ -1,32 +1,29 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan;
-
-public class ImageWriteException extends SanselanException
-{
- static final long serialVersionUID = -1L;
-
- public ImageWriteException(String s)
- {
- super(s);
- }
-
- public ImageWriteException(String s, Exception e)
- {
- super(s, e);
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.itu_t4;
+
+class HuffmanTreeException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public HuffmanTreeException(final String message) {
+ super(message);
+ }
+
+ public HuffmanTreeException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/itu_t4/T4AndT6Compression.java b/src/main/java/org/apache/commons/imaging/common/itu_t4/T4AndT6Compression.java
new file mode 100644
index 0000000..864b3cf
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/itu_t4/T4AndT6Compression.java
@@ -0,0 +1,754 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.itu_t4;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.ImageWriteException;
+import org.apache.commons.imaging.common.itu_t4.T4_T6_Tables.Entry;
+import org.apache.commons.imaging.util.IoUtils;
+
+public final class T4AndT6Compression {
+ private static final HuffmanTree WHITE_RUN_LENGTHS = new HuffmanTree();
+ private static final HuffmanTree BLACK_RUN_LENGTHS = new HuffmanTree();
+ private static final HuffmanTree CONTROL_CODES = new HuffmanTree();
+
+ public static final int WHITE = 0;
+ public static final int BLACK = 1;
+
+ static {
+ try {
+ for (final Entry entry : T4_T6_Tables.WHITE_TERMINATING_CODES) {
+ WHITE_RUN_LENGTHS.insert(entry.bitString, entry.value);
+ }
+ for (final Entry entry : T4_T6_Tables.WHITE_MAKE_UP_CODES) {
+ WHITE_RUN_LENGTHS.insert(entry.bitString, entry.value);
+ }
+ for (final Entry entry : T4_T6_Tables.BLACK_TERMINATING_CODES) {
+ BLACK_RUN_LENGTHS.insert(entry.bitString, entry.value);
+ }
+ for (final Entry entry : T4_T6_Tables.BLACK_MAKE_UP_CODES) {
+ BLACK_RUN_LENGTHS.insert(entry.bitString, entry.value);
+ }
+ for (final Entry entry : T4_T6_Tables.ADDITIONAL_MAKE_UP_CODES) {
+ WHITE_RUN_LENGTHS.insert(entry.bitString, entry.value);
+ BLACK_RUN_LENGTHS.insert(entry.bitString, entry.value);
+ }
+ CONTROL_CODES.insert(T4_T6_Tables.EOL.bitString, T4_T6_Tables.EOL);
+ CONTROL_CODES.insert(T4_T6_Tables.EOL13.bitString, T4_T6_Tables.EOL13);
+ CONTROL_CODES.insert(T4_T6_Tables.EOL14.bitString, T4_T6_Tables.EOL14);
+ CONTROL_CODES.insert(T4_T6_Tables.EOL15.bitString, T4_T6_Tables.EOL15);
+ CONTROL_CODES.insert(T4_T6_Tables.EOL16.bitString, T4_T6_Tables.EOL16);
+ CONTROL_CODES.insert(T4_T6_Tables.EOL17.bitString, T4_T6_Tables.EOL17);
+ CONTROL_CODES.insert(T4_T6_Tables.EOL18.bitString, T4_T6_Tables.EOL18);
+ CONTROL_CODES.insert(T4_T6_Tables.EOL19.bitString, T4_T6_Tables.EOL19);
+ CONTROL_CODES.insert(T4_T6_Tables.P.bitString, T4_T6_Tables.P);
+ CONTROL_CODES.insert(T4_T6_Tables.H.bitString, T4_T6_Tables.H);
+ CONTROL_CODES.insert(T4_T6_Tables.V0.bitString, T4_T6_Tables.V0);
+ CONTROL_CODES.insert(T4_T6_Tables.VL1.bitString, T4_T6_Tables.VL1);
+ CONTROL_CODES.insert(T4_T6_Tables.VL2.bitString, T4_T6_Tables.VL2);
+ CONTROL_CODES.insert(T4_T6_Tables.VL3.bitString, T4_T6_Tables.VL3);
+ CONTROL_CODES.insert(T4_T6_Tables.VR1.bitString, T4_T6_Tables.VR1);
+ CONTROL_CODES.insert(T4_T6_Tables.VR2.bitString, T4_T6_Tables.VR2);
+ CONTROL_CODES.insert(T4_T6_Tables.VR3.bitString, T4_T6_Tables.VR3);
+ } catch (final HuffmanTreeException cannotHappen) {
+ throw new Error(cannotHappen);
+ }
+ }
+
+ private T4AndT6Compression() {
+ }
+
+ private static void compress1DLine(final BitInputStreamFlexible inputStream,
+ final BitArrayOutputStream outputStream, final int[] referenceLine, final int width)
+ throws ImageWriteException {
+ int color = WHITE;
+ int runLength = 0;
+
+ for (int x = 0; x < width; x++) {
+ try {
+ final int nextColor = inputStream.readBits(1);
+ if (referenceLine != null) {
+ referenceLine[x] = nextColor;
+ }
+ if (color == nextColor) {
+ ++runLength;
+ } else {
+ writeRunLength(outputStream, runLength, color);
+ color = nextColor;
+ runLength = 1;
+ }
+ } catch (final IOException ioException) {
+ throw new ImageWriteException("Error reading image to compress", ioException);
+ }
+ }
+
+ writeRunLength(outputStream, runLength, color);
+ }
+
+ /**
+ * Compressed with the "Modified Huffman" encoding of section 10 in the
+ * TIFF6 specification. No EOLs, no RTC, rows are padded to end on a byte
+ * boundary.
+ *
+ * @param uncompressed
+ * @param width
+ * @param height
+ * @return the compressed data
+ * @throws ImageWriteException
+ */
+ public static byte[] compressModifiedHuffman(final byte[] uncompressed,
+ final int width, final int height) throws ImageWriteException {
+ final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed));
+ final BitArrayOutputStream outputStream = new BitArrayOutputStream();
+ for (int y = 0; y < height; y++) {
+ compress1DLine(inputStream, outputStream, null, width);
+ inputStream.flushCache();
+ outputStream.flush();
+ }
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Decompresses the "Modified Huffman" encoding of section 10 in the TIFF6
+ * specification. No EOLs, no RTC, rows are padded to end on a byte
+ * boundary.
+ *
+ * @param compressed
+ * @param width
+ * @param height
+ * @return the decompressed data
+ * @throws ImageReadException
+ */
+ public static byte[] decompressModifiedHuffman(final byte[] compressed,
+ final int width, final int height) throws ImageReadException {
+ final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(
+ new ByteArrayInputStream(compressed));
+ BitArrayOutputStream outputStream = null;
+ boolean canThrow = false;
+ try {
+ outputStream = new BitArrayOutputStream();
+ for (int y = 0; y < height; y++) {
+ int color = WHITE;
+ int rowLength;
+ for (rowLength = 0; rowLength < width;) {
+ final int runLength = readTotalRunLength(inputStream, color);
+ for (int i = 0; i < runLength; i++) {
+ outputStream.writeBit(color);
+ }
+ color = 1 - color;
+ rowLength += runLength;
+ }
+
+ if (rowLength == width) {
+ inputStream.flushCache();
+ outputStream.flush();
+ } else if (rowLength > width) {
+ throw new ImageReadException("Unrecoverable row length error in image row " + y);
+ }
+ }
+ final byte[] ret = outputStream.toByteArray();
+ canThrow = true;
+ return ret;
+ } finally {
+ try {
+ IoUtils.closeQuietly(canThrow, outputStream);
+ } catch (final IOException ioException) {
+ throw new ImageReadException("I/O error", ioException);
+ }
+ }
+ }
+
+ public static byte[] compressT4_1D(final byte[] uncompressed, final int width,
+ final int height, final boolean hasFill) throws ImageWriteException {
+ final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed));
+ final BitArrayOutputStream outputStream = new BitArrayOutputStream();
+ if (hasFill) {
+ T4_T6_Tables.EOL16.writeBits(outputStream);
+ } else {
+ T4_T6_Tables.EOL.writeBits(outputStream);
+ }
+
+ for (int y = 0; y < height; y++) {
+ compress1DLine(inputStream, outputStream, null, width);
+ if (hasFill) {
+ int bitsAvailable = outputStream
+ .getBitsAvailableInCurrentByte();
+ if (bitsAvailable < 4) {
+ outputStream.flush();
+ bitsAvailable = 8;
+ }
+ for (; bitsAvailable > 4; bitsAvailable--) {
+ outputStream.writeBit(0);
+ }
+ }
+ T4_T6_Tables.EOL.writeBits(outputStream);
+ inputStream.flushCache();
+ }
+
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Decompresses T.4 1D encoded data. EOL at the beginning and after each
+ * row, can be preceded by fill bits to fit on a byte boundary, no RTC.
+ *
+ * @param compressed
+ * @param width
+ * @param height
+ * @return the decompressed data
+ * @throws ImageReadException
+ */
+ public static byte[] decompressT4_1D(final byte[] compressed, final int width,
+ final int height, final boolean hasFill) throws ImageReadException {
+ final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(compressed));
+ BitArrayOutputStream outputStream = null;
+ boolean canThrow = false;
+ try {
+ outputStream = new BitArrayOutputStream();
+ for (int y = 0; y < height; y++) {
+ int rowLength;
+ try {
+ T4_T6_Tables.Entry entry = CONTROL_CODES.decode(inputStream);
+ if (!isEOL(entry, hasFill)) {
+ throw new ImageReadException("Expected EOL not found");
+ }
+ int color = WHITE;
+ for (rowLength = 0; rowLength < width;) {
+ final int runLength = readTotalRunLength(inputStream, color);
+ for (int i = 0; i < runLength; i++) {
+ outputStream.writeBit(color);
+ }
+ color = 1 - color;
+ rowLength += runLength;
+ }
+ } catch (final HuffmanTreeException huffmanException) {
+ throw new ImageReadException("Decompression error", huffmanException);
+ }
+
+ if (rowLength == width) {
+ outputStream.flush();
+ } else if (rowLength > width) {
+ throw new ImageReadException("Unrecoverable row length error in image row " + y);
+ }
+ }
+ final byte[] ret = outputStream.toByteArray();
+ canThrow = true;
+ return ret;
+ } finally {
+ try {
+ IoUtils.closeQuietly(canThrow, outputStream);
+ } catch (final IOException ioException) {
+ throw new ImageReadException("I/O error", ioException);
+ }
+ }
+ }
+
+ public static byte[] compressT4_2D(final byte[] uncompressed, final int width,
+ final int height, final boolean hasFill, final int parameterK)
+ throws ImageWriteException {
+ final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed));
+ final BitArrayOutputStream outputStream = new BitArrayOutputStream();
+ int[] referenceLine = new int[width];
+ int[] codingLine = new int[width];
+ int kCounter = 0;
+ if (hasFill) {
+ T4_T6_Tables.EOL16.writeBits(outputStream);
+ } else {
+ T4_T6_Tables.EOL.writeBits(outputStream);
+ }
+
+ for (int y = 0; y < height; y++) {
+ if (kCounter > 0) {
+ // 2D
+ outputStream.writeBit(0);
+ for (int i = 0; i < width; i++) {
+ try {
+ codingLine[i] = inputStream.readBits(1);
+ } catch (final IOException ioException) {
+ throw new ImageWriteException("Error reading image to compress", ioException);
+ }
+ }
+ int codingA0Color = WHITE;
+ int referenceA0Color = WHITE;
+ int a1 = nextChangingElement(codingLine, codingA0Color, 0);
+ int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
+ int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ for (int a0 = 0; a0 < width;) {
+ if (b2 < a1) {
+ T4_T6_Tables.P.writeBits(outputStream);
+ a0 = b2;
+ } else {
+ final int a1b1 = a1 - b1;
+ if (-3 <= a1b1 && a1b1 <= 3) {
+ T4_T6_Tables.Entry entry;
+ if (a1b1 == -3) {
+ entry = T4_T6_Tables.VL3;
+ } else if (a1b1 == -2) {
+ entry = T4_T6_Tables.VL2;
+ } else if (a1b1 == -1) {
+ entry = T4_T6_Tables.VL1;
+ } else if (a1b1 == 0) {
+ entry = T4_T6_Tables.V0;
+ } else if (a1b1 == 1) {
+ entry = T4_T6_Tables.VR1;
+ } else if (a1b1 == 2) {
+ entry = T4_T6_Tables.VR2;
+ } else {
+ entry = T4_T6_Tables.VR3;
+ }
+ entry.writeBits(outputStream);
+ codingA0Color = 1 - codingA0Color;
+ a0 = a1;
+ } else {
+ final int a2 = nextChangingElement(codingLine, 1 - codingA0Color, a1 + 1);
+ final int a0a1 = a1 - a0;
+ final int a1a2 = a2 - a1;
+ T4_T6_Tables.H.writeBits(outputStream);
+ writeRunLength(outputStream, a0a1, codingA0Color);
+ writeRunLength(outputStream, a1a2, 1 - codingA0Color);
+ a0 = a2;
+ }
+ }
+ referenceA0Color = changingElementAt(referenceLine, a0);
+ a1 = nextChangingElement(codingLine, codingA0Color, a0 + 1);
+ if (codingA0Color == referenceA0Color) {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ } else {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ }
+ b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
+ }
+ final int[] swap = referenceLine;
+ referenceLine = codingLine;
+ codingLine = swap;
+ } else {
+ // 1D
+ outputStream.writeBit(1);
+ compress1DLine(inputStream, outputStream, referenceLine, width);
+ }
+ if (hasFill) {
+ int bitsAvailable = outputStream
+ .getBitsAvailableInCurrentByte();
+ if (bitsAvailable < 4) {
+ outputStream.flush();
+ bitsAvailable = 8;
+ }
+ for (; bitsAvailable > 4; bitsAvailable--) {
+ outputStream.writeBit(0);
+ }
+ }
+ T4_T6_Tables.EOL.writeBits(outputStream);
+ kCounter++;
+ if (kCounter == parameterK) {
+ kCounter = 0;
+ }
+ inputStream.flushCache();
+ }
+
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Decompressed T.4 2D encoded data. EOL at the beginning and after each
+ * row, can be preceded by fill bits to fit on a byte boundary, and is
+ * succeeded by a tag bit determining whether the next line is encoded using
+ * 1D or 2D. No RTC.
+ *
+ * @param compressed
+ * @param width
+ * @param height
+ * @return the decompressed data
+ * @throws ImageReadException
+ */
+ public static byte[] decompressT4_2D(final byte[] compressed, final int width,
+ final int height, final boolean hasFill) throws ImageReadException {
+ final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(compressed));
+ final BitArrayOutputStream outputStream = new BitArrayOutputStream();
+ final int[] referenceLine = new int[width];
+ for (int y = 0; y < height; y++) {
+ int rowLength = 0;
+ try {
+ T4_T6_Tables.Entry entry = CONTROL_CODES.decode(inputStream);
+ if (!isEOL(entry, hasFill)) {
+ throw new ImageReadException("Expected EOL not found");
+ }
+ final int tagBit = inputStream.readBits(1);
+ if (tagBit == 0) {
+ // 2D
+ int codingA0Color = WHITE;
+ int referenceA0Color = WHITE;
+ int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
+ int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ for (int a0 = 0; a0 < width;) {
+ int a1;
+ int a2;
+ entry = CONTROL_CODES.decode(inputStream);
+ if (entry == T4_T6_Tables.P) {
+ fillRange(outputStream, referenceLine, a0, b2, codingA0Color);
+ a0 = b2;
+ } else if (entry == T4_T6_Tables.H) {
+ final int a0a1 = readTotalRunLength(inputStream, codingA0Color);
+ a1 = a0 + a0a1;
+ fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
+ final int a1a2 = readTotalRunLength(inputStream, 1 - codingA0Color);
+ a2 = a1 + a1a2;
+ fillRange(outputStream, referenceLine, a1, a2, 1 - codingA0Color);
+ a0 = a2;
+ } else {
+ int a1b1;
+ if (entry == T4_T6_Tables.V0) {
+ a1b1 = 0;
+ } else if (entry == T4_T6_Tables.VL1) {
+ a1b1 = -1;
+ } else if (entry == T4_T6_Tables.VL2) {
+ a1b1 = -2;
+ } else if (entry == T4_T6_Tables.VL3) {
+ a1b1 = -3;
+ } else if (entry == T4_T6_Tables.VR1) {
+ a1b1 = 1;
+ } else if (entry == T4_T6_Tables.VR2) {
+ a1b1 = 2;
+ } else if (entry == T4_T6_Tables.VR3) {
+ a1b1 = 3;
+ } else {
+ throw new ImageReadException("Invalid/unknown T.4 control code " + entry.bitString);
+ }
+ a1 = b1 + a1b1;
+ fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
+ a0 = a1;
+ codingA0Color = 1 - codingA0Color;
+ }
+ referenceA0Color = changingElementAt(referenceLine, a0);
+ if (codingA0Color == referenceA0Color) {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ } else {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ }
+ b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
+ rowLength = a0;
+ }
+ } else {
+ // 1D
+ int color = WHITE;
+ for (rowLength = 0; rowLength < width;) {
+ final int runLength = readTotalRunLength(inputStream, color);
+ for (int i = 0; i < runLength; i++) {
+ outputStream.writeBit(color);
+ referenceLine[rowLength + i] = color;
+ }
+ color = 1 - color;
+ rowLength += runLength;
+ }
+ }
+ } catch (final IOException ioException) {
+ throw new ImageReadException("Decompression error", ioException);
+ } catch (final HuffmanTreeException huffmanException) {
+ throw new ImageReadException("Decompression error", huffmanException);
+ }
+
+ if (rowLength == width) {
+ outputStream.flush();
+ } else if (rowLength > width) {
+ throw new ImageReadException("Unrecoverable row length error in image row " + y);
+ }
+ }
+
+ return outputStream.toByteArray();
+ }
+
+ public static byte[] compressT6(final byte[] uncompressed, final int width, final int height)
+ throws ImageWriteException {
+ BitInputStreamFlexible inputStream = null;
+ boolean canThrow = false;
+ try {
+ inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(uncompressed));
+ final BitArrayOutputStream outputStream = new BitArrayOutputStream();
+ int[] referenceLine = new int[width];
+ int[] codingLine = new int[width];
+ for (int y = 0; y < height; y++) {
+ for (int i = 0; i < width; i++) {
+ try {
+ codingLine[i] = inputStream.readBits(1);
+ } catch (final IOException ioException) {
+ throw new ImageWriteException("Error reading image to compress", ioException);
+ }
+ }
+ int codingA0Color = WHITE;
+ int referenceA0Color = WHITE;
+ int a1 = nextChangingElement(codingLine, codingA0Color, 0);
+ int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
+ int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ for (int a0 = 0; a0 < width;) {
+ if (b2 < a1) {
+ T4_T6_Tables.P.writeBits(outputStream);
+ a0 = b2;
+ } else {
+ final int a1b1 = a1 - b1;
+ if (-3 <= a1b1 && a1b1 <= 3) {
+ T4_T6_Tables.Entry entry;
+ if (a1b1 == -3) {
+ entry = T4_T6_Tables.VL3;
+ } else if (a1b1 == -2) {
+ entry = T4_T6_Tables.VL2;
+ } else if (a1b1 == -1) {
+ entry = T4_T6_Tables.VL1;
+ } else if (a1b1 == 0) {
+ entry = T4_T6_Tables.V0;
+ } else if (a1b1 == 1) {
+ entry = T4_T6_Tables.VR1;
+ } else if (a1b1 == 2) {
+ entry = T4_T6_Tables.VR2;
+ } else {
+ entry = T4_T6_Tables.VR3;
+ }
+ entry.writeBits(outputStream);
+ codingA0Color = 1 - codingA0Color;
+ a0 = a1;
+ } else {
+ final int a2 = nextChangingElement(codingLine, 1 - codingA0Color, a1 + 1);
+ final int a0a1 = a1 - a0;
+ final int a1a2 = a2 - a1;
+ T4_T6_Tables.H.writeBits(outputStream);
+ writeRunLength(outputStream, a0a1, codingA0Color);
+ writeRunLength(outputStream, a1a2, 1 - codingA0Color);
+ a0 = a2;
+ }
+ }
+ referenceA0Color = changingElementAt(referenceLine, a0);
+ a1 = nextChangingElement(codingLine, codingA0Color, a0 + 1);
+ if (codingA0Color == referenceA0Color) {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ } else {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ }
+ b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
+ }
+ final int[] swap = referenceLine;
+ referenceLine = codingLine;
+ codingLine = swap;
+ inputStream.flushCache();
+ }
+ // EOFB
+ T4_T6_Tables.EOL.writeBits(outputStream);
+ T4_T6_Tables.EOL.writeBits(outputStream);
+ final byte[] ret = outputStream.toByteArray();
+ canThrow = true;
+ return ret;
+ } finally {
+ try {
+ IoUtils.closeQuietly(canThrow, inputStream);
+ } catch (final IOException ioException) {
+ throw new ImageWriteException("I/O error", ioException);
+ }
+ }
+ }
+
+ /**
+ * Decompress T.6 encoded data. No EOLs, except for 2 consecutive ones at
+ * the end (the EOFB, end of fax block). No RTC. No fill bits anywhere. All
+ * data is 2D encoded.
+ *
+ * @param compressed
+ * @param width
+ * @param height
+ * @return the decompressed data
+ * @throws ImageReadException
+ */
+ public static byte[] decompressT6(final byte[] compressed, final int width, final int height)
+ throws ImageReadException {
+ final BitInputStreamFlexible inputStream = new BitInputStreamFlexible(new ByteArrayInputStream(compressed));
+ final BitArrayOutputStream outputStream = new BitArrayOutputStream();
+ final int[] referenceLine = new int[width];
+ for (int y = 0; y < height; y++) {
+ int rowLength = 0;
+ try {
+ int codingA0Color = WHITE;
+ int referenceA0Color = WHITE;
+ int b1 = nextChangingElement(referenceLine, referenceA0Color, 0);
+ int b2 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ for (int a0 = 0; a0 < width;) {
+ int a1;
+ int a2;
+ T4_T6_Tables.Entry entry = CONTROL_CODES.decode(inputStream);
+ if (entry == T4_T6_Tables.P) {
+ fillRange(outputStream, referenceLine, a0, b2, codingA0Color);
+ a0 = b2;
+ } else if (entry == T4_T6_Tables.H) {
+ final int a0a1 = readTotalRunLength(inputStream, codingA0Color);
+ a1 = a0 + a0a1;
+ fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
+ final int a1a2 = readTotalRunLength(inputStream, 1 - codingA0Color);
+ a2 = a1 + a1a2;
+ fillRange(outputStream, referenceLine, a1, a2, 1 - codingA0Color);
+ a0 = a2;
+ } else {
+ int a1b1;
+ if (entry == T4_T6_Tables.V0) {
+ a1b1 = 0;
+ } else if (entry == T4_T6_Tables.VL1) {
+ a1b1 = -1;
+ } else if (entry == T4_T6_Tables.VL2) {
+ a1b1 = -2;
+ } else if (entry == T4_T6_Tables.VL3) {
+ a1b1 = -3;
+ } else if (entry == T4_T6_Tables.VR1) {
+ a1b1 = 1;
+ } else if (entry == T4_T6_Tables.VR2) {
+ a1b1 = 2;
+ } else if (entry == T4_T6_Tables.VR3) {
+ a1b1 = 3;
+ } else {
+ throw new ImageReadException("Invalid/unknown T.6 control code " + entry.bitString);
+ }
+ a1 = b1 + a1b1;
+ fillRange(outputStream, referenceLine, a0, a1, codingA0Color);
+ a0 = a1;
+ codingA0Color = 1 - codingA0Color;
+ }
+ referenceA0Color = changingElementAt(referenceLine, a0);
+ if (codingA0Color == referenceA0Color) {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ } else {
+ b1 = nextChangingElement(referenceLine, referenceA0Color, a0 + 1);
+ b1 = nextChangingElement(referenceLine, 1 - referenceA0Color, b1 + 1);
+ }
+ b2 = nextChangingElement(referenceLine, 1 - codingA0Color, b1 + 1);
+ rowLength = a0;
+ }
+ } catch (final HuffmanTreeException huffmanException) {
+ throw new ImageReadException("Decompression error", huffmanException);
+ }
+
+ if (rowLength == width) {
+ outputStream.flush();
+ } else if (rowLength > width) {
+ throw new ImageReadException("Unrecoverable row length error in image row " + y);
+ }
+ }
+
+ return outputStream.toByteArray();
+ }
+
+ private static boolean isEOL(final T4_T6_Tables.Entry entry, final boolean hasFill) {
+ if (entry == T4_T6_Tables.EOL) {
+ return true;
+ }
+ if (hasFill) {
+ return entry == T4_T6_Tables.EOL13 || entry == T4_T6_Tables.EOL14
+ || entry == T4_T6_Tables.EOL15
+ || entry == T4_T6_Tables.EOL16
+ || entry == T4_T6_Tables.EOL17
+ || entry == T4_T6_Tables.EOL18
+ || entry == T4_T6_Tables.EOL19;
+ }
+ return false;
+ }
+
+ private static void writeRunLength(final BitArrayOutputStream bitStream,
+ int runLength, final int color) {
+ final T4_T6_Tables.Entry[] makeUpCodes;
+ final T4_T6_Tables.Entry[] terminatingCodes;
+ if (color == WHITE) {
+ makeUpCodes = T4_T6_Tables.WHITE_MAKE_UP_CODES;
+ terminatingCodes = T4_T6_Tables.WHITE_TERMINATING_CODES;
+ } else {
+ makeUpCodes = T4_T6_Tables.BLACK_MAKE_UP_CODES;
+ terminatingCodes = T4_T6_Tables.BLACK_TERMINATING_CODES;
+ }
+ while (runLength >= 1792) {
+ final T4_T6_Tables.Entry entry = lowerBound(
+ T4_T6_Tables.ADDITIONAL_MAKE_UP_CODES, runLength);
+ entry.writeBits(bitStream);
+ runLength -= entry.value;
+ }
+ while (runLength >= 64) {
+ final T4_T6_Tables.Entry entry = lowerBound(makeUpCodes, runLength);
+ entry.writeBits(bitStream);
+ runLength -= entry.value;
+ }
+ final T4_T6_Tables.Entry terminatingEntry = terminatingCodes[runLength];
+ terminatingEntry.writeBits(bitStream);
+ }
+
+ private static T4_T6_Tables.Entry lowerBound(final T4_T6_Tables.Entry[] entries, final int value) {
+ int first = 0;
+ int last = entries.length - 1;
+ do {
+ final int middle = (first + last) >>> 1;
+ if (entries[middle].value <= value
+ && ((middle + 1) >= entries.length || value < entries[middle + 1].value)) {
+ return entries[middle];
+ } else if (entries[middle].value > value) {
+ last = middle - 1;
+ } else {
+ first = middle + 1;
+ }
+ } while (first < last);
+
+ return entries[first];
+ }
+
+ private static int readTotalRunLength(final BitInputStreamFlexible bitStream,
+ final int color) throws ImageReadException {
+ try {
+ int totalLength = 0;
+ Integer runLength;
+ do {
+ if (color == WHITE) {
+ runLength = WHITE_RUN_LENGTHS.decode(bitStream);
+ } else {
+ runLength = BLACK_RUN_LENGTHS.decode(bitStream);
+ }
+ totalLength += runLength;
+ } while (runLength > 63);
+ return totalLength;
+ } catch (final HuffmanTreeException huffmanException) {
+ throw new ImageReadException("Decompression error", huffmanException);
+ }
+ }
+
+ private static int changingElementAt(final int[] line, final int position) {
+ if (position < 0 || position >= line.length) {
+ return WHITE;
+ }
+ return line[position];
+ }
+
+ private static int nextChangingElement(final int[] line, final int currentColour, final int start) {
+ int position;
+ for (position = start; position < line.length
+ && line[position] == currentColour; position++) {
+ // noop
+ }
+
+ return position < line.length ? position : line.length;
+ }
+
+ private static void fillRange(final BitArrayOutputStream outputStream,
+ final int[] referenceRow, final int a0, final int end, final int color) {
+ for (int i = a0; i < end; i++) {
+ referenceRow[i] = color;
+ outputStream.writeBit(color);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/itu_t4/T4_T6_Tables.java b/src/main/java/org/apache/commons/imaging/common/itu_t4/T4_T6_Tables.java
new file mode 100644
index 0000000..f396c3d
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/itu_t4/T4_T6_Tables.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.itu_t4;
+
+class T4_T6_Tables {
+ public static final Entry[] WHITE_TERMINATING_CODES = {
+ new Entry("00110101", 0),
+ new Entry("000111", 1),
+ new Entry("0111", 2),
+ new Entry("1000", 3),
+ new Entry("1011", 4),
+ new Entry("1100", 5),
+ new Entry("1110", 6),
+ new Entry("1111", 7),
+ new Entry("10011", 8),
+ new Entry("10100", 9),
+ new Entry("00111", 10),
+ new Entry("01000", 11),
+ new Entry("001000", 12),
+ new Entry("000011", 13),
+ new Entry("110100", 14),
+ new Entry("110101", 15),
+ new Entry("101010", 16),
+ new Entry("101011", 17),
+ new Entry("0100111", 18),
+ new Entry("0001100", 19),
+ new Entry("0001000", 20),
+ new Entry("0010111", 21),
+ new Entry("0000011", 22),
+ new Entry("0000100", 23),
+ new Entry("0101000", 24),
+ new Entry("0101011", 25),
+ new Entry("0010011", 26),
+ new Entry("0100100", 27),
+ new Entry("0011000", 28),
+ new Entry("00000010", 29),
+ new Entry("00000011", 30),
+ new Entry("00011010", 31),
+ new Entry("00011011", 32),
+ new Entry("00010010", 33),
+ new Entry("00010011", 34),
+ new Entry("00010100", 35),
+ new Entry("00010101", 36),
+ new Entry("00010110", 37),
+ new Entry("00010111", 38),
+ new Entry("00101000", 39),
+ new Entry("00101001", 40),
+ new Entry("00101010", 41),
+ new Entry("00101011", 42),
+ new Entry("00101100", 43),
+ new Entry("00101101", 44),
+ new Entry("00000100", 45),
+ new Entry("00000101", 46),
+ new Entry("00001010", 47),
+ new Entry("00001011", 48),
+ new Entry("01010010", 49),
+ new Entry("01010011", 50),
+ new Entry("01010100", 51),
+ new Entry("01010101", 52),
+ new Entry("00100100", 53),
+ new Entry("00100101", 54),
+ new Entry("01011000", 55),
+ new Entry("01011001", 56),
+ new Entry("01011010", 57),
+ new Entry("01011011", 58),
+ new Entry("01001010", 59),
+ new Entry("01001011", 60),
+ new Entry("00110010", 61),
+ new Entry("00110011", 62),
+ new Entry("00110100", 63), };
+
+ public static final Entry[] BLACK_TERMINATING_CODES = {
+ new Entry("0000110111", 0),
+ new Entry("010", 1),
+ new Entry("11", 2),
+ new Entry("10", 3),
+ new Entry("011", 4),
+ new Entry("0011", 5),
+ new Entry("0010", 6),
+ new Entry("00011", 7),
+ new Entry("000101", 8),
+ new Entry("000100", 9),
+ new Entry("0000100", 10),
+ new Entry("0000101", 11),
+ new Entry("0000111", 12),
+ new Entry("00000100", 13),
+ new Entry("00000111", 14),
+ new Entry("000011000", 15),
+ new Entry("0000010111", 16),
+ new Entry("0000011000", 17),
+ new Entry("0000001000", 18),
+ new Entry("00001100111", 19),
+ new Entry("00001101000", 20),
+ new Entry("00001101100", 21),
+ new Entry("00000110111", 22),
+ new Entry("00000101000", 23),
+ new Entry("00000010111", 24),
+ new Entry("00000011000", 25),
+ new Entry("000011001010", 26),
+ new Entry("000011001011", 27),
+ new Entry("000011001100", 28),
+ new Entry("000011001101", 29),
+ new Entry("000001101000", 30),
+ new Entry("000001101001", 31),
+ new Entry("000001101010", 32),
+ new Entry("000001101011", 33),
+ new Entry("000011010010", 34),
+ new Entry("000011010011", 35),
+ new Entry("000011010100", 36),
+ new Entry("000011010101", 37),
+ new Entry("000011010110", 38),
+ new Entry("000011010111", 39),
+ new Entry("000001101100", 40),
+ new Entry("000001101101", 41),
+ new Entry("000011011010", 42),
+ new Entry("000011011011", 43),
+ new Entry("000001010100", 44),
+ new Entry("000001010101", 45),
+ new Entry("000001010110", 46),
+ new Entry("000001010111", 47),
+ new Entry("000001100100", 48),
+ new Entry("000001100101", 49),
+ new Entry("000001010010", 50),
+ new Entry("000001010011", 51),
+ new Entry("000000100100", 52),
+ new Entry("000000110111", 53),
+ new Entry("000000111000", 54),
+ new Entry("000000100111", 55),
+ new Entry("000000101000", 56),
+ new Entry("000001011000", 57),
+ new Entry("000001011001", 58),
+ new Entry("000000101011", 59),
+ new Entry("000000101100", 60),
+ new Entry("000001011010", 61),
+ new Entry("000001100110", 62),
+ new Entry("000001100111", 63), };
+
+ public static final Entry[] WHITE_MAKE_UP_CODES = {
+ new Entry("11011", 64),
+ new Entry("10010", 128),
+ new Entry("010111", 192),
+ new Entry("0110111", 256),
+ new Entry("00110110", 320),
+ new Entry("00110111", 384),
+ new Entry("01100100", 448),
+ new Entry("01100101", 512),
+ new Entry("01101000", 576),
+ new Entry("01100111", 640),
+ new Entry("011001100", 704),
+ new Entry("011001101", 768),
+ new Entry("011010010", 832),
+ new Entry("011010011", 896),
+ new Entry("011010100", 960),
+ new Entry("011010101", 1024),
+ new Entry("011010110", 1088),
+ new Entry("011010111", 1152),
+ new Entry("011011000", 1216),
+ new Entry("011011001", 1280),
+ new Entry("011011010", 1344),
+ new Entry("011011011", 1408),
+ new Entry("010011000", 1472),
+ new Entry("010011001", 1536),
+ new Entry("010011010", 1600),
+ new Entry("011000", 1664),
+ new Entry("010011011", 1728), };
+
+ public static final Entry[] BLACK_MAKE_UP_CODES = {
+ new Entry("0000001111", 64),
+ new Entry("000011001000", 128),
+ new Entry("000011001001", 192),
+ new Entry("000001011011", 256),
+ new Entry("000000110011", 320),
+ new Entry("000000110100", 384),
+ new Entry("000000110101", 448),
+ new Entry("0000001101100", 512),
+ new Entry("0000001101101", 576),
+ new Entry("0000001001010", 640),
+ new Entry("0000001001011", 704),
+ new Entry("0000001001100", 768),
+ new Entry("0000001001101", 832),
+ new Entry("0000001110010", 896),
+ new Entry("0000001110011", 960),
+ new Entry("0000001110100", 1024),
+ new Entry("0000001110101", 1088),
+ new Entry("0000001110110", 1152),
+ new Entry("0000001110111", 1216),
+ new Entry("0000001010010", 1280),
+ new Entry("0000001010011", 1344),
+ new Entry("0000001010100", 1408),
+ new Entry("0000001010101", 1472),
+ new Entry("0000001011010", 1536),
+ new Entry("0000001011011", 1600),
+ new Entry("0000001100100", 1664),
+ new Entry("0000001100101", 1728), };
+
+ public static final Entry[] ADDITIONAL_MAKE_UP_CODES = {
+ new Entry("00000001000", 1792),
+ new Entry("00000001100", 1856),
+ new Entry("00000001101", 1920),
+ new Entry("000000010010", 1984),
+ new Entry("000000010011", 2048),
+ new Entry("000000010100", 2112),
+ new Entry("000000010101", 2176),
+ new Entry("000000010110", 2240),
+ new Entry("000000010111", 2304),
+ new Entry("000000011100", 2368),
+ new Entry("000000011101", 2432),
+ new Entry("000000011110", 2496),
+ new Entry("000000011111", 2560), };
+
+ public static final Entry EOL = new Entry("000000000001", 0);
+ public static final Entry EOL13 = new Entry("0000000000001", 0);
+ public static final Entry EOL14 = new Entry("00000000000001", 0);
+ public static final Entry EOL15 = new Entry("000000000000001", 0);
+ public static final Entry EOL16 = new Entry("0000000000000001", 0);
+ public static final Entry EOL17 = new Entry("00000000000000001", 0);
+ public static final Entry EOL18 = new Entry("000000000000000001", 0);
+ public static final Entry EOL19 = new Entry("0000000000000000001", 0);
+ public static final Entry P = new Entry("0001", 0);
+ public static final Entry H = new Entry("001", 0);
+ public static final Entry V0 = new Entry("1", 0);
+ public static final Entry VR1 = new Entry("011", 0);
+ public static final Entry VR2 = new Entry("000011", 0);
+ public static final Entry VR3 = new Entry("0000011", 0);
+ public static final Entry VL1 = new Entry("010", 0);
+ public static final Entry VL2 = new Entry("000010", 0);
+ public static final Entry VL3 = new Entry("0000010", 0);
+
+ public static class Entry {
+ String bitString;
+ Integer value;
+
+ public Entry(final String bitString, final int value) {
+ this.bitString = bitString;
+ this.value = value;
+ }
+
+ public void writeBits(final BitArrayOutputStream outputStream) {
+ for (int i = 0; i < bitString.length(); i++) {
+ if (bitString.charAt(i) == '0') {
+ outputStream.writeBit(0);
+ } else {
+ outputStream.writeBit(1);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/itu_t4/package-info.java b/src/main/java/org/apache/commons/imaging/common/itu_t4/package-info.java
new file mode 100644
index 0000000..9c1510f
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/itu_t4/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provides ITU-T T.4 and T.6 compression classes.
+ */
+package org.apache.commons.imaging.common.itu_t4;
+
diff --git a/src/main/java/org/apache/sanselan/common/mylzw/BitsToByteInputStream.java b/src/main/java/org/apache/commons/imaging/common/mylzw/BitsToByteInputStream.java
similarity index 67%
rename from src/main/java/org/apache/sanselan/common/mylzw/BitsToByteInputStream.java
rename to src/main/java/org/apache/commons/imaging/common/mylzw/BitsToByteInputStream.java
index fe78877..ba01162 100644
--- a/src/main/java/org/apache/sanselan/common/mylzw/BitsToByteInputStream.java
+++ b/src/main/java/org/apache/commons/imaging/common/mylzw/BitsToByteInputStream.java
@@ -1,58 +1,56 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common.mylzw;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-public class BitsToByteInputStream extends InputStream
-{
- private final MyBitInputStream is;
- private final int desiredDepth;
-
- public BitsToByteInputStream(MyBitInputStream is, int desiredDepth)
- {
- this.is = is;
- this.desiredDepth = desiredDepth;
- }
-
- public int read() throws IOException
- {
- return readBits(8);
- }
-
- public int readBits(int bitCount) throws IOException
- {
- int i = is.readBits(bitCount);
- if (bitCount < desiredDepth)
- i <<= (desiredDepth - bitCount);
- else if (bitCount > desiredDepth)
- i >>= (bitCount - desiredDepth);
-
- return i;
- }
-
- public int[] readBitsArray(int sampleBits, int length) throws IOException
- {
- int result[] = new int[length];
-
- for (int i = 0; i < length; i++)
- result[i] = readBits(sampleBits);
-
- return result;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.mylzw;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class BitsToByteInputStream extends InputStream {
+ private final MyBitInputStream is;
+ private final int desiredDepth;
+
+ public BitsToByteInputStream(final MyBitInputStream is, final int desiredDepth) {
+ this.is = is;
+ this.desiredDepth = desiredDepth;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return readBits(8);
+ }
+
+ public int readBits(final int bitCount) throws IOException {
+ int i = is.readBits(bitCount);
+ if (bitCount < desiredDepth) {
+ i <<= (desiredDepth - bitCount);
+ } else if (bitCount > desiredDepth) {
+ i >>= (bitCount - desiredDepth);
+ }
+
+ return i;
+ }
+
+ public int[] readBitsArray(final int sampleBits, final int length) throws IOException {
+ final int[] result = new int[length];
+
+ for (int i = 0; i < length; i++) {
+ result[i] = readBits(sampleBits);
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/mylzw/MyBitInputStream.java b/src/main/java/org/apache/commons/imaging/common/mylzw/MyBitInputStream.java
new file mode 100644
index 0000000..721934a
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/mylzw/MyBitInputStream.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.mylzw;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+
+public class MyBitInputStream extends InputStream {
+ private final InputStream is;
+ private final ByteOrder byteOrder;
+ private boolean tiffLZWMode;
+ private long bytesRead;
+ private int bitsInCache;
+ private int bitCache;
+
+ public MyBitInputStream(final InputStream is, final ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
+ this.is = is;
+ }
+
+ @Override
+ public int read() throws IOException {
+ return readBits(8);
+ }
+
+ public void setTiffLZWMode() {
+ tiffLZWMode = true;
+ }
+
+ public int readBits(final int sampleBits) throws IOException {
+ while (bitsInCache < sampleBits) {
+ final int next = is.read();
+
+ if (next < 0) {
+ if (tiffLZWMode) {
+ // pernicious special case!
+ return 257;
+ }
+ return -1;
+ }
+
+ final int newByte = (0xff & next);
+
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ bitCache = (bitCache << 8) | newByte;
+ } else {
+ bitCache = (newByte << bitsInCache) | bitCache;
+ }
+
+ bytesRead++;
+ bitsInCache += 8;
+ }
+ final int sampleMask = (1 << sampleBits) - 1;
+
+ int sample;
+
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ sample = sampleMask & (bitCache >> (bitsInCache - sampleBits));
+ } else {
+ sample = sampleMask & bitCache;
+ bitCache >>= sampleBits;
+ }
+
+ final int result = sample;
+
+ bitsInCache -= sampleBits;
+ final int remainderMask = (1 << bitsInCache) - 1;
+ bitCache &= remainderMask;
+
+ return result;
+ }
+
+ public void flushCache() {
+ bitsInCache = 0;
+ bitCache = 0;
+ }
+
+ public long getBytesRead() {
+ return bytesRead;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/mylzw/MyBitOutputStream.java b/src/main/java/org/apache/commons/imaging/common/mylzw/MyBitOutputStream.java
new file mode 100644
index 0000000..5b90cb0
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/mylzw/MyBitOutputStream.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.mylzw;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteOrder;
+
+public class MyBitOutputStream extends OutputStream {
+ private final OutputStream os;
+ private final ByteOrder byteOrder;
+ private int bitsInCache;
+ private int bitCache;
+ private int bytesWritten;
+
+ public MyBitOutputStream(final OutputStream os, final ByteOrder byteOrder) {
+ this.byteOrder = byteOrder;
+ this.os = os;
+ }
+
+ @Override
+ public void write(final int value) throws IOException {
+ writeBits(value, 8);
+ }
+
+ // TODO: in and out streams CANNOT accurately read/write 32bits at a time,
+ // as int will overflow. should have used a long
+ public void writeBits(int value, final int sampleBits) throws IOException {
+ final int sampleMask = (1 << sampleBits) - 1;
+ value &= sampleMask;
+
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ // MSB, so add to right
+ bitCache = (bitCache << sampleBits) | value;
+ } else {
+ // LSB, so add to left
+ bitCache = bitCache | (value << bitsInCache);
+ }
+ bitsInCache += sampleBits;
+
+ while (bitsInCache >= 8) {
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ // MSB, so write from left
+ final int b = 0xff & (bitCache >> (bitsInCache - 8));
+ actualWrite(b);
+
+ bitsInCache -= 8;
+ } else {
+ // LSB, so write from right
+ final int b = 0xff & bitCache;
+ actualWrite(b);
+
+ bitCache >>= 8;
+ bitsInCache -= 8;
+ }
+ final int remainderMask = (1 << bitsInCache) - 1; // unneccesary
+ bitCache &= remainderMask; // unneccesary
+ }
+
+ }
+
+ private void actualWrite(final int value) throws IOException {
+ os.write(value);
+ bytesWritten++;
+ }
+
+ public void flushCache() throws IOException {
+ if (bitsInCache > 0) {
+ final int bitMask = (1 << bitsInCache) - 1;
+ int b = bitMask & bitCache;
+
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ // MSB, so write from left
+ b <<= 8 - bitsInCache; // left align fragment.
+ os.write(b);
+ } else {
+ // LSB, so write from right
+ os.write(b);
+ }
+ }
+
+ bitsInCache = 0;
+ bitCache = 0;
+ }
+
+ public int getBytesWritten() {
+ return bytesWritten + ((bitsInCache > 0) ? 1 : 0);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/mylzw/MyLzwCompressor.java b/src/main/java/org/apache/commons/imaging/common/mylzw/MyLzwCompressor.java
new file mode 100644
index 0000000..418cee2
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/mylzw/MyLzwCompressor.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.mylzw;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MyLzwCompressor {
+ private int codeSize;
+ private final int initialCodeSize;
+ private int codes = -1;
+
+ private final ByteOrder byteOrder;
+ private final boolean earlyLimit;
+ private final int clearCode;
+ private final int eoiCode;
+ private final Listener listener;
+ private final Map map = new HashMap();
+
+ public MyLzwCompressor(final int initialCodeSize, final ByteOrder byteOrder,
+ final boolean earlyLimit) {
+ this(initialCodeSize, byteOrder, earlyLimit, null);
+ }
+
+ public MyLzwCompressor(final int initialCodeSize, final ByteOrder byteOrder,
+ final boolean earlyLimit, final Listener listener) {
+ this.listener = listener;
+ this.byteOrder = byteOrder;
+ this.earlyLimit = earlyLimit;
+
+ this.initialCodeSize = initialCodeSize;
+
+ clearCode = 1 << initialCodeSize;
+ eoiCode = clearCode + 1;
+
+ if (null != listener) {
+ listener.init(clearCode, eoiCode);
+ }
+
+ initializeStringTable();
+ }
+
+ private void initializeStringTable() {
+ codeSize = initialCodeSize;
+
+ final int intialEntriesCount = (1 << codeSize) + 2;
+
+ map.clear();
+ for (codes = 0; codes < intialEntriesCount; codes++) {
+ if ((codes != clearCode) && (codes != eoiCode)) {
+ final ByteArray key = arrayToKey((byte) codes);
+
+ map.put(key, codes);
+ }
+ }
+ }
+
+ private void clearTable() {
+ initializeStringTable();
+ incrementCodeSize();
+ }
+
+ private void incrementCodeSize() {
+ if (codeSize != 12) {
+ codeSize++;
+ }
+ }
+
+ private ByteArray arrayToKey(final byte b) {
+ return arrayToKey(new byte[] { b, }, 0, 1);
+ }
+
+ private final static class ByteArray {
+ private final byte[] bytes;
+ private final int start;
+ private final int length;
+ private final int hash;
+
+ public ByteArray(final byte[] bytes, final int start, final int length) {
+ this.bytes = bytes;
+ this.start = start;
+ this.length = length;
+
+ int tempHash = length;
+
+ for (int i = 0; i < length; i++) {
+ final int b = 0xff & bytes[i + start];
+ tempHash = tempHash + (tempHash << 8) ^ b ^ i;
+ }
+
+ hash = tempHash;
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o instanceof ByteArray) {
+ final ByteArray other = (ByteArray) o;
+ if (other.hash != hash) {
+ return false;
+ }
+ if (other.length != length) {
+ return false;
+ }
+
+ for (int i = 0; i < length; i++) {
+ if (other.bytes[i + other.start] != bytes[i + start]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private ByteArray arrayToKey(final byte[] bytes, final int start, final int length) {
+ return new ByteArray(bytes, start, length);
+ }
+
+ private void writeDataCode(final MyBitOutputStream bos, final int code)
+ throws IOException {
+ if (null != listener) {
+ listener.dataCode(code);
+ }
+ writeCode(bos, code);
+ }
+
+ private void writeClearCode(final MyBitOutputStream bos) throws IOException {
+ if (null != listener) {
+ listener.dataCode(clearCode);
+ }
+ writeCode(bos, clearCode);
+ }
+
+ private void writeEoiCode(final MyBitOutputStream bos) throws IOException {
+ if (null != listener) {
+ listener.eoiCode(eoiCode);
+ }
+ writeCode(bos, eoiCode);
+ }
+
+ private void writeCode(final MyBitOutputStream bos, final int code)
+ throws IOException {
+ bos.writeBits(code, codeSize);
+ }
+
+ private boolean isInTable(final byte[] bytes, final int start, final int length) {
+ final ByteArray key = arrayToKey(bytes, start, length);
+
+ return map.containsKey(key);
+ }
+
+ private int codeFromString(final byte[] bytes, final int start, final int length)
+ throws IOException {
+ final ByteArray key = arrayToKey(bytes, start, length);
+ final Integer code = map.get(key);
+ if (code == null) {
+ throw new IOException("CodeFromString");
+ }
+ return code;
+ }
+
+ private boolean addTableEntry(final MyBitOutputStream bos, final byte[] bytes,
+ final int start, final int length) throws IOException {
+ final ByteArray key = arrayToKey(bytes, start, length);
+ return addTableEntry(bos, key);
+ }
+
+ private boolean addTableEntry(final MyBitOutputStream bos, final ByteArray key)
+ throws IOException {
+ boolean cleared = false;
+
+ int limit = (1 << codeSize);
+ if (earlyLimit) {
+ limit--;
+ }
+
+ if (codes == limit) {
+ if (codeSize < 12) {
+ incrementCodeSize();
+ } else {
+ writeClearCode(bos);
+ clearTable();
+ cleared = true;
+ }
+ }
+
+ if (!cleared) {
+ map.put(key, codes);
+ codes++;
+ }
+
+ return cleared;
+ }
+
+ public interface Listener {
+ void dataCode(int code);
+
+ void eoiCode(int code);
+
+ void clearCode(int code);
+
+ void init(int clearCode, int eoiCode);
+ }
+
+ public byte[] compress(final byte[] bytes) throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length);
+ final MyBitOutputStream bos = new MyBitOutputStream(baos, byteOrder);
+
+ initializeStringTable();
+ clearTable();
+ writeClearCode(bos);
+
+ int wStart = 0;
+ int wLength = 0;
+
+ for (int i = 0; i < bytes.length; i++) {
+ if (isInTable(bytes, wStart, wLength + 1)) {
+ wLength++;
+ } else {
+ final int code = codeFromString(bytes, wStart, wLength);
+ writeDataCode(bos, code);
+ addTableEntry(bos, bytes, wStart, wLength + 1);
+
+ wStart = i;
+ wLength = 1;
+ }
+ }
+
+ final int code = codeFromString(bytes, wStart, wLength);
+ writeDataCode(bos, code);
+
+ writeEoiCode(bos);
+
+ bos.flushCache();
+
+ return baos.toByteArray();
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/common/mylzw/MyLZWDecompressor.java b/src/main/java/org/apache/commons/imaging/common/mylzw/MyLzwDecompressor.java
similarity index 53%
rename from src/main/java/org/apache/sanselan/common/mylzw/MyLZWDecompressor.java
rename to src/main/java/org/apache/commons/imaging/common/mylzw/MyLzwDecompressor.java
index cb12b8d..34e4879 100644
--- a/src/main/java/org/apache/sanselan/common/mylzw/MyLZWDecompressor.java
+++ b/src/main/java/org/apache/commons/imaging/common/mylzw/MyLzwDecompressor.java
@@ -1,226 +1,204 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.common.mylzw;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-public final class MyLZWDecompressor
-{
- private static final int MAX_TABLE_SIZE = 1 << 12;
-
- private final byte[][] table;
- private int codeSize;
- private final int initialCodeSize;
- private int codes = -1;
-
- private final int byteOrder;
-
- private final Listener listener;
-
- public static interface Listener
- {
- public void code(int code);
-
- public void init(int clearCode, int eoiCode);
- }
-
- public MyLZWDecompressor(int initialCodeSize, int byteOrder)
- {
- this(initialCodeSize, byteOrder, null);
- }
-
- public MyLZWDecompressor(int initialCodeSize, int byteOrder,
- Listener listener)
- {
- this.listener = listener;
- this.byteOrder = byteOrder;
-
- this.initialCodeSize = initialCodeSize;
-
- table = new byte[MAX_TABLE_SIZE][];
- clearCode = 1 << initialCodeSize;
- eoiCode = clearCode + 1;
-
- if (null != listener)
- listener.init(clearCode, eoiCode);
-
- InitializeTable();
- }
-
- private final void InitializeTable()
- {
- codeSize = initialCodeSize;
-
- int intial_entries_count = 1 << codeSize + 2;
-
- for (int i = 0; i < intial_entries_count; i++)
- table[i] = new byte[] { (byte) i, };
- }
-
- private final void clearTable()
- {
- codes = (1 << initialCodeSize) + 2;
- codeSize = initialCodeSize;
- incrementCodeSize();
- }
-
- private final int clearCode;
- private final int eoiCode;
-
- private final int getNextCode(MyBitInputStream is) throws IOException
- {
- int code = is.readBits(codeSize);
-
- if (null != listener)
- listener.code(code);
- return code;
- }
-
- private final byte[] stringFromCode(int code) throws IOException
- {
- if ((code >= codes) || (code < 0))
- throw new IOException("Bad Code: " + code + " codes: " + codes
- + " code_size: " + codeSize + ", table: " + table.length);
-
- return table[code];
- }
-
- private final boolean isInTable(int Code)
- {
- return Code < codes;
- }
-
- private final byte firstChar(byte bytes[])
- {
- return bytes[0];
- }
-
- private final void addStringToTable(byte bytes[]) throws IOException
- {
- if (codes < (1 << codeSize))
- {
- table[codes] = bytes;
- codes++;
- } else
- throw new IOException("AddStringToTable: codes: " + codes
- + " code_size: " + codeSize);
-
- checkCodeSize();
- }
-
- private final byte[] appendBytes(byte bytes[], byte b)
- {
- byte result[] = new byte[bytes.length + 1];
-
- System.arraycopy(bytes, 0, result, 0, bytes.length);
- result[result.length - 1] = b;
- return result;
- }
-
- private int written = 0;
-
- private final void writeToResult(OutputStream os, byte bytes[])
- throws IOException
- {
- os.write(bytes);
- written += bytes.length;
- }
-
- private boolean tiffLZWMode = false;
-
- public void setTiffLZWMode()
- {
- tiffLZWMode = true;
- }
-
- public byte[] decompress(InputStream is, int expectedLength)
- throws IOException
- {
- int code, oldCode = -1;
- MyBitInputStream mbis = new MyBitInputStream(is, byteOrder);
- if (tiffLZWMode)
- mbis.setTiffLZWMode();
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedLength);
-
- clearTable();
-
- while ((code = getNextCode(mbis)) != eoiCode)
- {
- if (code == clearCode)
- {
- clearTable();
-
- if (written >= expectedLength)
- break;
- code = getNextCode(mbis);
-
- if (code == eoiCode)
- {
- break;
- }
- writeToResult(baos, stringFromCode(code));
-
- oldCode = code;
- } // end of ClearCode case
- else
- {
- if (isInTable(code))
- {
- writeToResult(baos, stringFromCode(code));
-
- addStringToTable(appendBytes(stringFromCode(oldCode),
- firstChar(stringFromCode(code))));
- oldCode = code;
- } else
- {
- byte OutString[] = appendBytes(stringFromCode(oldCode),
- firstChar(stringFromCode(oldCode)));
- writeToResult(baos, OutString);
- addStringToTable(OutString);
- oldCode = code;
- }
- } // end of not-ClearCode case
-
- if (written >= expectedLength)
- break;
- } // end of while loop
-
- byte result[] = baos.toByteArray();
-
- return result;
- }
-
- private final void checkCodeSize() // throws IOException
- {
- int limit = (1 << codeSize);
- if (tiffLZWMode)
- limit--;
-
- if (codes == limit)
- incrementCodeSize();
- }
-
- private final void incrementCodeSize() // throws IOException
- {
- if (codeSize != 12)
- codeSize++;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.common.mylzw;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteOrder;
+
+public final class MyLzwDecompressor {
+ private static final int MAX_TABLE_SIZE = 1 << 12;
+ private final byte[][] table;
+ private int codeSize;
+ private final int initialCodeSize;
+ private int codes = -1;
+ private final ByteOrder byteOrder;
+ private final Listener listener;
+ private final int clearCode;
+ private final int eoiCode;
+ private int written;
+ private boolean tiffLZWMode;
+
+ public interface Listener {
+ void code(int code);
+
+ void init(int clearCode, int eoiCode);
+ }
+
+ public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder) {
+ this(initialCodeSize, byteOrder, null);
+ }
+
+ public MyLzwDecompressor(final int initialCodeSize, final ByteOrder byteOrder,
+ final Listener listener) {
+ this.listener = listener;
+ this.byteOrder = byteOrder;
+
+ this.initialCodeSize = initialCodeSize;
+
+ table = new byte[MAX_TABLE_SIZE][];
+ clearCode = 1 << initialCodeSize;
+ eoiCode = clearCode + 1;
+
+ if (null != listener) {
+ listener.init(clearCode, eoiCode);
+ }
+
+ initializeTable();
+ }
+
+ private void initializeTable() {
+ codeSize = initialCodeSize;
+
+ final int intialEntriesCount = 1 << codeSize + 2;
+
+ for (int i = 0; i < intialEntriesCount; i++) {
+ table[i] = new byte[] { (byte) i, };
+ }
+ }
+
+ private void clearTable() {
+ codes = (1 << initialCodeSize) + 2;
+ codeSize = initialCodeSize;
+ incrementCodeSize();
+ }
+
+ private int getNextCode(final MyBitInputStream is) throws IOException {
+ final int code = is.readBits(codeSize);
+
+ if (null != listener) {
+ listener.code(code);
+ }
+ return code;
+ }
+
+ private byte[] stringFromCode(final int code) throws IOException {
+ if ((code >= codes) || (code < 0)) {
+ throw new IOException("Bad Code: " + code + " codes: " + codes
+ + " code_size: " + codeSize + ", table: " + table.length);
+ }
+ return table[code];
+ }
+
+ private boolean isInTable(final int code) {
+ return code < codes;
+ }
+
+ private byte firstChar(final byte[] bytes) {
+ return bytes[0];
+ }
+
+ private void addStringToTable(final byte[] bytes) throws IOException {
+ if (codes < (1 << codeSize)) {
+ table[codes] = bytes;
+ codes++;
+ } else {
+ throw new IOException("AddStringToTable: codes: " + codes
+ + " code_size: " + codeSize);
+ }
+ checkCodeSize();
+ }
+
+ private byte[] appendBytes(final byte[] bytes, final byte b) {
+ final byte[] result = new byte[bytes.length + 1];
+
+ System.arraycopy(bytes, 0, result, 0, bytes.length);
+ result[result.length - 1] = b;
+ return result;
+ }
+
+ private void writeToResult(final OutputStream os, final byte[] bytes)
+ throws IOException {
+ os.write(bytes);
+ written += bytes.length;
+ }
+
+ public void setTiffLZWMode() {
+ tiffLZWMode = true;
+ }
+
+ public byte[] decompress(final InputStream is, final int expectedLength) throws IOException {
+ int code;
+ int oldCode = -1;
+ final MyBitInputStream mbis = new MyBitInputStream(is, byteOrder);
+ if (tiffLZWMode) {
+ mbis.setTiffLZWMode();
+ }
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedLength);
+
+ clearTable();
+
+ while ((code = getNextCode(mbis)) != eoiCode) {
+ if (code == clearCode) {
+ clearTable();
+
+ if (written >= expectedLength) {
+ break;
+ }
+ code = getNextCode(mbis);
+
+ if (code == eoiCode) {
+ break;
+ }
+ writeToResult(baos, stringFromCode(code));
+
+ oldCode = code;
+ } // end of ClearCode case
+ else {
+ if (isInTable(code)) {
+ writeToResult(baos, stringFromCode(code));
+
+ addStringToTable(appendBytes(stringFromCode(oldCode),
+ firstChar(stringFromCode(code))));
+ oldCode = code;
+ } else {
+ final byte[] outString = appendBytes(stringFromCode(oldCode),
+ firstChar(stringFromCode(oldCode)));
+ writeToResult(baos, outString);
+ addStringToTable(outString);
+ oldCode = code;
+ }
+ } // end of not-ClearCode case
+
+ if (written >= expectedLength) {
+ break;
+ }
+ } // end of while loop
+
+ return baos.toByteArray();
+ }
+
+ private void checkCodeSize() {
+ int limit = (1 << codeSize);
+ if (tiffLZWMode) {
+ limit--;
+ }
+
+ if (codes == limit) {
+ incrementCodeSize();
+ }
+ }
+
+ private void incrementCodeSize() {
+ if (codeSize != 12) {
+ codeSize++;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/common/mylzw/package-info.java b/src/main/java/org/apache/commons/imaging/common/mylzw/package-info.java
new file mode 100644
index 0000000..1255a06
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/mylzw/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provides LZW compression.
+ */
+package org.apache.commons.imaging.common.mylzw;
+
diff --git a/src/main/java/org/apache/commons/imaging/common/package-info.java b/src/main/java/org/apache/commons/imaging/common/package-info.java
new file mode 100644
index 0000000..b1b8471
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/common/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Provides utility classes that are employed across multiple
+ * image formats and sub-packages.
+ */
+package org.apache.commons.imaging.common;
+
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/BmpHeaderInfo.java b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpHeaderInfo.java
new file mode 100644
index 0000000..ea1e53c
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpHeaderInfo.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+class BmpHeaderInfo {
+ // BM - Windows 3.1x, 95, NT
+ // BA - OS/2 Bitmap Array
+ // CI - OS/2 Color Icon
+ // CP - OS/2 Color Pointer
+ // IC - OS/2 Icon
+ // PT - OS/2 Pointer
+ public final byte identifier1;
+ public final byte identifier2;
+
+ public final int fileSize;
+ public final int reserved;
+ public final int bitmapDataOffset;
+
+ public final int bitmapHeaderSize;
+ public final int width;
+ public final int height;
+ public final int planes;
+ public final int bitsPerPixel;
+ public final int compression;
+ public final int bitmapDataSize;
+ public final int hResolution;
+ public final int vResolution;
+ public final int colorsUsed;
+ public final int colorsImportant;
+
+ public final int redMask;
+ public final int greenMask;
+ public final int blueMask;
+ public final int alphaMask;
+
+ public final int colorSpaceType;
+ public final ColorSpace colorSpace;
+ public final int gammaRed;
+ public final int gammaGreen;
+ public final int gammaBlue;
+ public final int intent;
+ public final int profileData;
+ public final int profileSize;
+ public final int reservedV5;
+
+ public static class ColorSpaceCoordinate {
+ public int x;
+ public int y;
+ public int z;
+ }
+
+ public static class ColorSpace {
+ public ColorSpaceCoordinate red;
+ public ColorSpaceCoordinate green;
+ public ColorSpaceCoordinate blue;
+ }
+
+ public BmpHeaderInfo(final byte identifier1, final byte identifier2, final int fileSize,
+ final int reserved, final int bitmapDataOffset, final int bitmapHeaderSize,
+ final int width, final int height, final int planes, final int bitsPerPixel,
+ final int compression, final int bitmapDataSize, final int hResolution,
+ final int vResolution, final int colorsUsed, final int colorsImportant, final int redMask,
+ final int greenMask, final int blueMask, final int alphaMask, final int colorSpaceType,
+ final ColorSpace colorSpace, final int gammaRed, final int gammaGreen, final int gammaBlue,
+ final int intent, final int profileData, final int profileSize, final int reservedV5) {
+ this.identifier1 = identifier1;
+ this.identifier2 = identifier2;
+ this.fileSize = fileSize;
+ this.reserved = reserved;
+ this.bitmapDataOffset = bitmapDataOffset;
+
+ this.bitmapHeaderSize = bitmapHeaderSize;
+ this.width = width;
+ this.height = height;
+ this.planes = planes;
+ this.bitsPerPixel = bitsPerPixel;
+ this.compression = compression;
+ this.bitmapDataSize = bitmapDataSize;
+ this.hResolution = hResolution;
+ this.vResolution = vResolution;
+ this.colorsUsed = colorsUsed;
+ this.colorsImportant = colorsImportant;
+
+ this.redMask = redMask;
+ this.greenMask = greenMask;
+ this.blueMask = blueMask;
+ this.alphaMask = alphaMask;
+ this.colorSpaceType = colorSpaceType;
+ this.colorSpace = colorSpace;
+ this.gammaRed = gammaRed;
+ this.gammaGreen = gammaGreen;
+ this.gammaBlue = gammaBlue;
+ this.intent = intent;
+ this.profileData = profileData;
+ this.profileSize = profileSize;
+ this.reservedV5 = reservedV5;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/BmpImageParser.java b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpImageParser.java
new file mode 100644
index 0000000..a28c5a8
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpImageParser.java
@@ -0,0 +1,820 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.imaging.FormatCompliance;
+import org.apache.commons.imaging.ImageFormat;
+import org.apache.commons.imaging.ImageFormats;
+import org.apache.commons.imaging.ImageInfo;
+import org.apache.commons.imaging.ImageParser;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.ImageWriteException;
+import org.apache.commons.imaging.PixelDensity;
+import org.apache.commons.imaging.common.BinaryOutputStream;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.ImageBuilder;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.palette.PaletteFactory;
+import org.apache.commons.imaging.palette.SimplePalette;
+import org.apache.commons.imaging.util.IoUtils;
+
+import static org.apache.commons.imaging.ImagingConstants.*;
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+public class BmpImageParser extends ImageParser {
+ private static final String DEFAULT_EXTENSION = ".bmp";
+ private static final String[] ACCEPTED_EXTENSIONS = { DEFAULT_EXTENSION, };
+ private static final byte[] BMP_HEADER_SIGNATURE = { 0x42, 0x4d, };
+ private static final int BI_RGB = 0;
+ private static final int BI_RLE4 = 2;
+ private static final int BI_RLE8 = 1;
+ private static final int BI_BITFIELDS = 3;
+ private static final int BITMAP_FILE_HEADER_SIZE = 14;
+ private static final int BITMAP_INFO_HEADER_SIZE = 40;
+
+ public BmpImageParser() {
+ super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @Override
+ public String getName() {
+ return "Bmp-Custom";
+ }
+
+ @Override
+ public String getDefaultExtension() {
+ return DEFAULT_EXTENSION;
+ }
+
+ @Override
+ protected String[] getAcceptedExtensions() {
+ return ACCEPTED_EXTENSIONS;
+ }
+
+ @Override
+ protected ImageFormat[] getAcceptedTypes() {
+ return new ImageFormat[] { ImageFormats.BMP, //
+ };
+ }
+
+ private BmpHeaderInfo readBmpHeaderInfo(final InputStream is,
+ final FormatCompliance formatCompliance, final boolean verbose)
+ throws ImageReadException, IOException {
+ final byte identifier1 = readByte("Identifier1", is, "Not a Valid BMP File");
+ final byte identifier2 = readByte("Identifier2", is, "Not a Valid BMP File");
+
+ if (formatCompliance != null) {
+ formatCompliance.compareBytes("Signature", BMP_HEADER_SIGNATURE,
+ new byte[]{identifier1, identifier2,});
+ }
+
+ final int fileSize = read4Bytes("File Size", is, "Not a Valid BMP File", getByteOrder());
+ final int reserved = read4Bytes("Reserved", is, "Not a Valid BMP File", getByteOrder());
+ final int bitmapDataOffset = read4Bytes("Bitmap Data Offset", is, "Not a Valid BMP File", getByteOrder());
+
+ final int bitmapHeaderSize = read4Bytes("Bitmap Header Size", is, "Not a Valid BMP File", getByteOrder());
+ int width = 0;
+ int height = 0;
+ int planes = 0;
+ int bitsPerPixel = 0;
+ int compression = 0;
+ int bitmapDataSize = 0;
+ int hResolution = 0;
+ int vResolution = 0;
+ int colorsUsed = 0;
+ int colorsImportant = 0;
+ int redMask = 0;
+ int greenMask = 0;
+ int blueMask = 0;
+ int alphaMask = 0;
+ int colorSpaceType = 0;
+ final BmpHeaderInfo.ColorSpace colorSpace = new BmpHeaderInfo.ColorSpace();
+ colorSpace.red = new BmpHeaderInfo.ColorSpaceCoordinate();
+ colorSpace.green = new BmpHeaderInfo.ColorSpaceCoordinate();
+ colorSpace.blue = new BmpHeaderInfo.ColorSpaceCoordinate();
+ int gammaRed = 0;
+ int gammaGreen = 0;
+ int gammaBlue = 0;
+ int intent = 0;
+ int profileData = 0;
+ int profileSize = 0;
+ int reservedV5 = 0;
+
+ if (bitmapHeaderSize >= 40) {
+ // BITMAPINFOHEADER
+ width = read4Bytes("Width", is, "Not a Valid BMP File", getByteOrder());
+ height = read4Bytes("Height", is, "Not a Valid BMP File", getByteOrder());
+ planes = read2Bytes("Planes", is, "Not a Valid BMP File", getByteOrder());
+ bitsPerPixel = read2Bytes("Bits Per Pixel", is, "Not a Valid BMP File", getByteOrder());
+ compression = read4Bytes("Compression", is, "Not a Valid BMP File", getByteOrder());
+ bitmapDataSize = read4Bytes("Bitmap Data Size", is, "Not a Valid BMP File", getByteOrder());
+ hResolution = read4Bytes("HResolution", is, "Not a Valid BMP File", getByteOrder());
+ vResolution = read4Bytes("VResolution", is, "Not a Valid BMP File", getByteOrder());
+ colorsUsed = read4Bytes("ColorsUsed", is, "Not a Valid BMP File", getByteOrder());
+ colorsImportant = read4Bytes("ColorsImportant", is, "Not a Valid BMP File", getByteOrder());
+ if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) {
+ // 52 = BITMAPV2INFOHEADER, now undocumented
+ // see http://en.wikipedia.org/wiki/BMP_file_format
+ redMask = read4Bytes("RedMask", is, "Not a Valid BMP File", getByteOrder());
+ greenMask = read4Bytes("GreenMask", is, "Not a Valid BMP File", getByteOrder());
+ blueMask = read4Bytes("BlueMask", is, "Not a Valid BMP File", getByteOrder());
+ }
+ if (bitmapHeaderSize >= 56) {
+ // 56 = the now undocumented BITMAPV3HEADER sometimes used by
+ // Photoshop
+ // see http://forums.adobe.com/thread/751592?tstart=1
+ alphaMask = read4Bytes("AlphaMask", is, "Not a Valid BMP File", getByteOrder());
+ }
+ if (bitmapHeaderSize >= 108) {
+ // BITMAPV4HEADER
+ colorSpaceType = read4Bytes("ColorSpaceType", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.red.x = read4Bytes("ColorSpaceRedX", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.red.y = read4Bytes("ColorSpaceRedY", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.red.z = read4Bytes("ColorSpaceRedZ", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.green.x = read4Bytes("ColorSpaceGreenX", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.green.y = read4Bytes("ColorSpaceGreenY", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.green.z = read4Bytes("ColorSpaceGreenZ", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.blue.x = read4Bytes("ColorSpaceBlueX", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.blue.y = read4Bytes("ColorSpaceBlueY", is, "Not a Valid BMP File", getByteOrder());
+ colorSpace.blue.z = read4Bytes("ColorSpaceBlueZ", is, "Not a Valid BMP File", getByteOrder());
+ gammaRed = read4Bytes("GammaRed", is, "Not a Valid BMP File", getByteOrder());
+ gammaGreen = read4Bytes("GammaGreen", is, "Not a Valid BMP File", getByteOrder());
+ gammaBlue = read4Bytes("GammaBlue", is, "Not a Valid BMP File", getByteOrder());
+ }
+ if (bitmapHeaderSize >= 124) {
+ // BITMAPV5HEADER
+ intent = read4Bytes("Intent", is, "Not a Valid BMP File", getByteOrder());
+ profileData = read4Bytes("ProfileData", is, "Not a Valid BMP File", getByteOrder());
+ profileSize = read4Bytes("ProfileSize", is, "Not a Valid BMP File", getByteOrder());
+ reservedV5 = read4Bytes("Reserved", is, "Not a Valid BMP File", getByteOrder());
+ }
+ } else {
+ throw new ImageReadException("Invalid/unsupported BMP file");
+ }
+
+ if (verbose) {
+ debugNumber("identifier1", identifier1, 1);
+ debugNumber("identifier2", identifier2, 1);
+ debugNumber("fileSize", fileSize, 4);
+ debugNumber("reserved", reserved, 4);
+ debugNumber("bitmapDataOffset", bitmapDataOffset, 4);
+ debugNumber("bitmapHeaderSize", bitmapHeaderSize, 4);
+ debugNumber("width", width, 4);
+ debugNumber("height", height, 4);
+ debugNumber("planes", planes, 2);
+ debugNumber("bitsPerPixel", bitsPerPixel, 2);
+ debugNumber("compression", compression, 4);
+ debugNumber("bitmapDataSize", bitmapDataSize, 4);
+ debugNumber("hResolution", hResolution, 4);
+ debugNumber("vResolution", vResolution, 4);
+ debugNumber("colorsUsed", colorsUsed, 4);
+ debugNumber("colorsImportant", colorsImportant, 4);
+ if (bitmapHeaderSize >= 52 || compression == BI_BITFIELDS) {
+ debugNumber("redMask", redMask, 4);
+ debugNumber("greenMask", greenMask, 4);
+ debugNumber("blueMask", blueMask, 4);
+ }
+ if (bitmapHeaderSize >= 56) {
+ debugNumber("alphaMask", alphaMask, 4);
+ }
+ if (bitmapHeaderSize >= 108) {
+ debugNumber("colorSpaceType", colorSpaceType, 4);
+ debugNumber("colorSpace.red.x", colorSpace.red.x, 1);
+ debugNumber("colorSpace.red.y", colorSpace.red.y, 1);
+ debugNumber("colorSpace.red.z", colorSpace.red.z, 1);
+ debugNumber("colorSpace.green.x", colorSpace.green.x, 1);
+ debugNumber("colorSpace.green.y", colorSpace.green.y, 1);
+ debugNumber("colorSpace.green.z", colorSpace.green.z, 1);
+ debugNumber("colorSpace.blue.x", colorSpace.blue.x, 1);
+ debugNumber("colorSpace.blue.y", colorSpace.blue.y, 1);
+ debugNumber("colorSpace.blue.z", colorSpace.blue.z, 1);
+ debugNumber("gammaRed", gammaRed, 4);
+ debugNumber("gammaGreen", gammaGreen, 4);
+ debugNumber("gammaBlue", gammaBlue, 4);
+ }
+ if (bitmapHeaderSize >= 124) {
+ debugNumber("intent", intent, 4);
+ debugNumber("profileData", profileData, 4);
+ debugNumber("profileSize", profileSize, 4);
+ debugNumber("reservedV5", reservedV5, 4);
+ }
+ }
+
+ return new BmpHeaderInfo(identifier1, identifier2,
+ fileSize, reserved, bitmapDataOffset, bitmapHeaderSize, width,
+ height, planes, bitsPerPixel, compression, bitmapDataSize,
+ hResolution, vResolution, colorsUsed, colorsImportant, redMask,
+ greenMask, blueMask, alphaMask, colorSpaceType, colorSpace,
+ gammaRed, gammaGreen, gammaBlue, intent, profileData,
+ profileSize, reservedV5);
+ }
+
+ private byte[] getRLEBytes(final InputStream is, final int rleSamplesPerByte) throws IOException {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ // this.setDebug(true);
+
+ boolean done = false;
+ while (!done) {
+ final int a = 0xff & readByte("RLE a", is, "BMP: Bad RLE");
+ baos.write(a);
+ final int b = 0xff & readByte("RLE b", is, "BMP: Bad RLE");
+ baos.write(b);
+
+ if (a == 0) {
+ switch (b) {
+ case 0: // EOL
+ break;
+ case 1: // EOF
+ // System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ // );
+ done = true;
+ break;
+ case 2: {
+ // System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ // );
+ final int c = 0xff & readByte("RLE c", is, "BMP: Bad RLE");
+ baos.write(c);
+ final int d = 0xff & readByte("RLE d", is, "BMP: Bad RLE");
+ baos.write(d);
+
+ }
+ break;
+ default: {
+ int size = b / rleSamplesPerByte;
+ if ((b % rleSamplesPerByte) > 0) {
+ size++;
+ }
+ if ((size % 2) != 0) {
+ size++;
+ }
+
+ // System.out.println("b: " + b);
+ // System.out.println("size: " + size);
+ // System.out.println("RLESamplesPerByte: " +
+ // RLESamplesPerByte);
+ // System.out.println("xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ // );
+ final byte[] bytes = readBytes("bytes", is, size,
+ "RLE: Absolute Mode");
+ baos.write(bytes);
+ }
+ break;
+ }
+ }
+ }
+
+ return baos.toByteArray();
+ }
+
+ private ImageContents readImageContents(final InputStream is,
+ final FormatCompliance formatCompliance, final boolean verbose)
+ throws ImageReadException, IOException {
+ final BmpHeaderInfo bhi = readBmpHeaderInfo(is, formatCompliance, verbose);
+
+ int colorTableSize = bhi.colorsUsed;
+ if (colorTableSize == 0) {
+ colorTableSize = (1 << bhi.bitsPerPixel);
+ }
+
+ if (verbose) {
+ debugNumber("ColorsUsed", bhi.colorsUsed, 4);
+ debugNumber("BitsPerPixel", bhi.bitsPerPixel, 4);
+ debugNumber("ColorTableSize", colorTableSize, 4);
+ debugNumber("bhi.colorsUsed", bhi.colorsUsed, 4);
+ debugNumber("Compression", bhi.compression, 4);
+ }
+
+ // A palette is always valid, even for images that don't need it
+ // (like 32 bpp), it specifies the "optimal color palette" for
+ // when the image is displayed on a <= 256 color graphics card.
+ int paletteLength;
+ int rleSamplesPerByte = 0;
+ boolean rle = false;
+
+ switch (bhi.compression) {
+ case BI_RGB:
+ if (verbose) {
+ System.out.println("Compression: BI_RGB");
+ }
+ if (bhi.bitsPerPixel <= 8) {
+ paletteLength = 4 * colorTableSize;
+ } else {
+ paletteLength = 0;
+ }
+ // BytesPerPaletteEntry = 0;
+ // System.out.println("Compression: BI_RGBx2: " + bhi.BitsPerPixel);
+ // System.out.println("Compression: BI_RGBx2: " + (bhi.BitsPerPixel
+ // <= 16));
+ break;
+
+ case BI_RLE4:
+ if (verbose) {
+ System.out.println("Compression: BI_RLE4");
+ }
+ paletteLength = 4 * colorTableSize;
+ rleSamplesPerByte = 2;
+ // ExtraBitsPerPixel = 4;
+ rle = true;
+ // // BytesPerPixel = 2;
+ // // BytesPerPaletteEntry = 0;
+ break;
+ //
+ case BI_RLE8:
+ if (verbose) {
+ System.out.println("Compression: BI_RLE8");
+ }
+ paletteLength = 4 * colorTableSize;
+ rleSamplesPerByte = 1;
+ // ExtraBitsPerPixel = 8;
+ rle = true;
+ // BytesPerPixel = 2;
+ // BytesPerPaletteEntry = 0;
+ break;
+ //
+ case BI_BITFIELDS:
+ if (verbose) {
+ System.out.println("Compression: BI_BITFIELDS");
+ }
+ if (bhi.bitsPerPixel <= 8) {
+ paletteLength = 4 * colorTableSize;
+ } else {
+ paletteLength = 0;
+ }
+ // BytesPerPixel = 2;
+ // BytesPerPaletteEntry = 4;
+ break;
+
+ default:
+ throw new ImageReadException("BMP: Unknown Compression: "
+ + bhi.compression);
+ }
+
+ byte[] colorTable = null;
+ if (paletteLength > 0) {
+ colorTable = readBytes("ColorTable", is, paletteLength,
+ "Not a Valid BMP File");
+ }
+
+ if (verbose) {
+ debugNumber("paletteLength", paletteLength, 4);
+ System.out.println("ColorTable: "
+ + ((colorTable == null) ? "null" : Integer.toString(colorTable.length)));
+ }
+
+ final int pixelCount = bhi.width * bhi.height;
+
+ int imageLineLength = (((bhi.bitsPerPixel) * bhi.width) + 7) / 8;
+
+ if (verbose) {
+ // this.debugNumber("Total BitsPerPixel",
+ // (ExtraBitsPerPixel + bhi.BitsPerPixel), 4);
+ // this.debugNumber("Total Bit Per Line",
+ // ((ExtraBitsPerPixel + bhi.BitsPerPixel) * bhi.Width), 4);
+ // this.debugNumber("ExtraBitsPerPixel", ExtraBitsPerPixel, 4);
+ debugNumber("bhi.Width", bhi.width, 4);
+ debugNumber("bhi.Height", bhi.height, 4);
+ debugNumber("ImageLineLength", imageLineLength, 4);
+ // this.debugNumber("imageDataSize", imageDataSize, 4);
+ debugNumber("PixelCount", pixelCount, 4);
+ }
+ // int ImageLineLength = BytesPerPixel * bhi.Width;
+ while ((imageLineLength % 4) != 0) {
+ imageLineLength++;
+ }
+
+ final int headerSize = BITMAP_FILE_HEADER_SIZE
+ + bhi.bitmapHeaderSize
+ + (bhi.bitmapHeaderSize == 40
+ && bhi.compression == BI_BITFIELDS ? 3 * 4 : 0);
+ final int expectedDataOffset = headerSize + paletteLength;
+
+ if (verbose) {
+ debugNumber("bhi.BitmapDataOffset", bhi.bitmapDataOffset, 4);
+ debugNumber("expectedDataOffset", expectedDataOffset, 4);
+ }
+ final int extraBytes = bhi.bitmapDataOffset - expectedDataOffset;
+ if (extraBytes < 0) {
+ throw new ImageReadException("BMP has invalid image data offset: "
+ + bhi.bitmapDataOffset + " (expected: "
+ + expectedDataOffset + ", paletteLength: " + paletteLength
+ + ", headerSize: " + headerSize + ")");
+ } else if (extraBytes > 0) {
+ readBytes("BitmapDataOffset", is, extraBytes, "Not a Valid BMP File");
+ }
+
+ final int imageDataSize = bhi.height * imageLineLength;
+
+ if (verbose) {
+ debugNumber("imageDataSize", imageDataSize, 4);
+ }
+
+ byte[] imageData;
+ if (rle) {
+ imageData = getRLEBytes(is, rleSamplesPerByte);
+ } else {
+ imageData = readBytes("ImageData", is, imageDataSize,
+ "Not a Valid BMP File");
+ }
+
+ if (verbose) {
+ debugNumber("ImageData.length", imageData.length, 4);
+ }
+
+ PixelParser pixelParser;
+
+ switch (bhi.compression) {
+ case BI_RLE4:
+ case BI_RLE8:
+ pixelParser = new PixelParserRle(bhi, colorTable, imageData);
+ break;
+ case BI_RGB:
+ pixelParser = new PixelParserRgb(bhi, colorTable, imageData);
+ break;
+ case BI_BITFIELDS:
+ pixelParser = new PixelParserBitFields(bhi, colorTable, imageData);
+ break;
+ default:
+ throw new ImageReadException("BMP: Unknown Compression: "
+ + bhi.compression);
+ }
+
+ return new ImageContents(bhi, colorTable, imageData, pixelParser);
+ }
+
+ private BmpHeaderInfo readBmpHeaderInfo(final ByteSource byteSource,
+ final boolean verbose) throws ImageReadException, IOException {
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+
+ // readSignature(is);
+ final BmpHeaderInfo ret = readBmpHeaderInfo(is, null, verbose);
+ canThrow = true;
+ return ret;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ @Override
+ public byte[] getICCProfileBytes(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public Dimension getImageSize(final ByteSource byteSource, Map params)
+ throws ImageReadException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ final boolean verbose = Boolean.TRUE.equals(params.get(PARAM_KEY_VERBOSE));
+
+ if (params.containsKey(PARAM_KEY_VERBOSE)) {
+ params.remove(PARAM_KEY_VERBOSE);
+ }
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageReadException("Unknown parameter: " + firstKey);
+ }
+
+ final BmpHeaderInfo bhi = readBmpHeaderInfo(byteSource, verbose);
+
+ if (bhi == null) {
+ throw new ImageReadException("BMP: couldn't read header");
+ }
+
+ return new Dimension(bhi.width, bhi.height);
+
+ }
+
+ @Override
+ public IImageMetadata getMetadata(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ private String getBmpTypeDescription(final int identifier1, final int identifier2) {
+ if ((identifier1 == 'B') && (identifier2 == 'M')) {
+ return "Windows 3.1x, 95, NT,";
+ }
+ if ((identifier1 == 'B') && (identifier2 == 'A')) {
+ return "OS/2 Bitmap Array";
+ }
+ if ((identifier1 == 'C') && (identifier2 == 'I')) {
+ return "OS/2 Color Icon";
+ }
+ if ((identifier1 == 'C') && (identifier2 == 'P')) {
+ return "OS/2 Color Pointer";
+ }
+ if ((identifier1 == 'I') && (identifier2 == 'C')) {
+ return "OS/2 Icon";
+ }
+ if ((identifier1 == 'P') && (identifier2 == 'T')) {
+ return "OS/2 Pointer";
+ }
+
+ return "Unknown";
+ }
+
+ @Override
+ public ImageInfo getImageInfo(final ByteSource byteSource, Map params)
+ throws ImageReadException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ final boolean verbose = Boolean.TRUE.equals(params.get(PARAM_KEY_VERBOSE));
+
+ if (params.containsKey(PARAM_KEY_VERBOSE)) {
+ params.remove(PARAM_KEY_VERBOSE);
+ }
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageReadException("Unknown parameter: " + firstKey);
+ }
+
+ InputStream is = null;
+ ImageContents ic = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+ ic = readImageContents(is, FormatCompliance.getDefault(), verbose);
+ canThrow = true;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+
+ if (ic == null) {
+ throw new ImageReadException("Couldn't read BMP Data");
+ }
+
+ final BmpHeaderInfo bhi = ic.bhi;
+ final byte[] colorTable = ic.colorTable;
+
+ if (bhi == null) {
+ throw new ImageReadException("BMP: couldn't read header");
+ }
+
+ final int height = bhi.height;
+ final int width = bhi.width;
+
+ final List comments = new ArrayList();
+ // TODO: comments...
+
+ final int bitsPerPixel = bhi.bitsPerPixel;
+ final ImageFormat format = ImageFormats.BMP;
+ final String name = "BMP Windows Bitmap";
+ final String mimeType = "image/x-ms-bmp";
+ // we ought to count images, but don't yet.
+ final int numberOfImages = -1;
+ // not accurate ... only reflects first
+ final boolean progressive = false;
+ // boolean progressive = (fPNGChunkIHDR.InterlaceMethod != 0);
+ //
+ // pixels per meter
+ final int physicalWidthDpi = (int) (bhi.hResolution * .0254);
+ final float physicalWidthInch = (float) ((double) width / (double) physicalWidthDpi);
+ // int physicalHeightDpi = 72;
+ final int physicalHeightDpi = (int) (bhi.vResolution * .0254);
+ final float physicalHeightInch = (float) ((double) height / (double) physicalHeightDpi);
+
+ final String formatDetails = "Bmp (" + (char) bhi.identifier1
+ + (char) bhi.identifier2 + ": "
+ + getBmpTypeDescription(bhi.identifier1, bhi.identifier2) + ")";
+
+ final boolean transparent = false;
+
+ final boolean usesPalette = colorTable != null;
+ final int colorType = ImageInfo.COLOR_TYPE_RGB;
+ final String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_RLE;
+
+ return new ImageInfo(formatDetails, bitsPerPixel, comments,
+ format, name, height, mimeType, numberOfImages,
+ physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
+ physicalWidthInch, width, progressive, transparent,
+ usesPalette, colorType, compressionAlgorithm);
+ }
+
+ @Override
+ public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ pw.println("bmp.dumpImageFile");
+
+ final ImageInfo imageData = getImageInfo(byteSource, null);
+
+ imageData.toString(pw, "");
+
+ pw.println("");
+
+ return true;
+ }
+
+ @Override
+ public FormatCompliance getFormatCompliance(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final boolean verbose = false;
+
+ final FormatCompliance result = new FormatCompliance(
+ byteSource.getDescription());
+
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+ readImageContents(is, result, verbose);
+ canThrow = true;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+
+ return result;
+ }
+
+ @Override
+ public BufferedImage getBufferedImage(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+ final BufferedImage ret = getBufferedImage(is, params);
+ canThrow = true;
+ return ret;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ public BufferedImage getBufferedImage(final InputStream inputStream, Map params)
+ throws ImageReadException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ final boolean verbose = Boolean.TRUE.equals(params.get(PARAM_KEY_VERBOSE));
+
+ if (params.containsKey(PARAM_KEY_VERBOSE)) {
+ params.remove(PARAM_KEY_VERBOSE);
+ }
+ if (params.containsKey(BUFFERED_IMAGE_FACTORY)) {
+ params.remove(BUFFERED_IMAGE_FACTORY);
+ }
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageReadException("Unknown parameter: " + firstKey);
+ }
+
+ final ImageContents ic = readImageContents(inputStream,
+ FormatCompliance.getDefault(), verbose);
+ if (ic == null) {
+ throw new ImageReadException("Couldn't read BMP Data");
+ }
+
+ final BmpHeaderInfo bhi = ic.bhi;
+ // byte colorTable[] = ic.colorTable;
+ // byte imageData[] = ic.imageData;
+
+ final int width = bhi.width;
+ final int height = bhi.height;
+
+ if (verbose) {
+ System.out.println("width: " + width);
+ System.out.println("height: " + height);
+ System.out.println("width*height: " + width * height);
+ System.out.println("width*height*4: " + width * height * 4);
+ }
+
+ final PixelParser pixelParser = ic.pixelParser;
+ final ImageBuilder imageBuilder = new ImageBuilder(width, height, true);
+ pixelParser.processImage(imageBuilder);
+
+ return imageBuilder.getBufferedImage();
+
+ }
+
+ @Override
+ public void writeImage(final BufferedImage src, final OutputStream os, Map params)
+ throws ImageWriteException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ PixelDensity pixelDensity = null;
+
+ // clear format key.
+ if (params.containsKey(PARAM_KEY_FORMAT)) {
+ params.remove(PARAM_KEY_FORMAT);
+ }
+ if (params.containsKey(PARAM_KEY_PIXEL_DENSITY)) {
+ pixelDensity = (PixelDensity) params
+ .remove(PARAM_KEY_PIXEL_DENSITY);
+ }
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageWriteException("Unknown parameter: " + firstKey);
+ }
+
+ final SimplePalette palette = new PaletteFactory().makeExactRgbPaletteSimple(
+ src, 256);
+
+ BmpWriter writer;
+ if (palette == null) {
+ writer = new BmpWriterRgb();
+ } else {
+ writer = new BmpWriterPalette(palette);
+ }
+
+ final byte[] imagedata = writer.getImageData(src);
+ final BinaryOutputStream bos = new BinaryOutputStream(os, ByteOrder.LITTLE_ENDIAN);
+
+ // write BitmapFileHeader
+ os.write(0x42); // B, Windows 3.1x, 95, NT, Bitmap
+ os.write(0x4d); // M
+
+ final int filesize = BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE + // header
+ // size
+ 4 * writer.getPaletteSize() + // palette size in bytes
+ imagedata.length;
+ bos.write4Bytes(filesize);
+
+ bos.write4Bytes(0); // reserved
+ bos.write4Bytes(BITMAP_FILE_HEADER_SIZE + BITMAP_INFO_HEADER_SIZE
+ + 4 * writer.getPaletteSize()); // Bitmap Data Offset
+
+ final int width = src.getWidth();
+ final int height = src.getHeight();
+
+ // write BitmapInfoHeader
+ bos.write4Bytes(BITMAP_INFO_HEADER_SIZE); // Bitmap Info Header Size
+ bos.write4Bytes(width); // width
+ bos.write4Bytes(height); // height
+ bos.write2Bytes(1); // Number of Planes
+ bos.write2Bytes(writer.getBitsPerPixel()); // Bits Per Pixel
+
+ bos.write4Bytes(BI_RGB); // Compression
+ bos.write4Bytes(imagedata.length); // Bitmap Data Size
+ bos.write4Bytes(pixelDensity != null ? (int) Math
+ .round(pixelDensity.horizontalDensityMetres()) : 0); // HResolution
+ bos.write4Bytes(pixelDensity != null ? (int) Math
+ .round(pixelDensity.verticalDensityMetres()) : 0); // VResolution
+ if (palette == null) {
+ bos.write4Bytes(0); // Colors
+ } else {
+ bos.write4Bytes(palette.length()); // Colors
+ }
+ bos.write4Bytes(0); // Important Colors
+ // bos.write_4_bytes(0); // Compression
+
+ // write Palette
+ writer.writePalette(bos);
+ // write Image Data
+ bos.write(imagedata);
+ }
+
+ /**
+ * Extracts embedded XML metadata as XML string.
+ *
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ @Override
+ public String getXmpXml(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/formats/bmp/writers/BMPWriter.java b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriter.java
similarity index 79%
rename from src/main/java/org/apache/sanselan/formats/bmp/writers/BMPWriter.java
rename to src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriter.java
index 76c41ec..81401ed 100644
--- a/src/main/java/org/apache/sanselan/formats/bmp/writers/BMPWriter.java
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriter.java
@@ -1,36 +1,33 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.bmp.writers;
-
-import java.io.IOException;
-
-import org.apache.sanselan.common.BinaryOutputStream;
-
-import com.google.code.appengine.awt.image.BufferedImage;
-
-
-public abstract class BMPWriter
-{
- public abstract int getPaletteSize();
-
- public abstract int getBitsPerPixel();
-
- public abstract void writePalette(BinaryOutputStream bos)
- throws IOException;
-
- public abstract byte[] getImageData(BufferedImage src);
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.IOException;
+
+import org.apache.commons.imaging.common.BinaryOutputStream;
+
+abstract class BmpWriter {
+
+ public abstract int getPaletteSize();
+
+ public abstract int getBitsPerPixel();
+
+ public abstract void writePalette(BinaryOutputStream bos) throws IOException;
+
+ public abstract byte[] getImageData(BufferedImage src);
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriterPalette.java b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriterPalette.java
new file mode 100644
index 0000000..434cc34
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriterPalette.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.imaging.common.BinaryOutputStream;
+import org.apache.commons.imaging.palette.SimplePalette;
+
+class BmpWriterPalette extends BmpWriter {
+
+ private final SimplePalette palette;
+ private final int bitsPerSample;
+
+ public BmpWriterPalette(final SimplePalette palette) {
+ this.palette = palette;
+
+ if (palette.length() <= 2) {
+ bitsPerSample = 1;
+ } else if (palette.length() <= 16) {
+ bitsPerSample = 4;
+ } else {
+ bitsPerSample = 8;
+ }
+ }
+
+ @Override
+ public int getPaletteSize() {
+ return palette.length();
+ }
+
+ @Override
+ public int getBitsPerPixel() {
+ return bitsPerSample;
+ }
+
+ @Override
+ public void writePalette(final BinaryOutputStream bos) throws IOException {
+ for (int i = 0; i < palette.length(); i++) {
+ final int rgb = palette.getEntry(i);
+
+ final int red = 0xff & (rgb >> 16);
+ final int green = 0xff & (rgb >> 8);
+ final int blue = 0xff & (rgb >> 0);
+
+ bos.write(blue);
+ bos.write(green);
+ bos.write(red);
+ bos.write(0);
+ }
+ }
+
+ @Override
+ public byte[] getImageData(final BufferedImage src) {
+ final int width = src.getWidth();
+ final int height = src.getHeight();
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ int bitCache = 0;
+ int bitsInCache = 0;
+
+ int bytecount = 0;
+ for (int y = height - 1; y >= 0; y--) {
+ for (int x = 0; x < width; x++) {
+ final int argb = src.getRGB(x, y);
+ final int rgb = 0xffffff & argb;
+
+ final int index = palette.getPaletteIndex(rgb);
+
+ if (bitsPerSample == 8) {
+ baos.write(0xff & index);
+ bytecount++;
+ } else {
+ // 4 or 1
+ bitCache = (bitCache << bitsPerSample) | index;
+ bitsInCache += bitsPerSample;
+ if (bitsInCache >= 8) {
+ baos.write(0xff & bitCache);
+ bytecount++;
+ bitCache = 0;
+ bitsInCache = 0;
+ }
+ }
+ }
+
+ if (bitsInCache > 0) {
+ bitCache = (bitCache << (8 - bitsInCache));
+
+ baos.write(0xff & bitCache);
+ bytecount++;
+ bitCache = 0;
+ bitsInCache = 0;
+ }
+
+ while ((bytecount % 4) != 0) {
+ baos.write(0);
+ bytecount++;
+ }
+ }
+
+ return baos.toByteArray();
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriterRgb.java b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriterRgb.java
new file mode 100644
index 0000000..7652154
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/BmpWriterRgb.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.apache.commons.imaging.common.BinaryOutputStream;
+
+class BmpWriterRgb extends BmpWriter {
+ // private final boolean alpha;
+ //
+ // public BmpWriterRgb(boolean alpha)
+ // {
+ // this.alpha = alpha;
+ // }
+
+ @Override
+ public int getPaletteSize() {
+ return 0;
+ }
+
+ @Override
+ public int getBitsPerPixel() {
+ // return alpha ? 32 : 24;
+ return 24;
+ }
+
+ @Override
+ public void writePalette(final BinaryOutputStream bos) throws IOException {
+ // no palette
+ }
+
+ @Override
+ public byte[] getImageData(final BufferedImage src) {
+ final int width = src.getWidth();
+ final int height = src.getHeight();
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ // BinaryOutputStream bos = new BinaryOutputStream(baos,
+ // BYTE_ORDER_Network);
+
+ int bytecount = 0;
+ for (int y = height - 1; y >= 0; y--) {
+ // for (int y = 0; y < height; y++)
+ for (int x = 0; x < width; x++) {
+ final int argb = src.getRGB(x, y);
+ final int rgb = 0xffffff & argb;
+
+ final int red = 0xff & (rgb >> 16);
+ final int green = 0xff & (rgb >> 8);
+ final int blue = 0xff & (rgb >> 0);
+
+ baos.write(blue);
+ baos.write(green);
+ baos.write(red);
+ bytecount += 3;
+ }
+ while ((bytecount % 4) != 0) {
+ baos.write(0);
+ bytecount++;
+ }
+ }
+
+ return baos.toByteArray();
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/formats/bmp/ImageContents.java b/src/main/java/org/apache/commons/imaging/formats/bmp/ImageContents.java
similarity index 65%
rename from src/main/java/org/apache/sanselan/formats/bmp/ImageContents.java
rename to src/main/java/org/apache/commons/imaging/formats/bmp/ImageContents.java
index 126e8f5..6096c29 100644
--- a/src/main/java/org/apache/sanselan/formats/bmp/ImageContents.java
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/ImageContents.java
@@ -1,37 +1,33 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.bmp;
-
-import org.apache.sanselan.formats.bmp.pixelparsers.PixelParser;
-
-class ImageContents
-{
- public final BmpHeaderInfo bhi;
- public final byte colorTable[];
- public final byte imageData[];
- public final PixelParser pixelParser;
-
- public ImageContents(BmpHeaderInfo bhi, byte ColorTable[],
- byte ImageData[], PixelParser fPixelParser)
- {
- this.bhi = bhi;
- this.colorTable = ColorTable;
- this.imageData = ImageData;
- this.pixelParser = fPixelParser;
- }
-
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+class ImageContents {
+
+ public final BmpHeaderInfo bhi;
+ public final byte[] colorTable;
+ public final byte[] imageData;
+ public final PixelParser pixelParser;
+
+ public ImageContents(BmpHeaderInfo bhi, byte[] colorTable, byte[] imageData, PixelParser pixelParser) {
+ this.bhi = bhi;
+ this.colorTable = colorTable;
+ this.imageData = imageData;
+ this.pixelParser = pixelParser;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParser.java b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParser.java
new file mode 100644
index 0000000..d9fb8aa
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParser.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.ImageBuilder;
+
+abstract class PixelParser {
+
+ public final BmpHeaderInfo bhi;
+ public final byte[] colorTable;
+ public final byte[] imageData;
+
+ protected final InputStream is;
+
+ public PixelParser(final BmpHeaderInfo bhi, final byte[] colorTable, final byte[] imageData) {
+ this.bhi = bhi;
+ this.colorTable = colorTable;
+ this.imageData = imageData;
+
+ is = new ByteArrayInputStream(imageData);
+ }
+
+ public abstract void processImage(ImageBuilder imageBuilder) throws ImageReadException, IOException;
+
+ protected int getColorTableRGB(int index) {
+ index *= 4;
+ final int blue = 0xff & colorTable[index + 0];
+ final int green = 0xff & colorTable[index + 1];
+ final int red = 0xff & colorTable[index + 2];
+ final int alpha = 0xff;
+
+ return (alpha << 24)
+ | (red << 16)
+ | (green << 8)
+ | (blue << 0);
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/formats/bmp/pixelparsers/PixelParserBitFields.java b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserBitFields.java
similarity index 53%
rename from src/main/java/org/apache/sanselan/formats/bmp/pixelparsers/PixelParserBitFields.java
rename to src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserBitFields.java
index c09db11..f02abb1 100644
--- a/src/main/java/org/apache/sanselan/formats/bmp/pixelparsers/PixelParserBitFields.java
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserBitFields.java
@@ -1,129 +1,112 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.bmp.pixelparsers;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.apache.sanselan.ImageReadException;
-import org.apache.sanselan.formats.bmp.BmpHeaderInfo;
-
-public class PixelParserBitFields extends PixelParserSimple
-{
-
- private final int redShift;
- private final int greenShift;
- private final int blueShift;
-
- private final int redMask;
- private final int greenMask;
- private final int blueMask;
-
- public PixelParserBitFields(BmpHeaderInfo bhi, byte ColorTable[],
- byte ImageData[]) throws ImageReadException, IOException
- {
- super(bhi, ColorTable, ImageData);
-
- InputStream bais = new ByteArrayInputStream(ColorTable);
-
- redMask = bfp.read4Bytes("redMask", bais,
- "BMP BI_BITFIELDS Bad Color Table");
- greenMask = bfp.read4Bytes("greenMask", bais,
- "BMP BI_BITFIELDS Bad Color Table");
- blueMask = bfp.read4Bytes("blueMask", bais,
- "BMP BI_BITFIELDS Bad Color Table");
-
- redShift = getMaskShift(redMask);
- greenShift = getMaskShift(greenMask);
- blueShift = getMaskShift(blueMask);
- }
-
- private int getMaskShift(int mask)
- {
- int trailingZeroes = 0;
-
- while ((0x1 & mask) == 0)
- {
- mask = 0x7fffffff & (mask >> 1);
- trailingZeroes++;
- }
-
- int maskLength = 0;
-
- while ((0x1 & mask) == 1)
- {
- mask = 0x7fffffff & (mask >> 1);
- maskLength++;
- }
-
- return (trailingZeroes - (8 - maskLength));
- }
- private int bytecount = 0;
-
- public int getNextRGB() throws ImageReadException, IOException
- {
- int data;
-
- if (bhi.bitsPerPixel == 8)
- {
- data = 0xff & imageData[bytecount + 0];
- bytecount += 1;
- }
- else if (bhi.bitsPerPixel == 24)
- {
- data = bfp.read3Bytes("Pixel", is, "BMP Image Data");
- bytecount += 3;
- }
- else if (bhi.bitsPerPixel == 32)
- {
- data = bfp.read4Bytes("Pixel", is, "BMP Image Data");
- bytecount += 4;
- }
- else if (bhi.bitsPerPixel == 16)
- {
- data = bfp.read2Bytes("Pixel", is, "BMP Image Data");
- bytecount += 2;
- }
- else
- throw new ImageReadException("Unknown BitsPerPixel: "
- + bhi.bitsPerPixel);
-
- int red = (redMask & data);
- int green = (greenMask & data);
- int blue = (blueMask & data);
-
- red = (redShift >= 0) ? red >> redShift : red << -redShift;
- green = (greenShift >= 0) ? green >> greenShift : green << -greenShift;
- blue = (blueShift >= 0) ? blue >> blueShift : blue << -blueShift;
-
- int alpha = 0xff;
-
- int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
-
- return rgb;
- }
-
- public void newline() throws ImageReadException, IOException
- {
- while (((bytecount) % 4) != 0)
- {
- bfp.readByte("Pixel", is, "BMP Image Data");
- bytecount++;
- }
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import java.io.IOException;
+import java.nio.ByteOrder;
+
+import org.apache.commons.imaging.ImageReadException;
+
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+class PixelParserBitFields extends PixelParserSimple {
+
+ private final int redShift;
+ private final int greenShift;
+ private final int blueShift;
+ private final int alphaShift;
+
+ private final int redMask;
+ private final int greenMask;
+ private final int blueMask;
+ private final int alphaMask;
+
+ private int bytecount;
+
+ public PixelParserBitFields(final BmpHeaderInfo bhi, final byte[] colorTable, final byte[] imageData) {
+ super(bhi, colorTable, imageData);
+
+ redMask = bhi.redMask;
+ greenMask = bhi.greenMask;
+ blueMask = bhi.blueMask;
+ alphaMask = bhi.alphaMask;
+
+ redShift = getMaskShift(redMask);
+ greenShift = getMaskShift(greenMask);
+ blueShift = getMaskShift(blueMask);
+ alphaShift = (alphaMask != 0 ? getMaskShift(alphaMask) : 0);
+ }
+
+ private int getMaskShift(int mask) {
+ int trailingZeroes = 0;
+
+ while ((0x1 & mask) == 0) {
+ mask = 0x7fffffff & (mask >> 1);
+ trailingZeroes++;
+ }
+
+ int maskLength = 0;
+
+ while ((0x1 & mask) == 1) {
+ mask = 0x7fffffff & (mask >> 1);
+ maskLength++;
+ }
+
+ return trailingZeroes - (8 - maskLength);
+ }
+
+ @Override
+ public int getNextRGB() throws ImageReadException, IOException {
+ int data;
+
+ if (bhi.bitsPerPixel == 8) {
+ data = 0xff & imageData[bytecount + 0];
+ bytecount += 1;
+ } else if (bhi.bitsPerPixel == 24) {
+ data = read3Bytes("Pixel", is, "BMP Image Data", ByteOrder.LITTLE_ENDIAN);
+ bytecount += 3;
+ } else if (bhi.bitsPerPixel == 32) {
+ data = read4Bytes("Pixel", is, "BMP Image Data", ByteOrder.LITTLE_ENDIAN);
+ bytecount += 4;
+ } else if (bhi.bitsPerPixel == 16) {
+ data = read2Bytes("Pixel", is, "BMP Image Data", ByteOrder.LITTLE_ENDIAN);
+ bytecount += 2;
+ } else {
+ throw new ImageReadException("Unknown BitsPerPixel: " + bhi.bitsPerPixel);
+ }
+
+ int red = (redMask & data);
+ int green = (greenMask & data);
+ int blue = (blueMask & data);
+ int alpha = (alphaMask != 0 ? alphaMask & data : 0xff);
+
+ red = (redShift >= 0) ? red >> redShift : red << -redShift;
+ green = (greenShift >= 0) ? green >> greenShift : green << -greenShift;
+ blue = (blueShift >= 0) ? blue >> blueShift : blue << -blueShift;
+ alpha = (alphaShift >= 0) ? alpha >> alphaShift : alpha << -alphaShift;
+
+ return (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+ }
+
+ @Override
+ public void newline() throws ImageReadException, IOException {
+ while ((bytecount % 4) != 0) {
+ readByte("Pixel", is, "BMP Image Data");
+ bytecount++;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserRgb.java b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserRgb.java
new file mode 100644
index 0000000..4c53193
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserRgb.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import java.io.IOException;
+import java.nio.ByteOrder;
+
+import org.apache.commons.imaging.ImageReadException;
+
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+class PixelParserRgb extends PixelParserSimple {
+ private int bytecount;
+ private int cachedBitCount;
+ private int cachedByte;
+ private int pixelCount;
+
+ public PixelParserRgb(final BmpHeaderInfo bhi, final byte[] colorTable, final byte[] imageData) {
+ super(bhi, colorTable, imageData);
+
+ }
+
+ @Override
+ public int getNextRGB() throws ImageReadException, IOException {
+ pixelCount++;
+
+ if ((bhi.bitsPerPixel == 1)
+ || (bhi.bitsPerPixel == 4)) { // always grayscale?
+ if (cachedBitCount < bhi.bitsPerPixel) {
+ if (cachedBitCount != 0) {
+ throw new ImageReadException("Unexpected leftover bits: "
+ + cachedBitCount + "/" + bhi.bitsPerPixel);
+ }
+
+ cachedBitCount += 8;
+ cachedByte = (0xff & imageData[bytecount]);
+ bytecount++;
+ }
+ final int cacheMask = (1 << bhi.bitsPerPixel) - 1;
+ final int sample = cacheMask & (cachedByte >> (8 - bhi.bitsPerPixel));
+ cachedByte = 0xff & (cachedByte << bhi.bitsPerPixel);
+ cachedBitCount -= bhi.bitsPerPixel;
+
+ return getColorTableRGB(sample);
+ } else if (bhi.bitsPerPixel == 8) { // always grayscale?
+ final int sample = 0xff & imageData[bytecount + 0];
+
+ final int rgb = getColorTableRGB(sample);
+
+ bytecount += 1;
+
+ return rgb;
+ } else if (bhi.bitsPerPixel == 16) {
+ final int data = read2Bytes("Pixel", is, "BMP Image Data", ByteOrder.LITTLE_ENDIAN);
+
+ final int blue = (0x1f & (data >> 0)) << 3;
+ final int green = (0x1f & (data >> 5)) << 3;
+ final int red = (0x1f & (data >> 10)) << 3;
+ final int alpha = 0xff;
+
+ final int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+
+ bytecount += 2;
+
+ return rgb;
+ } else if (bhi.bitsPerPixel == 24) {
+ final int blue = 0xff & imageData[bytecount + 0];
+ final int green = 0xff & imageData[bytecount + 1];
+ final int red = 0xff & imageData[bytecount + 2];
+ final int alpha = 0xff;
+
+ final int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+
+ bytecount += 3;
+
+ return rgb;
+ } else if (bhi.bitsPerPixel == 32) {
+ final int blue = 0xff & imageData[bytecount + 0];
+ final int green = 0xff & imageData[bytecount + 1];
+ final int red = 0xff & imageData[bytecount + 2];
+ final int alpha = 0xff;
+
+ final int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+
+ bytecount += 4;
+
+ return rgb;
+ }
+
+ throw new ImageReadException("Unknown BitsPerPixel: "
+ + bhi.bitsPerPixel);
+ }
+
+ @Override
+ public void newline() throws ImageReadException, IOException {
+ cachedBitCount = 0;
+
+ while (((bytecount) % 4) != 0) {
+ readByte("Pixel", is, "BMP Image Data");
+ bytecount++;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserRle.java b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserRle.java
new file mode 100644
index 0000000..1106520
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserRle.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import java.io.IOException;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.BinaryFunctions;
+import org.apache.commons.imaging.common.ImageBuilder;
+
+class PixelParserRle extends PixelParser {
+
+ public PixelParserRle(final BmpHeaderInfo bhi, final byte[] colorTable, final byte[] imageData) {
+ super(bhi, colorTable, imageData);
+
+ }
+
+ private int getSamplesPerByte() throws ImageReadException {
+ if (bhi.bitsPerPixel == 8) {
+ return 1;
+ } else if (bhi.bitsPerPixel == 4) {
+ return 2;
+ } else {
+ throw new ImageReadException("BMP RLE: bad BitsPerPixel: "
+ + bhi.bitsPerPixel);
+ }
+ }
+
+ private int[] convertDataToSamples(final int data) throws ImageReadException {
+ int[] rgbs;
+ if (bhi.bitsPerPixel == 8) {
+ rgbs = new int[1];
+ rgbs[0] = getColorTableRGB(data);
+ // pixels_written = 1;
+ } else if (bhi.bitsPerPixel == 4) {
+ rgbs = new int[2];
+ final int sample1 = data >> 4;
+ final int sample2 = 0x0f & data;
+ rgbs[0] = getColorTableRGB(sample1);
+ rgbs[1] = getColorTableRGB(sample2);
+ // pixels_written = 2;
+ } else {
+ throw new ImageReadException("BMP RLE: bad BitsPerPixel: "
+ + bhi.bitsPerPixel);
+ }
+
+ return rgbs;
+ }
+
+ private int processByteOfData(final int[] rgbs, final int repeat, int x, final int y,
+ final int width, final int height, final ImageBuilder imageBuilder) {
+ // int rbg
+ int pixelsWritten = 0;
+ for (int i = 0; i < repeat; i++) {
+
+ if ((x >= 0) && (x < width) && (y >= 0) && (y < height)) {
+ // int rgb = 0xff000000;
+ // rgb = getNextRGB();
+ final int rgb = rgbs[i % rgbs.length];
+ // bi.setRGB(x, y, rgb);
+ imageBuilder.setRGB(x, y, rgb);
+ // bi.setRGB(x, y, 0xff00ff00);
+ } else {
+ System.out.println("skipping bad pixel (" + x + "," + y + ")");
+ }
+
+ x++;
+ pixelsWritten++;
+ }
+
+ return pixelsWritten;
+ }
+
+ @Override
+ public void processImage(final ImageBuilder imageBuilder)
+ throws ImageReadException, IOException {
+ final int width = bhi.width;
+ final int height = bhi.height;
+ int x = 0;
+ int y = height - 1;
+
+ boolean done = false;
+ while (!done) {
+ final int a = 0xff & BinaryFunctions.readByte("RLE (" + x + "," + y + ") a", is, "BMP: Bad RLE");
+ final int b = 0xff & BinaryFunctions.readByte("RLE (" + x + "," + y + ") b", is, "BMP: Bad RLE");
+
+ if (a == 0) {
+ switch (b) {
+ case 0: {
+ // EOL
+ y--;
+ x = 0;
+ break;
+ }
+ case 1:
+ // EOF
+ done = true;
+ break;
+ case 2: {
+ final int deltaX = 0xff & BinaryFunctions.readByte("RLE deltaX", is, "BMP: Bad RLE");
+ final int deltaY = 0xff & BinaryFunctions.readByte("RLE deltaY", is, "BMP: Bad RLE");
+ x += deltaX;
+ y -= deltaY;
+ break;
+ }
+ default: {
+ final int samplesPerByte = getSamplesPerByte();
+ int size = b / samplesPerByte;
+ if ((b % samplesPerByte) > 0) {
+ size++;
+ }
+ if ((size % 2) != 0) {
+ size++;
+ }
+
+ // System.out.println("b: " + b);
+ // System.out.println("size: " + size);
+ // System.out.println("SamplesPerByte: " + SamplesPerByte);
+
+ final byte[] bytes = BinaryFunctions.readBytes("bytes", is, size, "RLE: Absolute Mode");
+
+ int remaining = b;
+
+ for (int i = 0; remaining > 0; i++) {
+ // for (int i = 0; i < bytes.length; i++)
+ final int[] samples = convertDataToSamples(0xff & bytes[i]);
+ final int towrite = Math.min(remaining, samplesPerByte);
+ // System.out.println("remaining: " + remaining);
+ // System.out.println("SamplesPerByte: "
+ // + SamplesPerByte);
+ // System.out.println("towrite: " + towrite);
+ final int written = processByteOfData(samples, towrite, x, y,
+ width, height, imageBuilder);
+ // System.out.println("written: " + written);
+ // System.out.println("");
+ x += written;
+ remaining -= written;
+ }
+ break;
+ }
+ }
+ } else {
+ final int[] rgbs = convertDataToSamples(b);
+
+ x += processByteOfData(rgbs, a, x, y, width, height,
+ imageBuilder);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/formats/bmp/pixelparsers/PixelParserSimple.java b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserSimple.java
similarity index 53%
rename from src/main/java/org/apache/sanselan/formats/bmp/pixelparsers/PixelParserSimple.java
rename to src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserSimple.java
index 45b91b0..b2171d4 100644
--- a/src/main/java/org/apache/sanselan/formats/bmp/pixelparsers/PixelParserSimple.java
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/PixelParserSimple.java
@@ -1,56 +1,45 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.bmp.pixelparsers;
-
-import java.io.IOException;
-
-import org.apache.sanselan.ImageReadException;
-import org.apache.sanselan.formats.bmp.BmpHeaderInfo;
-
-import com.google.code.appengine.awt.image.BufferedImage;
-
-
-public abstract class PixelParserSimple extends PixelParser
-{
- public PixelParserSimple(BmpHeaderInfo bhi, byte ColorTable[],
- byte ImageData[])
- {
- super(bhi, ColorTable, ImageData);
- }
-
- public abstract int getNextRGB() throws ImageReadException, IOException;
-
- public abstract void newline() throws ImageReadException, IOException;
-
- public void processImage(BufferedImage bi) throws ImageReadException,
- IOException
- {
-// DataBuffer db = bi.getRaster().getDataBuffer();
-
- for (int y = bhi.height - 1; y >= 0; y--)
- {
- for (int x = 0; x < bhi.width; x++)
- {
- int rgb = getNextRGB();
-
- bi.setRGB(x, y, rgb);
-// db.setElem(y * bhi.width + x, rgb);
- }
- newline();
- }
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
+import java.io.IOException;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.ImageBuilder;
+
+abstract class PixelParserSimple extends PixelParser {
+ public PixelParserSimple(final BmpHeaderInfo bhi, final byte[] colorTable, final byte[] imageData) {
+ super(bhi, colorTable, imageData);
+ }
+
+ public abstract int getNextRGB() throws ImageReadException, IOException;
+
+ public abstract void newline() throws ImageReadException, IOException;
+
+ @Override
+ public void processImage(final ImageBuilder imageBuilder) throws ImageReadException, IOException {
+ for (int y = bhi.height - 1; y >= 0; y--) {
+ for (int x = 0; x < bhi.width; x++) {
+ final int rgb = getNextRGB();
+
+ imageBuilder.setRGB(x, y, rgb);
+ // db.setElem(y * bhi.width + x, rgb);
+ }
+ newline();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/bmp/package-info.java b/src/main/java/org/apache/commons/imaging/formats/bmp/package-info.java
new file mode 100644
index 0000000..138a8a4
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/bmp/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The BMP image format.
+ */
+package org.apache.commons.imaging.formats.bmp;
+
diff --git a/src/main/java/org/apache/commons/imaging/formats/dcx/DcxImageParser.java b/src/main/java/org/apache/commons/imaging/formats/dcx/DcxImageParser.java
new file mode 100644
index 0000000..f600669
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/dcx/DcxImageParser.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.dcx;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.imaging.ImageFormat;
+import org.apache.commons.imaging.ImageFormats;
+import org.apache.commons.imaging.ImageInfo;
+import org.apache.commons.imaging.ImageParser;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.ImageWriteException;
+import org.apache.commons.imaging.PixelDensity;
+import org.apache.commons.imaging.common.BinaryOutputStream;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.common.bytesource.ByteSourceInputStream;
+import org.apache.commons.imaging.formats.pcx.PcxConstants;
+import org.apache.commons.imaging.formats.pcx.PcxImageParser;
+import org.apache.commons.imaging.util.IoUtils;
+
+import static org.apache.commons.imaging.ImagingConstants.*;
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+public class DcxImageParser extends ImageParser {
+ // See http://www.fileformat.info/format/pcx/egff.htm for documentation
+ private static final String DEFAULT_EXTENSION = ".dcx";
+ private static final String[] ACCEPTED_EXTENSIONS = { ".dcx", };
+
+ public DcxImageParser() {
+ super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @Override
+ public String getName() {
+ return "Dcx-Custom";
+ }
+
+ @Override
+ public String getDefaultExtension() {
+ return DEFAULT_EXTENSION;
+ }
+
+ @Override
+ protected String[] getAcceptedExtensions() {
+ return ACCEPTED_EXTENSIONS;
+ }
+
+ @Override
+ protected ImageFormat[] getAcceptedTypes() {
+ return new ImageFormat[] {
+ ImageFormats.DCX, //
+ };
+ }
+
+ @Override
+ public IImageMetadata getMetadata(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public ImageInfo getImageInfo(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public Dimension getImageSize(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public byte[] getICCProfileBytes(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ private static class DcxHeader {
+
+ public static final int DCX_ID = 0x3ADE68B1;
+ public final int id;
+ public final long[] pageTable;
+
+ public DcxHeader(final int id, final long[] pageTable) {
+ this.id = id;
+ this.pageTable = pageTable;
+ }
+
+ public void dump(final PrintWriter pw) {
+ pw.println("DcxHeader");
+ pw.println("Id: 0x" + Integer.toHexString(id));
+ pw.println("Pages: " + pageTable.length);
+ pw.println();
+ }
+ }
+
+ private DcxHeader readDcxHeader(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+ final int id = read4Bytes("Id", is, "Not a Valid DCX File", getByteOrder());
+ final List pageTable = new ArrayList(1024);
+ for (int i = 0; i < 1024; i++) {
+ final long pageOffset = 0xFFFFffffL & read4Bytes("PageTable", is,
+ "Not a Valid DCX File", getByteOrder());
+ if (pageOffset == 0) {
+ break;
+ }
+ pageTable.add(pageOffset);
+ }
+
+ if (id != DcxHeader.DCX_ID) {
+ throw new ImageReadException(
+ "Not a Valid DCX File: file id incorrect");
+ }
+ if (pageTable.size() == 1024) {
+ throw new ImageReadException(
+ "DCX page table not terminated by zero entry");
+ }
+
+ final Object[] objects = pageTable.toArray();
+ final long[] pages = new long[objects.length];
+ for (int i = 0; i < objects.length; i++) {
+ pages[i] = ((Long) objects[i]);
+ }
+
+ final DcxHeader ret = new DcxHeader(id, pages);
+ canThrow = true;
+ return ret;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ @Override
+ public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ readDcxHeader(byteSource).dump(pw);
+ return true;
+ }
+
+ @Override
+ public final BufferedImage getBufferedImage(final ByteSource byteSource,
+ final Map params) throws ImageReadException, IOException {
+ final List list = getAllBufferedImages(byteSource);
+ if (list.isEmpty()) {
+ return null;
+ }
+ return list.get(0);
+ }
+
+ @Override
+ public List getAllBufferedImages(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final DcxHeader dcxHeader = readDcxHeader(byteSource);
+ final List images = new ArrayList();
+ final PcxImageParser pcxImageParser = new PcxImageParser();
+ for (final long element : dcxHeader.pageTable) {
+ InputStream stream = null;
+ boolean canThrow = false;
+ try {
+ stream = byteSource.getInputStream(element);
+ final ByteSourceInputStream pcxSource = new ByteSourceInputStream(
+ stream, null);
+ final BufferedImage image = pcxImageParser.getBufferedImage(
+ pcxSource, new HashMap());
+ images.add(image);
+ canThrow = true;
+ } finally {
+ IoUtils.closeQuietly(canThrow, stream);
+ }
+ }
+ return images;
+ }
+
+ @Override
+ public void writeImage(final BufferedImage src, final OutputStream os, Map params)
+ throws ImageWriteException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ final HashMap pcxParams = new HashMap();
+
+ // clear format key.
+ if (params.containsKey(PARAM_KEY_FORMAT)) {
+ params.remove(PARAM_KEY_FORMAT);
+ }
+
+ if (params.containsKey(PcxConstants.PARAM_KEY_PCX_COMPRESSION)) {
+ final Object value = params
+ .remove(PcxConstants.PARAM_KEY_PCX_COMPRESSION);
+ pcxParams.put(PcxConstants.PARAM_KEY_PCX_COMPRESSION, value);
+ }
+
+ if (params.containsKey(PARAM_KEY_PIXEL_DENSITY)) {
+ final Object value = params.remove(PARAM_KEY_PIXEL_DENSITY);
+ if (value != null) {
+ if (!(value instanceof PixelDensity)) {
+ throw new ImageWriteException(
+ "Invalid pixel density parameter");
+ }
+ pcxParams.put(PARAM_KEY_PIXEL_DENSITY, value);
+ }
+ }
+
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageWriteException("Unknown parameter: " + firstKey);
+ }
+
+ final int headerSize = 4 + 1024 * 4;
+
+ final BinaryOutputStream bos = new BinaryOutputStream(os,
+ ByteOrder.LITTLE_ENDIAN);
+ bos.write4Bytes(DcxHeader.DCX_ID);
+ // Some apps may need a full 1024 entry table
+ bos.write4Bytes(headerSize);
+ for (int i = 0; i < 1023; i++) {
+ bos.write4Bytes(0);
+ }
+ final PcxImageParser pcxImageParser = new PcxImageParser();
+ pcxImageParser.writeImage(src, bos, pcxParams);
+ }
+
+ /**
+ * Extracts embedded XML metadata as XML string.
+ *
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ @Override
+ public String getXmpXml(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/dcx/package-info.java b/src/main/java/org/apache/commons/imaging/formats/dcx/package-info.java
new file mode 100644
index 0000000..9c4706c
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/dcx/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The DCX image format.
+ */
+package org.apache.commons.imaging.formats.dcx;
+
diff --git a/src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java b/src/main/java/org/apache/commons/imaging/formats/gif/GenericGifBlock.java
similarity index 63%
rename from src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java
rename to src/main/java/org/apache/commons/imaging/formats/gif/GenericGifBlock.java
index d85cbde..c4c6ae3 100644
--- a/src/main/java/org/apache/sanselan/formats/gif/GenericGIFBlock.java
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/GenericGifBlock.java
@@ -1,55 +1,51 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.gif;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-
-class GenericGIFBlock extends GIFBlock
-{
- public final ArrayList subblocks;
-
- public GenericGIFBlock(int blockCode, ArrayList subblocks)
- {
- super(blockCode);
-
- this.subblocks = subblocks;
-
- }
-
- public byte[] appendSubBlocks() throws IOException
- {
- return appendSubBlocks(false);
- }
-
- public byte[] appendSubBlocks(boolean includeLengths) throws IOException
- {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
-
- for (int i = 0; i < subblocks.size(); i++)
- {
- byte subblock[] = (byte[]) subblocks.get(i);
- if(includeLengths && i>0)
- out.write(subblock.length);
- out.write(subblock);
- }
-
- return out.toByteArray();
- }
-
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.gif;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+class GenericGifBlock extends GifBlock {
+ public final List subblocks;
+
+ public GenericGifBlock(final int blockCode, final List subblocks) {
+ super(blockCode);
+
+ this.subblocks = subblocks;
+
+ }
+
+ public byte[] appendSubBlocks() throws IOException {
+ return appendSubBlocks(false);
+ }
+
+ public byte[] appendSubBlocks(final boolean includeLengths) throws IOException {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ for (int i = 0; i < subblocks.size(); i++) {
+ final byte[] subblock = subblocks.get(i);
+ if (includeLengths && i > 0) {
+ out.write(subblock.length);
+ }
+ out.write(subblock);
+ }
+
+ return out.toByteArray();
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/formats/gif/GIFBlock.java b/src/main/java/org/apache/commons/imaging/formats/gif/GifBlock.java
similarity index 87%
rename from src/main/java/org/apache/sanselan/formats/gif/GIFBlock.java
rename to src/main/java/org/apache/commons/imaging/formats/gif/GifBlock.java
index 27c9089..71d8f5e 100644
--- a/src/main/java/org/apache/sanselan/formats/gif/GIFBlock.java
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/GifBlock.java
@@ -1,27 +1,25 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.gif;
-
-class GIFBlock
-{
- public final int blockCode;
-
- public GIFBlock(int blockCode)
- {
- this.blockCode = blockCode;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.gif;
+
+class GifBlock {
+ public final int blockCode;
+
+ public GifBlock(final int blockCode) {
+ this.blockCode = blockCode;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/formats/gif/GIFHeaderInfo.java b/src/main/java/org/apache/commons/imaging/formats/gif/GifHeaderInfo.java
similarity index 52%
rename from src/main/java/org/apache/sanselan/formats/gif/GIFHeaderInfo.java
rename to src/main/java/org/apache/commons/imaging/formats/gif/GifHeaderInfo.java
index 3443198..ee4f11f 100644
--- a/src/main/java/org/apache/sanselan/formats/gif/GIFHeaderInfo.java
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/GifHeaderInfo.java
@@ -1,61 +1,59 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.gif;
-
-class GIFHeaderInfo
-{
- public final byte identifier1;
- public final byte identifier2;
- public final byte identifier3;
- public final byte version1;
- public final byte version2;
- public final byte version3;
-
- public final int logicalScreenWidth;
- public final int logicalScreenHeight;
- public final byte packedFields;
- public final byte backgroundColorIndex;
- public final byte pixelAspectRatio;
- public final boolean globalColorTableFlag;
- public final byte colorResolution;
- public final boolean sortFlag;
- public final byte sizeOfGlobalColorTable;
-
- public GIFHeaderInfo(byte Identifier1, byte Identifier2, byte Identifier3,
- byte Version1, byte Version2, byte Version3,
- int LogicalScreenWidth, int LogicalScreenHeight, byte PackedFields,
- byte BackgroundColorIndex, byte PixelAspectRatio,
- boolean GlobalColorTableFlag, byte ColorResolution,
- boolean SortFlag, byte SizeofGlobalColorTable)
- {
- this.identifier1 = Identifier1;
- this.identifier2 = Identifier2;
- this.identifier3 = Identifier3;
- this.version1 = Version1;
- this.version2 = Version2;
- this.version3 = Version3;
- this.logicalScreenWidth = LogicalScreenWidth;
- this.logicalScreenHeight = LogicalScreenHeight;
- this.packedFields = PackedFields;
- this.backgroundColorIndex = BackgroundColorIndex;
- this.pixelAspectRatio = PixelAspectRatio;
- this.globalColorTableFlag = GlobalColorTableFlag;
- this.colorResolution = ColorResolution;
- this.sortFlag = SortFlag;
- this.sizeOfGlobalColorTable = SizeofGlobalColorTable;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.gif;
+
+class GifHeaderInfo {
+ public final byte identifier1;
+ public final byte identifier2;
+ public final byte identifier3;
+ public final byte version1;
+ public final byte version2;
+ public final byte version3;
+
+ public final int logicalScreenWidth;
+ public final int logicalScreenHeight;
+ public final byte packedFields;
+ public final byte backgroundColorIndex;
+ public final byte pixelAspectRatio;
+ public final boolean globalColorTableFlag;
+ public final byte colorResolution;
+ public final boolean sortFlag;
+ public final byte sizeOfGlobalColorTable;
+
+ public GifHeaderInfo(final byte identifier1, final byte identifier2, final byte identifier3,
+ final byte version1, final byte version2, final byte version3,
+ final int logicalScreenWidth, final int logicalScreenHeight, final byte packedFields,
+ final byte backgroundColorIndex, final byte pixelAspectRatio,
+ final boolean globalColorTableFlag, final byte colorResolution,
+ final boolean sortFlag, final byte sizeOfGlobalColorTable) {
+ this.identifier1 = identifier1;
+ this.identifier2 = identifier2;
+ this.identifier3 = identifier3;
+ this.version1 = version1;
+ this.version2 = version2;
+ this.version3 = version3;
+ this.logicalScreenWidth = logicalScreenWidth;
+ this.logicalScreenHeight = logicalScreenHeight;
+ this.packedFields = packedFields;
+ this.backgroundColorIndex = backgroundColorIndex;
+ this.pixelAspectRatio = pixelAspectRatio;
+ this.globalColorTableFlag = globalColorTableFlag;
+ this.colorResolution = colorResolution;
+ this.sortFlag = sortFlag;
+ this.sizeOfGlobalColorTable = sizeOfGlobalColorTable;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/gif/GifImageParser.java b/src/main/java/org/apache/commons/imaging/formats/gif/GifImageParser.java
new file mode 100644
index 0000000..eadc1be
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/GifImageParser.java
@@ -0,0 +1,1094 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.gif;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.imaging.FormatCompliance;
+import org.apache.commons.imaging.ImageFormat;
+import org.apache.commons.imaging.ImageFormats;
+import org.apache.commons.imaging.ImageInfo;
+import org.apache.commons.imaging.ImageParser;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.ImageWriteException;
+import org.apache.commons.imaging.common.BinaryOutputStream;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.ImageBuilder;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.common.mylzw.MyLzwCompressor;
+import org.apache.commons.imaging.common.mylzw.MyLzwDecompressor;
+import org.apache.commons.imaging.palette.Palette;
+import org.apache.commons.imaging.palette.PaletteFactory;
+import org.apache.commons.imaging.util.IoUtils;
+
+import static org.apache.commons.imaging.ImagingConstants.*;
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+public class GifImageParser extends ImageParser {
+ private static final String DEFAULT_EXTENSION = ".gif";
+ private static final String[] ACCEPTED_EXTENSIONS = { DEFAULT_EXTENSION, };
+ private static final byte[] GIF_HEADER_SIGNATURE = { 71, 73, 70 };
+ private final static int EXTENSION_CODE = 0x21;
+ private final static int IMAGE_SEPARATOR = 0x2C;
+ private final static int GRAPHIC_CONTROL_EXTENSION = (EXTENSION_CODE << 8) | 0xf9;
+ private final static int COMMENT_EXTENSION = 0xfe;
+ private final static int PLAIN_TEXT_EXTENSION = 0x01;
+ private final static int XMP_EXTENSION = 0xff;
+ private final static int TERMINATOR_BYTE = 0x3b;
+ private final static int APPLICATION_EXTENSION_LABEL = 0xff;
+ private final static int XMP_COMPLETE_CODE = (EXTENSION_CODE << 8)
+ | XMP_EXTENSION;
+ private static final int LOCAL_COLOR_TABLE_FLAG_MASK = 1 << 7;
+ private static final int INTERLACE_FLAG_MASK = 1 << 6;
+ private static final int SORT_FLAG_MASK = 1 << 5;
+ private static final byte[] XMP_APPLICATION_ID_AND_AUTH_CODE = {
+ 0x58, // X
+ 0x4D, // M
+ 0x50, // P
+ 0x20, //
+ 0x44, // D
+ 0x61, // a
+ 0x74, // t
+ 0x61, // a
+ 0x58, // X
+ 0x4D, // M
+ 0x50, // P
+ };
+
+ public GifImageParser() {
+ super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @Override
+ public String getName() {
+ return "Graphics Interchange Format";
+ }
+
+ @Override
+ public String getDefaultExtension() {
+ return DEFAULT_EXTENSION;
+ }
+
+ @Override
+ protected String[] getAcceptedExtensions() {
+ return ACCEPTED_EXTENSIONS;
+ }
+
+ @Override
+ protected ImageFormat[] getAcceptedTypes() {
+ return new ImageFormat[] { ImageFormats.GIF, //
+ };
+ }
+
+ private GifHeaderInfo readHeader(final InputStream is,
+ final FormatCompliance formatCompliance) throws ImageReadException,
+ IOException {
+ final byte identifier1 = readByte("identifier1", is, "Not a Valid GIF File");
+ final byte identifier2 = readByte("identifier2", is, "Not a Valid GIF File");
+ final byte identifier3 = readByte("identifier3", is, "Not a Valid GIF File");
+
+ final byte version1 = readByte("version1", is, "Not a Valid GIF File");
+ final byte version2 = readByte("version2", is, "Not a Valid GIF File");
+ final byte version3 = readByte("version3", is, "Not a Valid GIF File");
+
+ if (formatCompliance != null) {
+ formatCompliance.compareBytes("Signature", GIF_HEADER_SIGNATURE,
+ new byte[]{identifier1, identifier2, identifier3,});
+ formatCompliance.compare("version", 56, version1);
+ formatCompliance
+ .compare("version", new int[] { 55, 57, }, version2);
+ formatCompliance.compare("version", 97, version3);
+ }
+
+ if (getDebug()) {
+ printCharQuad("identifier: ", ((identifier1 << 16)
+ | (identifier2 << 8) | (identifier3 << 0)));
+ printCharQuad("version: ",
+ ((version1 << 16) | (version2 << 8) | (version3 << 0)));
+ }
+
+ final int logicalScreenWidth = read2Bytes("Logical Screen Width", is, "Not a Valid GIF File", getByteOrder());
+ final int logicalScreenHeight = read2Bytes("Logical Screen Height", is, "Not a Valid GIF File", getByteOrder());
+
+ if (formatCompliance != null) {
+ formatCompliance.checkBounds("Width", 1, Integer.MAX_VALUE,
+ logicalScreenWidth);
+ formatCompliance.checkBounds("Height", 1, Integer.MAX_VALUE,
+ logicalScreenHeight);
+ }
+
+ final byte packedFields = readByte("Packed Fields", is,
+ "Not a Valid GIF File");
+ final byte backgroundColorIndex = readByte("Background Color Index", is,
+ "Not a Valid GIF File");
+ final byte pixelAspectRatio = readByte("Pixel Aspect Ratio", is,
+ "Not a Valid GIF File");
+
+ if (getDebug()) {
+ printByteBits("PackedFields bits", packedFields);
+ }
+
+ final boolean globalColorTableFlag = ((packedFields & 128) > 0);
+ if (getDebug()) {
+ System.out.println("GlobalColorTableFlag: " + globalColorTableFlag);
+ }
+ final byte colorResolution = (byte) ((packedFields >> 4) & 7);
+ if (getDebug()) {
+ System.out.println("ColorResolution: " + colorResolution);
+ }
+ final boolean sortFlag = ((packedFields & 8) > 0);
+ if (getDebug()) {
+ System.out.println("SortFlag: " + sortFlag);
+ }
+ final byte sizeofGlobalColorTable = (byte) (packedFields & 7);
+ if (getDebug()) {
+ System.out.println("SizeofGlobalColorTable: "
+ + sizeofGlobalColorTable);
+ }
+
+ if (formatCompliance != null) {
+ if (globalColorTableFlag && backgroundColorIndex != -1) {
+ formatCompliance.checkBounds("Background Color Index", 0,
+ convertColorTableSize(sizeofGlobalColorTable),
+ backgroundColorIndex);
+ }
+ }
+
+ return new GifHeaderInfo(identifier1, identifier2, identifier3,
+ version1, version2, version3, logicalScreenWidth,
+ logicalScreenHeight, packedFields, backgroundColorIndex,
+ pixelAspectRatio, globalColorTableFlag, colorResolution,
+ sortFlag, sizeofGlobalColorTable);
+ }
+
+ private GraphicControlExtension readGraphicControlExtension(final int code,
+ final InputStream is) throws IOException {
+ readByte("block_size", is, "GIF: corrupt GraphicControlExt");
+ final int packed = readByte("packed fields", is,
+ "GIF: corrupt GraphicControlExt");
+
+ final int dispose = (packed & 0x1c) >> 2; // disposal method
+ final boolean transparency = (packed & 1) != 0;
+
+ final int delay = read2Bytes("delay in milliseconds", is, "GIF: corrupt GraphicControlExt", getByteOrder());
+ final int transparentColorIndex = 0xff & readByte("transparent color index",
+ is, "GIF: corrupt GraphicControlExt");
+ readByte("block terminator", is, "GIF: corrupt GraphicControlExt");
+
+ return new GraphicControlExtension(code, packed, dispose, transparency,
+ delay, transparentColorIndex);
+ }
+
+ private byte[] readSubBlock(final InputStream is) throws IOException {
+ final int blockSize = 0xff & readByte("block_size", is, "GIF: corrupt block");
+
+ return readBytes("block", is, blockSize, "GIF: corrupt block");
+ }
+
+ private GenericGifBlock readGenericGIFBlock(final InputStream is, final int code)
+ throws IOException {
+ return readGenericGIFBlock(is, code, null);
+ }
+
+ private GenericGifBlock readGenericGIFBlock(final InputStream is, final int code,
+ final byte[] first) throws IOException {
+ final List subblocks = new ArrayList();
+
+ if (first != null) {
+ subblocks.add(first);
+ }
+
+ while (true) {
+ final byte[] bytes = readSubBlock(is);
+ if (bytes.length < 1) {
+ break;
+ }
+ subblocks.add(bytes);
+ }
+
+ return new GenericGifBlock(code, subblocks);
+ }
+
+ private List readBlocks(final GifHeaderInfo ghi, final InputStream is,
+ final boolean stopBeforeImageData, final FormatCompliance formatCompliance)
+ throws ImageReadException, IOException {
+ final List result = new ArrayList();
+
+ while (true) {
+ final int code = is.read();
+
+ switch (code) {
+ case -1:
+ throw new ImageReadException("GIF: unexpected end of data");
+
+ case IMAGE_SEPARATOR:
+ final ImageDescriptor id = readImageDescriptor(ghi, code, is,
+ stopBeforeImageData, formatCompliance);
+ result.add(id);
+ // if (stopBeforeImageData)
+ // return result;
+
+ break;
+
+ case EXTENSION_CODE: // extension
+ {
+ final int extensionCode = is.read();
+ final int completeCode = ((0xff & code) << 8)
+ | (0xff & extensionCode);
+
+ switch (extensionCode) {
+ case 0xf9:
+ final GraphicControlExtension gce = readGraphicControlExtension(
+ completeCode, is);
+ result.add(gce);
+ break;
+
+ case COMMENT_EXTENSION:
+ case PLAIN_TEXT_EXTENSION: {
+ final GenericGifBlock block = readGenericGIFBlock(is,
+ completeCode);
+ result.add(block);
+ break;
+ }
+
+ case APPLICATION_EXTENSION_LABEL: // 255 (hex 0xFF) Application
+ // Extension Label
+ {
+ final byte[] label = readSubBlock(is);
+
+ if (formatCompliance != null) {
+ formatCompliance.addComment(
+ "Unknown Application Extension ("
+ + new String(label, "US-ASCII") + ")",
+ completeCode);
+ }
+
+ // if (label == new String("ICCRGBG1"))
+ //{
+ // GIF's can have embedded ICC Profiles - who knew?
+ //}
+
+ if ((label != null) && (label.length > 0)) {
+ final GenericGifBlock block = readGenericGIFBlock(is,
+ completeCode, label);
+ result.add(block);
+ }
+ break;
+ }
+
+ default: {
+
+ if (formatCompliance != null) {
+ formatCompliance.addComment("Unknown block",
+ completeCode);
+ }
+
+ final GenericGifBlock block = readGenericGIFBlock(is,
+ completeCode);
+ result.add(block);
+ break;
+ }
+ }
+ }
+ break;
+
+ case TERMINATOR_BYTE:
+ return result;
+
+ case 0x00: // bad byte, but keep going and see what happens
+ break;
+
+ default:
+ throw new ImageReadException("GIF: unknown code: " + code);
+ }
+ }
+ }
+
+ private ImageDescriptor readImageDescriptor(final GifHeaderInfo ghi,
+ final int blockCode, final InputStream is, final boolean stopBeforeImageData,
+ final FormatCompliance formatCompliance) throws ImageReadException,
+ IOException {
+ final int imageLeftPosition = read2Bytes("Image Left Position", is, "Not a Valid GIF File", getByteOrder());
+ final int imageTopPosition = read2Bytes("Image Top Position", is, "Not a Valid GIF File", getByteOrder());
+ final int imageWidth = read2Bytes("Image Width", is, "Not a Valid GIF File", getByteOrder());
+ final int imageHeight = read2Bytes("Image Height", is, "Not a Valid GIF File", getByteOrder());
+ final byte packedFields = readByte("Packed Fields", is, "Not a Valid GIF File");
+
+ if (formatCompliance != null) {
+ formatCompliance.checkBounds("Width", 1, ghi.logicalScreenWidth, imageWidth);
+ formatCompliance.checkBounds("Height", 1, ghi.logicalScreenHeight, imageHeight);
+ formatCompliance.checkBounds("Left Position", 0, ghi.logicalScreenWidth - imageWidth, imageLeftPosition);
+ formatCompliance.checkBounds("Top Position", 0, ghi.logicalScreenHeight - imageHeight, imageTopPosition);
+ }
+
+ if (getDebug()) {
+ printByteBits("PackedFields bits", packedFields);
+ }
+
+ final boolean localColorTableFlag = (((packedFields >> 7) & 1) > 0);
+ if (getDebug()) {
+ System.out.println("LocalColorTableFlag: " + localColorTableFlag);
+ }
+ final boolean interlaceFlag = (((packedFields >> 6) & 1) > 0);
+ if (getDebug()) {
+ System.out.println("Interlace Flag: " + interlaceFlag);
+ }
+ final boolean sortFlag = (((packedFields >> 5) & 1) > 0);
+ if (getDebug()) {
+ System.out.println("Sort Flag: " + sortFlag);
+ }
+
+ final byte sizeOfLocalColorTable = (byte) (packedFields & 7);
+ if (getDebug()) {
+ System.out.println("SizeofLocalColorTable: " + sizeOfLocalColorTable);
+ }
+
+ byte[] localColorTable = null;
+ if (localColorTableFlag) {
+ localColorTable = readColorTable(is, sizeOfLocalColorTable);
+ }
+
+ byte[] imageData = null;
+ if (!stopBeforeImageData) {
+ final int lzwMinimumCodeSize = is.read();
+
+ final GenericGifBlock block = readGenericGIFBlock(is, -1);
+ final byte[] bytes = block.appendSubBlocks();
+ final InputStream bais = new ByteArrayInputStream(bytes);
+
+ final int size = imageWidth * imageHeight;
+ final MyLzwDecompressor myLzwDecompressor = new MyLzwDecompressor(
+ lzwMinimumCodeSize, ByteOrder.LITTLE_ENDIAN);
+ imageData = myLzwDecompressor.decompress(bais, size);
+ } else {
+ final int LZWMinimumCodeSize = is.read();
+ if (getDebug()) {
+ System.out.println("LZWMinimumCodeSize: " + LZWMinimumCodeSize);
+ }
+
+ readGenericGIFBlock(is, -1);
+ }
+
+ return new ImageDescriptor(blockCode,
+ imageLeftPosition, imageTopPosition, imageWidth, imageHeight,
+ packedFields, localColorTableFlag, interlaceFlag, sortFlag,
+ sizeOfLocalColorTable, localColorTable, imageData);
+ }
+
+ private int simplePow(final int base, final int power) {
+ int result = 1;
+
+ for (int i = 0; i < power; i++) {
+ result *= base;
+ }
+
+ return result;
+ }
+
+ private int convertColorTableSize(final int tableSize) {
+ return 3 * simplePow(2, tableSize + 1);
+ }
+
+ private byte[] readColorTable(final InputStream is, final int tableSize) throws IOException {
+ final int actualSize = convertColorTableSize(tableSize);
+
+ return readBytes("block", is, actualSize, "GIF: corrupt Color Table");
+ }
+
+ private GifBlock findBlock(final List blocks, final int code) {
+ for (GifBlock gifBlock : blocks) {
+ if (gifBlock.blockCode == code) {
+ return gifBlock;
+ }
+ }
+ return null;
+ }
+
+ private ImageContents readFile(final ByteSource byteSource,
+ final boolean stopBeforeImageData) throws ImageReadException, IOException {
+ return readFile(byteSource, stopBeforeImageData,
+ FormatCompliance.getDefault());
+ }
+
+ private ImageContents readFile(final ByteSource byteSource,
+ final boolean stopBeforeImageData, final FormatCompliance formatCompliance)
+ throws ImageReadException, IOException {
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+
+ final GifHeaderInfo ghi = readHeader(is, formatCompliance);
+
+ byte[] globalColorTable = null;
+ if (ghi.globalColorTableFlag) {
+ globalColorTable = readColorTable(is,
+ ghi.sizeOfGlobalColorTable);
+ }
+
+ final List blocks = readBlocks(ghi, is, stopBeforeImageData,
+ formatCompliance);
+
+ final ImageContents result = new ImageContents(ghi, globalColorTable,
+ blocks);
+ canThrow = true;
+ return result;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ @Override
+ public byte[] getICCProfileBytes(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public Dimension getImageSize(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageContents blocks = readFile(byteSource, false);
+
+ if (blocks == null) {
+ throw new ImageReadException("GIF: Couldn't read blocks");
+ }
+
+ final GifHeaderInfo bhi = blocks.gifHeaderInfo;
+ if (bhi == null) {
+ throw new ImageReadException("GIF: Couldn't read Header");
+ }
+
+ final ImageDescriptor id = (ImageDescriptor) findBlock(blocks.blocks,
+ IMAGE_SEPARATOR);
+ if (id == null) {
+ throw new ImageReadException("GIF: Couldn't read ImageDescriptor");
+ }
+
+ // Prefer the size information in the ImageDescriptor; it is more
+ // reliable
+ // than the size information in the header.
+ return new Dimension(id.imageWidth, id.imageHeight);
+ }
+
+ @Override
+ public IImageMetadata getMetadata(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ private List getComments(final List blocks) throws IOException {
+ final List result = new ArrayList();
+ final int code = 0x21fe;
+
+ for (GifBlock block : blocks) {
+ if (block.blockCode == code) {
+ final byte[] bytes = ((GenericGifBlock) block).appendSubBlocks();
+ result.add(new String(bytes, "US-ASCII"));
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public ImageInfo getImageInfo(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageContents blocks = readFile(byteSource, false);
+
+ if (blocks == null) {
+ throw new ImageReadException("GIF: Couldn't read blocks");
+ }
+
+ final GifHeaderInfo bhi = blocks.gifHeaderInfo;
+ if (bhi == null) {
+ throw new ImageReadException("GIF: Couldn't read Header");
+ }
+
+ final ImageDescriptor id = (ImageDescriptor) findBlock(blocks.blocks,
+ IMAGE_SEPARATOR);
+ if (id == null) {
+ throw new ImageReadException("GIF: Couldn't read ImageDescriptor");
+ }
+
+ final GraphicControlExtension gce = (GraphicControlExtension) findBlock(
+ blocks.blocks, GRAPHIC_CONTROL_EXTENSION);
+
+ // Prefer the size information in the ImageDescriptor; it is more
+ // reliable than the size information in the header.
+ final int height = id.imageHeight;
+ final int width = id.imageWidth;
+
+ final List comments = getComments(blocks.blocks);
+ final int bitsPerPixel = (bhi.colorResolution + 1);
+ final ImageFormat format = ImageFormats.GIF;
+ final String formatName = "GIF Graphics Interchange Format";
+ final String mimeType = "image/gif";
+ // we ought to count images, but don't yet.
+ final int numberOfImages = -1;
+
+ final boolean progressive = id.interlaceFlag;
+
+ final int physicalWidthDpi = 72;
+ final float physicalWidthInch = (float) ((double) width / (double) physicalWidthDpi);
+ final int physicalHeightDpi = 72;
+ final float physicalHeightInch = (float) ((double) height / (double) physicalHeightDpi);
+
+ final String formatDetails = "Gif " + ((char) blocks.gifHeaderInfo.version1)
+ + ((char) blocks.gifHeaderInfo.version2)
+ + ((char) blocks.gifHeaderInfo.version3);
+
+ boolean transparent = false;
+ if (gce != null && gce.transparency) {
+ transparent = true;
+ }
+
+ final boolean usesPalette = true;
+ final int colorType = ImageInfo.COLOR_TYPE_RGB;
+ final String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_LZW;
+
+ return new ImageInfo(formatDetails, bitsPerPixel, comments,
+ format, formatName, height, mimeType, numberOfImages,
+ physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
+ physicalWidthInch, width, progressive, transparent,
+ usesPalette, colorType, compressionAlgorithm);
+ }
+
+ @Override
+ public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ pw.println("gif.dumpImageFile");
+
+ final ImageInfo imageData = getImageInfo(byteSource);
+ if (imageData == null) {
+ return false;
+ }
+
+ imageData.toString(pw, "");
+
+ final ImageContents blocks = readFile(byteSource, false);
+
+ pw.println("gif.blocks: " + blocks.blocks.size());
+ for (int i = 0; i < blocks.blocks.size(); i++) {
+ final GifBlock gifBlock = blocks.blocks.get(i);
+ this.debugNumber(pw, "\t" + i + " ("
+ + gifBlock.getClass().getName() + ")",
+ gifBlock.blockCode, 4);
+ }
+
+ pw.println("");
+
+ return true;
+ }
+
+ private int[] getColorTable(final byte[] bytes) throws ImageReadException {
+ if ((bytes.length % 3) != 0) {
+ throw new ImageReadException("Bad Color Table Length: "
+ + bytes.length);
+ }
+ final int length = bytes.length / 3;
+
+ final int[] result = new int[length];
+
+ for (int i = 0; i < length; i++) {
+ final int red = 0xff & bytes[(i * 3) + 0];
+ final int green = 0xff & bytes[(i * 3) + 1];
+ final int blue = 0xff & bytes[(i * 3) + 2];
+
+ final int alpha = 0xff;
+
+ final int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+ result[i] = rgb;
+ }
+
+ return result;
+ }
+
+ @Override
+ public FormatCompliance getFormatCompliance(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final FormatCompliance result = new FormatCompliance(
+ byteSource.getDescription());
+
+ readFile(byteSource, false, result);
+
+ return result;
+ }
+
+ @Override
+ public BufferedImage getBufferedImage(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final ImageContents imageContents = readFile(byteSource, false);
+
+ if (imageContents == null) {
+ throw new ImageReadException("GIF: Couldn't read blocks");
+ }
+
+ final GifHeaderInfo ghi = imageContents.gifHeaderInfo;
+ if (ghi == null) {
+ throw new ImageReadException("GIF: Couldn't read Header");
+ }
+
+ final ImageDescriptor id = (ImageDescriptor) findBlock(imageContents.blocks,
+ IMAGE_SEPARATOR);
+ if (id == null) {
+ throw new ImageReadException("GIF: Couldn't read Image Descriptor");
+ }
+ final GraphicControlExtension gce = (GraphicControlExtension) findBlock(
+ imageContents.blocks, GRAPHIC_CONTROL_EXTENSION);
+
+ // Prefer the size information in the ImageDescriptor; it is more
+ // reliable
+ // than the size information in the header.
+ final int width = id.imageWidth;
+ final int height = id.imageHeight;
+
+ boolean hasAlpha = false;
+ if (gce != null && gce.transparency) {
+ hasAlpha = true;
+ }
+
+ final ImageBuilder imageBuilder = new ImageBuilder(width, height, hasAlpha);
+
+ int[] colorTable;
+ if (id.localColorTable != null) {
+ colorTable = getColorTable(id.localColorTable);
+ } else if (imageContents.globalColorTable != null) {
+ colorTable = getColorTable(imageContents.globalColorTable);
+ } else {
+ throw new ImageReadException("Gif: No Color Table");
+ }
+
+ int transparentIndex = -1;
+ if (gce != null && hasAlpha) {
+ transparentIndex = gce.transparentColorIndex;
+ }
+
+ int counter = 0;
+
+ final int rowsInPass1 = (height + 7) / 8;
+ final int rowsInPass2 = (height + 3) / 8;
+ final int rowsInPass3 = (height + 1) / 4;
+ final int rowsInPass4 = (height) / 2;
+
+ for (int row = 0; row < height; row++) {
+ int y;
+ if (id.interlaceFlag) {
+ int theRow = row;
+ if (theRow < rowsInPass1) {
+ y = theRow * 8;
+ } else {
+ theRow -= rowsInPass1;
+ if (theRow < (rowsInPass2)) {
+ y = 4 + (theRow * 8);
+ } else {
+ theRow -= rowsInPass2;
+ if (theRow < (rowsInPass3)) {
+ y = 2 + (theRow * 4);
+ } else {
+ theRow -= rowsInPass3;
+ if (theRow < (rowsInPass4)) {
+ y = 1 + (theRow * 2);
+ } else {
+ throw new ImageReadException("Gif: Strange Row");
+ }
+ }
+ }
+ }
+ } else {
+ y = row;
+ }
+
+ for (int x = 0; x < width; x++) {
+ final int index = 0xff & id.imageData[counter++];
+ int rgb = colorTable[index];
+
+ if (transparentIndex == index) {
+ rgb = 0x00;
+ }
+
+ imageBuilder.setRGB(x, y, rgb);
+ }
+
+ }
+
+ return imageBuilder.getBufferedImage();
+
+ }
+
+ private void writeAsSubBlocks(final OutputStream os, final byte[] bytes) throws IOException {
+ int index = 0;
+
+ while (index < bytes.length) {
+ final int blockSize = Math.min(bytes.length - index, 255);
+ os.write(blockSize);
+ os.write(bytes, index, blockSize);
+ index += blockSize;
+ }
+ os.write(0); // last block
+ }
+
+ @Override
+ public void writeImage(final BufferedImage src, final OutputStream os, Map params)
+ throws ImageWriteException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = new HashMap(params);
+
+ final boolean verbose = Boolean.TRUE.equals(params.get(PARAM_KEY_VERBOSE));
+
+ // clear format key.
+ if (params.containsKey(PARAM_KEY_FORMAT)) {
+ params.remove(PARAM_KEY_FORMAT);
+ }
+ if (params.containsKey(PARAM_KEY_VERBOSE)) {
+ params.remove(PARAM_KEY_VERBOSE);
+ }
+
+ String xmpXml = null;
+ if (params.containsKey(PARAM_KEY_XMP_XML)) {
+ xmpXml = (String) params.get(PARAM_KEY_XMP_XML);
+ params.remove(PARAM_KEY_XMP_XML);
+ }
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageWriteException("Unknown parameter: " + firstKey);
+ }
+
+ final int width = src.getWidth();
+ final int height = src.getHeight();
+
+ final boolean hasAlpha = new PaletteFactory().hasTransparency(src);
+
+ final int maxColors = hasAlpha ? 255 : 256;
+
+ Palette palette2 = new PaletteFactory().makeExactRgbPaletteSimple(src, maxColors);
+ // int palette[] = new PaletteFactory().makePaletteSimple(src, 256);
+ // Map palette_map = paletteToMap(palette);
+
+ if (palette2 == null) {
+ palette2 = new PaletteFactory().makeQuantizedRgbPalette(src, maxColors);
+ if (verbose) {
+ System.out.println("quantizing");
+ }
+ } else if (verbose) {
+ System.out.println("exact palette");
+ }
+
+ if (palette2 == null) {
+ throw new ImageWriteException("Gif: can't write images with more than 256 colors");
+ }
+ final int paletteSize = palette2.length() + (hasAlpha ? 1 : 0);
+
+ final BinaryOutputStream bos = new BinaryOutputStream(os, ByteOrder.LITTLE_ENDIAN);
+
+ // write Header
+ os.write(0x47); // G magic numbers
+ os.write(0x49); // I
+ os.write(0x46); // F
+
+ os.write(0x38); // 8 version magic numbers
+ os.write(0x39); // 9
+ os.write(0x61); // a
+
+ // Logical Screen Descriptor.
+
+ bos.write2Bytes(width);
+ bos.write2Bytes(height);
+
+ final int colorTableScaleLessOne = (paletteSize > 128) ? 7
+ : (paletteSize > 64) ? 6 : (paletteSize > 32) ? 5
+ : (paletteSize > 16) ? 4 : (paletteSize > 8) ? 3
+ : (paletteSize > 4) ? 2
+ : (paletteSize > 2) ? 1 : 0;
+
+ final int colorTableSizeInFormat = 1 << (colorTableScaleLessOne + 1);
+ {
+ final byte colorResolution = (byte) colorTableScaleLessOne; // TODO:
+
+ final boolean globalColorTableFlag = false;
+ final boolean sortFlag = false;
+ final int globalColorTableFlagMask = 1 << 7;
+ final int sortFlagMask = 8;
+ final int sizeOfGlobalColorTable = 0;
+
+ final int packedFields = ((globalColorTableFlag ? globalColorTableFlagMask
+ : 0)
+ | (sortFlag ? sortFlagMask : 0)
+ | ((7 & colorResolution) << 4) | (7 & sizeOfGlobalColorTable));
+ bos.write(packedFields); // one byte
+ }
+ {
+ final byte backgroundColorIndex = 0;
+ bos.write(backgroundColorIndex);
+ }
+ {
+ final byte pixelAspectRatio = 0;
+ bos.write(pixelAspectRatio);
+ }
+
+ //{
+ // write Global Color Table.
+
+ //}
+
+ { // ALWAYS write GraphicControlExtension
+ bos.write(EXTENSION_CODE);
+ bos.write((byte) 0xf9);
+ // bos.write(0xff & (kGraphicControlExtension >> 8));
+ // bos.write(0xff & (kGraphicControlExtension >> 0));
+
+ bos.write((byte) 4); // block size;
+ final int packedFields = hasAlpha ? 1 : 0; // transparency flag
+ bos.write((byte) packedFields);
+ bos.write((byte) 0); // Delay Time
+ bos.write((byte) 0); // Delay Time
+ bos.write((byte) (hasAlpha ? palette2.length() : 0)); // Transparent
+ // Color
+ // Index
+ bos.write((byte) 0); // terminator
+ }
+
+ if (null != xmpXml) {
+ bos.write(EXTENSION_CODE);
+ bos.write(APPLICATION_EXTENSION_LABEL);
+
+ bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE.length); // 0x0B
+ bos.write(XMP_APPLICATION_ID_AND_AUTH_CODE);
+
+ final byte[] xmpXmlBytes = xmpXml.getBytes("utf-8");
+ bos.write(xmpXmlBytes);
+
+ // write "magic trailer"
+ for (int magic = 0; magic <= 0xff; magic++) {
+ bos.write(0xff - magic);
+ }
+
+ bos.write((byte) 0); // terminator
+
+ }
+
+ { // Image Descriptor.
+ bos.write(IMAGE_SEPARATOR);
+ bos.write2Bytes(0); // Image Left Position
+ bos.write2Bytes(0); // Image Top Position
+ bos.write2Bytes(width); // Image Width
+ bos.write2Bytes(height); // Image Height
+
+ {
+ final boolean localColorTableFlag = true;
+ // boolean LocalColorTableFlag = false;
+ final boolean interlaceFlag = false;
+ final boolean sortFlag = false;
+ final int sizeOfLocalColorTable = colorTableScaleLessOne;
+
+ // int SizeOfLocalColorTable = 0;
+
+ final int packedFields;
+ if (localColorTableFlag) {
+ packedFields = (LOCAL_COLOR_TABLE_FLAG_MASK
+ | (interlaceFlag ? INTERLACE_FLAG_MASK : 0)
+ | (sortFlag ? SORT_FLAG_MASK : 0)
+ | (7 & sizeOfLocalColorTable));
+ } else {
+ packedFields = (0
+ | (interlaceFlag ? INTERLACE_FLAG_MASK : 0)
+ | (sortFlag ? SORT_FLAG_MASK : 0)
+ | (7 & sizeOfLocalColorTable));
+ }
+ bos.write(packedFields); // one byte
+ }
+ }
+
+ { // write Local Color Table.
+ for (int i = 0; i < colorTableSizeInFormat; i++) {
+ if (i < palette2.length()) {
+ final int rgb = palette2.getEntry(i);
+
+ final int red = 0xff & (rgb >> 16);
+ final int green = 0xff & (rgb >> 8);
+ final int blue = 0xff & (rgb >> 0);
+
+ bos.write(red);
+ bos.write(green);
+ bos.write(blue);
+ } else {
+ bos.write(0);
+ bos.write(0);
+ bos.write(0);
+ }
+ }
+ }
+
+ { // get Image Data.
+// int image_data_total = 0;
+
+ int lzwMinimumCodeSize = colorTableScaleLessOne + 1;
+ // LZWMinimumCodeSize = Math.max(8, LZWMinimumCodeSize);
+ if (lzwMinimumCodeSize < 2) {
+ lzwMinimumCodeSize = 2;
+ }
+
+ // TODO:
+ // make
+ // better
+ // choice
+ // here.
+ bos.write(lzwMinimumCodeSize);
+
+ final MyLzwCompressor compressor = new MyLzwCompressor(
+ lzwMinimumCodeSize, ByteOrder.LITTLE_ENDIAN, false); // GIF
+ // Mode);
+
+ final byte[] imagedata = new byte[width * height];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ final int argb = src.getRGB(x, y);
+ final int rgb = 0xffffff & argb;
+ int index;
+
+ if (hasAlpha) {
+ final int alpha = 0xff & (argb >> 24);
+ final int alphaThreshold = 255;
+ if (alpha < alphaThreshold) {
+ index = palette2.length(); // is transparent
+ } else {
+ index = palette2.getPaletteIndex(rgb);
+ }
+ } else {
+ index = palette2.getPaletteIndex(rgb);
+ }
+
+ imagedata[y * width + x] = (byte) index;
+ }
+ }
+
+ final byte[] compressed = compressor.compress(imagedata);
+ writeAsSubBlocks(bos, compressed);
+// image_data_total += compressed.length;
+ }
+
+ // palette2.dump();
+
+ bos.write(TERMINATOR_BYTE);
+
+ bos.close();
+ os.close();
+ }
+
+ /**
+ * Extracts embedded XML metadata as XML string.
+ *
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ @Override
+ public String getXmpXml(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+
+ final FormatCompliance formatCompliance = null;
+ final GifHeaderInfo ghi = readHeader(is, formatCompliance);
+
+ if (ghi.globalColorTableFlag) {
+ readColorTable(is, ghi.sizeOfGlobalColorTable);
+ }
+
+ final List blocks = readBlocks(ghi, is, true, formatCompliance);
+
+ final List result = new ArrayList();
+ for (GifBlock block : blocks) {
+ if (block.blockCode != XMP_COMPLETE_CODE) {
+ continue;
+ }
+
+ final GenericGifBlock genericBlock = (GenericGifBlock) block;
+
+ final byte[] blockBytes = genericBlock.appendSubBlocks(true);
+ if (blockBytes.length < XMP_APPLICATION_ID_AND_AUTH_CODE.length) {
+ continue;
+ }
+
+ if (!compareBytes(blockBytes, 0,
+ XMP_APPLICATION_ID_AND_AUTH_CODE, 0,
+ XMP_APPLICATION_ID_AND_AUTH_CODE.length)) {
+ continue;
+ }
+
+ final byte[] GIF_MAGIC_TRAILER = new byte[256];
+ for (int magic = 0; magic <= 0xff; magic++) {
+ GIF_MAGIC_TRAILER[magic] = (byte) (0xff - magic);
+ }
+
+ if (blockBytes.length < XMP_APPLICATION_ID_AND_AUTH_CODE.length
+ + GIF_MAGIC_TRAILER.length) {
+ continue;
+ }
+ if (!compareBytes(blockBytes, blockBytes.length
+ - GIF_MAGIC_TRAILER.length, GIF_MAGIC_TRAILER, 0,
+ GIF_MAGIC_TRAILER.length)) {
+ throw new ImageReadException(
+ "XMP block in GIF missing magic trailer.");
+ }
+
+ try {
+ // XMP is UTF-8 encoded xml.
+ final String xml = new String(
+ blockBytes,
+ XMP_APPLICATION_ID_AND_AUTH_CODE.length,
+ blockBytes.length
+ - (XMP_APPLICATION_ID_AND_AUTH_CODE.length + GIF_MAGIC_TRAILER.length),
+ "utf-8");
+ result.add(xml);
+ } catch (final UnsupportedEncodingException e) {
+ throw new ImageReadException("Invalid XMP Block in GIF.", e);
+ }
+ }
+
+ if (result.size() < 1) {
+ return null;
+ }
+ if (result.size() > 1) {
+ throw new ImageReadException("More than one XMP Block in GIF.");
+ }
+ canThrow = true;
+ return result.get(0);
+
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/formats/gif/GraphicControlExtension.java b/src/main/java/org/apache/commons/imaging/formats/gif/GraphicControlExtension.java
similarity index 80%
rename from src/main/java/org/apache/sanselan/formats/gif/GraphicControlExtension.java
rename to src/main/java/org/apache/commons/imaging/formats/gif/GraphicControlExtension.java
index 68e54a8..4a46f99 100644
--- a/src/main/java/org/apache/sanselan/formats/gif/GraphicControlExtension.java
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/GraphicControlExtension.java
@@ -1,41 +1,39 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.gif;
-
-class GraphicControlExtension extends GIFBlock
-{
-
- public final int packed;
- public final int dispose;
- public final boolean transparency;
- public final int delay;
- public final int transparentColorIndex;
-
- public GraphicControlExtension(int blockCode, int packed, int dispose,
- boolean transparency, int delay, int transparentColorIndex)
- {
- super(blockCode);
-
- this.packed = packed;
- this.dispose = dispose;
- this.transparency = transparency;
- this.delay = delay;
- this.transparentColorIndex = transparentColorIndex;
-
- }
-
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.gif;
+
+class GraphicControlExtension extends GifBlock {
+
+ public final int packed;
+ public final int dispose;
+ public final boolean transparency;
+ public final int delay;
+ public final int transparentColorIndex;
+
+ public GraphicControlExtension(final int blockCode, final int packed, final int dispose,
+ final boolean transparency, final int delay, final int transparentColorIndex) {
+ super(blockCode);
+
+ this.packed = packed;
+ this.dispose = dispose;
+ this.transparency = transparency;
+ this.delay = delay;
+ this.transparentColorIndex = transparentColorIndex;
+
+ }
+
+}
diff --git a/src/main/java/org/apache/sanselan/formats/gif/ImageContents.java b/src/main/java/org/apache/commons/imaging/formats/gif/ImageContents.java
similarity index 72%
rename from src/main/java/org/apache/sanselan/formats/gif/ImageContents.java
rename to src/main/java/org/apache/commons/imaging/formats/gif/ImageContents.java
index d20eeb8..6585bb9 100644
--- a/src/main/java/org/apache/sanselan/formats/gif/ImageContents.java
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/ImageContents.java
@@ -1,35 +1,33 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.gif;
-
-import java.util.ArrayList;
-
-class ImageContents
-{
- public final GIFHeaderInfo gifHeaderInfo;
-
- public final ArrayList blocks;
- public final byte globalColorTable[];
-
- public ImageContents(GIFHeaderInfo gifHeaderInfo, byte globalColorTable[],
- ArrayList blocks)
- {
- this.gifHeaderInfo = gifHeaderInfo;
- this.globalColorTable = globalColorTable;
- this.blocks = blocks;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.gif;
+
+import java.util.List;
+
+class ImageContents {
+ public final GifHeaderInfo gifHeaderInfo;
+
+ public final List blocks;
+ public final byte[] globalColorTable;
+
+ public ImageContents(final GifHeaderInfo gifHeaderInfo, final byte[] globalColorTable,
+ final List blocks) {
+ this.gifHeaderInfo = gifHeaderInfo;
+ this.globalColorTable = globalColorTable;
+ this.blocks = blocks;
+ }
+}
diff --git a/src/main/java/org/apache/sanselan/formats/gif/ImageDescriptor.java b/src/main/java/org/apache/commons/imaging/formats/gif/ImageDescriptor.java
similarity index 52%
rename from src/main/java/org/apache/sanselan/formats/gif/ImageDescriptor.java
rename to src/main/java/org/apache/commons/imaging/formats/gif/ImageDescriptor.java
index fc98f3f..a34f3c7 100644
--- a/src/main/java/org/apache/sanselan/formats/gif/ImageDescriptor.java
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/ImageDescriptor.java
@@ -1,57 +1,55 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.sanselan.formats.gif;
-
-public class ImageDescriptor extends GIFBlock
-{
-
- public final int imageLeftPosition;
- public final int imageTopPosition;
- public final int imageWidth;
- public final int imageHeight;
- public final byte packedFields;
- public final boolean localColorTableFlag;
- public final boolean interlaceFlag;
- public final boolean sortFlag;
- public final byte sizeOfLocalColorTable;
-
- public final byte localColorTable[];
- public final byte imageData[];
-
- public ImageDescriptor(int blockCode, int ImageLeftPosition,
- int ImageTopPosition, int ImageWidth, int ImageHeight,
- byte PackedFields, boolean LocalColorTableFlag,
- boolean InterlaceFlag, boolean SortFlag,
- byte SizeofLocalColorTable, byte LocalColorTable[],
- byte ImageData[])
- {
- super(blockCode);
-
- this.imageLeftPosition = ImageLeftPosition;
- this.imageTopPosition = ImageTopPosition;
- this.imageWidth = ImageWidth;
- this.imageHeight = ImageHeight;
- this.packedFields = PackedFields;
- this.localColorTableFlag = LocalColorTableFlag;
- this.interlaceFlag = InterlaceFlag;
- this.sortFlag = SortFlag;
- this.sizeOfLocalColorTable = SizeofLocalColorTable;
-
- this.localColorTable = LocalColorTable;
- this.imageData = ImageData;
- }
-}
\ No newline at end of file
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.gif;
+
+class ImageDescriptor extends GifBlock {
+
+ public final int imageLeftPosition;
+ public final int imageTopPosition;
+ public final int imageWidth;
+ public final int imageHeight;
+ public final byte packedFields;
+ public final boolean localColorTableFlag;
+ public final boolean interlaceFlag;
+ public final boolean sortFlag;
+ public final byte sizeOfLocalColorTable;
+
+ public final byte[] localColorTable;
+ public final byte[] imageData;
+
+ public ImageDescriptor(final int blockCode, final int imageLeftPosition,
+ final int imageTopPosition, final int imageWidth, final int imageHeight,
+ final byte packedFields, final boolean localColorTableFlag,
+ final boolean interlaceFlag, final boolean sortFlag,
+ final byte sizeofLocalColorTable, final byte[] localColorTable,
+ final byte[] imageData) {
+ super(blockCode);
+
+ this.imageLeftPosition = imageLeftPosition;
+ this.imageTopPosition = imageTopPosition;
+ this.imageWidth = imageWidth;
+ this.imageHeight = imageHeight;
+ this.packedFields = packedFields;
+ this.localColorTableFlag = localColorTableFlag;
+ this.interlaceFlag = interlaceFlag;
+ this.sortFlag = sortFlag;
+ this.sizeOfLocalColorTable = sizeofLocalColorTable;
+
+ this.localColorTable = localColorTable;
+ this.imageData = imageData;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/gif/package-info.java b/src/main/java/org/apache/commons/imaging/formats/gif/package-info.java
new file mode 100644
index 0000000..1c7940d
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/gif/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The GIF image format.
+ */
+package org.apache.commons.imaging.formats.gif;
+
diff --git a/src/main/java/org/apache/commons/imaging/formats/icns/IcnsDecoder.java b/src/main/java/org/apache/commons/imaging/formats/icns/IcnsDecoder.java
new file mode 100644
index 0000000..5168529
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/icns/IcnsDecoder.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.icns;
+
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.ImageBuilder;
+import org.apache.commons.imaging.formats.icns.IcnsImageParser.IcnsElement;
+
+final class IcnsDecoder {
+ private static final int[] PALETTE_4BPP = { 0xffffffff, 0xfffcf305,
+ 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4,
+ 0xff02abea, 0xff1fb714, 0xff006411, 0xff562c05, 0xff90713a,
+ 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000 };
+
+ private static final int[] PALETTE_8BPP = { 0xFFFFFFFF, 0xFFFFFFCC,
+ 0xFFFFFF99, 0xFFFFFF66, 0xFFFFFF33, 0xFFFFFF00, 0xFFFFCCFF,
+ 0xFFFFCCCC, 0xFFFFCC99, 0xFFFFCC66, 0xFFFFCC33, 0xFFFFCC00,
+ 0xFFFF99FF, 0xFFFF99CC, 0xFFFF9999, 0xFFFF9966, 0xFFFF9933,
+ 0xFFFF9900, 0xFFFF66FF, 0xFFFF66CC, 0xFFFF6699, 0xFFFF6666,
+ 0xFFFF6633, 0xFFFF6600, 0xFFFF33FF, 0xFFFF33CC, 0xFFFF3399,
+ 0xFFFF3366, 0xFFFF3333, 0xFFFF3300, 0xFFFF00FF, 0xFFFF00CC,
+ 0xFFFF0099, 0xFFFF0066, 0xFFFF0033, 0xFFFF0000, 0xFFCCFFFF,
+ 0xFFCCFFCC, 0xFFCCFF99, 0xFFCCFF66, 0xFFCCFF33, 0xFFCCFF00,
+ 0xFFCCCCFF, 0xFFCCCCCC, 0xFFCCCC99, 0xFFCCCC66, 0xFFCCCC33,
+ 0xFFCCCC00, 0xFFCC99FF, 0xFFCC99CC, 0xFFCC9999, 0xFFCC9966,
+ 0xFFCC9933, 0xFFCC9900, 0xFFCC66FF, 0xFFCC66CC, 0xFFCC6699,
+ 0xFFCC6666, 0xFFCC6633, 0xFFCC6600, 0xFFCC33FF, 0xFFCC33CC,
+ 0xFFCC3399, 0xFFCC3366, 0xFFCC3333, 0xFFCC3300, 0xFFCC00FF,
+ 0xFFCC00CC, 0xFFCC0099, 0xFFCC0066, 0xFFCC0033, 0xFFCC0000,
+ 0xFF99FFFF, 0xFF99FFCC, 0xFF99FF99, 0xFF99FF66, 0xFF99FF33,
+ 0xFF99FF00, 0xFF99CCFF, 0xFF99CCCC, 0xFF99CC99, 0xFF99CC66,
+ 0xFF99CC33, 0xFF99CC00, 0xFF9999FF, 0xFF9999CC, 0xFF999999,
+ 0xFF999966, 0xFF999933, 0xFF999900, 0xFF9966FF, 0xFF9966CC,
+ 0xFF996699, 0xFF996666, 0xFF996633, 0xFF996600, 0xFF9933FF,
+ 0xFF9933CC, 0xFF993399, 0xFF993366, 0xFF993333, 0xFF993300,
+ 0xFF9900FF, 0xFF9900CC, 0xFF990099, 0xFF990066, 0xFF990033,
+ 0xFF990000, 0xFF66FFFF, 0xFF66FFCC, 0xFF66FF99, 0xFF66FF66,
+ 0xFF66FF33, 0xFF66FF00, 0xFF66CCFF, 0xFF66CCCC, 0xFF66CC99,
+ 0xFF66CC66, 0xFF66CC33, 0xFF66CC00, 0xFF6699FF, 0xFF6699CC,
+ 0xFF669999, 0xFF669966, 0xFF669933, 0xFF669900, 0xFF6666FF,
+ 0xFF6666CC, 0xFF666699, 0xFF666666, 0xFF666633, 0xFF666600,
+ 0xFF6633FF, 0xFF6633CC, 0xFF663399, 0xFF663366, 0xFF663333,
+ 0xFF663300, 0xFF6600FF, 0xFF6600CC, 0xFF660099, 0xFF660066,
+ 0xFF660033, 0xFF660000, 0xFF33FFFF, 0xFF33FFCC, 0xFF33FF99,
+ 0xFF33FF66, 0xFF33FF33, 0xFF33FF00, 0xFF33CCFF, 0xFF33CCCC,
+ 0xFF33CC99, 0xFF33CC66, 0xFF33CC33, 0xFF33CC00, 0xFF3399FF,
+ 0xFF3399CC, 0xFF339999, 0xFF339966, 0xFF339933, 0xFF339900,
+ 0xFF3366FF, 0xFF3366CC, 0xFF336699, 0xFF336666, 0xFF336633,
+ 0xFF336600, 0xFF3333FF, 0xFF3333CC, 0xFF333399, 0xFF333366,
+ 0xFF333333, 0xFF333300, 0xFF3300FF, 0xFF3300CC, 0xFF330099,
+ 0xFF330066, 0xFF330033, 0xFF330000, 0xFF00FFFF, 0xFF00FFCC,
+ 0xFF00FF99, 0xFF00FF66, 0xFF00FF33, 0xFF00FF00, 0xFF00CCFF,
+ 0xFF00CCCC, 0xFF00CC99, 0xFF00CC66, 0xFF00CC33, 0xFF00CC00,
+ 0xFF0099FF, 0xFF0099CC, 0xFF009999, 0xFF009966, 0xFF009933,
+ 0xFF009900, 0xFF0066FF, 0xFF0066CC, 0xFF006699, 0xFF006666,
+ 0xFF006633, 0xFF006600, 0xFF0033FF, 0xFF0033CC, 0xFF003399,
+ 0xFF003366, 0xFF003333, 0xFF003300, 0xFF0000FF, 0xFF0000CC,
+ 0xFF000099, 0xFF000066, 0xFF000033, 0xFFEE0000, 0xFFDD0000,
+ 0xFFBB0000, 0xFFAA0000, 0xFF880000, 0xFF770000, 0xFF550000,
+ 0xFF440000, 0xFF220000, 0xFF110000, 0xFF00EE00, 0xFF00DD00,
+ 0xFF00BB00, 0xFF00AA00, 0xFF008800, 0xFF007700, 0xFF005500,
+ 0xFF004400, 0xFF002200, 0xFF001100, 0xFF0000EE, 0xFF0000DD,
+ 0xFF0000BB, 0xFF0000AA, 0xFF000088, 0xFF000077, 0xFF000055,
+ 0xFF000044, 0xFF000022, 0xFF000011, 0xFFEEEEEE, 0xFFDDDDDD,
+ 0xFFBBBBBB, 0xFFAAAAAA, 0xFF888888, 0xFF777777, 0xFF555555,
+ 0xFF444444, 0xFF222222, 0xFF111111, 0xFF000000 };
+
+ private IcnsDecoder() {
+ }
+
+ private static void decode1BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
+ int position = 0;
+ int bitsLeft = 0;
+ int value = 0;
+ for (int y = 0; y < imageType.getHeight(); y++) {
+ for (int x = 0; x < imageType.getWidth(); x++) {
+ if (bitsLeft == 0) {
+ value = 0xff & imageData[position++];
+ bitsLeft = 8;
+ }
+ int argb;
+ if ((value & 0x80) != 0) {
+ argb = 0xff000000;
+ } else {
+ argb = 0xffffffff;
+ }
+ value <<= 1;
+ bitsLeft--;
+ image.setRGB(x, y, argb);
+ }
+ }
+ }
+
+ private static void decode4BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
+ int i = 0;
+ boolean visited = false;
+ for (int y = 0; y < imageType.getHeight(); y++) {
+ for (int x = 0; x < imageType.getWidth(); x++) {
+ int index;
+ if (!visited) {
+ index = 0xf & (imageData[i] >> 4);
+ } else {
+ index = 0xf & imageData[i++];
+ }
+ visited = !visited;
+ image.setRGB(x, y, PALETTE_4BPP[index]);
+ }
+ }
+ }
+
+ private static void decode8BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
+ for (int y = 0; y < imageType.getHeight(); y++) {
+ for (int x = 0; x < imageType.getWidth(); x++) {
+ final int index = 0xff & imageData[y * imageType.getWidth() + x];
+ image.setRGB(x, y, PALETTE_8BPP[index]);
+ }
+ }
+ }
+
+ private static void decode32BPPImage(final IcnsType imageType, final byte[] imageData, final ImageBuilder image) {
+ for (int y = 0; y < imageType.getHeight(); y++) {
+ for (int x = 0; x < imageType.getWidth(); x++) {
+ final int argb = 0xff000000 /* the "alpha" is ignored */
+ | ((0xff & imageData[4 * (y * imageType.getWidth() + x) + 1]) << 16)
+ | ((0xff & imageData[4 * (y * imageType.getWidth() + x) + 2]) << 8)
+ | (0xff & imageData[4 * (y * imageType.getWidth() + x) + 3]);
+ image.setRGB(x, y, argb);
+ }
+ }
+ }
+
+ private static void apply1BPPMask(final byte[] maskData, final ImageBuilder image) throws ImageReadException {
+ int position = 0;
+ int bitsLeft = 0;
+ int value = 0;
+
+ // 1 bit icon types have image data followed by mask data in the same
+ // entry
+ final int totalBytes = (image.getWidth() * image.getHeight() + 7) / 8;
+ if (maskData.length >= 2 * totalBytes) {
+ position = totalBytes;
+ } else {
+ throw new ImageReadException("1 BPP mask underrun parsing ICNS file");
+ }
+
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ if (bitsLeft == 0) {
+ value = 0xff & maskData[position++];
+ bitsLeft = 8;
+ }
+ int alpha;
+ if ((value & 0x80) != 0) {
+ alpha = 0xff;
+ } else {
+ alpha = 0x00;
+ }
+ value <<= 1;
+ bitsLeft--;
+ image.setRGB(x, y, (alpha << 24) | (0xffffff & image.getRGB(x, y)));
+ }
+ }
+ }
+
+ private static void apply8BPPMask(final byte[] maskData, final ImageBuilder image) {
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ final int alpha = 0xff & maskData[y * image.getWidth() + x];
+ image.setRGB(x, y,
+ (alpha << 24) | (0xffffff & image.getRGB(x, y)));
+ }
+ }
+ }
+
+ public static List decodeAllImages(final IcnsImageParser.IcnsElement[] icnsElements)
+ throws ImageReadException {
+ final List result = new ArrayList();
+ for (final IcnsElement imageElement : icnsElements) {
+ final IcnsType imageType = IcnsType.findImageType(imageElement.type);
+ if (imageType == null) {
+ continue;
+ }
+
+ IcnsType maskType;
+ IcnsImageParser.IcnsElement maskElement = null;
+ if (imageType.hasMask()) {
+ maskType = imageType;
+ maskElement = imageElement;
+ } else {
+ maskType = IcnsType.find8BPPMaskType(imageType);
+ if (maskType != null) {
+ for (final IcnsElement icnsElement : icnsElements) {
+ if (icnsElement.type == maskType.getType()) {
+ maskElement = icnsElement;
+ break;
+ }
+ }
+ }
+ if (maskElement == null) {
+ maskType = IcnsType.find1BPPMaskType(imageType);
+ if (maskType != null) {
+ for (final IcnsElement icnsElement : icnsElements) {
+ if (icnsElement.type == maskType.getType()) {
+ maskElement = icnsElement;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // FIXME: don't skip these when JPEG 2000 support is added:
+ if (imageType == IcnsType.ICNS_256x256_32BIT_ARGB_IMAGE
+ || imageType == IcnsType.ICNS_512x512_32BIT_ARGB_IMAGE) {
+ continue;
+ }
+
+ final int expectedSize = (imageType.getWidth() * imageType.getHeight()
+ * imageType.getBitsPerPixel() + 7) / 8;
+ byte[] imageData;
+ if (imageElement.data.length < expectedSize) {
+ if (imageType.getBitsPerPixel() == 32) {
+ imageData = Rle24Compression.decompress(
+ imageType.getWidth(), imageType.getHeight(),
+ imageElement.data);
+ } else {
+ throw new ImageReadException("Short image data but not a 32 bit compressed type");
+ }
+ } else {
+ imageData = imageElement.data;
+ }
+
+ final ImageBuilder imageBuilder = new ImageBuilder(imageType.getWidth(),
+ imageType.getHeight(), true);
+ switch (imageType.getBitsPerPixel()) {
+ case 1:
+ decode1BPPImage(imageType, imageData, imageBuilder);
+ break;
+ case 4:
+ decode4BPPImage(imageType, imageData, imageBuilder);
+ break;
+ case 8:
+ decode8BPPImage(imageType, imageData, imageBuilder);
+ break;
+ case 32:
+ decode32BPPImage(imageType, imageData, imageBuilder);
+ break;
+ default:
+ throw new ImageReadException("Unsupported bit depth " + imageType.getBitsPerPixel());
+ }
+
+ if (maskElement != null) {
+ if (maskType.getBitsPerPixel() == 1) {
+ apply1BPPMask(maskElement.data, imageBuilder);
+ } else if (maskType.getBitsPerPixel() == 8) {
+ apply8BPPMask(maskElement.data, imageBuilder);
+ } else {
+ throw new ImageReadException("Unsupport mask bit depth " + maskType.getBitsPerPixel());
+ }
+ }
+
+ result.add(imageBuilder.getBufferedImage());
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/icns/IcnsImageParser.java b/src/main/java/org/apache/commons/imaging/formats/icns/IcnsImageParser.java
new file mode 100644
index 0000000..263c245
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/icns/IcnsImageParser.java
@@ -0,0 +1,362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.icns;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.imaging.ImageFormat;
+import org.apache.commons.imaging.ImageFormats;
+import org.apache.commons.imaging.ImageInfo;
+import org.apache.commons.imaging.ImageParser;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.ImageWriteException;
+import org.apache.commons.imaging.common.BinaryOutputStream;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.util.IoUtils;
+
+import static org.apache.commons.imaging.ImagingConstants.*;
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+public class IcnsImageParser extends ImageParser {
+ static final int ICNS_MAGIC = IcnsType.typeAsInt("icns");
+ private static final String DEFAULT_EXTENSION = ".icns";
+ private static final String[] ACCEPTED_EXTENSIONS = { ".icns", };
+
+ public IcnsImageParser() {
+ super.setByteOrder(ByteOrder.BIG_ENDIAN);
+ }
+
+ @Override
+ public String getName() {
+ return "Apple Icon Image";
+ }
+
+ @Override
+ public String getDefaultExtension() {
+ return DEFAULT_EXTENSION;
+ }
+
+ @Override
+ protected String[] getAcceptedExtensions() {
+ return ACCEPTED_EXTENSIONS;
+ }
+
+ @Override
+ protected ImageFormat[] getAcceptedTypes() {
+ return new ImageFormat[] { ImageFormats.ICNS };
+ }
+
+ @Override
+ public IImageMetadata getMetadata(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public ImageInfo getImageInfo(final ByteSource byteSource, Map params)
+ throws ImageReadException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ if (params.containsKey(PARAM_KEY_VERBOSE)) {
+ params.remove(PARAM_KEY_VERBOSE);
+ }
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageReadException("Unknown parameter: " + firstKey);
+ }
+
+ final IcnsContents contents = readImage(byteSource);
+ final List images = IcnsDecoder
+ .decodeAllImages(contents.icnsElements);
+ if (images.isEmpty()) {
+ throw new ImageReadException("No icons in ICNS file");
+ }
+ final BufferedImage image0 = images.get(0);
+ return new ImageInfo("Icns", 32, new ArrayList(),
+ ImageFormats.ICNS, "ICNS Apple Icon Image",
+ image0.getHeight(), "image/x-icns", images.size(), 0, 0, 0, 0,
+ image0.getWidth(), false, true, false,
+ ImageInfo.COLOR_TYPE_RGB,
+ ImageInfo.COMPRESSION_ALGORITHM_UNKNOWN);
+ }
+
+ @Override
+ public Dimension getImageSize(final ByteSource byteSource, Map params)
+ throws ImageReadException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ if (params.containsKey(PARAM_KEY_VERBOSE)) {
+ params.remove(PARAM_KEY_VERBOSE);
+ }
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageReadException("Unknown parameter: " + firstKey);
+ }
+
+ final IcnsContents contents = readImage(byteSource);
+ final List images = IcnsDecoder
+ .decodeAllImages(contents.icnsElements);
+ if (images.isEmpty()) {
+ throw new ImageReadException("No icons in ICNS file");
+ }
+ final BufferedImage image0 = images.get(0);
+ return new Dimension(image0.getWidth(), image0.getHeight());
+ }
+
+ @Override
+ public byte[] getICCProfileBytes(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ private static class IcnsHeader {
+ public final int magic; // Magic literal (4 bytes), always "icns"
+ public final int fileSize; // Length of file (4 bytes), in bytes.
+
+ public IcnsHeader(final int magic, final int fileSize) {
+ this.magic = magic;
+ this.fileSize = fileSize;
+ }
+
+ public void dump(final PrintWriter pw) {
+ pw.println("IcnsHeader");
+ pw.println("Magic: 0x" + Integer.toHexString(magic) + " ("
+ + IcnsType.describeType(magic) + ")");
+ pw.println("FileSize: " + fileSize);
+ pw.println("");
+ }
+ }
+
+ private IcnsHeader readIcnsHeader(final InputStream is)
+ throws ImageReadException, IOException {
+ final int magic = read4Bytes("Magic", is, "Not a Valid ICNS File", getByteOrder());
+ final int fileSize = read4Bytes("FileSize", is, "Not a Valid ICNS File", getByteOrder());
+
+ if (magic != ICNS_MAGIC) {
+ throw new ImageReadException("Not a Valid ICNS File: " + "magic is 0x" + Integer.toHexString(magic));
+ }
+
+ return new IcnsHeader(magic, fileSize);
+ }
+
+ static class IcnsElement {
+ public final int type;
+ public final int elementSize;
+ public final byte[] data;
+
+ public IcnsElement(final int type, final int elementSize, final byte[] data) {
+ this.type = type;
+ this.elementSize = elementSize;
+ this.data = data;
+ }
+
+ public void dump(final PrintWriter pw) {
+ pw.println("IcnsElement");
+ final IcnsType icnsType = IcnsType.findAnyType(type);
+ String typeDescription;
+ if (icnsType == null) {
+ typeDescription = "";
+ } else {
+ typeDescription = " " + icnsType.toString();
+ }
+ pw.println("Type: 0x" + Integer.toHexString(type) + " ("
+ + IcnsType.describeType(type) + ")" + typeDescription);
+ pw.println("ElementSize: " + elementSize);
+ pw.println("");
+ }
+ }
+
+ private IcnsElement readIcnsElement(final InputStream is) throws IOException {
+ final int type = read4Bytes("Type", is, "Not a Valid ICNS File", getByteOrder()); // Icon type
+ // (4 bytes)
+ final int elementSize = read4Bytes("ElementSize", is, "Not a Valid ICNS File", getByteOrder()); // Length
+ // of
+ // data
+ // (4
+ // bytes),
+ // in
+ // bytes,
+ // including
+ // this
+ // header
+ final byte[] data = readBytes("Data", is, elementSize - 8,
+ "Not a Valid ICNS File");
+
+ return new IcnsElement(type, elementSize, data);
+ }
+
+ private static class IcnsContents {
+ public final IcnsHeader icnsHeader;
+ public final IcnsElement[] icnsElements;
+
+ public IcnsContents(final IcnsHeader icnsHeader, final IcnsElement[] icnsElements) {
+ super();
+ this.icnsHeader = icnsHeader;
+ this.icnsElements = icnsElements;
+ }
+ }
+
+ private IcnsContents readImage(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+ final IcnsHeader icnsHeader = readIcnsHeader(is);
+
+ final List icnsElementList = new ArrayList();
+ for (int remainingSize = icnsHeader.fileSize - 8; remainingSize > 0;) {
+ final IcnsElement icnsElement = readIcnsElement(is);
+ icnsElementList.add(icnsElement);
+ remainingSize -= icnsElement.elementSize;
+ }
+
+ final IcnsElement[] icnsElements = new IcnsElement[icnsElementList.size()];
+ for (int i = 0; i < icnsElements.length; i++) {
+ icnsElements[i] = icnsElementList.get(i);
+ }
+
+ final IcnsContents ret = new IcnsContents(icnsHeader, icnsElements);
+ canThrow = true;
+ return ret;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ @Override
+ public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final IcnsContents icnsContents = readImage(byteSource);
+ icnsContents.icnsHeader.dump(pw);
+ for (final IcnsElement icnsElement : icnsContents.icnsElements) {
+ icnsElement.dump(pw);
+ }
+ return true;
+ }
+
+ @Override
+ public final BufferedImage getBufferedImage(final ByteSource byteSource,
+ final Map params) throws ImageReadException, IOException {
+ final IcnsContents icnsContents = readImage(byteSource);
+ final List result = IcnsDecoder
+ .decodeAllImages(icnsContents.icnsElements);
+ if (!result.isEmpty()) {
+ return result.get(0);
+ }
+ throw new ImageReadException("No icons in ICNS file");
+ }
+
+ @Override
+ public List getAllBufferedImages(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final IcnsContents icnsContents = readImage(byteSource);
+ return IcnsDecoder.decodeAllImages(icnsContents.icnsElements);
+ }
+
+ @Override
+ public void writeImage(final BufferedImage src, final OutputStream os, Map params)
+ throws ImageWriteException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ // clear format key.
+ if (params.containsKey(PARAM_KEY_FORMAT)) {
+ params.remove(PARAM_KEY_FORMAT);
+ }
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageWriteException("Unknown parameter: " + firstKey);
+ }
+
+ IcnsType imageType;
+ if (src.getWidth() == 16 && src.getHeight() == 16) {
+ imageType = IcnsType.ICNS_16x16_32BIT_IMAGE;
+ } else if (src.getWidth() == 32 && src.getHeight() == 32) {
+ imageType = IcnsType.ICNS_32x32_32BIT_IMAGE;
+ } else if (src.getWidth() == 48 && src.getHeight() == 48) {
+ imageType = IcnsType.ICNS_48x48_32BIT_IMAGE;
+ } else if (src.getWidth() == 128 && src.getHeight() == 128) {
+ imageType = IcnsType.ICNS_128x128_32BIT_IMAGE;
+ } else {
+ throw new ImageWriteException("Invalid/unsupported source width "
+ + src.getWidth() + " and height " + src.getHeight());
+ }
+
+ final BinaryOutputStream bos = new BinaryOutputStream(os,
+ ByteOrder.BIG_ENDIAN);
+ bos.write4Bytes(ICNS_MAGIC);
+ bos.write4Bytes(4 + 4 + 4 + 4 + 4 * imageType.getWidth()
+ * imageType.getHeight() + 4 + 4 + imageType.getWidth()
+ * imageType.getHeight());
+
+ bos.write4Bytes(imageType.getType());
+ bos.write4Bytes(4 + 4 + 4 * imageType.getWidth()
+ * imageType.getHeight());
+ for (int y = 0; y < src.getHeight(); y++) {
+ for (int x = 0; x < src.getWidth(); x++) {
+ final int argb = src.getRGB(x, y);
+ bos.write(0);
+ bos.write(argb >> 16);
+ bos.write(argb >> 8);
+ bos.write(argb);
+ }
+ }
+
+ final IcnsType maskType = IcnsType.find8BPPMaskType(imageType);
+ bos.write4Bytes(maskType.getType());
+ bos.write4Bytes(4 + 4 + imageType.getWidth() * imageType.getWidth());
+ for (int y = 0; y < src.getHeight(); y++) {
+ for (int x = 0; x < src.getWidth(); x++) {
+ final int argb = src.getRGB(x, y);
+ bos.write(argb >> 24);
+ }
+ }
+ }
+
+ /**
+ * Extracts embedded XML metadata as XML string.
+ *
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ @Override
+ public String getXmpXml(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/icns/IcnsType.java b/src/main/java/org/apache/commons/imaging/formats/icns/IcnsType.java
new file mode 100644
index 0000000..6d9a4fd
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/icns/IcnsType.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.icns;
+
+import java.io.UnsupportedEncodingException;
+
+enum IcnsType {
+
+ ICNS_16x12_1BIT_IMAGE_AND_MASK("icm#", 16, 12, 1, true),
+ ICNS_16x12_4BIT_IMAGE("icm4", 16, 12, 4, false),
+ ICNS_16x12_8BIT_IMAGE("icm8", 16, 12, 8, false),
+
+ ICNS_16x16_8BIT_MASK("s8mk", 16, 16, 8, true),
+ ICNS_16x16_1BIT_IMAGE_AND_MASK("ics#", 16, 16, 1, true),
+ ICNS_16x16_4BIT_IMAGE("ics4", 16, 16, 4, false),
+ ICNS_16x16_8BIT_IMAGE("ics8", 16, 16, 8, false),
+ ICNS_16x16_32BIT_IMAGE("is32", 16, 16, 32, false),
+
+ ICNS_32x32_8BIT_MASK("l8mk", 32, 32, 8, true),
+ ICNS_32x32_1BIT_IMAGE_AND_MASK("ICN#", 32, 32, 1, true),
+ ICNS_32x32_4BIT_IMAGE("icl4", 32, 32, 4, false),
+ ICNS_32x32_8BIT_IMAGE("icl8", 32, 32, 8, false),
+ ICNS_32x32_32BIT_IMAGE("il32", 32, 32, 32, false),
+
+ ICNS_48x48_8BIT_MASK("h8mk", 48, 48, 8, true),
+ ICNS_48x48_1BIT_IMAGE_AND_MASK("ich#", 48, 48, 1, true),
+ ICNS_48x48_4BIT_IMAGE("ich4", 48, 48, 4, false),
+ ICNS_48x48_8BIT_IMAGE("ich8", 48, 48, 8, false),
+ ICNS_48x48_32BIT_IMAGE("ih32", 48, 48, 32, false),
+
+ ICNS_128x128_8BIT_MASK("t8mk", 128, 128, 8, true),
+ ICNS_128x128_32BIT_IMAGE("it32", 128, 128, 32, false),
+
+ ICNS_256x256_32BIT_ARGB_IMAGE("ic08", 256, 256, 32, false),
+
+ ICNS_512x512_32BIT_ARGB_IMAGE("ic09", 512, 512, 32, false);
+
+ private static final IcnsType[] ALL_IMAGE_TYPES = {
+ ICNS_16x12_1BIT_IMAGE_AND_MASK,
+ ICNS_16x12_4BIT_IMAGE,
+ ICNS_16x12_8BIT_IMAGE,
+ ICNS_16x16_1BIT_IMAGE_AND_MASK,
+ ICNS_16x16_4BIT_IMAGE,
+ ICNS_16x16_8BIT_IMAGE,
+ ICNS_16x16_32BIT_IMAGE,
+ ICNS_32x32_1BIT_IMAGE_AND_MASK,
+ ICNS_32x32_4BIT_IMAGE,
+ ICNS_32x32_8BIT_IMAGE,
+ ICNS_32x32_32BIT_IMAGE,
+ ICNS_48x48_1BIT_IMAGE_AND_MASK,
+ ICNS_48x48_4BIT_IMAGE,
+ ICNS_48x48_8BIT_IMAGE,
+ ICNS_48x48_32BIT_IMAGE,
+ ICNS_128x128_32BIT_IMAGE,
+ ICNS_256x256_32BIT_ARGB_IMAGE,
+ ICNS_512x512_32BIT_ARGB_IMAGE};
+
+ private static final IcnsType[] ALL_MASK_TYPES = {
+ ICNS_16x12_1BIT_IMAGE_AND_MASK,
+ ICNS_16x16_1BIT_IMAGE_AND_MASK,
+ ICNS_16x16_8BIT_MASK,
+ ICNS_32x32_1BIT_IMAGE_AND_MASK,
+ ICNS_32x32_8BIT_MASK,
+ ICNS_48x48_1BIT_IMAGE_AND_MASK,
+ ICNS_48x48_8BIT_MASK,
+ ICNS_128x128_8BIT_MASK};
+
+ private final int type;
+ private final int width;
+ private final int height;
+ private final int bitsPerPixel;
+ private final boolean hasMask;
+
+ private IcnsType(String type, int width, int height, int bitsPerPixel, boolean hasMask) {
+ this.type = typeAsInt(type);
+ this.width = width;
+ this.height = height;
+ this.bitsPerPixel = bitsPerPixel;
+ this.hasMask = hasMask;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getBitsPerPixel() {
+ return bitsPerPixel;
+ }
+
+ public boolean hasMask() {
+ return hasMask;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "[" + "width=" + width + "," + "height="
+ + height + "," + "bpp=" + bitsPerPixel + "," + "hasMask="
+ + hasMask + "]";
+ }
+
+ public static IcnsType findAnyType(final int type) {
+ for (final IcnsType allImageType : ALL_IMAGE_TYPES) {
+ if (allImageType.getType() == type) {
+ return allImageType;
+ }
+ }
+ for (final IcnsType allMaskType : ALL_MASK_TYPES) {
+ if (allMaskType.getType() == type) {
+ return allMaskType;
+ }
+ }
+ return null;
+ }
+
+ public static IcnsType findImageType(final int type) {
+ for (final IcnsType allImageType : ALL_IMAGE_TYPES) {
+ if (allImageType.getType() == type) {
+ return allImageType;
+ }
+ }
+ return null;
+ }
+
+ public static IcnsType find8BPPMaskType(final IcnsType imageType) {
+ for (final IcnsType allMaskType : ALL_MASK_TYPES) {
+ if (allMaskType.getBitsPerPixel() == 8
+ && allMaskType.getWidth() == imageType.getWidth()
+ && allMaskType.getHeight() == imageType.getHeight()) {
+ return allMaskType;
+ }
+ }
+ return null;
+ }
+
+ public static IcnsType find1BPPMaskType(final IcnsType imageType) {
+ for (final IcnsType allMaskType : ALL_MASK_TYPES) {
+ if (allMaskType.getBitsPerPixel() == 1
+ && allMaskType.getWidth() == imageType.getWidth()
+ && allMaskType.getHeight() == imageType.getHeight()) {
+ return allMaskType;
+ }
+ }
+ return null;
+ }
+
+ public static int typeAsInt(final String type) {
+ byte[] bytes;
+ try {
+ bytes = type.getBytes("US-ASCII");
+ } catch (final UnsupportedEncodingException unsupportedEncodingException) {
+ throw new IllegalArgumentException("Your Java doesn't support US-ASCII", unsupportedEncodingException);
+ }
+ if (bytes.length != 4) {
+ throw new IllegalArgumentException("Invalid ICNS type");
+ }
+ return ((0xff & bytes[0]) << 24)
+ | ((0xff & bytes[1]) << 16)
+ | ((0xff & bytes[2]) << 8)
+ | (0xff & bytes[3]);
+ }
+
+ public static String describeType(final int type) {
+ final byte[] bytes = new byte[4];
+ bytes[0] = (byte) (0xff & (type >> 24));
+ bytes[1] = (byte) (0xff & (type >> 16));
+ bytes[2] = (byte) (0xff & (type >> 8));
+ bytes[3] = (byte) (0xff & type);
+ try {
+ return new String(bytes, "US-ASCII");
+ } catch (final UnsupportedEncodingException unsupportedEncodingException) {
+ throw new IllegalArgumentException("Your Java doesn't support US-ASCII", unsupportedEncodingException);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/icns/Rle24Compression.java b/src/main/java/org/apache/commons/imaging/formats/icns/Rle24Compression.java
new file mode 100644
index 0000000..a7176ea
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/icns/Rle24Compression.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.icns;
+
+final class Rle24Compression {
+ private Rle24Compression() {
+ }
+
+ public static byte[] decompress(final int width, final int height, final byte[] data) {
+ final int pixelCount = width * height;
+ final byte[] result = new byte[4 * pixelCount];
+
+ // Several ICNS parsers advance by 4 bytes here:
+ // http://code.google.com/p/icns2png/ - when the width is >= 128
+ // http://icns.sourceforge.net/ - when those 4 bytes are all zero
+ //
+ // A scan of all .icns files on MacOS shows that
+ // all 128x128 images indeed start with 4 zeroes,
+ // while all smaller images don't.
+ // However it is dangerous to assume
+ // that 4 initial zeroes always need to be skipped,
+ // because they could encode valid pixels on smaller images.
+ // So always skip on 128x128, and never skip on anything else.
+ int dataPos = 0;
+ if (width >= 128 && height >= 128) {
+ dataPos = 4;
+ }
+
+ // argb, band by band in 3 passes, with no alpha
+ for (int band = 1; band <= 3; band++) {
+ int remaining = pixelCount;
+ int resultPos = 0;
+ while (remaining > 0) {
+ if ((data[dataPos] & 0x80) != 0) {
+ final int count = (0xff & data[dataPos]) - 125;
+ for (int i = 0; i < count; i++) {
+ result[band + 4 * (resultPos++)] = data[dataPos + 1];
+ }
+ dataPos += 2;
+ remaining -= count;
+ } else {
+ final int count = (0xff & data[dataPos]) + 1;
+ dataPos++;
+ for (int i = 0; i < count; i++) {
+ result[band + 4 * (resultPos++)] = data[dataPos++];
+ }
+ remaining -= count;
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/icns/package-info.java b/src/main/java/org/apache/commons/imaging/formats/icns/package-info.java
new file mode 100644
index 0000000..771d436
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/icns/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The ICNS image format.
+ */
+package org.apache.commons.imaging.formats.icns;
+
diff --git a/src/main/java/org/apache/commons/imaging/formats/ico/IcoImageParser.java b/src/main/java/org/apache/commons/imaging/formats/ico/IcoImageParser.java
new file mode 100644
index 0000000..9b458f2
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/ico/IcoImageParser.java
@@ -0,0 +1,830 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.ico;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.imaging.ImageFormat;
+import org.apache.commons.imaging.ImageFormats;
+import org.apache.commons.imaging.ImageInfo;
+import org.apache.commons.imaging.ImageParser;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.ImageWriteException;
+import org.apache.commons.imaging.Imaging;
+import org.apache.commons.imaging.PixelDensity;
+import org.apache.commons.imaging.common.BinaryOutputStream;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.formats.bmp.BmpImageParser;
+import org.apache.commons.imaging.palette.PaletteFactory;
+import org.apache.commons.imaging.palette.SimplePalette;
+import org.apache.commons.imaging.util.IoUtils;
+
+import static org.apache.commons.imaging.ImagingConstants.*;
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+public class IcoImageParser extends ImageParser {
+ private static final String DEFAULT_EXTENSION = ".ico";
+ private static final String[] ACCEPTED_EXTENSIONS = { ".ico", ".cur", };
+
+ public IcoImageParser() {
+ super.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ @Override
+ public String getName() {
+ return "ico-Custom";
+ }
+
+ @Override
+ public String getDefaultExtension() {
+ return DEFAULT_EXTENSION;
+ }
+
+ @Override
+ protected String[] getAcceptedExtensions() {
+ return ACCEPTED_EXTENSIONS;
+ }
+
+ @Override
+ protected ImageFormat[] getAcceptedTypes() {
+ return new ImageFormat[] { ImageFormats.ICO, //
+ };
+ }
+
+ @Override
+ public IImageMetadata getMetadata(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public ImageInfo getImageInfo(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public Dimension getImageSize(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ @Override
+ public byte[] getICCProfileBytes(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+
+ private static class FileHeader {
+ public final int reserved; // Reserved (2 bytes), always 0
+ public final int iconType; // IconType (2 bytes), if the image is an
+ // icon it?s 1, for cursors the value is 2.
+ public final int iconCount; // IconCount (2 bytes), number of icons in
+ // this file.
+
+ public FileHeader(final int reserved, final int iconType, final int iconCount) {
+ this.reserved = reserved;
+ this.iconType = iconType;
+ this.iconCount = iconCount;
+ }
+
+ public void dump(final PrintWriter pw) {
+ pw.println("FileHeader");
+ pw.println("Reserved: " + reserved);
+ pw.println("IconType: " + iconType);
+ pw.println("IconCount: " + iconCount);
+ pw.println();
+ }
+ }
+
+ private FileHeader readFileHeader(final InputStream is) throws ImageReadException, IOException {
+ final int reserved = read2Bytes("Reserved", is, "Not a Valid ICO File", getByteOrder());
+ final int iconType = read2Bytes("IconType", is, "Not a Valid ICO File", getByteOrder());
+ final int iconCount = read2Bytes("IconCount", is, "Not a Valid ICO File", getByteOrder());
+
+ if (reserved != 0) {
+ throw new ImageReadException("Not a Valid ICO File: reserved is " + reserved);
+ }
+ if (iconType != 1 && iconType != 2) {
+ throw new ImageReadException("Not a Valid ICO File: icon type is " + iconType);
+ }
+
+ return new FileHeader(reserved, iconType, iconCount);
+
+ }
+
+ private static class IconInfo {
+ public final byte width;
+ public final byte height;
+ public final byte colorCount;
+ public final byte reserved;
+ public final int planes;
+ public final int bitCount;
+ public final int imageSize;
+ public final int imageOffset;
+
+ public IconInfo(final byte width, final byte height,
+ final byte colorCount, final byte reserved, final int planes,
+ final int bitCount, final int imageSize, final int imageOffset) {
+ this.width = width;
+ this.height = height;
+ this.colorCount = colorCount;
+ this.reserved = reserved;
+ this.planes = planes;
+ this.bitCount = bitCount;
+ this.imageSize = imageSize;
+ this.imageOffset = imageOffset;
+ }
+
+ public void dump(final PrintWriter pw) {
+ pw.println("IconInfo");
+ pw.println("Width: " + width);
+ pw.println("Height: " + height);
+ pw.println("ColorCount: " + colorCount);
+ pw.println("Reserved: " + reserved);
+ pw.println("Planes: " + planes);
+ pw.println("BitCount: " + bitCount);
+ pw.println("ImageSize: " + imageSize);
+ pw.println("ImageOffset: " + imageOffset);
+ }
+ }
+
+ private IconInfo readIconInfo(final InputStream is) throws IOException {
+ // Width (1 byte), Width of Icon (1 to 255)
+ final byte width = readByte("Width", is, "Not a Valid ICO File");
+ // Height (1 byte), Height of Icon (1 to 255)
+ final byte height = readByte("Height", is, "Not a Valid ICO File");
+ // ColorCount (1 byte), Number of colors, either
+ // 0 for 24 bit or higher,
+ // 2 for monochrome or 16 for 16 color images.
+ final byte colorCount = readByte("ColorCount", is, "Not a Valid ICO File");
+ // Reserved (1 byte), Not used (always 0)
+ final byte reserved = readByte("Reserved", is, "Not a Valid ICO File");
+ // Planes (2 bytes), always 1
+ final int planes = read2Bytes("Planes", is, "Not a Valid ICO File", getByteOrder());
+ // BitCount (2 bytes), number of bits per pixel (1 for monchrome,
+ // 4 for 16 colors, 8 for 256 colors, 24 for true colors,
+ // 32 for true colors + alpha channel)
+ final int bitCount = read2Bytes("BitCount", is, "Not a Valid ICO File", getByteOrder());
+ // ImageSize (4 bytes), Length of resource in bytes
+ final int imageSize = read4Bytes("ImageSize", is, "Not a Valid ICO File", getByteOrder());
+ // ImageOffset (4 bytes), start of the image in the file
+ final int imageOffset = read4Bytes("ImageOffset", is, "Not a Valid ICO File", getByteOrder());
+
+ return new IconInfo(width, height, colorCount, reserved, planes, bitCount, imageSize, imageOffset);
+ }
+
+ private static class BitmapHeader {
+ public final int size;
+ public final int width;
+ public final int height;
+ public final int planes;
+ public final int bitCount;
+ public final int compression;
+ public final int sizeImage;
+ public final int xPelsPerMeter;
+ public final int yPelsPerMeter;
+ public final int colorsUsed;
+ public final int colorsImportant;
+
+ public BitmapHeader(final int size, final int width, final int height,
+ final int planes, final int bitCount, final int compression,
+ final int sizeImage, final int pelsPerMeter,
+ final int pelsPerMeter2, final int colorsUsed,
+ final int colorsImportant) {
+ this.size = size;
+ this.width = width;
+ this.height = height;
+ this.planes = planes;
+ this.bitCount = bitCount;
+ this.compression = compression;
+ this.sizeImage = sizeImage;
+ xPelsPerMeter = pelsPerMeter;
+ yPelsPerMeter = pelsPerMeter2;
+ this.colorsUsed = colorsUsed;
+ this.colorsImportant = colorsImportant;
+ }
+
+ public void dump(final PrintWriter pw) {
+ pw.println("BitmapHeader");
+
+ pw.println("Size: " + size);
+ pw.println("Width: " + width);
+ pw.println("Height: " + height);
+ pw.println("Planes: " + planes);
+ pw.println("BitCount: " + bitCount);
+ pw.println("Compression: " + compression);
+ pw.println("SizeImage: " + sizeImage);
+ pw.println("XPelsPerMeter: " + xPelsPerMeter);
+ pw.println("YPelsPerMeter: " + yPelsPerMeter);
+ pw.println("ColorsUsed: " + colorsUsed);
+ pw.println("ColorsImportant: " + colorsImportant);
+ }
+ }
+
+ private static abstract class IconData {
+ public final IconInfo iconInfo;
+
+ public IconData(final IconInfo iconInfo) {
+ this.iconInfo = iconInfo;
+ }
+
+ public void dump(final PrintWriter pw) {
+ iconInfo.dump(pw);
+ pw.println();
+ dumpSubclass(pw);
+ }
+
+ protected abstract void dumpSubclass(PrintWriter pw);
+
+ public abstract BufferedImage readBufferedImage()
+ throws ImageReadException;
+ }
+
+ private static class BitmapIconData extends IconData {
+ public final BitmapHeader header;
+ public final BufferedImage bufferedImage;
+
+ public BitmapIconData(final IconInfo iconInfo,
+ final BitmapHeader header, final BufferedImage bufferedImage) {
+ super(iconInfo);
+ this.header = header;
+ this.bufferedImage = bufferedImage;
+ }
+
+ @Override
+ public BufferedImage readBufferedImage() throws ImageReadException {
+ return bufferedImage;
+ }
+
+ @Override
+ protected void dumpSubclass(final PrintWriter pw) {
+ pw.println("BitmapIconData");
+ header.dump(pw);
+ pw.println();
+ }
+ }
+
+ private static class PNGIconData extends IconData {
+ public final BufferedImage bufferedImage;
+
+ public PNGIconData(final IconInfo iconInfo,
+ final BufferedImage bufferedImage) {
+ super(iconInfo);
+ this.bufferedImage = bufferedImage;
+ }
+
+ @Override
+ public BufferedImage readBufferedImage() {
+ return bufferedImage;
+ }
+
+ @Override
+ protected void dumpSubclass(final PrintWriter pw) {
+ pw.println("PNGIconData");
+ pw.println();
+ }
+ }
+
+ private IconData readBitmapIconData(final byte[] iconData, final IconInfo fIconInfo)
+ throws ImageReadException, IOException {
+ final ByteArrayInputStream is = new ByteArrayInputStream(iconData);
+ final int size = read4Bytes("size", is, "Not a Valid ICO File", getByteOrder()); // Size (4
+ // bytes),
+ // size of
+ // this
+ // structure
+ // (always
+ // 40)
+ final int width = read4Bytes("width", is, "Not a Valid ICO File", getByteOrder()); // Width (4
+ // bytes),
+ // width of
+ // the
+ // image
+ // (same as
+ // iconinfo.width)
+ final int height = read4Bytes("height", is, "Not a Valid ICO File", getByteOrder()); // Height
+ // (4
+ // bytes),
+ // scanlines
+ // in the
+ // color
+ // map +
+ // transparent
+ // map
+ // (iconinfo.height
+ // * 2)
+ final int planes = read2Bytes("planes", is, "Not a Valid ICO File", getByteOrder()); // Planes
+ // (2
+ // bytes),
+ // always
+ // 1
+ final int bitCount = read2Bytes("bitCount", is, "Not a Valid ICO File", getByteOrder()); // BitCount
+ // (2
+ // bytes),
+ // 1,4,8,16,24,32
+ // (see
+ // iconinfo
+ // for
+ // details)
+ int compression = read4Bytes("compression", is, "Not a Valid ICO File", getByteOrder()); // Compression
+ // (4
+ // bytes),
+ // we
+ // don?t
+ // use
+ // this
+ // (0)
+ final int sizeImage = read4Bytes("sizeImage", is, "Not a Valid ICO File", getByteOrder()); // SizeImage
+ // (4
+ // bytes),
+ // we
+ // don?t
+ // use
+ // this
+ // (0)
+ final int xPelsPerMeter = read4Bytes("xPelsPerMeter", is,
+ "Not a Valid ICO File", getByteOrder()); // XPelsPerMeter (4 bytes), we don?t
+ // use this (0)
+ final int yPelsPerMeter = read4Bytes("yPelsPerMeter", is,
+ "Not a Valid ICO File", getByteOrder()); // YPelsPerMeter (4 bytes), we don?t
+ // use this (0)
+ final int colorsUsed = read4Bytes("colorsUsed", is, "Not a Valid ICO File", getByteOrder()); // ColorsUsed
+ // (4
+ // bytes),
+ // we
+ // don?t
+ // use
+ // this
+ // (0)
+ final int colorsImportant = read4Bytes("ColorsImportant", is,
+ "Not a Valid ICO File", getByteOrder()); // ColorsImportant (4 bytes), we don?t
+ // use this (0)
+ int redMask = 0;
+ int greenMask = 0;
+ int blueMask = 0;
+ int alphaMask = 0;
+ if (compression == 3) {
+ redMask = read4Bytes("redMask", is, "Not a Valid ICO File", getByteOrder());
+ greenMask = read4Bytes("greenMask", is, "Not a Valid ICO File", getByteOrder());
+ blueMask = read4Bytes("blueMask", is, "Not a Valid ICO File", getByteOrder());
+ }
+ final byte[] restOfFile = readBytes("RestOfFile", is, is.available());
+
+ if (size != 40) {
+ throw new ImageReadException("Not a Valid ICO File: Wrong bitmap header size " + size);
+ }
+ if (planes != 1) {
+ throw new ImageReadException("Not a Valid ICO File: Planes can't be " + planes);
+ }
+
+ if (compression == 0 && bitCount == 32) {
+ // 32 BPP RGB icons need an alpha channel, but BMP files don't have
+ // one unless BI_BITFIELDS is used...
+ compression = 3;
+ redMask = 0x00ff0000;
+ greenMask = 0x0000ff00;
+ blueMask = 0x000000ff;
+ alphaMask = 0xff000000;
+ }
+
+ final BitmapHeader header = new BitmapHeader(size, width, height, planes,
+ bitCount, compression, sizeImage, xPelsPerMeter, yPelsPerMeter,
+ colorsUsed, colorsImportant);
+
+ final int bitmapPixelsOffset = 14 + 56 + 4 * ((colorsUsed == 0 && bitCount <= 8) ? (1 << bitCount)
+ : colorsUsed);
+ final int bitmapSize = 14 + 56 + restOfFile.length;
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream(bitmapSize);
+ BinaryOutputStream bos = null;
+ boolean canThrow = false;
+ try {
+ bos = new BinaryOutputStream(baos,
+ ByteOrder.LITTLE_ENDIAN);
+
+ bos.write('B');
+ bos.write('M');
+ bos.write4Bytes(bitmapSize);
+ bos.write4Bytes(0);
+ bos.write4Bytes(bitmapPixelsOffset);
+
+ bos.write4Bytes(56);
+ bos.write4Bytes(width);
+ bos.write4Bytes(height / 2);
+ bos.write2Bytes(planes);
+ bos.write2Bytes(bitCount);
+ bos.write4Bytes(compression);
+ bos.write4Bytes(sizeImage);
+ bos.write4Bytes(xPelsPerMeter);
+ bos.write4Bytes(yPelsPerMeter);
+ bos.write4Bytes(colorsUsed);
+ bos.write4Bytes(colorsImportant);
+ bos.write4Bytes(redMask);
+ bos.write4Bytes(greenMask);
+ bos.write4Bytes(blueMask);
+ bos.write4Bytes(alphaMask);
+ bos.write(restOfFile);
+ bos.flush();
+ canThrow = true;
+ } finally {
+ IoUtils.closeQuietly(canThrow, bos);
+ }
+
+ final ByteArrayInputStream bmpInputStream = new ByteArrayInputStream(baos.toByteArray());
+ final BufferedImage bmpImage = new BmpImageParser().getBufferedImage(bmpInputStream, null);
+
+ // Transparency map is optional with 32 BPP icons, because they already
+ // have
+ // an alpha channel, and Windows only uses the transparency map when it
+ // has to
+ // display the icon on a < 32 BPP screen. But it's still used instead of
+ // alpha
+ // if the image would be completely transparent with alpha...
+ int t_scanline_size = (width + 7) / 8;
+ if ((t_scanline_size % 4) != 0) {
+ t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4
+ // byte size.
+ }
+ final int colorMapSizeBytes = t_scanline_size * (height / 2);
+ byte[] transparencyMap = null;
+ try {
+ transparencyMap = readBytes("transparency_map",
+ bmpInputStream, colorMapSizeBytes,
+ "Not a Valid ICO File");
+ } catch (final IOException ioEx) {
+ if (bitCount != 32) {
+ throw ioEx;
+ }
+ }
+
+ boolean allAlphasZero = true;
+ if (bitCount == 32) {
+ for (int y = 0; allAlphasZero && y < bmpImage.getHeight(); y++) {
+ for (int x = 0; x < bmpImage.getWidth(); x++) {
+ if ((bmpImage.getRGB(x, y) & 0xff000000) != 0) {
+ allAlphasZero = false;
+ break;
+ }
+ }
+ }
+ }
+ BufferedImage resultImage;
+ if (allAlphasZero) {
+ resultImage = new BufferedImage(bmpImage.getWidth(),
+ bmpImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ for (int y = 0; y < resultImage.getHeight(); y++) {
+ for (int x = 0; x < resultImage.getWidth(); x++) {
+ int alpha = 0xff;
+ if (transparencyMap != null) {
+ final int alphaByte = 0xff & transparencyMap[t_scanline_size
+ * (bmpImage.getHeight() - y - 1) + (x / 8)];
+ alpha = 0x01 & (alphaByte >> (7 - (x % 8)));
+ alpha = (alpha == 0) ? 0xff : 0x00;
+ }
+ resultImage.setRGB(x, y, (alpha << 24)
+ | (0xffffff & bmpImage.getRGB(x, y)));
+ }
+ }
+ } else {
+ resultImage = bmpImage;
+ }
+ return new BitmapIconData(fIconInfo, header, resultImage);
+ }
+
+ private IconData readIconData(final byte[] iconData, final IconInfo fIconInfo)
+ throws ImageReadException, IOException {
+ final ImageFormat imageFormat = Imaging.guessFormat(iconData);
+ if (imageFormat.equals(ImageFormats.PNG)) {
+ final BufferedImage bufferedImage = Imaging.getBufferedImage(iconData);
+ return new PNGIconData(fIconInfo, bufferedImage);
+ }
+ return readBitmapIconData(iconData, fIconInfo);
+ }
+
+ private static class ImageContents {
+ public final FileHeader fileHeader;
+ public final IconData[] iconDatas;
+
+ public ImageContents(final FileHeader fileHeader,
+ final IconData[] iconDatas) {
+ super();
+ this.fileHeader = fileHeader;
+ this.iconDatas = iconDatas;
+ }
+ }
+
+ private ImageContents readImage(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ InputStream is = null;
+ boolean canThrow = false;
+ try {
+ is = byteSource.getInputStream();
+ final FileHeader fileHeader = readFileHeader(is);
+
+ final IconInfo[] fIconInfos = new IconInfo[fileHeader.iconCount];
+ for (int i = 0; i < fileHeader.iconCount; i++) {
+ fIconInfos[i] = readIconInfo(is);
+ }
+
+ final IconData[] fIconDatas = new IconData[fileHeader.iconCount];
+ for (int i = 0; i < fileHeader.iconCount; i++) {
+ final byte[] iconData = byteSource.getBlock(
+ fIconInfos[i].imageOffset, fIconInfos[i].imageSize);
+ fIconDatas[i] = readIconData(iconData, fIconInfos[i]);
+ }
+
+ final ImageContents ret = new ImageContents(fileHeader, fIconDatas);
+ canThrow = true;
+ return ret;
+ } finally {
+ IoUtils.closeQuietly(canThrow, is);
+ }
+ }
+
+ @Override
+ public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final ImageContents contents = readImage(byteSource);
+ contents.fileHeader.dump(pw);
+ for (final IconData iconData : contents.iconDatas) {
+ iconData.dump(pw);
+ }
+ return true;
+ }
+
+ @Override
+ public final BufferedImage getBufferedImage(final ByteSource byteSource,
+ final Map params) throws ImageReadException, IOException {
+ final ImageContents contents = readImage(byteSource);
+ final FileHeader fileHeader = contents.fileHeader;
+ if (fileHeader.iconCount > 0) {
+ return contents.iconDatas[0].readBufferedImage();
+ }
+ throw new ImageReadException("No icons in ICO file");
+ }
+
+ @Override
+ public List getAllBufferedImages(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final List result = new ArrayList();
+ final ImageContents contents = readImage(byteSource);
+
+ final FileHeader fileHeader = contents.fileHeader;
+ for (int i = 0; i < fileHeader.iconCount; i++) {
+ final IconData iconData = contents.iconDatas[i];
+
+ final BufferedImage image = iconData.readBufferedImage();
+
+ result.add(image);
+ }
+
+ return result;
+ }
+
+ // public boolean extractImages(ByteSource byteSource, File dst_dir,
+ // String dst_root, ImageParser encoder) throws ImageReadException,
+ // IOException, ImageWriteException
+ // {
+ // ImageContents contents = readImage(byteSource);
+ //
+ // FileHeader fileHeader = contents.fileHeader;
+ // for (int i = 0; i < fileHeader.iconCount; i++)
+ // {
+ // IconData iconData = contents.iconDatas[i];
+ //
+ // BufferedImage image = readBufferedImage(iconData);
+ //
+ // int size = Math.max(iconData.iconInfo.Width,
+ // iconData.iconInfo.Height);
+ // File file = new File(dst_dir, dst_root + "_" + size + "_"
+ // + iconData.iconInfo.BitCount
+ // + encoder.getDefaultExtension());
+ // encoder.writeImage(image, new FileOutputStream(file), null);
+ // }
+ //
+ // return true;
+ // }
+
+ @Override
+ public void writeImage(final BufferedImage src, final OutputStream os, Map params)
+ throws ImageWriteException, IOException {
+ // make copy of params; we'll clear keys as we consume them.
+ params = (params == null) ? new HashMap() : new HashMap(params);
+
+ // clear format key.
+ if (params.containsKey(PARAM_KEY_FORMAT)) {
+ params.remove(PARAM_KEY_FORMAT);
+ }
+
+ final PixelDensity pixelDensity = (PixelDensity) params.remove(PARAM_KEY_PIXEL_DENSITY);
+
+ if (!params.isEmpty()) {
+ final Object firstKey = params.keySet().iterator().next();
+ throw new ImageWriteException("Unknown parameter: " + firstKey);
+ }
+
+ final PaletteFactory paletteFactory = new PaletteFactory();
+ final SimplePalette palette = paletteFactory
+ .makeExactRgbPaletteSimple(src, 256);
+ final int bitCount;
+ final boolean hasTransparency = paletteFactory.hasTransparency(src);
+ if (palette == null) {
+ if (hasTransparency) {
+ bitCount = 32;
+ } else {
+ bitCount = 24;
+ }
+ } else if (palette.length() <= 2) {
+ bitCount = 1;
+ } else if (palette.length() <= 16) {
+ bitCount = 4;
+ } else {
+ bitCount = 8;
+ }
+
+ final BinaryOutputStream bos = new BinaryOutputStream(os, ByteOrder.LITTLE_ENDIAN);
+
+ int scanline_size = (bitCount * src.getWidth() + 7) / 8;
+ if ((scanline_size % 4) != 0) {
+ scanline_size += 4 - (scanline_size % 4); // pad scanline to 4 byte
+ // size.
+ }
+ int t_scanline_size = (src.getWidth() + 7) / 8;
+ if ((t_scanline_size % 4) != 0) {
+ t_scanline_size += 4 - (t_scanline_size % 4); // pad scanline to 4
+ // byte size.
+ }
+ final int imageSize = 40 + 4 * (bitCount <= 8 ? (1 << bitCount) : 0)
+ + src.getHeight() * scanline_size + src.getHeight()
+ * t_scanline_size;
+
+ // ICONDIR
+ bos.write2Bytes(0); // reserved
+ bos.write2Bytes(1); // 1=ICO, 2=CUR
+ bos.write2Bytes(1); // count
+
+ // ICONDIRENTRY
+ int iconDirEntryWidth = src.getWidth();
+ int iconDirEntryHeight = src.getHeight();
+ if (iconDirEntryWidth > 255 || iconDirEntryHeight > 255) {
+ iconDirEntryWidth = 0;
+ iconDirEntryHeight = 0;
+ }
+ bos.write(iconDirEntryWidth);
+ bos.write(iconDirEntryHeight);
+ bos.write((bitCount >= 8) ? 0 : (1 << bitCount));
+ bos.write(0); // reserved
+ bos.write2Bytes(1); // color planes
+ bos.write2Bytes(bitCount);
+ bos.write4Bytes(imageSize);
+ bos.write4Bytes(22); // image offset
+
+ // BITMAPINFOHEADER
+ bos.write4Bytes(40); // size
+ bos.write4Bytes(src.getWidth());
+ bos.write4Bytes(2 * src.getHeight());
+ bos.write2Bytes(1); // planes
+ bos.write2Bytes(bitCount);
+ bos.write4Bytes(0); // compression
+ bos.write4Bytes(0); // image size
+ bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres())); // x pixels per meter
+ bos.write4Bytes(pixelDensity == null ? 0 : (int) Math.round(pixelDensity.horizontalDensityMetres())); // y pixels per meter
+ bos.write4Bytes(0); // colors used, 0 = (1 << bitCount) (ignored)
+ bos.write4Bytes(0); // colors important
+
+ if (palette != null) {
+ for (int i = 0; i < (1 << bitCount); i++) {
+ if (i < palette.length()) {
+ final int argb = palette.getEntry(i);
+ bos.write(0xff & argb);
+ bos.write(0xff & (argb >> 8));
+ bos.write(0xff & (argb >> 16));
+ bos.write(0);
+ } else {
+ bos.write(0);
+ bos.write(0);
+ bos.write(0);
+ bos.write(0);
+ }
+ }
+ }
+
+ int bitCache = 0;
+ int bitsInCache = 0;
+ final int rowPadding = scanline_size - (bitCount * src.getWidth() + 7) / 8;
+ for (int y = src.getHeight() - 1; y >= 0; y--) {
+ for (int x = 0; x < src.getWidth(); x++) {
+ final int argb = src.getRGB(x, y);
+ if (bitCount < 8) {
+ final int rgb = 0xffffff & argb;
+ final int index = palette.getPaletteIndex(rgb);
+ bitCache <<= bitCount;
+ bitCache |= index;
+ bitsInCache += bitCount;
+ if (bitsInCache >= 8) {
+ bos.write(0xff & bitCache);
+ bitCache = 0;
+ bitsInCache = 0;
+ }
+ } else if (bitCount == 8) {
+ final int rgb = 0xffffff & argb;
+ final int index = palette.getPaletteIndex(rgb);
+ bos.write(0xff & index);
+ } else if (bitCount == 24) {
+ bos.write(0xff & argb);
+ bos.write(0xff & (argb >> 8));
+ bos.write(0xff & (argb >> 16));
+ } else if (bitCount == 32) {
+ bos.write(0xff & argb);
+ bos.write(0xff & (argb >> 8));
+ bos.write(0xff & (argb >> 16));
+ bos.write(0xff & (argb >> 24));
+ }
+ }
+
+ if (bitsInCache > 0) {
+ bitCache <<= (8 - bitsInCache);
+ bos.write(0xff & bitCache);
+ bitCache = 0;
+ bitsInCache = 0;
+ }
+
+ for (int x = 0; x < rowPadding; x++) {
+ bos.write(0);
+ }
+ }
+
+ final int t_row_padding = t_scanline_size - (src.getWidth() + 7) / 8;
+ for (int y = src.getHeight() - 1; y >= 0; y--) {
+ for (int x = 0; x < src.getWidth(); x++) {
+ final int argb = src.getRGB(x, y);
+ final int alpha = 0xff & (argb >> 24);
+ bitCache <<= 1;
+ if (alpha == 0) {
+ bitCache |= 1;
+ }
+ bitsInCache++;
+ if (bitsInCache >= 8) {
+ bos.write(0xff & bitCache);
+ bitCache = 0;
+ bitsInCache = 0;
+ }
+ }
+
+ if (bitsInCache > 0) {
+ bitCache <<= (8 - bitsInCache);
+ bos.write(0xff & bitCache);
+ bitCache = 0;
+ bitsInCache = 0;
+ }
+
+ for (int x = 0; x < t_row_padding; x++) {
+ bos.write(0);
+ }
+ }
+ }
+
+ /**
+ * Extracts embedded XML metadata as XML string.
+ *
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ @Override
+ public String getXmpXml(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ return null;
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/ico/package-info.java b/src/main/java/org/apache/commons/imaging/formats/ico/package-info.java
new file mode 100644
index 0000000..b5a03b1
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/ico/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The ICO/CUR image formats.
+ */
+package org.apache.commons.imaging.formats.ico;
+
diff --git a/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegConstants.java b/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegConstants.java
new file mode 100644
index 0000000..9ae530e
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegConstants.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.jpeg;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.imaging.common.BinaryConstant;
+import org.apache.commons.imaging.common.BinaryFunctions;
+
+public final class JpegConstants {
+ public static final int MAX_SEGMENT_SIZE = 0xffff;
+
+ public static final BinaryConstant JFIF0_SIGNATURE = new BinaryConstant(
+ new byte[] { 0x4a, // J
+ 0x46, // F
+ 0x49, // I
+ 0x46, // F
+ 0x0, //
+ });
+ public static final BinaryConstant JFIF0_SIGNATURE_ALTERNATIVE = new BinaryConstant(
+ new byte[] { 0x4a, // J
+ 0x46, // F
+ 0x49, // I
+ 0x46, // F
+ 0x20, //
+ });
+
+ public static final BinaryConstant EXIF_IDENTIFIER_CODE = new BinaryConstant(
+ new byte[] { 0x45, // E
+ 0x78, // x
+ 0x69, // i
+ 0x66, // f
+ });
+
+ public static final BinaryConstant XMP_IDENTIFIER = new BinaryConstant(
+ new byte[] { 0x68, // h
+ 0x74, // t
+ 0x74, // t
+ 0x70, // p
+ 0x3A, // :
+ 0x2F, // /
+ 0x2F, // /
+ 0x6E, // n
+ 0x73, // s
+ 0x2E, // .
+ 0x61, // a
+ 0x64, // d
+ 0x6F, // o
+ 0x62, // b
+ 0x65, // e
+ 0x2E, // .
+ 0x63, // c
+ 0x6F, // o
+ 0x6D, // m
+ 0x2F, // /
+ 0x78, // x
+ 0x61, // a
+ 0x70, // p
+ 0x2F, // /
+ 0x31, // 1
+ 0x2E, // .
+ 0x30, // 0
+ 0x2F, // /
+ 0, // 0-terminated us-ascii string.
+ });
+
+ public static final BinaryConstant SOI = new BinaryConstant(new byte[] {
+ (byte) 0xff, (byte) 0xd8 });
+ public static final BinaryConstant EOI = new BinaryConstant(new byte[] {
+ (byte) 0xff, (byte) 0xd9 });
+
+ public static final int JPEG_APP0 = 0xE0;
+ public static final int JPEG_APP0_MARKER = (0xff00) | (JPEG_APP0);
+ public static final int JPEG_APP1_MARKER = (0xff00) | (JPEG_APP0 + 1);
+ public static final int JPEG_APP2_MARKER = (0xff00) | (JPEG_APP0 + 2);
+ public static final int JPEG_APP13_MARKER = (0xff00) | (JPEG_APP0 + 13);
+ public static final int JPEG_APP14_MARKER = (0xff00) | (JPEG_APP0 + 14);
+ public static final int JPEG_APP15_MARKER = (0xff00) | (JPEG_APP0 + 15);
+
+ public static final int JFIF_MARKER = 0xFFE0;
+ public static final int SOF0_MARKER = 0xFFc0;
+ public static final int SOF1_MARKER = 0xFFc0 + 0x1;
+ public static final int SOF2_MARKER = 0xFFc0 + 0x2;
+ public static final int SOF3_MARKER = 0xFFc0 + 0x3;
+ public static final int DHT_MARKER = 0xFFc0 + 0x4;
+ public static final int SOF5_MARKER = 0xFFc0 + 0x5;
+ public static final int SOF6_MARKER = 0xFFc0 + 0x6;
+ public static final int SOF7_MARKER = 0xFFc0 + 0x7;
+ public static final int SOF8_MARKER = 0xFFc0 + 0x8;
+ public static final int SOF9_MARKER = 0xFFc0 + 0x9;
+ public static final int SOF10_MARKER = 0xFFc0 + 0xa;
+ public static final int SOF11_MARKER = 0xFFc0 + 0xb;
+ public static final int DAC_MARKER = 0xFFc0 + 0xc;
+ public static final int SOF13_MARKER = 0xFFc0 + 0xd;
+ public static final int SOF14_MARKER = 0xFFc0 + 0xe;
+ public static final int SOF15_MARKER = 0xFFc0 + 0xf;
+
+ public static final int EOI_MARKER = 0xFFd9;
+ public static final int SOS_MARKER = 0xFFda;
+ public static final int DQT_MARKER = 0xFFdb;
+ public static final int DNL_MARKER = 0xFFdc;
+ public static final int COM_MARKER = 0xFFfe;
+
+ public static final List MARKERS = Collections
+ .unmodifiableList(Arrays.asList(JPEG_APP0, JPEG_APP0_MARKER,
+ JPEG_APP1_MARKER, JPEG_APP2_MARKER, JPEG_APP13_MARKER,
+ JPEG_APP14_MARKER, JPEG_APP15_MARKER, JFIF_MARKER,
+ SOF0_MARKER, SOF1_MARKER, SOF2_MARKER, SOF3_MARKER, DHT_MARKER,
+ SOF5_MARKER, SOF6_MARKER, SOF7_MARKER, SOF8_MARKER, SOF9_MARKER,
+ SOF10_MARKER, SOF11_MARKER, DAC_MARKER, SOF13_MARKER,
+ SOF14_MARKER, SOF15_MARKER, EOI_MARKER, SOS_MARKER, DQT_MARKER,
+ DNL_MARKER, COM_MARKER));
+
+ public static final BinaryConstant ICC_PROFILE_LABEL = new BinaryConstant(
+ new byte[] { 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49,
+ 0x4C, 0x45, 0x0 });
+
+ public static final BinaryConstant PHOTOSHOP_IDENTIFICATION_STRING = new BinaryConstant(
+ new byte[] { 0x50, // P
+ 0x68, // h
+ 0x6F, // o
+ 0x74, // t
+ 0x6F, // o
+ 0x73, // s
+ 0x68, // h
+ 0x6F, // o
+ 0x70, // p
+ 0x20, //
+ 0x33, // 3
+ 0x2E, // .
+ 0x30, // 0
+ 0, });
+ public static final int CONST_8BIM = BinaryFunctions.charsToQuad('8', 'B', 'I', 'M');
+
+ private JpegConstants() {
+ }
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegImageMetadata.java b/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegImageMetadata.java
new file mode 100644
index 0000000..af1b313
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegImageMetadata.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.jpeg;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.Imaging;
+import org.apache.commons.imaging.ImagingException;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.formats.tiff.JpegImageData;
+import org.apache.commons.imaging.formats.tiff.TiffField;
+import org.apache.commons.imaging.formats.tiff.TiffImageData;
+import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
+import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
+import org.apache.commons.imaging.util.Debug;
+
+public class JpegImageMetadata implements IImageMetadata {
+ private final JpegPhotoshopMetadata photoshop;
+ private final TiffImageMetadata exif;
+ private static final String NEWLINE = System.getProperty("line.separator");
+
+ public JpegImageMetadata(final JpegPhotoshopMetadata photoshop,
+ final TiffImageMetadata exif) {
+ this.photoshop = photoshop;
+ this.exif = exif;
+ }
+
+ public TiffImageMetadata getExif() {
+ return exif;
+ }
+
+ public JpegPhotoshopMetadata getPhotoshop() {
+ return photoshop;
+ }
+
+ public TiffField findEXIFValue(final TagInfo tagInfo) {
+ try {
+ return exif != null ? exif.findField(tagInfo) : null;
+ } catch (final ImageReadException cannotHappen) {
+ return null;
+ }
+ }
+
+ public TiffField findEXIFValueWithExactMatch(final TagInfo tagInfo) {
+ try {
+ return exif != null ? exif.findField(tagInfo, true) : null;
+ } catch (final ImageReadException cannotHappen) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the size of the first JPEG thumbnail found in the EXIF metadata.
+ *
+ * @return Thumbnail width and height or null if no thumbnail.
+ * @throws ImageReadException
+ * @throws IOException
+ */
+ public Dimension getEXIFThumbnailSize() throws ImageReadException,
+ IOException {
+ final byte[] data = getEXIFThumbnailData();
+
+ if (data != null) {
+ return Imaging.getImageSize(data);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the data of the first JPEG thumbnail found in the EXIF metadata.
+ *
+ * @return JPEG data or null if no thumbnail.
+ * @throws ImageReadException
+ * @throws IOException
+ */
+ public byte[] getEXIFThumbnailData() throws ImageReadException, IOException {
+ if (exif == null) {
+ return null;
+ }
+ final List extends IImageMetadataItem> dirs = exif.getDirectories();
+ for (IImageMetadataItem d : dirs) {
+ final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
+
+ byte[] data = null;
+ if (dir.getJpegImageData() != null) {
+ data = dir.getJpegImageData().data;
+ }
+ // Support other image formats here.
+
+ if (data != null) {
+ return data;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the thumbnail image if available.
+ *
+ * @return the thumbnail image. May be null if no image could
+ * be found.
+ * @throws ImageReadException
+ * @throws IOException
+ */
+ public BufferedImage getEXIFThumbnail() throws ImageReadException,
+ IOException {
+
+ if (exif == null) {
+ return null;
+ }
+
+ final List extends IImageMetadataItem> dirs = exif.getDirectories();
+ for (IImageMetadataItem d : dirs) {
+ final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
+ // Debug.debug("dir", dir);
+ BufferedImage image = dir.getThumbnail();
+ if (null != image) {
+ return image;
+ }
+
+ final JpegImageData jpegImageData = dir.getJpegImageData();
+ if (jpegImageData != null) {
+ // JPEG thumbnail as JPEG or other format; try to parse.
+ //boolean imageSucceeded = false;
+ //try {
+ image = Imaging.getBufferedImage(jpegImageData.data);
+ //imageSucceeded = true;
+ /*} catch (final ImagingException imagingException) { // NOPMD
+ } catch (final IOException ioException) { // NOPMD
+ } finally {
+ // our JPEG reading is still a bit buggy -
+ // fall back to ImageIO on error
+ if (!imageSucceeded) {
+ final ByteArrayInputStream input = new ByteArrayInputStream(
+ jpegImageData.data);
+ image = ImageIO.read(input);
+ }
+ }*/
+ if (image != null) {
+ return image;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public TiffImageData getRawImageData() {
+ if (exif == null) {
+ return null;
+ }
+ final List extends IImageMetadataItem> dirs = exif.getDirectories();
+ for (IImageMetadataItem d : dirs) {
+ final TiffImageMetadata.Directory dir = (TiffImageMetadata.Directory) d;
+ // Debug.debug("dir", dir);
+ final TiffImageData rawImageData = dir.getTiffImageData();
+ if (null != rawImageData) {
+ return rawImageData;
+ }
+ }
+
+ return null;
+ }
+
+ public List getItems() {
+ final List result = new ArrayList();
+
+ if (null != exif) {
+ result.addAll(exif.getItems());
+ }
+
+ if (null != photoshop) {
+ result.addAll(photoshop.getItems());
+ }
+
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return toString(null);
+ }
+
+ public String toString(String prefix) {
+ if (prefix == null) {
+ prefix = "";
+ }
+
+ final StringBuilder result = new StringBuilder();
+
+ result.append(prefix);
+ if (null == exif) {
+ result.append("No Exif metadata.");
+ } else {
+ result.append("Exif metadata:");
+ result.append(NEWLINE);
+ result.append(exif.toString("\t"));
+ }
+
+ // if (null != exif && null != photoshop)
+ result.append(NEWLINE);
+
+ result.append(prefix);
+ if (null == photoshop) {
+ result.append("No Photoshop (IPTC) metadata.");
+ } else {
+ result.append("Photoshop (IPTC) metadata:");
+ result.append(NEWLINE);
+ result.append(photoshop.toString("\t"));
+ }
+
+ return result.toString();
+ }
+
+ public void dump() {
+ Debug.debug(this.toString());
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegImageParser.java b/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegImageParser.java
new file mode 100644
index 0000000..cfab48a
--- /dev/null
+++ b/src/main/java/org/apache/commons/imaging/formats/jpeg/JpegImageParser.java
@@ -0,0 +1,1159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.imaging.formats.jpeg;
+
+import com.google.code.appengine.awt.Dimension;
+import com.google.code.appengine.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteOrder;
+import java.text.NumberFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.imaging.ImageFormat;
+import org.apache.commons.imaging.ImageFormats;
+import org.apache.commons.imaging.ImageInfo;
+import org.apache.commons.imaging.ImageParser;
+import org.apache.commons.imaging.ImageReadException;
+import org.apache.commons.imaging.common.IImageMetadata;
+import org.apache.commons.imaging.common.bytesource.ByteSource;
+import org.apache.commons.imaging.formats.jpeg.decoder.JpegDecoder;
+import org.apache.commons.imaging.formats.jpeg.iptc.IptcParser;
+import org.apache.commons.imaging.formats.jpeg.iptc.PhotoshopApp13Data;
+import org.apache.commons.imaging.formats.jpeg.segments.App13Segment;
+import org.apache.commons.imaging.formats.jpeg.segments.App14Segment;
+import org.apache.commons.imaging.formats.jpeg.segments.App2Segment;
+import org.apache.commons.imaging.formats.jpeg.segments.ComSegment;
+import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment;
+import org.apache.commons.imaging.formats.jpeg.segments.GenericSegment;
+import org.apache.commons.imaging.formats.jpeg.segments.JfifSegment;
+import org.apache.commons.imaging.formats.jpeg.segments.Segment;
+import org.apache.commons.imaging.formats.jpeg.segments.SofnSegment;
+import org.apache.commons.imaging.formats.jpeg.segments.UnknownSegment;
+import org.apache.commons.imaging.formats.jpeg.xmp.JpegXmpParser;
+import org.apache.commons.imaging.formats.tiff.TiffField;
+import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
+import org.apache.commons.imaging.formats.tiff.TiffImageParser;
+import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
+import org.apache.commons.imaging.util.Debug;
+
+import static org.apache.commons.imaging.ImagingConstants.*;
+import static org.apache.commons.imaging.common.BinaryFunctions.*;
+
+public class JpegImageParser extends ImageParser {
+ private static final String DEFAULT_EXTENSION = ".jpg";
+ private static final String[] ACCEPTED_EXTENSIONS = { ".jpg", ".jpeg", };
+
+ public JpegImageParser() {
+ setByteOrder(ByteOrder.BIG_ENDIAN);
+ // setDebug(true);
+ }
+
+ @Override
+ protected ImageFormat[] getAcceptedTypes() {
+ return new ImageFormat[] { ImageFormats.JPEG, //
+ };
+ }
+
+ @Override
+ public String getName() {
+ return "Jpeg-Custom";
+ }
+
+ @Override
+ public String getDefaultExtension() {
+ return DEFAULT_EXTENSION;
+ }
+
+
+ @Override
+ protected String[] getAcceptedExtensions() {
+ return ACCEPTED_EXTENSIONS;
+ }
+
+ @Override
+ public final BufferedImage getBufferedImage(final ByteSource byteSource,
+ final Map params) throws ImageReadException, IOException {
+ final JpegDecoder jpegDecoder = new JpegDecoder();
+ return jpegDecoder.decode(byteSource);
+ }
+
+ private boolean keepMarker(final int marker, final int[] markers) {
+ if (markers == null) {
+ return true;
+ }
+
+ for (final int marker2 : markers) {
+ if (marker2 == marker) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public List readSegments(final ByteSource byteSource,
+ final int[] markers, final boolean returnAfterFirst,
+ final boolean readEverything) throws ImageReadException, IOException {
+ final List result = new ArrayList();
+ final JpegImageParser parser = this;
+ final int[] sofnSegments = {
+ // kJFIFMarker,
+ JpegConstants.SOF0_MARKER,
+ JpegConstants.SOF1_MARKER,
+ JpegConstants.SOF2_MARKER,
+ JpegConstants.SOF3_MARKER,
+ JpegConstants.SOF5_MARKER,
+ JpegConstants.SOF6_MARKER,
+ JpegConstants.SOF7_MARKER,
+ JpegConstants.SOF9_MARKER,
+ JpegConstants.SOF10_MARKER,
+ JpegConstants.SOF11_MARKER,
+ JpegConstants.SOF13_MARKER,
+ JpegConstants.SOF14_MARKER,
+ JpegConstants.SOF15_MARKER,
+ };
+
+ final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
+ // return false to exit before reading image data.
+ public boolean beginSOS() {
+ return false;
+ }
+
+ public void visitSOS(final int marker, final byte[] markerBytes,
+ final byte[] imageData) {
+ // don't need image data
+ }
+
+ // return false to exit traversal.
+ public boolean visitSegment(final int marker, final byte[] markerBytes,
+ final int markerLength, final byte[] markerLengthBytes,
+ final byte[] segmentData) throws ImageReadException, IOException {
+ if (marker == JpegConstants.EOI_MARKER) {
+ return false;
+ }
+
+ // Debug.debug("visitSegment marker", marker);
+ // // Debug.debug("visitSegment keepMarker(marker, markers)",
+ // keepMarker(marker, markers));
+ // Debug.debug("visitSegment keepMarker(marker, markers)",
+ // keepMarker(marker, markers));
+
+ if (!keepMarker(marker, markers)) {
+ return true;
+ }
+
+ if (marker == JpegConstants.JPEG_APP13_MARKER) {
+ // Debug.debug("app 13 segment data", segmentData.length);
+ result.add(new App13Segment(parser, marker, segmentData));
+ } else if (marker == JpegConstants.JPEG_APP14_MARKER) {
+ result.add(new App14Segment(marker, segmentData));
+ } else if (marker == JpegConstants.JPEG_APP2_MARKER) {
+ result.add(new App2Segment(marker, segmentData));
+ } else if (marker == JpegConstants.JFIF_MARKER) {
+ result.add(new JfifSegment(marker, segmentData));
+ } else if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
+ result.add(new SofnSegment(marker, segmentData));
+ } else if (marker == JpegConstants.DQT_MARKER) {
+ result.add(new DqtSegment(marker, segmentData));
+ } else if ((marker >= JpegConstants.JPEG_APP1_MARKER)
+ && (marker <= JpegConstants.JPEG_APP15_MARKER)) {
+ result.add(new UnknownSegment(marker, segmentData));
+ } else if (marker == JpegConstants.COM_MARKER) {
+ result.add(new ComSegment(marker, segmentData));
+ }
+
+ if (returnAfterFirst) {
+ return false;
+ }
+
+ return true;
+ }
+ };
+
+ new JpegUtils().traverseJFIF(byteSource, visitor);
+
+ return result;
+ }
+
+ private byte[] assembleSegments(List segments) throws ImageReadException {
+ try {
+ return assembleSegments(segments, false);
+ } catch (ImageReadException e) {
+ return assembleSegments(segments, true);
+ }
+ }
+
+ private byte[] assembleSegments(final List segments, final boolean startWithZero)
+ throws ImageReadException {
+ if (segments.isEmpty()) {
+ throw new ImageReadException("No App2 Segments Found.");
+ }
+
+ final int markerCount = segments.get(0).numMarkers;
+
+ if (segments.size() != markerCount) {
+ throw new ImageReadException("App2 Segments Missing. Found: "
+ + segments.size() + ", Expected: " + markerCount + ".");
+ }
+
+ Collections.sort(segments);
+
+ final int offset = startWithZero ? 0 : 1;
+
+ int total = 0;
+ for (int i = 0; i < segments.size(); i++) {
+ final App2Segment segment = segments.get(i);
+
+ if ((i + offset) != segment.curMarker) {
+ dumpSegments(segments);
+ throw new ImageReadException(
+ "Incoherent App2 Segment Ordering. i: " + i
+ + ", segment[" + i + "].curMarker: "
+ + segment.curMarker + ".");
+ }
+
+ if (markerCount != segment.numMarkers) {
+ dumpSegments(segments);
+ throw new ImageReadException(
+ "Inconsistent App2 Segment Count info. markerCount: "
+ + markerCount + ", segment[" + i
+ + "].numMarkers: " + segment.numMarkers + ".");
+ }
+
+ total += segment.iccBytes.length;
+ }
+
+ final byte[] result = new byte[total];
+ int progress = 0;
+
+ for (App2Segment segment : segments) {
+ System.arraycopy(segment.iccBytes, 0, result, progress, segment.iccBytes.length);
+ progress += segment.iccBytes.length;
+ }
+
+ return result;
+ }
+
+ private void dumpSegments(final List extends Segment> v) {
+ Debug.debug();
+ Debug.debug("dumpSegments: " + v.size());
+
+ for (int i = 0; i < v.size(); i++) {
+ final App2Segment segment = (App2Segment) v.get(i);
+
+ Debug.debug(i + ": " + segment.curMarker + " / " + segment.numMarkers);
+ }
+ Debug.debug();
+ }
+
+ public List readSegments(final ByteSource byteSource, final int[] markers,
+ final boolean returnAfterFirst) throws ImageReadException, IOException {
+ return readSegments(byteSource, markers, returnAfterFirst, false);
+ }
+
+ @Override
+ public byte[] getICCProfileBytes(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final List segments = readSegments(byteSource,
+ new int[] { JpegConstants.JPEG_APP2_MARKER, }, false);
+
+ final List filtered = new ArrayList();
+ if (segments != null) {
+ // throw away non-icc profile app2 segments.
+ for (Segment s : segments) {
+ final App2Segment segment = (App2Segment) s;
+ if (segment.iccBytes != null) {
+ filtered.add(segment);
+ }
+ }
+ }
+
+ if (filtered.isEmpty()) {
+ return null;
+ }
+
+ final byte[] bytes = assembleSegments(filtered);
+
+ if (getDebug()) {
+ System.out.println("bytes" + ": " + bytes.length);
+ }
+
+ if (getDebug()) {
+ System.out.println("");
+ }
+
+ return bytes;
+ }
+
+ @Override
+ public IImageMetadata getMetadata(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+ final TiffImageMetadata exif = getExifMetadata(byteSource, params);
+
+ final JpegPhotoshopMetadata photoshop = getPhotoshopMetadata(byteSource,
+ params);
+
+ if (null == exif && null == photoshop) {
+ return null;
+ }
+
+ return new JpegImageMetadata(photoshop, exif);
+ }
+
+ public static boolean isExifAPP1Segment(final GenericSegment segment) {
+ return startsWith(segment.getSegmentData(), JpegConstants.EXIF_IDENTIFIER_CODE);
+ }
+
+ private List filterAPP1Segments(final List segments) {
+ final List result = new ArrayList();
+
+ for (Segment s : segments) {
+ final GenericSegment segment = (GenericSegment) s;
+ if (isExifAPP1Segment(segment)) {
+ result.add(segment);
+ }
+ }
+
+ return result;
+ }
+
+ public TiffImageMetadata getExifMetadata(final ByteSource byteSource, Map params)
+ throws ImageReadException, IOException {
+ final byte[] bytes = getExifRawData(byteSource);
+ if (null == bytes) {
+ return null;
+ }
+
+ if (params == null) {
+ params = new HashMap();
+ }
+ if (!params.containsKey(PARAM_KEY_READ_THUMBNAILS)) {
+ params.put(PARAM_KEY_READ_THUMBNAILS, Boolean.TRUE);
+ }
+
+ return (TiffImageMetadata) new TiffImageParser().getMetadata(bytes,
+ params);
+ }
+
+ public byte[] getExifRawData(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final List segments = readSegments(byteSource,
+ new int[] { JpegConstants.JPEG_APP1_MARKER, }, false);
+
+ if ((segments == null) || (segments.isEmpty())) {
+ return null;
+ }
+
+ final List exifSegments = filterAPP1Segments(segments);
+ if (getDebug()) {
+ System.out.println("exif_segments.size" + ": "
+ + exifSegments.size());
+ }
+
+ // Debug.debug("segments", segments);
+ // Debug.debug("exifSegments", exifSegments);
+
+ // TODO: concatenate if multiple segments, need example.
+ if (exifSegments.isEmpty()) {
+ return null;
+ }
+ if (exifSegments.size() > 1) {
+ throw new ImageReadException(
+ "Imaging currently can't parse EXIF metadata split across multiple APP1 segments. "
+ + "Please send this image to the Imaging project.");
+ }
+
+ final GenericSegment segment = (GenericSegment) exifSegments.get(0);
+ final byte[] bytes = segment.getSegmentData();
+
+ // byte head[] = readBytearray("exif head", bytes, 0, 6);
+ //
+ // Debug.debug("head", head);
+
+ return remainingBytes("trimmed exif bytes", bytes, 6);
+ }
+
+ public boolean hasExifSegment(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final boolean[] result = { false, };
+
+ final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
+ // return false to exit before reading image data.
+ public boolean beginSOS() {
+ return false;
+ }
+
+ public void visitSOS(final int marker, final byte[] markerBytes,
+ final byte[] imageData) {
+ // don't need image data
+ }
+
+ // return false to exit traversal.
+ public boolean visitSegment(final int marker, final byte[] markerBytes,
+ final int markerLength, final byte[] markerLengthBytes,
+ final byte[] segmentData) throws ImageReadException, IOException {
+ if (marker == 0xffd9) {
+ return false;
+ }
+
+ if (marker == JpegConstants.JPEG_APP1_MARKER) {
+ if (startsWith(segmentData, JpegConstants.EXIF_IDENTIFIER_CODE)) {
+ result[0] = true;
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+
+ new JpegUtils().traverseJFIF(byteSource, visitor);
+
+ return result[0];
+ }
+
+ public boolean hasIptcSegment(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final boolean[] result = { false, };
+
+ final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
+ // return false to exit before reading image data.
+ public boolean beginSOS() {
+ return false;
+ }
+
+ public void visitSOS(final int marker, final byte[] markerBytes,
+ final byte[] imageData) {
+ // don't need image data
+ }
+
+ // return false to exit traversal.
+ public boolean visitSegment(final int marker, final byte[] markerBytes,
+ final int markerLength, final byte[] markerLengthBytes,
+ final byte[] segmentData) throws ImageReadException, IOException {
+ if (marker == 0xffd9) {
+ return false;
+ }
+
+ if (marker == JpegConstants.JPEG_APP13_MARKER) {
+ if (new IptcParser().isPhotoshopJpegSegment(segmentData)) {
+ result[0] = true;
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+
+ new JpegUtils().traverseJFIF(byteSource, visitor);
+
+ return result[0];
+ }
+
+ public boolean hasXmpSegment(final ByteSource byteSource)
+ throws ImageReadException, IOException {
+ final boolean[] result = { false, };
+
+ final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
+ // return false to exit before reading image data.
+ public boolean beginSOS() {
+ return false;
+ }
+
+ public void visitSOS(final int marker, final byte[] markerBytes,
+ final byte[] imageData) {
+ // don't need image data
+ }
+
+ // return false to exit traversal.
+ public boolean visitSegment(final int marker, final byte[] markerBytes,
+ final int markerLength, final byte[] markerLengthBytes,
+ final byte[] segmentData) throws ImageReadException, IOException {
+ if (marker == 0xffd9) {
+ return false;
+ }
+
+ if (marker == JpegConstants.JPEG_APP1_MARKER) {
+ if (new JpegXmpParser().isXmpJpegSegment(segmentData)) {
+ result[0] = true;
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ new JpegUtils().traverseJFIF(byteSource, visitor);
+
+ return result[0];
+ }
+
+ /**
+ * Extracts embedded XML metadata as XML string.
+ *
+ *
+ * @param byteSource
+ * File containing image data.
+ * @param params
+ * Map of optional parameters, defined in ImagingConstants.
+ * @return Xmp Xml as String, if present. Otherwise, returns null.
+ */
+ @Override
+ public String getXmpXml(final ByteSource byteSource, final Map params)
+ throws ImageReadException, IOException {
+
+ final List result = new ArrayList