/*
* Copyright (c) 2009, 2019, 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;
}
}
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;
}
}
}