1 /*
   2  * Copyright (c) 2009, 2018, 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.javafx.iio.jpeg;
  27 
  28 import com.sun.javafx.iio.ImageFrame;
  29 import com.sun.javafx.iio.ImageMetadata;
  30 import com.sun.javafx.iio.ImageStorage.ImageType;
  31 import com.sun.glass.utils.NativeLibLoader;
  32 import com.sun.javafx.iio.common.ImageLoaderImpl;
  33 import com.sun.javafx.iio.common.ImageTools;
  34 import java.io.IOException;
  35 import java.io.InputStream;
  36 import java.nio.ByteBuffer;
  37 import java.security.AccessController;
  38 import java.security.PrivilegedAction;
  39 
  40 public class JPEGImageLoader extends ImageLoaderImpl {
  41 
  42     // IJG Color codes.
  43     public static final int JCS_UNKNOWN = 0;       // error/unspecified
  44     public static final int JCS_GRAYSCALE = 1;     // monochrome
  45     public static final int JCS_RGB = 2;           // red/green/blue
  46     public static final int JCS_YCbCr = 3;         // Y/Cb/Cr (also known as YUV)
  47     public static final int JCS_CMYK = 4;          // C/M/Y/K
  48     public static final int JCS_YCC = 5;           // PhotoYCC
  49     public static final int JCS_RGBA = 6;          // RGB-Alpha
  50     public static final int JCS_YCbCrA = 7;        // Y/Cb/Cr/Alpha
  51     // 8 and 9 were old "Legacy" codes which the old code never identified
  52     // on reading anyway.  Support for writing them is being dropped, too.
  53     public static final int JCS_YCCA = 10;         // PhotoYCC-Alpha
  54     public static final int JCS_YCCK = 11;         // Y/Cb/Cr/K
  55     /**
  56      * The following variable contains a pointer to the IJG library
  57      * structure for this reader.  It is assigned in the constructor
  58      * and then is passed in to every native call.  It is set to 0
  59      * by dispose to avoid disposing twice.
  60      */
  61     private long structPointer = 0L;
  62     /** Set by setInputAttributes native code callback */
  63     private int inWidth;
  64     /** Set by setInputAttributes native code callback */
  65     private int inHeight;
  66     /**
  67      * Set by setInputAttributes native code callback.  A modified
  68      * IJG+NIFTY colorspace code.
  69      */
  70     private int inColorSpaceCode;
  71     /**
  72      * Set by setInputAttributes native code callback.  A modified
  73      * IJG+NIFTY colorspace code.
  74      */
  75     private int outColorSpaceCode;
  76     /** Set by setInputAttributes native code callback */
  77     private byte[] iccData;
  78     /** Set by setOutputAttributes native code callback. */
  79     private int outWidth;
  80     /** Set by setOutputAttributes native code callback. */
  81     private int outHeight;
  82     private ImageType outImageType;
  83 
  84     private boolean isDisposed = false;
  85 
  86     private Lock accessLock = new Lock();
  87 
  88     /** Sets up static C structures. */
  89     private static native void initJPEGMethodIDs(Class inputStreamClass);
  90 
  91     private static native void disposeNative(long structPointer);
  92 
  93     /** Sets up per-reader C structure and returns a pointer to it. */
  94     private native long initDecompressor(InputStream stream) throws IOException;
  95 
  96     /** Sets output color space and scale factor.
  97      *  Returns number of components which native decoder
  98      *  will produce for requested output color space.
  99      */
 100     private native int startDecompression(long structPointer,
 101             int outColorSpaceCode, int scaleNum, int scaleDenom);
 102 
 103     private native boolean decompressIndirect(long structPointer, boolean reportProgress, byte[] array) throws IOException;
 104 
 105     static {
 106         AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
 107             NativeLibLoader.loadLibrary("javafx_iio");
 108             return null;
 109         });
 110         initJPEGMethodIDs(InputStream.class);
 111     }
 112 
 113     /*
 114      * Called by the native code when the image header has been read.
 115      */
 116     private void setInputAttributes(int width,
 117             int height,
 118             int colorSpaceCode,
 119             int outColorSpaceCode,
 120             int numComponents,
 121             byte[] iccData) {
 122         this.inWidth = width;
 123         this.inHeight = height;
 124         this.inColorSpaceCode = colorSpaceCode;
 125         this.outColorSpaceCode = outColorSpaceCode;
 126         this.iccData = iccData;
 127 
 128         // Set outImageType.
 129         switch (outColorSpaceCode) {
 130             case JCS_GRAYSCALE:
 131                 this.outImageType = ImageType.GRAY;
 132                 break;
 133             case JCS_YCbCr:
 134             case JCS_YCC:
 135             case JCS_RGB:
 136                 this.outImageType = ImageType.RGB;
 137                 break;
 138             case JCS_CMYK:
 139             case JCS_YCbCrA:
 140             case JCS_YCCA:
 141             case JCS_YCCK:
 142             case JCS_RGBA:
 143                 this.outImageType = ImageType.RGBA_PRE;
 144                 break;
 145             case JCS_UNKNOWN:
 146                 switch (numComponents) {
 147                     case 1:
 148                         this.outImageType = ImageType.GRAY;
 149                         break;
 150                     case 3:
 151                         this.outImageType = ImageType.RGB;
 152                         break;
 153                     case 4:
 154                         this.outImageType = ImageType.RGBA_PRE;
 155                         break;
 156                     default:
 157                         assert false;
 158                 }
 159                 break;
 160             default:
 161                 assert false;
 162                 break;
 163         }
 164     }
 165 
 166     /*
 167      * Called by the native code after starting decompression.
 168      */
 169     private void setOutputAttributes(int width, int height) {
 170         this.outWidth = width;
 171         this.outHeight = height;
 172     }
 173 
 174     private void updateImageProgress(int outLinesDecoded) {
 175         updateImageProgress(100.0F * outLinesDecoded / outHeight);
 176     }
 177 
 178     JPEGImageLoader(InputStream input) throws IOException {
 179         super(JPEGDescriptor.getInstance());
 180         if (input == null) {
 181             throw new IllegalArgumentException("input == null!");
 182         }
 183 
 184         try {
 185             this.structPointer = initDecompressor(input);
 186         } catch (IOException e) {
 187             dispose();
 188             throw e;
 189         }
 190 
 191         if (this.structPointer == 0L) {
 192             throw new IOException("Unable to initialize JPEG decompressor");
 193         }
 194     }
 195 
 196     public synchronized void dispose() {
 197         if(!accessLock.isLocked() && !isDisposed && structPointer != 0L) {
 198             isDisposed = true;
 199             disposeNative(structPointer);
 200             structPointer = 0L;
 201         }
 202     }
 203 
 204     protected void finalize() {
 205         dispose();
 206     }
 207 
 208     public ImageFrame load(int imageIndex, int width, int height, boolean preserveAspectRatio, boolean smooth) throws IOException {
 209         if (imageIndex != 0) {
 210             return null;
 211         }
 212 
 213         accessLock.lock();
 214 
 215         // Determine output image dimensions.
 216         int[] widthHeight = ImageTools.computeDimensions(inWidth, inHeight, width, height, preserveAspectRatio);
 217         width = widthHeight[0];
 218         height = widthHeight[1];
 219 
 220         ImageMetadata md = new ImageMetadata(null, true,
 221                 null, null, null, null, null,
 222                 width, height, null, null, null);
 223 
 224         updateImageMetadata(md);
 225 
 226         ByteBuffer buffer = null;
 227 
 228         int outNumComponents;
 229         try {
 230             outNumComponents = startDecompression(structPointer,
 231                     outColorSpaceCode, width, height);
 232 
 233             if (outWidth < 0 || outHeight < 0 || outNumComponents < 0) {
 234                throw new IOException("negative dimension.");
 235             }
 236             if (outWidth > (Integer.MAX_VALUE / outNumComponents)) {
 237                throw new IOException("bad width.");
 238             }
 239             int scanlineStride = outWidth * outNumComponents;
 240             if (scanlineStride > (Integer.MAX_VALUE / outHeight)) {
 241                throw new IOException("bad height.");
 242             }
 243 
 244             byte[] array = new byte[scanlineStride*outHeight];
 245             buffer = ByteBuffer.wrap(array);
 246             decompressIndirect(structPointer, listeners != null && !listeners.isEmpty(), buffer.array());
 247         } catch (IOException e) {
 248             throw e;
 249         } catch (Throwable t) {
 250             throw new IOException(t);
 251         } finally {
 252             accessLock.unlock();
 253             dispose();
 254         }
 255 
 256         if (buffer == null) {
 257             throw new IOException("Error decompressing JPEG stream!");
 258         }
 259 
 260         // Check whether the decompressed image has been scaled to the correct
 261         // dimensions. If not, downscale it here. Note outData, outHeight, and
 262         // outWidth refer to the image as returned by the decompressor. This
 263         // image might have been downscaled from the original source by a factor
 264         // of N/8 where 1 <= N <=8.
 265         if (outWidth != width || outHeight != height) {
 266             buffer = ImageTools.scaleImage(buffer,
 267                     outWidth, outHeight, outNumComponents, width, height, smooth);
 268         }
 269 
 270         return new ImageFrame(outImageType, buffer,
 271                 width, height, width * outNumComponents, null, md);
 272     }
 273 
 274     private static class Lock {
 275         private boolean locked;
 276 
 277         public Lock() {
 278             locked = false;
 279         }
 280 
 281         public synchronized boolean isLocked() {
 282             return locked;
 283         }
 284 
 285         public synchronized void lock() {
 286             if (locked) {
 287                 throw new IllegalStateException("Recursive loading is not allowed.");
 288             }
 289             locked = true;
 290         }
 291 
 292         public synchronized void unlock() {
 293             if (!locked) {
 294                 throw new IllegalStateException("Invalid loader state.");
 295             }
 296             locked = false;
 297         }
 298     }
 299 }