/**
 * Scale2x.java
 * 
 * Written by Markus Persson of Mojang Specifications for a super mario programming contest.
 * Implements the Scale2x algorithm described here: http://scale2x.sourceforge.net/algorithm.html
 * Works on any input image size, and uses a fancy border hack to prevent range checking.
 * It's fast enough for real time use on smaller images (320x240 and thereabouts)
 * 
 * This code is public domain. Do whatever you want with it.
 */
package dk.itu.mario.engine;

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;


public class Scale2x
{
    private int width;
    private int height;

    private BufferedImage sourceImage;
    private int[] sourcePixels;
    private Graphics sourceGraphics;

    private BufferedImage targetImage;
    private int[] targetPixels;

    /**
     * Creates a new Scale2x object. The new object will scale images of the specified size to images
     * that are twice as large.<br>
     * 
     * @param width The width of the images to be scaled
     * @param height The height of the images to be scaled
     */
    public Scale2x(int width, int height)
    {
        this.width = width;
        this.height = height;

        // A border of one pixel in each direction, and one down, to avoid if statements in the scale loop
        sourceImage = new BufferedImage(width + 2, height + 3, BufferedImage.TYPE_INT_RGB);
        DataBufferInt sourceDataBuffer = (DataBufferInt) sourceImage.getRaster().getDataBuffer();
        sourcePixels = sourceDataBuffer.getData();
        sourceGraphics = sourceImage.getGraphics();

        targetImage = new BufferedImage(width * 2, height * 2, BufferedImage.TYPE_INT_RGB);
        DataBufferInt targetDataBuffer = (DataBufferInt) targetImage.getRaster().getDataBuffer();
        targetPixels = targetDataBuffer.getData();
    }

    /**
     * Scales an image and returns a twice as large image.<br>
     * This assumes the input image is of the dimensions specified in the Scale2x constructor.<br>
     * The returned image is a reference to the internal scale target in this Scale2x, so it
     * will get changed if you call this method again, so don't hold on to it for too long.<br>
     * In other words:<br>
     * <code>Image i0 = scale2x.scale(image0);<br>
     * Image i1 = scale2x.scale(image1);<br>
     * if (i0 == i1) System.exit(0); // Will always terminate</code><br>
     * 
     * @param img The image to be scaled
     * @returns A scaled image. If you want that image to survive the next call to this method, make a copy of it.
     */
    public Image scale(Image img)
    {
        // Offset the image by one pixel so there's a border around it.
        // This lets us avoid having to check that A-I are in range of the image before samping them
        sourceGraphics.drawImage(img, 1, 1, null);

        int line = width + 2;
        for (int y = 0; y < height; y++)
        {
            // Two lines of target pixel pointers
            int tp0 = y * width * 4 - 1;
            int tp1 = tp0 + width * 2;

            // Three lines of source pixel pointers
            int sp0 = (y) * line;
            int sp1 = (y + 1) * line;
            int sp2 = (y + 2) * line;

            // Fill the initial A-I values
            int A = sourcePixels[sp0];
            int B = sourcePixels[++sp0];
            int C = sourcePixels[++sp0];
            int D = sourcePixels[sp1];
            int E = sourcePixels[++sp1];
            int F = sourcePixels[++sp1];
            int G = sourcePixels[sp2];
            int H = sourcePixels[++sp2];
            int I = sourcePixels[++sp2];

            for (int x = 0; x < width; x++)
            {
                if (B != H && D != F)
                {
                    targetPixels[++tp0] = D == B ? D : E;
                    targetPixels[++tp0] = B == F ? F : E;
                    targetPixels[++tp1] = D == H ? D : E;
                    targetPixels[++tp1] = H == F ? F : E;
                }
                else
                {
                    targetPixels[++tp0] = E;
                    targetPixels[++tp0] = E;
                    targetPixels[++tp1] = E;
                    targetPixels[++tp1] = E;
                }

                // Scroll A-I left
                A = B;
                B = C;
                D = E;
                E = F;
                G = H;
                H = I;

                // Resample rightmost edge
                C = sourcePixels[++sp0];
                F = sourcePixels[++sp1];
                I = sourcePixels[++sp2];
            }
        }

        return targetImage;
    }
}