001/* 002 * $RCSfile: WBMPImageWriter.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.1 $ 042 * $Date: 2005/02/11 05:01:52 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.impl.plugins.wbmp; 046 047import java.awt.Point; 048import java.awt.Rectangle; 049import java.awt.image.ColorModel; 050import java.awt.image.DataBuffer; 051import java.awt.image.DataBufferByte; 052import java.awt.image.IndexColorModel; 053import java.awt.image.MultiPixelPackedSampleModel; 054import java.awt.image.Raster; 055import java.awt.image.RenderedImage; 056import java.awt.image.SampleModel; 057import java.awt.image.WritableRaster; 058 059import java.io.IOException; 060 061import javax.imageio.IIOImage; 062import javax.imageio.IIOException; 063import javax.imageio.ImageTypeSpecifier; 064import javax.imageio.ImageWriteParam; 065import javax.imageio.ImageWriter; 066import javax.imageio.metadata.IIOMetadata; 067import javax.imageio.metadata.IIOMetadataFormatImpl; 068import javax.imageio.metadata.IIOInvalidTreeException; 069import javax.imageio.spi.ImageWriterSpi; 070import javax.imageio.stream.ImageOutputStream; 071 072/** 073 * The Java Image IO plugin writer for encoding a binary RenderedImage into 074 * a WBMP format. 075 * 076 * The encoding process may clip, subsample using the parameters 077 * specified in the <code>ImageWriteParam</code>. 078 * 079 * @see com.github.jaiimageio.plugins.WBMPImageWriteParam 080 */ 081public class WBMPImageWriter extends ImageWriter { 082 /** The output stream to write into */ 083 private ImageOutputStream stream = null; 084 085 // Get the number of bits required to represent an int. 086 private static int getNumBits(int intValue) { 087 int numBits = 32; 088 int mask = 0x80000000; 089 while(mask != 0 && (intValue & mask) == 0) { 090 numBits--; 091 mask >>>= 1; 092 } 093 return numBits; 094 } 095 096 // Convert an int value to WBMP multi-byte format. 097 private static byte[] intToMultiByte(int intValue) { 098 int numBitsLeft = getNumBits(intValue); 099 byte[] multiBytes = new byte[(numBitsLeft + 6)/7]; 100 101 int maxIndex = multiBytes.length - 1; 102 for(int b = 0; b <= maxIndex; b++) { 103 multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f); 104 if(b != maxIndex) { 105 multiBytes[b] |= (byte)0x80; 106 } 107 } 108 109 return multiBytes; 110 } 111 112 /** Constructs <code>WBMPImageWriter</code> based on the provided 113 * <code>ImageWriterSpi</code>. 114 */ 115 public WBMPImageWriter(ImageWriterSpi originator) { 116 super(originator); 117 } 118 119 public void setOutput(Object output) { 120 super.setOutput(output); // validates output 121 if (output != null) { 122 if (!(output instanceof ImageOutputStream)) 123 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter")); 124 this.stream = (ImageOutputStream)output; 125 } else 126 this.stream = null; 127 } 128 129 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 130 return null; 131 } 132 133 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, 134 ImageWriteParam param) { 135 WBMPMetadata meta = new WBMPMetadata(); 136 meta.wbmpType = 0; // default wbmp level 137 return meta; 138 } 139 140 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 141 ImageWriteParam param) { 142 return null; 143 } 144 145 public IIOMetadata convertImageMetadata(IIOMetadata metadata, 146 ImageTypeSpecifier type, 147 ImageWriteParam param) { 148 return null; 149 } 150 151 public boolean canWriteRasters() { 152 return true; 153 } 154 155 public void write(IIOMetadata streamMetadata, 156 IIOImage image, 157 ImageWriteParam param) throws IOException { 158 if (stream == null) { 159 throw new IllegalStateException(I18N.getString("WBMPImageWriter3")); 160 } 161 162 if (image == null) { 163 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4")); 164 } 165 166 clearAbortRequest(); 167 processImageStarted(0); 168 if (param == null) 169 param = getDefaultWriteParam(); 170 171 RenderedImage input = null; 172 Raster inputRaster = null; 173 boolean writeRaster = image.hasRaster(); 174 Rectangle sourceRegion = param.getSourceRegion(); 175 SampleModel sampleModel = null; 176 177 if (writeRaster) { 178 inputRaster = image.getRaster(); 179 sampleModel = inputRaster.getSampleModel(); 180 } else { 181 input = image.getRenderedImage(); 182 sampleModel = input.getSampleModel(); 183 184 inputRaster = input.getData(); 185 } 186 187 checkSampleModel(sampleModel); 188 if (sourceRegion == null) 189 sourceRegion = inputRaster.getBounds(); 190 else 191 sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); 192 193 if (sourceRegion.isEmpty()) 194 throw new RuntimeException(I18N.getString("WBMPImageWriter1")); 195 196 int scaleX = param.getSourceXSubsampling(); 197 int scaleY = param.getSourceYSubsampling(); 198 int xOffset = param.getSubsamplingXOffset(); 199 int yOffset = param.getSubsamplingYOffset(); 200 201 sourceRegion.translate(xOffset, yOffset); 202 sourceRegion.width -= xOffset; 203 sourceRegion.height -= yOffset; 204 205 int minX = sourceRegion.x / scaleX; 206 int minY = sourceRegion.y / scaleY; 207 int w = (sourceRegion.width + scaleX - 1) / scaleX; 208 int h = (sourceRegion.height + scaleY - 1) / scaleY; 209 210 Rectangle destinationRegion = new Rectangle(minX, minY, w, h); 211 sampleModel = sampleModel.createCompatibleSampleModel(w, h); 212 213 SampleModel destSM= sampleModel; 214 215 // If the data are not formatted nominally then reformat. 216 if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE || 217 !(sampleModel instanceof MultiPixelPackedSampleModel) || 218 ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) { 219 destSM = 220 new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, 221 w, h, 1, 222 w + 7 >> 3, 0); 223 } 224 225 if (!destinationRegion.equals(sourceRegion)) { 226 if (scaleX == 1 && scaleY == 1) 227 inputRaster = inputRaster.createChild(inputRaster.getMinX(), 228 inputRaster.getMinY(), 229 w, h, minX, minY, null); 230 else { 231 WritableRaster ras = Raster.createWritableRaster(destSM, 232 new Point(minX, minY)); 233 234 byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData(); 235 236 for(int j = minY, y = sourceRegion.y, k = 0; 237 j < minY + h; j++, y += scaleY) { 238 239 for (int i = 0, x = sourceRegion.x; 240 i <w; i++, x +=scaleX) { 241 int v = inputRaster.getSample(x, y, 0); 242 data[k + (i >> 3)] |= v << (7 - (i & 7)); 243 } 244 k += w + 7 >> 3; 245 } 246 inputRaster = ras; 247 } 248 } 249 250 // If the data are not formatted nominally then reformat. 251 if(!destSM.equals(inputRaster.getSampleModel())) { 252 WritableRaster raster = 253 Raster.createWritableRaster(destSM, 254 new Point(inputRaster.getMinX(), 255 inputRaster.getMinY())); 256 raster.setRect(inputRaster); 257 inputRaster = raster; 258 } 259 260 // Check whether the image is white-is-zero. 261 boolean isWhiteZero = false; 262 if(!writeRaster && input.getColorModel() instanceof IndexColorModel) { 263 IndexColorModel icm = (IndexColorModel)input.getColorModel(); 264 isWhiteZero = icm.getRed(0) > icm.getRed(1); 265 } 266 267 // Get the line stride, bytes per row, and data array. 268 int lineStride = 269 ((MultiPixelPackedSampleModel)destSM).getScanlineStride(); 270 int bytesPerRow = (w + 7)/8; 271 byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData(); 272 273 // Write WBMP header. 274 stream.write(0); // TypeField 275 stream.write(0); // FixHeaderField 276 stream.write(intToMultiByte(w)); // width 277 stream.write(intToMultiByte(h)); // height 278 279 // Write the data. 280 if(!isWhiteZero && lineStride == bytesPerRow) { 281 // Write the entire image. 282 stream.write(bdata, 0, h * bytesPerRow); 283 processImageProgress(100.0F); 284 } else { 285 // Write the image row-by-row. 286 int offset = 0; 287 if(!isWhiteZero) { 288 // Black-is-zero 289 for(int row = 0; row < h; row++) { 290 if (abortRequested()) 291 break; 292 stream.write(bdata, offset, bytesPerRow); 293 offset += lineStride; 294 processImageProgress(100.0F * row / h); 295 } 296 } else { 297 // White-is-zero: need to invert data. 298 byte[] inverted = new byte[bytesPerRow]; 299 for(int row = 0; row < h; row++) { 300 if (abortRequested()) 301 break; 302 for(int col = 0; col < bytesPerRow; col++) { 303 inverted[col] = (byte)(~(bdata[col+offset])); 304 } 305 stream.write(inverted, 0, bytesPerRow); 306 offset += lineStride; 307 processImageProgress(100.0F * row / h); 308 } 309 } 310 } 311 312 if (abortRequested()) 313 processWriteAborted(); 314 else { 315 processImageComplete(); 316 stream.flushBefore(stream.getStreamPosition()); 317 } 318 } 319 320 public void reset() { 321 super.reset(); 322 stream = null; 323 } 324 325 private void checkSampleModel(SampleModel sm) { 326 int type = sm.getDataType(); 327 if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT 328 || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1) 329 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2")); 330 } 331}