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