001: /*
002: * $RCSfile: JPEGImageEncoder.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.5 $
009: * $Date: 2005/11/14 22:44:48 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.codecimpl;
013:
014: import java.awt.Point;
015: import java.awt.RenderingHints;
016: import java.awt.color.ColorSpace;
017: import java.awt.image.BufferedImage;
018: import java.awt.image.ColorModel;
019: import java.awt.image.DataBuffer;
020: import java.awt.image.DirectColorModel;
021: import java.awt.image.IndexColorModel;
022: import java.awt.image.PackedColorModel;
023: import java.awt.image.Raster;
024: import java.awt.image.RenderedImage;
025: import java.awt.image.SampleModel;
026: import java.awt.image.WritableRaster;
027: import java.io.IOException;
028: import java.io.OutputStream;
029: import com.sun.media.jai.codec.ImageEncoderImpl;
030: import com.sun.media.jai.codec.ImageEncodeParam;
031: import com.sun.media.jai.codec.JPEGEncodeParam; //
032: // Need these classes since we are currently using the
033: // Java2D JpegEncoder for our Jpeg Implementation.
034: //
035: import com.sun.image.codec.jpeg.JPEGQTable;
036: import com.sun.image.codec.jpeg.JPEGDecodeParam;
037: import com.sun.media.jai.codecimpl.ImagingListenerProxy;
038: import com.sun.media.jai.codecimpl.util.ImagingException;
039:
040: /**
041: * An ImageEncoder for the JPEG (JFIF) file format.
042: *
043: * The common cases of single band grayscale and three or four band RGB images
044: * are handled so as to minimize the amount of information required of the
045: * programmer. See the comments pertaining to the constructor and the
046: * <code>writeToStream()</code> method for more detailed information.
047: *
048: * @since EA2
049: */
050: public class JPEGImageEncoder extends ImageEncoderImpl {
051:
052: private JPEGEncodeParam jaiEP = null;
053:
054: public JPEGImageEncoder(OutputStream output, ImageEncodeParam param) {
055: super (output, param);
056: if (param != null) {
057: jaiEP = (JPEGEncodeParam) param;
058: }
059: }
060:
061: //
062: // Go through the settable encoding parameters and see
063: // if any of them have been set. If so, transfer then to the
064: // com.sun.image.codec.jpeg.JPEGEncodeParam object.
065: //
066: static void modifyEncodeParam(JPEGEncodeParam jaiEP,
067: com.sun.image.codec.jpeg.JPEGEncodeParam j2dEP, int nbands) {
068:
069: int val;
070: int[] qTab;
071: for (int i = 0; i < nbands; i++) {
072: //
073: // If subsampling factors were set, apply them
074: //
075: val = jaiEP.getHorizontalSubsampling(i);
076: j2dEP.setHorizontalSubsampling(i, val);
077:
078: val = jaiEP.getVerticalSubsampling(i);
079: j2dEP.setVerticalSubsampling(i, val);
080:
081: //
082: // If new Q factors were supplied, apply them
083: //
084: if (jaiEP.isQTableSet(i)) {
085: qTab = jaiEP.getQTable(i);
086: val = jaiEP.getQTableSlot(i);
087: j2dEP.setQTableComponentMapping(i, val);
088: j2dEP.setQTable(val, new JPEGQTable(qTab));
089: }
090: }
091:
092: // Apply new quality, if set
093: if (jaiEP.isQualitySet()) {
094: float fval = jaiEP.getQuality();
095: j2dEP.setQuality(fval, true);
096: }
097:
098: // Apply new restart interval, if set
099: val = jaiEP.getRestartInterval();
100: j2dEP.setRestartInterval(val);
101:
102: // Write a tables-only abbreviated JPEG file
103: if (jaiEP.getWriteTablesOnly() == true) {
104: j2dEP.setImageInfoValid(false);
105: j2dEP.setTableInfoValid(true);
106: }
107:
108: // Write an image-only abbreviated JPEG file
109: if (jaiEP.getWriteImageOnly() == true) {
110: j2dEP.setTableInfoValid(false);
111: j2dEP.setImageInfoValid(true);
112: }
113:
114: // Write the JFIF (APP0) marker
115: if (jaiEP.getWriteJFIFHeader() == false) {
116: j2dEP
117: .setMarkerData(
118: com.sun.image.codec.jpeg.JPEGDecodeParam.APP0_MARKER,
119: null);
120: }
121:
122: }
123:
124: /**
125: * Encodes a RenderedImage and writes the output to the
126: * OutputStream associated with this ImageEncoder.
127: */
128: public void encode(RenderedImage im) throws IOException {
129: //
130: // Check data type and band count compatibility.
131: // This implementation handles only 1 and 3 band source images.
132: //
133: SampleModel sampleModel = im.getSampleModel();
134: ColorModel colorModel = im.getColorModel();
135:
136: // Must be a 1 or 3 band BYTE image
137: int numBands = colorModel.getNumColorComponents();
138: int transType = sampleModel.getTransferType();
139: if (((transType != DataBuffer.TYPE_BYTE) && !CodecUtils
140: .isPackedByteImage(im))
141: || ((numBands != 1) && (numBands != 3))) {
142: throw new RuntimeException(JaiI18N
143: .getString("JPEGImageEncoder0"));
144: }
145:
146: // Must be GRAY or RGB
147: int cspaceType = colorModel.getColorSpace().getType();
148: if (cspaceType != ColorSpace.TYPE_GRAY
149: && cspaceType != ColorSpace.TYPE_RGB) {
150: throw new RuntimeException(JaiI18N
151: .getString("JPEGImageEncoder1"));
152: }
153:
154: //
155: // Create a BufferedImage to be encoded.
156: // The JPEG interfaces really need a whole image.
157: //
158: BufferedImage bi;
159: if (im instanceof BufferedImage) {
160: bi = (BufferedImage) im;
161: } else {
162: //
163: // Get a contiguous raster. Jpeg compression can't work
164: // on tiled data in most cases.
165: // Also need to be sure that the raster doesn't have a
166: // non-zero origin, since BufferedImage won't accept that.
167: // (Bug ID 4253990)
168: //
169:
170: //Fix 4694162: JPEGImageEncoder throws ClassCastException
171: // Obtain the contiguous Raster.
172: Raster ras;
173: if (im.getNumXTiles() == 1 && im.getNumYTiles() == 1) {
174: // Image is not tiled so just get a reference to the tile.
175: ras = im.getTile(im.getMinTileX(), im.getMinTileY());
176: } else {
177: // Image is tiled so need to get a contiguous raster.
178:
179: // Create an interleaved raster for copying for 8-bit case.
180: // This ensures that for RGB data the band offsets are {0,1,2}.
181: // If the JPEG encoder encounters data with BGR offsets as
182: // {2,1,0} then it will make yet another copy of the data
183: // which might as well be averted here.
184: WritableRaster target = sampleModel.getSampleSize(0) == 8 ? Raster
185: .createInterleavedRaster(DataBuffer.TYPE_BYTE,
186: im.getWidth(), im.getHeight(),
187: sampleModel.getNumBands(), new Point(im
188: .getMinX(), im.getMinY()))
189: : null;
190:
191: // Copy the data.
192: ras = im.copyData(target);
193: }
194:
195: // Convert the Raster to a WritableRaster.
196: WritableRaster wRas;
197: if (ras instanceof WritableRaster) {
198: wRas = (WritableRaster) ras;
199: } else {
200: wRas = Raster.createWritableRaster(
201: ras.getSampleModel(), ras.getDataBuffer(),
202: new Point(ras.getSampleModelTranslateX(), ras
203: .getSampleModelTranslateY()));
204: }
205:
206: // Ensure that the WritableRaster has origin (0,0) and the
207: // same dimensions as the image (if derived from a single
208: // image tile, the tile dimensions might differ from the
209: // image dimensions.
210: if (wRas.getMinX() != 0 || wRas.getMinY() != 0
211: || wRas.getWidth() != im.getWidth()
212: || wRas.getHeight() != im.getHeight())
213: wRas = wRas.createWritableChild(wRas.getMinX(), wRas
214: .getMinY(), im.getWidth(), im.getHeight(), 0,
215: 0, null);
216:
217: bi = new BufferedImage(colorModel, wRas, false, null);
218: }
219:
220: if (colorModel instanceof IndexColorModel) {
221: //
222: // Need to expand the indexed data to components.
223: // The convertToIntDiscrete method is used to perform this.
224: //
225: IndexColorModel icm = (IndexColorModel) colorModel;
226: bi = icm.convertToIntDiscrete(bi.getRaster(), false);
227:
228: if (bi.getSampleModel().getNumBands() == 4) {
229: //
230: // Without copying data create a BufferedImage which has
231: // only the RGB bands, not the alpha band.
232: //
233: WritableRaster rgbaRas = bi.getRaster();
234: WritableRaster rgbRas = rgbaRas.createWritableChild(0,
235: 0, bi.getWidth(), bi.getHeight(), 0, 0,
236: new int[] { 0, 1, 2 });
237: //
238: // IndexColorModel.convertToIntDiscrete() is guaranteed
239: // to return an image which has a DirectColorModel which
240: // is a subclass of PackedColorModel.
241: //
242: PackedColorModel pcm = (PackedColorModel) bi
243: .getColorModel();
244: int bits = pcm.getComponentSize(0)
245: + pcm.getComponentSize(1)
246: + pcm.getComponentSize(2);
247: DirectColorModel dcm = new DirectColorModel(bits, pcm
248: .getMask(0), pcm.getMask(1), pcm.getMask(2));
249: bi = new BufferedImage(dcm, rgbRas, false, null);
250: }
251: }
252:
253: // Create the Java2D encodeParam based on the BufferedImage
254: com.sun.image.codec.jpeg.JPEGEncodeParam j2dEP = com.sun.image.codec.jpeg.JPEGCodec
255: .getDefaultJPEGEncodeParam(bi);
256:
257: // Now modify the Java2D encodeParam based on the options set
258: // in the JAI encodeParam object.
259: if (jaiEP != null) {
260: modifyEncodeParam(jaiEP, j2dEP, numBands);
261: }
262:
263: // Now create the encoder with the modified Java2D encodeParam
264: com.sun.image.codec.jpeg.JPEGImageEncoder encoder;
265: encoder = com.sun.image.codec.jpeg.JPEGCodec.createJPEGEncoder(
266: output, j2dEP);
267:
268: try {
269: // Write the image data.
270: encoder.encode(bi);
271: } catch (IOException e) {
272: String message = JaiI18N.getString("JPEGImageEncoder2");
273: ImagingListenerProxy.errorOccurred(message,
274: new ImagingException(message, e), this , false);
275: // throw new RuntimeException(e.getMessage());
276: }
277:
278: }
279:
280: }
|