/*
* @(#)FocusedBorder.java 1.0 2011-07-26
*
* Copyright (c) 2011 Werner Randelshofer, Immensee, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*/
package ch.randelshofer.quaqua.border;
import javax.swing.text.JTextComponent;
import ch.randelshofer.quaqua.QuaquaManager;
import javax.swing.UIManager;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.Kernel;
import java.awt.image.ConvolveOp;
import ch.randelshofer.quaqua.QuaquaUtilities;
import java.awt.Component;
import java.awt.Graphics;
import static java.lang.Math.*;
/**
* {@code FocusedBorder}.
*
* @author Werner Randelshofer
* @version 1.0 2011-07-26 Created.
*/
public abstract class AbstractFocusedPainter {
private final static ConvolveOp edgeLeftOp = new ConvolveOp(new Kernel(3, 1, new float[]{1, 0, -1}));
private final static ConvolveOp edgeRightOp = new ConvolveOp(new Kernel(3, 1, new float[]{-1, 0, 1}));
private final static ConvolveOp edgeTopOp = new ConvolveOp(new Kernel(1, 3, new float[]{1, 0, -1}));
private final static ConvolveOp edgeBottomOp = new ConvolveOp(new Kernel(1, 3, new float[]{-1, 0, 1}));
private static float[] gaussian;
// private final static float[] gaussian = gaussian(2f, 2.5f , 0.9f);
private static ConvolveOp gaussianVOp;
private static ConvolveOp gaussianHOp;
public AbstractFocusedPainter() {
initFilters();
}
private static void initFilters() {
float sum;
if ("true".equals(QuaquaManager.getProperty("apple.awt.graphics.UseQuartz", "false"))) {
sum = 0.9f;
} else {
sum = 0.65f;
}
gaussian = gaussian(2f, 4.0f, sum);
//gaussian = pyramid(2.5f, sum);
gaussianVOp = new ConvolveOp(new Kernel(1, gaussian.length, gaussian));
gaussianHOp = new ConvolveOp(new Kernel(gaussian.length, 1, gaussian));
}
protected void paint(Component c, Graphics cgx, int x, int y, int width, int height) {
boolean isEditable;
if (c instanceof JTextComponent) isEditable=((JTextComponent)c).isEditable();
else isEditable=true;
if (QuaquaUtilities.isFocused(c)&&isEditable) {
Graphics2D cg = (Graphics2D) cgx;
int slack = 2;
// FIXME - Can the garbage collector cope with these two images?
BufferedImage borderImg = new BufferedImage(width + 2 * slack, height + 2 * slack, BufferedImage.TYPE_INT_ARGB_PRE);
BufferedImage focusImg = new BufferedImage(width + 2 * slack, height + 2 * slack, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D bg = borderImg.createGraphics();
// draw the border, once into the borderImg and once onto the component
doPaint(c, bg, slack, slack, width, height);
cg.drawImage(borderImg, x - slack, y - slack, c);
paintFocusRing(borderImg, focusImg, cgx, x-slack,y-slack);
bg.dispose();
} else {
doPaint(c, cgx, x, y, width, height);
}
}
/** Paints an focus ring on cgx.
*
* @param borderImg The input image which is used to compute the border.
* @param focusImg A temporary image. Must have the same size as borderImg.
* @param cgx The output object.
*/
public static void paintFocusRing(BufferedImage borderImg, BufferedImage focusImg, Graphics cgx, int x, int y) {
Graphics2D cg = (Graphics2D) cgx;
Graphics2D fg = focusImg.createGraphics();
// generate the focusImg from the borderImg
fg.setComposite(AlphaComposite.SrcOver);
fg.drawImage(borderImg, edgeLeftOp, 0, 0);
fg.drawImage(borderImg, edgeRightOp, 0, 0);
fg.drawImage(borderImg, edgeTopOp, 0, 0);
fg.drawImage(borderImg, edgeBottomOp, 0, 0);
fg.setComposite(AlphaComposite.SrcIn);
fg.setColor(UIManager.getColor("Focus.color"));
fg.fillRect(0, 0, borderImg.getWidth(), borderImg.getHeight());
// draw the focusImg blurred onto the component
cg.drawImage(focusImg, gaussianHOp, x, y);
cg.drawImage(focusImg, gaussianVOp, x, y);
fg.dispose();
}
protected abstract void doPaint(Component c, Graphics cgx, int x, int y, int width, int height);
/** Creates a gaussian kernel with the specified radius, sigma and sum. */
private static float[] gaussian(float radius, float sigma, float sum) {
int r = (int) Math.ceil(radius);
float[] gaussian = new float[r * 2 + 1];
// compute the gaussian
float h = 1f; // height of the peak
float c = r; // position of the centre of the peak
float invs2sq = 1f / (2f * sigma * sigma);
for (int i = 0; i < gaussian.length; i++) {
float x = i;
gaussian[i] = (float) (h * exp(-pow(x - c, 2) * invs2sq));
}
normalizeKernel(gaussian, sum);
return gaussian;
}
/** Creates a pyramid kernel with the specified radius and sum. */
private static float[] pyramid(float radius, float sum) {
int r = (int) Math.ceil(radius);
float[] pyramid = new float[r * 2 + 1];
// compute the pyramid
float c = r; // position of the centre of the peak
for (int i = 0; i < pyramid.length; i++) {
float x = i;
pyramid[i] = (float) c - abs(x - c);
}
normalizeKernel(pyramid, sum);
return pyramid;
}
/** Normalizes the kernel so that all its elements add up to the given
* sum.
*
* @param kernel
* @param sum
*/
private static void normalizeKernel(float[] kernel, float sum) {
float total = 0;
for (int i = 0; i < kernel.length; i++) {
total += kernel[i];
}
if (abs(total) > 1e-20) {
total = sum / total;
for (int i = 0; i < kernel.length; i++) {
kernel[i] *= total;
}
}
}
}