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.Rectangle;
  29 import java.awt.image.BufferedImage;
  30 import java.awt.image.DataBufferByte;
  31 import java.awt.image.MultiPixelPackedSampleModel;
  32 import java.awt.image.Raster;
  33 import java.awt.image.WritableRaster;
  34 
  35 import javax.imageio.IIOException;
  36 import javax.imageio.ImageReader;
  37 import javax.imageio.ImageReadParam;
  38 import javax.imageio.ImageTypeSpecifier;
  39 import javax.imageio.metadata.IIOMetadata;
  40 import javax.imageio.spi.ImageReaderSpi;
  41 import javax.imageio.stream.ImageInputStream;
  42 
  43 import java.io.*;
  44 import java.util.ArrayList;
  45 import java.util.Iterator;
  46 
  47 import com.sun.imageio.plugins.common.I18N;
  48 import com.sun.imageio.plugins.common.ReaderUtil;
  49 
  50 /** This class is the Java Image IO plugin reader for WBMP images.
  51  *  It may subsample the image, clip the image,
  52  *  and shift the decoded image origin if the proper decoding parameter
  53  *  are set in the provided {@code WBMPImageReadParam}.
  54  */
  55 public class WBMPImageReader extends ImageReader {
  56     /** The input stream where reads from */
  57     private ImageInputStream iis = null;
  58 
  59     /** Indicates whether the header is read. */
  60     private boolean gotHeader = false;
  61 
  62     /** The original image width. */
  63     private int width;
  64 
  65     /** The original image height. */
  66     private int height;
  67 
  68     private int wbmpType;
  69 
  70     private WBMPMetadata metadata;
  71 
  72     /** Constructs {@code WBMPImageReader} from the provided
  73      *  {@code ImageReaderSpi}.
  74      */
  75     public WBMPImageReader(ImageReaderSpi originator) {
  76         super(originator);
  77     }
  78 
  79     @Override
  80     public void setInput(Object input,
  81                          boolean seekForwardOnly,
  82                          boolean ignoreMetadata) {
  83         super.setInput(input, seekForwardOnly, ignoreMetadata);
  84         iis = (ImageInputStream) input; // Always works
  85         gotHeader = false;
  86     }
  87 
  88     @Override
  89     public int getNumImages(boolean allowSearch) throws IOException {
  90         if (iis == null) {
  91             throw new IllegalStateException(I18N.getString("GetNumImages0"));
  92         }
  93         if (seekForwardOnly && allowSearch) {
  94             throw new IllegalStateException(I18N.getString("GetNumImages1"));
  95         }
  96         return 1;
  97     }
  98 
  99     @Override
 100     public int getWidth(int imageIndex) throws IOException {
 101         checkIndex(imageIndex);
 102         readHeader();
 103         return width;
 104     }
 105 
 106     @Override
 107     public int getHeight(int imageIndex) throws IOException {
 108         checkIndex(imageIndex);
 109         readHeader();
 110         return height;
 111     }
 112 
 113     @Override
 114     public boolean isRandomAccessEasy(int imageIndex) throws IOException {
 115         checkIndex(imageIndex);
 116         return true;
 117     }
 118 
 119     private void checkIndex(int imageIndex) {
 120         if (imageIndex != 0) {
 121             throw new IndexOutOfBoundsException(I18N.getString("WBMPImageReader0"));
 122         }
 123     }
 124 
 125     public void readHeader() throws IOException {
 126         if (gotHeader)
 127             return;
 128 
 129         if (iis == null) {
 130             throw new IllegalStateException("Input source not set!");
 131         }
 132 
 133         metadata = new WBMPMetadata();
 134 
 135         wbmpType = iis.readByte();   // TypeField
 136         byte fixHeaderField = iis.readByte();
 137 
 138         // check for valid wbmp image
 139         if (fixHeaderField != 0
 140             || !isValidWbmpType(wbmpType))
 141         {
 142             throw new IIOException(I18N.getString("WBMPImageReader2"));
 143         }
 144 
 145         metadata.wbmpType = wbmpType;
 146 
 147         // Read image width
 148         width = ReaderUtil.readMultiByteInteger(iis);
 149         metadata.width = width;
 150 
 151         // Read image height
 152         height = ReaderUtil.readMultiByteInteger(iis);
 153         metadata.height = height;
 154 
 155         gotHeader = true;
 156     }
 157 
 158     @Override
 159     public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
 160         throws IOException {
 161         checkIndex(imageIndex);
 162         readHeader();
 163 
 164         BufferedImage bi =
 165             new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY);
 166         ArrayList<ImageTypeSpecifier> list = new ArrayList<>(1);
 167         list.add(new ImageTypeSpecifier(bi));
 168         return list.iterator();
 169     }
 170 
 171     @Override
 172     public ImageReadParam getDefaultReadParam() {
 173         return new ImageReadParam();
 174     }
 175 
 176     @Override
 177     public IIOMetadata getImageMetadata(int imageIndex)
 178         throws IOException {
 179         checkIndex(imageIndex);
 180         if (metadata == null) {
 181             readHeader();
 182         }
 183         return metadata;
 184     }
 185 
 186     @Override
 187     public IIOMetadata getStreamMetadata() throws IOException {
 188         return null;
 189     }
 190 
 191     @Override
 192     public BufferedImage read(int imageIndex, ImageReadParam param)
 193         throws IOException {
 194 
 195         if (iis == null) {
 196             throw new IllegalStateException(I18N.getString("WBMPImageReader1"));
 197         }
 198 
 199         checkIndex(imageIndex);
 200         clearAbortRequest();
 201         processImageStarted(imageIndex);
 202         if (param == null)
 203             param = getDefaultReadParam();
 204 
 205         //read header
 206         readHeader();
 207 
 208         Rectangle sourceRegion = new Rectangle(0, 0, 0, 0);
 209         Rectangle destinationRegion = new Rectangle(0, 0, 0, 0);
 210 
 211         computeRegions(param, this.width, this.height,
 212                        param.getDestination(),
 213                        sourceRegion,
 214                        destinationRegion);
 215 
 216         int scaleX = param.getSourceXSubsampling();
 217         int scaleY = param.getSourceYSubsampling();
 218         int xOffset = param.getSubsamplingXOffset();
 219         int yOffset = param.getSubsamplingYOffset();
 220 
 221         // If the destination is provided, then use it.  Otherwise, create new one
 222         BufferedImage bi = param.getDestination();
 223 
 224         if (bi == null)
 225             bi = new BufferedImage(destinationRegion.x + destinationRegion.width,
 226                               destinationRegion.y + destinationRegion.height,
 227                               BufferedImage.TYPE_BYTE_BINARY);
 228 
 229         boolean noTransform =
 230             destinationRegion.equals(new Rectangle(0, 0, width, height)) &&
 231             destinationRegion.equals(new Rectangle(0, 0, bi.getWidth(), bi.getHeight()));
 232 
 233         // Get the image data.
 234         WritableRaster tile = bi.getWritableTile(0, 0);
 235 
 236         // Get the SampleModel.
 237         MultiPixelPackedSampleModel sm =
 238             (MultiPixelPackedSampleModel)bi.getSampleModel();
 239 
 240         if (noTransform) {
 241             if (abortRequested()) {
 242                 processReadAborted();
 243                 return bi;
 244             }
 245 
 246             // If noTransform is necessary, read the data.
 247             iis.read(((DataBufferByte)tile.getDataBuffer()).getData(),
 248                      0, height*sm.getScanlineStride());
 249             processImageUpdate(bi,
 250                                0, 0,
 251                                width, height, 1, 1,
 252                                new int[]{0});
 253             processImageProgress(100.0F);
 254         } else {
 255             int len = (this.width + 7) / 8;
 256             byte[] buf = new byte[len];
 257             byte[] data = ((DataBufferByte)tile.getDataBuffer()).getData();
 258             int lineStride = sm.getScanlineStride();
 259             iis.skipBytes(len * sourceRegion.y);
 260             int skipLength = len * (scaleY - 1);
 261 
 262             // cache the values to avoid duplicated computation
 263             int[] srcOff = new int[destinationRegion.width];
 264             int[] destOff = new int[destinationRegion.width];
 265             int[] srcPos = new int[destinationRegion.width];
 266             int[] destPos = new int[destinationRegion.width];
 267 
 268             for (int i = destinationRegion.x, x = sourceRegion.x, j = 0;
 269                 i < destinationRegion.x + destinationRegion.width;
 270                     i++, j++, x += scaleX) {
 271                 srcPos[j] = x >> 3;
 272                 srcOff[j] = 7 - (x & 7);
 273                 destPos[j] = i >> 3;
 274                 destOff[j] = 7 - (i & 7);
 275             }
 276 
 277             for (int j = 0, y = sourceRegion.y,
 278                 k = destinationRegion.y * lineStride;
 279                 j < destinationRegion.height; j++, y+=scaleY) {
 280 
 281                 if (abortRequested())
 282                     break;
 283                 iis.read(buf, 0, len);
 284                 for (int i = 0; i < destinationRegion.width; i++) {
 285                     //get the bit and assign to the data buffer of the raster
 286                     int v = (buf[srcPos[i]] >> srcOff[i]) & 1;
 287                     data[k + destPos[i]] |= v << destOff[i];
 288                 }
 289 
 290                 k += lineStride;
 291                 iis.skipBytes(skipLength);
 292                         processImageUpdate(bi,
 293                                            0, j,
 294                                            destinationRegion.width, 1, 1, 1,
 295                                            new int[]{0});
 296                         processImageProgress(100.0F*j/destinationRegion.height);
 297             }
 298         }
 299 
 300         if (abortRequested())
 301             processReadAborted();
 302         else
 303             processImageComplete();
 304         return bi;
 305     }
 306 
 307     @Override
 308     public boolean canReadRaster() {
 309         return true;
 310     }
 311 
 312     @Override
 313     public Raster readRaster(int imageIndex,
 314                              ImageReadParam param) throws IOException {
 315         BufferedImage bi = read(imageIndex, param);
 316         return bi.getData();
 317     }
 318 
 319     @Override
 320     public void reset() {
 321         super.reset();
 322         iis = null;
 323         gotHeader = false;
 324     }
 325 
 326     /*
 327      * This method verifies that given byte is valid wbmp type marker.
 328      * At the moment only 0x0 marker is described by wbmp spec.
 329      */
 330     boolean isValidWbmpType(int type) {
 331         return type == 0;
 332     }
 333 }