I'm doing the following to a Canvas object.
graphics.setColor(BLUE);
graphics.fill(new Rectangle2D.Double(x, y, width, height));
I'd like to fade in the fill colour to create a smooth transition from the canvas background colour to the new colour (and possibly fade out whatever colour was originally there).
I've played with this kind of thing (setting the graphics object's composite to an AlphaComposite which a Timer updating the alpha value every n milliseconds) but I get flickering.
I'm wondering what general concept I'm missing.
Thanks for any pointers.
First of all, how could you be using the AWT? It is quite outdated. I reccomend you switch to swing, mainly because swing has double buffering, which would remove your flicker.
Your application does exactly what you tell it to do. If you want to make a fade-in effect, you have to determine what kind of color changes you want to make, create a function which does it, and implement the fade itself.
I'd approach it like that:
class FadeEffect{
int totalDurationMs;
int elapsedDurationMs;
Color initialColor;
Color finalColor;
Color getColor(int durationDelta) {
elapsedDurationMs += durationDelta;
if (elapsedDurationMs > totalDurationMs) {
return finalColor;
}
double progress = 1.0d*elapsedDurationMs/totalDurationMs;
return new Color( (int)(finalColor.getRed()-initialColor.getRed())*progress,
(int)(finalColor.getGreen()-initialColor.getGreen())*progress,
(int)(finalColor.getBlue()-initialColor.getBlue())*progress);
}
//getters, setters, etc
}
As for the flickering issue: make sure you are using double buffering - either in your component, or by manually drawing on a off-screen buffer (image) and only posting the image to the screen when the drawing is complete.
Here is a sample code from my Graphic2D app doing the double buffering:
private VolatileImage vImg;
#Override
public void paint(Graphics g) {
if (gc==null) gc = this.getGraphicsConfiguration();
do {
boolean sizeChanged = false;
sizeChanged = (vImg!=null&&(vImg.getWidth()!=getWidth()|| vImg.getHeight()!=getHeight()));
if (vImg == null || vImg.validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE
|| sizeChanged) {
vImg = gc.createCompatibleVolatileImage(getWidth(), getHeight());
vImg.setAccelerationPriority(1);
}
final Graphics gimg = vImg.getGraphics();
if (gimg instanceof Graphics2D) {
renderContents((Graphics2D) gimg);
gimg.dispose();
g.drawImage(vImg, 0, 0, null);
} else {
throw new UnsupportedOperationException("Rendering impossible, graphics are not of Graphics2D class");
}
} while (vImg.contentsLost());
updateAnimationNo();
}
Related
I am attempting to create a day/night cycle in a simple RPG-style game with Java. I have reached a point where I have included a low-alpha color overlay will change hue over time to simulate this:
// Render the overlay rectangle.
public void render(Graphics2D g) {
// Set the color to yellow with ~50% opacity.
g.setColor(new Color(255, 255, 0, 125));
// Draw the rectangle.
g.fillRect(0, 0, handler.getWidth(), handler.getHeight());
g.dispose();
}
While this works, I would like to introduce blending modes (i.e. add, subtract, screen, soft light) into my code to increase the day/night cycle's realism.
Here's what I have tried, but to no avail:
Researching JavaFX's blending mode options. I don't want to switch to JavaFX.
Creating an all-white image that overlays the screen and uses setComposite() and setXORMode() from a different StackOverflow answer.
Using an image with a BlendingMode and setComposite() from the package org.jdesktop.swingx.graphics.BlendComposite.
My desired code is something similar to what follows, but I will take what I can get.
public void render(Graphics2D g) {
g.setColor(new Color(255, 255, 0, 125));
g.setBlendMode(ADD);
g.fillRect(0, 0, handler.getWidth(), handler.getHeight());
g.dispose();
}
One possibility to solve this would be to buffer your screen in a BufferedImage and then implement your own drawing methods(This definitely isn't a fast way to do this as it throws away all hardware-acceleration. If you need to render a big screen this can be laggy depending on your system and your implementation.):
class MyGraphics {
public enum BlendingMode {
NORMAL, ADD, …
}
public BufferedImage buffer;
private BlendingMode bm = BlendingMode.NORMAL;
private int color = 0;
public MyGraphics(BufferedImage buf) {
buffer = buf; // Initialize it with a bufferedImage of your panel size.
}
public void setBlendMode(BlendingMode b) {
bm = b;
}
private int applyBlendingMode(int imgColor1, int newColor) {
int rImg, rNew, gImg, gNew, bImg, bNew, alphaImg, alphaNew; // You can intialize those using bitshifts from the color integers.
switch(bm) {
// Implement all your blending modes here.
}
// And don't forget to return the result!
}
public void setColor(int c) {
color = c;
}
public void fillRect(int x0, int y0, int width, int height) {
for(int x = x0; x < x0+width; x++) {
for(int y = y0; y < y0+height; y++) {
buffer.setRGB(x, y, applyBlendingMode(buffer.getRGB(x, y), color));
}
}
}
}
If the setRGB and getRGB are working to slow for you, you might speed it up by working directly on the BufferedImage's data array(see here for further info).
Also keep in mind, that if you want to use normal blending mode, you can still do it the normal way using buffer.getGraphics() to get your Graphics object. This should be faster in most cases.
After you are done with implementing your blending-modes and maybe some further functionality to draw more then a rectangle you can just simply draw the underlying BufferedImage on your screen using Graphics.drawImage(buffer, 0, 0);
I am making an interface which contains a drawn image that is sized as big as possible while maintaining aspect ratio within the JFrame. The code below is used to achieve this behavior:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (image == null) {
return;
}
// Calculate what image size fits within the component's bounds
Dimension fittingImageSize = getFittingImageSize();
// Scale the image. We use AffineTransform as it's much faster than scaledInstance(...)
AffineTransform at = new AffineTransform();
at.scale(fittingImageSize.getWidth() / image.getWidth(), fittingImageSize.getHeight() / image.getHeight());
AffineTransformOp scaleOp = new AffineTransformOp(at, AffineTransformOp.TYPE_BILINEAR);
BufferedImage scaledImage = new BufferedImage((int)fittingImageSize.getWidth(), (int)fittingImageSize.getHeight(), image.getType());
scaleOp.filter(image, scaledImage);
// Calculate the offset
Point offset = getImageOffset(fittingImageSize);
// Draw the image
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(scaledImage, offset.x, offset.y, null);
}
However, when I resize the JFrame I get a heap space exception:
Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:75)
at java.awt.image.Raster.createPackedRaster(Raster.java:467)
at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1032)
at java.awt.image.BufferedImage.<init>(BufferedImage.java:333)
As far as I can see, I am not doing anything strange aside from properly sizing the image. However, it seems that this operation is what is causing the issue. Can anyone provide a bunch of pointers at how I can better approach this?
I ended up using a small workaround to prevent repainting while the component is being resized. I added a ComponentListener to the relevant component, and in the componentResized method I did the following:
#Override
public void componentResized(ComponentEvent e) {
isResizing = true;
timer.cancel();
timer.schedule(new TimerTask() {
#Override
public void run() {
isResizing = false;
revalidate();
repaint();
}
}, redrawAfterResizeDelay);
}
Then, in paintComponent I guard against the isResizing boolean: if it's true then paintComponent returns early.
It isn't the nicest of fixes, so I am still hoping for better answers, but in my situation this is an acceptable fix.
Edit: The above fix may work for others, but it was not the underlying problem.
The problem was in the way I calculated the fittingImageSize. When the width divided by the height of the component would be larger than the height divided by the width of the component, then a wrong fittingImageSize was returned causing the app to attempt to create a BufferedImage with a width of several million px. That is what ended up causing the memory exceptions.
I'm coding a GUI that will be doing some graphics translations/rotations, etc.
My problem is that when I try to translate my graphics,
(a) The entire screen translates instead of my one little painted area
(b) The old paint stays there, leaving a big paint blob instead of a translated image
(c) If I use the clearRect method to allow me to avoid (b), the entire screen goes white and (a) is still a problem
my DrawPanel class (I called it "LaunchTubeImage" for whatever reason)
private class LaunchTubeImage extends JPanel {
private Color colour;
public LaunchTubeImage(Color color) {
super();
this.colour = color;
}
public void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,
tubeImage.getSize().height - 50);
g.setColor(colour);
g.clearRect(0,0,getWidth(),getHeight());
g.fillRect(tubeImage.getSize().width/2,
tubeImage.getSize().height - 100 , 10, 50);
}
}
where this is called in my code
tubeImage = new LaunchTubeImage(Color.MAGENTA);
angle.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e) {
tubeImage.repaint();
}
});
Case 1: Comment out clearRect in that 1st block of code I posted
http://i58.tinypic.com/2d1l5w2_th.png
Black background as desired. Not rotated yet. Looks good so far.
http://oi60.tinypic.com/1zw1sm.jpg
Rotated it with my JSpinner... you see that the previous location was not removed (and note how my buttons randomly doubled and put themselves at the top of the screen).
Case 2: Keeping in the clearRect method
oi57.tinypic.com/2s84307.jpg
Layout is fine so far, but I wanted the background to be black
oi57.tinypic.com/4rde8x.jpg
Yay! It rotated. But note the weird behavior of that random "15" that appeared in my top right corner
oi58.tinypic.com/vymljm.jpg
And finally... when I resize the window you see that my entire screen was rotated - not just the pink image I wanted to rotate
Tips/fixes/advice? Thanks!! I hope I've provided enough information
(P.s. if you insist on us asking clear/useful questions.... then DON'T limit the number of images you can post... :/ )
The first line of an overridden paintComponent method should usually be super.paintComponent(g). On a JPanel, this will cause the drawing area to be cleared with the background color. If you want to clear the background with a different color, you can do this by manually filling a rectangle (clearRect is discouraged, see the JavaDoc), but of course, this has to be done before applying any transform.
So your method should probably look like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(colour);
g.fillRect(0,0,getWidth(),getHeight());
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,tubeImage.getSize().height - 50);
gg.fillRect(tubeImage.getSize().width/2,tubeImage.getSize().height - 100 , 10, 50);
}
I have a JScrollPane with a JPanel where I can draw by mouse and code.
I need the possibility to zoom on details in my drawing.
But soon I get a outOfMemoryError. I think because I make my drawing to big while zooming.
This is my code:
private BufferedImage _bufferedImage;
private int _panelWidth = 2000, _panelHeight = 1500;
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
if(_bufferedImage != null){
g.drawImage(_bufferedImage, 0, 0, this);
}
}
public void draw(float zoomFactor){
try {
int width = (int)(_panelWidth * zoomFactor);
int height = (int)(_panelHeight * zoomFactor);
_bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = _bufferedImage.createGraphics();
g2.setBackground(Color.WHITE);
g2.setPaint(Color.BLACK);
g2.scale(zoomFactor, zoomFactor);
drawHouse(g2); ...
g2.dispose();
}
catch (Exception e) {
e.printStackTrace();
}
repaint();
}
There must be better practice then what I did.
I can just draw the area of the scrollpane, but then I can't use the scrollbars,
then I have to use buttons with arrow up, right, left, down to scroll in my drawing.
Anyone who can me give a hint?
but then I can't use the scrollbars
Scrollbars work when the preferred size of the component is greater than the size of the scrollpane. If you are zooming the image in the paintComponent() method then you would also need to override the getPreferredSize() method to return the appropriate size of the image that takes into account the zooming factor.
So in your case the preferred size would be the size of your image.
If you want to zoom in, I am assuming you are no trying to make "bigger pixels", but to draw the same figures at a higher scale. In that case, you should not be using a BufferedImage at all -- instead, you should draw to a suitably scaled JPanel or similar. You can always take a snapshot of whatever you are rendering whenever you need it; but rendering to a BufferedImage without need is wasteful (of time and memory).
See this answer for details.
Hope this question could emphasize more about the fading out effect of Jlabel (swing).
Certainly, yes... I already follow some guide and some answers given from This Link Thread, but mine is quite a bit different. It's not just only A text inside the JLabel, there's an image i put on.
I proceed to follow on the Thread Located out of stackoverflow. And yet, it gives me a fade out effect. But there's horrible thing occured; the white background.
How to solve this out?
I share the interface here...
The First screenshot taken here is the earlier phase when the fade out have not occured yet. While,
The Second screenshot taken here is the unwanted result i mentioned.
Tobe honest, I used the Trident Library to do animatiing;
So, whenever the user click over the image it will execute this code;
Timeline tm = new Timeline(jll_btnProceed);
tm.addPropertyToInterpolate("intensity", 1.0f, 0.0f);
tm.setDuration(1000);
tm.play();
and... the JLabel itself, I used to override it using this source code;
/**
*
* #author Gumuruh
*/
public class JLabelFader extends JLabel {
private float intensity = 1.0f;
public void setIntensity(float intensity) {
this.intensity = intensity;
this.setOpaque(false);
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
final Composite oldComposite = g2.getComposite();
g2.setComposite(AlphaComposite.SrcOver);
final Color c = getBackground();
final Color color = new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) (255 * (1.0f - intensity)));
g2.setColor(color);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setComposite(oldComposite);
}
}
My hand and my head can't stop for making this trully solved. Thus I tried to follow up some example from the Java Filthy Rich Client ebook and then applying the source code given below, but first I need to COMMENT the protected void paint(Graphic g) method written above, and simply adding this source code;
private BufferedImage buttonImage = null;
public void paint(Graphics g) {
// Create an image for the button graphics if necessary
if (buttonImage == null || buttonImage.getWidth() != getWidth()
|| buttonImage.getHeight() != getHeight()) {
buttonImage = getGraphicsConfiguration().
createCompatibleImage(getWidth(), getHeight());
}
Graphics gButton = buttonImage.getGraphics();
gButton.setClip(g.getClip());
// Have the superclass render the button for us
super.paint(gButton);
// Make the graphics object sent to this paint() method translucent
Graphics2D g2d = (Graphics2D) g;
AlphaComposite newComposite =
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, intensity);
g2d.setComposite(newComposite);
// Copy the button's image to the destination graphics, translucently
g2d.drawImage(buttonImage, 0, 0, null);
}
in which at the end... giving me nice fade out effect. But At first, it gave me the 2nd horrible effect which is BLACK BACKGROUND rendered first. Can't believe me?? Okay, Here is First screen shot AFTER applying code from ebook. and here is the nice fade out effect result.
If there's somebody telling me;
"YOUR PNG IS NOT TRANSPARENT!".
Please, dont say like that. Because, I followed the creation of PNG inside the Photoshop nicely using this Tut.
Now, My head's spinned, but my heart laughed and can't handle it over. OMG. Geeezzz....!
And the New Stories begun...
* UPDATED FROM HERE AND BELOW *
Ehm, depply thank you very much to our friend called... MKorbel,
from his thread given at this link. Providing a clear example of fading out effect the Swing JButton and I tried to implement it into my JLabel, and violaaa...!!
IT works.
Let's give a big clap for MKorbel. :D
SO anyway, how could I fix the earlier code? Pretty simple, just COMMENT the Paint() method, and use again the paintComponent() method and it should be overriden with the new source code below;
#Override
public void paintComponent(java.awt.Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, intensity));
if (rectangularLAF && isBackgroundSet()) {
Color c = getBackground();
g2.setColor(c);
g.fillRect(0, 0, getWidth(), getHeight());
}
super.paintComponent(g2);
g2.dispose();
}
Now the JLabel become easy to be changed with its intensity -variable.
Fading out and Fading in is accomplished.
Okay. Now everything seems "OKAY". BUt hold a moment, there's something strangely occured again here. Have you noticed it? Hmmm....
I tried to give a Mouse Event (Hover On) into the JLabel that we override the paintComponent() method discussed earlier.With this line of code;
myJLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
Logically, it should change the Cursor when Hover ON. But, Here comes another strange effect. (Really sorry, but this is stil the continue of the main case). The strange effect now is the Mouse Cursor can't be changed when we Hover On the Jlabel. It still can't change the Cursor. Seems the paintComponent() method effecting the way Cursor react. Is that true?