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