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 }