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}