mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-07-08 10:08:12 +00:00
Issue #895 Correct handling CMYK JPEG images (Monte Video library)
Correct JPEG to Image conversion when it has alpha
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -58,3 +58,5 @@ hs_err_pid*.log
|
||||
/version.properties
|
||||
/tools.properties
|
||||
/nbproject/private/
|
||||
/libsrc/cmykjpeg/build/
|
||||
/libsrc/cmykjpeg/nbproject/private/
|
||||
BIN
lib/cmykjpeg.jar
Normal file
BIN
lib/cmykjpeg.jar
Normal file
Binary file not shown.
73
libsrc/cmykjpeg/build.xml
Normal file
73
libsrc/cmykjpeg/build.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- You may freely edit this file. See commented blocks below for -->
|
||||
<!-- some examples of how to customize the build. -->
|
||||
<!-- (If you delete it and reopen the project it will be recreated.) -->
|
||||
<!-- By default, only the Clean and Build commands use this build script. -->
|
||||
<!-- Commands such as Run, Debug, and Test only use this build script if -->
|
||||
<!-- the Compile on Save feature is turned off for the project. -->
|
||||
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
|
||||
<!-- in the project's Project Properties dialog box.-->
|
||||
<project name="cmykjpeg" default="default" basedir=".">
|
||||
<description>Builds, tests, and runs the project cmykjpeg.</description>
|
||||
<import file="nbproject/build-impl.xml"/>
|
||||
<!--
|
||||
|
||||
There exist several targets which are by default empty and which can be
|
||||
used for execution of your tasks. These targets are usually executed
|
||||
before and after some main targets. They are:
|
||||
|
||||
-pre-init: called before initialization of project properties
|
||||
-post-init: called after initialization of project properties
|
||||
-pre-compile: called before javac compilation
|
||||
-post-compile: called after javac compilation
|
||||
-pre-compile-single: called before javac compilation of single file
|
||||
-post-compile-single: called after javac compilation of single file
|
||||
-pre-compile-test: called before javac compilation of JUnit tests
|
||||
-post-compile-test: called after javac compilation of JUnit tests
|
||||
-pre-compile-test-single: called before javac compilation of single JUnit test
|
||||
-post-compile-test-single: called after javac compilation of single JUunit test
|
||||
-pre-jar: called before JAR building
|
||||
-post-jar: called after JAR building
|
||||
-post-clean: called after cleaning build products
|
||||
|
||||
(Targets beginning with '-' are not intended to be called on their own.)
|
||||
|
||||
Example of inserting an obfuscator after compilation could look like this:
|
||||
|
||||
<target name="-post-compile">
|
||||
<obfuscate>
|
||||
<fileset dir="${build.classes.dir}"/>
|
||||
</obfuscate>
|
||||
</target>
|
||||
|
||||
For list of available properties check the imported
|
||||
nbproject/build-impl.xml file.
|
||||
|
||||
|
||||
Another way to customize the build is by overriding existing main targets.
|
||||
The targets of interest are:
|
||||
|
||||
-init-macrodef-javac: defines macro for javac compilation
|
||||
-init-macrodef-junit: defines macro for junit execution
|
||||
-init-macrodef-debug: defines macro for class debugging
|
||||
-init-macrodef-java: defines macro for class execution
|
||||
-do-jar: JAR building
|
||||
run: execution of project
|
||||
-javadoc-build: Javadoc generation
|
||||
test-report: JUnit report generation
|
||||
|
||||
An example of overriding the target for project execution could look like this:
|
||||
|
||||
<target name="run" depends="cmykjpeg-impl.jar">
|
||||
<exec dir="bin" executable="launcher.exe">
|
||||
<arg file="${dist.jar}"/>
|
||||
</exec>
|
||||
</target>
|
||||
|
||||
Notice that the overridden target depends on the jar target and not only on
|
||||
the compile target as the regular run target does. Again, for a list of available
|
||||
properties which you can use, check the target you are overriding in the
|
||||
nbproject/build-impl.xml file.
|
||||
|
||||
-->
|
||||
</project>
|
||||
1413
libsrc/cmykjpeg/nbproject/build-impl.xml
Normal file
1413
libsrc/cmykjpeg/nbproject/build-impl.xml
Normal file
File diff suppressed because it is too large
Load Diff
8
libsrc/cmykjpeg/nbproject/genfiles.properties
Normal file
8
libsrc/cmykjpeg/nbproject/genfiles.properties
Normal file
@@ -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
|
||||
73
libsrc/cmykjpeg/nbproject/project.properties
Normal file
73
libsrc/cmykjpeg/nbproject/project.properties
Normal file
@@ -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
|
||||
15
libsrc/cmykjpeg/nbproject/project.xml
Normal file
15
libsrc/cmykjpeg/nbproject/project.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://www.netbeans.org/ns/project/1">
|
||||
<type>org.netbeans.modules.java.j2seproject</type>
|
||||
<configuration>
|
||||
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
|
||||
<name>cmykjpeg</name>
|
||||
<source-roots>
|
||||
<root id="src.dir"/>
|
||||
</source-roots>
|
||||
<test-roots>
|
||||
<root id="test.src.dir"/>
|
||||
</test-roots>
|
||||
</data>
|
||||
</configuration>
|
||||
</project>
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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 <code>buf[0]</code>
|
||||
* through <code>buf[count-1]</code> are the
|
||||
* only bytes that can ever be read from the
|
||||
* stream; element <code>buf[streamPos]</code> 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 <code>buf</code>.
|
||||
* It is one greater than the position of
|
||||
* the last byte within <code>buf</code> 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 <code>int</code> in the range
|
||||
* <code>0</code> to <code>255</code>. If no byte is available
|
||||
* because the end of the stream has been reached, the value
|
||||
* <code>-1</code> is returned.
|
||||
* <p>
|
||||
* This <code>read</code> method
|
||||
* cannot block.
|
||||
*
|
||||
* @return the next byte of data, or <code>-1</code> 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 <code>len</code> bytes of data into an array of bytes
|
||||
* from this input stream.
|
||||
* If <code>streamPos</code> equals <code>count</code>,
|
||||
* then <code>-1</code> is returned to indicate
|
||||
* end of file. Otherwise, the number <code>k</code>
|
||||
* of bytes read is equal to the smaller of
|
||||
* <code>len</code> and <code>count-streamPos</code>.
|
||||
* If <code>k</code> is positive, then bytes
|
||||
* <code>buf[streamPos]</code> through <code>buf[streamPos+k-1]</code>
|
||||
* are copied into <code>b[off]</code> through
|
||||
* <code>b[off+k-1]</code> in the manner performed
|
||||
* by <code>System.arraycopy</code>. The
|
||||
* value <code>k</code> is added into <code>streamPos</code>
|
||||
* and <code>k</code> is returned.
|
||||
* <p>
|
||||
* This <code>read</code> method cannot block.
|
||||
*
|
||||
* @param b the buffer into which the data is read.
|
||||
* @param off the start offset in the destination array <code>b</code>
|
||||
* @param len the maximum number of bytes read.
|
||||
* @return the total number of bytes read into the buffer, or
|
||||
* <code>-1</code> if there is no more data because the end of
|
||||
* the stream has been reached.
|
||||
* @exception NullPointerException If <code>b</code> is <code>null</code>.
|
||||
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
|
||||
* <code>len</code> is negative, or <code>len</code> is greater than
|
||||
* <code>b.length - off</code>
|
||||
*/
|
||||
@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 <code>n</code> bytes of input from this input stream. Fewer
|
||||
* bytes might be skipped if the end of the input stream is reached.
|
||||
* The actual number <code>k</code>
|
||||
* of bytes to be skipped is equal to the smaller
|
||||
* of <code>n</code> and <code>count-streamPos</code>.
|
||||
* The value <code>k</code> is added into <code>streamPos</code>
|
||||
* and <code>k</code> 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.
|
||||
* <p>
|
||||
* The value returned is <code>count - streamPos</code>,
|
||||
* 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 <tt>ByteArrayInputStream</tt> has no effect. The methods in
|
||||
* this class can be called after the stream has been closed without
|
||||
* generating an <tt>IOException</tt>.
|
||||
* <p>
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -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 <code>int</code> in the range
|
||||
* <code>0</code> to <code>255</code>. If no byte is available
|
||||
* because the end of the stream has been reached, the value
|
||||
* <code>-1</code> is returned. This method blocks until input data
|
||||
* is available, the end of the stream is detected, or an exception
|
||||
* is thrown.
|
||||
* <p>
|
||||
* This method
|
||||
* simply performs <code>in.read()</code> and returns the result.
|
||||
*
|
||||
* @return the next byte of data, or <code>-1</code> 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 <code>len</code> bytes of data from this input stream
|
||||
* into an array of bytes. If <code>len</code> is not zero, the method
|
||||
* blocks until some input is available; otherwise, no
|
||||
* bytes are read and <code>0</code> is returned.
|
||||
* <p>
|
||||
* This method simply performs <code>in.read(b, off, len)</code>
|
||||
* and returns the result.
|
||||
*
|
||||
* @param b the buffer into which the data is read.
|
||||
* @param off the start offset in the destination array <code>b</code>
|
||||
* @param len the maximum number of bytes read.
|
||||
* @return the total number of bytes read into the buffer, or
|
||||
* <code>-1</code> if there is no more data because the end of
|
||||
* the stream has been reached.
|
||||
* @exception NullPointerException If <code>b</code> is <code>null</code>.
|
||||
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
|
||||
* <code>len</code> is negative, or <code>len</code> is greater than
|
||||
* <code>b.length - off</code>
|
||||
* @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}
|
||||
* <p>
|
||||
* This method simply performs <code>in.skip(n)</code>.
|
||||
*/
|
||||
@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.
|
||||
* <p>
|
||||
* 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 <code>in.close()</code>.
|
||||
*
|
||||
* @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 <code>reset</code> method repositions this stream at
|
||||
* the last marked position so that subsequent reads re-read the same bytes.
|
||||
* <p>
|
||||
* The <code>readlimit</code> argument tells this input stream to
|
||||
* allow that many bytes to be read before the mark position gets
|
||||
* invalidated.
|
||||
* <p>
|
||||
* This method simply performs <code>in.mark(readlimit)</code>.
|
||||
*
|
||||
* @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
|
||||
* <code>mark</code> method was last called on this input stream.
|
||||
* <p>
|
||||
* This method
|
||||
* simply performs <code>in.reset()</code>.
|
||||
* <p>
|
||||
* 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 <code>mark</code>
|
||||
* and <code>reset</code> methods.
|
||||
* This method
|
||||
* simply performs <code>in.markSupported()</code>.
|
||||
*
|
||||
* @return <code>true</code> if this stream type supports the
|
||||
* <code>mark</code> and <code>reset</code> method;
|
||||
* <code>false</code> otherwise.
|
||||
* @see java.io.FilterInputStream#in
|
||||
* @see java.io.InputStream#mark(int)
|
||||
* @see java.io.InputStream#reset()
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
readHeader();
|
||||
LinkedList<ImageTypeSpecifier> l = new LinkedList<ImageTypeSpecifier>();
|
||||
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<ImageReader> 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <br>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";
|
||||
}
|
||||
}
|
||||
Binary file not shown.
521
libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java
Normal file
521
libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java
Normal file
@@ -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.
|
||||
* <p>
|
||||
* This InputStream uses two special marker values which do not exist
|
||||
* in the JFIF stream:
|
||||
* <ul>
|
||||
* <li><b>-1</b>: marks junk data at the beginning of the file.</li>
|
||||
* <li><b>0</b>: marks entropy encoded image data.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* References:<br>
|
||||
* JPEG File Interchange Format Version 1.02<br>
|
||||
* <a href="http://www.jpeg.org/public/jfif.pdf">http://www.jpeg.org/public/jfif.pdf</a>
|
||||
* <p>
|
||||
* Pennebaker, W., Mitchell, J. (1993).<br>
|
||||
* JPEG Still Image Data Compression Standard.<br>
|
||||
* Chapmann & Hall, New York.<br>
|
||||
* ISBN 0-442-01272-1<br>
|
||||
*
|
||||
*
|
||||
* @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<Integer> standaloneMarkers = new HashSet<Integer>();
|
||||
/**
|
||||
* This hash set holds the Id's of markers which have a data
|
||||
* segment followed by a entropy-coded data segment.
|
||||
*/
|
||||
private final HashSet<Integer> doubleSegMarkers = new HashSet<Integer>();
|
||||
|
||||
/** 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 <code>int</code> in the range
|
||||
* <code>0</code> to <code>255</code>. If no byte is available
|
||||
* because the end of the stream has been reached, the value
|
||||
* <code>-1</code> is returned. This method blocks until input data
|
||||
* is available, the end of the stream is detected, or an exception
|
||||
* is thrown.
|
||||
* <p>
|
||||
* This method
|
||||
* simply performs <code>in.read()</code> and returns the result.
|
||||
*
|
||||
* @return the next byte of data, or <code>-1</code> 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 <code>len</code> b of data from this input stream
|
||||
* into an array of b. This method blocks until some input is
|
||||
* available.
|
||||
* <p>
|
||||
* This method simply performs <code>in.read(b, off, len)</code>
|
||||
* 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
|
||||
* <code>-1</code> 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 <code>n</code> b of data from the
|
||||
* input stream. The <code>skip</code> method may, for a variety of
|
||||
* reasons, end up skipping over some smaller number of b,
|
||||
* possibly <code>0</code>. The actual number of b skipped is
|
||||
* returned.
|
||||
* <p>
|
||||
* This method
|
||||
* simply performs <code>in.skip(n)</code>.
|
||||
*
|
||||
* @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 <code>reset</code> method repositions this stream at
|
||||
* the last marked position so that subsequent reads re-read the same b.
|
||||
* <p>
|
||||
* The <code>readlimit</code> argument tells this input stream to
|
||||
* allow that many b to be read before the mark position gets
|
||||
* invalidated.
|
||||
* <p>
|
||||
* This method simply performs <code>in.mark(readlimit)</code>.
|
||||
*
|
||||
* @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
|
||||
* <code>mark</code> method was last called on this input stream.
|
||||
* <p>
|
||||
* This method
|
||||
* simply performs <code>in.reset()</code>.
|
||||
* <p>
|
||||
* 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 <code>mark</code>
|
||||
* and <code>reset</code> methods.
|
||||
* This method
|
||||
* simply performs <code>in.markSupported()</code>.
|
||||
*
|
||||
* @return <code>true</code> if this stream type supports the
|
||||
* <code>mark</code> and <code>reset</code> method;
|
||||
* <code>false</code> otherwise.
|
||||
* @see java.io.FilterInputStream#in
|
||||
* @see java.io.InputStream#mark(int)
|
||||
* @see java.io.InputStream#reset()
|
||||
*/
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -236,7 +236,7 @@ auxiliary.show.customizer.message=<message>
|
||||
<java-data xmlns="http://www.netbeans.org/ns/freeform-project-java/4">
|
||||
<compilation-unit>
|
||||
<package-root>src</package-root>
|
||||
<classpath mode="compile">../../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</classpath>
|
||||
<classpath mode="compile">../../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</classpath>
|
||||
<built-to>build</built-to>
|
||||
<built-to>reports</built-to>
|
||||
<built-to>dist</built-to>
|
||||
|
||||
@@ -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<File> exportImages(AbortRetryIgnoreHandler handler, String outdir, List<Tag> tags, ImageExportSettings settings, EventListener evl) throws IOException {
|
||||
List<File> 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<File> exportImages(AbortRetryIgnoreHandler handler, String outdir, List<Tag> tags, ImageExportSettings settings, EventListener evl) throws IOException {
|
||||
List<File> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user