1 /* 2 * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.imageio.plugins.wbmp; 27 28 import java.awt.Point; 29 import java.awt.Rectangle; 30 import java.awt.image.ColorModel; 31 import java.awt.image.DataBuffer; 32 import java.awt.image.DataBufferByte; 33 import java.awt.image.IndexColorModel; 34 import java.awt.image.MultiPixelPackedSampleModel; 35 import java.awt.image.Raster; 36 import java.awt.image.RenderedImage; 37 import java.awt.image.SampleModel; 38 import java.awt.image.WritableRaster; 39 40 import java.io.IOException; 41 42 import javax.imageio.IIOImage; 43 import javax.imageio.IIOException; 44 import javax.imageio.ImageTypeSpecifier; 45 import javax.imageio.ImageWriteParam; 46 import javax.imageio.ImageWriter; 47 import javax.imageio.metadata.IIOMetadata; 48 import javax.imageio.metadata.IIOMetadataFormatImpl; 49 import javax.imageio.metadata.IIOInvalidTreeException; 50 import javax.imageio.spi.ImageWriterSpi; 51 import javax.imageio.stream.ImageOutputStream; 52 53 import com.sun.imageio.plugins.common.I18N; 54 55 /** 56 * The Java Image IO plugin writer for encoding a binary RenderedImage into 57 * a WBMP format. 58 * 59 * The encoding process may clip, subsample using the parameters 60 * specified in the {@code ImageWriteParam}. 61 * 62 * @see com.sun.media.imageio.plugins.WBMPImageWriteParam 63 */ 64 public class WBMPImageWriter extends ImageWriter { 65 /** The output stream to write into */ 66 private ImageOutputStream stream = null; 67 68 // Get the number of bits required to represent an int. 69 private static int getNumBits(int intValue) { 70 int numBits = 32; 71 int mask = 0x80000000; 72 while(mask != 0 && (intValue & mask) == 0) { 73 numBits--; 74 mask >>>= 1; 75 } 76 return numBits; 77 } 78 79 // Convert an int value to WBMP multi-byte format. 80 private static byte[] intToMultiByte(int intValue) { 81 int numBitsLeft = getNumBits(intValue); 82 byte[] multiBytes = new byte[(numBitsLeft + 6)/7]; 83 84 int maxIndex = multiBytes.length - 1; 85 for(int b = 0; b <= maxIndex; b++) { 86 multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f); 87 if(b != maxIndex) { 88 multiBytes[b] |= (byte)0x80; 89 } 90 } 91 92 return multiBytes; 93 } 94 95 /** Constructs {@code WBMPImageWriter} based on the provided 96 * {@code ImageWriterSpi}. 97 */ 98 public WBMPImageWriter(ImageWriterSpi originator) { 99 super(originator); 100 } 101 102 public void setOutput(Object output) { 103 super.setOutput(output); // validates output 104 if (output != null) { 105 if (!(output instanceof ImageOutputStream)) 106 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter")); 107 this.stream = (ImageOutputStream)output; 108 } else 109 this.stream = null; 110 } 111 112 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 113 return null; 114 } 115 116 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, 117 ImageWriteParam param) { 118 WBMPMetadata meta = new WBMPMetadata(); 119 meta.wbmpType = 0; // default wbmp level 120 return meta; 121 } 122 123 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 124 ImageWriteParam param) { 125 return null; 126 } 127 128 public IIOMetadata convertImageMetadata(IIOMetadata metadata, 129 ImageTypeSpecifier type, 130 ImageWriteParam param) { 131 return null; 132 } 133 134 public boolean canWriteRasters() { 135 return true; 136 } 137 138 public void write(IIOMetadata streamMetadata, 139 IIOImage image, 140 ImageWriteParam param) throws IOException { 141 142 if (stream == null) { 143 throw new IllegalStateException(I18N.getString("WBMPImageWriter3")); 144 } 145 146 if (image == null) { 147 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4")); 148 } 149 150 clearAbortRequest(); 151 processImageStarted(0); 152 if (param == null) 153 param = getDefaultWriteParam(); 154 155 RenderedImage input = null; 156 Raster inputRaster = null; 157 boolean writeRaster = image.hasRaster(); 158 Rectangle sourceRegion = param.getSourceRegion(); 159 SampleModel sampleModel = null; 160 161 if (writeRaster) { 162 inputRaster = image.getRaster(); 163 sampleModel = inputRaster.getSampleModel(); 164 } else { 165 input = image.getRenderedImage(); 166 sampleModel = input.getSampleModel(); 167 168 inputRaster = input.getData(); 169 } 170 171 checkSampleModel(sampleModel); 172 if (sourceRegion == null) 173 sourceRegion = inputRaster.getBounds(); 174 else 175 sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); 176 177 if (sourceRegion.isEmpty()) 178 throw new RuntimeException(I18N.getString("WBMPImageWriter1")); 179 180 int scaleX = param.getSourceXSubsampling(); 181 int scaleY = param.getSourceYSubsampling(); 182 int xOffset = param.getSubsamplingXOffset(); 183 int yOffset = param.getSubsamplingYOffset(); 184 185 sourceRegion.translate(xOffset, yOffset); 186 sourceRegion.width -= xOffset; 187 sourceRegion.height -= yOffset; 188 189 int minX = sourceRegion.x / scaleX; 190 int minY = sourceRegion.y / scaleY; 191 int w = (sourceRegion.width + scaleX - 1) / scaleX; 192 int h = (sourceRegion.height + scaleY - 1) / scaleY; 193 194 Rectangle destinationRegion = new Rectangle(minX, minY, w, h); 195 sampleModel = sampleModel.createCompatibleSampleModel(w, h); 196 197 SampleModel destSM= sampleModel; 198 199 // If the data are not formatted nominally then reformat. 200 if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE || 201 !(sampleModel instanceof MultiPixelPackedSampleModel) || 202 ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) { 203 destSM = 204 new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, 205 w, h, 1, 206 w + 7 >> 3, 0); 207 } 208 209 if (!destinationRegion.equals(sourceRegion)) { 210 if (scaleX == 1 && scaleY == 1) 211 inputRaster = inputRaster.createChild(inputRaster.getMinX(), 212 inputRaster.getMinY(), 213 w, h, minX, minY, null); 214 else { 215 WritableRaster ras = Raster.createWritableRaster(destSM, 216 new Point(minX, minY)); 217 218 byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData(); 219 220 for(int j = minY, y = sourceRegion.y, k = 0; 221 j < minY + h; j++, y += scaleY) { 222 223 for (int i = 0, x = sourceRegion.x; 224 i <w; i++, x +=scaleX) { 225 int v = inputRaster.getSample(x, y, 0); 226 data[k + (i >> 3)] |= v << (7 - (i & 7)); 227 } 228 k += w + 7 >> 3; 229 } 230 inputRaster = ras; 231 } 232 } 233 234 // If the data are not formatted nominally then reformat. 235 if(!destSM.equals(inputRaster.getSampleModel())) { 236 WritableRaster raster = 237 Raster.createWritableRaster(destSM, 238 new Point(inputRaster.getMinX(), 239 inputRaster.getMinY())); 240 raster.setRect(inputRaster); 241 inputRaster = raster; 242 } 243 244 // Check whether the image is white-is-zero. 245 boolean isWhiteZero = false; 246 if(!writeRaster && input.getColorModel() instanceof IndexColorModel) { 247 IndexColorModel icm = (IndexColorModel)input.getColorModel(); 248 isWhiteZero = icm.getRed(0) > icm.getRed(1); 249 } 250 251 // Get the line stride, bytes per row, and data array. 252 int lineStride = 253 ((MultiPixelPackedSampleModel)destSM).getScanlineStride(); 254 int bytesPerRow = (w + 7)/8; 255 byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData(); 256 257 // Write WBMP header. 258 stream.write(0); // TypeField 259 stream.write(0); // FixHeaderField 260 stream.write(intToMultiByte(w)); // width 261 stream.write(intToMultiByte(h)); // height 262 263 // Write the data. 264 if(!isWhiteZero && lineStride == bytesPerRow) { 265 // Write the entire image. 266 stream.write(bdata, 0, h * bytesPerRow); 267 processImageProgress(100.0F); 268 } else { 269 // Write the image row-by-row. 270 int offset = 0; 271 if(!isWhiteZero) { 272 // Black-is-zero 273 for(int row = 0; row < h; row++) { 274 if (abortRequested()) 275 break; 276 stream.write(bdata, offset, bytesPerRow); 277 offset += lineStride; 278 processImageProgress(100.0F * row / h); 279 } 280 } else { 281 // White-is-zero: need to invert data. 282 byte[] inverted = new byte[bytesPerRow]; 283 for(int row = 0; row < h; row++) { 284 if (abortRequested()) 285 break; 286 for(int col = 0; col < bytesPerRow; col++) { 287 inverted[col] = (byte)(~(bdata[col+offset])); 288 } 289 stream.write(inverted, 0, bytesPerRow); 290 offset += lineStride; 291 processImageProgress(100.0F * row / h); 292 } 293 } 294 } 295 296 if (abortRequested()) 297 processWriteAborted(); 298 else { 299 processImageComplete(); 300 stream.flushBefore(stream.getStreamPosition()); 301 } 302 } 303 304 public void reset() { 305 super.reset(); 306 stream = null; 307 } 308 309 private void checkSampleModel(SampleModel sm) { 310 int type = sm.getDataType(); 311 if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT 312 || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1) 313 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2")); 314 } 315 }