mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-04-30 00:00:01 -04:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -23,27 +23,54 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>batik</groupId>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-rasterizer-ext</artifactId>
|
||||
<version>1.6-1</version>
|
||||
<version>${batik.version}</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-extensions</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-extension</artifactId>
|
||||
<version>${batik.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>batik</groupId>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>xmlgraphics-commons</artifactId>
|
||||
<version>2.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-anim</artifactId>
|
||||
<version>${batik.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-svggen</artifactId>
|
||||
<version>1.6-1</version>
|
||||
<version>${batik.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>batik</groupId>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-transcoder</artifactId>
|
||||
<version>1.6-1</version>
|
||||
<version>${batik.version}</version>
|
||||
<scope>provided</scope>
|
||||
|
||||
<!--
|
||||
@@ -59,4 +86,8 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<batik.version>1.8</batik.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
+65
-67
@@ -31,9 +31,9 @@ package com.twelvemonkeys.imageio.plugins.svg;
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
||||
import org.apache.batik.anim.dom.SVGOMDocument;
|
||||
import org.apache.batik.bridge.*;
|
||||
import org.apache.batik.dom.svg.SVGDOMImplementation;
|
||||
import org.apache.batik.dom.svg.SVGOMDocument;
|
||||
import org.apache.batik.dom.util.DOMUtilities;
|
||||
import org.apache.batik.ext.awt.image.GraphicsUtil;
|
||||
import org.apache.batik.gvt.CanvasGraphicsNode;
|
||||
@@ -74,18 +74,19 @@ import java.util.Map;
|
||||
* @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
|
||||
*/
|
||||
public class SVGImageReader extends ImageReaderBase {
|
||||
private Rasterizer rasterizer = new Rasterizer();
|
||||
private Rasterizer rasterizer;
|
||||
|
||||
/**
|
||||
* Creates an {@code SVGImageReader}.
|
||||
*
|
||||
* @param pProvider the provider
|
||||
*/
|
||||
public SVGImageReader(ImageReaderSpi pProvider) {
|
||||
public SVGImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
rasterizer = new Rasterizer();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,7 +135,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
try {
|
||||
processImageStarted(pIndex);
|
||||
|
||||
rasterizer.mTranscoderInput.setURI(baseURI);
|
||||
rasterizer.transcoderInput.setURI(baseURI);
|
||||
BufferedImage image = rasterizer.getImage();
|
||||
|
||||
Graphics2D g = destination.createGraphics();
|
||||
@@ -252,31 +253,29 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
*/
|
||||
private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ {
|
||||
|
||||
BufferedImage mImage = null;
|
||||
private TranscoderInput mTranscoderInput;
|
||||
private float mDefaultWidth;
|
||||
private float mDefaultHeight;
|
||||
private boolean mInit = false;
|
||||
private SVGOMDocument mDocument;
|
||||
private String mURI;
|
||||
private GraphicsNode mGVTRoot;
|
||||
private TranscoderException mException;
|
||||
private BridgeContext mContext;
|
||||
BufferedImage image = null;
|
||||
private TranscoderInput transcoderInput;
|
||||
private float defaultWidth;
|
||||
private float defaultHeight;
|
||||
private boolean initialized = false;
|
||||
private SVGOMDocument document;
|
||||
private String uri;
|
||||
private GraphicsNode gvtRoot;
|
||||
private TranscoderException exception;
|
||||
private BridgeContext context;
|
||||
|
||||
public BufferedImage createImage(int w, int h) {
|
||||
return ImageUtil.createTransparent(w, h);//, BufferedImage.TYPE_INT_ARGB);
|
||||
public BufferedImage createImage(final int width, final int height) {
|
||||
return ImageUtil.createTransparent(width, height);//, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
|
||||
// This is cheating... We don't fully transcode after all
|
||||
protected void transcode(Document document, String uri, TranscoderOutput output) throws TranscoderException {
|
||||
protected void transcode(Document document, final String uri, final TranscoderOutput output) throws TranscoderException {
|
||||
// Sets up root, curTxf & curAoi
|
||||
// ----
|
||||
if ((document != null) &&
|
||||
!(document.getImplementation() instanceof SVGDOMImplementation)) {
|
||||
DOMImplementation impl;
|
||||
impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION);
|
||||
// impl = ExtensibleSVGDOMImplementation.getDOMImplementation();
|
||||
if ((document != null) && !(document.getImplementation() instanceof SVGDOMImplementation)) {
|
||||
DOMImplementation impl = (DOMImplementation) hints.get(KEY_DOM_IMPLEMENTATION);
|
||||
document = DOMUtilities.deepCloneDocument(document, impl);
|
||||
|
||||
if (uri != null) {
|
||||
try {
|
||||
URL url = new URL(uri);
|
||||
@@ -289,7 +288,6 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
ctx = createBridgeContext();
|
||||
SVGOMDocument svgDoc = (SVGOMDocument) document;
|
||||
//SVGSVGElement root = svgDoc.getRootElement();
|
||||
|
||||
// build the GVT tree
|
||||
builder = new GVTBuilder();
|
||||
@@ -297,7 +295,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
boolean isDynamic =
|
||||
(hints.containsKey(KEY_EXECUTE_ONLOAD) &&
|
||||
(Boolean) hints.get(KEY_EXECUTE_ONLOAD) &&
|
||||
BaseScriptingEnvironment.isDynamicDocument(ctx, svgDoc));
|
||||
BaseScriptingEnvironment.isDynamicDocument(ctx, svgDoc));
|
||||
|
||||
if (isDynamic) {
|
||||
ctx.setDynamicState(BridgeContext.DYNAMIC);
|
||||
@@ -311,8 +309,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
catch (BridgeException ex) {
|
||||
// Note: This might fail, but we STILL have the dimensions we need
|
||||
// However, we need to reparse later...
|
||||
//throw new TranscoderException(ex);
|
||||
mException = new TranscoderException(ex);
|
||||
exception = new TranscoderException(ex);
|
||||
}
|
||||
|
||||
// ----
|
||||
@@ -320,24 +317,23 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
// get the 'width' and 'height' attributes of the SVG document
|
||||
Dimension2D docSize = ctx.getDocumentSize();
|
||||
if (docSize != null) {
|
||||
mDefaultWidth = (float) docSize.getWidth();
|
||||
mDefaultHeight = (float) docSize.getHeight();
|
||||
defaultWidth = (float) docSize.getWidth();
|
||||
defaultHeight = (float) docSize.getHeight();
|
||||
}
|
||||
else {
|
||||
mDefaultWidth = 200;
|
||||
mDefaultHeight = 200;
|
||||
defaultWidth = 200;
|
||||
defaultHeight = 200;
|
||||
}
|
||||
|
||||
// Hack to work around exception above
|
||||
if (root != null) {
|
||||
mGVTRoot = root;
|
||||
gvtRoot = root;
|
||||
}
|
||||
mDocument = svgDoc;
|
||||
mURI = uri;
|
||||
this.document = svgDoc;
|
||||
this.uri = uri;
|
||||
|
||||
//ctx.dispose();
|
||||
// Hack to avoid the transcode method wacking my context...
|
||||
mContext = ctx;
|
||||
context = ctx;
|
||||
ctx = null;
|
||||
}
|
||||
|
||||
@@ -352,24 +348,24 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
|
||||
// Hacky workaround below...
|
||||
if (mGVTRoot == null) {
|
||||
if (gvtRoot == null) {
|
||||
// Try to reparse, if we had no URI last time...
|
||||
if (mURI != mTranscoderInput.getURI()) {
|
||||
if (uri != transcoderInput.getURI()) {
|
||||
try {
|
||||
mContext.dispose();
|
||||
mDocument.setURLObject(new URL(mTranscoderInput.getURI()));
|
||||
transcode(mDocument, mTranscoderInput.getURI(), null);
|
||||
context.dispose();
|
||||
document.setURLObject(new URL(transcoderInput.getURI()));
|
||||
transcode(document, transcoderInput.getURI(), null);
|
||||
}
|
||||
catch (MalformedURLException ignore) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
if (mGVTRoot == null) {
|
||||
throw mException;
|
||||
if (gvtRoot == null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
ctx = mContext;
|
||||
ctx = context;
|
||||
// /Hacky
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -377,13 +373,13 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
processImageProgress(20f);
|
||||
|
||||
// -- --
|
||||
SVGSVGElement root = mDocument.getRootElement();
|
||||
// ----
|
||||
SVGSVGElement root = document.getRootElement();
|
||||
// ----
|
||||
|
||||
|
||||
// ----
|
||||
setImageSize(mDefaultWidth, mDefaultHeight);
|
||||
setImageSize(defaultWidth, defaultHeight);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -393,22 +389,22 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
// compute the preserveAspectRatio matrix
|
||||
AffineTransform Px;
|
||||
String ref = new ParsedURL(mURI).getRef();
|
||||
String ref = new ParsedURL(uri).getRef();
|
||||
|
||||
try {
|
||||
Px = ViewBox.getViewTransform(ref, root, width, height);
|
||||
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
||||
|
||||
}
|
||||
catch (BridgeException ex) {
|
||||
throw new TranscoderException(ex);
|
||||
}
|
||||
|
||||
if (Px.isIdentity() && (width != mDefaultWidth || height != mDefaultHeight)) {
|
||||
if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) {
|
||||
// The document has no viewBox, we need to resize it by hand.
|
||||
// we want to keep the document size ratio
|
||||
float xscale, yscale;
|
||||
xscale = width / mDefaultWidth;
|
||||
yscale = height / mDefaultHeight;
|
||||
xscale = width / defaultWidth;
|
||||
yscale = height / defaultHeight;
|
||||
float scale = Math.min(xscale, yscale);
|
||||
Px = AffineTransform.getScaleInstance(scale, scale);
|
||||
}
|
||||
@@ -439,7 +435,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
processImageProgress(50f);
|
||||
|
||||
CanvasGraphicsNode cgn = getCanvasGraphicsNode(mGVTRoot);
|
||||
CanvasGraphicsNode cgn = getCanvasGraphicsNode(gvtRoot);
|
||||
if (cgn != null) {
|
||||
cgn.setViewingTransform(Px);
|
||||
curTxf = new AffineTransform();
|
||||
@@ -461,7 +457,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
throw new TranscoderException(ex);
|
||||
}
|
||||
|
||||
this.root = mGVTRoot;
|
||||
this.root = gvtRoot;
|
||||
// ----
|
||||
|
||||
// NOTE: The code below is copied and pasted from the Batik
|
||||
@@ -509,6 +505,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
g2d.setPaint(bgcolor);
|
||||
g2d.fillRect(0, 0, w, h);
|
||||
}
|
||||
|
||||
if (rend != null) { // might be null if the svg document is empty
|
||||
g2d.drawRenderedImage(rend, new AffineTransform());
|
||||
}
|
||||
@@ -533,43 +530,44 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
throw exception;
|
||||
}
|
||||
finally {
|
||||
if (mContext != null) {
|
||||
mContext.dispose();
|
||||
if (context != null) {
|
||||
context.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void init() throws TranscoderException {
|
||||
if (!mInit) {
|
||||
if (mTranscoderInput == null) {
|
||||
if (!initialized) {
|
||||
if (transcoderInput == null) {
|
||||
throw new IllegalStateException("input == null");
|
||||
}
|
||||
|
||||
mInit = true;
|
||||
initialized = true;
|
||||
|
||||
super.transcode(mTranscoderInput, null);
|
||||
super.transcode(transcoderInput, null);
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage getImage() throws TranscoderException {
|
||||
if (mImage == null) {
|
||||
mImage = readImage();
|
||||
if (image == null) {
|
||||
image = readImage();
|
||||
}
|
||||
return mImage;
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
protected int getDefaultWidth() throws TranscoderException {
|
||||
init();
|
||||
return (int) (mDefaultWidth + 0.5);
|
||||
return (int) (defaultWidth + 0.5);
|
||||
}
|
||||
|
||||
protected int getDefaultHeight() throws TranscoderException {
|
||||
init();
|
||||
return (int) (mDefaultHeight + 0.5);
|
||||
return (int) (defaultHeight + 0.5);
|
||||
}
|
||||
|
||||
public void setInput(TranscoderInput pInput) {
|
||||
mTranscoderInput = pInput;
|
||||
public void setInput(final TranscoderInput pInput) {
|
||||
transcoderInput = pInput;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+77
-72
@@ -28,17 +28,18 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.svg.SVGProviderInfo.SVG_READER_AVAILABLE;
|
||||
|
||||
/**
|
||||
* SVGImageReaderSpi
|
||||
* <p/>
|
||||
@@ -46,52 +47,26 @@ import java.util.Locale;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: SVGImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 haku Exp $
|
||||
*/
|
||||
public class SVGImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
private final static boolean SVG_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
|
||||
public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
||||
|
||||
/**
|
||||
* Creates an {@code SVGImageReaderSpi}.
|
||||
*/
|
||||
public SVGImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(SVGImageReaderSpi.class));
|
||||
super(new SVGProviderInfo());
|
||||
}
|
||||
|
||||
private SVGImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
SVG_READER_AVAILABLE ? new String[]{"svg", "SVG"} : new String[]{""}, // Names
|
||||
SVG_READER_AVAILABLE ? new String[]{"svg"} : null, // Suffixes
|
||||
SVG_READER_AVAILABLE ? new String[]{"image/svg", "image/x-svg", "image/svg+xml", "image/svg-xml"} : null, // Mime-types
|
||||
"com.twelvemonkeys.imageio.plugins.svg.SVGImageReader", // Reader class name
|
||||
new Class[] {ImageInputStream.class}, // Input types
|
||||
null, // Writer SPI names
|
||||
true, // Supports standard stream metadata format
|
||||
null, // Native stream metadata format name
|
||||
null, // Native stream metadata format class name
|
||||
null, // Extra stream metadata format names
|
||||
null, // Extra stream metadata format class names
|
||||
true, // Supports standard image metadata format
|
||||
null, // Native image metadata format name
|
||||
null, // Native image metadata format class name
|
||||
null, // Extra image metadata format names
|
||||
null // Extra image metadata format class names
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canDecodeInput(Object pSource) throws IOException {
|
||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
||||
return pSource instanceof ImageInputStream && SVG_READER_AVAILABLE && canDecode((ImageInputStream) pSource);
|
||||
}
|
||||
|
||||
private static boolean canDecode(ImageInputStream pInput) throws IOException {
|
||||
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
|
||||
// NOTE: This test is quite quick as it does not involve any parsing,
|
||||
// however it requires the doctype to be "svg", which may not be correct
|
||||
// in all cases...
|
||||
// however it may not recognize all kinds of SVG documents.
|
||||
try {
|
||||
pInput.mark();
|
||||
|
||||
// TODO: This is may not be ok for non-UTF/iso-latin encodings...
|
||||
// TODO: This is not ok for UTF-16 and other wide encodings
|
||||
// TODO: Use an XML (encoding) aware Reader instance instead
|
||||
// Need to figure out pretty fast if this is XML or not
|
||||
int b;
|
||||
@@ -99,46 +74,76 @@ public class SVGImageReaderSpi extends ImageReaderSpi {
|
||||
// Skip over leading WS
|
||||
}
|
||||
|
||||
if (!((b == '<') && (pInput.read() == '?') && (pInput.read() == 'x') && (pInput.read() == 'm')
|
||||
&& (pInput.read() == 'l'))) {
|
||||
// If it's not a tag, this can't be valid XML
|
||||
if (b != '<') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Okay, we have XML. But, is it really SVG?
|
||||
boolean docTypeFound = false;
|
||||
while (!docTypeFound) {
|
||||
while (pInput.read() != '<') {
|
||||
// Skip over, until begin tag
|
||||
}
|
||||
// Algorithm for detecting SVG:
|
||||
// - Skip until begin tag '<' and read 4 bytes
|
||||
// - if next is "?" skip until "?>" and start over
|
||||
// - else if next is "!--" skip until "-->" and start over
|
||||
// - else if next is "!DOCTYPE " skip any whitespace
|
||||
// - compare next 3 bytes against "svg", return result
|
||||
// - else
|
||||
// - compare next 3 bytes against "svg", return result
|
||||
|
||||
// If this is not a comment, or the DOCTYPE declaration, the doc
|
||||
// has no DOCTYPE and it can't be svg
|
||||
if (pInput.read() != '!') {
|
||||
byte[] buffer = new byte[4];
|
||||
while (true) {
|
||||
pInput.readFully(buffer);
|
||||
|
||||
if (buffer[0] == '?') {
|
||||
// This is the XML declaration or a processing instruction
|
||||
while (!(pInput.read() == '?' && pInput.read() == '>')) {
|
||||
// Skip until end of XML declaration or processing instruction
|
||||
}
|
||||
}
|
||||
else if (buffer[0] == '!') {
|
||||
if (buffer[1] == '-' && buffer[2] == '-') {
|
||||
// This is a comment
|
||||
while (!(pInput.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
|
||||
// Skip until end of comment
|
||||
}
|
||||
}
|
||||
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
|
||||
&& pInput.read() == 'T' && pInput.read() == 'Y'
|
||||
&& pInput.read() == 'P' && pInput.read() == 'E') {
|
||||
// This is the DOCTYPE declaration
|
||||
while (Character.isWhitespace((char) (b = pInput.read()))) {
|
||||
// Skip over WS
|
||||
}
|
||||
|
||||
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
|
||||
// It's SVG, identified by DOCTYPE
|
||||
return true;
|
||||
}
|
||||
|
||||
// DOCTYPE found, but not SVG
|
||||
return false;
|
||||
}
|
||||
|
||||
// Something else, we'll skip
|
||||
}
|
||||
else {
|
||||
// This is a normal tag
|
||||
if (buffer[0] == 's' && buffer[1] == 'v' && buffer[2] == 'g'
|
||||
&& (Character.isWhitespace((char) buffer[3]) || buffer[3] == ':')) {
|
||||
// It's SVG, identified by root tag
|
||||
// TODO: Support svg with prefix + recognize namespace (http://www.w3.org/2000/svg)!
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the tag is not "svg", this isn't SVG
|
||||
return false;
|
||||
}
|
||||
|
||||
// There might be comments before the doctype, unfortunately...
|
||||
// If next is "--", this is a comment
|
||||
if ((b = pInput.read()) == '-' && pInput.read() == '-') {
|
||||
while (!(pInput.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
|
||||
// Skip until end of comment
|
||||
}
|
||||
}
|
||||
|
||||
// If we are lucky, this is DOCTYPE declaration
|
||||
if (b == 'D' && pInput.read() == 'O' && pInput.read() == 'C'
|
||||
&& pInput.read() == 'T' && pInput.read() == 'Y' && pInput.read() == 'P'
|
||||
&& pInput.read() == 'E') {
|
||||
docTypeFound = true;
|
||||
while (Character.isWhitespace((char) (b = pInput.read()))) {
|
||||
// Skip over WS
|
||||
}
|
||||
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
|
||||
//System.out.println("It's svg!");
|
||||
return true;
|
||||
}
|
||||
while (pInput.read() != '<') {
|
||||
// Skip over, until next begin tag
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EOFException ignore) {
|
||||
// Possible for small files...
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
@@ -146,18 +151,17 @@ public class SVGImageReaderSpi extends ImageReaderSpi {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||
public ImageReader createReaderInstance(final Object extension) throws IOException {
|
||||
return new SVGImageReader(this);
|
||||
}
|
||||
|
||||
public String getDescription(Locale locale) {
|
||||
return "Scaleable Vector Graphics (SVG) format image reader";
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Scalable Vector Graphics (SVG) format image reader";
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation"})
|
||||
@Override
|
||||
public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
if (!SVG_READER_AVAILABLE) {
|
||||
try {
|
||||
// NOTE: This will break, but it gives us some useful debug info
|
||||
@@ -170,5 +174,6 @@ public class SVGImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
IIOUtil.deregisterProvider(registry, this, category);
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
|
||||
/**
|
||||
* SVGProviderInfo.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: SVGProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class SVGProviderInfo extends ReaderWriterProviderInfo {
|
||||
final static boolean SVG_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
|
||||
|
||||
protected SVGProviderInfo() {
|
||||
super(
|
||||
SVGProviderInfo.class,
|
||||
SVG_READER_AVAILABLE ? new String[]{"svg", "SVG"} : new String[]{""}, // Names
|
||||
SVG_READER_AVAILABLE ? new String[]{"svg"} : null, // Suffixes
|
||||
SVG_READER_AVAILABLE ? new String[]{"image/svg", "image/x-svg", "image/svg+xml", "image/svg-xml"} : null, // Mime-types
|
||||
"com.twelvemonkeys.imageio.plugins.svg.SVGImageReader", // Reader class name
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi"},
|
||||
null,
|
||||
null,
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
);
|
||||
}
|
||||
}
|
||||
+11
-39
@@ -28,64 +28,38 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.wmf.WMFProviderInfo.WMF_READER_AVAILABLE;
|
||||
|
||||
/**
|
||||
* WMFImageReaderSpi
|
||||
* <p/>
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: WMFImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 wmhakur Exp $
|
||||
*/
|
||||
public class WMFImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
// This is correct, as we rely on the SVG reader
|
||||
private final static boolean WMF_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
|
||||
public final class WMFImageReaderSpi extends ImageReaderSpiBase {
|
||||
|
||||
/**
|
||||
* Creates a {@code WMFImageReaderSpi}.
|
||||
*/
|
||||
public WMFImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(WMFImageReaderSpi.class));
|
||||
super(new WMFProviderInfo());
|
||||
}
|
||||
|
||||
private WMFImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
WMF_READER_AVAILABLE ? new String[]{"wmf", "WMF"} : new String[]{""}, // Names
|
||||
WMF_READER_AVAILABLE ? new String[]{"wmf", "emf"} : null, // Suffixes
|
||||
WMF_READER_AVAILABLE ? new String[]{"application/x-msmetafile", "image/x-wmf"} : null, // Mime-types
|
||||
"com.twelvemonkeys.imageio.plugins.wmf.WMFImageReader", // Reader class name..?
|
||||
new Class[] {ImageInputStream.class}, // Input types
|
||||
null, // Writer SPI names
|
||||
true, // Supports standard stream metadata format
|
||||
null, // Native stream metadata format name
|
||||
null, // Native stream metadata format class name
|
||||
null, // Extra stream metadata format names
|
||||
null, // Extra stream metadata format class names
|
||||
true, // Supports standard image metadata format
|
||||
null, // Native image metadata format name
|
||||
null, // Native image metadata format class name
|
||||
null, // Extra image metadata format names
|
||||
null // Extra image metadata format class names
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canDecodeInput(Object source) throws IOException {
|
||||
public boolean canDecodeInput(final Object source) throws IOException {
|
||||
return source instanceof ImageInputStream && WMF_READER_AVAILABLE && canDecode((ImageInputStream) source);
|
||||
}
|
||||
|
||||
public static boolean canDecode(ImageInputStream pInput) throws IOException {
|
||||
public static boolean canDecode(final ImageInputStream pInput) throws IOException {
|
||||
if (pInput == null) {
|
||||
throw new IllegalArgumentException("input == null");
|
||||
}
|
||||
@@ -96,7 +70,6 @@ public class WMFImageReaderSpi extends ImageReaderSpi {
|
||||
for (byte header : WMF.HEADER) {
|
||||
int read = (byte) pInput.read();
|
||||
if (header != read) {
|
||||
// System.out.println("--> " + i + ": " + read + " (expected " + header + ")");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -108,18 +81,17 @@ public class WMFImageReaderSpi extends ImageReaderSpi {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||
public ImageReader createReaderInstance(final Object extension) throws IOException {
|
||||
return new WMFImageReader(this);
|
||||
}
|
||||
|
||||
public String getDescription(Locale locale) {
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Windows Meta File (WMF) image reader";
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation"})
|
||||
@Override
|
||||
public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
if (!WMF_READER_AVAILABLE) {
|
||||
IIOUtil.deregisterProvider(registry, this, category);
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
|
||||
/**
|
||||
* WMFProviderInfo.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: WMFProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class WMFProviderInfo extends ReaderWriterProviderInfo {
|
||||
// This is correct, as we rely on the SVG reader
|
||||
final static boolean WMF_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
|
||||
|
||||
protected WMFProviderInfo() {
|
||||
super(
|
||||
WMFProviderInfo.class,
|
||||
WMF_READER_AVAILABLE ? new String[]{"wmf", "WMF"} : new String[]{""}, // Names
|
||||
WMF_READER_AVAILABLE ? new String[]{"wmf", "emf"} : null, // Suffixes
|
||||
WMF_READER_AVAILABLE ? new String[]{"application/x-msmetafile", "image/x-wmf"} : null, // Mime-types
|
||||
"com.twelvemonkeys.imageio.plugins.wmf.WMFImageReader", // Reader class name..?
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi"},
|
||||
null,
|
||||
null,
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
);
|
||||
}
|
||||
}
|
||||
+35
-8
@@ -28,30 +28,39 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ImagingOpException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* SVGImageReaderTestCase
|
||||
* SVGImageReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SVGImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
* @version $Id: SVGImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class SVGImageReaderTestCase extends ImageReaderAbstractTestCase<SVGImageReader> {
|
||||
public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader> {
|
||||
private SVGImageReaderSpi provider = new SVGImageReaderSpi();
|
||||
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500))
|
||||
new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500)),
|
||||
new TestData(getClassLoaderResource("/svg/red-square.svg"), new Dimension(100, 100)),
|
||||
new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100)),
|
||||
new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(400, 400))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,15 +78,15 @@ public class SVGImageReaderTestCase extends ImageReaderAbstractTestCase<SVGImage
|
||||
}
|
||||
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("svg");
|
||||
return Collections.singletonList("svg");
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("svg");
|
||||
return Collections.singletonList("svg");
|
||||
}
|
||||
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/svg+xml");
|
||||
return Collections.singletonList("image/svg+xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,4 +119,22 @@ public class SVGImageReaderTestCase extends ImageReaderAbstractTestCase<SVGImage
|
||||
public void testReadWithSourceRegionParamEqualImage() throws IOException {
|
||||
super.testReadWithSourceRegionParamEqualImage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatedRead() throws IOException {
|
||||
Dimension dim = new Dimension(100, 100);
|
||||
ImageReader reader = createReader();
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
param.setSourceRenderSize(dim);
|
||||
|
||||
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
|
||||
reader.setInput(redSquare.getInputStream());
|
||||
BufferedImage imageRed = reader.read(0, param);
|
||||
assertEquals(0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF);
|
||||
|
||||
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
|
||||
reader.setInput(blueSquare.getInputStream());
|
||||
BufferedImage imageBlue = reader.read(0, param);
|
||||
assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* SVGProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: SVGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class SVGProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new SVGProviderInfo();
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -28,7 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -36,22 +36,22 @@ import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SVGImageReaderTestCase
|
||||
* WMFImageReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SVGImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
* @version $Id: WMFImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class WMFImageReaderTestCase extends ImageReaderAbstractTestCase<WMFImageReader> {
|
||||
public class WMFImageReaderTest extends ImageReaderAbstractTest<WMFImageReader> {
|
||||
private WMFImageReaderSpi provider = new WMFImageReaderSpi();
|
||||
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
// TODO: Dimensions does not look right...
|
||||
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(841, 673))
|
||||
return Collections.singletonList(
|
||||
new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(133, 106))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class WMFImageReaderTestCase extends ImageReaderAbstractTestCase<WMFImage
|
||||
}
|
||||
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("wmf");
|
||||
return Collections.singletonList("wmf");
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes() {
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* WMFProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: WMFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class WMFProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new WMFProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-147 -70 294 345">
|
||||
<g fill="#a4c639">
|
||||
<use stroke-width="14.4" xlink:href="#b" stroke="#FFF"/>
|
||||
<use xlink:href="#a" transform="scale(-1,1)"/>
|
||||
<g id="a" stroke="#FFF" stroke-width="7.2">
|
||||
<rect rx="6.5" transform="rotate(29)" height="86" width="13" y="-86" x="14"/>
|
||||
<rect id="c" rx="24" height="133" width="48" y="41" x="-143"/>
|
||||
<use y="97" x="85" xlink:href="#c"/>
|
||||
</g>
|
||||
<g id="b">
|
||||
<ellipse cy="41" rx="91" ry="84"/>
|
||||
<rect rx="22" height="182" width="182" y="20" x="-91"/>
|
||||
</g>
|
||||
</g>
|
||||
<g stroke="#FFF" stroke-width="7.2" fill="#FFF">
|
||||
<path d="m-95 44.5h190"/><circle cx="-42" r="4"/><circle cx="42" r="4"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 706 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" id="blue-square" version="1.1">
|
||||
<g id="layer1">
|
||||
<rect id="rect2985" width="100" height="100" x="0" y="0"
|
||||
style="color:#000000;fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 438 B |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" id="red-square" version="1.1">
|
||||
<g id="layer1">
|
||||
<rect id="rect2985" width="100" height="100" x="0" y="0"
|
||||
style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 441 B |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-bmp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||
@@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+24
-53
@@ -28,17 +28,16 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
/**
|
||||
* BMPMetadata.
|
||||
*/
|
||||
final class BMPMetadata extends IIOMetadata {
|
||||
final class BMPMetadata extends AbstractMetadata {
|
||||
/** We return metadata in the exact same form as the JRE built-in, to be compatible with the BMPImageWriter. */
|
||||
public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0";
|
||||
|
||||
@@ -46,46 +45,13 @@ final class BMPMetadata extends IIOMetadata {
|
||||
private final int[] colorMap;
|
||||
|
||||
BMPMetadata(final DIBHeader header, final int[] colorMap) {
|
||||
super(true, nativeMetadataFormatName, "com.sun.imageio.plugins.bmp.BMPMetadataFormat", null, null);
|
||||
this.header = Validate.notNull(header, "header");
|
||||
this.colorMap = colorMap == null || colorMap.length == 0 ? null : colorMap;
|
||||
|
||||
standardFormatSupported = true;
|
||||
}
|
||||
|
||||
@Override public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Node getAsTree(final String formatName) {
|
||||
if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
else if (nativeMetadataFormatName.equals(formatName)) {
|
||||
return getNativeTree();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void mergeTree(final String formatName, final Node root) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void reset() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNativeMetadataFormatName() {
|
||||
return nativeMetadataFormatName;
|
||||
}
|
||||
|
||||
private Node getNativeTree() {
|
||||
protected Node getNativeTree() {
|
||||
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
|
||||
|
||||
addChildNode(root, "BMPVersion", header.getBMPVersion());
|
||||
@@ -170,7 +136,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
// NOTE: BMP files may contain a color map, even if true color...
|
||||
// Not sure if this is a good idea to expose to the meta data,
|
||||
// as it might be unexpected... Then again...
|
||||
@@ -197,7 +164,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardCompressionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode compression = new IIOMetadataNode("Compression");
|
||||
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
|
||||
compressionTypeName.setAttribute("value", "NONE");
|
||||
@@ -229,7 +197,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
// }
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
// IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
|
||||
@@ -294,7 +263,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
@@ -302,16 +272,16 @@ final class BMPMetadata extends IIOMetadata {
|
||||
addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null);
|
||||
addChildNode(dimension, "VerticalPhysicalPixelSpacing", null);
|
||||
|
||||
// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
//
|
||||
// if (header.topDown) {
|
||||
// imageOrientation.setAttribute("value", "FlipH");
|
||||
// }
|
||||
// else {
|
||||
// imageOrientation.setAttribute("value", "Normal");
|
||||
// }
|
||||
//
|
||||
// dimension.appendChild(imageOrientation);
|
||||
// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
//
|
||||
// if (header.topDown) {
|
||||
// imageOrientation.setAttribute("value", "FlipH");
|
||||
// }
|
||||
// else {
|
||||
// imageOrientation.setAttribute("value", "Normal");
|
||||
// }
|
||||
//
|
||||
// dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
@@ -325,7 +295,8 @@ final class BMPMetadata extends IIOMetadata {
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
return null;
|
||||
|
||||
// IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
+14
@@ -43,6 +43,7 @@ abstract class BitmapDescriptor {
|
||||
protected final DIBHeader header;
|
||||
|
||||
protected BufferedImage image;
|
||||
protected BitmapMask mask;
|
||||
|
||||
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||
Validate.notNull(pEntry, "entry");
|
||||
@@ -69,4 +70,17 @@ abstract class BitmapDescriptor {
|
||||
protected final int getBitCount() {
|
||||
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
|
||||
}
|
||||
|
||||
public final void setMask(final BitmapMask mask) {
|
||||
this.mask = mask;
|
||||
}
|
||||
|
||||
public final boolean hasMask() {
|
||||
return header.getHeight() == getHeight() * 2;
|
||||
}
|
||||
}
|
||||
|
||||
+25
-34
@@ -28,8 +28,6 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
@@ -46,8 +44,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
protected final int[] bits;
|
||||
protected final int[] colors;
|
||||
|
||||
private BitmapMask mask;
|
||||
|
||||
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||
super(pEntry, pHeader);
|
||||
bits = new int[getWidth() * getHeight()];
|
||||
@@ -65,7 +61,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
// This is slightly obscure, and should probably be moved..
|
||||
Hashtable<String, Object> properties = null;
|
||||
if (entry instanceof DirectoryEntry.CUREntry) {
|
||||
properties = new Hashtable<String, Object>(1);
|
||||
properties = new Hashtable<>(1);
|
||||
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
||||
}
|
||||
|
||||
@@ -89,8 +85,6 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
|
||||
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
|
||||
|
||||
//System.out.println("Image: " + image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
@@ -100,40 +94,40 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
IndexColorModel createColorModel() {
|
||||
// NOTE: This is a hack to make room for transparent pixel for mask
|
||||
int bits = getBitCount();
|
||||
|
||||
|
||||
int colors = this.colors.length;
|
||||
int trans = -1;
|
||||
int transparent = -1;
|
||||
|
||||
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
|
||||
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
|
||||
if (colors > (1 << getBitCount())) {
|
||||
int index = findTransIndexMaybeRemap(this.colors, this.bits);
|
||||
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
||||
|
||||
if (index == -1) {
|
||||
// No duplicate found, increase bitcount
|
||||
bits++;
|
||||
trans = this.colors.length - 1;
|
||||
transparent = this.colors.length - 1;
|
||||
}
|
||||
else {
|
||||
// Found a duplicate, use it as trans
|
||||
trans = index;
|
||||
// Found a duplicate, use it as transparent
|
||||
transparent = index;
|
||||
colors--;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
||||
return new InverseColorMapIndexColorModel(
|
||||
bits, colors, this.colors, 0, true, trans,
|
||||
return new IndexColorModel(
|
||||
bits, colors, this.colors, 0, true, transparent,
|
||||
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
||||
);
|
||||
}
|
||||
|
||||
private static int findTransIndexMaybeRemap(final int[] pColors, final int[] pBits) {
|
||||
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
||||
// Look for unused colors, to use as transparent
|
||||
final boolean[] used = new boolean[pColors.length - 1];
|
||||
for (int pBit : pBits) {
|
||||
if (!used[pBit]) {
|
||||
used[pBit] = true;
|
||||
boolean[] used = new boolean[colors.length - 1];
|
||||
for (int bit : bits) {
|
||||
if (!used[bit]) {
|
||||
used[bit] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,38 +138,35 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
}
|
||||
|
||||
// Try to find duplicates in colormap, and remap
|
||||
int trans = -1;
|
||||
int transparent = -1;
|
||||
int duplicate = -1;
|
||||
for (int i = 0; trans == -1 && i < pColors.length - 1; i++) {
|
||||
for (int j = i + 1; j < pColors.length - 1; j++) {
|
||||
if (pColors[i] == pColors[j]) {
|
||||
trans = j;
|
||||
for (int i = 0; transparent == -1 && i < colors.length - 1; i++) {
|
||||
for (int j = i + 1; j < colors.length - 1; j++) {
|
||||
if (colors[i] == colors[j]) {
|
||||
transparent = j;
|
||||
duplicate = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trans != -1) {
|
||||
if (transparent != -1) {
|
||||
// Remap duplicate
|
||||
for (int i = 0; i < pBits.length; i++) {
|
||||
if (pBits[i] == trans) {
|
||||
pBits[i] = duplicate;
|
||||
for (int i = 0; i < bits.length; i++) {
|
||||
if (bits[i] == transparent) {
|
||||
bits[i] = duplicate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return trans;
|
||||
return transparent;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
if (image == null) {
|
||||
image = createImageIndexed();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setMask(final BitmapMask pMask) {
|
||||
mask = pMask;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -38,19 +38,19 @@ import java.awt.image.BufferedImage;
|
||||
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
class BitmapMask extends BitmapDescriptor {
|
||||
protected final BitmapIndexed mask;
|
||||
protected final BitmapIndexed bitMask;
|
||||
|
||||
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
||||
super(pParent, pHeader);
|
||||
mask = new BitmapIndexed(pParent, pHeader);
|
||||
bitMask = new BitmapIndexed(pParent, pHeader);
|
||||
}
|
||||
|
||||
boolean isTransparent(final int pX, final int pY) {
|
||||
// NOTE: 1: Fully transparent, 0: Opaque...
|
||||
return mask.bits[pX + pY * getWidth()] != 0;
|
||||
return bitMask.bits[pX + pY * getWidth()] != 0;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return mask.getImage();
|
||||
return bitMask.getImage();
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -28,7 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.WritableRaster;
|
||||
|
||||
/**
|
||||
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
||||
@@ -43,6 +45,38 @@ class BitmapRGB extends BitmapDescriptor {
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
// Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
|
||||
// might still have bitmask, but we don't read or use it.
|
||||
if (mask != null) {
|
||||
image = createMaskedImage();
|
||||
mask = null;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private BufferedImage createMaskedImage() {
|
||||
BufferedImage masked = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
|
||||
|
||||
Graphics2D graphics = masked.createGraphics();
|
||||
try {
|
||||
graphics.drawImage(image, 0, 0, null);
|
||||
}
|
||||
finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
|
||||
WritableRaster alphaRaster = masked.getAlphaRaster();
|
||||
|
||||
byte[] trans = {0x0};
|
||||
for (int y = 0; y < getHeight(); y++) {
|
||||
for (int x = 0; x < getWidth(); x++) {
|
||||
if (mask.isTransparent(x, y)) {
|
||||
alphaRaster.setDataElements(x, y, trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return masked;
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -1,3 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
|
||||
+35
-14
@@ -66,14 +66,15 @@ import java.util.List;
|
||||
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
||||
// TODO: Support loading icons from DLLs, see
|
||||
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
|
||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry
|
||||
// (seem impossible as the PNGs are all true color)
|
||||
abstract class DIBImageReader extends ImageReaderBase {
|
||||
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
||||
private Directory directory;
|
||||
|
||||
// TODO: Review these, make sure we don't have a memory leak
|
||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<DirectoryEntry, DIBHeader>();
|
||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
|
||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
||||
|
||||
private ImageReader pngImageReader;
|
||||
|
||||
@@ -101,7 +102,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
return getImageTypesPNG(entry);
|
||||
}
|
||||
|
||||
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
|
||||
List<ImageTypeSpecifier> types = new ArrayList<>();
|
||||
DIBHeader header = getHeader(entry);
|
||||
|
||||
// Use data from header to create specifier
|
||||
@@ -121,10 +122,13 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
|
||||
break;
|
||||
case 16:
|
||||
// TODO: May have mask?!
|
||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||
break;
|
||||
case 24:
|
||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
specifier = new BitmapRGB(entry, header).hasMask()
|
||||
? ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)
|
||||
: ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
break;
|
||||
case 32:
|
||||
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||
@@ -184,6 +188,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
Graphics2D g = destination.createGraphics();
|
||||
|
||||
try {
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.drawImage(image, 0, 0, null);
|
||||
@@ -335,7 +340,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||
readBitmapIndexed1(mask.mask, true);
|
||||
readBitmapIndexed1(mask.bitMask, true);
|
||||
pBitmap.setMask(mask);
|
||||
}
|
||||
|
||||
@@ -370,7 +375,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: If we are reading the mask, we don't abort or progress
|
||||
// NOTE: If we are reading the mask, we don't abort or report progress
|
||||
if (!pAsMask) {
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -455,7 +460,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||
|
||||
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
||||
// Will create TYPE_USHORT_555;
|
||||
// Will create TYPE_USHORT_555
|
||||
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
||||
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
||||
WritableRaster raster = Raster.createPackedRaster(
|
||||
@@ -480,6 +485,8 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
|
||||
// TODO: Might be mask!?
|
||||
}
|
||||
|
||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
||||
@@ -494,16 +501,19 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||
);
|
||||
|
||||
int scanlineStride = pBitmap.getWidth() * 3;
|
||||
// BMP rows are padded to 4 byte boundary
|
||||
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
||||
|
||||
WritableRaster raster = Raster.createInterleavedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
|
||||
);
|
||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
|
||||
|
||||
// TODO: Possibly read padding byte here!
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
|
||||
imageInput.readFully(pixels, offset, scanlineStride);
|
||||
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -512,6 +522,14 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
|
||||
// 24 bit icons usually have a bit mask
|
||||
if (pBitmap.hasMask()) {
|
||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||
readBitmapIndexed1(mask.bitMask, true);
|
||||
|
||||
pBitmap.setMask(mask);
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
||||
@@ -535,6 +553,9 @@ abstract class DIBImageReader extends ImageReaderBase {
|
||||
}
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
|
||||
// There might be a mask here as well, but we'll ignore it,
|
||||
// and use the 8 bit alpha channel in the ARGB pixel data
|
||||
}
|
||||
|
||||
private Directory getDirectory() throws IOException {
|
||||
|
||||
+28
@@ -1,3 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
|
||||
+9
-7
@@ -1,6 +1,6 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -19,10 +19,12 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNoException;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
@@ -34,7 +36,7 @@ import static org.mockito.Mockito.*;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BMPImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageReader> {
|
||||
public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader> {
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
// BMP Suite "Good"
|
||||
@@ -90,7 +92,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageRead
|
||||
new TestData(getClassLoaderResource("/os2/money-256-(os2).bmp"), new Dimension(455, 341)),
|
||||
new TestData(getClassLoaderResource("/os2/money-24bit-os2.bmp"), new Dimension(455, 341)),
|
||||
|
||||
// Vaious other samples
|
||||
// Various other samples
|
||||
new TestData(getClassLoaderResource("/bmp/Blue Lace 16.bmp"), new Dimension(48, 48)),
|
||||
new TestData(getClassLoaderResource("/bmp/blauesglas_mono.bmp"), new Dimension(301, 331)),
|
||||
new TestData(getClassLoaderResource("/bmp/blauesglas_4.bmp"), new Dimension(301, 331)),
|
||||
@@ -124,7 +126,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageRead
|
||||
}
|
||||
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("bmp");
|
||||
return Collections.singletonList("bmp");
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes() {
|
||||
@@ -132,7 +134,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageRead
|
||||
}
|
||||
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/bmp");
|
||||
return Collections.singletonList("image/bmp");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -234,7 +236,6 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageRead
|
||||
|
||||
@Test
|
||||
public void testMetadataEqualsJRE() throws IOException, URISyntaxException {
|
||||
// Ignore this test if not on an Oracle JRE (com.sun...BMPImageReader not available)
|
||||
ImageReader jreReader;
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -243,8 +244,9 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase<BMPImageRead
|
||||
jreReader = constructor.newInstance(new Object[] {null});
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.err.println("WARNING: Skipping metadata tests: " + e);
|
||||
e.printStackTrace();
|
||||
// Ignore this test if not on an Oracle JRE (com.sun...BMPImageReader not available)
|
||||
assumeNoException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* BMPProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: BMPProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class BMPProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new BMPProviderInfo();
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -1,6 +1,6 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
@@ -21,7 +22,7 @@ import static org.junit.Assert.*;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class CURImageReaderTest extends ImageReaderAbstractTestCase<CURImageReader> {
|
||||
public class CURImageReaderTest extends ImageReaderAbstractTest<CURImageReader> {
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)),
|
||||
@@ -43,11 +44,11 @@ public class CURImageReaderTest extends ImageReaderAbstractTestCase<CURImageRead
|
||||
}
|
||||
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("cur");
|
||||
return Collections.singletonList("cur");
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("cur");
|
||||
return Collections.singletonList("cur");
|
||||
}
|
||||
|
||||
protected List<String> getMIMETypes() {
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* CURProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: CURProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class CURProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new CURProviderInfo();
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -1,6 +1,6 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -8,6 +8,7 @@ import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -17,7 +18,7 @@ import java.util.List;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class ICOImageReaderTest extends ImageReaderAbstractTestCase<ICOImageReader> {
|
||||
public class ICOImageReaderTest extends ImageReaderAbstractTest<ICOImageReader> {
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(
|
||||
@@ -38,7 +39,9 @@ public class ICOImageReaderTest extends ImageReaderAbstractTestCase<ICOImageRead
|
||||
new Dimension(16, 16), new Dimension(16, 16), new Dimension(32, 32), new Dimension(32, 32),
|
||||
new Dimension(48, 48), new Dimension(48, 48), new Dimension(256, 256), new Dimension(256, 256),
|
||||
new Dimension(16, 16), new Dimension(32, 32), new Dimension(48, 48), new Dimension(256, 256)
|
||||
)
|
||||
),
|
||||
// Problematic icon that reports 24 bit in the descriptor, but has separate 1 bit ''mask (height 2 x icon height)!
|
||||
new TestData(getClassLoaderResource("/ico/rgb24bitmask.ico"), new Dimension(32, 32))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,11 +59,11 @@ public class ICOImageReaderTest extends ImageReaderAbstractTestCase<ICOImageRead
|
||||
}
|
||||
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("ico");
|
||||
return Collections.singletonList("ico");
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("ico");
|
||||
return Collections.singletonList("ico");
|
||||
}
|
||||
|
||||
protected List<String> getMIMETypes() {
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* ICOProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ICOProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class ICOProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new ICOProviderInfo();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
@@ -1,12 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-clippath</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||
@@ -22,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||
|
||||
+32
-30
@@ -26,7 +26,7 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
@@ -42,13 +42,15 @@ import java.util.Arrays;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$
|
||||
*/
|
||||
abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
// TODO: Move to core...
|
||||
public abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
protected AbstractMetadata(final boolean standardFormatSupported,
|
||||
final String nativeFormatName, final String nativeFormatClassName,
|
||||
final String[] extraFormatNames, final String[] extraFormatClassNames) {
|
||||
super(standardFormatSupported, nativeFormatName, nativeFormatClassName, extraFormatNames, extraFormatClassNames);
|
||||
}
|
||||
|
||||
protected AbstractMetadata(final boolean pStandardFormatSupported,
|
||||
final String pNativeFormatName, final String pNativeFormatClassName,
|
||||
final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) {
|
||||
super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames);
|
||||
protected AbstractMetadata() {
|
||||
super(true, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,36 +65,38 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getAsTree(final String pFormatName) {
|
||||
validateFormatName(pFormatName);
|
||||
public Node getAsTree(final String formatName) {
|
||||
validateFormatName(formatName);
|
||||
|
||||
if (pFormatName.equals(nativeMetadataFormatName)) {
|
||||
if (formatName.equals(nativeMetadataFormatName)) {
|
||||
return getNativeTree();
|
||||
}
|
||||
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
else if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
|
||||
// TODO: What about extra formats??
|
||||
throw new AssertionError("Unreachable");
|
||||
// Subclasses that supports extra formats need to check for these formats themselves...
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation that throws {@code UnsupportedOperationException}.
|
||||
* Subclasses that supports formats other than standard metadata should override this method.
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
protected Node getNativeTree() {
|
||||
throw new UnsupportedOperationException("getNativeTree");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
|
||||
public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
|
||||
assertMutable();
|
||||
|
||||
validateFormatName(pFormatName);
|
||||
validateFormatName(formatName);
|
||||
|
||||
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
|
||||
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
|
||||
}
|
||||
|
||||
Node node = pRoot.getFirstChild();
|
||||
while (node != null) {
|
||||
// TODO: Merge values from node into this
|
||||
|
||||
// Move to the next sibling
|
||||
node = node.getNextSibling();
|
||||
if (!root.getNodeName().equals(formatName)) {
|
||||
throw new IIOInvalidTreeException("Root must be " + formatName, root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,21 +116,19 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Node getNativeTree();
|
||||
|
||||
protected final void validateFormatName(final String pFormatName) {
|
||||
protected final void validateFormatName(final String formatName) {
|
||||
String[] metadataFormatNames = getMetadataFormatNames();
|
||||
|
||||
if (metadataFormatNames != null) {
|
||||
for (String metadataFormatName : metadataFormatNames) {
|
||||
if (metadataFormatName.equals(pFormatName)) {
|
||||
if (metadataFormatName.equals(formatName)) {
|
||||
return; // Found, we're ok!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
|
||||
String.format("Bad format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import com.twelvemonkeys.image.BufferedImageIcon;
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.*;
|
||||
@@ -37,6 +38,9 @@ import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.awt.event.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
@@ -424,6 +428,8 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
static final String ZOOM_OUT = "zoom-out";
|
||||
static final String ZOOM_ACTUAL = "zoom-actual";
|
||||
|
||||
private BufferedImage image;
|
||||
|
||||
Paint backgroundPaint;
|
||||
|
||||
final Paint checkeredBG;
|
||||
@@ -434,6 +440,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
setOpaque(false);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
|
||||
image = pImage;
|
||||
checkeredBG = createTexture();
|
||||
|
||||
// For indexed color, default to the color of the transparent pixel, if any
|
||||
@@ -441,7 +448,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
|
||||
backgroundPaint = defaultBG != null ? defaultBG : checkeredBG;
|
||||
|
||||
setupActions(pImage);
|
||||
setupActions();
|
||||
setComponentPopupMenu(createPopupMenu());
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
@@ -451,16 +458,59 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setTransferHandler(new TransferHandler() {
|
||||
@Override
|
||||
public int getSourceActions(JComponent c) {
|
||||
return COPY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Transferable createTransferable(JComponent c) {
|
||||
return new ImageTransferable(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean importData(JComponent comp, Transferable t) {
|
||||
if (canImport(comp, t.getTransferDataFlavors())) {
|
||||
try {
|
||||
Image transferData = (Image) t.getTransferData(DataFlavor.imageFlavor);
|
||||
image = ImageUtil.toBuffered(transferData);
|
||||
setIcon(new BufferedImageIcon(image));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (UnsupportedFlavorException | IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
|
||||
for (DataFlavor flavor : transferFlavors) {
|
||||
if (flavor.equals(DataFlavor.imageFlavor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupActions(final BufferedImage pImage) {
|
||||
private void setupActions() {
|
||||
// Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always...
|
||||
bindAction(new ZoomAction("Zoom in", pImage, 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));
|
||||
bindAction(new ZoomAction("Zoom out", pImage, .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
|
||||
bindAction(new ZoomAction("Zoom actual", pImage), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
|
||||
bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));
|
||||
bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
|
||||
bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
|
||||
|
||||
bindAction(TransferHandler.getCopyAction(), (String) TransferHandler.getCopyAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
bindAction(TransferHandler.getPasteAction(), (String) TransferHandler.getPasteAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
}
|
||||
|
||||
private void bindAction(final AbstractAction action, final String key, final KeyStroke... keyStrokes) {
|
||||
private void bindAction(final Action action, final String key, final KeyStroke... keyStrokes) {
|
||||
for (KeyStroke keyStroke : keyStrokes) {
|
||||
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, key);
|
||||
}
|
||||
@@ -588,20 +638,18 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
|
||||
private class ZoomAction extends AbstractAction {
|
||||
private final BufferedImage image;
|
||||
private final double zoomFactor;
|
||||
|
||||
public ZoomAction(final String name, final BufferedImage image, final double zoomFactor) {
|
||||
public ZoomAction(final String name, final double zoomFactor) {
|
||||
super(name);
|
||||
this.image = image;
|
||||
this.zoomFactor = zoomFactor;
|
||||
}
|
||||
|
||||
public ZoomAction(final String name, final BufferedImage image) {
|
||||
this(name, image, 0);
|
||||
public ZoomAction(final String name) {
|
||||
this(name, 0);
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (zoomFactor <= 0) {
|
||||
setIcon(new BufferedImageIcon(image));
|
||||
}
|
||||
@@ -614,6 +662,33 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImageTransferable implements Transferable {
|
||||
private final BufferedImage image;
|
||||
|
||||
public ImageTransferable(final BufferedImage image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataFlavor[] getTransferDataFlavors() {
|
||||
return new DataFlavor[] {DataFlavor.imageFlavor};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDataFlavorSupported(final DataFlavor flavor) {
|
||||
return DataFlavor.imageFlavor.equals(flavor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException, IOException {
|
||||
if (isDataFlavorSupported(flavor)) {
|
||||
return image;
|
||||
}
|
||||
|
||||
throw new UnsupportedFlavorException(flavor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExitIfNoWindowPresentHandler extends WindowAdapter {
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
/**
|
||||
* Converts between CIE L*a*b* and sRGB color spaces.
|
||||
*/
|
||||
// Code adapted from ImageJ's Color_Space_Converter.java (Public Domain):
|
||||
// http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
|
||||
public final class CIELabColorConverter {
|
||||
// TODO: Create interface in the color package?
|
||||
// TODO: Make YCbCr/YCCK -> RGB/CMYK implement same interface?
|
||||
|
||||
public enum Illuminant {
|
||||
D50(new float[] {96.4212f, 100.0f, 82.5188f}),
|
||||
D65(new float[] {95.0429f, 100.0f, 108.8900f});
|
||||
|
||||
private final float[] whitePoint;
|
||||
|
||||
Illuminant(final float[] wp) {
|
||||
whitePoint = Validate.isTrue(wp != null && wp.length == 3, wp, "Bad white point definition: %s");
|
||||
}
|
||||
|
||||
public float[] getWhitePoint() {
|
||||
return whitePoint;
|
||||
}
|
||||
}
|
||||
|
||||
private final float[] whitePoint;
|
||||
|
||||
public CIELabColorConverter(final Illuminant illuminant) {
|
||||
whitePoint = Validate.notNull(illuminant, "illuminant").getWhitePoint();
|
||||
}
|
||||
|
||||
private float clamp(float x) {
|
||||
if (x < 0.0f) {
|
||||
return 0.0f;
|
||||
}
|
||||
else if (x > 255.0f) {
|
||||
return 255.0f;
|
||||
}
|
||||
else {
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
public void toRGB(float l, float a, float b, float[] rgbResult) {
|
||||
XYZtoRGB(LABtoXYZ(l, a, b, rgbResult), rgbResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert LAB to XYZ.
|
||||
* @param L
|
||||
* @param a
|
||||
* @param b
|
||||
* @return XYZ values
|
||||
*/
|
||||
private float[] LABtoXYZ(float L, float a, float b, float[] xyzResult) {
|
||||
// Significant speedup: Removing Math.pow
|
||||
float y = (L + 16.0f) / 116.0f;
|
||||
float y3 = y * y * y; // Math.pow(y, 3.0);
|
||||
float x = (a / 500.0f) + y;
|
||||
float x3 = x * x * x; // Math.pow(x, 3.0);
|
||||
float z = y - (b / 200.0f);
|
||||
float z3 = z * z * z; // Math.pow(z, 3.0);
|
||||
|
||||
if (y3 > 0.008856f) {
|
||||
y = y3;
|
||||
}
|
||||
else {
|
||||
y = (y - (16.0f / 116.0f)) / 7.787f;
|
||||
}
|
||||
|
||||
if (x3 > 0.008856f) {
|
||||
x = x3;
|
||||
}
|
||||
else {
|
||||
x = (x - (16.0f / 116.0f)) / 7.787f;
|
||||
}
|
||||
|
||||
if (z3 > 0.008856f) {
|
||||
z = z3;
|
||||
}
|
||||
else {
|
||||
z = (z - (16.0f / 116.0f)) / 7.787f;
|
||||
}
|
||||
|
||||
xyzResult[0] = x * whitePoint[0];
|
||||
xyzResult[1] = y * whitePoint[1];
|
||||
xyzResult[2] = z * whitePoint[2];
|
||||
|
||||
return xyzResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert XYZ to RGB
|
||||
* @param xyz
|
||||
* @return RGB values
|
||||
*/
|
||||
private float[] XYZtoRGB(final float[] xyz, final float[] rgbResult) {
|
||||
return XYZtoRGB(xyz[0], xyz[1], xyz[2], rgbResult);
|
||||
}
|
||||
|
||||
private float[] XYZtoRGB(final float X, final float Y, final float Z, float[] rgbResult) {
|
||||
float x = X / 100.0f;
|
||||
float y = Y / 100.0f;
|
||||
float z = Z / 100.0f;
|
||||
|
||||
float r = x * 3.2406f + y * -1.5372f + z * -0.4986f;
|
||||
float g = x * -0.9689f + y * 1.8758f + z * 0.0415f;
|
||||
float b = x * 0.0557f + y * -0.2040f + z * 1.0570f;
|
||||
|
||||
// assume sRGB
|
||||
if (r > 0.0031308f) {
|
||||
r = ((1.055f * (float) pow(r, 1.0 / 2.4)) - 0.055f);
|
||||
}
|
||||
else {
|
||||
r = (r * 12.92f);
|
||||
}
|
||||
|
||||
if (g > 0.0031308f) {
|
||||
g = ((1.055f * (float) pow(g, 1.0 / 2.4)) - 0.055f);
|
||||
}
|
||||
else {
|
||||
g = (g * 12.92f);
|
||||
}
|
||||
|
||||
if (b > 0.0031308f) {
|
||||
b = ((1.055f * (float) pow(b, 1.0 / 2.4)) - 0.055f);
|
||||
}
|
||||
else {
|
||||
b = (b * 12.92f);
|
||||
}
|
||||
|
||||
// convert 0..1 into 0..255
|
||||
rgbResult[0] = clamp(r * 255);
|
||||
rgbResult[1] = clamp(g * 255);
|
||||
rgbResult[2] = clamp(b * 255);
|
||||
|
||||
return rgbResult;
|
||||
}
|
||||
|
||||
// TODO: Test, to figure out if accuracy is good enough.
|
||||
// Visual inspection looks good! The author claims 5-12% error, worst case up to 25%...
|
||||
// http://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/
|
||||
static double pow(final double a, final double b) {
|
||||
long tmp = Double.doubleToLongBits(a);
|
||||
long tmp2 = (long) (b * (tmp - 4606921280493453312L)) + 4606921280493453312L;
|
||||
return Double.longBitsToDouble(tmp2);
|
||||
}
|
||||
}
|
||||
+48
-15
@@ -52,7 +52,7 @@ import java.util.Properties;
|
||||
* <p />
|
||||
* Color profiles may be configured by placing a property-file
|
||||
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||
* on the classpath, specifying the full path to the profile.
|
||||
* on the classpath, specifying the full path to the profiles.
|
||||
* ICC color profiles are probably already present on your system, or
|
||||
* can be downloaded from
|
||||
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||
@@ -84,11 +84,11 @@ public final class ColorSpaces {
|
||||
public static final int CS_GENERIC_CMYK = 5001;
|
||||
|
||||
// Weak references to hold the color spaces while cached
|
||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<ICC_Profile>(null);
|
||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<ICC_Profile>(null);
|
||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
||||
|
||||
// Cache for the latest used color spaces
|
||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<Key, ICC_ColorSpace>(10);
|
||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
|
||||
|
||||
private ColorSpaces() {}
|
||||
|
||||
@@ -100,7 +100,8 @@ public final class ColorSpaces {
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return an ICC color space
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}.
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
*/
|
||||
public static ICC_ColorSpace createColorSpace(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
@@ -161,6 +162,11 @@ public final class ColorSpaces {
|
||||
|
||||
if (cs == null) {
|
||||
cs = new ICC_ColorSpace(profile);
|
||||
|
||||
// Validate the color space, to avoid caching bad color spaces
|
||||
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
||||
cs.fromRGB(new float[] {1f, 0f, 0f});
|
||||
|
||||
cache.put(key, cs);
|
||||
}
|
||||
|
||||
@@ -195,7 +201,7 @@ public final class ColorSpaces {
|
||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
public static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// NOTE:
|
||||
@@ -213,6 +219,26 @@ public final class ColorSpaces {
|
||||
return data[ICC_Profile.icHdrRenderingIntent] != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is valid.
|
||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p />
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code profile} if valid.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
*/
|
||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||
createColorSpace(profile); // Creating a color space will fail if the profile is bad
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color space specified by the given color space constant.
|
||||
* <p />
|
||||
@@ -249,7 +275,7 @@ public final class ColorSpaces {
|
||||
}
|
||||
}
|
||||
|
||||
adobeRGB1998 = new WeakReference<ICC_Profile>(profile);
|
||||
adobeRGB1998 = new WeakReference<>(profile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +298,7 @@ public final class ColorSpaces {
|
||||
return CMYKColorSpace.getInstance();
|
||||
}
|
||||
|
||||
genericCMYK = new WeakReference<ICC_Profile>(profile);
|
||||
genericCMYK = new WeakReference<>(profile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +343,7 @@ public final class ColorSpaces {
|
||||
try {
|
||||
return ICC_Profile.getInstance(profilePath);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
catch (SecurityException | IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
@@ -367,15 +393,18 @@ public final class ColorSpaces {
|
||||
}
|
||||
|
||||
private static class Profiles {
|
||||
private static final Properties PROFILES = loadProfiles(Platform.os());
|
||||
private static final Properties PROFILES = loadProfiles();
|
||||
|
||||
private static Properties loadProfiles(final Platform.OperatingSystem os) {
|
||||
private static Properties loadProfiles() {
|
||||
Properties systemDefaults;
|
||||
|
||||
try {
|
||||
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os.id());
|
||||
systemDefaults = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
|
||||
);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
catch (SecurityException | IOException ignore) {
|
||||
System.err.printf(
|
||||
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||
ignore.getMessage()
|
||||
@@ -392,10 +421,14 @@ public final class ColorSpaces {
|
||||
Properties profiles = new Properties(systemDefaults);
|
||||
|
||||
try {
|
||||
Properties userOverrides = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles");
|
||||
Properties userOverrides = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles"
|
||||
);
|
||||
profiles.putAll(userOverrides);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
catch (SecurityException | IOException ignore) {
|
||||
// Most likely, this file won't be there
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import java.awt.color.ICC_Profile;
|
||||
interface ICCProfileSanitizer {
|
||||
void fixProfile(ICC_Profile profile, byte[] profileHeader);
|
||||
|
||||
static class Factory {
|
||||
class Factory {
|
||||
static ICCProfileSanitizer get() {
|
||||
// Strategy pattern:
|
||||
// - KCMSSanitizerStrategy - Current behaviour, default for Java 1.6 and Oracle JRE 1.7
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
|
||||
/**
|
||||
* ComponentColorModel subclass that correctly handles full 16 bit {@code TYPE_SHORT} signed integral samples.
|
||||
**
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: UInt32ColorModel.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
public final class Int16ComponentColorModel extends ComponentColorModel {
|
||||
private final ComponentColorModel delegate;
|
||||
|
||||
public Int16ComponentColorModel(final ColorSpace cs, final boolean hasAlpha, boolean isAlphaPremultiplied) {
|
||||
super(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_SHORT);
|
||||
|
||||
delegate = new ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_USHORT);
|
||||
}
|
||||
|
||||
private void remap(final short[] s, final int i) {
|
||||
// MIN ... -1 -> 0 ... MAX
|
||||
// 0 ... MAX -> MIN ... -1
|
||||
short sample = s[i];
|
||||
|
||||
if (sample < 0) {
|
||||
s[i] = (short) (sample - Short.MIN_VALUE);
|
||||
}
|
||||
else {
|
||||
s[i] = (short) (sample + Short.MIN_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(final Object inData) {
|
||||
remap((short[]) inData, 0);
|
||||
|
||||
return delegate.getRed(inData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(final Object inData) {
|
||||
remap((short[]) inData, 1);
|
||||
|
||||
return delegate.getGreen(inData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(final Object inData) {
|
||||
remap((short[]) inData, 2);
|
||||
|
||||
return delegate.getBlue(inData);
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
/**
|
||||
* Fast YCbCr to RGB conversion.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author Original code by Werner Randelshofer (used by permission).
|
||||
*/
|
||||
public final class YCbCrConverter {
|
||||
/**
|
||||
* Define tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private final static int SCALEBITS = 16;
|
||||
private final static int MAXJSAMPLE = 255;
|
||||
private final static int CENTERJSAMPLE = 128;
|
||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||
|
||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (ColorSpaces.DEBUG) {
|
||||
System.err.println("Building YCC conversion table");
|
||||
}
|
||||
|
||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||
// Cr=>R value is nearest int to 1.40200 * x
|
||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cb=>B value is nearest int to 1.77200 * x
|
||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cr=>G value is scaled-up -0.71414 * x
|
||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||
// Cb=>G value is scaled-up -0.34414 * x
|
||||
// We also add in ONE_HALF so that need not do it in inner loop
|
||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
|
||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
|
||||
double y = (yCbCr[offset] & 0xff);
|
||||
double cb = (yCbCr[offset + 1] & 0xff) - 128;
|
||||
double cr = (yCbCr[offset + 2] & 0xff) - 128;
|
||||
|
||||
double lumaRed = coefficients[0];
|
||||
double lumaGreen = coefficients[1];
|
||||
double lumaBlue = coefficients[2];
|
||||
|
||||
int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
|
||||
int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
|
||||
int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
|
||||
|
||||
rgb[offset] = clamp(red);
|
||||
rgb[offset + 2] = clamp(blue);
|
||||
rgb[offset + 1] = clamp(green);
|
||||
}
|
||||
|
||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
|
||||
rgb[offset] = clamp(y + Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||
}
|
||||
|
||||
private static byte clamp(int val) {
|
||||
return (byte) Math.max(0, Math.min(255, val));
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class ProviderInfo {
|
||||
return name.startsWith("com.twelvemonkeys") ? "TwelveMonkeys" : name;
|
||||
}
|
||||
|
||||
private String fakeVersion(Package pPackage) {
|
||||
private String fakeVersion(final Package pPackage) {
|
||||
String name = pPackage.getName();
|
||||
return name.startsWith("com.twelvemonkeys") ? "DEV" : "Unspecified";
|
||||
}
|
||||
|
||||
+33
-4
@@ -1,3 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.spi;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -24,7 +52,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
private final String[] writerSpiClassNames;
|
||||
private final Class[] outputTypes = new Class[] {ImageOutputStream.class};
|
||||
private final boolean supportsStandardStreamMetadata;
|
||||
private final String nativeStreameMetadataFormatName;
|
||||
private final String nativeStreamMetadataFormatName;
|
||||
private final String nativeStreamMetadataFormatClassName;
|
||||
private final String[] extraStreamMetadataFormatNames;
|
||||
private final String[] extraStreamMetadataFormatClassNames;
|
||||
@@ -38,7 +66,8 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
* Creates a provider information instance based on the given class.
|
||||
*
|
||||
* @param infoClass the class to get provider information from.
|
||||
* The provider info will be taken from the class' package. @throws IllegalArgumentException if {@code pPackage == null}
|
||||
* The provider info will be taken from the class' package.
|
||||
* @throws IllegalArgumentException if {@code pPackage == null}
|
||||
*/
|
||||
protected ReaderWriterProviderInfo(final Class<? extends ReaderWriterProviderInfo> infoClass,
|
||||
final String[] formatNames,
|
||||
@@ -68,7 +97,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
this.writerClassName = writerClassName;
|
||||
this.writerSpiClassNames = writerSpiClassNames;
|
||||
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
|
||||
this.nativeStreameMetadataFormatName = nativeStreameMetadataFormatName;
|
||||
this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
|
||||
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
|
||||
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
|
||||
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
|
||||
@@ -120,7 +149,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
}
|
||||
|
||||
public String nativeStreamMetadataFormatName() {
|
||||
return nativeStreameMetadataFormatName;
|
||||
return nativeStreamMetadataFormatName;
|
||||
}
|
||||
|
||||
public String nativeStreamMetadataFormatClassName() {
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SubImageOutputStream.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: SubImageOutputStream.java,v 1.0 30/03/15 harald.kuhr Exp$
|
||||
*/
|
||||
public class SubImageOutputStream extends ImageOutputStreamImpl {
|
||||
private final ImageOutputStream stream;
|
||||
|
||||
public SubImageOutputStream(final ImageOutputStream stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
stream.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
stream.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return stream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return stream.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCached() {
|
||||
return stream.isCached();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCachedMemory() {
|
||||
return stream.isCachedMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCachedFile() {
|
||||
return stream.isCachedFile();
|
||||
}
|
||||
}
|
||||
+60
-5
@@ -28,14 +28,19 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* Factory class for creating {@code ImageTypeSpecifier}s.
|
||||
* In most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
|
||||
* Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
|
||||
* in most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
|
||||
*
|
||||
* @see javax.imageio.ImageTypeSpecifier
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
@@ -54,6 +59,20 @@ public final class ImageTypeSpecifiers {
|
||||
final int redMask, final int greenMask,
|
||||
final int blueMask, final int alphaMask,
|
||||
final int transferType, boolean isAlphaPremultiplied) {
|
||||
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
|
||||
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
|
||||
|
||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
||||
|
||||
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
|
||||
isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
|
||||
}
|
||||
|
||||
@@ -79,7 +98,11 @@ public final class ImageTypeSpecifiers {
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType) {
|
||||
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
|
||||
// As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
|
||||
return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
|
||||
}
|
||||
else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
|
||||
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
|
||||
}
|
||||
@@ -89,7 +112,11 @@ public final class ImageTypeSpecifiers {
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType, final boolean isAlphaPremultiplied) {
|
||||
if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
|
||||
// As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
|
||||
return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
|
||||
}
|
||||
else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
|
||||
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
|
||||
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
|
||||
}
|
||||
@@ -98,6 +125,34 @@ public final class ImageTypeSpecifiers {
|
||||
return ImageTypeSpecifier.createGrayscale(bits, dataType, false, isAlphaPremultiplied);
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createPackedGrayscale(final ColorSpace colorSpace, final int bits, final int dataType) {
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_GRAY, colorSpace, "ColorSpace must be TYPE_GRAY");
|
||||
isTrue(bits == 1 || bits == 2 || bits == 4, bits, "bits must be 1, 2, or 4: %s");
|
||||
isTrue(dataType == DataBuffer.TYPE_BYTE, dataType, "dataType must be TYPE_BYTE: %s");
|
||||
|
||||
int numEntries = 1 << bits;
|
||||
|
||||
byte[] arr = new byte[numEntries];
|
||||
byte[] arg = new byte[numEntries];
|
||||
byte[] arb = new byte[numEntries];
|
||||
|
||||
// Scale array values according to color profile..
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
float[] gray = new float[]{i / (float) (numEntries - 1)};
|
||||
float[] rgb = colorSpace.toRGB(gray);
|
||||
|
||||
arr[i] = (byte) (rgb[0] * 255);
|
||||
arg[i] = (byte) (rgb[1] * 255);
|
||||
arb[i] = (byte) (rgb[2]* 255);
|
||||
}
|
||||
|
||||
ColorModel colorModel = new IndexColorModel(bits, numEntries, arr, arg, arb);
|
||||
SampleModel sampleModel = new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, sampleModel);
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createIndexed(final byte[] redLUT, final byte[] greenLUT,
|
||||
final byte[] blueLUT, final byte[] alphaLUT,
|
||||
final int bits, final int dataType) {
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.Int16ComponentColorModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.PixelInterleavedSampleModel;
|
||||
|
||||
/**
|
||||
* ImageTypeSpecifier for interleaved 16 bit signed integral samples.
|
||||
*
|
||||
* @see com.twelvemonkeys.imageio.color.Int16ColorModel
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Int16ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
final class Int16ImageTypeSpecifier extends ImageTypeSpecifier {
|
||||
Int16ImageTypeSpecifier(final ColorSpace cs, int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) {
|
||||
super(
|
||||
new Int16ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied),
|
||||
new PixelInterleavedSampleModel(
|
||||
DataBuffer.TYPE_SHORT, 1, 1,
|
||||
cs.getNumComponents() + (hasAlpha ? 1 : 0),
|
||||
cs.getNumComponents() + (hasAlpha ? 1 : 0),
|
||||
bandOffsets
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
/**
|
||||
* CIELabColorConverterTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: CIELabColorConverterTest.java,v 1.0 22/10/15 harald.kuhr Exp$
|
||||
*/
|
||||
public class CIELabColorConverterTest {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testNoIllumninant() {
|
||||
new CIELabColorConverter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testD50() {
|
||||
CIELabColorConverter converter = new CIELabColorConverter(Illuminant.D50);
|
||||
float[] rgb = new float[3];
|
||||
|
||||
converter.toRGB(100, -128, -128, rgb);
|
||||
assertArrayEquals(new float[] {0, 255, 255}, rgb, 1);
|
||||
|
||||
converter.toRGB(100, 0, 0, rgb);
|
||||
assertArrayEquals(new float[] {255, 252, 220}, rgb, 5);
|
||||
|
||||
converter.toRGB(0, 0, 0, rgb);
|
||||
assertArrayEquals(new float[] {0, 0, 0}, rgb, 1);
|
||||
|
||||
converter.toRGB(100, 0, 127, rgb);
|
||||
assertArrayEquals(new float[] {255, 249, 0}, rgb, 5);
|
||||
|
||||
converter.toRGB(50, -128, 127, rgb);
|
||||
assertArrayEquals(new float[] {0, 152, 0}, rgb, 2);
|
||||
|
||||
converter.toRGB(50, 127, -128, rgb);
|
||||
assertArrayEquals(new float[] {222, 0, 255}, rgb, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testD65() {
|
||||
CIELabColorConverter converter = new CIELabColorConverter(Illuminant.D65);
|
||||
float[] rgb = new float[3];
|
||||
|
||||
converter.toRGB(100, -128, -128, rgb);
|
||||
assertArrayEquals(new float[] {0, 255, 255}, rgb, 1);
|
||||
|
||||
converter.toRGB(100, 0, 0, rgb);
|
||||
assertArrayEquals(new float[] {255, 252, 255}, rgb, 5);
|
||||
|
||||
converter.toRGB(0, 0, 0, rgb);
|
||||
assertArrayEquals(new float[] {0, 0, 0}, rgb, 1);
|
||||
|
||||
converter.toRGB(100, 0, 127, rgb);
|
||||
assertArrayEquals(new float[] {255, 250, 0}, rgb, 5);
|
||||
|
||||
converter.toRGB(50, -128, 127, rgb);
|
||||
assertArrayEquals(new float[] {0, 152, 0}, rgb, 3);
|
||||
|
||||
converter.toRGB(50, 127, -128, rgb);
|
||||
assertArrayEquals(new float[] {184, 0, 255}, rgb, 5);
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package com.twelvemonkeys.imageio.spi;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.matchers.TypeSafeMatcher;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.metadata.IIOMetadataFormat;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ReaderWriterProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ReaderWriterProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public abstract class ReaderWriterProviderInfoTest {
|
||||
|
||||
private final ReaderWriterProviderInfo providerInfo = createProviderInfo();
|
||||
|
||||
protected abstract ReaderWriterProviderInfo createProviderInfo();
|
||||
|
||||
protected final ReaderWriterProviderInfo getProviderInfo() {
|
||||
return providerInfo;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readerClassName() throws Exception {
|
||||
assertClassExists(providerInfo.readerClassName(), ImageReader.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readerSpiClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.readerSpiClassNames(), ImageReaderSpi.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inputTypes() throws Exception {
|
||||
assertNotNull(providerInfo.inputTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writerClassName() throws Exception {
|
||||
assertClassExists(providerInfo.writerClassName(), ImageWriter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writerSpiClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.writerSpiClassNames(), ImageWriterSpi.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outputTypes() throws Exception {
|
||||
assertNotNull(providerInfo.outputTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeStreamMetadataFormatClassName() throws Exception {
|
||||
assertClassExists(providerInfo.nativeStreamMetadataFormatClassName(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extraStreamMetadataFormatClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.extraStreamMetadataFormatClassNames(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeImageMetadataFormatClassName() throws Exception {
|
||||
assertClassExists(providerInfo.nativeImageMetadataFormatClassName(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extraImageMetadataFormatClassNames() throws Exception {
|
||||
assertClassesExist(providerInfo.extraImageMetadataFormatClassNames(), IIOMetadataFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatNames() {
|
||||
String[] names = providerInfo.formatNames();
|
||||
assertNotNull(names);
|
||||
assertFalse(names.length == 0);
|
||||
|
||||
List<String> list = asList(names);
|
||||
|
||||
for (String name : list) {
|
||||
assertNotNull(name);
|
||||
assertFalse(name.isEmpty());
|
||||
|
||||
assertTrue(list.contains(name.toLowerCase()));
|
||||
assertTrue(list.contains(name.toUpperCase()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suffixes() {
|
||||
String[] suffixes = providerInfo.suffixes();
|
||||
assertNotNull(suffixes);
|
||||
assertFalse(suffixes.length == 0);
|
||||
|
||||
for (String suffix : suffixes) {
|
||||
assertNotNull(suffix);
|
||||
assertFalse(suffix.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mimeTypes() {
|
||||
String[] mimeTypes = providerInfo.mimeTypes();
|
||||
assertNotNull(mimeTypes);
|
||||
assertFalse(mimeTypes.length == 0);
|
||||
|
||||
for (String mimeType : mimeTypes) {
|
||||
assertNotNull(mimeType);
|
||||
assertFalse(mimeType.isEmpty());
|
||||
|
||||
assertTrue(mimeType.length() > 1);
|
||||
assertTrue(mimeType.indexOf('/') > 0);
|
||||
assertTrue(mimeType.indexOf('/') < mimeType.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void assertClassExists(final String className, final Class<T> type) {
|
||||
if (className != null) {
|
||||
try {
|
||||
final Class<?> cl = Class.forName(className);
|
||||
|
||||
assertThat(cl, new TypeSafeMatcher<Class<?>>() {
|
||||
@Override
|
||||
public boolean matchesSafely(Class<?> item) {
|
||||
return type.isAssignableFrom(cl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("is subclass of ").appendValue(type);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
fail("Class not found: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void assertClassesExist(final String[] classNames, final Class<T> type) {
|
||||
if (classNames != null) {
|
||||
for (String className : classNames) {
|
||||
assertClassExists(className, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-21
@@ -57,13 +57,13 @@ import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* ImageReaderAbstractTestCase
|
||||
* ImageReaderAbstractTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ImageReaderAbstractTestCase.java,v 1.0 Apr 1, 2008 10:36:46 PM haraldk Exp$
|
||||
* @version $Id: ImageReaderAbstractTest.java,v 1.0 Apr 1, 2008 10:36:46 PM haraldk Exp$
|
||||
*/
|
||||
public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
||||
// TODO: Should we really test if the provider is installed?
|
||||
// - Pro: Tests the META-INF/services config
|
||||
// - Con: Not all providers should be installed at runtime...
|
||||
@@ -83,10 +83,7 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
try {
|
||||
return getReaderClass().newInstance();
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@@ -1002,7 +999,7 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
/*aspectRatio =*/ reader.getAspectRatio(-1);
|
||||
//assertEquals("Wrong aspect ratio", data.getDimension().width / (float) data.getDimension().height, aspectRatio, 0f);
|
||||
}
|
||||
catch (IndexOutOfBoundsException expected) {
|
||||
catch (IndexOutOfBoundsException ignore) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("Could not read image aspect ratio" + e);
|
||||
@@ -1391,7 +1388,7 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
reader.read(0, param);
|
||||
fail("Expected to throw exception with illegal type specifier");
|
||||
}
|
||||
catch (IIOException expected) {
|
||||
catch (IIOException | IllegalArgumentException expected) {
|
||||
// TODO: This is thrown by ImageReader.getDestination. But are we happy with that?
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
if (!(message.contains("destination") && message.contains("type"))) {
|
||||
@@ -1399,23 +1396,16 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
if (!(message.contains("destination") && message.contains("type"))) {
|
||||
// Allow this to bubble up, du to a bug in the Sun PNGImageReader
|
||||
throw expected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<ImageTypeSpecifier> createIllegalTypes(Iterator<ImageTypeSpecifier> pValidTypes) {
|
||||
List<ImageTypeSpecifier> allTypes = new ArrayList<ImageTypeSpecifier>();
|
||||
List<ImageTypeSpecifier> allTypes = new ArrayList<>();
|
||||
for (int i = BufferedImage.TYPE_INT_RGB; i < BufferedImage.TYPE_BYTE_INDEXED; i++) {
|
||||
allTypes.add(ImageTypeSpecifier.createFromBufferedImageType(i));
|
||||
}
|
||||
|
||||
List<ImageTypeSpecifier> illegalTypes = new ArrayList<ImageTypeSpecifier>(allTypes);
|
||||
List<ImageTypeSpecifier> illegalTypes = new ArrayList<>(allTypes);
|
||||
while (pValidTypes.hasNext()) {
|
||||
ImageTypeSpecifier valid = pValidTypes.next();
|
||||
boolean removed = illegalTypes.remove(valid);
|
||||
@@ -1454,6 +1444,7 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
assertEquals(reader.getHeight(0) + point.y, image.getHeight());
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void testSetDestinationOffsetNull() throws IOException {
|
||||
final ImageReader reader = createReader();
|
||||
@@ -1629,12 +1620,12 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
throw new IllegalArgumentException("input == null");
|
||||
}
|
||||
|
||||
sizes = new ArrayList<Dimension>();
|
||||
images = new ArrayList<BufferedImage>();
|
||||
sizes = new ArrayList<>();
|
||||
images = new ArrayList<>();
|
||||
|
||||
List<Dimension> sizes = pSizes;
|
||||
if (sizes == null) {
|
||||
sizes = new ArrayList<Dimension>();
|
||||
sizes = new ArrayList<>();
|
||||
if (pImages != null) {
|
||||
for (BufferedImage image : pImages) {
|
||||
sizes.add(new Dimension(image.getWidth(), image.getHeight()));
|
||||
@@ -1690,6 +1681,7 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> {
|
||||
return sizes.get(pIndex);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public BufferedImage getImage(final int pIndex) {
|
||||
return images.get(pIndex);
|
||||
}
|
||||
+78
-29
@@ -1,12 +1,11 @@
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -40,7 +39,7 @@ public class ImageTypeSpecifiersTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked() {
|
||||
public void testCreatePacked32() {
|
||||
// TYPE_INT_RGB
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
@@ -61,31 +60,70 @@ public class ImageTypeSpecifiersTest {
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked16() {
|
||||
// TYPE_USHORT_555_RGB
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "SHORT 555 RGB" (impossible for some reason)
|
||||
// assertEquals(
|
||||
// ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false),
|
||||
// ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false)
|
||||
// );
|
||||
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
|
||||
|
||||
// TYPE_USHORT_565_RGB
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "USHORT 4444 ARGB"
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
||||
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
|
||||
);
|
||||
// "USHORT 4444 ARGB PRE"
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
||||
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
|
||||
);
|
||||
|
||||
// Extra: Make sure color models bits is actually 16 (ImageTypeSpecifier equivalent returns 32)
|
||||
assertEquals(16, ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePacked8() {
|
||||
// "BYTE 332 RGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
|
||||
);
|
||||
// "BYTE 2222 ARGB"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
|
||||
);
|
||||
// "BYTE 2222 ARGB PRE"
|
||||
assertEquals(
|
||||
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
|
||||
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true)
|
||||
);
|
||||
|
||||
// Extra: Make sure color models bits is actually 8 (ImageTypeSpecifiers equivalent returns 32)
|
||||
assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize());
|
||||
}
|
||||
|
||||
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace,
|
||||
final int redMask, final int greenMask, final int blueMask, final int alphaMask,
|
||||
final int transferType, final boolean isAlphaPremultiplied) {
|
||||
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s");
|
||||
|
||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
||||
|
||||
ColorModel colorModel =
|
||||
new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -337,11 +375,7 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false), // NOTE: Unsigned TYPE_SHORT makes no sense...
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
|
||||
);
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true),
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
|
||||
);
|
||||
}
|
||||
@@ -400,19 +434,11 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, false),
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, false),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
|
||||
);
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, true),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
|
||||
);
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, false),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
|
||||
);
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, true),
|
||||
new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, true),
|
||||
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
|
||||
);
|
||||
}
|
||||
@@ -437,6 +463,30 @@ public class ImageTypeSpecifiersTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePackedGrayscale1() {
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(1, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 1, DataBuffer.TYPE_BYTE)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePackedGrayscale2() {
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(2, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 2, DataBuffer.TYPE_BYTE)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePackedGrayscale4() {
|
||||
assertEquals(
|
||||
ImageTypeSpecifier.createGrayscale(4, DataBuffer.TYPE_BYTE, false),
|
||||
ImageTypeSpecifiers.createPackedGrayscale(GRAY, 4, DataBuffer.TYPE_BYTE)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateIndexedByteArrays1to8() {
|
||||
for (int bits = 1; bits <= 8; bits <<= 1) {
|
||||
@@ -562,7 +612,6 @@ public class ImageTypeSpecifiersTest {
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static byte[] createByteLut(final int count) {
|
||||
byte[] lut = new byte[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
+4
-6
@@ -63,6 +63,7 @@ public abstract class ImageWriterAbstractTestCase {
|
||||
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
|
||||
protected abstract ImageWriter createImageWriter();
|
||||
@@ -120,23 +121,20 @@ public abstract class ImageWriterAbstractTestCase {
|
||||
|
||||
for (RenderedImage testData : getTestData()) {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
|
||||
writer.setOutput(stream);
|
||||
|
||||
try {
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||
writer.setOutput(stream);
|
||||
writer.write(drawSomething((BufferedImage) testData));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
stream.close(); // Force data to be written
|
||||
}
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void testWriteNull() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
- Rename to imageio-common?
|
||||
- Separate modules for more for more plugins
|
||||
- The BMP reader supports some special formats not supported by Sun reader
|
||||
- PNM package is pretty complete, but useless, as it's provided by Sun? License?
|
||||
- WBMP?
|
||||
- XBM?
|
||||
- Add stream support for NIO and NIO2 stuff? Path, ByteChannel
|
||||
- Faster RAF support (need to buffer the slow readShort/readInt/readLong/readUnsignedShort/readUnsignedInt)
|
||||
- See if it's possible to use FileImageInputStream for FileChannels and FileInputStream to avoid the caching
|
||||
- And the above for output streams too...
|
||||
DONE:
|
||||
- Split up into separate plugins (modules), to allow easier configuration
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-hdr</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||
<description>
|
||||
ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
/**
|
||||
* HDR.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDR.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
interface HDR {
|
||||
byte[] RADIANCE_MAGIC = new byte[] {'#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E'};
|
||||
byte[] RGBE_MAGIC = new byte[] {'#', '?', 'R', 'G', 'B', 'E'};
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* HDRHeader.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDRHeader.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class HDRHeader {
|
||||
private static final String KEY_FORMAT = "FORMAT=";
|
||||
private static final String KEY_PRIMARIES = "PRIMARIES=";
|
||||
private static final String KEY_EXPOSURE = "EXPOSURE=";
|
||||
private static final String KEY_GAMMA = "GAMMA=";
|
||||
private static final String KEY_SOFTWARE = "SOFTWARE=";
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
private String software;
|
||||
|
||||
public static HDRHeader read(final ImageInputStream stream) throws IOException {
|
||||
HDRHeader header = new HDRHeader();
|
||||
|
||||
while (true) {
|
||||
String line = stream.readLine().trim();
|
||||
|
||||
if (line.isEmpty()) {
|
||||
// This is the last line before the dimensions
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.startsWith("#?")) {
|
||||
// Program specifier, don't need that...
|
||||
}
|
||||
else if (line.startsWith("#")) {
|
||||
// Comment (ignore)
|
||||
}
|
||||
else if (line.startsWith(KEY_FORMAT)) {
|
||||
String format = line.substring(KEY_FORMAT.length()).trim();
|
||||
|
||||
if (!format.equals("32-bit_rle_rgbe")) {
|
||||
throw new IIOException("Unsupported format \"" + format + "\"(expected \"32-bit_rle_rgbe\")");
|
||||
}
|
||||
// TODO: Support the 32-bit_rle_xyze format
|
||||
}
|
||||
else if (line.startsWith(KEY_PRIMARIES)) {
|
||||
// TODO: We are going to need these values...
|
||||
// Should contain 8 (RGB + white point) coordinates
|
||||
}
|
||||
else if (line.startsWith(KEY_EXPOSURE)) {
|
||||
// TODO: We are going to need these values...
|
||||
}
|
||||
else if (line.startsWith(KEY_GAMMA)) {
|
||||
// TODO: We are going to need these values...
|
||||
}
|
||||
else if (line.startsWith(KEY_SOFTWARE)) {
|
||||
header.software = line.substring(KEY_SOFTWARE.length()).trim();
|
||||
}
|
||||
else {
|
||||
// ...ignore
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Proper parsing of width/height and orientation!
|
||||
String dimensionsLine = stream.readLine().trim();
|
||||
String[] dims = dimensionsLine.split("\\s");
|
||||
|
||||
if (dims[0].equals("-Y") && dims[2].equals("+X")) {
|
||||
header.height = Integer.parseInt(dims[1]);
|
||||
header.width = Integer.parseInt(dims[3]);
|
||||
|
||||
return header;
|
||||
}
|
||||
else {
|
||||
throw new IIOException("Unsupported RGBE orientation (expected \"-Y ... +X ...\")");
|
||||
}
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public String getSoftware() {
|
||||
return software;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.DefaultToneMapper;
|
||||
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
|
||||
|
||||
import javax.imageio.ImageReadParam;
|
||||
|
||||
/**
|
||||
* HDRImageReadParam.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDRImageReadParam.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class HDRImageReadParam extends ImageReadParam {
|
||||
static final ToneMapper DEFAULT_TONE_MAPPER = new DefaultToneMapper(.1f);
|
||||
|
||||
private ToneMapper toneMapper = DEFAULT_TONE_MAPPER;
|
||||
|
||||
public ToneMapper getToneMapper() {
|
||||
return toneMapper;
|
||||
}
|
||||
|
||||
public void setToneMapper(final ToneMapper toneMapper) {
|
||||
this.toneMapper = toneMapper != null ? toneMapper : DEFAULT_TONE_MAPPER;
|
||||
}
|
||||
}
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* HDRImageReader.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDRImageReader.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class HDRImageReader extends ImageReaderBase {
|
||||
// Specs: http://radsite.lbl.gov/radiance/refer/filefmts.pdf
|
||||
|
||||
private HDRHeader header;
|
||||
|
||||
protected HDRImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetMembers() {
|
||||
header = null;
|
||||
}
|
||||
|
||||
private void readHeader() throws IOException {
|
||||
if (header == null) {
|
||||
header = HDRHeader.read(imageInput);
|
||||
|
||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||
}
|
||||
|
||||
imageInput.seek(imageInput.getFlushedPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return header.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return header.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
return Collections.singletonList(ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false)).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
int width = getWidth(imageIndex);
|
||||
int height = getHeight(imageIndex);
|
||||
|
||||
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
|
||||
|
||||
Rectangle srcRegion = new Rectangle();
|
||||
Rectangle destRegion = new Rectangle();
|
||||
computeRegions(param, width, height, destination, srcRegion, destRegion);
|
||||
|
||||
WritableRaster raster = destination.getRaster()
|
||||
.createWritableChild(destRegion.x, destRegion.y, destRegion.width, destRegion.height, 0, 0, null);
|
||||
|
||||
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
||||
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||
|
||||
// Allow pluggable tone mapper via ImageReadParam
|
||||
ToneMapper toneMapper = param instanceof HDRImageReadParam
|
||||
? ((HDRImageReadParam) param).getToneMapper()
|
||||
: HDRImageReadParam.DEFAULT_TONE_MAPPER;
|
||||
|
||||
byte[] rowRGBE = new byte[width * 4];
|
||||
float[] rgb = new float[3];
|
||||
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
// Process one scanline of RGBE data at a time
|
||||
for (int srcY = 0; srcY < height; srcY++) {
|
||||
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
|
||||
if (dstY >= destRegion.height) {
|
||||
break;
|
||||
}
|
||||
|
||||
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
|
||||
|
||||
if (srcY % ySub == 0 && dstY >= destRegion.y) {
|
||||
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
|
||||
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
|
||||
if (dstX >= destRegion.width) {
|
||||
break;
|
||||
}
|
||||
|
||||
RGBE.rgbe2float(rgb, rowRGBE, srcX * 4);
|
||||
|
||||
// Map/clamp RGB values into visible range, normally [0...1]
|
||||
toneMapper.map(rgb);
|
||||
|
||||
raster.setDataElements(dstX, dstY, rgb);
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(srcY * 100f / height);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReadRaster() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
int width = getWidth(imageIndex);
|
||||
int height = getHeight(imageIndex);
|
||||
|
||||
Rectangle srcRegion = new Rectangle();
|
||||
Rectangle destRegion = new Rectangle();
|
||||
computeRegions(param, width, height, null, srcRegion, destRegion);
|
||||
destRegion = srcRegion; // We don't really care about destination for raster
|
||||
|
||||
BufferedImage destination = new BufferedImage(srcRegion.width, srcRegion.height, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
WritableRaster raster = destination.getRaster();
|
||||
|
||||
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
||||
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||
|
||||
byte[] rowRGBE = new byte[width * 4];
|
||||
byte[] pixelRGBE = new byte[width];
|
||||
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
// Process one scanline of RGBE data at a time
|
||||
for (int srcY = 0; srcY < height; srcY++) {
|
||||
int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
|
||||
if (dstY >= destRegion.height) {
|
||||
break;
|
||||
}
|
||||
|
||||
RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
|
||||
|
||||
if (srcY % ySub == 0 && dstY >= destRegion.y) {
|
||||
for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
|
||||
int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
|
||||
if (dstX >= destRegion.width) {
|
||||
break;
|
||||
}
|
||||
|
||||
System.arraycopy(rowRGBE, srcX * 4, pixelRGBE, 0, 4);
|
||||
raster.setDataElements(dstX, dstY, pixelRGBE);
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(srcY * 100f / height);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return destination.getRaster();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReadParam getDefaultReadParam() {
|
||||
return new HDRImageReadParam();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new HDRMetadata(header);
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws IOException {
|
||||
File file = new File(args[0]);
|
||||
|
||||
BufferedImage image = ImageIO.read(file);
|
||||
|
||||
showIt(image, file.getName());
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* HDRImageReaderSpi.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDRImageReaderSpi.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class HDRImageReaderSpi extends ImageReaderSpiBase {
|
||||
public HDRImageReaderSpi() {
|
||||
super(new HDRProviderInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeInput(final Object source) throws IOException {
|
||||
if (!(source instanceof ImageInputStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageInputStream stream = (ImageInputStream) source;
|
||||
|
||||
stream.mark();
|
||||
|
||||
try {
|
||||
// NOTE: All images I have found starts with #?RADIANCE (or has no #? line at all),
|
||||
// although some sources claim that #?RGBE is also used.
|
||||
byte[] magic = new byte[HDR.RADIANCE_MAGIC.length];
|
||||
stream.readFully(magic);
|
||||
|
||||
return Arrays.equals(HDR.RADIANCE_MAGIC, magic)
|
||||
|| Arrays.equals(HDR.RGBE_MAGIC, Arrays.copyOf(magic, 6));
|
||||
}
|
||||
finally {
|
||||
stream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||
return new HDRImageReader(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Radiance RGBE High Dynaimc Range (HDR) image reader";
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
final class HDRMetadata extends AbstractMetadata {
|
||||
private final HDRHeader header;
|
||||
|
||||
HDRMetadata(final HDRHeader header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
chroma.appendChild(csType);
|
||||
csType.setAttribute("name", "RGB");
|
||||
// TODO: Support XYZ
|
||||
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", "3");
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
// No compression
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", "RLE");
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", "TRUE");
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", "UnsignedIntegral");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", "8 8 8 8");
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
// TODO: Support other orientations
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value", "Normal");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No document node
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (header.getSoftware() != null) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
textEntry.setAttribute("keyword", "Software");
|
||||
textEntry.setAttribute("value", header.getSoftware());
|
||||
text.appendChild(textEntry);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
// No transparency
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
|
||||
/**
|
||||
* HDRProviderInfo.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDRProviderInfo.java,v 1.0 27/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
final class HDRProviderInfo extends ReaderWriterProviderInfo {
|
||||
protected HDRProviderInfo() {
|
||||
super(
|
||||
HDRProviderInfo.class,
|
||||
new String[] {"HDR", "hdr", "RGBE", "rgbe"},
|
||||
new String[] {"hdr", "rgbe", "xyze", "pic"},
|
||||
new String[] {"image/vnd.radiance"},
|
||||
"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReader",
|
||||
new String[]{"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi"},
|
||||
null,
|
||||
null,
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* This file contains code to read and write four byte rgbe file format
|
||||
* developed by Greg Ward. It handles the conversions between rgbe and
|
||||
* pixels consisting of floats. The data is assumed to be an array of floats.
|
||||
* By default there are three floats per pixel in the order red, green, blue.
|
||||
* (RGBE_DATA_??? values control this.) Only the mimimal header reading and
|
||||
* writing is implemented. Each routine does error checking and will return
|
||||
* a status value as defined below. This code is intended as a skeleton so
|
||||
* feel free to modify it to suit your needs. <P>
|
||||
* <p/>
|
||||
* Ported to Java and restructured by Kenneth Russell. <BR>
|
||||
* posted to http://www.graphics.cornell.edu/~bjw/ <BR>
|
||||
* written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95 <BR>
|
||||
* based on code written by Greg Ward <BR>
|
||||
* <p/>
|
||||
* Source: https://java.net/projects/jogl-demos/sources/svn/content/trunk/src/demos/hdr/RGBE.java
|
||||
*/
|
||||
final class RGBE {
|
||||
// Flags indicating which fields in a Header are valid
|
||||
private static final int VALID_PROGRAMTYPE = 0x01;
|
||||
private static final int VALID_GAMMA = 0x02;
|
||||
private static final int VALID_EXPOSURE = 0x04;
|
||||
|
||||
private static final String gammaString = "GAMMA=";
|
||||
private static final String exposureString = "EXPOSURE=";
|
||||
|
||||
private static final Pattern widthHeightPattern = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
|
||||
|
||||
public static class Header {
|
||||
// Indicates which fields are valid
|
||||
private int valid;
|
||||
|
||||
// Listed at beginning of file to identify it after "#?".
|
||||
// Defaults to "RGBE"
|
||||
private String programType;
|
||||
|
||||
// Image has already been gamma corrected with given gamma.
|
||||
// Defaults to 1.0 (no correction)
|
||||
private float gamma;
|
||||
|
||||
// A value of 1.0 in an image corresponds to <exposure>
|
||||
// watts/steradian/m^2. Defaults to 1.0.
|
||||
private float exposure;
|
||||
|
||||
// Width and height of image
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
private Header(int valid,
|
||||
String programType,
|
||||
float gamma,
|
||||
float exposure,
|
||||
int width,
|
||||
int height) {
|
||||
this.valid = valid;
|
||||
this.programType = programType;
|
||||
this.gamma = gamma;
|
||||
this.exposure = exposure;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public boolean isProgramTypeValid() {
|
||||
return ((valid & VALID_PROGRAMTYPE) != 0);
|
||||
}
|
||||
|
||||
public boolean isGammaValid() {
|
||||
return ((valid & VALID_GAMMA) != 0);
|
||||
}
|
||||
|
||||
public boolean isExposureValid() {
|
||||
return ((valid & VALID_EXPOSURE) != 0);
|
||||
}
|
||||
|
||||
public String getProgramType() {
|
||||
return programType;
|
||||
}
|
||||
|
||||
public float getGamma() {
|
||||
return gamma;
|
||||
}
|
||||
|
||||
public float getExposure() {
|
||||
return exposure;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
if (isProgramTypeValid()) {
|
||||
buf.append(" Program type: ");
|
||||
buf.append(getProgramType());
|
||||
}
|
||||
buf.append(" Gamma");
|
||||
if (isGammaValid()) {
|
||||
buf.append(" [valid]");
|
||||
}
|
||||
buf.append(": ");
|
||||
buf.append(getGamma());
|
||||
buf.append(" Exposure");
|
||||
if (isExposureValid()) {
|
||||
buf.append(" [valid]");
|
||||
}
|
||||
buf.append(": ");
|
||||
buf.append(getExposure());
|
||||
buf.append(" Width: ");
|
||||
buf.append(getWidth());
|
||||
buf.append(" Height: ");
|
||||
buf.append(getHeight());
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static Header readHeader(final DataInput in) throws IOException {
|
||||
int valid = 0;
|
||||
String programType = null;
|
||||
float gamma = 1.0f;
|
||||
float exposure = 1.0f;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
String buf = in.readLine();
|
||||
if (buf == null) {
|
||||
throw new IOException("Unexpected EOF reading magic token");
|
||||
}
|
||||
if (buf.charAt(0) == '#' && buf.charAt(1) == '?') {
|
||||
valid |= VALID_PROGRAMTYPE;
|
||||
programType = buf.substring(2);
|
||||
buf = in.readLine();
|
||||
if (buf == null) {
|
||||
throw new IOException("Unexpected EOF reading line after magic token");
|
||||
}
|
||||
}
|
||||
|
||||
boolean foundFormat = false;
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
if (buf.equals("FORMAT=32-bit_rle_rgbe")) {
|
||||
foundFormat = true;
|
||||
}
|
||||
else if (buf.startsWith(gammaString)) {
|
||||
valid |= VALID_GAMMA;
|
||||
gamma = Float.parseFloat(buf.substring(gammaString.length()));
|
||||
}
|
||||
else if (buf.startsWith(exposureString)) {
|
||||
valid |= VALID_EXPOSURE;
|
||||
exposure = Float.parseFloat(buf.substring(exposureString.length()));
|
||||
}
|
||||
else {
|
||||
Matcher m = widthHeightPattern.matcher(buf);
|
||||
if (m.matches()) {
|
||||
width = Integer.parseInt(m.group(2));
|
||||
height = Integer.parseInt(m.group(1));
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
buf = in.readLine();
|
||||
if (buf == null) {
|
||||
throw new IOException("Unexpected EOF reading header");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFormat) {
|
||||
throw new IOException("No FORMAT specifier found");
|
||||
}
|
||||
|
||||
return new Header(valid, programType, gamma, exposure, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple read routine. Will not correctly handle run length encoding.
|
||||
*/
|
||||
public static void readPixels(DataInput in, float[] data, int numpixels) throws IOException {
|
||||
byte[] rgbe = new byte[4];
|
||||
float[] rgb = new float[3];
|
||||
int offset = 0;
|
||||
|
||||
while (numpixels-- > 0) {
|
||||
in.readFully(rgbe);
|
||||
|
||||
rgbe2float(rgb, rgbe, 0);
|
||||
|
||||
data[offset++] = rgb[0];
|
||||
data[offset++] = rgb[1];
|
||||
data[offset++] = rgb[2];
|
||||
}
|
||||
}
|
||||
|
||||
public static void readPixelsRaw(DataInput in, byte[] data, int offset, int numpixels) throws IOException {
|
||||
int numExpected = 4 * numpixels;
|
||||
in.readFully(data, offset, numExpected);
|
||||
}
|
||||
|
||||
public static void readPixelsRawRLE(DataInput in, byte[] data, int offset,
|
||||
int scanline_width, int num_scanlines) throws IOException {
|
||||
byte[] rgbe = new byte[4];
|
||||
byte[] scanline_buffer = null;
|
||||
int ptr, ptr_end;
|
||||
int count;
|
||||
byte[] buf = new byte[2];
|
||||
|
||||
if ((scanline_width < 8) || (scanline_width > 0x7fff)) {
|
||||
// run length encoding is not allowed so read flat
|
||||
readPixelsRaw(in, data, offset, scanline_width * num_scanlines);
|
||||
}
|
||||
|
||||
// read in each successive scanline
|
||||
while (num_scanlines > 0) {
|
||||
in.readFully(rgbe);
|
||||
|
||||
if ((rgbe[0] != 2) || (rgbe[1] != 2) || ((rgbe[2] & 0x80) != 0)) {
|
||||
// this file is not run length encoded
|
||||
data[offset++] = rgbe[0];
|
||||
data[offset++] = rgbe[1];
|
||||
data[offset++] = rgbe[2];
|
||||
data[offset++] = rgbe[3];
|
||||
readPixelsRaw(in, data, offset, scanline_width * num_scanlines - 1);
|
||||
}
|
||||
|
||||
if ((((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) != scanline_width) {
|
||||
throw new IOException("Wrong scanline width " +
|
||||
(((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) +
|
||||
", expected " + scanline_width);
|
||||
}
|
||||
|
||||
if (scanline_buffer == null) {
|
||||
scanline_buffer = new byte[4 * scanline_width];
|
||||
}
|
||||
|
||||
ptr = 0;
|
||||
// read each of the four channels for the scanline into the buffer
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ptr_end = (i + 1) * scanline_width;
|
||||
while (ptr < ptr_end) {
|
||||
in.readFully(buf);
|
||||
|
||||
if ((buf[0] & 0xFF) > 128) {
|
||||
// a run of the same value
|
||||
count = (buf[0] & 0xFF) - 128;
|
||||
if ((count == 0) || (count > ptr_end - ptr)) {
|
||||
throw new IOException("Bad scanline data");
|
||||
}
|
||||
while (count-- > 0) {
|
||||
scanline_buffer[ptr++] = buf[1];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// a non-run
|
||||
count = buf[0] & 0xFF;
|
||||
if ((count == 0) || (count > ptr_end - ptr)) {
|
||||
throw new IOException("Bad scanline data");
|
||||
}
|
||||
scanline_buffer[ptr++] = buf[1];
|
||||
if (--count > 0) {
|
||||
in.readFully(scanline_buffer, ptr, count);
|
||||
ptr += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// copy byte data to output
|
||||
for (int i = 0; i < scanline_width; i++) {
|
||||
data[offset++] = scanline_buffer[i];
|
||||
data[offset++] = scanline_buffer[i + scanline_width];
|
||||
data[offset++] = scanline_buffer[i + 2 * scanline_width];
|
||||
data[offset++] = scanline_buffer[i + 3 * scanline_width];
|
||||
}
|
||||
num_scanlines--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard conversion from float pixels to rgbe pixels.
|
||||
*/
|
||||
public static void float2rgbe(byte[] rgbe, float red, float green, float blue) {
|
||||
float v;
|
||||
int e;
|
||||
|
||||
v = red;
|
||||
if (green > v) {
|
||||
v = green;
|
||||
}
|
||||
if (blue > v) {
|
||||
v = blue;
|
||||
}
|
||||
if (v < 1e-32f) {
|
||||
rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
|
||||
}
|
||||
else {
|
||||
FracExp fe = frexp(v);
|
||||
v = (float) (fe.getFraction() * 256.0 / v);
|
||||
rgbe[0] = (byte) (red * v);
|
||||
rgbe[1] = (byte) (green * v);
|
||||
rgbe[2] = (byte) (blue * v);
|
||||
rgbe[3] = (byte) (fe.getExponent() + 128);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard conversion from rgbe to float pixels. Note: Ward uses
|
||||
* ldexp(col+0.5,exp-(128+8)). However we wanted pixels in the
|
||||
* range [0,1] to map back into the range [0,1].
|
||||
*/
|
||||
public static void rgbe2float(float[] rgb, byte[] rgbe, int startRGBEOffset) {
|
||||
float f;
|
||||
|
||||
if (rgbe[startRGBEOffset + 3] != 0) { // nonzero pixel
|
||||
f = (float) ldexp(1.0, (rgbe[startRGBEOffset + 3] & 0xFF) - (128 + 8));
|
||||
rgb[0] = (rgbe[startRGBEOffset + 0] & 0xFF) * f;
|
||||
rgb[1] = (rgbe[startRGBEOffset + 1] & 0xFF) * f;
|
||||
rgb[2] = (rgbe[startRGBEOffset + 2] & 0xFF) * f;
|
||||
}
|
||||
else {
|
||||
rgb[0] = 0;
|
||||
rgb[1] = 0;
|
||||
rgb[2] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static double ldexp(double value, int exp) {
|
||||
if (!finite(value) || value == 0.0) {
|
||||
return value;
|
||||
}
|
||||
value = scalbn(value, exp);
|
||||
// No good way to indicate errno (want to avoid throwing
|
||||
// exceptions because don't know about stability of calculations)
|
||||
// if(!finite(value)||value==0.0) errno = ERANGE;
|
||||
return value;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Internals only below this point
|
||||
//
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Math routines, some fdlibm-derived
|
||||
//
|
||||
|
||||
static class FracExp {
|
||||
private double fraction;
|
||||
private int exponent;
|
||||
|
||||
public FracExp(double fraction, int exponent) {
|
||||
this.fraction = fraction;
|
||||
this.exponent = exponent;
|
||||
}
|
||||
|
||||
public double getFraction() {
|
||||
return fraction;
|
||||
}
|
||||
|
||||
public int getExponent() {
|
||||
return exponent;
|
||||
}
|
||||
}
|
||||
|
||||
private static final double two54 = 1.80143985094819840000e+16; // 43500000 00000000
|
||||
private static final double twom54 = 5.55111512312578270212e-17; // 0x3C900000 0x00000000
|
||||
private static final double huge = 1.0e+300;
|
||||
private static final double tiny = 1.0e-300;
|
||||
|
||||
private static int hi(double x) {
|
||||
long bits = Double.doubleToRawLongBits(x);
|
||||
return (int) (bits >>> 32);
|
||||
}
|
||||
|
||||
private static int lo(double x) {
|
||||
long bits = Double.doubleToRawLongBits(x);
|
||||
return (int) bits;
|
||||
}
|
||||
|
||||
private static double fromhilo(int hi, int lo) {
|
||||
return Double.longBitsToDouble((((long) hi) << 32) |
|
||||
(((long) lo) & 0xFFFFFFFFL));
|
||||
}
|
||||
|
||||
private static FracExp frexp(double x) {
|
||||
int hx = hi(x);
|
||||
int ix = 0x7fffffff & hx;
|
||||
int lx = lo(x);
|
||||
int e = 0;
|
||||
if (ix >= 0x7ff00000 || ((ix | lx) == 0)) {
|
||||
return new FracExp(x, e); // 0,inf,nan
|
||||
}
|
||||
if (ix < 0x00100000) { // subnormal
|
||||
x *= two54;
|
||||
hx = hi(x);
|
||||
ix = hx & 0x7fffffff;
|
||||
e = -54;
|
||||
}
|
||||
e += (ix >> 20) - 1022;
|
||||
hx = (hx & 0x800fffff) | 0x3fe00000;
|
||||
lx = lo(x);
|
||||
return new FracExp(fromhilo(hx, lx), e);
|
||||
}
|
||||
|
||||
private static boolean finite(double x) {
|
||||
int hx;
|
||||
hx = hi(x);
|
||||
return (((hx & 0x7fffffff) - 0x7ff00000) >> 31) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* copysign(double x, double y) <BR>
|
||||
* copysign(x,y) returns a value with the magnitude of x and
|
||||
* with the sign bit of y.
|
||||
*/
|
||||
private static double copysign(double x, double y) {
|
||||
return fromhilo((hi(x) & 0x7fffffff) | (hi(y) & 0x80000000), lo(x));
|
||||
}
|
||||
|
||||
/**
|
||||
* scalbn (double x, int n) <BR>
|
||||
* scalbn(x,n) returns x* 2**n computed by exponent
|
||||
* manipulation rather than by actually performing an
|
||||
* exponentiation or a multiplication.
|
||||
*/
|
||||
private static double scalbn(double x, int n) {
|
||||
int hx = hi(x);
|
||||
int lx = lo(x);
|
||||
int k = (hx & 0x7ff00000) >> 20; // extract exponent
|
||||
if (k == 0) { // 0 or subnormal x
|
||||
if ((lx | (hx & 0x7fffffff)) == 0) {
|
||||
return x; // +-0
|
||||
}
|
||||
x *= two54;
|
||||
hx = hi(x);
|
||||
k = ((hx & 0x7ff00000) >> 20) - 54;
|
||||
if (n < -50000) {
|
||||
return tiny * x; // underflow
|
||||
}
|
||||
}
|
||||
if (k == 0x7ff) {
|
||||
return x + x; // NaN or Inf
|
||||
}
|
||||
k = k + n;
|
||||
if (k > 0x7fe) {
|
||||
return huge * copysign(huge, x); // overflow
|
||||
}
|
||||
if (k > 0) {
|
||||
// normal result
|
||||
return fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
|
||||
}
|
||||
if (k <= -54) {
|
||||
if (n > 50000) {
|
||||
// in case integer overflow in n+k
|
||||
return huge * copysign(huge, x); // overflow
|
||||
}
|
||||
else {
|
||||
return tiny * copysign(tiny, x); // underflow
|
||||
}
|
||||
}
|
||||
k += 54; // subnormal result
|
||||
x = fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
|
||||
return x * twom54;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Test harness
|
||||
//
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
try {
|
||||
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(args[i])));
|
||||
Header header = RGBE.readHeader(in);
|
||||
System.err.println("Header for file \"" + args[i] + "\":");
|
||||
System.err.println(" " + header);
|
||||
byte[] data = new byte[header.getWidth() * header.getHeight() * 4];
|
||||
readPixelsRawRLE(in, data, 0, header.getWidth(), header.getHeight());
|
||||
in.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||
|
||||
/**
|
||||
* DefaultToneMapper.
|
||||
* <p/>
|
||||
* Normalizes values to range [0...1] using:
|
||||
*
|
||||
* <p><em>V<sub>out</sub> = V<sub>in</sub> / (V<sub>in</sub> + C)</em></p>
|
||||
*
|
||||
* Where <em>C</em> is constant.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: DefaultToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class DefaultToneMapper implements ToneMapper {
|
||||
|
||||
private final float constant;
|
||||
|
||||
public DefaultToneMapper() {
|
||||
this(1);
|
||||
}
|
||||
|
||||
public DefaultToneMapper(final float constant) {
|
||||
this.constant = constant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void map(final float[] rgb) {
|
||||
// Default Vo = Vi / (Vi + 1)
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
rgb[i] = rgb[i] / (rgb[i] + constant);
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||
|
||||
/**
|
||||
* GammaToneMapper.
|
||||
* <p/>
|
||||
* Normalizes values to range [0...1] using:
|
||||
*
|
||||
* <p><em>V<sub>out</sub> = A V<sub>in</sub><sup>\u03b3</sup></em></p>
|
||||
*
|
||||
* Where <em>A</em> is constant and <em>\u03b3</em> is the gamma.
|
||||
* Values > 1 are clamped.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: GammaToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class GammaToneMapper implements ToneMapper {
|
||||
|
||||
private final float constant;
|
||||
private final float gamma;
|
||||
|
||||
public GammaToneMapper() {
|
||||
this(0.5f, .25f);
|
||||
}
|
||||
|
||||
public GammaToneMapper(final float constant, final float gamma) {
|
||||
this.constant = constant;
|
||||
this.gamma = gamma;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void map(final float[] rgb) {
|
||||
// Gamma Vo = A * Vi^y
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
rgb[i] = Math.min(1f, (float) (constant * Math.pow(rgb[i], gamma)));
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||
|
||||
/**
|
||||
* NullToneMapper.
|
||||
* <p/>
|
||||
* This {@code ToneMapper} does *not* normalize or clamp values
|
||||
* to range [0...1], but leaves the values as-is.
|
||||
* Useful for applications that implements custom tone mapping.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: NullToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class NullToneMapper implements ToneMapper {
|
||||
@Override
|
||||
public void map(float[] rgb) {
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr.tonemap;
|
||||
|
||||
/**
|
||||
* ToneMapper.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
|
||||
*/
|
||||
public interface ToneMapper {
|
||||
void map(float[] rgb);
|
||||
}
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
|
||||
Executable
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TGAImageReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
|
||||
*/
|
||||
public class HDRImageReaderTest extends ImageReaderAbstractTest<HDRImageReader> {
|
||||
@Override
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return new HDRImageReaderSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<HDRImageReader> getReaderClass() {
|
||||
return HDRImageReader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HDRImageReader createReader() {
|
||||
return new HDRImageReader(createProvider());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("hdr", "rgbe", "xyze");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getMIMETypes() {
|
||||
return Collections.singletonList(
|
||||
"image/vnd.radiance"
|
||||
);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* HDRProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: HDRProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class HDRProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new HDRProviderInfo();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-icns</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||
@@ -18,7 +18,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+29
-1
@@ -1,3 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
@@ -19,7 +47,7 @@ final class ICNSProviderInfo extends ReaderWriterProviderInfo {
|
||||
"image/x-apple-icons", // Common extension MIME
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReader",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.ics.ICNImageReaderSpi"},
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi"},
|
||||
null, null,
|
||||
false, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
|
||||
+6
-5
@@ -28,7 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -37,6 +37,7 @@ import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -46,7 +47,7 @@ import java.util.List;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNSImageReaderTest.java,v 1.0 25.10.11 18:44 haraldk Exp$
|
||||
*/
|
||||
public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
|
||||
public class ICNSImageReaderTest extends ImageReaderAbstractTest {
|
||||
@Override
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
@@ -119,17 +120,17 @@ public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
|
||||
|
||||
@Override
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("icns");
|
||||
return Collections.singletonList("icns");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("icns");
|
||||
return Collections.singletonList("icns");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/x-apple-icons");
|
||||
return Collections.singletonList("image/x-apple-icons");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* ICNSProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: ICNSProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class ICNSProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new ICNSProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-iff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
||||
@@ -21,7 +21,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+28
@@ -1,3 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
|
||||
+5
-6
@@ -28,7 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -39,11 +39,10 @@ import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* IFFImageReaderTestCase
|
||||
@@ -52,7 +51,7 @@ import static org.junit.Assert.assertTrue;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IFFImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class IFFImageReaderTest extends ImageReaderAbstractTestCase<IFFImageReader> {
|
||||
public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader> {
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
// 32 bit - Ok
|
||||
@@ -93,7 +92,7 @@ public class IFFImageReaderTest extends ImageReaderAbstractTestCase<IFFImageRead
|
||||
}
|
||||
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("iff");
|
||||
return Collections.singletonList("iff");
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes() {
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* IFFProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: IFFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class IFFProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new IFFProviderInfo();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
+6
-9
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
@@ -102,7 +103,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
thumbnail = readJPEG();
|
||||
}
|
||||
|
||||
cachedThumbnail = pixelsExposed ? null : new SoftReference<BufferedImage>(thumbnail);
|
||||
cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
@@ -132,14 +133,10 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input);
|
||||
|
||||
try {
|
||||
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input);
|
||||
|
||||
try {
|
||||
try (MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input)) {
|
||||
return readJPEGThumbnail(reader, stream);
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
@@ -195,15 +192,15 @@ final class EXIFThumbnailReader extends ThumbnailReader {
|
||||
break;
|
||||
case 6:
|
||||
// YCbCr
|
||||
for (int i = 0, thumbDataLength = thumbData.length; i < thumbDataLength; i += 3) {
|
||||
JPEGImageReader.YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
|
||||
for (int i = 0; i < thumbSize; i += 3) {
|
||||
YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IIOException("Unknown PhotometricInterpretation value for uncompressed EXIF thumbnail (expected 2 or 6): " + interpretation);
|
||||
}
|
||||
|
||||
return ThumbnailReader.readRawThumbnail(thumbData, thumbData.length, 0, w, h);
|
||||
return ThumbnailReader.readRawThumbnail(thumbData, thumbSize, 0, w, h);
|
||||
}
|
||||
|
||||
throw new IIOException("Missing StripOffsets tag for uncompressed EXIF thumbnail");
|
||||
|
||||
+191
-173
@@ -28,9 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
@@ -112,7 +112,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = createSegmentIds();
|
||||
|
||||
private static Map<Integer, List<String>> createSegmentIds() {
|
||||
Map<Integer, List<String>> map = new LinkedHashMap<Integer, List<String>>();
|
||||
Map<Integer, List<String>> map = new LinkedHashMap<>();
|
||||
|
||||
// Need all APP markers to be able to re-generate proper metadata later
|
||||
for (int appMarker = JPEG.APP0; appMarker <= JPEG.APP15; appMarker++) {
|
||||
@@ -152,10 +152,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
/** Cached list of JPEG segments we filter from the underlying stream */
|
||||
private List<JPEGSegment> segments;
|
||||
|
||||
JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||
protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||
super(provider);
|
||||
this.delegate = Validate.notNull(delegate);
|
||||
|
||||
this.delegate = Validate.notNull(delegate);
|
||||
progressDelegator = new ProgressDelegator();
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
JPEGColorSpace csType = getSourceCSType(getJFIF(), getAdobeDCT(), getSOF());
|
||||
|
||||
if (types == null || !types.hasNext() || csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
|
||||
ArrayList<ImageTypeSpecifier> typeList = new ArrayList<ImageTypeSpecifier>();
|
||||
ArrayList<ImageTypeSpecifier> typeList = new ArrayList<>();
|
||||
// Add the standard types, we can always convert to these
|
||||
typeList.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||
typeList.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
|
||||
@@ -268,10 +268,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
@Override
|
||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||
// If delegate can determine the spec, we'll just go with that
|
||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||
try {
|
||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
||||
|
||||
if (rawType != null) {
|
||||
return rawType;
|
||||
if (rawType != null) {
|
||||
return rawType;
|
||||
}
|
||||
}
|
||||
catch (NullPointerException ignore) {
|
||||
// Fall through
|
||||
}
|
||||
|
||||
// Otherwise, consult the image metadata
|
||||
@@ -298,7 +303,9 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
||||
|
||||
// JPEGSegmentImageInputStream that filters out/skips bad/unnecessary segments
|
||||
delegate.setInput(imageInput != null ? new JPEGSegmentImageInputStream(imageInput) : null, seekForwardOnly, ignoreMetadata);
|
||||
delegate.setInput(imageInput != null
|
||||
? new JPEGSegmentImageInputStream(imageInput)
|
||||
: null, seekForwardOnly, ignoreMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -311,22 +318,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
assertInput();
|
||||
checkBounds(imageIndex);
|
||||
|
||||
// CompoundDirectory exif = getExif();
|
||||
// if (exif != null) {
|
||||
// System.err.println("exif: " + exif);
|
||||
// System.err.println("Orientation: " + exif.getEntryById(TIFF.TAG_ORIENTATION));
|
||||
// Entry exifIFDEntry = exif.getEntryById(TIFF.TAG_EXIF_IFD);
|
||||
//
|
||||
// if (exifIFDEntry != null) {
|
||||
// Directory exifIFD = (Directory) exifIFDEntry.getValue();
|
||||
// System.err.println("PixelXDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_X_DIMENSION));
|
||||
// System.err.println("PixelYDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_Y_DIMENSION));
|
||||
// }
|
||||
// }
|
||||
|
||||
SOFSegment sof = getSOF();
|
||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||
AdobeDCTSegment adobeDCT = getAdobeDCT();
|
||||
boolean bogusAdobeDCT = false;
|
||||
|
||||
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
|
||||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
|
||||
@@ -337,6 +332,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
sof.marker & 0xf, sof.componentsInFrame()
|
||||
));
|
||||
|
||||
bogusAdobeDCT = true;
|
||||
adobeDCT = null;
|
||||
}
|
||||
|
||||
@@ -345,11 +341,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||
if (delegate.canReadRaster() && (
|
||||
bogusAdobeDCT ||
|
||||
sourceCSType == JPEGColorSpace.CMYK ||
|
||||
sourceCSType == JPEGColorSpace.YCCK ||
|
||||
adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
|
||||
profile != null && !ColorSpaces.isCS_sRGB(profile)) ||
|
||||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null) { // TODO: Issue warning?
|
||||
profile != null && !ColorSpaces.isCS_sRGB(profile) ||
|
||||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
|
||||
if (DEBUG) {
|
||||
System.out.println("Reading using raster and extra conversion");
|
||||
System.out.println("ICC color profile: " + profile);
|
||||
@@ -371,6 +367,10 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
int origHeight = getHeight(imageIndex);
|
||||
|
||||
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||
// TODO: Avoid creating destination here, if possible (as it saves time and memory)
|
||||
// If YCbCr or RGB, we could instead create a BufferedImage around the converted raster directly.
|
||||
// If YCCK or CMYK, we could instead create a BufferedImage around the converted raster,
|
||||
// leaving the fourth band as alpha (or pretend it's not there, by creating a child raster).
|
||||
BufferedImage image = getDestination(param, imageTypes, origWidth, origHeight);
|
||||
WritableRaster destination = image.getRaster();
|
||||
|
||||
@@ -404,6 +404,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
if (DEBUG) {
|
||||
System.err.println("Converting from " + intendedCS + " to " + (image.getColorModel().getColorSpace().isCS_sRGB() ? "sRGB" : image.getColorModel().getColorSpace()));
|
||||
}
|
||||
|
||||
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
|
||||
}
|
||||
// Else, pass through with no conversion
|
||||
@@ -462,10 +463,13 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
// Apply source color conversion from implicit color space
|
||||
if (csType == JPEGColorSpace.YCbCr || csType == JPEGColorSpace.YCbCrA) {
|
||||
YCbCrConverter.convertYCbCr2RGB(raster);
|
||||
convertYCbCr2RGB(raster);
|
||||
}
|
||||
else if (csType == JPEGColorSpace.YCCK) {
|
||||
YCbCrConverter.convertYCCK2CMYK(raster);
|
||||
// TODO: Need to rethink this (non-) inversion, see #147
|
||||
// TODO: Allow param to specify inversion, or possibly the PDF decode array
|
||||
// flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
|
||||
convertYCCK2CMYK(raster);
|
||||
}
|
||||
else if (csType == JPEGColorSpace.CMYK) {
|
||||
invertCMYK(raster);
|
||||
@@ -620,7 +624,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
|
||||
protected ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
|
||||
// NOTE: This is probably not the right way to do it... :-P
|
||||
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
|
||||
|
||||
@@ -670,16 +674,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
||||
}
|
||||
catch (IIOException ignore) {
|
||||
catch (IIOException | IllegalArgumentException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException foo) {
|
||||
if (DEBUG) {
|
||||
foo.printStackTrace();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
imageInput.reset();
|
||||
}
|
||||
@@ -699,7 +698,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
if ((marker == ALL_APP_MARKERS && segment.marker() >= JPEG.APP0 && segment.marker() <= JPEG.APP15 || segment.marker() == marker)
|
||||
&& (identifier == null || identifier.equals(segment.identifier()))) {
|
||||
if (appSegments == Collections.EMPTY_LIST) {
|
||||
appSegments = new ArrayList<JPEGSegment>(segments.size());
|
||||
appSegments = new ArrayList<>(segments.size());
|
||||
}
|
||||
|
||||
appSegments.add(segment);
|
||||
@@ -831,7 +830,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
return data;
|
||||
}
|
||||
|
||||
ICC_Profile getEmbeddedICCProfile(final boolean allowBadIndexes) throws IOException {
|
||||
protected ICC_Profile getEmbeddedICCProfile(final boolean allowBadIndexes) throws IOException {
|
||||
// ICC v 1.42 (2006) annex B:
|
||||
// APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination)
|
||||
// + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments)
|
||||
@@ -853,7 +852,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
return readICCProfileSafe(stream);
|
||||
return readICCProfileSafe(stream, allowBadIndexes);
|
||||
}
|
||||
else if (!segments.isEmpty()) {
|
||||
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
|
||||
@@ -900,15 +899,17 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
streams[badICC ? i : chunkNumber - 1] = stream;
|
||||
}
|
||||
|
||||
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))));
|
||||
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ICC_Profile readICCProfileSafe(final InputStream stream) throws IOException {
|
||||
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) throws IOException {
|
||||
try {
|
||||
return ICC_Profile.getInstance(stream);
|
||||
ICC_Profile profile = ICC_Profile.getInstance(stream);
|
||||
|
||||
return allowBadProfile ? profile : ColorSpaces.validateProfile(profile);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
|
||||
@@ -940,6 +941,11 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
delegate.abort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReadParam getDefaultReadParam() {
|
||||
return delegate.getDefaultReadParam();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readerSupportsThumbnails() {
|
||||
return true; // We support EXIF, JFIF and JFXX style thumbnails
|
||||
@@ -949,7 +955,7 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
checkBounds(imageIndex);
|
||||
|
||||
if (thumbnails == null) {
|
||||
thumbnails = new ArrayList<ThumbnailReader>();
|
||||
thumbnails = new ArrayList<>();
|
||||
ThumbnailReadProgressListener thumbnailProgressDelegator = new ThumbnailProgressDelegate();
|
||||
|
||||
// Read JFIF thumbnails if present
|
||||
@@ -1101,105 +1107,32 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static inner class for lazy-loading of conversion tables.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author Original code by Werner Randelshofer
|
||||
*/
|
||||
static final class YCbCrConverter {
|
||||
/** Define tables for YCC->RGB color space conversion. */
|
||||
private final static int SCALEBITS = 16;
|
||||
private final static int MAXJSAMPLE = 255;
|
||||
private final static int CENTERJSAMPLE = 128;
|
||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||
public static void convertYCbCr2RGB(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (DEBUG) {
|
||||
System.err.println("Building YCC conversion table");
|
||||
}
|
||||
|
||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||
// Cr=>R value is nearest int to 1.40200 * x
|
||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cb=>B value is nearest int to 1.77200 * x
|
||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cr=>G value is scaled-up -0.71414 * x
|
||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||
// Cb=>G value is scaled-up -0.34414 * x
|
||||
// We also add in ONE_HALF so that need not do it in inner loop
|
||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
YCbCrConverter.convertYCbCr2RGB(data, data, (x + y * width) * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
public static void convertYCCK2CMYK(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
static void convertYCbCr2RGB(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCbCr2RGB(data, data, (x + y * width) * 3);
|
||||
}
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int offset = (x + y * width) * 4;
|
||||
// YCC -> CMY
|
||||
YCbCrConverter.convertYCbCr2RGB(data, data, offset);
|
||||
// Inverse K
|
||||
data[offset + 3] = (byte) (0xff - data[offset + 3] & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset ] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
|
||||
rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||
}
|
||||
|
||||
static void convertYCCK2CMYK(final Raster raster) {
|
||||
final int height = raster.getHeight();
|
||||
final int width = raster.getWidth();
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
convertYCCK2CMYK(data, data, (x + y * width) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
|
||||
// Inverted
|
||||
int y = 255 - ycck[offset ] & 0xff;
|
||||
int cb = 255 - ycck[offset + 1] & 0xff;
|
||||
int cr = 255 - ycck[offset + 2] & 0xff;
|
||||
int k = 255 - ycck[offset + 3] & 0xff;
|
||||
|
||||
int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
|
||||
int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
|
||||
|
||||
cmyk[offset ] = clamp(cmykC);
|
||||
cmyk[offset + 1] = clamp(cmykM);
|
||||
cmyk[offset + 2] = clamp(cmykY);
|
||||
cmyk[offset + 3] = (byte) k; // K passes through unchanged
|
||||
}
|
||||
|
||||
private static byte clamp(int val) {
|
||||
return (byte) Math.max(0, Math.min(255, val));
|
||||
}
|
||||
}
|
||||
|
||||
private class ProgressDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
|
||||
@@ -1297,7 +1230,68 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
public static void main(final String[] args) throws IOException {
|
||||
for (final String arg : args) {
|
||||
ImageIO.setUseCache(false);
|
||||
|
||||
int subX = 1;
|
||||
int subY = 1;
|
||||
int xOff = 0;
|
||||
int yOff = 0;
|
||||
Rectangle roi = null;
|
||||
boolean metadata = false;
|
||||
boolean thumbnails = false;
|
||||
|
||||
for (int argIdx = 0; argIdx < args.length; argIdx++) {
|
||||
final String arg = args[argIdx];
|
||||
|
||||
if (arg.charAt(0) == '-') {
|
||||
if (arg.equals("-s") || arg.equals("--subsample") && args.length > argIdx) {
|
||||
String[] sub = args[++argIdx].split(",");
|
||||
|
||||
try {
|
||||
if (sub.length >= 4) {
|
||||
subX = Integer.parseInt(sub[0]);
|
||||
subY = Integer.parseInt(sub[1]);
|
||||
xOff = Integer.parseInt(sub[2]);
|
||||
yOff = Integer.parseInt(sub[3]);
|
||||
}
|
||||
else {
|
||||
subX = Integer.parseInt(sub[0]);
|
||||
subY = sub.length > 1 ? Integer.parseInt(sub[1]) : subX;
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
System.err.println("Bad sub sampling (x,y): '" + args[argIdx] + "'");
|
||||
}
|
||||
}
|
||||
else if (arg.equals("-r") || arg.equals("--roi") && args.length > argIdx) {
|
||||
String[] region = args[++argIdx].split(",");
|
||||
|
||||
try {
|
||||
if (region.length >= 4) {
|
||||
roi = new Rectangle(Integer.parseInt(region[0]), Integer.parseInt(region[2]), Integer.parseInt(region[2]), Integer.parseInt(region[3]));
|
||||
}
|
||||
else {
|
||||
roi = new Rectangle(Integer.parseInt(region[0]), Integer.parseInt(region[2]));
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfBoundsException | NumberFormatException e) {
|
||||
System.err.println("Bad source region ([x,y,]w, h): '" + args[argIdx] + "'");
|
||||
}
|
||||
}
|
||||
else if (arg.equals("-m") || arg.equals("--metadata")) {
|
||||
metadata = true;
|
||||
}
|
||||
else if (arg.equals("-t") || arg.equals("--thumbnails")) {
|
||||
thumbnails = true;
|
||||
}
|
||||
else {
|
||||
System.err.println("Unknown argument: '" + arg + "'");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
File file = new File(arg);
|
||||
|
||||
ImageInputStream input = ImageIO.createImageInputStream(file);
|
||||
@@ -1313,15 +1307,15 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImageReader reader = readers.next();
|
||||
// System.err.println("Reading using: " + reader);
|
||||
final ImageReader reader = readers.next();
|
||||
System.err.println("Reading using: " + reader);
|
||||
|
||||
reader.addIIOReadWarningListener(new IIOReadWarningListener() {
|
||||
public void warningOccurred(ImageReader source, String warning) {
|
||||
System.err.println("Warning: " + arg + ": " + warning);
|
||||
}
|
||||
});
|
||||
reader.addIIOReadProgressListener(new ProgressListenerBase() {
|
||||
final ProgressListenerBase listener = new ProgressListenerBase() {
|
||||
private static final int MAX_W = 78;
|
||||
int lastProgress = 0;
|
||||
|
||||
@@ -1350,29 +1344,35 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
System.out.println("]");
|
||||
}
|
||||
});
|
||||
};
|
||||
reader.addIIOReadProgressListener(listener);
|
||||
|
||||
reader.setInput(input);
|
||||
|
||||
// For a tables-only image, we can't read image, but we should get metadata.
|
||||
if (reader.getNumImages(true) == 0) {
|
||||
IIOMetadata streamMetadata = reader.getStreamMetadata();
|
||||
IIOMetadataNode streamNativeTree = (IIOMetadataNode) streamMetadata.getAsTree(streamMetadata.getNativeMetadataFormatName());
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(streamNativeTree, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// For a tables-only image, we can't read image, but we should get metadata.
|
||||
if (reader.getNumImages(true) == 0) {
|
||||
IIOMetadata streamMetadata = reader.getStreamMetadata();
|
||||
IIOMetadataNode streamNativeTree = (IIOMetadataNode) streamMetadata.getAsTree(streamMetadata.getNativeMetadataFormatName());
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(streamNativeTree, false);
|
||||
continue;
|
||||
}
|
||||
|
||||
BufferedImage image;
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
// if (args.length > 1) {
|
||||
// int sub = Integer.parseInt(args[1]);
|
||||
// int sub = 4;
|
||||
// param.setSourceSubsampling(sub, sub, 0, 0);
|
||||
// }
|
||||
BufferedImage image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
|
||||
if (subX > 1 || subY > 1 || roi != null) {
|
||||
param.setSourceSubsampling(subX, subY, xOff, yOff);
|
||||
param.setSourceRegion(roi);
|
||||
|
||||
image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY);
|
||||
}
|
||||
else {
|
||||
image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
|
||||
}
|
||||
param.setDestination(image);
|
||||
|
||||
// long start = System.currentTimeMillis();
|
||||
long start = DEBUG ? System.currentTimeMillis() : 0;
|
||||
|
||||
try {
|
||||
image = reader.read(0, param);
|
||||
}
|
||||
@@ -1383,12 +1383,13 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
// System.err.println("image: " + image);
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
System.err.println("image: " + image);
|
||||
}
|
||||
|
||||
// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
|
||||
/*
|
||||
int maxW = 1280;
|
||||
int maxH = 800;
|
||||
if (image.getWidth() > maxW || image.getHeight() > maxH) {
|
||||
@@ -1402,28 +1403,45 @@ public class JPEGImageReader extends ImageReaderBase {
|
||||
}
|
||||
// System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
}
|
||||
*/
|
||||
|
||||
showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(0), reader.getHeight(0)));
|
||||
|
||||
try {
|
||||
IIOMetadata imageMetadata = reader.getImageMetadata(0);
|
||||
System.out.println("Metadata for File: " + file.getName());
|
||||
System.out.println("Native:");
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()), false);
|
||||
System.out.println("Standard:");
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||
System.out.println();
|
||||
if (metadata) {
|
||||
try {
|
||||
IIOMetadata imageMetadata = reader.getImageMetadata(0);
|
||||
System.out.println("Metadata for File: " + file.getName());
|
||||
|
||||
int numThumbnails = reader.getNumThumbnails(0);
|
||||
for (int i = 0; i < numThumbnails; i++) {
|
||||
BufferedImage thumbnail = reader.readThumbnail(0, i);
|
||||
// System.err.println("thumbnail: " + thumbnail);
|
||||
showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
|
||||
if (imageMetadata.getNativeMetadataFormatName() != null) {
|
||||
System.out.println("Native:");
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()), false);
|
||||
}
|
||||
if (imageMetadata.isStandardMetadataFormatSupported()) {
|
||||
System.out.println("Standard:");
|
||||
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
}
|
||||
catch (IIOException e) {
|
||||
System.err.println("Could not read thumbnails: " + arg + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
System.err.println("Could not read thumbnails: " + arg + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
|
||||
if (thumbnails) {
|
||||
try {
|
||||
int numThumbnails = reader.getNumThumbnails(0);
|
||||
for (int i = 0; i < numThumbnails; i++) {
|
||||
BufferedImage thumbnail = reader.readThumbnail(0, i);
|
||||
// System.err.println("thumbnail: " + thumbnail);
|
||||
showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
System.err.println("Could not read thumbnails: " + arg + ": " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
|
||||
+13
-3
@@ -29,6 +29,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
@@ -48,14 +49,14 @@ import java.util.Locale;
|
||||
* @version $Id: JPEGImageReaderSpi.java,v 1.0 24.01.11 22.12 haraldk Exp$
|
||||
*/
|
||||
public class JPEGImageReaderSpi extends ImageReaderSpiBase {
|
||||
private ImageReaderSpi delegateProvider;
|
||||
protected ImageReaderSpi delegateProvider;
|
||||
|
||||
/**
|
||||
* Constructor for use by {@link javax.imageio.spi.IIORegistry} only.
|
||||
* The instance created will not work without being properly registered.
|
||||
*/
|
||||
public JPEGImageReaderSpi() {
|
||||
super(new JPEGProviderInfo());
|
||||
this(new JPEGProviderInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,6 +70,15 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
|
||||
this.delegateProvider = Validate.notNull(delegateProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for subclasses.
|
||||
*
|
||||
* @param info
|
||||
*/
|
||||
protected JPEGImageReaderSpi(final ReaderWriterProviderInfo info) {
|
||||
super(info);
|
||||
}
|
||||
|
||||
static ImageReaderSpi lookupDelegateProvider(final ServiceRegistry registry) {
|
||||
Iterator<ImageReaderSpi> providers = registry.getServiceProviders(ImageReaderSpi.class, true);
|
||||
|
||||
@@ -83,7 +93,7 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@SuppressWarnings({"unchecked", "deprecation"})
|
||||
@Override
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
if (delegateProvider == null) {
|
||||
|
||||
+28
@@ -1,3 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
|
||||
+10
-5
@@ -240,16 +240,21 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
||||
private void streamInit() throws IOException {
|
||||
stream.seek(0);
|
||||
|
||||
int soi = stream.readUnsignedShort();
|
||||
if (soi != JPEG.SOI) {
|
||||
throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
|
||||
}
|
||||
else {
|
||||
try {
|
||||
int soi = stream.readUnsignedShort();
|
||||
|
||||
if (soi != JPEG.SOI) {
|
||||
throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
|
||||
}
|
||||
|
||||
segment = new Segment(soi, 0, 0, 2);
|
||||
|
||||
segments.add(segment);
|
||||
currentSegment = segments.size() - 1; // 0
|
||||
}
|
||||
catch (EOFException eof) {
|
||||
throw new IIOException(String.format("Not a JPEG stream (short stream. expected SOI: 0x%04x)", JPEG.SOI), eof);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isAppSegmentMarker(final int marker) {
|
||||
|
||||
+54
-19
@@ -28,7 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import org.hamcrest.core.IsInstanceOf;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
@@ -59,10 +59,11 @@ import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNoException;
|
||||
import static org.junit.Assume.assumeNotNull;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* JPEGImageReaderTest
|
||||
@@ -71,11 +72,11 @@ import static org.mockito.Mockito.verify;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JPEGImageReaderTest.java,v 1.0 24.01.11 22.04 haraldk Exp$
|
||||
*/
|
||||
public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageReader> {
|
||||
public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader> {
|
||||
|
||||
private static final JPEGImageReaderSpi SPI = new JPEGImageReaderSpi(lookupDelegateProvider());
|
||||
protected static final JPEGImageReaderSpi SPI = new JPEGImageReaderSpi(lookupDelegateProvider());
|
||||
|
||||
private static ImageReaderSpi lookupDelegateProvider() {
|
||||
protected static ImageReaderSpi lookupDelegateProvider() {
|
||||
return JPEGImageReaderSpi.lookupDelegateProvider(IIORegistry.getDefaultInstance());
|
||||
}
|
||||
|
||||
@@ -85,6 +86,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
return Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/jpeg/cmm-exception-adobe-rgb.jpg"), new Dimension(626, 76)),
|
||||
new TestData(getClassLoaderResource("/jpeg/cmm-exception-srgb.jpg"), new Dimension(1800, 1200)),
|
||||
new TestData(getClassLoaderResource("/jpeg/corrupted-icc-srgb.jpg"), new Dimension(1024, 685)),
|
||||
new TestData(getClassLoaderResource("/jpeg/gray-sample.jpg"), new Dimension(386, 396)),
|
||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample.jpg"), new Dimension(160, 227)),
|
||||
new TestData(getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), new Dimension(2707, 3804)),
|
||||
@@ -149,7 +151,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
|
||||
@Override
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/jpeg");
|
||||
return Collections.singletonList("image/jpeg");
|
||||
}
|
||||
|
||||
// TODO: Test that subsampling is actually reading something
|
||||
@@ -368,7 +370,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
assertEquals(1772, image.getWidth());
|
||||
assertEquals(8, image.getHeight());
|
||||
|
||||
verify(warningListener).warningOccurred(eq(reader), anyString());
|
||||
verify(warningListener, atLeast(1)).warningOccurred(eq(reader), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1098,7 +1100,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
assertTrue(markerSequences.getLength() == 1 || markerSequences.getLength() == 2); // In case of JPEG encoded thumbnail, there will be 2
|
||||
IIOMetadataNode markerSequence = (IIOMetadataNode) markerSequences.item(0);
|
||||
assertNotNull(markerSequence);
|
||||
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<Integer>(0));
|
||||
assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
|
||||
|
||||
NodeList unknowns = markerSequence.getElementsByTagName("unknown");
|
||||
for (int j = 0; j < unknowns.getLength(); j++) {
|
||||
@@ -1157,10 +1159,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
JPEGImageReader reader = createReader();
|
||||
ImageReader referenceReader = createReferenceReader();
|
||||
|
||||
if (referenceReader == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (TestData testData : getTestData()) {
|
||||
reader.setInput(testData.getInputStream());
|
||||
referenceReader.setInput(testData.getInputStream());
|
||||
@@ -1201,13 +1199,15 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
Class<ImageReaderSpi> spiClass = (Class<ImageReaderSpi>) Class.forName("com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi");
|
||||
ImageReaderSpi provider = spiClass.newInstance();
|
||||
|
||||
return provider.createReaderInstance();
|
||||
ImageReader reader = provider.createReaderInstance();
|
||||
assumeNotNull(reader);
|
||||
return reader;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
System.err.println("WARNING: Could not create ImageReader for reference (missing dependency): " + t.getMessage());
|
||||
|
||||
return null;
|
||||
assumeNoException(t);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void assertTreesEquals(String message, Node expectedTree, Node actualTree) {
|
||||
@@ -1290,7 +1290,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
}
|
||||
|
||||
private List<IIOMetadataNode> sortNodes(final NodeList nodes) {
|
||||
ArrayList<IIOMetadataNode> sortedNodes = new ArrayList<IIOMetadataNode>(new AbstractList<IIOMetadataNode>() {
|
||||
ArrayList<IIOMetadataNode> sortedNodes = new ArrayList<>(new AbstractList<IIOMetadataNode>() {
|
||||
@Override
|
||||
public IIOMetadataNode get(int index) {
|
||||
return (IIOMetadataNode) nodes.item(index);
|
||||
@@ -1438,7 +1438,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
// TODO: Report bug!
|
||||
|
||||
ImageReader reader = createReader();
|
||||
// ImageReader reader = createReferenceReader();
|
||||
|
||||
try {
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/progressive-adobe-sof-bands-dont-match-sos-band-count.jpg")));
|
||||
@@ -1494,4 +1493,40 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase<JPEGImageRe
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRawImageTypeAdobeAPP14CMYKAnd3channelData() throws IOException {
|
||||
JPEGImageReader reader = createReader();
|
||||
|
||||
try {
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
|
||||
|
||||
ImageTypeSpecifier rawType = reader.getRawImageType(0);
|
||||
assertNull(rawType); // But no exception, please...
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadAdobeAPP14CMYKAnd3channelData() throws IOException {
|
||||
JPEGImageReader reader = createReader();
|
||||
|
||||
try {
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/exif-jfif-app13-app14ycck-3channel.jpg")));
|
||||
|
||||
assertEquals(310, reader.getWidth(0));
|
||||
assertEquals(384, reader.getHeight(0));
|
||||
|
||||
BufferedImage image = reader.read(0, null);
|
||||
assertNotNull(image);
|
||||
assertEquals(310, image.getWidth());
|
||||
assertEquals(384, image.getHeight());
|
||||
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
|
||||
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
|
||||
|
||||
/**
|
||||
* JPEGProviderInfoTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: JPEGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
|
||||
*/
|
||||
public class JPEGProviderInfoTest extends ReaderWriterProviderInfoTest {
|
||||
|
||||
@Override
|
||||
protected ReaderWriterProviderInfo createProviderInfo() {
|
||||
return new JPEGProviderInfo();
|
||||
}
|
||||
}
|
||||
+18
@@ -74,6 +74,24 @@ public class JPEGSegmentImageInputStreamTest {
|
||||
stream.read();
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testStreamNonJPEGArray() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[] {42, 42, 0, 0, 77, 99})));
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testStreamEmpty() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
|
||||
stream.read();
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testStreamEmptyArray() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStreamRealData() throws IOException {
|
||||
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg")));
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 242 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
<version>3.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -64,4 +64,8 @@ public interface Entry {
|
||||
|
||||
// For arrays only
|
||||
int valueCount();
|
||||
|
||||
// TODO: getValueAsInt, UnsignedInt, Short, UnsignedShort, Byte, UnsignedByte etc
|
||||
// TODO: getValueAsIntArray, ShortArray, ByteArray, StringArray etc (also for non-arrays, to return a single element array)
|
||||
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.twelvemonkeys.imageio.metadata;
|
||||
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MetadataWriter.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: MetadataWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
|
||||
*/
|
||||
public abstract class MetadataWriter {
|
||||
abstract public boolean write(Directory directory, ImageOutputStream stream) throws IOException;
|
||||
}
|
||||
+35
-12
@@ -38,13 +38,14 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
||||
*/
|
||||
final class EXIFEntry extends AbstractEntry {
|
||||
// TODO: Expose as TIFFEntry
|
||||
final private short type;
|
||||
|
||||
EXIFEntry(final int identifier, final Object value, final short type) {
|
||||
super(identifier, value);
|
||||
|
||||
if (type < 1 || type > TIFF.TYPE_NAMES.length) {
|
||||
throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", type));
|
||||
if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
|
||||
throw new IllegalArgumentException(String.format("Illegal TIFF type: %s", type));
|
||||
}
|
||||
|
||||
// TODO: Validate that type is applicable to value?
|
||||
@@ -71,6 +72,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "IPTC";
|
||||
case TIFF.TAG_PHOTOSHOP:
|
||||
return "Adobe";
|
||||
case TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA:
|
||||
return "ImageSourceData";
|
||||
case TIFF.TAG_ICC_PROFILE:
|
||||
return "ICCProfile";
|
||||
|
||||
@@ -84,8 +87,16 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "Compression";
|
||||
case TIFF.TAG_PHOTOMETRIC_INTERPRETATION:
|
||||
return "PhotometricInterpretation";
|
||||
case TIFF.TAG_FILL_ORDER:
|
||||
return "FillOrder";
|
||||
case TIFF.TAG_DOCUMENT_NAME:
|
||||
return "DocumentName";
|
||||
case TIFF.TAG_IMAGE_DESCRIPTION:
|
||||
return "ImageDescription";
|
||||
case TIFF.TAG_MAKE:
|
||||
return "Make";
|
||||
case TIFF.TAG_MODEL:
|
||||
return "Model";
|
||||
case TIFF.TAG_STRIP_OFFSETS:
|
||||
return "StripOffsets";
|
||||
case TIFF.TAG_ORIENTATION:
|
||||
@@ -104,14 +115,10 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "PlanarConfiguration";
|
||||
case TIFF.TAG_RESOLUTION_UNIT:
|
||||
return "ResolutionUnit";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
|
||||
return "JPEGInterchangeFormat";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
||||
return "JPEGInterchangeFormatLength";
|
||||
case TIFF.TAG_MAKE:
|
||||
return "Make";
|
||||
case TIFF.TAG_MODEL:
|
||||
return "Model";
|
||||
case TIFF.TAG_PAGE_NAME:
|
||||
return "PageName";
|
||||
case TIFF.TAG_PAGE_NUMBER:
|
||||
return "PageNumber";
|
||||
case TIFF.TAG_SOFTWARE:
|
||||
return "Software";
|
||||
case TIFF.TAG_DATE_TIME:
|
||||
@@ -138,10 +145,20 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "YCbCrPositioning";
|
||||
case TIFF.TAG_COLOR_MAP:
|
||||
return "ColorMap";
|
||||
case TIFF.TAG_INK_SET:
|
||||
return "InkSet";
|
||||
case TIFF.TAG_INK_NAMES:
|
||||
return "InkNames";
|
||||
case TIFF.TAG_EXTRA_SAMPLES:
|
||||
return "ExtraSamples";
|
||||
case TIFF.TAG_SAMPLE_FORMAT:
|
||||
return "SampleFormat";
|
||||
case TIFF.TAG_JPEG_TABLES:
|
||||
return "JPEGTables";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
|
||||
return "JPEGInterchangeFormat";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
||||
return "JPEGInterchangeFormatLength";
|
||||
|
||||
case TIFF.TAG_SUB_IFD:
|
||||
return "SubIFD";
|
||||
@@ -176,6 +193,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "Flash";
|
||||
case EXIF.TAG_FOCAL_LENGTH:
|
||||
return "FocalLength";
|
||||
case EXIF.TAG_SENSING_METHOD:
|
||||
return "SensingMethod";
|
||||
case EXIF.TAG_FILE_SOURCE:
|
||||
return "FileSource";
|
||||
case EXIF.TAG_SCENE_TYPE:
|
||||
@@ -189,7 +208,7 @@ final class EXIFEntry extends AbstractEntry {
|
||||
case EXIF.TAG_WHITE_BALANCE:
|
||||
return "WhiteBalance";
|
||||
case EXIF.TAG_DIGITAL_ZOOM_RATIO:
|
||||
return "DigitalZoomRation";
|
||||
return "DigitalZoomRatio";
|
||||
case EXIF.TAG_FOCAL_LENGTH_IN_35_MM_FILM:
|
||||
return "FocalLengthIn35mmFilm";
|
||||
case EXIF.TAG_SCENE_CAPTURE_TYPE:
|
||||
@@ -202,6 +221,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "Saturation";
|
||||
case EXIF.TAG_SHARPNESS:
|
||||
return "Sharpness";
|
||||
case EXIF.TAG_IMAGE_UNIQUE_ID:
|
||||
return "ImageUniqueID";
|
||||
|
||||
case EXIF.TAG_FLASHPIX_VERSION:
|
||||
return "FlashpixVersion";
|
||||
@@ -214,6 +235,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "DateTimeDigitized";
|
||||
case EXIF.TAG_IMAGE_NUMBER:
|
||||
return "ImageNumber";
|
||||
case EXIF.TAG_MAKER_NOTE:
|
||||
return "MakerNote";
|
||||
case EXIF.TAG_USER_COMMENT:
|
||||
return "UserComment";
|
||||
|
||||
@@ -259,6 +282,6 @@ final class EXIFEntry extends AbstractEntry {
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return TIFF.TYPE_NAMES[type - 1];
|
||||
return TIFF.TYPE_NAMES[type];
|
||||
}
|
||||
}
|
||||
|
||||
+49
-41
@@ -71,7 +71,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
else if (bom[0] == 'M' && bom[1] == 'M') {
|
||||
input.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
else {
|
||||
else {
|
||||
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
|
||||
int magic = input.readUnsignedShort();
|
||||
if (magic != TIFF.TIFF_MAGIC) {
|
||||
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
|
||||
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
|
||||
}
|
||||
|
||||
long directoryOffset = input.readUnsignedInt();
|
||||
@@ -89,8 +89,8 @@ public final class EXIFReader extends MetadataReader {
|
||||
|
||||
// TODO: Consider re-writing so that the linked IFD parsing is done externally to the method
|
||||
protected Directory readDirectory(final ImageInputStream pInput, final long pOffset, final boolean readLinked) throws IOException {
|
||||
List<IFD> ifds = new ArrayList<IFD>();
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
List<IFD> ifds = new ArrayList<>();
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
|
||||
pInput.seek(pOffset);
|
||||
long nextOffset = -1;
|
||||
@@ -105,21 +105,27 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
EXIFEntry entry = readEntry(pInput);
|
||||
try {
|
||||
EXIFEntry entry = readEntry(pInput);
|
||||
|
||||
if (entry == null) {
|
||||
// System.err.println("Expected: " + entryCount + " values, found only " + i);
|
||||
// TODO: Log warning?
|
||||
nextOffset = 0;
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
break;
|
||||
}
|
||||
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
if (readLinked) {
|
||||
if (nextOffset == -1) {
|
||||
nextOffset = pInput.readUnsignedInt();
|
||||
try {
|
||||
nextOffset = pInput.readUnsignedInt();
|
||||
}
|
||||
catch (EOFException e) {
|
||||
// catch EOF here as missing EOF marker
|
||||
nextOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Read linked IFDs
|
||||
@@ -138,7 +144,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
);
|
||||
|
||||
ifds.add(0, new IFD(entries));
|
||||
|
||||
|
||||
return new EXIFDirectory(ifds);
|
||||
}
|
||||
|
||||
@@ -156,7 +162,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
try {
|
||||
if (KNOWN_IFDS.contains(tagId)) {
|
||||
long[] pointerOffsets = getPointerOffsets(entry);
|
||||
List<IFD> subIFDs = new ArrayList<IFD>(pointerOffsets.length);
|
||||
List<IFD> subIFDs = new ArrayList<>(pointerOffsets.length);
|
||||
|
||||
for (long pointerOffset : pointerOffsets) {
|
||||
CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
|
||||
@@ -170,15 +176,18 @@ public final class EXIFReader extends MetadataReader {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new EXIFEntry(tagId, subIFDs.get(0), entry.getType()));
|
||||
}
|
||||
else {
|
||||
else {
|
||||
// Replace the entry with parsed data
|
||||
entries.set(i, new EXIFEntry(tagId, subIFDs.toArray(new IFD[subIFDs.size()]), entry.getType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IIOException e) {
|
||||
// TODO: Issue warning without crashing...?
|
||||
e.printStackTrace();
|
||||
if (DEBUG) {
|
||||
// TODO: Issue warning without crashing...?
|
||||
System.err.println("Error parsing sub-IFD: " + tagId);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,7 +213,9 @@ public final class EXIFReader extends MetadataReader {
|
||||
offsets = (long[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IIOException(String.format("Unknown pointer type: %s", (value != null ? value.getClass() : null)));
|
||||
throw new IIOException(String.format("Unknown pointer type: %s", (value != null
|
||||
? value.getClass()
|
||||
: null)));
|
||||
}
|
||||
|
||||
return offsets;
|
||||
@@ -215,11 +226,6 @@ public final class EXIFReader extends MetadataReader {
|
||||
int tagId = pInput.readUnsignedShort();
|
||||
short type = pInput.readShort();
|
||||
|
||||
// This isn't really an entry, and the directory entry count was wrong OR bad data...
|
||||
if (tagId == 0 && type == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int count = pInput.readInt(); // Number of values
|
||||
|
||||
// It's probably a spec violation to have count 0, but we'll be lenient about it
|
||||
@@ -228,32 +234,33 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
if (type <= 0 || type > 13) {
|
||||
pInput.skipBytes(4); // read Value
|
||||
|
||||
// Invalid tag, this is just for debugging
|
||||
long offset = pInput.getStreamPosition() - 8l;
|
||||
long offset = pInput.getStreamPosition() - 12l;
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
|
||||
System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
|
||||
System.err.println("type: " + type + " (INVALID)");
|
||||
System.err.println("count: " + count);
|
||||
}
|
||||
|
||||
pInput.mark();
|
||||
pInput.seek(offset);
|
||||
pInput.mark();
|
||||
pInput.seek(offset);
|
||||
|
||||
try {
|
||||
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
|
||||
int len = pInput.read(bytes);
|
||||
try {
|
||||
byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
|
||||
int len = pInput.read(bytes);
|
||||
|
||||
if (DEBUG) {
|
||||
System.err.print(HexDump.dump(offset, bytes, 0, len));
|
||||
System.err.println(len < count ? "[...]" : "");
|
||||
if (DEBUG) {
|
||||
System.err.print(HexDump.dump(offset, bytes, 0, len));
|
||||
System.err.println(len < count ? "[...]" : "");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -446,8 +453,8 @@ public final class EXIFReader extends MetadataReader {
|
||||
}
|
||||
|
||||
static int getValueLength(final int pType, final int pCount) {
|
||||
if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType - 1] * pCount;
|
||||
if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType] * pCount;
|
||||
}
|
||||
|
||||
return -1;
|
||||
@@ -501,7 +508,8 @@ public final class EXIFReader extends MetadataReader {
|
||||
//////////////////////
|
||||
// TODO: Stream based hex dump util?
|
||||
public static class HexDump {
|
||||
private HexDump() {}
|
||||
private HexDump() {
|
||||
}
|
||||
|
||||
private static final int WIDTH = 32;
|
||||
|
||||
@@ -515,7 +523,7 @@ public final class EXIFReader extends MetadataReader {
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (i % WIDTH == 0) {
|
||||
if (i > 0 ) {
|
||||
if (i > 0) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(String.format("%08x: ", i + off + offset));
|
||||
|
||||
+82
-16
@@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
@@ -39,6 +40,7 @@ import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -48,7 +50,7 @@ import java.util.*;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
|
||||
*/
|
||||
public class EXIFWriter {
|
||||
public final class EXIFWriter extends MetadataWriter {
|
||||
|
||||
static final int WORD_LENGTH = 2;
|
||||
static final int LONGWORD_LENGTH = 4;
|
||||
@@ -58,6 +60,7 @@ public class EXIFWriter {
|
||||
return write(new IFD(entries), stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||
Validate.notNull(directory);
|
||||
Validate.notNull(stream);
|
||||
@@ -92,11 +95,11 @@ public class EXIFWriter {
|
||||
stream.writeShort(42);
|
||||
}
|
||||
|
||||
public long writeIFD(final Collection<Entry> entries, ImageOutputStream stream) throws IOException {
|
||||
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
return writeIFD(new IFD(entries), stream, false);
|
||||
}
|
||||
|
||||
private long writeIFD(final Directory original, ImageOutputStream stream, boolean isSubIFD) throws IOException {
|
||||
private long writeIFD(final Directory original, final ImageOutputStream stream, final boolean isSubIFD) throws IOException {
|
||||
// TIFF spec says tags should be in increasing order, enforce that when writing
|
||||
Directory ordered = ensureOrderedDirectory(original);
|
||||
|
||||
@@ -155,6 +158,10 @@ public class EXIFWriter {
|
||||
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
|
||||
}
|
||||
|
||||
public long computeIFDOffsetSize(final Collection<Entry> directory) {
|
||||
return computeDataSize(new IFD(directory)) + LONGWORD_LENGTH;
|
||||
}
|
||||
|
||||
private long computeDataSize(final Directory directory) {
|
||||
long dataSize = 0;
|
||||
|
||||
@@ -181,7 +188,7 @@ public class EXIFWriter {
|
||||
|
||||
private Directory ensureOrderedDirectory(final Directory directory) {
|
||||
if (!isSorted(directory)) {
|
||||
List<Entry> entries = new ArrayList<Entry>(directory.size());
|
||||
List<Entry> entries = new ArrayList<>(directory.size());
|
||||
|
||||
for (Entry entry : directory) {
|
||||
entries.add(entry);
|
||||
@@ -215,7 +222,7 @@ public class EXIFWriter {
|
||||
return true;
|
||||
}
|
||||
|
||||
private long writeValue(Entry entry, long dataOffset, ImageOutputStream stream) throws IOException {
|
||||
private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException {
|
||||
short type = getType(entry);
|
||||
int valueLength = EXIFReader.getValueLength(type, getCount(entry));
|
||||
|
||||
@@ -236,18 +243,22 @@ public class EXIFWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private int getCount(Entry entry) {
|
||||
private int getCount(final Entry entry) {
|
||||
Object value = entry.getValue();
|
||||
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
|
||||
}
|
||||
|
||||
private void writeValueInline(Object value, short type, ImageOutputStream stream) throws IOException {
|
||||
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
if (value.getClass().isArray()) {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
case TIFF.TYPE_BYTE:
|
||||
case TIFF.TYPE_SBYTE:
|
||||
stream.write((byte[]) value);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_SHORT:
|
||||
case TIFF.TYPE_SSHORT:
|
||||
short[] shorts;
|
||||
|
||||
if (value instanceof short[]) {
|
||||
@@ -276,7 +287,9 @@ public class EXIFWriter {
|
||||
|
||||
stream.writeShorts(shorts, 0, shorts.length);
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
int[] ints;
|
||||
|
||||
if (value instanceof int[]) {
|
||||
@@ -291,21 +304,49 @@ public class EXIFWriter {
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass());
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF LONG: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeInts(ints, 0, ints.length);
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational[] rationals = (Rational[]) value;
|
||||
for (Rational rational : rationals) {
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
}
|
||||
|
||||
// TODO: More types
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_FLOAT:
|
||||
float[] floats;
|
||||
|
||||
if (value instanceof float[]) {
|
||||
floats = (float[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeFloats(floats, 0, floats.length);
|
||||
|
||||
break;
|
||||
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
double[] doubles;
|
||||
|
||||
if (value instanceof double[]) {
|
||||
doubles = (double[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeDoubles(doubles, 0, doubles.length);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
@@ -317,25 +358,35 @@ public class EXIFWriter {
|
||||
else {
|
||||
switch (type) {
|
||||
case TIFF.TYPE_BYTE:
|
||||
stream.writeByte((Integer) value);
|
||||
case TIFF.TYPE_SBYTE:
|
||||
case TIFF.TYPE_UNDEFINED:
|
||||
stream.writeByte(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_ASCII:
|
||||
byte[] bytes = ((String) value).getBytes(Charset.forName("UTF-8"));
|
||||
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
|
||||
stream.write(bytes);
|
||||
stream.write(0);
|
||||
break;
|
||||
case TIFF.TYPE_SHORT:
|
||||
stream.writeShort((Integer) value);
|
||||
case TIFF.TYPE_SSHORT:
|
||||
stream.writeShort(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
stream.writeInt(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
case TIFF.TYPE_SRATIONAL:
|
||||
Rational rational = (Rational) value;
|
||||
stream.writeInt((int) rational.numerator());
|
||||
stream.writeInt((int) rational.denominator());
|
||||
break;
|
||||
// TODO: More types
|
||||
case TIFF.TYPE_FLOAT:
|
||||
stream.writeFloat(((Number) value).floatValue());
|
||||
break;
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
stream.writeDouble(((Number) value).doubleValue());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
@@ -343,7 +394,7 @@ public class EXIFWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeValueAt(long dataOffset, Object value, short type, ImageOutputStream stream) throws IOException {
|
||||
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long position = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
@@ -351,12 +402,27 @@ public class EXIFWriter {
|
||||
stream.seek(position);
|
||||
}
|
||||
|
||||
private short getType(Entry entry) {
|
||||
private short getType(final Entry entry) {
|
||||
// TODO: What a MESS! Rewrite and expose EXIFEntry as TIFFEntry or so...
|
||||
|
||||
// For internal entries use type directly
|
||||
if (entry instanceof EXIFEntry) {
|
||||
EXIFEntry exifEntry = (EXIFEntry) entry;
|
||||
return exifEntry.getType();
|
||||
}
|
||||
|
||||
// For other entries, use name if it matches
|
||||
String typeName = entry.getTypeName();
|
||||
|
||||
if (typeName != null) {
|
||||
for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
|
||||
if (typeName.equals(TIFF.TYPE_NAMES[i])) {
|
||||
return (short) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, fall back to the native Java type
|
||||
Object value = Validate.notNull(entry.getValue());
|
||||
|
||||
boolean array = value.getClass().isArray();
|
||||
|
||||
+23
@@ -88,13 +88,16 @@ public interface TIFF {
|
||||
Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
|
||||
*/
|
||||
String[] TYPE_NAMES = {
|
||||
null,
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
"IFD",
|
||||
null, null,
|
||||
"LONG8", "SLONG8", "IFD8"
|
||||
};
|
||||
/** Length of the corresponding type, in bytes. */
|
||||
int[] TYPE_LENGTHS = {
|
||||
-1,
|
||||
1, 1, 2, 4, 8,
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
4,
|
||||
@@ -124,6 +127,8 @@ public interface TIFF {
|
||||
int TAG_YCBCR_POSITIONING = 531;
|
||||
int TAG_X_RESOLUTION = 282;
|
||||
int TAG_Y_RESOLUTION = 283;
|
||||
int TAG_X_POSITION = 286;
|
||||
int TAG_Y_POSITION = 287;
|
||||
int TAG_RESOLUTION_UNIT = 296;
|
||||
|
||||
/// B. Tags relating to recording offset
|
||||
@@ -131,9 +136,13 @@ public interface TIFF {
|
||||
int TAG_STRIP_OFFSETS = 273;
|
||||
int TAG_ROWS_PER_STRIP = 278;
|
||||
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||
int TAG_FREE_OFFSETS = 288; // "Not recommended for general interchange."
|
||||
// "Old-style" JPEG (still used as EXIF thumbnail)
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||
|
||||
int TAG_GROUP3OPTIONS = 292;
|
||||
int TAG_GROUP4OPTIONS = 293;
|
||||
|
||||
/// C. Tags relating to image data characteristics
|
||||
|
||||
@@ -153,9 +162,11 @@ public interface TIFF {
|
||||
/// D. Other tags
|
||||
|
||||
int TAG_DATE_TIME = 306;
|
||||
int TAG_DOCUMENT_NAME = 269;
|
||||
int TAG_IMAGE_DESCRIPTION = 270;
|
||||
int TAG_MAKE = 271;
|
||||
int TAG_MODEL = 272;
|
||||
int TAG_PAGE_NAME = 285;
|
||||
int TAG_PAGE_NUMBER = 297;
|
||||
int TAG_SOFTWARE = 305;
|
||||
int TAG_ARTIST = 315;
|
||||
@@ -184,6 +195,18 @@ public interface TIFF {
|
||||
*/
|
||||
int TAG_PHOTOSHOP = 34377;
|
||||
|
||||
/**
|
||||
* Photoshop layer and mask information (byte order follows TIFF container).
|
||||
* Layer and mask information found in a typical layered Photoshop file.
|
||||
* Starts with a character string of "Adobe Photoshop Document Data Block"
|
||||
* (or "Adobe Photoshop Document Data V0002" for PSB)
|
||||
* including the null termination character.
|
||||
* @see com.twelvemonkeys.imageio.metadata.psd.PSD
|
||||
*/
|
||||
int TAG_PHOTOSHOP_IMAGE_SOURCE_DATA = 37724;
|
||||
|
||||
int TAG_PHOTOSHOP_ANNOTATIONS = 50255;
|
||||
|
||||
/**
|
||||
* ICC Color Profile.
|
||||
* @see java.awt.color.ICC_Profile
|
||||
|
||||
+77
-53
@@ -36,110 +36,115 @@ package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
* @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$
|
||||
*/
|
||||
public interface IPTC {
|
||||
static final int ENVELOPE_RECORD = 1 << 8;
|
||||
static final int APPLICATION_RECORD = 2 << 8;
|
||||
int ENVELOPE_RECORD = 1 << 8;
|
||||
int APPLICATION_RECORD = 2 << 8;
|
||||
|
||||
static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
|
||||
/** 1:05: Destination */
|
||||
int TAG_DESTINATION = ENVELOPE_RECORD | 5;
|
||||
/** 1:50: Product ID */
|
||||
int TAG_PRODUCT_ID = ENVELOPE_RECORD | 50;
|
||||
/** 1:90: Coded Character Set */
|
||||
int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
|
||||
|
||||
/** 2:00 Record Version (mandatory) */
|
||||
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
||||
int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
||||
|
||||
/** 2:03 Object Type Reference */
|
||||
public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
|
||||
int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
|
||||
/** 2:04 Object Attribute Reference (repeatable) */
|
||||
public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
|
||||
int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
|
||||
/** 2:05 Object Name */
|
||||
public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
|
||||
int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
|
||||
/** 2:07 Edit Status */
|
||||
public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
||||
int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
||||
/** 2:08 Editorial Update */
|
||||
public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
||||
int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
||||
/** 2:10 Urgency */
|
||||
public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
|
||||
int TAG_URGENCY = APPLICATION_RECORD | 10;
|
||||
/** 2:12 Subect Reference (repeatable) */
|
||||
public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
|
||||
int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
|
||||
/** 2:15 Category */
|
||||
public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
||||
int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
||||
/** 2:20 Supplemental Category (repeatable) */
|
||||
public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
|
||||
int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
|
||||
/** 2:22 Fixture Identifier */
|
||||
public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
||||
int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
||||
/** 2:25 Keywords (repeatable) */
|
||||
public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25;
|
||||
int TAG_KEYWORDS = APPLICATION_RECORD | 25;
|
||||
/** 2:26 Content Locataion Code (repeatable) */
|
||||
public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
|
||||
int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
|
||||
/** 2:27 Content Locataion Name (repeatable) */
|
||||
public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
|
||||
int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
|
||||
/** 2:30 Release Date */
|
||||
public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
|
||||
int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
|
||||
/** 2:35 Release Time */
|
||||
public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
||||
int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
||||
/** 2:37 Expiration Date */
|
||||
public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
|
||||
int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
|
||||
/** 2:38 Expiration Time */
|
||||
public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
||||
int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
||||
/** 2:40 Special Instructions */
|
||||
public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
|
||||
int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
|
||||
/** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */
|
||||
public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
|
||||
int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
|
||||
/** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
|
||||
public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
|
||||
int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
|
||||
/** 2:47 Reference Date (mandatory if 2:45 present) */
|
||||
public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
|
||||
int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
|
||||
/** 2:50 Reference Number (mandatory if 2:45 present) */
|
||||
public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
||||
int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
||||
/** 2:55 Date Created */
|
||||
public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
|
||||
int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
|
||||
/** 2:60 Time Created */
|
||||
public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
||||
int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
||||
/** 2:62 Digital Creation Date */
|
||||
public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
|
||||
int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
|
||||
/** 2:63 Digital Creation Date */
|
||||
public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
||||
int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
||||
/** 2:65 Originating Program */
|
||||
public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
|
||||
int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
|
||||
/** 2:70 Program Version (only valid if 2:65 present) */
|
||||
public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
|
||||
int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
|
||||
/** 2:75 Object Cycle (a: morning, p: evening, b: both) */
|
||||
public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
||||
int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
||||
/** 2:80 By-line (repeatable) */
|
||||
public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
|
||||
int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
|
||||
/** 2:85 By-line Title (repeatable) */
|
||||
public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
|
||||
int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
|
||||
/** 2:90 City */
|
||||
public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
||||
int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
||||
/** 2:92 Sub-location */
|
||||
public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
|
||||
int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
|
||||
/** 2:95 Province/State */
|
||||
public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
|
||||
int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
|
||||
/** 2:100 Country/Primary Location Code */
|
||||
public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
|
||||
int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
|
||||
/** 2:101 Country/Primary Location Name */
|
||||
public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
|
||||
int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
|
||||
/** 2:103 Original Transmission Reference */
|
||||
public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
|
||||
int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
|
||||
/** 2:105 Headline */
|
||||
public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
||||
int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
||||
/** 2:110 Credit */
|
||||
public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
||||
int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
||||
/** 2:115 Source */
|
||||
public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
||||
int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
||||
/** 2:116 Copyright Notice */
|
||||
public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
|
||||
int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
|
||||
/** 2:118 Contact */
|
||||
public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
|
||||
int TAG_CONTACT = APPLICATION_RECORD | 118;
|
||||
/** 2:120 Catption/Abstract */
|
||||
public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
|
||||
int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
|
||||
/** 2:122 Writer/Editor (repeatable) */
|
||||
public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
||||
int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
||||
/** 2:125 Rasterized Caption (binary data) */
|
||||
public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
|
||||
int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
|
||||
/** 2:130 Image Type */
|
||||
public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
||||
int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
||||
/** 2:131 Image Orientation */
|
||||
public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
|
||||
int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
|
||||
/** 2:135 Language Identifier */
|
||||
public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
||||
int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
||||
|
||||
// TODO: 2:150-2:154 Audio
|
||||
|
||||
@@ -150,9 +155,28 @@ public interface IPTC {
|
||||
*
|
||||
* @see <a href="http://www.jobminder.net/">JobMinder Homepage</a>
|
||||
*/
|
||||
static final int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
|
||||
int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
|
||||
|
||||
// TODO: Other custom fields in 155-200 range?
|
||||
|
||||
// TODO: 2:200-2:202 Object Preview Data
|
||||
|
||||
final class Tags {
|
||||
static boolean isArray(final short tagId) {
|
||||
switch (tagId) {
|
||||
case IPTC.TAG_DESTINATION:
|
||||
case IPTC.TAG_PRODUCT_ID:
|
||||
case IPTC.TAG_SUBJECT_REFERENCE:
|
||||
case IPTC.TAG_SUPPLEMENTAL_CATEGORIES:
|
||||
case IPTC.TAG_KEYWORDS:
|
||||
case IPTC.TAG_CONTENT_LOCATION_CODE:
|
||||
case IPTC.TAG_CONTENT_LOCATION_NAME:
|
||||
case IPTC.TAG_BY_LINE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ import java.util.Collection;
|
||||
*/
|
||||
final class IPTCDirectory extends AbstractDirectory {
|
||||
IPTCDirectory(final Collection<? extends Entry> entries) {
|
||||
// TODO: Normalize multiple entries with same key to single entry w/array
|
||||
super(entries);
|
||||
}
|
||||
}
|
||||
+24
@@ -47,6 +47,30 @@ class IPTCEntry extends AbstractEntry {
|
||||
switch ((Integer) getIdentifier()) {
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
return "RecordVersion";
|
||||
case IPTC.TAG_KEYWORDS:
|
||||
return "Keywords";
|
||||
case IPTC.TAG_SPECIAL_INSTRUCTIONS:
|
||||
return "Instructions";
|
||||
case IPTC.TAG_DIGITAL_CREATION_DATE:
|
||||
return "DigitalCreationDate";
|
||||
case IPTC.TAG_DIGITAL_CREATION_TIME:
|
||||
return "DigitalCreationTime";
|
||||
case IPTC.TAG_DATE_CREATED:
|
||||
return "DateCreated";
|
||||
case IPTC.TAG_TIME_CREATED:
|
||||
return "TimeCreated";
|
||||
case IPTC.TAG_BY_LINE_TITLE:
|
||||
return "ByLineTitle";
|
||||
case IPTC.TAG_CITY:
|
||||
return "City";
|
||||
case IPTC.TAG_SUB_LOCATION:
|
||||
return "SubLocation";
|
||||
case IPTC.TAG_PROVINCE_OR_STATE:
|
||||
return "StateProvince";
|
||||
case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE:
|
||||
return "CountryCode";
|
||||
case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION:
|
||||
return "Country";
|
||||
case IPTC.TAG_SOURCE:
|
||||
return "Source";
|
||||
case IPTC.TAG_CAPTION:
|
||||
|
||||
+56
-28
@@ -43,8 +43,9 @@ import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IPTCReader
|
||||
@@ -60,61 +61,88 @@ public final class IPTCReader extends MetadataReader {
|
||||
|
||||
private int encoding = ENCODING_UNSPECIFIED;
|
||||
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
Map<Short, Entry> entries = new LinkedHashMap<>();
|
||||
|
||||
// 0x1c identifies start of a tag
|
||||
while (input.read() == 0x1c) {
|
||||
short tagId = input.readShort();
|
||||
int tagByteCount = input.readUnsignedShort();
|
||||
Entry entry = readEntry(input, tagId, tagByteCount);
|
||||
|
||||
boolean array = IPTC.Tags.isArray(tagId);
|
||||
Entry entry = readEntry(input, tagId, tagByteCount, array, array ? entries.get(tagId) : null);
|
||||
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
entries.put(tagId, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCDirectory(entries);
|
||||
return new IPTCDirectory(entries.values());
|
||||
}
|
||||
|
||||
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException {
|
||||
Object value = null;
|
||||
private IPTCEntry mergeEntries(final short tagId, final Object newValue, final Entry oldEntry) {
|
||||
Object[] oldValue = oldEntry != null ? (Object[]) oldEntry.getValue() : null;
|
||||
Object[] value;
|
||||
|
||||
if (newValue instanceof String) {
|
||||
if (oldValue == null) {
|
||||
value = new String[] {(String) newValue};
|
||||
}
|
||||
else {
|
||||
String[] array = (String[]) oldValue;
|
||||
value = Arrays.copyOf(array, array.length + 1);
|
||||
value[value.length - 1] = newValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (oldValue == null) {
|
||||
value = new Object[] {newValue};
|
||||
}
|
||||
else {
|
||||
value = Arrays.copyOf(oldValue, oldValue.length + 1);
|
||||
value [value .length - 1] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCEntry(tagId, value);
|
||||
}
|
||||
|
||||
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength, final boolean array, final Entry oldEntry) throws IOException {
|
||||
Object value;
|
||||
|
||||
switch (pTagId) {
|
||||
case IPTC.TAG_CODED_CHARACTER_SET:
|
||||
// TODO: Mapping from ISO 646 to Java supported character sets?
|
||||
// TODO: Move somewhere else?
|
||||
encoding = parseEncoding(pInput, pLength);
|
||||
return null;
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
// TODO: Assert length == 2?
|
||||
// A single unsigned short value
|
||||
value = pInput.readUnsignedShort();
|
||||
break;
|
||||
default:
|
||||
// Skip non-Application fields, as they are typically not human readable
|
||||
if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) {
|
||||
pInput.skipBytes(pLength);
|
||||
return null;
|
||||
// TODO: Create Tags.getType(tag), to allow for more flexible types
|
||||
if ((pTagId & 0xff00) == IPTC.APPLICATION_RECORD) {
|
||||
// Treat Application records as Strings
|
||||
if (pLength < 1) {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Non-Application fields, typically not human readable
|
||||
byte[] data = new byte[pLength];
|
||||
pInput.readFully(data);
|
||||
value = data;
|
||||
}
|
||||
|
||||
// fall through
|
||||
}
|
||||
|
||||
// If we don't have a value, treat it as a string
|
||||
if (value == null) {
|
||||
if (pLength < 1) {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCEntry(pTagId, value);
|
||||
return array ? mergeEntries(pTagId, value, oldEntry) : new IPTCEntry(pTagId, value);
|
||||
}
|
||||
|
||||
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
|
||||
@@ -148,7 +176,7 @@ public final class IPTCReader extends MetadataReader {
|
||||
}
|
||||
|
||||
// Fall back to use ISO-8859-1
|
||||
// This will not fail, but may may create wrong fallback-characters
|
||||
// This will not fail, but may create wrong fallback-characters
|
||||
return StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
||||
}
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* IPTCWriter.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: harald.kuhr$
|
||||
* @version $Id: IPTCWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
|
||||
*/
|
||||
public final class IPTCWriter extends MetadataWriter {
|
||||
@Override
|
||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||
notNull(directory, "directory");
|
||||
notNull(stream, "stream");
|
||||
|
||||
// TODO: Make sure we always write application record version (2.00)
|
||||
// TODO: Write encoding UTF8?
|
||||
|
||||
for (Entry entry : directory) {
|
||||
int tag = (Integer) entry.getIdentifier();
|
||||
Object value = entry.getValue();
|
||||
|
||||
if (IPTC.Tags.isArray((short) tag)) {
|
||||
Object[] values = (Object[]) value;
|
||||
|
||||
for (Object v : values) {
|
||||
stream.write(0x1c);
|
||||
stream.writeShort(tag);
|
||||
writeValue(stream, v);
|
||||
}
|
||||
}
|
||||
else {
|
||||
stream.write(0x1c);
|
||||
stream.writeShort(tag);
|
||||
writeValue(stream, value);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void writeValue(final ImageOutputStream stream, final Object value) throws IOException {
|
||||
if (value instanceof String) {
|
||||
byte[] data = ((String) value).getBytes(StandardCharsets.UTF_8);
|
||||
stream.writeShort(data.length);
|
||||
stream.write(data);
|
||||
}
|
||||
else if (value instanceof byte[]) {
|
||||
byte[] data = (byte[]) value;
|
||||
stream.writeShort(data.length);
|
||||
stream.write(data);
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
// TODO: Need to know types from tag
|
||||
stream.writeShort(2);
|
||||
stream.writeShort((Integer) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -66,7 +66,8 @@ class PSDEntry extends AbstractEntry {
|
||||
field.setAccessible(true);
|
||||
|
||||
if (field.get(null).equals(getIdentifier())) {
|
||||
return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
|
||||
String fieldName = StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
|
||||
return name != null ? fieldName + ": " + name : fieldName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ public final class PSDReader extends MetadataReader {
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
List<PSDEntry> entries = new ArrayList<PSDEntry>();
|
||||
List<PSDEntry> entries = new ArrayList<>();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
|
||||
+1
@@ -71,6 +71,7 @@ public final class XMPReader extends MetadataReader {
|
||||
// TODO: Consider parsing using SAX?
|
||||
// TODO: Determine encoding and parse using a Reader...
|
||||
// TODO: Refactor scanner to return inputstream?
|
||||
// TODO: Be smarter about ASCII-NULL termination/padding (the SAXParser aka Xerces DOMParser doesn't like it)...
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user