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