/* * Copyright (c) 2009, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javafx.iio.jpeg; import com.sun.javafx.iio.ImageFrame; import com.sun.javafx.iio.ImageMetadata; import com.sun.javafx.iio.ImageStorage.ImageType; import com.sun.glass.utils.NativeLibLoader; import com.sun.javafx.iio.common.ImageLoaderImpl; import com.sun.javafx.iio.common.ImageTools; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.security.AccessController; import java.security.PrivilegedAction; public class JPEGImageLoader extends ImageLoaderImpl { // IJG Color codes. public static final int JCS_UNKNOWN = 0; // error/unspecified public static final int JCS_GRAYSCALE = 1; // monochrome public static final int JCS_RGB = 2; // red/green/blue public static final int JCS_YCbCr = 3; // Y/Cb/Cr (also known as YUV) public static final int JCS_CMYK = 4; // C/M/Y/K public static final int JCS_YCC = 5; // PhotoYCC public static final int JCS_RGBA = 6; // RGB-Alpha public static final int JCS_YCbCrA = 7; // Y/Cb/Cr/Alpha // 8 and 9 were old "Legacy" codes which the old code never identified // on reading anyway. Support for writing them is being dropped, too. public static final int JCS_YCCA = 10; // PhotoYCC-Alpha public static final int JCS_YCCK = 11; // Y/Cb/Cr/K /** * The following variable contains a pointer to the IJG library * structure for this reader. It is assigned in the constructor * and then is passed in to every native call. It is set to 0 * by dispose to avoid disposing twice. */ private long structPointer = 0L; /** Set by setInputAttributes native code callback */ private int inWidth; /** Set by setInputAttributes native code callback */ private int inHeight; /** * Set by setInputAttributes native code callback. A modified * IJG+NIFTY colorspace code. */ private int inColorSpaceCode; /** * Set by setInputAttributes native code callback. A modified * IJG+NIFTY colorspace code. */ private int outColorSpaceCode; /** Set by setInputAttributes native code callback */ private byte[] iccData; /** Set by setOutputAttributes native code callback. */ private int outWidth; /** Set by setOutputAttributes native code callback. */ private int outHeight; private ImageType outImageType; private boolean isDisposed = false; private Lock accessLock = new Lock(); /** Sets up static C structures. */ private static native void initJPEGMethodIDs(Class inputStreamClass); private static native void disposeNative(long structPointer); /** Sets up per-reader C structure and returns a pointer to it. */ private native long initDecompressor(InputStream stream) throws IOException; /** Sets output color space and scale factor. * Returns number of components which native decoder * will produce for requested output color space. */ private native int startDecompression(long structPointer, int outColorSpaceCode, int scaleNum, int scaleDenom); private native boolean decompressIndirect(long structPointer, boolean reportProgress, byte[] array) throws IOException; static { AccessController.doPrivileged((PrivilegedAction) () -> { NativeLibLoader.loadLibrary("javafx_iio"); return null; }); initJPEGMethodIDs(InputStream.class); } /* * Called by the native code when the image header has been read. */ private void setInputAttributes(int width, int height, int colorSpaceCode, int outColorSpaceCode, int numComponents, byte[] iccData) { this.inWidth = width; this.inHeight = height; this.inColorSpaceCode = colorSpaceCode; this.outColorSpaceCode = outColorSpaceCode; this.iccData = iccData; // Set outImageType. switch (outColorSpaceCode) { case JCS_GRAYSCALE: this.outImageType = ImageType.GRAY; break; case JCS_YCbCr: case JCS_YCC: case JCS_RGB: this.outImageType = ImageType.RGB; break; case JCS_CMYK: case JCS_YCbCrA: case JCS_YCCA: case JCS_YCCK: case JCS_RGBA: this.outImageType = ImageType.RGBA_PRE; break; case JCS_UNKNOWN: switch (numComponents) { case 1: this.outImageType = ImageType.GRAY; break; case 3: this.outImageType = ImageType.RGB; break; case 4: this.outImageType = ImageType.RGBA_PRE; break; default: assert false; } break; default: assert false; break; } } /* * Called by the native code after starting decompression. */ private void setOutputAttributes(int width, int height) { this.outWidth = width; this.outHeight = height; } private void updateImageProgress(int outLinesDecoded) { updateImageProgress(100.0F * outLinesDecoded / outHeight); } JPEGImageLoader(InputStream input) throws IOException { super(JPEGDescriptor.getInstance()); if (input == null) { throw new IllegalArgumentException("input == null!"); } try { this.structPointer = initDecompressor(input); } catch (IOException e) { dispose(); throw e; } if (this.structPointer == 0L) { throw new IOException("Unable to initialize JPEG decompressor"); } } public synchronized void dispose() { if(!accessLock.isLocked() && !isDisposed && structPointer != 0L) { isDisposed = true; disposeNative(structPointer); structPointer = 0L; } } protected void finalize() { dispose(); } public ImageFrame load(int imageIndex, int width, int height, boolean preserveAspectRatio, boolean smooth) throws IOException { if (imageIndex != 0) { return null; } accessLock.lock(); // Determine output image dimensions. int[] widthHeight = ImageTools.computeDimensions(inWidth, inHeight, width, height, preserveAspectRatio); width = widthHeight[0]; height = widthHeight[1]; ImageMetadata md = new ImageMetadata(null, true, null, null, null, null, null, width, height, null, null, null); updateImageMetadata(md); ByteBuffer buffer = null; int outNumComponents; try { outNumComponents = startDecompression(structPointer, outColorSpaceCode, width, height); if (outWidth < 0 || outHeight < 0 || outNumComponents < 0) { throw new IOException("negative dimension."); } if (outWidth > (Integer.MAX_VALUE / outNumComponents)) { throw new IOException("bad width."); } int scanlineStride = outWidth * outNumComponents; if (scanlineStride > (Integer.MAX_VALUE / outHeight)) { throw new IOException("bad height."); } byte[] array = new byte[scanlineStride*outHeight]; buffer = ByteBuffer.wrap(array); decompressIndirect(structPointer, listeners != null && !listeners.isEmpty(), buffer.array()); } catch (IOException e) { throw e; } catch (Throwable t) { throw new IOException(t); } finally { accessLock.unlock(); dispose(); } if (buffer == null) { throw new IOException("Error decompressing JPEG stream!"); } // Check whether the decompressed image has been scaled to the correct // dimensions. If not, downscale it here. Note outData, outHeight, and // outWidth refer to the image as returned by the decompressor. This // image might have been downscaled from the original source by a factor // of N/8 where 1 <= N <=8. if (outWidth != width || outHeight != height) { buffer = ImageTools.scaleImage(buffer, outWidth, outHeight, outNumComponents, width, height, smooth); } return new ImageFrame(outImageType, buffer, width, height, width * outNumComponents, null, md); } private static class Lock { private boolean locked; public Lock() { locked = false; } public synchronized boolean isLocked() { return locked; } public synchronized void lock() { if (locked) { throw new IllegalStateException("Recursive loading is not allowed."); } locked = true; } public synchronized void unlock() { if (!locked) { throw new IllegalStateException("Invalid loader state."); } locked = false; } } }