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 < testDHT.tables.size(); k++) {
  1162. DHTMarkerSegment.Htable testTable =
  1163. (DHTMarkerSegment.Htable) testDHT.tables.get(k);
  1164. if ((childID == testTable.tableID) &&
  1165. (childClass == testTable.tableClass)) {
  1166. dht = testDHT;
  1167. tableIndex = k;
  1168. break;
  1169. }
  1170. }
  1171. if (dht != null) break;
  1172. }
  1173. if (dht != null) {
  1174. dht.tables.set(tableIndex, dht.getHtableFromNode(child));
  1175. } else {
  1176. dht = (DHTMarkerSegment) oldDHTs.get(oldDHTs.size()-1);
  1177. dht.tables.add(dht.getHtableFromNode(child));
  1178. }
  1179. }
  1180. } else {
  1181. DHTMarkerSegment newGuy = new DHTMarkerSegment(node);
  1182. int lastDQT = findMarkerSegmentPosition(DQTMarkerSegment.class, false);
  1183. int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
  1184. int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
  1185. if (lastDQT != -1) {
  1186. markerSequence.add(lastDQT+1, newGuy);
  1187. } else if (firstSOF != -1) {
  1188. markerSequence.add(firstSOF, newGuy);
  1189. } else if (firstSOS != -1) {
  1190. markerSequence.add(firstSOS, newGuy);
  1191. } else {
  1192. markerSequence.add(newGuy);
  1193. }
  1194. }
  1195. }
  1196. /**
  1197. * Merge the given DRI node into the marker sequence.
  1198. * If there already exists a DRI marker segment, the restart interval
  1199. * value is updated.
  1200. * If there is no DRI segment, then a new one is created and added as
  1201. * follows:
  1202. * If there is an SOF segment, the new DRI segment is inserted before
  1203. * it.
  1204. * If there is no SOF segment, the new DRI segment is inserted before
  1205. * the first SOS segment, if there is one.
  1206. * If there is no SOS segment, the new DRI segment is added to the end
  1207. * of the sequence.
  1208. */
  1209. private void mergeDRINode(Node node) throws IIOInvalidTreeException {
  1210. DRIMarkerSegment dri =
  1211. (DRIMarkerSegment) findMarkerSegment(DRIMarkerSegment.class, true);
  1212. if (dri != null) {
  1213. dri.updateFromNativeNode(node, false);
  1214. } else {
  1215. DRIMarkerSegment newGuy = new DRIMarkerSegment(node);
  1216. int firstSOF = findMarkerSegmentPosition(SOFMarkerSegment.class, true);
  1217. int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
  1218. if (firstSOF != -1) {
  1219. markerSequence.add(firstSOF, newGuy);
  1220. } else if (firstSOS != -1) {
  1221. markerSequence.add(firstSOS, newGuy);
  1222. } else {
  1223. markerSequence.add(newGuy);
  1224. }
  1225. }
  1226. }
  1227. /**
  1228. * Merge the given COM node into the marker sequence.
  1229. * A new COM marker segment is created and added to the sequence
  1230. * using insertCOMMarkerSegment.
  1231. */
  1232. private void mergeCOMNode(Node node) throws IIOInvalidTreeException {
  1233. COMMarkerSegment newGuy = new COMMarkerSegment(node);
  1234. insertCOMMarkerSegment(newGuy);
  1235. }
  1236. /**
  1237. * Insert a new COM marker segment into an appropriate place in the
  1238. * marker sequence, as follows:
  1239. * If there already exist COM marker segments, the new one is inserted
  1240. * after the last one.
  1241. * If there are no COM segments, the new COM segment is inserted after the
  1242. * JFIF segment, if there is one.
  1243. * If there is no JFIF segment, the new COM segment is inserted after the
  1244. * Adobe marker segment, if there is one.
  1245. * If there is no Adobe segment, the new COM segment is inserted
  1246. * at the beginning of the sequence.
  1247. */
  1248. private void insertCOMMarkerSegment(COMMarkerSegment newGuy) {
  1249. int lastCOM = findMarkerSegmentPosition(COMMarkerSegment.class, false);
  1250. boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
  1251. int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
  1252. if (lastCOM != -1) {
  1253. markerSequence.add(lastCOM+1, newGuy);
  1254. } else if (hasJFIF) {
  1255. markerSequence.add(1, newGuy); // JFIF is always 0
  1256. } else if (firstAdobe != -1) {
  1257. markerSequence.add(firstAdobe+1, newGuy);
  1258. } else {
  1259. markerSequence.add(0, newGuy);
  1260. }
  1261. }
  1262. /**
  1263. * Merge the given Adobe APP14 node into the marker sequence.
  1264. * If there already exists an Adobe marker segment, then its attributes
  1265. * are updated from the node.
  1266. * If there is no Adobe segment, then a new one is created and added
  1267. * using insertAdobeMarkerSegment.
  1268. */
  1269. private void mergeAdobeNode(Node node) throws IIOInvalidTreeException {
  1270. AdobeMarkerSegment adobe =
  1271. (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
  1272. if (adobe != null) {
  1273. adobe.updateFromNativeNode(node, false);
  1274. } else {
  1275. AdobeMarkerSegment newGuy = new AdobeMarkerSegment(node);
  1276. insertAdobeMarkerSegment(newGuy);
  1277. }
  1278. }
  1279. /**
  1280. * Insert the given AdobeMarkerSegment into the marker sequence, as
  1281. * follows (we assume there is no Adobe segment yet):
  1282. * If there is a JFIF segment, then the new Adobe segment is inserted
  1283. * after it.
  1284. * If there is no JFIF segment, the new Adobe segment is inserted after the
  1285. * last Unknown segment, if there are any.
  1286. * If there are no Unknown segments, the new Adobe segment is inserted
  1287. * at the beginning of the sequence.
  1288. */
  1289. private void insertAdobeMarkerSegment(AdobeMarkerSegment newGuy) {
  1290. boolean hasJFIF =
  1291. (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
  1292. int lastUnknown = findLastUnknownMarkerSegmentPosition();
  1293. if (hasJFIF) {
  1294. markerSequence.add(1, newGuy); // JFIF is always 0
  1295. } else if (lastUnknown != -1) {
  1296. markerSequence.add(lastUnknown+1, newGuy);
  1297. } else {
  1298. markerSequence.add(0, newGuy);
  1299. }
  1300. }
  1301. /**
  1302. * Merge the given Unknown node into the marker sequence.
  1303. * A new Unknown marker segment is created and added to the sequence as
  1304. * follows:
  1305. * If there already exist Unknown marker segments, the new one is inserted
  1306. * after the last one.
  1307. * If there are no Unknown marker segments, the new Unknown marker segment
  1308. * is inserted after the JFIF segment, if there is one.
  1309. * If there is no JFIF segment, the new Unknown segment is inserted before
  1310. * the Adobe marker segment, if there is one.
  1311. * If there is no Adobe segment, the new Unknown segment is inserted
  1312. * at the beginning of the sequence.
  1313. */
  1314. private void mergeUnknownNode(Node node) throws IIOInvalidTreeException {
  1315. MarkerSegment newGuy = new MarkerSegment(node);
  1316. int lastUnknown = findLastUnknownMarkerSegmentPosition();
  1317. boolean hasJFIF = (findMarkerSegment(JFIFMarkerSegment.class, true) != null);
  1318. int firstAdobe = findMarkerSegmentPosition(AdobeMarkerSegment.class, true);
  1319. if (lastUnknown != -1) {
  1320. markerSequence.add(lastUnknown+1, newGuy);
  1321. } else if (hasJFIF) {
  1322. markerSequence.add(1, newGuy); // JFIF is always 0
  1323. } if (firstAdobe != -1) {
  1324. markerSequence.add(firstAdobe, newGuy);
  1325. } else {
  1326. markerSequence.add(0, newGuy);
  1327. }
  1328. }
  1329. /**
  1330. * Merge the given SOF node into the marker sequence.
  1331. * If there already exists an SOF marker segment in the sequence, then
  1332. * its values are updated from the node.
  1333. * If there is no SOF segment, then a new one is created and added as
  1334. * follows:
  1335. * If there are any SOS segments, the new SOF segment is inserted before
  1336. * the first one.
  1337. * If there is no SOS segment, the new SOF segment is added to the end
  1338. * of the sequence.
  1339. *
  1340. */
  1341. private void mergeSOFNode(Node node) throws IIOInvalidTreeException {
  1342. SOFMarkerSegment sof =
  1343. (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
  1344. if (sof != null) {
  1345. sof.updateFromNativeNode(node, false);
  1346. } else {
  1347. SOFMarkerSegment newGuy = new SOFMarkerSegment(node);
  1348. int firstSOS = findMarkerSegmentPosition(SOSMarkerSegment.class, true);
  1349. if (firstSOS != -1) {
  1350. markerSequence.add(firstSOS, newGuy);
  1351. } else {
  1352. markerSequence.add(newGuy);
  1353. }
  1354. }
  1355. }
  1356. /**
  1357. * Merge the given SOS node into the marker sequence.
  1358. * If there already exists a single SOS marker segment, then the values
  1359. * are updated from the node.
  1360. * If there are more than one existing SOS marker segments, then an
  1361. * IIOInvalidTreeException is thrown, as SOS segments cannot be merged
  1362. * into a set of progressive scans.
  1363. * If there are no SOS marker segments, a new one is created and added
  1364. * to the end of the sequence.
  1365. */
  1366. private void mergeSOSNode(Node node) throws IIOInvalidTreeException {
  1367. SOSMarkerSegment firstSOS =
  1368. (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
  1369. SOSMarkerSegment lastSOS =
  1370. (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, false);
  1371. if (firstSOS != null) {
  1372. if (firstSOS != lastSOS) {
  1373. throw new IIOInvalidTreeException
  1374. ("Can't merge SOS node into a tree with > 1 SOS node", node);
  1375. }
  1376. firstSOS.updateFromNativeNode(node, false);
  1377. } else {
  1378. markerSequence.add(new SOSMarkerSegment(node));
  1379. }
  1380. }
  1381. private boolean transparencyDone;
  1382. private void mergeStandardTree(Node root) throws IIOInvalidTreeException {
  1383. transparencyDone = false;
  1384. NodeList children = root.getChildNodes();
  1385. for (int i = 0; i < children.getLength(); i++) {
  1386. Node node = children.item(i);
  1387. String name = node.getNodeName();
  1388. if (name.equals("Chroma")) {
  1389. mergeStandardChromaNode(node, children);
  1390. } else if (name.equals("Compression")) {
  1391. mergeStandardCompressionNode(node);
  1392. } else if (name.equals("Data")) {
  1393. mergeStandardDataNode(node);
  1394. } else if (name.equals("Dimension")) {
  1395. mergeStandardDimensionNode(node);
  1396. } else if (name.equals("Document")) {
  1397. mergeStandardDocumentNode(node);
  1398. } else if (name.equals("Text")) {
  1399. mergeStandardTextNode(node);
  1400. } else if (name.equals("Transparency")) {
  1401. mergeStandardTransparencyNode(node);
  1402. } else {
  1403. throw new IIOInvalidTreeException("Invalid node: " + name, node);
  1404. }
  1405. }
  1406. }
  1407. /*
  1408. * In general, it could be possible to convert all non-pixel data to some
  1409. * textual form and include it in comments, but then this would create the
  1410. * expectation that these comment forms be recognized by the reader, thus
  1411. * creating a defacto extension to JPEG metadata capabilities. This is
  1412. * probably best avoided, so the following convert only text nodes to
  1413. * comments, and lose the keywords as well.
  1414. */
  1415. private void mergeStandardChromaNode(Node node, NodeList siblings)
  1416. throws IIOInvalidTreeException {
  1417. // ColorSpaceType can change the target colorspace for compression
  1418. // This must take any transparency node into account as well, as
  1419. // that affects the number of channels (if alpha is present). If
  1420. // a transparency node is dealt with here, set a flag to indicate
  1421. // this to the transparency processor below. If we discover that
  1422. // the nodes are not in order, throw an exception as the tree is
  1423. // invalid.
  1424. if (transparencyDone) {
  1425. throw new IIOInvalidTreeException
  1426. ("Transparency node must follow Chroma node", node);
  1427. }
  1428. Node csType = node.getFirstChild();
  1429. if ((csType == null) || !csType.getNodeName().equals("ColorSpaceType")) {
  1430. // If there is no ColorSpaceType node, we have nothing to do
  1431. return;
  1432. }
  1433. String csName = csType.getAttributes().getNamedItem("name").getNodeValue();
  1434. int numChannels = 0;
  1435. boolean wantJFIF = false;
  1436. boolean wantAdobe = false;
  1437. int transform = 0;
  1438. boolean willSubsample = false;
  1439. byte [] ids = {1, 2, 3, 4}; // JFIF compatible
  1440. if (csName.equals("GRAY")) {
  1441. numChannels = 1;
  1442. wantJFIF = true;
  1443. } else if (csName.equals("YCbCr")) {
  1444. numChannels = 3;
  1445. wantJFIF = true;
  1446. willSubsample = true;
  1447. } else if (csName.equals("PhotoYCC")) {
  1448. numChannels = 3;
  1449. wantAdobe = true;
  1450. transform = JPEG.ADOBE_YCC;
  1451. ids[0] = (byte) 'Y';
  1452. ids[1] = (byte) 'C';
  1453. ids[2] = (byte) 'c';
  1454. } else if (csName.equals("RGB")) {
  1455. numChannels = 3;
  1456. wantAdobe = true;
  1457. transform = JPEG.ADOBE_UNKNOWN;
  1458. ids[0] = (byte) 'R';
  1459. ids[1] = (byte) 'G';
  1460. ids[2] = (byte) 'B';
  1461. } else if ((csName.equals("XYZ"))
  1462. || (csName.equals("Lab"))
  1463. || (csName.equals("Luv"))
  1464. || (csName.equals("YxY"))
  1465. || (csName.equals("HSV"))
  1466. || (csName.equals("HLS"))
  1467. || (csName.equals("CMY"))
  1468. || (csName.equals("3CLR"))) {
  1469. numChannels = 3;
  1470. } else if (csName.equals("YCCK")) {
  1471. numChannels = 4;
  1472. wantAdobe = true;
  1473. transform = JPEG.ADOBE_YCCK;
  1474. willSubsample = true;
  1475. } else if (csName.equals("CMYK")) {
  1476. numChannels = 4;
  1477. wantAdobe = true;
  1478. transform = JPEG.ADOBE_UNKNOWN;
  1479. } else if (csName.equals("4CLR")) {
  1480. numChannels = 4;
  1481. } else { // We can't handle them, so don't modify any metadata
  1482. return;
  1483. }
  1484. boolean wantAlpha = false;
  1485. for (int i = 0; i < siblings.getLength(); i++) {
  1486. Node trans = siblings.item(i);
  1487. if (trans.getNodeName().equals("Transparency")) {
  1488. wantAlpha = wantAlpha(trans);
  1489. break; // out of for
  1490. }
  1491. }
  1492. if (wantAlpha) {
  1493. numChannels++;
  1494. wantJFIF = false;
  1495. if (ids[0] == (byte) 'R') {
  1496. ids[3] = (byte) 'A';
  1497. wantAdobe = false;
  1498. }
  1499. }
  1500. JFIFMarkerSegment jfif =
  1501. (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
  1502. AdobeMarkerSegment adobe =
  1503. (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class, true);
  1504. SOFMarkerSegment sof =
  1505. (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
  1506. SOSMarkerSegment sos =
  1507. (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class, true);
  1508. // If the metadata specifies progressive, then the number of channels
  1509. // must match, so that we can modify all the existing SOS marker segments.
  1510. // If they don't match, we don't know what to do with SOS so we can't do
  1511. // the merge. We then just return silently.
  1512. // An exception would not be appropriate. A warning might, but we have
  1513. // nowhere to send it to.
  1514. if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
  1515. if ((sof.componentSpecs.length != numChannels) && (sos != null)) {
  1516. return;
  1517. }
  1518. }
  1519. // JFIF header might be removed
  1520. if (!wantJFIF && (jfif != null)) {
  1521. markerSequence.remove(jfif);
  1522. }
  1523. // Now add a JFIF if we do want one, but only if it isn't stream metadata
  1524. if (wantJFIF && !isStream) {
  1525. markerSequence.add(0, new JFIFMarkerSegment());
  1526. }
  1527. // Adobe header might be removed or the transform modified, if it isn't
  1528. // stream metadata
  1529. if (wantAdobe) {
  1530. if ((adobe == null) && !isStream) {
  1531. adobe = new AdobeMarkerSegment(transform);
  1532. insertAdobeMarkerSegment(adobe);
  1533. } else {
  1534. adobe.transform = transform;
  1535. }
  1536. } else if (adobe != null) {
  1537. markerSequence.remove(adobe);
  1538. }
  1539. boolean updateQtables = false;
  1540. boolean updateHtables = false;
  1541. boolean progressive = false;
  1542. int [] subsampledSelectors = {0, 1, 1, 0 } ;
  1543. int [] nonSubsampledSelectors = { 0, 0, 0, 0};
  1544. int [] newTableSelectors = willSubsample
  1545. ? subsampledSelectors
  1546. : nonSubsampledSelectors;
  1547. // Keep the old componentSpecs array
  1548. SOFMarkerSegment.ComponentSpec [] oldCompSpecs = null;
  1549. // SOF might be modified
  1550. if (sof != null) {
  1551. oldCompSpecs = sof.componentSpecs;
  1552. progressive = (sof.tag == JPEG.SOF2);
  1553. // Now replace the SOF with a new one; it might be the same, but
  1554. // this is easier.
  1555. markerSequence.set(markerSequence.indexOf(sof),
  1556. new SOFMarkerSegment(progressive,
  1557. false, // we never need extended
  1558. willSubsample,
  1559. ids,
  1560. numChannels));
  1561. // Now suss out if subsampling changed and set the boolean for
  1562. // updating the q tables
  1563. // if the old componentSpec q table selectors don't match
  1564. // the new ones, update the qtables. The new selectors are already
  1565. // in place in the new SOF segment above.
  1566. for (int i = 0; i < oldCompSpecs.length; i++) {
  1567. if (oldCompSpecs[i].QtableSelector != newTableSelectors[i]) {
  1568. updateQtables = true;
  1569. }
  1570. }
  1571. if (progressive) {
  1572. // if the component ids are different, update all the existing scans
  1573. // ignore Huffman tables
  1574. boolean idsDiffer = false;
  1575. for (int i = 0; i < oldCompSpecs.length; i++) {
  1576. if (ids[i] != oldCompSpecs[i].componentId) {
  1577. idsDiffer = true;
  1578. }
  1579. }
  1580. if (idsDiffer) {
  1581. // update the ids in each SOS marker segment
  1582. for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
  1583. MarkerSegment seg = (MarkerSegment) iter.next();
  1584. if (seg instanceof SOSMarkerSegment) {
  1585. SOSMarkerSegment target = (SOSMarkerSegment) seg;
  1586. for (int i = 0; i < target.componentSpecs.length; i++) {
  1587. int oldSelector =
  1588. target.componentSpecs[i].componentSelector;
  1589. // Find the position in the old componentSpecs array
  1590. // of the old component with the old selector
  1591. // and replace the component selector with the
  1592. // new id at the same position, as these match
  1593. // the new component specs array in the SOF created
  1594. // above.
  1595. for (int j = 0; j < oldCompSpecs.length; j++) {
  1596. if (oldCompSpecs[j].componentId == oldSelector) {
  1597. target.componentSpecs[i].componentSelector =
  1598. ids[j];
  1599. }
  1600. }
  1601. }
  1602. }
  1603. }
  1604. }
  1605. } else {
  1606. if (sos != null) {
  1607. // htables - if the old htable selectors don't match the new ones,
  1608. // update the tables.
  1609. for (int i = 0; i < sos.componentSpecs.length; i++) {
  1610. if ((sos.componentSpecs[i].dcHuffTable
  1611. != newTableSelectors[i])
  1612. || (sos.componentSpecs[i].acHuffTable
  1613. != newTableSelectors[i])) {
  1614. updateHtables = true;
  1615. }
  1616. }
  1617. // Might be the same as the old one, but this is easier.
  1618. markerSequence.set(markerSequence.indexOf(sos),
  1619. new SOSMarkerSegment(willSubsample,
  1620. ids,
  1621. numChannels));
  1622. }
  1623. }
  1624. } else {
  1625. // should be stream metadata if there isn't an SOF, but check it anyway
  1626. if (isStream) {
  1627. // update tables - routines below check if it's really necessary
  1628. updateQtables = true;
  1629. updateHtables = true;
  1630. }
  1631. }
  1632. if (updateQtables) {
  1633. List tableSegments = new ArrayList();
  1634. for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
  1635. MarkerSegment seg = (MarkerSegment) iter.next();
  1636. if (seg instanceof DQTMarkerSegment) {
  1637. tableSegments.add(seg);
  1638. }
  1639. }
  1640. // If there are no tables, don't add them, as the metadata encodes an
  1641. // abbreviated stream.
  1642. // If we are not subsampling, we just need one, so don't do anything
  1643. if (!tableSegments.isEmpty() && willSubsample) {
  1644. // Is it really necessary? There should be at least 2 tables.
  1645. // If there is only one, assume it's a scaled "standard"
  1646. // luminance table, extract the scaling factor, and generate a
  1647. // scaled "standard" chrominance table.
  1648. // Find the table with selector 1.
  1649. boolean found = false;
  1650. for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
  1651. DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
  1652. for (Iterator tabiter = testdqt.tables.iterator();
  1653. tabiter.hasNext();) {
  1654. DQTMarkerSegment.Qtable tab =
  1655. (DQTMarkerSegment.Qtable) tabiter.next();
  1656. if (tab.tableID == 1) {
  1657. found = true;
  1658. }
  1659. }
  1660. }
  1661. if (!found) {
  1662. // find the table with selector 0. There should be one.
  1663. DQTMarkerSegment.Qtable table0 = null;
  1664. for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
  1665. DQTMarkerSegment testdqt = (DQTMarkerSegment) iter.next();
  1666. for (Iterator tabiter = testdqt.tables.iterator();
  1667. tabiter.hasNext();) {
  1668. DQTMarkerSegment.Qtable tab =
  1669. (DQTMarkerSegment.Qtable) tabiter.next();
  1670. if (tab.tableID == 0) {
  1671. table0 = tab;
  1672. }
  1673. }
  1674. }
  1675. // Assuming that the table with id 0 is a luminance table,
  1676. // compute a new chrominance table of the same quality and
  1677. // add it to the last DQT segment
  1678. DQTMarkerSegment dqt =
  1679. (DQTMarkerSegment) tableSegments.get(tableSegments.size()-1);
  1680. dqt.tables.add(dqt.getChromaForLuma(table0));
  1681. }
  1682. }
  1683. }
  1684. if (updateHtables) {
  1685. List tableSegments = new ArrayList();
  1686. for (Iterator iter = markerSequence.iterator(); iter.hasNext();) {
  1687. MarkerSegment seg = (MarkerSegment) iter.next();
  1688. if (seg instanceof DHTMarkerSegment) {
  1689. tableSegments.add(seg);
  1690. }
  1691. }
  1692. // If there are no tables, don't add them, as the metadata encodes an
  1693. // abbreviated stream.
  1694. // If we are not subsampling, we just need one, so don't do anything
  1695. if (!tableSegments.isEmpty() && willSubsample) {
  1696. // Is it really necessary? There should be at least 2 dc and 2 ac
  1697. // tables. If there is only one, add a
  1698. // "standard " chrominance table.
  1699. // find a table with selector 1. AC/DC is irrelevant
  1700. boolean found = false;
  1701. for (Iterator iter = tableSegments.iterator(); iter.hasNext();) {
  1702. DHTMarkerSegment testdht = (DHTMarkerSegment) iter.next();
  1703. for (Iterator tabiter = testdht.tables.iterator();
  1704. tabiter.hasNext();) {
  1705. DHTMarkerSegment.Htable tab =
  1706. (DHTMarkerSegment.Htable) tabiter.next();
  1707. if (tab.tableID == 1) {
  1708. found = true;
  1709. }
  1710. }
  1711. }
  1712. if (!found) {
  1713. // Create new standard dc and ac chrominance tables and add them
  1714. // to the last DHT segment
  1715. DHTMarkerSegment lastDHT =
  1716. (DHTMarkerSegment) tableSegments.get(tableSegments.size()-1);
  1717. lastDHT.addHtable(JPEGHuffmanTable.StdDCLuminance, true, 1);
  1718. lastDHT.addHtable(JPEGHuffmanTable.StdACLuminance, true, 1);
  1719. }
  1720. }
  1721. }
  1722. }
  1723. private boolean wantAlpha(Node transparency) {
  1724. boolean returnValue = false;
  1725. Node alpha = transparency.getFirstChild(); // Alpha must be first if present
  1726. if (alpha.getNodeName().equals("Alpha")) {
  1727. if (alpha.hasAttributes()) {
  1728. String value =
  1729. alpha.getAttributes().getNamedItem("value").getNodeValue();
  1730. if (!value.equals("none")) {
  1731. returnValue = true;
  1732. }
  1733. }
  1734. }
  1735. transparencyDone = true;
  1736. return returnValue;
  1737. }
  1738. private void mergeStandardCompressionNode(Node node)
  1739. throws IIOInvalidTreeException {
  1740. // NumProgressiveScans is ignored. Progression must be enabled on the
  1741. // ImageWriteParam.
  1742. // No-op
  1743. }
  1744. private void mergeStandardDataNode(Node node)
  1745. throws IIOInvalidTreeException {
  1746. // No-op
  1747. }
  1748. private void mergeStandardDimensionNode(Node node)
  1749. throws IIOInvalidTreeException {
  1750. // Pixel Aspect Ratio or pixel size can be incorporated if there is,
  1751. // or can be, a JFIF segment
  1752. JFIFMarkerSegment jfif =
  1753. (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class, true);
  1754. if (jfif == null) {
  1755. // Can there be one?
  1756. // Criteria:
  1757. // SOF must be present with 1 or 3 channels, (stream metadata fails this)
  1758. // Component ids must be JFIF compatible.
  1759. boolean canHaveJFIF = false;
  1760. SOFMarkerSegment sof =
  1761. (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class, true);
  1762. if (sof != null) {
  1763. int numChannels = sof.componentSpecs.length;
  1764. if ((numChannels == 1) || (numChannels == 3)) {
  1765. canHaveJFIF = true; // remaining tests are negative
  1766. for (int i = 0; i < sof.componentSpecs.length; i++) {
  1767. if (sof.componentSpecs[i].componentId != i+1)
  1768. canHaveJFIF = false;
  1769. }
  1770. // if Adobe present, transform = ADOBE_UNKNOWN for 1-channel,
  1771. // ADOBE_YCC for 3-channel.
  1772. AdobeMarkerSegment adobe =
  1773. (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
  1774. true);
  1775. if (adobe != null) {
  1776. if (adobe.transform != ((numChannels == 1)
  1777. ? JPEG.ADOBE_UNKNOWN
  1778. : JPEG.ADOBE_YCC)) {
  1779. canHaveJFIF = false;
  1780. }
  1781. }
  1782. }
  1783. }
  1784. // If so, create one and insert it into the sequence. Note that
  1785. // default is just pixel ratio at 1:1
  1786. if (canHaveJFIF) {
  1787. jfif = new JFIFMarkerSegment();
  1788. markerSequence.add(0, jfif);
  1789. }
  1790. }
  1791. if (jfif != null) {
  1792. NodeList children = node.getChildNodes();
  1793. for (int i = 0; i < children.getLength(); i++) {
  1794. Node child = children.item(i);
  1795. NamedNodeMap attrs = child.getAttributes();
  1796. String name = child.getNodeName();
  1797. if (name.equals("PixelAspectRatio")) {
  1798. String valueString = attrs.getNamedItem("value").getNodeValue();
  1799. float value = Float.parseFloat(valueString);
  1800. Point p = findIntegerRatio(value);
  1801. jfif.resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;
  1802. jfif.Xdensity = p.x;
  1803. jfif.Xdensity = p.y;
  1804. } else if (name.equals("HorizontalPixelSize")) {
  1805. String valueString = attrs.getNamedItem("value").getNodeValue();
  1806. float value = Float.parseFloat(valueString);
  1807. // Convert from mm/dot to dots/cm
  1808. int dpcm = (int) Math.round(1.0/(value*10.0));
  1809. jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
  1810. jfif.Xdensity = dpcm;
  1811. } else if (name.equals("VerticalPixelSize")) {
  1812. String valueString = attrs.getNamedItem("value").getNodeValue();
  1813. float value = Float.parseFloat(valueString);
  1814. // Convert from mm/dot to dots/cm
  1815. int dpcm = (int) Math.round(1.0/(value*10.0));
  1816. jfif.resUnits = JPEG.DENSITY_UNIT_DOTS_CM;
  1817. jfif.Ydensity = dpcm;
  1818. }
  1819. }
  1820. }
  1821. }
  1822. /*
  1823. * Return a pair of integers whose ratio (x/y) approximates the given
  1824. * float value.
  1825. */
  1826. private static Point findIntegerRatio(float value) {
  1827. float epsilon = 0.005F;
  1828. // Normalize
  1829. value = Math.abs(value);
  1830. // Deal with min case
  1831. if (value <= epsilon) {
  1832. return new Point(1, 255);
  1833. }
  1834. // Deal with max case
  1835. if (value >= 255) {
  1836. return new Point(255, 1);
  1837. }
  1838. // Remember if we invert
  1839. boolean inverted = false;
  1840. if (value < 1.0) {
  1841. value = 1.0Fvalue;
  1842. inverted = true;
  1843. }
  1844. // First approximation
  1845. int y = 1;
  1846. int x = (int) Math.round(value);
  1847. float ratio = (float) x;
  1848. float delta = Math.abs(value - ratio);
  1849. while (delta > epsilon) { // not close enough
  1850. // Increment y and compute a new x
  1851. y++;
  1852. x = (int) Math.round(y*value);
  1853. ratio = (float)x(float)y;
  1854. delta = Math.abs(value - ratio);
  1855. }
  1856. return inverted ? new Point(y, x) : new Point(x, y);
  1857. }
  1858. private void mergeStandardDocumentNode(Node node)
  1859. throws IIOInvalidTreeException {
  1860. // No-op
  1861. }
  1862. private void mergeStandardTextNode(Node node)
  1863. throws IIOInvalidTreeException {
  1864. // Convert to comments. For the moment ignore the encoding issue.
  1865. // Ignore keywords, language, and encoding (for the moment).
  1866. // If compression tag is present, use only entries with "none".
  1867. NodeList children = node.getChildNodes();
  1868. for (int i = 0; i < children.getLength(); i++) {
  1869. Node child = children.item(i);
  1870. NamedNodeMap attrs = child.getAttributes();
  1871. Node comp = attrs.getNamedItem("compression");
  1872. boolean copyIt = true;
  1873. if (comp != null) {
  1874. String compString = comp.getNodeValue();
  1875. if (!compString.equals("none")) {
  1876. copyIt = false;
  1877. }
  1878. }
  1879. if (copyIt) {
  1880. String value = attrs.getNamedItem("value").getNodeValue();
  1881. COMMarkerSegment com = new COMMarkerSegment(value);
  1882. insertCOMMarkerSegment(com);
  1883. }
  1884. }
  1885. }
  1886. private void mergeStandardTransparencyNode(Node node)
  1887. throws IIOInvalidTreeException {
  1888. // This might indicate that an alpha channel is being added or removed.
  1889. // The nodes must appear in order, and a Chroma node will process any
  1890. // transparency, so process it here only if there was no Chroma node
  1891. // Do nothing for stream metadata
  1892. if (!transparencyDone && !isStream) {
  1893. boolean wantAlpha = wantAlpha(node);
  1894. // do we have alpha already? If the number of channels is 2 or 4,
  1895. // we do, as we don't support CMYK, nor can we add alpha to it
  1896. // The number of channels can be determined from the SOF
  1897. JFIFMarkerSegment jfif = (JFIFMarkerSegment) findMarkerSegment
  1898. (JFIFMarkerSegment.class, true);
  1899. AdobeMarkerSegment adobe = (AdobeMarkerSegment) findMarkerSegment
  1900. (AdobeMarkerSegment.class, true);
  1901. SOFMarkerSegment sof = (SOFMarkerSegment) findMarkerSegment
  1902. (SOFMarkerSegment.class, true);
  1903. SOSMarkerSegment sos = (SOSMarkerSegment) findMarkerSegment
  1904. (SOSMarkerSegment.class, true);
  1905. // We can do nothing for progressive, as we don't know how to
  1906. // modify the scans.
  1907. if ((sof != null) && (sof.tag == JPEG.SOF2)) { // Progressive
  1908. return;
  1909. }
  1910. // Do we already have alpha? We can tell by the number of channels
  1911. // We must have an sof, or we can't do anything further
  1912. if (sof != null) {
  1913. int numChannels = sof.componentSpecs.length;
  1914. boolean hadAlpha = (numChannels == 2) || (numChannels == 4);
  1915. // proceed only if the old state and the new state differ
  1916. if (hadAlpha != wantAlpha) {
  1917. if (wantAlpha) { // Adding alpha
  1918. numChannels++;
  1919. if (jfif != null) {
  1920. markerSequence.remove(jfif);
  1921. }
  1922. // If an adobe marker is present, transform must be UNKNOWN
  1923. if (adobe != null) {
  1924. adobe.transform = JPEG.ADOBE_UNKNOWN;
  1925. }
  1926. // Add a component spec with appropriate parameters to SOF
  1927. SOFMarkerSegment.ComponentSpec [] newSpecs =
  1928. new SOFMarkerSegment.ComponentSpec[numChannels];
  1929. for (int i = 0; i < sof.componentSpecs.length; i++) {
  1930. newSpecs[i] = sof.componentSpecs[i];
  1931. }
  1932. byte oldFirstID = (byte) sof.componentSpecs[0].componentId;
  1933. byte newID = (byte) ((oldFirstID > 1) ? 'A' : 4);
  1934. newSpecs[numChannels-1] =
  1935. sof.getComponentSpec(newID,
  1936. sof.componentSpecs[0].HsamplingFactor,
  1937. sof.componentSpecs[0].QtableSelector);
  1938. sof.componentSpecs = newSpecs;
  1939. // Add a component spec with appropriate parameters to SOS
  1940. SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
  1941. new SOSMarkerSegment.ScanComponentSpec [numChannels];
  1942. for (int i = 0; i < sos.componentSpecs.length; i++) {
  1943. newScanSpecs[i] = sos.componentSpecs[i];
  1944. }
  1945. newScanSpecs[numChannels-1] =
  1946. sos.getScanComponentSpec (newID, 0);
  1947. sos.componentSpecs = newScanSpecs;
  1948. } else { // Removing alpha
  1949. numChannels--;
  1950. // Remove a component spec from SOF
  1951. SOFMarkerSegment.ComponentSpec [] newSpecs =
  1952. new SOFMarkerSegment.ComponentSpec[numChannels];
  1953. for (int i = 0; i < numChannels; i++) {
  1954. newSpecs[i] = sof.componentSpecs[i];
  1955. }
  1956. sof.componentSpecs = newSpecs;
  1957. // Remove a component spec from SOS
  1958. SOSMarkerSegment.ScanComponentSpec [] newScanSpecs =
  1959. new SOSMarkerSegment.ScanComponentSpec [numChannels];
  1960. for (int i = 0; i < numChannels; i++) {
  1961. newScanSpecs[i] = sos.componentSpecs[i];
  1962. }
  1963. sos.componentSpecs = newScanSpecs;
  1964. }
  1965. }
  1966. }
  1967. }
  1968. }
  1969. public void setFromTree(String formatName, Node root)
  1970. throws IIOInvalidTreeException {
  1971. if (formatName == null) {
  1972. throw new IllegalArgumentException("null formatName!");
  1973. }
  1974. if (root == null) {
  1975. throw new IllegalArgumentException("null root!");
  1976. }
  1977. if (isStream &&
  1978. (formatName.equals(JPEG.nativeStreamMetadataFormatName))) {
  1979. setFromNativeTree(root);
  1980. } else if (!isStream &&
  1981. (formatName.equals(JPEG.nativeImageMetadataFormatName))) {
  1982. setFromNativeTree(root);
  1983. } else if (!isStream &&
  1984. (formatName.equals
  1985. (IIOMetadataFormatImpl.standardMetadataFormatName))) {
  1986. // In this case a reset followed by a merge is correct
  1987. super.setFromTree(formatName, root);
  1988. } else {
  1989. throw new IllegalArgumentException("Unsupported format name: "
  1990. + formatName);
  1991. }
  1992. }
  1993. private void setFromNativeTree(Node root) throws IIOInvalidTreeException {
  1994. if (resetSequence == null) {
  1995. resetSequence = markerSequence;
  1996. }
  1997. markerSequence = new ArrayList();
  1998. // Build a whole new marker sequence from the tree
  1999. String name = root.getNodeName();
  2000. if (name != ((isStream) ? JPEG.nativeStreamMetadataFormatName
  2001. : JPEG.nativeImageMetadataFormatName)) {
  2002. throw new IIOInvalidTreeException("Invalid root node name: " + name,
  2003. root);
  2004. }
  2005. if (!isStream) {
  2006. if (root.getChildNodes().getLength() != 2) { // JPEGvariety and markerSequence
  2007. throw new IIOInvalidTreeException(
  2008. "JPEGvariety and markerSequence nodes must be present", root);
  2009. }
  2010. Node JPEGvariety = root.getFirstChild();
  2011. if (JPEGvariety.getChildNodes().getLength() != 0) {
  2012. markerSequence.add(new JFIFMarkerSegment(JPEGvariety.getFirstChild()));
  2013. }
  2014. }
  2015. Node markerSequenceNode = isStream ? root : root.getLastChild();
  2016. setFromMarkerSequenceNode(markerSequenceNode);
  2017. }
  2018. void setFromMarkerSequenceNode(Node markerSequenceNode)
  2019. throws IIOInvalidTreeException{
  2020. NodeList children = markerSequenceNode.getChildNodes();
  2021. // for all the children, add a marker segment
  2022. for (int i = 0; i < children.getLength(); i++) {
  2023. Node node = children.item(i);
  2024. String childName = node.getNodeName();
  2025. if (childName.equals("dqt")) {
  2026. markerSequence.add(new DQTMarkerSegment(node));
  2027. } else if (childName.equals("dht")) {
  2028. markerSequence.add(new DHTMarkerSegment(node));
  2029. } else if (childName.equals("dri")) {
  2030. markerSequence.add(new DRIMarkerSegment(node));
  2031. } else if (childName.equals("com")) {
  2032. markerSequence.add(new COMMarkerSegment(node));
  2033. } else if (childName.equals("app14Adobe")) {
  2034. markerSequence.add(new AdobeMarkerSegment(node));
  2035. } else if (childName.equals("unknown")) {
  2036. markerSequence.add(new MarkerSegment(node));
  2037. } else if (childName.equals("sof")) {
  2038. markerSequence.add(new SOFMarkerSegment(node));
  2039. } else if (childName.equals("sos")) {
  2040. markerSequence.add(new SOSMarkerSegment(node));
  2041. } else {
  2042. throw new IIOInvalidTreeException("Invalid "
  2043. + (isStream ? "stream " : "image ") + "child: "
  2044. + childName, node);
  2045. }
  2046. }
  2047. }
  2048. /**
  2049. * Check that this metadata object is in a consistent state and
  2050. * return <code>true</code> if it is or <code>false</code>
  2051. * otherwise. All the constructors and modifiers should call
  2052. * this method at the end to guarantee that the data is always
  2053. * consistent, as the writer relies on this.
  2054. */
  2055. private boolean isConsistent() {
  2056. SOFMarkerSegment sof =
  2057. (SOFMarkerSegment) findMarkerSegment(SOFMarkerSegment.class,
  2058. true);
  2059. JFIFMarkerSegment jfif =
  2060. (JFIFMarkerSegment) findMarkerSegment(JFIFMarkerSegment.class,
  2061. true);
  2062. AdobeMarkerSegment adobe =
  2063. (AdobeMarkerSegment) findMarkerSegment(AdobeMarkerSegment.class,
  2064. true);
  2065. boolean retval = true;
  2066. if (!isStream) {
  2067. if (sof != null) {
  2068. // SOF numBands = total scan bands
  2069. int numSOFBands = sof.componentSpecs.length;
  2070. int numScanBands = countScanBands();
  2071. if (numScanBands != 0) { // No SOS is OK
  2072. if (numScanBands != numSOFBands) {
  2073. retval = false;
  2074. }
  2075. }
  2076. // If JFIF is present, component ids are 1-3, bands are 1 or 3
  2077. if (jfif != null) {
  2078. if ((numSOFBands != 1) && (numSOFBands != 3)) {
  2079. retval = false;
  2080. }
  2081. for (int i = 0; i < numSOFBands; i++) {
  2082. if (sof.componentSpecs[i].componentId != i+1) {
  2083. retval = false;
  2084. }
  2085. }
  2086. // If both JFIF and Adobe are present,
  2087. // Adobe transform == unknown for gray,
  2088. // YCC for 3-chan.
  2089. if ((adobe != null)
  2090. && (((numSOFBands == 1)
  2091. && (adobe.transform != JPEG.ADOBE_UNKNOWN))
  2092. || ((numSOFBands == 3)
  2093. && (adobe.transform != JPEG.ADOBE_YCC)))) {
  2094. retval = false;
  2095. }
  2096. }
  2097. } else {
  2098. // stream can't have jfif, adobe, sof, or sos
  2099. SOSMarkerSegment sos =
  2100. (SOSMarkerSegment) findMarkerSegment(SOSMarkerSegment.class,
  2101. true);
  2102. if ((jfif != null) || (adobe != null)
  2103. || (sof != null) || (sos != null)) {
  2104. retval = false;
  2105. }
  2106. }
  2107. }
  2108. return retval;
  2109. }
  2110. /**
  2111. * Returns the total number of bands referenced in all SOS marker
  2112. * segments, including 0 if there are no SOS marker segments.
  2113. */
  2114. private int countScanBands() {
  2115. List ids = new ArrayList();
  2116. Iterator iter = markerSequence.iterator();
  2117. while(iter.hasNext()) {
  2118. MarkerSegment seg = (MarkerSegment)iter.next();
  2119. if (seg instanceof SOSMarkerSegment) {
  2120. SOSMarkerSegment sos = (SOSMarkerSegment) seg;
  2121. SOSMarkerSegment.ScanComponentSpec [] specs = sos.componentSpecs;
  2122. for (int i = 0; i < specs.length; i++) {
  2123. Integer id = new Integer(specs[i].componentSelector);
  2124. if (!ids.contains(id)) {
  2125. ids.add(id);
  2126. }
  2127. }
  2128. }
  2129. }
  2130. return ids.size();
  2131. }
  2132. ///// Writer support
  2133. void writeToStream(ImageOutputStream ios,
  2134. boolean ignoreJFIF,
  2135. boolean forceJFIF,
  2136. List thumbnails,
  2137. ICC_Profile iccProfile,
  2138. boolean ignoreAdobe,
  2139. int newAdobeTransform,
  2140. JPEGImageWriter writer)
  2141. throws IOException {
  2142. if (forceJFIF) {
  2143. // Write a default JFIF segment, including thumbnails
  2144. // This won't be duplicated below because forceJFIF will be
  2145. // set only if there is no JFIF present already.
  2146. JFIFMarkerSegment.writeDefaultJFIF(ios,
  2147. thumbnails,
  2148. iccProfile,
  2149. writer);
  2150. if ((ignoreAdobe == false)
  2151. && (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE)) {
  2152. if ((newAdobeTransform != JPEG.ADOBE_UNKNOWN)
  2153. && (newAdobeTransform != JPEG.ADOBE_YCC)) {
  2154. // Not compatible, so ignore Adobe.
  2155. ignoreAdobe = true;
  2156. writer.warningOccurred
  2157. (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
  2158. }
  2159. }
  2160. }
  2161. // Iterate over each MarkerSegment
  2162. Iterator iter = markerSequence.iterator();
  2163. while(iter.hasNext()) {
  2164. MarkerSegment seg = (MarkerSegment)iter.next();
  2165. if (seg instanceof JFIFMarkerSegment) {
  2166. if (ignoreJFIF == false) {
  2167. JFIFMarkerSegment jfif = (JFIFMarkerSegment) seg;
  2168. jfif.writeWithThumbs(ios, thumbnails, writer);
  2169. if (iccProfile != null) {
  2170. JFIFMarkerSegment.writeICC(iccProfile, ios);
  2171. }
  2172. } // Otherwise ignore it, as requested
  2173. } else if (seg instanceof AdobeMarkerSegment) {
  2174. if (ignoreAdobe == false) {
  2175. if (newAdobeTransform != JPEG.ADOBE_IMPOSSIBLE) {
  2176. AdobeMarkerSegment newAdobe =
  2177. (AdobeMarkerSegment) seg.clone();
  2178. newAdobe.transform = newAdobeTransform;
  2179. newAdobe.write(ios);
  2180. } else if (forceJFIF) {
  2181. // If adobe isn't JFIF compatible, ignore it
  2182. AdobeMarkerSegment adobe = (AdobeMarkerSegment) seg;
  2183. if ((adobe.transform == JPEG.ADOBE_UNKNOWN)
  2184. || (adobe.transform == JPEG.ADOBE_YCC)) {
  2185. adobe.write(ios);
  2186. } else {
  2187. writer.warningOccurred
  2188. (JPEGImageWriter.WARNING_METADATA_ADJUSTED_FOR_THUMB);
  2189. }
  2190. } else {
  2191. seg.write(ios);
  2192. }
  2193. } // Otherwise ignore it, as requested
  2194. } else {
  2195. seg.write(ios);
  2196. }
  2197. }
  2198. }
  2199. //// End of writer support
  2200. public void reset() {
  2201. if (resetSequence != null) { // Otherwise no need to reset
  2202. markerSequence = resetSequence;
  2203. resetSequence = null;
  2204. }
  2205. }
  2206. public void print() {
  2207. for (int i = 0; i < markerSequence.size(); i++) {
  2208. MarkerSegment seg = (MarkerSegment) markerSequence.get(i);
  2209. seg.print();
  2210. }
  2211. }
  2212. }