KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > imageio > plugins > jpeg > JPEGImageWriter


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
8 package com.sun.imageio.plugins.jpeg;
9
10 import javax.imageio.IIOException JavaDoc;
11 import javax.imageio.ImageWriter JavaDoc;
12 import javax.imageio.ImageWriteParam JavaDoc;
13 import javax.imageio.IIOImage JavaDoc;
14 import javax.imageio.ImageTypeSpecifier JavaDoc;
15 import javax.imageio.metadata.IIOMetadata JavaDoc;
16 import javax.imageio.metadata.IIOMetadataFormatImpl JavaDoc;
17 import javax.imageio.metadata.IIOInvalidTreeException JavaDoc;
18 import javax.imageio.spi.ImageWriterSpi JavaDoc;
19 import javax.imageio.stream.ImageOutputStream JavaDoc;
20 import javax.imageio.plugins.jpeg.JPEGImageWriteParam JavaDoc;
21 import javax.imageio.plugins.jpeg.JPEGQTable JavaDoc;
22 import javax.imageio.plugins.jpeg.JPEGHuffmanTable JavaDoc;
23
24 import org.w3c.dom.Node JavaDoc;
25
26 import java.awt.image.Raster JavaDoc;
27 import java.awt.image.WritableRaster JavaDoc;
28 import java.awt.image.SampleModel JavaDoc;
29 import java.awt.image.DataBuffer JavaDoc;
30 import java.awt.image.DataBufferByte JavaDoc;
31 import java.awt.image.ColorModel JavaDoc;
32 import java.awt.image.IndexColorModel JavaDoc;
33 import java.awt.image.ColorConvertOp JavaDoc;
34 import java.awt.image.RenderedImage JavaDoc;
35 import java.awt.image.BufferedImage JavaDoc;
36 import java.awt.color.ColorSpace JavaDoc;
37 import java.awt.color.ICC_ColorSpace JavaDoc;
38 import java.awt.color.ICC_Profile JavaDoc;
39 import java.awt.Dimension JavaDoc;
40 import java.awt.Rectangle JavaDoc;
41 import java.awt.Transparency JavaDoc;
42
43 import java.io.IOException JavaDoc;
44
45 import java.util.List JavaDoc;
46 import java.util.ArrayList JavaDoc;
47 import java.util.Iterator JavaDoc;
48
49 import sun.java2d.Disposer;
50 import sun.java2d.DisposerRecord;
51
52 public class JPEGImageWriter extends ImageWriter JavaDoc {
53
54     ///////// Private variables
55

56     private boolean debug = false;
57
58     /**
59      * The following variable contains a pointer to the IJG library
60      * structure for this reader. It is assigned in the constructor
61      * and then is passed in to every native call. It is set to 0
62      * by dispose to avoid disposing twice.
63      */

64     private long structPointer = 0;
65
66
67     /** The output stream we write to */
68     private ImageOutputStream JavaDoc ios = null;
69
70     /** The Raster we will write from */
71     private Raster JavaDoc srcRas = null;
72     
73     /** An intermediate Raster holding compressor-friendly data */
74     private WritableRaster JavaDoc raster = null;
75
76     /**
77      * Set to true if we are writing an image with an
78      * indexed ColorModel
79      */

80     private boolean indexed = false;
81     private IndexColorModel JavaDoc indexCM = null;
82
83     private boolean convertTosRGB = false; // Used by PhotoYCC only
84
private WritableRaster JavaDoc converted = null;
85
86     private boolean isAlphaPremultiplied = false;
87     private ColorModel JavaDoc srcCM = null;
88
89     /**
90      * If there are thumbnails to be written, this is the list.
91      */

92     private List JavaDoc thumbnails = null;
93
94     /**
95      * If metadata should include an icc profile, store it here.
96      */

97     private ICC_Profile JavaDoc iccProfile = null;
98     
99     private int sourceXOffset = 0;
100     private int sourceYOffset = 0;
101     private int sourceWidth = 0;
102     private int [] srcBands = null;
103     private int sourceHeight = 0;
104
105     /** Used when calling listeners */
106     private int currentImage = 0;
107
108     private ColorConvertOp JavaDoc convertOp = null;
109
110     private JPEGQTable JavaDoc [] streamQTables = null;
111     private JPEGHuffmanTable JavaDoc[] streamDCHuffmanTables = null;
112     private JPEGHuffmanTable JavaDoc[] streamACHuffmanTables = null;
113
114     // Parameters for writing metadata
115
private boolean ignoreJFIF = false; // If it's there, use it
116
private boolean forceJFIF = false; // Add one for the thumbnails
117
private boolean ignoreAdobe = false; // If it's there, use it
118
private int newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed
119
private boolean writeDefaultJFIF = false;
120     private boolean writeAdobe = false;
121     private JPEGMetadata metadata = null;
122     
123     private boolean sequencePrepared = false;
124
125     private int numScans = 0;
126
127     /** The referent to be registered with the Disposer. */
128     private Object JavaDoc disposerReferent = new Object JavaDoc();
129
130     /** The DisposerRecord that handles the actual disposal of this writer. */
131     private DisposerRecord disposerRecord;
132
133     ///////// End of Private variables
134

135     ///////// Protected variables
136

137     protected static final int WARNING_DEST_IGNORED = 0;
138     protected static final int WARNING_STREAM_METADATA_IGNORED = 1;
139     protected static final int WARNING_DEST_METADATA_COMP_MISMATCH = 2;
140     protected static final int WARNING_DEST_METADATA_JFIF_MISMATCH = 3;
141     protected static final int WARNING_DEST_METADATA_ADOBE_MISMATCH = 4;
142     protected static final int WARNING_IMAGE_METADATA_JFIF_MISMATCH = 5;
143     protected static final int WARNING_IMAGE_METADATA_ADOBE_MISMATCH = 6;
144     protected static final int WARNING_METADATA_NOT_JPEG_FOR_RASTER = 7;
145     protected static final int WARNING_NO_BANDS_ON_INDEXED = 8;
146     protected static final int WARNING_ILLEGAL_THUMBNAIL = 9;
147     protected static final int WARNING_IGNORING_THUMBS = 10;
148     protected static final int WARNING_FORCING_JFIF = 11;
149     protected static final int WARNING_THUMB_CLIPPED = 12;
150     protected static final int WARNING_METADATA_ADJUSTED_FOR_THUMB = 13;
151     protected static final int WARNING_NO_RGB_THUMB_AS_INDEXED = 14;
152     protected static final int WARNING_NO_GRAY_THUMB_AS_INDEXED = 15;
153
154     private static final int MAX_WARNING = WARNING_NO_GRAY_THUMB_AS_INDEXED;
155
156     ///////// End of Protected variables
157

158     ///////// static initializer
159

160     static {
161         java.security.AccessController.doPrivileged(
162             new sun.security.action.LoadLibraryAction("jpeg"));
163         initWriterIDs(ImageOutputStream JavaDoc.class,
164                       JPEGQTable JavaDoc.class,
165                       JPEGHuffmanTable JavaDoc.class);
166     }
167
168     //////// Public API
169

170     public JPEGImageWriter(ImageWriterSpi JavaDoc originator) {
171         super(originator);
172         structPointer = initJPEGImageWriter();
173         disposerRecord = new JPEGWriterDisposerRecord(structPointer);
174         Disposer.addRecord(disposerReferent, disposerRecord);
175     }
176
177     public void setOutput(Object JavaDoc output) {
178         super.setOutput(output); // validates output
179
resetInternalState();
180         ios = (ImageOutputStream JavaDoc) output; // so this will always work
181
// Set the native destination
182
setDest(structPointer, ios);
183     }
184
185     public ImageWriteParam JavaDoc getDefaultWriteParam() {
186         return new JPEGImageWriteParam JavaDoc(null);
187     }
188
189     public IIOMetadata JavaDoc getDefaultStreamMetadata(ImageWriteParam JavaDoc param) {
190         return new JPEGMetadata(param, this);
191     }
192
193     public IIOMetadata JavaDoc
194         getDefaultImageMetadata(ImageTypeSpecifier JavaDoc imageType,
195                                 ImageWriteParam JavaDoc param) {
196         return new JPEGMetadata(imageType, param, this);
197     }
198
199     public IIOMetadata JavaDoc convertStreamMetadata(IIOMetadata JavaDoc inData,
200                                              ImageWriteParam JavaDoc param) {
201         // There isn't much we can do. If it's one of ours, then
202
// return it. Otherwise just return null. We use it only
203
// for tables, so we can't get a default and modify it,
204
// as this will usually not be what is intended.
205
if (inData instanceof JPEGMetadata) {
206             JPEGMetadata jpegData = (JPEGMetadata) inData;
207             if (jpegData.isStream) {
208                 return inData;
209             }
210         }
211         return null;
212     }
213
214     public IIOMetadata JavaDoc
215         convertImageMetadata(IIOMetadata JavaDoc inData,
216                              ImageTypeSpecifier JavaDoc imageType,
217                              ImageWriteParam JavaDoc param) {
218         // If it's one of ours, just return it
219
if (inData instanceof JPEGMetadata) {
220             JPEGMetadata jpegData = (JPEGMetadata) inData;
221             if (!jpegData.isStream) {
222                 return inData;
223             } else {
224                 // Can't convert stream metadata to image metadata
225
// XXX Maybe this should put out a warning?
226
return null;
227             }
228         }
229         // If it's not one of ours, create a default and set it from
230
// the standard tree from the input, if it exists.
231
if (inData.isStandardMetadataFormatSupported()) {
232             String JavaDoc formatName =
233                 IIOMetadataFormatImpl.standardMetadataFormatName;
234             Node JavaDoc tree = inData.getAsTree(formatName);
235             if (tree != null) {
236                 JPEGMetadata jpegData = new JPEGMetadata(imageType,
237                                                          param,
238                                                          this);
239                 try {
240                     jpegData.setFromTree(formatName, tree);
241                 } catch (IIOInvalidTreeException JavaDoc e) {
242                     // Other plug-in generates bogus standard tree
243
// XXX Maybe this should put out a warning?
244
return null;
245                 }
246
247                 return jpegData;
248             }
249         }
250             return null;
251     }
252
253     public int getNumThumbnailsSupported(ImageTypeSpecifier JavaDoc imageType,
254                                          ImageWriteParam JavaDoc param,
255                                          IIOMetadata JavaDoc streamMetadata,
256                                          IIOMetadata JavaDoc imageMetadata) {
257         if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
258             return Integer.MAX_VALUE;
259         }
260         return 0;
261     }
262
263     static final Dimension JavaDoc [] preferredThumbSizes = {new Dimension JavaDoc(1, 1),
264                                                      new Dimension JavaDoc(255, 255)};
265     
266     public Dimension JavaDoc[] getPreferredThumbnailSizes(ImageTypeSpecifier JavaDoc imageType,
267                                                   ImageWriteParam JavaDoc param,
268                                                   IIOMetadata JavaDoc streamMetadata,
269                                                   IIOMetadata JavaDoc imageMetadata) {
270         if (jfifOK(imageType, param, streamMetadata, imageMetadata)) {
271             return (Dimension JavaDoc [])preferredThumbSizes.clone();
272         }
273         return null;
274     }
275
276     private boolean jfifOK(ImageTypeSpecifier JavaDoc imageType,
277                            ImageWriteParam JavaDoc param,
278                            IIOMetadata JavaDoc streamMetadata,
279                            IIOMetadata JavaDoc imageMetadata) {
280         // If the image type and metadata are JFIF compatible, return true
281
if ((imageType != null) &&
282             (!JPEG.isJFIFcompliant(imageType, true))) {
283             return false;
284         }
285         if (imageMetadata != null) {
286             JPEGMetadata metadata = null;
287             if (imageMetadata instanceof JPEGMetadata) {
288                 metadata = (JPEGMetadata) imageMetadata;
289             } else {
290                 metadata = (JPEGMetadata)convertImageMetadata(imageMetadata,
291                                                               imageType,
292                                                               param);
293             }
294             // metadata must have a jfif node
295
if (metadata.findMarkerSegment
296                 (JFIFMarkerSegment.class, true) == null){
297                 return false;
298             }
299         }
300         return true;
301     }
302
303     public boolean canWriteRasters() {
304         return true;
305     }
306
307     public void write(IIOMetadata JavaDoc streamMetadata,
308                       IIOImage JavaDoc image,
309                       ImageWriteParam JavaDoc param) throws IOException JavaDoc {
310
311         if (ios == null) {
312             throw new IllegalStateException JavaDoc("Output has not been set!");
313         }
314
315         if (image == null) {
316             throw new IllegalArgumentException JavaDoc("image is null!");
317         }
318
319         // if streamMetadata is not null, issue a warning
320
if (streamMetadata != null) {
321             warningOccurred(WARNING_STREAM_METADATA_IGNORED);
322         }
323
324         // Obtain the raster and image, if there is one
325
boolean rasterOnly = image.hasRaster();
326
327         RenderedImage JavaDoc rimage = null;
328         if (rasterOnly) {
329             srcRas = image.getRaster();
330         } else {
331             rimage = image.getRenderedImage();
332             if (rimage instanceof BufferedImage JavaDoc) {
333                 srcRas = ((BufferedImage JavaDoc)rimage).getRaster();
334             } else {
335                 // XXX - this makes a copy, which is memory-inefficient
336
srcRas = rimage.getData();
337             }
338         }
339
340         // Now determine if we are using a band subset
341

342         // By default, we are using all source bands
343
int numSrcBands = srcRas.getNumBands();
344         indexed = false;
345         indexCM = null;
346         ColorModel JavaDoc cm = null;
347         ColorSpace JavaDoc cs = null;
348         isAlphaPremultiplied = false;
349         srcCM = null;
350         if (!rasterOnly) {
351             cm = rimage.getColorModel();
352             if (cm != null) {
353                 cs = cm.getColorSpace();
354                 if (cm instanceof IndexColorModel JavaDoc) {
355                     indexed = true;
356                     indexCM = (IndexColorModel JavaDoc) cm;
357                     numSrcBands = cm.getNumComponents();
358                 }
359                 if (cm.isAlphaPremultiplied()) {
360                     isAlphaPremultiplied = true;
361                     srcCM = cm;
362                 }
363             }
364         }
365
366         srcBands = JPEG.bandOffsets[numSrcBands-1];
367         int numBandsUsed = numSrcBands;
368         // Consult the param to determine if we're writing a subset
369

370         if (param != null) {
371             int[] sBands = param.getSourceBands();
372             if (sBands != null) {
373                 if (indexed) {
374                     warningOccurred(WARNING_NO_BANDS_ON_INDEXED);
375                 } else {
376                     srcBands = sBands;
377                     numBandsUsed = srcBands.length;
378                     if (numBandsUsed > numSrcBands) {
379                         throw new IIOException JavaDoc
380                         ("ImageWriteParam specifies too many source bands");
381                     }
382                 }
383             }
384         }
385
386         boolean usingBandSubset = (numBandsUsed != numSrcBands);
387         boolean fullImage = ((!rasterOnly) && (!usingBandSubset));
388
389         int [] bandSizes = null;
390         if (!indexed) {
391             bandSizes = srcRas.getSampleModel().getSampleSize();
392             // If this is a subset, we must adjust bandSizes
393
if (usingBandSubset) {
394                 int [] temp = new int [numBandsUsed];
395                 for (int i = 0; i < numBandsUsed; i++) {
396                     temp[i] = bandSizes[srcBands[i]];
397                 }
398                 bandSizes = temp;
399             }
400         } else {
401             int [] tempSize = srcRas.getSampleModel().getSampleSize();
402             bandSizes = new int [numSrcBands];
403             for (int i = 0; i < numSrcBands; i++) {
404                 bandSizes[i] = tempSize[0]; // All the same
405
}
406         }
407
408         for (int i = 0; i < bandSizes.length; i++) {
409             // 4450894 part 1: The IJG libraries are compiled so they only
410
// handle <= 8-bit samples. We now check the band sizes and throw
411
// an exception for images, such as USHORT_GRAY, with > 8 bits
412
// per sample.
413
if (bandSizes[i] > 8) {
414                 throw new IIOException JavaDoc("Sample size must be <= 8");
415             }
416             // 4450894 part 2: We expand IndexColorModel images to full 24-
417
// or 32-bit in grabPixels() for each scanline. For indexed
418
// images such as BYTE_BINARY, we need to ensure that we update
419
// bandSizes to account for the scaling from 1-bit band sizes
420
// to 8-bit.
421
if (indexed) {
422                 bandSizes[i] = 8;
423             }
424         }
425
426         if (debug) {
427             System.out.println("numSrcBands is " + numSrcBands);
428             System.out.println("numBandsUsed is " + numBandsUsed);
429             System.out.println("usingBandSubset is " + usingBandSubset);
430             System.out.println("fullImage is " + fullImage);
431             System.out.print("Band sizes:");
432             for (int i = 0; i< bandSizes.length; i++) {
433                 System.out.print(" " + bandSizes[i]);
434             }
435             System.out.println();
436         }
437
438         // Destination type, if there is one
439
ImageTypeSpecifier JavaDoc destType = null;
440         if (param != null) {
441             destType = param.getDestinationType();
442             // Ignore dest type if we are writing a complete image
443
if ((fullImage) && (destType != null)) {
444                 warningOccurred(WARNING_DEST_IGNORED);
445                 destType = null;
446             }
447         }
448
449         // Examine the param
450

451         sourceXOffset = srcRas.getMinX();
452         sourceYOffset = srcRas.getMinY();
453         int imageWidth = srcRas.getWidth();
454         int imageHeight = srcRas.getHeight();
455         sourceWidth = imageWidth;
456         sourceHeight = imageHeight;
457         int periodX = 1;
458         int periodY = 1;
459         int gridX = 0;
460         int gridY = 0;
461         JPEGQTable JavaDoc [] qTables = null;
462         JPEGHuffmanTable JavaDoc[] DCHuffmanTables = null;
463         JPEGHuffmanTable JavaDoc[] ACHuffmanTables = null;
464         boolean optimizeHuffman = false;
465         JPEGImageWriteParam JavaDoc jparam = null;
466         int progressiveMode = ImageWriteParam.MODE_DISABLED;
467
468         if (param != null) {
469
470             Rectangle JavaDoc sourceRegion = param.getSourceRegion();
471             if (sourceRegion != null) {
472                 Rectangle JavaDoc imageBounds = new Rectangle JavaDoc(sourceXOffset,
473                                                       sourceYOffset,
474                                                       sourceWidth,
475                                                       sourceHeight);
476                 sourceRegion = sourceRegion.intersection(imageBounds);
477                 sourceXOffset = sourceRegion.x;
478                 sourceYOffset = sourceRegion.y;
479                 sourceWidth = sourceRegion.width;
480                 sourceHeight = sourceRegion.height;
481             }
482
483             if (sourceWidth + sourceXOffset > imageWidth) {
484                 sourceWidth = imageWidth - sourceXOffset;
485             }
486             if (sourceHeight + sourceYOffset > imageHeight) {
487                 sourceHeight = imageHeight - sourceYOffset;
488             }
489
490             periodX = param.getSourceXSubsampling();
491             periodY = param.getSourceYSubsampling();
492             gridX = param.getSubsamplingXOffset();
493             gridY = param.getSubsamplingYOffset();
494
495             switch(param.getCompressionMode()) {
496             case ImageWriteParam.MODE_DISABLED:
497                 throw new IIOException JavaDoc("JPEG compression cannot be disabled");
498             case ImageWriteParam.MODE_EXPLICIT:
499                 float quality = param.getCompressionQuality();
500                 quality = JPEG.convertToLinearQuality(quality);
501                 qTables = new JPEGQTable JavaDoc[2];
502                 qTables[0] = JPEGQTable.K1Luminance.getScaledInstance
503                     (quality, true);
504                 qTables[1] = JPEGQTable.K2Chrominance.getScaledInstance
505                     (quality, true);
506                 break;
507             case ImageWriteParam.MODE_DEFAULT:
508                 qTables = new JPEGQTable JavaDoc[2];
509                 qTables[0] = JPEGQTable.K1Div2Luminance;
510                 qTables[1] = JPEGQTable.K2Div2Chrominance;
511                 break;
512             // We'll handle the metadata case later
513
}
514
515             progressiveMode = param.getProgressiveMode();
516
517             if (param instanceof JPEGImageWriteParam JavaDoc) {
518                 jparam = (JPEGImageWriteParam JavaDoc)param;
519                 optimizeHuffman = jparam.getOptimizeHuffmanTables();
520             }
521         }
522
523         // Now examine the metadata
524
IIOMetadata JavaDoc mdata = image.getMetadata();
525         if (mdata != null) {
526             if (mdata instanceof JPEGMetadata) {
527                 metadata = (JPEGMetadata) mdata;
528                 if (debug) {
529                     System.out.println
530                         ("We have metadata, and it's JPEG metadata");
531                 }
532             } else {
533                 if (!rasterOnly) {
534                     ImageTypeSpecifier JavaDoc type = destType;
535                     if (type == null) {
536                         type = new ImageTypeSpecifier JavaDoc(rimage);
537                     }
538                     metadata = (JPEGMetadata) convertImageMetadata(mdata,
539                                                                    type,
540                                                                    param);
541                 } else {
542                     warningOccurred(WARNING_METADATA_NOT_JPEG_FOR_RASTER);
543                 }
544             }
545         }
546
547         // First set a default state
548

549         ignoreJFIF = false; // If it's there, use it
550
ignoreAdobe = false; // If it's there, use it
551
newAdobeTransform = JPEG.ADOBE_IMPOSSIBLE; // Change if needed
552
writeDefaultJFIF = false;
553         writeAdobe = false;
554
555         // By default we'll do no conversion:
556
int inCsType = JPEG.JCS_UNKNOWN;
557         int outCsType = JPEG.JCS_UNKNOWN;
558
559         JFIFMarkerSegment jfif = null;
560         AdobeMarkerSegment adobe = null;
561         SOFMarkerSegment sof = null;
562
563         if (metadata != null) {
564             jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
565                 (JFIFMarkerSegment.class, true);
566             adobe = (AdobeMarkerSegment) metadata.findMarkerSegment
567                 (AdobeMarkerSegment.class, true);
568             sof = (SOFMarkerSegment) metadata.findMarkerSegment
569                 (SOFMarkerSegment.class, true);
570         }
571         
572         iccProfile = null; // By default don't write one
573
convertTosRGB = false; // PhotoYCC does this
574
converted = null;
575
576         if (destType != null) {
577             if (numBandsUsed != destType.getNumBands()) {
578                 throw new IIOException JavaDoc
579                     ("Number of source bands != number of destination bands");
580             }
581             cs = destType.getColorModel().getColorSpace();
582             // Check the metadata against the destination type
583
if (metadata != null) {
584                 checkSOFBands(sof, numBandsUsed);
585
586                 checkJFIF(jfif, destType, false);
587                 // Do we want to write an ICC profile?
588
if ((jfif != null) && (ignoreJFIF == false)) {
589                     if (JPEG.isNonStandardICC(cs)) {
590                         iccProfile = ((ICC_ColorSpace JavaDoc) cs).getProfile();
591                     }
592                 }
593                 checkAdobe(adobe, destType, false);
594
595             } else { // no metadata, but there is a dest type
596
// If we can add a JFIF or an Adobe marker segment, do so
597
if (JPEG.isJFIFcompliant(destType, false)) {
598                     writeDefaultJFIF = true;
599                     // Do we want to write an ICC profile?
600
if (JPEG.isNonStandardICC(cs)) {
601                         iccProfile = ((ICC_ColorSpace JavaDoc) cs).getProfile();
602                     }
603                 } else {
604                     int transform = JPEG.transformForType(destType, false);
605                     if (transform != JPEG.ADOBE_IMPOSSIBLE) {
606                         writeAdobe = true;
607                         newAdobeTransform = transform;
608                     }
609                 }
610             }
611         } else { // no destination type
612
if (metadata == null) {
613                 if (fullImage) { // no dest, no metadata, full image
614
// Use default metadata matching the image and param
615
metadata = new JPEGMetadata(new ImageTypeSpecifier JavaDoc(rimage),
616                                                 param, this);
617                     if (metadata.findMarkerSegment
618                         (JFIFMarkerSegment.class, true) != null) {
619                         cs = rimage.getColorModel().getColorSpace();
620                         if (JPEG.isNonStandardICC(cs)) {
621                             iccProfile = ((ICC_ColorSpace JavaDoc) cs).getProfile();
622                         }
623                     }
624
625                     inCsType = getSrcCSType(rimage);
626                     outCsType = getDefaultDestCSType(rimage);
627                 }
628                 // else no dest, no metadata, not an image,
629
// so no special headers, no color conversion
630
} else { // no dest type, but there is metadata
631
checkSOFBands(sof, numBandsUsed);
632                 if (fullImage) { // no dest, metadata, image
633
// Check that the metadata and the image match
634

635                     ImageTypeSpecifier JavaDoc inputType =
636                         new ImageTypeSpecifier JavaDoc(rimage);
637
638                     inCsType = getSrcCSType(rimage);
639
640                     if (cm != null) {
641                         boolean alpha = cm.hasAlpha();
642                         switch (cs.getType()) {
643                         case ColorSpace.TYPE_GRAY:
644                             if (!alpha) {
645                                 outCsType = JPEG.JCS_GRAYSCALE;
646                             } else {
647                                 if (jfif != null) {
648                                     ignoreJFIF = true;
649                                     warningOccurred
650                                     (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
651                                 }
652                                 // out colorspace remains unknown
653
}
654                             if ((adobe != null)
655                                 && (adobe.transform != JPEG.ADOBE_UNKNOWN)) {
656                                 newAdobeTransform = JPEG.ADOBE_UNKNOWN;
657                                 warningOccurred
658                                 (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
659                             }
660                             break;
661                         case ColorSpace.TYPE_RGB:
662                             if (!alpha) {
663                                 if (jfif != null) {
664                                     outCsType = JPEG.JCS_YCbCr;
665                                     if (JPEG.isNonStandardICC(cs)
666                                         || ((cs instanceof ICC_ColorSpace JavaDoc)
667                                             && (jfif.iccSegment != null))) {
668                                         iccProfile =
669                                             ((ICC_ColorSpace JavaDoc) cs).getProfile();
670                                     }
671                                 } else if (adobe != null) {
672                                     switch (adobe.transform) {
673                                     case JPEG.ADOBE_UNKNOWN:
674                                         outCsType = JPEG.JCS_RGB;
675                                         break;
676                                     case JPEG.ADOBE_YCC:
677                                         outCsType = JPEG.JCS_YCbCr;
678                                         break;
679                                     default:
680                                         warningOccurred
681                                         (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
682                                         newAdobeTransform = JPEG.ADOBE_UNKNOWN;
683                                         outCsType = JPEG.JCS_RGB;
684                                         break;
685                                     }
686                                 } else {
687                                     // consult the ids
688
int outCS = sof.getIDencodedCSType();
689                                     // if they don't resolve it,
690
// consult the sampling factors
691
if (outCS != JPEG.JCS_UNKNOWN) {
692                                         outCsType = outCS;
693                                     } else {
694                                         boolean subsampled =
695                                         isSubsampled(sof.componentSpecs);
696                                         if (subsampled) {
697                                             outCsType = JPEG.JCS_YCbCr;
698                                         } else {
699                                             outCsType = JPEG.JCS_RGB;
700                                         }
701                                     }
702                                 }
703                             } else { // RGBA
704
if (jfif != null) {
705                                     ignoreJFIF = true;
706                                     warningOccurred
707                                     (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
708                                 }
709                                 if (adobe != null) {
710                                     if (adobe.transform
711                                         != JPEG.ADOBE_UNKNOWN) {
712                                         newAdobeTransform = JPEG.ADOBE_UNKNOWN;
713                                         warningOccurred
714                                         (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
715                                     }
716                                     outCsType = JPEG.JCS_RGBA;
717                                 } else {
718                                     // consult the ids
719
int outCS = sof.getIDencodedCSType();
720                                     // if they don't resolve it,
721
// consult the sampling factors
722
if (outCS != JPEG.JCS_UNKNOWN) {
723                                         outCsType = outCS;
724                                     } else {
725                                         boolean subsampled =
726                                         isSubsampled(sof.componentSpecs);
727                                         outCsType = subsampled ?
728                                             JPEG.JCS_YCbCrA : JPEG.JCS_RGBA;
729                                     }
730                                 }
731                             }
732                             break;
733                         case ColorSpace.TYPE_3CLR:
734                             if (cs == JPEG.YCC) {
735                                 if (!alpha) {
736                                     if (jfif != null) {
737                                         convertTosRGB = true;
738                                         convertOp =
739                                         new ColorConvertOp JavaDoc(cs,
740                                                            JPEG.sRGB,
741                                                            null);
742                                         outCsType = JPEG.JCS_YCbCr;
743                                     } else if (adobe != null) {
744                                         if (adobe.transform
745                                             != JPEG.ADOBE_YCC) {
746                                             newAdobeTransform = JPEG.ADOBE_YCC;
747                                             warningOccurred
748                                             (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
749                                         }
750                                         outCsType = JPEG.JCS_YCC;
751                                     } else {
752                                         outCsType = JPEG.JCS_YCC;
753                                     }
754                                 } else { // PhotoYCCA
755
if (jfif != null) {
756                                         ignoreJFIF = true;
757                                         warningOccurred
758                                         (WARNING_IMAGE_METADATA_JFIF_MISMATCH);
759                                     } else if (adobe != null) {
760                                         if (adobe.transform
761                                             != JPEG.ADOBE_UNKNOWN) {
762                                             newAdobeTransform
763                                             = JPEG.ADOBE_UNKNOWN;
764                                             warningOccurred
765                                             (WARNING_IMAGE_METADATA_ADOBE_MISMATCH);
766                                         }
767                                     }
768                                     outCsType = JPEG.JCS_YCCA;
769                                 }
770                             }
771                         }
772                     }
773                 } // else no dest, metadata, not an image. Defaults ok
774
}
775         }
776
777         boolean metadataProgressive = false;
778         int [] scans = null;
779
780         if (metadata != null) {
781             if (sof == null) {
782                 sof = (SOFMarkerSegment) metadata.findMarkerSegment
783                     (SOFMarkerSegment.class, true);
784             }
785             if ((sof != null) && (sof.tag == JPEG.SOF2)) {
786                 metadataProgressive = true;
787                 if (progressiveMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
788                     scans = collectScans(metadata, sof); // Might still be null
789
} else {
790                     numScans = 0;
791                 }
792             }
793             if (jfif == null) {
794                 jfif = (JFIFMarkerSegment) metadata.findMarkerSegment
795                     (JFIFMarkerSegment.class, true);
796             }
797         }
798
799         thumbnails = image.getThumbnails();
800         int numThumbs = image.getNumThumbnails();
801         forceJFIF = false;
802         // determine if thumbnails can be written
803
// If we are going to add a default JFIF marker segment,
804
// then thumbnails can be written
805
if (!writeDefaultJFIF) {
806             // If there is no metadata, then we can't write thumbnails
807
if (metadata == null) {
808                 thumbnails = null;
809                 if (numThumbs != 0) {
810                     warningOccurred(WARNING_IGNORING_THUMBS);
811                 }
812             } else {
813                 // There is metadata
814
// If we are writing a raster or subbands,
815
// then the user must specify JFIF on the metadata
816
if (fullImage == false) {
817                     if (jfif == null) {
818                         thumbnails = null; // Or we can't include thumbnails
819
if (numThumbs != 0) {
820                             warningOccurred(WARNING_IGNORING_THUMBS);
821                         }
822                     }
823                 } else { // It is a full image, and there is metadata
824
if (jfif == null) { // Not JFIF
825
// Can it have JFIF?
826
if ((outCsType == JPEG.JCS_GRAYSCALE)
827                             || (outCsType == JPEG.JCS_YCbCr)) {
828                             if (numThumbs != 0) {
829                                 forceJFIF = true;
830                                 warningOccurred(WARNING_FORCING_JFIF);
831                             }
832                         } else { // Nope, not JFIF-compatible
833
thumbnails = null;
834                             if (numThumbs != 0) {
835                                 warningOccurred(WARNING_IGNORING_THUMBS);
836                             }
837                         }
838                     }
839                 }
840             }
841         }
842
843         // Set up a boolean to indicate whether we need to call back to
844
// write metadata
845
boolean haveMetadata =
846             ((metadata != null) || writeDefaultJFIF || writeAdobe);
847
848         // Now that we have dealt with metadata, finalize our tables set up
849

850         // Are we going to write tables? By default, yes.
851
boolean writeDQT = true;
852         boolean writeDHT = true;
853
854         // But if the metadata has no tables, no.
855
DQTMarkerSegment dqt = null;
856         DHTMarkerSegment dht = null;
857
858         int restartInterval = 0;
859
860         if (metadata != null) {
861             dqt = (DQTMarkerSegment) metadata.findMarkerSegment
862                 (DQTMarkerSegment.class, true);
863             dht = (DHTMarkerSegment) metadata.findMarkerSegment
864                 (DHTMarkerSegment.class, true);
865             DRIMarkerSegment dri =
866                 (DRIMarkerSegment) metadata.findMarkerSegment
867                 (DRIMarkerSegment.class, true);
868             if (dri != null) {
869                 restartInterval = dri.restartInterval;
870             }
871
872             if (dqt == null) {
873                 writeDQT = false;
874             }
875             if (dht == null) {
876                 writeDHT = false; // Ignored if optimizeHuffman is true
877
}
878         }
879
880         // Whether we write tables or not, we need to figure out which ones
881
// to use
882
if (qTables == null) { // Get them from metadata, or use defaults
883
if (dqt != null) {
884                 qTables = collectQTablesFromMetadata(metadata);
885             } else if (streamQTables != null) {
886                 qTables = streamQTables;
887             } else if ((jparam != null) && (jparam.areTablesSet())) {
888                 qTables = jparam.getQTables();
889             } else {
890                 qTables = JPEG.getDefaultQTables();
891             }
892
893         }
894
895         // If we are optimizing, we don't want any tables.
896
if (optimizeHuffman == false) {
897             // If they were for progressive scans, we can't use them.
898
if ((dht != null) && (metadataProgressive == false)) {
899                 DCHuffmanTables = collectHTablesFromMetadata(metadata, true);
900                 ACHuffmanTables = collectHTablesFromMetadata(metadata, false);
901             } else if (streamDCHuffmanTables != null) {
902                 DCHuffmanTables = streamDCHuffmanTables;
903                 ACHuffmanTables = streamACHuffmanTables;
904             } else if ((jparam != null) && (jparam.areTablesSet())) {
905                 DCHuffmanTables = jparam.getDCHuffmanTables();
906                 ACHuffmanTables = jparam.getACHuffmanTables();
907             } else {
908                 DCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
909                 ACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
910             }
911         }
912
913         // By default, ids are 1 - N, no subsampling
914
int [] componentIds = new int[numBandsUsed];
915         int [] HsamplingFactors = new int[numBandsUsed];
916         int [] VsamplingFactors = new int[numBandsUsed];
917         int [] QtableSelectors = new int[numBandsUsed];
918         for (int i = 0; i < numBandsUsed; i++) {
919             componentIds[i] = i+1; // JFIF compatible
920
HsamplingFactors[i] = 1;
921             VsamplingFactors[i] = 1;
922             QtableSelectors[i] = 0;
923         }
924
925         // Now override them with the contents of sof, if there is one,
926
if (sof != null) {
927             for (int i = 0; i < numBandsUsed; i++) {
928                 if (forceJFIF == false) { // else use JFIF-compatible default
929
componentIds[i] = sof.componentSpecs[i].componentId;
930                 }
931                 HsamplingFactors[i] = sof.componentSpecs[i].HsamplingFactor;
932                 VsamplingFactors[i] = sof.componentSpecs[i].VsamplingFactor;
933                 QtableSelectors[i] = sof.componentSpecs[i].QtableSelector;
934             }
935         }
936
937         sourceXOffset += gridX;
938         sourceWidth -= gridX;
939         sourceYOffset += gridY;
940         sourceHeight -= gridY;
941
942         int destWidth = (sourceWidth + periodX - 1)/periodX;
943         int destHeight = (sourceHeight + periodY - 1)/periodY;
944
945         // Create an appropriate 1-line databuffer for writing
946
int lineSize = sourceWidth*numBandsUsed;
947         
948         DataBufferByte JavaDoc buffer = new DataBufferByte JavaDoc(lineSize);
949
950         // Create a raster from that
951
int [] bandOffs = JPEG.bandOffsets[numBandsUsed-1];
952         
953         raster = Raster.createInterleavedRaster(buffer,
954                                                 sourceWidth, 1,
955                                                 lineSize,
956                                                 numBandsUsed,
957                                                 bandOffs,
958                                                 null);
959
960         // Call the writer, who will call back for every scanline
961

962         processImageStarted(currentImage);
963             
964         boolean aborted = false;
965
966         if (debug) {
967             System.out.println("inCsType: " + inCsType);
968             System.out.println("outCsType: " + outCsType);
969         }
970
971         aborted = writeImage(structPointer,
972                              buffer.getData(),
973                              inCsType, outCsType,
974                              numBandsUsed,
975                              bandSizes,
976                              sourceWidth,
977                              destWidth, destHeight,
978                              periodX, periodY,
979                              qTables,
980                              writeDQT,
981                              DCHuffmanTables,
982                              ACHuffmanTables,
983                              writeDHT,
984                              optimizeHuffman,
985                              (progressiveMode
986                               != ImageWriteParam.MODE_DISABLED),
987                              numScans,
988                              scans,
989                              componentIds,
990                              HsamplingFactors,
991                              VsamplingFactors,
992                              QtableSelectors,
993                              haveMetadata,
994                              restartInterval);
995
996         if (aborted) {
997             processWriteAborted();
998         } else {
999             processImageComplete();
1000        }
1001
1002        ios.flush();
1003        currentImage++; // After a successful write
1004
}
1005
1006    public void prepareWriteSequence(IIOMetadata JavaDoc streamMetadata)
1007        throws IOException JavaDoc {
1008        if (ios == null) {
1009            throw new IllegalStateException JavaDoc("Output has not been set!");
1010        }
1011
1012        /*
1013         * from jpeg_metadata.html:
1014         * If no stream metadata is supplied to
1015         * <code>ImageWriter.prepareWriteSequence</code>, then no
1016         * tables-only image is written. If stream metadata containing
1017         * no tables is supplied to
1018         * <code>ImageWriter.prepareWriteSequence</code>, then a tables-only
1019         * image containing default visually lossless tables is written.
1020         */

1021        if (streamMetadata != null) {
1022            if (streamMetadata instanceof JPEGMetadata) {
1023                // write a complete tables-only image at the beginning of
1024
// the stream.
1025
JPEGMetadata jmeta = (JPEGMetadata) streamMetadata;
1026                if (jmeta.isStream == false) {
1027                    throw new IllegalArgumentException JavaDoc
1028                        ("Invalid stream metadata object.");
1029                }
1030                // Check that we are
1031
// at the beginning of the stream, or can go there, and haven't
1032
// written out the metadata already.
1033
if (currentImage != 0) {
1034                    throw new IIOException JavaDoc
1035                        ("JPEG Stream metadata must precede all images");
1036                }
1037                if (sequencePrepared == true) {
1038                    throw new IIOException JavaDoc("Stream metadata already written!");
1039                }
1040                
1041                // Set the tables
1042
// If the metadata has no tables, use default tables.
1043
streamQTables = collectQTablesFromMetadata(jmeta);
1044                if (debug) {
1045                    System.out.println("after collecting from stream metadata, "
1046                                       + "streamQTables.length is "
1047                                       + streamQTables.length);
1048                }
1049                if (streamQTables == null) {
1050                    streamQTables = JPEG.getDefaultQTables();
1051                }
1052                streamDCHuffmanTables =
1053                    collectHTablesFromMetadata(jmeta, true);
1054                if (streamDCHuffmanTables == null) {
1055                    streamDCHuffmanTables = JPEG.getDefaultHuffmanTables(true);
1056                }
1057                streamACHuffmanTables =
1058                    collectHTablesFromMetadata(jmeta, false);
1059                if (streamACHuffmanTables == null) {
1060                    streamACHuffmanTables = JPEG.getDefaultHuffmanTables(false);
1061                }
1062
1063                // Now write them out
1064
writeTables(structPointer,
1065                            streamQTables,
1066                            streamDCHuffmanTables,
1067                            streamACHuffmanTables);
1068            } else {
1069                throw new IIOException JavaDoc("Stream metadata must be JPEG metadata");
1070            }
1071        }
1072        sequencePrepared = true;
1073    }
1074
1075    public void writeToSequence(IIOImage JavaDoc image, ImageWriteParam JavaDoc param)
1076        throws IOException JavaDoc {
1077        if (sequencePrepared == false) {
1078            throw new IllegalStateException JavaDoc("sequencePrepared not called!");
1079        }
1080        // In the case of JPEG this does nothing different from write
1081
write(null, image, param);
1082    }
1083
1084    public void endWriteSequence() throws IOException JavaDoc {
1085        if (sequencePrepared == false) {
1086            throw new IllegalStateException JavaDoc("sequencePrepared not called!");
1087        }
1088        sequencePrepared = false;
1089    }
1090
1091    public synchronized void abort() {
1092        super.abort();
1093        abortWrite(structPointer);
1094    }
1095
1096    private void resetInternalState() {
1097        // reset C structures
1098
resetWriter(structPointer);
1099
1100        // reset local Java structures
1101
srcRas = null;
1102        raster = null;
1103        convertTosRGB = false;
1104        currentImage = 0;
1105        numScans = 0;
1106        metadata = null;
1107    }
1108
1109    /**
1110     * Note that there is no need to override reset() here, as the default
1111     * implementation will call setOutput(null), which will invoke
1112     * resetInternalState().
1113     */

1114
1115    public void dispose() {
1116        if (structPointer != 0) {
1117            disposerRecord.dispose();
1118            structPointer = 0;
1119        }
1120    }
1121
1122    ////////// End of public API
1123

1124    ///////// Package-access API
1125

1126    /**
1127     * Called by the native code or other classes to signal a warning.
1128     * The code is used to lookup a localized message to be used when
1129     * sending warnings to listeners.
1130     */

1131    void warningOccurred(int code) {
1132        if ((code < 0) || (code > MAX_WARNING)){
1133            throw new InternalError JavaDoc("Invalid warning index");
1134        }
1135        processWarningOccurred
1136            (currentImage,
1137             "com.sun.imageio.plugins.jpeg.JPEGImageWriterResources",
1138             Integer.toString(code));
1139    }
1140
1141    /**
1142     * The library has it's own error facility that emits warning messages.
1143     * This routine is called by the native code when it has already
1144     * formatted a string for output.
1145     * XXX For truly complete localization of all warning messages,
1146     * the sun_jpeg_output_message routine in the native code should
1147     * send only the codes and parameters to a method here in Java,
1148     * which will then format and send the warnings, using localized
1149     * strings. This method will have to deal with all the parameters
1150     * and formats (%u with possibly large numbers, %02d, %02x, etc.)
1151     * that actually occur in the JPEG library. For now, this prevents
1152     * library warnings from being printed to stderr.
1153     */

1154    void warningWithMessage(String JavaDoc msg) {
1155        processWarningOccurred(currentImage, msg);
1156    }
1157
1158    void thumbnailStarted(int thumbnailIndex) {
1159        processThumbnailStarted(currentImage, thumbnailIndex);
1160    }
1161
1162    // Provide access to protected superclass method
1163
void thumbnailProgress(float percentageDone) {
1164        processThumbnailProgress(percentageDone);
1165    }
1166
1167    // Provide access to protected superclass method
1168
void thumbnailComplete() {
1169        processThumbnailComplete();
1170    }
1171
1172    ///////// End of Package-access API
1173

1174    ///////// Private methods
1175

1176    ///////// Metadata handling
1177

1178    private void checkSOFBands(SOFMarkerSegment sof, int numBandsUsed)
1179        throws IIOException JavaDoc {
1180        // Does the metadata frame header, if any, match numBandsUsed?
1181
if (sof != null) {
1182            if (sof.componentSpecs.length != numBandsUsed) {
1183                throw new IIOException JavaDoc
1184                    ("Metadata components != number of destination bands");
1185            }
1186        }
1187    }
1188
1189    private void checkJFIF(JFIFMarkerSegment jfif,
1190                           ImageTypeSpecifier JavaDoc type,
1191                           boolean input) {
1192        if (jfif != null) {
1193            if (!JPEG.isJFIFcompliant(type, input)) {
1194                ignoreJFIF = true; // type overrides metadata
1195
warningOccurred(input
1196                                ? WARNING_IMAGE_METADATA_JFIF_MISMATCH
1197                                : WARNING_DEST_METADATA_JFIF_MISMATCH);
1198            }
1199        }
1200    }
1201
1202    private void checkAdobe(AdobeMarkerSegment adobe,
1203                           ImageTypeSpecifier JavaDoc type,
1204                           boolean input) {
1205        if (adobe != null) {
1206            int rightTransform = JPEG.transformForType(type, input);
1207            if (adobe.transform != rightTransform) {
1208                warningOccurred(input
1209                                ? WARNING_IMAGE_METADATA_ADOBE_MISMATCH
1210                                : WARNING_DEST_METADATA_ADOBE_MISMATCH);
1211                if (rightTransform == JPEG.ADOBE_IMPOSSIBLE) {
1212                    ignoreAdobe = true;
1213                } else {
1214                    newAdobeTransform = rightTransform;
1215                }
1216            }
1217        }
1218    }
1219
1220    /**
1221     * Collect all the scan info from the given metadata, and
1222     * organize it into the scan info array required by the
1223     * IJG libray. It is much simpler to parse out this
1224     * data in Java and then just copy the data in C.
1225     */

1226    private int [] collectScans(JPEGMetadata metadata,
1227                                SOFMarkerSegment sof) {
1228        List JavaDoc segments = new ArrayList JavaDoc();
1229        int SCAN_SIZE = 9;
1230        int MAX_COMPS_PER_SCAN = 4;
1231        for (Iterator JavaDoc iter = metadata.markerSequence.iterator();
1232             iter.hasNext();) {
1233            MarkerSegment seg = (MarkerSegment) iter.next();
1234            if (seg instanceof SOSMarkerSegment) {
1235                segments.add(seg);
1236            }
1237        }
1238        int [] retval = null;
1239        numScans = 0;
1240        if (!segments.isEmpty()) {
1241            numScans = segments.size();
1242            retval = new int [numScans*SCAN_SIZE];
1243            int index = 0;
1244            for (int i = 0; i < numScans; i++) {
1245                SOSMarkerSegment sos = (SOSMarkerSegment) segments.get(i);
1246                retval[index++] = sos.componentSpecs.length; // num comps
1247
for (int j = 0; j < MAX_COMPS_PER_SCAN; j++) {
1248                    if (j < sos.componentSpecs.length) {
1249                        int compSel = sos.componentSpecs[j].componentSelector;
1250                        for (int k = 0; k < sof.componentSpecs.length; k++) {
1251                            if (compSel == sof.componentSpecs[k].componentId) {
1252                                retval[index++] = k;
1253                                break; // out of for over sof comps
1254
}
1255                        }
1256                    } else {
1257                        retval[index++] = 0;
1258                    }
1259                }
1260                retval[index++] = sos.startSpectralSelection;
1261                retval[index++] = sos.endSpectralSelection;
1262                retval[index++] = sos.approxHigh;
1263                retval[index++] = sos.approxLow;
1264            }
1265        }
1266        return retval;
1267    }
1268
1269    /**
1270     * Finds all DQT marker segments and returns all the q
1271     * tables as a single array of JPEGQTables.
1272     */

1273    private JPEGQTable JavaDoc [] collectQTablesFromMetadata
1274        (JPEGMetadata metadata) {
1275        ArrayList JavaDoc tables = new ArrayList JavaDoc();
1276        Iterator JavaDoc iter = metadata.markerSequence.iterator();
1277        while (iter.hasNext()) {
1278            MarkerSegment seg = (MarkerSegment) iter.next();
1279            if (seg instanceof DQTMarkerSegment) {
1280                DQTMarkerSegment dqt =
1281                    (DQTMarkerSegment) seg;
1282                tables.addAll(dqt.tables);
1283            }
1284        }
1285        JPEGQTable JavaDoc [] retval = null;
1286        if (tables.size() != 0) {
1287            retval = new JPEGQTable JavaDoc[tables.size()];
1288            for (int i = 0; i < retval.length; i++) {
1289                retval[i] =
1290                    new JPEGQTable JavaDoc(((DQTMarkerSegment.Qtable)tables.get(i)).data);
1291            }
1292        }
1293        return retval;
1294    }
1295
1296    /**
1297     * Finds all DHT marker segments and returns all the q
1298     * tables as a single array of JPEGQTables. The metadata
1299     * must not be for a progressive image, or an exception
1300     * will be thrown when two Huffman tables with the same
1301     * table id are encountered.
1302     */

1303    private JPEGHuffmanTable JavaDoc[] collectHTablesFromMetadata
1304        (JPEGMetadata metadata, boolean wantDC) throws IIOException JavaDoc {
1305        ArrayList JavaDoc tables = new ArrayList JavaDoc();
1306        Iterator JavaDoc iter = metadata.markerSequence.iterator();
1307        while (iter.hasNext()) {
1308            MarkerSegment seg = (MarkerSegment) iter.next();
1309            if (seg instanceof DHTMarkerSegment) {
1310                DHTMarkerSegment dht =
1311                    (DHTMarkerSegment) seg;
1312                for (int i = 0; i < dht.tables.size(); i++) {
1313                    DHTMarkerSegment.Htable htable =
1314                        (DHTMarkerSegment.Htable) dht.tables.get(i);
1315                    if (htable.tableClass == (wantDC ? 0 : 1)) {
1316                        tables.add(htable);
1317                    }
1318                }
1319            }
1320        }
1321        JPEGHuffmanTable JavaDoc [] retval = null;
1322        if (tables.size() != 0) {
1323            DHTMarkerSegment.Htable [] htables =
1324                new DHTMarkerSegment.Htable[tables.size()];
1325            tables.toArray(htables);
1326            retval = new JPEGHuffmanTable JavaDoc[tables.size()];
1327            for (int i = 0; i < retval.length; i++) {
1328                retval[i] = null;
1329                for (int j = 0; j < tables.size(); j++) {
1330                    if (htables[j].tableID == i) {
1331                        if (retval[i] != null) {
1332                            throw new IIOException JavaDoc("Metadata has duplicate Htables!");
1333                        }
1334                        retval[i] = new JPEGHuffmanTable JavaDoc(htables[j].numCodes,
1335                                                         htables[j].values);
1336                    }
1337                }
1338            }
1339        }
1340        
1341        return retval;
1342    }
1343
1344    /////////// End of metadata handling
1345

1346    ////////////// ColorSpace conversion
1347

1348    private int getSrcCSType(RenderedImage JavaDoc rimage) {
1349        int retval = JPEG.JCS_UNKNOWN;
1350        ColorModel JavaDoc cm = rimage.getColorModel();
1351        if (cm != null) {
1352            boolean alpha = cm.hasAlpha();
1353            ColorSpace JavaDoc cs = cm.getColorSpace();
1354            switch (cs.getType()) {
1355            case ColorSpace.TYPE_GRAY:
1356                retval = JPEG.JCS_GRAYSCALE;
1357                break;
1358            case ColorSpace.TYPE_RGB:
1359                if (alpha) {
1360                    retval = JPEG.JCS_RGBA;
1361                } else {
1362                    retval = JPEG.JCS_RGB;
1363                }
1364                break;
1365            case ColorSpace.TYPE_YCbCr:
1366                if (alpha) {
1367                    retval = JPEG.JCS_YCbCrA;
1368                } else {
1369                    retval = JPEG.JCS_YCbCr;
1370                }
1371                break;
1372            case ColorSpace.TYPE_3CLR:
1373                if (cs == JPEG.YCC) {
1374                    if (alpha) {
1375                        retval = JPEG.JCS_YCCA;
1376                    } else {
1377                        retval = JPEG.JCS_YCC;
1378                    }
1379                }
1380            case ColorSpace.TYPE_CMYK:
1381                retval = JPEG.JCS_CMYK;
1382                break;
1383            }
1384        }
1385        return retval;
1386    }
1387
1388    private int getDestCSType(ImageTypeSpecifier JavaDoc destType) {
1389        ColorModel JavaDoc cm = destType.getColorModel();
1390        boolean alpha = cm.hasAlpha();
1391        ColorSpace JavaDoc cs = cm.getColorSpace();
1392        int retval = JPEG.JCS_UNKNOWN;
1393        switch (cs.getType()) {
1394        case ColorSpace.TYPE_GRAY:
1395                retval = JPEG.JCS_GRAYSCALE;
1396                break;
1397            case ColorSpace.TYPE_RGB:
1398                if (alpha) {
1399                    retval = JPEG.JCS_RGBA;
1400                } else {
1401                    retval = JPEG.JCS_RGB;
1402                }
1403                break;
1404            case ColorSpace.TYPE_YCbCr:
1405                if (alpha) {
1406                    retval = JPEG.JCS_YCbCrA;
1407                } else {
1408                    retval = JPEG.JCS_YCbCr;
1409                }
1410                break;
1411            case ColorSpace.TYPE_3CLR:
1412                if (cs == JPEG.YCC) {
1413                    if (alpha) {
1414                        retval = JPEG.JCS_YCCA;
1415                    } else {
1416                        retval = JPEG.JCS_YCC;
1417                    }
1418                }
1419            case ColorSpace.TYPE_CMYK:
1420                retval = JPEG.JCS_CMYK;
1421                break;
1422            }
1423        return retval;
1424        }
1425
1426    private int getDefaultDestCSType(RenderedImage JavaDoc rimage) {
1427        int retval = JPEG.JCS_UNKNOWN;
1428        ColorModel JavaDoc cm = rimage.getColorModel();
1429        if (cm != null) {
1430            boolean alpha = cm.hasAlpha();
1431            ColorSpace JavaDoc cs = cm.getColorSpace();
1432            switch (cs.getType()) {
1433            case ColorSpace.TYPE_GRAY:
1434                retval = JPEG.JCS_GRAYSCALE;
1435                break;
1436            case ColorSpace.TYPE_RGB:
1437                if (alpha) {
1438                    retval = JPEG.JCS_YCbCrA;
1439                } else {
1440                    retval = JPEG.JCS_YCbCr;
1441                }
1442                break;
1443            case ColorSpace.TYPE_YCbCr:
1444                if (alpha) {
1445                    retval = JPEG.JCS_YCbCrA;
1446                } else {
1447                    retval = JPEG.JCS_YCbCr;
1448                }
1449                break;
1450            case ColorSpace.TYPE_3CLR:
1451                if (cs == JPEG.YCC) {
1452                    if (alpha) {
1453                        retval = JPEG.JCS_YCCA;
1454                    } else {
1455                        retval = JPEG.JCS_YCC;
1456                    }
1457                }
1458            case ColorSpace.TYPE_CMYK:
1459                retval = JPEG.JCS_YCCK;
1460                break;
1461            }
1462        }
1463        return retval;
1464    }
1465
1466    private boolean isSubsampled(SOFMarkerSegment.ComponentSpec [] specs) {
1467        int hsamp0 = specs[0].HsamplingFactor;
1468        int vsamp0 = specs[0].VsamplingFactor;
1469        for (int i = 1; i < specs.length; i++) {
1470            if ((specs[i].HsamplingFactor != hsamp0) ||
1471                (specs[i].HsamplingFactor != hsamp0))
1472                return true;
1473        }
1474        return false;
1475    }
1476
1477    ////////////// End of ColorSpace conversion
1478

1479    ////////////// Native methods and callbacks
1480

1481    /** Sets up static native structures. */
1482    private static native void initWriterIDs(Class JavaDoc iosClass,
1483                                             Class JavaDoc qTableClass,
1484                                             Class JavaDoc huffClass);
1485
1486    /** Sets up per-writer native structure and returns a pointer to it. */
1487    private native long initJPEGImageWriter();
1488
1489    /** Sets up native structures for output stream */
1490    private native void setDest(long structPointer,
1491                                ImageOutputStream JavaDoc ios);
1492
1493    /**
1494     * Returns <code>true</code> if the write was aborted.
1495     */

1496    private native boolean writeImage(long structPointer,
1497                                      byte [] data,
1498                                      int inCsType, int outCsType,
1499                                      int numBands,
1500                                      int [] bandSizes,
1501                                      int srcWidth,
1502                                      int destWidth, int destHeight,
1503                                      int stepX, int stepY,
1504                                      JPEGQTable JavaDoc [] qtables,
1505                                      boolean writeDQT,
1506                                      JPEGHuffmanTable JavaDoc[] DCHuffmanTables,
1507                                      JPEGHuffmanTable JavaDoc[] ACHuffmanTables,
1508                                      boolean writeDHT,
1509                                      boolean optimizeHuffman,
1510                                      boolean progressive,
1511                                      int numScans,
1512                                      int [] scans,
1513                                      int [] componentIds,
1514                                      int [] HsamplingFactors,
1515                                      int [] VsamplingFactors,
1516                                      int [] QtableSelectors,
1517                                      boolean haveMetadata,
1518                                      int restartInterval);
1519
1520
1521    /**
1522     * Writes the metadata out when called by the native code,
1523     * which will have already written the header to the stream
1524     * and established the library state. This is simpler than
1525     * breaking the write call in two.
1526     */

1527    private void writeMetadata() throws IOException JavaDoc {
1528        if (metadata == null) {
1529            if (writeDefaultJFIF) {
1530                JFIFMarkerSegment.writeDefaultJFIF(ios,
1531                                                   thumbnails,
1532                                                   iccProfile,
1533                                                   this);
1534            }
1535            if (writeAdobe) {
1536                AdobeMarkerSegment.writeAdobeSegment(ios, newAdobeTransform);
1537            }
1538        } else {
1539            metadata.writeToStream(ios,
1540                                   ignoreJFIF,
1541                                   forceJFIF,
1542                                   thumbnails,
1543                                   iccProfile,
1544                                   ignoreAdobe,
1545                                   newAdobeTransform,
1546                                   this);
1547        }
1548    }
1549
1550    /**
1551     * Write out a tables-only image to the stream.
1552     */

1553    private native void writeTables(long structPointer,
1554                                    JPEGQTable JavaDoc [] qtables,
1555                                    JPEGHuffmanTable JavaDoc[] DCHuffmanTables,
1556                                    JPEGHuffmanTable JavaDoc[] ACHuffmanTables);
1557
1558    /**
1559     * Put the scanline y of the source ROI view Raster into the
1560     * 1-line Raster for writing. This handles ROI and band
1561     * rearrangements, and expands indexed images. Subsampling is
1562     * done in the native code.
1563     * This is called by the native code.
1564     */

1565    private void grabPixels(int y) {
1566
1567        Raster JavaDoc sourceLine = null;
1568        if (indexed) {
1569            sourceLine = srcRas.createChild(sourceXOffset,
1570                                            sourceYOffset+y,
1571                                            sourceWidth, 1,
1572                                            0, 0,
1573                                            new int [] {0});
1574            // If the image has BITMASK transparency, we need to make sure
1575
// it gets converted to 32-bit ARGB, because the JPEG encoder
1576
// relies upon the full 8-bit alpha channel.
1577
boolean forceARGB =
1578                (indexCM.getTransparency() != Transparency.OPAQUE);
1579            BufferedImage JavaDoc temp = indexCM.convertToIntDiscrete(sourceLine,
1580                                                              forceARGB);
1581            sourceLine = temp.getRaster();
1582        } else {
1583            sourceLine = srcRas.createChild(sourceXOffset,
1584                                            sourceYOffset+y,
1585                                            sourceWidth, 1,
1586                                            0, 0,
1587                                            srcBands);
1588        }
1589        if (convertTosRGB) {
1590            if (debug) {
1591                System.out.println("Converting to sRGB");
1592            }
1593            // The first time through, converted is null, so
1594
// a new raster is allocated. It is then reused
1595
// on subsequent lines.
1596
converted = convertOp.filter(sourceLine, converted);
1597            sourceLine = converted;
1598        }
1599        if (isAlphaPremultiplied) {
1600            WritableRaster JavaDoc wr = sourceLine.createCompatibleWritableRaster();
1601            int[] data = null;
1602            data = sourceLine.getPixels(sourceLine.getMinX(), sourceLine.getMinY(),
1603                                        sourceLine.getWidth(), sourceLine.getHeight(),
1604                                        data);
1605            wr.setPixels(sourceLine.getMinX(), sourceLine.getMinY(),
1606                         sourceLine.getWidth(), sourceLine.getHeight(),
1607                         data);
1608            srcCM.coerceData(wr, false);
1609            sourceLine = wr.createChild(wr.getMinX(), wr.getMinY(),
1610                                        wr.getWidth(), wr.getHeight(),
1611                                        0, 0,
1612                                        srcBands);
1613        }
1614        raster.setRect(sourceLine);
1615        if ((y > 7) && (y%8 == 0)) { // Every 8 scanlines
1616
processImageProgress((float) y / (float) sourceHeight * 100.0F);
1617        }
1618    }
1619
1620    /** Aborts the current write in the native code */
1621    private native void abortWrite(long structPointer);
1622
1623    /** Resets native structures */
1624    private native void resetWriter(long structPointer);
1625
1626    /** Releases native structures */
1627    private static native void disposeWriter(long structPointer);
1628
1629    private static class JPEGWriterDisposerRecord extends DisposerRecord {
1630        private long pData;
1631
1632        public JPEGWriterDisposerRecord(long pData) {
1633            this.pData = pData;
1634        }
1635
1636        public synchronized void dispose() {
1637            if (pData != 0) {
1638                disposeWriter(pData);
1639                pData = 0;
1640            }
1641        }
1642    }
1643}
1644
Popular Tags