import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
import javax.media.*;
import javax.media.renderer.*;
import javax.media.format.*;

/**
 * A Canvas that implements the VideoRenderer interface
 * and, hence, can be used to display video.
 *
 * This particular implementation can only be used with RGB
 * .mov files
 *
 * @version 1.0
 * @author  Prof. David Bernstein, James Madison University
 */
public class RGBRenderer implements VideoRenderer {

    protected boolean              started;
    protected Buffer               lastBuffer;
    protected Component            component;
    protected int                  inputHeight, inputWidth;
    protected Format[]             supportedFormats;
    protected Image                destImage;
    protected MemoryImageSource    sourceImage;
    protected Rectangle            bounds;
    protected RGBFormat            inputFormat, supportedRGB;


    protected static final int rMask = 0x000000FF;
    protected static final int gMask = 0x0000FF00;
    protected static final int bMask = 0x00FF0000;


    /**
     * Constructor
     */
    public RGBRenderer() 
    {
	lastBuffer = null;
	component  = null;
	bounds     = null;
	started    = false;

	inputWidth  = 0;
	inputHeight = 0;

	// Prepare supported input formats and preferred format
	supportedRGB = new RGBFormat(null,      // size
			  Format.NOT_SPECIFIED, // maxDataLength
			  int[].class,          // buffer type
			  Format.NOT_SPECIFIED, // frame rate
			  32,                   // bitsPerPixel
			  rMask, gMask, bMask,  // component masks
			  1,                    // pixel stride
			  Format.NOT_SPECIFIED, // line stride
			  Format.FALSE,         // flipped
			  Format.NOT_SPECIFIED  // endian
			  );

	supportedFormats = new VideoFormat[1];
	supportedFormats[0] = supportedRGB;
    }



    /**
     * Close the Renderer (required by VideoRenderer)
     */
    public void close() 
    {
	// Do nothing
    }



    /**
     * Get the portion of the Component being used by 
     * this Renderer (required by VideoRenderer)
     *
     * @return  The Rectangle used (null for the whole Component)
     */
    public Rectangle getBounds() 
    {
	return bounds;
    }




    /**
     * Get the Component that this Renderer is 
     * using (required by VideoRenderer)
     *
     * @return  The Component
     */
    public Component getComponent() 
    {
	if (component == null) {

	    component = new RendererCanvas(this);
	}

	return component;
    }





    /**
     * Get the Controls for this renderer 
     * (required by VideoRenderer)
     *
     * Since this implementation does not support Controls it
     * returns an empty array.
     *
     * @return   The controls
     */
    public Object[] getControls() 
    {
	return (Object[]) new Control[0];
    }



    /**
     * Get a Control based on a control type 
     * (required by VideoRenderer)
     *
     * Since this implementation does not support Controls it
     * returns an empty array.
     *
     * @return   The controls
     */
    public Object getControl(String controlType) 
    {
	return null;
    }



    /**
     * Get the name of this Renderer 
     * (required by VideoRenderer)
     *
     * @return The name
     */
    public String getName() {

	return "RendererCanvas";
    }



    /**
     * Get the size of the input data
     *
     * @return The size (in pixels)
     */
    public Dimension getSize()
    {
	return new Dimension(inputWidth, inputHeight);
    }



    /**
     * Get the array of supported formats 
     * (required by VideoRenderer)
     *
     * @return  The array of supported formats
     */
    public Format[] getSupportedInputFormats() 
    {
	return supportedFormats;
    }



    /**
     * Open this Renderer (required by VideoRenderer)
     *
     */
    public void open() throws ResourceUnavailableException 
    {
	sourceImage = null;
	destImage   = null;
	lastBuffer  = null;
    }



    /**
     * Paint the current image
     *
     * @param g   The Graphics context
     */
    public void paintImage(Graphics g)
    {
	Rectangle tempBounds;


	if ( (g == null) || (destImage == null) ||
             (component == null)) return;

	if (bounds == null) {

	    tempBounds = component.getBounds();
	    tempBounds.x = 0;
	    tempBounds.y = 0;
	    
	} else {

	    tempBounds = bounds;
	}

	g.drawImage(destImage, tempBounds.x, tempBounds.y,
		    tempBounds.width, tempBounds.height,
		    0, 0, inputWidth, inputHeight, component);
    }





    /**
     * Process the input data and render it
     *
     * @param buffer  The input data
     * @return        BUFFER_PROCESSED_OK if successful
     */
    public synchronized int process(Buffer buffer) 
    {
	Object    data;
	Format    inf;
	Graphics  g;

	// Initial error checking
	if (component == null) return BUFFER_PROCESSED_FAILED;

	inf = buffer.getFormat();
	if (inf == null)       return BUFFER_PROCESSED_FAILED;

	if ( (inf != inputFormat) || 
	     (!buffer.getFormat().equals(inputFormat)) ) {

	    if (setInputFormat(inf)!=null) {
		return BUFFER_PROCESSED_FAILED;
	    }
	}

	data = buffer.getData();
	if (!(data instanceof int[])) {
               return BUFFER_PROCESSED_FAILED;
	}



	// Check to see if the data have changed
	if (lastBuffer != buffer) {

	    lastBuffer = buffer;
	    buildImage(buffer);
	}


	// Render the image
	sourceImage.newPixels(0, 0, inputWidth, inputHeight);
	g = component.getGraphics();

	if (g != null) paintImage(g);

	return BUFFER_PROCESSED_OK;
    }





    /** 
     * Reset the Renderer (required by VideoRenderer)
     *
     */
    public void reset() 
    {
	// Do nothing
    }



    /**
     * Set the portion of the Component to use 
     * (required by VideoRenderer)
     *
     * @param rect  The area to use (null for the whole Component)
     */
    public void setBounds(Rectangle bounds) 
    {
	this.bounds = bounds;
    }




    /**
     * Set the Component that the Renderer should 
     * use (required by VideoRenderer)
     *
     * @return false if the Renderer can't use the Component
     */
    public boolean setComponent(Component comp) 
    {

	component = comp;
	return true;
    }



    /**
     * Set the input format (required by VideoRenderer)
     *
     * @param format  The Format of the input data
     * @return        The Format that was set or null
     */
    public Format setInputFormat(Format format) 
    {
	Dimension  size;
	Format     retval;

	if ( (format != null) && 
	     (format instanceof RGBFormat) &&
	     (format.matches(supportedRGB))    ) {

	    inputFormat = (RGBFormat)format;

	    size = inputFormat.getSize();
	    inputWidth  = size.width;
	    inputHeight = size.height;

	    retval = format;

	} else {

	    retval = null;
	}

	return retval;
    }





    /**
     * Start the Renderer (required by VideoRenderer)
     */
    public void start() 
    {
	started = true;
    }



    /**
     * Stop the Renderer (required by VideoRenderer)
     */
    public void stop() 
    {
	started = false;
    }




    /**
     * Build a new image from the input data
     *
     * @param buffer  The input data
     */
    protected void buildImage(Buffer buffer) 
    {
	DirectColorModel  colorModel;
	Object            data;
	RGBFormat         format;


	data = buffer.getData();

	if (!(data instanceof int[])) return;

	format = (RGBFormat) buffer.getFormat();

	colorModel=new DirectColorModel(format.getBitsPerPixel(),
				        format.getRedMask(),
					format.getGreenMask(),
					format.getBlueMask());

	sourceImage=new MemoryImageSource(format.getLineStride(),
					  format.getSize().height,
					  colorModel,
					  (int[])data, 0,
					  format.getLineStride());
	sourceImage.setAnimated(true);

	sourceImage.setFullBufferUpdates(true);

	if (component != null) {

	    destImage = component.createImage(sourceImage);
	    component.prepareImage(destImage, component);
	}
    }
}
    
    
