/*
 * Copyright (C)2011-2015, 2018, 2020, 2022-2025 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 compressor
 */
public class TJCompressor implements Closeable {

  private static final String NO_ASSOC_ERROR =
    "No source image is associated with this instance";

  /**
   * Create a TurboJPEG compressor instance.
   */
  public TJCompressor() throws TJException {
    init();
  }

  /**
   * Create a TurboJPEG compressor instance and associate the packed-pixel
   * source image, stored in <code>srcImage</code> with 2 to 8 bits of data
   * precision per sample, with the newly created instance.
   *
   * @param srcImage see {@link #setSourceImage} for description
   *
   * @param x see {@link #setSourceImage} for description
   *
   * @param y see {@link #setSourceImage} for description
   *
   * @param width see {@link #setSourceImage} for description
   *
   * @param pitch see {@link #setSourceImage} for description
   *
   * @param height see {@link #setSourceImage} for description
   *
   * @param pixelFormat pixel format of the source image (one of
   * {@link TJ#PF_RGB TJ.PF_*})
   */
  public TJCompressor(byte[] srcImage, int x, int y, int width, int pitch,
                      int height, int pixelFormat) throws TJException {
    setSourceImage(srcImage, x, y, width, pitch, height, pixelFormat);
  }

  /**
   * Create a TurboJPEG compressor instance and associate the 8-bit-per-sample
   * packed-pixel source image stored in <code>srcImage</code> with the newly
   * created instance.
   *
   * @param srcImage see
   * {@link #setSourceImage(BufferedImage, int, int, int, int)} for description
   *
   * @param x see
   * {@link #setSourceImage(BufferedImage, int, int, int, int)} for description
   *
   * @param y see
   * {@link #setSourceImage(BufferedImage, int, int, int, int)} for description
   *
   * @param width see
   * {@link #setSourceImage(BufferedImage, int, int, int, int)} for description
   *
   * @param height see
   * {@link #setSourceImage(BufferedImage, int, int, int, int)} for description
   */
  public TJCompressor(BufferedImage srcImage, int x, int y, int width,
                      int height) throws TJException {
    setSourceImage(srcImage, x, y, width, height);
  }

  /**
   * Associate a packed-pixel RGB, grayscale, or CMYK source image with 2 to 8
   * bits of data precision per sample with this compressor instance.  Note
   * that packed-pixel source images with 2 to 7 bits of data precision per
   * sample can only be compressed into lossless JPEG images.
   *
   * @param srcImage buffer containing a packed-pixel RGB, grayscale, or CMYK
   * source image to be compressed or encoded.  The data precision of the
   * source image (from 2 to 8 bits per sample) can be specified using
   * {@link TJ#PARAM_PRECISION} and defaults to 8 if {@link TJ#PARAM_PRECISION}
   * is unset or out of range.  This buffer is not modified.
   *
   * @param x x offset (in pixels) of the region in the source image from which
   * the JPEG or YUV image should be compressed/encoded
   *
   * @param y y offset (in pixels) of the region in the source image from which
   * the JPEG or YUV image should be compressed/encoded
   *
   * @param width width (in pixels) of the region in the source image from
   * which the JPEG or YUV image should be compressed/encoded
   *
   * @param pitch bytes per row in the source image.  Normally this should be
   * <code>width * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>,
   * if the source image is unpadded.  (Setting this parameter to 0 is the
   * equivalent of setting it to <code>width *
   * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>.)  However,
   * you can also use this parameter to specify the row alignment/padding of
   * the source image, to skip rows, or to compress/encode a JPEG or YUV image
   * from a specific region of a larger source image.
   *
   * @param height height (in pixels) of the region in the source image from
   * which the JPEG or YUV image should be compressed/encoded
   *
   * @param pixelFormat pixel format of the source image (one of
   * {@link TJ#PF_RGB TJ.PF_*})
   */
  public void setSourceImage(byte[] srcImage, int x, int y, int width,
                             int pitch, int height, int pixelFormat)
                             throws TJException {
    if (handle == 0) init();
    if (srcImage == null || x < 0 || y < 0 || width < 1 || height < 1 ||
        pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF)
      throw new IllegalArgumentException("Invalid argument in setSourceImage()");
    srcBuf8 = srcImage;
    srcWidth = width;
    if (pitch == 0)
      srcPitch = width * TJ.getPixelSize(pixelFormat);
    else
      srcPitch = pitch;
    srcHeight = height;
    srcPixelFormat = pixelFormat;
    srcX = x;
    srcY = y;
    srcBuf12 = null;
    srcBuf16 = null;
    srcBufInt = null;
    srcYUVImage = null;
  }

  /**
   * Associate a packed-pixel RGB, grayscale, or CMYK source image with 9 to 12
   * bits of data precision per sample with this compressor instance.  Note
   * that packed-pixel source images with 9 to 11 bits of data precision per
   * sample can only be compressed into lossless JPEG images.
   *
   * @param srcImage buffer containing a packed-pixel RGB, grayscale, or CMYK
   * source image to be compressed.  The data precision of the source image
   * (from 9 to 12 bits per sample) can be specified using
   * {@link TJ#PARAM_PRECISION} and defaults to 12 if
   * {@link TJ#PARAM_PRECISION} is unset or out of range.  This buffer is not
   * modified.
   *
   * @param x x offset (in pixels) of the region in the source image from which
   * the JPEG image should be compressed
   *
   * @param y y offset (in pixels) of the region in the source image from which
   * the JPEG image should be compressed
   *
   * @param width width (in pixels) of the region in the source image from
   * which the JPEG image should be compressed
   *
   * @param pitch samples per row in the source image.  Normally this should be
   * <code>width * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>,
   * if the source image is unpadded.  (Setting this parameter to 0 is the
   * equivalent of setting it to <code>width *
   * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>.)  However,
   * you can also use this parameter to specify the row alignment/padding of
   * the source image, to skip rows, or to compress a JPEG image from a
   * specific region of a larger source image.
   *
   * @param height height (in pixels) of the region in the source image from
   * which the JPEG image should be compressed
   *
   * @param pixelFormat pixel format of the source image (one of
   * {@link TJ#PF_RGB TJ.PF_*})
   */
  public void setSourceImage12(short[] srcImage, int x, int y, int width,
                               int pitch, int height, int pixelFormat)
                               throws TJException {
    if (handle == 0) init();
    if (srcImage == null || x < 0 || y < 0 || width < 1 || height < 1 ||
        pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF)
      throw new IllegalArgumentException("Invalid argument in setSourceImage()");
    srcBuf12 = srcImage;
    srcWidth = width;
    if (pitch == 0)
      srcPitch = width * TJ.getPixelSize(pixelFormat);
    else
      srcPitch = pitch;
    srcHeight = height;
    srcPixelFormat = pixelFormat;
    srcX = x;
    srcY = y;
    srcBuf8 = null;
    srcBuf16 = null;
    srcBufInt = null;
    srcYUVImage = null;
  }

  /**
   * Associate a packed-pixel RGB, grayscale, or CMYK source image with 13 to
   * 16 bits of data precision per sample with this compressor instance.  Note
   * that packed-pixel source images with 13 to 16 bits of data precision per
   * sample can only be compressed into lossless JPEG images.
   *
   * @param srcImage buffer containing a packed-pixel RGB, grayscale, or CMYK
   * source image to be compressed.  The data precision of the source image
   * (from 13 to 16 bits per sample) can be specified using
   * {@link TJ#PARAM_PRECISION} and defaults to 16 if
   * {@link TJ#PARAM_PRECISION} is unset or out of range.  This buffer is not
   * modified.
   *
   * @param x x offset (in pixels) of the region in the source image from which
   * the JPEG image should be compressed
   *
   * @param y y offset (in pixels) of the region in the source image from which
   * the JPEG image should be compressed
   *
   * @param width width (in pixels) of the region in the source image from
   * which the JPEG image should be compressed
   *
   * @param pitch samples per row in the source image.  Normally this should be
   * <code>width * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>,
   * if the source image is unpadded.  (Setting this parameter to 0 is the
   * equivalent of setting it to <code>width *
   * {@link TJ#getPixelSize TJ.getPixelSize}(pixelFormat)</code>.)  However,
   * you can also use this parameter to specify the row alignment/padding of
   * the source image, to skip rows, or to compress a JPEG image from a
   * specific region of a larger source image.
   *
   * @param height height (in pixels) of the region in the source image from
   * which the JPEG image should be compressed
   *
   * @param pixelFormat pixel format of the source image (one of
   * {@link TJ#PF_RGB TJ.PF_*})
   */
  public void setSourceImage16(short[] srcImage, int x, int y, int width,
                               int pitch, int height, int pixelFormat)
                               throws TJException {
    if (handle == 0) init();
    if (srcImage == null || x < 0 || y < 0 || width < 1 || height < 1 ||
        pitch < 0 || pixelFormat < 0 || pixelFormat >= TJ.NUMPF)
      throw new IllegalArgumentException("Invalid argument in setSourceImage()");
    srcBuf16 = srcImage;
    srcWidth = width;
    if (pitch == 0)
      srcPitch = width * TJ.getPixelSize(pixelFormat);
    else
      srcPitch = pitch;
    srcHeight = height;
    srcPixelFormat = pixelFormat;
    srcX = x;
    srcY = y;
    srcBuf8 = null;
    srcBuf12 = null;
    srcBufInt = null;
    srcYUVImage = null;
  }

  /**
   * Associate an 8-bit-per-sample packed-pixel RGB or grayscale source image
   * with this compressor instance.
   *
   * @param srcImage a <code>BufferedImage</code> instance containing a
   * packed-pixel RGB or grayscale source image to be compressed or encoded.
   * This image is not modified.
   *
   * @param x x offset (in pixels) of the region in the source image from which
   * the JPEG or YUV image should be compressed/encoded
   *
   * @param y y offset (in pixels) of the region in the source image from which
   * the JPEG or YUV image should be compressed/encoded
   *
   * @param width width (in pixels) of the region in the source image from
   * which the JPEG or YUV image should be compressed/encoded (0 = use the
   * width of the source image)
   *
   * @param height height (in pixels) of the region in the source image from
   * which the JPEG or YUV image should be compressed/encoded (0 = use the
   * height of the source image)
   */
  public void setSourceImage(BufferedImage srcImage, int x, int y, int width,
                             int height) throws TJException {
    if (handle == 0) init();
    if (srcImage == null || x < 0 || y < 0 || width < 0 || height < 0)
      throw new IllegalArgumentException("Invalid argument in setSourceImage()");
    srcX = x;
    srcY = y;
    srcWidth = (width == 0) ? srcImage.getWidth() : width;
    srcHeight = (height == 0) ? srcImage.getHeight() : height;
    if (x + width > srcImage.getWidth() || y + height > srcImage.getHeight())
      throw new IllegalArgumentException("Compression region exceeds the bounds of the source image");

    int pixelFormat;
    boolean intPixels = false;
    if (byteOrder == null)
      byteOrder = ByteOrder.nativeOrder();
    switch (srcImage.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:
    case BufferedImage.TYPE_INT_ARGB:
    case BufferedImage.TYPE_INT_ARGB_PRE:
      if (byteOrder == ByteOrder.BIG_ENDIAN)
        pixelFormat = TJ.PF_XRGB;
      else
        pixelFormat = TJ.PF_BGRX;
      intPixels = true;  break;
    default:
      throw new IllegalArgumentException("Unsupported BufferedImage format");
    }
    srcPixelFormat = pixelFormat;

    WritableRaster wr = srcImage.getRaster();
    if (intPixels) {
      SinglePixelPackedSampleModel sm =
        (SinglePixelPackedSampleModel)srcImage.getSampleModel();
      srcPitch = sm.getScanlineStride();
      DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
      srcBufInt = db.getData();
      srcBuf8 = null;
      srcBuf12 = null;
      srcBuf16 = null;
    } else {
      ComponentSampleModel sm =
        (ComponentSampleModel)srcImage.getSampleModel();
      int pixelSize = sm.getPixelStride();
      if (pixelSize != TJ.getPixelSize(pixelFormat))
        throw new IllegalArgumentException("Inconsistency between pixel format and pixel size in BufferedImage");
      srcPitch = sm.getScanlineStride();
      DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
      srcBuf8 = db.getData();
      srcBuf12 = null;
      srcBuf16 = null;
      srcBufInt = null;
    }
    srcYUVImage = null;
  }

  /**
   * Associate an 8-bit-per-sample planar YUV source image with this compressor
   * instance.  This method sets {@link TJ#PARAM_SUBSAMP} to the chrominance
   * subsampling level of the source image.
   *
   * @param srcImage planar YUV source image to be compressed.  This image is
   * not modified.
   */
  public void setSourceImage(YUVImage srcImage) throws TJException {
    if (handle == 0) init();
    if (srcImage == null)
      throw new IllegalArgumentException("Invalid argument in setSourceImage()");
    srcYUVImage = srcImage;
    set(TJ.PARAM_SUBSAMP, srcImage.getSubsamp());
    srcBuf8 = null;
    srcBuf12 = null;
    srcBuf16 = null;
    srcBufInt = null;
  }

  /**
   * Load a packed-pixel RGB or grayscale source image with 2 to 16 bits of
   * data precision per sample from disk into memory and associate it with this
   * compressor instance.  Note that packed-pixel source images with 2 to 7, 9
   * to 11, or 13 to 16 bits of data precision per sample can only be
   * compressed into lossless JPEG images.
   *
   * <p>{@link #getWidth()}, {@link #getPitch()}, {@link #getHeight()},
   * {@link #getPixelFormat()}, and {@link #getSourceBuf()} can be used to
   * obtain the source image's dimensions, pixel format, and buffer after it is
   * loaded.
   *
   * @param fileName name of a file containing a packed-pixel image in Windows
   * BMP or PBMPLUS (PPM/PGM) format.  Windows BMP files require
   * 8-bit-per-sample data precision.  When loading a PBMPLUS file, the target
   * data precision (from 2 to 16 bits per sample) can be specified using
   * {@link TJ#PARAM_PRECISION} and defaults to 8 if {@link TJ#PARAM_PRECISION}
   * is unset.  If the data precision of the PBMPLUS file does not match the
   * target data precision, then upconverting or downconverting will be
   * performed.
   *
   * @param align row alignment (in samples) of the packed-pixel buffer into
   * which the source image will be loaded (must be a power of 2.)  Setting
   * this parameter to n will cause all rows in the buffer to be padded to the
   * nearest multiple of n samples (1 = unpadded.)
   *
   * @param pixelFormat pixel format of the packed-pixel buffer into which the
   * source image will be loaded.  The behavior of this method varies depending
   * on the value of <code>pixelFormat</code>:
   * <ul>
   * <li> {@link TJ#PF_UNKNOWN} : The packed-pixel buffer created by this
   * method will use the most optimal pixel format for the file type.  Use
   * {@link #getPixelFormat()} to obtain the ID of that pixel format.
   * <li> {@link TJ#PF_GRAY} : Only PGM files and 8-bit-per-pixel BMP files
   * with a grayscale colormap can be loaded.
   * <li> {@link TJ#PF_CMYK} : The RGB or grayscale pixels stored in the file
   * will be converted using a quick &amp; dirty algorithm that is suitable
   * only for testing purposes.  (Proper conversion between CMYK and other
   * formats requires a color management system.)
   * <li> Other {@link TJ#PF_RGB pixel formats} : The packed-pixel buffer
   * will use the specified pixel format, and pixel format conversion will be
   * performed if necessary.
   * </ul>
   */
  public void loadSourceImage(String fileName, int align, int pixelFormat)
                              throws TJException {
    int precision = get(TJ.PARAM_PRECISION);
    if (precision < 2 || precision > 16)
      precision = 8;
    Object srcBuf = loadSourceImage(precision, fileName, align, pixelFormat);
    if (precision <= 8) {
      srcBuf8 = (byte[])srcBuf;
      srcBuf12 = null;
      srcBuf16 = null;
    } else if (precision <= 12) {
      srcBuf12 = (short[])srcBuf;
      srcBuf8 = null;
      srcBuf16 = null;
    } else {
      srcBuf16 = (short[])srcBuf;
      srcBuf8 = null;
      srcBuf12 = null;
    }
    srcBufInt = null;
  }

  /**
   * Returns the width of the source image (packed-pixel or YUV) associated
   * with this compressor instance.
   *
   * @return the width of the source image (packed-pixel or YUV) associated
   * with this compressor instance.
   */
  public int getWidth() {
    if (srcYUVImage != null)
      return srcYUVImage.getWidth();
    if (srcWidth < 1)
      throw new IllegalStateException(NO_ASSOC_ERROR);
    return srcWidth;
  }

  /**
   * Returns the pitch (samples per row) of the packed-pixel source image
   * associated with this compressor instance.  If the source image is a
   * <code>BufferedImage</code> instance with integer pixels, then the stride
   * (pixels per row) is returned instead.
   *
   * @return the pitch (samples per row) of the packed-pixel source image
   * associated with this compressor instance.
   */
  public int getPitch() {
    if (srcPitch < 1)
      throw new IllegalStateException(NO_ASSOC_ERROR);
    return srcPitch;
  }

  /**
   * Returns the height of the source image (packed-pixel or YUV) associated
   * with this compressor instance.
   *
   * @return the height of the source image (packed-pixel or YUV) associated
   * with this compressor instance.
   */
  public int getHeight() {
    if (srcYUVImage != null)
      return srcYUVImage.getHeight();
    if (srcHeight < 1)
      throw new IllegalStateException(NO_ASSOC_ERROR);
    return srcHeight;
  }

  /**
   * Returns the pixel format of the packed-pixel source image associated with
   * this compressor instance.  The pixel format is one of
   * {@link TJ#PF_RGB TJ.PF_*}.
   *
   * @return the pixel format of the packed-pixel source image associated with
   * this compressor instance.
   */
  public int getPixelFormat() {
    if (srcPixelFormat == TJ.PF_UNKNOWN)
      throw new IllegalStateException(NO_ASSOC_ERROR);
    return srcPixelFormat;
  }

  /**
   * Returns the buffer containing the packed-pixel image associated with this
   * compressor instance.  The buffer is a <code>byte</code> array if the
   * source image has 2 to 8 bits of data precision per sample, a
   * <code>short</code> array if the source image has 9 to 16 bits of data
   * precision per sample, and either a <code>byte</code> or an
   * <code>int</code> array (depending on the image type) if the source image
   * is a <code>BufferedImage</code> instance.
   *
   * @return the buffer containing the packed-pixel image associated with this
   * compressor instance.
   */
  public Object getSourceBuf() {
    if (srcBuf8 != null)
      return srcBuf8;
    else if (srcBuf12 != null)
      return srcBuf12;
    else if (srcBuf16 != null)
      return srcBuf16;
    else if (srcBufInt != null)
      return srcBufInt;
    else
      throw new IllegalStateException(NO_ASSOC_ERROR);
  }

  /**
   * Set the value of a compression parameter.
   *
   * @param param one of {@link TJ#PARAM_STOPONWARNING TJ.PARAM_*}
   *
   * @param value value of the compression parameter (refer to
   * {@link TJ#PARAM_STOPONWARNING parameter documentation})
   */
  public native void set(int param, int value);

  /**
   * Get the value of a compression parameter.
   *
   * @param param one of {@link TJ#PARAM_STOPONWARNING TJ.PARAM_*}
   *
   * @return the value of the specified compression parameter, or -1 if the
   * value is unknown.
   */
  public native int get(int param);

  /**
   * Embed an ICC (International Color Consortium) color management profile in
   * JPEG images generated by subsequent compression operations.
   *
   * @param iccBuf buffer containing an ICC profile.  (The size of the ICC
   * profile is assumed to be the length of the array.)  A copy is made of the
   * ICC profile, so this buffer can be reused as soon as this method returns.
   * Setting this parameter to null removes any ICC profile that was previously
   * associated with this compressor instance.
   */
  public native void setICCProfile(byte[] iccBuf) throws TJException;

  /**
   * Compress the packed-pixel or planar YUV source image associated with this
   * compressor instance and output a JPEG image to the given destination
   * buffer.
   *
   * @param dstBuf buffer that will receive the JPEG image.  Use
   * {@link TJ#bufSize TJ.bufSize()} to determine the maximum size for this
   * buffer based on the source image's width and height and the desired level
   * of chrominance subsampling (see {@link TJ#PARAM_SUBSAMP}), then add the
   * size of the ICC profile (if any) that was previously associated with this
   * compressor instance (see {@link #setICCProfile setICCProfile()}.)
   */
  public void compress(byte[] dstBuf) throws TJException {
    if (dstBuf == null)
      throw new IllegalArgumentException("Invalid argument in compress()");

    if (srcYUVImage != null) {
      checkSubsampling();
      if (get(TJ.PARAM_SUBSAMP) != srcYUVImage.getSubsamp())
        throw new IllegalStateException("TJ.PARAM_SUBSAMP must match subsampling level of YUV image");
      compressedSize = compressFromYUV8(srcYUVImage.getPlanes(),
                                        srcYUVImage.getOffsets(),
                                        srcYUVImage.getWidth(),
                                        srcYUVImage.getStrides(),
                                        srcYUVImage.getHeight(), dstBuf);
    } else if (srcBuf8 != null)
      compressedSize = compress8(srcBuf8, srcX, srcY, srcWidth, srcPitch,
                                 srcHeight, srcPixelFormat, dstBuf);
    else if (srcBuf12 != null)
      compressedSize = compress12(srcBuf12, srcX, srcY, srcWidth, srcPitch,
                                  srcHeight, srcPixelFormat, dstBuf);
    else if (srcBuf16 != null)
      compressedSize = compress16(srcBuf16, srcX, srcY, srcWidth, srcPitch,
                                  srcHeight, srcPixelFormat, dstBuf);
    else if (srcBufInt != null)
      compressedSize = compress8(srcBufInt, srcX, srcY, srcWidth, srcPitch,
                                 srcHeight, srcPixelFormat, dstBuf);
    else
      throw new IllegalStateException("No source image is associated with this instance");
  }

  /**
   * Compress the packed-pixel or planar YUV source image associated with this
   * compressor instance and return a buffer containing a JPEG image.
   *
   * @return a buffer containing a JPEG image.  The length of this buffer will
   * not be equal to the size of the JPEG image.  Use
   * {@link #getCompressedSize} to obtain the size of the JPEG image.
   */
  public byte[] compress() throws TJException {
    byte[] buf;
    if (srcYUVImage != null) {
      buf = new byte[TJ.bufSize(srcYUVImage.getWidth(),
                                srcYUVImage.getHeight(),
                                srcYUVImage.getSubsamp())];
    } else {
      checkSubsampling();
      int subsamp = get(TJ.PARAM_SUBSAMP);
      if (get(TJ.PARAM_LOSSLESS) == 1 && subsamp != TJ.SAMP_GRAY)
        subsamp = TJ.SAMP_444;
      buf = new byte[TJ.bufSize(srcWidth, srcHeight, subsamp) + iccSize];
    }
    compress(buf);
    return buf;
  }

  /**
   * Encode the 8-bit-per-sample packed-pixel source image associated with this
   * compressor instance into an 8-bit-per-sample planar YUV image and store it
   * in the given {@link YUVImage} instance.  This method performs color
   * conversion and downsampling (which are accelerated in the libjpeg-turbo
   * implementation) but does not execute any of the other steps in the JPEG
   * compression process.  Encoding CMYK source images into YUV images is not
   * supported.  This method sets {@link TJ#PARAM_SUBSAMP} to the chrominance
   * subsampling level of the destination image.
   *
   * @param dstImage {@link YUVImage} instance that will receive the planar YUV
   * image
   */
  public void encodeYUV(YUVImage dstImage) throws TJException {
    if (dstImage == null)
      throw new IllegalArgumentException("Invalid argument in encodeYUV()");
    if (srcBuf8 == null && srcBufInt == null)
      throw new IllegalStateException("No 8-bit-per-sample source image is associated with this instance");
    if (srcYUVImage != null)
      throw new IllegalStateException("Source image is not correct type");
    if (srcWidth != dstImage.getWidth() || srcHeight != dstImage.getHeight())
      throw new IllegalStateException("Destination image is the wrong size");
    set(TJ.PARAM_SUBSAMP, dstImage.getSubsamp());

    if (srcBufInt != null) {
      encodeYUV8(srcBufInt, srcX, srcY, srcWidth, srcPitch, srcHeight,
                 srcPixelFormat, dstImage.getPlanes(), dstImage.getOffsets(),
                 dstImage.getStrides());
    } else {
      encodeYUV8(srcBuf8, srcX, srcY, srcWidth, srcPitch, srcHeight,
                 srcPixelFormat, dstImage.getPlanes(), dstImage.getOffsets(),
                 dstImage.getStrides());
    }
    compressedSize = 0;
  }

  /**
   * Encode the 8-bit-per-sample packed-pixel source image associated with this
   * compressor instance into an 8-bit-per-sample unified planar YUV image and
   * return a {@link YUVImage} instance containing the encoded image.  This
   * method performs color conversion and downsampling (which are accelerated
   * in the libjpeg-turbo implementation) but does not execute any of the other
   * steps in the JPEG compression process.  Encoding CMYK source images into
   * YUV images is not supported.
   *
   * @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
   * encoded image
   */
  public YUVImage encodeYUV(int align) throws TJException {
    if (srcBuf8 == null && srcBufInt == null)
      throw new IllegalStateException("No 8-bit-per-sample source image is associated with this instance");
    checkSubsampling();
    if (align < 1 || ((align & (align - 1)) != 0))
      throw new IllegalStateException("Invalid argument in encodeYUV()");
    YUVImage dstYUVImage = new YUVImage(srcWidth, align, srcHeight,
                                        get(TJ.PARAM_SUBSAMP));
    encodeYUV(dstYUVImage);
    return dstYUVImage;
  }

  /**
   * Encode the 8-bit-per-sample packed-pixel source image associated with this
   * compressor instance into separate 8-bit-per-sample Y, U (Cb), and V (Cr)
   * image planes and return a {@link YUVImage} instance containing the encoded
   * image planes.  This method performs color conversion and downsampling
   * (which are accelerated in the libjpeg-turbo implementation) but does not
   * execute any of the other steps in the JPEG compression process.  Encoding
   * CMYK source images into YUV images is not supported.
   *
   * @param strides an array of integers, each specifying the number of bytes
   * per row in the corresponding plane of the YUV source image.  Setting the
   * stride for any plane to 0 is the same as setting it to the plane width
   * (see {@link YUVImage}.)  If <code>strides</code> is null, then the strides
   * for all planes will be set to their respective 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 encoded image planes
   */
  public YUVImage encodeYUV(int[] strides) throws TJException {
    if (srcBuf8 == null && srcBufInt == null)
      throw new IllegalStateException("No 8-bit-per-sample source image is associated with this instance");
    checkSubsampling();
    YUVImage dstYUVImage = new YUVImage(srcWidth, strides, srcHeight,
                                        get(TJ.PARAM_SUBSAMP));
    encodeYUV(dstYUVImage);
    return dstYUVImage;
  }

  /**
   * Returns the size of the image (in bytes) generated by the most recent
   * compression operation.
   *
   * @return the size of the image (in bytes) generated by the most recent
   * compression operation.
   */
  public int getCompressedSize() {
    return compressedSize;
  }

  /**
   * Free the native structures associated with this compressor 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();
    }
  };

  private void checkSubsampling() {
    if (get(TJ.PARAM_SUBSAMP) == TJ.SAMP_UNKNOWN)
      throw new IllegalStateException("TJ.PARAM_SUBSAMP must be specified");
  }

  private native void init() throws TJException;

  private native void destroy() throws TJException;

  // JPEG size in bytes is returned
  @SuppressWarnings("checkstyle:HiddenField")
  private native int compress8(byte[] srcBuf, int x, int y, int width,
    int pitch, int height, int pixelFormat, byte[] jpegBuf) throws TJException;

  @SuppressWarnings("checkstyle:HiddenField")
  private native int compress12(short[] srcBuf, int x, int y, int width,
    int pitch, int height, int pixelFormat, byte[] jpegBuf) throws TJException;

  @SuppressWarnings("checkstyle:HiddenField")
  private native int compress16(short[] srcBuf, int x, int y, int width,
    int pitch, int height, int pixelFormat, byte[] jpegBuf) throws TJException;

  @SuppressWarnings("checkstyle:HiddenField")
  private native int compress8(int[] srcBuf, int x, int y, int width,
    int stride, int height, int pixelFormat, byte[] jpegBuf)
    throws TJException;

  @SuppressWarnings("checkstyle:HiddenField")
  private native int compressFromYUV8(byte[][] srcPlanes, int[] srcOffsets,
    int width, int[] srcStrides, int height, byte[] jpegBuf)
    throws TJException;

  @SuppressWarnings("checkstyle:HiddenField")
  private native void encodeYUV8(byte[] srcBuf, int x, int y, int width,
    int pitch, int height, int pixelFormat, byte[][] dstPlanes,
    int[] dstOffsets, int[] dstStrides) throws TJException;

  @SuppressWarnings("checkstyle:HiddenField")
  private native void encodeYUV8(int[] srcBuf, int x, int y, int width,
    int srcStride, int height, int pixelFormat, byte[][] dstPlanes,
    int[] dstOffsets, int[] dstStrides) throws TJException;

  private native Object loadSourceImage(int precision, String fileName,
    int align, int pixelFormat) throws TJException;

  static {
    TJLoader.load();
  }

  private long handle = 0;
  private byte[] srcBuf8 = null;
  private short[] srcBuf12 = null;
  private short[] srcBuf16 = null;
  private int[] srcBufInt = null;
  private int srcWidth = 0;
  private int srcHeight = 0;
  private int srcX = -1;
  private int srcY = -1;
  private int srcPitch = -1;
  private int srcPixelFormat = TJ.PF_UNKNOWN;
  private YUVImage srcYUVImage = null;
  private int compressedSize = 0;
  private ByteOrder byteOrder = null;
  private int iccSize = 0;
}
