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