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 */
045package com.sun.media.imageioimpl.plugins.jpeg2000;
046
047import java.awt.image.ColorModel;
048import java.awt.image.DataBuffer;
049import java.awt.image.DataBufferByte;
050import java.awt.image.IndexColorModel;
051import java.awt.image.MultiPixelPackedSampleModel;
052import java.awt.image.Raster;
053import java.awt.image.RenderedImage;
054import java.awt.image.SampleModel;
055
056import java.io.File;
057import java.io.IOException;
058
059import java.util.Arrays;
060import java.util.List;
061
062import javax.imageio.IIOImage;
063import javax.imageio.IIOException;
064import javax.imageio.ImageTypeSpecifier;
065import javax.imageio.ImageWriteParam;
066import javax.imageio.ImageWriter;
067import javax.imageio.metadata.IIOMetadata;
068import javax.imageio.metadata.IIOMetadataFormatImpl;
069import javax.imageio.metadata.IIOInvalidTreeException;
070import javax.imageio.spi.ImageWriterSpi;
071import javax.imageio.stream.ImageOutputStream;
072
073import jj2000.j2k.codestream.writer.CodestreamWriter;
074import jj2000.j2k.codestream.writer.FileCodestreamWriter;
075import jj2000.j2k.codestream.writer.HeaderEncoder;
076import jj2000.j2k.entropy.encoder.EntropyCoder;
077import jj2000.j2k.entropy.encoder.PostCompRateAllocator;
078import jj2000.j2k.fileformat.writer.FileFormatWriter;
079import jj2000.j2k.image.ImgDataConverter;
080import jj2000.j2k.image.Tiler;
081import jj2000.j2k.image.forwcomptransf.ForwCompTransf;
082import jj2000.j2k.quantization.quantizer.Quantizer;
083import jj2000.j2k.roi.encoder.ROIScaler;
084import jj2000.j2k.util.CodestreamManipulator;
085import jj2000.j2k.wavelet.analysis.ForwardWT;
086
087import com.sun.media.imageioimpl.common.ImageUtil;
088import com.sun.media.imageio.plugins.jpeg2000.J2KImageWriteParam;
089import 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 */
108public 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
118    /** When the writing is aborted, <code>RenderedImageSrc</code> throws a
119     *  <code>RuntimeException</code>.
120     */
121    public static String WRITE_ABORTED = "Write aborted.";
122
123    /** The output stream to write into */
124    private ImageOutputStream stream = null;
125
126    /** Constructs <code>J2KImageWriter</code> based on the provided
127     *  <code>ImageWriterSpi</code>.
128     */
129    public J2KImageWriter(ImageWriterSpi originator) {
130        super(originator);
131    }
132
133    public void setOutput(Object output) {
134        super.setOutput(output); // validates output
135        if (output != null) {
136            if (!(output instanceof ImageOutputStream))
137                throw new IllegalArgumentException(I18N.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(ImageTypeSpecifier imageType,
152                                               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,
163                                            ImageWriteParam param) {
164        // Check arguments.
165        if(inData == null) {
166            throw new IllegalArgumentException("inData == null!");
167        }
168        if(imageType == null) {
169            throw new IllegalArgumentException("imageType == null!");
170        }
171
172        // If it's one of ours, return a clone.
173        if (inData instanceof J2KMetadata) {
174            return (IIOMetadata)((J2KMetadata)inData).clone();
175        }
176
177        try {
178            J2KMetadata outData = new J2KMetadata();
179
180            List formats = Arrays.asList(inData.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,
207                      IIOImage image,
208                      ImageWriteParam param) throws IOException {
209        if (stream == null) {
210            throw new IllegalStateException(I18N.getString("J2KImageWriter7"));
211        }
212        if (image == null) {
213            throw new IllegalArgumentException(I18N.getString("J2KImageWriter8"));
214        }
215
216        clearAbortRequest();
217        processImageStarted(0);
218        RenderedImage input = null;
219
220        boolean writeRaster = image.hasRaster();
221        Raster raster = null;
222
223        SampleModel sampleModel = null;
224        if (writeRaster) {
225            raster = image.getRaster();
226            sampleModel = raster.getSampleModel();
227        } else {
228            input = image.getRenderedImage();
229            sampleModel = input.getSampleModel();
230        }
231
232        checkSampleModel(sampleModel);
233        if (param == null)
234            param = getDefaultWriteParam();
235
236        J2KImageWriteParamJava j2kwparam =
237            new J2KImageWriteParamJava(image, param);
238
239        // Packet header cannot exist in two places.
240        if (j2kwparam.getPackPacketHeaderInTile() &&
241            j2kwparam.getPackPacketHeaderInMain())
242            throw new IllegalArgumentException(I18N.getString("J2KImageWriter1"));
243
244        // Lossless and encoding rate cannot be set at the same time
245        if (j2kwparam.getLossless() &&
246            j2kwparam.getEncodingRate()!=Double.MAX_VALUE)
247            throw new IllegalArgumentException(I18N.getString("J2KImageWriter2"));
248
249        // If the source image is bilevel or color-indexed, or, the
250        // encoding rate is Double.MAX_VALUE, use lossless
251        if ((!writeRaster && input.getColorModel() instanceof IndexColorModel) ||
252             (writeRaster &&
253              raster.getSampleModel() instanceof MultiPixelPackedSampleModel)) {
254            j2kwparam.setDecompositionLevel("0");
255            j2kwparam.setLossless(true);
256            j2kwparam.setEncodingRate(Double.MAX_VALUE);
257            j2kwparam.setQuantizationType("reversible");
258            j2kwparam.setFilters(J2KImageWriteParam.FILTER_53);
259        } else if (j2kwparam.getEncodingRate() == Double.MAX_VALUE) {
260            j2kwparam.setLossless(true);
261            j2kwparam.setQuantizationType("reversible");
262            j2kwparam.setFilters(J2KImageWriteParam.FILTER_53);
263        }
264
265        // Gets parameters from the write parameter
266        boolean pphTile = j2kwparam.getPackPacketHeaderInTile();
267        boolean pphMain = j2kwparam.getPackPacketHeaderInMain();
268        boolean tempSop = false;
269        boolean tempEph = false;
270
271        int[] bands = param.getSourceBands();
272        int ncomp = sampleModel.getNumBands();
273
274        if (bands != null)
275            ncomp = bands.length;
276
277        // create the encoding source recognized by jj2000 packages
278        RenderedImageSrc imgsrc = null;
279        if (writeRaster)
280            imgsrc = new RenderedImageSrc(raster, j2kwparam, this);
281        else
282            imgsrc = new RenderedImageSrc(input, j2kwparam, this);
283
284        // if the components signed
285        boolean[] imsigned = new boolean[ncomp];
286        if (bands != null) {
287            for (int i=0; i<ncomp; i++)
288                imsigned[i] = ((RenderedImageSrc)imgsrc).isOrigSigned(bands[i]);
289        } else {
290            for (int i=0; i<ncomp; i++)
291                imsigned[i] = ((RenderedImageSrc)imgsrc).isOrigSigned(i);
292        }
293
294        // Gets the tile dimensions
295        int tw = j2kwparam.getTileWidth();
296        int th = j2kwparam.getTileHeight();
297
298        //Gets the image position
299        int refx = j2kwparam.getMinX();
300        int refy = j2kwparam.getMinY();
301        if (refx < 0 || refy < 0)
302            throw new IIOException(I18N.getString("J2KImageWriter3"));
303
304        // Gets tile grid offsets and validates them
305        int trefx = j2kwparam.getTileGridXOffset();
306        int trefy = j2kwparam.getTileGridYOffset();
307        if (trefx < 0 || trefy < 0 || trefx > refx || trefy > refy)
308            throw new IIOException(I18N.getString("J2KImageWriter4"));
309
310        // Instantiate tiler
311        Tiler imgtiler = new Tiler(imgsrc,refx,refy,trefx,trefy,tw,th);
312
313        // Creates the forward component transform
314        ForwCompTransf fctransf = new ForwCompTransf(imgtiler, j2kwparam);
315
316        // Creates ImgDataConverter
317        ImgDataConverter converter = new ImgDataConverter(fctransf);
318
319        // Creates ForwardWT (forward wavelet transform)
320        ForwardWT dwt = ForwardWT.createInstance(converter, j2kwparam);
321
322        // Creates Quantizer
323        Quantizer quant = Quantizer.createInstance(dwt,j2kwparam);
324
325        // Creates ROIScaler
326        ROIScaler rois = ROIScaler.createInstance(quant, j2kwparam);
327
328        // Creates EntropyCoder
329        EntropyCoder ecoder =
330            EntropyCoder.createInstance(rois, j2kwparam,
331                j2kwparam.getCodeBlockSize(),
332                j2kwparam.getPrecinctPartition(),
333                j2kwparam.getBypass(),
334                j2kwparam.getResetMQ(),
335                j2kwparam.getTerminateOnByte(),
336                j2kwparam.getCausalCXInfo(),
337                j2kwparam.getCodeSegSymbol(),
338                j2kwparam.getMethodForMQLengthCalc(),
339                j2kwparam.getMethodForMQTermination());
340
341        // Rely on rate allocator to limit amount of data
342        File tmpFile = File.createTempFile("jiio-", ".tmp");
343        tmpFile.deleteOnExit();
344
345        // Creates CodestreamWriter
346        FileCodestreamWriter bwriter =
347            new FileCodestreamWriter(tmpFile, Integer.MAX_VALUE);
348
349        // Creates the rate allocator
350        float rate = (float)j2kwparam.getEncodingRate();
351        PostCompRateAllocator ralloc =
352            PostCompRateAllocator.createInstance(ecoder,
353                                                 rate,
354                                                 bwriter,
355                                                 j2kwparam);
356
357        // Instantiates the HeaderEncoder
358        HeaderEncoder headenc =
359            new HeaderEncoder(imgsrc, imsigned, dwt, imgtiler,
360                              j2kwparam, rois,ralloc);
361
362        ralloc.setHeaderEncoder(headenc);
363
364        // Writes header to be able to estimate header overhead
365        headenc.encodeMainHeader();
366
367        //Initializes rate allocator, with proper header
368        // overhead. This will also encode all the data
369        try {
370            ralloc.initialize();
371        } catch (RuntimeException e) {
372            if (WRITE_ABORTED.equals(e.getMessage())) {
373                bwriter.close();
374                tmpFile.delete();
375                processWriteAborted();
376                return;
377            } else 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 =
401                new CodestreamManipulator(tmpFile, ntiles, pktspertp,
402                                          pphMain, pphTile, tempSop,
403                                          tempEph);
404            fileLength += cm.doCodestreamManipulation();
405        }
406
407        // File Format
408        int nc= imgsrc.getNumComps() ;
409        int[] bpc = new int[nc];
410        for(int comp = 0; comp<nc; comp++)
411            bpc[comp]=imgsrc.getNomRangeBits(comp);
412
413        ColorModel colorModel = (input != null) ? input.getColorModel() : 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).getWriteCodeStreamOnly()) {
429            IIOMetadata inMetadata = image.getMetadata();
430
431            J2KMetadata metadata1 = new J2KMetadata(colorModel,
432                                                    sampleModel,
433                                                    imgsrc.getImgWidth(),
434                                                    imgsrc.getImgHeight(),
435                                                    param,
436                                                    this);
437
438            if (inMetadata == null) {
439                metadata = metadata1;
440            } else {
441                // Convert the input metadata tree to a J2KMetadata.
442                if(colorModel != null) {
443                    ImageTypeSpecifier imageType = 
444                        new ImageTypeSpecifier(colorModel, sampleModel);
445                    metadata =
446                        (J2KMetadata)convertImageMetadata(inMetadata,
447                                                          imageType,
448                                                          param);
449                } else {
450                    String metaFormat = null;
451                    List metaFormats =
452                        Arrays.asList(inMetadata.getMetadataFormatNames());
453                    if(metaFormats.contains(J2KMetadata.nativeMetadataFormatName)) {
454                        // Initialize from native image metadata format.
455                        metaFormat = J2KMetadata.nativeMetadataFormatName;
456                    } else if(inMetadata.isStandardMetadataFormatSupported()) {
457                        // Initialize from standard metadata form of the
458                        // input tree.
459                        metaFormat = 
460                            IIOMetadataFormatImpl.standardMetadataFormatName;
461                    }
462
463                    metadata = new J2KMetadata();
464                    if(metaFormat != null) {
465                        metadata.setFromTree(metaFormat,
466                                             inMetadata.getAsTree(metaFormat));
467                    }
468                }
469
470                metadata.mergeTree(J2KMetadata.nativeMetadataFormatName,
471                                   metadata1.getAsTree(J2KMetadata.nativeMetadataFormatName));
472            }
473        }
474
475        FileFormatWriter ffw =
476            new FileFormatWriter(tmpFile, stream,
477                                 imgsrc.getImgHeight(),
478                                 imgsrc.getImgWidth(), nc, bpc,
479                                 fileLength,
480                                 colorModel,
481                                 sampleModel,
482                                 metadata);
483        fileLength += ffw.writeFileFormat();
484        tmpFile.delete();
485
486        processImageComplete();
487    }
488
489    public synchronized void abort() {
490        super.abort();
491    }
492
493    public void reset() {
494        // reset local Java structures
495        super.reset();
496        stream = null;
497    }
498
499    /** This method wraps the protected method <code>abortRequested</code>
500     *  to allow the abortions be monitored by <code>J2KRenderedImage</code>.
501     */
502    public boolean getAbortRequest() {
503        return abortRequested();
504    }
505
506    private void checkSampleModel(SampleModel sm) {
507        int type = sm.getDataType();
508
509        if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT)
510            throw new IllegalArgumentException(I18N.getString("J2KImageWriter5"));
511        if (sm.getNumBands() > 16384)
512            throw new IllegalArgumentException(I18N.getString("J2KImageWriter6"));
513    }
514}