001: /*
002: * $RCSfile: CLibJPEGImageWriter.java,v $
003: *
004: *
005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * - Redistribution of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * - Redistribution in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * Neither the name of Sun Microsystems, Inc. or the names of
020: * contributors may be used to endorse or promote products derived
021: * from this software without specific prior written permission.
022: *
023: * This software is provided "AS IS," without a warranty of any
024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035: * POSSIBILITY OF SUCH DAMAGES.
036: *
037: * You acknowledge that this software is not designed or intended for
038: * use in the design, construction, operation or maintenance of any
039: * nuclear facility.
040: *
041: * $Revision: 1.5 $
042: * $Date: 2006/04/26 01:14:14 $
043: * $State: Exp $
044: */
045: package com.sun.media.imageioimpl.plugins.jpeg;
046:
047: import java.awt.image.BufferedImage;
048: import java.awt.image.ColorModel;
049: import java.awt.image.DirectColorModel;
050: import java.awt.image.IndexColorModel;
051: import java.awt.image.PackedColorModel;
052: import java.awt.image.Raster;
053: import java.awt.image.RenderedImage;
054: import java.awt.image.WritableRaster;
055: import java.io.IOException;
056: import java.io.OutputStream;
057: import java.util.Locale;
058: import javax.imageio.IIOException;
059: import javax.imageio.IIOImage;
060: import javax.imageio.ImageWriter;
061: import javax.imageio.ImageWriteParam;
062: import javax.imageio.ImageTypeSpecifier;
063: import javax.imageio.metadata.IIOMetadata;
064: import javax.imageio.spi.ImageWriterSpi;
065: import javax.imageio.stream.ImageOutputStream;
066: import com.sun.media.imageioimpl.common.ImageUtil;
067: import com.sun.media.imageioimpl.plugins.clib.CLibImageWriter;
068: import com.sun.media.imageioimpl.plugins.clib.OutputStreamAdapter;
069: import com.sun.medialib.codec.jpeg.Encoder;
070: import com.sun.medialib.codec.jiio.Constants;
071: import com.sun.medialib.codec.jiio.mediaLibImage;
072:
073: final class CLibJPEGImageWriter extends CLibImageWriter {
074: private Encoder encoder;
075:
076: /**
077: * Convert an IndexColorModel-based image to 3-band component RGB.
078: *
079: * @param im The source image.
080: * @throws IllegalArgumentException if the parameter is <code>null</code>.
081: * @throws IllegalArgumentException if the source does is not indexed.
082: */
083: private static BufferedImage convertTo3BandRGB(RenderedImage im) {
084: // Check parameter.
085: if (im == null) {
086: throw new IllegalArgumentException("im == null");
087: }
088:
089: ColorModel cm = im.getColorModel();
090: if (!(cm instanceof IndexColorModel)) {
091: throw new IllegalArgumentException(
092: "!(im.getColorModel() instanceof IndexColorModel)");
093: }
094:
095: Raster src;
096: if (im.getNumXTiles() == 1 && im.getNumYTiles() == 1) {
097: // Image is not tiled so just get a reference to the tile.
098: src = im.getTile(im.getMinTileX(), im.getMinTileY());
099:
100: if (src.getWidth() != im.getWidth()
101: || src.getHeight() != im.getHeight()) {
102: src = src.createChild(src.getMinX(), src.getMinY(), im
103: .getWidth(), im.getHeight(), src.getMinX(), src
104: .getMinY(), null);
105: }
106: } else {
107: // Image is tiled so need to get a contiguous raster.
108: src = im.getData();
109: }
110:
111: // This is probably not the most efficient approach given that
112: // the mediaLibImage will eventually need to be in component form.
113: BufferedImage dst = ((IndexColorModel) cm)
114: .convertToIntDiscrete(src, false);
115:
116: if (dst.getSampleModel().getNumBands() == 4) {
117: //
118: // Without copying data create a BufferedImage which has
119: // only the RGB bands, not the alpha band.
120: //
121: WritableRaster rgbaRas = dst.getRaster();
122: WritableRaster rgbRas = rgbaRas.createWritableChild(0, 0,
123: dst.getWidth(), dst.getHeight(), 0, 0, new int[] {
124: 0, 1, 2 });
125: PackedColorModel pcm = (PackedColorModel) dst
126: .getColorModel();
127: int bits = pcm.getComponentSize(0)
128: + pcm.getComponentSize(1) + pcm.getComponentSize(2);
129: DirectColorModel dcm = new DirectColorModel(bits, pcm
130: .getMask(0), pcm.getMask(1), pcm.getMask(2));
131: dst = new BufferedImage(dcm, rgbRas, false, null);
132: }
133:
134: return dst;
135: }
136:
137: CLibJPEGImageWriter(ImageWriterSpi originatingProvider)
138: throws IOException {
139: super (originatingProvider);
140:
141: try {
142: encoder = new Encoder();
143: encoder.setExtend(Encoder.JPEG_IMAGE_NONEXTENDED);
144: } catch (Throwable t) {
145: throw new IIOException("codecLib error", t);
146: }
147: }
148:
149: public ImageWriteParam getDefaultWriteParam() {
150: return new CLibJPEGImageWriteParam(getLocale());
151: }
152:
153: public void write(IIOMetadata streamMetadata, IIOImage image,
154: ImageWriteParam param) throws IOException {
155: if (output == null) {
156: throw new IllegalStateException("output == null");
157: }
158:
159: OutputStream stream = null;
160: if (output instanceof ImageOutputStream) {
161: stream = new OutputStreamAdapter((ImageOutputStream) output);
162: } else {
163: throw new IllegalArgumentException(
164: "!(output instanceof ImageOutputStream)");
165: }
166:
167: RenderedImage renderedImage = image.getRenderedImage();
168:
169: if (renderedImage.getColorModel() instanceof IndexColorModel) {
170: renderedImage = convertTo3BandRGB(renderedImage);
171: }
172:
173: // Test for all.
174: ImageUtil.canEncodeImage(this , renderedImage.getColorModel(),
175: renderedImage.getSampleModel());
176:
177: // Test for baseline.
178: int bitDepth = renderedImage.getColorModel()
179: .getComponentSize(0);
180: if ((param == null || (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT && !param
181: .isCompressionLossless()))
182: && bitDepth > 12) {
183: throw new IIOException(
184: "JPEG baseline encoding is limited to 12 bits: "
185: + this );
186: }
187:
188: // Set compression mode and quality from ImageWriteParam, if any.
189: if (param != null
190: && param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
191: if (param.isCompressionLossless()) {
192: try {
193: if (bitDepth >= 2 && bitDepth <= 16
194: && bitDepth % 8 != 0) {
195: encoder.setDepth(bitDepth);
196: }
197: if (param
198: .getCompressionType()
199: .equalsIgnoreCase(
200: CLibJPEGImageWriteParam.LOSSLESS_COMPRESSION_TYPE)) {
201: encoder.setMode(Encoder.JPEG_MODE_LOSSLESS);
202: } else {
203: encoder.setMode(Encoder.JPEG_MODE_HPLOCO);
204: }
205: } catch (Throwable t) {
206: throw new IIOException("codecLib error", t);
207: }
208: } else {
209: try {
210: encoder.setMode(Encoder.JPEG_MODE_BASELINE);
211: // XXX Q == 100 caused a core dump during testing.
212: encoder.setQuality((int) (param
213: .getCompressionQuality() * 100));
214: } catch (Throwable t) {
215: throw new IIOException("codecLib error", t);
216: }
217: }
218: } else {
219: try {
220: encoder.setMode(Encoder.JPEG_MODE_BASELINE);
221: encoder.setQuality(75);
222: } catch (Throwable t) {
223: throw new IIOException("codecLib error", t);
224: }
225: }
226:
227: int[] supportedFormats = param == null
228: || (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT && !param
229: .isCompressionLossless()) ? new int[] {
230: Constants.MLIB_FORMAT_GRAYSCALE,
231: Constants.MLIB_FORMAT_GRAYSCALE_ALPHA,
232: Constants.MLIB_FORMAT_BGR, Constants.MLIB_FORMAT_RGB,
233: Constants.MLIB_FORMAT_CMYK } : // baseline
234: new int[] { Constants.MLIB_FORMAT_GRAYSCALE,
235: Constants.MLIB_FORMAT_RGB }; // lossless & LS
236: mediaLibImage mlibImage = getMediaLibImage(renderedImage,
237: param, false, supportedFormats);
238:
239: try {
240: if (mlibImage.getChannels() == 2) {
241: // GRAYSCALE_ALPHA
242: encoder.setType(Encoder.JPEG_TYPE_GRAYSCALE);
243: } else if (mlibImage.getChannels() == 4) {
244: // XXX The selection of CMYK (Adobe transform 0) or
245: // YCCK (Adobe transform 2) should probably be made
246: // on the basis of image metadata passed in so this
247: // code should be modified once the writer supports
248: // image metadata. Until then select CMYK type which
249: // will generate Adobe transform 0 and non-subsampled
250: // data.
251: if (mlibImage.getFormat() == Constants.MLIB_FORMAT_CMYK) {
252: // CMYK
253: encoder.setType(Encoder.JPEG_TYPE_CMYK);
254: } else if (mlibImage.getFormat() == Constants.MLIB_FORMAT_YCCK) {
255: // YCCK
256: encoder.setType(Encoder.JPEG_TYPE_YCCK);
257: }
258: }
259: encoder.encode(stream, mlibImage);
260: } catch (Throwable t) {
261: throw new IIOException("codecLib error", t);
262: }
263: }
264: }
265:
266: /**
267: * This differs from the core JPEG ImageWriteParam in that:
268: *
269: * <ul>
270: * <li>compression types are: "JPEG" (standard), "JPEG-LOSSLESS"
271: * (lossless JPEG from 10918-1/ITU-T81), "JPEG-LS" (ISO 14495-1 lossless).</li>
272: * <li>compression modes are: MODE_DEFAULT and MODE_EXPLICIT and the
273: * other modes (MODE_DISABLED and MODE_COPY_FROM_METADATA) cause
274: * an UnsupportedOperationException.</li>
275: * <li>isCompressionLossless() will return true if type is NOT "JPEG".</li>
276: * </ul>
277: */
278: final class CLibJPEGImageWriteParam extends ImageWriteParam {
279: private static final float DEFAULT_COMPRESSION_QUALITY = 0.75F;
280:
281: static final String LOSSY_COMPRESSION_TYPE = "JPEG";
282: static final String LOSSLESS_COMPRESSION_TYPE = "JPEG-LOSSLESS";
283: static final String LS_COMPRESSION_TYPE = "JPEG-LS";
284:
285: private static final String[] compressionQualityDescriptions = new String[] {
286: I18N.getString("CLibJPEGImageWriteParam0"),
287: I18N.getString("CLibJPEGImageWriteParam1"),
288: I18N.getString("CLibJPEGImageWriteParam2") };
289:
290: CLibJPEGImageWriteParam(Locale locale) {
291: super (locale);
292:
293: canWriteCompressed = true;
294: compressionMode = MODE_EXPLICIT;
295: compressionQuality = DEFAULT_COMPRESSION_QUALITY;
296: compressionType = LOSSY_COMPRESSION_TYPE;
297: compressionTypes = new String[] { LOSSY_COMPRESSION_TYPE,
298: LOSSLESS_COMPRESSION_TYPE, LS_COMPRESSION_TYPE };
299: }
300:
301: public String[] getCompressionQualityDescriptions() {
302: super .getCompressionQualityDescriptions(); // Performs checks.
303:
304: return compressionQualityDescriptions;
305: }
306:
307: public float[] getCompressionQualityValues() {
308: super .getCompressionQualityValues(); // Performs checks.
309:
310: return new float[] { 0.05F, // "Minimum useful"
311: 0.75F, // "Visually lossless"
312: 0.95F }; // "Maximum useful"
313: }
314:
315: public boolean isCompressionLossless() {
316: super .isCompressionLossless(); // Performs checks.
317:
318: return !compressionType
319: .equalsIgnoreCase(LOSSY_COMPRESSION_TYPE);
320: }
321:
322: public void setCompressionMode(int mode) {
323: if (mode == MODE_DISABLED || mode == MODE_COPY_FROM_METADATA) {
324: throw new UnsupportedOperationException(
325: "mode == MODE_DISABLED || mode == MODE_COPY_FROM_METADATA");
326: }
327:
328: super .setCompressionMode(mode); // This sets the instance variable.
329: }
330:
331: public void unsetCompression() {
332: super .unsetCompression(); // Performs checks.
333:
334: compressionQuality = DEFAULT_COMPRESSION_QUALITY;
335: compressionType = LOSSY_COMPRESSION_TYPE;
336: }
337: }
|