1 /* 2 * Copyright (c) 2009, 2015, 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 }