| 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
 | 
 | 
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.ColorModel;  | 
 | 
import java.awt.Point;  | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 */  | 
 | 
public class JPEGMetadata extends IIOMetadata implements Cloneable { | 
 | 
 | 
 | 
    //////// Private variables  | 
 | 
 | 
 | 
    private static final boolean debug = false;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private List resetSequence = null;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private boolean inThumb = false;  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private boolean hasAlpha;  | 
 | 
 | 
 | 
    //////// end of private variables  | 
 | 
 | 
 | 
    /////// Package-access variables  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    List markerSequence = new ArrayList();  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    final boolean isStream;  | 
 | 
 | 
 | 
    /////// End of package-access variables  | 
 | 
 | 
 | 
    /////// Constructors  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    JPEGMetadata(boolean isStream, boolean inThumb) { | 
 | 
        super(true,    | 
 | 
              JPEG.nativeImageMetadataFormatName,    | 
 | 
              JPEG.nativeImageMetadataFormatClassName,  | 
 | 
              null, null);    | 
 | 
        this.inThumb = inThumb;  | 
 | 
          | 
 | 
        this.isStream = isStream;  | 
 | 
        if (isStream) { | 
 | 
            nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;  | 
 | 
            nativeMetadataFormatClassName =  | 
 | 
                JPEG.nativeStreamMetadataFormatClassName;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    JPEGMetadata(boolean isStream,  | 
 | 
                 boolean isThumb,  | 
 | 
                 ImageInputStream iis,  | 
 | 
                 JPEGImageReader reader) throws IOException { | 
 | 
        this(isStream, isThumb);  | 
 | 
 | 
 | 
        JPEGBuffer buffer = new JPEGBuffer(iis);  | 
 | 
 | 
 | 
        buffer.loadBuf(0);  | 
 | 
 | 
 | 
          | 
 | 
        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;    | 
 | 
        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);   | 
 | 
                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  | 
 | 
                          | 
 | 
                        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);   | 
 | 
                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);   | 
 | 
                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++;   | 
 | 
                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"); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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 { | 
 | 
              | 
 | 
            markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));  | 
 | 
            markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),  | 
 | 
                                                    JPEG.getDefaultHuffmanTables(false)));  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (!isConsistent()) { | 
 | 
            throw new InternalError("Default stream metadata is inconsistent"); | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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) { | 
 | 
                      | 
 | 
                    writer.warningOccurred  | 
 | 
                        (JPEGImageWriter.WARNING_DEST_IGNORED);  | 
 | 
                    destType = null;  | 
 | 
                }  | 
 | 
            }  | 
 | 
              | 
 | 
            if (param.canWriteProgressive()) { | 
 | 
                // the param may not be one of ours, so it may return false.  | 
 | 
                  | 
 | 
                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;    | 
 | 
                    wantHTables = false;  | 
 | 
                    if ((jparam.getDCHuffmanTables().length > 2)  | 
 | 
                            || (jparam.getACHuffmanTables().length > 2)) { | 
 | 
                        wantExtended = true;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                // Progressive forces optimized, regardless of param setting  | 
 | 
                  | 
 | 
                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  | 
 | 
              | 
 | 
            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) {   | 
 | 
                    wantJFIF = false;  | 
 | 
                }  | 
 | 
                break;  | 
 | 
            case ColorSpace.TYPE_3CLR:  | 
 | 
                if (cs == JPEG.JCS.getYCC()) { | 
 | 
                    wantJFIF = false;  | 
 | 
                    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) {  | 
 | 
                    wantJFIF = false;  | 
 | 
                    if (!hasAlpha) {  | 
 | 
                        wantAdobe = true;  | 
 | 
                        transform = JPEG.ADOBE_YCCK;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                break;  | 
 | 
            case ColorSpace.TYPE_RGB:    | 
 | 
                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,  | 
 | 
                  | 
 | 
                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) {   | 
 | 
                    wantJFIF = false;  | 
 | 
                }  | 
 | 
                break;  | 
 | 
            case ColorSpace.TYPE_RGB:    | 
 | 
                  | 
 | 
                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) {  | 
 | 
                    wantJFIF = false;  | 
 | 
                    if (!hasAlpha) {   | 
 | 
                        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,  | 
 | 
                  | 
 | 
                wantJFIF = false;  | 
 | 
                willSubsample = false;  | 
 | 
            }  | 
 | 
 | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (wantJFIF && JPEG.isNonStandardICC(cs)) { | 
 | 
            wantICC = true;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (wantJFIF) { | 
 | 
            JFIFMarkerSegment jfif = new JFIFMarkerSegment();  | 
 | 
            markerSequence.add(jfif);  | 
 | 
            if (wantICC) { | 
 | 
                try { | 
 | 
                    jfif.addICC((ICC_ColorSpace)cs);  | 
 | 
                } catch (IOException e) {}  | 
 | 
            }  | 
 | 
        }  | 
 | 
          | 
 | 
        if (wantAdobe) { | 
 | 
            markerSequence.add(new AdobeMarkerSegment(transform));  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (wantQTables) { | 
 | 
            markerSequence.add(new DQTMarkerSegment(quality, willSubsample));  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (wantHTables) { | 
 | 
            markerSequence.add(new DHTMarkerSegment(willSubsample));  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        markerSequence.add(new SOFMarkerSegment(wantProg,  | 
 | 
                                                wantExtended,  | 
 | 
                                                willSubsample,  | 
 | 
                                                componentIDs,  | 
 | 
                                                numComponents));  | 
 | 
 | 
 | 
          | 
 | 
        if (!wantProg) {   | 
 | 
            markerSequence.add(new SOSMarkerSegment(willSubsample,  | 
 | 
                                                    componentIDs,  | 
 | 
                                                    numComponents));  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        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.  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    MarkerSegment findMarkerSegment(int tag) { | 
 | 
        Iterator iter = markerSequence.iterator();  | 
 | 
        while (iter.hasNext()) { | 
 | 
            MarkerSegment seg = (MarkerSegment)iter.next();  | 
 | 
            if (seg.tag == tag) { | 
 | 
                return seg;  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return null;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    MarkerSegment findMarkerSegment(Class cls, boolean first) { | 
 | 
        if (first) { | 
 | 
            Iterator iter = markerSequence.iterator();  | 
 | 
            while (iter.hasNext()) { | 
 | 
                MarkerSegment seg = (MarkerSegment)iter.next();  | 
 | 
                if (cls.isInstance(seg)) { | 
 | 
                    return seg;  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } else { | 
 | 
            ListIterator iter = markerSequence.listIterator(markerSequence.size());  | 
 | 
            while (iter.hasPrevious()) { | 
 | 
                MarkerSegment seg = (MarkerSegment)iter.previous();  | 
 | 
                if (cls.isInstance(seg)) { | 
 | 
                    return seg;  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return null;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private int findMarkerSegmentPosition(Class cls, boolean first) { | 
 | 
        if (first) { | 
 | 
            ListIterator iter = markerSequence.listIterator();  | 
 | 
            for (int i = 0; iter.hasNext(); i++) { | 
 | 
                MarkerSegment seg = (MarkerSegment)iter.next();  | 
 | 
                if (cls.isInstance(seg)) { | 
 | 
                    return i;  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } else { | 
 | 
            ListIterator iter = markerSequence.listIterator(markerSequence.size());  | 
 | 
            for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) { | 
 | 
                MarkerSegment seg = (MarkerSegment)iter.previous();  | 
 | 
                if (cls.isInstance(seg)) { | 
 | 
                    return i;  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return -1;  | 
 | 
    }  | 
 | 
 | 
 | 
    private int findLastUnknownMarkerSegmentPosition() { | 
 | 
        ListIterator iter = markerSequence.listIterator(markerSequence.size());  | 
 | 
        for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) { | 
 | 
            MarkerSegment seg = (MarkerSegment)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) {}  | 
 | 
        if (markerSequence != null) { | 
 | 
            newGuy.markerSequence = (List) cloneSequence();  | 
 | 
        }  | 
 | 
        newGuy.resetSequence = null;  | 
 | 
        return newGuy;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
     */  | 
 | 
    private List cloneSequence() { | 
 | 
        if (markerSequence == null) { | 
 | 
            return null;  | 
 | 
        }  | 
 | 
        List retval = new ArrayList(markerSequence.size());  | 
 | 
        Iterator iter = markerSequence.iterator();  | 
 | 
        while(iter.hasNext()) { | 
 | 
            MarkerSegment seg = (MarkerSegment)iter.next();  | 
 | 
            retval.add(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 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();    | 
 | 
                    header.appendChild(jfif.getNativeNode());  | 
 | 
                }  | 
 | 
                root.appendChild(sequence);  | 
 | 
            } else { | 
 | 
                root = sequence;  | 
 | 
            }  | 
 | 
            top = sequence;  | 
 | 
        }  | 
 | 
        while(iter.hasNext()) { | 
 | 
            MarkerSegment seg = (MarkerSegment) iter.next();  | 
 | 
            top.appendChild(seg.getNativeNode());  | 
 | 
        }  | 
 | 
        return root;  | 
 | 
    }  | 
 | 
 | 
 | 
    // Standard tree node methods  | 
 | 
 | 
 | 
    protected IIOMetadataNode getStandardChromaNode() { | 
 | 
        hasAlpha = false;    | 
 | 
 | 
 | 
        // Colorspace type - follow the rules in the spec  | 
 | 
          | 
 | 
        SOFMarkerSegment sof = (SOFMarkerSegment)  | 
 | 
            findMarkerSegment(SOFMarkerSegment.class, true);  | 
 | 
        if (sof == null) { | 
 | 
              | 
 | 
            return null;  | 
 | 
        }  | 
 | 
 | 
 | 
        IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); | 
 | 
        IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); | 
 | 
        chroma.appendChild(csType);  | 
 | 
 | 
 | 
          | 
 | 
        int numChannels = sof.componentSpecs.length;  | 
 | 
 | 
 | 
        IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels"); | 
 | 
        chroma.appendChild(numChanNode);  | 
 | 
        numChanNode.setAttribute("value", Integer.toString(numChannels)); | 
 | 
 | 
 | 
          | 
 | 
        if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) { | 
 | 
            if (numChannels == 1) { | 
 | 
                csType.setAttribute("name", "GRAY"); | 
 | 
            } else { | 
 | 
                csType.setAttribute("name", "YCbCr"); | 
 | 
            }  | 
 | 
            return chroma;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        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;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (numChannels < 3) { | 
 | 
            csType.setAttribute("name", "GRAY"); | 
 | 
            if (numChannels == 2) { | 
 | 
                hasAlpha = true;  | 
 | 
            }  | 
 | 
            return chroma;  | 
 | 
        }  | 
 | 
 | 
 | 
        boolean idsAreJFIF = true;  | 
 | 
 | 
 | 
        for (int i = 0; i < sof.componentSpecs.length; i++) { | 
 | 
            int id = sof.componentSpecs[i].componentId;  | 
 | 
            if ((id < 1) || (id >= sof.componentSpecs.length)) { | 
 | 
                idsAreJFIF = false;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (idsAreJFIF) { | 
 | 
            csType.setAttribute("name", "YCbCr"); | 
 | 
            if (numChannels == 4) { | 
 | 
                hasAlpha = true;  | 
 | 
            }  | 
 | 
            return chroma;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        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;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (numChannels == 3) { | 
 | 
            csType.setAttribute("name", "RGB"); | 
 | 
        } else { | 
 | 
            csType.setAttribute("name", "CMYK"); | 
 | 
        }  | 
 | 
 | 
 | 
        return chroma;  | 
 | 
    }  | 
 | 
 | 
 | 
    protected IIOMetadataNode getStandardCompressionNode() { | 
 | 
 | 
 | 
        IIOMetadataNode compression = new IIOMetadataNode("Compression"); | 
 | 
 | 
 | 
          | 
 | 
        IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName"); | 
 | 
        name.setAttribute("value", "JPEG"); | 
 | 
        compression.appendChild(name);  | 
 | 
 | 
 | 
          | 
 | 
        IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); | 
 | 
        lossless.setAttribute("value", "FALSE"); | 
 | 
        compression.appendChild(lossless);  | 
 | 
 | 
 | 
          | 
 | 
        int sosCount = 0;  | 
 | 
        Iterator iter = markerSequence.iterator();  | 
 | 
        while (iter.hasNext()) { | 
 | 
            MarkerSegment ms = (MarkerSegment) 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  | 
 | 
          | 
 | 
        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) { | 
 | 
 | 
 | 
              | 
 | 
            float aspectRatio;  | 
 | 
            if (jfif.resUnits == 0) { | 
 | 
                  | 
 | 
                aspectRatio = ((float) jfif.Xdensity)/jfif.Ydensity;  | 
 | 
            } else { | 
 | 
                  | 
 | 
                aspectRatio = ((float) jfif.Ydensity)/jfif.Xdensity;  | 
 | 
            }  | 
 | 
            IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio"); | 
 | 
            aspect.setAttribute("value", Float.toString(aspectRatio)); | 
 | 
            dim.insertBefore(aspect, orient);  | 
 | 
 | 
 | 
              | 
 | 
            if (jfif.resUnits != 0) { | 
 | 
                  | 
 | 
                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;  | 
 | 
          | 
 | 
        if (findMarkerSegment(JPEG.COM) != null) { | 
 | 
            text = new IIOMetadataNode("Text"); | 
 | 
            Iterator iter = markerSequence.iterator();  | 
 | 
            while (iter.hasNext()) { | 
 | 
                MarkerSegment seg = (MarkerSegment) 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");  | 
 | 
            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 copy = null;  | 
 | 
        if (resetSequence == null) { | 
 | 
            resetSequence = cloneSequence();    | 
 | 
            copy = resetSequence;    | 
 | 
        } 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) {  | 
 | 
            throw new IIOInvalidTreeException(  | 
 | 
                "JPEGvariety and markerSequence nodes must be present", root);  | 
 | 
        }  | 
 | 
        mergeJFIFsubtree(root.getFirstChild());  | 
 | 
        mergeSequenceSubtree(root.getLastChild());  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void mergeJFIFsubtree(Node JPEGvariety)  | 
 | 
        throws IIOInvalidTreeException { | 
 | 
        if (JPEGvariety.getChildNodes().getLength() != 0) { | 
 | 
            Node jfifNode = JPEGvariety.getFirstChild();  | 
 | 
              | 
 | 
            JFIFMarkerSegment jfifSeg =  | 
 | 
                (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);  | 
 | 
            if (jfifSeg != null) { | 
 | 
                jfifSeg.updateFromNativeNode(jfifNode, false);  | 
 | 
            } else { | 
 | 
                  | 
 | 
                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); | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void mergeDQTNode(Node node) throws IIOInvalidTreeException { | 
 | 
          | 
 | 
        ArrayList oldDQTs = new ArrayList();  | 
 | 
        Iterator iter = markerSequence.iterator();  | 
 | 
        while (iter.hasNext()) { | 
 | 
            MarkerSegment seg = (MarkerSegment) iter.next();  | 
 | 
            if (seg instanceof DQTMarkerSegment) { | 
 | 
                oldDQTs.add(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 = (DQTMarkerSegment) oldDQTs.get(j);  | 
 | 
                    for (int k = 0; k < testDQT.tables.size(); k++) { | 
 | 
                        DQTMarkerSegment.Qtable testTable =  | 
 | 
                            (DQTMarkerSegment.Qtable) 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 = (DQTMarkerSegment) 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);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void mergeDHTNode(Node node) throws IIOInvalidTreeException { | 
 | 
          | 
 | 
        ArrayList oldDHTs = new ArrayList();  | 
 | 
        Iterator iter = markerSequence.iterator();  | 
 | 
        while (iter.hasNext()) { | 
 | 
            MarkerSegment seg = (MarkerSegment) iter.next();  | 
 | 
            if (seg instanceof DHTMarkerSegment) { | 
 | 
                oldDHTs.add(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 = (DHTMarkerSegment) oldDHTs.get(j);  | 
 | 
                    for (int k = 0; k < testDHT.tables.size(); k++) { | 
 | 
                        DHTMarkerSegment.Htable testTable =  | 
 | 
                            (DHTMarkerSegment.Htable) 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 = (DHTMarkerSegment) 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);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void mergeCOMNode(Node node) throws IIOInvalidTreeException { | 
 | 
        COMMarkerSegment newGuy = new COMMarkerSegment(node);  | 
 | 
        insertCOMMarkerSegment(newGuy);  | 
 | 
    }  | 
 | 
 | 
 | 
       | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
      */  | 
 | 
    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);    | 
 | 
        } else if (firstAdobe != -1) { | 
 | 
            markerSequence.add(firstAdobe+1, newGuy);  | 
 | 
        } else { | 
 | 
            markerSequence.add(0, newGuy);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private void insertAdobeMarkerSegment(AdobeMarkerSegment newGuy) { | 
 | 
        boolean hasJFIF =  | 
 | 
            (findMarkerSegment(JFIFMarkerSegment.class, true) != null);  | 
 | 
        int lastUnknown = findLastUnknownMarkerSegmentPosition();  | 
 | 
        if (hasJFIF) { | 
 | 
            markerSequence.add(1, newGuy);    | 
 | 
        } else if (lastUnknown != -1) { | 
 | 
            markerSequence.add(lastUnknown+1, newGuy);  | 
 | 
        } else { | 
 | 
            markerSequence.add(0, newGuy);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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);    | 
 | 
        } if (firstAdobe != -1) { | 
 | 
            markerSequence.add(firstAdobe, newGuy);  | 
 | 
        } else { | 
 | 
            markerSequence.add(0, newGuy);  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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")) { | 
 | 
              | 
 | 
            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};   | 
 | 
        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 {  | 
 | 
            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;    | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        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  | 
 | 
          | 
 | 
        if ((sof != null) && (sof.tag == JPEG.SOF2)) {  | 
 | 
            if ((sof.componentSpecs.length != numChannels) && (sos != null)) { | 
 | 
                return;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (!wantJFIF && (jfif != null)) { | 
 | 
            markerSequence.remove(jfif);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (wantJFIF && !isStream) { | 
 | 
            markerSequence.add(0, new JFIFMarkerSegment());  | 
 | 
        }  | 
 | 
 | 
 | 
        // Adobe header might be removed or the transform modified, if it isn't  | 
 | 
          | 
 | 
        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;  | 
 | 
 | 
 | 
          | 
 | 
        SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null;  | 
 | 
          | 
 | 
        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  | 
 | 
              | 
 | 
            markerSequence.set(markerSequence.indexOf(sof),  | 
 | 
                               new SOFMarkerSegment(progressive,  | 
 | 
                                                    false,   | 
 | 
                                                    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  | 
 | 
              | 
 | 
            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  | 
 | 
                  | 
 | 
                boolean idsDiffer = false;  | 
 | 
                for (int i = 0; i < oldCompSpecs.length; i++) { | 
 | 
                    if (ids[i] != oldCompSpecs[i].componentId) { | 
 | 
                        idsDiffer = true;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                if (idsDiffer) { | 
 | 
                      | 
 | 
                    for (Iterator iter = markerSequence.iterator(); iter.hasNext();) { | 
 | 
                        MarkerSegment seg = (MarkerSegment) 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  | 
 | 
                                  | 
 | 
                                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,  | 
 | 
                      | 
 | 
                    for (int i = 0; i < sos.componentSpecs.length; i++) { | 
 | 
                        if ((sos.componentSpecs[i].dcHuffTable  | 
 | 
                             != newTableSelectors[i])  | 
 | 
                            || (sos.componentSpecs[i].acHuffTable  | 
 | 
                                != newTableSelectors[i])) { | 
 | 
                            updateHtables = true;  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
 | 
 | 
                      | 
 | 
                    markerSequence.set(markerSequence.indexOf(sos),  | 
 | 
                               new SOSMarkerSegment(willSubsample,  | 
 | 
                                                    ids,  | 
 | 
                                                    numChannels));  | 
 | 
                }  | 
 | 
            }  | 
 | 
        } else { | 
 | 
              | 
 | 
            if (isStream) { | 
 | 
                  | 
 | 
                updateQtables = true;  | 
 | 
                updateHtables = true;  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (updateQtables) { | 
 | 
            List tableSegments = new ArrayList();  | 
 | 
            for (Iterator iter = markerSequence.iterator(); iter.hasNext();) { | 
 | 
                MarkerSegment seg = (MarkerSegment) iter.next();  | 
 | 
                if (seg instanceof DQTMarkerSegment) { | 
 | 
                    tableSegments.add(seg);  | 
 | 
                }  | 
 | 
            }  | 
 | 
            // If there are no tables, don't add them, as the metadata encodes an  | 
 | 
            // abbreviated stream.  | 
 | 
              | 
 | 
            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.  | 
 | 
 | 
 | 
                  | 
 | 
                boolean found = false;  | 
 | 
                for (Iterator iter = tableSegments.iterator(); iter.hasNext();) { | 
 | 
                    DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();  | 
 | 
                    for (Iterator tabiter = testdqt.tables.iterator();  | 
 | 
                         tabiter.hasNext();) { | 
 | 
                        DQTMarkerSegment.Qtable tab =  | 
 | 
                            (DQTMarkerSegment.Qtable) tabiter.next();  | 
 | 
                        if (tab.tableID == 1) { | 
 | 
                            found = true;  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                if (!found) { | 
 | 
                      | 
 | 
                    DQTMarkerSegment.Qtable table0 = null;  | 
 | 
                    for (Iterator iter = tableSegments.iterator(); iter.hasNext();) { | 
 | 
                        DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();  | 
 | 
                        for (Iterator tabiter = testdqt.tables.iterator();  | 
 | 
                             tabiter.hasNext();) { | 
 | 
                            DQTMarkerSegment.Qtable tab =  | 
 | 
                                (DQTMarkerSegment.Qtable) 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  | 
 | 
                      | 
 | 
                    DQTMarkerSegment dqt =  | 
 | 
                        (DQTMarkerSegment) tableSegments.get(tableSegments.size()-1);  | 
 | 
                    dqt.tables.add(dqt.getChromaForLuma(table0));  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        if (updateHtables) { | 
 | 
            List tableSegments = new ArrayList();  | 
 | 
            for (Iterator iter = markerSequence.iterator(); iter.hasNext();) { | 
 | 
                MarkerSegment seg = (MarkerSegment) iter.next();  | 
 | 
                if (seg instanceof DHTMarkerSegment) { | 
 | 
                    tableSegments.add(seg);  | 
 | 
                }  | 
 | 
            }  | 
 | 
            // If there are no tables, don't add them, as the metadata encodes an  | 
 | 
            // abbreviated stream.  | 
 | 
              | 
 | 
            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.  | 
 | 
 | 
 | 
                  | 
 | 
                boolean found = false;  | 
 | 
                for (Iterator iter = tableSegments.iterator(); iter.hasNext();) { | 
 | 
                    DHTMarkerSegment testdht = (DHTMarkerSegment) iter.next();  | 
 | 
                    for (Iterator tabiter = testdht.tables.iterator();  | 
 | 
                         tabiter.hasNext();) { | 
 | 
                        DHTMarkerSegment.Htable tab =  | 
 | 
                            (DHTMarkerSegment.Htable) tabiter.next();  | 
 | 
                        if (tab.tableID == 1) { | 
 | 
                            found = true;  | 
 | 
                        }  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                if (!found) { | 
 | 
                    // Create new standard dc and ac chrominance tables and add them  | 
 | 
                      | 
 | 
                    DHTMarkerSegment lastDHT =  | 
 | 
                        (DHTMarkerSegment) 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();    | 
 | 
        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,  | 
 | 
          | 
 | 
        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)  | 
 | 
              | 
 | 
            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;   | 
 | 
                    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,  | 
 | 
                      | 
 | 
                    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  | 
 | 
              | 
 | 
            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);  | 
 | 
                      | 
 | 
                    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);  | 
 | 
                      | 
 | 
                    int dpcm = (int) Math.round(1.0/(value*10.0));  | 
 | 
                    jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;  | 
 | 
                    jfif.Ydensity = dpcm;  | 
 | 
                }  | 
 | 
 | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private static Point findIntegerRatio(float value) { | 
 | 
        float epsilon = 0.005F;  | 
 | 
 | 
 | 
          | 
 | 
        value = Math.abs(value);  | 
 | 
 | 
 | 
          | 
 | 
        if (value <= epsilon) { | 
 | 
            return new Point(1, 255);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        if (value >= 255) { | 
 | 
            return new Point(255, 1);  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        boolean inverted = false;  | 
 | 
        if (value < 1.0) { | 
 | 
            value = 1.0F/value;  | 
 | 
            inverted = true;  | 
 | 
        }  | 
 | 
 | 
 | 
          | 
 | 
        int y = 1;  | 
 | 
        int x = (int) Math.round(value);  | 
 | 
 | 
 | 
        float ratio = (float) x;  | 
 | 
        float delta = Math.abs(value - ratio);  | 
 | 
        while (delta > epsilon) { // not close enough | 
 | 
              | 
 | 
            y++;  | 
 | 
            x = (int) 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).  | 
 | 
          | 
 | 
        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  | 
 | 
          | 
 | 
        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  | 
 | 
              | 
 | 
            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  | 
 | 
              | 
 | 
            if ((sof != null) && (sof.tag == JPEG.SOF2)) {  | 
 | 
                return;  | 
 | 
            }  | 
 | 
 | 
 | 
            // Do we already have alpha?  We can tell by the number of channels  | 
 | 
              | 
 | 
            if (sof != null) { | 
 | 
                int numChannels = sof.componentSpecs.length;  | 
 | 
                boolean hadAlpha = (numChannels == 2) || (numChannels == 4);  | 
 | 
                  | 
 | 
                if (hadAlpha != wantAlpha) { | 
 | 
                    if (wantAlpha) {   | 
 | 
                        numChannels++;  | 
 | 
                        if (jfif != null) { | 
 | 
                            markerSequence.remove(jfif);  | 
 | 
                        }  | 
 | 
 | 
 | 
                          | 
 | 
                        if (adobe != null) { | 
 | 
                            adobe.transform = JPEG.ADOBE_UNKNOWN;  | 
 | 
                        }  | 
 | 
 | 
 | 
                          | 
 | 
                        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;  | 
 | 
 | 
 | 
                          | 
 | 
                        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 {   | 
 | 
                        numChannels--;  | 
 | 
                          | 
 | 
                        SOFMarkerSegment.ComponentSpec [] newSpecs =  | 
 | 
                            new SOFMarkerSegment.ComponentSpec[numChannels];  | 
 | 
                        for (int i = 0; i < numChannels; i++) { | 
 | 
                            newSpecs[i] = sof.componentSpecs[i];  | 
 | 
                        }  | 
 | 
                        sof.componentSpecs = newSpecs;  | 
 | 
 | 
 | 
                          | 
 | 
                        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))) { | 
 | 
              | 
 | 
            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) {  | 
 | 
                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 (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);  | 
 | 
            }  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    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) { | 
 | 
                  | 
 | 
                int numSOFBands = sof.componentSpecs.length;  | 
 | 
                int numScanBands = countScanBands();  | 
 | 
                if (numScanBands != 0) {   | 
 | 
                    if (numScanBands != numSOFBands) { | 
 | 
                        retval = false;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
                  | 
 | 
                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,  | 
 | 
                      | 
 | 
                    if ((adobe != null)  | 
 | 
                        && (((numSOFBands == 1)  | 
 | 
                             && (adobe.transform != JPEG.ADOBE_UNKNOWN))  | 
 | 
                            || ((numSOFBands == 3)  | 
 | 
                                && (adobe.transform != JPEG.ADOBE_YCC)))) { | 
 | 
                        retval = false;  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            } else { | 
 | 
                  | 
 | 
                SOSMarkerSegment sos =  | 
 | 
                    (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,  | 
 | 
                                                         true);  | 
 | 
                if ((jfif != null) || (adobe != null)  | 
 | 
                    || (sof != null) || (sos != null)) { | 
 | 
                    retval = false;  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
        return retval;  | 
 | 
    }  | 
 | 
 | 
 | 
      | 
 | 
 | 
 | 
 | 
 | 
     */  | 
 | 
    private int countScanBands() { | 
 | 
        List ids = new ArrayList();  | 
 | 
        Iterator iter = markerSequence.iterator();  | 
 | 
        while(iter.hasNext()) { | 
 | 
            MarkerSegment seg = (MarkerSegment)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 = new Integer(specs[i].componentSelector);  | 
 | 
                    if (!ids.contains(id)) { | 
 | 
                        ids.add(id);  | 
 | 
                    }  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
 | 
 | 
        return ids.size();  | 
 | 
    }  | 
 | 
 | 
 | 
    ///// Writer support  | 
 | 
 | 
 | 
    void writeToStream(ImageOutputStream ios,  | 
 | 
                       boolean ignoreJFIF,  | 
 | 
                       boolean forceJFIF,  | 
 | 
                       List 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  | 
 | 
              | 
 | 
            JFIFMarkerSegment.writeDefaultJFIF(ios,  | 
 | 
                                               thumbnails,  | 
 | 
                                               iccProfile,  | 
 | 
                                               writer);  | 
 | 
            if ((ignoreAdobe == false)  | 
 | 
                && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) { | 
 | 
                if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN)  | 
 | 
                    && (newAdobeTransform != JPEG.ADOBE_YCC)) { | 
 | 
                      | 
 | 
                    ignoreAdobe = true;  | 
 | 
                    writer.warningOccurred  | 
 | 
                        (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);  | 
 | 
                }  | 
 | 
            }  | 
 | 
        }  | 
 | 
          | 
 | 
        Iterator iter = markerSequence.iterator();  | 
 | 
        while(iter.hasNext()) { | 
 | 
            MarkerSegment seg = (MarkerSegment)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) { | 
 | 
                          | 
 | 
                        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) {   | 
 | 
            markerSequence = resetSequence;  | 
 | 
            resetSequence = null;  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
    public void print() { | 
 | 
        for (int i = 0; i < markerSequence.size(); i++) { | 
 | 
            MarkerSegment seg = (MarkerSegment) markerSequence.get(i);  | 
 | 
            seg.print();  | 
 | 
        }  | 
 | 
    }  | 
 | 
 | 
 | 
}  |