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