/*
*
* Copyright 2014 http://Bither.net
*
* 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.
* /
*/
package net.bither.viewsystem.components;
import com.google.common.collect.Maps;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.util.Map;
/**
* <p>Decorator to provide the following to UI:</p>
* <ul>
* <li>Various image effects</li>
* <li>Consistent rendering hints</li>
* </ul>
*
* @since 0.0.1
*/
public class ImageDecorator {
/**
* Utilities have no public constructor
*/
private ImageDecorator() {
}
/**
* @param image The original image
* @param cornerRadius The required corner radius in pixels
* @return A new image with the required cornering
*/
public static BufferedImage applyRoundedCorners(BufferedImage image, int cornerRadius) {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = output.createGraphics();
// Perform soft-clipping in fully opaque mask with standard hints
g2.setComposite(AlphaComposite.Src);
g2.setColor(Color.MAGENTA);
g2.setRenderingHints(smoothRenderingHints());
g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
// Use the mask as an alpha source and apply the image respecting existing transparency
g2.setComposite(AlphaComposite.SrcIn);
g2.drawImage(image, 0, 0, null);
g2.dispose();
return output;
}
/**
* @param icon The icon
* @return A buffered image suitable for use with panels, overlays etc
*/
public static BufferedImage toBufferedImage(Icon icon) {
// Get the size
int w = icon.getIconWidth();
int h = icon.getIconHeight();
// Set up the graphics environment
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
// Create the buffered image
BufferedImage image = gc.createCompatibleImage(w, h, Transparency.BITMASK);
Graphics2D g = image.createGraphics();
// Keep it smooth
g.setRenderingHints(smoothRenderingHints());
// Paint the icon on to it
icon.paintIcon(null, g, 0, 0);
g.dispose();
return image;
}
/**
* @param image The buffered image
* @return An image icon suitable for use in tables etc
*/
public static ImageIcon toImageIcon(BufferedImage image) {
return new ImageIcon(image);
}
/**
* @param icon The icon
* @return An image icon suitable for use in tables etc
*/
public static ImageIcon toImageIcon(Icon icon) {
if (icon instanceof ImageIcon) {
return (ImageIcon) icon;
}
// Use buffered image as an intermediate format
BufferedImage image = toBufferedImage(icon);
// Convert to image icon for tables
return toImageIcon(image);
}
/**
* @return Rendering hints for anti-aliased and symmetrical output (smooth)
*/
public static Map<RenderingHints.Key, ?> smoothRenderingHints() {
Map<RenderingHints.Key, Object> hints = Maps.newHashMap();
// Anti-aliasing to ensure smooth edges
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Pure strokes to ensure symmetrical corners
hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
return hints;
}
/**
* <p>Applies a single alpha-blended color over all pixels</p>
*
* @param image The source image
* @param newColor The color to use as the replacement to non-transparent pixels
* @return The new image with color applied
*/
public static BufferedImage applyColor(BufferedImage image, Color newColor) {
int width = image.getWidth();
int height = image.getHeight();
WritableRaster raster = image.getRaster();
int newColorRed = newColor.getRed();
int newColorGreen = newColor.getGreen();
int newColorBlue = newColor.getBlue();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int[] pixels = raster.getPixel(x, y, (int[]) null);
pixels[0] = newColorRed;
pixels[1] = newColorGreen;
pixels[2] = newColorBlue;
raster.setPixel(x, y, pixels);
}
}
return image;
}
/**
* <p>Overlay the foreground onto the background with an offset</p>
*
* @param foreground The foreground image
* @param background The background image
* @param x The x position on the background to place the foreground image
* @param y The y position on the background to place the foreground image
* @return The (clipped if necessary) foreground image placed over the background image
*/
public static BufferedImage overlayImages(BufferedImage foreground, BufferedImage background, int x, int y) {
int bx = background.getWidth();
int by = background.getHeight();
// Get the graphics context
Graphics2D g2 = background.createGraphics();
// Blend images smoothly
g2.setRenderingHints(smoothRenderingHints());
// Draw background image at (0,0)
g2.drawImage(background, 0, 0, null);
// Draw clipped foreground image at (x,y) not exceeding background boundaries
g2.drawImage(foreground, x, y, bx - x, by - y, null);
// Tidy up
g2.dispose();
return background;
}
/**
* <p>Rotate an image about its center</p>
*
* @param theta The number of radians to rotate (-PI rotates 180 degrees clockwise)
* @return A copy of the original image rotated by the required amount
*/
public static BufferedImage rotate(BufferedImage image, double theta) {
// Calculate the center of rotation
double x = image.getWidth() / 2;
double y = image.getHeight() / 2;
// Copy the image
BufferedImage copy = image.getSubimage(0, 0, image.getWidth(), image.getHeight());
// Get the graphics context
Graphics2D g2 = copy.createGraphics();
// Blend images smoothly
g2.setRenderingHints(smoothRenderingHints());
// Rotate the image about the given center
g2.rotate(theta, x, y);
// Draw the image
g2.drawImage(copy, 0, 0, null);
// Tidy up
g2.dispose();
return copy;
}
/**
* @param image The source image
* @param maxWidth The maximum width (assumes a landscape image)
* @return The re-sized image with no blurring and preserved transparency
*/
public static BufferedImage resizeSharp(BufferedImage image, int maxWidth) {
// Assume a screen shot and calculate the appropriate ratio
// for minimum UI width
double ratio = (double) image.getWidth(null) / maxWidth;
int height = (int) (image.getHeight(null) / ratio);
// Preserve transparency
BufferedImage thumbnail = new BufferedImage(maxWidth, height, BufferedImage.TYPE_INT_ARGB);
// Perform a bi-cubic interpolation with anti-aliasing for sharp result
Graphics2D g = thumbnail.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawImage(image, 0, 0, maxWidth, height, null);
g.dispose();
return thumbnail;
}
}