001/* 002 * $RCSfile: PCXImageReader.java,v $ 003 * 004 * 005 * Copyright (c) 2007 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.3 $ 042 * $Date: 2007/09/07 19:13:02 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.impl.plugins.pcx; 046 047import java.awt.*; 048import java.awt.color.ColorSpace; 049import java.awt.image.*; 050import java.io.*; 051import java.nio.ByteOrder; 052import java.util.*; 053 054import javax.imageio.*; 055import javax.imageio.metadata.IIOMetadata; 056import javax.imageio.stream.ImageInputStream; 057 058public class PCXImageReader extends ImageReader implements PCXConstants { 059 060 private ImageInputStream iis; 061 private int width, height; 062 private boolean gotHeader = false; 063 private byte manufacturer; 064 private byte encoding; 065 private short xmax, ymax; 066 private byte[] smallPalette = new byte[3 * 16]; 067 private byte[] largePalette = new byte[3 * 256]; 068 private byte colorPlanes; 069 private short bytesPerLine; 070 private short paletteType; 071 072 private PCXMetadata metadata; 073 074 private SampleModel sampleModel, originalSampleModel; 075 private ColorModel colorModel, originalColorModel; 076 077 /** The destination region. */ 078 private Rectangle destinationRegion; 079 080 /** The source region. */ 081 private Rectangle sourceRegion; 082 083 /** The destination image. */ 084 private BufferedImage bi; 085 086 /** Indicates whether subsampled, subregion is required, and offset is 087 * defined 088 */ 089 private boolean noTransform = true; 090 091 /** Indicates whether subband is selected. */ 092 private boolean seleBand = false; 093 094 /** The scaling factors. */ 095 private int scaleX, scaleY; 096 097 /** source and destination bands. */ 098 private int[] sourceBands, destBands; 099 100 public PCXImageReader(PCXImageReaderSpi imageReaderSpi) { 101 super(imageReaderSpi); 102 } 103 104 public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { 105 super.setInput(input, seekForwardOnly, ignoreMetadata); 106 iis = (ImageInputStream) input; // Always works 107 if (iis != null) 108 iis.setByteOrder(ByteOrder.LITTLE_ENDIAN); 109 gotHeader = false; 110 } 111 112 public int getHeight(int imageIndex) throws IOException { 113 checkIndex(imageIndex); 114 readHeader(); 115 return height; 116 } 117 118 public IIOMetadata getImageMetadata(int imageIndex) throws IOException { 119 checkIndex(imageIndex); 120 readHeader(); 121 return metadata; 122 } 123 124 public Iterator getImageTypes(int imageIndex) throws IOException { 125 checkIndex(imageIndex); 126 readHeader(); 127 return Collections.singletonList(new ImageTypeSpecifier(originalColorModel, originalSampleModel)).iterator(); 128 } 129 130 public int getNumImages(boolean allowSearch) throws IOException { 131 if (iis == null) { 132 throw new IllegalStateException("input is null"); 133 } 134 if (seekForwardOnly && allowSearch) { 135 throw new IllegalStateException("cannot search with forward only input"); 136 } 137 return 1; 138 } 139 140 public IIOMetadata getStreamMetadata() throws IOException { 141 return null; 142 } 143 144 public int getWidth(int imageIndex) throws IOException { 145 checkIndex(imageIndex); 146 readHeader(); 147 return width; 148 } 149 150 public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { 151 checkIndex(imageIndex); 152 readHeader(); 153 154 if (iis == null) 155 throw new IllegalStateException("input is null"); 156 157 BufferedImage img; 158 159 clearAbortRequest(); 160 processImageStarted(imageIndex); 161 162 if (param == null) 163 param = getDefaultReadParam(); 164 165 sourceRegion = new Rectangle(0, 0, 0, 0); 166 destinationRegion = new Rectangle(0, 0, 0, 0); 167 168 computeRegions(param, this.width, this.height, param.getDestination(), sourceRegion, destinationRegion); 169 170 scaleX = param.getSourceXSubsampling(); 171 scaleY = param.getSourceYSubsampling(); 172 173 // If the destination band is set used it 174 sourceBands = param.getSourceBands(); 175 destBands = param.getDestinationBands(); 176 177 seleBand = (sourceBands != null) && (destBands != null); 178 noTransform = destinationRegion.equals(new Rectangle(0, 0, width, height)) || seleBand; 179 180 if (!seleBand) { 181 sourceBands = new int[colorPlanes]; 182 destBands = new int[colorPlanes]; 183 for (int i = 0; i < colorPlanes; i++) 184 destBands[i] = sourceBands[i] = i; 185 } 186 187 // If the destination is provided, then use it. Otherwise, create new one 188 bi = param.getDestination(); 189 190 // Get the image data. 191 WritableRaster raster = null; 192 193 if (bi == null) { 194 if (sampleModel != null && colorModel != null) { 195 sampleModel = sampleModel.createCompatibleSampleModel(destinationRegion.width + destinationRegion.x, destinationRegion.height 196 + destinationRegion.y); 197 if (seleBand) 198 sampleModel = sampleModel.createSubsetSampleModel(sourceBands); 199 raster = Raster.createWritableRaster(sampleModel, new Point(0, 0)); 200 bi = new BufferedImage(colorModel, raster, false, null); 201 } 202 } else { 203 raster = bi.getWritableTile(0, 0); 204 sampleModel = bi.getSampleModel(); 205 colorModel = bi.getColorModel(); 206 207 noTransform &= destinationRegion.equals(raster.getBounds()); 208 } 209 210 byte bdata[] = null; // buffer for byte data 211 212 if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE) 213 bdata = (byte[]) ((DataBufferByte) raster.getDataBuffer()).getData(); 214 215 readImage(bdata); 216 217 if (abortRequested()) 218 processReadAborted(); 219 else 220 processImageComplete(); 221 222 return bi; 223 } 224 225 private void readImage(byte[] data) throws IOException { 226 227 byte[] scanline = new byte[bytesPerLine*colorPlanes]; 228 229 if (noTransform) { 230 try { 231 int offset = 0; 232 int nbytes = (width * metadata.bitsPerPixel + 8 - metadata.bitsPerPixel) / 8; 233 for (int line = 0; line < height; line++) { 234 readScanLine(scanline); 235 for (int band = 0; band < colorPlanes; band++) { 236 System.arraycopy(scanline, bytesPerLine * band, data, offset, nbytes); 237 offset += nbytes; 238 } 239 processImageProgress(100.0F * line / height); 240 } 241 } catch (EOFException e) { 242 } 243 } else { 244 if (metadata.bitsPerPixel == 1) 245 read1Bit(data); 246 else if (metadata.bitsPerPixel == 4) 247 read4Bit(data); 248 else 249 read8Bit(data); 250 } 251 } 252 253 private void read1Bit(byte[] data) throws IOException { 254 byte[] scanline = new byte[bytesPerLine]; 255 256 try { 257 // skip until source region 258 for (int line = 0; line < sourceRegion.y; line++) { 259 readScanLine(scanline); 260 } 261 int lineStride = 262 ((MultiPixelPackedSampleModel)sampleModel).getScanlineStride(); 263 264 // cache the values to avoid duplicated computation 265 int[] srcOff = new int[destinationRegion.width]; 266 int[] destOff = new int[destinationRegion.width]; 267 int[] srcPos = new int[destinationRegion.width]; 268 int[] destPos = new int[destinationRegion.width]; 269 270 for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; i < destinationRegion.x + destinationRegion.width; i++, j++, x += scaleX) { 271 srcPos[j] = x >> 3; 272 srcOff[j] = 7 - (x & 7); 273 destPos[j] = i >> 3; 274 destOff[j] = 7 - (i & 7); 275 } 276 277 int k = destinationRegion.y * lineStride; 278 279 for (int line = 0; line < sourceRegion.height; line++) { 280 readScanLine(scanline); 281 if (line % scaleY == 0) { 282 for (int i = 0; i < destinationRegion.width; i++) { 283 //get the bit and assign to the data buffer of the raster 284 int v = (scanline[srcPos[i]] >> srcOff[i]) & 1; 285 data[k + destPos[i]] |= v << destOff[i]; 286 } 287 k += lineStride; 288 } 289 processImageProgress(100.0F * line / sourceRegion.height); 290 } 291 } catch (EOFException e) { 292 } 293 } 294 295 private void read4Bit(byte[] data) throws IOException { 296 byte[] scanline = new byte[bytesPerLine]; 297 try { 298 int lineStride = 299 ((MultiPixelPackedSampleModel)sampleModel).getScanlineStride(); 300 301 // cache the values to avoid duplicated computation 302 int[] srcOff = new int[destinationRegion.width]; 303 int[] destOff = new int[destinationRegion.width]; 304 int[] srcPos = new int[destinationRegion.width]; 305 int[] destPos = new int[destinationRegion.width]; 306 307 for (int i = destinationRegion.x, x = sourceRegion.x, j = 0; 308 i < destinationRegion.x + destinationRegion.width; 309 i++, j++, x += scaleX) { 310 srcPos[j] = x >> 1; 311 srcOff[j] = (1 - (x & 1)) << 2; 312 destPos[j] = i >> 1; 313 destOff[j] = (1 - (i & 1)) << 2; 314 } 315 316 int k = destinationRegion.y * lineStride; 317 318 for (int line = 0; line < sourceRegion.height; line++) { 319 readScanLine(scanline); 320 321 if (abortRequested()) 322 break; 323 if (line % scaleY == 0) { 324 for (int i = 0; i < destinationRegion.width; i++) { 325 //get the bit and assign to the data buffer of the raster 326 int v = (scanline[srcPos[i]] >> srcOff[i]) & 0x0F; 327 data[k + destPos[i]] |= v << destOff[i]; 328 } 329 k += lineStride; 330 } 331 processImageProgress(100.0F * line / sourceRegion.height); 332 } 333 }catch(EOFException e){ 334 } 335 } 336 337 /* also handles 24 bit (three 8 bit planes) */ 338 private void read8Bit(byte[] data) throws IOException { 339 byte[] scanline = new byte[colorPlanes * bytesPerLine]; 340 try { 341 // skip until source region 342 for (int line = 0; line < sourceRegion.y; line++) { 343 readScanLine(scanline); 344 } 345 int dstOffset = destinationRegion.y * (destinationRegion.x + destinationRegion.width) * colorPlanes; 346 for (int line = 0; line < sourceRegion.height; line++) { 347 readScanLine(scanline); 348 if (line % scaleY == 0) { 349 int srcOffset = sourceRegion.x; 350 for (int band = 0; band < colorPlanes; band++) { 351 dstOffset += destinationRegion.x; 352 for (int x = 0; x < destinationRegion.width; x += scaleX) { 353 data[dstOffset++] = scanline[srcOffset + x]; 354 } 355 srcOffset += bytesPerLine; 356 } 357 } 358 processImageProgress(100.0F * line / sourceRegion.height); 359 } 360 } catch (EOFException e) { 361 } 362 } 363 364 private void readScanLine(byte[] buffer) throws IOException { 365 int max = bytesPerLine * colorPlanes; 366 for (int j = 0; j < max;) { 367 int val = iis.readUnsignedByte(); 368 369 if ((val & 0xC0) == 0xC0) { 370 int count = val & ~0xC0; 371 val = iis.readUnsignedByte(); 372 for (int k = 0; k < count && j < max; k++) { 373 buffer[j++] = (byte) (val & 0xFF); 374 } 375 } else { 376 buffer[j++] = (byte) (val & 0xFF); 377 } 378 } 379 } 380 381 private void checkIndex(int imageIndex) { 382 if (imageIndex != 0) { 383 throw new IndexOutOfBoundsException("only one image exists in the stream"); 384 } 385 } 386 387 private void readHeader() throws IOException { 388 if (gotHeader) { 389 iis.seek(128); 390 return; 391 } 392 393 metadata = new PCXMetadata(); 394 395 manufacturer = iis.readByte(); // manufacturer 396 if (manufacturer != MANUFACTURER) 397 throw new IllegalStateException("image is not a PCX file"); 398 metadata.version = iis.readByte(); // version 399 encoding = iis.readByte(); // encoding 400 if (encoding != ENCODING) 401 throw new IllegalStateException("image is not a PCX file, invalid encoding " + encoding); 402 403 metadata.bitsPerPixel = iis.readByte(); 404 405 metadata.xmin = iis.readShort(); 406 metadata.ymin = iis.readShort(); 407 xmax = iis.readShort(); 408 ymax = iis.readShort(); 409 410 metadata.hdpi = iis.readShort(); 411 metadata.vdpi = iis.readShort(); 412 413 iis.readFully(smallPalette); 414 415 iis.readByte(); // reserved 416 417 colorPlanes = iis.readByte(); 418 bytesPerLine = iis.readShort(); 419 paletteType = iis.readShort(); 420 421 metadata.hsize = iis.readShort(); 422 metadata.vsize = iis.readShort(); 423 424 iis.skipBytes(54); // skip filler 425 426 width = xmax - metadata.xmin + 1; 427 height = ymax - metadata.ymin + 1; 428 429 if (colorPlanes == 1) { 430 if (paletteType == PALETTE_GRAYSCALE) { 431 ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); 432 int[] nBits = { 8 }; 433 colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 434 sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE, width, height, 1, width, new int[] { 0 }); 435 } else { 436 if (metadata.bitsPerPixel == 8) { 437 // read palette from end of file, then reset back to image data 438 iis.mark(); 439 440 if (iis.length() == -1) { 441 // read until eof, and work backwards 442 while (iis.read() != -1) 443 ; 444 iis.seek(iis.getStreamPosition() - 256 * 3 - 1); 445 } else { 446 iis.seek(iis.length() - 256 * 3 - 1); 447 } 448 449 int palletteMagic = iis.read(); 450 if(palletteMagic != 12) 451 processWarningOccurred("Expected palette magic number 12; instead read "+ 452 palletteMagic+" from this image."); 453 454 iis.readFully(largePalette); 455 iis.reset(); 456 457 colorModel = new IndexColorModel(metadata.bitsPerPixel, 256, largePalette, 0, false); 458 sampleModel = colorModel.createCompatibleSampleModel(width, height); 459 } else { 460 int msize = metadata.bitsPerPixel == 1 ? 2 : 16; 461 colorModel = new IndexColorModel(metadata.bitsPerPixel, msize, smallPalette, 0, false); 462 sampleModel = colorModel.createCompatibleSampleModel(width, height); 463 } 464 } 465 } else { 466 ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); 467 int[] nBits = { 8, 8, 8 }; 468 colorModel = new ComponentColorModel(cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE); 469 sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE, width, height, 1, width * colorPlanes, new int[] { 0, width, width * 2 }); 470 } 471 472 originalSampleModel = sampleModel; 473 originalColorModel = colorModel; 474 475 gotHeader = true; 476 } 477}