001: /*
002: * $RCSfile: CLibPNGImageWriter.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.7 $
042: * $Date: 2006/11/01 22:37:00 $
043: * $State: Exp $
044: */
045: package com.sun.media.imageioimpl.plugins.png;
046:
047: import java.awt.Color;
048: import java.awt.color.ColorSpace;
049: import java.awt.color.ICC_ColorSpace;
050: import java.awt.color.ICC_Profile;
051: import java.awt.image.ColorModel;
052: import java.awt.image.IndexColorModel;
053: import java.awt.image.RenderedImage;
054: import java.awt.image.SampleModel;
055: import java.io.IOException;
056: import java.io.OutputStream;
057: import java.util.Arrays;
058: import java.util.Locale;
059: import javax.imageio.IIOException;
060: import javax.imageio.IIOImage;
061: import javax.imageio.ImageWriter;
062: import javax.imageio.ImageWriteParam;
063: import javax.imageio.ImageTypeSpecifier;
064: import javax.imageio.metadata.IIOInvalidTreeException;
065: import javax.imageio.metadata.IIOMetadata;
066: import javax.imageio.metadata.IIOMetadataFormatImpl;
067: import javax.imageio.spi.ImageWriterSpi;
068: import javax.imageio.stream.ImageOutputStream;
069: import com.sun.media.imageioimpl.common.ImageUtil;
070: import com.sun.media.imageioimpl.plugins.clib.CLibImageWriter;
071: import com.sun.media.imageioimpl.plugins.clib.OutputStreamAdapter;
072: import com.sun.medialib.codec.png.Constants;
073: import com.sun.medialib.codec.png.Encoder;
074: import com.sun.medialib.codec.jiio.mediaLibImage;
075:
076: final class CLibPNGImageWriter extends CLibImageWriter {
077:
078: CLibPNGImageWriter(ImageWriterSpi originatingProvider) {
079: super (originatingProvider);
080: }
081:
082: public ImageWriteParam getDefaultWriteParam() {
083: return new CLibPNGImageWriteParam(getLocale());
084: }
085:
086: public IIOMetadata getDefaultImageMetadata(
087: ImageTypeSpecifier imageType, ImageWriteParam param) {
088: CLibPNGMetadata m = new CLibPNGMetadata();
089: if (param != null && param.getDestinationType() != null) {
090: imageType = param.getDestinationType();
091: }
092: if (imageType != null) {
093: m.initialize(imageType, imageType.getSampleModel()
094: .getNumBands(), param, 0);
095: }
096: return m;
097: }
098:
099: public IIOMetadata convertImageMetadata(IIOMetadata inData,
100: ImageTypeSpecifier imageType, ImageWriteParam param) {
101: // Check arguments.
102: if (inData == null) {
103: throw new IllegalArgumentException("inData == null!");
104: }
105: if (imageType == null) {
106: throw new IllegalArgumentException("imageType == null!");
107: }
108:
109: CLibPNGMetadata outData = null;
110:
111: // Obtain a CLibPNGMetadata object.
112: if (inData instanceof CLibPNGMetadata) {
113: // Clone the input metadata.
114: outData = (CLibPNGMetadata) ((CLibPNGMetadata) inData)
115: .clone();
116: } else {
117: try {
118: outData = new CLibPNGMetadata(inData);
119: } catch (IIOInvalidTreeException e) {
120: // XXX Warning
121: outData = new CLibPNGMetadata();
122: }
123: }
124:
125: // Update the metadata per the image type and param.
126: outData.initialize(imageType, imageType.getSampleModel()
127: .getNumBands(), param, outData.IHDR_interlaceMethod);
128:
129: return outData;
130: }
131:
132: public void write(IIOMetadata streamMetadata, IIOImage image,
133: ImageWriteParam param) throws IOException {
134: if (output == null) {
135: throw new IllegalStateException("output == null");
136: }
137:
138: OutputStream stream = null;
139: if (output instanceof ImageOutputStream) {
140: stream = new OutputStreamAdapter((ImageOutputStream) output);
141: } else {
142: throw new IllegalArgumentException(
143: "!(output instanceof ImageOutputStream)");
144: }
145:
146: RenderedImage renderedImage = image.getRenderedImage();
147: ImageUtil.canEncodeImage(this , renderedImage.getColorModel(),
148: renderedImage.getSampleModel());
149: int[] supportedFormats = new int[] {
150: Constants.MLIB_FORMAT_GRAYSCALE,
151: Constants.MLIB_FORMAT_GRAYSCALE_ALPHA,
152: Constants.MLIB_FORMAT_INDEXED,
153: Constants.MLIB_FORMAT_BGR, Constants.MLIB_FORMAT_RGB,
154: Constants.MLIB_FORMAT_BGRA, Constants.MLIB_FORMAT_RGBA };
155: mediaLibImage mlImage = getMediaLibImage(renderedImage, param,
156: true, supportedFormats);
157:
158: Encoder encoder = null;
159: try {
160: encoder = new Encoder(mlImage);
161: } catch (Throwable t) {
162: throw new IIOException("codecLib error", t);
163: }
164:
165: // Set the maximum length of the iDAT chunk.
166: encoder.setIDATSize(8192);
167:
168: // Determine the image type.
169: ImageTypeSpecifier imageType;
170: if (param != null && param.getDestinationType() != null) {
171: imageType = param.getDestinationType();
172: } else if (mlImage.getType() == mediaLibImage.MLIB_BIT) {
173: if (renderedImage.getColorModel() instanceof IndexColorModel) {
174: imageType = new ImageTypeSpecifier(renderedImage
175: .getColorModel(), renderedImage
176: .getSampleModel());
177: } else {
178: int dataType = renderedImage.getSampleModel()
179: .getDataType();
180: imageType = ImageTypeSpecifier.createGrayscale(1,
181: dataType, false);
182: }
183: } else if (mlImage.getChannels() == renderedImage
184: .getSampleModel().getNumBands()) {
185: // Note: ImageTypeSpecifier.createFromRenderedImage() gave an
186: // incorrect result here for an indexed BufferedImage as the
187: // ImageTypeSpecifier generated by createFromBufferedImage()
188: // does not match the actual image.
189: imageType = new ImageTypeSpecifier(renderedImage);
190: } else {
191: SampleModel sm = renderedImage.getSampleModel();
192: int dataType = sm.getDataType();
193: int bitDepth = sm.getSampleSize(0);
194: int numBands = mlImage.getChannels();
195: switch (numBands) {
196: case 1:
197: imageType = ImageTypeSpecifier.createGrayscale(
198: bitDepth, dataType, false);
199: break;
200: case 2:
201: imageType = ImageTypeSpecifier.createGrayscale(
202: bitDepth, dataType, false, false);
203: break;
204: case 3:
205: ColorSpace cs = ColorSpace
206: .getInstance(ColorSpace.CS_sRGB);
207: imageType = ImageTypeSpecifier.createInterleaved(cs,
208: new int[] { 0, 1, 2 }, dataType, false, false);
209: break;
210: default:
211: throw new IIOException("Cannot encode image with "
212: + numBands + " bands!");
213: }
214: }
215:
216: // Get metadata.
217: IIOMetadata imageMetadata = image.getMetadata();
218:
219: if (imageMetadata != null) {
220: // Convert metadata.
221: imageMetadata = convertImageMetadata(imageMetadata,
222: imageType, param);
223: } else {
224: // Use default.
225: imageMetadata = getDefaultImageMetadata(imageType, param);
226: }
227:
228: // Set metadata on encoder.
229: ((CLibPNGMetadata) imageMetadata).writeMetadata(encoder);
230:
231: ColorModel colorModel = null;
232: if (param != null) {
233: ImageTypeSpecifier destinationType = param
234: .getDestinationType();
235: if (destinationType != null) {
236: colorModel = destinationType.getColorModel();
237: }
238:
239: // Set compression level to (int)(9*(1.0F - compressionQuality)).
240: if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
241: try {
242: int compressionLevel = (int) (9 * (1.0F - param
243: .getCompressionQuality()));
244: encoder.setCompressionLevel(compressionLevel);
245: } catch (Throwable t) {
246: throw new IIOException("codecLib error", t);
247: }
248:
249: // Set the strategy if appropriate.
250: if (param instanceof CLibPNGImageWriteParam) {
251: try {
252: encoder
253: .setStrategy(((CLibPNGImageWriteParam) param)
254: .getStrategy());
255: } catch (Throwable t) {
256: throw new IIOException("codecLib error", t);
257: }
258: }
259: }
260: } else { // null ImageWriteParam
261: try {
262: // Do not set the compression level: let it default.
263:
264: // Z_DEFAULT_STRATEGY
265: encoder.setStrategy(0);
266: } catch (Throwable t) {
267: throw new IIOException("codecLib error", t);
268: }
269: }
270:
271: if (colorModel == null) {
272: colorModel = renderedImage.getColorModel();
273: }
274:
275: // If no iCCP chunk is already in the metadata and the color space
276: // is a non-standard ICC color space, the write it to iCCP chunk.
277: if (!((CLibPNGMetadata) imageMetadata).iCCP_present
278: && colorModel != null
279: && ImageUtil.isNonStandardICCColorSpace(colorModel
280: .getColorSpace())) {
281: // Get the profile data.
282: ICC_ColorSpace iccColorSpace = (ICC_ColorSpace) colorModel
283: .getColorSpace();
284: ICC_Profile iccProfile = iccColorSpace.getProfile();
285: byte[] iccProfileData = iccColorSpace.getProfile()
286: .getData();
287:
288: // Get the profile name.
289: byte[] desc = iccProfile
290: .getData(ICC_Profile.icSigProfileDescriptionTag);
291: String profileName;
292: if (desc != null) {
293: long len = ((desc[8] & 0xff) << 24)
294: | ((desc[9] & 0xff) << 16)
295: | ((desc[10] & 0xff) << 8) | (desc[11] & 0xff);
296: profileName = new String(desc, 12, (int) len);
297: } else {
298: profileName = "ICCProfile";
299: }
300:
301: // Set the profile on the Encoder.
302: profileName = CLibPNGMetadata
303: .toPrintableLatin1(profileName);
304: encoder.setEmbeddedICCProfile(profileName, iccProfileData);
305: }
306:
307: try {
308: encoder.encode(stream);
309: } catch (Throwable t) {
310: throw new IIOException("codecLib error", t);
311: }
312: }
313: }
314:
315: /**
316: * This differs from the core PNG ImageWriteParam in that:
317: *
318: * . 'canWriteCompressed' is set to 'true' so that canWriteCompressed()
319: * will return 'true'.
320: * . compression types are: "DEFAULT", "FILTERED", and "HUFFMAN_ONLY"
321: * and are used to set the encoder strategy to Z_DEFAULT, Z_FILTERED,
322: * and Z_HUFFMAN_ONLY as described in the PNG specification.
323: * . compression modes are: MODE_DEFAULT, MODE_EXPLICIT and
324: * MODE_COPY_FROM_METADATA); MODE_DISABLED is not allowed.
325: * . compression quality is used to set the compression level of the
326: * encoder according to:
327: *
328: * compressionLevel = (int)(9*(1.0F - compressionQuality))
329: *
330: * As in the core PNG writer, a progressiveMode of MODE_DEFAULT sets
331: * Adam7 interlacing whereas MODE_DISABLED sets default interlacing,
332: * i.e., none.
333: */
334: final class CLibPNGImageWriteParam extends ImageWriteParam {
335: private static final float DEFAULT_COMPRESSION_QUALITY = 1.0F / 3.0F;
336:
337: // Encoder strategies mapped to compression types.
338: private static final String DEFAULT_COMPRESSION_TYPE = "DEFAULT";
339: private static final String FILTERED_COMPRESSION_TYPE = "FILTERED";
340: private static final String HUFFMAN_COMPRESSION_TYPE = "HUFFMAN_ONLY";
341:
342: // Compression descriptions
343: private static final String[] compressionQualityDescriptions = new String[] {
344: I18N.getString("CLibPNGImageWriteParam0"),
345: I18N.getString("CLibPNGImageWriteParam1"),
346: I18N.getString("CLibPNGImageWriteParam2") };
347:
348: CLibPNGImageWriteParam(Locale locale) {
349: super (locale);
350:
351: canWriteCompressed = true;
352: canWriteProgressive = true;
353: compressionTypes = new String[] { DEFAULT_COMPRESSION_TYPE,
354: FILTERED_COMPRESSION_TYPE, HUFFMAN_COMPRESSION_TYPE };
355:
356: compressionQuality = DEFAULT_COMPRESSION_QUALITY;
357: compressionType = DEFAULT_COMPRESSION_TYPE;
358: }
359:
360: int getStrategy() {
361: if (compressionType.equals(FILTERED_COMPRESSION_TYPE)) {
362: return 1; // Z_FILTERED
363: } else if (compressionType.equals(HUFFMAN_COMPRESSION_TYPE)) {
364: return 2; // Z_HUFFMAN_ONLY
365: } else {
366: return 0; // Z_DEFAULT_STRATEGY
367: }
368: }
369:
370: public String[] getCompressionQualityDescriptions() {
371: super .getCompressionQualityDescriptions(); // Performs checks.
372:
373: return compressionQualityDescriptions;
374: }
375:
376: public float[] getCompressionQualityValues() {
377: super .getCompressionQualityValues(); // Performs checks.
378:
379: // According to the java.util.zip.Deflater class, the Deflater
380: // level 1 gives the best speed (short of no compression). Since
381: // quality is derived from level as
382: //
383: // quality = 1 - level/9
384: //
385: // this gives a value of 8.0/9.0 for the corresponding quality.
386: return new float[] { 0.0F, // "Best Compression"
387: (float) (8.0F / 9.0F), // "Best Speed"
388: 1.0F }; // "No Compression"
389: }
390:
391: public void setCompressionMode(int mode) {
392: if (mode == MODE_DISABLED) {
393: throw new UnsupportedOperationException(
394: "mode == MODE_DISABLED");
395: }
396:
397: super .setCompressionMode(mode); // This sets the instance variable.
398: }
399:
400: public void unsetCompression() {
401: super .unsetCompression(); // Performs checks.
402:
403: compressionQuality = DEFAULT_COMPRESSION_QUALITY;
404: compressionType = DEFAULT_COMPRESSION_TYPE;
405: }
406: }
|