I have tried using the method drawOval with equal height and width but as the diameter increases the circle becomes worse looking. What can I do to have a decent looking circle no matter the size. How would I implement anti-aliasing in java or some other method.
As it turns out, Java2D (which I'm assuming is what you're using) is already pretty good at this! There's a decent tutorial here: http://www.javaworld.com/javaworld/jw-08-1998/jw-08-media.html
The important line is:
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
you can set rendering hints:
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Two things that may help:
Use Graphics2D.draw(Shape) with an instance of java.awt.geom.Ellipse2D instead of Graphics.drawOval
If the result is still not satisfactory, try using Graphics2D.setRenderingHint to enable antialiasing
Example
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Shape theCircle = new Ellipse2D.Double(centerX - radius, centerY - radius, 2.0 * radius, 2.0 * radius);
g2d.draw(theCircle);
}
See Josef's answer for an example of setRenderingHint
Of course you set your radius to what ever you need:
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
Ellipse2D.Double hole = new Ellipse2D.Double();
hole.width = 28;
hole.height = 28;
hole.x = 14;
hole.y = 14;
g2d.draw(hole);
}
Thanks to Oleg Estekhin for pointing out the bug report, because it explains how to do it.
Here are some small circles before and after. Magnified a few times to see the pixel grid.
Going down a row, they're moving slightly by subpixel amounts.
The first column is without rendering hints. The second is with antialias only. The third is with antialias and pure mode.
Note how with antialias hints only, the first three circles are the same, and the last two are also the same. There seems to be some discrete transition happening. Probably rounding at some point.
Here's the code. It's in Jython for readability, but it drives the Java runtime library underneath and can be losslessly ported to equivalent Java source, with exactly the same effect.
from java.lang import *
from java.io import *
from java.awt import *
from java.awt.geom import *
from java.awt.image import *
from javax.imageio import *
bim = BufferedImage(30, 42, BufferedImage.TYPE_INT_ARGB)
g = bim.createGraphics()
g.fillRect(0, 0, 100, 100)
g.setColor(Color.BLACK)
for i in range(5):
g.draw(Ellipse2D.Double(2+0.2*i, 2+8.2*i, 5, 5))
g.setRenderingHint( RenderingHints. KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON)
for i in range(5):
g.draw(Ellipse2D.Double(12+0.2*i, 2+8.2*i, 5, 5))
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE)
for i in range(5):
g.draw(Ellipse2D.Double(22+0.2*i, 2+8.2*i, 5, 5))
#You'll probably want this too later on:
#g.setRenderingHint( RenderingHints. KEY_INTERPOLATION,
# RenderingHints.VALUE_INTERPOLATION_BICUBIC)
#g.setRenderingHint( RenderingHints. KEY_RENDERING,
# RenderingHints.VALUE_RENDER_QUALITY)
ImageIO.write(bim, "PNG", File("test.png"))
Summary: you need both VALUE_ANTIALIAS_ON and VALUE_STROKE_PURE to get proper looking circles drawn with subpixel accuracy.
Inability to draw a "decent looking circle" is related to the very old bug 6431487.
Turning antialiasing on does not help a lot - just check the kind of "circle" produced by the drawOval() or drawShape(Eclipse) when the required circle size is 16 pixels (still pretty common for icon size) and antialiasing is on. Bigger antialiased circles will look better but they are still asymmetric, if somebody will care to look at them closely.
It seems that to draw a "decent looking circle" one has to manually draw one. Without antialiasing it will be midpoint circle algorithm (this question has an answer with a pretty java code for it).
EDITED: 06 September 2017
That's an algorithm invented by me to draw a circle over a integer matrix. The same idea could be used to write a circle inside a BufferedImage.
If you are trying to draw that circle using the class Graphics this is not the answare you are looking for (unless you wish to modify each color-assignement with g.drawLine(x, y, x+1, y), but it could be very slow).
protected boolean runOnCircumference(int[][] matrix, int x, int y, int ray, int color) {
boolean ret;
int[] rowUpper = null, rowInferior = null, rowCenterUpper = null, rowCenterInferior = null;
if (ret = ray > 0) {
if (ray == 1) {
matrix[y][x + 1] = color;
rowUpper = matrix[++y];
rowUpper[x] = color;
rowUpper[x + 2] = color;
matrix[y][x] = color;
} else {
double rRay = ray + 0.5;
int r = 0, c = 0, ray2 = ray << 1, ray_1 = ray - 1, halfRay = (ray >> 1) + ray % 2, rInf,
ray1 = ray + 1, horizontalSymmetricOldC;
// draw cardinal points
rowUpper = matrix[ray + y];
rowUpper[x] = color;
rowUpper[x + ray2] = color;
matrix[y][x + ray] = color;
matrix[ray2 + y][x + ray] = color;
horizontalSymmetricOldC = ray1;
rInf = ray2;
c = ray_1;
for (r = 0; r < halfRay; r++, rInf--) {
rowUpper = matrix[r + y];
rowInferior = matrix[rInf + y];
while (c > 0 && (Math.hypot(ray - c, (ray - r)) < rRay)) {
rowUpper[x + c] = color;
rowUpper[x + horizontalSymmetricOldC] = color;
rowInferior[x + c] = color;
rowInferior[x + horizontalSymmetricOldC] = color;
// get the row pointer to optimize
rowCenterUpper = matrix[c + y];
rowCenterInferior = matrix[horizontalSymmetricOldC + y];
// draw
rowCenterUpper[x + r] = color;
rowCenterUpper[x + rInf] = color;
rowCenterInferior[x + r] = color;
rowCenterInferior[x + rInf] = color;
horizontalSymmetricOldC++;
c--;
}
} // end r circle
}
}
return ret;
}
I tried it so many times, verifying manually it correctness, so I think it will work. I haven't made any range-check just to simplify the code.
I hope it will help you and everyone wish to draw a circle over a matrix (for example, those programmer who tries to create their own videogames on pure code and need to manage a matrix-oriented game-map to store the objects lying on the game-map [if you need help on this, email me]).
Related
I am manipulating code of a image renderer that is making output image from Color[] array and my code simply update it with additional stuff right before saving, that is when the original image is actually prepared (all pixels positions prepared to be filled with RGBs in that Color[] array ready for final saving).
Reason why I am doing this is to have ability to insert text describing my render without need of another external graphics program that would do that (I want to have it all in one-go! action without need of another external app).
For that cause - as I have no reach/access for the original prepared BufferedImage (but I have access to actual Color[] that it is created from) I had to make my own class method that:
convert that original Color[] to my own temporary BufferedImage
update that temp. BufferedImage with my stuff via Graphics2D (adding some text to image)
convert my result (temp. BufferedImage with Graphics2D) back to Color[]
send that final Color[] back to the original image rendering method
that would actually make it to be the final image that is rendered out
and saved as png
Now everything works just fine as I expected except one really annoying thing that I cannot get rid off: my updated image looks very bleached-like/pale (almost no depth or shadows presented) compared to the original un-watermarked version...
To me it simply looks like after the image2color[] conversion (using #stacker's solution from here Converting Image to Color array) something goes wrong/is not right so the colors become pale and I do not have any clue why.
Here is the main part of my code that is in question:
BufferedImage sourceImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Color[] to BufferedImage
for (int k = 0; k < multiArrayList.size(); k++) {
// PREPARE...
int x = (int) multiArrayList.get(k)[0];
int y = (int) multiArrayList.get(k)[1];
int w = (int) multiArrayList.get(k)[2];
int h = (int) multiArrayList.get(k)[3];
Color[] data = (Color[]) multiArrayList.get(k)[4];
int border = BORDERS[k % BORDERS.length];
for (int by = 0; by < h; by++) {
for (int bx = 0; bx < w; bx++) {
if (bx == 0 || bx == w - 1) {
if (5 * by < h || 5 * (h - by - 1) < h) {
sourceImage.setRGB(x + bx, y + by, border);
}
} else if (by == 0 || by == h - 1) {
if (5 * bx < w || 5 * (w - bx - 1) < w) {
sourceImage.setRGB(x + bx, y + by, border);
}
}
}
}
// UPDATE...
for (int j = 0, index = 0; j < h; j++) {
for (int i = 0; i < w; i++, index++) {
sourceImage.setRGB(x + i, y + j, data[index].copy().toNonLinear().toRGB());
}
}
}
Graphics2D g2d = (Graphics2D) sourceImage.getGraphics();
// paints the textual watermark
drawString(g2d, text, centerX, centerY, sourceImage.getWidth());
// when saved to png at this point ALL IS JUST FINE
ImageIO.write(sourceImage, "png", new File(imageSavePath));
g2d.dispose();
// BufferedImage to Color array
int[] dt = ((DataBufferInt) sourceImage.getRaster().getDataBuffer()).getData();
bucketFull = new Color[dt.length];
for (int i = 0; i < dt.length; i++) {
bucketFull[i] = new Color(dt[i]);
}
// update and repaint output image - THIS OUTPUT IS ALREADY BLEACHED/PALE
d.ip(0, 0, width, height, renderThreads.length + 1);
d.iu(0, 0, width, height, bucketFull);
// reset objects
g2d = null;
sourceImage = null;
bucketFull = null;
multiArrayList = new ArrayList<>();
I have tested (by saving it to another .png file right after the Graphics2D addition) that before it gets that 2nd conversion it looks absolutely OK 1:1 to the original image incl. my text on that image.
But as I said when it is send for render it becomes bleached/pale that is a problem I am trying to solve.
BTW I first thought it might be that Graphics2D addition so I did try it without it but the result was the same, that is bleached/pale version.
Although my process and code is completely different the output image is basically suffering exactly the same way as in this topic (still not solved) BufferedImage color saturation
Here are my 2 examples - 1st ORIGINAL, 2nd UPDATED (bleached/pale)
As suspected, the problem is that you convert the color values from linear RGB to gamma-corrected/sRGB values when setting the RGB values to the BufferedImage, but the reverse transformation (back to linear RGB) is not done when you put the values back into the Color array.
Either change the line (inside the double for loop):
sourceImage.setRGB(x + i, y + j, data[index].copy().toNonLinear().toRGB());
to
sourceImage.setRGB(x + i, y + j, data[index].toRGB());
(you don't need the copy() any more, as you no longer mutate the values, using toNonLinear()).
This avoids the conversion altogether.
... or you could probably also change the line setting the values back, from:
bucketFull[i] = new Color(dt[i]);
to
bucketFull[i] = new Color(dt[i]).toLinear();
Arguably, this is more "correct" (as AWT treats the values as being in the sRGB color space, regardless), but I believe the first version is faster, and the difference in color is negligible. So I'd probably try the first suggested fix first, and use that unless you experience colors that are off.
This question already has an answer here:
Clear portion of graphics with underlying image
(1 answer)
Closed 7 years ago.
I'm in the process of making a 2D game in which a player roams around a maze.
I want to implement some sort of "darkness", even something as simple as a transparent shape around the player surrounded by black, like this:
The problem I've found using Swing is that, while this is possible, it means having to redraw everything, which produces an annoying "flickering" effect every time it happens. Is there a way to make some sort of overlay, or just a good way of doing this in general in Swing? I'm not very experienced with GUI/visual stuff right now so I'd like to stick with Swing if possible.
EDIT: This is my method to paint the background, i.e. the floor, walls and exit:
public final void paintBG(Graphics g){
g.setColor(Color.LIGHT_GRAY); // Screen background
g.fillRect(0, 0, getWidth(), getHeight());
// Draw the Walls of the maze
// scalex and y are for scaling images/walls within the maze since I let users specify how big they want the maze
for (int j = 0; j < this.height; j++, y += scaley) {
x = 20;
for (int i = 0; i < this.width; i++, x += scalex) {
if (!(maze[j][i].northwall.isBroken())) // If the north wall isn't broken
{
g.drawImage(walltile, x, y, scalex, scaley / 5, null); // Draw a wall there (image, xpos, ypos, width, height, observer)
}
if (!(maze[j][i].eastwall.isBroken())) // etc
{
g.drawImage(walltile, x + scalex, y, scalex / 5, scaley, null);
}
if (!(maze[j][i].southwall.isBroken())) {
g.drawImage(walltile, x, y + scaley, scalex, scaley / 5, null);
}
if (!(maze[j][i].westwall.isBroken())) {
g.drawImage(walltile, x, y, scalex / 5, scaley, null);
}
if ((j == mazeinfo.getTargetM()) && (i == mazeinfo.getTargetN())) {
// Draw the exit
g.drawImage(jeep, x + (scalex / 2), y + (scaley / 2), cx, cy, null);
g.setColor(Color.LIGHT_GRAY);
if (maze[j][i].northwall.isEdge()) {
// Paint over the edge creating a 'way out'
g.fillRect(x, y, scalex, scaley / 4);
} else if (maze[j][i].eastwall.isEdge()) {
g.fillRect(x + scalex, y, scalex / 4, scaley);
} else if (maze[j][i].southwall.isEdge()) {
g.fillRect(x, y + scaley, scalex, scaley / 4);
} else if (maze[j][i].westwall.isEdge()) {
g.fillRect(x, y, scalex / 4, scaley);
}
}
}
}
}
I then have "paintPlayer" and "paintEnemy" methods to paint those sprites each time they move. The background only gets painted once, at the start.
Possibilities:
You may be drawing directly in a top level window such as a JFrame. If so, don't draw in the paintComonent method of a JPanel so that you use the automatic double buffering availabe.
You may be reading in an image from within a painting method, and if so, don't. These methods must paint and paint only and must be blindingly fast.
You may not be using a BufferedImage in your painting method but creating an image de-novo, and if so, don't. Draw the BufferedImage using Graphics#drawImage(...).
Perhaps your animation code is off. You may be calling repaint() from within paint or paintComponent, something that should never be done.
And the possible guesses can go on and on...
Edit
Your code shows that you may be re-paint the maze with every painting iteration -- don't do this. Instead draw the above into a BufferedImage, and draw that image within your paintComponent method. Then change the BufferedImage if the walls structurally change.
Note that the maze's logical structure (the non-visual data that tells which wall is open, which is closed) should be part of your program's data, and not its code.
Here in an example of using a LayerUI from Oracle's Swing UI documentation. Just change the AlphaComposite constant to something darker.
The following is a LayerUI subclass that draws a translucent circle wherever the mouse moves inside a panel.
class SpotlightLayerUI extends LayerUI<JPanel> {
private boolean mActive;
private int mX, mY;
#Override
public void installUI(JComponent c) {
super.installUI(c);
JLayer jlayer = (JLayer)c;
jlayer.setLayerEventMask(
AWTEvent.MOUSE_EVENT_MASK |
AWTEvent.MOUSE_MOTION_EVENT_MASK
);
}
#Override
public void uninstallUI(JComponent c) {
JLayer jlayer = (JLayer)c;
jlayer.setLayerEventMask(0);
super.uninstallUI(c);
}
#Override
public void paint (Graphics g, JComponent c) {
Graphics2D g2 = (Graphics2D)g.create();
// Paint the view.
super.paint (g2, c);
if (mActive) {
// Create a radial gradient, transparent in the middle.
java.awt.geom.Point2D center = new java.awt.geom.Point2D.Float(mX, mY);
float radius = 72;
float[] dist = {0.0f, 1.0f};
Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
RadialGradientPaint p =
new RadialGradientPaint(center, radius, dist, colors);
g2.setPaint(p);
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, .6f));
g2.fillRect(0, 0, c.getWidth(), c.getHeight());
}
g2.dispose();
}
#Override
protected void processMouseEvent(MouseEvent e, JLayer l) {
if (e.getID() == MouseEvent.MOUSE_ENTERED) mActive = true;
if (e.getID() == MouseEvent.MOUSE_EXITED) mActive = false;
l.repaint();
}
#Override
protected void processMouseMotionEvent(MouseEvent e, JLayer l) {
Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
mX = p.x;
mY = p.y;
l.repaint();
}
}
To keep the spotlight's center updated on your player, create an event for player movement and register the LayerUI to listen for updates. See the setLayerEventMask() example in the JLayer link below.
source: How to Decorate Components with the JLayer Class
I need to place an image onto a canvas with the corners at specific co-ordinates.
// Blank canvas
BufferedImage img = new BufferedImage(2338, 1654, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, width, EXTRA_HEADER_HEIGHT);
I have all 4 corner co-ordinates that the image corners must be placed at on the background canvas. The problem is that the original image might need to be rotated. This is basically what I need to achieve:
I don't have much experience with Graphics2D but based on a quick review of the API I can't see a method to achieve this. I am hoping that I am wrong here and that somebody can save me some time but my current thinking is:
Use the co-ordinates to calculate the rotation of the placed image relative to the supplied image.
Place the image with one of its corners in the correct position.
Rotate the image around that corner (without rotating background canvas).
Any help with the above would be appreciated.
As tucuxi commented, if you really have 4 points and want the transform to place the image corners at these exact points, and affine transform won't do -- you'll need a perspective transform.
However, if you select two points of the four, you can do what you want, but you may have to scale the image. So let's say you just want to place a rotated and scaled version of your image such that its top edge goes from A' to B'. What you'll have to do is compute the affine transform, which involves determining the rotation angle, scaling factor, and translation from the segment AB to A'B'.
Here's a commented method that should do just that. I have not thoroughly tested it, but it shows how to implement the algorithm in Java.
package stackoverflow;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class ComputeImageTransform
{
public static AffineTransform computeTransform(
Rectangle2D imageBounds, Point2D a2, Point2D b2) {
double dx = b2.getX() - a2.getX();
double dy = b2.getY() - a2.getY();
// compute length of segment
double length = Math.hypot(dx, dy);
// compute scaling factor from image width to segment length
double scaling = length / imageBounds.getWidth();
// compute rotation angle
double rotation = Math.atan2(dy, dx);
// build the corresponding transform
// NOTE: the order of the individual transformations are applied is the
// reverse of the order in which the transform will apply them!
AffineTransform transform = new AffineTransform();
transform.translate(a2.getX(), a2.getY());
transform.rotate(rotation);
transform.scale(scaling, scaling);
transform.translate(-imageBounds.getX(), -imageBounds.getY());
return transform;
}
public static void main(String[] args) {
// transform top edge of image within this axis-aligned rectangle...
double imageX = 20;
double imageY = 30;
double imageWidth = 400;
double imageHeight = 300;
Rectangle2D imageBounds = new Rectangle2D.Double(
imageX, imageY, imageWidth, imageHeight);
// to the line segment a2-b2:
Point2D a2 = new Point2D.Double(100, 30);
Point2D b2 = new Point2D.Double(120, 200);
System.out.println("Transform image bounds " + imageBounds);
System.out.println(" to top edge " + a2 + ", " + b2 + ":");
AffineTransform transform = computeTransform(imageBounds, a2, b2);
// test
Point2D corner = new Point2D.Double();
corner.setLocation(imageX, imageY);
System.out.println("top left: " + transform.transform(corner, null));
corner.setLocation(imageX + imageWidth, imageY);
System.out.println("top right: " + transform.transform(corner, null));
corner.setLocation(imageX, imageY + imageHeight);
System.out.println("bottom left: " + transform.transform(corner, null));
corner.setLocation(imageX + imageWidth, imageY + imageHeight);
System.out.println("bottom right: " + transform.transform(corner, null));
}
}
This is the output:
Transform image bounds java.awt.geom.Rectangle2D$Double[x=20.0,y=30.0,w=400.0,h=300.0]
to top edge Point2D.Double[100.0, 30.0], Point2D.Double[120.0, 200.0]:
top left: Point2D.Double[100.0, 30.0]
top right: Point2D.Double[119.99999999999999, 199.99999999999997]
bottom left: Point2D.Double[-27.49999999999997, 44.999999999999986]
bottom right: Point2D.Double[-7.499999999999986, 214.99999999999997]
As you can see, you'll get some rounding errors due to the nature of floating-point computations.
I found a performance bottleneck and I don't know how to solve it.
Introduction: I wrote a sub class of JComponent which paints images in a 50 to 50 grid. Each image has the size of 50 to 50 pixel. The performance of "paintComponent" becomes quite bad when the images are "TRANSLUCENT".
So, I did some testing with fillRect instead of drawImage and I got the same behavior when the color has an alpha value.
Here the example code: (just an example code to demonstrate the performance difference)
private final GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice()
.getDefaultConfiguration();
// ...
protected void paintComponent(final Graphics graphics) {
super.paintComponent(graphics);
final BufferedImage buffer = this.graphicsConfiguration.createCompatibleImage(this.getWidth(), this.getHeight(), Transparency.TRANSLUCENT);
final Graphics2D bufferGraphics = (Graphics2D) buffer.getGraphics();
bufferGraphics.setColor(new Color(110, 110, 110));
for (int dy = 0; dy <= super.getHeight(); dy++) {
for (short dx = 0; dx <= super.getWidth(); dx++) {
bufferGraphics.fillRect(dx, dy, 1, 1);
}
}
graphics.drawImage(buffer, 0, 0, null);
}
The performance becomes worse (around 30 times slower) when "bufferGraphics.setColor(new Color(110, 110, 110));" is replaced by "bufferGraphics.setColor(new Color(110, 110, 110, 110));"
Question: Does anyone have an idea how to improve the performance?
Thanks in advance
Yes, draw directly on the graphics passed to your paintComponent(). Why do you create a buffered image, draw on it then copy/paint the whole image on the graphics (and then discard the buffered image)?
Swing is already double-buffered (by default): drawing on the passed graphics already happens on a buffer which will be made visible at the end of painting process of the JComponent. So your attempt to double-buffer is completely unnecessary and redundant.
If for some unknown reason you really need this, you should cache the created BufferedImage and reuse it in subsequent paintComponent() calls. Swing is single threaded, you don't even have to synchronize it or worry about concurrent access.
As far as tested on Java 13, raw bytewise operation for alphablending on image data array, written from scratch, are faster than calling awt primitives, especially if you have determined considerations on image data format and drawing operations. You can access buffered image pixels directly like this:
image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
data = ((DataBufferInt) mage.getRaster().getDataBuffer()).getData();
For example, this technique to logarithmically fade is about 3 times faster than fillrect with 50% alpha:
public void shift(int x0, int y0, int x1, int y1, int shift) {
int channelMask = 0xff & (~(0xFF << (8 - shift)));
int mask = (channelMask << 16) | (channelMask << 8) | (channelMask);
for (int i = y0; i < y1; i++) {
for (int x = i * w + x0; x < i * w + x1; x++)
data[x] = (data[x] >> shift) & mask;
}
}
Given 2 rgb colors and a rectangular area, I'd like to generate a basic linear gradient between the colors. I've done a quick search and the only thing I've been able to find is this blog entry, but the example code seems to be missing, or at least it was as of this posting. Anything helps, algorithms, code examples, whatever. This will be written in Java, but the display layer is already taken care of, I just need to figure out how to figure out what to display.
you want an interpolation between the first and the second colour. Interpolating colours is easy by calculating the same interpolation for each of its components (R, G, B). There are many ways to interpolate. The easiest is to use linear interpolation: just take percentage p of the first colour and percentage 1 - p of the second:
R = firstCol.R * p + secondCol.R * (1 - p)
There's another question related to this.
There are other methods of interpolation that sometimes work better. For example, using a bell-shaped (sigmoidal) interpolation function makes the transition smoother.
/EDIT: Oops, you mean using a predefined function. OK, even easier. The blog post you linked now has an example code in Python.
In Java, you could use the GradientPaint.
You can use the built in GradientPaint class.
void Paint(Graphics2D g, Regtangle r, Color c1, Color c2)
{
GradientPaint gp = new GradientPaint(0,0,c1,r.getWidth(),r.getHeight(),c2);
g.setPaint(gp);
g.fill(rect);
}
Using the basic AWT classes, you could do something like this:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JPanel;
public class LinearGradient extends JPanel {
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
Color color1 = Color.RED;
Color color2 = Color.BLUE;
int steps = 30;
int rectWidth = 10;
int rectHeight = 10;
for (int i = 0; i < steps; i++) {
float ratio = (float) i / (float) steps;
int red = (int) (color2.getRed() * ratio + color1.getRed() * (1 - ratio));
int green = (int) (color2.getGreen() * ratio + color1.getGreen() * (1 - ratio));
int blue = (int) (color2.getBlue() * ratio + color1.getBlue() * (1 - ratio));
Color stepColor = new Color(red, green, blue);
Rectangle2D rect2D = new Rectangle2D.Float(rectWidth * i, 0, rectWidth, rectHeight);
g2.setPaint(stepColor);
g2.fill(rect2D);
}
}
}
Following up on the execllent answer of David Crow, here's a Kotlin example implementation
fun gradientColor(x: Double, minX: Double, maxX: Double,
from: Color = Color.RED, to: Color = Color.GREEN): Color {
val range = maxX - minX
val p = (x - minX) / range
return Color(
from.red * p + to.red * (1 - p),
from.green * p + to.green * (1 - p),
from.blue * p + to.blue * (1 - p),
1.0
)
}
I've been using RMagick for that. If you need to go further the simple gradient, ImageMagick and one of its wrappers (like RMagick or JMagick for Java) could be useful.