java/org/libjpegturbo/turbojpeg/TJDecompressor.java (539 lines of code) (raw):

/* * Copyright (C)2011-2015, 2018, 2022-2024 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.Rectangle; 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 or "abbreviated table specification" (AKA "tables-only") datastream * stored in <code>jpegImage</code> with the newly created instance. Refer * to {@link #setSourceImage(byte[], int)} for more details. * * @param jpegImage buffer containing a JPEG source image or tables-only * datastream. (The size of the JPEG image or datastream 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 or "abbreviated table specification" (AKA "tables-only") datastream * of length <code>imageSize</code> bytes stored in <code>jpegImage</code> * with the newly created instance. Refer to * {@link #setSourceImage(byte[], int)} for more details. * * @param jpegImage buffer containing a JPEG source image or tables-only * datastream. This buffer is not modified. * * @param imageSize size of the JPEG source image or tables-only datastream * (in bytes) */ public TJDecompressor(byte[] jpegImage, int imageSize) throws TJException { init(); setSourceImage(jpegImage, imageSize); } /** * Create a TurboJPEG decompressor instance and associate the * 8-bit-per-sample planar YUV source image stored in <code>yuvImage</code> * with the newly created instance. Refer to * {@link #setSourceImage(YUVImage)} for more details. * * @param yuvImage {@link YUVImage} instance containing a planar YUV source * image to be decoded. This image is not modified. */ @SuppressWarnings("checkstyle:HiddenField") public TJDecompressor(YUVImage yuvImage) throws TJException { init(); setSourceImage(yuvImage); } /** * Associate the JPEG image or "abbreviated table specification" (AKA * "tables-only") datastream of length <code>imageSize</code> bytes stored in * <code>jpegImage</code> with this decompressor instance. If * <code>jpegImage</code> contains a JPEG image, then this image will be used * as the source image for subsequent decompression operations. Passing a * tables-only datastream to this method primes the decompressor with * quantization and Huffman tables that can be used when decompressing * subsequent "abbreviated image" datastreams. This is useful, for instance, * when decompressing video streams in which all frames share the same * quantization and Huffman tables. If a JPEG image is passed to this * method, then the {@link TJ#PARAM_STOPONWARNING parameters} that describe * the JPEG image will be set when the method returns. * * @param jpegImage buffer containing a JPEG source image or tables-only * datastream. This buffer is not modified. * * @param imageSize size of the JPEG source image or tables-only datastream * (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; } /** * Associate the specified planar YUV source image with this decompressor * instance. Subsequent decompression operations will decode this image into * a packed-pixel RGB or grayscale destination image. This method sets * {@link TJ#PARAM_SUBSAMP} to the chrominance subsampling level of the * source image. * * @param srcImage {@link YUVImage} instance containing a planar YUV source * 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; set(TJ.PARAM_SUBSAMP, srcImage.getSubsamp()); 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(); return getJPEGWidth(); } private int getJPEGWidth() { int jpegWidth = get(TJ.PARAM_JPEGWIDTH); 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(); return getJPEGHeight(); } private int getJPEGHeight() { int jpegHeight = get(TJ.PARAM_JPEGHEIGHT); if (jpegHeight < 1) throw new IllegalStateException(NO_ASSOC_ERROR); return jpegHeight; } /** * Set the value of a decompression parameter. * * @param param one of {@link TJ#PARAM_STOPONWARNING TJ.PARAM_*} * * @param value value of the decompression parameter (refer to * {@link TJ#PARAM_STOPONWARNING parameter documentation}) */ public native void set(int param, int value); /** * Get the value of a decompression parameter. * * @param param one of {@link TJ#PARAM_STOPONWARNING TJ.PARAM_*} * * @return the value of the specified decompression parameter, or -1 if the * value is unknown. */ public native int get(int param); /** * Set the scaling factor for subsequent lossy decompression operations. * * @param scalingFactor {@link TJScalingFactor} instance that specifies a * fractional scaling factor that the decompressor supports (see * {@link TJ#getScalingFactors}), or {@link TJ#UNSCALED} for no scaling. * Decompression scaling is a function of the IDCT algorithm, so scaling * factors are generally limited to multiples of 1/8. If the entire JPEG * image will be decompressed, then the width and height of the scaled * destination image can be determined by calling * <code>scalingFactor.</code>{@link TJScalingFactor#getScaled getScaled()} * with the JPEG image width and height (see {@link #getWidth} and * {@link #getHeight}.) When decompressing into a planar YUV image, an * intermediate buffer copy will be performed if the width or height of the * scaled destination image is not an even multiple of the iMCU size (see * {@link TJ#getMCUWidth TJ.getMCUWidth()} and {@link TJ#getMCUHeight * TJ.getMCUHeight()}.) Note that decompression scaling is not available * (and the specified scaling factor is ignored) when decompressing lossless * JPEG images (see {@link TJ#PARAM_LOSSLESS}), since the IDCT algorithm is * not used with those images. Note also that {@link TJ#PARAM_FASTDCT} is * ignored when decompression scaling is enabled. */ @SuppressWarnings("checkstyle:HiddenField") public void setScalingFactor(TJScalingFactor scalingFactor) { if (scalingFactor == null) throw new IllegalArgumentException("Invalid argument in setScalingFactor()"); TJScalingFactor[] sf = TJ.getScalingFactors(); int i; for (i = 0; i < sf.length; i++) { if (scalingFactor.getNum() == sf[i].getNum() && scalingFactor.getDenom() == sf[i].getDenom()) break; } if (i >= sf.length) throw new IllegalArgumentException("Unsupported scaling factor"); this.scalingFactor = scalingFactor; } /** * Set the cropping region for partially decompressing a lossy JPEG image * into a packed-pixel image. * * @param croppingRegion <code>java.awt.Rectangle</code> instance that * specifies a subregion of the JPEG image to decompress, or * {@link TJ#UNCROPPED} for no cropping. The left boundary of the cropping * region must be evenly divisible by the scaled iMCU width, which can be * determined by calling {@link TJScalingFactor#getScaled * TJScalingFactor.getScaled()} with the specified scaling factor (see * {@link #setScalingFactor setScalingFactor()}) and the iMCU width (see * {@link TJ#getMCUWidth TJ.getMCUWidth()}) for the level of chrominance * subsampling in the JPEG image (see {@link TJ#PARAM_SUBSAMP}.) The * cropping region should be specified relative to the scaled image * dimensions. Unless <code>croppingRegion</code> is {@link TJ#UNCROPPED}, * the JPEG header must be read (see {@link #setSourceImage(byte[], int)}) * prior to calling this method. */ @SuppressWarnings("checkstyle:HiddenField") public void setCroppingRegion(Rectangle croppingRegion) throws TJException { this.croppingRegion = croppingRegion; setCroppingRegion(); } /** * @deprecated Use <code>{@link #get get}({@link TJ#PARAM_SUBSAMP})</code> * instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public int getSubsamp() { int subsamp = get(TJ.PARAM_SUBSAMP); if (subsamp == TJ.SAMP_UNKNOWN) throw new IllegalStateException(NO_ASSOC_ERROR); if (subsamp >= TJ.NUMSAMP) throw new IllegalStateException("JPEG header information is invalid"); return subsamp; } /** * @deprecated Use <code>{@link #get get}({@link TJ#PARAM_COLORSPACE})</code> * instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public int getColorspace() { if (yuvImage != null) return TJ.CS_YCbCr; int jpegColorspace = get(TJ.PARAM_COLORSPACE); 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 buffer associated with this decompressor instance. * * @return the JPEG 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; } /** * @deprecated Use {@link #setScalingFactor setScalingFactor()} and * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public int getScaledWidth(int desiredWidth, int desiredHeight) { TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); return sf.getScaled(getJPEGWidth()); } /** * @deprecated Use {@link #setScalingFactor setScalingFactor()} and * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public int getScaledHeight(int desiredWidth, int desiredHeight) { TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); return sf.getScaled(getJPEGHeight()); } private TJScalingFactor getScalingFactor(int desiredWidth, int desiredHeight) { int jpegWidth = getJPEGWidth(); int jpegHeight = getJPEGHeight(); if (desiredWidth < 0 || desiredHeight < 0) throw new IllegalArgumentException("Invalid argument"); TJScalingFactor[] sf = TJ.getScalingFactors(); if (desiredWidth == 0) desiredWidth = jpegWidth; if (desiredHeight == 0) desiredHeight = jpegHeight; int i; for (i = 0; i < sf.length; i++) { if (sf[i].getScaled(jpegWidth) <= desiredWidth && sf[i].getScaled(jpegHeight) <= desiredHeight) break; } if (i >= sf.length) throw new IllegalArgumentException("Could not scale down to desired image dimensions"); return sf[i]; } /** * Decompress the 8-bit-per-sample JPEG source image or decode the planar YUV * source image associated with this decompressor instance and output an * 8-bit-per-sample packed-pixel grayscale, RGB, or CMYK image to the given * destination buffer. * * <p>NOTE: The destination image is fully recoverable if this method throws * a non-fatal {@link TJException} (unless {@link TJ#PARAM_STOPONWARNING} is * set.) * * @param dstBuf buffer that will receive the packed-pixel * decompressed/decoded image. This buffer should normally be * <code>pitch * destinationHeight</code> bytes in size. However, the buffer * may also be larger, 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. NOTE: If the source * image is a lossy JPEG image, then <code>destinationHeight</code> is either * the scaled JPEG height (see {@link #setScalingFactor setScalingFactor()}, * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()}, and * {@link #getHeight}) or the height of the cropping region (see * {@link #setCroppingRegion setCroppingRegion()}.) If the source image is a * YUV image or a lossless JPEG image, then <code>destinationHeight</code> is * the height of the source image. * * @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 pitch bytes per row in the destination image. Normally this should * be set to <code>destinationWidth * * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>, if the * destination image will be unpadded. (Setting this parameter to 0 is the * equivalent of setting it to <code>destinationWidth * * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>.) However, * you can also use this parameter to specify the row alignment/padding of * the destination image, to skip rows, or to decompress/decode into a * specific region of a larger image. NOTE: If the source image is a lossy * JPEG image, then <code>destinationWidth</code> is either the scaled JPEG * width (see {@link #setScalingFactor setScalingFactor()}, * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()}, and * {@link #getWidth}) or the width of the cropping region (see * {@link #setCroppingRegion setCroppingRegion()}.) If the source image is a * YUV image or a lossless JPEG image, then <code>destinationWidth</code> is * the width of the source image. * * @param pixelFormat pixel format of the decompressed/decoded image (one of * {@link TJ#PF_RGB TJ.PF_*}) */ public void decompress8(byte[] dstBuf, int x, int y, int pitch, int pixelFormat) throws TJException { if (jpegBuf == null && yuvImage == null) throw new IllegalStateException("No source image is associated with this instance"); if (dstBuf == null || x < 0 || y < 0 || pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF) throw new IllegalArgumentException("Invalid argument in decompress8()"); if (yuvImage != null) { checkSubsampling(); decodeYUV8(yuvImage.getPlanes(), yuvImage.getOffsets(), yuvImage.getStrides(), dstBuf, x, y, yuvImage.getWidth(), pitch, yuvImage.getHeight(), pixelFormat); } else decompress8(jpegBuf, jpegBufSize, dstBuf, x, y, pitch, pixelFormat); } /** * @deprecated Use {@link #set set()}, * {@link #setScalingFactor setScalingFactor()}, and * {@link #decompress8(byte[], int, int, int, int)} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public void decompress(byte[] dstBuf, int x, int y, int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws TJException { if ((yuvImage != null && (desiredWidth < 0 || desiredHeight < 0)) || flags < 0) throw new IllegalArgumentException("Invalid argument in decompress()"); if (yuvImage == null) { TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); setScalingFactor(sf); } processFlags(flags); decompress8(dstBuf, x, y, pitch, pixelFormat); } /** * Decompress the 8-bit-per-sample JPEG source image or decode the planar YUV * source image associated with this decompressor instance and return a * buffer containing an 8-bit-per-sample packed-pixel decompressed image. * * @param pitch see * {@link #decompress8(byte[], int, int, int, int)} for description * * @param pixelFormat pixel format of the decompressed image (one of * {@link TJ#PF_RGB TJ.PF_*}) * * @return a buffer containing an 8-bit-per-sample packed-pixel decompressed * image. */ public byte[] decompress8(int pitch, int pixelFormat) throws TJException { if (pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF) throw new IllegalArgumentException("Invalid argument in decompress8()"); int pixelSize = TJ.getPixelSize(pixelFormat); int scaledWidth = scalingFactor.getScaled(getJPEGWidth()); int scaledHeight = scalingFactor.getScaled(getJPEGHeight()); if (pitch == 0) pitch = scaledWidth * pixelSize; byte[] buf = new byte[pitch * scaledHeight]; decompress8(buf, 0, 0, pitch, pixelFormat); return buf; } /** * @deprecated Use {@link #set set()}, * {@link #setScalingFactor setScalingFactor()}, and * {@link #decompress8(int, int)} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public byte[] decompress(int desiredWidth, int pitch, int desiredHeight, int pixelFormat, int flags) throws TJException { if ((yuvImage == null && (desiredWidth < 0 || desiredHeight < 0)) || flags < 0) throw new IllegalArgumentException("Invalid argument in decompress()"); if (yuvImage == null) { TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); setScalingFactor(sf); } processFlags(flags); return decompress8(pitch, pixelFormat); } /** * Decompress the 12-bit-per-sample JPEG source image associated with this * decompressor instance and output a 12-bit-per-sample packed-pixel * grayscale, RGB, or CMYK image to the given destination buffer. * * <p>NOTE: The destination image is fully recoverable if this method throws * a non-fatal {@link TJException} (unless {@link TJ#PARAM_STOPONWARNING} is * set.) * * @param dstBuf buffer that will receive the packed-pixel * decompressed image. This buffer should normally be * <code>pitch * destinationHeight</code> samples in size. However, the * buffer may also be larger, 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. NOTE: If * the source image is a lossy JPEG image, then * <code>destinationHeight</code> is either the scaled JPEG height (see * {@link #setScalingFactor setScalingFactor()}, * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()}, and * {@link #getHeight}) or the height of the cropping region (see * {@link #setCroppingRegion setCroppingRegion()}.) If the source image is a * lossless JPEG image, then <code>destinationHeight</code> is the height of * the source image. * * @param x x offset (in pixels) of the region in the destination image into * which the source image should be decompressed * * @param y y offset (in pixels) of the region in the destination image into * which the source image should be decompressed * * @param pitch samples per row in the destination image. Normally this * should be set to <code>destinationWidth * * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>, if the * destination image will be unpadded. (Setting this parameter to 0 is the * equivalent of setting it to <code>destinationWidth * * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>.) However, * you can also use this parameter to specify the row alignment/padding of * the destination image, to skip rows, or to decompress into a specific * region of a larger image. NOTE: If the source image is a lossy JPEG * image, then <code>destinationWidth</code> is either the scaled JPEG width * (see {@link #setScalingFactor setScalingFactor()}, * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()}, and * {@link #getWidth}) or the width of the cropping region (see * {@link #setCroppingRegion setCroppingRegion()}.) If the source image is a * YUV image or a lossless JPEG image, then <code>destinationWidth</code> is * the width of the source image. * * @param pixelFormat pixel format of the decompressed image (one of * {@link TJ#PF_RGB TJ.PF_*}) */ public void decompress12(short[] dstBuf, int x, int y, int pitch, int pixelFormat) throws TJException { if (jpegBuf == null) throw new IllegalStateException(NO_ASSOC_ERROR); if (dstBuf == null || x < 0 || y < 0 || pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF) throw new IllegalArgumentException("Invalid argument in decompress12()"); decompress12(jpegBuf, jpegBufSize, dstBuf, x, y, pitch, pixelFormat); } /** * Decompress the 12-bit-per-sample JPEG source image associated with this * decompressor instance and return a buffer containing a 12-bit-per-sample * packed-pixel decompressed image. * * @param pitch see * {@link #decompress12(short[], int, int, int, int)} for description * * @param pixelFormat pixel format of the decompressed image (one of * {@link TJ#PF_RGB TJ.PF_*}) * * @return a buffer containing a 12-bit-per-sample packed-pixel decompressed * image. */ public short[] decompress12(int pitch, int pixelFormat) throws TJException { if (pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF) throw new IllegalArgumentException("Invalid argument in decompress12()"); int pixelSize = TJ.getPixelSize(pixelFormat); int scaledWidth = scalingFactor.getScaled(getJPEGWidth()); int scaledHeight = scalingFactor.getScaled(getJPEGHeight()); if (pitch == 0) pitch = scaledWidth * pixelSize; short[] buf = new short[pitch * scaledHeight]; decompress12(buf, 0, 0, pitch, pixelFormat); return buf; } /** * Decompress the 16-bit-per-sample lossless JPEG source image associated * with this decompressor instance and output a 16-bit-per-sample * packed-pixel grayscale, RGB, or CMYK image to the given destination * buffer. * * <p>NOTE: The destination image is fully recoverable if this method throws * a non-fatal {@link TJException} (unless {@link TJ#PARAM_STOPONWARNING} is * set.) * * @param dstBuf buffer that will receive the packed-pixel * decompressed image. This buffer should normally be * <code>pitch * jpegHeight</code> samples in size. However, the buffer may * also be larger, 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. * * @param x x offset (in pixels) of the region in the destination image into * which the source image should be decompressed * * @param y y offset (in pixels) of the region in the destination image into * which the source image should be decompressed * * @param pitch samples per row in the destination image. Normally this * should be set to <code>jpegWidth * * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>, if the * destination image will be unpadded. (Setting this parameter to 0 is the * equivalent of setting it to <code>jpegWidth * * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>.) However, * you can also use this parameter to specify the row alignment/padding of * the destination image, to skip rows, or to decompress into a specific * region of a larger image. * * @param pixelFormat pixel format of the decompressed image (one of * {@link TJ#PF_RGB TJ.PF_*}) */ public void decompress16(short[] dstBuf, int x, int y, int pitch, int pixelFormat) throws TJException { if (jpegBuf == null) throw new IllegalStateException(NO_ASSOC_ERROR); if (dstBuf == null || x < 0 || y < 0 || pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF) throw new IllegalArgumentException("Invalid argument in decompress16()"); decompress16(jpegBuf, jpegBufSize, dstBuf, x, y, pitch, pixelFormat); } /** * Decompress the 16-bit-per-sample JPEG source image associated with this * decompressor instance and return a buffer containing a 16-bit-per-sample * packed-pixel decompressed image. * * @param pitch see * {@link #decompress16(short[], int, int, int, int)} for description * * @param pixelFormat pixel format of the decompressed image (one of * {@link TJ#PF_RGB TJ.PF_*}) * * @return a buffer containing a 16-bit-per-sample packed-pixel decompressed * image. */ public short[] decompress16(int pitch, int pixelFormat) throws TJException { if (pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF) throw new IllegalArgumentException("Invalid argument in decompress16()"); int pixelSize = TJ.getPixelSize(pixelFormat); int scaledWidth = scalingFactor.getScaled(getJPEGWidth()); int scaledHeight = scalingFactor.getScaled(getJPEGHeight()); if (pitch == 0) pitch = scaledWidth * pixelSize; short[] buf = new short[pitch * scaledHeight]; decompress16(buf, 0, 0, pitch, pixelFormat); return buf; } /** * Decompress the 8-bit-per-sample JPEG source image associated with this * decompressor instance into an 8-bit-per-sample planar YUV image and store * it in the given {@link YUVImage} instance. This method performs JPEG * decompression but leaves out the color conversion step, so a planar YUV * image is generated instead of a packed-pixel image. This method cannot be * used to decompress JPEG source images with the CMYK or YCCK colorspace. * * <p>NOTE: The planar YUV destination image is fully recoverable if this * method throws a non-fatal {@link TJException} (unless * {@link TJ#PARAM_STOPONWARNING} is set.) * * @param dstImage {@link YUVImage} instance that will receive the planar YUV * decompressed image. The level of subsampling specified in this * {@link YUVImage} instance must match that of the JPEG image, and the width * and height specified in the {@link YUVImage} instance must match the * scaled JPEG width and height (see {@link #setScalingFactor * setScalingFactor()}, {@link TJScalingFactor#getScaled * TJScalingFactor.getScaled()}, {@link #getWidth}, and {@link #getHeight}.) */ public void decompressToYUV(YUVImage dstImage) throws TJException { if (jpegBuf == null) throw new IllegalStateException(NO_ASSOC_ERROR); if (dstImage == null) throw new IllegalArgumentException("Invalid argument in decompressToYUV()"); checkSubsampling(); if (get(TJ.PARAM_SUBSAMP) != dstImage.getSubsamp()) throw new IllegalArgumentException("YUVImage subsampling level does not match that of the JPEG image"); if (scalingFactor.getScaled(getJPEGWidth()) != dstImage.getWidth() || scalingFactor.getScaled(getJPEGHeight()) != dstImage.getHeight()) throw new IllegalArgumentException("YUVImage dimensions do not match the scaled JPEG dimensions"); decompressToYUV8(jpegBuf, jpegBufSize, dstImage.getPlanes(), dstImage.getOffsets(), dstImage.getStrides()); } /** * @deprecated Use {@link #set set()}, {@link #setScalingFactor * setScalingFactor()}, and {@link #decompressToYUV(YUVImage)} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public void decompressToYUV(YUVImage dstImage, int flags) throws TJException { if (flags < 0) throw new IllegalArgumentException("Invalid argument in decompressToYUV()"); TJScalingFactor sf = getScalingFactor(dstImage.getWidth(), dstImage.getHeight()); if (sf.getScaled(getJPEGWidth()) != dstImage.getWidth() || sf.getScaled(getJPEGHeight()) != dstImage.getHeight()) throw new IllegalArgumentException("YUVImage dimensions do not match one of the scaled image sizes that the decompressor is capable of generating."); setScalingFactor(sf); processFlags(flags); decompressToYUV(dstImage); } /** * Decompress the 8-bit-per-sample JPEG source image associated with this * decompressor instance into a set of 8-bit-per-sample Y, U (Cb), and V (Cr) * image planes and return a {@link YUVImage} 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 a packed-pixel image. This method cannot be used to decompress * JPEG source images with the CMYK or YCCK colorspace. * * @param strides an array of integers, each specifying the number of bytes * per row in the corresponding plane of the YUV image. Setting the stride * for any plane to 0 is the same as setting it to the scaled plane width * (see {@link YUVImage}.) If <code>strides</code> is null, then the strides * for all planes will be set to their respective scaled plane widths. You * can adjust the strides in order to add an arbitrary amount of row padding * to each plane. * * @return a {@link YUVImage} instance containing the decompressed image * planes */ public YUVImage decompressToYUV(int[] strides) throws TJException { int jpegWidth = getJPEGWidth(); int jpegHeight = getJPEGHeight(); checkSubsampling(); if (yuvImage != null) throw new IllegalStateException("Source image is the wrong type"); YUVImage dstYUVImage = new YUVImage(scalingFactor.getScaled(jpegWidth), null, scalingFactor.getScaled(jpegHeight), get(TJ.PARAM_SUBSAMP)); decompressToYUV(dstYUVImage); return dstYUVImage; } /** * @deprecated Use {@link #set set()}, {@link #setScalingFactor * setScalingFactor()}, and {@link #decompressToYUV(int[])} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public YUVImage decompressToYUV(int desiredWidth, int[] strides, int desiredHeight, int flags) throws TJException { if (flags < 0) throw new IllegalArgumentException("Invalid argument in decompressToYUV()"); TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); setScalingFactor(sf); processFlags(flags); return decompressToYUV(strides); } /** * Decompress the 8-bit-per-sample JPEG source image associated with this * decompressor instance into an 8-bit-per-sample unified planar YUV image * and return a {@link YUVImage} 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 a * packed-pixel image. This method cannot be used to decompress JPEG source * images with the CMYK or YCCK colorspace. * * @param align row alignment (in bytes) of the YUV image (must be a power of * 2.) Setting this parameter to n will cause each row in each plane of the * YUV image to be padded to the nearest multiple of n bytes (1 = unpadded.) * * @return a {@link YUVImage} instance containing the unified planar YUV * decompressed image */ public YUVImage decompressToYUV(int align) throws TJException { int jpegWidth = getJPEGWidth(); int jpegHeight = getJPEGHeight(); checkSubsampling(); if (yuvImage != null) throw new IllegalStateException("Source image is the wrong type"); YUVImage dstYUVImage = new YUVImage(scalingFactor.getScaled(jpegWidth), align, scalingFactor.getScaled(jpegHeight), get(TJ.PARAM_SUBSAMP)); decompressToYUV(dstYUVImage); return dstYUVImage; } /** * @deprecated Use {@link #set set()}, {@link #setScalingFactor * setScalingFactor()}, and {@link #decompressToYUV(int)} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public YUVImage decompressToYUV(int desiredWidth, int align, int desiredHeight, int flags) throws TJException { if (flags < 0) throw new IllegalArgumentException("Invalid argument in decompressToYUV()"); TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); setScalingFactor(sf); processFlags(flags); return decompressToYUV(align); } /** * Decompress the 8-bit-per-sample JPEG source image or decode the planar YUV * source image associated with this decompressor instance and output an * 8-bit-per-sample packed-pixel grayscale, RGB, or CMYK image to the given * destination buffer. * * <p>NOTE: The destination image is fully recoverable if this method throws * a non-fatal {@link TJException} (unless {@link TJ#PARAM_STOPONWARNING} * is set.) * * @param dstBuf buffer that will receive the packed-pixel * decompressed/decoded image. This buffer should normally be * <code>stride * destinationHeight</code> pixels in size. However, the * buffer may also be larger, 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. * NOTE: If the source image is a lossy JPEG image, then * <code>destinationHeight</code> is either the scaled JPEG height (see * {@link #setScalingFactor setScalingFactor()}, * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()}, and * {@link #getHeight}) or the height of the cropping region (see * {@link #setCroppingRegion setCroppingRegion()}.) If the source image is a * YUV image or a lossless JPEG image, then <code>destinationHeight</code> is * the height of the source image. * * @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 stride pixels per row in the destination image. Normally this * should be set to <code>destinationWidth</code>. (Setting this parameter * to 0 is the equivalent of setting it to <code>destinationWidth</code>.) * However, you can also use this parameter to skip rows or to * decompress/decode into a specific region of a larger image. NOTE: If the * source image is a lossy JPEG image, then <code>destinationWidth</code> is * either the scaled JPEG width (see {@link #setScalingFactor * setScalingFactor()}, {@link TJScalingFactor#getScaled * TJScalingFactor.getScaled()}, and {@link #getWidth}) or the width of the * cropping region (see {@link #setCroppingRegion setCroppingRegion()}.) If * the source image is a YUV image or a lossless JPEG image, then * <code>destinationWidth</code> is the width of the source image. * * @param pixelFormat pixel format of the decompressed/decoded image (one of * {@link TJ#PF_RGB TJ.PF_*}) */ public void decompress8(int[] dstBuf, int x, int y, int stride, int pixelFormat) throws TJException { if (jpegBuf == null && yuvImage == null) throw new IllegalStateException("No source image is associated with this instance"); if (dstBuf == null || x < 0 || y < 0 || stride < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF) throw new IllegalArgumentException("Invalid argument in decompress8()"); if (yuvImage != null) { checkSubsampling(); decodeYUV8(yuvImage.getPlanes(), yuvImage.getOffsets(), yuvImage.getStrides(), dstBuf, x, y, yuvImage.getWidth(), stride, yuvImage.getHeight(), pixelFormat); } else decompress8(jpegBuf, jpegBufSize, dstBuf, x, y, stride, pixelFormat); } /** * @deprecated Use {@link #set set()}, {@link #setScalingFactor * setScalingFactor()}, and {@link #decompress8(int[], int, int, int, int)} * instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public void decompress(int[] dstBuf, int x, int y, int desiredWidth, int stride, int desiredHeight, int pixelFormat, int flags) throws TJException { if ((yuvImage != null && (desiredWidth < 0 || desiredHeight < 0)) || flags < 0) throw new IllegalArgumentException("Invalid argument in decompress()"); if (yuvImage == null) { TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); setScalingFactor(sf); } processFlags(flags); decompress8(dstBuf, x, y, stride, pixelFormat); } /** * Decompress the 8-bit-per-sample JPEG source image or decode the planar YUV * source image associated with this decompressor instance and output an * 8-bit-per-sample packed-pixel decompressed/decoded image to the given * <code>BufferedImage</code> instance. * * <p>NOTE: The destination image is fully recoverable if this method throws * a non-fatal {@link TJException} (unless {@link TJ#PARAM_STOPONWARNING} * is set.) * * @param dstImage a <code>BufferedImage</code> instance that will receive * the packed-pixel decompressed/decoded image. If the source image is a * lossy JPEG image, then the width and height of the * <code>BufferedImage</code> instance must match the scaled JPEG width and * height (see {@link #setScalingFactor setScalingFactor()}, * {@link TJScalingFactor#getScaled TJScalingFactor.getScaled()}, * {@link #getWidth}, and {@link #getHeight}) or the width and height of the * cropping region (see {@link #setCroppingRegion setCroppingRegion()}.) If * the source image is a YUV image or a lossless JPEG image, then the width * and height of the <code>BufferedImage</code> instance must match the width * and height of the source image. */ public void decompress8(BufferedImage dstImage) throws TJException { if (dstImage == null) throw new IllegalArgumentException("Invalid argument in decompress8()"); if (yuvImage != null) { if (dstImage.getWidth() != yuvImage.getWidth() || dstImage.getHeight() != yuvImage.getHeight()) throw new IllegalArgumentException("BufferedImage dimensions do not match the dimensions of the source image."); } else { if (scalingFactor.getScaled(getJPEGWidth()) != dstImage.getWidth() || scalingFactor.getScaled(getJPEGHeight()) != dstImage.getHeight()) throw new IllegalArgumentException("BufferedImage dimensions do not match the scaled JPEG dimensions."); } 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) { checkSubsampling(); decodeYUV8(yuvImage.getPlanes(), yuvImage.getOffsets(), yuvImage.getStrides(), buf, 0, 0, yuvImage.getWidth(), stride, yuvImage.getHeight(), pixelFormat); } else { if (jpegBuf == null) throw new IllegalStateException(NO_ASSOC_ERROR); decompress8(jpegBuf, jpegBufSize, buf, 0, 0, stride, pixelFormat); } } 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(); decompress8(buf, 0, 0, pitch, pixelFormat); } } /** * @deprecated Use {@link #set set()}, {@link #setScalingFactor * setScalingFactor()}, and {@link #decompress8(BufferedImage)} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated public void decompress(BufferedImage dstImage, int flags) throws TJException { if (flags < 0) throw new IllegalArgumentException("Invalid argument in decompress()"); if (yuvImage == null) { TJScalingFactor sf = getScalingFactor(dstImage.getWidth(), dstImage.getHeight()); if (sf.getScaled(getJPEGWidth()) != dstImage.getWidth() || sf.getScaled(getJPEGHeight()) != dstImage.getHeight()) throw new IllegalArgumentException("BufferedImage dimensions do not match one of the scaled image sizes that TurboJPEG is capable of generating."); setScalingFactor(sf); } processFlags(flags); decompress8(dstImage); } /** * Decompress the 8-bit-per-sample JPEG source image or decode the planar YUV * source image associated with this decompressor instance and return a * <code>BufferedImage</code> instance containing the 8-bit-per-sample * packed-pixel decompressed/decoded image. * * @param bufferedImageType the image type of the <code>BufferedImage</code> * instance that will be created (for instance, * <code>BufferedImage.TYPE_INT_RGB</code>) * * @return a <code>BufferedImage</code> instance containing the * 8-bit-per-sample packed-pixel decompressed/decoded image. */ public BufferedImage decompress8(int bufferedImageType) throws TJException { BufferedImage img = new BufferedImage(scalingFactor.getScaled(getJPEGWidth()), scalingFactor.getScaled(getJPEGHeight()), bufferedImageType); decompress8(img); return img; } /** * @deprecated Use {@link #set set()}, {@link #setScalingFactor * setScalingFactor()}, and {@link #decompress8(int)} instead. */ @SuppressWarnings("checkstyle:JavadocMethod") @Deprecated 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()"); if (yuvImage == null) { TJScalingFactor sf = getScalingFactor(desiredWidth, desiredHeight); setScalingFactor(sf); } processFlags(flags); return decompress8(bufferedImageType); } /** * Free the native structures associated with this decompressor instance. */ @Override public void close() throws TJException { if (handle != 0) destroy(); } @SuppressWarnings("checkstyle:DesignForExtension") @Override protected void finalize() throws Throwable { try { close(); } catch (TJException e) { } finally { super.finalize(); } }; @SuppressWarnings("deprecation") final void processFlags(int flags) { set(TJ.PARAM_BOTTOMUP, (flags & TJ.FLAG_BOTTOMUP) != 0 ? 1 : 0); set(TJ.PARAM_FASTUPSAMPLE, (flags & TJ.FLAG_FASTUPSAMPLE) != 0 ? 1 : 0); set(TJ.PARAM_FASTDCT, (flags & TJ.FLAG_FASTDCT) != 0 ? 1 : 0); set(TJ.PARAM_STOPONWARNING, (flags & TJ.FLAG_STOPONWARNING) != 0 ? 1 : 0); set(TJ.PARAM_SCANLIMIT, (flags & TJ.FLAG_LIMITSCANS) != 0 ? 500 : 0); } final void checkSubsampling() { if (get(TJ.PARAM_SUBSAMP) == TJ.SAMP_UNKNOWN) throw new IllegalStateException("Unknown or unspecified subsampling level"); } private native void init() throws TJException; private native void destroy() throws TJException; private native void decompressHeader(byte[] srcBuf, int size) throws TJException; private native void setCroppingRegion() throws TJException; @SuppressWarnings("checkstyle:HiddenField") private native void decompress8(byte[] srcBuf, int size, byte[] dstBuf, int x, int y, int pitch, int pixelFormat) throws TJException; @SuppressWarnings("checkstyle:HiddenField") private native void decompress12(byte[] srcBuf, int size, short[] dstBuf, int x, int y, int pitch, int pixelFormat) throws TJException; @SuppressWarnings("checkstyle:HiddenField") private native void decompress16(byte[] srcBuf, int size, short[] dstBuf, int x, int y, int pitch, int pixelFormat) throws TJException; @SuppressWarnings("checkstyle:HiddenField") private native void decompress8(byte[] srcBuf, int size, int[] dstBuf, int x, int y, int stride, int pixelFormat) throws TJException; @SuppressWarnings("checkstyle:HiddenField") private native void decompressToYUV8(byte[] srcBuf, int size, byte[][] dstPlanes, int[] dstOffsets, int[] dstStrides) throws TJException; private native void decodeYUV8(byte[][] srcPlanes, int[] srcOffsets, int[] srcStrides, byte[] dstBuf, int x, int y, int width, int pitch, int height, int pixelFormat) throws TJException; private native void decodeYUV8(byte[][] srcPlanes, int[] srcOffsets, int[] srcStrides, int[] dstBuf, int x, int y, int width, int stride, int height, int pixelFormat) throws TJException; /** * @hidden * Ugly hack alert. It isn't straightforward to save 12-bit-per-sample and * 16-bit-per-sample images using the ImageIO and BufferedImage classes, and * ImageIO doesn't support PBMPLUS files anyhow. This method accesses * tj3SaveImage() through JNI and copies the pixel data between the C and * Java heaps. Currently it is undocumented and used only by TJBench. */ @SuppressWarnings("checkstyle:JavadocMethod") public native void saveImage(int precision, String fileName, Object srcBuf, int width, int pitch, int height, int pixelFormat) throws TJException; static { TJLoader.load(); } private long handle = 0; private byte[] jpegBuf = null; private int jpegBufSize = 0; private YUVImage yuvImage = null; private TJScalingFactor scalingFactor = TJ.UNSCALED; private Rectangle croppingRegion = TJ.UNCROPPED; private ByteOrder byteOrder = null; }