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