Can't accelerate pixel-modified BufferedImages - java

For quite a long time, 1-2 months, I have been trying to find an answer to this particular problem:
I can't get my image hardware accelerated!
I've been searching on the net, created my own methods, hit my head with the keyboard (still feel the pain) but no success.
Although I hate libraries other than Java SDK, I tried LWJGL and JOGL but for some stupid reason they dont work on my computer.
I tried using System.setProperty("Dsun.java2d.opengl", "True"), I used VolatileImage but I can't draw individual pixels to it.(I tried using drawLine(x,y,x,y) but it's slow)
Now I am so desperate. I will do anything to fix this! So please, if you know the solution (I know some of you do) tell me so I can get rid of this.
My code:
public static void render(int x, int y, int w, int h, ) {
int a[] = new int[3]; // The array that contains RGB values for every pixel
BufferedImage bImg = Launcher.contObj.getGraphicsConfiguration().createCompatibleImage(800, 600, Transparency.TRANSLUCENT); // Creates an image compatible to my JPanel (Runs at 20-24 FPS on 800x600 resolution)
int[] wr = ((DataBufferInt) bImg.getRaster().getDataBuffer()).getData(); // Contains the image data, used for drawing pixels
for (int i = 0; i < bImg.getWidth(); i++) {
for (int j = 0; j < bImg.getHeight(); j++) {
a[0] = i % 256;
a[2] = j % 256;
a[1] = i * j % 256;
wr[i + j * bImg.getWidth()] = new Color(a[0], a[1], a[2]).getRGB(); // Sets the pixels from a[]
}
}
bImg.flush();
g.drawImage(bImg, x, y, w, h, null); // Draws the image on the JPanel
g.dispose();
System.out.println(bImg.getCapabilities(Launcher.contObj.getGraphicsConfiguration()).isAccelerated()); // Prints out whether I was successful and made the image accelerated or failed and made everything worse
}
I hope you understand the code. Please modify it in any way to help me find the solution to my problems.
Note: Please don't post anything about external libraries unless you are absolutely sure that I can't get this working without them.
Also, could it be that my graphics card doesn't support acceleration? (because I saw posts where hardware accel works for other people but not for me) Btw, it's a GeForce 430 GT.
THANKS IN ADVANCE!

Source copied from : Java Hardware Acceleration not working with Intel Integrated Graphics
Try this:
package graphicstest;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferStrategy;
public class GraphicsTest extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new GraphicsTest();
}
});
}
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferCapabilities bufferCapabilities;
BufferStrategy bufferStrategy;
int y = 0;
int delta = 1;
public GraphicsTest() {
setTitle("Hardware Acceleration Test");
setSize(500, 300);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setVisible(true);
createBufferStrategy(2);
bufferStrategy = getBufferStrategy();
bufferCapabilities = gc.getBufferCapabilities();
new AnimationThread().start();
}
class AnimationThread extends Thread {
#Override
public void run() {
while(true) {
Graphics2D g2 = null;
try {
g2 = (Graphics2D) bufferStrategy.getDrawGraphics();
draw(g2);
} finally {
if(g2 != null) g2.dispose();
}
bufferStrategy.show();
try {
// CHANGE HERE, DONT SLEEP
//Thread.sleep(16);
} catch(Exception err) {
err.printStackTrace();
}
}
}
}
public void draw(Graphics2D g2) {
if(!bufferCapabilities.isPageFlipping() || bufferCapabilities.isFullScreenRequired()) {
g2.setColor(Color.black);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setColor(Color.red);
g2.drawString("Hardware Acceleration is not supported...", 100, 100);
g2.setColor(Color.white);
g2.drawString("Page Flipping: " + (bufferCapabilities.isPageFlipping() ? "Available" : "Not Supported"), 100, 130);
g2.drawString("Full Screen Required: " + (bufferCapabilities.isFullScreenRequired() ? "Required" : "Not Required"), 100, 160);
g2.drawString("Multiple Buffer Capable: " + (bufferCapabilities.isMultiBufferAvailable() ? "Yes" : "No"), 100, 190);
} else {
g2.setColor(Color.black);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setColor(Color.white);
g2.drawString("Hardware Acceleration is Working...", 100, 100);
g2.drawString("Page Flipping: " + (bufferCapabilities.isPageFlipping() ? "Available" : "Not Supported"), 100, 130);
g2.drawString("Full Screen Required: " + (bufferCapabilities.isFullScreenRequired() ? "Required" : "Not Required"), 100, 160);
g2.drawString("Multiple Buffer Capable: " + (bufferCapabilities.isMultiBufferAvailable() ? "Yes" : "No"), 100, 190);
}
y += delta;
if((y + 50) > getHeight() || y < 0) {
delta *= -1;
}
g2.setColor(Color.blue);
g2.fillRect(getWidth()-50, y, 50, 50);
}
}
Output I got hardware accelaration not available. java.exe was taking 12% CPU. 700 FPS
Then I added System variable:
Variable name: J2D_D3D_NO_HWCHECK
Variable value: true
then restarted IDE, and ran program:
I got amazing result. I got hardware acceleration available. java.exe was taking 5% CPU. 1700 FPS. Animation was great!
Above things are to check whether hardware acceleration works in your system.
Now to your question:
AFAIK: You can't get real hardware acceleration with BufferedImage. You should work with VolatileImage to get hardware acceleration. But with VolatileImage, you can't get pixel data buffer to modify pixels. And rendering pixel by pixel using hardware doesn't make sense.
What I suggest:
1) Design your logic, which can render pixels using Graphics and Graphics2D of volatile image. But don't go to make hack like drawing lines of 1 pixel.
2) Use buffer strategies, double / triple buffering.
3) If you want to stick with BufferedImage for setting each pixel, use BufferedImage as data model, while rendering draw buffered image on volatile image. This will help a lot if you are scaling image.
4) Cache the images frequently being rendered.
5) Write code better, like:
int wi = bImg.getWidth();
int he = bImg.getHeight();
for (int i = 0; i < wi; i++) {
for (int j = 0; j < he; j++) {
wr[i + j * wi] = ((i % 256) << 16) | ((i * j % 256) << 8) | (j % 256);
}
}
6) For time consuming math operation like sqrt(), sin(), cos(), cache results of these operations and create lookup tables.

Related

How can I plot in Java using Graphics2d.setPaint() fast for large data set

I have a large set of data I wish to plot on a graph that can range from 10k points to about 20 Million Points. At 10k points the plot happens at an ok speed(within a second), but at 20 Million points the plot seems to take minutes.
I'm using Java for the program and rewriting the code in another language just to improve the graphical plotting speed for one single plot that only occurs at at maximum data set is not in the cards.
Is this speed something I have to live with because a 20 Million point plot inherently will take this long due to the data size or am I missing out on some optimisation flag/method/etc?
My Data is in a 2d Array of 13,000 by 4096 called Data.
This is populated from outside the Plot Function in Main.java
//In Plot.java
public class PlotG extends JPanel
{
double xscale = 0.0;
double yscale = 0.0;
protected void paintComponent(Graphics g)
{
super.paintCompnent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHint.Key_ANTIALIASING, RenderingHint.VALUE_ANTIALIAS_ON);
//Scaling
int sizew = Data.size();
int sizeh = Data.get(0).size();
xscale = (getWidth()*1.0)/(sizew *1.0);
yscale = (getHeight()*1.0)/(sizeh *1.0);
//Set Colour
g2.setPaint(Color.GREEN);
//Plot
for(int j=0; j<sizew; j++)
{
for(int k=0;k<sizeh; k++)
{
if(Data.get(j).get(k) > MinimumValueToPlot) //I only plot points above the constant value MinimumValueToPlot
{
int x = xscale*j;
int y = yscale*k;
g2.fillOval(x,y,1,1);
}
}
}
return;
}
}
private Plot dataPlot = new Plot()
public PlotStuff(ArrayList<ArrayList<Double>> In)
{
Data = In;
InitPLot(getContentPane());
}
private void InitPlot(Container contentPane)
{
getContentPane().setBackground(Color.GRAY);
getContentPane().setLayout(new FlowLayout(FlowLayout.LEADING));
setMinimumSize(new Dimension(1650, 830));
pack();
GraphPanel = new JPanel();
GraphPanel.setBounds(6,11,1470,750);
GraphPanel.setBorder( BorderFactory.createTitleBorder( BorderFactory.createLineBorder(Color.GREEN, 2),
"Title",
TitledBorder.DEFAULT_JUSTIFICATION,
TitledBorder.DEFAULT_POSITION,
new Font("Tahoma", Font.BOLD, 18),
Color.WHITE));
getContentPanel().add(GraphPanel);
GraphPanel.setLayout(new BorderLayout());
dataPlot.setBackGround(Color.BLUE);
dataPlot.setForeGround(Color.WHITE);
dataPlot.setPreferredSize(new Dimension(1470, 750));
GraphPanel.add(dataPlot);
return;
}
//in Main.java
.
.
PlotStuff p = new PlotStuff(Data);
p.redrawGraph();
p.setVisible();
.
.
Is there anyway to improve the speed if the number of points above my constant MinimumValueToPlot reaches 20 Million and above? Given my maximum possible data set is 13k x 4096 = 53,248,000, it is possible, but the highest experienced number of points above MinimumValueToPlot is so far only 20 Million.
Am I doing something wrong in the JPanel declarations? I have seen some discussions say that setPreferredSize shouldn't be used? Is this the case?
Thanks.
You claim Data is a 2d array, but it is not. It is an ArrayList of ArrayLists. Work with an actual array instead.
Instead of ArrayList<ArrayList<Double>> you'd do double[][].
In your loop you are calling fillOval to set the color of a single pixel. That causes huge unneccessary overhead. You are basically telling your application to calculate the pixels for an oval shape of size 1x1 20 million times!
I suggest you create a BufferedImage, get its pixel array, and set the pixel values directly. Then, when done, draw that image to your Graphics2d object:
BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D imgGraphics2D = img.createGraphics();
imgGraphics2D.setColor(Color.white);
imgGraphics2D.fillRect(0, 0, getWidth(), getHeight());
imgGraphics2D.dispose();
byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
for(int j=0; j < sizew; j++)
{
for(int k=0; k < sizeh; k++)
{
if(data[j][k] > minimumValueToPlot)
{
int x = (int)(xscale * j);
int y = (int)(yscale * k);
int pixelindex = (y * getWidth() + x) * 3;
pixels[pixelindex + 1] = 255; // set the green byte
}
}
}
g2.setComposite(AlphaComposite.Src);
g2.drawImage(img, 0, 0, null);
Please take this with a grain of salt, as I wrote this from memory.
(Also I took the liberty of renaming Data to data and MinimumValueToPlot to minimumValueToPlot. Per convention variables in Java start with a lower case letter to distinguish them from classes.)
Plotting points like that in your paint components means that you paint that many points every time the component is repainted This means it is an expensive operation to resize the window, or click a button because it has to repaint every point.
In this version, I've made a method to redo the graphics, and and the paintComponent just repaints the backing buffer. Note, that painting to the backing buffer will be about as fast as you can accomplish paint, and it ignores the display properties of swing. That way you can use a profiler and see how fast you can make the painting routine go. For example using some of the suggestions of Max above.
public class PlotG extends JPanel
{
BufferedImage buffer;
double xscale = 0.0;
double yscale = 0.0;
public PlotG(){
buffer = new BufferedImage(1470, 750, BufferedImage.TYPE_INT_ARGB);
}
protected void paintComponent(Graphics g)
{
super.paintCompnent(g);
g.drawImage(buffer, 0, 0, this);
}
void updateBuffer(){
Graphics2D g2 = (Graphics2D)backing.getGraphics();
g2.setRenderingHint(RenderingHint.Key_ANTIALIASING, RenderingHint.VALUE_ANTIALIAS_ON);
//Scaling
int sizew = Data.size();
int sizeh = Data.get(0).size();
xscale = (getWidth()*1.0)/(sizew *1.0);
yscale = (getHeight()*1.0)/(sizeh *1.0);
//Set Colour
g2.setPaint(Color.GREEN);
//Plot
for(int j=0; j<sizew; j++)
{
for(int k=0;k<sizeh; k++)
{
if(Data.get(j).get(k) > MinimumValueToPlot) //I only plot points above the constant value MinimumValueToPlot
{
int x = xscale*j;
int y = yscale*k;
g2.fillOval(x,y,1,1);
}
}
}
repaint();
}
public Dimension getPreferredSize(){
return new Dimension(buffer.getWidth(this), buffer.getHeight(this));
}
}
The problem with setPreferredSize, is not a performance issue, it can conflict with swings swings layout managers. When you have a component, such as your custom graphing panel, you do want to have it determine the size. I've added it to the PlotG class, so now it will try to layout according to the backing buffer.
The you have to update the buffer, I suggest doing it on the main thread after you've set your panel to visible. That way it won't block the edt.

Why am I getting a black image after replacing all the pixels of a BufferedImage object with magenta colored ones?

I am following a Java Game development tutorial, and I hit a roadblock because I am unable to figure out what is wrong with my code. I am supposed to render a new image by changing each pixel of the buffered image into the color I want. Then I copy that array over to the array in my Game class and I draw the rendered image. The relevant code is directly below. I discuss what I have tried directly after the code snippets.
This code snippet is from my Game class:
private Screen screen;
private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// Converts image into array of integers
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
public Game() {
Dimension size = new Dimension(width * scale, height * scale);
setPreferredSize(size);
screen = new Screen(width, height);
frame = new JFrame();
pixels = new int[width * height];
}
// Displays images to the screen
public void render() {
BufferStrategy bs = getBufferStrategy();
// Checks if the buffer strategy exists. If it doesn't create a triple buffer strategy
if(bs == null) {
// Triple buffering
createBufferStrategy(3);
return;
}
screen.render();
// Copies array in Screen class to pixels array in (this) class
for (int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
}
// Must be in chronological order
Graphics g = bs.getDrawGraphics();
g.setColor(Color.CYAN);
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.dispose();
// Display next available buffer
bs.show();
}
This code snippet is from my Screen class:
private int width;
private int height;
public int[] pixels;
public Screen(int width, int height) {
this.width = width;
this.height = height;
// Size of the pixels array reserves one element for each pixel
pixels = new int[width * height];
}
public void render() {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
pixels[x + y * width] = 0xff00ff;
}
}
}
I have tried to "debug" by checking to see if both of my pixels arrays in the Game and the Screen class are being loaded with the magenta color. I simply did:
for (int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
System.out.println(pixels[i]);
}
and I also tried:
for (int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
System.out.println(screen.pixels[i]);
}
In both cases, the decimal representation of magenta (FF00FF in Hex) gets printed out until I close the window.
Another thing I tried is using the image.setRGBmethod to change the color of the image.
I added this line
image.setRGB(20, 20, 16711935); into the render method as follows:
// Displays images to the screen
public void render() {
BufferStrategy bs = getBufferStrategy();
// Checks if the buffer strategy exists. If it doesn't create a triple buffer strategy
if(bs == null) {
// Triple buffering
createBufferStrategy(3);
return;
}
screen.render();
// Copies array in Screen class to pixels array in Game (this) class
/* for (int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
}
*/
// Must be in chronological order
Graphics g = bs.getDrawGraphics();
g.setColor(Color.CYAN);
g.fillRect(0, 0, getWidth(), getHeight());
image.setRGB(20, 20, 16711935);
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.dispose();
// Display next available buffer
bs.show();
}
A magenta dot showed up in my frame, and I am also able to turn the entire frame into a solid magenta color by modifying the render method in the Screen class. Now, I am wondering why my "original" code isn't working. From what I understand, the setRGB method is much slower than what I originally intended on using to render images, so I don't want to just give up and settle with using setRGB.
I have spent practically the entire day going over all the spelling in my code, making sure those nested for loops in the Screen class are correct, and etc. I am at a loss here. I don't understand what the issue is.
If the code snippets I included are not enough, I have created a Gist of my code in its entirety here.
This code gets the color int for the input rgb colors
public static int getIRGB(int Red, int Green, int Blue){
Red = (Red << 16) & 0x00FF0000; //Shift red 16-bits and mask out other stuff
Green = (Green << 8) & 0x0000FF00; //Shift Green 8-bits and mask out other stuff
Blue = Blue & 0x000000FF; //Mask out anything not blue.
return 0xFF000000 | Red | Green | Blue; //0xFF000000 for 100% Alpha. Bitwise OR everything together.
}
I suspect you are drawing a magenta color with 0% opacity

Adding multiple images to GPanel with Java

Im building a blackjack game in java for school and I can't seem to figure out how to add the first four cards to the GPanel. The array of cards are shuffled and the strings in the array match the file name of the images. I can get the first card in the array to load but not the other three. Any help would be awesome.
public void paintComponent(Graphics g) {
//System.out.println(gameInProgress);
if(gameInProgress == true) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Image image = null;
int i;
currentPosition = 0;
for (i = 0; i < 4; i++) {
image = Toolkit.getDefaultToolkit().getImage("images/"+deck[currentPosition - i]+".png");
currentPosition++;
}
g2.drawImage(image, 115, 5, 80, (int) 106.67, this);
g2.finalize();
}
}
You have 3 main issues in your code.
The first issue is caused by [currentPosition - i] because the result will always equal 0, so the image/card at array index 0 will be the only one that will ever be drawn.
The second issue is that you only draw one image because g2.drawImage is only called after the for loop, instead it should be inside the for loop so that all 4 images/cards are drawn.
The third issue is that you always draw your images in the same place, so even if you where drawing all 4 images you would only see the last one because it covers the previous ones.
Try this, it should print all 4 images (Assuming you have a 435x112 panel):
public void paintComponent(Graphics g) {
//System.out.println(gameInProgress);
if(gameInProgress == true) {
super.paintComponent(g);
//Add a couple new variables
int x = 115;
Graphics2D g2 = (Graphics2D) g;
Image image = null;
int i;
for (i = 0; i < 4; i++) {
image = Toolkit.getDefaultToolkit().getImage("images/"+deck[i]+".png");
//New location of g2.drawImage
//The X positions should change each time
g2.drawImage(image, x, 5, 80, 107, this);
//Change the X location of the next image
x = x + 80;
}
//Moved g2.drawImage from here to above the bracket
g2.finalize();
}
}

Issue with drawRect/fillRect from Swing Library (Images included)

Just started messing around with Swing for a class project GUI in Java. I'm trying to draw a game board, however, not a conventional one. I'm trying to draw one more like a parchessi board, so each board tile needs to have a specific location rather than a grid.
So far, I've run into this issue. In paint(), I'm trying to paint 5 rectangles, odd ones blue and empty, even ones red and filled in. However, instead of a nice checkered pattern, I get this:
Can anyone help me figure out why it's doing this?
Code:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rectangles extends JPanel {
public static void main(String[] a) {
JFrame f = new JFrame();
f.setSize(800, 800);
f.add(new Rectangles());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
public void paint(Graphics g) {
int x = 15;
int y = 15;
int w = 15;
int h = 15;
for(int i = 0; i < 5; i++){
if(i%2==0){
g.setColor(Color.RED);
g.fillRect (x, y, x+w, y+h);
}
else{
g.setColor(Color.BLUE);
g.drawRect (x, y, x+w, y+h);
}
x+=15;
System.out.println(Integer.toString(x) + ' ' + Integer.toString(y) + '|' + Integer.toString(w) + ' ' + Integer.toString(h));
}
}
}
Output from the Println statement(x,y,width,height):
30 15|15 15
45 15|15 15
60 15|15 15
75 15|15 15
90 15|15 15
It looked like there was overlap in the first image, so I modified the code and tried this:
for(int i = 0; i < 5; i++){
g.setColor(Color.BLUE);
g.drawRect (x, y, x+w, y+h);
x+=15;
}
Here's what happens with this code:
Why is there overlap? What causes this?
Also, does anyone know a good way to make an easily modifiable array of Rectangles? Or any good advice or tools for drawing that type of board?
Welcome to the reasons you should not break the paint chain...
Start by calling super.paint(g) as the first line of your paint method, before you do any custom painting.
A better solution would be to override paintComponent instead of paint, but still making sure you call super.paintComponent before you perform any custom painting...
Take a look at Performing Custom Painting and Painting in AWT and Swing for more details
Next, start reading the JavaDocs on Graphics#fillRect, you will see that the last two parameters represent the width and height, not the x/y position of the bottom corner
public class Rectangles extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int x = 15;
int y = 15;
int w = 15;
int h = 15;
for (int i = 0; i < 5; i++) {
if (i % 2 == 0) {
g.setColor(Color.RED);
g.fillRect(x, y, w, h);
} else {
g.setColor(Color.BLUE);
g.drawRect(x, y, w, h);
}
x += 15;
System.out.println(Integer.toString(x) + ' ' + Integer.toString(y) + '|' + Integer.toString(w) + ' ' + Integer.toString(h));
}
}
}

How to draw a decent looking Circle in Java

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]).

Categories

Resources