1. /*
  2. * @(#)JPEGMetadata.java 1.27 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.imageio.plugins.jpeg;
  8. import javax.imageio.ImageTypeSpecifier;
  9. import javax.imageio.ImageWriteParam;
  10. import javax.imageio.IIOException;
  11. import javax.imageio.stream.ImageInputStream;
  12. import javax.imageio.stream.ImageOutputStream;
  13. import javax.imageio.metadata.IIOMetadata;
  14. import javax.imageio.metadata.IIOMetadataNode;
  15. import javax.imageio.metadata.IIOMetadataFormat;
  16. import javax.imageio.metadata.IIOMetadataFormatImpl;
  17. import javax.imageio.metadata.IIOInvalidTreeException;
  18. import javax.imageio.plugins.jpeg.JPEGQTable;
  19. import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
  20. import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
  21. import org.w3c.dom.Node;
  22. import org.w3c.dom.NodeList;
  23. import org.w3c.dom.NamedNodeMap;
  24. import java.util.List;
  25. import java.util.ArrayList;
  26. import java.util.Arrays;
  27. import java.util.Iterator;
  28. import java.util.ListIterator;
  29. import java.io.IOException;
  30. import java.awt.color.ICC_Profile;
  31. import java.awt.color.ICC_ColorSpace;
  32. import java.awt.color.ColorSpace;
  33. import java.awt.image.ColorModel;
  34. import java.awt.Point;
  35. /**
  36. * Metadata for the JPEG plug-in.
  37. */
  38. public class JPEGMetadata extends IIOMetadata implements Cloneable {
  39. //////// Private variables
  40. private static final boolean debug = false;
  41. /**
  42. * A copy of <code>markerSequence</code>, created the first time the
  43. * <code>markerSequence</code> is modified. This is used by reset
  44. * to restore the original state.
  45. */
  46. private List resetSequence = null;
  47. /**
  48. * Set to <code>true</code> when reading a thumbnail stored as
  49. * JPEG. This is used to enforce the prohibition of JFIF thumbnails
  50. * containing any JFIF marker segments, and to ensure generation of
  51. * a correct native subtree during <code>getAsTree</code>.
  52. */
  53. private boolean inThumb = false;
  54. /**
  55. * Set by the chroma node construction method to signal the
  56. * presence or absence of an alpha channel to the transparency
  57. * node construction method. Used only when constructing a
  58. * standard metadata tree.
  59. */
  60. private boolean hasAlpha;
  61. //////// end of private variables
  62. /////// Package-access variables
  63. /**
  64. * All data is a list of <code>MarkerSegment</code> objects.
  65. * When accessing the list, use the tag to identify the particular
  66. * subclass. Any JFIF marker segment must be the first element
  67. * of the list if it is present, and any JFXX or APP2ICC marker
  68. * segments are subordinate to the JFIF marker segment. This
  69. * list is package visible so that the writer can access it.
  70. * @see #MarkerSegment
  71. */
  72. List markerSequence = new ArrayList();
  73. /**
  74. * Indicates whether this object represents stream or image
  75. * metadata. Package-visible so the writer can see it.
  76. */
  77. final boolean isStream;
  78. /////// End of package-access variables
  79. /////// Constructors
  80. /**
  81. * Constructor containing code shared by other constructors.
  82. */
  83. JPEGMetadata(boolean isStream, boolean inThumb) {
  84. super(true, // Supports standard format
  85. JPEG.nativeImageMetadataFormatName, // and a native format
  86. JPEG.nativeImageMetadataFormatClassName,
  87. null, null); // No other formats
  88. this.inThumb = inThumb;
  89. // But if we are stream metadata, adjust the variables
  90. this.isStream = isStream;
  91. if (isStream) {
  92. nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;
  93. nativeMetadataFormatClassName =
  94. JPEG.nativeStreamMetadataFormatClassName;
  95. }
  96. }
  97. /*
  98. * Constructs a <code>JPEGMetadata</code> object by reading the
  99. * contents of an <code>ImageInputStream</code>. Has package-only
  100. * access.
  101. *
  102. * @param isStream A boolean indicating whether this object will be
  103. * stream or image metadata.
  104. * @param isThumb A boolean indicating whether this metadata object
  105. * is for an image or for a thumbnail stored as JPEG.
  106. * @param iis An <code>ImageInputStream</code> from which to read
  107. * the metadata.
  108. * @param reader The <code>JPEGImageReader</code> calling this
  109. * constructor, to which warnings should be sent.
  110. */
  111. JPEGMetadata(boolean isStream,
  112. boolean isThumb,
  113. ImageInputStream iis,
  114. JPEGImageReader reader) throws IOException {
  115. this(isStream, isThumb);
  116. JPEGBuffer buffer = new JPEGBuffer(iis);
  117. buffer.loadBuf(0);
  118. // The first three bytes should be FF, SOI, FF
  119. if (((buffer.buf[0] & 0xff) != 0xff)
  120. || ((buffer.buf[1] & 0xff) != JPEG.SOI)
  121. || ((buffer.buf[2] & 0xff) != 0xff)) {
  122. throw new IIOException ("Image format error");
  123. }
  124. boolean done = false;
  125. buffer.bufAvail -=2; // Next byte should be the ff before a marker
  126. buffer.bufPtr = 2;
  127. MarkerSegment newGuy = null;
  128. while (!done) {
  129. byte [] buf;
  130. int ptr;
  131. buffer.loadBuf(1);
  132. if (debug) {
  133. System.out.println("top of loop");
  134. buffer.print(10);
  135. }
  136. buffer.scanForFF(reader);
  137. switch (buffer.buf[buffer.bufPtr] & 0xff) {
  138. case 0:
  139. if (debug) {
  140. System.out.println("Skipping 0");
  141. }
  142. buffer.bufAvail--;
  143. buffer.bufPtr++;
  144. break;
  145. case JPEG.SOF0:
  146. case JPEG.SOF1:
  147. case JPEG.SOF2:
  148. if (isStream) {
  149. throw new IIOException
  150. ("SOF not permitted in stream metadata");
  151. }
  152. newGuy = new SOFMarkerSegment(buffer);
  153. break;
  154. case JPEG.DQT:
  155. newGuy = new DQTMarkerSegment(buffer);
  156. break;
  157. case JPEG.DHT:
  158. newGuy = new DHTMarkerSegment(buffer);
  159. break;
  160. case JPEG.DRI:
  161. newGuy = new DRIMarkerSegment(buffer);
  162. break;
  163. case JPEG.APP0:
  164. // Either JFIF, JFXX, or unknown APP0
  165. buffer.loadBuf(8); // tag, length, id
  166. buf = buffer.buf;
  167. ptr = buffer.bufPtr;
  168. if ((buf[ptr+3] == 'J')
  169. && (buf[ptr+4] == 'F')
  170. && (buf[ptr+5] == 'I')
  171. && (buf[ptr+6] == 'F')
  172. && (buf[ptr+7] == 0)) {
  173. if (inThumb) {
  174. reader.warningOccurred
  175. (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB);
  176. // Leave newGuy null
  177. // Read a dummy to skip the segment
  178. JFIFMarkerSegment dummy =
  179. new JFIFMarkerSegment(buffer);
  180. } else if (isStream) {
  181. throw new IIOException
  182. ("JFIF not permitted in stream metadata");
  183. } else if (markerSequence.isEmpty() == false) {
  184. throw new IIOException
  185. ("JFIF APP0 must be first marker after SOI");
  186. } else {
  187. newGuy = new JFIFMarkerSegment(buffer);
  188. }
  189. } else if ((buf[ptr+3] == 'J')
  190. && (buf[ptr+4] == 'F')
  191. && (buf[ptr+5] == 'X')
  192. && (buf[ptr+6] == 'X')
  193. && (buf[ptr+7] == 0)) {
  194. if (isStream) {
  195. throw new IIOException
  196. ("JFXX not permitted in stream metadata");
  197. }
  198. if (inThumb) {
  199. throw new IIOException
  200. ("JFXX markers not allowed in JFIF JPEG thumbnail");
  201. }
  202. JFIFMarkerSegment jfif =
  203. (JFIFMarkerSegment) findMarkerSegment
  204. (JFIFMarkerSegment.class, true);
  205. if (jfif == null) {
  206. throw new IIOException
  207. ("JFXX encountered without prior JFIF!");
  208. }
  209. jfif.addJFXX(buffer, reader);
  210. // newGuy remains null
  211. } else {
  212. newGuy = new MarkerSegment(buffer);
  213. newGuy.loadData(buffer);
  214. }
  215. break;
  216. case JPEG.APP2:
  217. // Either an ICC profile or unknown APP2
  218. buffer.loadBuf(15); // tag, length, id
  219. if ((buffer.buf[buffer.bufPtr+3] == 'I')
  220. && (buffer.buf[buffer.bufPtr+4] == 'C')
  221. && (buffer.buf[buffer.bufPtr+5] == 'C')
  222. && (buffer.buf[buffer.bufPtr+6] == '_')
  223. && (buffer.buf[buffer.bufPtr+7] == 'P')
  224. && (buffer.buf[buffer.bufPtr+8] == 'R')
  225. && (buffer.buf[buffer.bufPtr+9] == 'O')
  226. && (buffer.buf[buffer.bufPtr+10] == 'F')
  227. && (buffer.buf[buffer.bufPtr+11] == 'I')
  228. && (buffer.buf[buffer.bufPtr+12] == 'L')
  229. && (buffer.buf[buffer.bufPtr+13] == 'E')
  230. && (buffer.buf[buffer.bufPtr+14] == 0)
  231. ) {
  232. if (isStream) {
  233. throw new IIOException
  234. ("ICC profiles not permitted in stream metadata");
  235. }
  236. JFIFMarkerSegment jfif =
  237. (JFIFMarkerSegment) findMarkerSegment
  238. (JFIFMarkerSegment.class, true);
  239. if (jfif == null) {
  240. throw new IIOException
  241. ("ICC APP2 encountered without prior JFIF!");
  242. }
  243. jfif.addICC(buffer);
  244. // newGuy remains null
  245. } else {
  246. newGuy = new MarkerSegment(buffer);
  247. newGuy.loadData(buffer);
  248. }
  249. break;
  250. case JPEG.APP14:
  251. // Either Adobe or unknown APP14
  252. buffer.loadBuf(8); // tag, length, id
  253. if ((buffer.buf[buffer.bufPtr+3] == 'A')
  254. && (buffer.buf[buffer.bufPtr+4] == 'd')
  255. && (buffer.buf[buffer.bufPtr+5] == 'o')
  256. && (buffer.buf[buffer.bufPtr+6] == 'b')
  257. && (buffer.buf[buffer.bufPtr+7] == 'e')) {
  258. if (isStream) {
  259. throw new IIOException
  260. ("Adobe APP14 markers not permitted in stream metadata");
  261. }
  262. newGuy = new AdobeMarkerSegment(buffer);
  263. } else {
  264. newGuy = new MarkerSegment(buffer);
  265. newGuy.loadData(buffer);
  266. }
  267. break;
  268. case JPEG.COM:
  269. newGuy = new COMMarkerSegment(buffer);
  270. break;
  271. case JPEG.SOS:
  272. if (isStream) {
  273. throw new IIOException
  274. ("SOS not permitted in stream metadata");
  275. }
  276. newGuy = new SOSMarkerSegment(buffer);
  277. break;
  278. case JPEG.RST0:
  279. case JPEG.RST1:
  280. case JPEG.RST2:
  281. case JPEG.RST3:
  282. case JPEG.RST4:
  283. case JPEG.RST5:
  284. case JPEG.RST6:
  285. case JPEG.RST7:
  286. if (debug) {
  287. System.out.println("Restart Marker");
  288. }
  289. buffer.bufPtr++; // Just skip it
  290. buffer.bufAvail--;
  291. break;
  292. case JPEG.EOI:
  293. done = true;
  294. buffer.bufPtr++;
  295. buffer.bufAvail--;
  296. break;
  297. default:
  298. newGuy = new MarkerSegment(buffer);
  299. newGuy.loadData(buffer);
  300. newGuy.unknown = true;
  301. break;
  302. }
  303. if (newGuy != null) {
  304. markerSequence.add(newGuy);
  305. if (debug) {
  306. newGuy.print();
  307. }
  308. newGuy = null;
  309. }
  310. }
  311. // Now that we've read up to the EOI, we need to push back
  312. // whatever is left in the buffer, so that the next read
  313. // in the native code will work.
  314. buffer.pushBack();
  315. if (!isConsistent()) {
  316. throw new IIOException("Inconsistent metadata read from stream");
  317. }
  318. }
  319. /**
  320. * Constructs a default stream <code>JPEGMetadata</code> object appropriate
  321. * for the given write parameters.
  322. */
  323. JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) {
  324. this(true, false);
  325. JPEGImageWriteParam jparam = null;
  326. if ((param != null) && (param instanceof JPEGImageWriteParam)) {
  327. jparam = (JPEGImageWriteParam) param;
  328. if (!jparam.areTablesSet()) {
  329. jparam = null;
  330. }
  331. }
  332. if (jparam != null) {
  333. markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));
  334. markerSequence.add
  335. (new DHTMarkerSegment(jparam.getDCHuffmanTables(),
  336. jparam.getACHuffmanTables()));
  337. } else {
  338. // default tables.
  339. markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));
  340. markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),
  341. JPEG.getDefaultHuffmanTables(false)));
  342. }
  343. // Defensive programming
  344. if (!isConsistent()) {
  345. throw new InternalError("Default stream metadata is inconsistent");
  346. }
  347. }
  348. /**
  349. * Constructs a default image <code>JPEGMetadata</code> object appropriate
  350. * for the given image type and write parameters.
  351. */
  352. JPEGMetadata(ImageTypeSpecifier imageType,
  353. ImageWriteParam param,
  354. JPEGImageWriter writer) {
  355. this(false, false);
  356. boolean wantJFIF = true;
  357. boolean wantAdobe = false;
  358. int transform = JPEG.ADOBE_UNKNOWN;
  359. boolean willSubsample = true;
  360. boolean wantICC = false;
  361. boolean wantProg = false;
  362. boolean wantOptimized = false;
  363. boolean wantExtended = false;
  364. boolean wantQTables = true;
  365. boolean wantHTables = true;
  366. float quality = JPEG.DEFAULT_QUALITY;
  367. byte[] componentIDs = { 1, 2, 3, 4};
  368. int numComponents = 0;
  369. ImageTypeSpecifier destType = null;
  370. if (param != null) {
  371. destType = param.getDestinationType();
  372. if (destType != null) {
  373. if (imageType != null) {
  374. // Ignore the destination type.
  375. writer.warningOccurred
  376. (JPEGImageWriter.WARNING_DEST_IGNORED);
  377. destType = null;
  378. }
  379. }
  380. // The only progressive mode that makes sense here is MODE_DEFAULT
  381. if (param.canWriteProgressive()) {
  382. // the param may not be one of ours, so it may return false.
  383. // If so, the following would throw an exception
  384. if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {
  385. wantProg = true;
  386. wantOptimized = true;
  387. wantHTables = false;
  388. }
  389. }
  390. if (param instanceof JPEGImageWriteParam) {
  391. JPEGImageWriteParam jparam = (JPEGImageWriteParam) param;
  392. if (jparam.areTablesSet()) {
  393. wantQTables = false; // If the param has them, metadata shouldn't
  394. wantHTables = false;
  395. if ((jparam.getDCHuffmanTables().length > 2)
  396. || (jparam.getACHuffmanTables().length > 2)) {
  397. wantExtended = true;
  398. }
  399. }
  400. // Progressive forces optimized, regardless of param setting
  401. // so consult the param re optimized only if not progressive
  402. if (!wantProg) {
  403. wantOptimized = jparam.getOptimizeHuffmanTables();
  404. if (wantOptimized) {
  405. wantHTables = false;
  406. }
  407. }
  408. }
  409. // compression quality should determine the q tables. Note that this
  410. // will be ignored if we already decided not to create any.
  411. // Again, the param may not be one of ours, so we must check that it
  412. // supports compression settings
  413. if (param.canWriteCompressed()) {
  414. if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
  415. quality = param.getCompressionQuality();
  416. }
  417. }
  418. }
  419. // We are done with the param, now for the image types
  420. ColorSpace cs = null;
  421. if (destType != null) {
  422. ColorModel cm = destType.getColorModel();
  423. numComponents = cm.getNumComponents();
  424. boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
  425. boolean hasAlpha = cm.hasAlpha();
  426. cs = cm.getColorSpace();
  427. int type = cs.getType();
  428. switch(type) {
  429. case ColorSpace.TYPE_GRAY:
  430. willSubsample = false;
  431. if (hasExtraComponents) { // e.g. alpha
  432. wantJFIF = false;
  433. }
  434. break;
  435. case ColorSpace.TYPE_3CLR:
  436. if (cs == JPEG.YCC) {
  437. wantJFIF = false;
  438. componentIDs[0] = (byte) 'Y';
  439. componentIDs[1] = (byte) 'C';
  440. componentIDs[2] = (byte) 'c';
  441. if (hasAlpha) {
  442. componentIDs[3] = (byte) 'A';
  443. }
  444. }
  445. break;
  446. case ColorSpace.TYPE_YCbCr:
  447. if (hasExtraComponents) { // e.g. K or alpha
  448. wantJFIF = false;
  449. if (!hasAlpha) { // Not alpha, so must be K
  450. wantAdobe = true;
  451. transform = JPEG.ADOBE_YCCK;
  452. }
  453. }
  454. break;
  455. case ColorSpace.TYPE_RGB: // with or without alpha
  456. wantJFIF = false;
  457. wantAdobe = true;
  458. willSubsample = false;
  459. componentIDs[0] = (byte) 'R';
  460. componentIDs[1] = (byte) 'G';
  461. componentIDs[2] = (byte) 'B';
  462. if (hasAlpha) {
  463. componentIDs[3] = (byte) 'A';
  464. }
  465. break;
  466. default:
  467. // Everything else is not subsampled, gets no special marker,
  468. // and component ids are 1 - N
  469. wantJFIF = false;
  470. willSubsample = false;
  471. }
  472. } else if (imageType != null) {
  473. ColorModel cm = imageType.getColorModel();
  474. numComponents = cm.getNumComponents();
  475. boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);
  476. boolean hasAlpha = cm.hasAlpha();
  477. cs = cm.getColorSpace();
  478. int type = cs.getType();
  479. switch(type) {
  480. case ColorSpace.TYPE_GRAY:
  481. willSubsample = false;
  482. if (hasExtraComponents) { // e.g. alpha
  483. wantJFIF = false;
  484. }
  485. break;
  486. case ColorSpace.TYPE_RGB: // with or without alpha
  487. // without alpha we just accept the JFIF defaults
  488. if (hasAlpha) {
  489. wantJFIF = false;
  490. }
  491. break;
  492. case ColorSpace.TYPE_3CLR:
  493. wantJFIF = false;
  494. willSubsample = false;
  495. if (cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))) {
  496. willSubsample = true;
  497. wantAdobe = true;
  498. componentIDs[0] = (byte) 'Y';
  499. componentIDs[1] = (byte) 'C';
  500. componentIDs[2] = (byte) 'c';
  501. if (hasAlpha) {
  502. componentIDs[3] = (byte) 'A';
  503. }
  504. }
  505. break;
  506. case ColorSpace.TYPE_YCbCr:
  507. if (hasExtraComponents) { // e.g. K or alpha
  508. wantJFIF = false;
  509. if (!hasAlpha) { // then it must be K
  510. wantAdobe = true;
  511. transform = JPEG.ADOBE_YCCK;
  512. }
  513. }
  514. break;
  515. case ColorSpace.TYPE_CMYK:
  516. wantJFIF = false;
  517. wantAdobe = true;
  518. transform = JPEG.ADOBE_YCCK;
  519. break;
  520. default:
  521. // Everything else is not subsampled, gets no special marker,
  522. // and component ids are 0 - N
  523. wantJFIF = false;
  524. willSubsample = false;
  525. }
  526. }
  527. // do we want an ICC profile?
  528. if (wantJFIF && JPEG.isNonStandardICC(cs)) {
  529. wantICC = true;
  530. }
  531. // Now step through the markers, consulting our variables.
  532. if (wantJFIF) {
  533. JFIFMarkerSegment jfif = new JFIFMarkerSegment();
  534. markerSequence.add(jfif);
  535. if (wantICC) {
  536. try {
  537. jfif.addICC((ICC_ColorSpace)cs);
  538. } catch (IOException e) {} // Can't happen here
  539. }
  540. }
  541. // Adobe
  542. if (wantAdobe) {
  543. markerSequence.add(new AdobeMarkerSegment(transform));
  544. }
  545. // dqt
  546. if (wantQTables) {
  547. markerSequence.add(new DQTMarkerSegment(quality, willSubsample));
  548. }
  549. // dht
  550. if (wantHTables) {
  551. markerSequence.add(new DHTMarkerSegment(willSubsample));
  552. }
  553. // sof
  554. markerSequence.add(new SOFMarkerSegment(wantProg,
  555. wantExtended,
  556. willSubsample,
  557. componentIDs,
  558. numComponents));
  559. // sos
  560. if (!wantProg) { // Default progression scans are done in the writer
  561. markerSequence.add(new SOSMarkerSegment(willSubsample,
  562. componentIDs,
  563. numComponents));
  564. }
  565. // Defensive programming
  566. if (!isConsistent()) {
  567. throw new InternalError("Default image metadata is inconsistent");
  568. }
  569. }
  570. ////// End of constructors
  571. // Utilities for dealing with the marker sequence.
  572. // The first ones have package access for access from the writer.
  573. /**
  574. * Returns the first MarkerSegment object in the list
  575. * with the given tag, or null if none is found.
  576. */
  577. MarkerSegment findMarkerSegment(int tag) {
  578. Iterator iter = markerSequence.iterator();
  579. while (iter.hasNext()) {
  580. MarkerSegment seg = (MarkerSegment)iter.next();
  581. if (seg.tag == tag) {
  582. return seg;
  583. }
  584. }
  585. return null;
  586. }
  587. /**
  588. * Returns the first or last MarkerSegment object in the list
  589. * of the given class, or null if none is found.
  590. */
  591. MarkerSegment findMarkerSegment(Class cls, boolean first) {
  592. if (first) {
  593. Iterator iter = markerSequence.iterator();
  594. while (iter.hasNext()) {
  595. MarkerSegment seg = (MarkerSegment)iter.next();
  596. if (cls.isInstance(seg)) {
  597. return seg;
  598. }
  599. }
  600. } else {
  601. ListIterator iter = markerSequence.listIterator(markerSequence.size());
  602. while (iter.hasPrevious()) {
  603. MarkerSegment seg = (MarkerSegment)iter.previous();
  604. if (cls.isInstance(seg)) {
  605. return seg;
  606. }
  607. }
  608. }
  609. return null;
  610. }
  611. /**
  612. * Returns the index of the first or last MarkerSegment in the list
  613. * of the given class, or -1 if none is found.
  614. */
  615. private int findMarkerSegmentPosition(Class cls, boolean first) {
  616. if (first) {
  617. ListIterator iter = markerSequence.listIterator();
  618. for (int i = 0; iter.hasNext(); i++) {
  619. MarkerSegment seg = (MarkerSegment)iter.next();
  620. if (cls.isInstance(seg)) {
  621. return i;
  622. }
  623. }
  624. } else {
  625. ListIterator iter = markerSequence.listIterator(markerSequence.size());
  626. for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
  627. MarkerSegment seg = (MarkerSegment)iter.previous();
  628. if (cls.isInstance(seg)) {
  629. return i;
  630. }
  631. }
  632. }
  633. return -1;
  634. }
  635. private int findLastUnknownMarkerSegmentPosition() {
  636. ListIterator iter = markerSequence.listIterator(markerSequence.size());
  637. for (int i = markerSequence.size()-1; iter.hasPrevious(); i--) {
  638. MarkerSegment seg = (MarkerSegment)iter.previous();
  639. if (seg.unknown == true) {
  640. return i;
  641. }
  642. }
  643. return -1;
  644. }
  645. // Implement Cloneable, but restrict access
  646. protected Object clone() {
  647. JPEGMetadata newGuy = null;
  648. try {
  649. newGuy = (JPEGMetadata) super.clone();
  650. } catch (CloneNotSupportedException e) {} // won't happen
  651. if (markerSequence != null) {
  652. newGuy.markerSequence = (List) cloneSequence();
  653. }
  654. newGuy.resetSequence = null;
  655. return newGuy;
  656. }
  657. /**
  658. * Returns a deep copy of the current marker sequence.
  659. */
  660. private List cloneSequence() {
  661. if (markerSequence == null) {
  662. return null;
  663. }
  664. List retval = new ArrayList(markerSequence.size());
  665. Iterator iter = markerSequence.iterator();
  666. while(iter.hasNext()) {
  667. MarkerSegment seg = (MarkerSegment)iter.next();
  668. retval.add(seg.clone());
  669. }
  670. return retval;
  671. }
  672. // Tree methods
  673. public Node getAsTree(String formatName) {
  674. if (formatName == null) {
  675. throw new IllegalArgumentException("null formatName!");
  676. }
  677. if (isStream) {
  678. if (formatName.equals(JPEG.nativeStreamMetadataFormatName)) {
  679. return getNativeTree();
  680. }
  681. } else {
  682. if (formatName.equals(JPEG.nativeImageMetadataFormatName)) {
  683. return getNativeTree();
  684. }
  685. if (formatName.equals
  686. (IIOMetadataFormatImpl.standardMetadataFormatName)) {
  687. return getStandardTree();
  688. }
  689. }
  690. throw new IllegalArgumentException("Unsupported format name: "
  691. + formatName);
  692. }
  693. IIOMetadataNode getNativeTree() {
  694. IIOMetadataNode root;
  695. IIOMetadataNode top;
  696. Iterator iter = markerSequence.iterator();
  697. if (isStream) {
  698. root = new IIOMetadataNode(JPEG.nativeStreamMetadataFormatName);
  699. top = root;
  700. } else {
  701. IIOMetadataNode sequence = new IIOMetadataNode("markerSequence");
  702. if (!inThumb) {
  703. root = new IIOMetadataNode(JPEG.nativeImageMetadataFormatName);
  704. IIOMetadataNode header = new IIOMetadataNode("JPEGvariety");
  705. root.appendChild(header);
  706. JFIFMarkerSegment jfif = (JFIFMarkerSegment)
  707. findMarkerSegment(JFIFMarkerSegment.class, true);
  708. if (jfif != null) {
  709. iter.next(); // JFIF must be first, so this skips it
  710. header.appendChild(jfif.getNativeNode());
  711. }
  712. root.appendChild(sequence);
  713. } else {
  714. root = sequence;
  715. }
  716. top = sequence;
  717. }
  718. while(iter.hasNext()) {
  719. MarkerSegment seg = (MarkerSegment) iter.next();
  720. top.appendChild(seg.getNativeNode());
  721. }
  722. return root;
  723. }
  724. // Standard tree node methods
  725. protected IIOMetadataNode getStandardChromaNode() {
  726. hasAlpha = false; // Unless we find otherwise
  727. // Colorspace type - follow the rules in the spec
  728. // First get the SOF marker segment, if there is one
  729. SOFMarkerSegment sof = (SOFMarkerSegment)
  730. findMarkerSegment(SOFMarkerSegment.class, true);
  731. if (sof == null) {
  732. // No image, so no chroma
  733. return null;
  734. }
  735. IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
  736. IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
  737. chroma.appendChild(csType);
  738. // get the number of channels
  739. int numChannels = sof.componentSpecs.length;
  740. IIOMetadataNode numChanNode = new IIOMetadataNode("NumChannels");
  741. chroma.appendChild(numChanNode);
  742. numChanNode.setAttribute("value", Integer.toString(numChannels));
  743. // is there a JFIF marker segment?
  744. if (findMarkerSegment(JFIFMarkerSegment.class, true) != null) {
  745. if (numChannels == 1) {
  746. csType.setAttribute("name", "GRAY");
  747. } else {
  748. csType.setAttribute("name", "YCbCr");
  749. }
  750. return chroma;
  751. }
  752. // How about an Adobe marker segment?
  753. AdobeMarkerSegment adobe =
  754. (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
  755. if (adobe != null){
  756. switch (adobe.transform) {
  757. case JPEG.ADOBE_YCCK:
  758. csType.setAttribute("name", "YCCK");
  759. break;
  760. case JPEG.ADOBE_YCC:
  761. csType.setAttribute("name", "YCbCr");
  762. break;
  763. case JPEG.ADOBE_UNKNOWN:
  764. if (numChannels == 3) {
  765. csType.setAttribute("name", "RGB");
  766. } else if (numChannels == 4) {
  767. csType.setAttribute("name", "CMYK");
  768. }
  769. break;
  770. }
  771. return chroma;
  772. }
  773. // Neither marker. Check components
  774. if (numChannels < 3) {
  775. csType.setAttribute("name", "GRAY");
  776. if (numChannels == 2) {
  777. hasAlpha = true;
  778. }
  779. return chroma;
  780. }
  781. boolean idsAreJFIF = true;
  782. for (int i = 0; i < sof.componentSpecs.length; i++) {
  783. int id = sof.componentSpecs[i].componentId;
  784. if ((id < 1) || (id >= sof.componentSpecs.length)) {
  785. idsAreJFIF = false;
  786. }
  787. }
  788. if (idsAreJFIF) {
  789. csType.setAttribute("name", "YCbCr");
  790. if (numChannels == 4) {
  791. hasAlpha = true;
  792. }
  793. return chroma;
  794. }
  795. // Check against the letters
  796. if ((sof.componentSpecs[0].componentId == 'R')
  797. && (sof.componentSpecs[1].componentId == 'G')
  798. && (sof.componentSpecs[2].componentId == 'B')){
  799. csType.setAttribute("name", "RGB");
  800. if ((numChannels == 4)
  801. && (sof.componentSpecs[3].componentId == 'A')) {
  802. hasAlpha = true;
  803. }
  804. return chroma;
  805. }
  806. if ((sof.componentSpecs[0].componentId == 'Y')
  807. && (sof.componentSpecs[1].componentId == 'C')
  808. && (sof.componentSpecs[2].componentId == 'c')){
  809. csType.setAttribute("name", "PhotoYCC");
  810. if ((numChannels == 4)
  811. && (sof.componentSpecs[3].componentId == 'A')) {
  812. hasAlpha = true;
  813. }
  814. return chroma;
  815. }
  816. // Finally, 3-channel subsampled are YCbCr, unsubsampled are RGB
  817. // 4-channel subsampled are YCbCrA, unsubsampled are CMYK
  818. boolean subsampled = false;
  819. int hfactor = sof.componentSpecs[0].HsamplingFactor;
  820. int vfactor = sof.componentSpecs[0].VsamplingFactor;
  821. for (int i = 1; i<sof.componentSpecs.length; i++) {
  822. if ((sof.componentSpecs[i].HsamplingFactor != hfactor)
  823. || (sof.componentSpecs[i].VsamplingFactor != vfactor)){
  824. subsampled = true;
  825. break;
  826. }
  827. }
  828. if (subsampled) {
  829. csType.setAttribute("name", "YCbCr");
  830. if (numChannels == 4) {
  831. hasAlpha = true;
  832. }
  833. return chroma;
  834. }
  835. // Not subsampled. numChannels < 3 is taken care of above
  836. if (numChannels == 3) {
  837. csType.setAttribute("name", "RGB");
  838. } else {
  839. csType.setAttribute("name", "CMYK");
  840. }
  841. return chroma;
  842. }
  843. protected IIOMetadataNode getStandardCompressionNode() {
  844. IIOMetadataNode compression = new IIOMetadataNode("Compression");
  845. // CompressionTypeName
  846. IIOMetadataNode name = new IIOMetadataNode("CompressionTypeName");
  847. name.setAttribute("value", "JPEG");
  848. compression.appendChild(name);
  849. // Lossless - false
  850. IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
  851. lossless.setAttribute("value", "false");
  852. compression.appendChild(lossless);
  853. // NumProgressiveScans - count sos segments
  854. int sosCount = 0;
  855. Iterator iter = markerSequence.iterator();
  856. while (iter.hasNext()) {
  857. MarkerSegment ms = (MarkerSegment) iter.next();
  858. if (ms.tag == JPEG.SOS) {
  859. sosCount++;
  860. }
  861. }
  862. if (sosCount != 0) {
  863. IIOMetadataNode prog = new IIOMetadataNode("NumProgressiveScans");
  864. prog.setAttribute("value", Integer.toString(sosCount));
  865. compression.appendChild(prog);
  866. }
  867. return compression;
  868. }
  869. protected IIOMetadataNode getStandardDimensionNode() {
  870. // If we have a JFIF marker segment, we know a little
  871. // otherwise all we know is the orientation, which is always normal
  872. IIOMetadataNode dim = new IIOMetadataNode("Dimension");
  873. IIOMetadataNode orient = new IIOMetadataNode("ImageOrientation");
  874. orient.setAttribute("value", "normal");
  875. dim.appendChild(orient);
  876. JFIFMarkerSegment jfif =
  877. (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
  878. if (jfif != null) {
  879. // Aspect Ratio is width of pixel / height of pixel
  880. float aspectRatio;
  881. if (jfif.resUnits == 0) {
  882. // In this case they just encode aspect ratio directly
  883. aspectRatio = ((float) jfif.Xdensity)/jfif.Ydensity;
  884. } else {
  885. // They are true densities (e.g. dpi) and must be inverted
  886. aspectRatio = ((float) jfif.Ydensity)/jfif.Xdensity;
  887. }
  888. IIOMetadataNode aspect = new IIOMetadataNode("PixelAspectRatio");
  889. aspect.setAttribute("value", Float.toString(aspectRatio));
  890. dim.insertBefore(aspect, orient);
  891. // Pixel size
  892. if (jfif.resUnits != 0) {
  893. // 1 == dpi, 2 == dpc
  894. float scale = (jfif.resUnits == 1) ? 25.4F : 10.0F;
  895. IIOMetadataNode horiz =
  896. new IIOMetadataNode("HorizontalPixelSize");
  897. horiz.setAttribute("value",
  898. Float.toString(scalejfif.Xdensity));
  899. dim.appendChild(horiz);
  900. IIOMetadataNode vert =
  901. new IIOMetadataNode("VerticalPixelSize");
  902. vert.setAttribute("value",
  903. Float.toString(scalejfif.Ydensity));
  904. dim.appendChild(vert);
  905. }
  906. }
  907. return dim;
  908. }
  909. protected IIOMetadataNode getStandardTextNode() {
  910. IIOMetadataNode text = null;
  911. // Add a text entry for each COM Marker Segment
  912. if (findMarkerSegment(JPEG.COM) != null) {
  913. text = new IIOMetadataNode("Text");
  914. Iterator iter = markerSequence.iterator();
  915. while (iter.hasNext()) {
  916. MarkerSegment seg = (MarkerSegment) iter.next();
  917. if (seg.tag == JPEG.COM) {
  918. COMMarkerSegment com = (COMMarkerSegment) seg;
  919. IIOMetadataNode entry = new IIOMetadataNode("TextEntry");
  920. entry.setAttribute("keyword", "comment");
  921. entry.setAttribute("value", com.getComment());
  922. text.appendChild(entry);
  923. }
  924. }
  925. }
  926. return text;
  927. }
  928. protected IIOMetadataNode getStandardTransparencyNode() {
  929. IIOMetadataNode trans = null;
  930. if (hasAlpha == true) {
  931. trans = new IIOMetadataNode("Transparency");
  932. IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
  933. alpha.setAttribute("value", "nonpremultiplied"); // Always assume
  934. trans.appendChild(alpha);
  935. }
  936. return trans;
  937. }
  938. // Editing
  939. public boolean isReadOnly() {
  940. return false;
  941. }
  942. public void mergeTree(String formatName, Node root)
  943. throws IIOInvalidTreeException {
  944. if (formatName == null) {
  945. throw new IllegalArgumentException("null formatName!");
  946. }
  947. if (root == null) {
  948. throw new IllegalArgumentException("null root!");
  949. }
  950. List copy = null;
  951. if (resetSequence == null) {
  952. resetSequence = cloneSequence(); // Deep copy
  953. copy = resetSequence; // Avoid cloning twice
  954. } else {
  955. copy = cloneSequence();
  956. }
  957. if (isStream &&
  958. (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
  959. mergeNativeTree(root);
  960. } else if (!isStream &&
  961. (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
  962. mergeNativeTree(root);
  963. } else if (!isStream &&
  964. (formatName.equals
  965. (IIOMetadataFormatImpl.standardMetadataFormatName))) {
  966. mergeStandardTree(root);
  967. } else {
  968. throw new IllegalArgumentException("Unsupported format name: "
  969. + formatName);
  970. }
  971. if (!isConsistent()) {
  972. markerSequence = copy;
  973. throw new IIOInvalidTreeException
  974. ("Merged tree is invalid; original restored", root);
  975. }
  976. }
  977. private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
  978. String name = root.getNodeName();
  979. if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
  980. : JPEG.nativeImageMetadataFormatName)) {
  981. throw new IIOInvalidTreeException("Invalid root node name: " + name,
  982. root);
  983. }
  984. if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
  985. throw new IIOInvalidTreeException(
  986. "JPEGvariety and markerSequence nodes must be present", root);
  987. }
  988. mergeJFIFsubtree(root.getFirstChild());
  989. mergeSequenceSubtree(root.getLastChild());
  990. }
  991. /**
  992. * Merge a JFIF subtree into the marker sequence, if the subtree
  993. * is non-empty.
  994. * If a JFIF marker exists, update it from the subtree.
  995. * If none exists, create one from the subtree and insert it at the
  996. * beginning of the marker sequence.
  997. */
  998. private void mergeJFIFsubtree(Node JPEGvariety)
  999. throws IIOInvalidTreeException {
  1000. if (JPEGvariety.getChildNodes().getLength() != 0) {
  1001. Node jfifNode = JPEGvariety.getFirstChild();
  1002. // is there already a jfif marker segment?
  1003. JFIFMarkerSegment jfifSeg =
  1004. (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
  1005. if (jfifSeg != null) {
  1006. jfifSeg.updateFromNativeNode(jfifNode, false);
  1007. } else {
  1008. // Add it as the first element in the list.
  1009. markerSequence.add(0, new JFIFMarkerSegment(jfifNode));
  1010. }
  1011. }
  1012. }
  1013. private void mergeSequenceSubtree(Node sequenceTree)
  1014. throws IIOInvalidTreeException {
  1015. NodeList children = sequenceTree.getChildNodes();
  1016. for (int i = 0; i < children.getLength(); i++) {
  1017. Node node = children.item(i);
  1018. String name = node.getNodeName();
  1019. if (name.equals("dqt")) {
  1020. mergeDQTNode(node);
  1021. } else if (name.equals("dht")) {
  1022. mergeDHTNode(node);
  1023. } else if (name.equals("dri")) {
  1024. mergeDRINode(node);
  1025. } else if (name.equals("com")) {
  1026. mergeCOMNode(node);
  1027. } else if (name.equals("app14Adobe")) {
  1028. mergeAdobeNode(node);
  1029. } else if (name.equals("unknown")) {
  1030. mergeUnknownNode(node);
  1031. } else if (name.equals("sof")) {
  1032. mergeSOFNode(node);
  1033. } else if (name.equals("sos")) {
  1034. mergeSOSNode(node);
  1035. } else {
  1036. throw new IIOInvalidTreeException("Invalid node: " + name, node);
  1037. }
  1038. }
  1039. }
  1040. /**
  1041. * Merge the given DQT node into the marker sequence. If there already
  1042. * exist DQT marker segments in the sequence, then each table in the
  1043. * node replaces the first table, in any DQT segment, with the same
  1044. * table id. If none of the existing DQT segments contain a table with
  1045. * the same id, then the table is added to the last existing DQT segment.
  1046. * If there are no DQT segments, then a new one is created and added
  1047. * as follows:
  1048. * If there are DHT segments, the new DQT segment is inserted before the
  1049. * first one.
  1050. * If there are no DHT segments, the new DQT segment is inserted before
  1051. * an SOF segment, if there is one.
  1052. * If there is no SOF segment, the new DQT segment is inserted before
  1053. * the first SOS segment, if there is one.
  1054. * If there is no SOS segment, the new DQT segment is added to the end
  1055. * of the sequence.
  1056. */
  1057. private void mergeDQTNode(Node node) throws IIOInvalidTreeException {
  1058. // First collect any existing DQT nodes into a local list
  1059. ArrayList oldDQTs = new ArrayList();
  1060. Iterator iter = markerSequence.iterator();
  1061. while (iter.hasNext()) {
  1062. MarkerSegment seg = (MarkerSegment) iter.next();
  1063. if (seg instanceof DQTMarkerSegment) {
  1064. oldDQTs.add(seg);
  1065. }
  1066. }
  1067. if (!oldDQTs.isEmpty()) {
  1068. NodeList children = node.getChildNodes();
  1069. for (int i = 0; i < children.getLength(); i++) {
  1070. Node child = children.item(i);
  1071. int childID = MarkerSegment.getAttributeValue(child,
  1072. null,
  1073. "qtableId",
  1074. 0, 3,
  1075. true);
  1076. DQTMarkerSegment dqt = null;
  1077. int tableIndex = -1;
  1078. for (int j = 0; j < oldDQTs.size(); j++) {
  1079. DQTMarkerSegment testDQT = (DQTMarkerSegment) oldDQTs.get(j);
  1080. for (int k = 0; k < testDQT.tables.size(); k++) {
  1081. DQTMarkerSegment.Qtable testTable =
  1082. (DQTMarkerSegment.Qtable) testDQT.tables.get(k);
  1083. if (childID == testTable.tableID) {
  1084. dqt = testDQT;
  1085. tableIndex = k;
  1086. break;
  1087. }
  1088. }
  1089. if (dqt != null) break;
  1090. }
  1091. if (dqt != null) {
  1092. dqt.tables.set(tableIndex, dqt.getQtableFromNode(child));
  1093. } else {
  1094. dqt = (DQTMarkerSegment) oldDQTs.get(oldDQTs.size()-1);
  1095. dqt.tables.add(dqt.getQtableFromNode(child));
  1096. }
  1097. }
  1098. } else {
  1099. DQTMarkerSegment newGuy = new DQTMarkerSegment(node);
  1100. int firstDHT = findMarkerSegmentPosition(DHTMarkerSegment.class, true);
  1101. int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
  1102. int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
  1103. if (firstDHT != -1) {
  1104. markerSequence.add(firstDHT, newGuy);
  1105. } else if (firstSOF != -1) {
  1106. markerSequence.add(firstSOF, newGuy);
  1107. } else if (firstSOS != -1) {
  1108. markerSequence.add(firstSOS, newGuy);
  1109. } else {
  1110. markerSequence.add(newGuy);
  1111. }
  1112. }
  1113. }
  1114. /**
  1115. * Merge the given DHT node into the marker sequence. If there already
  1116. * exist DHT marker segments in the sequence, then each table in the
  1117. * node replaces the first table, in any DHT segment, with the same
  1118. * table class and table id. If none of the existing DHT segments contain
  1119. * a table with the same class and id, then the table is added to the last
  1120. * existing DHT segment.
  1121. * If there are no DHT segments, then a new one is created and added
  1122. * as follows:
  1123. * If there are DQT segments, the new DHT segment is inserted immediately
  1124. * following the last DQT segment.
  1125. * If there are no DQT segments, the new DHT segment is inserted before
  1126. * an SOF segment, if there is one.
  1127. * If there is no SOF segment, the new DHT segment is inserted before
  1128. * the first SOS segment, if there is one.
  1129. * If there is no SOS segment, the new DHT segment is added to the end
  1130. * of the sequence.
  1131. */
  1132. private void mergeDHTNode(Node node) throws IIOInvalidTreeException {
  1133. // First collect any existing DQT nodes into a local list
  1134. ArrayList oldDHTs = new ArrayList();
  1135. Iterator iter = markerSequence.iterator();
  1136. while (iter.hasNext()) {
  1137. MarkerSegment seg = (MarkerSegment) iter.next();
  1138. if (seg instanceof DHTMarkerSegment) {
  1139. oldDHTs.add(seg);
  1140. }
  1141. }
  1142. if (!oldDHTs.isEmpty()) {
  1143. NodeList children = node.getChildNodes();
  1144. for (int i = 0; i < children.getLength(); i++) {
  1145. Node child = children.item(i);
  1146. NamedNodeMap attrs = node.getAttributes();
  1147. int childID = MarkerSegment.getAttributeValue(child,
  1148. attrs,
  1149. "htableId",
  1150. 0, 3,
  1151. true);
  1152. int childClass = MarkerSegment.getAttributeValue(child,
  1153. attrs,
  1154. "class",
  1155. 0, 1,
  1156. true);
  1157. DHTMarkerSegment dht = null;
  1158. int tableIndex = -1;
  1159. for (int j = 0; j < oldDHTs.size(); j++) {
  1160. DHTMarkerSegment testDHT = (DHTMarkerSegment) oldDHTs.get(j);
  1161. for (int k = 0; k <