As part of a program I am writing, I wish to print an image to SVG format. I need it to be in SVG format, so that I can modify it using Adobe Illustrator later on. As it stands, although I can draw a rectangle directly in the print method and export that successfully to SVG format.
When I draw the same rectangle in my getTagCloud method, the result (when printed to SVG) is a rectangle made up of a huge number of tiny rectangles. I am at a loss as to why this might be so, though hopefully the answer will be blindingly obvious to someone reading this!
Ultimately, I need to print out more than just a rectangle but the exported "Group" in Illustrator is so large (containing as it does all these tiny rectangles of varying sizes) that I am unable to find the other objects I have drawn (as everything, no matter what colour I originally used, is rendered in black). Any help would be greatly appreciated.
Below is the relevant code. I have not included the import statements as I don't have any problems compiling the code.
public class TagCloud {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
System.out.println("Created GUI on EDT? "+
SwingUtilities.isEventDispatchThread());
JFrame f = new JFrame("Tag Cloud Generator");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyPanel myPanel = new MyPanel();
Toolkit tk = f.getToolkit();
Dimension wndSize = tk.getScreenSize();
f.setBounds(0, 0,
wndSize.width, wndSize.height);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
});
f.add("Center",myPanel);
f.pack();
f.setVisible(true);
myPanel.printTagCloud();
}
}
class MyPanel extends JPanel implements Printable{
private int squareX = 50;
private int squareY = 50;
private int squareW = 20;
private int squareH = 20;
private int x_offset = 30;
private int y_offset = 30;
private BufferedImage img = null;
private int defaultFontSize = 16;
public int print(Graphics g, PageFormat pf, int page) throws
PrinterException {
if (page > 0) { /* We have only one page, and 'page' is zero-based */
return NO_SUCH_PAGE;
}
Graphics2D g2d = (Graphics2D)g;
g2d.translate(pf.getImageableX(), pf.getImageableY());
if (img == null){
getTagCloudImage();
}
g.setColor(Color.red);
g.fillRect(0, 0, 250, 400);
//g.drawImage(img, 0, 0, null);
g.dispose();
return PAGE_EXISTS;
}
public void printTagCloud(){
PrinterJob job = PrinterJob.getPrinterJob();
PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
PageFormat pf = job.pageDialog(aset);
job.setPrintable(this);
boolean ok = job.printDialog(aset);
if (ok) {
try {
job.print(aset);
} catch (PrinterException ex) {
}
}
}
public MyPanel() {
setBorder(BorderFactory.createLineBorder(Color.black));
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
moveSquare(e.getX(),e.getY());
}
});
addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
moveSquare(e.getX(),e.getY());
}
});
}
private void moveSquare(int x, int y) {
int OFFSET = 1;
if ((squareX!=x) || (squareY!=y)) {
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
squareX=x;
squareY=y;
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
}
}
public Dimension getPreferredSize() {
return new Dimension(1000,800);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (img == null){
getTagCloudImage();
}
else{
g.drawImage(img, x_offset, y_offset, null);
}
}
public void getTagCloudImage(){
img = new BufferedImage(250, 250, BufferedImage.TYPE_INT_RGB);
Graphics g = img.getGraphics();
Graphics2D g2 = (Graphics2D)g;
Rectangle r = new Rectangle (0,0,250,250);
g2.draw(r);
g2.setColor(Color.black);
g2.fill(r);
g2.dispose();
}
I can't help much without knowledge about Java BufferedImage,Graphics, PrinterJob and other related classes implementation. (You may start a bounty for this question to possibly draw attention of people having more intristic knowledge about java awt graphic stuff).
As you obviously must have noticed using BufferedImage (or not using it) is what makes a difference in SVG output. In the version that works you draw the rectangle directly on Graphic context provided to you as print() method argument and I believe that's how it was designed to be used by authors of Printable interface and printing framework.
In the second approach (that doesn't work correct) you first draw rectangle onto new BufferedImage object and then draw this image on the provided Graphic context. So you do something much less straightforward than just drawing directly on the context. There is well known truth or intuition among developers that the less straightforward way do you use some API, the bigger is a chance that you do something unexpected by its authors :(.
My hypotesis is following: BufferedImage is (as you can deduce from its Javadocs)just a raster image, ie. grid of pixels. That's why svg file is populated with lots of small rectangles (trying to mimic pixels). The Graphics object provided by draw() method may be more abstract and operate on shapes rather than pixels, which is much more appropriate to be written to vector graphics formats like SVG. But that's just hypotesis.
The question is, do you really need to use BufferedImage? If I understand correctly, you want user to be able to edit rectangle on screen and when ready export it to SVG. Can't you just remember for example upper left corner and dimensions of rectangle edited by user and then use this data to recreate this rectangle directly on Graphics object provided by print(), like:
public int print(Graphics g, PageFormat pf, int page)
throws PrinterException {
...
g.fillRect(userRect.x,userRect.y,userRect.width,userRect.height);
...
}
?
(userRect is object of your own custom class which just stores data about image edited by user)
Related
I have a function that is drawing an image and then saving it immediately after but the problem is that it seems to be drawing it twice, once for the view on the screen and then once to save it to the disk
public class myFrame {
public static void main(String[] args) {
JFrame lv_frame = new JFrame();
// setup jframe here
lv_frame.add(new image());
lv_frame.setVisible(true);
}
}
class image extends JPanel {
public void paintComponent(Graphics graphic) {
super.paintComponent(graphic);
draw(graphic);
save();
}
public void draw(Graphics graphic) {
Graphics2D graphic2D = (Graphics2D) graphic;
graphic2D.fillArc(0, 0, 250, 250, 0, 90);
}
public void save() {
BufferedImage image = new BufferedImage(250, 250, BufferedImage.TYPE_4BYTE_ABGR_PRE);
Graphics2D graphic2D = image.createGraphics();
try {
File output = new File("file.png");
// is it possible to use the graphic I've already
// drawn here instead of re-drawing?
draw(graphic2D);
ImageIO.write(image, "png", output);
} catch(IOException log) {
System.out.println(log);
}
}
}
I have a function draw which creates the image on the gui screen and another function save which saves it to the disk but the save function is calling draw as well.
Is it possible to save the image that has already been drawn without re-calling the draw function as it is being done in my code?
I was answering your question on Display to GUI and Save to Disk with a Single Object/Variable, however it was closed probably due to the similar nature of your question.
I think there are several issues which you seemed confused with and I would like to write my solution here which also addresses your question in your duplicated post.
Is it possible to save the image that has already been drawn without re-calling the draw function as it is being done in my code?
Don't be confused between drawing an image on the Panel and saving it. The following shows a working example on saving a drawn image.
class DrawingSpace extends JPanel
{
private BufferedImage buf;
public DrawingSpace(){
setPreferredSize(new Dimension(200, 200));
buf = new BufferedImage(200, 200, BufferedImage.TYPE_INT_ARGB);
drawOnBuffer();
}
public void drawOnBuffer(){
Graphics g = buf.createGraphics();
g.setColor(Color.BLUE);
g.fillOval(0,0,200,200);
g.setColor(Color.RED);
g.fillOval(50,50,100,100);
g.dispose();
}
public void saveBufferAsImage(String pathname){
String fmt = "";
if(!(pathname == null || pathname.equals(""))){
fmt = pathname.substring(pathname.lastIndexOf(".")+1);
File outputfile = new File(pathname);
try{
ImageIO.write(buf, fmt, outputfile);
}catch(IOException ioe){System.out.println("Unable to save file");}
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(buf, 0, 0, 200, 200, null); //Only for user to see
}
}
To save an image, you not necessarily have to draw and display it in the JPanel first. Note that I created a BufferedImage called buf and I am drawing on buf. Once I have drawn onto a BufferedImage, I can simply save that image as a file (I don't have to draw it to the panel first).
Notice that I did the following:
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(buf, 0, 0, 200, 200, null); //Only for user to see
}
g.drawImage(buf, 0, 0, 200, 200, null) will draw whatever on buf onto the JPanel. It can be deleted and the saving will still work. When I draw it on the panel, it is only for the user to see the outcome of the drawing.
To test the program:
class SaveImageRunner{
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
JFrame frame = new JFrame("Saving a buffered image");
DrawingSpace ds = new DrawingSpace();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(ds);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
ds.saveBufferAsImage("BlueCircle.png");
}
});
}
}
Saved Image:
Some pointers on drawing:
paintComponent(Graphics) shall only contain codes for drawing and nothing else. Do not implement your codes for saving the image to a file inside here.
Try not to create a new BufferedImage in the paintComponent(Graphics). If not, a new instance of a BufferedImage will be created on every repaint.
Change the place where you create the graphics2D object and reuse it.
Try this out.
public class myFrame {
public static void main(String[] args) {
JFrame lv_frame = new JFrame();
// setup jframe here
lv_frame.add(new image());
lv_frame.setVisible(true);
}
}
class image extends JPanel {
public void paintComponent(Graphics graphic) {
super.paintComponent(graphic);
// Image and graphics are now created here
BufferedImage image = new BufferedImage(250, 250, BufferedImage.TYPE_4BYTE_ABGR_PRE);
Graphics2D graphic2D = image.createGraphics();
draw(graphic2D);
save(image);
}
public void draw(Graphics graphic) {
Graphics2D graphic2D = (Graphics2D) graphic;
graphic2D.fillArc(0, 0, 250, 250, 0, 90);
}
public void save(BufferedImage image) {
try {
File output = new File("file.png");
ImageIO.write(image, "png", output);
} catch(IOException log) {
System.out.println(log);
}
}
}
EDIT
I have found the answer in another post. It is not a bad idea to make the drawing twice. What you are doing is, like #Hovercraft says in this post, separating the screen drawing from the writing to files so that you don't see your graphics drawing performance hurt.
I have tried to make the drawing only once but you have no easy method to store the drawing Graphics object. Probably, it is implemented to prevent this. If you see the way the method works, you are given the Graphics object with the only purpose to draw on it. Using it in other ways could impact the performance.
I am creating a lightweight client for the game Runescape that has various user features such as custom keybinds/hotkeys. I'm pretty good with java and swing, but AWT and applets I am mediocre at best. I was able to get the applet downloaded and displayed on a JPanel and then in a JFrame and the game runs perfectly so far. However, when adding features I ran into a problem when I tried to implement a screenshot function to the client.
It's hard to post a concise working example so I'll post the methods I was trying based off of some other SO answers I was reading. I tried creating BufferedImage from the Applet, applet Canvas, the JPanel it is handled by, and the JFrame.
Here are some things I tried to create a screenshot from the Applet canvas:
public BufferedImage getScreenShot() {
//getting the applet canvas
Canvas canvas = (Canvas) this.getApplet().getComponent(0);
//bufferedImage to draw to
BufferedImage image = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_ARGB);
//Graphics2D g2=(Graphics2D)image.getGraphics();
//test.print(g2);
//g2.dispose();
Graphics g = image.getGraphics();
Image image2 = canvas.createImage(canvas.getWidth(), canvas.getHeight());
canvas.print(g);
System.out.println("Canvas Size: " + canvas.getSize().width + " x " + canvas.getSize().height);
return image;
}
This one I tried in the JPanel class which holds only the Applet:
public BufferedImage getScreenShotFINAL(){
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
applet.paint(bi.createGraphics());
return bi;
}
I understand that the idea here is to create an off-screen BufferedImage, then to create a Graphics2d object and to then call the applet's paint() method to paint to the off-screen image. I tried this solution from here:
public BufferedImage getScreenShotFINAL2() {
Dimension size = applet.getSize();
BufferedImage offScreenImage = (BufferedImage) applet.createImage(size.width, size.height);
Graphics2D g2 = offScreenImage.createGraphics();
g2.setBackground(applet.getBackground());
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.clearRect(0, 0, size.width, size.height);
applet.paint(g2);
return offScreenImage;
}
When saving the BufferedImages these and other methods I tried to a file the images just show up as black, just the default background color. Is there a good way to just get a BufferedImage from the JPanel which would include the Applet so I never have to deal with the Applet directly? Another thing, I had to override the java Canvas class in order to get the game to display (because it is double buffered I believe). I tried following a bunch of solutions from here but had no success. Any suggestions would be greatly appreciated. I also can provide a link to the full project or the custom Canvas project if you would like. Thanks!
EDIT
Here is the Canvas class which overrides the java AWT one. Didn't write it, followed a tutorial for this one and I think this is where the problem is, because the paint() method only calls clearRect():
package java.awt;
import Client.Client;
import java.awt.image.BufferStrategy;
import java.awt.peer.CanvasPeer;
import javax.accessibility.*;
import loaders.ClientPool;
public class Canvas extends Component implements Accessible {
private Client client = null;
private static final String base = "canvas";
private static int nameCounter = 0;
private static final long serialVersionUID = -2284879212465893870L;
public Canvas() {
super();
}
public Canvas(GraphicsConfiguration config) {
this();
setGraphicsConfiguration(config);
}
#Override
void setGraphicsConfiguration(GraphicsConfiguration gc) {
synchronized(getTreeLock()) {
CanvasPeer peer = (CanvasPeer)getPeer();
if (peer != null) {
gc = peer.getAppropriateGraphicsConfiguration(gc);
}
super.setGraphicsConfiguration(gc);
}
}
#Override
public Graphics getGraphics(){
if (this.client == null) {
this.client = ClientPool.getClient(this);
}
if (client != null) {
//call custom draw functions for specific client. for drawing/double buffering
return client.drawGraphics((Graphics2D) super.getGraphics());
}
return super.getGraphics();
}
#Override
String constructComponentName() {
synchronized (Canvas.class) {
return base + nameCounter++;
}
}
#Override
public void addNotify() {
synchronized (getTreeLock()) {
if (peer == null)
peer = getToolkit().createCanvas(this);
super.addNotify();
}
}
#Override
public void paint(Graphics g) {
g.clearRect(0, 0, width, height);
}
#Override
public void update(Graphics g) {
g.clearRect(0, 0, width, height);
super.paint(g);
}
#Override
boolean postsOldMouseEvents() {
return true;
}
#Override
public void createBufferStrategy(int numBuffers) {
super.createBufferStrategy(numBuffers);
}
#Override
public void createBufferStrategy(int numBuffers,
BufferCapabilities caps) throws AWTException {
super.createBufferStrategy(numBuffers, caps);
}
#Override
public BufferStrategy getBufferStrategy() {
return super.getBufferStrategy();
}
#Override
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleAWTCanvas();
}
return accessibleContext;
}
protected class AccessibleAWTCanvas extends AccessibleAWTComponent
{
private static final long serialVersionUID = -6325592262103146699L;
public AccessibleRole getAccessibleRole() {
return AccessibleRole.CANVAS;
}
}
}
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();
I have an image, with a complete transparent background. However when I draw this image, ingame, it has a kind of shade to it, and I have no clue why. I would like to get that out of there. Does anyone have an idea? I don't have the reputation to post images of it apparently... So I'll try to give some more information.
I have the Color.DARK_GRAY as background, and when I draw the image, you see a lighter gray square around it.
Then when I draw a couple of these images ontop of eachother, that square gets lighter and lighter.
If I draw the image ontop of another image however, this effect does not occur.
Here I load the image
public BlackChip() {
this.value = 500;
this.url = "res/images/poker/blackchip.png";
this.file = new File(url);
BufferedImage bi;
try {
bi = ImageIO.read(file);
this.image = bi;
} catch (IOException e) {
e.printStackTrace();
}
}
Here I draw the image
public void renderChip(Chip chip, int x, int y) {
g.drawImage(chip.getImage(), x, y, null);
}
Here I call that method
public void render() {
screen.renderBackground(Color.DARK_GRAY);
pokertable.render(Game.width / 2 - pokertable.getImage().getWidth(null) / 2, 50);
screen.renderChip(cs.getWhiteChip(), 380, 310);
screen.renderChip(cs.getRedChip(), 430, 310);
screen.renderChip(cs.getGreenChip(), 480, 310);
screen.renderChip(cs.getBlueChip(), 530, 310);
screen.renderChip(cs.getBlackChip(), 580, 310); //this one is it
}
link to the images:
https://drive.google.com/file/d/0Bz-4pfUssUeHRWkxaUhodWNILWc/edit?usp=sharing
Well... this doesn't work either because i need 10 reputation to post more then 1 link
you can see the effect on this link, it's the image with full transparent background, drawn multiple times.
I can't tell if this is the exact cause of the problem, because you haven't provided a MCVE but this method
public void renderChip(Chip chip, int x, int y) {
g.drawImage(chip.getImage(), x, y, null);
}
Just looks wrong. All custom painting should be done within the context of the provided Graphics object in the overridden paintComponent method. If you have not overriden paintComponent in a JPanel or a JComponent then you are likely not painting correctly. You may be doing something like
public class SomePanel extends JPanel {
private Graphics g;
public SomePanel() {
g = getGraphics();
}
}
Which is completely wrong. You should instead be doing something like
public class SomePanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// do painting here
}
}
You Classes can then have it's own render method that takes a Graphics object as an argument. Then can be called in the paintComponent method. Maybe something like
public class Chip {
private JComponent imageObserver;
private BufferedImage chipImage;
int x, y;
public Chip(BufferedImage chipImage, int x, int y, JComponent imageObserver){
this.chipImage;
this.x = x;
this.y = y;
this.imageObserver = imageObserver;
}
public void renderChip(Graphics g) {
g.getImage(chipImage, x, y, imageObserver);
}
}
And your panel
public class SomePanel extends JPanel {
private List<Chip> chips;
public SomePanel() {
chips = new ArrayList<Chip>();
// add new Chips
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Chip chip: chips) {
chip.renderChip(g);
}
}
}
I'm creating a graphical front-end for a JBox2D simulation. The simulation runs incrementally, and in between the updates, the contents of the simulation are supposed to be drawn. Similar to a game except without input.
I only need geometric primitives to draw a JBox2D simulation. This API seemed like the simplest choice, but its design is a bit confusing.
Currently I have one class called Window extending JFrame, that contains as a member another class called Renderer. The Window class only initializes itself and provides an updateDisplay() method (that is called by the main loop), that calls updateDisplay(objects) method on the Renderer. I made these two methods myself and their only purpose is to call repaint() on the Renderer.
Is the JPanel supposed to be used that way? Or am I supposed to use some more sophisticated method for animation (such that involves events and/or time intervals in some back-end thread)?
If you are wanting to schedule the updates at a set interval, javax.swing.Timer provides a Swing-integrated service for it. Timer runs its task on the EDT periodically, without having an explicit loop. (An explicit loop would block the EDT from processing events, which would freeze the UI. I explained this more in-depth here.)
Ultimately doing any kind of painting in Swing you'll still be doing two things:
Overriding paintComponent to do your drawing.
Calling repaint as-needed to request that your drawing be made visible. (Swing normally only repaints when it's needed, for example when some other program's window passes over top of a Swing component.)
If you're doing those two things you're probably doing it right. Swing doesn't really have a high-level API for animation. It's designed primarily with drawing GUI components in mind. It can certainly do some good stuff, but you will have to write a component mostly from scratch, like you're doing.
Painting in AWT and Swing covers some of the 'behind the scenes' stuff if you do not have it bookmarked.
You might look in to JavaFX. I don't know that much about it personally, but it's supposed to be more geared towards animation.
As somewhat of an optimization, one thing that can be done is to paint on a separate image and then paint the image on to the panel in paintComponent. This is especially useful if the painting is long: repaints can be scheduled by the system so this keeps when it happens more under control.
If you aren't drawing to an image, then you'd need to build a model with objects, and paint all of them every time inside paintComponent.
Here's an example of drawing to an image:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn't interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we've created
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
If the routine is long-running and repaints could happen concurrently, double buffering can also be used. Drawing is done to an image which is separate from the one being shown. Then, when the drawing routine is done, the image references are swapped so the update is seamless.
You should typically use double buffering for a game, for example. Double buffering prevents the image from being shown in a partial state. This could happen if, for example, you were using a background thread for the game loop (instead of a Timer) and a repaint happened the game was doing the painting. Without double buffering, this kind of situation would result in flickering or tearing.
Swing components are double buffered by default, so if all of your drawing is happening on the EDT you don't need to write double buffering logic yourself. Swing already does it.
Here is a somewhat more complicated example which shows a long-running task and a buffer swap:
import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.*;
/**
* Left-click to spawn a new background
* painting task.
*/
class DoubleBuffer {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DoubleBuffer();
}
});
}
final int width = 640;
final int height = 480;
BufferedImage createCompatibleImage() {
GraphicsConfiguration gc =
GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// createCompatibleImage creates an image that is
// optimized for the display device.
// See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-
return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
}
// The front image is the one which is
// displayed in the panel.
BufferedImage front = createCompatibleImage();
// The back image is the one that gets
// painted to.
BufferedImage back = createCompatibleImage();
boolean isPainting = false;
final JFrame frame = new JFrame("Double Buffer");
final JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Scaling the image to fit the panel.
Dimension actualSize = getSize();
int w = actualSize.width;
int h = actualSize.height;
g.drawImage(front, 0, 0, w, h, null);
}
};
final MouseAdapter onClick = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (!isPainting) {
isPainting = true;
new PaintTask(e.getPoint()).execute();
}
}
};
DoubleBuffer() {
panel.setPreferredSize(new Dimension(width, height));
panel.setBackground(Color.WHITE);
panel.addMouseListener(onClick);
frame.setContentPane(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void swap() {
BufferedImage temp = front;
front = back;
back = temp;
}
class PaintTask extends SwingWorker<Void, Void> {
final Point pt;
PaintTask(Point pt) {
this.pt = pt;
}
#Override
public Void doInBackground() {
Random rand = new Random();
synchronized(DoubleBuffer.this) {
Graphics2D g2 = back.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
g2.setBackground(new Color(0, true));
g2.clearRect(0, 0, width, height);
// (This computes pow(2, rand.nextInt(3) + 7).)
int depth = 1 << ( rand.nextInt(3) + 7 );
float hue = rand.nextInt(depth);
int radius = 1;
int c;
// This loop just draws concentric circles,
// starting from the inside and extending
// outwards until it hits the outside of
// the image.
do {
int rgb = Color.HSBtoRGB(hue / depth, 1, 1);
g2.setColor(new Color(rgb));
int x = pt.x - radius;
int y = pt.y - radius;
int d = radius * 2;
g2.drawOval(x, y, d, d);
++radius;
++hue;
c = (int) (radius * Math.cos(Math.PI / 4));
} while (
(0 <= pt.x - c) || (pt.x + c < width)
|| (0 <= pt.y - c) || (pt.y + c < height)
);
g2.dispose();
back.flush();
return (Void) null;
}
}
#Override
public void done() {
// done() is completed on the EDT,
// so for this small program, this
// is the only place where synchronization
// is necessary.
// paintComponent will see the swap
// happen the next time it is called.
synchronized(DoubleBuffer.this) {
swap();
}
isPainting = false;
panel.repaint();
}
}
}
The painting routine is just intended draw garbage which takes a long time:
For a tightly coupled simulation, javax.swing.Timer is a good choice. Let the timer's listener invoke your implementation of paintComponent(), as shown here and in the example cited here.
For a loosely coupled simulation, let the model evolve in the background thread of a SwingWorker, as shown here. Invoke publish() when apropos to you simulation.
The choice is dictated in part by the nature of the simulation and the duty cycle of the model.
Why not just use stuff from the testbed? It already does everything. Just take the JPanel, controller, and debug draw. It uses Java 2D drawing.
See here for the JPanel that does the buffered rendering:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/TestPanelJ2D.java
and here for the debug draw:
https://github.com/dmurph/jbox2d/blob/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d/DebugDrawJ2D.java
See the TestbedMain.java file to see how the normal testbed is launched, and rip out what you don't need :)
Edits:
Disclaimer: I maintain jbox2d
Here is the package for the testbed framework: https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework
TestbedMain.java is in the j2d folder, here:
https://github.com/dmurph/jbox2d/tree/master/jbox2d-testbed/src/main/java/org/jbox2d/testbed/framework/j2d