com.twelvemonkeys.imageio.plugins.jpeg.JPEGImage10Metadata Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of imageio-jpeg Show documentation
Show all versions of imageio-jpeg Show documentation
ImageIO plugin for Joint Photographer Expert Group images (JPEG/JFIF).
The newest version!
/*
* Copyright (c) 2016, 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 of the copyright holder 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 HOLDER 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.AbstractMetadata;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import org.w3c.dom.Node;
import javax.imageio.IIOException;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.color.ICC_Profile;
import java.util.List;
/**
* JPEGImage10Metadata.
*
* @author Harald Kuhr
* @author last modified by $Author: harald.kuhr$
* @version $Id: JPEGImage10Metadata.java,v 1.0 10/08/16 harald.kuhr Exp$
*/
class JPEGImage10Metadata extends AbstractMetadata {
/**
* Native metadata format name
*/
static final String JAVAX_IMAGEIO_JPEG_IMAGE_1_0 = "javax_imageio_jpeg_image_1.0";
// TODO: Create our own native format, which is simply markerSequence from the Sun format, with the segments as-is, in sequence...
// + add special case for app segments, containing appXX + identifier (ie. to or
private final List segments;
private final Frame frame;
private final JFIF jfif;
private final AdobeDCT adobeDCT;
private final JFXX jfxx;
private final ICC_Profile embeddedICCProfile;
private final CompoundDirectory exif;
// TODO: Consider moving all the metadata stuff from the reader, over here...
JPEGImage10Metadata(final List segments, Frame frame, JFIF jfif, JFXX jfxx, ICC_Profile embeddedICCProfile, AdobeDCT adobeDCT, final CompoundDirectory exif) {
super(true, JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
this.segments = segments;
this.frame = frame;
this.jfif = jfif;
this.adobeDCT = adobeDCT;
this.jfxx = jfxx;
this.embeddedICCProfile = embeddedICCProfile;
this.exif = exif;
}
@Override
protected Node getNativeTree() {
IIOMetadataNode root = new IIOMetadataNode(JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
boolean isJFIF = jfif != null;
if (isJFIF) {
IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
app0JFIF.setAttribute("majorVersion", Integer.toString(jfif.majorVersion));
app0JFIF.setAttribute("minorVersion", Integer.toString(jfif.minorVersion));
app0JFIF.setAttribute("resUnits", Integer.toString(jfif.units));
app0JFIF.setAttribute("Xdensity", Integer.toString(jfif.xDensity));
app0JFIF.setAttribute("Ydensity", Integer.toString(jfif.yDensity));
app0JFIF.setAttribute("thumbWidth", Integer.toString(jfif.xThumbnail));
app0JFIF.setAttribute("thumbHeight", Integer.toString(jfif.yThumbnail));
jpegVariety.appendChild(app0JFIF);
// Due to format oddity, add JFXX and app2ICC as subnodes here...
// ...and ignore them below, if added...
apendJFXX(app0JFIF);
appendICCProfile(app0JFIF);
}
root.appendChild(jpegVariety);
appendMarkerSequence(root, segments, isJFIF);
return root;
}
private void appendMarkerSequence(IIOMetadataNode root, List segments, boolean isJFIF) {
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
root.appendChild(markerSequence);
for (Segment segment : segments)
switch (segment.marker) {
case JPEG.SOF0:
case JPEG.SOF1:
case JPEG.SOF2:
case JPEG.SOF3:
case JPEG.SOF5:
case JPEG.SOF6:
case JPEG.SOF7:
case JPEG.SOF9:
case JPEG.SOF10:
case JPEG.SOF11:
case JPEG.SOF13:
case JPEG.SOF14:
case JPEG.SOF15:
Frame sofSegment = (Frame) segment;
IIOMetadataNode sof = new IIOMetadataNode("sof");
sof.setAttribute("process", String.valueOf(sofSegment.marker & 0xf));
sof.setAttribute("samplePrecision", String.valueOf(sofSegment.samplePrecision));
sof.setAttribute("numLines", String.valueOf(sofSegment.lines));
sof.setAttribute("samplesPerLine", String.valueOf(sofSegment.samplesPerLine));
sof.setAttribute("numFrameComponents", String.valueOf(sofSegment.componentsInFrame()));
for (Frame.Component component : sofSegment.components) {
IIOMetadataNode componentSpec = new IIOMetadataNode("componentSpec");
componentSpec.setAttribute("componentId", String.valueOf(component.id));
componentSpec.setAttribute("HsamplingFactor", String.valueOf(component.hSub));
componentSpec.setAttribute("VsamplingFactor", String.valueOf(component.vSub));
componentSpec.setAttribute("QtableSelector", String.valueOf(component.qtSel));
sof.appendChild(componentSpec);
}
markerSequence.appendChild(sof);
break;
case JPEG.DHT:
HuffmanTable huffmanTable = (HuffmanTable) segment;
IIOMetadataNode dcTables = new IIOMetadataNode("dht");
IIOMetadataNode acTables = new IIOMetadataNode("dht");
appendHuffmanTables(huffmanTable, 0, dcTables);
appendHuffmanTables(huffmanTable, 1, acTables);
markerSequence.appendChild(dcTables);
// Native metadata has a limit of max 4 children of the DHT, we split by class only if we must...
if (dcTables.getLength() + acTables.getLength() > 4) {
markerSequence.appendChild(acTables);
}
else {
while (acTables.hasChildNodes()) {
dcTables.appendChild(acTables.removeChild(acTables.getFirstChild()));
}
}
break;
case JPEG.DQT:
QuantizationTable quantizationTable = (QuantizationTable) segment;
IIOMetadataNode dqt = new IIOMetadataNode("dqt");
for (int i = 0; i < 4; i++) {
if (quantizationTable.isPresent(i)) {
IIOMetadataNode dqtable = new IIOMetadataNode("dqtable");
dqtable.setAttribute("elementPrecision", quantizationTable.precision(i) != 16 ? "0" : "1"); // 0 = 8 bits, 1 = 16 bits
dqtable.setAttribute("qtableId", Integer.toString(i));
dqtable.setUserObject(quantizationTable.toNativeTable(i));
dqt.appendChild(dqtable);
}
}
markerSequence.appendChild(dqt);
break;
case JPEG.DRI:
RestartInterval restartInterval = (RestartInterval) segment;
IIOMetadataNode dri = new IIOMetadataNode("dri");
dri.setAttribute("interval", Integer.toString(restartInterval.interval));
markerSequence.appendChild(dri);
break;
case JPEG.SOS:
Scan scan = (Scan) segment;
IIOMetadataNode sos = new IIOMetadataNode("sos");
sos.setAttribute("numScanComponents", String.valueOf(scan.components.length));
sos.setAttribute("startSpectralSelection", String.valueOf(scan.spectralSelStart));
sos.setAttribute("endSpectralSelection", String.valueOf(scan.spectralSelEnd));
sos.setAttribute("approxHigh", String.valueOf(scan.approxHigh));
sos.setAttribute("approxLow", String.valueOf(scan.approxLow));
for (Scan.Component component : scan.components) {
IIOMetadataNode spec = new IIOMetadataNode("scanComponentSpec");
spec.setAttribute("componentSelector", String.valueOf(component.scanCompSel));
spec.setAttribute("dcHuffTable", String.valueOf(component.dcTabSel));
spec.setAttribute("acHuffTable", String.valueOf(component.acTabSel));
sos.appendChild(spec);
}
markerSequence.appendChild(sos);
break;
case JPEG.COM:
IIOMetadataNode com = new IIOMetadataNode("com");
com.setAttribute("comment", ((Comment) segment).comment);
markerSequence.appendChild(com);
break;
case JPEG.APP0:
if (segment instanceof JFIF) {
// Either already added, or we'll ignore it anyway...
break;
}
else if (isJFIF && segment instanceof JFXX) {
// Already added
break;
}
// Else, fall through to unknown segment
case JPEG.APP2:
if (isJFIF && segment instanceof ICCProfile) {
// Already added
break;
}
// Else, fall through to unknown segment
case JPEG.APP14:
if (segment instanceof AdobeDCT) {
AdobeDCT adobe = (AdobeDCT) segment;
IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe");
app14Adobe.setAttribute("version", String.valueOf(adobe.version));
app14Adobe.setAttribute("flags0", String.valueOf(adobe.flags0));
app14Adobe.setAttribute("flags1", String.valueOf(adobe.flags1));
app14Adobe.setAttribute("transform", String.valueOf(adobe.transform));
markerSequence.appendChild(app14Adobe);
break;
}
// Else, fall through to unknown segment
default:
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
unknown.setAttribute("MarkerTag", String.valueOf(segment.marker & 0xFF));
unknown.setUserObject(((Application) segment).data);
markerSequence.appendChild(unknown);
break;
}
}
private void appendHuffmanTables(HuffmanTable huffmanTable, int tableClass, IIOMetadataNode dht) {
for (int i = 0; i < 4; i++) {
if (huffmanTable.isPresent(i, tableClass)) {
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
dhtable.setAttribute("class", String.valueOf(tableClass));
dhtable.setAttribute("htableId", String.valueOf(i));
dhtable.setUserObject(huffmanTable.toNativeTable(i, tableClass));
dht.appendChild(dhtable);
}
}
}
private void appendICCProfile(IIOMetadataNode app0JFIF) {
if (embeddedICCProfile != null) {
IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
app2ICC.setUserObject(embeddedICCProfile);
app0JFIF.appendChild(app2ICC);
}
}
private void apendJFXX(IIOMetadataNode app0JFIF) {
if (jfxx != null) {
IIOMetadataNode jfxxNode = new IIOMetadataNode("JFXX");
app0JFIF.appendChild(jfxxNode);
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
app0JFXX.setAttribute("extensionCode", Integer.toString(jfxx.extensionCode));
jfxxNode.appendChild(app0JFXX);
switch (jfxx.extensionCode) {
case JFXX.JPEG:
IIOMetadataNode thumbJPEG = new IIOMetadataNode("JFIFthumbJPEG");
thumbJPEG.appendChild(new IIOMetadataNode("markerSequence"));
// TODO: Insert segments in marker sequence...
// List segments = JPEGSegmentUtil.readSegments(new ByteArrayImageInputStream(jfxx.thumbnail), JPEGSegmentUtil.ALL_SEGMENTS);
// Convert to Segment as in JPEGImageReader...
// appendMarkerSequence(thumbJPEG, segments, false);
app0JFXX.appendChild(thumbJPEG);
break;
case JFXX.INDEXED:
IIOMetadataNode thumbPalette = new IIOMetadataNode("JFIFthumbPalette");
thumbPalette.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
thumbPalette.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
app0JFXX.appendChild(thumbPalette);
break;
case JFXX.RGB:
IIOMetadataNode thumbRGB = new IIOMetadataNode("JFIFthumbRGB");
thumbRGB.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
thumbRGB.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
app0JFXX.appendChild(thumbRGB);
break;
}
}
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
colorSpaceType.setAttribute("name", getColorSpaceType());
chroma.appendChild(colorSpaceType);
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", String.valueOf(frame.componentsInFrame()));
chroma.appendChild(numChannels);
return chroma;
}
private String getColorSpaceType() {
try {
JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame);
switch (csType) {
case Gray:
case GrayA:
return "GRAY";
case YCbCr:
case YCbCrA:
return "YCbCr";
case RGB:
case RGBA:
return "RGB";
case PhotoYCC:
case PhotoYCCA:
return "PhotoYCC";
case YCCK:
return "YCCK";
case CMYK:
return "CMYK";
default:
}
}
catch (IIOException ignore) {
}
return Integer.toString(frame.componentsInFrame(), 16) + "CLR";
}
private boolean hasAlpha() {
try {
JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame);
switch (csType) {
case GrayA:
case YCbCrA:
case RGBA:
case PhotoYCCA:
return true;
default:
}
}
catch (IIOException ignore) {
}
return false;
}
private boolean isLossess() {
switch (frame.marker) {
case JPEG.SOF3:
case JPEG.SOF7:
case JPEG.SOF11:
case JPEG.SOF15:
return true;
default:
return false;
}
}
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
if (hasAlpha()) {
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", "nonpremultipled");
transparency.appendChild(alpha);
return transparency;
}
return null;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "JPEG"); // ...or "JPEG-LOSSLESS" (which is the name used by the JAI JPEGImageWriter for it's compression name)?
compression.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", isLossess() ? "TRUE" : "FALSE");
compression.appendChild(lossless);
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
numProgressiveScans.setAttribute("value", "1"); // TODO!
compression.appendChild(numProgressiveScans);
return compression;
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", getExifOrientation(exif));
dimension.appendChild(imageOrientation);
if (jfif != null) {
// Aspect ratio
float xDensity = Math.max(1, jfif.xDensity);
float yDensity = Math.max(1, jfif.yDensity);
float aspectRatio = jfif.units == 0 ? xDensity / yDensity : yDensity / xDensity;
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatio.setAttribute("value", Float.toString(aspectRatio));
dimension.insertBefore(pixelAspectRatio, imageOrientation); // Keep order
if (jfif.units != 0) {
// Pixel size
float scale = jfif.units == 1 ? 25.4F : 10.0F; // DPI or DPcm
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
horizontalPixelSize.setAttribute("value", Float.toString(scale / xDensity));
dimension.appendChild(horizontalPixelSize);
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
verticalPixelSize.setAttribute("value", Float.toString(scale / yDensity));
dimension.appendChild(verticalPixelSize);
}
}
return dimension;
}
private String getExifOrientation(Directory exif) {
if (exif != null) {
Entry orientationEntry = exif.getEntryById(TIFF.TAG_ORIENTATION);
if (orientationEntry != null) {
switch (((Number) orientationEntry.getValue()).intValue()) {
case 2:
return "FlipH";
case 3:
return "Rotate180";
case 4:
return "FlipV";
case 5:
return "FlipVRotate90";
case 6:
return "Rotate270";
case 7:
return "FlipHRotate90";
case 8:
return "Rotate90";
case 0:
case 1:
default:
// Fall-through
}
}
}
return "Normal";
}
@Override
protected IIOMetadataNode getStandardTextNode() {
IIOMetadataNode text = new IIOMetadataNode("Text");
for (Segment segment : segments) {
if (segment instanceof Comment) {
IIOMetadataNode com = new IIOMetadataNode("TextEntry");
com.setAttribute("keyword", "comment");
com.setAttribute("value", ((Comment) segment).comment);
text.appendChild(com);
}
}
// TODO: Add the following from Exif (as in TIFFMetadata)
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
return text.hasChildNodes() ? text : null;
}
}