FileDocCategorySizeDatePackage
JFIFMarkerSegment.javaAPI DocJava SE 5 API58832Fri Aug 26 14:54:42 BST 2005com.sun.imageio.plugins.jpeg

JFIFMarkerSegment

public class JFIFMarkerSegment extends MarkerSegment
A JFIF (JPEG File Interchange Format) APP0 (Application-Specific) marker segment. Inner classes are included for JFXX extension marker segments, for different varieties of thumbnails, and for ICC Profile APP2 marker segments. Any of these secondary types that occur are kept as members of a single JFIFMarkerSegment object.

Fields Summary
int
majorVersion
int
minorVersion
int
resUnits
int
Xdensity
int
Ydensity
int
thumbWidth
int
thumbHeight
JFIFThumbRGB
thumb
ArrayList
extSegments
ICCMarkerSegment
iccSegment
private static final int
THUMB_JPEG
private static final int
THUMB_PALETTE
private static final int
THUMB_UNASSIGNED
private static final int
THUMB_RGB
private static final int
DATA_SIZE
private static final int
ID_SIZE
private final int
MAX_THUMB_WIDTH
private final int
MAX_THUMB_HEIGHT
private final boolean
debug
private boolean
inICC
Set to true when reading the chunks of an ICC profile. All chunks are consolidated to create a single "segment" containing all the chunks. This flag is a state variable identifying whether to construct a new segment or append to an old one.
private ICCMarkerSegment
tempICCSegment
A placeholder for an ICC profile marker segment under construction. The segment is not added to the list until all chunks have been read.
Constructors Summary
JFIFMarkerSegment()
Default constructor. Used to create a default JFIF header



                   
     
        super(JPEG.APP0);
        majorVersion = 1;
        minorVersion = 2;
        resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;
        Xdensity = 1;
        Ydensity = 1;
        thumbWidth = 0;
        thumbHeight = 0;
    
JFIFMarkerSegment(JPEGBuffer buffer)
Constructs a JFIF header by reading from a stream wrapped in a JPEGBuffer.

        super(buffer);
        buffer.bufPtr += ID_SIZE;  // skip the id, we already checked it
            
        majorVersion = buffer.buf[buffer.bufPtr++];
        minorVersion = buffer.buf[buffer.bufPtr++];
        resUnits = buffer.buf[buffer.bufPtr++];
        Xdensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
        Xdensity |= buffer.buf[buffer.bufPtr++] & 0xff;
        Ydensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
        Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff;
        thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff;
        thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff;
        buffer.bufAvail -= DATA_SIZE;
        if (thumbWidth > 0) {
            thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight);
        }
    
JFIFMarkerSegment(Node node)
Constructs a JFIF header from a DOM Node.

        this();
        updateFromNativeNode(node, true);
    
Methods Summary
voidaddICC(com.sun.imageio.plugins.jpeg.JPEGBuffer buffer)
Adds an ICC Profile APP2 segment from the stream wrapped in the JPEGBuffer.

        if (inICC == false) {
            if (iccSegment != null) {
                throw new IIOException
                    ("> 1 ICC APP2 Marker Segment not supported");
            }
            tempICCSegment = new ICCMarkerSegment(buffer);
            if (inICC == false) { // Just one chunk
                iccSegment = tempICCSegment;
                tempICCSegment = null;
            }                
        } else {
            if (tempICCSegment.addData(buffer) == true) {
                iccSegment = tempICCSegment;
                tempICCSegment = null;
            }
        }
    
voidaddICC(java.awt.color.ICC_ColorSpace cs)
Add an ICC Profile APP2 segment by constructing it from the given ICC_ColorSpace object.

        if (iccSegment != null) {
            throw new IIOException
                ("> 1 ICC APP2 Marker Segment not supported");
        }
        iccSegment = new ICCMarkerSegment(cs);
    
voidaddJFXX(com.sun.imageio.plugins.jpeg.JPEGBuffer buffer, com.sun.imageio.plugins.jpeg.JPEGImageReader reader)
Add an JFXX extension marker segment from the stream wrapped in the JPEGBuffer to the list of extension segments.

        extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader));
    
protected java.lang.Objectclone()
Returns a deep-copy clone of this object.

        JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone();
        if (!extSegments.isEmpty()) { // Clone the list with a deep copy
            newGuy.extSegments = new ArrayList();
            for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
                JFIFExtensionMarkerSegment jfxx = 
                    (JFIFExtensionMarkerSegment) iter.next();
                newGuy.extSegments.add(jfxx.clone());
            }
        }
        if (iccSegment != null) {
            newGuy.iccSegment = (ICCMarkerSegment) iccSegment.clone();
        }
        return newGuy;
    
private static java.awt.image.BufferedImageexpandGrayThumb(java.awt.image.BufferedImage thumb)
Return an RGB image that is the expansion of the given grayscale image.

        BufferedImage ret = new BufferedImage(thumb.getWidth(), 
                                              thumb.getHeight(),
                                              BufferedImage.TYPE_INT_RGB);
        Graphics g = ret.getGraphics();
        g.drawImage(thumb, 0, 0, null);
        return ret;
    
javax.imageio.metadata.IIOMetadataNodegetNativeNode()
Returns a tree of DOM nodes representing this object and any subordinate JFXX extension or ICC Profile segments.

        IIOMetadataNode node = new IIOMetadataNode("app0JFIF");
        node.setAttribute("majorVersion", Integer.toString(majorVersion));
        node.setAttribute("minorVersion", Integer.toString(minorVersion));
        node.setAttribute("resUnits", Integer.toString(resUnits));
        node.setAttribute("Xdensity", Integer.toString(Xdensity));
        node.setAttribute("Ydensity", Integer.toString(Ydensity));
        node.setAttribute("thumbWidth", Integer.toString(thumbWidth));
        node.setAttribute("thumbHeight", Integer.toString(thumbHeight));
        if (!extSegments.isEmpty()) {
            IIOMetadataNode JFXXnode = new IIOMetadataNode("JFXX");
            node.appendChild(JFXXnode);
            for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
                JFIFExtensionMarkerSegment seg = 
                    (JFIFExtensionMarkerSegment) iter.next();
                JFXXnode.appendChild(seg.getNativeNode());
            }
        }
        if (iccSegment != null) {
            node.appendChild(iccSegment.getNativeNode());
        }

        return node;
    
java.awt.image.BufferedImagegetThumbnail(javax.imageio.stream.ImageInputStream iis, int index, com.sun.imageio.plugins.jpeg.JPEGImageReader reader)

        reader.thumbnailStarted(index);
        BufferedImage ret = null;
        if ((thumb != null) && (index == 0)) {
                ret = thumb.getThumbnail(iis, reader);
        } else {
            if (thumb != null) {
                index--;
            }
            JFIFExtensionMarkerSegment jfxx = 
                (JFIFExtensionMarkerSegment) extSegments.get(index);
            ret = jfxx.thumb.getThumbnail(iis, reader);
        }
        reader.thumbnailComplete();
        return ret;
    
intgetThumbnailHeight(int index)

        if (thumb != null) {
            if (index == 0) {
                return thumb.getHeight();
            }
            index--;
        }
        JFIFExtensionMarkerSegment jfxx = 
            (JFIFExtensionMarkerSegment) extSegments.get(index);
        return jfxx.thumb.getHeight();
    
intgetThumbnailWidth(int index)

        if (thumb != null) {
            if (index == 0) {
                return thumb.getWidth();
            }
            index--;
        }
        JFIFExtensionMarkerSegment jfxx = 
            (JFIFExtensionMarkerSegment) extSegments.get(index);
        return jfxx.thumb.getWidth();
    
voidprint()
Prints out the contents of this object to System.out for debugging.

        printTag("JFIF");
        System.out.print("Version ");
        System.out.print(majorVersion);
        System.out.println(".0"
                           + Integer.toString(minorVersion));
        System.out.print("Resolution units: ");
        System.out.println(resUnits);
        System.out.print("X density: ");
        System.out.println(Xdensity);
        System.out.print("Y density: ");
        System.out.println(Ydensity);
        System.out.print("Thumbnail Width: ");
        System.out.println(thumbWidth);
        System.out.print("Thumbnail Height: ");
        System.out.println(thumbHeight);
        if (!extSegments.isEmpty()) {
            for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
                JFIFExtensionMarkerSegment extSegment = 
                    (JFIFExtensionMarkerSegment) iter.next();
                extSegment.print();
            }
        }
        if (iccSegment != null) {
            iccSegment.print();
        }
    
voidupdateFromNativeNode(org.w3c.dom.Node node, boolean fromScratch)
Updates the data in this object from the given DOM Node tree. If fromScratch is true, this object is being constructed. Otherwise an existing object is being modified. Throws an IIOInvalidTreeException if the tree is invalid in any way.

        // none of the attributes are required
        NamedNodeMap attrs = node.getAttributes();
        if (attrs.getLength() > 0) {
            int value = getAttributeValue(node, attrs, "majorVersion", 
                                          0, 255, false);
            majorVersion = (value != -1) ? value : majorVersion;
            value = getAttributeValue(node, attrs, "minorVersion", 
                                      0, 255, false);
            minorVersion = (value != -1) ? value : minorVersion;
            value = getAttributeValue(node, attrs, "resUnits", 0, 2, false);
            resUnits = (value != -1) ? value : resUnits;
            value = getAttributeValue(node, attrs, "Xdensity", 1, 65535, false);
            Xdensity = (value != -1) ? value : Xdensity;
            value = getAttributeValue(node, attrs, "Ydensity", 1, 65535, false);
            Ydensity = (value != -1) ? value : Ydensity;
            value = getAttributeValue(node, attrs, "thumbWidth", 0, 255, false);
            thumbWidth = (value != -1) ? value : thumbWidth;
            value = getAttributeValue(node, attrs, "thumbHeight", 0, 255, false);
            thumbHeight = (value != -1) ? value : thumbHeight;
        }
        if (node.hasChildNodes()) {
            NodeList children = node.getChildNodes();
            int count = children.getLength();
            if (count > 2) {
                throw new IIOInvalidTreeException
                    ("app0JFIF node cannot have > 2 children", node);
            }
            for (int i = 0; i < count; i++) {
                Node child = children.item(i);
                String name = child.getNodeName();
                if (name.equals("JFXX")) {
                    if ((!extSegments.isEmpty()) && fromScratch) {
                        throw new IIOInvalidTreeException
                            ("app0JFIF node cannot have > 1 JFXX node", node);
                    }
                    NodeList exts = child.getChildNodes();
                    int extCount = exts.getLength();
                    for (int j = 0; j < extCount; j++) {
                        Node ext = exts.item(j);
                        extSegments.add(new JFIFExtensionMarkerSegment(ext));
                    }
                }
                if (name.equals("app2ICC")) {
                    if ((iccSegment != null) && fromScratch) {
                        throw new IIOInvalidTreeException
                            ("> 1 ICC APP2 Marker Segment not supported", node);
                    }
                    iccSegment = new ICCMarkerSegment(child);
                }
            }
        }
    
voidwrite(javax.imageio.stream.ImageOutputStream ios, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)
Writes the data for this segment to the stream in valid JPEG format. Assumes that there will be no thumbnail.

        // No thumbnail
        write(ios, null, writer);
    
voidwrite(javax.imageio.stream.ImageOutputStream ios, java.awt.image.BufferedImage thumb, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)
Writes the data for this segment to the stream in valid JPEG format. The length written takes the thumbnail width and height into account. If necessary, the thumbnail is clipped to 255 x 255 and a warning is sent to the writer argument. Progress updates are sent to the writer argument.

        int thumbWidth = 0;
        int thumbHeight = 0;
        int thumbLength = 0;
        int [] thumbData = null;
        if (thumb != null) {
            // Clip if necessary and get the data in thumbData
            thumbWidth = thumb.getWidth();
            thumbHeight = thumb.getHeight();
            if ((thumbWidth > MAX_THUMB_WIDTH) 
                || (thumbHeight > MAX_THUMB_HEIGHT)) {
                writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED);
            }
            thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH);
            thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT);
            thumbData = thumb.getRaster().getPixels(0, 0, 
                                                    thumbWidth, thumbHeight, 
                                                    (int []) null);
            thumbLength = thumbData.length;
        }
        length = DATA_SIZE + LENGTH_SIZE + thumbLength;
        writeTag(ios);
        byte [] id = {0x4A, 0x46, 0x49, 0x46, 0x00};
        ios.write(id);
        ios.write(majorVersion);
        ios.write(minorVersion);
        ios.write(resUnits);
        write2bytes(ios, Xdensity);
        write2bytes(ios, Ydensity);
        ios.write(thumbWidth);
        ios.write(thumbHeight);
        if (thumbData != null) {
            writer.thumbnailStarted(0);
            writeThumbnailData(ios, thumbData, writer);
            writer.thumbnailComplete();
        }
    
static voidwriteDefaultJFIF(javax.imageio.stream.ImageOutputStream ios, java.util.List thumbnails, java.awt.color.ICC_Profile iccProfile, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)
Writes out a default JFIF marker segment to the given output stream. If thumbnails is not null, writes out the set of thumbnail images as JFXX marker segments, or incorporated into the JFIF segment if appropriate. If iccProfile is not null, writes out the profile after the JFIF segment using as many APP2 marker segments as necessary.


        JFIFMarkerSegment jfif = new JFIFMarkerSegment();
        jfif.writeWithThumbs(ios, thumbnails, writer);
        if (iccProfile != null) {
            writeICC(iccProfile, ios);
        }
    
static voidwriteICC(java.awt.color.ICC_Profile profile, javax.imageio.stream.ImageOutputStream ios)
Write out the given profile to the stream, embedded in the necessary number of APP2 segments, per the ICC spec. This is the only mechanism for writing an ICC profile to a stream.

        int LENGTH_LENGTH = 2;
        final String ID = "ICC_PROFILE";
        int ID_LENGTH = ID.length()+1; // spec says it's null-terminated
        int COUNTS_LENGTH = 2;
        int MAX_ICC_CHUNK_SIZE = 
            65535 - LENGTH_LENGTH - ID_LENGTH - COUNTS_LENGTH;

        byte [] data = profile.getData();
        int numChunks = data.length / MAX_ICC_CHUNK_SIZE;
        if ((data.length % MAX_ICC_CHUNK_SIZE) != 0) {
            numChunks++;
        }
        int chunkNum = 1;
        int offset = 0;
        for (int i = 0; i < numChunks; i++) {
            int dataLength = Math.min(data.length-offset, MAX_ICC_CHUNK_SIZE);
            int segLength = dataLength+COUNTS_LENGTH+ID_LENGTH+LENGTH_LENGTH;
            ios.write(0xff);
            ios.write(JPEG.APP2);
            MarkerSegment.write2bytes(ios, segLength);
            byte [] id = ID.getBytes("US-ASCII");
            ios.write(id);
            ios.write(0); // Null-terminate the string
            ios.write(chunkNum++);
            ios.write(numChunks);
            ios.write(data, offset, dataLength);
            offset += dataLength;
        }
    
private voidwriteJFXXSegment(int index, java.awt.image.BufferedImage thumbnail, javax.imageio.stream.ImageOutputStream ios, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)
Writes out a new JFXX extension segment, without saving it.

        JFIFExtensionMarkerSegment jfxx = null;
        try {
             jfxx = new JFIFExtensionMarkerSegment(thumbnail);
        } catch (IllegalThumbException e) {
            writer.warningOccurred
                (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL);
            return;
        }
        writer.thumbnailStarted(index);
        jfxx.write(ios, writer);
        writer.thumbnailComplete();
    
private voidwriteThumb(javax.imageio.stream.ImageOutputStream ios, java.awt.image.BufferedImage thumb, com.sun.imageio.plugins.jpeg.JFIFMarkerSegment$JFIFExtensionMarkerSegment jfxx, int index, boolean onlyOne, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)

        ColorModel cm = thumb.getColorModel();
        ColorSpace cs = cm.getColorSpace();
        
        if (cm instanceof IndexColorModel) {
            // We never write a palette image into the header
            // So if it's the only one, we need to write the header first
            if (onlyOne) { 
                write(ios, writer); 
            }
            if ((jfxx == null) 
                || (jfxx.code == THUMB_PALETTE)) {
                writeJFXXSegment(index, thumb, ios, writer); // default
            } else {
                // Expand to RGB
                BufferedImage thumbRGB = 
                    ((IndexColorModel) cm).convertToIntDiscrete
                    (thumb.getRaster(), false);
                jfxx.setThumbnail(thumbRGB);
                writer.thumbnailStarted(index);
                jfxx.write(ios, writer);  // Handles clipping if needed
                writer.thumbnailComplete();
            }
        } else if (cs.getType() == ColorSpace.TYPE_RGB) {
            if (jfxx == null) {
                if (onlyOne) {
                    write(ios, thumb, writer); // As part of the header
                } else {
                    writeJFXXSegment(index, thumb, ios, writer); // default
                }
            } else {
                // If this is the only one, write the header first
                if (onlyOne) {
                    write(ios, writer); 
                }
                if (jfxx.code == THUMB_PALETTE) {
                    writeJFXXSegment(index, thumb, ios, writer); // default
                    writer.warningOccurred
                        (JPEGImageWriter.WARNING_NO_RGB_THUMB_AS_INDEXED);
                } else {
                    jfxx.setThumbnail(thumb);
                    writer.thumbnailStarted(index);
                    jfxx.write(ios, writer);  // Handles clipping if needed
                    writer.thumbnailComplete();
                }
            }
        } else if (cs.getType() == ColorSpace.TYPE_GRAY) {
            if (jfxx == null) {
                if (onlyOne) {
                    BufferedImage thumbRGB = expandGrayThumb(thumb);
                    write(ios, thumbRGB, writer); // As part of the header
                } else {
                    writeJFXXSegment(index, thumb, ios, writer); // default
                }
            } else {
                // If this is the only one, write the header first
                if (onlyOne) {
                    write(ios, writer); 
                }
                if (jfxx.code == THUMB_RGB) {
                    BufferedImage thumbRGB = expandGrayThumb(thumb);
                    writeJFXXSegment(index, thumbRGB, ios, writer);
                } else if (jfxx.code == THUMB_JPEG) {
                    jfxx.setThumbnail(thumb);
                    writer.thumbnailStarted(index);
                    jfxx.write(ios, writer);  // Handles clipping if needed
                    writer.thumbnailComplete();
                } else if (jfxx.code == THUMB_PALETTE) {
                    writeJFXXSegment(index, thumb, ios, writer); // default
                    writer.warningOccurred
                        (JPEGImageWriter.WARNING_NO_GRAY_THUMB_AS_INDEXED);
                }
            }
        } else {
            writer.warningOccurred
                (JPEGImageWriter.WARNING_ILLEGAL_THUMBNAIL);
        }
    
voidwriteThumbnailData(javax.imageio.stream.ImageOutputStream ios, int[] thumbData, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)

        int progInterval = thumbData.length / 20;  // approx. every 5%
        if (progInterval == 0) {
            progInterval = 1;
        }
        for (int i = 0; i < thumbData.length; i++) {
            ios.write(thumbData[i]);
            if ((i > progInterval) && (i % progInterval == 0)) {
                writer.thumbnailProgress
                    (((float) i * 100) / ((float) thumbData.length)); 
            }
        }
    
voidwriteWithThumbs(javax.imageio.stream.ImageOutputStream ios, java.util.List thumbnails, com.sun.imageio.plugins.jpeg.JPEGImageWriter writer)
Write out this JFIF Marker Segment, including a thumbnail or appending a series of JFXX Marker Segments, as appropriate. Warnings and progress reports are sent to the writer argument. The list of thumbnails is matched to the list of JFXX extension segments, if any, in order to determine how to encode the thumbnails. If there are more thumbnails than metadata segments, default encoding is used for the extra thumbnails.

        if (thumbnails != null) {
            JFIFExtensionMarkerSegment jfxx = null;
            if (thumbnails.size() == 1) {
                if (!extSegments.isEmpty()) {
                    jfxx = (JFIFExtensionMarkerSegment) extSegments.get(0);
                }
                writeThumb(ios, 
                           (BufferedImage) thumbnails.get(0),
                           jfxx,
                           0,
                           true,
                           writer);
            } else {
                // All others write as separate JFXX segments
                write(ios, writer);  // Just the header without any thumbnail
                for (int i = 0; i < thumbnails.size(); i++) {
                    jfxx = null;
                    if (i < extSegments.size()) {
                        jfxx = (JFIFExtensionMarkerSegment) extSegments.get(i);
                    }
                    writeThumb(ios,
                               (BufferedImage) thumbnails.get(i),
                               jfxx,
                               i,
                               false,
                               writer);
                }
            }
        } else {  // No thumbnails
            write(ios, writer);
        }