001/* 002 * $RCSfile: J2KImageReader.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.7 $ 042 * $Date: 2006/10/05 01:08:30 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.jpeg2000.impl; 046 047import java.awt.Point; 048import java.awt.Rectangle; 049import java.awt.image.BufferedImage; 050import java.awt.image.Raster; 051import java.awt.image.RenderedImage; 052import java.io.IOException; 053import java.util.ArrayList; 054import java.util.Iterator; 055 056import javax.imageio.ImageReadParam; 057import javax.imageio.ImageReader; 058import javax.imageio.ImageTypeSpecifier; 059import javax.imageio.metadata.IIOMetadata; 060import javax.imageio.spi.ImageReaderSpi; 061import javax.imageio.stream.ImageInputStream; 062 063import jj2000.j2k.codestream.reader.HeaderDecoder; 064import jj2000.j2k.util.FacilityManager; 065import jj2000.j2k.util.MsgLogger; 066 067import com.github.jaiimageio.jpeg2000.J2KImageReadParam; 068 069/** This class is the Java Image IO plugin reader for JPEG 2000 JP2 image file 070 * format. It has the capability to load the compressed bilevel images, 071 * color-indexed byte images, or multi-band images in byte/ushort/short/int 072 * data type. It may subsample the image, select bands, clip the image, 073 * and shift the decoded image origin if the proper decoding parameter 074 * are set in the provided <code>J2KImageReadParam</code>. 075 */ 076public class J2KImageReader extends ImageReader implements MsgLogger { 077 /** The input stream where reads from */ 078 private ImageInputStream iis = null; 079 080 /** Stream position when setInput() was called. */ 081 private long streamPosition0; 082 083 /** Indicates whether the header is read. */ 084 private boolean gotHeader = false; 085 086 /** The image width. */ 087 private int width; 088 089 /** The image height. */ 090 private int height; 091 092 /** Image metadata, valid for the imageMetadataIndex only. */ 093 private J2KMetadata imageMetadata = null; 094 095 /** The image index for the cached metadata. */ 096 private int imageMetadataIndex = -1; 097 098 /** The J2K HeaderDecoder defined in jj2000 packages. Used to extract image 099 * header information. 100 */ 101 private HeaderDecoder hd; 102 103 /** The J2KReadState for this reading session based on the current input 104 * and J2KImageReadParam. 105 */ 106 private J2KReadState readState = null; 107 108 /** 109 * Whether to log JJ2000 messages. 110 */ 111 private boolean logJJ2000Msg = false; 112 113 /** Wrapper for the protected method <code>computeRegions</code>. So it 114 * can be access from the classes which are not in <code>ImageReader</code> 115 * hierarchy. 116 */ 117 public static void computeRegionsWrapper(ImageReadParam param, 118 boolean allowZeroDestOffset, 119 int srcWidth, 120 int srcHeight, 121 BufferedImage image, 122 Rectangle srcRegion, 123 Rectangle destRegion) { 124 if (srcRegion == null) { 125 throw new IllegalArgumentException(I18N.getString("J2KImageReader0")); 126 } 127 if (destRegion == null) { 128 throw new IllegalArgumentException(I18N.getString("J2KImageReader1")); 129 } 130 131 // Clip that to the param region, if there is one 132 int periodX = 1; 133 int periodY = 1; 134 int gridX = 0; 135 int gridY = 0; 136 if (param != null) { 137 Rectangle paramSrcRegion = param.getSourceRegion(); 138 if (paramSrcRegion != null) { 139 srcRegion.setBounds(srcRegion.intersection(paramSrcRegion)); 140 } 141 periodX = param.getSourceXSubsampling(); 142 periodY = param.getSourceYSubsampling(); 143 gridX = param.getSubsamplingXOffset(); 144 gridY = param.getSubsamplingYOffset(); 145 srcRegion.translate(gridX, gridY); 146 srcRegion.width -= gridX; 147 srcRegion.height -= gridY; 148 if(allowZeroDestOffset) { 149 destRegion.setLocation(param.getDestinationOffset()); 150 } else { 151 Point destOffset = param.getDestinationOffset(); 152 if(destOffset.x != 0 || destOffset.y != 0) { 153 destRegion.setLocation(param.getDestinationOffset()); 154 } 155 } 156 } 157 158 // Now clip any negative destination offsets, i.e. clip 159 // to the top and left of the destination image 160 if (destRegion.x < 0) { 161 int delta = -destRegion.x*periodX; 162 srcRegion.x += delta; 163 srcRegion.width -= delta; 164 destRegion.x = 0; 165 } 166 if (destRegion.y < 0) { 167 int delta = -destRegion.y*periodY; 168 srcRegion.y += delta; 169 srcRegion.height -= delta; 170 destRegion.y = 0; 171 } 172 173 // Now clip the destination Region to the subsampled width and height 174 int subsampledWidth = (srcRegion.width + periodX - 1)/periodX; 175 int subsampledHeight = (srcRegion.height + periodY - 1)/periodY; 176 destRegion.width = subsampledWidth; 177 destRegion.height = subsampledHeight; 178 179 // Now clip that to right and bottom of the destination image, 180 // if there is one, taking subsampling into account 181 if (image != null) { 182 Rectangle destImageRect = new Rectangle(0, 0, 183 image.getWidth(), 184 image.getHeight()); 185 destRegion.setBounds(destRegion.intersection(destImageRect)); 186 if (destRegion.isEmpty()) { 187 throw new IllegalArgumentException 188 (I18N.getString("J2KImageReader2")); 189 } 190 191 int deltaX = destRegion.x + subsampledWidth - image.getWidth(); 192 if (deltaX > 0) { 193 srcRegion.width -= deltaX*periodX; 194 } 195 int deltaY = destRegion.y + subsampledHeight - image.getHeight(); 196 if (deltaY > 0) { 197 srcRegion.height -= deltaY*periodY; 198 } 199 } 200 if (srcRegion.isEmpty() || destRegion.isEmpty()) { 201 throw new IllegalArgumentException(I18N.getString("J2KImageReader3")); 202 } 203 } 204 205 /** Wrapper for the protected method <code>checkReadParamBandSettings</code>. 206 * So it can be access from the classes which are not in 207 * <code>ImageReader</code> hierarchy. 208 */ 209 public static void checkReadParamBandSettingsWrapper(ImageReadParam param, 210 int numSrcBands, 211 int numDstBands) { 212 checkReadParamBandSettings(param, numSrcBands, numDstBands); 213 } 214 215 /** 216 * Convert a rectangle provided in the coordinate system of the JPEG2000 217 * reference grid to coordinates at a lower resolution level where zero 218 * denotes the lowest resolution level. 219 * 220 * @param r A rectangle in references grid coordinates. 221 * @param maxLevel The highest resolution level in the image. 222 * @param level The resolution level of the returned rectangle. 223 * @param subX The horizontal subsampling step size. 224 * @param subY The vertical subsampling step size. 225 * @return The parameter rectangle converted to a lower resolution level. 226 * @throws IllegalArgumentException if <core>r</code> is <code>null</code>, 227 * <code>maxLevel</code> or <code>level</code> is negative, or 228 * <code>level</code> is greater than <code>maxLevel</code>. 229 */ 230 static Rectangle getReducedRect(Rectangle r, int maxLevel, int level, 231 int subX, int subY) { 232 if(r == null) { 233 throw new IllegalArgumentException("r == null!"); 234 } else if(maxLevel < 0 || level < 0) { 235 throw new IllegalArgumentException("maxLevel < 0 || level < 0!"); 236 } else if(level > maxLevel) { 237 throw new IllegalArgumentException("level > maxLevel"); 238 } 239 240 // At the highest level; return the parameter. 241 if(level == maxLevel && subX == 1 && subY == 1) { 242 return r; 243 } 244 245 // Resolution divisor is step*2^(maxLevel - level). 246 int divisor = 1 << (maxLevel - level); 247 int divX = divisor*subX; 248 int divY = divisor*subY; 249 250 // Convert upper left and lower right corners. 251 int x1 = (r.x + divX - 1)/divX; 252 int x2 = (r.x + r.width + divX - 1)/divX; 253 int y1 = (r.y + divY - 1)/divY; 254 int y2 = (r.y + r.height + divY - 1)/divY; 255 256 // Create lower resolution rectangle and return. 257 return new Rectangle(x1, y1, x2 - x1, y2 - y1); 258 } 259 260 /** Wrapper for the protected method <code>processImageUpdate</code> 261 * So it can be access from the classes which are not in 262 * <code>ImageReader</code> hierarchy. 263 */ 264 public void processImageUpdateWrapper(BufferedImage theImage, 265 int minX, int minY, 266 int width, int height, 267 int periodX, int periodY, 268 int[] bands) { 269 processImageUpdate(theImage, 270 minX, minY, 271 width, height, 272 periodX, periodY, 273 bands); 274 } 275 276 /** Wrapper for the protected method <code>processImageProgress</code> 277 * So it can be access from the classes which are not in 278 * <code>ImageReader</code> hierarchy. 279 */ 280 public void processImageProgressWrapper(float percentageDone) { 281 processImageProgress(percentageDone); 282 } 283 284 /** Constructs <code>J2KImageReader</code> from the provided 285 * <code>ImageReaderSpi</code>. 286 */ 287 public J2KImageReader(ImageReaderSpi originator) { 288 super(originator); 289 290 this.logJJ2000Msg = Boolean.getBoolean("jj2000.j2k.decoder.log"); 291 292 FacilityManager.registerMsgLogger(null, this); 293 } 294 295 /** Overrides the method defined in the superclass. */ 296 public void setInput(Object input, 297 boolean seekForwardOnly, 298 boolean ignoreMetadata) { 299 super.setInput(input, seekForwardOnly, ignoreMetadata); 300 this.ignoreMetadata = ignoreMetadata; 301 iis = (ImageInputStream) input; // Always works 302 imageMetadata = null; 303 try { 304 this.streamPosition0 = iis.getStreamPosition(); 305 } catch(IOException e) { 306 // XXX ignore 307 } 308 } 309 310 /** Overrides the method defined in the superclass. */ 311 public int getNumImages(boolean allowSearch) throws IOException { 312 return 1; 313 } 314 315 public int getWidth(int imageIndex) throws IOException { 316 checkIndex(imageIndex); 317 readHeader(); 318 return width; 319 } 320 321 public int getHeight(int imageIndex) throws IOException { 322 checkIndex(imageIndex); 323 readHeader(); 324 return height; 325 } 326 327 public int getTileGridXOffset(int imageIndex) throws IOException { 328 checkIndex(imageIndex); 329 readHeader(); 330 return hd.getTilingOrigin(null).x; 331 } 332 333 public int getTileGridYOffset(int imageIndex) throws IOException { 334 checkIndex(imageIndex); 335 readHeader(); 336 return hd.getTilingOrigin(null).y; 337 } 338 339 public int getTileWidth(int imageIndex) throws IOException { 340 checkIndex(imageIndex); 341 readHeader(); 342 return hd.getNomTileWidth(); 343 } 344 345 public int getTileHeight(int imageIndex) throws IOException { 346 checkIndex(imageIndex); 347 readHeader(); 348 return hd.getNomTileHeight(); 349 } 350 351 private void checkIndex(int imageIndex) { 352 if (imageIndex != 0) { 353 throw new IndexOutOfBoundsException(I18N.getString("J2KImageReader4")); 354 } 355 } 356 357 public void readHeader() { 358 if (gotHeader) 359 return; 360 361 if (readState == null) { 362 try { 363 iis.seek(streamPosition0); 364 } catch(IOException e) { 365 // XXX ignore 366 } 367 368 readState = 369 new J2KReadState(iis, 370 new J2KImageReadParamJava(getDefaultReadParam()), 371 this); 372 } 373 374 hd = readState.getHeader(); 375 gotHeader = true; 376 377 this.width = hd.getImgWidth(); 378 this.height = hd.getImgHeight(); 379 } 380 381 public Iterator getImageTypes(int imageIndex) 382 throws IOException { 383 checkIndex(imageIndex); 384 readHeader(); 385 if (readState != null) { 386 ArrayList list = new ArrayList(); 387 list.add(new ImageTypeSpecifier(readState.getColorModel(), 388 readState.getSampleModel())); 389 return list.iterator(); 390 } 391 return null; 392 } 393 394 public ImageReadParam getDefaultReadParam() { 395 return new J2KImageReadParam(); 396 } 397 398 public IIOMetadata getImageMetadata(int imageIndex) 399 throws IOException { 400 checkIndex(imageIndex); 401 if (ignoreMetadata) 402 return null; 403 404 if (imageMetadata == null) { 405 iis.mark(); 406 imageMetadata = new J2KMetadata(iis, this); 407 iis.reset(); 408 } 409 return imageMetadata; 410 } 411 412 public IIOMetadata getStreamMetadata() throws IOException { 413 return null; 414 } 415 416 public BufferedImage read(int imageIndex, ImageReadParam param) 417 throws IOException { 418 checkIndex(imageIndex); 419 clearAbortRequest(); 420 processImageStarted(imageIndex); 421 422 if (param == null) 423 param = getDefaultReadParam(); 424 425 param = new J2KImageReadParamJava(param); 426 427 if (!ignoreMetadata) { 428 imageMetadata = new J2KMetadata(); 429 iis.seek(streamPosition0); 430 readState = new J2KReadState(iis, 431 (J2KImageReadParamJava)param, 432 imageMetadata, 433 this); 434 } else { 435 iis.seek(streamPosition0); 436 readState = new J2KReadState(iis, 437 (J2KImageReadParamJava)param, 438 this); 439 } 440 441 BufferedImage bi = readState.readBufferedImage(); 442 if (abortRequested()) 443 processReadAborted(); 444 else 445 processImageComplete(); 446 return bi; 447 } 448 449 public RenderedImage readAsRenderedImage(int imageIndex, 450 ImageReadParam param) 451 throws IOException { 452 checkIndex(imageIndex); 453 RenderedImage ri = null; 454 clearAbortRequest(); 455 processImageStarted(imageIndex); 456 457 if (param == null) 458 param = getDefaultReadParam(); 459 460 param = new J2KImageReadParamJava(param); 461 if (!ignoreMetadata) { 462 if (imageMetadata == null) 463 imageMetadata = new J2KMetadata(); 464 ri = new J2KRenderedImage(iis, 465 (J2KImageReadParamJava)param, 466 imageMetadata, 467 this); 468 } 469 else 470 ri = new J2KRenderedImage(iis, (J2KImageReadParamJava)param, this); 471 if (abortRequested()) 472 processReadAborted(); 473 else 474 processImageComplete(); 475 return ri; 476 } 477 478 public boolean canReadRaster() { 479 return true; 480 } 481 482 public boolean isRandomAccessEasy(int imageIndex) throws IOException { 483 checkIndex(imageIndex); 484 return false; 485 } 486 487 public Raster readRaster(int imageIndex, 488 ImageReadParam param) throws IOException { 489 checkIndex(imageIndex); 490 processImageStarted(imageIndex); 491 param = new J2KImageReadParamJava(param); 492 493 if (!ignoreMetadata) { 494 imageMetadata = new J2KMetadata(); 495 iis.seek(streamPosition0); 496 readState = new J2KReadState(iis, 497 (J2KImageReadParamJava)param, 498 imageMetadata, 499 this); 500 } else { 501 iis.seek(streamPosition0); 502 readState = new J2KReadState(iis, 503 (J2KImageReadParamJava)param, 504 this); 505 } 506 507 Raster ras = readState.readAsRaster(); 508 if (abortRequested()) 509 processReadAborted(); 510 else 511 processImageComplete(); 512 return ras; 513 } 514 515 public boolean isImageTiled(int imageIndex) { 516 checkIndex(imageIndex); 517 readHeader(); 518 if (readState != null) { 519 RenderedImage image = new J2KRenderedImage(readState); 520 if (image.getNumXTiles() * image.getNumYTiles() > 0) 521 return true; 522 return false; 523 } 524 return false; 525 } 526 527 public void reset() { 528 // reset local Java structures 529 super.reset(); 530 531 iis = null; 532 gotHeader = false; 533 imageMetadata = null; 534 readState = null; 535 System.gc(); 536 } 537 538 /** This method wraps the protected method <code>abortRequested</code> 539 * to allow the abortions be monitored by <code>J2KReadState</code>. 540 */ 541 public boolean getAbortRequest() { 542 return abortRequested(); 543 } 544 545 private ImageTypeSpecifier getImageType(int imageIndex) 546 throws IOException { 547 checkIndex(imageIndex); 548 readHeader(); 549 if (readState != null) { 550 return new ImageTypeSpecifier(readState.getColorModel(), 551 readState.getSampleModel()); 552 } 553 return null; 554 } 555 556 // --- Begin jj2000.j2k.util.MsgLogger implementation --- 557 public void flush() { 558 // Do nothing. 559 } 560 561 public void println(String str, int flind, int ind) { 562 printmsg(INFO, str); 563 } 564 565 public void printmsg(int sev, String msg) { 566 if(logJJ2000Msg) { 567 String msgSev; 568 switch(sev) { 569 case ERROR: 570 msgSev = "ERROR"; 571 break; 572 case INFO: 573 msgSev = "INFO"; 574 break; 575 case LOG: 576 msgSev = "LOG"; 577 break; 578 case WARNING: 579 default: 580 msgSev = "WARNING"; 581 break; 582 } 583 584 processWarningOccurred("[JJ2000 "+msgSev+"] "+msg); 585 } 586 } 587 // --- End jj2000.j2k.util.MsgLogger implementation --- 588}