/******************************************************************************* * Mission Control Technologies, Copyright (c) 2009-2012, United States Government * as represented by the Administrator of the National Aeronautics and Space * Administration. All rights reserved. * * The MCT platform is licensed under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * MCT includes source code licensed under additional open source licenses. See * the MCT Open Source Licenses file included with this distribution or the About * MCT Licenses dialog available at runtime from the MCT Help menu for additional * information. *******************************************************************************/ /** * MCTIcons.java Aug 18, 2008 * * This code is the property of the National Aeronautics and Space * Administration and was produced for the Mission Control Technologies (MCT) * project. * */ package gov.nasa.arc.mct.util; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.RescaleOp; import java.util.HashMap; import java.util.Map; import javax.swing.ImageIcon; /** * MCT icons for errors, warnings and component object. * */ public class MCTIcons { private static ImageIcon warningIcon32x32 = new ImageIcon(MCTIcons.class.getResource("/images/warning_32x32.png")); private static ImageIcon errorIcon32x32 = new ImageIcon(MCTIcons.class.getResource("/images/error_32x32.png")); private static ImageIcon componentIcon12x12 = new ImageIcon(MCTIcons.class.getResource("/images/object_icon.png")); // Cache processed icons for later retrieval private static Map<ImageIcon, ImageIcon> processedIcons = new HashMap<ImageIcon, ImageIcon>(); private static Map<ProcessDescription, ImageIcon> processedIconCache = new HashMap<ProcessDescription, ImageIcon>(); private static enum Icons { WARNING_ICON, ERROR_ICON, COMPONENT_ICON }; private static ImageIcon getIcon(Icons anIconEnum) { switch(anIconEnum) { case WARNING_ICON: return warningIcon32x32; case ERROR_ICON: return errorIcon32x32; case COMPONENT_ICON: return componentIcon12x12; default: return null; } } /** * Gets the warning image icon. * @return ImageIcon - the image icon. */ public static ImageIcon getWarningIcon() { return getIcon(Icons.WARNING_ICON); } /** * Gets the error image icon scaled to designated width and height. * @param width - number. * @param height - number. * @return the error image icon. */ public static ImageIcon getErrorIcon(int width, int height) { Image scaled = getIcon(Icons.ERROR_ICON).getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH); return new ImageIcon(scaled); } /** * Gets the component image icon. * @return the component image icon. */ public static ImageIcon getComponent() { return getIcon(Icons.COMPONENT_ICON); } private MCTIcons() { // no instantiation } /** * Generate an icon based on the provided hash code. Icon will exhibit * symmetry and have a generally similar appearance to other "default" * icons. * * Icons for any given hash are always identical. Icons for different * hashes are unique up to at least the first 8 bits. * * Useful when, for instance, there is a component type for which no * icon has been provided. Generated icons will not be representative * of the component's abstraction, but will generally permit that * component type to be distinguished from others (in a "this is * like that, this is not like that" sense) * * @param hash a hash code * @param sz size, in pixels (icon will be square) * @param color color to use (background transparent) * @return a generated icon */ public static ImageIcon generateIcon(int hash, int sz, Color color) { BufferedImage image = new BufferedImage(sz,sz,BufferedImage.TYPE_4BYTE_ABGR); Graphics2D g = image.createGraphics(); g.setColor(color); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Draw corners int w = hash & 3; hash>>>=1; // Only shift one; Will get shifted again BEFORE it's checked if (w > 0) { w+=1; w*= (sz/14); g.fillRect(1,1,w,w); g.fillRect(sz-w,sz-w,w,w); g.fillRect(sz-w,1,w,w); g.fillRect(1,sz-w,w,w); } // Draw concentric circles for (int radius = 1; radius < sz/2; radius += sz/7) { if (((hash>>>=1) & 1) != 0) { if (radius < 3) { g.fillOval(sz/2-1, sz/2-1, 3, 3); } else { g.drawOval(sz/2-radius, sz/2-radius, radius*2, radius*2); } } } // Draw concentric Squares for (int radius = 3; radius < sz/2; radius += sz/7) { if (((hash>>>=1) & 1) != 0) { g.drawRect(sz/2-radius, sz/2-radius, radius*2, radius*2); } } // Draw top/bottom dots if (((hash>>>=1) & 1) != 0) { g.fillOval(1,sz/2-sz/14,sz/7,sz/7+1); g.fillOval(sz-sz/7,sz/2-sz/14,sz/7,sz/7+1); } if (((hash>>>=1) & 1) != 0) { g.fillOval(sz/2-sz/14,1,sz/7+1,sz/7); g.fillOval(sz/2-sz/14,sz-sz/7,sz/7+1,sz/7); } return new ImageIcon(image); } /** * Process the given icon to be consistent with MCT icon * look and feel. (Add drop shadow, colorize). * @param icon the icon to process * @return an appropriately-processed icon */ public static ImageIcon processIcon(ImageIcon icon) { // Check cache first if (processedIcons.containsKey(icon)) { return processedIcons.get(icon); } else if (processedIcons.containsValue(icon)) { // Already processed return icon; } // Process icon with default appearance parameters ImageIcon newIcon = processIcon(icon, 0.775f, 0.85f, 0.95f, true); processedIcons.put(icon, newIcon); return newIcon; } /** * Process the given icon to be consistent with MCT * icon look and feel. Desired color can be specified * and drop shadow can be enabled / disabled. * * @param icon the icon to process * @param r scale for red channel * @param g scale for green channel * @param b scale for blue channel * @param dropShadow whether or not to add drop shadow * @return a processed icon */ public static ImageIcon processIcon(ImageIcon icon, float r, float g, float b, boolean dropShadow) { return processIcon(icon, new Color((int)(r * 255f),(int)(g*255f),(int)(b*255f)), dropShadow); } /** * Process the given icon to be consistent with MCT * icon look and feel. Desired color can be specified * and drop shadow can be enabled / disabled. * * @param icon the icon to process * @param c desired color * @param dropShadow whether or not to add drop shadow * @return a processed icon */ public static ImageIcon processIcon(ImageIcon icon, Color c, boolean dropShadow) { return processIcon(new ProcessDescription(icon, c, c, dropShadow)); } /** * Process the given icon to be consistent with MCT * icon look and feel. Desired color is specified for * a gradient fill from top to bottom, and * drop shadow can be enabled / disabled. * * @param icon the icon to process * @param top color for top of icon * @param bottom color for bottom of icon * @param dropShadow whether or not to add drop shadow * @return a processed icon */ public static ImageIcon processIcon(ImageIcon icon, Color top, Color bottom, boolean dropShadow) { return processIcon(new ProcessDescription(icon, top, bottom, dropShadow)); } private static ImageIcon processIcon(ProcessDescription pd) { ImageIcon icon = processedIconCache.get(pd); if (icon == null) { // Need to process and cache icon = doProcessing(pd); processedIconCache.put(pd, icon); } return icon; } private static ImageIcon doProcessing(ProcessDescription pd) { // A processed null is still a null if (pd.icon == null) { return null; } // Create a copy of the image with some extra padding for drop shadow BufferedImage bufferedImage = new BufferedImage( pd.icon.getIconWidth() + 2, pd.icon.getIconHeight() + 2, BufferedImage.TYPE_INT_ARGB); if (pd.dropShadow) { // Color rescale for shadowing float preshadow[] = {0f,0f,0.25f,0.125f}; float shadow[] = {0.1f,0.1f,0.1f,0.65f}; float offset[] = {0f,0f,0f,0f}; // Draw the icon upper-left "shadow" (subtle outline) pd.icon.paintIcon(null, bufferedImage.getGraphics(), 0, 0); bufferedImage = new RescaleOp(preshadow, offset, null).filter(bufferedImage, null); // Draw the lower-right shadow pd.icon.paintIcon(null, bufferedImage.getGraphics(), 2, 2); bufferedImage = new RescaleOp(shadow, offset, null).filter(bufferedImage, null); } // Repaint original icon pd.icon.paintIcon(null, bufferedImage.getGraphics(), 1, 1); // Colorize if (pd.firstColor != null) { // Gradient fill if (pd.secondColor != null && pd.secondColor.getRGB() != pd.firstColor.getRGB()) { // Repaint original icon & colorize pd.icon.paintIcon(null, bufferedImage.getGraphics(), 1, 1); bufferedImage = colorize(bufferedImage, pd.firstColor); BufferedImage secondBufferedImage = colorize(bufferedImage, pd.secondColor); fade(secondBufferedImage); bufferedImage.getGraphics().drawImage(secondBufferedImage, 0, 0, null); } else { // Repaint original icon & colorize pd.icon.paintIcon(null, bufferedImage.getGraphics(), 1, 1); bufferedImage = colorize(bufferedImage, pd.firstColor); } } return new ImageIcon(bufferedImage); } private static void fade(BufferedImage b) { // Reduce alpha in image, such that top is transparent // and bottom is as opaque as original, with a smooth // fade in between. This supports color gradients. float step = 1f / (float) (b.getHeight()); float fade = 0f; // Will increase to 1f by bottom of image for (int y = 0; y < b.getHeight(); y++) { for (int x = 0; x < b.getWidth(); x++) { // Get pixel as integer int argb = b.getRGB(x, y); // Separate out alpha from RGB int a = (argb >>> 24) & 0xFF; int rgb = argb - (a << 24); // Compute new alpha a = (int) (((float) a) * fade); b.setRGB(x, y, rgb | (a << 24)); } // Linearly interpolate fade += step; } } private static BufferedImage colorize(BufferedImage b, Color c) { // Convert color to scaling factor int rgb = c.getRGB(); float coloration[] = new float[4]; for (int i = 0; i < 3; i++) { coloration[2-i] = (float) (rgb & 0xFF) / 255f; rgb >>>= 8; } coloration[3] = 1f; // Always full alpha float offset[] = {0f,0f,0f,0f}; // Repaint original icon & colorize return new RescaleOp(coloration, offset, null).filter(b, null); } /** * Describes a set of processing arguments used upon an icon. * Used to support a HashMap cache of already-processed icons. * */ private static class ProcessDescription { private ImageIcon icon; private Color firstColor; private Color secondColor; private boolean dropShadow; private int hash; public ProcessDescription(ImageIcon icon, Color firstColor, Color secondColor, boolean dropShadow) { super(); this.icon = icon; this.firstColor = firstColor; this.secondColor = secondColor; this.dropShadow = dropShadow; hash = 0; Object[] objects = {icon, firstColor, secondColor, dropShadow}; for (Object o : objects) { if (o != null) { hash ^= o.hashCode(); } } } @Override public int hashCode() { return hash; } @Override public boolean equals(Object o) { if (o instanceof ProcessDescription) { ProcessDescription p = (ProcessDescription) o; return icon == p.icon && colorsEqual(firstColor, p.firstColor) && colorsEqual(secondColor, p.secondColor) && dropShadow == p.dropShadow; } return false; } private boolean colorsEqual(Color a, Color b) { if (a == null) { return b == null; } else if (b == null) { return false; } else { return a.getRGB() == b.getRGB(); } } } }