package ch.sahits.game.graphic.image;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.RasterFormatException;
import java.io.IOException;
import java.util.HashMap;
import javax.swing.JPanel;
import ch.sahits.game.graphic.image.FontLoader;
import ch.sahits.game.graphic.image.ImagesLoader;
/**
* Utility class for with methods common to all UI painters
* @author Andi Hotz, (c) Sahits GmbH, 2011
* Created on Mar 26, 2011
*
*/
public class OpenPatricianPainter {
private HashMap<String,Dimension> imgDimensionCache = new HashMap<String,Dimension>();
/**
* Retrieve the image dimensions from the cache or store it there if they are not available
* @param fileName
* @return
*/
private synchronized Dimension getImageDimesnion(String fileName){
if (imgDimensionCache.containsKey(fileName)){
return imgDimensionCache.get(fileName);
} else {
ImagesLoader loader = new ImagesLoader();
BufferedImage baseImg = loader.loadImage(fileName);
Dimension dim = new Dimension(baseImg.getWidth(), baseImg.getHeight());
imgDimensionCache.put(fileName, dim);
return dim;
}
}
/**
* Create a BufferedImage of the specified dimensions. The image is tiled with <code>imageName</code>.
* @param imageName of the image
* @param width of the image
* @param heigth of the image
* @param comp Component on which is drawn
* @return
*/
public static BufferedImage createTiledImage(String imageName, int width, int heigth){
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gconf = gs.getDefaultConfiguration();
ImagesLoader loader = new ImagesLoader();
BufferedImage baseImg = loader.loadImage(imageName);
final int xSize = baseImg.getWidth();
final int ySize = baseImg.getHeight();
int vCount = width/xSize;
if (width%xSize>0){
vCount++;
}
int hCount = heigth/ySize;
if (heigth%ySize>0){
hCount++;
}
BufferedImage img = gconf.createCompatibleImage(vCount*xSize, hCount*ySize, Transparency.BITMASK);
// fill in the image
Graphics2D g = img.createGraphics();
for (int xCount=0;xCount<=vCount;xCount++){
for (int yCount=0;yCount<=hCount;yCount++){
final int xpos = xCount*xSize;
final int ypos = yCount*ySize;
g.drawImage(baseImg, xpos, ypos,null);
}
}
return cropImage(img, width, heigth);
}
/**
* Paint the image onto the component. If the image is larger the image is clipped. If the image
* is smaller it is repeated.
* @param g Graphics used to draw on
* @param imageName name of the tiled image
* @param comp Component on which to draw
* @param rect rectangle which is to be drawn of the whole
*/
public static void drawBackground(Graphics g, String imageName, Rectangle rect) {
final int width = rect.width;
final int height = rect.height;
BufferedImage img = createTiledImage(imageName, width, height);
drawTile(img, rect.x, rect.y, width, height, g);
}
/**
* Wrapper method for drawing a tile at a specified position
* @param img Original image to be used
* @param x position of the top right corner
* @param y position of the top right corner
* @param width of the image to be drawn (the rest is clipped away)
* @param height of the image to be drawn (the rest is clipped away)
* @param comp Component on which the image is drawn
* @param g Graphics used for the drawing
*/
public static void drawTile(BufferedImage img, int x, int y, int width,
int height, Graphics g) {
if (width>img.getWidth()){
throw new IllegalArgumentException("The drawing is wider than the image: no stretching allowd");
}
if (height>img.getHeight()){
throw new IllegalArgumentException("The drawing is heigher than the image: no stretching allowd");
}
//System.out.println("Draw image with size ("+img.getWidth()+"x"+img.getHeight()+") at ("+x+"x"+y+") with dimensions ("+width+"x"+height+")");
g.drawImage(img, x, y, width, height, null);
}
/**
* Create an image that crops the image on the left and bottom side to create an image of the desired
* proportions.
* @param img Original image
* @param destWidth destination width must be smaller than the image's width
* @param destHeight destination height must be smaller than the image's height
* @return cropped image with the dimension <code>destWidthxdestHeight</code>
*/
private static BufferedImage cropImage(BufferedImage img, int destWidth, int destHeight){
if (img.getWidth()<destWidth){
throw new IllegalArgumentException("The destination width must be smaller than the image width");
}
if (img.getHeight()<destHeight){
throw new IllegalArgumentException("The destination height must be smaller than the image height");
}
try {
return img.getSubimage(0, 0, destWidth, destHeight);
} catch (RasterFormatException e) {
System.out.println(e.getMessage()+" - Original"+img.getWidth()+"x"+img.getHeight()+", "+destWidth+"x"+destHeight);
throw e;
}
}
/**
* Draw a string on the component
* @param graphics Graphics context that is used
* @param s String to be drawn
* @param fontSize Font size in pt to be used
* @param posX Center X position of the string
* @param posY Center y position of the string
*/
public void drawString(Graphics2D graphics, String s, int fontSize,
int posX, int posY) {
try {
GlyphVector gv = createGlyphVector(graphics, s, fontSize);
Rectangle2D box = gv.getVisualBounds();
float x = posX-(float)box.getCenterX();
float y = posY-(float)box.getCenterY();
graphics.drawGlyphVector(gv, x, y);
} catch (FontFormatException e) {
} catch (IOException e) {
}
}
/**
* Create the glyph vector using the string
* @param graphics to be used
* @param s String
* @param fontSize font size in points
* @return {@link GlyphVector}
* @throws FontFormatException
* @throws IOException
*/
public static GlyphVector createGlyphVector(Graphics2D graphics, String s,
int fontSize) throws FontFormatException, IOException {
// TODO: make non static
Font font = FontLoader.getInstance().createStranbergFont(fontSize);
return createGlyphVector(graphics, s, font);
}
/**
* Create a glyph vector based on a font
* @param graphics
* @param s
* @param font
* @return
*/
public static GlyphVector createGlyphVector(Graphics2D graphics, String s,
Font font) {
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
FontRenderContext frc = graphics.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, s);
return gv;
}
/**
* Create the glyph vector using the string
* @param s String
* @param fontSize font size in points
* @return {@link GlyphVector}
* @throws FontFormatException
* @throws IOException
*/
public GlyphVector createGlyphVector(String s, int fontSize) throws FontFormatException, IOException{
FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true);
Font font = FontLoader.getInstance().createStranbergFont(fontSize);
GlyphVector gv = font.createGlyphVector(frc, s);
return gv;
}
/**
* Create turned steering wheel. 0% is whole on the left and 100% whole on the right
* @param width width of the component
* @param percent of the turning
*/
public BufferedImage createSteeringWheel(int width, float percent){
if (percent<0 || percent>100){
throw new IllegalArgumentException("percent must ly withing [0,100]");
}
String fileName = "SteeringWheel.png";
Dimension dim = getImageDimesnion(fileName);
double scale = width / dim.getWidth();
final double angle = computeSteeringWheelAngle(percent);
OpenPatricianImageRotator rotator = new OpenPatricianImageRotator();
return rotator.rotateImage(fileName, angle, scale, 1, 2);
}
/**
* Compute the angle in degrees based on the percentage.
* @param percentage
* @return
*/
private double computeSteeringWheelAngle(float percentage){
return percentage*160/100+10;
}
/**
* Create the polygon that represents the handle of the steering wheel
* @param width of the component the polygon is based upon
* @param percent of the rotation
* @return
*/
public Polygon getSteeringWheelHandle(int width, float percent){
String fileName = "SteeringWheel.png";
Dimension dim = getImageDimesnion(fileName);
double scale = width / dim.getWidth();
final double angle = computeSteeringWheelAngle(percent);
Polygon poly = new Polygon(); // The handle in the unscaled unrotated image
poly.addPoint(0, 235);
poly.addPoint(80, 235);
poly.addPoint(80, 260);
poly.addPoint(0, 260);
//System.out.print("Original polynom: ");
//OpenPatricianImageRotator.print(poly);
//System.out.println("");
OpenPatricianImageRotator rotator = new OpenPatricianImageRotator();
return rotator.rotatePolygon(poly, angle, scale, (int)Math.rint(dim.getWidth()), (int)Math.rint(dim.getHeight()));
}
/**
* Compute the slider track rectangle.
* @param orientation
* @param width
* @param castMin cast value of the minimum
* @param castMax cast value of the maximum
* @return
*/
public Rectangle computeSliderTrack(int width, float castMin, float castMax){
Polygon poly = getSteeringWheelHandle(width, castMin);
int minX = (int) Math.rint(poly.getBounds().getMinX());
int maxY = (int)Math.rint(poly.getBounds().getMaxY());
poly = getSteeringWheelHandle(width, castMax);
int maxX = (int)Math.rint(poly.getBounds().getMaxX());
poly = getSteeringWheelHandle(width, (castMax-castMax)/2);
int minY = (int)Math.rint(poly.getBounds().getMinY());
return new Rectangle(minX, minY, maxX-minX, maxY-minY);
}
/**
* Scale an input image along the x-axis only. The scaled dimension may not exactly match the
* desired, but it will be best match
* @param img Image to be scaled
* @param destWidth width of the scaled image
* @return scaled image or null if the input was null
*/
public static BufferedImage scaleX(BufferedImage img, int destWidth){
if (destWidth<=0){
throw new IllegalArgumentException("The final width must be larger than 0");
}
if (img==null) return null; // can happen when quitting
float xScale = (float) destWidth / img.getWidth();
float yScale = 1;
AffineTransform at = new AffineTransform();
at.scale(xScale, yScale);
// instantiate and apply affine transformation filter
BufferedImageOp bio = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
return bio.filter(img, null);
}
/**
* Scale an input image along the y-axis only. The scaled dimension may not exactly match the
* desired, but it will be best match
* @param img Image to be scaled
* @param destHeight height of the scaled image
* @return scaled image or null if the input was null
*/
public static BufferedImage scaleY(BufferedImage img, int destHeight){
if (destHeight<=0){
throw new IllegalArgumentException("The final height must be larger than 0");
}
if (img==null) return null; // can happen when quitting
float xScale = 1;
float yScale = (float) destHeight / img.getHeight();
AffineTransform at = new AffineTransform();
at.scale(xScale, yScale);
// instantiate and apply affine transformation filter
BufferedImageOp bio = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
return bio.filter(img, null);
}
}