/* * Copyright (C)2011-2015 D. R. Commander. All Rights Reserved. * Copyright (C)2015 Viktor Szathmáry. 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.*; import java.io.*; /** * TurboJPEG decompressor */ public class TJDecompressor implements Closeable { private static final String NO_ASSOC_ERROR = "No JPEG image is associated with this instance"; /** * Create a TurboJPEG decompresssor instance. */ public TJDecompressor() throws TJException { init(); } /** * Create a TurboJPEG decompressor instance and associate the JPEG source * image stored in <code>jpegImage</code> with the newly created instance. * * @param jpegImage JPEG image buffer (size of the JPEG image is assumed to * be the length of the array.) This buffer is not modified. */ public TJDecompressor(byte[] jpegImage) throws TJException { init(); setSourceImage(jpegImage, jpegImage.length); } /** * Create a TurboJPEG decompressor instance and associate the JPEG source * image of length <code>imageSize</code> bytes stored in * <code>jpegImage</code> with the newly created instance. * * @param jpegImage JPEG image buffer. This buffer is not modified. * * @param imageSize size of the JPEG image (in bytes) */ public TJDecompressor(byte[] jpegImage, int imageSize) throws TJException { init(); setSourceImage(jpegImage, imageSize); } /** * Create a TurboJPEG decompressor instance and associate the YUV planar * source image stored in <code>yuvImage</code> with the newly created * instance. * * @param yuvImage {@link YUVImage} instance containing a YUV planar * image to be decoded. This image is not modified. */ public TJDecompressor(YUVImage yuvImage) throws TJException { init(); setSourceImage(yuvImage); } /** * Associate the JPEG image of length <code>imageSize</code> bytes stored in * <code>jpegImage</code> with this decompressor instance. This image will * be used as the source image for subsequent decompress operations. * * @param jpegImage JPEG image buffer. This buffer is not modified. * * @param imageSize size of the JPEG image (in bytes) */ public void setSourceImage(byte[] jpegImage, int imageSize) throws TJException { if (jpegImage == null || imageSize < 1) throw new IllegalArgumentException("Invalid argument in setSourceImage()"); jpegBuf = jpegImage; jpegBufSize = imageSize; decompressHeader(jpegBuf, jpegBufSize); yuvImage = null; } /** * @deprecated Use {@link #setSourceImage(byte[], int)} instead. */ @Deprecated public void setJPEGImage(byte[] jpegImage, int imageSize) throws TJException { setSourceImage(jpegImage, imageSize); } /** * Associate the specified YUV planar source image with this decompressor * instance. Subsequent decompress operations will decode this image into an * RGB or grayscale destination image. * * @param srcImage {@link YUVImage} instance containing a YUV planar image to * be decoded. This image is not modified. */ public void setSourceImage(YUVImage srcImage) { if (srcImage == null) throw new IllegalArgumentException("Invalid argument in setSourceImage()"); yuvImage = srcImage; jpegBuf = null; jpegBufSize = 0; } /** * Returns the width of the source image (JPEG or YUV) associated with this * decompressor instance. * * @return the width of the source image (JPEG or YUV) associated with this * decompressor instance. */ public int getWidth() { if (yuvImage != null) return yuvImage.getWidth(); if (jpegWidth < 1) throw new IllegalStateException(NO_ASSOC_ERROR); return jpegWidth; } /** * Returns the height of the source image (JPEG or YUV) associated with this * decompressor instance. * * @return the height of the source image (JPEG or YUV) associated with this * decompressor instance. */ public int getHeight() { if (yuvImage != null) return yuvImage.getHeight(); if (jpegHeight < 1) throw new IllegalStateException(NO_ASSOC_ERROR); return jpegHeight; } /** * Returns the level of chrominance subsampling used in the source image * (JPEG or YUV) associated with this decompressor instance. See * {@link TJ#SAMP_444 TJ.SAMP_*}. * * @return the level of chrominance subsampling used in the source image * (JPEG or YUV) associated with this decompressor instance. */ public int getSubsamp() { if (yuvImage != null) return yuvImage.getSubsamp(); if (jpegSubsamp < 0) throw new IllegalStateException(NO_ASSOC_ERROR); if (jpegSubsamp >= TJ.NUMSAMP) throw new IllegalStateException("JPEG header information is invalid"); return jpegSubsamp; } /** * Returns the colorspace used in the source image (JPEG or YUV) associated * with this decompressor instance. See {@link TJ#CS_RGB TJ.CS_*}. If the * source image is YUV, then this always returns {@link TJ#CS_YCbCr}. * * @return the colorspace used in the source image (JPEG or YUV) associated * with this decompressor instance. */ public int getColorspace() { if (yuvImage != null) return TJ.CS_YCbCr; if (jpegColorspace < 0) throw new IllegalStateException(NO_ASSOC_ERROR); if (jpegColorspace >= TJ.NUMCS) throw new IllegalStateException("JPEG header information is invalid"); return jpegColorspace; } /** * Returns the JPEG image buffer associated with this decompressor instance. * * @return the JPEG image buffer associated with this decompressor instance. */ public byte[] getJPEGBuf() { if (jpegBuf == null) throw new IllegalStateException(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() { if (jpegBufSize < 1) throw new IllegalStateException(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) { if (jpegWidth < 1 || jpegHeight < 1) throw new IllegalStateException(NO_ASSOC_ERROR); if (desiredWidth < 0 || desiredHeight < 0) throw new IllegalArgumentException("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 IllegalArgumentException("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) { if (jpegWidth < 1 || jpegHeight < 1) throw new IllegalStateException(NO_ASSOC_ERROR); if (desiredWidth < 0 || desiredHeight < 0) throw new IllegalArgumentException("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 IllegalArgumentException("Could not scale down to desired image dimensions"); return scaledHeight; } /** * Decompress the JPEG source image or decode the YUV source image associated * with this decompressor instance and output a grayscale, RGB, or CMYK image * to the given destination buffer. * * @param dstBuf buffer that will receive the decompressed/decoded image. * If the source image is a JPEG image, then this buffer should normally be * <code>pitch * scaledHeight</code> bytes in size, where * <code>scaledHeight</code> can be determined by calling <code> * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegHeight) * </code> with one of the scaling factors returned from {@link * TJ#getScalingFactors} or by calling {@link #getScaledHeight}. If the * source image is a YUV image, then this buffer should normally be * <code>pitch * height</code> bytes in size, where <code>height</code> is * the height of the YUV image. However, the buffer may also be larger than * the dimensions of the source image, in which case the <code>x</code>, * <code>y</code>, and <code>pitch</code> parameters can be used to specify * the region into which the source image should be decompressed/decoded. * * @param x x offset (in pixels) of the region in the destination image into * which the source image should be decompressed/decoded * * @param y y offset (in pixels) of the region in the destination image into * which the source image should be decompressed/decoded * * @param desiredWidth If the source image is a JPEG image, then this * specifies the desired width (in pixels) of the decompressed image (or * image region.) If the desired destination image dimensions are different * than the source image dimensions, 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.) This parameter is * ignored if the source image is a YUV image. * * @param pitch bytes per line of the destination image. Normally, this * should be set to <code>scaledWidth * TJ.pixelSize(pixelFormat)</code> if * the destination image is unpadded, but you can use this to, for instance, * pad each line of the destination image to a 4-byte boundary or to * decompress/decode the source image into a region of a larger image. NOTE: * if the source image is a JPEG image, then <code>scaledWidth</code> can be * determined by calling <code> * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegWidth) * </code> or by calling {@link #getScaledWidth}. If the source image is a * YUV image, then <code>scaledWidth</code> is the width of the YUV image. * Setting this parameter to 0 is the equivalent of setting it to * <code>scaledWidth * TJ.pixelSize(pixelFormat)</code>. * * @param desiredHeight If the source image is a JPEG image, then this * specifies the desired height (in pixels) of the decompressed image (or * image region.) If the desired destination image dimensions are different * than the source image dimensions, 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.) This parameter is * ignored if the source image is a YUV image. * * @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 TJException { if (jpegBuf == null && yuvImage == null) throw new IllegalStateException(NO_ASSOC_ERROR); if (dstBuf == null || x < 0 || y < 0 || pitch < 0 || (yuvImage != null && (desiredWidth < 0 || desiredHeight < 0)) || pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0) throw new IllegalArgumentException("Invalid argument in decompress()"); if (yuvImage != null) decodeYUV(yuvImage.getPlanes(), yuvImage.getOffsets(), yuvImage.getStrides(), yuvImage.getSubsamp(), dstBuf, x, y, yuvImage.getWidth(), pitch, yuvImage.getHeight(), pixelFormat, flags); else { 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 TJException { 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 TJException { if (pitch < 0 || (yuvImage == null && (desiredWidth < 0 || desiredHeight < 0)) || pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0) throw new IllegalArgumentException("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 into a YUV planar image and store it in the given * <code>YUVImage</code> instance. This method performs JPEG decompression * but leaves out the color conversion step, so a planar YUV image is * generated instead of an RGB or grayscale image. This method cannot be * used to decompress JPEG source images with the CMYK or YCCK colorspace. * * @param dstImage {@link YUVImage} instance that will receive the YUV planar * image. The level of subsampling specified in this <code>YUVImage</code> * instance must match that of the JPEG image, and the width and height * specified in the <code>YUVImage</code> instance must match one of the * scaled image sizes that TurboJPEG is capable of generating from the JPEG * source image. * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} */ public void decompressToYUV(YUVImage dstImage, int flags) throws TJException { if (jpegBuf == null) throw new IllegalStateException(NO_ASSOC_ERROR); if (dstImage == null || flags < 0) throw new IllegalArgumentException("Invalid argument in decompressToYUV()"); int scaledWidth = getScaledWidth(dstImage.getWidth(), dstImage.getHeight()); int scaledHeight = getScaledHeight(dstImage.getWidth(), dstImage.getHeight()); if (scaledWidth != dstImage.getWidth() || scaledHeight != dstImage.getHeight()) throw new IllegalArgumentException("YUVImage dimensions do not match one of the scaled image sizes that TurboJPEG is capable of generating."); if (jpegSubsamp != dstImage.getSubsamp()) throw new IllegalArgumentException("YUVImage subsampling level does not match that of the JPEG image"); decompressToYUV(jpegBuf, jpegBufSize, dstImage.getPlanes(), dstImage.getOffsets(), dstImage.getWidth(), dstImage.getStrides(), dstImage.getHeight(), flags); } /** * @deprecated Use {@link #decompressToYUV(YUVImage, int)} instead. */ @Deprecated public void decompressToYUV(byte[] dstBuf, int flags) throws TJException { YUVImage dstImage = new YUVImage(dstBuf, jpegWidth, 4, jpegHeight, jpegSubsamp); decompressToYUV(dstImage, flags); } /** * Decompress the JPEG source image associated with this decompressor * instance into a set of Y, U (Cb), and V (Cr) image planes and return a * <code>YUVImage</code> instance containing the decompressed image planes. * This method performs JPEG decompression but leaves out the color * conversion step, so a planar YUV image is generated instead of an RGB or * grayscale image. This method cannot be used to decompress JPEG source * images with the CMYK or YCCK colorspace. * * @param desiredWidth desired width (in pixels) of the YUV image. 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 strides an array of integers, each specifying the number of bytes * per line in the corresponding plane of the output image. Setting the * stride for any plane to 0 is the same as setting it to the scaled * component width of the plane. If <tt>strides</tt> is NULL, then the * strides for all planes will be set to their respective scaled component * widths. You can adjust the strides in order to add an arbitrary amount of * line padding to each plane. * * @param desiredHeight desired height (in pixels) of the YUV image. 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 flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} * * @return a YUV planar image. */ public YUVImage decompressToYUV(int desiredWidth, int[] strides, int desiredHeight, int flags) throws TJException { if (flags < 0) throw new IllegalArgumentException("Invalid argument in decompressToYUV()"); if (jpegWidth < 1 || jpegHeight < 1 || jpegSubsamp < 0) throw new IllegalStateException(NO_ASSOC_ERROR); if (jpegSubsamp >= TJ.NUMSAMP) throw new IllegalStateException("JPEG header information is invalid"); if (yuvImage != null) throw new IllegalStateException("Source image is the wrong type"); int scaledWidth = getScaledWidth(desiredWidth, desiredHeight); int scaledHeight = getScaledHeight(desiredWidth, desiredHeight); YUVImage yuvImage = new YUVImage(scaledWidth, null, scaledHeight, jpegSubsamp); decompressToYUV(yuvImage, flags); return yuvImage; } /** * Decompress the JPEG source image associated with this decompressor * instance into a unified YUV planar image buffer and return a * <code>YUVImage</code> instance containing the decompressed image. This * method performs JPEG decompression but leaves out the color conversion * step, so a planar YUV image is generated instead of an RGB or grayscale * image. This method cannot be used to decompress JPEG source images with * the CMYK or YCCK colorspace. * * @param desiredWidth desired width (in pixels) of the YUV image. 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 pad the width of each line in each plane of the YUV image will be * padded to the nearest multiple of this number of bytes (must be a power of * 2.) * * @param desiredHeight desired height (in pixels) of the YUV image. 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 flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} * * @return a YUV planar image. */ public YUVImage decompressToYUV(int desiredWidth, int pad, int desiredHeight, int flags) throws TJException { if (flags < 0) throw new IllegalArgumentException("Invalid argument in decompressToYUV()"); if (jpegWidth < 1 || jpegHeight < 1 || jpegSubsamp < 0) throw new IllegalStateException(NO_ASSOC_ERROR); if (jpegSubsamp >= TJ.NUMSAMP) throw new IllegalStateException("JPEG header information is invalid"); if (yuvImage != null) throw new IllegalStateException("Source image is the wrong type"); int scaledWidth = getScaledWidth(desiredWidth, desiredHeight); int scaledHeight = getScaledHeight(desiredWidth, desiredHeight); YUVImage yuvImage = new YUVImage(scaledWidth, pad, scaledHeight, jpegSubsamp); decompressToYUV(yuvImage, flags); return yuvImage; } /** * @deprecated Use {@link #decompressToYUV(int, int, int, int)} instead. */ @Deprecated public byte[] decompressToYUV(int flags) throws TJException { YUVImage dstImage = new YUVImage(jpegWidth, 4, jpegHeight, jpegSubsamp); decompressToYUV(dstImage, flags); return dstImage.getBuf(); } /** * Decompress the JPEG source image or decode the YUV source image associated * with this decompressor instance and output a grayscale, RGB, or CMYK image * to the given destination buffer. * * @param dstBuf buffer that will receive the decompressed/decoded image. * If the source image is a JPEG image, then this buffer should normally be * <code>stride * scaledHeight</code> pixels in size, where * <code>scaledHeight</code> can be determined by calling <code> * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegHeight) * </code> with one of the scaling factors returned from {@link * TJ#getScalingFactors} or by calling {@link #getScaledHeight}. If the * source image is a YUV image, then this buffer should normally be * <code>stride * height</code> pixels in size, where <code>height</code> is * the height of the YUV image. However, the buffer may also be larger than * the dimensions of the JPEG image, in which case the <code>x</code>, * <code>y</code>, and <code>stride</code> parameters can be used to specify * the region into which the source image should be decompressed. * * @param x x offset (in pixels) of the region in the destination image into * which the source image should be decompressed/decoded * * @param y y offset (in pixels) of the region in the destination image into * which the source image should be decompressed/decoded * * @param desiredWidth If the source image is a JPEG image, then this * specifies the desired width (in pixels) of the decompressed image (or * image region.) If the desired destination image dimensions are different * than the source image dimensions, 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.) This parameter is * ignored if the source image is a YUV image. * * @param stride pixels per line of the destination image. Normally, this * should be set to <code>scaledWidth</code>, but you can use this to, for * instance, decompress the JPEG image into a region of a larger image. * NOTE: if the source image is a JPEG image, then <code>scaledWidth</code> * can be determined by calling <code> * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegWidth) * </code> or by calling {@link #getScaledWidth}. If the source image is a * YUV image, then <code>scaledWidth</code> is the width of the YUV image. * Setting this parameter to 0 is the equivalent of setting it to * <code>scaledWidth</code>. * * @param desiredHeight If the source image is a JPEG image, then this * specifies the desired height (in pixels) of the decompressed image (or * image region.) If the desired destination image dimensions are different * than the source image dimensions, 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.) This parameter is * ignored if the source image is a YUV image. * * @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 TJException { if (jpegBuf == null && yuvImage == null) throw new IllegalStateException(NO_ASSOC_ERROR); if (dstBuf == null || x < 0 || y < 0 || stride < 0 || (yuvImage != null && (desiredWidth < 0 || desiredHeight < 0)) || pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0) throw new IllegalArgumentException("Invalid argument in decompress()"); if (yuvImage != null) decodeYUV(yuvImage.getPlanes(), yuvImage.getOffsets(), yuvImage.getStrides(), yuvImage.getSubsamp(), dstBuf, x, y, yuvImage.getWidth(), stride, yuvImage.getHeight(), pixelFormat, flags); else decompress(jpegBuf, jpegBufSize, dstBuf, x, y, desiredWidth, stride, desiredHeight, pixelFormat, flags); } /** * Decompress the JPEG source image or decode the YUV source image associated * with this decompressor instance and output a decompressed/decoded image to * the given <code>BufferedImage</code> instance. * * @param dstImage a <code>BufferedImage</code> instance that will receive * the decompressed/decoded image. If the source image is a JPEG image, then * the width and height of the <code>BufferedImage</code> instance must match * one of the scaled image sizes that TurboJPEG is capable of generating from * the JPEG image. If the source image is a YUV image, then the width and * height of the <code>BufferedImage</code> instance must match the width and * height of the YUV 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 TJException { if (dstImage == null || flags < 0) throw new IllegalArgumentException("Invalid argument in decompress()"); int desiredWidth = dstImage.getWidth(); int desiredHeight = dstImage.getHeight(); int scaledWidth, scaledHeight; if (yuvImage != null) { if (desiredWidth != yuvImage.getWidth() || desiredHeight != yuvImage.getHeight()) throw new IllegalArgumentException("BufferedImage dimensions do not match the dimensions of the source image."); scaledWidth = yuvImage.getWidth(); scaledHeight = yuvImage.getHeight(); } else { scaledWidth = getScaledWidth(desiredWidth, desiredHeight); scaledHeight = getScaledHeight(desiredWidth, desiredHeight); if (scaledWidth != desiredWidth || scaledHeight != desiredHeight) throw new IllegalArgumentException("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 IllegalArgumentException("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 (yuvImage != null) decodeYUV(yuvImage.getPlanes(), yuvImage.getOffsets(), yuvImage.getStrides(), yuvImage.getSubsamp(), buf, 0, 0, yuvImage.getWidth(), stride, yuvImage.getHeight(), pixelFormat, flags); else { if (jpegBuf == null) throw new IllegalStateException(NO_ASSOC_ERROR); decompress(jpegBuf, jpegBufSize, buf, 0, 0, scaledWidth, stride, scaledHeight, pixelFormat, flags); } } else { ComponentSampleModel sm = (ComponentSampleModel)dstImage.getSampleModel(); int pixelSize = sm.getPixelStride(); if (pixelSize != TJ.getPixelSize(pixelFormat)) throw new IllegalArgumentException("Inconsistency between pixel format and pixel size in BufferedImage"); int pitch = sm.getScanlineStride(); DataBufferByte db = (DataBufferByte)wr.getDataBuffer(); byte[] buf = db.getData(); decompress(buf, 0, 0, scaledWidth, pitch, scaledHeight, pixelFormat, flags); } } /** * Decompress the JPEG source image or decode the YUV source image associated * with this decompressor instance and return a <code>BufferedImage</code> * instance containing the decompressed/decoded 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 <code>BufferedImage</code> * instance that will be created (for instance, * <code>BufferedImage.TYPE_INT_RGB</code>) * * @param flags the bitwise OR of one or more of * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*} * * @return a <code>BufferedImage</code> instance containing the * decompressed/decoded image. */ public BufferedImage decompress(int desiredWidth, int desiredHeight, int bufferedImageType, int flags) throws TJException { if ((yuvImage == null && (desiredWidth < 0 || desiredHeight < 0)) || flags < 0) throw new IllegalArgumentException("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. */ @Override public void close() throws TJException { if (handle != 0) destroy(); } @Override protected void finalize() throws Throwable { try { close(); } catch(TJException e) { } finally { super.finalize(); } }; private native void init() throws TJException; private native void destroy() throws TJException; private native void decompressHeader(byte[] srcBuf, int size) throws TJException; @Deprecated private native void decompress(byte[] srcBuf, int size, byte[] dstBuf, int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws TJException; 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 TJException; @Deprecated private native void decompress(byte[] srcBuf, int size, int[] dstBuf, int desiredWidth, int stride, int desiredHeight, int pixelFormat, int flags) throws TJException; 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 TJException; @Deprecated private native void decompressToYUV(byte[] srcBuf, int size, byte[] dstBuf, int flags) throws TJException; private native void decompressToYUV(byte[] srcBuf, int size, byte[][] dstPlanes, int[] dstOffsets, int desiredWidth, int[] dstStrides, int desiredheight, int flags) throws TJException; private native void decodeYUV(byte[][] srcPlanes, int[] srcOffsets, int[] srcStrides, int subsamp, byte[] dstBuf, int x, int y, int width, int pitch, int height, int pixelFormat, int flags) throws TJException; private native void decodeYUV(byte[][] srcPlanes, int[] srcOffsets, int[] srcStrides, int subsamp, int[] dstBuf, int x, int y, int width, int stride, int height, int pixelFormat, int flags) throws TJException; static { TJLoader.load(); } protected long handle = 0; protected byte[] jpegBuf = null; protected int jpegBufSize = 0; protected YUVImage yuvImage = null; protected int jpegWidth = 0; protected int jpegHeight = 0; protected int jpegSubsamp = -1; protected int jpegColorspace = -1; private ByteOrder byteOrder = null; }