/*
* AP(r) Computer Science GridWorld Case Study:
* Copyright(c) 2002-2006 College Entrance Examination Board
* (http://www.collegeboard.com).
*
* This code 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.
*
* This code 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.
*
* @author Julie Zelenski
* @author Cay Horstmann
*/
package info.gridworld.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.FilteredImageSource;
import java.awt.image.RGBImageFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
/**
* An ImageDisplay displays an object as a tinted image from an image file whose
* name matches the class name. <br />
* This code is not tested on the AP CS A and AB exams. It contains GUI
* implementation details that are not intended to be understood by AP CS
* students.
*/
public class ImageDisplay extends AbstractDisplay
{
private Class cl;
private String imageFilename;
private static final String imageExtension = ".gif";
private Map<String, Image> tintedVersions = new HashMap<String, Image>();
/**
* Constructs an object that knows how to display an image. Looks for the
* named file first in the jar file, then in the current directory.
* @param imageFilename name of file containing image
*/
public ImageDisplay(Class cl) throws IOException
{
this.cl = cl;
imageFilename = cl.getName().replace('.', '/');
URL url = cl.getClassLoader().getResource(
imageFilename + imageExtension);
if (url == null)
throw new FileNotFoundException(imageFilename + imageExtension
+ " not found.");
tintedVersions.put("", ImageIO.read(url));
}
/**
* Draws a unit-length object facing North. This implementation draws an
* object by tinting, scaling, and rotating the image in the image file.
* @param obj object we want to draw
* @param comp the component we're drawing on
* @param g2 drawing surface
*/
public void draw(Object obj, Component comp, Graphics2D g2)
{
Color color;
if (obj == null)
color = null;
else
color = (Color) getProperty(obj, "color");
String imageSuffix = (String) getProperty(obj, "imageSuffix");
if (imageSuffix == null)
imageSuffix = "";
// Compose image with color using an image filter.
Image tinted = tintedVersions.get(color + imageSuffix);
if (tinted == null) // not cached, need new filter for color
{
Image untinted = tintedVersions.get(imageSuffix);
if (untinted == null) // not cached, need to fetch
{
try
{
URL url = cl.getClassLoader().getResource(
imageFilename + imageSuffix + imageExtension);
if (url == null)
throw new FileNotFoundException(imageFilename
+ imageSuffix + imageExtension + " not found.");
untinted = ImageIO.read(url);
tintedVersions.put(imageSuffix, untinted);
}
catch (IOException ex)
{
untinted = tintedVersions.get("");
}
}
if (color == null)
tinted = untinted;
else
{
FilteredImageSource src = new FilteredImageSource(untinted
.getSource(), new TintFilter(color));
tinted = comp.createImage(src);
// Cache tinted image in map by color, we're likely to need it
// again.
tintedVersions.put(color + imageSuffix, tinted);
}
}
int width = tinted.getWidth(null);
int height = tinted.getHeight(null);
int size = Math.max(width, height);
// Scale to shrink or enlarge the image to fit the size 1x1 cell.
g2.scale(1.0 / size, 1.0 / size);
g2.clip(new Rectangle(-width / 2, -height / 2, width, height));
g2.drawImage(tinted, -width / 2, -height / 2, null);
}
/**
* An image filter class that tints colors based on the tint provided to the
* constructor (the color of an object).
*/
private static class TintFilter extends RGBImageFilter
{
private int tintR, tintG, tintB;
/**
* Constructs an image filter for tinting colors in an image.
* @param color the tint color
*/
public TintFilter(Color color)
{
canFilterIndexColorModel = true;
int rgb = color.getRGB();
tintR = (rgb >> 16) & 0xff;
tintG = (rgb >> 8) & 0xff;
tintB = rgb & 0xff;
}
public int filterRGB(int x, int y, int argb)
{
// Separate pixel into its RGB coomponents.
int alpha = (argb >> 24) & 0xff;
int red = (argb >> 16) & 0xff;
int green = (argb >> 8) & 0xff;
int blue = argb & 0xff;
// Use NTSC/PAL algorithm to convert RGB to gray level
double lum = (0.2989 * red + 0.5866 * green + 0.1144 * blue) / 255;
// interpolate between tint and pixel color. Pixels with
// gray level 0.5 are colored with the tint color,
// white and black pixels stay unchanged.
// We use a quadratic interpolation function
// f(x) = 1 - 4 * (x - 0.5)^2 that has
// the property f(0) = f(1) = 0, f(0.5) = 1
// Note: Julie's algorithm used a linear interpolation
// function f(x) = min(2 - 2 * x, 2 * x);
// and it interpolated between tint and
// (lum < 0.5 ? black : white)
double scale = 1 - (4 * ((lum - 0.5) * (lum - 0.5)));
red = (int) (tintR * scale + red * (1 - scale));
green = (int) (tintG * scale + green * (1 - scale));
blue = (int) (tintB * scale + blue * (1 - scale));
return (alpha << 24) | (red << 16) | (green << 8) | blue;
}
}
}