FileDocCategorySizeDatePackage
JPEGMetadata.javaAPI DocJava SE 6 API98008Tue Jun 10 00:21:52 BST 2008com.sun.imageio.plugins.jpeg

JPEGMetadata

public class JPEGMetadata extends IIOMetadata implements Cloneable
Metadata for the JPEG plug-in.

Fields Summary
private static final boolean
debug
private List
resetSequence
A copy of markerSequence, created the first time the markerSequence is modified. This is used by reset to restore the original state.
private boolean
inThumb
Set to true when reading a thumbnail stored as JPEG. This is used to enforce the prohibition of JFIF thumbnails containing any JFIF marker segments, and to ensure generation of a correct native subtree during getAsTree.
private boolean
hasAlpha
Set by the chroma node construction method to signal the presence or absence of an alpha channel to the transparency node construction method. Used only when constructing a standard metadata tree.
List
markerSequence
All data is a list of MarkerSegment objects. When accessing the list, use the tag to identify the particular subclass. Any JFIF marker segment must be the first element of the list if it is present, and any JFXX or APP2ICC marker segments are subordinate to the JFIF marker segment. This list is package visible so that the writer can access it.
final boolean
isStream
Indicates whether this object represents stream or image metadata. Package-visible so the writer can see it.
private boolean
transparencyDone
Constructors Summary
JPEGMetadata(boolean isStream, boolean inThumb)
Constructor containing code shared by other constructors.


    /////// End of package-access variables

    /////// Constructors

                
        
        super(true,  // Supports standard format
              JPEG.nativeImageMetadataFormatName,  // and a native format
              JPEG.nativeImageMetadataFormatClassName,
              null, null);  // No other formats
        this.inThumb = inThumb;
        // But if we are stream metadata, adjust the variables
        this.isStream = isStream;
        if (isStream) {
            nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;
            nativeMetadataFormatClassName = 
                JPEG.nativeStreamMetadataFormatClassName;
        }
    
JPEGMetadata(boolean isStream, boolean isThumb, ImageInputStream iis, JPEGImageReader reader)

 
        this(isStream, isThumb);

        JPEGBuffer buffer = new JPEGBuffer(iis);

        buffer.loadBuf(0);

        // The first three bytes should be FF, SOI, FF
        if (((buffer.buf[0] & 0xff) != 0xff) 
            || ((buffer.buf[1] & 0xff) != JPEG.SOI)
            || ((buffer.buf[2] & 0xff) != 0xff)) {
            throw new IIOException ("Image format error");
        }

        boolean done = false;
        buffer.bufAvail -=2;  // Next byte should be the ff before a marker
        buffer.bufPtr = 2;
        MarkerSegment newGuy = null;
        while (!done) {
            byte [] buf;
            int ptr;
            buffer.loadBuf(1);
            if (debug) {
                System.out.println("top of loop");
                buffer.print(10);
            }
            buffer.scanForFF(reader);
            switch (buffer.buf[buffer.bufPtr] & 0xff) {
            case 0:
                if (debug) {
                    System.out.println("Skipping 0");
                }
                buffer.bufAvail--;
                buffer.bufPtr++;
                break;
            case JPEG.SOF0:
            case JPEG.SOF1:
            case JPEG.SOF2:
                if (isStream) {
                    throw new IIOException 
                        ("SOF not permitted in stream metadata");
                }
                newGuy = new SOFMarkerSegment(buffer);
                break;
            case JPEG.DQT:
                newGuy = new DQTMarkerSegment(buffer);
                break;
            case JPEG.DHT:
                newGuy = new DHTMarkerSegment(buffer);
                break;
            case JPEG.DRI:
                newGuy = new DRIMarkerSegment(buffer);
                break;
            case JPEG.APP0:
                // Either JFIF, JFXX, or unknown APP0
                buffer.loadBuf(8); // tag, length, id
                buf = buffer.buf;
                ptr = buffer.bufPtr;
                if ((buf[ptr+3] == 'J") 
                    && (buf[ptr+4] == 'F")
                    && (buf[ptr+5] == 'I")
                    && (buf[ptr+6] == 'F")
                    && (buf[ptr+7] == 0)) {
                    if (inThumb) {
                        reader.warningOccurred
                            (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB);
                        // Leave newGuy null
                        // Read a dummy to skip the segment
                        JFIFMarkerSegment dummy = 
                            new JFIFMarkerSegment(buffer);
                    } else if (isStream) {
                        throw new IIOException 
                            ("JFIF not permitted in stream metadata");
                    } else if (markerSequence.isEmpty() == false) {
                        throw new IIOException
                            ("JFIF APP0 must be first marker after SOI");
                    } else {
                        newGuy = new JFIFMarkerSegment(buffer);
                    }
                } else if ((buf[ptr+3] == 'J") 
                           && (buf[ptr+4] == 'F")
                           && (buf[ptr+5] == 'X")
                           && (buf[ptr+6] == 'X")
                           && (buf[ptr+7] == 0)) {
                    if (isStream) {
                        throw new IIOException 
                            ("JFXX not permitted in stream metadata");
                    }
                    if (inThumb) {
                        throw new IIOException
                          ("JFXX markers not allowed in JFIF JPEG thumbnail");
                    }
                    JFIFMarkerSegment jfif = 
                        (JFIFMarkerSegment) findMarkerSegment
                               (JFIFMarkerSegment.class, true);
                    if (jfif == null) {
                        throw new IIOException 
                            ("JFXX encountered without prior JFIF!");
                    }
                    jfif.addJFXX(buffer, reader);
                    // newGuy remains null
                } else {
                    newGuy = new MarkerSegment(buffer);
                    newGuy.loadData(buffer);
                }
                break;
            case JPEG.APP2:
                // Either an ICC profile or unknown APP2
                buffer.loadBuf(15); // tag, length, id
                if ((buffer.buf[buffer.bufPtr+3] == 'I")
                    && (buffer.buf[buffer.bufPtr+4] == 'C")
                    && (buffer.buf[buffer.bufPtr+5] == 'C")
                    && (buffer.buf[buffer.bufPtr+6] == '_")
                    && (buffer.buf[buffer.bufPtr+7] == 'P")
                    && (buffer.buf[buffer.bufPtr+8] == 'R")
                    && (buffer.buf[buffer.bufPtr+9] == 'O")
                    && (buffer.buf[buffer.bufPtr+10] == 'F")
                    && (buffer.buf[buffer.bufPtr+11] == 'I")
                    && (buffer.buf[buffer.bufPtr+12] == 'L")
                    && (buffer.buf[buffer.bufPtr+13] == 'E")
                    && (buffer.buf[buffer.bufPtr+14] == 0)
                    ) {
                    if (isStream) {
                        throw new IIOException 
                            ("ICC profiles not permitted in stream metadata");
                    }

                    JFIFMarkerSegment jfif = 
                        (JFIFMarkerSegment) findMarkerSegment
                        (JFIFMarkerSegment.class, true);
                    if (jfif == null) {
                        throw new IIOException 
                            ("ICC APP2 encountered without prior JFIF!");
                    }
                    jfif.addICC(buffer);
                    // newGuy remains null
                } else {
                    newGuy = new MarkerSegment(buffer);
                    newGuy.loadData(buffer);
                }
                break;
            case JPEG.APP14:
                // Either Adobe or unknown APP14
                buffer.loadBuf(8); // tag, length, id
                if ((buffer.buf[buffer.bufPtr+3] == 'A")
                    && (buffer.buf[buffer.bufPtr+4] == 'd")
                    && (buffer.buf[buffer.bufPtr+5] == 'o")
                    && (buffer.buf[buffer.bufPtr+6] == 'b")
                    && (buffer.buf[buffer.bufPtr+7] == 'e")) {
                    if (isStream) {
                        throw new IIOException 
                      ("Adobe APP14 markers not permitted in stream metadata");
                    }
                    newGuy = new AdobeMarkerSegment(buffer);
                } else {
                    newGuy = new MarkerSegment(buffer);
                    newGuy.loadData(buffer);
                }

                break;
            case JPEG.COM:
                newGuy = new COMMarkerSegment(buffer);
                break;
            case JPEG.SOS:
                if (isStream) {
                    throw new IIOException 
                        ("SOS not permitted in stream metadata");
                }
                newGuy = new SOSMarkerSegment(buffer);
                break;
            case JPEG.RST0:
            case JPEG.RST1:
            case JPEG.RST2:
            case JPEG.RST3:
            case JPEG.RST4:
            case JPEG.RST5:
            case JPEG.RST6:
            case JPEG.RST7:
                if (debug) {
                    System.out.println("Restart Marker");
                }
                buffer.bufPtr++; // Just skip it
                buffer.bufAvail--;
                break;
            case JPEG.EOI:
                done = true;
                buffer.bufPtr++;
                buffer.bufAvail--;
                break;
            default:
                newGuy = new MarkerSegment(buffer);
                newGuy.loadData(buffer);
                newGuy.unknown = true;
                break;
            }
            if (newGuy != null) {
                markerSequence.add(newGuy);
                if (debug) {
                    newGuy.print();
                }
                newGuy = null;
            }
        }

        // Now that we've read up to the EOI, we need to push back 
        // whatever is left in the buffer, so that the next read 
        // in the native code will work.

        buffer.pushBack();

        if (!isConsistent()) {
            throw new IIOException("Inconsistent metadata read from stream");
        }
    
JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer)
Constructs a default stream JPEGMetadata object appropriate for the given write parameters.

        this(true, false);
        
        JPEGImageWriteParam jparam = null;
        
        if ((param != null) && (param instanceof JPEGImageWriteParam)) {
            jparam = (JPEGImageWriteParam) param;
            if (!jparam.areTablesSet()) {
                jparam = null;
            }
        }
        if (jparam != null) {
            markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));
            markerSequence.add
                (new DHTMarkerSegment(jparam.getDCHuffmanTables(),
                                      jparam.getACHuffmanTables()));
        } else {
            // default tables.
            markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));
            markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),
                                                    JPEG.getDefaultHuffmanTables(false)));
        }

        // Defensive programming
        if (!isConsistent()) {
            throw new InternalError("Default stream metadata is inconsistent");
        }
    
JPEGMetadata(ImageTypeSpecifier imageType, ImageWriteParam param, JPEGImageWriter writer)
Constructs a default image JPEGMetadata object appropriate for the given image type and write parameters.

        this(false, false);

        boolean wantJFIF = true;
        boolean wantAdobe = false;
        int transform = JPEG.ADOBE_UNKNOWN;
        boolean willSubsample = true;
        boolean wantICC = false;
        boolean wantProg = false;
        boolean wantOptimized = false;
        boolean wantExtended = false;
        boolean wantQTables = true;
        boolean wantHTables = true;
        float quality = JPEG.DEFAULT_QUALITY;
        byte[] componentIDs = { 1, 2, 3, 4};
        int numComponents = 0;

        ImageTypeSpecifier destType = null;

        if (param != null) {
            destType = param.getDestinationType();
            if (destType != null) {
                if (imageType != null) {
                    // Ignore the destination type. 
                    writer.warningOccurred
                        (JPEGImageWriter.WARNING_DEST_IGNORED);
                    destType = null;
                }
            }
            // The only progressive mode that makes sense here is MODE_DEFAULT
            if (param.canWriteProgressive()) {  
                // the param may not be one of ours, so it may return false.
                // If so, the following would throw an exception
                if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
                    wantProg = true;
                    wantOptimized = true;
                    wantHTables = false;
                }
            }

            if (param instanceof JPEGImageWriteParam) {
                JPEGImageWriteParam jparam = (JPEGImageWriteParam) param;
                if (jparam.areTablesSet()) {
                    wantQTables = false;  // If the param has them, metadata shouldn't
                    wantHTables = false;
                    if ((jparam.getDCHuffmanTables().length > 2) 
                            || (jparam.getACHuffmanTables().length > 2)) {
                        wantExtended = true;
                    }
                }
                // Progressive forces optimized, regardless of param setting
                // so consult the param re optimized only if not progressive
                if (!wantProg) {
                    wantOptimized = jparam.getOptimizeHuffmanTables();
                    if (wantOptimized) {
                        wantHTables = false;
                    }
                }
            }

            // compression quality should determine the q tables.  Note that this
            // will be ignored if we already decided not to create any.
            // Again, the param may not be one of ours, so we must check that it
            // supports compression settings
            if (param.canWriteCompressed()) {
                if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
                    quality = param.getCompressionQuality();
                }
            }                
        }
        
        // We are done with the param, now for the image types
        
        ColorSpace cs = null;
        if (destType != null) {
            ColorModel cm = destType.getColorModel();
            numComponents = cm.getNumComponents();
            boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
            boolean hasAlpha = cm.hasAlpha();
            cs = cm.getColorSpace();
            int type = cs.getType();
            switch(type) {
            case ColorSpace.TYPE_GRAY:
                willSubsample = false;
                if (hasExtraComponents) {  // e.g. alpha
                    wantJFIF = false;
                }
                break;
            case ColorSpace.TYPE_3CLR:
                if (cs == JPEG.YCC) {
                    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) { // e.g. K or alpha
                    wantJFIF = false;
                    if (!hasAlpha) { // Not alpha, so must be K
                        wantAdobe = true;
                        transform = JPEG.ADOBE_YCCK;
                    }
                }
                break;
            case ColorSpace.TYPE_RGB:  // with or without alpha
                wantJFIF = false;
                wantAdobe = true;
                willSubsample = false;
                componentIDs[0] = (byte) 'R";
                componentIDs[1] = (byte) 'G";
                componentIDs[2] = (byte) 'B";
                if (hasAlpha) {
                    componentIDs[3] = (byte) 'A";
                }
                break;
            default:  
                // Everything else is not subsampled, gets no special marker,
                // and component ids are 1 - N
                wantJFIF = false;
                willSubsample = false;
            }
        } else if (imageType != null) {
            ColorModel cm = imageType.getColorModel();
            numComponents = cm.getNumComponents();
            boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
            boolean hasAlpha = cm.hasAlpha();
            cs = cm.getColorSpace();
            int type = cs.getType();
            switch(type) {
            case ColorSpace.TYPE_GRAY:
                willSubsample = false;
                if (hasExtraComponents) {  // e.g. alpha
                    wantJFIF = false;
                }
                break;
            case ColorSpace.TYPE_RGB:  // with or without alpha
                // without alpha we just accept the JFIF defaults
                if (hasAlpha) {
                    wantJFIF = false;
                }
                break;
            case ColorSpace.TYPE_3CLR:
                wantJFIF = false;
                willSubsample = false;
                if (cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) {
                    willSubsample = true;
                    wantAdobe = true;
                    componentIDs[0] = (byte) 'Y";
                    componentIDs[1] = (byte) 'C";
                    componentIDs[2] = (byte) 'c";
                    if (hasAlpha) {
                        componentIDs[3] = (byte) 'A";
                    }
                }
                break;
            case ColorSpace.TYPE_YCbCr:
                if (hasExtraComponents) { // e.g. K or alpha
                    wantJFIF = false;
                    if (!hasAlpha) {  // then it must be K
                        wantAdobe = true;
                        transform = JPEG.ADOBE_YCCK;
                    }
                }
                break;
            case ColorSpace.TYPE_CMYK:
                wantJFIF = false;
                wantAdobe = true;
                transform = JPEG.ADOBE_YCCK;
                break;
                
            default:  
                // Everything else is not subsampled, gets no special marker,
                // and component ids are 0 - N
                wantJFIF = false;
                willSubsample = false;
            }

        }

        // do we want an ICC profile?
        if (wantJFIF && JPEG.isNonStandardICC(cs)) {
            wantICC = true;
        }

        // Now step through the markers, consulting our variables.
        if (wantJFIF) {
            JFIFMarkerSegment jfif = new JFIFMarkerSegment();
            markerSequence.add(jfif);
            if (wantICC) {
                try {
                    jfif.addICC((ICC_ColorSpace)cs);
                } catch (IOException e) {} // Can't happen here
            }
        }
        // Adobe
        if (wantAdobe) {
            markerSequence.add(new AdobeMarkerSegment(transform));
        }

        // dqt
        if (wantQTables) {
            markerSequence.add(new DQTMarkerSegment(quality, willSubsample));
        }

        // dht
        if (wantHTables) {
            markerSequence.add(new DHTMarkerSegment(willSubsample));
        }

        // sof
        markerSequence.add(new SOFMarkerSegment(wantProg,
                                                wantExtended, 
                                                willSubsample,
                                                componentIDs,
                                                numComponents)); 

        // sos
        if (!wantProg) {  // Default progression scans are done in the writer
            markerSequence.add(new SOSMarkerSegment(willSubsample, 
                                                    componentIDs,
                                                    numComponents));
        }

        // Defensive programming
        if (!isConsistent()) {
            throw new InternalError("Default image metadata is inconsistent");
        }
    
Methods Summary
protected java.lang.Objectclone()

        JPEGMetadata newGuy = null;
        try {
            newGuy = (JPEGMetadata) super.clone();
        } catch (CloneNotSupportedException e) {} // won't happen
        if (markerSequence != null) {
            newGuy.markerSequence = (List) cloneSequence();
        }
        newGuy.resetSequence = null;
        return newGuy;
    
private java.util.ListcloneSequence()
Returns a deep copy of the current marker sequence.

        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;
    
private intcountScanBands()
Returns the total number of bands referenced in all SOS marker segments, including 0 if there are no SOS marker segments.

        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();
    
private static java.awt.PointfindIntegerRatio(float value)

        float epsilon = 0.005F;

        // Normalize
        value = Math.abs(value);

        // Deal with min case
        if (value <= epsilon) {
            return new Point(1, 255);
        }

        // Deal with max case
        if (value >= 255) {
            return new Point(255, 1);
        }

        // Remember if we invert
        boolean inverted = false;
        if (value < 1.0) {
            value = 1.0F/value;
            inverted = true;
        }

        // First approximation
        int y = 1;
        int x = (int) Math.round(value);

        float ratio = (float) x;
        float delta = Math.abs(value - ratio);
        while (delta > epsilon) { // not close enough
            // Increment y and compute a new x
            y++;
            x = (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 intfindLastUnknownMarkerSegmentPosition()

        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;
    
com.sun.imageio.plugins.jpeg.MarkerSegmentfindMarkerSegment(int tag)
Returns the first MarkerSegment object in the list with the given tag, or null if none is found.

        Iterator iter = markerSequence.iterator();
        while (iter.hasNext()) {
            MarkerSegment seg = (MarkerSegment)iter.next();
            if (seg.tag == tag) {
                return seg;
            }
        }
        return null;
    
com.sun.imageio.plugins.jpeg.MarkerSegmentfindMarkerSegment(java.lang.Class cls, boolean first)
Returns the first or last MarkerSegment object in the list of the given class, or null if none is found.

        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 intfindMarkerSegmentPosition(java.lang.Class cls, boolean first)
Returns the index of the first or last MarkerSegment in the list of the given class, or -1 if none is found.

        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;
    
public org.w3c.dom.NodegetAsTree(java.lang.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);
    
javax.imageio.metadata.IIOMetadataNodegetNativeTree()

        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();  // JFIF must be first, so this skips it
                    header.appendChild(jfif.getNativeNode());
                }
                root.appendChild(sequence);
            } else {
                root = sequence;
            }
            top = sequence;
        }
        while(iter.hasNext()) {
            MarkerSegment seg = (MarkerSegment) iter.next();
            top.appendChild(seg.getNativeNode());
        }
        return root;
    
protected javax.imageio.metadata.IIOMetadataNodegetStandardChromaNode()

        hasAlpha = false;  // Unless we find otherwise

        // Colorspace type - follow the rules in the spec
        // First get the SOF marker segment, if there is one
        SOFMarkerSegment sof = (SOFMarkerSegment) 
            findMarkerSegment(SOFMarkerSegment.class, true);
        if (sof == null) {
            // No image, so no chroma
            return null;
        }

        IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
        IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
        chroma.appendChild(csType);

        // get the number of channels
        int numChannels = sof.componentSpecs.length;

        IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels");
        chroma.appendChild(numChanNode);
        numChanNode.setAttribute("value", Integer.toString(numChannels));

        // is there a JFIF marker segment?
        if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) {
            if (numChannels == 1) {
                csType.setAttribute("name", "GRAY");
            } else {
                csType.setAttribute("name", "YCbCr");
            }
            return chroma;
        }

        // How about an Adobe marker segment?
        AdobeMarkerSegment adobe = 
            (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
        if (adobe != null){
            switch (adobe.transform) {
            case JPEG.ADOBE_YCCK:
                csType.setAttribute("name", "YCCK");
                break;
            case JPEG.ADOBE_YCC:
                csType.setAttribute("name", "YCbCr");
                break;
            case JPEG.ADOBE_UNKNOWN:
                if (numChannels == 3) {
                    csType.setAttribute("name", "RGB");
                } else if (numChannels == 4) {
                    csType.setAttribute("name", "CMYK");
                }
                break;
            }
            return chroma;
        }

        // Neither marker.  Check components
        if (numChannels < 3) {
            csType.setAttribute("name", "GRAY");
            if (numChannels == 2) {
                hasAlpha = true;
            }
            return chroma;
        }

        boolean idsAreJFIF = 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;
        }

        // Check against the letters
        if ((sof.componentSpecs[0].componentId == 'R")
            && (sof.componentSpecs[1].componentId == 'G")
            && (sof.componentSpecs[2].componentId == 'B")){

            csType.setAttribute("name", "RGB");
            if ((numChannels == 4) 
                && (sof.componentSpecs[3].componentId == 'A")) {
                hasAlpha = true;
            }
            return chroma;
        }
        
        if ((sof.componentSpecs[0].componentId == 'Y")
            && (sof.componentSpecs[1].componentId == 'C")
            && (sof.componentSpecs[2].componentId == 'c")){

            csType.setAttribute("name", "PhotoYCC");
            if ((numChannels == 4) 
                && (sof.componentSpecs[3].componentId == 'A")) {
                hasAlpha = true;
            }            
            return chroma;
        }
        
        // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
        // 4-channel subsampled are YCbCrA, unsubsampled are CMYK

        boolean subsampled = false;

        int hfactor = sof.componentSpecs[0].HsamplingFactor;
        int vfactor = sof.componentSpecs[0].VsamplingFactor;

        for (int i = 1; i<sof.componentSpecs.length; i++) {
            if ((sof.componentSpecs[i].HsamplingFactor != hfactor)
                || (sof.componentSpecs[i].VsamplingFactor != vfactor)){
                subsampled = true;
                break;
            }
        }

        if (subsampled) {
            csType.setAttribute("name", "YCbCr");
            if (numChannels == 4) {
                hasAlpha = true;
            }
            return chroma;
        }
         
        // Not subsampled.  numChannels < 3 is taken care of above
        if (numChannels == 3) {
            csType.setAttribute("name", "RGB");
        } else {
            csType.setAttribute("name", "CMYK");
        }

        return chroma;
    
protected javax.imageio.metadata.IIOMetadataNodegetStandardCompressionNode()


        IIOMetadataNode compression = new IIOMetadataNode("Compression");

        // CompressionTypeName
        IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName");
        name.setAttribute("value", "JPEG");
        compression.appendChild(name);

        // Lossless - false
        IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
        lossless.setAttribute("value", "false");
        compression.appendChild(lossless);

        // NumProgressiveScans - count sos segments
        int sosCount = 0;
        Iterator 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 javax.imageio.metadata.IIOMetadataNodegetStandardDimensionNode()

        // If we have a JFIF marker segment, we know a little
        // otherwise all we know is the orientation, which is always normal
        IIOMetadataNode dim = new IIOMetadataNode("Dimension");
        IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
        orient.setAttribute("value", "normal");
        dim.appendChild(orient);

        JFIFMarkerSegment jfif = 
            (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
        if (jfif != null) {

            // Aspect Ratio is width of pixel / height of pixel
            float aspectRatio;
            if (jfif.resUnits == 0) {
                // In this case they just encode aspect ratio directly
                aspectRatio = ((float) jfif.Xdensity)/jfif.Ydensity;
            } else {
                // They are true densities (e.g. dpi) and must be inverted
                aspectRatio = ((float) jfif.Ydensity)/jfif.Xdensity;
            }
            IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio");
            aspect.setAttribute("value", Float.toString(aspectRatio));
            dim.insertBefore(aspect, orient);

            // Pixel size
            if (jfif.resUnits != 0) {
                // 1 == dpi, 2 == dpc
                float scale = (jfif.resUnits == 1) ? 25.4F : 10.0F;

                IIOMetadataNode horiz = 
                    new IIOMetadataNode("HorizontalPixelSize");
                horiz.setAttribute("value", 
                                   Float.toString(scale/jfif.Xdensity));
                dim.appendChild(horiz);

                IIOMetadataNode vert = 
                    new IIOMetadataNode("VerticalPixelSize");
                vert.setAttribute("value", 
                                  Float.toString(scale/jfif.Ydensity));
                dim.appendChild(vert);
            }
        }
        return dim;
    
protected javax.imageio.metadata.IIOMetadataNodegetStandardTextNode()

        IIOMetadataNode text = null;
        // Add a text entry for each COM Marker Segment
        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 javax.imageio.metadata.IIOMetadataNodegetStandardTransparencyNode()

        IIOMetadataNode trans = null;
        if (hasAlpha == true) {
            trans = new IIOMetadataNode("Transparency");
            IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
            alpha.setAttribute("value", "nonpremultiplied"); // Always assume
            trans.appendChild(alpha);
        }
        return trans;
    
private voidinsertAdobeMarkerSegment(com.sun.imageio.plugins.jpeg.AdobeMarkerSegment newGuy)
Insert the given AdobeMarkerSegment into the marker sequence, as follows (we assume there is no Adobe segment yet): If there is a JFIF segment, then the new Adobe segment is inserted after it. If there is no JFIF segment, the new Adobe segment is inserted after the last Unknown segment, if there are any. If there are no Unknown segments, the new Adobe segment is inserted at the beginning of the sequence.

        boolean hasJFIF = 
            (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
        int lastUnknown = findLastUnknownMarkerSegmentPosition();
        if (hasJFIF) {
            markerSequence.add(1, newGuy);  // JFIF is always 0
        } else if (lastUnknown != -1) {
            markerSequence.add(lastUnknown+1, newGuy);
        } else {
            markerSequence.add(0, newGuy);
        }
    
private voidinsertCOMMarkerSegment(com.sun.imageio.plugins.jpeg.COMMarkerSegment newGuy)
Insert a new COM marker segment into an appropriate place in the marker sequence, as follows: If there already exist COM marker segments, the new one is inserted after the last one. If there are no COM segments, the new COM segment is inserted after the JFIF segment, if there is one. If there is no JFIF segment, the new COM segment is inserted after the Adobe marker segment, if there is one. If there is no Adobe segment, the new COM segment is inserted at the beginning of the sequence.

        int lastCOM = findMarkerSegmentPosition(COMMarkerSegment.class, false);
        boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
        int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
        if (lastCOM != -1) {
            markerSequence.add(lastCOM+1, newGuy);
        } else if (hasJFIF) {
            markerSequence.add(1, newGuy);  // JFIF is always 0
        } else if (firstAdobe != -1) {
            markerSequence.add(firstAdobe+1, newGuy);
        } else {
            markerSequence.add(0, newGuy);
        }
    
private booleanisConsistent()
Check that this metadata object is in a consistent state and return true if it is or false otherwise. All the constructors and modifiers should call this method at the end to guarantee that the data is always consistent, as the writer relies on this.

        SOFMarkerSegment sof = 
            (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, 
                                                 true);
        JFIFMarkerSegment jfif =
            (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, 
                                                  true);
        AdobeMarkerSegment adobe =
            (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, 
                                                   true);
        boolean retval = true;
        if (!isStream) {
            if (sof != null) {
                // SOF numBands = total scan bands
                int numSOFBands = sof.componentSpecs.length;
                int numScanBands = countScanBands();
                if (numScanBands != 0) {  // No SOS is OK
                    if (numScanBands != numSOFBands) {
                        retval = false;
                    }
                }
                // If JFIF is present, component ids are 1-3, bands are 1 or 3
                if (jfif != null) {
                    if ((numSOFBands != 1) && (numSOFBands != 3)) {
                        retval = false;
                    }
                    for (int i = 0; i < numSOFBands; i++) {
                        if (sof.componentSpecs[i].componentId != i+1) {
                            retval = false;
                        }
                    }
                
                    // If both JFIF and Adobe are present,
                    // Adobe transform == unknown for gray,
                    // YCC for 3-chan.
                    if ((adobe != null) 
                        && (((numSOFBands == 1) 
                             && (adobe.transform != JPEG.ADOBE_UNKNOWN))
                            || ((numSOFBands == 3)
                                && (adobe.transform != JPEG.ADOBE_YCC)))) {
                        retval = false;
                    }
                }
            } else {
                // stream can't have jfif, adobe, sof, or sos
                SOSMarkerSegment sos = 
                    (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,
                                                         true);
                if ((jfif != null) || (adobe != null) 
                    || (sof != null) || (sos != null)) {
                    retval = false;
                }
            }
        }
        return retval;
    
public booleanisReadOnly()

        return false;
    
private voidmergeAdobeNode(org.w3c.dom.Node node)
Merge the given Adobe APP14 node into the marker sequence. If there already exists an Adobe marker segment, then its attributes are updated from the node. If there is no Adobe segment, then a new one is created and added using insertAdobeMarkerSegment.

        AdobeMarkerSegment adobe = 
            (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
        if (adobe != null) {
            adobe.updateFromNativeNode(node, false);
        } else {
            AdobeMarkerSegment newGuy = new AdobeMarkerSegment(node);
            insertAdobeMarkerSegment(newGuy);
        }
    
private voidmergeCOMNode(org.w3c.dom.Node node)
Merge the given COM node into the marker sequence. A new COM marker segment is created and added to the sequence using insertCOMMarkerSegment.

        COMMarkerSegment newGuy = new COMMarkerSegment(node);
        insertCOMMarkerSegment(newGuy);
    
private voidmergeDHTNode(org.w3c.dom.Node node)
Merge the given DHT node into the marker sequence. If there already exist DHT marker segments in the sequence, then each table in the node replaces the first table, in any DHT segment, with the same table class and table id. If none of the existing DHT segments contain a table with the same class and id, then the table is added to the last existing DHT segment. If there are no DHT segments, then a new one is created and added as follows: If there are DQT segments, the new DHT segment is inserted immediately following the last DQT segment. If there are no DQT segments, the new DHT segment is inserted before an SOF segment, if there is one. If there is no SOF segment, the new DHT segment is inserted before the first SOS segment, if there is one. If there is no SOS segment, the new DHT segment is added to the end of the sequence.

        // First collect any existing DQT nodes into a local list
        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 voidmergeDQTNode(org.w3c.dom.Node node)
Merge the given DQT node into the marker sequence. If there already exist DQT marker segments in the sequence, then each table in the node replaces the first table, in any DQT segment, with the same table id. If none of the existing DQT segments contain a table with the same id, then the table is added to the last existing DQT segment. If there are no DQT segments, then a new one is created and added as follows: If there are DHT segments, the new DQT segment is inserted before the first one. If there are no DHT segments, the new DQT segment is inserted before an SOF segment, if there is one. If there is no SOF segment, the new DQT segment is inserted before the first SOS segment, if there is one. If there is no SOS segment, the new DQT segment is added to the end of the sequence.

        // First collect any existing DQT nodes into a local list
        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 voidmergeDRINode(org.w3c.dom.Node node)
Merge the given DRI node into the marker sequence. If there already exists a DRI marker segment, the restart interval value is updated. If there is no DRI segment, then a new one is created and added as follows: If there is an SOF segment, the new DRI segment is inserted before it. If there is no SOF segment, the new DRI segment is inserted before the first SOS segment, if there is one. If there is no SOS segment, the new DRI segment is added to the end of the sequence.

        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 voidmergeJFIFsubtree(org.w3c.dom.Node JPEGvariety)
Merge a JFIF subtree into the marker sequence, if the subtree is non-empty. If a JFIF marker exists, update it from the subtree. If none exists, create one from the subtree and insert it at the beginning of the marker sequence.

        if (JPEGvariety.getChildNodes().getLength() != 0) {
            Node jfifNode = JPEGvariety.getFirstChild();
            // is there already a jfif marker segment?
            JFIFMarkerSegment jfifSeg = 
                (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
            if (jfifSeg != null) {
                jfifSeg.updateFromNativeNode(jfifNode, false);
            } else {
                // Add it as the first element in the list.
                markerSequence.add(0, new JFIFMarkerSegment(jfifNode));
            }
        }
    
private voidmergeNativeTree(org.w3c.dom.Node root)

        String name = root.getNodeName();
        if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
                                : JPEG.nativeImageMetadataFormatName)) {
            throw new IIOInvalidTreeException("Invalid root node name: " + name, 
                                              root);
        }
        if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
            throw new IIOInvalidTreeException(
                "JPEGvariety and markerSequence nodes must be present", root);
        }
        mergeJFIFsubtree(root.getFirstChild());
        mergeSequenceSubtree(root.getLastChild());
    
private voidmergeSOFNode(org.w3c.dom.Node node)
Merge the given SOF node into the marker sequence. If there already exists an SOF marker segment in the sequence, then its values are updated from the node. If there is no SOF segment, then a new one is created and added as follows: If there are any SOS segments, the new SOF segment is inserted before the first one. If there is no SOS segment, the new SOF segment is added to the end of the sequence.

        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 voidmergeSOSNode(org.w3c.dom.Node node)
Merge the given SOS node into the marker sequence. If there already exists a single SOS marker segment, then the values are updated from the node. If there are more than one existing SOS marker segments, then an IIOInvalidTreeException is thrown, as SOS segments cannot be merged into a set of progressive scans. If there are no SOS marker segments, a new one is created and added to the end of the sequence.

        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 voidmergeSequenceSubtree(org.w3c.dom.Node sequenceTree)

        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 voidmergeStandardChromaNode(org.w3c.dom.Node node, org.w3c.dom.NodeList siblings)

        // ColorSpaceType can change the target colorspace for compression
        // This must take any transparency node into account as well, as
        // that affects the number of channels (if alpha is present).  If
        // a transparency node is dealt with here, set a flag to indicate
        // this to the transparency processor below.  If we discover that
        // the nodes are not in order, throw an exception as the tree is
        // invalid.

        if (transparencyDone) {
            throw new IIOInvalidTreeException
                ("Transparency node must follow Chroma node", node);
        }

        Node csType = node.getFirstChild();
        if ((csType == null) || !csType.getNodeName().equals("ColorSpaceType")) {
            // If there is no ColorSpaceType node, we have nothing to do
            return;
        }

        String csName = csType.getAttributes().getNamedItem("name").getNodeValue();

        int numChannels = 0;
        boolean wantJFIF = false;
        boolean wantAdobe = false;
        int transform = 0;
        boolean willSubsample = false;
        byte [] ids = {1, 2, 3, 4};  // JFIF compatible
        if (csName.equals("GRAY")) {
            numChannels = 1;
            wantJFIF = true;
        } else if (csName.equals("YCbCr")) {
            numChannels = 3;
            wantJFIF = true;
            willSubsample = true;
        } else if (csName.equals("PhotoYCC")) {
            numChannels = 3;
            wantAdobe = true;
            transform = JPEG.ADOBE_YCC;
            ids[0] = (byte) 'Y";
            ids[1] = (byte) 'C";
            ids[2] = (byte) 'c";
        } else if (csName.equals("RGB")) {
            numChannels = 3;
            wantAdobe = true;
            transform = JPEG.ADOBE_UNKNOWN;
            ids[0] = (byte) 'R";
            ids[1] = (byte) 'G";
            ids[2] = (byte) 'B";
        } else if ((csName.equals("XYZ"))
                   || (csName.equals("Lab"))
                   || (csName.equals("Luv"))
                   || (csName.equals("YxY"))
                   || (csName.equals("HSV"))
                   || (csName.equals("HLS"))
                   || (csName.equals("CMY"))
                   || (csName.equals("3CLR"))) {
            numChannels = 3;
        } else if (csName.equals("YCCK")) {
            numChannels = 4;
            wantAdobe = true;
            transform = JPEG.ADOBE_YCCK;
            willSubsample = true;
        } else if (csName.equals("CMYK")) {
            numChannels = 4;
            wantAdobe = true;
            transform = JPEG.ADOBE_UNKNOWN;
        } else if (csName.equals("4CLR")) {
            numChannels = 4;
        } else { // We can't handle them, so don't modify any metadata
            return;
        }

        boolean wantAlpha = false;
        for (int i = 0; i < siblings.getLength(); i++) {
            Node trans = siblings.item(i);
            if (trans.getNodeName().equals("Transparency")) {
                wantAlpha = wantAlpha(trans);
                break;  // out of for
            }
        }

        if (wantAlpha) {
            numChannels++;
            wantJFIF = false;
            if (ids[0] == (byte) 'R") {
                ids[3] = (byte) 'A";
                wantAdobe = false;
            }
        }

        JFIFMarkerSegment jfif = 
            (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
        AdobeMarkerSegment adobe = 
            (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
        SOFMarkerSegment sof = 
            (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
        SOSMarkerSegment sos = 
            (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);

        // If the metadata specifies progressive, then the number of channels
        // must match, so that we can modify all the existing SOS marker segments.
        // If they don't match, we don't know what to do with SOS so we can't do 
        // the merge.  We then just return silently.
        // An exception would not be appropriate.  A warning might, but we have
        // nowhere to send it to.
        if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
            if ((sof.componentSpecs.length != numChannels) && (sos != null)) {
                return;
            }
        }

        // JFIF header might be removed
        if (!wantJFIF && (jfif != null)) {
            markerSequence.remove(jfif);
        }

        // Now add a JFIF if we do want one, but only if it isn't stream metadata
        if (wantJFIF && !isStream) {
            markerSequence.add(0, new JFIFMarkerSegment());
        }

        // Adobe header might be removed or the transform modified, if it isn't
        // stream metadata
        if (wantAdobe) {
            if ((adobe == null) && !isStream) {
                adobe = new AdobeMarkerSegment(transform);
                insertAdobeMarkerSegment(adobe);
            } else {
                adobe.transform = transform;
            }
        } else if (adobe != null) {
            markerSequence.remove(adobe);
        }

        boolean updateQtables = false;
        boolean updateHtables = false;

        boolean progressive = false;
        
        int [] subsampledSelectors = {0, 1, 1, 0 } ;
        int [] nonSubsampledSelectors = { 0, 0, 0, 0};

        int [] newTableSelectors = willSubsample 
                                   ? subsampledSelectors
                                   : nonSubsampledSelectors;

        // Keep the old componentSpecs array
        SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null;
        // SOF might be modified
        if (sof != null) {
            oldCompSpecs = sof.componentSpecs;
            progressive = (sof.tag == JPEG.SOF2);
            // Now replace the SOF with a new one; it might be the same, but
            // this is easier.
            markerSequence.set(markerSequence.indexOf(sof), 
                               new SOFMarkerSegment(progressive, 
                                                    false, // we never need extended
                                                    willSubsample,
                                                    ids,
                                                    numChannels));

            // Now suss out if subsampling changed and set the boolean for 
            // updating the q tables
            // if the old componentSpec q table selectors don't match
            // the new ones, update the qtables.  The new selectors are already
            // in place in the new SOF segment above.
            for (int i = 0; i < oldCompSpecs.length; i++) {
                if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) {
                    updateQtables = true;
                }
            }

            if (progressive) {
                // if the component ids are different, update all the existing scans
                // ignore Huffman tables
                boolean idsDiffer = false;
                for (int i = 0; i < oldCompSpecs.length; i++) {
                    if (ids[i] != oldCompSpecs[i].componentId) {
                        idsDiffer = true;
                    }
                }
                if (idsDiffer) {
                    // update the ids in each SOS marker segment
                    for (Iterator 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
                                // above.
                                for (int j = 0; j < oldCompSpecs.length; j++) {
                                    if (oldCompSpecs[j].componentId == oldSelector) {
                                        target.componentSpecs[i].componentSelector = 
                                            ids[j];
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                if (sos != null) {
                    // htables - if the old htable selectors don't match the new ones,
                    // update the tables.
                    for (int i = 0; i < sos.componentSpecs.length; i++) {
                        if ((sos.componentSpecs[i].dcHuffTable 
                             != newTableSelectors[i]) 
                            || (sos.componentSpecs[i].acHuffTable 
                                != newTableSelectors[i])) {
                            updateHtables = true;
                        }
                    }

                    // Might be the same as the old one, but this is easier.
                    markerSequence.set(markerSequence.indexOf(sos), 
                               new SOSMarkerSegment(willSubsample, 
                                                    ids,
                                                    numChannels));
                }
            }
        } else {
            // should be stream metadata if there isn't an SOF, but check it anyway
            if (isStream) {
                // update tables - routines below check if it's really necessary
                updateQtables = true;
                updateHtables = true;
            }
        }

        if (updateQtables) {
            List 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 we are not subsampling, we just need one, so don't do anything
            if (!tableSegments.isEmpty() && willSubsample) {
                // Is it really necessary?  There should be at least 2 tables. 
                // If there is only one, assume it's a scaled "standard"
                // luminance table, extract the scaling factor, and generate a 
                // scaled "standard" chrominance table. 

                // Find the table with selector 1.
                boolean found = false;
                for (Iterator 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) {
                    //    find the table with selector 0.  There should be one.
                    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
                    // add it to the last DQT segment
                    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 we are not subsampling, we just need one, so don't do anything
            if (!tableSegments.isEmpty() && willSubsample) {
                // Is it really necessary?  There should be at least 2 dc and 2 ac
                // tables.  If there is only one, add a
                // "standard " chrominance table.

                // find a table with selector 1. AC/DC is irrelevant
                boolean found = false;
                for (Iterator 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
                    // to the last DHT segment
                    DHTMarkerSegment lastDHT = 
                        (DHTMarkerSegment) tableSegments.get(tableSegments.size()-1);
                    lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1);
                    lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1);
                }
            }
        }
    
private voidmergeStandardCompressionNode(org.w3c.dom.Node node)

        // NumProgressiveScans is ignored.  Progression must be enabled on the
        // ImageWriteParam.
        // No-op
    
private voidmergeStandardDataNode(org.w3c.dom.Node node)

        // No-op
    
private voidmergeStandardDimensionNode(org.w3c.dom.Node node)

        // Pixel Aspect Ratio or pixel size can be incorporated if there is, 
        // or can be, a JFIF segment
        JFIFMarkerSegment jfif =
            (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
        if (jfif == null) {
            // Can there be one?
            // Criteria: 
            // SOF must be present with 1 or 3 channels, (stream metadata fails this)
            //     Component ids must be JFIF compatible.
            boolean canHaveJFIF = false;
            SOFMarkerSegment sof = 
                (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
            if (sof != null) {
                int numChannels = sof.componentSpecs.length;
                if ((numChannels == 1) || (numChannels == 3)) {
                    canHaveJFIF = true; // remaining tests are negative
                    for (int i = 0; i < sof.componentSpecs.length; i++) {
                        if (sof.componentSpecs[i].componentId != i+1)
                            canHaveJFIF = false;
                    }
                    // if Adobe present, transform = ADOBE_UNKNOWN for 1-channel,
                    //     ADOBE_YCC for 3-channel.
                    AdobeMarkerSegment adobe = 
                        (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
                                                               true);
                    if (adobe != null) {
                        if (adobe.transform != ((numChannels == 1) 
                                                ? JPEG.ADOBE_UNKNOWN 
                                                : JPEG.ADOBE_YCC)) {
                            canHaveJFIF = false;
                        }
                    }
                }
            }
            // If so, create one and insert it into the sequence.  Note that
            // default is just pixel ratio at 1:1
            if (canHaveJFIF) {
                jfif = new JFIFMarkerSegment();
                markerSequence.add(0, jfif);
            }
        }
        if (jfif != null) {
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                NamedNodeMap attrs = child.getAttributes();
                String name = child.getNodeName();
                if (name.equals("PixelAspectRatio")) {
                    String valueString = attrs.getNamedItem("value").getNodeValue();
                    float value = Float.parseFloat(valueString);
                    Point p = findIntegerRatio(value);
                    jfif.resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;
                    jfif.Xdensity = p.x;
                    jfif.Xdensity = p.y;
                } else if (name.equals("HorizontalPixelSize")) {
                    String valueString = attrs.getNamedItem("value").getNodeValue();
                    float value = Float.parseFloat(valueString);
                    // Convert from mm/dot to dots/cm
                    int dpcm = (int) Math.round(1.0/(value*10.0));
                    jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
                    jfif.Xdensity = dpcm;
                } else if (name.equals("VerticalPixelSize")) {
                    String valueString = attrs.getNamedItem("value").getNodeValue();
                    float value = Float.parseFloat(valueString);
                    // Convert from mm/dot to dots/cm
                    int dpcm = (int) Math.round(1.0/(value*10.0));
                    jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
                    jfif.Ydensity = dpcm;
                }
                
            }
        }
    
private voidmergeStandardDocumentNode(org.w3c.dom.Node node)

        // No-op
    
private voidmergeStandardTextNode(org.w3c.dom.Node node)

        // Convert to comments.  For the moment ignore the encoding issue.
        // Ignore keywords, language, and encoding (for the moment).
        // If compression tag is present, use only entries with "none".
        NodeList children = node.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            NamedNodeMap attrs = child.getAttributes();
            Node comp = attrs.getNamedItem("compression");
            boolean copyIt = true;
            if (comp != null) {
                String compString = comp.getNodeValue();
                if (!compString.equals("none")) {
                    copyIt = false;
                }
            }
            if (copyIt) {
                String value = attrs.getNamedItem("value").getNodeValue();
                COMMarkerSegment com = new COMMarkerSegment(value);
                insertCOMMarkerSegment(com);
            }
        }
    
private voidmergeStandardTransparencyNode(org.w3c.dom.Node node)

        // This might indicate that an alpha channel is being added or removed.
        // The nodes must appear in order, and a Chroma node will process any
        // transparency, so process it here only if there was no Chroma node
        // Do nothing for stream metadata
        if (!transparencyDone && !isStream) {
            boolean wantAlpha = wantAlpha(node);
            // do we have alpha already?  If the number of channels is 2 or 4, 
            // we do, as we don't support CMYK, nor can we add alpha to it
            // The number of channels can be determined from the SOF
            JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment
                (JFIFMarkerSegment.class, true);
            AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment
                (AdobeMarkerSegment.class, true);
            SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment
                (SOFMarkerSegment.class, true);
            SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment
                (SOSMarkerSegment.class, true);

            // We can do nothing for progressive, as we don't know how to
            // modify the scans.
            if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
                return;
            }
            
            // Do we already have alpha?  We can tell by the number of channels
            // We must have an sof, or we can't do anything further
            if (sof != null) {
                int numChannels = sof.componentSpecs.length;
                boolean hadAlpha = (numChannels == 2) || (numChannels == 4);
                // proceed only if the old state and the new state differ
                if (hadAlpha != wantAlpha) {
                    if (wantAlpha) {  // Adding alpha
                        numChannels++;
                        if (jfif != null) {
                            markerSequence.remove(jfif);
                        }
            
                        // If an adobe marker is present, transform must be UNKNOWN
                        if (adobe != null) {
                            adobe.transform = JPEG.ADOBE_UNKNOWN;
                        }
                        
                        // Add a component spec with appropriate parameters to SOF
                        SOFMarkerSegment.ComponentSpec [] newSpecs = 
                            new SOFMarkerSegment.ComponentSpec[numChannels];
                        for (int i = 0; i < sof.componentSpecs.length; i++) {
                            newSpecs[i] = sof.componentSpecs[i];
                        }
                        byte oldFirstID = (byte) sof.componentSpecs[0].componentId;
                        byte newID = (byte) ((oldFirstID > 1) ? 'A" : 4);
                        newSpecs[numChannels-1] = 
                            sof.getComponentSpec(newID,
                                sof.componentSpecs[0].HsamplingFactor,
                                sof.componentSpecs[0].QtableSelector);

                        sof.componentSpecs = newSpecs;

                        // Add a component spec with appropriate parameters to SOS
                        SOSMarkerSegment.ScanComponentSpec [] newScanSpecs = 
                            new SOSMarkerSegment.ScanComponentSpec [numChannels];
                        for (int i = 0; i < sos.componentSpecs.length; i++) {
                            newScanSpecs[i] = sos.componentSpecs[i];
                        }
                        newScanSpecs[numChannels-1] = 
                            sos.getScanComponentSpec (newID, 0);
                        sos.componentSpecs = newScanSpecs;
                    } else {  // Removing alpha
                        numChannels--;
                        // Remove a component spec from SOF
                        SOFMarkerSegment.ComponentSpec [] newSpecs = 
                            new SOFMarkerSegment.ComponentSpec[numChannels];
                        for (int i = 0; i < numChannels; i++) {
                            newSpecs[i] = sof.componentSpecs[i];
                        }
                        sof.componentSpecs = newSpecs;

                        // Remove a component spec from SOS
                        SOSMarkerSegment.ScanComponentSpec [] newScanSpecs = 
                            new SOSMarkerSegment.ScanComponentSpec [numChannels];
                        for (int i = 0; i < numChannels; i++) {
                            newScanSpecs[i] = sos.componentSpecs[i];
                        }
                        sos.componentSpecs = newScanSpecs;
                    }
                }
            }
        }
    
private voidmergeStandardTree(org.w3c.dom.Node root)

        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);
            }
        }
    
public voidmergeTree(java.lang.String formatName, org.w3c.dom.Node root)

        if (formatName == null) {
            throw new IllegalArgumentException("null formatName!");
        } 
        if (root == null) {
            throw new IllegalArgumentException("null root!");
        }
        List copy = null;
        if (resetSequence == null) {
            resetSequence = cloneSequence();  // Deep copy
            copy = resetSequence;  // Avoid cloning twice
        } else {
            copy = cloneSequence();
        }
        if (isStream &&
            (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
                mergeNativeTree(root);
        } else if (!isStream &&
                   (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
            mergeNativeTree(root);
        } else if (!isStream &&
                   (formatName.equals
                    (IIOMetadataFormatImpl.standardMetadataFormatName))) {
            mergeStandardTree(root);
        } else {
            throw  new IllegalArgumentException("Unsupported format name: " 
                                                + formatName);
        }
        if (!isConsistent()) {
            markerSequence = copy;
            throw new IIOInvalidTreeException
                ("Merged tree is invalid; original restored", root);
        }
    
private voidmergeUnknownNode(org.w3c.dom.Node node)
Merge the given Unknown node into the marker sequence. A new Unknown marker segment is created and added to the sequence as follows: If there already exist Unknown marker segments, the new one is inserted after the last one. If there are no Unknown marker segments, the new Unknown marker segment is inserted after the JFIF segment, if there is one. If there is no JFIF segment, the new Unknown segment is inserted before the Adobe marker segment, if there is one. If there is no Adobe segment, the new Unknown segment is inserted at the beginning of the sequence.

        MarkerSegment newGuy = new MarkerSegment(node);
        int lastUnknown = findLastUnknownMarkerSegmentPosition();
        boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
        int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
        if (lastUnknown != -1) {
            markerSequence.add(lastUnknown+1, newGuy);
        } else if (hasJFIF) {
            markerSequence.add(1, newGuy);  // JFIF is always 0
        } if (firstAdobe != -1) {
            markerSequence.add(firstAdobe, newGuy);
        } else {
            markerSequence.add(0, newGuy);
        }
    
public voidprint()

        for (int i = 0; i < markerSequence.size(); i++) {
            MarkerSegment seg = (MarkerSegment) markerSequence.get(i);
            seg.print();
        }
    
public voidreset()

        if (resetSequence != null) {  // Otherwise no need to reset
            markerSequence = resetSequence;
            resetSequence = null;
        }
    
voidsetFromMarkerSequenceNode(org.w3c.dom.Node markerSequenceNode)


        NodeList children = markerSequenceNode.getChildNodes();
        // for all the children, add a marker segment
        for (int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            String childName = node.getNodeName();
            if (childName.equals("dqt")) {
                markerSequence.add(new DQTMarkerSegment(node));
            } else if (childName.equals("dht")) {
                markerSequence.add(new DHTMarkerSegment(node));
            } else if (childName.equals("dri")) {
                markerSequence.add(new DRIMarkerSegment(node));
            } else if (childName.equals("com")) {
                markerSequence.add(new COMMarkerSegment(node));
            } else if (childName.equals("app14Adobe")) {
                markerSequence.add(new AdobeMarkerSegment(node));
            } else if (childName.equals("unknown")) {
                markerSequence.add(new MarkerSegment(node));
            } else if (childName.equals("sof")) {
                markerSequence.add(new SOFMarkerSegment(node));
            } else if (childName.equals("sos")) {
                markerSequence.add(new SOSMarkerSegment(node));
            } else {
                throw new IIOInvalidTreeException("Invalid " 
                    + (isStream ? "stream " : "image ") + "child: " 
                    + childName, node);
            }
        }
    
private voidsetFromNativeTree(org.w3c.dom.Node root)

        if (resetSequence == null) {
            resetSequence = markerSequence;
        }
        markerSequence = new ArrayList();

        // Build a whole new marker sequence from the tree

        String name = root.getNodeName();
        if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
                                : JPEG.nativeImageMetadataFormatName)) {
            throw new IIOInvalidTreeException("Invalid root node name: " + name, 
                                              root);
        }
        if (!isStream) {
            if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
                throw new IIOInvalidTreeException(
                    "JPEGvariety and markerSequence nodes must be present", root);
            }

            Node JPEGvariety = root.getFirstChild();

            if (JPEGvariety.getChildNodes().getLength() != 0) {
                markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild()));
            }
        }

        Node markerSequenceNode = isStream ? root : root.getLastChild();
        setFromMarkerSequenceNode(markerSequenceNode);

    
public voidsetFromTree(java.lang.String formatName, org.w3c.dom.Node root)

        if (formatName == null) {
            throw new IllegalArgumentException("null formatName!");
        } 
        if (root == null) {
            throw new IllegalArgumentException("null root!");
        }
        if (isStream && 
            (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
            setFromNativeTree(root);
        } else if (!isStream &&
                   (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
            setFromNativeTree(root);
        } else if (!isStream &&
                   (formatName.equals
                    (IIOMetadataFormatImpl.standardMetadataFormatName))) {
            // In this case a reset followed by a merge is correct
            super.setFromTree(formatName, root);
        } else {
            throw  new IllegalArgumentException("Unsupported format name: " 
                                                + formatName);
        }
    
private booleanwantAlpha(org.w3c.dom.Node transparency)

        boolean returnValue = false;
        Node alpha = transparency.getFirstChild();  // Alpha must be first if present
        if (alpha.getNodeName().equals("Alpha")) {
            if (alpha.hasAttributes()) {
                String value = 
                    alpha.getAttributes().getNamedItem("value").getNodeValue();
                if (!value.equals("none")) {
                    returnValue = true;
                }
            }
        }
        transparencyDone = true;
        return returnValue;
    
voidwriteToStream(javax.imageio.stream.ImageOutputStream ios, boolean ignoreJFIF, boolean forceJFIF, java.util.List thumbnails, java.awt.color.ICC_Profile iccProfile, boolean ignoreAdobe, int newAdobeTransform, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)

        if (forceJFIF) {
            // Write a default JFIF segment, including thumbnails
            // This won't be duplicated below because forceJFIF will be
            // set only if there is no JFIF present already.
            JFIFMarkerSegment.writeDefaultJFIF(ios, 
                                               thumbnails,
                                               iccProfile,
                                               writer);
            if ((ignoreAdobe == false) 
                && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) {
                if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN) 
                    && (newAdobeTransform != JPEG.ADOBE_YCC)) {
                    // Not compatible, so ignore Adobe.
                    ignoreAdobe = true;
                    writer.warningOccurred
                        (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
                }
            }
        }
        // Iterate over each MarkerSegment
        Iterator 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) {
                        // If adobe isn't JFIF compatible, ignore it
                        AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg;
                        if ((adobe.transform == JPEG.ADOBE_UNKNOWN) 
                            || (adobe.transform == JPEG.ADOBE_YCC)) {
                            adobe.write(ios);
                        } else {
                            writer.warningOccurred
                         (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
                        }
                    } else {
                        seg.write(ios);
                    }
                } // Otherwise ignore it, as requested
            } else {
                seg.write(ios);
            }
        }