/*
 * #%L
 * Java bindings and pre-built binaries for libjpeg-turbo.
 * %%
 * Copyright (C) 2013 - 2017 Open Microscopy Environment:
 *   - Board of Regents of the University of Wisconsin-Madison
 *   - Glencoe Software, Inc.
 *   - University of Dundee
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */
/*
 * Copyright (C)2011-2012 D. R. Commander.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of the libjpeg-turbo Project nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * This program demonstrates how to compress and decompress JPEG files using
 * the TurboJPEG JNI wrapper
 */

import java.io.*;
import java.awt.*;
import java.awt.image.*;
import java.nio.*;
import javax.imageio.*;
import javax.swing.*;
import org.libjpegturbo.turbojpeg.*;

public class TJExample implements TJCustomFilter {

  public static final String classname = new TJExample().getClass().getName();

  private static void usage() throws Exception {
    System.out.println("\nUSAGE: java " + classname + " <Input file> <Output file> [options]\n");
    System.out.println("Input and output files can be any image format that the Java Image I/O");
    System.out.println("extensions understand.  If either filename ends in a .jpg extension, then");
    System.out.println("TurboJPEG will be used to compress or decompress the file.\n");
    System.out.println("Options:\n");
    System.out.println("-scale M/N = if the input image is a JPEG file, scale the width/height of the");
    System.out.print("             output image by a factor of M/N (M/N = ");
    for(int i = 0; i < sf.length; i++) {
      System.out.print(sf[i].getNum() + "/" + sf[i].getDenom());
      if(sf.length == 2 && i != sf.length - 1) System.out.print(" or ");
      else if(sf.length > 2) {
        if(i != sf.length - 1) System.out.print(", ");
        if(i == sf.length - 2) System.out.print("or ");
      }
    }
    System.out.println(")\n");
    System.out.println("-samp <444|422|420|gray> = If the output image is a JPEG file, this specifies");
    System.out.println("                           the level of chrominance subsampling to use when");
    System.out.println("                           recompressing it.  Default is to use the same level");
    System.out.println("                           of subsampling as the input, if the input is a JPEG");
    System.out.println("                           file, or 4:4:4 otherwise.\n");
    System.out.println("-q <1-100> = If the output image is a JPEG file, this specifies the JPEG");
    System.out.println("             quality to use when recompressing it (default = 95).\n");
    System.out.println("-hflip, -vflip, -transpose, -transverse, -rot90, -rot180, -rot270 =");
    System.out.println("     If the input image is a JPEG file, perform the corresponding lossless");
    System.out.println("     transform prior to decompression (these options are mutually exclusive)\n");
    System.out.println("-grayscale = If the input image is a JPEG file, perform lossless grayscale");
    System.out.println("     conversion prior to decompression (can be combined with the other");
    System.out.println("     transforms above)\n");
    System.out.println("-crop X,Y,WxH = If the input image is a JPEG file, perform lossless cropping");
    System.out.println("     prior to decompression.  X,Y specifies the upper left corner of the");
    System.out.println("     cropping region, and WxH specifies its width and height.  X,Y must be");
    System.out.println("     evenly divible by the MCU block size (8x8 if the source image was");
    System.out.println("     compressed using no subsampling or grayscale, or 16x8 for 4:2:2 or 16x16");
    System.out.println("     for 4:2:0.)\n");
    System.out.println("-display = Display output image (Output file need not be specified in this");
    System.out.println("     case.)\n");
    System.out.println("-fastupsample = Use the fastest chrominance upsampling algorithm available in");
    System.out.println("     the underlying codec\n");
    System.out.println("-fastdct = Use the fastest DCT/IDCT algorithms available in the underlying");
    System.out.println("     codec\n");
    System.out.println("-accuratedct = Use the most accurate DCT/IDCT algorithms available in the");
    System.out.println("     underlying codec\n");
    System.exit(1);
  }

  private final static String sampName[] = {
    "4:4:4", "4:2:2", "4:2:0", "Grayscale", "4:4:0"
  };

  public static void main(String argv[]) {

    BufferedImage img = null;  byte[] bmpBuf = null;
    TJTransform xform = new TJTransform();
    int flags = 0;

    try {

      sf = TJ.getScalingFactors();

      if(argv.length < 2) {
        usage();
      }

      TJScalingFactor scaleFactor = new TJScalingFactor(1, 1);
      String inFormat = "jpg", outFormat = "jpg";
      int outSubsamp = -1, outQual = 95;
      boolean display = false;

      if(argv.length > 1) {
        for(int i = 1; i < argv.length; i++) {
          if(argv[i].length() < 2) continue;
          if(argv[i].length() > 2
            && argv[i].substring(0, 3).equalsIgnoreCase("-sc")) {
            int match = 0;
            if(i < argv.length - 1) {
              String[] scaleArg = argv[++i].split("/");
              if(scaleArg.length == 2) {
                TJScalingFactor tempsf =
                  new TJScalingFactor(Integer.parseInt(scaleArg[0]),
                    Integer.parseInt(scaleArg[1]));
                for(int j = 0; j < sf.length; j++) {
                  if(tempsf.equals(sf[j])) {
                    scaleFactor = sf[j];
                    match = 1;  break;
                  }
                }
              }
            }
            if(match != 1) usage();
          }
          if(argv[i].equalsIgnoreCase("-h") || argv[i].equalsIgnoreCase("-?"))
            usage();
          if(argv[i].length() > 2
            && argv[i].substring(0, 3).equalsIgnoreCase("-sa")) {
            if(i < argv.length - 1) {
              i++;
              if(argv[i].substring(0, 1).equalsIgnoreCase("g"))
                outSubsamp = TJ.SAMP_GRAY;
              else if(argv[i].equals("444")) outSubsamp = TJ.SAMP_444;
              else if(argv[i].equals("422")) outSubsamp = TJ.SAMP_422;
              else if(argv[i].equals("420")) outSubsamp = TJ.SAMP_420;
              else usage();
            }
            else usage();
          }
          if(argv[i].substring(0, 2).equalsIgnoreCase("-q")) {
            if(i < argv.length - 1) {
              int qual = Integer.parseInt(argv[++i]);
              if(qual >= 1 && qual <= 100) outQual = qual;
              else usage();
            }
            else usage();
          }
          if(argv[i].substring(0, 2).equalsIgnoreCase("-g"))
            xform.options |= TJTransform.OPT_GRAY;
          if(argv[i].equalsIgnoreCase("-hflip"))
            xform.op = TJTransform.OP_HFLIP;
          if(argv[i].equalsIgnoreCase("-vflip"))
            xform.op = TJTransform.OP_VFLIP;
          if(argv[i].equalsIgnoreCase("-transpose"))
            xform.op = TJTransform.OP_TRANSPOSE;
          if(argv[i].equalsIgnoreCase("-transverse"))
            xform.op = TJTransform.OP_TRANSVERSE;
          if(argv[i].equalsIgnoreCase("-rot90"))
            xform.op = TJTransform.OP_ROT90;
          if(argv[i].equalsIgnoreCase("-rot180"))
            xform.op = TJTransform.OP_ROT180;
          if(argv[i].equalsIgnoreCase("-rot270"))
            xform.op = TJTransform.OP_ROT270;
          if(argv[i].equalsIgnoreCase("-custom"))
            xform.cf = new TJExample();
          else if(argv[i].length() > 2
            && argv[i].substring(0, 2).equalsIgnoreCase("-c")) {
            if(i >= argv.length - 1) usage();
            String[] cropArg = argv[++i].split(",");
            if(cropArg.length != 3) usage();
            String[] dimArg = cropArg[2].split("[xX]");
            if(dimArg.length != 2) usage();
            int tempx = Integer.parseInt(cropArg[0]);
            int tempy = Integer.parseInt(cropArg[1]);
            int tempw = Integer.parseInt(dimArg[0]);
            int temph = Integer.parseInt(dimArg[1]);
            if(tempx < 0 || tempy < 0 || tempw < 0 || temph < 0) usage();
            xform.x = tempx;  xform.y = tempy;
            xform.width = tempw;  xform.height = temph;
            xform.options |= TJTransform.OPT_CROP;
          }
          if(argv[i].substring(0, 2).equalsIgnoreCase("-d"))
            display = true;
          if(argv[i].equalsIgnoreCase("-fastupsample")) {
            System.out.println("Using fast upsampling code");
            flags |= TJ.FLAG_FASTUPSAMPLE;
          }
          if(argv[i].equalsIgnoreCase("-fastdct")) {
            System.out.println("Using fastest DCT/IDCT algorithm");
            flags |= TJ.FLAG_FASTDCT;
          }
          if(argv[i].equalsIgnoreCase("-accuratedct")) {
            System.out.println("Using most accurate DCT/IDCT algorithm");
            flags |= TJ.FLAG_ACCURATEDCT;
          }
        }
      }
      String[] inFileTokens = argv[0].split("\\.");
      if(inFileTokens.length > 1)
        inFormat = inFileTokens[inFileTokens.length - 1];
      String[] outFileTokens;
      if(display) outFormat = "bmp";
      else {
        outFileTokens = argv[1].split("\\.");
        if(outFileTokens.length > 1)
          outFormat = outFileTokens[outFileTokens.length - 1];
      }

      File file = new File(argv[0]);
      int width, height;

      if(inFormat.equalsIgnoreCase("jpg")) {
        FileInputStream fis = new FileInputStream(file);
        int inputSize = fis.available();
        if(inputSize < 1) {
          System.out.println("Input file contains no data");
          System.exit(1);
        }
        byte[] inputBuf = new byte[inputSize];
        fis.read(inputBuf);
        fis.close();

        TJDecompressor tjd;
        if(xform.op != TJTransform.OP_NONE || xform.options != 0
          || xform.cf != null) {
          TJTransformer tjt = new TJTransformer(inputBuf);
          TJTransform t[] = new TJTransform[1];
          t[0] = xform;
          t[0].options |= TJTransform.OPT_TRIM;
          TJDecompressor[] tjdx = tjt.transform(t, 0);
          tjd = tjdx[0];
        }
        else tjd = new TJDecompressor(inputBuf);

        width = tjd.getWidth();
        height = tjd.getHeight();
        int inSubsamp = tjd.getSubsamp();
        System.out.println("Source Image: " + width + " x " + height
          + " pixels, " + sampName[inSubsamp] + " subsampling");
        if(outSubsamp < 0) outSubsamp = inSubsamp;

        if(outFormat.equalsIgnoreCase("jpg")
          && (xform.op != TJTransform.OP_NONE || xform.options != 0)
          && scaleFactor.isOne()) {
          file = new File(argv[1]);
          FileOutputStream fos = new FileOutputStream(file);
          fos.write(tjd.getJPEGBuf(), 0, tjd.getJPEGSize());
          fos.close();
          System.exit(0);
        }

        width = scaleFactor.getScaled(width);
        height = scaleFactor.getScaled(height);

        if(!outFormat.equalsIgnoreCase("jpg"))
          img = tjd.decompress(width, height, BufferedImage.TYPE_INT_RGB,
                               flags);
        else bmpBuf = tjd.decompress(width, 0, height, TJ.PF_BGRX, flags);
        tjd.close();
      }
      else {
        img = ImageIO.read(file);
        width = img.getWidth();
        height = img.getHeight();
        if(outSubsamp < 0) {
          if(img.getType() == BufferedImage.TYPE_BYTE_GRAY)
            outSubsamp = TJ.SAMP_GRAY;
          else outSubsamp = TJ.SAMP_444;
        }
      }
      System.gc();
      if(!display)
        System.out.print("Dest. Image (" + outFormat + "):  " + width + " x "
          + height + " pixels");

      if(display) {
        ImageIcon icon = new ImageIcon(img);
        JLabel label = new JLabel(icon, JLabel.CENTER);
        JOptionPane.showMessageDialog(null, label, "Output Image",
          JOptionPane.PLAIN_MESSAGE);
      }
      else if(outFormat.equalsIgnoreCase("jpg")) {
        System.out.println(", " + sampName[outSubsamp]
          + " subsampling, quality = " + outQual);
        TJCompressor tjc = new TJCompressor();
        int jpegSize;
        byte[] jpegBuf;

        tjc.setSubsamp(outSubsamp);
        tjc.setJPEGQuality(outQual);
        if(img != null)
          jpegBuf = tjc.compress(img, flags);
        else {
          tjc.setSourceImage(bmpBuf, width, 0, height, TJ.PF_BGRX);
          jpegBuf = tjc.compress(flags);
        }
        jpegSize = tjc.getCompressedSize();
        tjc.close();

        file = new File(argv[1]);
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(jpegBuf, 0, jpegSize);
        fos.close();
      }
      else {
        System.out.print("\n");
        file = new File(argv[1]);
        ImageIO.write(img, outFormat, file);
      }

    }
    catch(Exception e) {
      e.printStackTrace();
      System.exit(-1);
    }
  }

  public void customFilter(ShortBuffer coeffBuffer, Rectangle bufferRegion,
    Rectangle planeRegion, int componentIndex, int transformIndex,
    TJTransform transform) throws Exception {
    for(int i=0; i<bufferRegion.width*bufferRegion.height; i++) {
	    coeffBuffer.put(i, (short)(-coeffBuffer.get(i)));
    }
  }

  static TJScalingFactor sf [] = null;
};
