/*
 * $Id: DCTDecode.java,v 1.3 2010-06-14 17:32:08 lujke Exp $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.sun.pdfview.decode;

import org.monte.media.jpeg.JPEGImageIO;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.nio.ByteBuffer;

import com.sun.pdfview.PDFObject;
import com.sun.pdfview.PDFParseException;
import com.sun.pdfview.colorspace.PDFColorSpace;
import java.awt.image.Raster;
import java.io.ByteArrayInputStream;

/**
 * decode a DCT encoded array into a byte array.  This class uses Java's
 * built-in JPEG image class to do the decoding.
 *
 * @author Mike Wessler
 */
public class DCTDecode {

    /**
     * decode an array of bytes in DCT format.
     * <p>
     * DCT is the format used by JPEG images, so this class simply
     * loads the DCT-format bytes as an image, then reads the bytes out
     * of the image to create the array.  If this were to be used
     * against image objects we'd end up wasting a lot of work, because
     * we'd be generating a buffered image here, writing out the bytes,
     * and then generating a buffered image again from those bytes in the
     * PDFImage class.
     * <p>
     * Luckily, the image processing has been optimised to detect
     * DCT decodes at the end of filters, in which case it avoids
     * running the stream through this filter, and just directly
     * generates a BufferedImage from the DCT encoded byte stream.
     * As such, this decode will be invoked only if there's been
     * some very unusual employment of filters in the PDF - e.g.,
     * DCTDecode applied to non-image data, or if DCTDecode is not at
     * the end of a Filter dictionary entry. This is permissible but
     * unlikely to occur in practice.
     * <p>
     * The DCT-encoded stream may have 1, 3 or 4 samples per pixel, depending
     * on the colorspace of the image.  In decoding, we look for the colorspace
     * in the stream object's dictionary to decide how to decode this image.
     * If no colorspace is present, we guess 3 samples per pixel.
     *
     * @param dict the stream dictionary
     * @param buf the DCT-encoded buffer
     * @param params the parameters to the decoder (ignored)
     * @return the decoded buffer
     */
    
    protected static ByteBuffer decode(PDFObject dict, ByteBuffer buf,
        PDFObject params) throws PDFParseException
    {
// BEGIN PATCH W. Randelshofer Completely rewrote decode routine in order to
// support JPEG images in the CMYK color space.
        buf.rewind();
        byte[] input = new byte[buf.remaining()];
        buf.get(input);
	byte[] output = null;
        try {
            BufferedImage bimg = JPEGImageIO.read(new ByteArrayInputStream(input), false);
            Raster raster = bimg.getRaster();

            //---
            int type = bimg.getType();

        if (type == BufferedImage.TYPE_INT_RGB) {
            // read back the data
            DataBufferInt db = (DataBufferInt) bimg.getData().getDataBuffer();
            int[] data = db.getData();

            output = new byte[data.length*3];
            for (int i=0, offset = 0; i<data.length; i++,offset+=3) {
                output[offset]= (byte)(data[i]>>16);
                output[offset+1]= (byte)(data[i]>>8);
                output[offset+2]= (byte)(data[i]);
            }
        } else if (type == BufferedImage.TYPE_BYTE_GRAY) {
            DataBufferByte db = (DataBufferByte) bimg.getData().getDataBuffer();
            output = db.getData();
        } else if (type == BufferedImage.TYPE_INT_ARGB) {
            // read back the data
            DataBufferInt db = (DataBufferInt) bimg.getData().getDataBuffer();
            int[] data = db.getData();

            output = new byte[data.length*4];
            for (int i=0, offset=0; i<data.length; i++,offset+=4) {
                output[offset]= (byte)(data[i]>>24);
                output[offset+1]= (byte)(data[i]>>16);
                output[offset+2]= (byte)(data[i]>>8);
                output[offset+3]= (byte)(data[i]);
            }
        } else {
             // The raster is in some other format.
             // We have to convert it into TYPE_INT_RGB before we can use it.
             BufferedImage tmp = new BufferedImage(bimg.getWidth(), bimg.getHeight(), BufferedImage.TYPE_INT_RGB);
             Graphics g = tmp.createGraphics();
             g.drawImage(bimg, 0, 0, null);
             g.dispose();
            // read back the data
            DataBufferInt db = (DataBufferInt) tmp.getData().getDataBuffer();
            int[] data = db.getData();

            output = new byte[data.length*3];
            for (int i=0, offset = 0; i<data.length; i++,offset+=3) {
                output[offset]= (byte)(data[i]>>16);
                output[offset+1]= (byte)(data[i]>>8);
                output[offset+2]= (byte)(data[i]);
            }
            tmp.flush();
        }
        } catch (IOException ex) {
            System.err.println("DCTDecode failed");
            ex.printStackTrace();
            /*
            PDFParseException ex2 = new PDFParseException("DCTDecode failed");
            ex2.initCause(ex);
            throw ex2;*/
        }
	//	System.out.println("Translated data");
	return ByteBuffer.wrap(output);
    }
// END PATCH W. Randelshofer Completely rewrote decode routine in order to
// support JPEG images in the CMYK color space.

    protected static ByteBuffer decodeORIGINAL(PDFObject dict, ByteBuffer buf,
        PDFObject params) throws PDFParseException
    {
	//	System.out.println("DCTDecode image info: "+params);
        buf.rewind();
        
        // copy the data into a byte array required by createimage
        byte[] ary = new byte[buf.remaining()];
        buf.get(ary);
        
        // wait for the image to get drawn
	Image img= Toolkit.getDefaultToolkit().createImage(ary);
	MyTracker mt= new MyTracker(img);
	mt.waitForAll();
        
        // the default components per pixel is 3
        int numComponents = 3;
        
        // see if we have a colorspace
        try {
            PDFObject csObj = dict.getDictRef("ColorSpace");
            if (csObj != null) {
                // we do, so get the number of components
                PDFColorSpace cs = PDFColorSpace.getColorSpace(csObj, null);
                numComponents = cs.getNumComponents();
            }
        } catch (IOException ioe) {
            // oh well
        }
        
        
        // figure out the type
        int type = BufferedImage.TYPE_INT_RGB;
        if (numComponents == 1) {
            type = BufferedImage.TYPE_BYTE_GRAY;
        } else if (numComponents == 4) {
            type = BufferedImage.TYPE_INT_ARGB;
        }
        
        // create a buffered image
        BufferedImage bimg = new BufferedImage(img.getWidth(null),
					       img.getHeight(null),
					       type);
        Graphics bg= bimg.getGraphics();
        
        // draw the image onto it
	bg.drawImage(img, 0, 0, null);
        
	byte[] output = null;

        // incidentally, there's a bit of an optimisation we could apply here,
        // if we weren't pretty confident that this isn't actually going to
        // be called, anyway. Namely, if we just use JAI to read in the data
        // the underlying data buffer seems to typically be byte[] based,
        // and probably already in the desired arrangement (and if not, that
        // could be engineered by supplying our own sample model). As it is,
        // we won't bother, since this code is most likely not going
        // to be used.        

        if (type == BufferedImage.TYPE_INT_RGB) {
            // read back the data
            DataBufferInt db = (DataBufferInt) bimg.getData().getDataBuffer();
            int[] data = db.getData();
        
            output = new byte[data.length*3];
            for (int i=0; i<data.length; i++) {
                output[i*3]= (byte)(data[i]>>16);
                output[i*3+1]= (byte)(data[i]>>8);
                output[i*3+2]= (byte)(data[i]);
            }
        } else if (type == BufferedImage.TYPE_BYTE_GRAY) {
            DataBufferByte db = (DataBufferByte) bimg.getData().getDataBuffer();
            output = db.getData(); 
        } else if (type == BufferedImage.TYPE_INT_ARGB) {
            // read back the data
            DataBufferInt db = (DataBufferInt) bimg.getData().getDataBuffer();
            int[] data = db.getData();
        
            output = new byte[data.length*4];
            for (int i=0; i<data.length; i++) {
                output[i*4]= (byte)(data[i]>>24);
                output[i*4+1]= (byte)(data[i]>>16);
                output[i*4+2]= (byte)(data[i]>>8);
                output[i*4+3]= (byte)(data[i]);
            }
        }
        
	//	System.out.println("Translated data");
	return ByteBuffer.wrap(output);
    }
}

/**
 * Image tracker.  I'm not sure why I'm not using the default Java
 * image tracker for this one.
 */
class MyTracker implements ImageObserver {
    boolean done= false;
    
    /**
     * create a new MyTracker that watches this image.  The image
     * will start loading immediately.
     */
    public MyTracker(Image img) {
	img.getWidth(this);
    }
    
    /**
     * More information has come in about the image.
     */
    public boolean imageUpdate(Image img, int infoflags, int x, int y,
			       int width, int height) {
	if ((infoflags & (ALLBITS | ERROR | ABORT))!=0) {
	    synchronized(this) {
		done= true;
		notifyAll();
	    }
	    return false;
	}
	return true;
    }
    
    /**
     * Wait until the image is done, then return.
     */
    public synchronized void waitForAll() {
	if (!done) {
	    try {
		wait();
	    } catch (InterruptedException ie) {}
	}
    }
}

