/*
 * 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.
/** * 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
/** Set by setInputAttributes native code callback */
private int inWidth;
Set by setInputAttributes native code callback
/** Set by setInputAttributes native code callback */
private int inHeight;
Set by setInputAttributes native code callback. A modified IJG+NIFTY colorspace code.
/** * 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.
/** * Set by setInputAttributes native code callback. A modified * IJG+NIFTY colorspace code. */
private int outColorSpaceCode;
Set by setInputAttributes native code callback
/** Set by setInputAttributes native code callback */
private byte[] iccData;
Set by setOutputAttributes native code callback.
/** Set by setOutputAttributes native code callback. */
private int outWidth;
Set by setOutputAttributes native code callback.
/** 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.
/** 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.
/** 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.
/** 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<Object>) () -> { 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; } } }