001/* 002 * $RCSfile: PCXImageWriter.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.2 $ 042 * $Date: 2007/09/11 20:45:42 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.impl.plugins.pcx; 046 047import java.awt.Rectangle; 048import java.awt.color.ColorSpace; 049import java.awt.image.*; 050import java.io.IOException; 051import java.nio.ByteOrder; 052 053import javax.imageio.*; 054import javax.imageio.metadata.IIOMetadata; 055import javax.imageio.stream.ImageOutputStream; 056 057import com.github.jaiimageio.impl.common.ImageUtil; 058 059public class PCXImageWriter extends ImageWriter implements PCXConstants { 060 061 private ImageOutputStream ios; 062 private Rectangle sourceRegion; 063 private Rectangle destinationRegion; 064 private int colorPlanes,bytesPerLine; 065 private Raster inputRaster = null; 066 private int scaleX,scaleY; 067 068 public PCXImageWriter(PCXImageWriterSpi imageWriterSpi) { 069 super(imageWriterSpi); 070 } 071 072 public void setOutput(Object output) { 073 super.setOutput(output); // validates output 074 if (output != null) { 075 if (!(output instanceof ImageOutputStream)) 076 throw new IllegalArgumentException("output not instance of ImageOutputStream"); 077 ios = (ImageOutputStream) output; 078 ios.setByteOrder(ByteOrder.LITTLE_ENDIAN); 079 } else 080 ios = null; 081 } 082 083 public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) { 084 if(inData instanceof PCXMetadata) 085 return inData; 086 return null; 087 } 088 089 public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) { 090 return null; 091 } 092 093 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) { 094 PCXMetadata md = new PCXMetadata(); 095 md.bitsPerPixel = (byte) imageType.getSampleModel().getSampleSize()[0]; 096 return md; 097 } 098 099 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 100 return null; 101 } 102 103 public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException { 104 if (ios == null) { 105 throw new IllegalStateException("output stream is null"); 106 } 107 108 if (image == null) { 109 throw new IllegalArgumentException("image is null"); 110 } 111 112 clearAbortRequest(); 113 processImageStarted(0); 114 if (param == null) 115 param = getDefaultWriteParam(); 116 117 boolean writeRaster = image.hasRaster(); 118 119 sourceRegion = param.getSourceRegion(); 120 121 SampleModel sampleModel = null; 122 ColorModel colorModel = null; 123 124 if (writeRaster) { 125 inputRaster = image.getRaster(); 126 sampleModel = inputRaster.getSampleModel(); 127 colorModel = ImageUtil.createColorModel(null, sampleModel); 128 if (sourceRegion == null) 129 sourceRegion = inputRaster.getBounds(); 130 else 131 sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); 132 } else { 133 RenderedImage input = image.getRenderedImage(); 134 inputRaster = input.getData(); 135 sampleModel = input.getSampleModel(); 136 colorModel = input.getColorModel(); 137 Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(), 138 input.getWidth(), input.getHeight()); 139 if (sourceRegion == null) 140 sourceRegion = rect; 141 else 142 sourceRegion = sourceRegion.intersection(rect); 143 } 144 145 if (sourceRegion.isEmpty()) 146 throw new IllegalArgumentException("source region is empty"); 147 148 IIOMetadata imageMetadata = image.getMetadata(); 149 PCXMetadata pcxImageMetadata = null; 150 151 ImageTypeSpecifier imageType = new ImageTypeSpecifier(colorModel, sampleModel); 152 if(imageMetadata != null) { 153 // Convert metadata. 154 pcxImageMetadata = (PCXMetadata)convertImageMetadata(imageMetadata, imageType, param); 155 } else { 156 // Use default. 157 pcxImageMetadata = (PCXMetadata)getDefaultImageMetadata(imageType, param); 158 } 159 160 scaleX = param.getSourceXSubsampling(); 161 scaleY = param.getSourceYSubsampling(); 162 163 int xOffset = param.getSubsamplingXOffset(); 164 int yOffset = param.getSubsamplingYOffset(); 165 166 // cache the data type; 167 int dataType = sampleModel.getDataType(); 168 169 sourceRegion.translate(xOffset, yOffset); 170 sourceRegion.width -= xOffset; 171 sourceRegion.height -= yOffset; 172 173 int minX = sourceRegion.x / scaleX; 174 int minY = sourceRegion.y / scaleY; 175 int w = (sourceRegion.width + scaleX - 1) / scaleX; 176 int h = (sourceRegion.height + scaleY - 1) / scaleY; 177 178 xOffset = sourceRegion.x % scaleX; 179 yOffset = sourceRegion.y % scaleY; 180 181 destinationRegion = new Rectangle(minX, minY, w, h); 182 183 boolean noTransform = destinationRegion.equals(sourceRegion); 184 185 // Raw data can only handle bytes, everything greater must be ASCII. 186 int[] sourceBands = param.getSourceBands(); 187 boolean noSubband = true; 188 int numBands = sampleModel.getNumBands(); 189 190 if (sourceBands != null) { 191 sampleModel = sampleModel.createSubsetSampleModel(sourceBands); 192 colorModel = null; 193 noSubband = false; 194 numBands = sampleModel.getNumBands(); 195 } else { 196 sourceBands = new int[numBands]; 197 for (int i = 0; i < numBands; i++) 198 sourceBands[i] = i; 199 } 200 201 ios.writeByte(MANUFACTURER); 202 ios.writeByte(VERSION_3_0); 203 ios.writeByte(ENCODING); 204 205 int bitsPerPixel = sampleModel.getSampleSize(0); 206 ios.writeByte(bitsPerPixel); 207 208 ios.writeShort(destinationRegion.x); // xmin 209 ios.writeShort(destinationRegion.y); // ymin 210 ios.writeShort(destinationRegion.x+destinationRegion.width-1); // xmax 211 ios.writeShort(destinationRegion.y+destinationRegion.height-1); // ymax 212 213 ios.writeShort(pcxImageMetadata.hdpi); 214 ios.writeShort(pcxImageMetadata.vdpi); 215 216 byte[] smallpalette = createSmallPalette(colorModel); 217 ios.write(smallpalette); 218 ios.writeByte(0); // reserved 219 220 colorPlanes = sampleModel.getNumBands(); 221 222 ios.writeByte(colorPlanes); 223 224 bytesPerLine = destinationRegion.width*bitsPerPixel/8; 225 bytesPerLine += bytesPerLine %2; 226 227 ios.writeShort(bytesPerLine); 228 229 if(colorModel.getColorSpace().getType()==ColorSpace.TYPE_GRAY) 230 ios.writeShort(PALETTE_GRAYSCALE); 231 else 232 ios.writeShort(PALETTE_COLOR); 233 234 ios.writeShort(pcxImageMetadata.hsize); 235 ios.writeShort(pcxImageMetadata.vsize); 236 237 for(int i=0;i<54;i++) 238 ios.writeByte(0); 239 240 // write image data 241 242 if(colorPlanes==1 && bitsPerPixel==1) { 243 write1Bit(); 244 } 245 else if(colorPlanes==1 && bitsPerPixel==4) { 246 write4Bit(); 247 } 248 else { 249 write8Bit(); 250 } 251 252 // write 256 color palette if needed 253 if(colorPlanes==1 && bitsPerPixel==8 && 254 colorModel.getColorSpace().getType()!=ColorSpace.TYPE_GRAY){ 255 ios.writeByte(12); // Magic number preceding VGA 256 Color Palette Information 256 ios.write(createLargePalette(colorModel)); 257 } 258 259 if (abortRequested()) { 260 processWriteAborted(); 261 } else { 262 processImageComplete(); 263 } 264 } 265 266 private void write4Bit() throws IOException { 267 int[] unpacked = new int[sourceRegion.width]; 268 int[] samples = new int[bytesPerLine]; 269 270 for (int line = 0; line < sourceRegion.height; line += scaleY) { 271 inputRaster.getSamples(sourceRegion.x, line + sourceRegion.y, sourceRegion.width, 1, 0, unpacked); 272 273 int val=0,dst=0; 274 for(int x=0,nibble=0;x<sourceRegion.width;x+=scaleX){ 275 val = val | (unpacked[x] & 0x0F); 276 if(nibble==1) { 277 samples[dst++]=val; 278 nibble=0; 279 val=0; 280 } else { 281 nibble=1; 282 val = val << 4; 283 } 284 } 285 286 int last = samples[0]; 287 int count = 0; 288 289 for (int x = 0; x < bytesPerLine; x += scaleX) { 290 int sample = samples[x]; 291 if (sample != last || count == 63) { 292 writeRLE(last, count); 293 count = 1; 294 last = sample; 295 } else 296 count++; 297 } 298 if (count >= 1) { 299 writeRLE(last, count); 300 } 301 302 processImageProgress(100.0F * line / sourceRegion.height); 303 } 304 } 305 306 private void write1Bit() throws IOException { 307 int[] unpacked = new int[sourceRegion.width]; 308 int[] samples = new int[bytesPerLine]; 309 310 for (int line = 0; line < sourceRegion.height; line += scaleY) { 311 inputRaster.getSamples(sourceRegion.x, line + sourceRegion.y, sourceRegion.width, 1, 0, unpacked); 312 313 int val=0,dst=0; 314 for(int x=0,bit=1<<7;x<sourceRegion.width;x+=scaleX){ 315 if(unpacked[x]>0) 316 val = val | bit; 317 if(bit==1) { 318 samples[dst++]=val; 319 bit=1<<7; 320 val=0; 321 } else { 322 bit = bit >> 1; 323 } 324 } 325 326 int last = samples[0]; 327 int count = 0; 328 329 for (int x = 0; x < bytesPerLine; x += scaleX) { 330 int sample = samples[x]; 331 if (sample != last || count == 63) { 332 writeRLE(last, count); 333 count = 1; 334 last = sample; 335 } else 336 count++; 337 } 338 if (count >= 1) { 339 writeRLE(last, count); 340 } 341 342 processImageProgress(100.0F * line / sourceRegion.height); 343 } 344 } 345 346 private void write8Bit() throws IOException { 347 int[][] samples = new int[colorPlanes][bytesPerLine]; 348 349 for(int line=0;line<sourceRegion.height;line+=scaleY) { 350 for(int band=0;band<colorPlanes;band++) { 351 inputRaster.getSamples(sourceRegion.x, line+sourceRegion.y,sourceRegion.width,1,band,samples[band]); 352 } 353 354 int last = samples[0][0]; 355 int count=0; 356 357 for(int band=0;band<colorPlanes;band++) { 358 for(int x=0;x<bytesPerLine;x+=scaleX) { 359 int sample = samples[band][x]; 360 if(sample!=last || count==63) { 361 writeRLE(last,count); 362 count=1; 363 last=sample; 364 } else 365 count++; 366 } 367 } 368 if(count>=1) { 369 writeRLE(last,count); 370 } 371 372 processImageProgress(100.0F * line / sourceRegion.height); 373 } 374 } 375 376 private void writeRLE(int val,int count) throws IOException{ 377 if(count==1 && (val & 0xC0) != 0xC0) { 378 ios.writeByte(val); 379 } else { 380 ios.writeByte(0xC0 | count); 381 ios.writeByte(val); 382 } 383 } 384 385 private byte[] createSmallPalette(ColorModel cm){ 386 byte[] palette = new byte[16*3]; 387 388 if(!(cm instanceof IndexColorModel)) 389 return palette; 390 391 IndexColorModel icm = (IndexColorModel) cm; 392 if(icm.getMapSize()>16) 393 return palette; 394 395 for(int i=0,offset=0;i<icm.getMapSize();i++) { 396 palette[offset++] = (byte) icm.getRed(i); 397 palette[offset++] = (byte) icm.getGreen(i); 398 palette[offset++] = (byte) icm.getBlue(i); 399 } 400 401 return palette; 402 } 403 private byte[] createLargePalette(ColorModel cm){ 404 byte[] palette = new byte[256*3]; 405 406 if(!(cm instanceof IndexColorModel)) 407 return palette; 408 409 IndexColorModel icm = (IndexColorModel) cm; 410 411 for(int i=0,offset=0;i<icm.getMapSize();i++) { 412 palette[offset++] = (byte) icm.getRed(i); 413 palette[offset++] = (byte) icm.getGreen(i); 414 palette[offset++] = (byte) icm.getBlue(i); 415 } 416 417 return palette; 418 } 419}