1. /*
  2. * @(#)JPEGImageWriter.java 1.33 03/10/01
  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.IIOException;
  9. import javax.imageio.ImageWriter;
  10. import javax.imageio.ImageWriteParam;
  11. import javax.imageio.IIOImage;
  12. import javax.imageio.ImageTypeSpecifier;
  13. import javax.imageio.metadata.IIOMetadata;
  14. import javax.imageio.metadata.IIOMetadataFormatImpl;
  15. import javax.imageio.metadata.IIOInvalidTreeException;
  16. import javax.imageio.spi.ImageWriterSpi;
  17. import javax.imageio.stream.ImageOutputStream;
  18. import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
  19. import javax.imageio.plugins.jpeg.JPEGQTable;
  20. import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
  21. import org.w3c.dom.Node;
  22. import java.awt.image.Raster;
  23. import java.awt.image.WritableRaster;
  24. import java.awt.image.SampleModel;
  25. import java.awt.image.DataBuffer;
  26. import java.awt.image.DataBufferByte;
  27. import java.awt.image.ColorModel;
  28. import java.awt.image.IndexColorModel;
  29. import java.awt.image.ColorConvertOp;
  30. import java.awt.image.RenderedImage;
  31. import java.awt.image.BufferedImage;
  32. import java.awt.color.ColorSpace;
  33. import java.awt.color.ICC_ColorSpace;
  34. import java.awt.color.ICC_Profile;
  35. import java.awt.Dimension;
  36. import java.awt.Rectangle;
  37. import java.awt.Transparency;
  38. import java.io.IOException;
  39. import java.util.List;
  40. import java.util.ArrayList;
  41. import java.util.Iterator;
  42. import sun.java2d.Disposer;
  43. import sun.java2d.DisposerRecord;
  44. public class JPEGImageWriter extends ImageWriter {
  45. ///////// Private variables
  46. private boolean debug = false;
  47. /**
  48. * The following variable contains a pointer to the IJG library
  49. * structure for this reader. It is assigned in the constructor
  50. * and then is passed in to every native call. It is set to 0
  51. * by dispose to avoid disposing twice.
  52. */
  53. private long structPointer = 0;
  54. /** The output stream we write to */
  55. private ImageOutputStream ios = null;
  56. /** The Raster we will write from */
  57. private Raster srcRas = null;
  58. /** An intermediate Raster holding compressor-friendly data */
  59. private WritableRaster raster = null;
  60. /**
  61. * Set to true if we are writing an image with an
  62. * indexed ColorModel
  63. */
  64. private boolean indexed = false;
  65. private IndexColorModel indexCM = null;
  66. private boolean convertTosRGB = false; // Used by PhotoYCC only
  67. private WritableRaster converted = null;
  68. private boolean isAlphaPremultiplied = false;
  69. private ColorModel srcCM = null;
  70. /**
  71. * If there are thumbnails to be written, this is the list.
  72. */
  73. private List thumbnails = null;
  74. /**
  75. * If metadata should include an icc profile, store it here.
  76. */
  77. private ICC_Profile iccProfile = null;
  78. private int sourceXOffset = 0;
  79. private int sourceYOffset = 0;
  80. private int sourceWidth = 0;
  81. private int [] srcBands = null;
  82. private int sourceHeight = 0;
  83. /** Used when calling listeners */
  84. private int currentImage = 0;
  85. private ColorConvertOp convertOp = null;
  86. private JPEGQTable [] streamQTables = null;
  87. private JPEGHuffmanTable[] streamDCHuffmanTables = null;
  88. private JPEGHuffmanTable[] streamACHuffmanTables = null;
  89. // Parameters for writing metadata
  90. private boolean ignoreJFIF = false; // If it's there, use it
  91. private boolean forceJFIF = false; // Add one for the thumbnails
  92. private boolean ignoreAdobe = false; // If it's there, use it
  93. private int newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed
  94. private boolean writeDefaultJFIF = false;
  95. private boolean writeAdobe = false;
  96. private JPEGMetadata metadata = null;
  97. private boolean sequencePrepared = false;
  98. private int numScans = 0;
  99. /** The referent to be registered with the Disposer. */
  100. private Object disposerReferent = new Object();
  101. /** The DisposerRecord that handles the actual disposal of this writer. */
  102. private DisposerRecord disposerRecord;
  103. ///////// End of Private variables
  104. ///////// Protected variables
  105. protected static final int WARNING_DEST_IGNORED = 0;
  106. protected static final int WARNING_STREAM_METADATA_IGNORED = 1;
  107. protected static final int WARNING_DEST_METADATA_COMP_MISMATCH = 2;
  108. protected static final int WARNING_DEST_METADATA_JFIF_MISMATCH = 3;
  109. protected static final int WARNING_DEST_METADATA_ADOBE_MISMATCH = 4;
  110. protected static final int WARNING_IMAGE_METADATA_JFIF_MISMATCH = 5;
  111. protected static final int WARNING_IMAGE_METADATA_ADOBE_MISMATCH = 6;
  112. protected static final int WARNING_METADATA_NOT_JPEG_FOR_RASTER = 7;
  113. protected static final int WARNING_NO_BANDS_ON_INDEXED = 8;
  114. protected static final int WARNING_ILLEGAL_THUMBNAIL = 9;
  115. protected static final int WARNING_IGNORING_THUMBS = 10;
  116. protected static final int WARNING_FORCING_JFIF = 11;
  117. protected static final int WARNING_THUMB_CLIPPED = 12;
  118. protected static final int WARNING_METADATA_ADJUSTED_FOR_THUMB = 13;
  119. protected static final int WARNING_NO_RGB_THUMB_AS_INDEXED = 14;
  120. protected static final int WARNING_NO_GRAY_THUMB_AS_INDEXED = 15;
  121. private static final int MAX_WARNING = WARNING_NO_GRAY_THUMB_AS_INDEXED;
  122. ///////// End of Protected variables
  123. ///////// static initializer
  124. static {
  125. java.security.AccessController.doPrivileged(
  126. new sun.security.action.LoadLibraryAction("jpeg"));
  127. initWriterIDs(ImageOutputStream.class,
  128. JPEGQTable.class,
  129. JPEGHuffmanTable.class);
  130. }
  131. //////// Public API
  132. public JPEGImageWriter(ImageWriterSpi originator) {
  133. super(originator);
  134. structPointer = initJPEGImageWriter();
  135. disposerRecord = new JPEGWriterDisposerRecord(structPointer);
  136. Disposer.addRecord(disposerReferent, disposerRecord);
  137. }
  138. public void setOutput(Object output) {
  139. super.setOutput(output); // validates output
  140. resetInternalState();
  141. ios = (ImageOutputStream) output; // so this will always work
  142. // Set the native destination
  143. setDest(structPointer, ios);
  144. }
  145. public ImageWriteParam getDefaultWriteParam() {
  146. return new JPEGImageWriteParam(null);
  147. }
  148. public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
  149. return new JPEGMetadata(param, this);
  150. }
  151. public IIOMetadata
  152. getDefaultImageMetadata(ImageTypeSpecifier imageType,
  153. ImageWriteParam param) {
  154. return new JPEGMetadata(imageType, param, this);
  155. }
  156. public IIOMetadata convertStreamMetadata(IIOMetadata inData,
  157. ImageWriteParam param) {
  158. // There isn't much we can do. If it's one of ours, then
  159. // return it. Otherwise just return null. We use it only
  160. // for tables, so we can't get a default and modify it,
  161. // as this will usually not be what is intended.
  162. if (inData instanceof JPEGMetadata) {
  163. JPEGMetadata jpegData = (JPEGMetadata) inData;
  164. if (jpegData.isStream) {
  165. return inData;
  166. }
  167. }
  168. return null;
  169. }
  170. public IIOMetadata
  171. convertImageMetadata(IIOMetadata inData,
  172. ImageTypeSpecifier imageType,
  173. ImageWriteParam param) {
  174. // If it's one of ours, just return it
  175. if (inData instanceof JPEGMetadata) {
  176. JPEGMetadata jpegData = (JPEGMetadata) inData;
  177. if (!jpegData.isStream) {
  178. return inData;
  179. } else {
  180. // Can't convert stream metadata to image metadata
  181. // XXX Maybe this should put out a warning?
  182. return null;
  183. }
  184. }
  185. // If it's not one of ours, create a default and set it from
  186. // the standard tree from the input, if it exists.
  187. if (inData.isStandardMetadataFormatSupported()) {
  188. String formatName =
  189. IIOMetadataFormatImpl.standardMetadataFormatName;
  190. Node tree = inData.getAsTree(formatName);
  191. if (tree != null) {
  192. JPEGMetadata jpegData = new JPEGMetadata(imageType,
  193. param,
  194. this);
  195. try {
  196. jpegData.setFromTree(formatName, tree);
  197. } catch (IIOInvalidTreeException e) {
  198. // Other plug-in generates bogus standard tree
  199. // XXX Maybe this should put out a warning?
  200. return null;
  201. }
  202. return jpegData;
  203. }
  204. }
  205. return null;
  206. }
  207. public int getNumThumbnailsSupported(ImageTypeSpecifier imageType,
  208. ImageWriteParam param,
  209. IIOMetadata streamMetadata,
  210. IIOMetadata imageMetadata) {
  211. if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
  212. return Integer.MAX_VALUE;
  213. }
  214. return 0;
  215. }
  216. static final Dimension [] preferredThumbSizes = {new Dimension(1, 1),
  217. new Dimension(255, 255)};
  218. public Dimension[] getPreferredThumbnailSizes(ImageTypeSpecifier imageType,
  219. ImageWriteParam param,
  220. IIOMetadata streamMetadata,
  221. IIOMetadata imageMetadata) {
  222. if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
  223. return (Dimension [])preferredThumbSizes.clone();
  224. }
  225. return null;
  226. }
  227. private boolean jfifOK(ImageTypeSpecifier imageType,
  228. ImageWriteParam param,
  229. IIOMetadata streamMetadata,
  230. IIOMetadata imageMetadata) {
  231. // If the image type and metadata are JFIF compatible, return true
  232. if ((imageType != null) &&
  233. (!JPEG.isJFIFcompliant(imageType, true))) {
  234. return false;
  235. }
  236. if (imageMetadata != null) {
  237. JPEGMetadata metadata = null;
  238. if (imageMetadata instanceof JPEGMetadata) {
  239. metadata = (JPEGMetadata) imageMetadata;
  240. } else {
  241. metadata = (JPEGMetadata)convertImageMetadata(imageMetadata,
  242. imageType,
  243. param);
  244. }
  245. // metadata must have a jfif node
  246. if (metadata.findMarkerSegment
  247. (JFIFMarkerSegment.class, true) == null){
  248. return false;
  249. }
  250. }
  251. return true;
  252. }
  253. public boolean canWriteRasters() {
  254. return true;
  255. }
  256. public void write(IIOMetadata streamMetadata,
  257. IIOImage image,
  258. ImageWriteParam param) throws IOException {
  259. if (ios == null) {
  260. throw new IllegalStateException("Output has not been set!");
  261. }
  262. if (image == null) {
  263. throw new IllegalArgumentException("image is null!");
  264. }
  265. // if streamMetadata is not null, issue a warning
  266. if (streamMetadata != null) {
  267. warningOccurred(WARNING_STREAM_METADATA_IGNORED);
  268. }
  269. // Obtain the raster and image, if there is one
  270. boolean rasterOnly = image.hasRaster();
  271. RenderedImage rimage = null;
  272. if (rasterOnly) {
  273. srcRas = image.getRaster();
  274. } else {
  275. rimage = image.getRenderedImage();
  276. if (rimage instanceof BufferedImage) {
  277. srcRas = ((BufferedImage)rimage).getRaster();
  278. } else {
  279. // XXX - this makes a copy, which is memory-inefficient
  280. srcRas = rimage.getData();
  281. }
  282. }
  283. // Now determine if we are using a band subset
  284. // By default, we are using all source bands
  285. int numSrcBands = srcRas.getNumBands();
  286. indexed = false;
  287. indexCM = null;
  288. ColorModel cm = null;
  289. ColorSpace cs = null;
  290. isAlphaPremultiplied = false;
  291. srcCM = null;
  292. if (!rasterOnly) {
  293. cm = rimage.getColorModel();
  294. if (cm != null) {
  295. cs = cm.getColorSpace();
  296. if (cm instanceof IndexColorModel) {
  297. indexed = true;
  298. indexCM = (IndexColorModel) cm;
  299. numSrcBands = cm.getNumComponents();
  300. }
  301. if (cm.isAlphaPremultiplied()) {
  302. isAlphaPremultiplied = true;
  303. srcCM = cm;
  304. }
  305. }
  306. }
  307. srcBands = JPEG.bandOffsets[numSrcBands-1];
  308. int numBandsUsed = numSrcBands;
  309. // Consult the param to determine if we're writing a subset
  310. if (param != null) {
  311. int[] sBands = param.getSourceBands();
  312. if (sBands != null) {
  313. if (indexed) {
  314. warningOccurred(WARNING_NO_BANDS_ON_INDEXED);
  315. } else {
  316. srcBands = sBands;
  317. numBandsUsed = srcBands.length;
  318. if (numBandsUsed > numSrcBands) {
  319. throw new IIOException
  320. ("ImageWriteParam specifies too many source bands");
  321. }
  322. }
  323. }
  324. }
  325. boolean usingBandSubset = (numBandsUsed != numSrcBands);
  326. boolean fullImage = ((!rasterOnly) && (!usingBandSubset));
  327. int [] bandSizes = null;
  328. if (!indexed) {
  329. bandSizes = srcRas.getSampleModel().getSampleSize();
  330. // If this is a subset, we must adjust bandSizes
  331. if (usingBandSubset) {
  332. int [] temp = new int [numBandsUsed];
  333. for (int i = 0; i < numBandsUsed; i++) {
  334. temp[i] = bandSizes[srcBands[i]];
  335. }
  336. bandSizes = temp;
  337. }
  338. } else {
  339. int [] tempSize = srcRas.getSampleModel().getSampleSize();
  340. bandSizes = new int [numSrcBands];
  341. for (int i = 0; i < numSrcBands; i++) {
  342. bandSizes[i] = tempSize[0]; // All the same
  343. }
  344. }
  345. for (int i = 0; i < bandSizes.length; i++) {
  346. // 4450894 part 1: The IJG libraries are compiled so they only
  347. // handle <= 8-bit samples. We now check the band sizes and throw
  348. // an exception for images, such as USHORT_GRAY, with > 8 bits
  349. // per sample.
  350. if (bandSizes[i] > 8) {
  351. throw new IIOException("Sample size must be <= 8");
  352. }
  353. // 4450894 part 2: We expand IndexColorModel images to full 24-
  354. // or 32-bit in grabPixels() for each scanline. For indexed
  355. // images such as BYTE_BINARY, we need to ensure that we update
  356. // bandSizes to account for the scaling from 1-bit band sizes
  357. // to 8-bit.
  358. if (indexed) {
  359. bandSizes[i] = 8;
  360. }
  361. }
  362. if (debug) {
  363. System.out.println("numSrcBands is " + numSrcBands);
  364. System.out.println("numBandsUsed is " + numBandsUsed);
  365. System.out.println("usingBandSubset is " + usingBandSubset);
  366. System.out.println("fullImage is " + fullImage);
  367. System.out.print("Band sizes:");
  368. for (int i = 0; i< bandSizes.length; i++) {
  369. System.out.print(" " + bandSizes[i]);
  370. }
  371. System.out.println();
  372. }
  373. // Destination type, if there is one
  374. ImageTypeSpecifier destType = null;
  375. if (param != null) {
  376. destType = param.getDestinationType();
  377. // Ignore dest type if we are writing a complete image
  378. if ((fullImage) && (destType != null)) {
  379. warningOccurred(WARNING_DEST_IGNORED);
  380. destType = null;
  381. }
  382. }
  383. // Examine the param
  384. sourceXOffset = srcRas.getMinX();
  385. sourceYOffset = srcRas.getMinY();
  386. int imageWidth = srcRas.getWidth();
  387. int imageHeight = srcRas.getHeight();
  388. sourceWidth = imageWidth;
  389. sourceHeight = imageHeight;
  390. int periodX = 1;
  391. int periodY = 1;
  392. int gridX = 0;
  393. int gridY = 0;
  394. JPEGQTable [] qTables = null;
  395. JPEGHuffmanTable[] DCHuffmanTables = null;
  396. JPEGHuffmanTable[] ACHuffmanTables = null;
  397. boolean optimizeHuffman = false;
  398. JPEGImageWriteParam jparam = null;
  399. int progressiveMode = ImageWriteParam.MODE_DISABLED;
  400. if (param != null) {
  401. Rectangle sourceRegion = param.getSourceRegion();
  402. if (sourceRegion != null) {
  403. Rectangle imageBounds = new Rectangle(sourceXOffset,
  404. sourceYOffset,
  405. sourceWidth,
  406. sourceHeight);
  407. sourceRegion = sourceRegion.intersection(imageBounds);
  408. sourceXOffset = sourceRegion.x;
  409. sourceYOffset = sourceRegion.y;
  410. sourceWidth = sourceRegion.width;
  411. sourceHeight = sourceRegion.height;
  412. }
  413. if (sourceWidth + sourceXOffset > imageWidth) {
  414. sourceWidth = imageWidth - sourceXOffset;
  415. }
  416. if (sourceHeight + sourceYOffset > imageHeight) {
  417. sourceHeight = imageHeight - sourceYOffset;
  418. }
  419. periodX = param.getSourceXSubsampling();
  420. periodY = param.getSourceYSubsampling();
  421. gridX = param.getSubsamplingXOffset();
  422. gridY = param.getSubsamplingYOffset();
  423. switch(param.getCompressionMode()) {
  424. case ImageWriteParam.MODE_DISABLED:
  425. throw new IIOException("JPEG compression cannot be disabled");
  426. case ImageWriteParam.MODE_EXPLICIT:
  427. float quality = param.getCompressionQuality();
  428. quality = JPEG.convertToLinearQuality(quality);
  429. qTables = new JPEGQTable[2];
  430. qTables[0] = JPEGQTable.K1Luminance.getScaledInstance
  431. (quality, true);
  432. qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance
  433. (quality, true);
  434. break;
  435. case ImageWriteParam.MODE_DEFAULT:
  436. qTables = new JPEGQTable[2];
  437. qTables[0] = JPEGQTable.K1Div2Luminance;
  438. qTables[1] = JPEGQTable.K2Div2Chrominance;
  439. break;
  440. // We'll handle the metadata case later
  441. }
  442. progressiveMode = param.getProgressiveMode();
  443. if (param instanceof JPEGImageWriteParam) {
  444. jparam = (JPEGImageWriteParam)param;
  445. optimizeHuffman = jparam.getOptimizeHuffmanTables();
  446. }
  447. }
  448. // Now examine the metadata
  449. IIOMetadata mdata = image.getMetadata();
  450. if (mdata != null) {
  451. if (mdata instanceof JPEGMetadata) {
  452. metadata = (JPEGMetadata) mdata;
  453. if (debug) {
  454. System.out.println
  455. ("We have metadata, and it's JPEG metadata");
  456. }
  457. } else {
  458. if (!rasterOnly) {
  459. ImageTypeSpecifier type = destType;
  460. if (type == null) {
  461. type = new ImageTypeSpecifier(rimage);
  462. }
  463. metadata = (JPEGMetadata) convertImageMetadata(mdata,
  464. type,
  465. param);
  466. } else {
  467. warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER);
  468. }
  469. }
  470. }
  471. // First set a default state
  472. ignoreJFIF = false; // If it's there, use it
  473. ignoreAdobe = false; // If it's there, use it
  474. newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed
  475. writeDefaultJFIF = false;
  476. writeAdobe = false;
  477. // By default we'll do no conversion:
  478. int inCsType = JPEG.JCS_UNKNOWN;
  479. int outCsType = JPEG.JCS_UNKNOWN;
  480. JFIFMarkerSegment jfif = null;
  481. AdobeMarkerSegment adobe = null;
  482. SOFMarkerSegment sof = null;
  483. if (metadata != null) {
  484. jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
  485. (JFIFMarkerSegment.class, true);
  486. adobe = (AdobeMarkerSegment) metadata.findMarkerSegment
  487. (AdobeMarkerSegment.class, true);
  488. sof = (SOFMarkerSegment) metadata.findMarkerSegment
  489. (SOFMarkerSegment.class, true);
  490. }
  491. iccProfile = null; // By default don't write one
  492. convertTosRGB = false; // PhotoYCC does this
  493. converted = null;
  494. if (destType != null) {
  495. if (numBandsUsed != destType.getNumBands()) {
  496. throw new IIOException
  497. ("Number of source bands != number of destination bands");
  498. }
  499. cs = destType.getColorModel().getColorSpace();
  500. // Check the metadata against the destination type
  501. if (metadata != null) {
  502. checkSOFBands(sof, numBandsUsed);
  503. checkJFIF(jfif, destType, false);
  504. // Do we want to write an ICC profile?
  505. if ((jfif != null) && (ignoreJFIF == false)) {
  506. if (JPEG.isNonStandardICC(cs)) {
  507. iccProfile = ((ICC_ColorSpace) cs).getProfile();
  508. }
  509. }
  510. checkAdobe(adobe, destType, false);
  511. } else { // no metadata, but there is a dest type
  512. // If we can add a JFIF or an Adobe marker segment, do so
  513. if (JPEG.isJFIFcompliant(destType, false)) {
  514. writeDefaultJFIF = true;
  515. // Do we want to write an ICC profile?
  516. if (JPEG.isNonStandardICC(cs)) {
  517. iccProfile = ((ICC_ColorSpace) cs).getProfile();
  518. }
  519. } else {
  520. int transform = JPEG.transformForType(destType, false);
  521. if (transform != JPEG.ADOBE_IMPOSSIBLE) {
  522. writeAdobe = true;
  523. newAdobeTransform = transform;
  524. }
  525. }
  526. }
  527. } else { // no destination type
  528. if (metadata == null) {
  529. if (fullImage) { // no dest, no metadata, full image
  530. // Use default metadata matching the image and param
  531. metadata = new JPEGMetadata(new ImageTypeSpecifier(rimage),
  532. param, this);
  533. if (metadata.findMarkerSegment
  534. (JFIFMarkerSegment.class, true) != null) {
  535. cs = rimage.getColorModel().getColorSpace();
  536. if (JPEG.isNonStandardICC(cs)) {
  537. iccProfile = ((ICC_ColorSpace) cs).getProfile();
  538. }
  539. }
  540. inCsType = getSrcCSType(rimage);
  541. outCsType = getDefaultDestCSType(rimage);
  542. }
  543. // else no dest, no metadata, not an image,
  544. // so no special headers, no color conversion
  545. } else { // no dest type, but there is metadata
  546. checkSOFBands(sof, numBandsUsed);
  547. if (fullImage) { // no dest, metadata, image
  548. // Check that the metadata and the image match
  549. ImageTypeSpecifier inputType =
  550. new ImageTypeSpecifier(rimage);
  551. inCsType = getSrcCSType(rimage);
  552. if (cm != null) {
  553. boolean alpha = cm.hasAlpha();
  554. switch (cs.getType()) {
  555. case ColorSpace.TYPE_GRAY:
  556. if (!alpha) {
  557. outCsType = JPEG.JCS_GRAYSCALE;
  558. } else {
  559. if (jfif != null) {
  560. ignoreJFIF = true;
  561. warningOccurred
  562. (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
  563. }
  564. // out colorspace remains unknown
  565. }
  566. if ((adobe != null)
  567. && (adobe.transform != JPEG.ADOBE_UNKNOWN)) {
  568. newAdobeTransform = JPEG.ADOBE_UNKNOWN;
  569. warningOccurred
  570. (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
  571. }
  572. break;
  573. case ColorSpace.TYPE_RGB:
  574. if (!alpha) {
  575. if (jfif != null) {
  576. outCsType = JPEG.JCS_YCbCr;
  577. if (JPEG.isNonStandardICC(cs)
  578. || ((cs instanceof ICC_ColorSpace)
  579. && (jfif.iccSegment != null))) {
  580. iccProfile =
  581. ((ICC_ColorSpace) cs).getProfile();
  582. }
  583. } else if (adobe != null) {
  584. switch (adobe.transform) {
  585. case JPEG.ADOBE_UNKNOWN:
  586. outCsType = JPEG.JCS_RGB;
  587. break;
  588. case JPEG.ADOBE_YCC:
  589. outCsType = JPEG.JCS_YCbCr;
  590. break;
  591. default:
  592. warningOccurred
  593. (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
  594. newAdobeTransform = JPEG.ADOBE_UNKNOWN;
  595. outCsType = JPEG.JCS_RGB;
  596. break;
  597. }
  598. } else {
  599. // consult the ids
  600. int outCS = sof.getIDencodedCSType();
  601. // if they don't resolve it,
  602. // consult the sampling factors
  603. if (outCS != JPEG.JCS_UNKNOWN) {
  604. outCsType = outCS;
  605. } else {
  606. boolean subsampled =
  607. isSubsampled(sof.componentSpecs);
  608. if (subsampled) {
  609. outCsType = JPEG.JCS_YCbCr;
  610. } else {
  611. outCsType = JPEG.JCS_RGB;
  612. }
  613. }
  614. }
  615. } else { // RGBA
  616. if (jfif != null) {
  617. ignoreJFIF = true;
  618. warningOccurred
  619. (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
  620. }
  621. if (adobe != null) {
  622. if (adobe.transform
  623. != JPEG.ADOBE_UNKNOWN) {
  624. newAdobeTransform = JPEG.ADOBE_UNKNOWN;
  625. warningOccurred
  626. (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
  627. }
  628. outCsType = JPEG.JCS_RGBA;
  629. } else {
  630. // consult the ids
  631. int outCS = sof.getIDencodedCSType();
  632. // if they don't resolve it,
  633. // consult the sampling factors
  634. if (outCS != JPEG.JCS_UNKNOWN) {
  635. outCsType = outCS;
  636. } else {
  637. boolean subsampled =
  638. isSubsampled(sof.componentSpecs);
  639. outCsType = subsampled ?
  640. JPEG.JCS_YCbCrA : JPEG.JCS_RGBA;
  641. }
  642. }
  643. }
  644. break;
  645. case ColorSpace.TYPE_3CLR:
  646. if (cs == JPEG.YCC) {
  647. if (!alpha) {
  648. if (jfif != null) {
  649. convertTosRGB = true;
  650. convertOp =
  651. new ColorConvertOp(cs,
  652. JPEG.sRGB,
  653. null);
  654. outCsType = JPEG.JCS_YCbCr;
  655. } else if (adobe != null) {
  656. if (adobe.transform
  657. != JPEG.ADOBE_YCC) {
  658. newAdobeTransform = JPEG.ADOBE_YCC;
  659. warningOccurred
  660. (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
  661. }
  662. outCsType = JPEG.JCS_YCC;
  663. } else {
  664. outCsType = JPEG.JCS_YCC;
  665. }
  666. } else { // PhotoYCCA
  667. if (jfif != null) {
  668. ignoreJFIF = true;
  669. warningOccurred
  670. (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
  671. } else if (adobe != null) {
  672. if (adobe.transform
  673. != JPEG.ADOBE_UNKNOWN) {
  674. newAdobeTransform
  675. = JPEG.ADOBE_UNKNOWN;
  676. warningOccurred
  677. (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
  678. }
  679. }
  680. outCsType = JPEG.JCS_YCCA;
  681. }
  682. }
  683. }
  684. }
  685. } // else no dest, metadata, not an image. Defaults ok
  686. }
  687. }
  688. boolean metadataProgressive = false;
  689. int [] scans = null;
  690. if (metadata != null) {
  691. if (sof == null) {
  692. sof = (SOFMarkerSegment) metadata.findMarkerSegment
  693. (SOFMarkerSegment.class, true);
  694. }
  695. if ((sof != null) && (sof.tag == JPEG.SOF2)) {
  696. metadataProgressive = true;
  697. if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
  698. scans = collectScans(metadata, sof); // Might still be null
  699. } else {
  700. numScans = 0;
  701. }
  702. }
  703. if (jfif == null) {
  704. jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
  705. (JFIFMarkerSegment.class, true);
  706. }
  707. }
  708. thumbnails = image.getThumbnails();
  709. int numThumbs = image.getNumThumbnails();
  710. forceJFIF = false;
  711. // determine if thumbnails can be written
  712. // If we are going to add a default JFIF marker segment,
  713. // then thumbnails can be written
  714. if (!writeDefaultJFIF) {
  715. // If there is no metadata, then we can't write thumbnails
  716. if (metadata == null) {
  717. thumbnails = null;
  718. if (numThumbs != 0) {
  719. warningOccurred(WARNING_IGNORING_THUMBS);
  720. }
  721. } else {
  722. // There is metadata
  723. // If we are writing a raster or subbands,
  724. // then the user must specify JFIF on the metadata
  725. if (fullImage == false) {
  726. if (jfif == null) {
  727. thumbnails = null; // Or we can't include thumbnails
  728. if (numThumbs != 0) {
  729. warningOccurred(WARNING_IGNORING_THUMBS);
  730. }
  731. }
  732. } else { // It is a full image, and there is metadata
  733. if (jfif == null) { // Not JFIF
  734. // Can it have JFIF?
  735. if ((outCsType == JPEG.JCS_GRAYSCALE)
  736. || (outCsType == JPEG.JCS_YCbCr)) {
  737. if (numThumbs != 0) {
  738. forceJFIF = true;
  739. warningOccurred(WARNING_FORCING_JFIF);
  740. }
  741. } else { // Nope, not JFIF-compatible
  742. thumbnails = null;
  743. if (numThumbs != 0) {
  744. warningOccurred(WARNING_IGNORING_THUMBS);
  745. }
  746. }
  747. }
  748. }
  749. }
  750. }
  751. // Set up a boolean to indicate whether we need to call back to
  752. // write metadata
  753. boolean haveMetadata =
  754. ((metadata != null) || writeDefaultJFIF || writeAdobe);
  755. // Now that we have dealt with metadata, finalize our tables set up
  756. // Are we going to write tables? By default, yes.
  757. boolean writeDQT = true;
  758. boolean writeDHT = true;
  759. // But if the metadata has no tables, no.
  760. DQTMarkerSegment dqt = null;
  761. DHTMarkerSegment dht = null;
  762. int restartInterval = 0;
  763. if (metadata != null) {
  764. dqt = (DQTMarkerSegment) metadata.findMarkerSegment
  765. (DQTMarkerSegment.class, true);
  766. dht = (DHTMarkerSegment) metadata.findMarkerSegment
  767. (DHTMarkerSegment.class, true);
  768. DRIMarkerSegment dri =
  769. (DRIMarkerSegment) metadata.findMarkerSegment
  770. (DRIMarkerSegment.class, true);
  771. if (dri != null) {
  772. restartInterval = dri.restartInterval;
  773. }
  774. if (dqt == null) {
  775. writeDQT = false;
  776. }
  777. if (dht == null) {
  778. writeDHT = false; // Ignored if optimizeHuffman is true
  779. }
  780. }
  781. // Whether we write tables or not, we need to figure out which ones
  782. // to use
  783. if (qTables == null) { // Get them from metadata, or use defaults
  784. if (dqt != null) {
  785. qTables = collectQTablesFromMetadata(metadata);
  786. } else if (streamQTables != null) {
  787. qTables = streamQTables;
  788. } else if ((jparam != null) && (jparam.areTablesSet())) {
  789. qTables = jparam.getQTables();
  790. } else {
  791. qTables = JPEG.getDefaultQTables();
  792. }
  793. }
  794. // If we are optimizing, we don't want any tables.
  795. if (optimizeHuffman == false) {
  796. // If they were for progressive scans, we can't use them.
  797. if ((dht != null) && (metadataProgressive == false)) {
  798. DCHuffmanTables = collectHTablesFromMetadata(metadata, true);
  799. ACHuffmanTables = collectHTablesFromMetadata(metadata, false);
  800. } else if (streamDCHuffmanTables != null) {
  801. DCHuffmanTables = streamDCHuffmanTables;
  802. ACHuffmanTables = streamACHuffmanTables;
  803. } else if ((jparam != null) && (jparam.areTablesSet())) {
  804. DCHuffmanTables = jparam.getDCHuffmanTables();
  805. ACHuffmanTables = jparam.getACHuffmanTables();
  806. } else {
  807. DCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
  808. ACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
  809. }
  810. }
  811. // By default, ids are 1 - N, no subsampling
  812. int [] componentIds = new int[numBandsUsed];
  813. int [] HsamplingFactors = new int[numBandsUsed];
  814. int [] VsamplingFactors = new int[numBandsUsed];
  815. int [] QtableSelectors = new int[numBandsUsed];
  816. for (int i = 0; i < numBandsUsed; i++) {
  817. componentIds[i] = i+1; // JFIF compatible
  818. HsamplingFactors[i] = 1;
  819. VsamplingFactors[i] = 1;
  820. QtableSelectors[i] = 0;
  821. }
  822. // Now override them with the contents of sof, if there is one,
  823. if (sof != null) {
  824. for (int i = 0; i < numBandsUsed; i++) {
  825. if (forceJFIF == false) { // else use JFIF-compatible default
  826. componentIds[i] = sof.componentSpecs[i].componentId;
  827. }
  828. HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor;
  829. VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor;
  830. QtableSelectors[i] = sof.componentSpecs[i].QtableSelector;
  831. }
  832. }
  833. sourceXOffset += gridX;
  834. sourceWidth -= gridX;
  835. sourceYOffset += gridY;
  836. sourceHeight -= gridY;
  837. int destWidth = (sourceWidth + periodX - 1)/periodX;
  838. int destHeight = (sourceHeight + periodY - 1)/periodY;
  839. // Create an appropriate 1-line databuffer for writing
  840. int lineSize = sourceWidth*numBandsUsed;
  841. DataBufferByte buffer = new DataBufferByte(lineSize);
  842. // Create a raster from that
  843. int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1];
  844. raster = Raster.createInterleavedRaster(buffer,
  845. sourceWidth, 1,
  846. lineSize,
  847. numBandsUsed,
  848. bandOffs,
  849. null);
  850. // Call the writer, who will call back for every scanline
  851. processImageStarted(currentImage);
  852. boolean aborted = false;
  853. if (debug) {
  854. System.out.println("inCsType: " + inCsType);
  855. System.out.println("outCsType: " + outCsType);
  856. }
  857. aborted = writeImage(structPointer,
  858. buffer.getData(),
  859. inCsType, outCsType,
  860. numBandsUsed,
  861. bandSizes,
  862. sourceWidth,
  863. destWidth, destHeight,
  864. periodX, periodY,
  865. qTables,
  866. writeDQT,
  867. DCHuffmanTables,
  868. ACHuffmanTables,
  869. writeDHT,
  870. optimizeHuffman,
  871. (progressiveMode
  872. != ImageWriteParam.MODE_DISABLED),
  873. numScans,
  874. scans,
  875. componentIds,
  876. HsamplingFactors,
  877. VsamplingFactors,
  878. QtableSelectors,
  879. haveMetadata,
  880. restartInterval);
  881. if (aborted) {
  882. processWriteAborted();
  883. } else {
  884. processImageComplete();
  885. }
  886. ios.flush();
  887. currentImage++; // After a successful write
  888. }
  889. public void prepareWriteSequence(IIOMetadata streamMetadata)
  890. throws IOException {
  891. if (ios == null) {
  892. throw new IllegalStateException("Output has not been set!");
  893. }
  894. /*
  895. * from jpeg_metadata.html:
  896. * If no stream metadata is supplied to
  897. * <code>ImageWriter.prepareWriteSequence</code>, then no
  898. * tables-only image is written. If stream metadata containing
  899. * no tables is supplied to
  900. * <code>ImageWriter.prepareWriteSequence</code>, then a tables-only
  901. * image containing default visually lossless tables is written.
  902. */
  903. if (streamMetadata != null) {
  904. if (streamMetadata instanceof JPEGMetadata) {
  905. // write a complete tables-only image at the beginning of
  906. // the stream.
  907. JPEGMetadata jmeta = (JPEGMetadata) streamMetadata;
  908. if (jmeta.isStream == false) {
  909. throw new IllegalArgumentException
  910. ("Invalid stream metadata object.");
  911. }
  912. // Check that we are
  913. // at the beginning of the stream, or can go there, and haven't
  914. // written out the metadata already.
  915. if (currentImage != 0) {
  916. throw new IIOException
  917. ("JPEG Stream metadata must precede all images");
  918. }
  919. if (sequencePrepared == true) {
  920. throw new IIOException("Stream metadata already written!");
  921. }
  922. // Set the tables
  923. // If the metadata has no tables, use default tables.
  924. streamQTables = collectQTablesFromMetadata(jmeta);
  925. if (debug) {
  926. System.out.println("after collecting from stream metadata, "
  927. + "streamQTables.length is "
  928. + streamQTables.length);
  929. }
  930. if (streamQTables == null) {
  931. streamQTables = JPEG.getDefaultQTables();
  932. }
  933. streamDCHuffmanTables =
  934. collectHTablesFromMetadata(jmeta, true);
  935. if (streamDCHuffmanTables == null) {
  936. streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
  937. }
  938. streamACHuffmanTables =
  939. collectHTablesFromMetadata(jmeta, false);
  940. if (streamACHuffmanTables == null) {
  941. streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
  942. }
  943. // Now write them out
  944. writeTables(structPointer,
  945. streamQTables,
  946. streamDCHuffmanTables,
  947. streamACHuffmanTables);
  948. } else {
  949. throw new IIOException("Stream metadata must be JPEG metadata");
  950. }
  951. }
  952. sequencePrepared = true;
  953. }
  954. public void writeToSequence(IIOImage image, ImageWriteParam param)
  955. throws IOException {
  956. if (sequencePrepared == false) {
  957. throw new IllegalStateException("sequencePrepared not called!");
  958. }
  959. // In the case of JPEG this does nothing different from write
  960. write(null, image, param);
  961. }
  962. public void endWriteSequence() throws IOException {
  963. if (sequencePrepared == false) {
  964. throw new IllegalStateException("sequencePrepared not called!");
  965. }
  966. sequencePrepared = false;
  967. }
  968. public synchronized void abort() {
  969. super.abort();
  970. abortWrite(structPointer);
  971. }
  972. private void resetInternalState() {
  973. // reset C structures
  974. resetWriter(structPointer);
  975. // reset local Java structures
  976. srcRas = null;
  977. raster = null;
  978. convertTosRGB = false;
  979. currentImage = 0;
  980. numScans = 0;
  981. metadata = null;
  982. }
  983. /**
  984. * Note that there is no need to override reset() here, as the default
  985. * implementation will call setOutput(null), which will invoke
  986. * resetInternalState().
  987. */
  988. public void dispose() {
  989. if (structPointer != 0) {
  990. disposerRecord.dispose();
  991. structPointer = 0;
  992. }
  993. }
  994. ////////// End of public API
  995. ///////// Package-access API
  996. /**
  997. * Called by the native code or other classes to signal a warning.
  998. * The code is used to lookup a localized message to be used when
  999. * sending warnings to listeners.
  1000. */
  1001. void warningOccurred(int code) {
  1002. if ((code < 0) || (code > MAX_WARNING)){
  1003. throw new InternalError("Invalid warning index");
  1004. }
  1005. processWarningOccurred
  1006. (currentImage,
  1007. "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources",
  1008. Integer.toString(code));
  1009. }
  1010. /**
  1011. * The library has it's own error facility that emits warning messages.
  1012. * This routine is called by the native code when it has already
  1013. * formatted a string for output.
  1014. * XXX For truly complete localization of all warning messages,
  1015. * the sun_jpeg_output_message routine in the native code should
  1016. * send only the codes and parameters to a method here in Java,
  1017. * which will then format and send the warnings, using localized
  1018. * strings. This method will have to deal with all the parameters
  1019. * and formats (%u with possibly large numbers, %02d, %02x, etc.)
  1020. * that actually occur in the JPEG library. For now, this prevents
  1021. * library warnings from being printed to stderr.
  1022. */
  1023. void warningWithMessage(String msg) {
  1024. processWarningOccurred(currentImage, msg);
  1025. }
  1026. void thumbnailStarted(int thumbnailIndex) {
  1027. processThumbnailStarted(currentImage, thumbnailIndex);
  1028. }
  1029. // Provide access to protected superclass method
  1030. void thumbnailProgress(float percentageDone) {
  1031. processThumbnailProgress(percentageDone);
  1032. }
  1033. // Provide access to protected superclass method
  1034. void thumbnailComplete() {
  1035. processThumbnailComplete();
  1036. }
  1037. ///////// End of Package-access API
  1038. ///////// Private methods
  1039. ///////// Metadata handling
  1040. private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed)
  1041. throws IIOException {
  1042. // Does the metadata frame header, if any, match numBandsUsed?
  1043. if (sof != null) {
  1044. if (sof.componentSpecs.length != numBandsUsed) {
  1045. throw new IIOException
  1046. ("Metadata components != number of destination bands");
  1047. }
  1048. }
  1049. }
  1050. private void checkJFIF(JFIFMarkerSegment jfif,
  1051. ImageTypeSpecifier type,
  1052. boolean input) {
  1053. if (jfif != null) {
  1054. if (!JPEG.isJFIFcompliant(type, input)) {
  1055. ignoreJFIF = true; // type overrides metadata
  1056. warningOccurred(input
  1057. ? WARNING_IMAGE_METADATA_JFIF_MISMATCH
  1058. : WARNING_DEST_METADATA_JFIF_MISMATCH);
  1059. }
  1060. }
  1061. }
  1062. private void checkAdobe(AdobeMarkerSegment adobe,
  1063. ImageTypeSpecifier type,
  1064. boolean input) {
  1065. if (adobe != null) {
  1066. int rightTransform = JPEG.transformForType(type, input);
  1067. if (adobe.transform != rightTransform) {
  1068. warningOccurred(input
  1069. ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH
  1070. : WARNING_DEST_METADATA_ADOBE_MISMATCH);
  1071. if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) {
  1072. ignoreAdobe = true;
  1073. } else {
  1074. newAdobeTransform = rightTransform;
  1075. }
  1076. }
  1077. }
  1078. }
  1079. /**
  1080. * Collect all the scan info from the given metadata, and
  1081. * organize it into the scan info array required by the
  1082. * IJG libray. It is much simpler to parse out this
  1083. * data in Java and then just copy the data in C.
  1084. */
  1085. private int [] collectScans(JPEGMetadata metadata,
  1086. SOFMarkerSegment sof) {
  1087. List segments = new ArrayList();
  1088. int SCAN_SIZE = 9;
  1089. int MAX_COMPS_PER_SCAN = 4;
  1090. for (Iterator iter = metadata.markerSequence.iterator();
  1091. iter.hasNext();) {
  1092. MarkerSegment seg = (MarkerSegment) iter.next();
  1093. if (seg instanceof SOSMarkerSegment) {
  1094. segments.add(seg);
  1095. }
  1096. }
  1097. int [] retval = null;
  1098. numScans = 0;
  1099. if (!segments.isEmpty()) {
  1100. numScans = segments.size();
  1101. retval = new int [numScans*SCAN_SIZE];
  1102. int index = 0;
  1103. for (int i = 0; i < numScans; i++) {
  1104. SOSMarkerSegment sos = (SOSMarkerSegment) segments.get(i);
  1105. retval[index++] = sos.componentSpecs.length; // num comps
  1106. for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) {
  1107. if (j < sos.componentSpecs.length) {
  1108. int compSel = sos.componentSpecs[j].componentSelector;
  1109. for (int k = 0; k < sof.componentSpecs.length; k++) {
  1110. if (compSel == sof.componentSpecs[k].componentId) {
  1111. retval[index++] = k;
  1112. break; // out of for over sof comps
  1113. }
  1114. }
  1115. } else {
  1116. retval[index++] = 0;
  1117. }
  1118. }
  1119. retval[index++] = sos.startSpectralSelection;
  1120. retval[index++] = sos.endSpectralSelection;
  1121. retval[index++] = sos.approxHigh;
  1122. retval[index++] = sos.approxLow;
  1123. }
  1124. }
  1125. return retval;
  1126. }
  1127. /**
  1128. * Finds all DQT marker segments and returns all the q
  1129. * tables as a single array of JPEGQTables.
  1130. */
  1131. private JPEGQTable [] collectQTablesFromMetadata
  1132. (JPEGMetadata metadata) {
  1133. ArrayList tables = new ArrayList();
  1134. Iterator iter = metadata.markerSequence.iterator();
  1135. while (iter.hasNext()) {
  1136. MarkerSegment seg = (MarkerSegment) iter.next();
  1137. if (seg instanceof DQTMarkerSegment) {
  1138. DQTMarkerSegment dqt =
  1139. (DQTMarkerSegment) seg;
  1140. tables.addAll(dqt.tables);
  1141. }
  1142. }
  1143. JPEGQTable [] retval = null;
  1144. if (tables.size() != 0) {
  1145. retval = new JPEGQTable[tables.size()];
  1146. for (int i = 0; i < retval.length; i++) {
  1147. retval[i] =
  1148. new JPEGQTable(((DQTMarkerSegment.Qtable)tables.get(i)).data);
  1149. }
  1150. }
  1151. return retval;
  1152. }
  1153. /**
  1154. * Finds all DHT marker segments and returns all the q
  1155. * tables as a single array of JPEGQTables. The metadata
  1156. * must not be for a progressive image, or an exception
  1157. * will be thrown when two Huffman tables with the same
  1158. * table id are encountered.
  1159. */
  1160. private JPEGHuffmanTable[] collectHTablesFromMetadata
  1161. (JPEGMetadata metadata, boolean wantDC) throws IIOException {
  1162. ArrayList tables = new ArrayList();
  1163. Iterator iter = metadata.markerSequence.iterator();
  1164. while (iter.hasNext()) {
  1165. MarkerSegment seg = (MarkerSegment) iter.next();
  1166. if (seg instanceof DHTMarkerSegment) {
  1167. DHTMarkerSegment dht =
  1168. (DHTMarkerSegment) seg;
  1169. for (int i = 0; i < dht.tables.size(); i++) {
  1170. DHTMarkerSegment.Htable htable =
  1171. (DHTMarkerSegment.Htable) dht.tables.get(i);
  1172. if (htable.tableClass == (wantDC ? 0 : 1)) {
  1173. tables.add(htable);
  1174. }
  1175. }
  1176. }
  1177. }
  1178. JPEGHuffmanTable [] retval = null;
  1179. if (tables.size() != 0) {
  1180. DHTMarkerSegment.Htable [] htables =
  1181. new DHTMarkerSegment.Htable[tables.size()];
  1182. tables.toArray(htables);
  1183. retval = new JPEGHuffmanTable[tables.size()];
  1184. for (int i = 0; i < retval.length; i++) {
  1185. retval[i] = null;
  1186. for (int j = 0; j < tables.size(); j++) {
  1187. if (htables[j].tableID == i) {
  1188. if (retval[i] != null) {
  1189. throw new IIOException("Metadata has duplicate Htables!");
  1190. }
  1191. retval[i] = new JPEGHuffmanTable(htables[j].numCodes,
  1192. htables[j].values);
  1193. }
  1194. }
  1195. }
  1196. }
  1197. return retval;
  1198. }
  1199. /////////// End of metadata handling
  1200. ////////////// ColorSpace conversion
  1201. private int getSrcCSType(RenderedImage rimage) {
  1202. int retval = JPEG.JCS_UNKNOWN;
  1203. ColorModel cm = rimage.getColorModel();
  1204. if (cm != null) {
  1205. boolean alpha = cm.hasAlpha();
  1206. ColorSpace cs = cm.getColorSpace();
  1207. switch (cs.getType()) {
  1208. case ColorSpace.TYPE_GRAY:
  1209. retval = JPEG.JCS_GRAYSCALE;
  1210. break;
  1211. case ColorSpace.TYPE_RGB:
  1212. if (alpha) {
  1213. retval = JPEG.JCS_RGBA;
  1214. } else {
  1215. retval = JPEG.JCS_RGB;
  1216. }
  1217. break;
  1218. case ColorSpace.TYPE_YCbCr:
  1219. if (alpha) {
  1220. retval = JPEG.JCS_YCbCrA;
  1221. } else {
  1222. retval = JPEG.JCS_YCbCr;
  1223. }
  1224. break;
  1225. case ColorSpace.TYPE_3CLR:
  1226. if (cs == JPEG.YCC) {
  1227. if (alpha) {
  1228. retval = JPEG.JCS_YCCA;
  1229. } else {
  1230. retval = JPEG.JCS_YCC;
  1231. }
  1232. }
  1233. case ColorSpace.TYPE_CMYK:
  1234. retval = JPEG.JCS_CMYK;
  1235. break;
  1236. }
  1237. }
  1238. return retval;
  1239. }
  1240. private int getDestCSType(ImageTypeSpecifier destType) {
  1241. ColorModel cm = destType.getColorModel();
  1242. boolean alpha = cm.hasAlpha();
  1243. ColorSpace cs = cm.getColorSpace();
  1244. int retval = JPEG.JCS_UNKNOWN;
  1245. switch (cs.getType()) {
  1246. case ColorSpace.TYPE_GRAY:
  1247. retval = JPEG.JCS_GRAYSCALE;
  1248. break;
  1249. case ColorSpace.TYPE_RGB:
  1250. if (alpha) {
  1251. retval = JPEG.JCS_RGBA;
  1252. } else {
  1253. retval = JPEG.JCS_RGB;
  1254. }
  1255. break;
  1256. case ColorSpace.TYPE_YCbCr:
  1257. if (alpha) {
  1258. retval = JPEG.JCS_YCbCrA;
  1259. } else {
  1260. retval = JPEG.JCS_YCbCr;
  1261. }
  1262. break;
  1263. case ColorSpace.TYPE_3CLR:
  1264. if (cs == JPEG.YCC) {
  1265. if (alpha) {
  1266. retval = JPEG.JCS_YCCA;
  1267. } else {
  1268. retval = JPEG.JCS_YCC;
  1269. }
  1270. }
  1271. case ColorSpace.TYPE_CMYK:
  1272. retval = JPEG.JCS_CMYK;
  1273. break;
  1274. }
  1275. return retval;
  1276. }
  1277. private int getDefaultDestCSType(RenderedImage rimage) {
  1278. int retval = JPEG.JCS_UNKNOWN;
  1279. ColorModel cm = rimage.getColorModel();
  1280. if (cm != null) {
  1281. boolean alpha = cm.hasAlpha();
  1282. ColorSpace cs = cm.getColorSpace();
  1283. switch (cs.getType()) {
  1284. case ColorSpace.TYPE_GRAY:
  1285. retval = JPEG.JCS_GRAYSCALE;
  1286. break;
  1287. case ColorSpace.TYPE_RGB:
  1288. if (alpha) {
  1289. retval = JPEG.JCS_YCbCrA;
  1290. } else {
  1291. retval = JPEG.JCS_YCbCr;
  1292. }
  1293. break;
  1294. case ColorSpace.TYPE_YCbCr:
  1295. if (alpha) {
  1296. retval = JPEG.JCS_YCbCrA;
  1297. } else {
  1298. retval = JPEG.JCS_YCbCr;
  1299. }
  1300. break;
  1301. case ColorSpace.TYPE_3CLR:
  1302. if (cs == JPEG.YCC) {
  1303. if (alpha) {
  1304. retval = JPEG.JCS_YCCA;
  1305. } else {
  1306. retval = JPEG.JCS_YCC;
  1307. }
  1308. }
  1309. case ColorSpace.TYPE_CMYK:
  1310. retval = JPEG.JCS_YCCK;
  1311. break;
  1312. }
  1313. }
  1314. return retval;
  1315. }
  1316. private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) {
  1317. int hsamp0 = specs[0].HsamplingFactor;
  1318. int vsamp0 = specs[0].VsamplingFactor;
  1319. for (int i = 1; i < specs.length; i++) {
  1320. if ((specs[i].HsamplingFactor != hsamp0) ||
  1321. (specs[i].HsamplingFactor != hsamp0))
  1322. return true;
  1323. }
  1324. return false;
  1325. }
  1326. ////////////// End of ColorSpace conversion
  1327. ////////////// Native methods and callbacks
  1328. /** Sets up static native structures. */
  1329. private static native void initWriterIDs(Class iosClass,
  1330. Class qTableClass,
  1331. Class huffClass);
  1332. /** Sets up per-writer native structure and returns a pointer to it. */
  1333. private native long initJPEGImageWriter();
  1334. /** Sets up native structures for output stream */
  1335. private native void setDest(long structPointer,
  1336. ImageOutputStream ios);
  1337. /**
  1338. * Returns <code>true</code> if the write was aborted.
  1339. */
  1340. private native boolean writeImage(long structPointer,
  1341. byte [] data,
  1342. int inCsType, int outCsType,
  1343. int numBands,
  1344. int [] bandSizes,
  1345. int srcWidth,
  1346. int destWidth, int destHeight,
  1347. int stepX, int stepY,
  1348. JPEGQTable [] qtables,
  1349. boolean writeDQT,
  1350. JPEGHuffmanTable[] DCHuffmanTables,
  1351. JPEGHuffmanTable[] ACHuffmanTables,
  1352. boolean writeDHT,
  1353. boolean optimizeHuffman,
  1354. boolean progressive,
  1355. int numScans,
  1356. int [] scans,
  1357. int [] componentIds,
  1358. int [] HsamplingFactors,
  1359. int [] VsamplingFactors,
  1360. int [] QtableSelectors,
  1361. boolean haveMetadata,
  1362. int restartInterval);
  1363. /**
  1364. * Writes the metadata out when called by the native code,
  1365. * which will have already written the header to the stream
  1366. * and established the library state. This is simpler than
  1367. * breaking the write call in two.
  1368. */
  1369. private void writeMetadata() throws IOException {
  1370. if (metadata == null) {
  1371. if (writeDefaultJFIF) {
  1372. JFIFMarkerSegment.writeDefaultJFIF(ios,
  1373. thumbnails,
  1374. iccProfile,
  1375. this);
  1376. }
  1377. if (writeAdobe) {
  1378. AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform);
  1379. }
  1380. } else {
  1381. metadata.writeToStream(ios,
  1382. ignoreJFIF,
  1383. forceJFIF,
  1384. thumbnails,
  1385. iccProfile,
  1386. ignoreAdobe,
  1387. newAdobeTransform,
  1388. this);
  1389. }
  1390. }
  1391. /**
  1392. * Write out a tables-only image to the stream.
  1393. */
  1394. private native void writeTables(long structPointer,
  1395. JPEGQTable [] qtables,
  1396. JPEGHuffmanTable[] DCHuffmanTables,
  1397. JPEGHuffmanTable[] ACHuffmanTables);
  1398. /**
  1399. * Put the scanline y of the source ROI view Raster into the
  1400. * 1-line Raster for writing. This handles ROI and band
  1401. * rearrangements, and expands indexed images. Subsampling is
  1402. * done in the native code.
  1403. * This is called by the native code.
  1404. */
  1405. private void grabPixels(int y) {
  1406. Raster sourceLine = null;
  1407. if (indexed) {
  1408. sourceLine = srcRas.createChild(sourceXOffset,
  1409. sourceYOffset+y,
  1410. sourceWidth, 1,
  1411. 0, 0,
  1412. new int [] {0});
  1413. // If the image has BITMASK transparency, we need to make sure
  1414. // it gets converted to 32-bit ARGB, because the JPEG encoder
  1415. // relies upon the full 8-bit alpha channel.
  1416. boolean forceARGB =
  1417. (indexCM.getTransparency() != Transparency.OPAQUE);
  1418. BufferedImage temp = indexCM.convertToIntDiscrete(sourceLine,
  1419. forceARGB);
  1420. sourceLine = temp.getRaster();
  1421. } else {
  1422. sourceLine = srcRas.createChild(sourceXOffset,
  1423. sourceYOffset+y,
  1424. sourceWidth, 1,
  1425. 0, 0,
  1426. srcBands);
  1427. }
  1428. if (convertTosRGB) {
  1429. if (debug) {
  1430. System.out.println("Converting to sRGB");
  1431. }
  1432. // The first time through, converted is null, so
  1433. // a new raster is allocated. It is then reused
  1434. // on subsequent lines.
  1435. converted = convertOp.filter(sourceLine, converted);
  1436. sourceLine = converted;
  1437. }
  1438. if (isAlphaPremultiplied) {
  1439. WritableRaster wr = sourceLine.createCompatibleWritableRaster();
  1440. int[] data = null;
  1441. data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(),
  1442. sourceLine.getWidth(), sourceLine.getHeight(),
  1443. data);
  1444. wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(),
  1445. sourceLine.getWidth(), sourceLine.getHeight(),
  1446. data);
  1447. srcCM.coerceData(wr, false);
  1448. sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(),
  1449. wr.getWidth(), wr.getHeight(),
  1450. 0, 0,
  1451. srcBands);
  1452. }
  1453. raster.setRect(sourceLine);
  1454. if ((y > 7) && (y%8 == 0)) { // Every 8 scanlines
  1455. processImageProgress((float) y / (float) sourceHeight * 100.0F);
  1456. }
  1457. }
  1458. /** Aborts the current write in the native code */
  1459. private native void abortWrite(long structPointer);
  1460. /** Resets native structures */
  1461. private native void resetWriter(long structPointer);
  1462. /** Releases native structures */
  1463. private static native void disposeWriter(long structPointer);
  1464. private static class JPEGWriterDisposerRecord extends DisposerRecord {
  1465. private long pData;
  1466. public JPEGWriterDisposerRecord(long pData) {
  1467. this.pData = pData;
  1468. }
  1469. public synchronized void dispose() {
  1470. if (pData != 0) {
  1471. disposeWriter(pData);
  1472. pData = 0;
  1473. }
  1474. }
  1475. }
  1476. }