This is the code from my answer to a another CR question. My goal was to provide a fast and simple computation and I'm rather unsatisfied with it. It's maybe 10 times faster than the original, but this may be still too slow for bigger displays and/or weaker devices.
I'm mostly interested in performance improvements, including better algorithm, use of graphic cards, or whatever helps. My usual disclaimer concerning coding conventions applies.
I'm splitting the code in parts for easier review. The whole code is actually a single file. You'd need lombok and guava to run it. Alternatively, you can replace the stuff by the obvious alternatives (e.g., replace ImmutableList
by ArrayList
and @RequiredArgsConstructor
by writing it manually) in less than five minutes.
Constants
Colors and sizes are takes from the original question, the last two constants are described in my answer.
private static final Color[] COLORS = new Color[256];
static {
for (int i=0; i<COLORS.length; ++i) COLORS[i] = new Color(i, 0, 0);
}
private static final int WIDTH = 1280;
private static final int HEIGHT = 720;
private static final int STEP = 30;
private static final int DISTANCE_THRESHOLD = 100;
Point
/** An immutable 2D point with some additional methods. */
@RequiredArgsConstructor @Getter @ToString private static class Point {
Point(Random random, int width, int height) {
this(random.nextInt(width), random.nextInt(height));
}
public double inverseDistance(int x, int y) {
return 1.0 / Math.sqrt(squaredDistance(x, y));
}
public int squaredDistance(int x, int y) {
return square(x-this.x) + square(y-this.y);
}
private static int square(int a) {
return a * a;
}
private final int x;
private final int y;
}
HeatSpotter
This is the core class and the only interesting part.
/** A holder for the heat spots doing the temperature computation. */
private static final class HeatSpotter {
private final ImmutableList<Point> spots;
// The coordinates of the top-left corner.
private int xHigh;
private int yHigh;
private int step;
private final List<Point> currentSpots = Lists.newArrayList();
// The temperatures in the four corners. The suffixes are (scaled) coordinates.
private float temperature00;
private float temperature01;
private float temperature10;
private float temperature11;
HeatSpotter(Random random, int width, int height) {
final ImmutableList.Builder<Point> spots = ImmutableList.builder();
final int count = WIDTH * HEIGHT / 5000;
for (int i=0; i<count; ++i) spots.add(new Point(random, WIDTH, HEIGHT));
this.spots = spots.build();
}
public void reset(int x, int y, int step) {
xHigh = x;
yHigh = y;
this.step = step;
currentSpots.clear();
final List<Point> otherSpots = Lists.newArrayList();
final int halfStep = step / 2;
final int squaredThreshold = DISTANCE_THRESHOLD * DISTANCE_THRESHOLD;
for (final Point p : spots) {
if (p.squaredDistance(x+halfStep, y+halfStep) < squaredThreshold) {
currentSpots.add(p);
} else {
otherSpots.add(p);
}
}
temperature00 = computeTemperature(x + 0*step, y + 0*step, otherSpots);
temperature01 = computeTemperature(x + 0*step, y + 1*step, otherSpots);
temperature10 = computeTemperature(x + 1*step, y + 0*step, otherSpots);
temperature11 = computeTemperature(x + 1*step, y + 1*step, otherSpots);
}
float getTemperature(int x, int y) {
return bilinearInterpolation(x-xHigh, y-yHigh) + computeTemperature(x, y, currentSpots);
}
/**
* Interpolate according to https://en.wikipedia.org/wiki/Bilinear_interpolation#Algorithm
*
* @param xLow The difference between the current {@code x} coordinate and {@link xHigh}.
* @param yLow The difference between the current {@code y} coordinate and {@link yHigh}.
* @return The interpolated temperature contribution by the spots not contained in {@link #currentSpots}.
*/
float bilinearInterpolation(int xLow, int yLow) {
float result = 0;
result += (step-xLow) * (step-yLow) * temperature00;
result += (step-xLow) * yLow * temperature01;
result += xLow * (step-yLow) * temperature10;
result += xLow * yLow * temperature11;
result /= step * step;
return result;
}
private float computeTemperature(int x, int y, List<Point> spots) {
float result = 0;
for (final Point p : spots) result += p.inverseDistance(x, y);
return result;
}
}
MyPanel
/** A trivial class used as canvas for the spots. */
private static class MyPanel extends JPanel {
public MyPanel(Random random, int width, int height) {
setPreferredSize(new Dimension(width, height));
heatSpotter = new HeatSpotter(random, width, height);
}
@Override protected void paintComponent(Graphics g) {
super.paintComponent(g);
final int width = getWidth();
final int height = getHeight();
for (int xHigh=0; xHigh<width; xHigh+=STEP) {
for (int yHigh=0; yHigh<height; yHigh+=STEP) {
heatSpotter.reset(xHigh, yHigh, STEP);
for (int xLow=0; xLow<STEP; ++xLow) {
final int x = xHigh + xLow;
for (int yLow=0; yLow<STEP; ++yLow) {
final int y = yHigh + yLow;
g.setColor(color(x, y));
g.fillRect(x, y, 1, 1);
}
}
}
}
}
private Color color(int x, int y) {
int temperature = (int) (COLORS.length * heatSpotter.getTemperature(x, y));
if (temperature >= COLORS.length) temperature = COLORS.length-1;
if (temperature < 0) temperature = 0;
return COLORS[temperature];
}
@NonNull private final HeatSpotter heatSpotter;
}
main
public static void main(String[] args) {
final JFrame frame = new JFrame("HeatSpot");
frame.add(new MyPanel(new Random(), WIDTH, HEIGHT));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
(STEP, DISTANCE_THRESHOLD) = (30, 100)
works well and I can't see any artifacts (but someone else might do?). Even(60, 60)
seems to work well while it should be way faster (it is not, as pixel-wise drawing usingfillRect
is slow itself; but this is not what I after). Unlike yours, my algorithm could make it look square-like when pushed too far. +++ An auxiliary array for the temperatures and honoring the clip would speed up repainting, but these are yet other things I didn't care about. \$\endgroup\$