Programming-Language-Compiler / edu / ufl / cise / plc / runtime / ImageOps.java
ImageOps.java
Raw
package edu.ufl.cise.plc.runtime;

import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;

/**
 * An image is represented by a 2D array of pixels. An image is implemented 
 * as an instance of java.awt.image.BufferedImage using the default color model.  
 *
 * The BufferedImage class offers a way to get and set the pixel value,  
 * getRGB(int x, int y), which returns an int, and setRGB(int x, int y, int rgb);  
 * In these methods, pixels are represented as an int that encodes the 3 colors 
 * plus the alpha (transparency) value.  
 * 
 * In PLCLang, we do not deal with the alpha value--whenever required, it is set to 0xff.
 * 
 * In our language, the type color is represented by an instance of the ColorTuple class, 
 * or in some cases by an instance of the ColorTupleFloat class.
 * 
 * In an image, the color of a pixel is packed into an int, where 8 bits are available 
 * for the alpha, red, green, and blue components.  The ColorTuple
 * class provides methods for converting between a ColorTuple and packed int 
 * representation. 
 * 
 */

public class ImageOps {
	
	/**  
	 * returns the pixel at the x,y location in the given image in packed int form.
	 * 
	 * @param image
	 * @param x
	 * @param y
	 * @return
	 */
	public static int getPackedColor(BufferedImage image, int x, int y) {
		return image.getRGB(x,y);
	}
	
	/**
	 * returns the pixel at the x,y location in the given image in ColorTuple form.
	 * @param image
	 * @param x
	 * @param y
	 * @return
	 */
	public static ColorTuple getColorTuple(BufferedImage image, int x, int y) {
		int packedColor = image.getRGB(x, y);
		return ColorTuple.unpack(packedColor);
	}
	
	/**
	 * sets the pixel at the x,y location in the given image to the given int value.
	 * 
	 * @param image
	 * @param x
	 * @param y
	 * @param packedColor
	 */
	public static void setColor(BufferedImage image, int x, int y, int packedColor) {
		image.setRGB(x, y, packedColor);
	}
	
	/**
	 * Sets the pixel at the x,y location in the given image to the value represented 
	 * by the given ColorTuple.  The color values will be truncated if necessary.  
	 * 
	 * @param image
	 * @param x
	 * @param y
	 * @param colorTuple
	 */
	public static void setColor(BufferedImage image, int x, int y, ColorTuple colorTuple) {
		image.setRGB(x, y, colorTuple.pack());
	}
	
	/**
	 * Returns a new image containing only the red component of the given image.  
	 * This method can be used to implement the getRed operator applied to an image.
	 * 
	 * @param image
	 * @return
	 */
	public static BufferedImage extractRed(BufferedImage image) {
		int width = image.getWidth();
		int height = image.getHeight();
		BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				int pixel = getPackedColor(image, x, y);
				int red = ColorTuple.getRed(pixel);
				int redPixel = ColorTuple.makePackedColor(red, 0, 0);
				newImage.setRGB(x, y, redPixel);
			}
		}
		return newImage;
	}

	/**
	 * Returns a new image containing only the green component of the given image.  
	 * This can be used to implement the getGreen operator applied to an image.
	 * 
	 * @param image
	 * @return
	 */
	public static BufferedImage extractGreen(BufferedImage image) {
		int width = image.getWidth();
		int height = image.getHeight();
		BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				int pixel = getPackedColor(image, x, y);
				int green = ColorTuple.getGreen(pixel);
				int greenPixel = ColorTuple.makePackedColor(0, green, 0);
				newImage.setRGB(x, y, greenPixel);
			}
		}
		return newImage;
	}

	/**
	 * Returns a new image containing only the blue component of the given image.  
	 * This can be used to implement the getBlue operator applied to an image.
	 * 
	 * @param image
	 * @return
	 */	
	public static BufferedImage extractBlue(BufferedImage image) {
		int width = image.getWidth();
		int height = image.getHeight();
		BufferedImage newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				int pixel = getPackedColor(image, x, y);
				int blue = ColorTuple.getBlue(pixel);
				int bluePixel = ColorTuple.makePackedColor(0, 0, blue);
				newImage.setRGB(x, y, bluePixel);
			}
		}
		return newImage;
	}


	public enum OP {
		PLUS, MINUS, TIMES, DIV, MOD
	}
	
	public enum BoolOP {
		EQUALS, NOT_EQUALS
	}
	
	
	/**
	 * Returns a new ColorTuple object obtained by applying given operator componentwise to the given
	 * ColorTuple objects.
	 * 
	 * @param op
	 * @param left
	 * @param right
	 * @return
	 */
	public static ColorTuple binaryTupleOp(OP op, ColorTuple left, ColorTuple right) {
		return switch(op) {
		case PLUS -> new ColorTuple (left.red() + right.red(), left.green() + right.green(), left.blue() + right.blue());
		case MINUS -> new ColorTuple (left.red() - right.red(), left.green() - right.green(), left.blue() - right.blue());
		case TIMES -> new ColorTuple (left.red() * right.red(), left.green() * right.green(), left.blue() * right.blue());
		case DIV -> new ColorTuple (left.red() / right.red(), left.green() / right.green(), left.blue() / right.blue());
		case MOD -> new ColorTuple (left.red() % right.red(), left.green() % right.green(), left.blue() % right.blue());
		default -> throw new IllegalArgumentException("Compiler/runtime error Unexpected value: " + op);
		};
	}
	
	
	/**
	 * Returns a new ColorTupleFloat object obtained by applying given operator componentwise to the given
	 * ColorTupleFloat objects.
	 * 
	 * @param op
	 * @param left
	 * @param right
	 * @return
	 */
	public static ColorTupleFloat binaryTupleOp(OP op, ColorTupleFloat left, ColorTupleFloat right) {
		return  switch(op) {
		case PLUS -> new ColorTupleFloat (left.red() + right.red(), left.green() + right.green(), left.blue() + right.blue());
		case MINUS -> new ColorTupleFloat (left.red() - right.red(), left.green() - right.green(), left.blue() - right.blue());
		case TIMES -> new ColorTupleFloat (left.red() * right.red(), left.green() * right.green(), left.blue() * right.blue());
		case DIV -> new ColorTupleFloat (left.red() / right.red(), left.green() / right.green(), left.blue() / right.blue());
		case MOD -> new ColorTupleFloat (left.red() % right.red(), left.green() % right.green(), left.blue() % right.blue());
		};
	}
	
	/**
	 * Applies operator to two ColorTuples and returns boolean value
	 * 
	 * @param op
	 * @param left
	 * @param right
	 * @return
	 */
	public static boolean binaryTupleOp(BoolOP op, ColorTuple left, ColorTuple right) {
		return switch(op) {
		case EQUALS -> left.equals(right);
		case NOT_EQUALS ->  !left.equals(right);
		default -> throw new IllegalArgumentException("Compiler/runtime error Unexpected value: " + op);
		};
	}
	
	/**
	 * Returns a new BufferedImage obtained by applying the given binary operator
	 * to each color component in each pixel in the given images.
	 * 
	 * If the images do not have the same shape, a PLCRuntimeException is thrown.
	 * 
	 * @param op
	 * @param left
	 * @param right
	 * @return
	 */
	
	public static BufferedImage binaryImageImageOp(OP op, BufferedImage left, BufferedImage right) {
		int lwidth = left.getWidth();
		int rwidth = right.getWidth();
		int lheight = left.getHeight();
		int rheight = right.getHeight();
		if (lwidth != rwidth || lheight != rheight) {
			throw new PLCRuntimeException("Attempting binary operation on images with unequal sizes");
		}
		BufferedImage result = new BufferedImage(lwidth, lheight, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < lwidth; x++) {
			for (int y = 0; y < lheight; y++) {
				ColorTuple leftColor = ColorTuple.unpack(left.getRGB(x, y));
				ColorTuple rightColor = ColorTuple.unpack(right.getRGB(x, y));
				ColorTuple newColor = binaryTupleOp(op, leftColor, rightColor);
				result.setRGB(x, y, newColor.pack());
			}
		}
		return result;
	}
	
	/**
	 * Returns a new buffered image obtained by applying the given binary operation 
	 * to each color component in each pixel in the given image (left) and the int value (right).
	 * 
	 * @param op
	 * @param left
	 * @param right
	 * @return
	 */
	public static BufferedImage binaryImageScalarOp(OP op, BufferedImage left, int right) {
		int lwidth = left.getWidth();
		int lheight = left.getHeight();
		BufferedImage result = new BufferedImage(lwidth, lheight, BufferedImage.TYPE_INT_RGB);
		ColorTuple rightColor = new ColorTuple(right);
		for (int x = 0; x < lwidth; x++) {
			for (int y = 0; y < lheight; y++) {
				ColorTuple leftColor = ColorTuple.unpack(left.getRGB(x, y));
				ColorTuple newColor = binaryTupleOp(op, leftColor, rightColor);
				result.setRGB(x, y, newColor.pack());
			}
		}
		return result;
	}
	
	public static BufferedImage binaryImageScalarOp(OP op, BufferedImage left, float right) {
		int lwidth = left.getWidth();
		int lheight = left.getHeight();
		BufferedImage result = new BufferedImage(lwidth, lheight, BufferedImage.TYPE_INT_RGB);
		ColorTupleFloat rightColor = new ColorTupleFloat(right);
		for (int x = 0; x < lwidth; x++) {
			for (int y = 0; y < lheight; y++) {
				ColorTupleFloat leftColor = new ColorTupleFloat(ColorTuple.unpack(left.getRGB(x, y)));
				ColorTupleFloat newColor = binaryTupleOp(op, leftColor, rightColor);
				result.setRGB(x, y, newColor.pack());
			}
		}
		return result;
	}
//	
//	
//	public static BufferedImage setAllPixels(BufferedImage image, int val) {
//		ColorTuple c = new ColorTuple(val);
//		for (int x = 0; x < image.getWidth(); x++)
//			for (int y = 0; y < image.getHeight(); y++) {
//				image.setRGB(x, y,  c.pack());
//			}
//		return image;
//	}

	
//	/**
//	 * Creates an image of given size.  All color components of all pixels have the indicated 
//	 * color intensity. 
//	 * 
//	 * @param width
//	 * @param height
//	 * @param colorComponentVal
//	 * @return
//	 */
//	public static BufferedImage makeConstantImage(int width, int height, int colorComponentVal) {
//		BufferedImage image = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);
//		return setAllPixels(image,colorComponentVal);
//	}
//	

	/**
	 * Returns a new image that is copy of the given BufferedImage 
	 * @param image
	 * @return new image that is copy of the given image
	 */
	public static final BufferedImage clone(BufferedImage image) {
	    BufferedImage clone = new BufferedImage(image.getWidth(),
	            image.getHeight(), image.getType());
	    Graphics2D g2d = clone.createGraphics();
	    g2d.drawImage(image, 0, 0, null);
	    g2d.dispose();
	    return clone;
	}
	
	/**
	 * Returns a new image that is a resized version of the 'before' image.
	 * 
	 * @param image
	 * @param maxX
	 * @param maxY
	 * @return new image that is a resized version of the 'before' image
	 */
	public static BufferedImage resize(BufferedImage image, int maxX,
			int maxY) {
		int w = image.getWidth();
		int h = image.getHeight();
		AffineTransform at = new AffineTransform();
		at.scale(((float) maxX) / w, ((float) maxY) / h);
		AffineTransformOp scaleOp = new AffineTransformOp(at,
				AffineTransformOp.TYPE_BILINEAR);
		BufferedImage newResizedImage= null;
		newResizedImage = scaleOp.filter(image, newResizedImage);
		return newResizedImage;
	}
	

	/**
	 * Returns an array of ints representing the packed pixels of the given image.
	 * 
	 * This can be used in Junit test to compare two images by using 
	 * assertArrayEquals on the array return from this method.
	 * 
	 * @param result
	 * @return array of ints representing the packed pixels of the given image
	 */
	public static int[] getRGBPixels(BufferedImage result) {
		return result.getRGB(0,0,result.getWidth(), result.getHeight(), null,0,result.getWidth());
	}
	
}