I am currently profiling my Java-2d-Application (Game-Engine for learning purposes).
Since I cannot guarantee that each frame is overwritten completely, I have to clear the background to a solid color (i.e. Color.BLACK) each frame.
The way I do it is SLOW (about 40% of drawing-time in my environment goes to just clearing the background).
First I get a graphics-context from the bufferStrategy, then I draw a [PickYourColor]-Rectangle in full resolution on it before drawing the actual content.
// fill background with solid color
graphics.setColor(Color.BLACK);
graphics.fillRect(
0,
0,
(int) bounds.getWidth(),
(int) bounds.getHeight());
Is there a more efficient, platform-independant, way to clear the background to a solid color each frame using Java-2D (this is not a LWJGL-question)?
What I'm looking for is a graphics.clearBackgroundToSolidColor(Color color) - Method...
By request: here the full rendering method (it's not an SSCCE, but it's pretty short and self explanatory)
/**
* Create a new graphics context to draw on and
* notify all RenderListeners about rendering.
*/
public void render() {
///// abort drawing if we don't have focus /////
if (!this.windowJFrame.hasFocus()) {
return;
}
///// draw and create new graphics context /////
Graphics2D graphics = null;
do {
try {
graphics = (Graphics2D) this.bufferStrategy.getDrawGraphics();
Rectangle2 bounds = this.getBounds();
// set an inexpensive, yet pretty nice looking, rendering directives
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// fill background with solid color
graphics.setColor(Color.BLACK);
graphics.fillRect(
0,
0,
(int) bounds.getWidth(),
(int) bounds.getHeight());
// notify all listeners that they can draw now
synchronized (this.renderListeners) {
for (RenderInterface r : this.renderListeners) {
r.render(graphics, bounds);
}
}
// show buffer
graphics.dispose();
this.bufferStrategy.show();
} catch (Exception e) {
Logger.saveMessage("window", Logger.WARNING, "Caught exception while drawing frame. Exception: " + e.toString());
}
} while (this.bufferStrategy.contentsLost());
}
I can't say why the fillRect is slow, but you can try creating an Image and draw it as bg. not sure if it will be faster though.
try:
BufferedImage bi = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
int[] imageData =((DataBufferInt)bi.getRaster().getDataBuffer()).getData();
Arrays.fill(imageData, 0);
then instead of fillRect draw the Image:
graphics.drawImage(bi, 0, 0, null);
Tell me how it went(I have my doubts about this).
If you would like to clear the entire background than try canvas.drawColor(color, PorterDuff.Mode.CLEAR). Should be a bit faster
Related
I am coding a Swing Application, it uses Apache PDFBox to draw a PDF page to the Graphics2D object of a JPanel in the paintComponent method. The drawing takes a while, so when my application needs to display many PDF pages simultaneously, it get's slow and laggy. I know, since the JPanel I draw the PDF page to is part of the GUI, it needs to be drawn in the Event Dispatch Thread. But is there absolutely no possibility to draw each JPanel in an own thread? Like using SwingWorker or so?
Example code (simplified):
public class PDFPanel extends JPanel {
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D g2 = (Graphics2D) graphics;
int scale = 1; // (simplified this line)
g2.setColor(getBackground());
g2.fillRect(0, 0, getWidth(), getHeight());
try {
pdfRenderer.renderPageToGraphics(pageNumber, g2, (float) scale, (float) scale);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Use a BufferedImage image field. It has a method createGraphics() where you can draw to. Afterward call Graphics.dispose() to clean up resources.
Then in paintComponent check the availability of the image, for displaying it.
The rendering can be done in a Future, SwingWorker or whatever. You are right that heavy operations should never be done in paintComponent especially as it may be called repeatedly.
Better launch the rendering in the constructor, or in your controller.
BufferedImage image = new BufferedImage(getWidth(), getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
try {
pdfRenderer.renderPageToGraphics(pageNumber, g2, (float) scale, (float) scale);
} finally {
g2d.dispose();
}
Initially width and height are not filled, so better use width and height from the PDF. Also not that a Graphics2D allows scaling; you could easily add zooming.
How to atomically handle passing the rendered image is probably clear.
I am stuck (beyond the limits of fun) at trying to fix text quality with offscreen image double buffering.
Screen capture worth a thousand words.
The ugly String is drawn to an offscreen image, and then copied to the paintComponent's Graphics argument.
The good looking String is written directly to the paintComponent's Graphics argument, bypassing the offscreen image.
Both Graphics instances (onscreen and offscreen) are identically setup in terms of rendering quality, antialiasing, and so on...
Thank you very much in advance for your wisdom.
The very simple code follows:
public class AcceleratedPanel extends JPanel {
private Dimension osd; //offscreen dimension
private BufferedImage osi; //offscreen image
private Graphics osg; //offscreen graphic
public AcceleratedPanel() {
super();
}
#Override
public final void paintComponent(Graphics g) {
super.paintComponent(g);
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
osg2D.drawString("Offscreen painting", 10, 20);
//Dump offscreen buffer to screen
g.drawImage(osi, 0, 0, this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
/*
To make sure same settings are used in different Graphics instances,
a unique setup procedure is used.
*/
private void setupGraphics(Graphics2D g) {
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
private Graphics2D getOffscreenGraphics() {
//Graphics Acceleration
Dimension currentDimension = getSize();
if (osi == null || !currentDimension.equals(osd)) {
osi = (BufferedImage)createImage(currentDimension.width, currentDimension.height);
osg = osi.createGraphics();
osd = currentDimension;
}
return (Graphics2D) osg;
}
} //End of mistery
You are not drawing your two strings with the same color. The default color for the offscreen Graphics is rgb(0, 0, 0) (that is, pure black), while Swing will set the color of a Graphics object to the look-and-feel’s default color—which, for me on Windows 7, using the default theme, is rgb(51, 51, 51), or dark gray.
Try placing g.setColor(Color.BLACK); in your setupGraphics method, to ensure both strings are drawn with the same color.
Thanks for the replies.
With mentioning DPI, MadProgrammer has lead me to a working fix which I offer here more as workaround than as a 'clean' solution to be proud of. It solves the issue, anyway.
I noticed that while my screen resolution is 2880x1800 (Retina Display), MouseEvent's getPoint() method reads x=1440, y=900 at the lower right corner of the screen. Then, the JPanel size is half the screen resolution, although it covers the full screen.
This seen, the solution is as follows:
first, create an offscreen image matching the screen resolution, not the JPanel.getSize() as suggested in dozens of double buffering articles.
then, draw in the offscreen image applying a magnifying transform, bigger than needed, in particular scaling by r = screen dimension / panel dimension ratio.
finally, copy a down scaled version of the offscreen image back into the screen, applying a shrinking factor of r (or scaling factor 1/r).
The solution implementation is split into two methods:
An ammended version of the initial paintComponent posted earlier,
a helper method getDPIFactor() explained afterwards.
The ammended paintComponent method follows:
public final void paintComponent(Graphics g) {
super.paintComponent(g);
double dpiFactor = getDPIFactor();
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
//Paint stuff bigger than needed
osg2D.setTransform(AffineTransform.getScaleInstance(dpiFactor, dpiFactor));
//Custom painting
performPainting(osg2D);
//Shrink offscreen buffer to screen.
((Graphics2D)g).drawImage(
osi,
AffineTransform.getScaleInstance(1.0/dpiFactor, 1.0/dpiFactor),
this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
To complete the task, the screen resolution must be obtained.
A call to Toolkit.getDefaultToolkit().getScreenResolution() doesn't solve the problem, as it returns the size of a JPanel covering the whole screen. As seen above, this figure doesn't match the actual screen size in physical dots.
The way to get this datum is cleared by Sarge Bosch in this stackoverflow post.
I have adapted his code to implement the last part of the puzzle, getDPIFactor().
/*
* Adapted from Sarge Bosch post in StackOverflow.
* https://stackoverflow.com/questions/40399643/how-to-find-real-display-density-dpi-from-java-code
*/
private Double getDPIFactor() {
GraphicsDevice defaultScreenDevice =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice();
// on OS X, it would be CGraphicsDevice
if (defaultScreenDevice instanceof CGraphicsDevice) {
CGraphicsDevice device = (CGraphicsDevice) defaultScreenDevice;
// this is the missing correction factor.
// It's equal to 2 on HiDPI a.k.a. Retina displays
int scaleFactor = device.getScaleFactor();
// now we can compute the real DPI of the screen
return scaleFactor * (device.getXResolution() + device.getYResolution()) / 2
/ Toolkit.getDefaultToolkit().getScreenResolution();
} else
return 1.0;
}
This code solves the issue for Mac Retina displays, but I am affraid nowhere else, since CGraphicsDevice is an explicit mention to a proprietary implementation of GraphicsDevice.
I do not have other HDPI hardware with which to play around to have a chance to offer a wider solution.
For some reason my image loads after 1 second even though I call render before my thread.sleep ()
public void init (){
image=new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g= (Graphics2D) image.getGraphics();
running=true;
}
public void run (){
init();
while(running){
long start=System.nanoTime();
update();
render();
drawToScreen();
try{
thread.sleep(1000);
}
catch (Exception e){
e.printStackTrace();
}
}
}
public void update (){
}
public void render (){
g.clearRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.white);
g.fillRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.red);
for(int r=0; r<16; r++){
for(int c=0; c<16; c++){
MazeCell m=maze[15-r][c];
int y=15-m.getRow();
int x=m.getCol();
if(m.getWallUp()) g.drawLine(x*50, y*50, (x+1)*50, y*50);
if(m.getWallDown()) g.drawLine(x*50, (y+1)*50, (x+1)*50, (y+1)*50);
if(m.getWallLeft()) g.drawLine(x*50, y*50, x*50, (y+1)*50);
if(m.getWallRight()) g.drawLine((x+1)*50, y*50, (x+1)*50, (y+1)*50);
}
}
g.drawImage(mouseImage, mouse.getCol()*50, (15-mouse.getRow())*50, null);
}
public void drawToScreen (){
Graphics g2=getGraphics();
g2.drawImage (image, 0, 0, null);
g2.dispose();
}
Everything draws but at first there is a blank gray screen and it only loads after 1 second (the time the thread sleeps) even though render and drawToScreen are called first. I don't really know the reason.
Change your:
g2.dispose();
To:
g2.finalize();
Also, the description of the Graphics.drawImage() method states:
Draws as much of the specified image as is currently available. The image is drawn with its top-left corner at (x, y) in this graphics context's coordinate space. Transparent pixels in the image do not affect whatever pixels are already there.
This method returns immediately in all cases, even if the complete image has not yet been loaded, and it has not been dithered and converted for the current output device.
If the image has completely loaded and its pixels are no longer being changed, then drawImage returns true. Otherwise, drawImage returns false and as more of the image becomes available or it is time to draw another frame of animation, the process that loads the image notifies the specified image observer.
In other words, the call returns immediately, whether or not the image drawing has completed.
You can use MediaTracker to first make sure the image is loaded before you call Graphics.drawImage():
long timeout = 60000; // 60 seconds
MediaTracker tracker = new MediaTracker(this);
tracker.addImage(image, 0);
try {
if (!tracker.waitForID( 0, timeout ) ) {
System.out.println( "Timed Out!" );
return;
}
}
catch( Exception ex) {
System.out.println( "Error waiting for the image to load." );
return;
}
boolean isFinished = g.drawImage(image, 0, 0, this);
public void draw(Graphics2D g) {
// draw background
g.drawImage(bgImage, 0, 0, null);
g.drawImage(bgImage, 700, 0, null);
g.drawImage(bgImage, 0, 500, null);
g.drawImage(bgImage, 700, 500, null);
AffineTransform transform = new AffineTransform();
for (int i = 0; i < NUM_SPRITES; i++) {
Sprite sprite = sprites[i];
// translate the sprite
transform.setToTranslation(sprite.getX(),
sprite.getY());
// if the sprite is moving left, flip the image
if (sprite.getVelocityX() < 0) {
transform.scale(-1, 1);
transform.translate(-sprite.getWidth(), 0);
}
// draw it
g.drawImage(sprite.getImage(), transform, null);
}
}
}
I was looking at a draw method in this class. The method is called with an animation loop, it draws a set of animated sprites on the screen that pass the method the correct frame in their animation depending on a timer. The sprites are also moving around the screen at a randomly generated velocity bouncing off the edges of the frame. Depending on whether they are traveling left or right the draw method transforms the image so the sprite is facing in the correct direction. All that transform information is contained in the transform for the drawImage method which I'm used to seeing as g.drawImage(Image, x, y, null); though obviously you can't do that here as you would lose the image flipping capabilities. Is there a way to do it though? Is there a way to transform the image scale but set its location by coordinate?
I ask because transforming is a more processors intensive activity and if lots of sprites need to be active at once it might really slow everything down. BONUS QUESTION: Is this a legitimate concern or should I not be too worried considering the strength of modern systems.
I want to make a image with rounded corners. A image will come from input and I will make it rounded corner then save it. I use pure java. How can I do that? I need a function like
public void makeRoundedCorner(Image image, File outputFile){
.....
}
Edit : Added an image for information.
I suggest this method that takes an image and produces an image and keeps the image IO outside:
Edit: I finally managed to make Java2D soft-clip the graphics with the help of Java 2D Trickery: Soft Clipping by Chris Campbell. Sadly, this isn't something Java2D supports out of the box with some RenderhingHint.
public static BufferedImage makeRoundedCorner(BufferedImage image, int cornerRadius) {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage output = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = output.createGraphics();
// This is what we want, but it only does hard-clipping, i.e. aliasing
// g2.setClip(new RoundRectangle2D ...)
// so instead fake soft-clipping by first drawing the desired clip shape
// in fully opaque white with antialiasing enabled...
g2.setComposite(AlphaComposite.Src);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fill(new RoundRectangle2D.Float(0, 0, w, h, cornerRadius, cornerRadius));
// ... then compositing the image on top,
// using the white shape from above as alpha source
g2.setComposite(AlphaComposite.SrcAtop);
g2.drawImage(image, 0, 0, null);
g2.dispose();
return output;
}
Here's a test driver:
public static void main(String[] args) throws IOException {
BufferedImage icon = ImageIO.read(new File("icon.png"));
BufferedImage rounded = makeRoundedCorner(icon, 20);
ImageIO.write(rounded, "png", new File("icon.rounded.png"));
}
This it what the input/output of the above method looks like:
Input:
Ugly, jagged output with setClip():
Nice, smooth output with composite trick:
Close up of the corners on gray background (setClip() obviously left, composite right):
I am writing a follow up to Philipp Reichart's answer.
the answer of as an answer.
To remove the white background (seems to be black in the pictures), change g2.setComposite(AlphaComposite.SrcAtop);
to g2.setComposite(AlphaComposite.SrcIn);
This was a big problem for me because I have different images with transparency that I don't want to lose.
My original image:
If I use g2.setComposite(AlphaComposite.SrcAtop);:
When I use g2.setComposite(AlphaComposite.SrcIn); the background is transparent.
I found another way using TexturePaint:
ImageObserver obs = ...;
int w = img.getWidth(obs);
int h = img.getHeight(obs);
// any shape can be used
Shape clipShape = new RoundRectangle2D.Double(0, 0, w, h, 20, 20);
// create a BufferedImage with transparency
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D bg = bi.createGraphics();
// make BufferedImage fully transparent
bg.setComposite(AlphaComposite.Clear);
bg.fillRect(0, 0, w, h);
bg.setComposite(AlphaComposite.SrcOver);
// copy/paint the actual image into the BufferedImage
bg.drawImage(img, 0, 0, w, h, obs);
// set the image to be used as TexturePaint on the target Graphics
g.setPaint(new TexturePaint(bi, new Rectangle2D.Float(0, 0, w, h)));
// activate AntiAliasing
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// translate the origin to where you want to paint the image
g.translate(x, y);
// draw the Image
g.fill(clipShape);
// reset paint
g.setPaint(null);
This code can be simplified if you have a non-animated image, by creating the BufferedImage only once and keeping it for each paint.
If your image is animated though you have to recreate the BufferedImage on each paint. (Or at least i have not found a better solution for this yet.)