/*
 * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.imageio.plugins.jpeg;

import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.metadata.IIOMetadataFormat;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.plugins.jpeg.JPEGQTable;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.ListIterator;
import java.io.IOException;
import java.awt.color.ICC_Profile;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.Point;

Metadata for the JPEG plug-in.
/** * Metadata for the JPEG plug-in. */
public class JPEGMetadata extends IIOMetadata implements Cloneable { //////// Private variables private static final boolean debug = false;
A copy of markerSequence, created the first time the markerSequence is modified. This is used by reset to restore the original state.
/** * A copy of {@code markerSequence}, created the first time the * {@code markerSequence} is modified. This is used by reset * to restore the original state. */
private List<MarkerSegment> resetSequence = null;
Set to true when reading a thumbnail stored as JPEG. This is used to enforce the prohibition of JFIF thumbnails containing any JFIF marker segments, and to ensure generation of a correct native subtree during getAsTree.
/** * Set to {@code true} when reading a thumbnail stored as * JPEG. This is used to enforce the prohibition of JFIF thumbnails * containing any JFIF marker segments, and to ensure generation of * a correct native subtree during {@code getAsTree}. */
private boolean inThumb = false;
Set by the chroma node construction method to signal the presence or absence of an alpha channel to the transparency node construction method. Used only when constructing a standard metadata tree.
/** * Set by the chroma node construction method to signal the * presence or absence of an alpha channel to the transparency * node construction method. Used only when constructing a * standard metadata tree. */
private boolean hasAlpha; //////// end of private variables /////// Package-access variables
All data is a list of MarkerSegment objects. When accessing the list, use the tag to identify the particular subclass. Any JFIF marker segment must be the first element of the list if it is present, and any JFXX or APP2ICC marker segments are subordinate to the JFIF marker segment. This list is package visible so that the writer can access it.
See Also:
/** * All data is a list of {@code MarkerSegment} objects. * When accessing the list, use the tag to identify the particular * subclass. Any JFIF marker segment must be the first element * of the list if it is present, and any JFXX or APP2ICC marker * segments are subordinate to the JFIF marker segment. This * list is package visible so that the writer can access it. * @see MarkerSegment */
List<MarkerSegment> markerSequence = new ArrayList<>();
Indicates whether this object represents stream or image metadata. Package-visible so the writer can see it.
/** * Indicates whether this object represents stream or image * metadata. Package-visible so the writer can see it. */
final boolean isStream; /////// End of package-access variables /////// Constructors
Constructor containing code shared by other constructors.
/** * Constructor containing code shared by other constructors. */
JPEGMetadata(boolean isStream, boolean inThumb) { super(true, // Supports standard format JPEG.nativeImageMetadataFormatName, // and a native format JPEG.nativeImageMetadataFormatClassName, null, null); // No other formats this.inThumb = inThumb; // But if we are stream metadata, adjust the variables this.isStream = isStream; if (isStream) { nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName; nativeMetadataFormatClassName = JPEG.nativeStreamMetadataFormatClassName; } } /* * Constructs a {@code JPEGMetadata} object by reading the * contents of an {@code ImageInputStream}. Has package-only * access. * * @param isStream A boolean indicating whether this object will be * stream or image metadata. * @param isThumb A boolean indicating whether this metadata object * is for an image or for a thumbnail stored as JPEG. * @param iis An {@code ImageInputStream} from which to read * the metadata. * @param reader The {@code JPEGImageReader} calling this * constructor, to which warnings should be sent. */ JPEGMetadata(boolean isStream, boolean isThumb, ImageInputStream iis, JPEGImageReader reader) throws IOException { this(isStream, isThumb); JPEGBuffer buffer = new JPEGBuffer(iis); buffer.loadBuf(0); // The first three bytes should be FF, SOI, FF if (((buffer.buf[0] & 0xff) != 0xff) || ((buffer.buf[1] & 0xff) != JPEG.SOI) || ((buffer.buf[2] & 0xff) != 0xff)) { throw new IIOException ("Image format error"); } boolean done = false; buffer.bufAvail -=2; // Next byte should be the ff before a marker buffer.bufPtr = 2; MarkerSegment newGuy = null; while (!done) { byte [] buf; int ptr; buffer.loadBuf(1); if (debug) { System.out.println("top of loop"); buffer.print(10); } buffer.scanForFF(reader); switch (buffer.buf[buffer.bufPtr] & 0xff) { case 0: if (debug) { System.out.println("Skipping 0"); } buffer.bufAvail--; buffer.bufPtr++; break; case JPEG.SOF0: case JPEG.SOF1: case JPEG.SOF2: if (isStream) { throw new IIOException ("SOF not permitted in stream metadata"); } newGuy = new SOFMarkerSegment(buffer); break; case JPEG.DQT: newGuy = new DQTMarkerSegment(buffer); break; case JPEG.DHT: newGuy = new DHTMarkerSegment(buffer); break; case JPEG.DRI: newGuy = new DRIMarkerSegment(buffer); break; case JPEG.APP0: // Either JFIF, JFXX, or unknown APP0 buffer.loadBuf(8); // tag, length, id buf = buffer.buf; ptr = buffer.bufPtr; if ((buf[ptr+3] == 'J') && (buf[ptr+4] == 'F') && (buf[ptr+5] == 'I') && (buf[ptr+6] == 'F') && (buf[ptr+7] == 0)) { if (inThumb) { reader.warningOccurred (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB); // Leave newGuy null // Read a dummy to skip the segment JFIFMarkerSegment dummy = new JFIFMarkerSegment(buffer); } else if (isStream) { throw new IIOException ("JFIF not permitted in stream metadata"); } else if (markerSequence.isEmpty() == false) { throw new IIOException ("JFIF APP0 must be first marker after SOI"); } else { newGuy = new JFIFMarkerSegment(buffer); } } else if ((buf[ptr+3] == 'J') && (buf[ptr+4] == 'F') && (buf[ptr+5] == 'X') && (buf[ptr+6] == 'X') && (buf[ptr+7] == 0)) { if (isStream) { throw new IIOException ("JFXX not permitted in stream metadata"); } if (inThumb) { throw new IIOException ("JFXX markers not allowed in JFIF JPEG thumbnail"); } JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment (JFIFMarkerSegment.class, true); if (jfif == null) { throw new IIOException ("JFXX encountered without prior JFIF!"); } jfif.addJFXX(buffer, reader); // newGuy remains null } else { newGuy = new MarkerSegment(buffer); newGuy.loadData(buffer); } break; case JPEG.APP2: // Either an ICC profile or unknown APP2 buffer.loadBuf(15); // tag, length, id if ((buffer.buf[buffer.bufPtr+3] == 'I') && (buffer.buf[buffer.bufPtr+4] == 'C') && (buffer.buf[buffer.bufPtr+5] == 'C') && (buffer.buf[buffer.bufPtr+6] == '_') && (buffer.buf[buffer.bufPtr+7] == 'P') && (buffer.buf[buffer.bufPtr+8] == 'R') && (buffer.buf[buffer.bufPtr+9] == 'O') && (buffer.buf[buffer.bufPtr+10] == 'F') && (buffer.buf[buffer.bufPtr+11] == 'I') && (buffer.buf[buffer.bufPtr+12] == 'L') && (buffer.buf[buffer.bufPtr+13] == 'E') && (buffer.buf[buffer.bufPtr+14] == 0) ) { if (isStream) { throw new IIOException ("ICC profiles not permitted in stream metadata"); } JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment (JFIFMarkerSegment.class, true); if (jfif == null) { newGuy = new MarkerSegment(buffer); newGuy.loadData(buffer); } else { jfif.addICC(buffer); } // newGuy remains null } else { newGuy = new MarkerSegment(buffer); newGuy.loadData(buffer); } break; case JPEG.APP14: // Either Adobe or unknown APP14 buffer.loadBuf(8); // tag, length, id if ((buffer.buf[buffer.bufPtr+3] == 'A') && (buffer.buf[buffer.bufPtr+4] == 'd') && (buffer.buf[buffer.bufPtr+5] == 'o') && (buffer.buf[buffer.bufPtr+6] == 'b') && (buffer.buf[buffer.bufPtr+7] == 'e')) { if (isStream) { throw new IIOException ("Adobe APP14 markers not permitted in stream metadata"); } newGuy = new AdobeMarkerSegment(buffer); } else { newGuy = new MarkerSegment(buffer); newGuy.loadData(buffer); } break; case JPEG.COM: newGuy = new COMMarkerSegment(buffer); break; case JPEG.SOS: if (isStream) { throw new IIOException ("SOS not permitted in stream metadata"); } newGuy = new SOSMarkerSegment(buffer); break; case JPEG.RST0: case JPEG.RST1: case JPEG.RST2: case JPEG.RST3: case JPEG.RST4: case JPEG.RST5: case JPEG.RST6: case JPEG.RST7: if (debug) { System.out.println("Restart Marker"); } buffer.bufPtr++; // Just skip it buffer.bufAvail--; break; case JPEG.EOI: done = true; buffer.bufPtr++; buffer.bufAvail--; break; default: newGuy = new MarkerSegment(buffer); newGuy.loadData(buffer); newGuy.unknown = true; break; } if (newGuy != null) { markerSequence.add(newGuy); if (debug) { newGuy.print(); } newGuy = null; } } // Now that we've read up to the EOI, we need to push back // whatever is left in the buffer, so that the next read // in the native code will work. buffer.pushBack(); if (!isConsistent()) { throw new IIOException("Inconsistent metadata read from stream"); } }
Constructs a default stream JPEGMetadata object appropriate for the given write parameters.
/** * Constructs a default stream {@code JPEGMetadata} object appropriate * for the given write parameters. */
JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) { this(true, false); JPEGImageWriteParam jparam = null; if ((param != null) && (param instanceof JPEGImageWriteParam)) { jparam = (JPEGImageWriteParam) param; if (!jparam.areTablesSet()) { jparam = null; } } if (jparam != null) { markerSequence.add(new DQTMarkerSegment(jparam.getQTables())); markerSequence.add (new DHTMarkerSegment(jparam.getDCHuffmanTables(), jparam.getACHuffmanTables())); } else { // default tables. markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables())); markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true), JPEG.getDefaultHuffmanTables(false))); } // Defensive programming if (!isConsistent()) { throw new InternalError("Default stream metadata is inconsistent"); } }
Constructs a default image JPEGMetadata object appropriate for the given image type and write parameters.
/** * Constructs a default image {@code JPEGMetadata} object appropriate * for the given image type and write parameters. */
JPEGMetadata(ImageTypeSpecifier imageType, ImageWriteParam param, JPEGImageWriter writer) { this(false, false); boolean wantJFIF = true; boolean wantAdobe = false; int transform = JPEG.ADOBE_UNKNOWN; boolean willSubsample = true; boolean wantICC = false; boolean wantProg = false; boolean wantOptimized = false; boolean wantExtended = false; boolean wantQTables = true; boolean wantHTables = true; float quality = JPEG.DEFAULT_QUALITY; byte[] componentIDs = { 1, 2, 3, 4}; int numComponents = 0; ImageTypeSpecifier destType = null; if (param != null) { destType = param.getDestinationType(); if (destType != null) { if (imageType != null) { // Ignore the destination type. writer.warningOccurred (JPEGImageWriter.WARNING_DEST_IGNORED); destType = null; } } // The only progressive mode that makes sense here is MODE_DEFAULT if (param.canWriteProgressive()) { // the param may not be one of ours, so it may return false. // If so, the following would throw an exception if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) { wantProg = true; wantOptimized = true; wantHTables = false; } } if (param instanceof JPEGImageWriteParam) { JPEGImageWriteParam jparam = (JPEGImageWriteParam) param; if (jparam.areTablesSet()) { wantQTables = false; // If the param has them, metadata shouldn't wantHTables = false; if ((jparam.getDCHuffmanTables().length > 2) || (jparam.getACHuffmanTables().length > 2)) { wantExtended = true; } } // Progressive forces optimized, regardless of param setting // so consult the param re optimized only if not progressive if (!wantProg) { wantOptimized = jparam.getOptimizeHuffmanTables(); if (wantOptimized) { wantHTables = false; } } } // compression quality should determine the q tables. Note that this // will be ignored if we already decided not to create any. // Again, the param may not be one of ours, so we must check that it // supports compression settings if (param.canWriteCompressed()) { if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) { quality = param.getCompressionQuality(); } } } // We are done with the param, now for the image types ColorSpace cs = null; if (destType != null) { ColorModel cm = destType.getColorModel(); numComponents = cm.getNumComponents(); boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents); boolean hasAlpha = cm.hasAlpha(); cs = cm.getColorSpace(); int type = cs.getType(); switch(type) { case ColorSpace.TYPE_GRAY: willSubsample = false; if (hasExtraComponents) { // e.g. alpha wantJFIF = false; } break; case ColorSpace.TYPE_YCbCr: if (hasExtraComponents) { // e.g. K or alpha wantJFIF = false; if (!hasAlpha) { // Not alpha, so must be K wantAdobe = true; transform = JPEG.ADOBE_YCCK; } } break; case ColorSpace.TYPE_RGB: // with or without alpha wantJFIF = false; wantAdobe = true; willSubsample = false; componentIDs[0] = (byte) 'R'; componentIDs[1] = (byte) 'G'; componentIDs[2] = (byte) 'B'; if (hasAlpha) { componentIDs[3] = (byte) 'A'; } break; default: // Everything else is not subsampled, gets no special marker, // and component ids are 1 - N wantJFIF = false; willSubsample = false; } } else if (imageType != null) { ColorModel cm = imageType.getColorModel(); numComponents = cm.getNumComponents(); boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents); boolean hasAlpha = cm.hasAlpha(); cs = cm.getColorSpace(); int type = cs.getType(); switch(type) { case ColorSpace.TYPE_GRAY: willSubsample = false; if (hasExtraComponents) { // e.g. alpha wantJFIF = false; } break; case ColorSpace.TYPE_RGB: // with or without alpha // without alpha we just accept the JFIF defaults if (hasAlpha) { wantJFIF = false; } break; case ColorSpace.TYPE_3CLR: wantJFIF = false; willSubsample = false; if (cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) { willSubsample = true; wantAdobe = true; componentIDs[0] = (byte) 'Y'; componentIDs[1] = (byte) 'C'; componentIDs[2] = (byte) 'c'; if (hasAlpha) { componentIDs[3] = (byte) 'A'; } } break; case ColorSpace.TYPE_YCbCr: if (hasExtraComponents) { // e.g. K or alpha wantJFIF = false; if (!hasAlpha) { // then it must be K wantAdobe = true; transform = JPEG.ADOBE_YCCK; } } break; case ColorSpace.TYPE_CMYK: wantJFIF = false; wantAdobe = true; transform = JPEG.ADOBE_YCCK; break; default: // Everything else is not subsampled, gets no special marker, // and component ids are 0 - N wantJFIF = false; willSubsample = false; } } // do we want an ICC profile? if (wantJFIF && JPEG.isNonStandardICC(cs)) { wantICC = true; } // Now step through the markers, consulting our variables. if (wantJFIF) { JFIFMarkerSegment jfif = new JFIFMarkerSegment(); markerSequence.add(jfif); if (wantICC) { try { jfif.addICC((ICC_ColorSpace)cs); } catch (IOException e) {} // Can't happen here } } // Adobe if (wantAdobe) { markerSequence.add(new AdobeMarkerSegment(transform)); } // dqt if (wantQTables) { markerSequence.add(new DQTMarkerSegment(quality, willSubsample)); } // dht if (wantHTables) { markerSequence.add(new DHTMarkerSegment(willSubsample)); } // sof markerSequence.add(new SOFMarkerSegment(wantProg, wantExtended, willSubsample, componentIDs, numComponents)); // sos if (!wantProg) { // Default progression scans are done in the writer markerSequence.add(new SOSMarkerSegment(willSubsample, componentIDs, numComponents)); } // Defensive programming if (!isConsistent()) { throw new InternalError("Default image metadata is inconsistent"); } } ////// End of constructors // Utilities for dealing with the marker sequence. // The first ones have package access for access from the writer.
Returns the first MarkerSegment object in the list with the given tag, or null if none is found.
/** * Returns the first MarkerSegment object in the list * with the given tag, or null if none is found. */
MarkerSegment findMarkerSegment(int tag) { Iterator<MarkerSegment> iter = markerSequence.iterator(); while (iter.hasNext()) { MarkerSegment seg = iter.next(); if (seg.tag == tag) { return seg; } } return null; }
Returns the first or last MarkerSegment object in the list of the given class, or null if none is found.
/** * Returns the first or last MarkerSegment object in the list * of the given class, or null if none is found. */
MarkerSegment findMarkerSegment(Class<? extends MarkerSegment> cls, boolean first) { if (first) { Iterator<MarkerSegment> iter = markerSequence.iterator(); while (iter.hasNext()) { MarkerSegment seg = iter.next(); if (cls.isInstance(seg)) { return seg; } } } else { ListIterator<MarkerSegment> iter = markerSequence.listIterator(markerSequence.size()); while (iter.hasPrevious()) { MarkerSegment seg = iter.previous(); if (cls.isInstance(seg)) { return seg; } } } return null; }
Returns the index of the first or last MarkerSegment in the list of the given class, or -1 if none is found.
/** * Returns the index of the first or last MarkerSegment in the list * of the given class, or -1 if none is found. */
private int findMarkerSegmentPosition(Class<? extends MarkerSegment> cls, boolean first) { if (first) { ListIterator<MarkerSegment> iter = markerSequence.listIterator(); for (int i = 0; iter.hasNext(); i++) { MarkerSegment seg = iter.next(); if (cls.isInstance(seg)) { return i; } } } else { ListIterator<MarkerSegment> iter = markerSequence.listIterator(markerSequence.size()); for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) { MarkerSegment seg = iter.previous(); if (cls.isInstance(seg)) { return i; } } } return -1; } private int findLastUnknownMarkerSegmentPosition() { ListIterator<MarkerSegment> iter = markerSequence.listIterator(markerSequence.size()); for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) { MarkerSegment seg = iter.previous(); if (seg.unknown == true) { return i; } } return -1; } // Implement Cloneable, but restrict access protected Object clone() { JPEGMetadata newGuy = null; try { newGuy = (JPEGMetadata) super.clone(); } catch (CloneNotSupportedException e) {} // won't happen if (markerSequence != null) { newGuy.markerSequence = cloneSequence(); } newGuy.resetSequence = null; return newGuy; }
Returns a deep copy of the current marker sequence.
/** * Returns a deep copy of the current marker sequence. */
private List<MarkerSegment> cloneSequence() { if (markerSequence == null) { return null; } List<MarkerSegment> retval = new ArrayList<>(markerSequence.size()); Iterator<MarkerSegment> iter = markerSequence.iterator(); while(iter.hasNext()) { MarkerSegment seg = iter.next(); retval.add((MarkerSegment) seg.clone()); } return retval; } // Tree methods public Node getAsTree(String formatName) { if (formatName == null) { throw new IllegalArgumentException("null formatName!"); } if (isStream) { if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) { return getNativeTree(); } } else { if (formatName.equals(JPEG.nativeImageMetadataFormatName)) { return getNativeTree(); } if (formatName.equals (IIOMetadataFormatImpl.standardMetadataFormatName)) { return getStandardTree(); } } throw new IllegalArgumentException("Unsupported format name: " + formatName); } IIOMetadataNode getNativeTree() { IIOMetadataNode root; IIOMetadataNode top; Iterator<MarkerSegment> iter = markerSequence.iterator(); if (isStream) { root = new IIOMetadataNode(JPEG.nativeStreamMetadataFormatName); top = root; } else { IIOMetadataNode sequence = new IIOMetadataNode("markerSequence"); if (!inThumb) { root = new IIOMetadataNode(JPEG.nativeImageMetadataFormatName); IIOMetadataNode header = new IIOMetadataNode("JPEGvariety"); root.appendChild(header); JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); if (jfif != null) { iter.next(); // JFIF must be first, so this skips it header.appendChild(jfif.getNativeNode()); } root.appendChild(sequence); } else { root = sequence; } top = sequence; } while(iter.hasNext()) { MarkerSegment seg = iter.next(); top.appendChild(seg.getNativeNode()); } return root; } // Standard tree node methods protected IIOMetadataNode getStandardChromaNode() { hasAlpha = false; // Unless we find otherwise // Colorspace type - follow the rules in the spec // First get the SOF marker segment, if there is one SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true); if (sof == null) { // No image, so no chroma return null; } IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); chroma.appendChild(csType); // get the number of channels int numChannels = sof.componentSpecs.length; IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels"); chroma.appendChild(numChanNode); numChanNode.setAttribute("value", Integer.toString(numChannels)); // is there a JFIF marker segment? if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) { if (numChannels == 1) { csType.setAttribute("name", "GRAY"); } else { csType.setAttribute("name", "YCbCr"); } return chroma; } // How about an Adobe marker segment? AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true); if (adobe != null){ switch (adobe.transform) { case JPEG.ADOBE_YCCK: csType.setAttribute("name", "YCCK"); break; case JPEG.ADOBE_YCC: csType.setAttribute("name", "YCbCr"); break; case JPEG.ADOBE_UNKNOWN: if (numChannels == 3) { csType.setAttribute("name", "RGB"); } else if (numChannels == 4) { csType.setAttribute("name", "CMYK"); } break; } return chroma; } // Neither marker. Check components if (numChannels < 3) { csType.setAttribute("name", "GRAY"); if (numChannels == 2) { hasAlpha = true; } return chroma; } boolean idsAreJFIF = false; int cid0 = sof.componentSpecs[0].componentId; int cid1 = sof.componentSpecs[1].componentId; int cid2 = sof.componentSpecs[2].componentId; if ((cid0 == 1) && (cid1 == 2) && (cid2 == 3)) { idsAreJFIF = true; } if (idsAreJFIF) { csType.setAttribute("name", "YCbCr"); if (numChannels == 4) { hasAlpha = true; } return chroma; } // Check against the letters if ((sof.componentSpecs[0].componentId == 'R') && (sof.componentSpecs[1].componentId == 'G') && (sof.componentSpecs[2].componentId == 'B')){ csType.setAttribute("name", "RGB"); if ((numChannels == 4) && (sof.componentSpecs[3].componentId == 'A')) { hasAlpha = true; } return chroma; } if ((sof.componentSpecs[0].componentId == 'Y') && (sof.componentSpecs[1].componentId == 'C') && (sof.componentSpecs[2].componentId == 'c')){ csType.setAttribute("name", "PhotoYCC"); if ((numChannels == 4) && (sof.componentSpecs[3].componentId == 'A')) { hasAlpha = true; } return chroma; } // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB // 4-channel subsampled are YCbCrA, unsubsampled are CMYK boolean subsampled = false; int hfactor = sof.componentSpecs[0].HsamplingFactor; int vfactor = sof.componentSpecs[0].VsamplingFactor; for (int i = 1; i<sof.componentSpecs.length; i++) { if ((sof.componentSpecs[i].HsamplingFactor != hfactor) || (sof.componentSpecs[i].VsamplingFactor != vfactor)){ subsampled = true; break; } } if (subsampled) { csType.setAttribute("name", "YCbCr"); if (numChannels == 4) { hasAlpha = true; } return chroma; } // Not subsampled. numChannels < 3 is taken care of above if (numChannels == 3) { csType.setAttribute("name", "RGB"); } else { csType.setAttribute("name", "CMYK"); } return chroma; } protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression = new IIOMetadataNode("Compression"); // CompressionTypeName IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName"); name.setAttribute("value", "JPEG"); compression.appendChild(name); // Lossless - false IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); lossless.setAttribute("value", "FALSE"); compression.appendChild(lossless); // NumProgressiveScans - count sos segments int sosCount = 0; Iterator<MarkerSegment> iter = markerSequence.iterator(); while (iter.hasNext()) { MarkerSegment ms = iter.next(); if (ms.tag == JPEG.SOS) { sosCount++; } } if (sosCount != 0) { IIOMetadataNode prog = new IIOMetadataNode("NumProgressiveScans"); prog.setAttribute("value", Integer.toString(sosCount)); compression.appendChild(prog); } return compression; } protected IIOMetadataNode getStandardDimensionNode() { // If we have a JFIF marker segment, we know a little // otherwise all we know is the orientation, which is always normal IIOMetadataNode dim = new IIOMetadataNode("Dimension"); IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation"); orient.setAttribute("value", "normal"); dim.appendChild(orient); JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); if (jfif != null) { // Aspect Ratio is width of pixel / height of pixel float aspectRatio; if (jfif.resUnits == 0) { // In this case they just encode aspect ratio directly aspectRatio = ((float) jfif.Xdensity)/jfif.Ydensity; } else { // They are true densities (e.g. dpi) and must be inverted aspectRatio = ((float) jfif.Ydensity)/jfif.Xdensity; } IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio"); aspect.setAttribute("value", Float.toString(aspectRatio)); dim.insertBefore(aspect, orient); // Pixel size if (jfif.resUnits != 0) { // 1 == dpi, 2 == dpc float scale = (jfif.resUnits == 1) ? 25.4F : 10.0F; IIOMetadataNode horiz = new IIOMetadataNode("HorizontalPixelSize"); horiz.setAttribute("value", Float.toString(scale/jfif.Xdensity)); dim.appendChild(horiz); IIOMetadataNode vert = new IIOMetadataNode("VerticalPixelSize"); vert.setAttribute("value", Float.toString(scale/jfif.Ydensity)); dim.appendChild(vert); } } return dim; } protected IIOMetadataNode getStandardTextNode() { IIOMetadataNode text = null; // Add a text entry for each COM Marker Segment if (findMarkerSegment(JPEG.COM) != null) { text = new IIOMetadataNode("Text"); Iterator<MarkerSegment> iter = markerSequence.iterator(); while (iter.hasNext()) { MarkerSegment seg = iter.next(); if (seg.tag == JPEG.COM) { COMMarkerSegment com = (COMMarkerSegment) seg; IIOMetadataNode entry = new IIOMetadataNode("TextEntry"); entry.setAttribute("keyword", "comment"); entry.setAttribute("value", com.getComment()); text.appendChild(entry); } } } return text; } protected IIOMetadataNode getStandardTransparencyNode() { IIOMetadataNode trans = null; if (hasAlpha == true) { trans = new IIOMetadataNode("Transparency"); IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); alpha.setAttribute("value", "nonpremultiplied"); // Always assume trans.appendChild(alpha); } return trans; } // Editing public boolean isReadOnly() { return false; } public void mergeTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName == null) { throw new IllegalArgumentException("null formatName!"); } if (root == null) { throw new IllegalArgumentException("null root!"); } List<MarkerSegment> copy = null; if (resetSequence == null) { resetSequence = cloneSequence(); // Deep copy copy = resetSequence; // Avoid cloning twice } else { copy = cloneSequence(); } if (isStream && (formatName.equals(JPEG.nativeStreamMetadataFormatName))) { mergeNativeTree(root); } else if (!isStream && (formatName.equals(JPEG.nativeImageMetadataFormatName))) { mergeNativeTree(root); } else if (!isStream && (formatName.equals (IIOMetadataFormatImpl.standardMetadataFormatName))) { mergeStandardTree(root); } else { throw new IllegalArgumentException("Unsupported format name: " + formatName); } if (!isConsistent()) { markerSequence = copy; throw new IIOInvalidTreeException ("Merged tree is invalid; original restored", root); } } private void mergeNativeTree(Node root) throws IIOInvalidTreeException { String name = root.getNodeName(); if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName : JPEG.nativeImageMetadataFormatName)) { throw new IIOInvalidTreeException("Invalid root node name: " + name, root); } if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence throw new IIOInvalidTreeException( "JPEGvariety and markerSequence nodes must be present", root); } mergeJFIFsubtree(root.getFirstChild()); mergeSequenceSubtree(root.getLastChild()); }
Merge a JFIF subtree into the marker sequence, if the subtree is non-empty. If a JFIF marker exists, update it from the subtree. If none exists, create one from the subtree and insert it at the beginning of the marker sequence.
/** * Merge a JFIF subtree into the marker sequence, if the subtree * is non-empty. * If a JFIF marker exists, update it from the subtree. * If none exists, create one from the subtree and insert it at the * beginning of the marker sequence. */
private void mergeJFIFsubtree(Node JPEGvariety) throws IIOInvalidTreeException { if (JPEGvariety.getChildNodes().getLength() != 0) { Node jfifNode = JPEGvariety.getFirstChild(); // is there already a jfif marker segment? JFIFMarkerSegment jfifSeg = (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); if (jfifSeg != null) { jfifSeg.updateFromNativeNode(jfifNode, false); } else { // Add it as the first element in the list. markerSequence.add(0, new JFIFMarkerSegment(jfifNode)); } } } private void mergeSequenceSubtree(Node sequenceTree) throws IIOInvalidTreeException { NodeList children = sequenceTree.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); String name = node.getNodeName(); if (name.equals("dqt")) { mergeDQTNode(node); } else if (name.equals("dht")) { mergeDHTNode(node); } else if (name.equals("dri")) { mergeDRINode(node); } else if (name.equals("com")) { mergeCOMNode(node); } else if (name.equals("app14Adobe")) { mergeAdobeNode(node); } else if (name.equals("unknown")) { mergeUnknownNode(node); } else if (name.equals("sof")) { mergeSOFNode(node); } else if (name.equals("sos")) { mergeSOSNode(node); } else { throw new IIOInvalidTreeException("Invalid node: " + name, node); } } }
Merge the given DQT node into the marker sequence. If there already exist DQT marker segments in the sequence, then each table in the node replaces the first table, in any DQT segment, with the same table id. If none of the existing DQT segments contain a table with the same id, then the table is added to the last existing DQT segment. If there are no DQT segments, then a new one is created and added as follows: If there are DHT segments, the new DQT segment is inserted before the first one. If there are no DHT segments, the new DQT segment is inserted before an SOF segment, if there is one. If there is no SOF segment, the new DQT segment is inserted before the first SOS segment, if there is one. If there is no SOS segment, the new DQT segment is added to the end of the sequence.
/** * Merge the given DQT node into the marker sequence. If there already * exist DQT marker segments in the sequence, then each table in the * node replaces the first table, in any DQT segment, with the same * table id. If none of the existing DQT segments contain a table with * the same id, then the table is added to the last existing DQT segment. * If there are no DQT segments, then a new one is created and added * as follows: * If there are DHT segments, the new DQT segment is inserted before the * first one. * If there are no DHT segments, the new DQT segment is inserted before * an SOF segment, if there is one. * If there is no SOF segment, the new DQT segment is inserted before * the first SOS segment, if there is one. * If there is no SOS segment, the new DQT segment is added to the end * of the sequence. */
private void mergeDQTNode(Node node) throws IIOInvalidTreeException { // First collect any existing DQT nodes into a local list ArrayList<DQTMarkerSegment> oldDQTs = new ArrayList<>(); Iterator<MarkerSegment> iter = markerSequence.iterator(); while (iter.hasNext()) { MarkerSegment seg = iter.next(); if (seg instanceof DQTMarkerSegment) { oldDQTs.add((DQTMarkerSegment) seg); } } if (!oldDQTs.isEmpty()) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); int childID = MarkerSegment.getAttributeValue(child, null, "qtableId", 0, 3, true); DQTMarkerSegment dqt = null; int tableIndex = -1; for (int j = 0; j < oldDQTs.size(); j++) { DQTMarkerSegment testDQT = oldDQTs.get(j); for (int k = 0; k < testDQT.tables.size(); k++) { DQTMarkerSegment.Qtable testTable = testDQT.tables.get(k); if (childID == testTable.tableID) { dqt = testDQT; tableIndex = k; break; } } if (dqt != null) break; } if (dqt != null) { dqt.tables.set(tableIndex, dqt.getQtableFromNode(child)); } else { dqt = oldDQTs.get(oldDQTs.size()-1); dqt.tables.add(dqt.getQtableFromNode(child)); } } } else { DQTMarkerSegment newGuy = new DQTMarkerSegment(node); int firstDHT = findMarkerSegmentPosition(DHTMarkerSegment.class, true); int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true); int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true); if (firstDHT != -1) { markerSequence.add(firstDHT, newGuy); } else if (firstSOF != -1) { markerSequence.add(firstSOF, newGuy); } else if (firstSOS != -1) { markerSequence.add(firstSOS, newGuy); } else { markerSequence.add(newGuy); } } }
Merge the given DHT node into the marker sequence. If there already exist DHT marker segments in the sequence, then each table in the node replaces the first table, in any DHT segment, with the same table class and table id. If none of the existing DHT segments contain a table with the same class and id, then the table is added to the last existing DHT segment. If there are no DHT segments, then a new one is created and added as follows: If there are DQT segments, the new DHT segment is inserted immediately following the last DQT segment. If there are no DQT segments, the new DHT segment is inserted before an SOF segment, if there is one. If there is no SOF segment, the new DHT segment is inserted before the first SOS segment, if there is one. If there is no SOS segment, the new DHT segment is added to the end of the sequence.
/** * Merge the given DHT node into the marker sequence. If there already * exist DHT marker segments in the sequence, then each table in the * node replaces the first table, in any DHT segment, with the same * table class and table id. If none of the existing DHT segments contain * a table with the same class and id, then the table is added to the last * existing DHT segment. * If there are no DHT segments, then a new one is created and added * as follows: * If there are DQT segments, the new DHT segment is inserted immediately * following the last DQT segment. * If there are no DQT segments, the new DHT segment is inserted before * an SOF segment, if there is one. * If there is no SOF segment, the new DHT segment is inserted before * the first SOS segment, if there is one. * If there is no SOS segment, the new DHT segment is added to the end * of the sequence. */
private void mergeDHTNode(Node node) throws IIOInvalidTreeException { // First collect any existing DQT nodes into a local list ArrayList<DHTMarkerSegment> oldDHTs = new ArrayList<>(); Iterator<MarkerSegment> iter = markerSequence.iterator(); while (iter.hasNext()) { MarkerSegment seg = iter.next(); if (seg instanceof DHTMarkerSegment) { oldDHTs.add((DHTMarkerSegment) seg); } } if (!oldDHTs.isEmpty()) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); NamedNodeMap attrs = child.getAttributes(); int childID = MarkerSegment.getAttributeValue(child, attrs, "htableId", 0, 3, true); int childClass = MarkerSegment.getAttributeValue(child, attrs, "class", 0, 1, true); DHTMarkerSegment dht = null; int tableIndex = -1; for (int j = 0; j < oldDHTs.size(); j++) { DHTMarkerSegment testDHT = oldDHTs.get(j); for (int k = 0; k < testDHT.tables.size(); k++) { DHTMarkerSegment.Htable testTable = testDHT.tables.get(k); if ((childID == testTable.tableID) && (childClass == testTable.tableClass)) { dht = testDHT; tableIndex = k; break; } } if (dht != null) break; } if (dht != null) { dht.tables.set(tableIndex, dht.getHtableFromNode(child)); } else { dht = oldDHTs.get(oldDHTs.size()-1); dht.tables.add(dht.getHtableFromNode(child)); } } } else { DHTMarkerSegment newGuy = new DHTMarkerSegment(node); int lastDQT = findMarkerSegmentPosition(DQTMarkerSegment.class, false); int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true); int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true); if (lastDQT != -1) { markerSequence.add(lastDQT+1, newGuy); } else if (firstSOF != -1) { markerSequence.add(firstSOF, newGuy); } else if (firstSOS != -1) { markerSequence.add(firstSOS, newGuy); } else { markerSequence.add(newGuy); } } }
Merge the given DRI node into the marker sequence. If there already exists a DRI marker segment, the restart interval value is updated. If there is no DRI segment, then a new one is created and added as follows: If there is an SOF segment, the new DRI segment is inserted before it. If there is no SOF segment, the new DRI segment is inserted before the first SOS segment, if there is one. If there is no SOS segment, the new DRI segment is added to the end of the sequence.
/** * Merge the given DRI node into the marker sequence. * If there already exists a DRI marker segment, the restart interval * value is updated. * If there is no DRI segment, then a new one is created and added as * follows: * If there is an SOF segment, the new DRI segment is inserted before * it. * If there is no SOF segment, the new DRI segment is inserted before * the first SOS segment, if there is one. * If there is no SOS segment, the new DRI segment is added to the end * of the sequence. */
private void mergeDRINode(Node node) throws IIOInvalidTreeException { DRIMarkerSegment dri = (DRIMarkerSegment) findMarkerSegment(DRIMarkerSegment.class, true); if (dri != null) { dri.updateFromNativeNode(node, false); } else { DRIMarkerSegment newGuy = new DRIMarkerSegment(node); int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true); int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true); if (firstSOF != -1) { markerSequence.add(firstSOF, newGuy); } else if (firstSOS != -1) { markerSequence.add(firstSOS, newGuy); } else { markerSequence.add(newGuy); } } }
Merge the given COM node into the marker sequence. A new COM marker segment is created and added to the sequence using insertCOMMarkerSegment.
/** * Merge the given COM node into the marker sequence. * A new COM marker segment is created and added to the sequence * using insertCOMMarkerSegment. */
private void mergeCOMNode(Node node) throws IIOInvalidTreeException { COMMarkerSegment newGuy = new COMMarkerSegment(node); insertCOMMarkerSegment(newGuy); }
Insert a new COM marker segment into an appropriate place in the marker sequence, as follows: If there already exist COM marker segments, the new one is inserted after the last one. If there are no COM segments, the new COM segment is inserted after the JFIF segment, if there is one. If there is no JFIF segment, the new COM segment is inserted after the Adobe marker segment, if there is one. If there is no Adobe segment, the new COM segment is inserted at the beginning of the sequence.
/** * Insert a new COM marker segment into an appropriate place in the * marker sequence, as follows: * If there already exist COM marker segments, the new one is inserted * after the last one. * If there are no COM segments, the new COM segment is inserted after the * JFIF segment, if there is one. * If there is no JFIF segment, the new COM segment is inserted after the * Adobe marker segment, if there is one. * If there is no Adobe segment, the new COM segment is inserted * at the beginning of the sequence. */
private void insertCOMMarkerSegment(COMMarkerSegment newGuy) { int lastCOM = findMarkerSegmentPosition(COMMarkerSegment.class, false); boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null); int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true); if (lastCOM != -1) { markerSequence.add(lastCOM+1, newGuy); } else if (hasJFIF) { markerSequence.add(1, newGuy); // JFIF is always 0 } else if (firstAdobe != -1) { markerSequence.add(firstAdobe+1, newGuy); } else { markerSequence.add(0, newGuy); } }
Merge the given Adobe APP14 node into the marker sequence. If there already exists an Adobe marker segment, then its attributes are updated from the node. If there is no Adobe segment, then a new one is created and added using insertAdobeMarkerSegment.
/** * Merge the given Adobe APP14 node into the marker sequence. * If there already exists an Adobe marker segment, then its attributes * are updated from the node. * If there is no Adobe segment, then a new one is created and added * using insertAdobeMarkerSegment. */
private void mergeAdobeNode(Node node) throws IIOInvalidTreeException { AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true); if (adobe != null) { adobe.updateFromNativeNode(node, false); } else { AdobeMarkerSegment newGuy = new AdobeMarkerSegment(node); insertAdobeMarkerSegment(newGuy); } }
Insert the given AdobeMarkerSegment into the marker sequence, as follows (we assume there is no Adobe segment yet): If there is a JFIF segment, then the new Adobe segment is inserted after it. If there is no JFIF segment, the new Adobe segment is inserted after the last Unknown segment, if there are any. If there are no Unknown segments, the new Adobe segment is inserted at the beginning of the sequence.
/** * Insert the given AdobeMarkerSegment into the marker sequence, as * follows (we assume there is no Adobe segment yet): * If there is a JFIF segment, then the new Adobe segment is inserted * after it. * If there is no JFIF segment, the new Adobe segment is inserted after the * last Unknown segment, if there are any. * If there are no Unknown segments, the new Adobe segment is inserted * at the beginning of the sequence. */
private void insertAdobeMarkerSegment(AdobeMarkerSegment newGuy) { boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null); int lastUnknown = findLastUnknownMarkerSegmentPosition(); if (hasJFIF) { markerSequence.add(1, newGuy); // JFIF is always 0 } else if (lastUnknown != -1) { markerSequence.add(lastUnknown+1, newGuy); } else { markerSequence.add(0, newGuy); } }
Merge the given Unknown node into the marker sequence. A new Unknown marker segment is created and added to the sequence as follows: If there already exist Unknown marker segments, the new one is inserted after the last one. If there are no Unknown marker segments, the new Unknown marker segment is inserted after the JFIF segment, if there is one. If there is no JFIF segment, the new Unknown segment is inserted before the Adobe marker segment, if there is one. If there is no Adobe segment, the new Unknown segment is inserted at the beginning of the sequence.
/** * Merge the given Unknown node into the marker sequence. * A new Unknown marker segment is created and added to the sequence as * follows: * If there already exist Unknown marker segments, the new one is inserted * after the last one. * If there are no Unknown marker segments, the new Unknown marker segment * is inserted after the JFIF segment, if there is one. * If there is no JFIF segment, the new Unknown segment is inserted before * the Adobe marker segment, if there is one. * If there is no Adobe segment, the new Unknown segment is inserted * at the beginning of the sequence. */
private void mergeUnknownNode(Node node) throws IIOInvalidTreeException { MarkerSegment newGuy = new MarkerSegment(node); int lastUnknown = findLastUnknownMarkerSegmentPosition(); boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null); int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true); if (lastUnknown != -1) { markerSequence.add(lastUnknown+1, newGuy); } else if (hasJFIF) { markerSequence.add(1, newGuy); // JFIF is always 0 } if (firstAdobe != -1) { markerSequence.add(firstAdobe, newGuy); } else { markerSequence.add(0, newGuy); } }
Merge the given SOF node into the marker sequence. If there already exists an SOF marker segment in the sequence, then its values are updated from the node. If there is no SOF segment, then a new one is created and added as follows: If there are any SOS segments, the new SOF segment is inserted before the first one. If there is no SOS segment, the new SOF segment is added to the end of the sequence.
/** * Merge the given SOF node into the marker sequence. * If there already exists an SOF marker segment in the sequence, then * its values are updated from the node. * If there is no SOF segment, then a new one is created and added as * follows: * If there are any SOS segments, the new SOF segment is inserted before * the first one. * If there is no SOS segment, the new SOF segment is added to the end * of the sequence. * */
private void mergeSOFNode(Node node) throws IIOInvalidTreeException { SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true); if (sof != null) { sof.updateFromNativeNode(node, false); } else { SOFMarkerSegment newGuy = new SOFMarkerSegment(node); int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true); if (firstSOS != -1) { markerSequence.add(firstSOS, newGuy); } else { markerSequence.add(newGuy); } } }
Merge the given SOS node into the marker sequence. If there already exists a single SOS marker segment, then the values are updated from the node. If there are more than one existing SOS marker segments, then an IIOInvalidTreeException is thrown, as SOS segments cannot be merged into a set of progressive scans. If there are no SOS marker segments, a new one is created and added to the end of the sequence.
/** * Merge the given SOS node into the marker sequence. * If there already exists a single SOS marker segment, then the values * are updated from the node. * If there are more than one existing SOS marker segments, then an * IIOInvalidTreeException is thrown, as SOS segments cannot be merged * into a set of progressive scans. * If there are no SOS marker segments, a new one is created and added * to the end of the sequence. */
private void mergeSOSNode(Node node) throws IIOInvalidTreeException { SOSMarkerSegment firstSOS = (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true); SOSMarkerSegment lastSOS = (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, false); if (firstSOS != null) { if (firstSOS != lastSOS) { throw new IIOInvalidTreeException ("Can't merge SOS node into a tree with > 1 SOS node", node); } firstSOS.updateFromNativeNode(node, false); } else { markerSequence.add(new SOSMarkerSegment(node)); } } private boolean transparencyDone; private void mergeStandardTree(Node root) throws IIOInvalidTreeException { transparencyDone = false; NodeList children = root.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); String name = node.getNodeName(); if (name.equals("Chroma")) { mergeStandardChromaNode(node, children); } else if (name.equals("Compression")) { mergeStandardCompressionNode(node); } else if (name.equals("Data")) { mergeStandardDataNode(node); } else if (name.equals("Dimension")) { mergeStandardDimensionNode(node); } else if (name.equals("Document")) { mergeStandardDocumentNode(node); } else if (name.equals("Text")) { mergeStandardTextNode(node); } else if (name.equals("Transparency")) { mergeStandardTransparencyNode(node); } else { throw new IIOInvalidTreeException("Invalid node: " + name, node); } } } /* * In general, it could be possible to convert all non-pixel data to some * textual form and include it in comments, but then this would create the * expectation that these comment forms be recognized by the reader, thus * creating a defacto extension to JPEG metadata capabilities. This is * probably best avoided, so the following convert only text nodes to * comments, and lose the keywords as well. */ private void mergeStandardChromaNode(Node node, NodeList siblings) throws IIOInvalidTreeException { // ColorSpaceType can change the target colorspace for compression // This must take any transparency node into account as well, as // that affects the number of channels (if alpha is present). If // a transparency node is dealt with here, set a flag to indicate // this to the transparency processor below. If we discover that // the nodes are not in order, throw an exception as the tree is // invalid. if (transparencyDone) { throw new IIOInvalidTreeException ("Transparency node must follow Chroma node", node); } Node csType = node.getFirstChild(); if ((csType == null) || !csType.getNodeName().equals("ColorSpaceType")) { // If there is no ColorSpaceType node, we have nothing to do return; } String csName = csType.getAttributes().getNamedItem("name").getNodeValue(); int numChannels = 0; boolean wantJFIF = false; boolean wantAdobe = false; int transform = 0; boolean willSubsample = false; byte [] ids = {1, 2, 3, 4}; // JFIF compatible if (csName.equals("GRAY")) { numChannels = 1; wantJFIF = true; } else if (csName.equals("YCbCr")) { numChannels = 3; wantJFIF = true; willSubsample = true; } else if (csName.equals("PhotoYCC")) { numChannels = 3; wantAdobe = true; transform = JPEG.ADOBE_YCC; ids[0] = (byte) 'Y'; ids[1] = (byte) 'C'; ids[2] = (byte) 'c'; } else if (csName.equals("RGB")) { numChannels = 3; wantAdobe = true; transform = JPEG.ADOBE_UNKNOWN; ids[0] = (byte) 'R'; ids[1] = (byte) 'G'; ids[2] = (byte) 'B'; } else if ((csName.equals("XYZ")) || (csName.equals("Lab")) || (csName.equals("Luv")) || (csName.equals("YxY")) || (csName.equals("HSV")) || (csName.equals("HLS")) || (csName.equals("CMY")) || (csName.equals("3CLR"))) { numChannels = 3; } else if (csName.equals("YCCK")) { numChannels = 4; wantAdobe = true; transform = JPEG.ADOBE_YCCK; willSubsample = true; } else if (csName.equals("CMYK")) { numChannels = 4; wantAdobe = true; transform = JPEG.ADOBE_UNKNOWN; } else if (csName.equals("4CLR")) { numChannels = 4; } else { // We can't handle them, so don't modify any metadata return; } boolean wantAlpha = false; for (int i = 0; i < siblings.getLength(); i++) { Node trans = siblings.item(i); if (trans.getNodeName().equals("Transparency")) { wantAlpha = wantAlpha(trans); break; // out of for } } if (wantAlpha) { numChannels++; wantJFIF = false; if (ids[0] == (byte) 'R') { ids[3] = (byte) 'A'; wantAdobe = false; } } JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true); SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true); SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true); // If the metadata specifies progressive, then the number of channels // must match, so that we can modify all the existing SOS marker segments. // If they don't match, we don't know what to do with SOS so we can't do // the merge. We then just return silently. // An exception would not be appropriate. A warning might, but we have // nowhere to send it to. if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive if ((sof.componentSpecs.length != numChannels) && (sos != null)) { return; } } // JFIF header might be removed if (!wantJFIF && (jfif != null)) { markerSequence.remove(jfif); } // Now add a JFIF if we do want one, but only if it isn't stream metadata if (wantJFIF && !isStream) { markerSequence.add(0, new JFIFMarkerSegment()); } // Adobe header might be removed or the transform modified, if it isn't // stream metadata if (wantAdobe) { if ((adobe == null) && !isStream) { adobe = new AdobeMarkerSegment(transform); insertAdobeMarkerSegment(adobe); } else { adobe.transform = transform; } } else if (adobe != null) { markerSequence.remove(adobe); } boolean updateQtables = false; boolean updateHtables = false; boolean progressive = false; int [] subsampledSelectors = {0, 1, 1, 0 } ; int [] nonSubsampledSelectors = { 0, 0, 0, 0}; int [] newTableSelectors = willSubsample ? subsampledSelectors : nonSubsampledSelectors; // Keep the old componentSpecs array SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null; // SOF might be modified if (sof != null) { oldCompSpecs = sof.componentSpecs; progressive = (sof.tag == JPEG.SOF2); // Now replace the SOF with a new one; it might be the same, but // this is easier. markerSequence.set(markerSequence.indexOf(sof), new SOFMarkerSegment(progressive, false, // we never need extended willSubsample, ids, numChannels)); // Now suss out if subsampling changed and set the boolean for // updating the q tables // if the old componentSpec q table selectors don't match // the new ones, update the qtables. The new selectors are already // in place in the new SOF segment above. for (int i = 0; i < oldCompSpecs.length; i++) { if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) { updateQtables = true; } } if (progressive) { // if the component ids are different, update all the existing scans // ignore Huffman tables boolean idsDiffer = false; for (int i = 0; i < oldCompSpecs.length; i++) { if (ids[i] != oldCompSpecs[i].componentId) { idsDiffer = true; } } if (idsDiffer) { // update the ids in each SOS marker segment for (Iterator<MarkerSegment> iter = markerSequence.iterator(); iter.hasNext();) { MarkerSegment seg = iter.next(); if (seg instanceof SOSMarkerSegment) { SOSMarkerSegment target = (SOSMarkerSegment) seg; for (int i = 0; i < target.componentSpecs.length; i++) { int oldSelector = target.componentSpecs[i].componentSelector; // Find the position in the old componentSpecs array // of the old component with the old selector // and replace the component selector with the // new id at the same position, as these match // the new component specs array in the SOF created // above. for (int j = 0; j < oldCompSpecs.length; j++) { if (oldCompSpecs[j].componentId == oldSelector) { target.componentSpecs[i].componentSelector = ids[j]; } } } } } } } else { if (sos != null) { // htables - if the old htable selectors don't match the new ones, // update the tables. for (int i = 0; i < sos.componentSpecs.length; i++) { if ((sos.componentSpecs[i].dcHuffTable != newTableSelectors[i]) || (sos.componentSpecs[i].acHuffTable != newTableSelectors[i])) { updateHtables = true; } } // Might be the same as the old one, but this is easier. markerSequence.set(markerSequence.indexOf(sos), new SOSMarkerSegment(willSubsample, ids, numChannels)); } } } else { // should be stream metadata if there isn't an SOF, but check it anyway if (isStream) { // update tables - routines below check if it's really necessary updateQtables = true; updateHtables = true; } } if (updateQtables) { List<DQTMarkerSegment> tableSegments = new ArrayList<>(); for (Iterator<MarkerSegment> iter = markerSequence.iterator(); iter.hasNext();) { MarkerSegment seg = iter.next(); if (seg instanceof DQTMarkerSegment) { tableSegments.add((DQTMarkerSegment) seg); } } // If there are no tables, don't add them, as the metadata encodes an // abbreviated stream. // If we are not subsampling, we just need one, so don't do anything if (!tableSegments.isEmpty() && willSubsample) { // Is it really necessary? There should be at least 2 tables. // If there is only one, assume it's a scaled "standard" // luminance table, extract the scaling factor, and generate a // scaled "standard" chrominance table. // Find the table with selector 1. boolean found = false; for (Iterator<DQTMarkerSegment> iter = tableSegments.iterator(); iter.hasNext();) { DQTMarkerSegment testdqt = iter.next(); for (Iterator<DQTMarkerSegment.Qtable> tabiter = testdqt.tables.iterator(); tabiter.hasNext();) { DQTMarkerSegment.Qtable tab = tabiter.next(); if (tab.tableID == 1) { found = true; } } } if (!found) { // find the table with selector 0. There should be one. DQTMarkerSegment.Qtable table0 = null; for (Iterator<DQTMarkerSegment> iter = tableSegments.iterator(); iter.hasNext();) { DQTMarkerSegment testdqt = iter.next(); for (Iterator<DQTMarkerSegment.Qtable> tabiter = testdqt.tables.iterator(); tabiter.hasNext();) { DQTMarkerSegment.Qtable tab = tabiter.next(); if (tab.tableID == 0) { table0 = tab; } } } // Assuming that the table with id 0 is a luminance table, // compute a new chrominance table of the same quality and // add it to the last DQT segment DQTMarkerSegment dqt = tableSegments.get(tableSegments.size()-1); dqt.tables.add(dqt.getChromaForLuma(table0)); } } } if (updateHtables) { List<DHTMarkerSegment> tableSegments = new ArrayList<>(); for (Iterator<MarkerSegment> iter = markerSequence.iterator(); iter.hasNext();) { MarkerSegment seg = iter.next(); if (seg instanceof DHTMarkerSegment) { tableSegments.add((DHTMarkerSegment) seg); } } // If there are no tables, don't add them, as the metadata encodes an // abbreviated stream. // If we are not subsampling, we just need one, so don't do anything if (!tableSegments.isEmpty() && willSubsample) { // Is it really necessary? There should be at least 2 dc and 2 ac // tables. If there is only one, add a // "standard " chrominance table. // find a table with selector 1. AC/DC is irrelevant boolean found = false; for (Iterator<DHTMarkerSegment> iter = tableSegments.iterator(); iter.hasNext();) { DHTMarkerSegment testdht = iter.next(); for (Iterator<DHTMarkerSegment.Htable> tabiter = testdht.tables.iterator(); tabiter.hasNext();) { DHTMarkerSegment.Htable tab = tabiter.next(); if (tab.tableID == 1) { found = true; } } } if (!found) { // Create new standard dc and ac chrominance tables and add them // to the last DHT segment DHTMarkerSegment lastDHT = tableSegments.get(tableSegments.size()-1); lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1); lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1); } } } } private boolean wantAlpha(Node transparency) { boolean returnValue = false; Node alpha = transparency.getFirstChild(); // Alpha must be first if present if (alpha.getNodeName().equals("Alpha")) { if (alpha.hasAttributes()) { String value = alpha.getAttributes().getNamedItem("value").getNodeValue(); if (!value.equals("none")) { returnValue = true; } } } transparencyDone = true; return returnValue; } private void mergeStandardCompressionNode(Node node) throws IIOInvalidTreeException { // NumProgressiveScans is ignored. Progression must be enabled on the // ImageWriteParam. // No-op } private void mergeStandardDataNode(Node node) throws IIOInvalidTreeException { // No-op } private void mergeStandardDimensionNode(Node node) throws IIOInvalidTreeException { // Pixel Aspect Ratio or pixel size can be incorporated if there is, // or can be, a JFIF segment JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); if (jfif == null) { // Can there be one? // Criteria: // SOF must be present with 1 or 3 channels, (stream metadata fails this) // Component ids must be JFIF compatible. boolean canHaveJFIF = false; SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true); if (sof != null) { int numChannels = sof.componentSpecs.length; if ((numChannels == 1) || (numChannels == 3)) { canHaveJFIF = true; // remaining tests are negative for (int i = 0; i < sof.componentSpecs.length; i++) { if (sof.componentSpecs[i].componentId != i+1) canHaveJFIF = false; } // if Adobe present, transform = ADOBE_UNKNOWN for 1-channel, // ADOBE_YCC for 3-channel. AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true); if (adobe != null) { if (adobe.transform != ((numChannels == 1) ? JPEG.ADOBE_UNKNOWN : JPEG.ADOBE_YCC)) { canHaveJFIF = false; } } } } // If so, create one and insert it into the sequence. Note that // default is just pixel ratio at 1:1 if (canHaveJFIF) { jfif = new JFIFMarkerSegment(); markerSequence.add(0, jfif); } } if (jfif != null) { NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); NamedNodeMap attrs = child.getAttributes(); String name = child.getNodeName(); if (name.equals("PixelAspectRatio")) { String valueString = attrs.getNamedItem("value").getNodeValue(); float value = Float.parseFloat(valueString); Point p = findIntegerRatio(value); jfif.resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO; jfif.Xdensity = p.x; jfif.Xdensity = p.y; } else if (name.equals("HorizontalPixelSize")) { String valueString = attrs.getNamedItem("value").getNodeValue(); float value = Float.parseFloat(valueString); // Convert from mm/dot to dots/cm int dpcm = (int) Math.round(1.0/(value*10.0)); jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM; jfif.Xdensity = dpcm; } else if (name.equals("VerticalPixelSize")) { String valueString = attrs.getNamedItem("value").getNodeValue(); float value = Float.parseFloat(valueString); // Convert from mm/dot to dots/cm int dpcm = (int) Math.round(1.0/(value*10.0)); jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM; jfif.Ydensity = dpcm; } } } } /* * Return a pair of integers whose ratio (x/y) approximates the given * float value. */ private static Point findIntegerRatio(float value) { float epsilon = 0.005F; // Normalize value = Math.abs(value); // Deal with min case if (value <= epsilon) { return new Point(1, 255); } // Deal with max case if (value >= 255) { return new Point(255, 1); } // Remember if we invert boolean inverted = false; if (value < 1.0) { value = 1.0F/value; inverted = true; } // First approximation int y = 1; int x = Math.round(value); float ratio = (float) x; float delta = Math.abs(value - ratio); while (delta > epsilon) { // not close enough // Increment y and compute a new x y++; x = Math.round(y*value); ratio = (float)x/(float)y; delta = Math.abs(value - ratio); } return inverted ? new Point(y, x) : new Point(x, y); } private void mergeStandardDocumentNode(Node node) throws IIOInvalidTreeException { // No-op } private void mergeStandardTextNode(Node node) throws IIOInvalidTreeException { // Convert to comments. For the moment ignore the encoding issue. // Ignore keywords, language, and encoding (for the moment). // If compression tag is present, use only entries with "none". NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength(); i++) { Node child = children.item(i); NamedNodeMap attrs = child.getAttributes(); Node comp = attrs.getNamedItem("compression"); boolean copyIt = true; if (comp != null) { String compString = comp.getNodeValue(); if (!compString.equals("none")) { copyIt = false; } } if (copyIt) { String value = attrs.getNamedItem("value").getNodeValue(); COMMarkerSegment com = new COMMarkerSegment(value); insertCOMMarkerSegment(com); } } } private void mergeStandardTransparencyNode(Node node) throws IIOInvalidTreeException { // This might indicate that an alpha channel is being added or removed. // The nodes must appear in order, and a Chroma node will process any // transparency, so process it here only if there was no Chroma node // Do nothing for stream metadata if (!transparencyDone && !isStream) { boolean wantAlpha = wantAlpha(node); // do we have alpha already? If the number of channels is 2 or 4, // we do, as we don't support CMYK, nor can we add alpha to it // The number of channels can be determined from the SOF JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment (JFIFMarkerSegment.class, true); AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment (AdobeMarkerSegment.class, true); SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment (SOFMarkerSegment.class, true); SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment (SOSMarkerSegment.class, true); // We can do nothing for progressive, as we don't know how to // modify the scans. if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive return; } // Do we already have alpha? We can tell by the number of channels // We must have an sof, or we can't do anything further if (sof != null) { int numChannels = sof.componentSpecs.length; boolean hadAlpha = (numChannels == 2) || (numChannels == 4); // proceed only if the old state and the new state differ if (hadAlpha != wantAlpha) { if (wantAlpha) { // Adding alpha numChannels++; if (jfif != null) { markerSequence.remove(jfif); } // If an adobe marker is present, transform must be UNKNOWN if (adobe != null) { adobe.transform = JPEG.ADOBE_UNKNOWN; } // Add a component spec with appropriate parameters to SOF SOFMarkerSegment.ComponentSpec [] newSpecs = new SOFMarkerSegment.ComponentSpec[numChannels]; for (int i = 0; i < sof.componentSpecs.length; i++) { newSpecs[i] = sof.componentSpecs[i]; } byte oldFirstID = (byte) sof.componentSpecs[0].componentId; byte newID = (byte) ((oldFirstID > 1) ? 'A' : 4); newSpecs[numChannels-1] = sof.getComponentSpec(newID, sof.componentSpecs[0].HsamplingFactor, sof.componentSpecs[0].QtableSelector); sof.componentSpecs = newSpecs; // Add a component spec with appropriate parameters to SOS SOSMarkerSegment.ScanComponentSpec [] newScanSpecs = new SOSMarkerSegment.ScanComponentSpec [numChannels]; for (int i = 0; i < sos.componentSpecs.length; i++) { newScanSpecs[i] = sos.componentSpecs[i]; } newScanSpecs[numChannels-1] = sos.getScanComponentSpec (newID, 0); sos.componentSpecs = newScanSpecs; } else { // Removing alpha numChannels--; // Remove a component spec from SOF SOFMarkerSegment.ComponentSpec [] newSpecs = new SOFMarkerSegment.ComponentSpec[numChannels]; for (int i = 0; i < numChannels; i++) { newSpecs[i] = sof.componentSpecs[i]; } sof.componentSpecs = newSpecs; // Remove a component spec from SOS SOSMarkerSegment.ScanComponentSpec [] newScanSpecs = new SOSMarkerSegment.ScanComponentSpec [numChannels]; for (int i = 0; i < numChannels; i++) { newScanSpecs[i] = sos.componentSpecs[i]; } sos.componentSpecs = newScanSpecs; } } } } } public void setFromTree(String formatName, Node root) throws IIOInvalidTreeException { if (formatName == null) { throw new IllegalArgumentException("null formatName!"); } if (root == null) { throw new IllegalArgumentException("null root!"); } if (isStream && (formatName.equals(JPEG.nativeStreamMetadataFormatName))) { setFromNativeTree(root); } else if (!isStream && (formatName.equals(JPEG.nativeImageMetadataFormatName))) { setFromNativeTree(root); } else if (!isStream && (formatName.equals (IIOMetadataFormatImpl.standardMetadataFormatName))) { // In this case a reset followed by a merge is correct super.setFromTree(formatName, root); } else { throw new IllegalArgumentException("Unsupported format name: " + formatName); } } private void setFromNativeTree(Node root) throws IIOInvalidTreeException { if (resetSequence == null) { resetSequence = markerSequence; } markerSequence = new ArrayList<>(); // Build a whole new marker sequence from the tree String name = root.getNodeName(); if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName : JPEG.nativeImageMetadataFormatName)) { throw new IIOInvalidTreeException("Invalid root node name: " + name, root); } if (!isStream) { if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence throw new IIOInvalidTreeException( "JPEGvariety and markerSequence nodes must be present", root); } Node JPEGvariety = root.getFirstChild(); if (JPEGvariety.getChildNodes().getLength() != 0) { markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild())); } } Node markerSequenceNode = isStream ? root : root.getLastChild(); setFromMarkerSequenceNode(markerSequenceNode); } void setFromMarkerSequenceNode(Node markerSequenceNode) throws IIOInvalidTreeException{ NodeList children = markerSequenceNode.getChildNodes(); // for all the children, add a marker segment for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); String childName = node.getNodeName(); if (childName.equals("dqt")) { markerSequence.add(new DQTMarkerSegment(node)); } else if (childName.equals("dht")) { markerSequence.add(new DHTMarkerSegment(node)); } else if (childName.equals("dri")) { markerSequence.add(new DRIMarkerSegment(node)); } else if (childName.equals("com")) { markerSequence.add(new COMMarkerSegment(node)); } else if (childName.equals("app14Adobe")) { markerSequence.add(new AdobeMarkerSegment(node)); } else if (childName.equals("unknown")) { markerSequence.add(new MarkerSegment(node)); } else if (childName.equals("sof")) { markerSequence.add(new SOFMarkerSegment(node)); } else if (childName.equals("sos")) { markerSequence.add(new SOSMarkerSegment(node)); } else { throw new IIOInvalidTreeException("Invalid " + (isStream ? "stream " : "image ") + "child: " + childName, node); } } }
Check that this metadata object is in a consistent state and return true if it is or false otherwise. All the constructors and modifiers should call this method at the end to guarantee that the data is always consistent, as the writer relies on this.
/** * Check that this metadata object is in a consistent state and * return {@code true} if it is or {@code false} * otherwise. All the constructors and modifiers should call * this method at the end to guarantee that the data is always * consistent, as the writer relies on this. */
private boolean isConsistent() { SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true); JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true); AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true); boolean retval = true; if (!isStream) { if (sof != null) { // SOF numBands = total scan bands int numSOFBands = sof.componentSpecs.length; int numScanBands = countScanBands(); if (numScanBands != 0) { // No SOS is OK if (numScanBands != numSOFBands) { retval = false; } } // If JFIF is present, component ids are 1-3, bands are 1 or 3 if (jfif != null) { if ((numSOFBands != 1) && (numSOFBands != 3)) { retval = false; } for (int i = 0; i < numSOFBands; i++) { if (sof.componentSpecs[i].componentId != i+1) { retval = false; } } // If both JFIF and Adobe are present, // Adobe transform == unknown for gray, // YCC for 3-chan. if ((adobe != null) && (((numSOFBands == 1) && (adobe.transform != JPEG.ADOBE_UNKNOWN)) || ((numSOFBands == 3) && (adobe.transform != JPEG.ADOBE_YCC)))) { retval = false; } } } else { // stream can't have jfif, adobe, sof, or sos SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true); if ((jfif != null) || (adobe != null) || (sof != null) || (sos != null)) { retval = false; } } } return retval; }
Returns the total number of bands referenced in all SOS marker segments, including 0 if there are no SOS marker segments.
/** * Returns the total number of bands referenced in all SOS marker * segments, including 0 if there are no SOS marker segments. */
private int countScanBands() { List<Integer> ids = new ArrayList<>(); Iterator<MarkerSegment> iter = markerSequence.iterator(); while(iter.hasNext()) { MarkerSegment seg = iter.next(); if (seg instanceof SOSMarkerSegment) { SOSMarkerSegment sos = (SOSMarkerSegment) seg; SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs; for (int i = 0; i < specs.length; i++) { Integer id = specs[i].componentSelector; if (!ids.contains(id)) { ids.add(id); } } } } return ids.size(); } ///// Writer support void writeToStream(ImageOutputStream ios, boolean ignoreJFIF, boolean forceJFIF, List<? extends BufferedImage> thumbnails, ICC_Profile iccProfile, boolean ignoreAdobe, int newAdobeTransform, JPEGImageWriter writer) throws IOException { if (forceJFIF) { // Write a default JFIF segment, including thumbnails // This won't be duplicated below because forceJFIF will be // set only if there is no JFIF present already. JFIFMarkerSegment.writeDefaultJFIF(ios, thumbnails, iccProfile, writer); if ((ignoreAdobe == false) && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) { if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN) && (newAdobeTransform != JPEG.ADOBE_YCC)) { // Not compatible, so ignore Adobe. ignoreAdobe = true; writer.warningOccurred (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB); } } } // Iterate over each MarkerSegment Iterator<MarkerSegment> iter = markerSequence.iterator(); while(iter.hasNext()) { MarkerSegment seg = iter.next(); if (seg instanceof JFIFMarkerSegment) { if (ignoreJFIF == false) { JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg; jfif.writeWithThumbs(ios, thumbnails, writer); if (iccProfile != null) { JFIFMarkerSegment.writeICC(iccProfile, ios); } } // Otherwise ignore it, as requested } else if (seg instanceof AdobeMarkerSegment) { if (ignoreAdobe == false) { if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) { AdobeMarkerSegment newAdobe = (AdobeMarkerSegment) seg.clone(); newAdobe.transform = newAdobeTransform; newAdobe.write(ios); } else if (forceJFIF) { // If adobe isn't JFIF compatible, ignore it AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg; if ((adobe.transform == JPEG.ADOBE_UNKNOWN) || (adobe.transform == JPEG.ADOBE_YCC)) { adobe.write(ios); } else { writer.warningOccurred (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB); } } else { seg.write(ios); } } // Otherwise ignore it, as requested } else { seg.write(ios); } } } //// End of writer support public void reset() { if (resetSequence != null) { // Otherwise no need to reset markerSequence = resetSequence; resetSequence = null; } } public void print() { for (int i = 0; i < markerSequence.size(); i++) { MarkerSegment seg = markerSequence.get(i); seg.print(); } } }