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