/* * Copyright (C)2011-2014 D. R. Commander. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the libjpeg-turbo Project nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS", * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.libjpegturbo.turbojpeg; import java.awt.image.*; import java.nio.*; /** * TurboJPEG decompressor */ public class TJDecompressor { private static final String NO_ASSOC_ERROR = "No JPEG image is associated with this instance"; /** * Create a TurboJPEG decompresssor instance. */ public TJDecompressor() throws Exception { init(); } /** * Create a TurboJPEG decompressor instance and associate the JPEG image * stored in jpegImage with the newly created instance. * * @param jpegImage JPEG image buffer (size of the JPEG image is assumed to * be the length of the array) */ public TJDecompressor(byte[] jpegImage) throws Exception { init(); setJPEGImage(jpegImage, jpegImage.length); } /** * Create a TurboJPEG decompressor instance and associate the JPEG image * of length imageSize bytes stored in jpegImage * with the newly created instance. * * @param jpegImage JPEG image buffer * * @param imageSize size of the JPEG image (in bytes) */ public TJDecompressor(byte[] jpegImage, int imageSize) throws Exception { init(); setJPEGImage(jpegImage, imageSize); } /** * Associate the JPEG image of length imageSize bytes stored in * jpegImage with this decompressor instance. This image will * be used as the source image for subsequent decompress operations. * * @param jpegImage JPEG image buffer * * @param imageSize size of the JPEG image (in bytes) */ public void setJPEGImage(byte[] jpegImage, int imageSize) throws Exception { if (jpegImage == null || imageSize < 1) throw new Exception("Invalid argument in setJPEGImage()"); jpegBuf = jpegImage; jpegBufSize = imageSize; decompressHeader(jpegBuf, jpegBufSize); } /** * Returns the width of the JPEG image associated with this decompressor * instance. * * @return the width of the JPEG image associated with this decompressor * instance */ public int getWidth() throws Exception { if (jpegWidth < 1) throw new Exception(NO_ASSOC_ERROR); return jpegWidth; } /** * Returns the height of the JPEG image associated with this decompressor * instance. * * @return the height of the JPEG image associated with this decompressor * instance */ public int getHeight() throws Exception { if (jpegHeight < 1) throw new Exception(NO_ASSOC_ERROR); return jpegHeight; } /** * Returns the level of chrominance subsampling used in the JPEG image * associated with this decompressor instance. See {@link TJ TJ.SAMP_*}. * * @return the level of chrominance subsampling used in the JPEG image * associated with this decompressor instance */ public int getSubsamp() throws Exception { if (jpegSubsamp < 0) throw new Exception(NO_ASSOC_ERROR); if (jpegSubsamp >= TJ.NUMSAMP) throw new Exception("JPEG header information is invalid"); return jpegSubsamp; } /** * Returns the JPEG image buffer associated with this decompressor instance. * * @return the JPEG image buffer associated with this decompressor instance */ public byte[] getJPEGBuf() throws Exception { if (jpegBuf == null) throw new Exception(NO_ASSOC_ERROR); return jpegBuf; } /** * Returns the size of the JPEG image (in bytes) associated with this * decompressor instance. * * @return the size of the JPEG image (in bytes) associated with this * decompressor instance */ public int getJPEGSize() throws Exception { if (jpegBufSize < 1) throw new Exception(NO_ASSOC_ERROR); return jpegBufSize; } /** * Returns the width of the largest scaled-down image that the TurboJPEG * decompressor can generate without exceeding the desired image width and * height. * * @param desiredWidth desired width (in pixels) of the decompressed image. * Setting this to 0 is the same as setting it to the width of the JPEG image * (in other words, the width will not be considered when determining the * scaled image size.) * * @param desiredHeight desired height (in pixels) of the decompressed image. * Setting this to 0 is the same as setting it to the height of the JPEG * image (in other words, the height will not be considered when determining * the scaled image size.) * * @return the width of the largest scaled-down image that the TurboJPEG * decompressor can generate without exceeding the desired image width and * height */ public int getScaledWidth(int desiredWidth, int desiredHeight) throws Exception { if (jpegWidth < 1 || jpegHeight < 1) throw new Exception(NO_ASSOC_ERROR); if (desiredWidth < 0 || desiredHeight < 0) throw new Exception("Invalid argument in getScaledWidth()"); TJScalingFactor[] sf = TJ.getScalingFactors(); if (desiredWidth == 0) desiredWidth = jpegWidth; if (desiredHeight == 0) desiredHeight = jpegHeight; int scaledWidth = jpegWidth, scaledHeight = jpegHeight; for (int i = 0; i < sf.length; i++) { scaledWidth = sf[i].getScaled(jpegWidth); scaledHeight = sf[i].getScaled(jpegHeight); if (scaledWidth <= desiredWidth && scaledHeight <= desiredHeight) break; } if (scaledWidth > desiredWidth || scaledHeight > desiredHeight) throw new Exception("Could not scale down to desired image dimensions"); return scaledWidth; } /** * Returns the height of the largest scaled-down image that the TurboJPEG * decompressor can generate without exceeding the desired image width and * height. * * @param desiredWidth desired width (in pixels) of the decompressed image. * Setting this to 0 is the same as setting it to the width of the JPEG image * (in other words, the width will not be considered when determining the * scaled image size.) * * @param desiredHeight desired height (in pixels) of the decompressed image. * Setting this to 0 is the same as setting it to the height of the JPEG * image (in other words, the height will not be considered when determining * the scaled image size.) * * @return the height of the largest scaled-down image that the TurboJPEG * decompressor can generate without exceeding the desired image width and * height */ public int getScaledHeight(int desiredWidth, int desiredHeight) throws Exception { if (jpegWidth < 1 || jpegHeight < 1) throw new Exception(NO_ASSOC_ERROR); if (desiredWidth < 0 || desiredHeight < 0) throw new Exception("Invalid argument in getScaledHeight()"); TJScalingFactor[] sf = TJ.getScalingFactors(); if (desiredWidth == 0) desiredWidth = jpegWidth; if (desiredHeight == 0) desiredHeight = jpegHeight; int scaledWidth = jpegWidth, scaledHeight = jpegHeight; for (int i = 0; i < sf.length; i++) { scaledWidth = sf[i].getScaled(jpegWidth); scaledHeight = sf[i].getScaled(jpegHeight); if (scaledWidth <= desiredWidth && scaledHeight <= desiredHeight) break; } if (scaledWidth > desiredWidth || scaledHeight > desiredHeight) throw new Exception("Could not scale down to desired image dimensions"); return scaledHeight; } /** * Decompress the JPEG source image associated with this decompressor * instance and output a decompressed image to the given destination buffer. * * @param dstBuf buffer that will receive the decompressed image. This * buffer should normally be pitch * scaledHeight bytes in size, * where scaledHeight can be determined by calling * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegHeight) * with one of the scaling factors returned from {@link * TJ#getScalingFactors} or by calling {@link #getScaledHeight}. However, * the buffer may also be larger than the dimensions of the JPEG image, in * which case the x, y, and pitch * parameters can be used to specify the region into which the JPEG image * should be decompressed. * * @param x x offset (in pixels) of the region into which the JPEG image * should be decompressed, relative to the start of dstBuf. * * @param y y offset (in pixels) of the region into which the JPEG image * should be decompressed, relative to the start of dstBuf. * * @param desiredWidth desired width (in pixels) of the decompressed image * (or image region.) If the desired image dimensions are different than the * dimensions of the JPEG image being decompressed, then TurboJPEG will use * scaling in the JPEG decompressor to generate the largest possible image * that will fit within the desired dimensions. Setting this to 0 is the * same as setting it to the width of the JPEG image (in other words, the * width will not be considered when determining the scaled image size.) * * @param pitch bytes per line of the destination image. Normally, this * should be set to scaledWidth * TJ.pixelSize(pixelFormat) if * the decompressed image is unpadded, but you can use this to, for instance, * pad each line of the decompressed image to a 4-byte boundary or to * decompress the JPEG image into a region of a larger image. NOTE: * scaledWidth can be determined by calling * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegWidth) * or by calling {@link #getScaledWidth}. Setting this parameter to * 0 is the equivalent of setting it to scaledWidth * * TJ.pixelSize(pixelFormat). * * @param desiredHeight desired height (in pixels) of the decompressed image * (or image region.) If the desired image dimensions are different than the * dimensions of the JPEG image being decompressed, then TurboJPEG will use * scaling in the JPEG decompressor to generate the largest possible image * that will fit within the desired dimensions. Setting this to 0 is the * same as setting it to the height of the JPEG image (in other words, the * height will not be considered when determining the scaled image size.) * * @param pixelFormat pixel format of the decompressed/decoded image (one of * {@link TJ#PF_RGB TJ.PF_*}) * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} */ public void decompress(byte[] dstBuf, int x, int y, int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws Exception { if (jpegBuf == null) throw new Exception(NO_ASSOC_ERROR); if (dstBuf == null || x < 0 || y < 0 || desiredWidth < 0 || pitch < 0 || desiredHeight < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0) throw new Exception("Invalid argument in decompress()"); if (x > 0 || y > 0) decompress(jpegBuf, jpegBufSize, dstBuf, x, y, desiredWidth, pitch, desiredHeight, pixelFormat, flags); else decompress(jpegBuf, jpegBufSize, dstBuf, desiredWidth, pitch, desiredHeight, pixelFormat, flags); } /** * @deprecated Use * {@link #decompress(byte[], int, int, int, int, int, int, int)} instead. */ @Deprecated public void decompress(byte[] dstBuf, int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws Exception { decompress(dstBuf, 0, 0, desiredWidth, pitch, desiredHeight, pixelFormat, flags); } /** * Decompress the JPEG source image associated with this decompressor * instance and return a buffer containing the decompressed image. * * @param desiredWidth see * {@link #decompress(byte[], int, int, int, int, int, int, int)} * for description * * @param pitch see * {@link #decompress(byte[], int, int, int, int, int, int, int)} * for description * * @param desiredHeight see * {@link #decompress(byte[], int, int, int, int, int, int, int)} * for description * * @param pixelFormat pixel format of the decompressed image (one of * {@link TJ#PF_RGB TJ.PF_*}) * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} * * @return a buffer containing the decompressed image */ public byte[] decompress(int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws Exception { if (desiredWidth < 0 || pitch < 0 || desiredHeight < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0) throw new Exception("Invalid argument in decompress()"); int pixelSize = TJ.getPixelSize(pixelFormat); int scaledWidth = getScaledWidth(desiredWidth, desiredHeight); int scaledHeight = getScaledHeight(desiredWidth, desiredHeight); if (pitch == 0) pitch = scaledWidth * pixelSize; byte[] buf = new byte[pitch * scaledHeight]; decompress(buf, desiredWidth, pitch, desiredHeight, pixelFormat, flags); return buf; } /** * Decompress the JPEG source image associated with this decompressor * instance and output a YUV planar image to the given destination buffer. * This method performs JPEG decompression but leaves out the color * conversion step, so a planar YUV image is generated instead of an RGB * image. The padding of the planes in this image is the same as in the * images generated by {@link TJCompressor#encodeYUV(byte[], int)}. *

* NOTE: Technically, the JPEG format uses the YCbCr colorspace, but per the * convention of the digital video community, the TurboJPEG API uses "YUV" to * refer to an image format consisting of Y, Cb, and Cr image planes. * * @param dstBuf buffer that will receive the YUV planar image. Use * {@link TJ#bufSizeYUV} to determine the appropriate size for this buffer * based on the image width, height, and level of chrominance subsampling. * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} */ public void decompressToYUV(byte[] dstBuf, int flags) throws Exception { if (jpegBuf == null) throw new Exception(NO_ASSOC_ERROR); if (dstBuf == null || flags < 0) throw new Exception("Invalid argument in decompressToYUV()"); decompressToYUV(jpegBuf, jpegBufSize, dstBuf, flags); } /** * Decompress the JPEG source image associated with this decompressor * instance and return a buffer containing a YUV planar image. See {@link * #decompressToYUV(byte[], int)} for more detail. * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} * * @return a buffer containing a YUV planar image */ public byte[] decompressToYUV(int flags) throws Exception { if (flags < 0) throw new Exception("Invalid argument in decompressToYUV()"); if (jpegWidth < 1 || jpegHeight < 1 || jpegSubsamp < 0) throw new Exception(NO_ASSOC_ERROR); if (jpegSubsamp >= TJ.NUMSAMP) throw new Exception("JPEG header information is invalid"); byte[] buf = new byte[TJ.bufSizeYUV(jpegWidth, jpegHeight, jpegSubsamp)]; decompressToYUV(buf, flags); return buf; } /** * Decompress the JPEG source image associated with this decompressor * instance and output a decompressed image to the given destination buffer. * * @param dstBuf buffer that will receive the decompressed image. This * buffer should normally be stride * scaledHeight pixels in * size, where scaledHeight can be determined by calling * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegHeight) * with one of the scaling factors returned from {@link * TJ#getScalingFactors} or by calling {@link #getScaledHeight}. However, * the buffer may also be larger than the dimensions of the JPEG image, in * which case the x, y, and stride * parameters can be used to specify the region into which the JPEG image * should be decompressed. * * @param x x offset (in pixels) of the region into which the JPEG image * should be decompressed, relative to the start of dstBuf. * * @param y y offset (in pixels) of the region into which the JPEG image * should be decompressed, relative to the start of dstBuf. * * @param desiredWidth desired width (in pixels) of the decompressed image * (or image region.) If the desired image dimensions are different than the * dimensions of the JPEG image being decompressed, then TurboJPEG will use * scaling in the JPEG decompressor to generate the largest possible image * that will fit within the desired dimensions. Setting this to 0 is the * same as setting it to the width of the JPEG image (in other words, the * width will not be considered when determining the scaled image size.) * * @param stride pixels per line of the destination image. Normally, this * should be set to scaledWidth, but you can use this to, for * instance, decompress the JPEG image into a region of a larger image. * NOTE: scaledWidth can be determined by calling * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegWidth) * or by calling {@link #getScaledWidth}. Setting this parameter to * 0 is the equivalent of setting it to scaledWidth. * * @param desiredHeight desired height (in pixels) of the decompressed image * (or image region.) If the desired image dimensions are different than the * dimensions of the JPEG image being decompressed, then TurboJPEG will use * scaling in the JPEG decompressor to generate the largest possible image * that will fit within the desired dimensions. Setting this to 0 is the * same as setting it to the height of the JPEG image (in other words, the * height will not be considered when determining the scaled image size.) * * @param pixelFormat pixel format of the decompressed image (one of * {@link TJ#PF_RGB TJ.PF_*}) * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} */ public void decompress(int[] dstBuf, int x, int y, int desiredWidth, int stride, int desiredHeight, int pixelFormat, int flags) throws Exception { if (jpegBuf == null) throw new Exception(NO_ASSOC_ERROR); if (dstBuf == null || x < 0 || y < 0 || desiredWidth < 0 || stride < 0 || desiredHeight < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0) throw new Exception("Invalid argument in decompress()"); decompress(jpegBuf, jpegBufSize, dstBuf, x, y, desiredWidth, stride, desiredHeight, pixelFormat, flags); } /** * Decompress the JPEG source image associated with this decompressor * instance and output a decompressed image to the given * BufferedImage instance. * * @param dstImage a BufferedImage instance that will receive * the decompressed image. The width and height of the * BufferedImage instance must match one of the scaled image * sizes that TurboJPEG is capable of generating from the JPEG image. * * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} */ public void decompress(BufferedImage dstImage, int flags) throws Exception { if (dstImage == null || flags < 0) throw new Exception("Invalid argument in decompress()"); int desiredWidth = dstImage.getWidth(); int desiredHeight = dstImage.getHeight(); int scaledWidth = getScaledWidth(desiredWidth, desiredHeight); int scaledHeight = getScaledHeight(desiredWidth, desiredHeight); if (scaledWidth != desiredWidth || scaledHeight != desiredHeight) throw new Exception("BufferedImage dimensions do not match one of the scaled image sizes that TurboJPEG is capable of generating."); int pixelFormat; boolean intPixels = false; if (byteOrder == null) byteOrder = ByteOrder.nativeOrder(); switch(dstImage.getType()) { case BufferedImage.TYPE_3BYTE_BGR: pixelFormat = TJ.PF_BGR; break; case BufferedImage.TYPE_4BYTE_ABGR: case BufferedImage.TYPE_4BYTE_ABGR_PRE: pixelFormat = TJ.PF_XBGR; break; case BufferedImage.TYPE_BYTE_GRAY: pixelFormat = TJ.PF_GRAY; break; case BufferedImage.TYPE_INT_BGR: if (byteOrder == ByteOrder.BIG_ENDIAN) pixelFormat = TJ.PF_XBGR; else pixelFormat = TJ.PF_RGBX; intPixels = true; break; case BufferedImage.TYPE_INT_RGB: if (byteOrder == ByteOrder.BIG_ENDIAN) pixelFormat = TJ.PF_XRGB; else pixelFormat = TJ.PF_BGRX; intPixels = true; break; case BufferedImage.TYPE_INT_ARGB: case BufferedImage.TYPE_INT_ARGB_PRE: if (byteOrder == ByteOrder.BIG_ENDIAN) pixelFormat = TJ.PF_ARGB; else pixelFormat = TJ.PF_BGRA; intPixels = true; break; default: throw new Exception("Unsupported BufferedImage format"); } WritableRaster wr = dstImage.getRaster(); if (intPixels) { SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)dstImage.getSampleModel(); int stride = sm.getScanlineStride(); DataBufferInt db = (DataBufferInt)wr.getDataBuffer(); int[] buf = db.getData(); if (jpegBuf == null) throw new Exception(NO_ASSOC_ERROR); decompress(jpegBuf, jpegBufSize, buf, scaledWidth, stride, scaledHeight, pixelFormat, flags); } else { ComponentSampleModel sm = (ComponentSampleModel)dstImage.getSampleModel(); int pixelSize = sm.getPixelStride(); if (pixelSize != TJ.getPixelSize(pixelFormat)) throw new Exception("Inconsistency between pixel format and pixel size in BufferedImage"); int pitch = sm.getScanlineStride(); DataBufferByte db = (DataBufferByte)wr.getDataBuffer(); byte[] buf = db.getData(); decompress(buf, scaledWidth, pitch, scaledHeight, pixelFormat, flags); } } /** * Decompress the JPEG source image associated with this decompressor * instance and return a BufferedImage instance containing the * decompressed image. * * @param desiredWidth see * {@link #decompress(byte[], int, int, int, int, int, int, int)} for * description * * @param desiredHeight see * {@link #decompress(byte[], int, int, int, int, int, int, int)} for * description * * @param bufferedImageType the image type of the BufferedImage * instance that will be created (for instance, * BufferedImage.TYPE_INT_RGB) * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} * * @return a BufferedImage instance containing the * decompressed image */ public BufferedImage decompress(int desiredWidth, int desiredHeight, int bufferedImageType, int flags) throws Exception { if (desiredWidth < 0 || desiredHeight < 0 || flags < 0) throw new Exception("Invalid argument in decompress()"); int scaledWidth = getScaledWidth(desiredWidth, desiredHeight); int scaledHeight = getScaledHeight(desiredWidth, desiredHeight); BufferedImage img = new BufferedImage(scaledWidth, scaledHeight, bufferedImageType); decompress(img, flags); return img; } /** * Free the native structures associated with this decompressor instance. */ public void close() throws Exception { destroy(); } protected void finalize() throws Throwable { try { close(); } catch(Exception e) { } finally { super.finalize(); } }; private native void init() throws Exception; private native void destroy() throws Exception; private native void decompressHeader(byte[] srcBuf, int size) throws Exception; private native void decompress(byte[] srcBuf, int size, byte[] dstBuf, int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws Exception; // deprecated private native void decompress(byte[] srcBuf, int size, byte[] dstBuf, int x, int y, int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws Exception; private native void decompress(byte[] srcBuf, int size, int[] dstBuf, int desiredWidth, int stride, int desiredHeight, int pixelFormat, int flags) throws Exception; // deprecated private native void decompress(byte[] srcBuf, int size, int[] dstBuf, int x, int y, int desiredWidth, int stride, int desiredHeight, int pixelFormat, int flags) throws Exception; private native void decompressToYUV(byte[] srcBuf, int size, byte[] dstBuf, int flags) throws Exception; static { TJLoader.load(); } protected long handle = 0; protected byte[] jpegBuf = null; protected int jpegBufSize = 0; protected int jpegWidth = 0; protected int jpegHeight = 0; protected int jpegSubsamp = -1; private ByteOrder byteOrder = null; };