001: /*
002: * $RCSfile: J2KImageWriter.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.1 $
042: * $Date: 2005/02/11 05:01:34 $
043: * $State: Exp $
044: */
045: package com.sun.media.imageioimpl.plugins.jpeg2000;
046:
047: import java.awt.image.ColorModel;
048: import java.awt.image.DataBuffer;
049: import java.awt.image.DataBufferByte;
050: import java.awt.image.IndexColorModel;
051: import java.awt.image.MultiPixelPackedSampleModel;
052: import java.awt.image.Raster;
053: import java.awt.image.RenderedImage;
054: import java.awt.image.SampleModel;
055:
056: import java.io.File;
057: import java.io.IOException;
058:
059: import java.util.Arrays;
060: import java.util.List;
061:
062: import javax.imageio.IIOImage;
063: import javax.imageio.IIOException;
064: import javax.imageio.ImageTypeSpecifier;
065: import javax.imageio.ImageWriteParam;
066: import javax.imageio.ImageWriter;
067: import javax.imageio.metadata.IIOMetadata;
068: import javax.imageio.metadata.IIOMetadataFormatImpl;
069: import javax.imageio.metadata.IIOInvalidTreeException;
070: import javax.imageio.spi.ImageWriterSpi;
071: import javax.imageio.stream.ImageOutputStream;
072:
073: import jj2000.j2k.codestream.writer.CodestreamWriter;
074: import jj2000.j2k.codestream.writer.FileCodestreamWriter;
075: import jj2000.j2k.codestream.writer.HeaderEncoder;
076: import jj2000.j2k.entropy.encoder.EntropyCoder;
077: import jj2000.j2k.entropy.encoder.PostCompRateAllocator;
078: import jj2000.j2k.fileformat.writer.FileFormatWriter;
079: import jj2000.j2k.image.ImgDataConverter;
080: import jj2000.j2k.image.Tiler;
081: import jj2000.j2k.image.forwcomptransf.ForwCompTransf;
082: import jj2000.j2k.quantization.quantizer.Quantizer;
083: import jj2000.j2k.roi.encoder.ROIScaler;
084: import jj2000.j2k.util.CodestreamManipulator;
085: import jj2000.j2k.wavelet.analysis.ForwardWT;
086:
087: import com.sun.media.imageioimpl.common.ImageUtil;
088: import com.sun.media.imageio.plugins.jpeg2000.J2KImageWriteParam;
089: import org.w3c.dom.Node;
090:
091: /**
092: * The Java Image IO plugin writer for encoding a RenderedImage into
093: * a JPEG 2000 part 1 file (JP2) format.
094: *
095: * This writer has the capability to (1) Losslessly encode
096: * <code>RenderedImage</code>s with an <code>IndexColorModel</code> (for
097: * example, bi-level or color indexed images). (2) Losslessly or lossy encode
098: * <code>RenderedImage</code> with a byte, short, ushort or integer types with
099: * band number upto 16384. (3) Encode an image with alpha channel.
100: * (4) Write the provided metadata into the code stream. It also can encode
101: * a raster wrapped in the provided <code>IIOImage</code>.
102: *
103: * The encoding process may re-tile image, clip, subsample, and select bands
104: * using the parameters specified in the <code>ImageWriteParam</code>.
105: *
106: * @see com.sun.media.imageio.plugins.J2KImageWriteParam
107: */
108: public class J2KImageWriter extends ImageWriter {
109: /** Wrapper for the protected method <code>processImageProgress</code>
110: * So it can be access from the classes which are not in
111: * <code>ImageWriter</code> hierachy.
112: */
113: public void processImageProgressWrapper(float percentageDone) {
114: processImageProgress(percentageDone);
115: }
116:
117: /** When the writing is aborted, <code>RenderedImageSrc</code> throws a
118: * <code>RuntimeException</code>.
119: */
120: public static String WRITE_ABORTED = "Write aborted.";
121:
122: /** The output stream to write into */
123: private ImageOutputStream stream = null;
124:
125: /** Constructs <code>J2KImageWriter</code> based on the provided
126: * <code>ImageWriterSpi</code>.
127: */
128: public J2KImageWriter(ImageWriterSpi originator) {
129: super (originator);
130: }
131:
132: public void setOutput(Object output) {
133: super .setOutput(output); // validates output
134: if (output != null) {
135: if (!(output instanceof ImageOutputStream))
136: throw new IllegalArgumentException(I18N
137: .getString("J2KImageWriter0"));
138: this .stream = (ImageOutputStream) output;
139: } else
140: this .stream = null;
141: }
142:
143: public ImageWriteParam getDefaultWriteParam() {
144: return new J2KImageWriteParam();
145: }
146:
147: public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
148: return null;
149: }
150:
151: public IIOMetadata getDefaultImageMetadata(
152: ImageTypeSpecifier imageType, ImageWriteParam param) {
153: return new J2KMetadata(imageType, param, this );
154: }
155:
156: public IIOMetadata convertStreamMetadata(IIOMetadata inData,
157: ImageWriteParam param) {
158: return null;
159: }
160:
161: public IIOMetadata convertImageMetadata(IIOMetadata inData,
162: ImageTypeSpecifier imageType, ImageWriteParam param) {
163: // Check arguments.
164: if (inData == null) {
165: throw new IllegalArgumentException("inData == null!");
166: }
167: if (imageType == null) {
168: throw new IllegalArgumentException("imageType == null!");
169: }
170:
171: // If it's one of ours, return a clone.
172: if (inData instanceof J2KMetadata) {
173: return (IIOMetadata) ((J2KMetadata) inData).clone();
174: }
175:
176: try {
177: J2KMetadata outData = new J2KMetadata();
178:
179: List formats = Arrays.asList(inData
180: .getMetadataFormatNames());
181:
182: String format = null;
183: if (formats.contains(J2KMetadata.nativeMetadataFormatName)) {
184: // Initialize from native image metadata format.
185: format = J2KMetadata.nativeMetadataFormatName;
186: } else if (inData.isStandardMetadataFormatSupported()) {
187: // Initialize from standard metadata form of the input tree.
188: format = IIOMetadataFormatImpl.standardMetadataFormatName;
189: }
190:
191: if (format != null) {
192: outData.setFromTree(format, inData.getAsTree(format));
193: return outData;
194: }
195: } catch (IIOInvalidTreeException e) {
196: return null;
197: }
198:
199: return null;
200: }
201:
202: public boolean canWriteRasters() {
203: return true;
204: }
205:
206: public void write(IIOMetadata streamMetadata, IIOImage image,
207: ImageWriteParam param) throws IOException {
208: if (stream == null) {
209: throw new IllegalStateException(I18N
210: .getString("J2KImageWriter7"));
211: }
212: if (image == null) {
213: throw new IllegalArgumentException(I18N
214: .getString("J2KImageWriter8"));
215: }
216:
217: clearAbortRequest();
218: processImageStarted(0);
219: RenderedImage input = null;
220:
221: boolean writeRaster = image.hasRaster();
222: Raster raster = null;
223:
224: SampleModel sampleModel = null;
225: if (writeRaster) {
226: raster = image.getRaster();
227: sampleModel = raster.getSampleModel();
228: } else {
229: input = image.getRenderedImage();
230: sampleModel = input.getSampleModel();
231: }
232:
233: checkSampleModel(sampleModel);
234: if (param == null)
235: param = getDefaultWriteParam();
236:
237: J2KImageWriteParamJava j2kwparam = new J2KImageWriteParamJava(
238: image, param);
239:
240: // Packet header cannot exist in two places.
241: if (j2kwparam.getPackPacketHeaderInTile()
242: && j2kwparam.getPackPacketHeaderInMain())
243: throw new IllegalArgumentException(I18N
244: .getString("J2KImageWriter1"));
245:
246: // Lossless and encoding rate cannot be set at the same time
247: if (j2kwparam.getLossless()
248: && j2kwparam.getEncodingRate() != Double.MAX_VALUE)
249: throw new IllegalArgumentException(I18N
250: .getString("J2KImageWriter2"));
251:
252: // If the source image is bilevel or color-indexed, or, the
253: // encoding rate is Double.MAX_VALUE, use lossless
254: if ((!writeRaster && input.getColorModel() instanceof IndexColorModel)
255: || (writeRaster && raster.getSampleModel() instanceof MultiPixelPackedSampleModel)) {
256: j2kwparam.setDecompositionLevel("0");
257: j2kwparam.setLossless(true);
258: j2kwparam.setEncodingRate(Double.MAX_VALUE);
259: j2kwparam.setQuantizationType("reversible");
260: j2kwparam.setFilters(J2KImageWriteParam.FILTER_53);
261: } else if (j2kwparam.getEncodingRate() == Double.MAX_VALUE) {
262: j2kwparam.setLossless(true);
263: j2kwparam.setQuantizationType("reversible");
264: j2kwparam.setFilters(J2KImageWriteParam.FILTER_53);
265: }
266:
267: // Gets parameters from the write parameter
268: boolean pphTile = j2kwparam.getPackPacketHeaderInTile();
269: boolean pphMain = j2kwparam.getPackPacketHeaderInMain();
270: boolean tempSop = false;
271: boolean tempEph = false;
272:
273: int[] bands = param.getSourceBands();
274: int ncomp = sampleModel.getNumBands();
275:
276: if (bands != null)
277: ncomp = bands.length;
278:
279: // create the encoding source recognized by jj2000 packages
280: RenderedImageSrc imgsrc = null;
281: if (writeRaster)
282: imgsrc = new RenderedImageSrc(raster, j2kwparam, this );
283: else
284: imgsrc = new RenderedImageSrc(input, j2kwparam, this );
285:
286: // if the components signed
287: boolean[] imsigned = new boolean[ncomp];
288: if (bands != null) {
289: for (int i = 0; i < ncomp; i++)
290: imsigned[i] = ((RenderedImageSrc) imgsrc)
291: .isOrigSigned(bands[i]);
292: } else {
293: for (int i = 0; i < ncomp; i++)
294: imsigned[i] = ((RenderedImageSrc) imgsrc)
295: .isOrigSigned(i);
296: }
297:
298: // Gets the tile dimensions
299: int tw = j2kwparam.getTileWidth();
300: int th = j2kwparam.getTileHeight();
301:
302: //Gets the image position
303: int refx = j2kwparam.getMinX();
304: int refy = j2kwparam.getMinY();
305: if (refx < 0 || refy < 0)
306: throw new IIOException(I18N.getString("J2KImageWriter3"));
307:
308: // Gets tile grid offsets and validates them
309: int trefx = j2kwparam.getTileGridXOffset();
310: int trefy = j2kwparam.getTileGridYOffset();
311: if (trefx < 0 || trefy < 0 || trefx > refx || trefy > refy)
312: throw new IIOException(I18N.getString("J2KImageWriter4"));
313:
314: // Instantiate tiler
315: Tiler imgtiler = new Tiler(imgsrc, refx, refy, trefx, trefy,
316: tw, th);
317:
318: // Creates the forward component transform
319: ForwCompTransf fctransf = new ForwCompTransf(imgtiler,
320: j2kwparam);
321:
322: // Creates ImgDataConverter
323: ImgDataConverter converter = new ImgDataConverter(fctransf);
324:
325: // Creates ForwardWT (forward wavelet transform)
326: ForwardWT dwt = ForwardWT.createInstance(converter, j2kwparam);
327:
328: // Creates Quantizer
329: Quantizer quant = Quantizer.createInstance(dwt, j2kwparam);
330:
331: // Creates ROIScaler
332: ROIScaler rois = ROIScaler.createInstance(quant, j2kwparam);
333:
334: // Creates EntropyCoder
335: EntropyCoder ecoder = EntropyCoder.createInstance(rois,
336: j2kwparam, j2kwparam.getCodeBlockSize(), j2kwparam
337: .getPrecinctPartition(), j2kwparam.getBypass(),
338: j2kwparam.getResetMQ(), j2kwparam.getTerminateOnByte(),
339: j2kwparam.getCausalCXInfo(), j2kwparam
340: .getCodeSegSymbol(), j2kwparam
341: .getMethodForMQLengthCalc(), j2kwparam
342: .getMethodForMQTermination());
343:
344: // Rely on rate allocator to limit amount of data
345: File tmpFile = File.createTempFile("jiio-", ".tmp");
346: tmpFile.deleteOnExit();
347:
348: // Creates CodestreamWriter
349: FileCodestreamWriter bwriter = new FileCodestreamWriter(
350: tmpFile, Integer.MAX_VALUE);
351:
352: // Creates the rate allocator
353: float rate = (float) j2kwparam.getEncodingRate();
354: PostCompRateAllocator ralloc = PostCompRateAllocator
355: .createInstance(ecoder, rate, bwriter, j2kwparam);
356:
357: // Instantiates the HeaderEncoder
358: HeaderEncoder headenc = new HeaderEncoder(imgsrc, imsigned,
359: dwt, imgtiler, j2kwparam, rois, ralloc);
360:
361: ralloc.setHeaderEncoder(headenc);
362:
363: // Writes header to be able to estimate header overhead
364: headenc.encodeMainHeader();
365:
366: //Initializes rate allocator, with proper header
367: // overhead. This will also encode all the data
368: try {
369: ralloc.initialize();
370: } catch (RuntimeException e) {
371: if (WRITE_ABORTED.equals(e.getMessage())) {
372: bwriter.close();
373: tmpFile.delete();
374: processWriteAborted();
375: return;
376: } else
377: throw e;
378: }
379:
380: // Write header (final)
381: headenc.reset();
382: headenc.encodeMainHeader();
383:
384: // Insert header into the codestream
385: bwriter.commitBitstreamHeader(headenc);
386:
387: // Now do the rate-allocation and write result
388: ralloc.runAndWrite();
389:
390: //Done for data encoding
391: bwriter.close();
392:
393: // Calculate file length
394: int fileLength = bwriter.getLength();
395:
396: // Tile-parts and packed packet headers
397: int pktspertp = j2kwparam.getPacketPerTilePart();
398: int ntiles = imgtiler.getNumTiles();
399: if (pktspertp > 0 || pphTile || pphMain) {
400: CodestreamManipulator cm = new CodestreamManipulator(
401: tmpFile, ntiles, pktspertp, pphMain, pphTile,
402: tempSop, tempEph);
403: fileLength += cm.doCodestreamManipulation();
404: }
405:
406: // File Format
407: int nc = imgsrc.getNumComps();
408: int[] bpc = new int[nc];
409: for (int comp = 0; comp < nc; comp++)
410: bpc[comp] = imgsrc.getNomRangeBits(comp);
411:
412: ColorModel colorModel = (input != null) ? input.getColorModel()
413: : null;
414: if (bands != null) {
415: ImageTypeSpecifier type = param.getDestinationType();
416: if (type != null)
417: colorModel = type.getColorModel();
418: //XXX: other wise should create proper color model based
419: // on the selected bands
420: }
421: if (colorModel == null) {
422: colorModel = ImageUtil.createColorModel(sampleModel);
423: }
424:
425: J2KMetadata metadata = null;
426:
427: if (param instanceof J2KImageWriteParam
428: && !((J2KImageWriteParam) param)
429: .getWriteCodeStreamOnly()) {
430: IIOMetadata inMetadata = image.getMetadata();
431:
432: J2KMetadata metadata1 = new J2KMetadata(colorModel,
433: sampleModel, imgsrc.getImgWidth(), imgsrc
434: .getImgHeight(), param, this );
435:
436: if (inMetadata == null) {
437: metadata = metadata1;
438: } else {
439: // Convert the input metadata tree to a J2KMetadata.
440: if (colorModel != null) {
441: ImageTypeSpecifier imageType = new ImageTypeSpecifier(
442: colorModel, sampleModel);
443: metadata = (J2KMetadata) convertImageMetadata(
444: inMetadata, imageType, param);
445: } else {
446: String metaFormat = null;
447: List metaFormats = Arrays.asList(inMetadata
448: .getMetadataFormatNames());
449: if (metaFormats
450: .contains(J2KMetadata.nativeMetadataFormatName)) {
451: // Initialize from native image metadata format.
452: metaFormat = J2KMetadata.nativeMetadataFormatName;
453: } else if (inMetadata
454: .isStandardMetadataFormatSupported()) {
455: // Initialize from standard metadata form of the
456: // input tree.
457: metaFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
458: }
459:
460: metadata = new J2KMetadata();
461: if (metaFormat != null) {
462: metadata.setFromTree(metaFormat, inMetadata
463: .getAsTree(metaFormat));
464: }
465: }
466:
467: metadata
468: .mergeTree(
469: J2KMetadata.nativeMetadataFormatName,
470: metadata1
471: .getAsTree(J2KMetadata.nativeMetadataFormatName));
472: }
473: }
474:
475: FileFormatWriter ffw = new FileFormatWriter(tmpFile, stream,
476: imgsrc.getImgHeight(), imgsrc.getImgWidth(), nc, bpc,
477: fileLength, colorModel, sampleModel, metadata);
478: fileLength += ffw.writeFileFormat();
479: tmpFile.delete();
480:
481: processImageComplete();
482: }
483:
484: public synchronized void abort() {
485: super .abort();
486: }
487:
488: public void reset() {
489: // reset local Java structures
490: super .reset();
491: stream = null;
492: }
493:
494: /** This method wraps the protected method <code>abortRequested</code>
495: * to allow the abortions be monitored by <code>J2KRenderedImage</code>.
496: */
497: public boolean getAbortRequest() {
498: return abortRequested();
499: }
500:
501: private void checkSampleModel(SampleModel sm) {
502: int type = sm.getDataType();
503:
504: if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT)
505: throw new IllegalArgumentException(I18N
506: .getString("J2KImageWriter5"));
507: if (sm.getNumBands() > 16384)
508: throw new IllegalArgumentException(I18N
509: .getString("J2KImageWriter6"));
510: }
511: }
|