1 /* 2 * Copyright (c) 2003, 2017, 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 public class WBMPImageWriter extends ImageWriter { 63 /** The output stream to write into */ 64 private ImageOutputStream stream = null; 65 66 // Get the number of bits required to represent an int. 67 private static int getNumBits(int intValue) { 68 int numBits = 32; 69 int mask = 0x80000000; 70 while(mask != 0 && (intValue & mask) == 0) { 71 numBits--; 72 mask >>>= 1; 73 } 74 return numBits; 75 } 76 77 // Convert an int value to WBMP multi-byte format. 78 private static byte[] intToMultiByte(int intValue) { 79 int numBitsLeft = getNumBits(intValue); 80 byte[] multiBytes = new byte[(numBitsLeft + 6)/7]; 81 82 int maxIndex = multiBytes.length - 1; 83 for(int b = 0; b <= maxIndex; b++) { 84 multiBytes[b] = (byte)((intValue >>> ((maxIndex - b)*7))&0x7f); 85 if(b != maxIndex) { 86 multiBytes[b] |= (byte)0x80; 87 } 88 } 89 90 return multiBytes; 91 } 92 93 /** Constructs {@code WBMPImageWriter} based on the provided 94 * {@code ImageWriterSpi}. 95 */ 96 public WBMPImageWriter(ImageWriterSpi originator) { 97 super(originator); 98 } 99 100 public void setOutput(Object output) { 101 super.setOutput(output); // validates output 102 if (output != null) { 103 if (!(output instanceof ImageOutputStream)) 104 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter")); 105 this.stream = (ImageOutputStream)output; 106 } else 107 this.stream = null; 108 } 109 110 public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) { 111 return null; 112 } 113 114 public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, 115 ImageWriteParam param) { 116 WBMPMetadata meta = new WBMPMetadata(); 117 meta.wbmpType = 0; // default wbmp level 118 return meta; 119 } 120 121 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 122 ImageWriteParam param) { 123 return null; 124 } 125 126 public IIOMetadata convertImageMetadata(IIOMetadata metadata, 127 ImageTypeSpecifier type, 128 ImageWriteParam param) { 129 return null; 130 } 131 132 public boolean canWriteRasters() { 133 return true; 134 } 135 136 public void write(IIOMetadata streamMetadata, 137 IIOImage image, 138 ImageWriteParam param) throws IOException { 139 140 if (stream == null) { 141 throw new IllegalStateException(I18N.getString("WBMPImageWriter3")); 142 } 143 144 if (image == null) { 145 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter4")); 146 } 147 148 clearAbortRequest(); 149 processImageStarted(0); 150 if (param == null) 151 param = getDefaultWriteParam(); 152 153 RenderedImage input = null; 154 Raster inputRaster = null; 155 boolean writeRaster = image.hasRaster(); 156 Rectangle sourceRegion = param.getSourceRegion(); 157 SampleModel sampleModel = null; 158 159 if (writeRaster) { 160 inputRaster = image.getRaster(); 161 sampleModel = inputRaster.getSampleModel(); 162 } else { 163 input = image.getRenderedImage(); 164 sampleModel = input.getSampleModel(); 165 166 inputRaster = input.getData(); 167 } 168 169 checkSampleModel(sampleModel); 170 if (sourceRegion == null) 171 sourceRegion = inputRaster.getBounds(); 172 else 173 sourceRegion = sourceRegion.intersection(inputRaster.getBounds()); 174 175 if (sourceRegion.isEmpty()) 176 throw new RuntimeException(I18N.getString("WBMPImageWriter1")); 177 178 int scaleX = param.getSourceXSubsampling(); 179 int scaleY = param.getSourceYSubsampling(); 180 int xOffset = param.getSubsamplingXOffset(); 181 int yOffset = param.getSubsamplingYOffset(); 182 183 sourceRegion.translate(xOffset, yOffset); 184 sourceRegion.width -= xOffset; 185 sourceRegion.height -= yOffset; 186 187 int minX = sourceRegion.x / scaleX; 188 int minY = sourceRegion.y / scaleY; 189 int w = (sourceRegion.width + scaleX - 1) / scaleX; 190 int h = (sourceRegion.height + scaleY - 1) / scaleY; 191 192 Rectangle destinationRegion = new Rectangle(minX, minY, w, h); 193 sampleModel = sampleModel.createCompatibleSampleModel(w, h); 194 195 SampleModel destSM= sampleModel; 196 197 // If the data are not formatted nominally then reformat. 198 if(sampleModel.getDataType() != DataBuffer.TYPE_BYTE || 199 !(sampleModel instanceof MultiPixelPackedSampleModel) || 200 ((MultiPixelPackedSampleModel)sampleModel).getDataBitOffset() != 0) { 201 destSM = 202 new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, 203 w, h, 1, 204 w + 7 >> 3, 0); 205 } 206 207 if (!destinationRegion.equals(sourceRegion)) { 208 if (scaleX == 1 && scaleY == 1) 209 inputRaster = inputRaster.createChild(inputRaster.getMinX(), 210 inputRaster.getMinY(), 211 w, h, minX, minY, null); 212 else { 213 WritableRaster ras = Raster.createWritableRaster(destSM, 214 new Point(minX, minY)); 215 216 byte[] data = ((DataBufferByte)ras.getDataBuffer()).getData(); 217 218 for(int j = minY, y = sourceRegion.y, k = 0; 219 j < minY + h; j++, y += scaleY) { 220 221 for (int i = 0, x = sourceRegion.x; 222 i <w; i++, x +=scaleX) { 223 int v = inputRaster.getSample(x, y, 0); 224 data[k + (i >> 3)] |= v << (7 - (i & 7)); 225 } 226 k += w + 7 >> 3; 227 } 228 inputRaster = ras; 229 } 230 } 231 232 // If the data are not formatted nominally then reformat. 233 if(!destSM.equals(inputRaster.getSampleModel())) { 234 WritableRaster raster = 235 Raster.createWritableRaster(destSM, 236 new Point(inputRaster.getMinX(), 237 inputRaster.getMinY())); 238 raster.setRect(inputRaster); 239 inputRaster = raster; 240 } 241 242 // Check whether the image is white-is-zero. 243 boolean isWhiteZero = false; 244 if(!writeRaster && input.getColorModel() instanceof IndexColorModel) { 245 IndexColorModel icm = (IndexColorModel)input.getColorModel(); 246 isWhiteZero = icm.getRed(0) > icm.getRed(1); 247 } 248 249 // Get the line stride, bytes per row, and data array. 250 int lineStride = 251 ((MultiPixelPackedSampleModel)destSM).getScanlineStride(); 252 int bytesPerRow = (w + 7)/8; 253 byte[] bdata = ((DataBufferByte)inputRaster.getDataBuffer()).getData(); 254 255 // Write WBMP header. 256 stream.write(0); // TypeField 257 stream.write(0); // FixHeaderField 258 stream.write(intToMultiByte(w)); // width 259 stream.write(intToMultiByte(h)); // height 260 261 // Write the data. 262 if(!isWhiteZero && lineStride == bytesPerRow) { 263 // Write the entire image. 264 stream.write(bdata, 0, h * bytesPerRow); 265 processImageProgress(100.0F); 266 } else { 267 // Write the image row-by-row. 268 int offset = 0; 269 if(!isWhiteZero) { 270 // Black-is-zero 271 for(int row = 0; row < h; row++) { 272 if (abortRequested()) 273 break; 274 stream.write(bdata, offset, bytesPerRow); 275 offset += lineStride; 276 processImageProgress(100.0F * row / h); 277 } 278 } else { 279 // White-is-zero: need to invert data. 280 byte[] inverted = new byte[bytesPerRow]; 281 for(int row = 0; row < h; row++) { 282 if (abortRequested()) 283 break; 284 for(int col = 0; col < bytesPerRow; col++) { 285 inverted[col] = (byte)(~(bdata[col+offset])); 286 } 287 stream.write(inverted, 0, bytesPerRow); 288 offset += lineStride; 289 processImageProgress(100.0F * row / h); 290 } 291 } 292 } 293 294 if (abortRequested()) 295 processWriteAborted(); 296 else { 297 processImageComplete(); 298 stream.flushBefore(stream.getStreamPosition()); 299 } 300 } 301 302 public void reset() { 303 super.reset(); 304 stream = null; 305 } 306 307 private void checkSampleModel(SampleModel sm) { 308 int type = sm.getDataType(); 309 if (type < DataBuffer.TYPE_BYTE || type > DataBuffer.TYPE_INT 310 || sm.getNumBands() != 1 || sm.getSampleSize(0) != 1) 311 throw new IllegalArgumentException(I18N.getString("WBMPImageWriter2")); 312 } 313 }