How to initialize a graphics g when building a RenderingEngine - java

So a buddy of mine built a RenderingEngine where it pretty much contains the methods that graphics has. That way you only need to call the RenderingEngines class's methods to render stuff to the screen. Without looking and trying to learn instead of cheating, im trying to replicate it based off my knowledge. I ran into a problem though. All my methods work its just that when the RenderingEngine class get initialized in my game class the graphics g in my RenderingEngine class is null after trying to get the JPanel's graphics. I don't know if i'm not understanding how this works and would love an explanation, ill put the code down below.
RENDERING CLASS BELOW:
package game.Main;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
public class RenderingEngine extends JPanel
{
private Graphics g;
public RenderingEngine()
{
g = this.getGraphics();
}
public void setColor(Color color)
{
g.setColor(color);
}
public void fillRect(Rectangle rect)
{
g.fillRect(rect.x, rect.y, rect.width, rect.height);
}
public void drawRect(Rectangle rect)
{
g.drawRect(rect.x, rect.y, rect.width, rect.height);
}
public void setStringFont(Font font)
{
g.setFont(font);
}
public void drawString(String string, int x, int y)
{
g.drawString(string, x, y);
}
public void drawImage(BufferedImage image, Rectangle rect)
{
g.drawImage(image, rect.x, rect.y, rect.width, rect.height, this);
}
public void clear(Rectangle rect)
{
g.clearRect(rect.x, rect.y, rect.width, rect.height);
}
public int widthofString(String string, Font font)
{
String text = string;
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
Font f1 = font;
int textwidth = (int)(font.getStringBounds(text, frc).getWidth());
return textwidth;
//int textheight = (int)(font.getStringBounds(text, frc).getHeight());
}
public int heightofString(String string, Font font)
{
String text = string;
AffineTransform affinetransform = new AffineTransform();
FontRenderContext frc = new FontRenderContext(affinetransform,true,true);
Font f1 = font;
//int textwidth = (int)(font.getStringBounds(text, frc).getWidth());
int textheight = (int)(font.getStringBounds(text, frc).getHeight());
return textheight;
}
public void paint()
{
}
}
WHERE I INITIALIZE RENDERINGENGINE IN THE GAME CLASS:
private void Load()
{
frame = new JFrame();
frame.setSize(800,600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
render = new RenderingEngine();
frame.add(render);
LoadContent();
gameloop = new GameLoop();
gameloop.Run();
}

You might consider it as unsatisfactory when I answer the question about "How to initialize a Graphics g" for a JPanel with ...
Not at all!
You might want to have a look at the Performing Custom Painting article, or the article about Painting in AWT and Swing (which unfortunately was slightly messed up during the transition from Sun to Oracle, but still contains valuable and more detailed information than the other article).
The Graphics of a JPanel is, so to say, managed by the operating system, and represents an area on the screen where the painting may happen. When the panel is not yet visible, the Graphics that is returned by a call to getGraphics() may be null. It may also be null at other occasions. Particularly, when it is accessed from a thread that is not the event dispatch thread.
In general, as a rule of thumb:
Never call getGraphics on a Component!
One possible solution for such a "RenderingEngine" class would be to use the Graphics object of a BufferedImage. This should not be too complicated, but you'll have to consider several aspects here:
When will the image be initialized?
When will the graphics be initialized?
When will the graphics be disposed?
What happens when the panel is resized?
...
However, with this method, it should at least be possible to create such a "RenderingEngine" that is suitable for the intended usage.

Related

How to leave the existing graphics on a JComponent during each call of paintComponent

I'm currently attempting to paint to a JComponent several times from a public method which calls the "paintComponent" method in order to place several shapes onto the JComponent, as if it had been painted to within a local context. (Don't ask why.) Whenever I attempt this, however, the JComponent is wiped before the new shape is drawn, leaving only the new shape. Is there a way the contents of the JComponent can be held over between calls of paintComponent, or is there an entirely different approach to take which achieves the same result?
No matter the solution, I need to be able to draw any given shape onto the JComponent using a public method, and have the contents of the JComponent be maintained between additions.
Here's my code for the JComponent in question:
package imageProcessor.elements;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
public class ImageElement extends JComponent {
private Shape shapeToBeDrawn;
private Color shapeColor;
public ImageElement(int width, int height) {
this.setSize(width, height);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D graphics = (Graphics2D)g;
if (shapeToBeDrawn != null) {
graphics.setColor(shapeColor);
graphics.fill(shapeToBeDrawn);
}
}
public void drawRect(int x, int y, int width, int height, Color color) {
shapeToBeDrawn = new Rectangle2D.Double(x, y, width, height);
shapeColor = color;
repaint();
}
}

How do you use re-size all Graphic2D

In java how can you make a game fully realizable! But so logic and graphics can work with it? I have tried using SCALE methods. But this doesn't allow perfect full-screen for every computer. So I made this:
public void resize(int WIDTH, int HEIGHT, boolean UNDECORATED) {
frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame.setMaximumSize(new Dimension(WIDTH, HEIGHT));
frame.setMinimumSize(new Dimension(WIDTH, HEIGHT));
this.WIDTH = WIDTH;
this.HEIGHT = HEIGHT;
frame.setUndecorated(UNDECORATED);
frame.setSize(WIDTH, HEIGHT);
}
So you can set your screen size to whatever you want! It works but the graphics will not work with it? Is there a way in Graphics2D to stretch all the graphics so it fits? For example if there was a method that existed like:
G2D.resize(WIDTH, HEIGHT, Image.NEAREST_PARENT_RESCALE);
Any idea?
Things I have tried:
Drawing all graphics to a Buffered-image then drawing that Image onto the screen size.
Just using SCALE and doing WIDTH * SCALE etc.
Lots of math
Things I do not mind
If you have a WIDE-SCREEN it stretches graphic2D objects to the size.
If you have a SQUARE-SCREEN it squishes graphics2D objects to the size.
So how can I make a perfectly resealable game using Graphics2D, JFrame.
In the most generic form, one can consider this as a classical problem of graphics programming, namely, as the transformation from world coordinates to screen coordinates. You have an object that has a size of "1.0 x 1.0" in your world coordinate system (regardless of which unit this has). And this object should be painted so that it has a size of, for example, "600 pixels * 600 pixels" on the screen.
Broadly speaking, there are at least three options to achieve this in Swing:
You can draw into an image, and then draw a scaled version of the image
You can draw into a scaled Graphics2D object
You can draw scaled objects
Each of this has possible advantages and disadvantages, and hidden caveats.
Drawing into an image, and drawing a scaled version of the image:
This might look like a simple solution, but has a potential drawback: The image itself has a certain resolution (size). If the image is too small, and you are scaling it up to fill the screen, it may appear blocky. If the image is too large, and you are scaling it down to fit into the screen, pixels of the image may be lost.
In both cases, there are several tuning parameters for the process of scaling the image. In fact, scaling an image is far more tricky than it looks at the first glance. For details, one may refer to the article The Perils of Image.getScaledInstance() by Chris Campbell.
Drawing into a scaled Graphics2D object
The Graphics2D class already offers the full functionality that is necessary to create the transformation between the world coordinate system and the screen coordinate system. This is accomplished by the Graphics2D class by internally storing an AffineTransform, which describes this transformation. This AffineTransform may be modified directly via the Graphics2D object:
void paintSomething(Graphics2D g) {
...
g.draw(someShape);
// Everything that is painted after this line will
// be painted 3 times as large:
g.scale(3.0, 3.0);
g.draw(someShape); // Will be drawn larger
}
Some care has to be taken to properly manage the transform that is stored in the Graphics2D object. In general, one should create a backup of the original AffineTransform before applying additional transformations, and restore this original transform afterwards:
// Create a backup of the original transform
AffineTransform oldAT = g.getTransform();
// Apply some transformations
g.scale(3.0, 4.0);
g.translate(10.0, 20.0);
// Do custom painting the the transformed graphics
paintSomething(g):
// Restore the original transformation
g.setTransform(oldAT);
(Another advice for the last method: The Graphics2D#setTransform method should never be used to apply a new coordinate transform on top of an existing transform. It is solely intended for restoring an "old" transform, as shown in this example (and in the documentation of this method)).
One potential drawback of scaling with the Graphics2D class is that afterwards, everything will be scaled. Particularly, this scaling will also affect line widths (that is, the width of the Stroke). For example, consider a sequence of calls like this one:
// By default, this will paint a line with a width (stroke) of 1.0:
g.draw(someLine);
// Apply some scaling...
g.scale(10.0, 10.0);
// Now, this will paint the same line, but with a width of 10.
g.draw(someLine);
The second call will cause a line to be drawn that is 10 pixels wide. This may not be desired in many cases. This effect can be avoided with the third alternative:
Drawing scaled objects
The transformation between the world coordinate system and the screen coordinate system can also be maintained manually. It is convenient to represent this as an AffineTransform. The AffineTransform class can be used to create transformed versions of Shape object, that can then be drawn directly into an (un-transformed) Graphics2D object. This is accomplished with the AffineTransform#createTransformedShape method:
void paintSomething(Graphics2D g) {
...
// Draw some shape in its normal size
g.draw(someShape);
// Create a scaling transform
AffineTransform at = AffineTransform.getScaleInstance(3.0, 3.0);
// Create a scaled version of the shape
Shape transformedShape = at.createTransformedShape(someShape);
// Draw the scaled shape
g.draw(transformedShape);
}
This is probably the most versatile approach. The only potential drawback is that, when many small, simple shapes are drawn, this will cause many, small temporary transformed shapes to be created, which may cause reduced performance. (There are ways to alleviate this problem, but detailed performance considerations and optimizations are beyond the scope of this answer).
Summary
The follwing image shows the comparison of all approaches. Some example objects (represented as Shape objects) are drawn. Each row compares the three different scaling methods mentioned above. With their "default" size, the objects fill a rectangle in world coordinates that has a size of 100x100. In the first two rows, they are scaled up to fill an area on the screen of 190x190 pixels. In the last two rows, they are scaled down to fill an area on the screen of 60x60 pixels. (These sizes have been chosen in order to have some "odd" scaling factors of 1.9 and 0.6. Certain effects (artifacts) may not appear when the scaling factors are whole numbers, or exactly 0.5, for example).
For the upscaling and the downscaling, there additionally is a comparison between the "standard" way of painting, and "high quality" painting (indicated by the "(HQ)" in the title of each panel). The "high quality" here simply means that the rendering hints
KEY_ANTIALIAS = VALUE_ANTIALIAS_ON
KEY_RENDERING = VALUE_RENDER_QUALITY
have been set:
Here is the corresponding program, as an MCVE:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ScalingMethodComparison
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new GridLayout(0,1));
Dimension larger = new Dimension(190,190);
Dimension smaller = new Dimension(60,60);
f.getContentPane().add(createPanel(larger, false));
f.getContentPane().add(createPanel(larger, true));
f.getContentPane().add(createPanel(smaller, false));
f.getContentPane().add(createPanel(smaller, true));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static JPanel createPanel(Dimension d, boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,3));
for (ScalingMethodComparisonPanel.ScalingMethod scalingMethod :
ScalingMethodComparisonPanel.ScalingMethod.values())
{
p.add(createPanel(d, scalingMethod, highQuality));
}
return p;
}
private static JPanel createPanel(
Dimension d, ScalingMethodComparisonPanel.ScalingMethod scalingMethod,
boolean highQuality)
{
JPanel p = new JPanel(new GridLayout(1,1));
p.setBorder(BorderFactory.createTitledBorder(
scalingMethod.toString()+(highQuality?" (HQ)":"")));
JPanel scalingMethodComparisonPanel =
new ScalingMethodComparisonPanel(
createObjects(), d, scalingMethod, highQuality);
p.add(scalingMethodComparisonPanel);
return p;
}
// Returns a list of objects that should be drawn,
// occupying a rectangle of 100x100 in WORLD COORDINATES
private static List<Shape> createObjects()
{
List<Shape> objects = new ArrayList<Shape>();
objects.add(new Ellipse2D.Double(10,10,80,80));
objects.add(new Rectangle2D.Double(20,20,60,60));
objects.add(new Line2D.Double(30,30,70,70));
return objects;
}
}
class ScalingMethodComparisonPanel extends JPanel
{
private static final Color COLORS[] = {
Color.RED, Color.GREEN, Color.BLUE,
};
enum ScalingMethod
{
SCALING_IMAGE,
SCALING_GRAPHICS,
SCALING_SHAPES,
}
private final List<Shape> objects;
private final ScalingMethod scalingMethod;
private final boolean highQuality;
private final Dimension originalSize = new Dimension(100,100);
private final Dimension scaledSize;
private BufferedImage image;
public ScalingMethodComparisonPanel(
List<Shape> objects,
Dimension scaledSize,
ScalingMethod scalingMethod,
boolean highQuality)
{
this.objects = objects;
this.scaledSize = new Dimension(scaledSize);
this.scalingMethod = scalingMethod;
this.highQuality = highQuality;
}
#Override
public Dimension getPreferredSize()
{
return new Dimension(scaledSize);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.WHITE);
g.fillRect(0,0,getWidth(), getHeight());
if (highQuality)
{
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
if (scalingMethod == ScalingMethod.SCALING_IMAGE)
{
paintByScalingImage(g);
}
else if (scalingMethod == ScalingMethod.SCALING_GRAPHICS)
{
paintByScalingGraphics(g);
}
else if (scalingMethod == ScalingMethod.SCALING_SHAPES)
{
paintByScalingShapes(g);
}
}
private void paintByScalingImage(Graphics2D g)
{
if (image == null)
{
image = new BufferedImage(
originalSize.width, originalSize.height,
BufferedImage.TYPE_INT_ARGB);
}
Graphics2D ig = image.createGraphics();
paintObjects(ig, null);
ig.dispose();
g.drawImage(image, 0, 0, scaledSize.width, scaledSize.height, null);
}
private void paintByScalingGraphics(Graphics2D g)
{
AffineTransform oldAT = g.getTransform();
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
g.scale(scaleX, scaleY);
paintObjects(g, null);
g.setTransform(oldAT);
}
private void paintByScalingShapes(Graphics2D g)
{
double scaleX = (double)scaledSize.width / originalSize.width;
double scaleY = (double)scaledSize.height / originalSize.height;
AffineTransform at =
AffineTransform.getScaleInstance(scaleX, scaleY);
paintObjects(g, at);
}
private void paintObjects(Graphics2D g, AffineTransform at)
{
for (int i=0; i<objects.size(); i++)
{
Shape shape = objects.get(i);
g.setColor(COLORS[i%COLORS.length]);
if (at == null)
{
g.draw(shape);
}
else
{
g.draw(at.createTransformedShape(shape));
}
}
}
}
This is actually quite easy in Java. In a Graphics2d environment, the logical coordinate system (the coordinates you use in the drawing routines) and the physical coordinate system (the coordinates as they appear) on the screen are completely unrelated. Every time you draw onto a Graphics2d object, the logical coordinates are first translated to the physical coordinates by an AffineTransform object, and this AffineTransform object can be modified. For this you can use the Graphics2D.scale(double,double), Graphics2D.rotate(double), Graphics2D.translate(double,double) and Graphics2D.shear(double,double) methods.
So if you first call
g2d.scale(2.0,2.0);
then all your graphics that you subsequently draw will be twice as large in both directions.
If I understood you correctly all you want is to draw your graphics in different resolutions without removing or adding any content.
Well one of the "things you have tried" can do that.
Drawing to a fixed size BufferedImage will ensure that all your components are visible within that BufferedImage (assuming you draw them correctly and relative to it's fixed size) then you can just draw the image to your flexible size screen.
Here's a full runnable code example that does that:
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Test extends Canvas implements Runnable {
// fixed size for the image
private static final int WIDTH = 640;
private static final int HEIGHT = 480;
private BufferedImage image;
private boolean running;
private Thread t;
public Test(Dimension dims) {
super();
setPreferredSize(dims); // actual screen size
image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
running = false;
}
public synchronized void start() {
if (running)
return;
t = new Thread(this);
running = true;
t.start();
}
public synchronized void stop() {
if (!running)
return;
running = false;
boolean retry = true;
while (retry) {
try {
t.join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void render() {
// draw to your image
Graphics2D g2d = (Graphics2D) image.getGraphics().create();
g2d.fillRect((WIDTH / 2) - 25, (HEIGHT / 2) - 25, 50, 50);
g2d.dispose();
// draw the image to your screen
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
g2d = (Graphics2D) bs.getDrawGraphics().create();
g2d.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g2d.dispose();
bs.show();
}
public void run() {
// approximately sync rendering to 60 FPS don't use it as it is.
// there are much better ways to do this.
long startTime = System.currentTimeMillis();
long frameTime = 1000 / 60;
long tick = 0;
while (running) {
while ((System.currentTimeMillis() - startTime) > tick) {
render();
tick += frameTime;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test test = new Test(new Dimension(800, 600));
JFrame frame = new JFrame("Fit to screen");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
test.stop();
frame.dispose();
super.windowClosing(e);
}
});
frame.getContentPane().add(test);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
test.start();
}
});
}
}
This is only a quick implementation there are things that can be done better in that code bu you get the picture. Hope this helps.
Maybe this will help :
Scaling graphics2D that contains basic shapes has a drawback : thickness of lines are doubled if the scale is doubled, that's a problem in an application implementing a zoom feature...
The only way I found is to make the preferred size of the container bigger and then, draw the shapes.
Here's a zoom function using mouse wheel and the pixel of the object pointed by the mouse stays under the mouse pointer.
It took me a long time to figure out how to do that properly, but I finally found out...(the application is an astrolabe and I wanted to zoom in and out)
The graphics2D belongs to a JPanel that is contained in the bottom part of a JSplitPane :
public void mouseWheelMoved(MouseWheelEvent e) {
Dimension dim = new Dimension(), oldDim = this.getPreferredSize();
double newX, newY;
Rectangle rect, oldRect;
if(this.mousewheel >= 0){
this.mousewheel += -e.getWheelRotation() * this.mousewheelSensibility;
}
else {
this.mousewheel = 0;
}
dim.setSize(this.astro.splitBottomDimension.getWidth() + this.mousewheel, this.astro.splitBottomDimension.getHeight() + this.mousewheel);
oldRect = this.getVisibleRect();
this.mouseX = e.getX();
this.mouseY = e.getY();
this.setPreferredSize(dim);
newX = this.mouseX / oldDim.getWidth() * dim.getWidth();
newY = this.mouseY / oldDim.getHeight() * dim.getHeight();
rect = new Rectangle((int)newX - (this.mouseX - oldRect.x), (int)newY - (this.mouseY - oldRect.y), oldRect.width, oldRect.height);
this.scrollRectToVisible(rect);
this.revalidate();

How to stop JComponent from clearing?

I am making a molecule designing application. I can draw the lines and circles, but it clears the old lines each time you click, so basically, you can only design molecules with 2 atoms.
Also, the mouseEvents don't deliver if you click very fast which is also a problem.
Here is the code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class MoleculeDesigner extends JComponent implements MouseListener {
private Point op, cp;
private boolean first = true;
public static final Color linecolor = new Color(0, 255, 0);
private static final long serialVersionUID = 1L;
private BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
public MoleculeDesigner() {
JFrame f = new JFrame("Molecule Designer");
f.setBackground(Color.WHITE);
f.addMouseListener(this);
f.add(this);
f.setSize(100, 100);
f.setDefaultCloseOperation(3);
f.setVisible(true);
}
public static void main(String[] args) {
new MoleculeDesigner();
}
#Override
protected void paintComponent(Graphics g) {
if(op != null && cp != null) {
Graphics2D g2 = img.createGraphics();
super.paintComponent(g2);
g2.setColor(linecolor);
g2.drawLine((int) op.getX(), (int) op.getY(), (int) cp.getX(), (int) cp.getY());
g2.setColor(Color.BLACK);
g2.fillOval((int) cp.getX(), (int) cp.getY(), 10, 10);
op = (Point) cp.clone();
g2.dispose();
}
}
#Override
public Dimension getPreferredSize() {
return getParent().getMaximumSize();
}
#Override
public void mouseClicked(MouseEvent e) {
if(!first) {
cp = e.getPoint();
cp.setLocation(cp.getX(), cp.getY() - 8);
}
else {
op = e.getPoint();
first = false;
}
repaint();
}
#Override public void mousePressed(MouseEvent e) {}
#Override public void mouseReleased(MouseEvent e) {}
#Override public void mouseEntered(MouseEvent e) {}
#Override public void mouseExited(MouseEvent e) {}
}
All help appreciated!
Either 1) draw in a BufferedImage which is then displayed inside of your paintComponent override, or 2) put your data into an ArrayList or other collection, and then iterate through the collection inside of paintComponent. I'd do the latter if I needed the data for other purposes. Also, never ever do this:
public void update(Graphics g) {
paintComponent(g);
}
This is not how Swing graphics are supposed to be done and is potentially dangerous code. Please read:
Basic Swing Graphics Tutorial
Advanced Swing Graphics Information
Edit
More detail regarding option 1:
Create a BufferedImage using one of its constructors.
Do your drawing on the image.
When you need to draw, get a Graphics object from the BufferedImage using getGraphics() or createGrahpics() (for a Graphics2D object)
Draw with this Graphics object
Then dispose() the Graphics object.
Then call repaint() to ask the JVM to repaint the component.
Draw the image in your paintComponent method by calling g.drawImage(...), passing in your buffered image.
Benefits: often the drawing is quicker, and I often use this to draw background images.
Drawbacks: the data points are not available, and so if you need to do manipulation or animation of your data points, this is not the way to go.
You don't, nor should you.
paint in Swing is a destructive process, this is the way it was designed. That is, there is an expectation that when you component is requested to paint itself, it will clean up the Graphics context before painting anything (this is slightly different for transparent components though).
Swing has no concept of what was painted on your component before and because the Graphics context is shared amongst all the components been painted, unless you clear the graphics first, you could end up with unwanted paint artifacts
Possible solutions might include...
Painting to some kind of backing buffer (such as a BufferedImage), which you use the paintComponent method to draw. This is limited in the fact that it just acts like a paint program, painting pixels to the image. You will also need to provide functionality when the size of the viewable area changes, as the BufferedImage won't know.
Place each object you wanted painted into some kind of List and iterate this list when paintComponent is called. This is a little more flexible in that you can control the order of the objects drawn, remove objects and insert new ones where you like

Non-opaque JButton background in non top level window becomes opaque?

Before you read, here are some clarifications on what the question is about:
The SSCCE is designed for Java 7. It would be possible to use sun.*.AWTUtilities to adapt it to Java 6, but it does not matter to me how it works on Java 6.
The faulting line is [...]new JDialog(someWindow). Ghosting can be fixed in the SSCCE by simply changing that line to [...]new JDialog().
Why don't top level windows exhibit ghosting?
Expected behavior: final JDialog d = new JDialog() (see SSCCE)
As you can see, the right window has a semitransparent background (as expected).
Actual behavior: final JDialog d = new JDialog(f) (see SSCCE)
In this case, the right window has an opaque background. As a matter of fact, it takes 3-4 repaints due to any reason (easiest to reproduce is repaint on rollover) for the background to become completely opaque.
SSCCE:
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.synth.ColorType;
import javax.swing.plaf.synth.Region;
import javax.swing.plaf.synth.SynthConstants;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthLookAndFeel;
import javax.swing.plaf.synth.SynthPainter;
import javax.swing.plaf.synth.SynthStyle;
import javax.swing.plaf.synth.SynthStyleFactory;
public class SynthSSCCE
{
public static void main(String[] args) throws Exception
{
final SynthLookAndFeel laf = new SynthLookAndFeel();
UIManager.setLookAndFeel(laf);
SynthLookAndFeel.setStyleFactory(new StyleFactory());
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
final JFrame f = new JFrame();
{
f.add(new JButton("Works properly"));
f.setUndecorated(true);
f.setBackground(new Color(0, true));
f.setSize(300, 300);
f.setLocation(0, 0);
f.setVisible(true);
}
{
final JDialog d = new JDialog(f);
final JButton btn = new JButton("WTF?");
// uncomment and notice that this has no effect
// btn.setContentAreaFilled(false);
d.add(btn);
d.setUndecorated(true);
d.setBackground(new Color(0, true));
d.setSize(300, 300);
d.setLocation(320, 0);
d.setVisible(true);
}
}
});
}
static class StyleFactory extends SynthStyleFactory
{
private final SynthStyle style = new Style();
#Override
public SynthStyle getStyle(JComponent c, Region id)
{
return style;
}
}
static class Style extends SynthStyle
{
private final SynthPainter painter = new Painter();
#Override
protected Color getColorForState(SynthContext context, ColorType type)
{
if (context.getRegion() == Region.BUTTON && type == ColorType.FOREGROUND)
return Color.GREEN;
return null;
}
#Override
protected Font getFontForState(SynthContext context)
{
return Font.decode("Monospaced-BOLD-30");
}
#Override
public SynthPainter getPainter(SynthContext context)
{
return painter;
}
#Override
public boolean isOpaque(SynthContext context)
{
return false;
}
}
static class Painter extends SynthPainter
{
#Override
public void paintPanelBackground(SynthContext context, Graphics g, int x, int y, int w, int h)
{
final Graphics g2 = g.create();
try
{
g2.setColor(new Color(255, 255, 255, 128));
g2.fillRect(x, y, w, h);
}
finally
{
g2.dispose();
}
}
#Override
public void paintButtonBackground(SynthContext context, Graphics g, int x, int y, int w, int h)
{
final Graphics g2 = g.create();
try
{
if ((context.getComponentState() & SynthConstants.MOUSE_OVER) == SynthConstants.MOUSE_OVER)
g2.setColor(new Color(255, 0, 0, 255));
else
g2.setColor(new Color(0xAA, 0xAA, 0xAA, 255));
g2.fillRoundRect(x, y, w, h, w / 2, h / 2);
}
finally
{
g2.dispose();
}
}
}
}
And these are my questions...
What is going on? As in, why this exhibits behavior of a custom-painted non-opaque component that forgets to call super?
Why doesn't it happen to TL windows?
What is the easiest way to fix it, aside from not using non-TL windows?
it takes 3-4 repaints due to any reason (easiest to reproduce is repaint on rollover) for the background to become completely opaque.
Check out Backgrounds With Transparency which should give you some insight into the problem.
I've never played with Synth so I don't know if the same solution will work or not.
Why don't top level windows exhibit ghosting?
According to Oracle (Java Tutorials):
Each top-level container has a content pane that, generally speaking,
contains (directly or indirectly) the visible components in that
top-level container's GUI.
http://docs.oracle.com/javase/tutorial/uiswing/components/toplevel.html
The glass pane is often used to intercept input events occuring over the top-level container, and can also be used to paint over multiple components. It doesnt allow transparency.
Hence how you used
final Graphics g2 = g.create();
If you have the javax.swing.JComponent.paintComponent overrided in a method opposed to creating the graphics object yourself it should mitigate the transparency by super.g();
Fix this by creating a separate method listed above for graphics

How to draw circle on JPanel? Java 2D

I have a JPanel for which I set some image as the background. I need to draw a bunch of circles on top of the image. Now the circles will be positioned based on some coordinate x,y, and the size will be based on some integer size. This is what I have as my class.
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JPanel;
class ImagePanel extends JPanel {
private Image img;
CircleList cList; //added this
public ImagePanel(Image img) {
this.img = img;
Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
setPreferredSize(size);
setMinimumSize(size);
setMaximumSize(size);
setSize(size);
setLayout(null);
cList = new CircleList(); //added this
}
public void paintComponent(Graphics g) {
g.drawImage(img, 0, 0, null);
cList.draw(null); //added this
}
}
How can I create some method that can performed this?
Your approach can be something similar to this, in which you use a class CircleList to hold all the circles and the drawing routine too:
class CircleList
{
static class Circle
{
public float x, y, diameter;
}
ArrayList<Circle> circles;
public CirclesList()
{
circles = new ArrayList<Circle>();
}
public void draw(Graphics2D g) // draw must be called by paintComponent of the panel
{
for (Circle c : circles)
g.fillOval(c.x, c.y, c.diameter, c.diameter)
}
}
Easiest thing to do would be to place something along these lines into your paintComponent method.
int x = ...;
int y = ...;
int radius = ...;
g.drawOval(x, y, radius, radius);
Well, you will probably want to create an ArrayList to store the information about the circles to be drawn. Then when the paintComponent() method is invoked you just loop through the ArrayList and draw the circles.
Custom Painting Approaches shows how this might be done for a rectangle. You can modify the code for an oval as well you would probably add methods to update the Array with the location information rather than by doing it dynamically.
Have you looked at JXLayer? It's an awesome library that allows you to layer special painting on top of any GUI element in an obvious way. I believe that will be included in the main java libraries for JDK7

Categories

Resources