Suspicious fillRect() Speed
Okay so let me get this straight. Java fills rectangles by iterating through an array and changing the rgb values to a designated color. If all it does is change the color then why is Texturepaint so expensive if all it is doing is changing the integer in the array? Does changing the integer in between take time to register?
Fast fillRect() operation using setPaint(new Color());
setPaint(new Color(0,0,0));
fillRect(0,0,frame.getWidth(),frame.getHeight());
// Around 100+ fps repainting with timer set to zero milliseconds.
Slow fillRect() operation using setPaint(new TexturePaint());
setPaint(new TexturePaint(image, rectangle));
fillRect(0,0,frame.getWidth(),frame.getHeight());
// Around 20+ fps repainting with timer set to zero milliseconds.
As you can see from its sourcecode, Graphics delegates this functionality to subclasses.
My implementation seems to use SunGraphics2d, which again delegates it to a PixelFillPipe, which there are many implementations of. The OGLRenderer delegates this functionality to the Graphics card if possible, using OpenGL. The X11Renderer uses native X calls, like this:
native void XFillRect(long pXSData, long xgc,
int x, int y, int w, int h);
public void fillRect(SunGraphics2D sg2d,
int x, int y, int width, int height)
{
SunToolkit.awtLock();
try {
long xgc = validate(sg2d);
XFillRect(sg2d.surfaceData.getNativeOps(), xgc,
x+sg2d.transX, y+sg2d.transY, width, height);
} finally {
SunToolkit.awtUnlock();
}
}
XRRenderer uses this code:
public synchronized void fillRect(SunGraphics2D sg2d,
int x, int y, int width, int height) {
SunToolkit.awtLock();
try {
validateSurface(sg2d);
XRSurfaceData xrsd = (XRSurfaceData) sg2d.surfaceData;
x += sg2d.transform.getTranslateX();
y += sg2d.transform.getTranslateY();
tileManager.addRect(x, y, width, height);
tileManager.fillMask(xrsd);
} finally {
SunToolkit.awtUnlock();
}
}
I showed you this code, because it is more than setting colors in an array. Your mileage will vary per platform and JRE.
As I don't know which renderer/fillpipe you use, I can only recommend to look at your very own code, it's not that hard.
Related
I need to render a large amount of images for my game and they need to be scaled to any aspect ration to fit the screen and I can't find a solution anywhere.
It already renders the image just not in the correct aspect ratio.
Rendering code:
class _canvas extends JComponent {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (renderable_object part : objs) {
if (part.type == 5) {
g.drawImage(part.get_Image(), part.i, part.j, 23,23);
}
}
}
}
Renderable object:
class renderable_object {
int i = 0;
int j = 0;
int k = 0;
int l = 0;
int type = 0;
String file = "";
Image get_Image() {
return(Toolkit.getDefaultToolkit().getImage(file));
}
}
The code you show does not scale the image at all. There are at least two ways you can go from here.
You already found the correct method (looking at the title of the question) to use. With that method you can define the source and destination coordinates and rendering will happen accordingly. It seems it is up to you to ensure the coordinates you demand have the correct aspect ratio.
Another possibility would be to cast Graphics into a Graphics2D, then use https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/Graphics2D.html#drawImage(java.awt.Image,java.awt.geom.AffineTransform,java.awt.image.ImageObserver) to actually render the image.
The scaling and exact positioning of the image can now be done via AffineTransform, which you could obtain by
getScaleInstance(double sx, double sy).concatenate(getTranslateInstance(double tx, double ty))
so it might be easier to setup. This way it is also very easy to apply rotation, shearing or other stuff.
I am making a paint application and the flood fill tool works, but it takes about two minutes for it to fill a 400x180. What can I do to speed up this process? Here is the code I am currently using for this.
public void gradientSize(int x, int y, int origRGB, int index){
queue = new ArrayList<String>(); //queue is an ArrayList<String> that holds the points
time = System.currentTimeMillis(); // time is a long so I can calculate the time it takes to finish a flood fill
if(new Color(origRGB).equals(foreground)){ //foreground is the color the flood fill is using to fill in. origRGB is the RGB of the color I clicked
return;
}
if(!testFill(x, y, origRGB)){
return;
}
queue.add(pixel(x,y));
while(!queue.isEmpty()){
String pixel = queue.get(0);
int x2 = Integer.parseInt(pixel.substring(0, pixel.indexOf(",")));
int y2 = Integer.parseInt(pixel.substring(pixel.indexOf(",")+1,pixel.length()));
queue.remove(0);
if(testFill(x2, y2, origRGB)){
queue.add(pixel(x2+1, y2));
queue.add(pixel(x2-1,y2));
queue.add(pixel(x2,y2+1));
queue.add(pixel(x2,y2-1));
gradientPoints.add(pixel(x2, y2)); //gradientPoints is an ArrayList<String> that contains all the points for the fill
processed[y*image.getWidth()+x] = true; //processed[] is a boolean array that has a true or false value for each pixel to determine if it has been looked at yet.
}
}
}
public boolean testFill(int x, int y,int origRGB){ //testFill tests if the current pixel is okay to be filled or not
if(x>=0&&x<image.getWidth()&&y>=0&&y<image.getHeight()){
int testRGB = image.getRGB(x, y);
Color orig = new Color(origRGB,true);
Color test = new Color(testRGB,true);
if ((Math.abs(orig.getRed() - test.getRed()) <= difference) && (Math.abs(orig.getGreen() - test.getGreen()) <= difference)&& (Math.abs(orig.getBlue() - test.getBlue()) <= difference)&&(Math.abs(orig.getAlpha() - test.getAlpha()) <= difference)) {
if (!gradientPoints.contains(pixel(x,y))) {
if (!queue.contains(pixel(x,y))) {
if (processed[y*image.getWidth()+x]==false) {
return true;
}
}
}
}
}
return false;
}
public String pixel(int x, int y){//this returns the String value of a pixel's x and y coordinates.
return String.valueOf(x)+","+String.valueOf(y);
}
public void gradientFillSolid(){ //This gets all the points from gradientPoints and fills each pixel from there.
for(String s:gradientPoints){
int x = Integer.parseInt(s.substring(0, s.indexOf(',')));
int y = Integer.parseInt(s.substring(s.indexOf(',')+1,s.length()));
image.setRGB(x, y, foreground.getRGB());
}
System.out.println(System.currentTimeMillis()-time);
repaint();
}
The output for a 400x180 rectangle was 148566 milliseconds. Is there a way for me to speed up this process at all? Any help is appreciated.
Here's your problem:
queue.add(pixel(x2+1, y2));
queue.add(pixel(x2-1,y2));
queue.add(pixel(x2,y2+1));
queue.add(pixel(x2,y2-1));
You're adding every pixel multiple times (once here, and once for every block around that particular pixel) and rechecking it every time it's added again. If you had a 4x4 block, or something, you really wouldn't notice a slowdown, but when we're talking about 400x180 (72,000) pixels being added and checked 3 or 4 times per pixel, it gets to be huge.
My suggestion is very simple: Check before you add. Or even better, make a small little "MyPixel" class that has a boolean value that is flipped to true after you've already checked it. That way, you can skip doing any math on it and you can just do something like this:
if(my_pixel.has_been_checked == false)
queue.add(my_pixel);
You are converting the pixel coordinates to a String, then parsing them back out. I have found in my experience that string concatenation is an expensive action. Instead, just store pixels as java.awt.Point objects and read the coordinates from those.
See Above for description
However my code is adding circles to the array in incorrect colours:
I have a Color baseColor, which contains a variable int baseGreen. This int is reduced during each recursive call, with the intention of changing the type of green for each set of 3 circles.
If anyone is able to hazard a guess as to why this is happening I would be very grateful. Thanks.
tracking base color is unnecessary as you are passing it into your method.
this is a simple way of make the color progressively darker
public void createCircles(int x, int y, int rad, Color parentColor){
Circle myCircle = new Circle(x, y, rad, parentColor);
...
if(!(rad<1)){
...
Color myColor = parentColor.darker();
createCircles(x - (2*rad), y, rad/3, myColor);
createCircles(x, y, rad/3, myColor);
createCircles(x + (2*rad), y, rad/3, myColor);
}
}
I've been looking for a explicit answer for this but I can't find one anywhere.
A Graphics object is always passed into the java paint methods examples:
paintComponent(Graphics c)
paintIcon(Component c, Graphics g, int x, int y)
Often these paint methods are overriden by subclassses and changes need to be applied to the Graphics object, examples: setColor() or fillRect(). These changes can either be applied to the passed in Graphics object, or a new one can be created using g.create().
I read in another SO answer that you should call g.create() anytime you are making any changes that are "not easily undone", but the article was not clear on which changes are "not easily undone" (I also can no longer find this article for reference).
I know that transposing or translating are situations where you should create a new graphics object. But what about simpler actions like g.setColor(...) or g.fillRect(...)?
My Question:
When should you call g.create() to use for your Graphics object
and what situations should you use the one passed into the method?
Is there any downside to creating a new Graphics object?
Example
To try to narrow this question down, I'll give an example. For the following situation, would I need to create a new graphics object?
private Icon delegate;
#Override
public void paintIcon(Component c, Graphics g, int x, int y)
{
delegate.paintIcon(c, g, x, y);
g.setColor(Color.gray);
int width = getIconWidth() - 4;
int height = getIconHeight() - 4;
g.fillRect(x + 2, y + 2, width, height);
}
I have an Image img that, when trying to use any of above mentioned get methods, returns -1. Why is this? And what is an ImageObserver object?
According to the documentation for Image, these methods return a -1 if the size, width, or height (respectively) are not yet known.
Also, ImageObserver is simply an interface that provides methods to get notifications about the information of an Image object that is being constructed.
You can use the MediaTrack to get your image's width & height like that :
// ...
Image img;
// ...
// ... loading the image using IO or Toolkit ot something
MediaTracker MTrack = new MediaTracker(this); // in my case, 'this' is a JFrame
MTrack.addImage(img,1);
try
{
MTrack.waitForID(1);
}
catch(InterruptedException e)
{
e.getMessage();
}
int width = img.getWidth(null);
int height = img.getHeight(null);
double ratio = (double)width/(double)height;
// ....
// ....
According to the Java docs, it seems that if you call getHeight() etc. on an Image while it's still in the construction phase, such that the height is not really "known", -1 is returned and the ImageObserver provided is notified.
ImageObserver is an interface that provides a way for you to have a callback method that is called when the image is updated in some way. ImageObserver is implemented by java.awt.Component, so if you extend that in the containing class you could override imageUpdated() and store the image height when the function is called asynchronously (if Image.getHeight returns -1).
In GraphTest, override imageUpdate:
#Override
public void imageUpdate(Image img, int flags,
int x, int y, int width, int height) {
if ((flags & (WIDTH|HEIGHT)) == (WIDTH|HEIGHT)) {
// width and height have meaningful values, do your resize here
}
super.imageUpdate(img, flags, x, y, width, height);
}
The problem is the time needed to load the Image.
As long as the image is not loaded you get the -1. When the image is finally loaded, it fires the ImageObserver. The obeserver, in your case the Background instance - g.drawImage(bkg, 0, 0, this); - will force a repaint of the Background causing the now loaded image to be painted.
You can also use the java.awt.MediaTracker to track the status of your Image.
[]]