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