001 // This class is based on the FishDisplay class, version 1 August 2002
002 // by Julie Zenekski
003
004 // Original copyright notice:
005
006 // AP(r) Computer Science Marine Biology Simulation:
007 // The FishImageDisplay class is copyright(c) 2002 College Entrance
008 // Examination Board (www.collegeboard.com).
009 //
010 // This class is free software; you can redistribute it and/or modify
011 // it under the terms of the GNU General Public License as published by
012 // the Free Software Foundation.
013 //
014 // This class is distributed in the hope that it will be useful,
015 // but WITHOUT ANY WARRANTY; without even the implied warranty of
016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017 // GNU General Public License for more details.
018
019 package model.fish.display;
020
021 import sysModel.fish.IFishDisplay;
022
023 import javax.swing.*;
024 import java.awt.*;
025 import java.awt.geom.Ellipse2D;
026 import java.awt.image.FilteredImageSource;
027 import java.awt.image.RGBImageFilter;
028 import java.util.HashMap;
029 import java.net.URL;
030
031 /**
032 * Use an image file to display a fish.
033 *
034 * @author Mathias Ricken
035 */
036 public class ImageFishDisplay implements IFishDisplay {
037 private ImageIcon _icon;
038 private Image _originalImage;
039 private double _radians;
040 private HashMap<Color,Image> _tintedVersions = new HashMap<Color,Image>();
041
042 /**
043 * Make an object that knows how to display a fish as an image. Looks for the named file first in the jar file, then
044 * in the current directory. If the named file is not found or the file is malformed, a colored circle will be
045 * substituted instead.
046 *
047 * @param imageFilename name of file containing image
048 * @param radians radians the fish has to be rotated by to make it face north
049 */
050 public ImageFishDisplay(String imageFilename, double radians) {
051 URL urlInJarFile = getClass().getResource(imageFilename);
052 if (null != urlInJarFile) {
053 _icon = new ImageIcon(urlInJarFile);
054 }
055 else {
056 String path = System.getProperty("user.dir") + java.io.File.separator + imageFilename;
057 _icon = new ImageIcon(path);
058 }
059 _radians = radians;
060 _originalImage = _icon.getImage();
061 }
062
063
064 /**
065 * Draw the fish. The Graphics2D object has been set up so that the origin is in the center of the fish. A fish that
066 * is 32x32 wide should thus drawFish from (-16,-16) to (16,16).
067 *
068 * @param g graphics object to drawFish on.
069 * @param comp the component to drawFish on
070 * @param fishColor color of the fish
071 */
072 public void draw(Graphics2D g, Component comp, Color fishColor) {
073 if (MediaTracker.COMPLETE != _icon.getImageLoadStatus()) {
074 // Image failed to load, so fall back to default display.
075 g.setPaint(fishColor);
076 g.fill(new Ellipse2D.Double(-.4, -.4, .8, .8));
077 return;
078 }
079
080 // Rotate drawing surface to compensate for the direction of the fish
081 // in the image.
082 g.rotate(_radians);
083
084 // Scale to shrink or enlarge the image to fit the size 1x1 cell.
085 g.scale(1.0 / _icon.getIconWidth(), 1.0 / _icon.getIconHeight());
086
087 // Compose image with fish color using an image filter.
088 Image tinted = (Image)_tintedVersions.get(fishColor);
089 if (null == tinted) { // not cached, need new filter for color
090 FilteredImageSource src = new FilteredImageSource(_originalImage.getSource(), new TintFilter(fishColor));
091 tinted = comp.createImage(src);
092 // Cache tinted image in map by color, we're likely to need it again.
093 _tintedVersions.put(fishColor, tinted);
094 }
095
096 _icon.setImage(tinted);
097 _icon.paintIcon(comp, g, -_icon.getIconWidth() / 2, -_icon.getIconHeight() / 2);
098 }
099
100 /**
101 * An image filter class that tints colors based on the tint provided to the constructor (the color of a fish).
102 */
103 private static class TintFilter extends RGBImageFilter {
104 private int tintR;
105 private int tintG;
106 private int tintB;
107
108 /**
109 * Construct an image filter for tinting colors in an image.
110 *
111 * @param color tint
112 */
113 public TintFilter(Color color) {
114 canFilterIndexColorModel = true;
115 int rgb = color.getRGB();
116 tintR = (rgb >> 16) & 0xff;
117 tintG = (rgb >> 8) & 0xff;
118 tintB = rgb & 0xff;
119 }
120
121 public int filterRGB(int x, int y, int argb) {
122 // Separate pixel into its RGB coomponents.
123 int alpha = (argb >> 24) & 0xff;
124 int red = (argb >> 16) & 0xff;
125 int green = (argb >> 8) & 0xff;
126 int blue = argb & 0xff;
127
128 // Use NTSC/PAL algorithm to convert RGB to grayscale.
129 int lum = (int)(0.2989 * red + 0.5866 * green + 0.1144 * blue);
130
131 // Interpolate along spectrum black->white with tint at midpoint
132 double scale = Math.abs((lum - 128) / 128.0); // absolute distance from midpt
133 int edge = 128 > lum ? 0 : 255; // going towards white or black?
134 red = tintR + (int)((edge - tintR) * scale); // scale from midpt to edge
135 green = tintG + (int)((edge - tintG) * scale);
136 blue = tintB + (int)((edge - tintB) * scale);
137 return (alpha << 24) | (red << 16) | (green << 8) | blue;
138 }
139 }
140 }
141