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 }