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