src/share/classes/com/sun/imageio/plugins/jpeg/JFIFMarkerSegment.java

Print this page
rev 9230 : imported patch 8033716


  59 import org.w3c.dom.Node;
  60 import org.w3c.dom.NodeList;
  61 import org.w3c.dom.NamedNodeMap;
  62 
  63 /**
  64  * A JFIF (JPEG File Interchange Format) APP0 (Application-Specific)
  65  * marker segment.  Inner classes are included for JFXX extension
  66  * marker segments, for different varieties of thumbnails, and for
  67  * ICC Profile APP2 marker segments.  Any of these secondary types
  68  * that occur are kept as members of a single JFIFMarkerSegment object.
  69  */
  70 class JFIFMarkerSegment extends MarkerSegment {
  71     int majorVersion;
  72     int minorVersion;
  73     int resUnits;
  74     int Xdensity;
  75     int Ydensity;
  76     int thumbWidth;
  77     int thumbHeight;
  78     JFIFThumbRGB thumb = null;  // If present
  79     ArrayList extSegments = new ArrayList();
  80     ICCMarkerSegment iccSegment = null; // optional ICC
  81     private static final int THUMB_JPEG = 0x10;
  82     private static final int THUMB_PALETTE = 0x11;
  83     private static final int THUMB_UNASSIGNED = 0x12;
  84     private static final int THUMB_RGB = 0x13;
  85     private static final int DATA_SIZE = 14;
  86     private static final int ID_SIZE = 5;
  87     private final int MAX_THUMB_WIDTH = 255;
  88     private final int MAX_THUMB_HEIGHT = 255;
  89 
  90     private final boolean debug = false;
  91 
  92     /**
  93      * Set to <code>true</code> when reading the chunks of an
  94      * ICC profile.  All chunks are consolidated to create a single
  95      * "segment" containing all the chunks.  This flag is a state
  96      * variable identifying whether to construct a new segment or
  97      * append to an old one.
  98      */
  99     private boolean inICC = false;


 137         Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff;
 138         thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff;
 139         thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff;
 140         buffer.bufAvail -= DATA_SIZE;
 141         if (thumbWidth > 0) {
 142             thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight);
 143         }
 144     }
 145 
 146     /**
 147      * Constructs a JFIF header from a DOM Node.
 148      */
 149     JFIFMarkerSegment(Node node) throws IIOInvalidTreeException {
 150         this();
 151         updateFromNativeNode(node, true);
 152     }
 153 
 154     /**
 155      * Returns a deep-copy clone of this object.
 156      */
 157     protected Object clone() {
 158         JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone();
 159         if (!extSegments.isEmpty()) { // Clone the list with a deep copy
 160             newGuy.extSegments = new ArrayList();
 161             for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
 162                 JFIFExtensionMarkerSegment jfxx =
 163                     (JFIFExtensionMarkerSegment) iter.next();
 164                 newGuy.extSegments.add(jfxx.clone());
 165             }
 166         }
 167         if (iccSegment != null) {
 168             newGuy.iccSegment = (ICCMarkerSegment) iccSegment.clone();
 169         }
 170         return newGuy;
 171     }
 172 
 173     /**
 174      * Add an JFXX extension marker segment from the stream wrapped
 175      * in the JPEGBuffer to the list of extension segments.
 176      */
 177     void addJFXX(JPEGBuffer buffer, JPEGImageReader reader)
 178         throws IOException {
 179         extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader));
 180     }
 181 
 182     /**
 183      * Adds an ICC Profile APP2 segment from the stream wrapped
 184      * in the JPEGBuffer.
 185      */
 186     void addICC(JPEGBuffer buffer) throws IOException {
 187         if (inICC == false) {
 188             if (iccSegment != null) {


 213         }
 214         iccSegment = new ICCMarkerSegment(cs);
 215     }
 216 
 217     /**
 218      * Returns a tree of DOM nodes representing this object and any
 219      * subordinate JFXX extension or ICC Profile segments.
 220      */
 221     IIOMetadataNode getNativeNode() {
 222         IIOMetadataNode node = new IIOMetadataNode("app0JFIF");
 223         node.setAttribute("majorVersion", Integer.toString(majorVersion));
 224         node.setAttribute("minorVersion", Integer.toString(minorVersion));
 225         node.setAttribute("resUnits", Integer.toString(resUnits));
 226         node.setAttribute("Xdensity", Integer.toString(Xdensity));
 227         node.setAttribute("Ydensity", Integer.toString(Ydensity));
 228         node.setAttribute("thumbWidth", Integer.toString(thumbWidth));
 229         node.setAttribute("thumbHeight", Integer.toString(thumbHeight));
 230         if (!extSegments.isEmpty()) {
 231             IIOMetadataNode JFXXnode = new IIOMetadataNode("JFXX");
 232             node.appendChild(JFXXnode);
 233             for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
 234                 JFIFExtensionMarkerSegment seg =
 235                     (JFIFExtensionMarkerSegment) iter.next();
 236                 JFXXnode.appendChild(seg.getNativeNode());
 237             }
 238         }
 239         if (iccSegment != null) {
 240             node.appendChild(iccSegment.getNativeNode());
 241         }
 242 
 243         return node;
 244     }
 245 
 246     /**
 247      * Updates the data in this object from the given DOM Node tree.
 248      * If fromScratch is true, this object is being constructed.
 249      * Otherwise an existing object is being modified.
 250      * Throws an IIOInvalidTreeException if the tree is invalid in
 251      * any way.
 252      */
 253     void updateFromNativeNode(Node node, boolean fromScratch)
 254         throws IIOInvalidTreeException {
 255         // none of the attributes are required


 295                     }
 296                 }
 297                 if (name.equals("app2ICC")) {
 298                     if ((iccSegment != null) && fromScratch) {
 299                         throw new IIOInvalidTreeException
 300                             ("> 1 ICC APP2 Marker Segment not supported", node);
 301                     }
 302                     iccSegment = new ICCMarkerSegment(child);
 303                 }
 304             }
 305         }
 306     }
 307 
 308     int getThumbnailWidth(int index) {
 309         if (thumb != null) {
 310             if (index == 0) {
 311                 return thumb.getWidth();
 312             }
 313             index--;
 314         }
 315         JFIFExtensionMarkerSegment jfxx =
 316             (JFIFExtensionMarkerSegment) extSegments.get(index);
 317         return jfxx.thumb.getWidth();
 318     }
 319 
 320     int getThumbnailHeight(int index) {
 321         if (thumb != null) {
 322             if (index == 0) {
 323                 return thumb.getHeight();
 324             }
 325             index--;
 326         }
 327         JFIFExtensionMarkerSegment jfxx =
 328             (JFIFExtensionMarkerSegment) extSegments.get(index);
 329         return jfxx.thumb.getHeight();
 330     }
 331 
 332     BufferedImage getThumbnail(ImageInputStream iis,
 333                                int index,
 334                                JPEGImageReader reader) throws IOException {
 335         reader.thumbnailStarted(index);
 336         BufferedImage ret = null;
 337         if ((thumb != null) && (index == 0)) {
 338                 ret = thumb.getThumbnail(iis, reader);
 339         } else {
 340             if (thumb != null) {
 341                 index--;
 342             }
 343             JFIFExtensionMarkerSegment jfxx =
 344                 (JFIFExtensionMarkerSegment) extSegments.get(index);
 345             ret = jfxx.thumb.getThumbnail(iis, reader);
 346         }
 347         reader.thumbnailComplete();
 348         return ret;
 349     }
 350 
 351 
 352     /**
 353      * Writes the data for this segment to the stream in
 354      * valid JPEG format.  Assumes that there will be no thumbnail.
 355      */
 356     void write(ImageOutputStream ios,
 357                JPEGImageWriter writer) throws IOException {
 358         // No thumbnail
 359         write(ios, null, writer);
 360     }
 361 
 362     /**
 363      * Writes the data for this segment to the stream in
 364      * valid JPEG format.  The length written takes the thumbnail


 419         }
 420         for (int i = 0; i < thumbData.length; i++) {
 421             ios.write(thumbData[i]);
 422             if ((i > progInterval) && (i % progInterval == 0)) {
 423                 writer.thumbnailProgress
 424                     (((float) i * 100) / ((float) thumbData.length));
 425             }
 426         }
 427     }
 428 
 429     /**
 430      * Write out this JFIF Marker Segment, including a thumbnail or
 431      * appending a series of JFXX Marker Segments, as appropriate.
 432      * Warnings and progress reports are sent to the writer argument.
 433      * The list of thumbnails is matched to the list of JFXX extension
 434      * segments, if any, in order to determine how to encode the
 435      * thumbnails.  If there are more thumbnails than metadata segments,
 436      * default encoding is used for the extra thumbnails.
 437      */
 438     void writeWithThumbs(ImageOutputStream ios,
 439                          List thumbnails,
 440                          JPEGImageWriter writer) throws IOException {
 441         if (thumbnails != null) {
 442             JFIFExtensionMarkerSegment jfxx = null;
 443             if (thumbnails.size() == 1) {
 444                 if (!extSegments.isEmpty()) {
 445                     jfxx = (JFIFExtensionMarkerSegment) extSegments.get(0);
 446                 }
 447                 writeThumb(ios,
 448                            (BufferedImage) thumbnails.get(0),
 449                            jfxx,
 450                            0,
 451                            true,
 452                            writer);
 453             } else {
 454                 // All others write as separate JFXX segments
 455                 write(ios, writer);  // Just the header without any thumbnail
 456                 for (int i = 0; i < thumbnails.size(); i++) {
 457                     jfxx = null;
 458                     if (i < extSegments.size()) {
 459                         jfxx = (JFIFExtensionMarkerSegment) extSegments.get(i);
 460                     }
 461                     writeThumb(ios,
 462                                (BufferedImage) thumbnails.get(i),
 463                                jfxx,
 464                                i,
 465                                false,
 466                                writer);
 467                 }
 468             }
 469         } else {  // No thumbnails
 470             write(ios, writer);
 471         }
 472 
 473     }
 474 
 475     private void writeThumb(ImageOutputStream ios,
 476                             BufferedImage thumb,
 477                             JFIFExtensionMarkerSegment jfxx,
 478                             int index,
 479                             boolean onlyOne,


 588      */
 589     private static BufferedImage expandGrayThumb(BufferedImage thumb) {
 590         BufferedImage ret = new BufferedImage(thumb.getWidth(),
 591                                               thumb.getHeight(),
 592                                               BufferedImage.TYPE_INT_RGB);
 593         Graphics g = ret.getGraphics();
 594         g.drawImage(thumb, 0, 0, null);
 595         return ret;
 596     }
 597 
 598     /**
 599      * Writes out a default JFIF marker segment to the given
 600      * output stream.  If <code>thumbnails</code> is not <code>null</code>,
 601      * writes out the set of thumbnail images as JFXX marker segments, or
 602      * incorporated into the JFIF segment if appropriate.
 603      * If <code>iccProfile</code> is not <code>null</code>,
 604      * writes out the profile after the JFIF segment using as many APP2
 605      * marker segments as necessary.
 606      */
 607     static void writeDefaultJFIF(ImageOutputStream ios,
 608                                  List thumbnails,
 609                                  ICC_Profile iccProfile,
 610                                  JPEGImageWriter writer)
 611         throws IOException {
 612 
 613         JFIFMarkerSegment jfif = new JFIFMarkerSegment();
 614         jfif.writeWithThumbs(ios, thumbnails, writer);
 615         if (iccProfile != null) {
 616             writeICC(iccProfile, ios);
 617         }
 618     }
 619 
 620     /**
 621      * Prints out the contents of this object to System.out for debugging.
 622      */
 623     void print() {
 624         printTag("JFIF");
 625         System.out.print("Version ");
 626         System.out.print(majorVersion);
 627         System.out.println(".0"
 628                            + Integer.toString(minorVersion));
 629         System.out.print("Resolution units: ");
 630         System.out.println(resUnits);
 631         System.out.print("X density: ");
 632         System.out.println(Xdensity);
 633         System.out.print("Y density: ");
 634         System.out.println(Ydensity);
 635         System.out.print("Thumbnail Width: ");
 636         System.out.println(thumbWidth);
 637         System.out.print("Thumbnail Height: ");
 638         System.out.println(thumbHeight);
 639         if (!extSegments.isEmpty()) {
 640             for (Iterator iter = extSegments.iterator(); iter.hasNext();) {
 641                 JFIFExtensionMarkerSegment extSegment =
 642                     (JFIFExtensionMarkerSegment) iter.next();
 643                 extSegment.print();
 644             }
 645         }
 646         if (iccSegment != null) {
 647             iccSegment.print();
 648         }
 649     }
 650 
 651     /**
 652      * A JFIF extension APP0 marker segment.
 653      */
 654     class JFIFExtensionMarkerSegment extends MarkerSegment {
 655         int code;
 656         JFIFThumb thumb;
 657         private static final int DATA_SIZE = 6;
 658         private static final int ID_SIZE = 5;
 659 
 660         JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader)
 661             throws IOException {
 662 


 751 
 752         void setThumbnail(BufferedImage thumbnail) {
 753             try {
 754                 switch (code) {
 755                 case THUMB_PALETTE:
 756                     thumb = new JFIFThumbPalette(thumbnail);
 757                     break;
 758                 case THUMB_RGB:
 759                     thumb = new JFIFThumbRGB(thumbnail);
 760                     break;
 761                 case THUMB_JPEG:
 762                     thumb = new JFIFThumbJPEG(thumbnail);
 763                     break;
 764                 }
 765             } catch (IllegalThumbException e) {
 766                 // Should never happen
 767                 throw new InternalError("Illegal thumb in setThumbnail!", e);
 768             }
 769         }
 770 
 771         protected Object clone() {
 772             JFIFExtensionMarkerSegment newGuy =
 773                 (JFIFExtensionMarkerSegment) super.clone();
 774             if (thumb != null) {
 775                 newGuy.thumb = (JFIFThumb) thumb.clone();
 776             }
 777             return newGuy;
 778         }
 779 
 780         IIOMetadataNode getNativeNode() {
 781             IIOMetadataNode node = new IIOMetadataNode("app0JFXX");
 782             node.setAttribute("extensionCode", Integer.toString(code));
 783             node.appendChild(thumb.getNativeNode());
 784             return node;
 785         }
 786 
 787         void write(ImageOutputStream ios,
 788                    JPEGImageWriter writer) throws IOException {
 789             length = LENGTH_SIZE + DATA_SIZE + thumb.getLength();
 790             writeTag(ios);
 791             byte [] id = {0x4A, 0x46, 0x58, 0x58, 0x00};


1356             ios.write(0xff);
1357             ios.write(JPEG.APP2);
1358             MarkerSegment.write2bytes(ios, segLength);
1359             byte [] id = ID.getBytes("US-ASCII");
1360             ios.write(id);
1361             ios.write(0); // Null-terminate the string
1362             ios.write(chunkNum++);
1363             ios.write(numChunks);
1364             ios.write(data, offset, dataLength);
1365             offset += dataLength;
1366         }
1367     }
1368 
1369     /**
1370      * An APP2 marker segment containing an ICC profile.  In the stream
1371      * a profile larger than 64K is broken up into a series of chunks.
1372      * This inner class represents the complete profile as a single object,
1373      * combining chunks as necessary.
1374      */
1375     class ICCMarkerSegment extends MarkerSegment {
1376         ArrayList chunks = null;
1377         byte [] profile = null; // The complete profile when it's fully read
1378                          // May remain null when writing
1379         private static final int ID_SIZE = 12;
1380         int chunksRead;
1381         int numChunks;
1382 
1383         ICCMarkerSegment(ICC_ColorSpace cs) {
1384             super(JPEG.APP2);
1385             chunks = null;
1386             chunksRead = 0;
1387             numChunks = 0;
1388             profile = cs.getProfile().getData();
1389         }
1390 
1391         ICCMarkerSegment(JPEGBuffer buffer) throws IOException {
1392             super(buffer);  // gets whole segment or fills the buffer
1393             if (debug) {
1394                 System.out.println("Creating new ICC segment");
1395             }
1396             buffer.bufPtr += ID_SIZE; // Skip the id


1411                 throw new IIOException
1412                     ("Image format Error; chunk num > num chunks");
1413             }
1414 
1415             // if there are no more chunks, set up the data
1416             if (numChunks == 1) {
1417                 // reduce the stored length by the two chunk numbering bytes
1418                 length -= 2;
1419                 profile = new byte[length];
1420                 buffer.bufPtr += 2;
1421                 buffer.bufAvail-=2;
1422                 buffer.readData(profile);
1423                 inICC = false;
1424             } else {
1425                 // If we store them away, include the chunk numbering bytes
1426                 byte [] profileData = new byte[length];
1427                 // Now reduce the stored length by the
1428                 // two chunk numbering bytes
1429                 length -= 2;
1430                 buffer.readData(profileData);
1431                 chunks = new ArrayList();
1432                 chunks.add(profileData);
1433                 chunksRead = 1;
1434                 inICC = true;
1435             }
1436         }
1437 
1438         ICCMarkerSegment(Node node) throws IIOInvalidTreeException {
1439             super(JPEG.APP2);
1440             if (node instanceof IIOMetadataNode) {
1441                 IIOMetadataNode ourNode = (IIOMetadataNode) node;
1442                 ICC_Profile prof = (ICC_Profile) ourNode.getUserObject();
1443                 if (prof != null) {  // May be null
1444                     profile = prof.getData();
1445                 }
1446             }
1447         }
1448 
1449         protected Object clone () {
1450             ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone();
1451             if (profile != null) {
1452                 newGuy.profile = profile.clone();
1453             }
1454             return newGuy;
1455         }
1456 
1457         boolean addData(JPEGBuffer buffer) throws IOException {
1458             if (debug) {
1459                 System.out.println("Adding to ICC segment");
1460             }
1461             // skip the tag
1462             buffer.bufPtr++;
1463             buffer.bufAvail--;
1464             // Get the length, but not in length
1465             int dataLen = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
1466             dataLen |= buffer.buf[buffer.bufPtr++] & 0xff;
1467             buffer.bufAvail -= 2;
1468             // Don't include length itself
1469             dataLen -= 2;


1501             buffer.readData(profileData);
1502             chunks.add(profileData);
1503             length += dataLen;
1504             chunksRead++;
1505             if (chunksRead < numChunks) {
1506                 inICC = true;
1507             } else {
1508                 if (debug) {
1509                     System.out.println("Completing profile; total length is "
1510                                        + length);
1511                 }
1512                 // create an array for the whole thing
1513                 profile = new byte[length];
1514                 // copy the existing chunks, releasing them
1515                 // Note that they may be out of order
1516 
1517                 int index = 0;
1518                 for (int i = 1; i <= numChunks; i++) {
1519                     boolean foundIt = false;
1520                     for (int chunk = 0; chunk < chunks.size(); chunk++) {
1521                         byte [] chunkData = (byte []) chunks.get(chunk);
1522                         if (chunkData[0] == i) { // Right one
1523                             System.arraycopy(chunkData, 2,
1524                                              profile, index,
1525                                              chunkData.length-2);
1526                             index += chunkData.length-2;
1527                             foundIt = true;
1528                         }
1529                     }
1530                     if (foundIt == false) {
1531                         throw new IIOException
1532                             ("Image Format Error: Missing ICC chunk num " + i);
1533                     }
1534                 }
1535 
1536                 chunks = null;
1537                 chunksRead = 0;
1538                 numChunks = 0;
1539                 inICC = false;
1540                 retval = true;
1541             }




  59 import org.w3c.dom.Node;
  60 import org.w3c.dom.NodeList;
  61 import org.w3c.dom.NamedNodeMap;
  62 
  63 /**
  64  * A JFIF (JPEG File Interchange Format) APP0 (Application-Specific)
  65  * marker segment.  Inner classes are included for JFXX extension
  66  * marker segments, for different varieties of thumbnails, and for
  67  * ICC Profile APP2 marker segments.  Any of these secondary types
  68  * that occur are kept as members of a single JFIFMarkerSegment object.
  69  */
  70 class JFIFMarkerSegment extends MarkerSegment {
  71     int majorVersion;
  72     int minorVersion;
  73     int resUnits;
  74     int Xdensity;
  75     int Ydensity;
  76     int thumbWidth;
  77     int thumbHeight;
  78     JFIFThumbRGB thumb = null;  // If present
  79     ArrayList<JFIFExtensionMarkerSegment> extSegments = new ArrayList<>();
  80     ICCMarkerSegment iccSegment = null; // optional ICC
  81     private static final int THUMB_JPEG = 0x10;
  82     private static final int THUMB_PALETTE = 0x11;
  83     private static final int THUMB_UNASSIGNED = 0x12;
  84     private static final int THUMB_RGB = 0x13;
  85     private static final int DATA_SIZE = 14;
  86     private static final int ID_SIZE = 5;
  87     private final int MAX_THUMB_WIDTH = 255;
  88     private final int MAX_THUMB_HEIGHT = 255;
  89 
  90     private final boolean debug = false;
  91 
  92     /**
  93      * Set to <code>true</code> when reading the chunks of an
  94      * ICC profile.  All chunks are consolidated to create a single
  95      * "segment" containing all the chunks.  This flag is a state
  96      * variable identifying whether to construct a new segment or
  97      * append to an old one.
  98      */
  99     private boolean inICC = false;


 137         Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff;
 138         thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff;
 139         thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff;
 140         buffer.bufAvail -= DATA_SIZE;
 141         if (thumbWidth > 0) {
 142             thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight);
 143         }
 144     }
 145 
 146     /**
 147      * Constructs a JFIF header from a DOM Node.
 148      */
 149     JFIFMarkerSegment(Node node) throws IIOInvalidTreeException {
 150         this();
 151         updateFromNativeNode(node, true);
 152     }
 153 
 154     /**
 155      * Returns a deep-copy clone of this object.
 156      */
 157     protected JFIFMarkerSegment clone() {
 158         JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone();
 159         if (!extSegments.isEmpty()) { // Clone the list with a deep copy
 160             newGuy.extSegments = new ArrayList<>();
 161             for (Iterator<JFIFExtensionMarkerSegment> iter = extSegments.iterator(); iter.hasNext();) {
 162                 JFIFExtensionMarkerSegment jfxx = iter.next();

 163                 newGuy.extSegments.add(jfxx.clone());
 164             }
 165         }
 166         if (iccSegment != null) {
 167             newGuy.iccSegment = iccSegment.clone();
 168         }
 169         return newGuy;
 170     }
 171 
 172     /**
 173      * Add an JFXX extension marker segment from the stream wrapped
 174      * in the JPEGBuffer to the list of extension segments.
 175      */
 176     void addJFXX(JPEGBuffer buffer, JPEGImageReader reader)
 177         throws IOException {
 178         extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader));
 179     }
 180 
 181     /**
 182      * Adds an ICC Profile APP2 segment from the stream wrapped
 183      * in the JPEGBuffer.
 184      */
 185     void addICC(JPEGBuffer buffer) throws IOException {
 186         if (inICC == false) {
 187             if (iccSegment != null) {


 212         }
 213         iccSegment = new ICCMarkerSegment(cs);
 214     }
 215 
 216     /**
 217      * Returns a tree of DOM nodes representing this object and any
 218      * subordinate JFXX extension or ICC Profile segments.
 219      */
 220     IIOMetadataNode getNativeNode() {
 221         IIOMetadataNode node = new IIOMetadataNode("app0JFIF");
 222         node.setAttribute("majorVersion", Integer.toString(majorVersion));
 223         node.setAttribute("minorVersion", Integer.toString(minorVersion));
 224         node.setAttribute("resUnits", Integer.toString(resUnits));
 225         node.setAttribute("Xdensity", Integer.toString(Xdensity));
 226         node.setAttribute("Ydensity", Integer.toString(Ydensity));
 227         node.setAttribute("thumbWidth", Integer.toString(thumbWidth));
 228         node.setAttribute("thumbHeight", Integer.toString(thumbHeight));
 229         if (!extSegments.isEmpty()) {
 230             IIOMetadataNode JFXXnode = new IIOMetadataNode("JFXX");
 231             node.appendChild(JFXXnode);
 232             for (Iterator<JFIFExtensionMarkerSegment> iter = extSegments.iterator(); iter.hasNext();) {
 233                 JFIFExtensionMarkerSegment seg = iter.next();

 234                 JFXXnode.appendChild(seg.getNativeNode());
 235             }
 236         }
 237         if (iccSegment != null) {
 238             node.appendChild(iccSegment.getNativeNode());
 239         }
 240 
 241         return node;
 242     }
 243 
 244     /**
 245      * Updates the data in this object from the given DOM Node tree.
 246      * If fromScratch is true, this object is being constructed.
 247      * Otherwise an existing object is being modified.
 248      * Throws an IIOInvalidTreeException if the tree is invalid in
 249      * any way.
 250      */
 251     void updateFromNativeNode(Node node, boolean fromScratch)
 252         throws IIOInvalidTreeException {
 253         // none of the attributes are required


 293                     }
 294                 }
 295                 if (name.equals("app2ICC")) {
 296                     if ((iccSegment != null) && fromScratch) {
 297                         throw new IIOInvalidTreeException
 298                             ("> 1 ICC APP2 Marker Segment not supported", node);
 299                     }
 300                     iccSegment = new ICCMarkerSegment(child);
 301                 }
 302             }
 303         }
 304     }
 305 
 306     int getThumbnailWidth(int index) {
 307         if (thumb != null) {
 308             if (index == 0) {
 309                 return thumb.getWidth();
 310             }
 311             index--;
 312         }
 313         JFIFExtensionMarkerSegment jfxx = extSegments.get(index);

 314         return jfxx.thumb.getWidth();
 315     }
 316 
 317     int getThumbnailHeight(int index) {
 318         if (thumb != null) {
 319             if (index == 0) {
 320                 return thumb.getHeight();
 321             }
 322             index--;
 323         }
 324         JFIFExtensionMarkerSegment jfxx = extSegments.get(index);

 325         return jfxx.thumb.getHeight();
 326     }
 327 
 328     BufferedImage getThumbnail(ImageInputStream iis,
 329                                int index,
 330                                JPEGImageReader reader) throws IOException {
 331         reader.thumbnailStarted(index);
 332         BufferedImage ret = null;
 333         if ((thumb != null) && (index == 0)) {
 334                 ret = thumb.getThumbnail(iis, reader);
 335         } else {
 336             if (thumb != null) {
 337                 index--;
 338             }
 339             JFIFExtensionMarkerSegment jfxx = extSegments.get(index);

 340             ret = jfxx.thumb.getThumbnail(iis, reader);
 341         }
 342         reader.thumbnailComplete();
 343         return ret;
 344     }
 345 
 346 
 347     /**
 348      * Writes the data for this segment to the stream in
 349      * valid JPEG format.  Assumes that there will be no thumbnail.
 350      */
 351     void write(ImageOutputStream ios,
 352                JPEGImageWriter writer) throws IOException {
 353         // No thumbnail
 354         write(ios, null, writer);
 355     }
 356 
 357     /**
 358      * Writes the data for this segment to the stream in
 359      * valid JPEG format.  The length written takes the thumbnail


 414         }
 415         for (int i = 0; i < thumbData.length; i++) {
 416             ios.write(thumbData[i]);
 417             if ((i > progInterval) && (i % progInterval == 0)) {
 418                 writer.thumbnailProgress
 419                     (((float) i * 100) / ((float) thumbData.length));
 420             }
 421         }
 422     }
 423 
 424     /**
 425      * Write out this JFIF Marker Segment, including a thumbnail or
 426      * appending a series of JFXX Marker Segments, as appropriate.
 427      * Warnings and progress reports are sent to the writer argument.
 428      * The list of thumbnails is matched to the list of JFXX extension
 429      * segments, if any, in order to determine how to encode the
 430      * thumbnails.  If there are more thumbnails than metadata segments,
 431      * default encoding is used for the extra thumbnails.
 432      */
 433     void writeWithThumbs(ImageOutputStream ios,
 434                          List<? extends BufferedImage> thumbnails,
 435                          JPEGImageWriter writer) throws IOException {
 436         if (thumbnails != null) {
 437             JFIFExtensionMarkerSegment jfxx = null;
 438             if (thumbnails.size() == 1) {
 439                 if (!extSegments.isEmpty()) {
 440                     jfxx = extSegments.get(0);
 441                 }
 442                 writeThumb(ios,
 443                            (BufferedImage) thumbnails.get(0),
 444                            jfxx,
 445                            0,
 446                            true,
 447                            writer);
 448             } else {
 449                 // All others write as separate JFXX segments
 450                 write(ios, writer);  // Just the header without any thumbnail
 451                 for (int i = 0; i < thumbnails.size(); i++) {
 452                     jfxx = null;
 453                     if (i < extSegments.size()) {
 454                         jfxx = extSegments.get(i);
 455                     }
 456                     writeThumb(ios,
 457                                (BufferedImage) thumbnails.get(i),
 458                                jfxx,
 459                                i,
 460                                false,
 461                                writer);
 462                 }
 463             }
 464         } else {  // No thumbnails
 465             write(ios, writer);
 466         }
 467 
 468     }
 469 
 470     private void writeThumb(ImageOutputStream ios,
 471                             BufferedImage thumb,
 472                             JFIFExtensionMarkerSegment jfxx,
 473                             int index,
 474                             boolean onlyOne,


 583      */
 584     private static BufferedImage expandGrayThumb(BufferedImage thumb) {
 585         BufferedImage ret = new BufferedImage(thumb.getWidth(),
 586                                               thumb.getHeight(),
 587                                               BufferedImage.TYPE_INT_RGB);
 588         Graphics g = ret.getGraphics();
 589         g.drawImage(thumb, 0, 0, null);
 590         return ret;
 591     }
 592 
 593     /**
 594      * Writes out a default JFIF marker segment to the given
 595      * output stream.  If <code>thumbnails</code> is not <code>null</code>,
 596      * writes out the set of thumbnail images as JFXX marker segments, or
 597      * incorporated into the JFIF segment if appropriate.
 598      * If <code>iccProfile</code> is not <code>null</code>,
 599      * writes out the profile after the JFIF segment using as many APP2
 600      * marker segments as necessary.
 601      */
 602     static void writeDefaultJFIF(ImageOutputStream ios,
 603                                  List<? extends BufferedImage> thumbnails,
 604                                  ICC_Profile iccProfile,
 605                                  JPEGImageWriter writer)
 606         throws IOException {
 607 
 608         JFIFMarkerSegment jfif = new JFIFMarkerSegment();
 609         jfif.writeWithThumbs(ios, thumbnails, writer);
 610         if (iccProfile != null) {
 611             writeICC(iccProfile, ios);
 612         }
 613     }
 614 
 615     /**
 616      * Prints out the contents of this object to System.out for debugging.
 617      */
 618     void print() {
 619         printTag("JFIF");
 620         System.out.print("Version ");
 621         System.out.print(majorVersion);
 622         System.out.println(".0"
 623                            + Integer.toString(minorVersion));
 624         System.out.print("Resolution units: ");
 625         System.out.println(resUnits);
 626         System.out.print("X density: ");
 627         System.out.println(Xdensity);
 628         System.out.print("Y density: ");
 629         System.out.println(Ydensity);
 630         System.out.print("Thumbnail Width: ");
 631         System.out.println(thumbWidth);
 632         System.out.print("Thumbnail Height: ");
 633         System.out.println(thumbHeight);
 634         if (!extSegments.isEmpty()) {
 635             for (Iterator<JFIFExtensionMarkerSegment> iter = extSegments.iterator(); iter.hasNext();) {
 636                 JFIFExtensionMarkerSegment extSegment = iter.next();

 637                 extSegment.print();
 638             }
 639         }
 640         if (iccSegment != null) {
 641             iccSegment.print();
 642         }
 643     }
 644 
 645     /**
 646      * A JFIF extension APP0 marker segment.
 647      */
 648     class JFIFExtensionMarkerSegment extends MarkerSegment {
 649         int code;
 650         JFIFThumb thumb;
 651         private static final int DATA_SIZE = 6;
 652         private static final int ID_SIZE = 5;
 653 
 654         JFIFExtensionMarkerSegment(JPEGBuffer buffer, JPEGImageReader reader)
 655             throws IOException {
 656 


 745 
 746         void setThumbnail(BufferedImage thumbnail) {
 747             try {
 748                 switch (code) {
 749                 case THUMB_PALETTE:
 750                     thumb = new JFIFThumbPalette(thumbnail);
 751                     break;
 752                 case THUMB_RGB:
 753                     thumb = new JFIFThumbRGB(thumbnail);
 754                     break;
 755                 case THUMB_JPEG:
 756                     thumb = new JFIFThumbJPEG(thumbnail);
 757                     break;
 758                 }
 759             } catch (IllegalThumbException e) {
 760                 // Should never happen
 761                 throw new InternalError("Illegal thumb in setThumbnail!", e);
 762             }
 763         }
 764 
 765         protected JFIFExtensionMarkerSegment clone() {
 766             JFIFExtensionMarkerSegment newGuy =
 767                 (JFIFExtensionMarkerSegment) super.clone();
 768             if (thumb != null) {
 769                 newGuy.thumb = (JFIFThumb) thumb.clone();
 770             }
 771             return newGuy;
 772         }
 773 
 774         IIOMetadataNode getNativeNode() {
 775             IIOMetadataNode node = new IIOMetadataNode("app0JFXX");
 776             node.setAttribute("extensionCode", Integer.toString(code));
 777             node.appendChild(thumb.getNativeNode());
 778             return node;
 779         }
 780 
 781         void write(ImageOutputStream ios,
 782                    JPEGImageWriter writer) throws IOException {
 783             length = LENGTH_SIZE + DATA_SIZE + thumb.getLength();
 784             writeTag(ios);
 785             byte [] id = {0x4A, 0x46, 0x58, 0x58, 0x00};


1350             ios.write(0xff);
1351             ios.write(JPEG.APP2);
1352             MarkerSegment.write2bytes(ios, segLength);
1353             byte [] id = ID.getBytes("US-ASCII");
1354             ios.write(id);
1355             ios.write(0); // Null-terminate the string
1356             ios.write(chunkNum++);
1357             ios.write(numChunks);
1358             ios.write(data, offset, dataLength);
1359             offset += dataLength;
1360         }
1361     }
1362 
1363     /**
1364      * An APP2 marker segment containing an ICC profile.  In the stream
1365      * a profile larger than 64K is broken up into a series of chunks.
1366      * This inner class represents the complete profile as a single object,
1367      * combining chunks as necessary.
1368      */
1369     class ICCMarkerSegment extends MarkerSegment {
1370         ArrayList<byte[]> chunks = null;
1371         byte [] profile = null; // The complete profile when it's fully read
1372                          // May remain null when writing
1373         private static final int ID_SIZE = 12;
1374         int chunksRead;
1375         int numChunks;
1376 
1377         ICCMarkerSegment(ICC_ColorSpace cs) {
1378             super(JPEG.APP2);
1379             chunks = null;
1380             chunksRead = 0;
1381             numChunks = 0;
1382             profile = cs.getProfile().getData();
1383         }
1384 
1385         ICCMarkerSegment(JPEGBuffer buffer) throws IOException {
1386             super(buffer);  // gets whole segment or fills the buffer
1387             if (debug) {
1388                 System.out.println("Creating new ICC segment");
1389             }
1390             buffer.bufPtr += ID_SIZE; // Skip the id


1405                 throw new IIOException
1406                     ("Image format Error; chunk num > num chunks");
1407             }
1408 
1409             // if there are no more chunks, set up the data
1410             if (numChunks == 1) {
1411                 // reduce the stored length by the two chunk numbering bytes
1412                 length -= 2;
1413                 profile = new byte[length];
1414                 buffer.bufPtr += 2;
1415                 buffer.bufAvail-=2;
1416                 buffer.readData(profile);
1417                 inICC = false;
1418             } else {
1419                 // If we store them away, include the chunk numbering bytes
1420                 byte [] profileData = new byte[length];
1421                 // Now reduce the stored length by the
1422                 // two chunk numbering bytes
1423                 length -= 2;
1424                 buffer.readData(profileData);
1425                 chunks = new ArrayList<>();
1426                 chunks.add(profileData);
1427                 chunksRead = 1;
1428                 inICC = true;
1429             }
1430         }
1431 
1432         ICCMarkerSegment(Node node) throws IIOInvalidTreeException {
1433             super(JPEG.APP2);
1434             if (node instanceof IIOMetadataNode) {
1435                 IIOMetadataNode ourNode = (IIOMetadataNode) node;
1436                 ICC_Profile prof = (ICC_Profile) ourNode.getUserObject();
1437                 if (prof != null) {  // May be null
1438                     profile = prof.getData();
1439                 }
1440             }
1441         }
1442 
1443         protected ICCMarkerSegment clone () {
1444             ICCMarkerSegment newGuy = (ICCMarkerSegment) super.clone();
1445             if (profile != null) {
1446                 newGuy.profile = profile.clone();
1447             }
1448             return newGuy;
1449         }
1450 
1451         boolean addData(JPEGBuffer buffer) throws IOException {
1452             if (debug) {
1453                 System.out.println("Adding to ICC segment");
1454             }
1455             // skip the tag
1456             buffer.bufPtr++;
1457             buffer.bufAvail--;
1458             // Get the length, but not in length
1459             int dataLen = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;
1460             dataLen |= buffer.buf[buffer.bufPtr++] & 0xff;
1461             buffer.bufAvail -= 2;
1462             // Don't include length itself
1463             dataLen -= 2;


1495             buffer.readData(profileData);
1496             chunks.add(profileData);
1497             length += dataLen;
1498             chunksRead++;
1499             if (chunksRead < numChunks) {
1500                 inICC = true;
1501             } else {
1502                 if (debug) {
1503                     System.out.println("Completing profile; total length is "
1504                                        + length);
1505                 }
1506                 // create an array for the whole thing
1507                 profile = new byte[length];
1508                 // copy the existing chunks, releasing them
1509                 // Note that they may be out of order
1510 
1511                 int index = 0;
1512                 for (int i = 1; i <= numChunks; i++) {
1513                     boolean foundIt = false;
1514                     for (int chunk = 0; chunk < chunks.size(); chunk++) {
1515                         byte [] chunkData = chunks.get(chunk);
1516                         if (chunkData[0] == i) { // Right one
1517                             System.arraycopy(chunkData, 2,
1518                                              profile, index,
1519                                              chunkData.length-2);
1520                             index += chunkData.length-2;
1521                             foundIt = true;
1522                         }
1523                     }
1524                     if (foundIt == false) {
1525                         throw new IIOException
1526                             ("Image Format Error: Missing ICC chunk num " + i);
1527                     }
1528                 }
1529 
1530                 chunks = null;
1531                 chunksRead = 0;
1532                 numChunks = 0;
1533                 inICC = false;
1534                 retval = true;
1535             }