From 8074aa5fd8ecc24ba5de6656581aaf8d3078eb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Tue, 26 May 2015 20:01:22 +0200 Subject: [PATCH] Issue #895 Correct handling CMYK JPEG images (Monte Video library) Correct JPEG to Image conversion when it has alpha --- .gitignore | 2 + lib/cmykjpeg.jar | Bin 0 -> 90117 bytes libsrc/cmykjpeg/build.xml | 73 + libsrc/cmykjpeg/nbproject/build-impl.xml | 1413 +++++++++++++++++ libsrc/cmykjpeg/nbproject/genfiles.properties | 8 + libsrc/cmykjpeg/nbproject/project.properties | 73 + libsrc/cmykjpeg/nbproject/project.xml | 15 + .../media/io/ByteArrayImageInputStream.java | 216 +++ .../media/io/ImageInputStreamAdapter.java | 189 +++ .../monte/media/io/ImageInputStreamImpl2.java | 65 + .../monte/media/jpeg/CMYKJPEGImageReader.java | 823 ++++++++++ .../media/jpeg/CMYKJPEGImageReaderSpi.java | 77 + .../monte/media/jpeg/Generic CMYK Profile.icc | Bin 0 -> 53964 bytes .../org/monte/media/jpeg/JFIFInputStream.java | 521 ++++++ libsrc/ffdec_lib/nbproject/project.xml | 2 +- .../flash/exporters/ImageExporter.java | 219 +-- .../decompiler/flash/helpers/ImageHelper.java | 21 +- .../flash/tags/DefineBitsJPEG2Tag.java | 9 +- .../flash/tags/DefineBitsJPEG3Tag.java | 20 +- .../flash/tags/DefineBitsJPEG4Tag.java | 15 +- 20 files changed, 3641 insertions(+), 120 deletions(-) create mode 100644 lib/cmykjpeg.jar create mode 100644 libsrc/cmykjpeg/build.xml create mode 100644 libsrc/cmykjpeg/nbproject/build-impl.xml create mode 100644 libsrc/cmykjpeg/nbproject/genfiles.properties create mode 100644 libsrc/cmykjpeg/nbproject/project.properties create mode 100644 libsrc/cmykjpeg/nbproject/project.xml create mode 100644 libsrc/cmykjpeg/src/org/monte/media/io/ByteArrayImageInputStream.java create mode 100644 libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamAdapter.java create mode 100644 libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamImpl2.java create mode 100644 libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReader.java create mode 100644 libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReaderSpi.java create mode 100644 libsrc/cmykjpeg/src/org/monte/media/jpeg/Generic CMYK Profile.icc create mode 100644 libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java diff --git a/.gitignore b/.gitignore index 43e23f6d5..6724a8300 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ hs_err_pid*.log /version.properties /tools.properties /nbproject/private/ +/libsrc/cmykjpeg/build/ +/libsrc/cmykjpeg/nbproject/private/ \ No newline at end of file diff --git a/lib/cmykjpeg.jar b/lib/cmykjpeg.jar new file mode 100644 index 0000000000000000000000000000000000000000..179d50c90045419651ba06f1228941dd5bc3f88d GIT binary patch literal 90117 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 literal 0 HcmV?d00001 diff --git a/libsrc/cmykjpeg/build.xml b/libsrc/cmykjpeg/build.xml new file mode 100644 index 000000000..81f02213d --- /dev/null +++ b/libsrc/cmykjpeg/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project cmykjpeg. + + + diff --git a/libsrc/cmykjpeg/nbproject/build-impl.xml b/libsrc/cmykjpeg/nbproject/build-impl.xml new file mode 100644 index 000000000..4044e3e5a --- /dev/null +++ b/libsrc/cmykjpeg/nbproject/build-impl.xml @@ -0,0 +1,1413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libsrc/cmykjpeg/nbproject/genfiles.properties b/libsrc/cmykjpeg/nbproject/genfiles.properties new file mode 100644 index 000000000..605d6401d --- /dev/null +++ b/libsrc/cmykjpeg/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=73f9688e +build.xml.script.CRC32=22af611f +build.xml.stylesheet.CRC32=8064a381@1.75.2.48 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=73f9688e +nbproject/build-impl.xml.script.CRC32=de1179e8 +nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.2.48 diff --git a/libsrc/cmykjpeg/nbproject/project.properties b/libsrc/cmykjpeg/nbproject/project.properties new file mode 100644 index 000000000..e39fb97c5 --- /dev/null +++ b/libsrc/cmykjpeg/nbproject/project.properties @@ -0,0 +1,73 @@ +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=cmykjpeg +application.vendor=JPEXS +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=../../lib/cmykjpeg.jar +dist.javadoc.dir=${dist.dir}/javadoc +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=true +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/libsrc/cmykjpeg/nbproject/project.xml b/libsrc/cmykjpeg/nbproject/project.xml new file mode 100644 index 000000000..f2591e5f5 --- /dev/null +++ b/libsrc/cmykjpeg/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + cmykjpeg + + + + + + + + + diff --git a/libsrc/cmykjpeg/src/org/monte/media/io/ByteArrayImageInputStream.java b/libsrc/cmykjpeg/src/org/monte/media/io/ByteArrayImageInputStream.java new file mode 100644 index 000000000..4baa50420 --- /dev/null +++ b/libsrc/cmykjpeg/src/org/monte/media/io/ByteArrayImageInputStream.java @@ -0,0 +1,216 @@ +/* + * @(#)ByteArrayImageInputStream.java + * + * Copyright (c) 2008-2011 Werner Randelshofer, Goldau, Switzerland. + * All rights reserved. + * + * You may not use, copy or modify this file, except in compliance with the + * license agreement you entered into with Werner Randelshofer. + * For details see accompanying license terms. + */ +package org.monte.media.io; + +import java.io.IOException; +import java.nio.ByteOrder; + +/** + * A {@code ByteArrayImageInputStream} contains + * an internal buffer that contains bytes that + * may be read from the stream. An internal + * counter keeps track of the next byte to + * be supplied by the {@code read} method. + *

+ * Closing a {@code ByteArrayImageInputStream} has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an {@code IOException}. + * + * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau + * @version $Id: ByteArrayImageInputStream.java 299 2013-01-03 07:40:18Z werner $ + */ +public class ByteArrayImageInputStream extends ImageInputStreamImpl2 { + /** + * An array of bytes that was provided + * by the creator of the stream. Elements buf[0] + * through buf[count-1] are the + * only bytes that can ever be read from the + * stream; element buf[streamPos] is + * the next byte to be read. + */ + protected byte buf[]; + + /** + * The index one greater than the last valid character in the input + * stream buffer. + * This value should always be nonnegative + * and not larger than the length of buf. + * It is one greater than the position of + * the last byte within buf that + * can ever be read from the input stream buffer. + */ + protected int count; + + /** The offset to the start of the array. */ + private final int arrayOffset; + + public ByteArrayImageInputStream(byte[] buf) { + this(buf, ByteOrder.BIG_ENDIAN); + } + + public ByteArrayImageInputStream(byte[] buf, ByteOrder byteOrder) { + this(buf, 0, buf.length, byteOrder); + } + + public ByteArrayImageInputStream(byte[] buf, int offset, int length, ByteOrder byteOrder) { + this.buf = buf; + this.streamPos = offset; + this.count = Math.min(offset + length, buf.length); + this.arrayOffset = offset; + this.byteOrder = byteOrder; + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. + *

+ * This read method + * cannot block. + * + * @return the next byte of data, or -1 if the end of the + * stream has been reached. + */ + @Override + public synchronized int read() { + flushBits(); + return (streamPos < count) ? (buf[(int)(streamPos++)] & 0xff) : -1; + } + + /** + * Reads up to len bytes of data into an array of bytes + * from this input stream. + * If streamPos equals count, + * then -1 is returned to indicate + * end of file. Otherwise, the number k + * of bytes read is equal to the smaller of + * len and count-streamPos. + * If k is positive, then bytes + * buf[streamPos] through buf[streamPos+k-1] + * are copied into b[off] through + * b[off+k-1] in the manner performed + * by System.arraycopy. The + * value k is added into streamPos + * and k is returned. + *

+ * This read method cannot block. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array b + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + * @exception NullPointerException If b is null. + * @exception IndexOutOfBoundsException If off is negative, + * len is negative, or len is greater than + * b.length - off + */ + @Override + public synchronized int read(byte b[], int off, int len) { + flushBits(); + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + if (streamPos >= count) { + return -1; + } + if (streamPos + len > count) { + len = (int)(count - streamPos); + } + if (len <= 0) { + return 0; + } + System.arraycopy(buf, (int)streamPos, b, off, len); + streamPos += len; + return len; + } + + /** + * Skips n bytes of input from this input stream. Fewer + * bytes might be skipped if the end of the input stream is reached. + * The actual number k + * of bytes to be skipped is equal to the smaller + * of n and count-streamPos. + * The value k is added into streamPos + * and k is returned. + * + * @param n the number of bytes to be skipped. + * @return the actual number of bytes skipped. + */ + public synchronized long skip(long n) { + if (streamPos + n > count) { + n = count - streamPos; + } + if (n < 0) { + return 0; + } + streamPos += n; + return n; + } + + /** + * Returns the number of remaining bytes that can be read (or skipped over) + * from this input stream. + *

+ * The value returned is count - streamPos, + * which is the number of bytes remaining to be read from the input buffer. + * + * @return the number of remaining bytes that can be read (or skipped + * over) from this input stream without blocking. + */ + public synchronized int available() { + return (int)(count - streamPos); + } + + + + /** + * Closing a ByteArrayInputStream has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an IOException. + *

+ */ + @Override + public void close() { + // does nothing!! + } + + @Override + public long getStreamPosition() throws IOException { + checkClosed(); + return streamPos-arrayOffset; + } + @Override + public void seek(long pos) throws IOException { + checkClosed(); + flushBits(); + + // This test also covers pos < 0 + if (pos < flushedPos) { + throw new IndexOutOfBoundsException("pos < flushedPos!"); + } + + this.streamPos = pos+arrayOffset; + } + + private void flushBits() { + bitOffset=0; + } + @Override + public long length() { + return count-arrayOffset; + } +} diff --git a/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamAdapter.java b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamAdapter.java new file mode 100644 index 000000000..30fd47813 --- /dev/null +++ b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamAdapter.java @@ -0,0 +1,189 @@ +/* + * @(#)ImageInputStreamAdapter.java 1.0 2009-12-17 + * + * Copyright (c) 2009 Werner Randelshofer, Goldau, Switzerland. + * All rights reserved. + * + * You may not use, copy or modify this file, except in compliance with the + * license agreement you entered into with Werner Randelshofer. + * For details see accompanying license terms. + */ + +package org.monte.media.io; + +import java.io.FilterInputStream; +import java.io.IOException; +import javax.imageio.stream.ImageInputStream; + +/** + * ImageInputStreamAdapter. + * + * @author Werner Randelshofer + * @version 1.0 2009-12-17 Created. + */ +public class ImageInputStreamAdapter extends FilterInputStream { + private ImageInputStream iis; + public ImageInputStreamAdapter(ImageInputStream iis) { + super(null); + this.iis=iis; + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * This method + * simply performs in.read() and returns the result. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + @Override + public int read() throws IOException { + return iis.read(); + } + + /** + * Reads up to len bytes of data from this input stream + * into an array of bytes. If len is not zero, the method + * blocks until some input is available; otherwise, no + * bytes are read and 0 is returned. + *

+ * This method simply performs in.read(b, off, len) + * and returns the result. + * + * @param b the buffer into which the data is read. + * @param off the start offset in the destination array b + * @param len the maximum number of bytes read. + * @return the total number of bytes read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + * @exception NullPointerException If b is null. + * @exception IndexOutOfBoundsException If off is negative, + * len is negative, or len is greater than + * b.length - off + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + @Override + public int read(byte b[], int off, int len) throws IOException { + return iis.read(b, off, len); + } + + /** + * {@inheritDoc} + *

+ * This method simply performs in.skip(n). + */ + @Override + public long skip(long n) throws IOException { + return iis.skipBytes(n); + } + + /** + * Returns an estimate of the number of bytes that can be read (or + * skipped over) from this input stream without blocking by the next + * caller of a method for this input stream. The next caller might be + * the same thread or another thread. A single read or skip of this + * many bytes will not block, but may read or skip fewer bytes. + *

+ * This method returns the result of {@link #in in}.available(). + * + * @return an estimate of the number of bytes that can be read (or skipped + * over) from this input stream without blocking. + * @exception IOException if an I/O error occurs. + */ + @Override + public int available() throws IOException { + return (iis.isCached()) ? // + (int)Math.min(Integer.MAX_VALUE, iis.length() - iis.getStreamPosition()) : + 0; + } + + /** + * Closes this input stream and releases any system resources + * associated with the stream. + * This + * method simply performs in.close(). + * + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + @Override + public void close() throws IOException { + iis.close(); + } + + /** + * Marks the current position in this input stream. A subsequent + * call to the reset method repositions this stream at + * the last marked position so that subsequent reads re-read the same bytes. + *

+ * The readlimit argument tells this input stream to + * allow that many bytes to be read before the mark position gets + * invalidated. + *

+ * This method simply performs in.mark(readlimit). + * + * @param readlimit the maximum limit of bytes that can be read before + * the mark position becomes invalid. + * @see java.io.FilterInputStream#in + * @see java.io.FilterInputStream#reset() + */ + @Override + public synchronized void mark(int readlimit) { + iis.mark(); + } + + /** + * Repositions this stream to the position at the time the + * mark method was last called on this input stream. + *

+ * This method + * simply performs in.reset(). + *

+ * Stream marks are intended to be used in + * situations where you need to read ahead a little to see what's in + * the stream. Often this is most easily done by invoking some + * general parser. If the stream is of the type handled by the + * parse, it just chugs along happily. If the stream is not of + * that type, the parser should toss an exception when it fails. + * If this happens within readlimit bytes, it allows the outer + * code to reset the stream and try another parser. + * + * @exception IOException if the stream has not been marked or if the + * mark has been invalidated. + * @see java.io.FilterInputStream#in + * @see java.io.FilterInputStream#mark(int) + */ + @Override + public synchronized void reset() throws IOException { + iis.reset(); + } + + /** + * Tests if this input stream supports the mark + * and reset methods. + * This method + * simply performs in.markSupported(). + * + * @return true if this stream type supports the + * mark and reset method; + * false otherwise. + * @see java.io.FilterInputStream#in + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#reset() + */ + @Override + public boolean markSupported() { + return true; + } + +} diff --git a/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamImpl2.java b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamImpl2.java new file mode 100644 index 000000000..501224398 --- /dev/null +++ b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamImpl2.java @@ -0,0 +1,65 @@ +/* + * @(#)ImageInputStreamImpl2.java + * + * Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland. + * All rights reserved. + * + * You may not use, copy or modify this file, except in compliance with the + * license agreement you entered into with Werner Randelshofer. + * For details see accompanying license terms. + */ +package org.monte.media.io; + +import java.io.IOException; +import java.nio.ByteOrder; +import javax.imageio.stream.ImageInputStreamImpl; + +/** + * {@code ImageInputStreamImpl2} fixes bugs in ImageInputStreamImpl. + *

+ * ImageInputStreamImpl uses read(byte[]) instead of readFully(byte[]) inside of + * readShort. This results in corrupt data input if the underlying stream can + * not fulfill the read operation in a single step. + * + * @author Werner Randelshofer + * @version $Id: ImageInputStreamImpl2.java 299 2013-01-03 07:40:18Z werner $ + */ +public abstract class ImageInputStreamImpl2 extends ImageInputStreamImpl { + // Length of the buffer used for readFully(type[], int, int) + private static final int BYTE_BUF_LENGTH = 8192; + /** + * Byte buffer used for readFully(type[], int, int). Note that this + * array is also used for bulk reads in readShort(), readInt(), etc, so + * it should be large enough to hold a primitive value (i.e. >= 8 bytes). + * Also note that this array is package protected, so that it can be + * used by ImageOutputStreamImpl in a similar manner. + */ + byte[] byteBuf = new byte[BYTE_BUF_LENGTH]; + + @Override + public short readShort() throws IOException { + readFully(byteBuf, 0, 2); + + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return (short) + (((byteBuf[0] & 0xff) << 8) | ((byteBuf[1] & 0xff) << 0)); + } else { + return (short) + (((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0)); + } + } + public int readInt() throws IOException { + readFully(byteBuf, 0, 4); + + if (byteOrder == ByteOrder.BIG_ENDIAN) { + return + (((byteBuf[0] & 0xff) << 24) | ((byteBuf[1] & 0xff) << 16) | + ((byteBuf[2] & 0xff) << 8) | ((byteBuf[3] & 0xff) << 0)); + } else { + return + (((byteBuf[3] & 0xff) << 24) | ((byteBuf[2] & 0xff) << 16) | + ((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0)); + } + } + +} diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReader.java b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReader.java new file mode 100644 index 000000000..206472bc4 --- /dev/null +++ b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReader.java @@ -0,0 +1,823 @@ +/* + * @(#)CMYJKJPEGImageReader.java + * + * Copyright (c) 2010-2011 Werner Randelshofer, Goldau, Switzerland. + * All rights reserved. + * + * You may not use, copy or modify this file, except in compliance with the + * license agreement you entered into with Werner Randelshofer. + * For details see accompanying license terms. + */ +package org.monte.media.jpeg; + +import com.sun.imageio.plugins.jpeg.JPEGImageReader; +import java.util.Iterator; +import java.util.LinkedList; +import javax.imageio.metadata.IIOMetadata; +import org.monte.media.io.ByteArrayImageInputStream; +import org.monte.media.io.ImageInputStreamAdapter; +//import com.sun.imageio.plugins.jpeg.JPEGImageReader; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.*; +import java.io.*; +import javax.imageio.*; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.*; +import static java.lang.Math.*; + +/** + * Reads a JPEG image with colors in the CMYK color space. + * + * @author Werner Randelshofer + * @version $Id: CMYKJPEGImageReader.java 308 2013-01-06 11:24:06Z werner $ + */ +public class CMYKJPEGImageReader extends ImageReader { + + private boolean isIgnoreICCProfile = false; + private boolean isYCCKInversed = true; + private static DirectColorModel RGB = new DirectColorModel(24, 0xff0000, 0xff00, 0xff, 0x0); + /** + * When we read the header, we read the whole image. + */ + private BufferedImage image; + + public CMYKJPEGImageReader(ImageReaderSpi originatingProvider) { + super(originatingProvider); + } + + @Override + public int getNumImages(boolean allowSearch) throws IOException { + return 1; + } + + @Override + public int getWidth(int imageIndex) throws IOException { + readHeader(); + return image.getWidth(); + } + + @Override + public int getHeight(int imageIndex) throws IOException { + readHeader(); + return image.getHeight(); + } + + @Override + public Iterator getImageTypes(int imageIndex) throws IOException { + readHeader(); + LinkedList l = new LinkedList(); + l.add(new ImageTypeSpecifier(RGB, RGB.createCompatibleSampleModel(image.getWidth(), image.getHeight()))); + return l.iterator(); + } + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + return null; + } + + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + return null; + } + + @Override + public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { + if (imageIndex > 0) { + throw new IndexOutOfBoundsException(); + } + readHeader(); + return image; + } + + /** + * Reads the PGM header. Does nothing if the header has already been loaded. + */ + private void readHeader() throws IOException { + if (image == null) { + + ImageInputStream iis = null; + Object in = getInput(); + /* No need for JMF support in CMYKJPEGImageReader. + if (in instanceof Buffer) { + in = ((Buffer) in).getData(); + }*/ + + if (in instanceof byte[]) { + iis = new ByteArrayImageInputStream((byte[]) in); + } else if (in instanceof ImageInputStream) { + iis = (ImageInputStream) in; + } else if (in instanceof InputStream) { + iis = new MemoryCacheImageInputStream((InputStream) in); + } else { + throw new IOException("Can't handle input of type " + in); + } + image = read(iis, isYCCKInversed, isIgnoreICCProfile); + } + } + + /** + * @return the YCCKInversed property. + */ + public boolean isYCCKInversed() { + return isYCCKInversed; + } + + /** + * @param newValue the new value + */ + public void setYCCKInversed(boolean newValue) { + this.isYCCKInversed = newValue; + } + + public boolean isIgnoreICCProfile() { + return isIgnoreICCProfile; + } + + public void setIgnoreICCProfile(boolean newValue) { + this.isIgnoreICCProfile = newValue; + } + + public static BufferedImage read(ImageInputStream in, boolean inverseYCCKColors, boolean isIgnoreColorProfile) throws IOException { + // Seek to start of input stream + in.seek(0); + + // Extract metadata from the JFIF stream. + // -------------------------------------- + // In particular, we are interested into the following fields: + int samplePrecision = 0; + int numberOfLines = 0; + int numberOfSamplesPerLine = 0; + int numberOfComponentsInFrame = 0; + int app14AdobeColorTransform = 0; + ByteArrayOutputStream app2ICCProfile = new ByteArrayOutputStream(); + // Browse for marker segments, and extract data from those + // which are of interest. + JFIFInputStream fifi = new JFIFInputStream(new ImageInputStreamAdapter(in)); + for (JFIFInputStream.Segment seg = fifi.getNextSegment(); seg != null; seg = fifi.getNextSegment()) { + if (0xffc0 <= seg.marker && seg.marker <= 0xffc3 + || 0xffc5 <= seg.marker && seg.marker <= 0xffc7 + || 0xffc9 <= seg.marker && seg.marker <= 0xffcb + || 0xffcd <= seg.marker && seg.marker <= 0xffcf) { + // SOF0 - SOF15: Start of Frame Header marker segment + DataInputStream dis = new DataInputStream(fifi); + samplePrecision = dis.readUnsignedByte(); + numberOfLines = dis.readUnsignedShort(); + numberOfSamplesPerLine = dis.readUnsignedShort(); + numberOfComponentsInFrame = dis.readUnsignedByte(); + // ...the rest of SOF header is not important to us. + // In fact, by encounterint a SOF header, we have reached + // the end of the metadata section we are interested in. + // Thus we can abort here. + break; + + } else if (seg.marker == 0xffe2) { + // APP2: Application-specific marker segment + if (seg.length >= 26) { + DataInputStream dis = new DataInputStream(fifi); + // Check for 12-bytes containing the null-terminated string: "ICC_PROFILE". + if (dis.readLong() == 0x4943435f50524f46L && dis.readInt() == 0x494c4500) { + // Skip 2 bytes + dis.skipBytes(2); + + // Read Adobe ICC_PROFILE int buffer. The profile is split up over + // multiple APP2 marker segments. + byte[] b = new byte[512]; + for (int count = dis.read(b); count != -1; count = dis.read(b)) { + app2ICCProfile.write(b, 0, count); + } + } + } + } else if (seg.marker == 0xffee) { + // APP14: Application-specific marker segment + if (seg.length == 12) { + DataInputStream dis = new DataInputStream(fifi); + // Check for 6-bytes containing the null-terminated string: "Adobe". + if (dis.readInt() == 0x41646f62L && dis.readUnsignedShort() == 0x6500) { + int version = dis.readUnsignedByte(); + int app14Flags0 = dis.readUnsignedShort(); + int app14Flags1 = dis.readUnsignedShort(); + app14AdobeColorTransform = dis.readUnsignedByte(); + } + } + } + } + //fifi.close(); + + // Read the image data + BufferedImage img = null; + if (numberOfComponentsInFrame != 4) { + // Read image with YCC color encoding. + in.seek(0); +// img = readImageFromYCCorGray(in); + img = readRGBImageFromYCC(new ImageInputStreamAdapter(in), null); + } else if (numberOfComponentsInFrame == 4) { + + // Try to instantiate an ICC_Profile from the app2ICCProfile + ICC_Profile profile = null; + if (!isIgnoreColorProfile && app2ICCProfile.size() > 0) { + try { + profile = ICC_Profile.getInstance(new ByteArrayInputStream(app2ICCProfile.toByteArray())); + } catch (Throwable ex) { + // icc profile is corrupt + ex.printStackTrace(); + } + } + + switch (app14AdobeColorTransform) { + case 0: + default: + // Read image with RGBA color encoding. + in.seek(0); + img = readRGBAImageFromRGBA(new ImageInputStreamAdapter(in), profile); + break; + case 1: + throw new IOException("YCbCr not supported"); + case 2: + // Read image with inverted YCCK color encoding. + // FIXME - How do we determine from the JFIF file whether + // YCCK colors are inverted? + + // We must have a color profile in order to perform a + // conersion from CMYK to RGB. + // I case none has been supplied, we create a default one here. + if (profile == null) { + profile = ICC_Profile.getInstance(CMYKJPEGImageReader.class.getResourceAsStream("Generic CMYK Profile.icc")); + } + in.seek(0); + if (inverseYCCKColors) { + img = readRGBImageFromInvertedYCCK(new ImageInputStreamAdapter(in), profile); + } else { + img = readRGBImageFromYCCK(new ImageInputStreamAdapter(in), profile); + } + break; + } + } + + return img; + } + + private static ImageReader createNativeJPEGReader() { + return new JPEGImageReader(new CMYKJPEGImageReaderSpi()); + /* + for (Iterator i = + ImageIO.getImageReadersByFormatName("jpeg"); i.hasNext();) { + ImageReader r = i.next(); + if (!(r instanceof CMYKJPEGImageReader) + && !r.getClass().getName().contains("CMYKJPEGImageReader")) { + return r; + } + } + + return null; + * + */ + } + + /** + * Reads a CMYK JPEG image from the provided InputStream, converting the + * colors to RGB using the provided CMYK ICC_Profile. The image data must be + * in the CMYK color space. + *

+ * Use this method, if you have already determined that the input stream + * contains a CMYK JPEG image. + * + * @param in An InputStream, preferably an ImageInputStream, in the JPEG + * File Interchange Format (JFIF). + * @param cmykProfile An ICC_Profile for conversion from the CMYK color + * space to the RGB color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage containing the decoded image converted into the + * RGB color space. + * @throws java.io.IOException + */ + public static BufferedImage readRGBImageFromCMYK(InputStream in, ICC_Profile cmykProfile) throws IOException { + ImageInputStream inputStream = null; + ImageReader reader = createNativeJPEGReader(); + inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in); + reader.setInput(inputStream); + Raster raster = reader.readRaster(0, null); + BufferedImage image = createRGBImageFromCMYK(raster, cmykProfile); + return image; + } + + /** + * Reads a RGBA JPEG image from the provided InputStream, converting the + * colors to RGBA using the provided RGBA ICC_Profile. The image data must + * be in the RGBA color space. + *

+ * Use this method, if you have already determined that the input stream + * contains a RGBA JPEG image. + * + * @param in An InputStream, preferably an ImageInputStream, in the JPEG + * File Interchange Format (JFIF). + * @param rgbaProfile An ICC_Profile for conversion from the RGBA color + * space to the RGBA color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage containing the decoded image converted into the + * RGB color space. + * @throws java.io.IOException + */ + public static BufferedImage readRGBAImageFromRGBA(InputStream in, ICC_Profile rgbaProfile) throws IOException { + ImageInputStream inputStream = null; + ImageReader reader = createNativeJPEGReader(); + inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in); + reader.setInput(inputStream); + Raster raster = reader.readRaster(0, null); + BufferedImage image = createRGBAImageFromRGBA(raster, rgbaProfile); + return image; + } + + public static BufferedImage readRGBImageFromRGB(InputStream in, ICC_Profile rgbaProfile) throws IOException { + ImageInputStream inputStream = null; + ImageReader reader = createNativeJPEGReader(); + inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in); + reader.setInput(inputStream); + Raster raster = reader.readRaster(0, null); + BufferedImage image = createRGBImageFromRGB(raster, rgbaProfile); + return image; + } + + public static BufferedImage readRGBImageFromYCC(InputStream in, ICC_Profile rgbaProfile) throws IOException { + ImageInputStream inputStream = null; + ImageReader reader = createNativeJPEGReader(); + inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in); + reader.setInput(inputStream); + Raster raster = reader.readRaster(0, null); + BufferedImage image = createRGBImageFromYCC(raster, rgbaProfile); + return image; + } + + /** + * Reads a YCCK JPEG image from the provided InputStream, converting the + * colors to RGB using the provided CMYK ICC_Profile. The image data must be + * in the YCCK color space. + *

+ * Use this method, if you have already determined that the input stream + * contains a YCCK JPEG image. + * + * @param in An InputStream, preferably an ImageInputStream, in the JPEG + * File Interchange Format (JFIF). + * @param cmykProfile An ICC_Profile for conversion from the CMYK color + * space to the RGB color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage containing the decoded image converted into the + * RGB color space. + * @throws java.io.IOException + */ + public static BufferedImage readRGBImageFromYCCK(InputStream in, ICC_Profile cmykProfile) throws IOException { + ImageInputStream inputStream = null; + ImageReader reader = createNativeJPEGReader(); + inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in); + reader.setInput(inputStream); + Raster raster = reader.readRaster(0, null); + BufferedImage image = createRGBImageFromYCCK(raster, cmykProfile); + return image; + } + + /** + * Reads an inverted-YCCK JPEG image from the provided InputStream, + * converting the colors to RGB using the provided CMYK ICC_Profile. The + * image data must be in the inverted-YCCK color space. + *

+ * Use this method, if you have already determined that the input stream + * contains an inverted-YCCK JPEG image. + * + * @param in An InputStream, preferably an ImageInputStream, in the JPEG + * File Interchange Format (JFIF). + * @param cmykProfile An ICC_Profile for conversion from the CMYK color + * space to the RGB color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage containing the decoded image converted into the + * RGB color space. + * @throws java.io.IOException + */ + public static BufferedImage readRGBImageFromInvertedYCCK(InputStream in, ICC_Profile cmykProfile) throws IOException { + ImageInputStream inputStream = null; + ImageReader reader = createNativeJPEGReader(); + inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in); + reader.setInput(inputStream); + Raster raster = reader.readRaster(0, null); + raster = convertInvertedYCCKToCMYK(raster); + BufferedImage image = createRGBImageFromCMYK(raster, cmykProfile); + return image; + } + + /** + * Creates a buffered image from a raster in the YCCK color space, + * converting the colors to RGB using the provided CMYK ICC_Profile. + * + * @param ycckRaster A raster with (at least) 4 bands of samples. + * @param cmykProfile An ICC_Profile for conversion from the CMYK color + * space to the RGB color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage in the RGB color space. + * @throws NullPointerException. + */ + public static BufferedImage createRGBImageFromYCCK(Raster ycckRaster, ICC_Profile cmykProfile) { + BufferedImage image; + if (cmykProfile != null) { + ycckRaster = convertYCCKtoCMYK(ycckRaster); + image = createRGBImageFromCMYK(ycckRaster, cmykProfile); + } else { + int w = ycckRaster.getWidth(), h = ycckRaster.getHeight(); + int[] rgb = new int[w * h]; + int[] Y = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] Cb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] Cr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null); + int[] K = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null); + + float vr, vg, vb; + for (int i = 0, imax = Y.length; i < imax; i++) { + // FIXME - Use integer arithmetic to improve performance + float k = K[i], y = Y[i], cb = Cb[i], cr = Cr[i]; + vr = y + 1.402f * (cr - 128) - k; + vg = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128) - k; + vb = y + 1.772f * (cb - 128) - k; + rgb[i] = (0xff & (vr < 0.0f ? 0 : vr > 255.0f ? 0xff : (int) (vr + 0.5f))) << 16 + | (0xff & (vg < 0.0f ? 0 : vg > 255.0f ? 0xff : (int) (vg + 0.5f))) << 8 + | (0xff & (vb < 0.0f ? 0 : vb > 255.0f ? 0xff : (int) (vb + 0.5f))); + } + + Raster rgbRaster = Raster.createPackedRaster( + new DataBufferInt(rgb, rgb.length), + w, h, w, new int[]{0xff0000, 0xff00, 0xff}, null); + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ColorModel cm = RGB;//new DirectColorModel(cs, 24, 0xff0000, 0xff00, 0xff, 0x0, false, DataBuffer.TYPE_INT); + + image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null); + } + return image; + } + + /** + * Creates a buffered image from a raster in the inverted YCCK color space, + * converting the colors to RGB using the provided CMYK ICC_Profile. + * + * @param ycckRaster A raster with (at least) 4 bands of samples. + * @param cmykProfile An ICC_Profile for conversion from the CMYK color + * space to the RGB color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage in the RGB color space. + */ + public static BufferedImage createRGBImageFromInvertedYCCK(Raster ycckRaster, ICC_Profile cmykProfile) { + BufferedImage image; + if (cmykProfile != null) { + ycckRaster = convertInvertedYCCKToCMYK(ycckRaster); + image = createRGBImageFromCMYK(ycckRaster, cmykProfile); + } else { + int w = ycckRaster.getWidth(), h = ycckRaster.getHeight(); + int[] rgb = new int[w * h]; + + PixelInterleavedSampleModel pix; + // if (Adobe_APP14 and transform==2) then YCCK else CMYK + int[] Y = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] Cb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] Cr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null); + int[] K = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null); + float vr, vg, vb; + for (int i = 0, imax = Y.length; i < imax; i++) { + // FIXME - Use integer arithmetic to improve performance + float k = 255 - K[i], y = 255 - Y[i], cb = 255 - Cb[i], cr = 255 - Cr[i]; + vr = y + 1.402f * (cr - 128) - k; + vg = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128) - k; + vb = y + 1.772f * (cb - 128) - k; + rgb[i] = (0xff & (vr < 0.0f ? 0 : vr > 255.0f ? 0xff : (int) (vr + 0.5f))) << 16 + | (0xff & (vg < 0.0f ? 0 : vg > 255.0f ? 0xff : (int) (vg + 0.5f))) << 8 + | (0xff & (vb < 0.0f ? 0 : vb > 255.0f ? 0xff : (int) (vb + 0.5f))); + } + + Raster rgbRaster = Raster.createPackedRaster( + new DataBufferInt(rgb, rgb.length), + w, h, w, new int[]{0xff0000, 0xff00, 0xff}, null); + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ColorModel cm = RGB;//new DirectColorModel(cs, 24, 0xff0000, 0xff00, 0xff, 0x0, false, DataBuffer.TYPE_INT); + + image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null); + } + return image; + } + + /** + * Creates a buffered image from a raster in the CMYK color space, + * converting the colors to RGB using the provided CMYK ICC_Profile. + * + * As seen from a comment made by 'phelps' at + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4799903 + * + * @param cmykRaster A raster with (at least) 4 bands of samples. + * @param cmykProfile An ICC_Profile for conversion from the CMYK color + * space to the RGB color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage in the RGB color space. + */ + public static BufferedImage createRGBImageFromCMYK(Raster cmykRaster, ICC_Profile cmykProfile) { + BufferedImage image; + int w = cmykRaster.getWidth(); + int h = cmykRaster.getHeight(); + + if (cmykProfile != null) { + ColorSpace cmykCS = new ICC_ColorSpace(cmykProfile); + image = new BufferedImage(w, h, + BufferedImage.TYPE_INT_RGB); + WritableRaster rgbRaster = image.getRaster(); + ColorSpace rgbCS = image.getColorModel().getColorSpace(); + ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null); + cmykToRgb.filter(cmykRaster, rgbRaster); + } else { + + int[] rgb = new int[w * h]; + + int[] C = cmykRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] M = cmykRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] Y = cmykRaster.getSamples(0, 0, w, h, 2, (int[]) null); + int[] K = cmykRaster.getSamples(0, 0, w, h, 3, (int[]) null); + + for (int i = 0, imax = C.length; i < imax; i++) { + int k = K[i]; + rgb[i] = (255 - min(255, C[i] + k)) << 16 + | (255 - min(255, M[i] + k)) << 8 + | (255 - min(255, Y[i] + k)); + } + + Raster rgbRaster = Raster.createPackedRaster( + new DataBufferInt(rgb, rgb.length), + w, h, w, new int[]{0xff0000, 0xff00, 0xff}, null); + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ColorModel cm = RGB;//new DirectColorModel(cs, 24, 0xff0000, 0xff00, 0xff, 0x0, false, DataBuffer.TYPE_INT); + image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null); + } + return image; + } + + /** + * Creates a buffered image from a raster in the RGBA color space, + * converting the colors to RGB using the provided CMYK ICC_Profile. + * + * As seen from a comment made by 'phelps' at + * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4799903 + * + * @param rgbaRaster A raster with (at least) 4 bands of samples. + * @param rgbaProfile An ICC_Profile for conversion from the CMYK color + * space to the RGB color space. If this parameter is null, a default + * profile is used. + * @return a BufferedImage in the RGB color space. + */ + public static BufferedImage createRGBAImageFromRGBA(Raster rgbaRaster, ICC_Profile rgbaProfile) { + BufferedImage image; + int w = rgbaRaster.getWidth(); + int h = rgbaRaster.getHeight(); + + if (rgbaProfile != null) { + ColorSpace rgbaCS = new ICC_ColorSpace(rgbaProfile); + image = new BufferedImage(w, h, + BufferedImage.TYPE_INT_RGB); + WritableRaster rgbRaster = image.getRaster(); + ColorSpace rgbCS = image.getColorModel().getColorSpace(); + ColorConvertOp cmykToRgb = new ColorConvertOp(rgbaCS, rgbCS, null); + cmykToRgb.filter(rgbaRaster, rgbRaster); + } else { + + int[] rgb = new int[w * h]; + + int[] R = rgbaRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] G = rgbaRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] B = rgbaRaster.getSamples(0, 0, w, h, 2, (int[]) null); + int[] A = rgbaRaster.getSamples(0, 0, w, h, 3, (int[]) null); + + for (int i = 0, imax = R.length; i < imax; i++) { + rgb[i] = A[i] << 24 | R[i] << 16 | G[i] << 8 | B[i]; + } + + Raster rgbRaster = Raster.createPackedRaster( + new DataBufferInt(rgb, rgb.length), + w, h, w, new int[]{0xff0000, 0xff00, 0xff, 0xff000000}, null); + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ColorModel cm = ColorModel.getRGBdefault();//new DirectColorModel(cs, 32, 0xff0000, 0xff00, 0xff, 0x0ff000000, false, DataBuffer.TYPE_INT); + image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null); + } + return image; + } + + public static BufferedImage createRGBImageFromRGB(Raster rgbaRaster, ICC_Profile rgbaProfile) { + BufferedImage image; + int w = rgbaRaster.getWidth(); + int h = rgbaRaster.getHeight(); + + // ICC_Profile currently not supported + rgbaProfile = null; + if (rgbaProfile != null) { + ColorSpace rgbaCS = new ICC_ColorSpace(rgbaProfile); + image = new BufferedImage(w, h, + BufferedImage.TYPE_INT_RGB); + WritableRaster rgbRaster = image.getRaster(); + ColorSpace rgbCS = image.getColorModel().getColorSpace(); + ColorConvertOp cmykToRgb = new ColorConvertOp(rgbaCS, rgbCS, null); + cmykToRgb.filter(rgbaRaster, rgbRaster); + } else { + + int[] rgb = new int[w * h]; + + int[] R = rgbaRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] G = rgbaRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] B = rgbaRaster.getSamples(0, 0, w, h, 2, (int[]) null); + //int[] A = rgbaRaster.getSamples(0, 0, w, h, 3, (int[]) null); + + for (int i = 0, imax = R.length; i < imax; i++) { + rgb[i] = 0xff << 24 | R[i] << 16 | G[i] << 8 | B[i]; + } + + Raster rgbRaster = Raster.createPackedRaster( + new DataBufferInt(rgb, rgb.length), + w, h, w, new int[]{0xff0000, 0xff00, 0xff, 0xff000000}, null); + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ColorModel cm = ColorModel.getRGBdefault();//new DirectColorModel(cs, 32, 0xff0000, 0xff00, 0xff, 0x0ff000000, false, DataBuffer.TYPE_INT); + image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null); + } + return image; + } + + public static BufferedImage createRGBImageFromYCC(Raster rgbaRaster, ICC_Profile rgbaProfile) { + BufferedImage image; + int w = rgbaRaster.getWidth(); + int h = rgbaRaster.getHeight(); + + // ICC_Profile currently not supported + rgbaProfile = null; + if (rgbaProfile != null) { + ColorSpace rgbaCS = new ICC_ColorSpace(rgbaProfile); + image = new BufferedImage(w, h, + BufferedImage.TYPE_INT_RGB); + WritableRaster rgbRaster = image.getRaster(); + ColorSpace rgbCS = image.getColorModel().getColorSpace(); + ColorConvertOp cmykToRgb = new ColorConvertOp(rgbaCS, rgbCS, null); + cmykToRgb.filter(rgbaRaster, rgbRaster); + } else { + + int[] rgb = new int[w * h]; + + int[] Y = rgbaRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] Cb = rgbaRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] Cr = rgbaRaster.getSamples(0, 0, w, h, 2, (int[]) null); + //int[] A = rgbaRaster.getSamples(0, 0, w, h, 3, (int[]) null); + + for (int i = 0, imax = Y.length; i < imax; i++) { + int Yi, Cbi, Cri; + int R, G, B; + + //RGB can be computed directly from YCbCr (256 levels) as follows: + //R = Y + 1.402 (Cr-128) + //G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128) + //B = Y + 1.772 (Cb-128) + Yi = Y[i]; + Cbi = Cb[i]; + Cri = Cr[i]; + R = (1000 * Yi + 1402 * (Cri - 128)) / 1000; + G = (100000 * Yi - 34414 * (Cbi - 128) - 71414 * (Cri - 128)) / 100000; + B = (1000 * Yi + 1772 * (Cbi - 128)) / 1000; + + R = min(255, max(0, R)); + G = min(255, max(0, G)); + B = min(255, max(0, B)); + + rgb[i] = 0xff << 24 | R << 16 | G << 8 | B; + } + + Raster rgbRaster = Raster.createPackedRaster( + new DataBufferInt(rgb, rgb.length), + w, h, w, new int[]{0xff0000, 0xff00, 0xff, 0xff000000}, null); + ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + ColorModel cm = ColorModel.getRGBdefault();//new DirectColorModel(cs, 32, 0xff0000, 0xff00, 0xff, 0x0ff000000, false, DataBuffer.TYPE_INT); + image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null); + } + return image; + } + /** + * Define tables for YCC->RGB color space conversion. + */ + private final static int SCALEBITS = 16; + private final static int MAXJSAMPLE = 255; + private final static int CENTERJSAMPLE = 128; + private final static int ONE_HALF = 1 << (SCALEBITS - 1); + private final static int[] Cr_r_tab = new int[MAXJSAMPLE + 1]; + private final static int[] Cb_b_tab = new int[MAXJSAMPLE + 1]; + private final static int[] Cr_g_tab = new int[MAXJSAMPLE + 1]; + private final static int[] Cb_g_tab = new int[MAXJSAMPLE + 1]; + + /* + * Initialize tables for YCC->RGB colorspace conversion. + */ + private static synchronized void buildYCCtoRGBtable() { + if (Cr_r_tab[0] == 0) { + for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) { + /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */ + /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */ + /* Cr=>R value is nearest int to 1.40200 * x */ + Cr_r_tab[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS; + /* Cb=>B value is nearest int to 1.77200 * x */ + Cb_b_tab[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS; + /* Cr=>G value is scaled-up -0.71414 * x */ + Cr_g_tab[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x; + /* Cb=>G value is scaled-up -0.34414 * x */ + /* We also add in ONE_HALF so that need not do it in inner loop */ + Cb_g_tab[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF; + } + } + } + + /* + * Adobe-style YCCK->CMYK conversion. + * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same + * conversion as above, while passing K (black) unchanged. + * We assume build_ycc_rgb_table has been called. + */ + private static Raster convertInvertedYCCKToCMYK(Raster ycckRaster) { + buildYCCtoRGBtable(); + + int w = ycckRaster.getWidth(), h = ycckRaster.getHeight(); + int[] ycckY = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] ycckCb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] ycckCr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null); + int[] ycckK = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null); + int[] cmyk = new int[ycckY.length]; + + for (int i = 0; i < ycckY.length; i++) { + int y = 255 - ycckY[i]; + int cb = 255 - ycckCb[i]; + int cr = 255 - ycckCr[i]; + int cmykC, cmykM, cmykY; + // Range-limiting is essential due to noise introduced by DCT losses. + cmykC = MAXJSAMPLE - (y + Cr_r_tab[cr]); // red + cmykM = MAXJSAMPLE - (y + // green + (Cb_g_tab[cb] + Cr_g_tab[cr] + >> SCALEBITS)); + cmykY = MAXJSAMPLE - (y + Cb_b_tab[cb]); // blue + /* K passes through unchanged */ + cmyk[i] = (cmykC < 0 ? 0 : (cmykC > 255) ? 255 : cmykC) << 24 + | (cmykM < 0 ? 0 : (cmykM > 255) ? 255 : cmykM) << 16 + | (cmykY < 0 ? 0 : (cmykY > 255) ? 255 : cmykY) << 8 + | 255 - ycckK[i]; + } + + Raster cmykRaster = Raster.createPackedRaster( + new DataBufferInt(cmyk, cmyk.length), + w, h, w, new int[]{0xff000000, 0xff0000, 0xff00, 0xff}, null); + return cmykRaster; + + } + + private static Raster convertYCCKtoCMYK(Raster ycckRaster) { + buildYCCtoRGBtable(); + + int w = ycckRaster.getWidth(), h = ycckRaster.getHeight(); + int[] ycckY = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null); + int[] ycckCb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null); + int[] ycckCr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null); + int[] ycckK = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null); + + int[] cmyk = new int[ycckY.length]; + + for (int i = 0; i < ycckY.length; i++) { + int y = ycckY[i]; + int cb = ycckCb[i]; + int cr = ycckCr[i]; + int cmykC, cmykM, cmykY; + // Range-limiting is essential due to noise introduced by DCT losses. + cmykC = MAXJSAMPLE - (y + Cr_r_tab[cr]); // red + cmykM = MAXJSAMPLE - (y + // green + (Cb_g_tab[cb] + Cr_g_tab[cr] + >> SCALEBITS)); + cmykY = MAXJSAMPLE - (y + Cb_b_tab[cb]); // blue + /* K passes through unchanged */ + cmyk[i] = (cmykC < 0 ? 0 : (cmykC > 255) ? 255 : cmykC) << 24 + | (cmykM < 0 ? 0 : (cmykM > 255) ? 255 : cmykM) << 16 + | (cmykY < 0 ? 0 : (cmykY > 255) ? 255 : cmykY) << 8 + | ycckK[i]; + } + + return Raster.createPackedRaster( + new DataBufferInt(cmyk, cmyk.length), + w, h, w, new int[]{0xff000000, 0xff0000, 0xff00, 0xff}, null); + } + + /** + * Reads a JPEG image from the provided InputStream. The image data must be + * in the YUV or the Gray color space. + *

+ * Use this method, if you have already determined that the input stream + * contains a YCC or Gray JPEG image. + * + * @param in An InputStream, preferably an ImageInputStream, in the JPEG + * File Interchange Format (JFIF). + * @return a BufferedImage containing the decoded image converted into the + * RGB color space. + * @throws java.io.IOException + */ + public static BufferedImage readImageFromYCCorGray(ImageInputStream in) throws IOException { + ImageReader r = createNativeJPEGReader(); + r.setInput(in); + BufferedImage img = r.read(0); + return img; + } +} diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReaderSpi.java b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReaderSpi.java new file mode 100644 index 000000000..e15280fdb --- /dev/null +++ b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReaderSpi.java @@ -0,0 +1,77 @@ +/* + * @(#)CMYKJPEGImageReaderSpi.java 1.2 2011-02-17 + * + * Copyright (c) 2010-2011 Werner Randelshofer, Goldau, Switzerland. + * All rights reserved. + * + * You may not use, copy or modify this file, except in compliance with the + * license agreement you entered into with Werner Randelshofer. + * For details see accompanying license terms. + */ +package org.monte.media.jpeg; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import javax.imageio.ImageReader; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; + + +/** + * A reader for JPEG images in the CMYK color space. + * + * @author Werner Randelshofer + * @version 1.2 2011-02-17 Removes support for JMF. + *
1.0 2010-07-23 Created. + */ +public class CMYKJPEGImageReaderSpi extends ImageReaderSpi { + + public CMYKJPEGImageReaderSpi() { + super("Werner Randelshofer",//vendor name + "1.0",//version + new String[]{"JPEG","JPG"},//names + new String[]{"jpg"},//suffixes, + new String[]{"image/jpg"},// MIMETypes, + "org.monte.media.jpeg.CMYKJPEGImageReader",// readerClassName, + new Class[]{ImageInputStream.class,InputStream.class,byte[].class},// inputTypes, + null,// writerSpiNames, + false,// supportsStandardStreamMetadataFormat, + null,// nativeStreamMetadataFormatName, + null,// nativeStreamMetadataFormatClassName, + null,// extraStreamMetadataFormatNames, + null,// extraStreamMetadataFormatClassNames, + false,// supportsStandardImageMetadataFormat, + null,// nativeImageMetadataFormatName, + null,// nativeImageMetadataFormatClassName, + null,// extraImageMetadataFormatNames, + null// extraImageMetadataFormatClassNames + ); + } + + @Override + public boolean canDecodeInput(Object source) throws IOException { + if (source instanceof ImageInputStream) { + ImageInputStream in = (ImageInputStream) source; + in.mark(); + // Check if file starts with a JFIF SOI magic (0xffd8=-40) + if (in.readShort() != -40) { + in.reset(); + return false; + } + in.reset(); + return true; + } + return false; + } + + @Override + public ImageReader createReaderInstance(Object extension) throws IOException { + return new CMYKJPEGImageReader(this); + } + + @Override + public String getDescription(Locale locale) { + return "CMYK JPEG Image Reader"; + } +} diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/Generic CMYK Profile.icc b/libsrc/cmykjpeg/src/org/monte/media/jpeg/Generic CMYK Profile.icc new file mode 100644 index 0000000000000000000000000000000000000000..458eb098ca749d1745692f16784e1cc26ecd401d GIT binary patch literal 53964 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

2{& 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 literal 0 HcmV?d00001 diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java b/libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java new file mode 100644 index 000000000..ebb0519c4 --- /dev/null +++ b/libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java @@ -0,0 +1,521 @@ +/* + * @(#)JFIFInputStream.java + * + * Copyright (c) 2008-2012 Werner Randelshofer, Goldau, Switzerland. + * All rights reserved. + * + * You may not use, copy or modify this file, except in compliance with the + * license agreement you entered into with Werner Randelshofer. + * For details see accompanying license terms. + */ +package org.monte.media.jpeg; + +import java.io.*; +import java.util.*; + +/** + * JFIFInputStream. + *

+ * This InputStream uses two special marker values which do not exist + * in the JFIF stream: + *

    + *
  • -1: marks junk data at the beginning of the file.
  • + *
  • 0: marks entropy encoded image data.
  • + *
+ *

+ * The junk data at the beginning of the file can be accessed by calling the + * read-methods immediately after opening the stream. Call nextSegment() + * immediately after opening the stream if you are not interested into this + * junk data. + *

+ * Junk data at the end of the file is delivered as part of the EOI_MARKER segment. + * Finish reading after encountering the EOI_MARKER segment if you are not interested + * in this junk data. + * + *

+ * References:
+ * JPEG File Interchange Format Version 1.02
+ *
http://www.jpeg.org/public/jfif.pdf + *

+ * Pennebaker, W., Mitchell, J. (1993).
+ * JPEG Still Image Data Compression Standard.
+ * Chapmann & Hall, New York.
+ * ISBN 0-442-01272-1
+ * + * + * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau + * @version $Id: JFIFInputStream.java 299 2013-01-03 07:40:18Z werner $ + */ +public class JFIFInputStream extends FilterInputStream { + + /** + * This hash set holds the Id's of markers which stand alone, + * respectively do no have a data segment. + */ + private final HashSet standaloneMarkers = new HashSet(); + /** + * This hash set holds the Id's of markers which have a data + * segment followed by a entropy-coded data segment. + */ + private final HashSet doubleSegMarkers = new HashSet(); + + /** Represents a segment within a JFIF File. + */ + public static class Segment { + + /** + * Holds the marker code. + * A marker is an unsigned short between 0xff01 and 0xfffe. + */ + public final int marker; + /** + * Holds the offset of the first data byte to the beginning + * of the stream. + */ + public final long offset; + /** + * If the marker starts a marker segment, holds the length + * of the data in the data segment. + * If the marker starts a entropy-coded data segment, holds + * the value -1. + */ + public final int length; + + public Segment(int marker, long offset, int length) { + this.marker = marker; + this.offset = offset; + this.length = length; + } + + public boolean isEntropyCoded() { + return length == -1; + } + + @Override + public String toString() { + return "Segment marker=0x" + Integer.toHexString(marker) + " offset=" + offset + "=0x" + Long.toHexString(offset); + } + } + private Segment segment; + /** + * This variable is set to true, if a 0xff byte has been found in + * entropy-code data. + */ + private boolean markerFound; + private int marker = JUNK_MARKER; + private long offset = 0; + private boolean isStuffed0xff = false; + /** JUNK_MARKER Marker (for data which is not part of the JFIF stream. */ + public final static int JUNK_MARKER = -1; + /** Start of image */ + public final static int SOI_MARKER = 0xffd8; + /** End of image */ + public final static int EOI_MARKER = 0xffd9; + /** Temporary private use in arithmetic coding */ + public final static int TEM_MARKER = 0xff01; + /** Start of scan */ + public final static int SOS_MARKER = 0xffda; + /** APP1_MARKER Reserved for application use */ + public final static int APP1_MARKER = 0xffe1; + /** APP2_MARKER Reserved for application use */ + public final static int APP2_MARKER = 0xffe2; + /** Reserved for JPEG extensions */ + public final static int JPG0_MARKER = 0xfff0; + public final static int JPG1_MARKER = 0xfff1; + public final static int JPG2_MARKER = 0xfff2; + public final static int JPG3_MARKER = 0xfff3; + public final static int JPG4_MARKER = 0xfff4; + public final static int JPG5_MARKER = 0xfff5; + public final static int JPG6_MARKER = 0xfff6; + public final static int JPG7_MARKER = 0xfff7; + public final static int JPG8_MARKER = 0xfff8; + public final static int JPG9_MARKER = 0xfff9; + public final static int JPGA_MARKER = 0xfffA; + public final static int JPGB_MARKER = 0xfffB; + public final static int JPGC_MARKER = 0xfffC; + public final static int JPGD_MARKER = 0xfffD; + /** Start of frame markers */ + public final static int SOF0_MARKER = 0xffc0;//nondifferential Huffman-coding frames with baseline DCT. + public final static int SOF1_MARKER = 0xffc1;//nondifferential Huffman-coding frames with extended sequential DCT. + public final static int SOF2_MARKER = 0xffc2;//nondifferential Huffman-coding frames with progressive DCT. + public final static int SOF3_MARKER = 0xffc3;//nondifferential Huffman-coding frames with lossless (sequential) data. + + //public final static int SOF4_MARKER = 0xffc4;// + public final static int SOF5_MARKER = 0xffc5;//differential Huffman-coding frames with differential sequential DCT. + public final static int SOF6_MARKER = 0xffc6;//differential Huffman-coding frames with differential progressive DCT. + public final static int SOF7_MARKER = 0xffc7;//differential Huffman-coding frames with differential lossless data. + + //public final static int SOF8_MARKER = 0xffc8;// + public final static int SOF9_MARKER = 0xffc9;//nondifferential Arithmetic-coding frames with extended sequential DCT. + public final static int SOFA_MARKER = 0xffcA;//nondifferential Arithmetic-coding frames with progressive DCT. + public final static int SOFB_MARKER = 0xffcB;//nondifferential Arithmetic-coding frames with lossless (sequential) data. + //public final static int SOFC_MARKER = 0xffcC;// + public final static int SOFD_MARKER = 0xffcD;//differential Arithmetic-coding frames with differential sequential DCT. + public final static int SOFE_MARKER = 0xffcE;//differential Arithmetic-coding frames with differential progressive DCT. + public final static int SOFF_MARKER = 0xffcF;//differential Arithmetic-coding frames with differential lossless DCT. + + // Restart markers + public final static int RST0_MARKER = 0xffd0; + public final static int RST1_MARKER = 0xffd1; + public final static int RST2_MARKER = 0xffd2; + public final static int RST3_MARKER = 0xffd3; + public final static int RST4_MARKER = 0xffd4; + public final static int RST5_MARKER = 0xffd5; + public final static int RST6_MARKER = 0xffd6; + public final static int RST7_MARKER = 0xffd7; + + public JFIFInputStream(File f) throws IOException { + this(new BufferedInputStream(new FileInputStream(f))); + } + + public JFIFInputStream(InputStream in) { + super(in); + + for (int i = RST0_MARKER; i <= RST7_MARKER; i++) { + standaloneMarkers.add(i); // RST(i) Restart interval termination + } + standaloneMarkers.add(SOI_MARKER); // SOI_MARKER Start of image + standaloneMarkers.add(EOI_MARKER); // EOI_MARKER End of image + standaloneMarkers.add(TEM_MARKER); // TEM_MARKER Temporary private use in arithmetic coding + standaloneMarkers.add(JPG0_MARKER); // JPEG Extensions + standaloneMarkers.add(JPG1_MARKER); + standaloneMarkers.add(JPG2_MARKER); + standaloneMarkers.add(JPG3_MARKER); + standaloneMarkers.add(JPG4_MARKER); + standaloneMarkers.add(JPG5_MARKER); + standaloneMarkers.add(JPG6_MARKER); + standaloneMarkers.add(JPG7_MARKER); + standaloneMarkers.add(JPG8_MARKER); + standaloneMarkers.add(JPG9_MARKER); + standaloneMarkers.add(JPGA_MARKER); + standaloneMarkers.add(JPGB_MARKER); + standaloneMarkers.add(JPGC_MARKER); + standaloneMarkers.add(JPGD_MARKER); + standaloneMarkers.add(0xffff); // Illegal marker + doubleSegMarkers.add(SOS_MARKER); // SOS_MARKER Start of Scan + + // Start with a dummy entropy-coded data segment. + segment = new Segment(-1, 0, -1); + } + + /** + * Gets the current segment from the input stream. + * + * @return The current segment. Returns null, if we encountered + * the end of the stream. + * @throws java.io.IOException + */ + public Segment getSegment() throws IOException { + return segment; + } + + /** + * Gets the next segment from the input stream. + * + * @return The next segment. Returns null, if we encountered + * the end of the stream. + * @throws java.io.IOException + */ + public Segment getNextSegment() throws IOException { + // If we are inside of a marker segment, skip the + // marker + if (!segment.isEntropyCoded()) { + markerFound = false; + do { + long skipped = in.skip(segment.length - offset + segment.offset); + if (skipped == -1) { + segment = new Segment(0, offset, -1); + return null; + } + offset += skipped; + } while (offset < segment.length + segment.offset); + + if (doubleSegMarkers.contains(segment.marker)) { + segment = new Segment(0, offset, -1); + return segment; + } + } + + // Scan the input stream for the next marker. + while (!markerFound) { + while (true) { + int b; + if (isStuffed0xff) { + b = 0xff; + isStuffed0xff = false; + } else { + b = read0(); + } + if (b == -1) { + return null; + } + if (b == 0xff) { + markerFound = true; + break; + } + } + int b = read0(); + if (b == -1) { + return null; + } + if (b == 0x00) { + markerFound = false; + } else if (b == 0xff) { + isStuffed0xff = true; + markerFound = false; + } else { + marker = 0xff00 | b; + } + } + markerFound = false; + /* + if (marker <= 0xff00 || marker >= 0xffff) { + throw new IOException("JFIFInputStream found illegal marker " + Integer.toHexString(marker) + " at offset " + offset + " 0x"+Long.toHexString(offset)+"."); + }*/ + + // Note: 0xffff is an illegal marker segment, we process it here + // for robustness. + if (standaloneMarkers.contains(marker)) { + segment = new Segment(0xff00 | marker, offset, -1); + } else { + int length = (read0() << 8) | read0(); + if (length < 2) { + throw new IOException("JFIFInputStream found illegal segment length " + length + " after marker " + Integer.toHexString(marker) + " at offset " + offset + "."); + } + segment = new Segment(0xff00 | marker, offset, length - 2); + } + return segment; + } + + public long getStreamPosition() { + return offset; + } + + private int read0() throws IOException { + int b = in.read(); + if (b != -1) { + offset++; + } + return b; + } + + /** + * Reads the next byte of data from this input stream. The value + * byte is returned as an int in the range + * 0 to 255. If no byte is available + * because the end of the stream has been reached, the value + * -1 is returned. This method blocks until input data + * is available, the end of the stream is detected, or an exception + * is thrown. + *

+ * This method + * simply performs in.read() and returns the result. + * + * @return the next byte of data, or -1 if the end of the + * stream is reached. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + @Override + public int read() throws IOException { + if (markerFound) { + return -1; + } + + int b; + if (isStuffed0xff) { + isStuffed0xff = false; + b = 0xff; + } else { + b = read0(); + } + + if (segment.isEntropyCoded()) { + if (b == 0xff) { + b = read0(); + if (b == 0x00) { + // found a stuffed 0xff byte + return 0xff; + } else if (b == 0xff) { + // found an invalid sequence of two 0xff bytes + isStuffed0xff = true; + return 0xff; + } + markerFound = true; + marker = 0xff00 | b; + return -1; + } + } + return b; + } + + /** + * Reads up to len b of data from this input stream + * into an array of b. This method blocks until some input is + * available. + *

+ * This method simply performs in.read(b, off, len) + * and returns the result. + * + * @param b the buffer into which the data is read. + * @param off the start offset of the data. + * @param len the maximum number of b read. + * @return the total number of b read into the buffer, or + * -1 if there is no more data because the end of + * the stream has been reached. + * @exception IOException if an I/O error occurs. + * @see java.io.FilterInputStream#in + */ + @Override + public int read(byte b[], int off, int len) throws IOException { + if (markerFound) { + return -1; + } + + int count = 0; + if (segment.isEntropyCoded()) { + for (; count < len; count++) { + int data = read(); + if (data == -1) { + if (count==0) return -1; + break; + } + + b[off + count] = (byte) data; + } + } else { + long available = segment.length - offset + segment.offset; + if (available <= 0) { + return -1; + } + if (available < len) { + len = (int) available; + } + count = in.read(b, off, len); + if (count != -1) { + offset += count; + } + } + return count; + } + + /** Fully skips the specified number of bytes. */ + public final void skipFully(long n) throws IOException { + long total = 0; + long cur = 0; + + while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) { + total += cur; + } + offset+=total; + if (total < n) { + throw new EOFException(); + } + } + + /** + * Skips over and discards n b of data from the + * input stream. The skip method may, for a variety of + * reasons, end up skipping over some smaller number of b, + * possibly 0. The actual number of b skipped is + * returned. + *

+ * This method + * simply performs in.skip(n). + * + * @param n the number of b to be skipped. + * @return the actual number of b skipped. + * @exception IOException if an I/O error occurs. + */ + @Override + public long skip(long n) throws IOException { + if (markerFound) { + return -1; + } + + long count = 0; + if (segment.isEntropyCoded()) { + for (; count < n; count++) { + int data = read(); + if (data == -1) { + break; + } + } + } else { + long available = segment.length - offset + segment.offset; + if (available < n) { + n = (int) available; + } + count = in.skip(n); + if (count != -1) { + offset += count; + } + } + return count; + } + + /** + * Marks the current position in this input stream. A subsequent + * call to the reset method repositions this stream at + * the last marked position so that subsequent reads re-read the same b. + *

+ * The readlimit argument tells this input stream to + * allow that many b to be read before the mark position gets + * invalidated. + *

+ * This method simply performs in.mark(readlimit). + * + * @param readlimit the maximum limit of b that can be read before + * the mark position becomes invalid. + * @see java.io.FilterInputStream#in + * @see java.io.FilterInputStream#reset() + */ + @Override + public synchronized void mark(int readlimit) { + // do nothing, since we don't support marking + } + + /** + * Repositions this stream to the position at the time the + * mark method was last called on this input stream. + *

+ * This method + * simply performs in.reset(). + *

+ * Stream marks are intended to be used in + * situations where you need to read ahead a little to see what's in + * the stream. Often this is most easily done by invoking some + * general parser. If the stream is of the type handled by the + * parse, it just chugs along happily. If the stream is not of + * that type, the parser should toss an exception when it fails. + * If this happens within readlimit b, it allows the outer + * code to reset the stream and try another parser. + * + * @exception IOException if the stream has not been marked or if the + * mark has been invalidated. + * @see java.io.FilterInputStream#in + * @see java.io.FilterInputStream#mark(int) + */ + @Override + public synchronized void reset() throws IOException { + throw new IOException("Reset not supported"); + } + + /** + * Tests if this input stream supports the mark + * and reset methods. + * This method + * simply performs in.markSupported(). + * + * @return true if this stream type supports the + * mark and reset method; + * false otherwise. + * @see java.io.FilterInputStream#in + * @see java.io.InputStream#mark(int) + * @see java.io.InputStream#reset() + */ + @Override + public boolean markSupported() { + return false; + } +} diff --git a/libsrc/ffdec_lib/nbproject/project.xml b/libsrc/ffdec_lib/nbproject/project.xml index 634fd0434..029f090fb 100644 --- a/libsrc/ffdec_lib/nbproject/project.xml +++ b/libsrc/ffdec_lib/nbproject/project.xml @@ -236,7 +236,7 @@ auxiliary.show.customizer.message= src - ../../lib/LZMA.jar;../../lib/avi.jar;../../lib/gif.jar;../../lib/gnujpdf.jar;../../lib/jl1.0.1.jar;../../lib/jpacker.jar;../../lib/nellymoser.jar;../../lib/sfntly.jar;../../lib/ttf.jar;../../src + ../../lib/LZMA.jar;../../lib/avi.jar;../../lib/gif.jar;../../lib/gnujpdf.jar;../../lib/jl1.0.1.jar;../../lib/jpacker.jar;../../lib/nellymoser.jar;../../lib/sfntly.jar;../../lib/ttf.jar;../../lib/cmykjpeg.jar;../../src build reports dist diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageExporter.java index 45ef6916a..96453d508 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ImageExporter.java @@ -1,108 +1,111 @@ -/* - * Copyright (C) 2010-2015 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.exporters; - -import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; -import com.jpexs.decompiler.flash.EventListener; -import com.jpexs.decompiler.flash.RetryTask; -import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; -import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings; -import com.jpexs.decompiler.flash.helpers.BMPFile; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.Path; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -public class ImageExporter { - - public List exportImages(AbortRetryIgnoreHandler handler, String outdir, List tags, ImageExportSettings settings, EventListener evl) throws IOException { - List ret = new ArrayList<>(); - if (tags.isEmpty()) { - return ret; - } - - File foutdir = new File(outdir); - Path.createDirectorySafe(foutdir); - - int count = 0; - for (Tag t : tags) { - if (t instanceof ImageTag) { - count++; - } - } - - if (count == 0) { - return ret; - } - - int currentIndex = 1; - for (Tag t : tags) { - if (t instanceof ImageTag) { - if (evl != null) { - evl.handleExportingEvent("image", currentIndex, count, t.getName()); - } - - final ImageTag imageTag = (ImageTag) t; - - ImageFormat fileFormat = imageTag.getImageFormat(); - if (settings.mode == ImageExportMode.PNG) { - fileFormat = ImageFormat.PNG; - } - - if (settings.mode == ImageExportMode.JPEG) { - fileFormat = ImageFormat.JPEG; - } - - if (settings.mode == ImageExportMode.BMP) { - fileFormat = ImageFormat.BMP; - } - - { - final File file = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + "." + ImageHelper.getImageFormatString(fileFormat))); - final ImageFormat ffileFormat = fileFormat; - - new RetryTask(() -> { - if (ffileFormat == ImageFormat.BMP) { - BMPFile.saveBitmap(imageTag.getImage().getBufferedImage(), file); - } else { - ImageHelper.write(imageTag.getImage().getBufferedImage(), ffileFormat, file); - } - }, handler).run(); - ret.add(file); - } - - if (evl != null) { - evl.handleExportedEvent("image", currentIndex, count, t.getName()); - } - - currentIndex++; - } - } - - return ret; - } -} +/* + * Copyright (C) 2010-2015 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.exporters; + +import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler; +import com.jpexs.decompiler.flash.EventListener; +import com.jpexs.decompiler.flash.RetryTask; +import com.jpexs.decompiler.flash.exporters.modes.ImageExportMode; +import com.jpexs.decompiler.flash.exporters.settings.ImageExportSettings; +import com.jpexs.decompiler.flash.helpers.BMPFile; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.Path; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class ImageExporter { + + public List exportImages(AbortRetryIgnoreHandler handler, String outdir, List tags, ImageExportSettings settings, EventListener evl) throws IOException { + List ret = new ArrayList<>(); + if (tags.isEmpty()) { + return ret; + } + + File foutdir = new File(outdir); + Path.createDirectorySafe(foutdir); + + int count = 0; + for (Tag t : tags) { + if (t instanceof ImageTag) { + count++; + } + } + + if (count == 0) { + return ret; + } + + int currentIndex = 1; + for (Tag t : tags) { + if (t instanceof ImageTag) { + if (evl != null) { + evl.handleExportingEvent("image", currentIndex, count, t.getName()); + } + + final ImageTag imageTag = (ImageTag) t; + + ImageFormat fileFormat = imageTag.getImageFormat(); + ImageFormat originalFormat = fileFormat; + if (settings.mode == ImageExportMode.PNG) { + fileFormat = ImageFormat.PNG; + } + + if (settings.mode == ImageExportMode.JPEG) { + fileFormat = ImageFormat.JPEG; + } + + if (settings.mode == ImageExportMode.BMP) { + fileFormat = ImageFormat.BMP; + } + + { + final File file = new File(outdir + File.separator + Helper.makeFileName(imageTag.getCharacterExportFileName() + "." + ImageHelper.getImageFormatString(fileFormat))); + final ImageFormat ffileFormat = fileFormat; + + new RetryTask(() -> { + if (ffileFormat == originalFormat) { + + } else if (ffileFormat == ImageFormat.BMP) { + BMPFile.saveBitmap(imageTag.getImage().getBufferedImage(), file); + } else { + ImageHelper.write(imageTag.getImage().getBufferedImage(), ffileFormat, file); + } + }, handler).run(); + ret.add(file); + } + + if (evl != null) { + evl.handleExportedEvent("image", currentIndex, count, t.getName()); + } + + currentIndex++; + } + } + + return ret; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java index 0442c9a2b..dee0b8fce 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/helpers/ImageHelper.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.helpers; import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.helpers.Helper; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -25,10 +26,15 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Iterator; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; +import org.monte.media.jpeg.CMYKJPEGImageReader; +import org.monte.media.jpeg.CMYKJPEGImageReaderSpi; /** * @@ -41,9 +47,18 @@ public class ImageHelper { } public static BufferedImage read(InputStream input) throws IOException { - BufferedImage in = ImageIO.read(input); - if (in == null) { - return null; + BufferedImage in; + byte data[] = Helper.readStream(input); + try (ImageInputStream iis = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) { + CMYKJPEGImageReader r = new CMYKJPEGImageReader(new CMYKJPEGImageReaderSpi()); + r.setInput(iis); + in = r.read(0); + } catch (IOException ex) { + try { + return ImageIO.read(ImageIO.createImageInputStream(new ByteArrayInputStream(data))); + } catch (IOException ex1) { + in = null; + } } int type = in.getType(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java index 409ac02fc..b7ef635b5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG2Tag.java @@ -58,7 +58,7 @@ public class DefineBitsJPEG2Tag extends ImageTag implements AloneTag { public DefineBitsJPEG2Tag(SWF swf) { super(swf, ID, NAME, null); characterID = swf.getNextCharacterId(); - imageData = ByteArrayRange.EMPTY; + imageData = new ByteArrayRange(createEmptyImage()); forceWriteAsLong = true; } @@ -68,6 +68,13 @@ public class DefineBitsJPEG2Tag extends ImageTag implements AloneTag { this.imageData = new ByteArrayRange(imageData); } + private byte[] createEmptyImage() { + BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); + return bitmapDataOS.toByteArray(); + } + public DefineBitsJPEG2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { super(sis.getSwf(), ID, NAME, data); readData(sis, data, 0, false, false, false); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java index 7ce6edc6d..87391402a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java @@ -153,7 +153,7 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { @Override public ImageFormat getImageFormat() { ImageFormat fmt = ImageTag.getImageFormat(imageData); - if (fmt == ImageFormat.JPEG) { + if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { fmt = ImageFormat.PNG; //transparency } return fmt; @@ -161,8 +161,17 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { @Override public InputStream getImageData() { - int errorLength = hasErrorHeader(imageData) ? 4 : 0; - return new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + + if (bitmapAlphaData.getLength() == 0) { //No alpha, then its JPEG + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + return new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + } + + //Make PNG + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageHelper.write(getImage().getBufferedImage(), ImageFormat.PNG, baos); + return new ByteArrayInputStream(baos.toByteArray()); + } @Override @@ -171,7 +180,10 @@ public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { return cachedImage; } try { - BufferedImage image = ImageHelper.read(getImageData()); + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + + BufferedImage image = ImageHelper.read(bis); if (image == null) { Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); return null; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java index b163dbd7a..d60790d11 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java @@ -158,7 +158,7 @@ public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag { @Override public ImageFormat getImageFormat() { ImageFormat fmt = ImageTag.getImageFormat(imageData); - if (fmt == ImageFormat.JPEG) { + if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { fmt = ImageFormat.PNG; //transparency } return fmt; @@ -166,7 +166,15 @@ public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag { @Override public InputStream getImageData() { - return new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); + + if (bitmapAlphaData.getLength() == 0) { //No alpha, then its JPEG + return new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); + } + //Make PNG + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageHelper.write(getImage().getBufferedImage(), ImageFormat.PNG, baos); + return new ByteArrayInputStream(baos.toByteArray()); + } @Override @@ -175,7 +183,8 @@ public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag { return cachedImage; } try { - BufferedImage image = ImageHelper.read(getImageData()); + + BufferedImage image = ImageHelper.read(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength())); if (image == null) { Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); return null;