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