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 }