I am trying to copy an area around the mouse cursor from my Canvas into an Image using the gc.copyarea method. Afterwards I want to paint that image onto a composite which appears after the mouse has been pressed for 1 second.
Below is my code so far:
In the paintControl Method of my canvas I copy the area to an image:
img = new Image(display, 40, 40);
gc.copyArea(img, mousePosition.x-20, mousePosition.y-20);
In the paintControl Method of my Composite I paint that image:
if (img != null) {
gc.drawImage(img, 0, 0);
}
The problem I am having is that only a very small part of the area is actually copied and painted onto the composite. Of the 40x40 pixel area, only an area of 10x20 pixel area in the bottom left corner is visible in the composite.
Example:
The cursor is next to the top left corner of the compisite. Only a small part of the area around the cursor is painted onto the compisite
Question: How can I copy an area from my canvas and paint it onto another composite correctly? What did I do wrong in my Code?
With some trial and error I managed to get something working that supposedly comes close to what you want.
public class CopyArea {
public static void main( String[] args ) {
CopyArea copyArea = new CopyArea();
copyArea.create();
copyArea.run();
}
private Display display;
private Image sourceImage;
private Point canvasSize;
private Shell shell;
private Canvas source;
private Canvas destination;
private Point snippetOrigin;
private Image snippet;
CopyArea() {
display = new Display();
sourceImage = new Image( display, getClass().getResourceAsStream( "mona-lisa.jpeg" ) );
canvasSize = new Point( sourceImage.getBounds().width, sourceImage.getBounds().height );
shell = new Shell( display );
source = new Canvas( shell, SWT.NONE );
destination = new Canvas( shell, SWT.NONE );
}
void create() {
shell.setLayout( new RowLayout() );
source.setLayoutData( new RowData( canvasSize ) );
source.addPaintListener( new PaintListener() {
#Override
public void paintControl( PaintEvent event ) {
event.gc.drawImage( sourceImage, 0, 0 );
if( snippetOrigin != null ) {
snippet = new Image( display, 40, 40 );
event.gc.copyArea( snippet, snippetOrigin.x, snippetOrigin.y );
destination.redraw();
snippetOrigin = null;
}
}
} );
source.addMouseListener( new MouseAdapter() {
#Override
public void mouseDown( MouseEvent event ) {
snippetOrigin = new Point( event.x, event.y );
source.redraw();
}
} );
destination.setLayoutData( new RowData( canvasSize ) );
destination.addPaintListener( new PaintListener() {
#Override
public void paintControl( PaintEvent event ) {
event.gc.setBackground( display.getSystemColor( SWT.COLOR_WHITE ) );
event.gc.fillRectangle( event.gc.getClipping() );
if( snippet != null ) {
event.gc.drawImage( snippet, 0, 0 );
}
}
} );
}
void run() {
shell.pack();
shell.open();
while( !shell.isDisposed() ) {
if( !display.readAndDispatch() )
display.sleep();
}
display.dispose();
}
}
The sourceImage is just a placeholder for your drawing code. The source Canvas listens to mouse-down events and then draws a 40x40 snippet of its client are to the destination Canvas. Note that I used Canvas in both cases, but Canvas and Composite should also work.
Here is how it works: The mouse-listeners stores the mouse location into snippetOrigin and triggers a repaint of the source. The paint-listener of source takes a partial screen-shot of what it just has drawn if requested (snippetOrigin != null) and then forces destination to redrawitself. The paint-listener of destination simple draws the snippet image if there is any.
The code deos not yet correctly clip the copied image. If you press the mouse in the lower right corner of the source Canvas, some of the shell`s trimming will be copied as well.
For brevity, the code does not dispose of the snippet image before reusing it (and may have other leaks).
If you own the drawing code that paints your equivalent of the source Canvas, I would rather refactor it so that arbitrary portions can be drawn and then call the code with appropriate parameters to draw on the destination widget.
try this code, sample to get a copy of an canvas area and paste in an object
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 50, 50);
function copy() {
var imgData = ctx.getImageData(10, 10, 50, 50);
ctx.putImageData(imgData, 10, 70);
}
in this case the copy was paste at the canvas destination as origin
try this reference
https://www.w3schools.com/tags/canvas_getimagedata.asp
and this it the API reference
https://developer.mozilla.org/es/docs/Web/API/CanvasRenderingContext2D/getImageData
Related
I'm developing a simple shape editor in Java, I was drawing everything on BufferedImage scince I want to save drawn image. The user can draw shapes by choosing shape from the menu and then click on image. I started to implement dragging shapes. When I drag a shape, a path of this shape is being created... I assume it's because I draw on BufferedImage so it's like 'temporary'. Or am I wrong? I was thinking of re-write everything, this time not drawing on BufferedImage, just creating Graphics2D, but I have problems with implementing listeners as I need to transfer my Graphics2D there to add shapes there, also I don't know how to call repaint in for eg. my MouseListener class. I was planning to create BufferedImage when user wants to save image, I would create it with help of vector where I store all shapes (or is it better way?). Here's my Panel class:
public class Panel {
public static Graphics2D img2;
public static Graphics2D getIm() {
return this.img2;
}
public Panel(JFrame frame) {
JPanel panel = new JPanel(true) {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
img2 = (Graphics2D)g.create();
img2.dispose();
};
};
RenderingHints.VALUE_ANTIALIAS_ON); // setting rendering to achieve better looking shapes
panel.setBackground(Color.WHITE);
MouseListenerShapes mouseListenerShapes = new MouseListenerShapes();
panel.addMouseListener(mouseListenerShapes);
//MouseMoveAdapter mouseMouseMoveAdapter = new MouseMoveAdapter();
//panel.addMouseMotionListener(mouseMouseMoveAdapter);
//panel.addMouseListener(mouseMouseMoveAdapter);
frame.add(panel);
};
}
I did a lot of reading but still I can't make it right, I need my shapes to be resizable, movable, etc. but at the same time I want to save image afterwards,
The solution I'm going to propose is going to be far from complete, but I hope it will illustrate some concepts that should help you find success. I'm going to build a panel with two movable images, but will do so using patterns that should make this code easier to maintain.
I highly recommend separating your rendering layer (your JComponents) and your model layer (the shapes and their locations) into distinct objects. This is referred to as separation of concerns and will make your life easier in the long run.
First lets define one of your shapes that appears in the screen. I'm going to keep mine very simple and create one with location, size, and a BufferedImage:
class DrawNode
{
private Rectangle bounds;
private BufferedImage image;
public DrawNode()
{
}
public Rectangle getBounds()
{
return bounds;
}
public void setBounds( Rectangle bounds )
{
this.bounds = bounds;
}
public BufferedImage getImage()
{
return image;
}
public void setImage( BufferedImage image )
{
this.image = image;
}
}
Next, let's create a model for holding the collection of your shapes. One neat thing we can do with the model is have it accept a listener that will get invoked whenever parts of our 2D space become "invalidated." Parts of the space can become invalid and will need repainting if a node was in a given area previously and has been moved to a new area. We'll also include some helper methods for determining what shape occupies a given space, etc. These helpers can be greatly optimized in terms of their efficiency, but my simple version will use brute-force iteration:
class DrawPanelModel
{
private final List<DrawNode> nodes;
private final Consumer<Rectangle> invalidAreaListener;
public DrawPanelModel( Consumer<Rectangle> invalidAreaListener )
{
this.invalidAreaListener = invalidAreaListener;
nodes = new ArrayList<>();
}
public void addNode( DrawNode node )
{
nodes.add( node );
}
public Optional<DrawNode> getNodeForPoint( Point p )
{
return nodes.stream()
.filter( node -> node.getBounds().contains( p ))
.findFirst();
}
public Stream<DrawNode> getNodesInRectangle( Rectangle r )
{
return nodes.stream()
.filter( node -> node.getBounds().intersects( r ));
}
public void setNodeLocation( DrawNode node, Point p )
{
Rectangle bounds = (Rectangle)node.getBounds().clone();
bounds.setLocation( p );
setNodeBounds( node, bounds );
}
public void setNodeBounds( DrawNode node, Rectangle bounds )
{
Rectangle old = node.getBounds();
node.setBounds( Objects.requireNonNull( bounds ));
if ( old == null || !old.equals( bounds ))
{
invalidAreaListener.accept( bounds );
if ( old != null ) {
invalidAreaListener.accept( old );
}
}
}
}
Next we need a way to render our shapes/nodes to the screen. We could draw each node every time we call paint(...), but that's not very efficient since we only really need to re-draw the invalid areas. We can leave the rest of the areas alone by making sure the JComponent utilizes double-buffering:
https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html#setDoubleBuffered(boolean)
Edit: Slight correction: double buffering will provide benefits mainly when it comes to scrolling if you were to place the component in a scroll pane. I don't think it will affect the behavior of simple repaint events. (Was tired when I wrote this yesterday)
One common technique used to accomplish this in Swing is to use a CellRendererPane in conjunction with a JLabel that you use as a renderer. Whenever we need to paint a node in a specific location, we can assign the desired image and size to the JLabel and have the CellRendererPane render that JLabel during the paint procedure (potentially more than once) in different locations.
Let's create such a JLabel subclass and give it a helper method to initialize its state form a given node:
class ShapeRenderer extends JLabel
{
private static final long serialVersionUID = 1L;
public ShapeRenderer() {
}
public void initFrom( DrawNode node )
{
setIcon( new ImageIcon( node.getImage() ));
setSize( node.getBounds().getSize() );
}
// Methods below are overridden as a performance optimization:
#Override
public void invalidate() {
}
#Override
public void validate() {
}
#Override
public void revalidate() {
}
#Override
public void repaint( long tm, int x, int y, int width, int height ) {
}
#Override
public void repaint( Rectangle r ) {
}
#Override
public void repaint() {
}
#Override
protected void firePropertyChange( String propertyName, Object oldValue, Object newValue ) {
}
#Override
public void firePropertyChange( String propertyName, boolean oldValue, boolean newValue ) {
}
}
At this point we have the JComponent left, let's create a custom one. We'll give it a DrawPanelModel, CellRendererPane, and our ShapeRenderer. It's going to subscribe to the model as a listener, calling repaint(Rectangle) in response to the model invalidating in area whenever a node moves. Keep in mind that repaint(...) does not paint immediately, but rather schedules a paint event to occur at a future point in time. What this implies is that we can actually move several nodes and the Swing framework will provide us with one paint event that will have the union of all of our invalid rectangles specified as the Graphic's clip area. But, our code doesn't really care whether the invalid areas are combined or not. If the framework decides to give us a paint event for each of the areas we invalidate, we can handle that as well:
class DrawPanel extends JComponent
{
private static final long serialVersionUID = 1L;
private final CellRendererPane renderPane;
private final ShapeRenderer renderer;
private final DrawPanelModel model;
public DrawPanel()
{
renderPane = new CellRendererPane();
add( renderPane );
setDoubleBuffered( true );
renderer = new ShapeRenderer();
model = new DrawPanelModel( this::repaint );
DrawMouseListener listener = new DrawMouseListener();
addMouseListener( listener );
addMouseMotionListener( listener );
}
public void addNode( BufferedImage image, Point loc )
{
DrawNode node = new DrawNode();
node.setImage( image );
model.addNode( node );
model.setNodeBounds( node, new Rectangle( loc, new Dimension( image.getWidth(), image.getHeight() )));
}
#Override
public void doLayout()
{
renderPane.setSize( getSize() );
}
private void paintBackground( Graphics2D g )
{
g.setColor( Color.WHITE );
g.fill( g.getClip() );
}
private void paintNodes( Graphics2D g )
{
model.getNodesInRectangle( g.getClipBounds() )
.forEach( node -> paintNode( node, g ));
}
private void paintNode( DrawNode node, Graphics2D g )
{
Rectangle r = node.getBounds();
renderer.initFrom( node );
renderPane.paintComponent( g, renderer, this, r );
}
#Override
public void paintComponent( Graphics aG )
{
Graphics2D g = (Graphics2D)aG.create();
paintBackground( g );
paintNodes( g );
}
class DrawMouseListener extends MouseAdapter
{
private Optional<DrawNode> movingNode;
public DrawMouseListener()
{
movingNode = Optional.empty();
}
#Override
public void mousePressed( MouseEvent e )
{
movingNode = model.getNodeForPoint( e.getPoint() );
}
#Override
public void mouseReleased( MouseEvent e )
{
movingNode = Optional.empty();
}
#Override
public void mouseDragged( MouseEvent e )
{
movingNode.ifPresent( node -> {
model.setNodeLocation( node, e.getPoint() );
} );
}
}
}
Finally, a manual test:
#Test
public void testPanel() throws InvocationTargetException, InterruptedException
{
SwingUtilities.invokeLater( () -> {
// Create frame:
JFrame frame = new JFrame();
frame.setLayout( new GridLayout( 1, 1 ));
// Create draw panel:
DrawPanel drawPanel = new DrawPanel();
frame.add( drawPanel );
// Show frame:
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setSize( new Dimension( 1000, 600 ));
frame.setVisible( true );
// Create first image:
BufferedImage image1 = new BufferedImage( 50, 50, BufferedImage.TYPE_INT_ARGB );
Graphics2D g = image1.createGraphics();
g.setColor( Color.BLUE );
g.fillOval( 0, 0, 50, 50 );
// Add first image to draw panel:
drawPanel.addNode( image1, new Point( 100, 100 ));
// Create second image:
BufferedImage image2 = new BufferedImage( 50, 50, BufferedImage.TYPE_INT_ARGB );
Graphics2D g2 = image2.createGraphics();
g2.setColor( Color.RED );
g2.fillOval( 0, 0, 50, 50 );
// Add second image to draw panel:
drawPanel.addNode( image2, new Point( 200, 100 ));
} );
Thread.sleep( Long.MAX_VALUE );
}
Results:
When you draw a freehand line with any paint application with whatever brush it ends up stacking together multiple points of that brush to form a brush stroke.
For example a basic pen stroke would stack up 1 pixel as you drag the mouse.
In more advanced applications you have a brush which is just a fancy Shape, say: a star for example, and stroking the canvas with the "star brush" would just cause the paint application to draw multiple stars as you drag your mouse over the canvas.
Please correct me if I'm wrong.
I already have implemented the "brush" ( i.e. a basic circle ) and whenever the user drags their mouse over the canvas while holding the left-mouse-button the application draws a circle for each new mouse position.
My problem is the "undo feature" if you may call it this way.
When I undo an action, my application only deletes the last Shape ( circle ) drawn, while I want it to delete the whole free-hand drawing ( collection of Shapes / circles ) from the user first press of the left-mouse-button to the release.
How do I "pack" a collection a Shape objects into one ?
A problem is also the repainting of all those circles, I want the repainting of maybe 30000 circles to be fast, just like a BufferedImage.
I already use a BufferedImage as the background of my image.
Every Shape that is "older" than 50 gets permanently stored in the BufferedImage background.
Currently I store the last 50 Shape objects in an ArrayList and the 51st ( the oldest ) gets permanently stored in the BufferedImage.
So the user can't undo 50 actions but rather 50 Shapes.
Thank You!
stripped down code sample:
public class GraphicPanel extends JComponent{
private int x = 0;
private int y = 0;
private int compXLen = 100;
private int compYLen = 100;
private boolean isCompFilled = false;
private boolean isAreaToBePainted = false;
private boolean isFocused = false;
private Shape ghost;
private ArrayList<Shape> shapeBuffer = new ArrayList<Shape>();
private BufferedImage img = new BufferedImage( PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB );
private static final int PREF_W = 800;
private static final int PREF_H = 500;
#Override
public void paintComponent( Graphics gPlain ){
super.repaint();
Graphics2D g = (Graphics2D)gPlain;
//paint background
if (img != null){
g.drawImage(img, 0, 0, null);
}
ghost = new Ellipse2D.Double( x-( compXLen/2 ), y-( compYLen/2 ), compXLen, compYLen );
if( isAreaToBePainted ){
//add ghost Shape to ArrayList
add( g, ghost )
}
//paint arrayList
for( Shape s : shapeBuffer ){
g.fill( s );
}
if( isFocused ){
// draw ghost shape
g.draw( ghost );
}
}
/**
* adds circles to arrayList
*/
private void add( Graphics2D g, Shape s ){
//fetch last arrayList element in Shape shp
//add ghost shape at the top of arrayList
Graphics2D g2 = img.createGraphics();
shapeBuffer.add( shp );
g2.fill( shp );
g2.dispose();
}
public void clearArea(){
shapeBuffer = new ArrayList<Shape>();
img = new BufferedImage( PREF_W, PREF_H, BufferedImage.TYPE_INT_ARGB );
repaint();
}
private class GraphicPanelMouseListen implements MouseListener, MouseMotionListener{
/**
* #param e Mouse Event
* #since 0.1
*/
#Override
public void mouseClicked( MouseEvent e ){}
public void mousePressed( MouseEvent e ){
x = e.getX();
y = e.getY();
isAreaToBePainted = true;
repaint();
}
public void mouseReleased( MouseEvent e ){}
public void mouseEntered( MouseEvent e ){
isFocused = true;
}
public void mouseExited( MouseEvent e ){
isFocused = false;
repaint();
}
public void mouseDragged( MouseEvent e ){
isAreaToBePainted = true;
x = e.getX();
y = e.getY();
repaint();
}
public void mouseMoved( MouseEvent e ){
x = e.getX();
y = e.getY();
repaint();
}
}//public class GraphicPanelMouseListen implements MouseListener
}//public class GraphicPanel
You can avoid using these lists altogether by storing every finished brush stroke, or any action for that matter, in its own image. You can then layer these in order they were made. It will be fewer of them, you can easily define how many actions in past you want to have while the bottom most one containing everything that didn't fit in the history.
Pixel blit is relatively fast operation, unlike stuff you do with shapes, and you can easily make it faster by using VolatileImage instead of buffered image for collecting all actions in history.
In my opinion is this approach much faster, and less restricting for actions added in the future. You can easily just mark certain layers as invisible and move back and forth through history like in Photoshop for instance, but without any dependency on actual contents of said layers.
I have been searching the internet to find examples or how to use java's drag and drop. I have found a few, but all the examples only allow you to drag into a specific location, i.e another list box or text area.
I want to know if there is a way to drag items onto a jpanel or similar container, having the item put anywhere freely on the container.
As long as the target is a supported drop target for the item you are dragging then you can drop it to containers like JPanel.
You control the way the dragged item is displayed at the drop location. If your panel overrides paintComponent() then you can paint the item however you find appropriate.
'is a way to drag items into a jpanel'
You can set a DropTarget to your JPanel.
public class MyDropTarget extends JPanel implements DropTargetListener {
private MyImage image;
private String text;
public MyDropTarget() {
setBackground(new Color(30,60,10));
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED, new Color(30,60,10).brighter(), new Color(30,60,10).darker() ) );
DropTarget dt = new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this, true, null);
this.setDropTarget( dt );
}
#Override
public void paintComponent( Graphics g ) {
super.paintComponent( g );
if( image != null && image.getImage() != null ) {
g.drawImage( image.getImage(), 0, 0, null );
if(image.isError()){
g.setColor(Color.BLACK);
g.drawString( text, 0, 0 );
}
}
}
public void dragEnter( DropTargetDragEvent dtde ) {
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.RAISED, Color.RED.brighter(), Color.RED.darker() ) );
}
public void dragExit( DropTargetEvent dte ) {
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED, UIManager.getColor( "MenuBar.highlight" ), UIManager.getColor( "MenuBar.shadow" ) ) );
}
public void dragOver( DropTargetDragEvent dtde ) {
}
public void drop( DropTargetDropEvent dtde ) {
try {
text = (String) dtde.getTransferable().getTransferData( DataFlavor.stringFlavor );
image = (MyImage)dtde.getTransferable().getTransferData( DataFlavor.imageFlavor );
this.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED, new Color(30,60,10).brighter(), new Color(30,60,10).darker() ) );
dtde.dropComplete( true );
repaint();
} catch( UnsupportedFlavorException e ) {
e.printStackTrace();
} catch( IOException e ) {
e.printStackTrace();
}
this.setDropTarget( null );
}
public void dropActionChanged( DropTargetDragEvent dtde ) {
}
}
I implemented Drag&Drop in this way:
The quite convenient mechanism for the implementation of Drag&Drop has appeared in Java 6, but it does have its disadvantages. For example, you should explicitly specify a Drop Target, what is not very useful when you need to lay down the object near the Drop Target. Also in the standard implementation there is no guarantee of execution order of listeners’ methods. I'll tell you the concept of implementing a more extensible Drag&Drop.
Initially the mouse listeners (Mouse Listener and MouseMotionListener) should be assigned to all Drag Sources. It’s need to implement 3 methods: a method of mouse click on the object, a method of moving the mouse while holding the mouse button on the object (mouseDragged in MouseMotionListener) and the mouse up method.
The listeners assignment looks as follows:
component.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
//block click right mouse button
if (MouseEvent.BUTTON1 == e.getButton()) {
startDrag(e);
}
}
#Override
public void mouseReleased(MouseEvent e) {
//block click right mouse button
if (MouseEvent.BUTTON1 == e.getButton()) {
endDrag(e);
}
}
});
component.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
drag(e);
}
});
Accordingly when you click on the object Drag&Drop starts, when you move the mouse the object should be moved, when you release the mouse the object should change its position and be moved to a new container. If the object will be moved in the frame of one container, then it is possible to implement only mouseDragged () method, in which the coordinates of the dragged object will be changed:
#Override
public void mouseDragged(MouseEvent e) {
Point mouseLocation = e.getLocationOnScreen();
Component draggedComponent = (Component) e.getSource();
SwingUtilities.convertPointFromScreen(mouseLocation,
draggedComponent.getParent());
draggedComponent.setLocation(mouseLocation);
}
But dragged object coordinates can be set relative to the container in which it is located. Accordingly, when the mouse is moved to another container it is necessary to add a component to a new container and to calculate the new coordinates, etc. This method is not very beautiful and extensible, so I suggest using GlassPane to display the dragged object.
The algorithm looks as follows:
Click on the object.
Get a screenshot of the object (see how to make a screenshot).
Hide the original object.
Draw on glassPane a screenshot of the object, based on the
coordinates of the mouse.
When you move the mouse you need to redraw a screenshot according
to the new coordinates.
When you release the mouse you need to place the object on the
container under which the cursor is located.
Display the original object.
With this approach, we have no any dependences on the container on which the cursor should be placed to make Drop and correspondingly the object can be "Dropped" anywhere.
GlassPane with transparency effect:
public class GhostGlassPane extends JPanel {
private final AlphaComposite composite;
private BufferedImage ghostImage = null;
private Point location = new Point(0, 0);
public GhostGlassPane() {
setOpaque(false);
composite = AlphaComposite.getInstance(AlphaComposite.
SRC_OVER, 0.7f);
}
public void paintComponent(Graphics g) {
if (ghostImage == null)
return;
Graphics2D g2 = (Graphics2D) g;
g2.setComposite(composite);
g2.drawImage(ghostImage, (int) (location.getX()),
(int) (location.getY()), null);
}
}
In this response only concept of implementation is given.
This information is taken from my article: Frequently Asked Questions during Java applet development
I have difficulties drawing an image on a JFreeChart - XYLineChart. The main problem is the x and y coordinates of the annotation is updated dynamically in real time.So with my code adding the annotation and clearing it for the new one to be drawn causes flickering which is annoying for the user.
I have checked some examples of flickering problems on JAVA using update() , paint () or repaint() methods using graphics but seems not implementable on a JFreeChart.
Do you have any ideas how to get rid of the flicker or a workaround to use one bufferedImage on the JFreeChart instead of an annotation ?
To be more specific here is the drawn line and the image :
Screenshot
So this cross hair (as the buffered image) should go on the plot line up and down with the updated values of x and y axis.But this motion causes the flickering unfortunately.
Here is the part of my code where I draw the image - I cannot provide SSCCE I guess since there are more than 15 classes and 5k of written code :
// After a button clicked on panel
SomeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// The chart and XYPlot is already drawn at this point
// Reading the image
try {
myPicture = ImageIO
.read(new File("\\\\Users2\\blabla\\Data\\MyPictures\\x.png"));
} catch (IOException e) {
e.printStackTrace();
}
// Setting up a timer
timer2 = new java.util.Timer();
Object source = event.getSource();
if (source == SomeButton) {
// Setting up a task
task2 = new java.util.TimerTask() {
#Override
public void run() {
double x1;
double y1;
try {
// Getting different x and y values from a microcontroller instantaneously
if (microContConnected()) {
x1 = microCont.getX();
y1 = microCont.getY();
// creating the annotation
XYImageAnnotation ImageAnn = new XYImageAnnotation(x1, y1, myPicture);
// Here is the drawing and clearing made !
plot.addAnnotation(ImageAnn);
pause(50);
plot.clearAnnotations();
}
} catch (SerialPortException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
timer2.scheduleAtFixedRate(task2, 50, 50);
}
}
});
It seems I found a solution myself ; instead of adding the image to plot I use the renderer and there is no pause function between adding and removing the picture with new coordinates.. also sequence of adding and removed are reversed. Surprising for me to work this way I must say. There is no flickering left; it's as smooth as a clipped graphics or double buffered. :) Here is the new code :
// renderer
final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer();
// Reading the image
try {
myPicture = ImageIO.read(new File("\\\\Users2\\blabla\\Data\\MyPictures\\x.png"));
} catch (IOException e) {
e.printStackTrace();
}
// Setting up a timer
timer2 = new java.util.Timer();
Object source = event.getSource();
if (source == someButton) {
task2 = new java.util.TimerTask() {
#Override
public void run() {
if (check == true) {
if (microContConnected()) {
x1 = microCont.getX();
y1 = microCont.getY();
renderer.removeAnnotations();
XYImageAnnotation img2 = new XYImageAnnotation(
x1, y1, myPicture);
renderer.addAnnotation(img2,
Layer.FOREGROUND);
}
}
}
};
timer2.scheduleAtFixedRate(task2, 50, 50);
}
I am trying to make a game within JFrame but have run into a problem. I have created an object consisting of four images strung into one. My problem is, how do i paint this object in a JFrame?
Here is the code:
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.*;
public class t4
{
static boolean running;
public static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
public static double width = screenSize.getWidth();
public static double height = screenSize.getHeight();
public static int x = ( 250 );
public static int y = ( 150 );
public static final int sx = (int)width;
public static final int sy = (int)height;
public static void main( String[] args ) throws IOException, InterruptedException
{
Image ur = new ImageIcon("redBlock.gif").getImage();
Image ll = new ImageIcon("redBlock.gif").getImage();
Image ul = new ImageIcon("blueBlock.gif").getImage();
Image lr = new ImageIcon("blueBlock.gif").getImage();
// Create game window...
JFrame app = new JFrame();
app.setIgnoreRepaint( true );
app.setUndecorated( true );
// Add ESC listener to quit...
app.addKeyListener( new KeyAdapter()
{
public void keyPressed( KeyEvent e )
{
if( e.getKeyCode() == KeyEvent.VK_ESCAPE )
running = false;
if((e.getKeyCode()==KeyEvent.VK_LEFT)||(e.getKeyCode()==KeyEvent.VK_KP_LEFT))
x-=10;
if((e.getKeyCode()==KeyEvent.VK_RIGHT)||(e.getKeyCode()==KeyEvent.VK_KP_RIGHT))
x+=10;
if((e.getKeyCode()==KeyEvent.VK_UP)||(e.getKeyCode()==KeyEvent.VK_KP_UP))
y-=10;
if((e.getKeyCode()==KeyEvent.VK_DOWN)||(e.getKeyCode()==KeyEvent.VK_KP_DOWN))
y+=10;
}
});
// Get graphics configuration...
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gd.getDefaultConfiguration();
// Change to full screen
gd.setFullScreenWindow( app );
if( gd.isDisplayChangeSupported() )
{
gd.setDisplayMode(new DisplayMode( sx, sy, 32, DisplayMode.REFRESH_RATE_UNKNOWN ));
}
// Create BackBuffer...
app.createBufferStrategy( 2 );
BufferStrategy buffer = app.getBufferStrategy();
// Create off-screen drawing surface
BufferedImage bi = gc.createCompatibleImage( sx, sy );
// Objects needed for rendering...
Graphics graphics = null;
Graphics2D g2d = null;
Color background = Color.BLACK;
Random rand = new Random();
// Variables for counting frames per seconds
int fps = 0;
int frames = 0;
long totalTime = 0;
long curTime = System.currentTimeMillis();
long lastTime = curTime;
running = true;
while( running )
{
try
{
// wait(500);
// count Frames per second...
lastTime = curTime;
curTime = System.currentTimeMillis();
totalTime += curTime - lastTime;
if( totalTime > 1000 )
{
totalTime -= 1000;
fps = frames;
frames = 0;
}
++frames;
// clear back buffer...
g2d = bi.createGraphics();
g2d.setColor( background );
g2d.fillRect( 0, 0, sx, sy );
// draw some rectangles...
/* int r = 45;
int g = 232;
int b = 163;
g2d.setColor( new Color(r,g,b) );
int w = ( 250 );
int h = ( 150 );
g2d.fillRect( x+25, y+25, w, h );*/
if(y<775)
{
y++;
}
else
{
y=0;
}
// display frames per second...
g2d.setFont( new Font( "Courier New", Font.PLAIN, 12 ) );
g2d.setColor( Color.GREEN );
g2d.drawString( String.format( "FPS: %s", fps ), 20, 20 );
// Blit image and flip...
graphics = buffer.getDrawGraphics();
graphics.drawImage( bi, 0, 0, null );
graphics.drawImage(ur,x,y,null);
graphics.drawImage(ll,x+50,y+50,null);
graphics.drawImage(ul,x,y+50,null);
graphics.drawImage(lr,x+50,y,null);
if( !buffer.contentsLost() )
buffer.show();
}
finally
{
// release resources
if( graphics != null )
graphics.dispose();
if( g2d != null )
g2d.dispose();
}
}
gd.setFullScreenWindow( null );
System.exit(0);
}
public static void wait(int x) throws InterruptedException
{
Thread.currentThread().sleep(x);
}
}
i want to create an object containing images ur,ll,ul, and lr and be able to draw it on the screen.
This is what you should do:
Modify the class to make it extend javax.swing.JComponent.
Override paintComponent(Graphics).
Create a javax.swing.Timer to manage the frame rate.
Override getPreferredSize().
First (as requested by DavidB) I'll give you an explanation as to why you should do these things, and then I'll show you how.
Explanation
Since you're trying to add your component to a JFrame, you'll need the component's class to be compatible with JFrame's add method (actually, it belongs to Container, but that doesn't matter very much). If you look at the JavaDoc documentation for add, you'll see that it won't accept just any Object; rather, it requires an instance of a Component (or subclass thereof). You could subclass Component instead of JComponent, but Component is more for AWT applications than Swing applications.
In short: Subclass JComponent so that JFrame.add will accept it as a parameter.
Once you've subclassed JComponent, you'll need to actually tell the window manager what to draw. You can put drawing code anywhere, but remember that it won't be invoked (used) unless something actually calls that method. The method that the graphics environment calls to start the painting process is called paintComponent*. If you override this method, then the graphics environment will invoke your custom painting code.
In short: Override paintComponent because that's what the graphics environment cares about.
Since you're most likely going to be animating in your game, you'll want to keep a constant rate of Frames per Second, right? If you don't, there are many factors (computer power, other applications running, drawing complexity, etc.) that could make the frame rate go haywire. To do this, you'll want to call the repaint method a specified number of times per second (once every frame). This is the point of the Swing timer. You give it a block of code and a number of milliseconds, and it will run that code every time the specified interval has been elapsed.
In short: Use a Swing timer so that you can keep the frame rate constant and controlled.
Imagine you have a word processing application. It has a menu bar at the top, the document window in the center, and a toolbar at the bottom. Obviously, you want the menu bar and toolbar to be small, and the document to take up as much space as possible, right? This is why you need to have each component tell you what its size should be, known as its preferred size. Overriding getPreferredSize allows you to return whatever size you want, thus controlling the size of the component.**
In short: Override getPreferredSize so that the window manager and graphics environment get all the sizes right.
* It's not actually paintComponent that's called; it's paint. However, the paint method calls paintComponent, paintBorder, and paintChildren:
This method actually delegates the work of painting to three protected
methods: paintComponent, paintBorder, and paintChildren. They're
called in the order listed to ensure that children appear on top of
component itself. Generally speaking, the component and its children
should not paint in the insets area allocated to the border.
Subclasses can just override this method, as always. A subclass that
just wants to specialize the UI (look and feel) delegate's paint
method should just override paintComponent.
(source: the JavaDoc)
** Overriding getPreferredSize does not actually guarantee that that is the size at which the component will be shown. It merely specifies the size at which it should be shown. Some layout managers will choose to ignore this (such as BorderLayout). However, when you call pack to size the window correctly, it should calculate the preferred size according to this size.
Procedure
Extending JComponent
To make the class extend JComponent, just change the class signature to this:
import javax.swing.JComponent;
public class MyGameDisplay extends JComponent {
...
}
Overriding paintComponent
You'll need to import the java.awt.Graphics class. See this example code for how to use paintComponent:
import javax.swing.JComponent;
import java.awt.Graphics;
public class MyGameDisplay extends JComponent {
// Some code here
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g); // this line is crucial; see below
g.drawString(100,100,"Hello world");
}
}
Note: Above, I mentioned the necessity of the invocation of super.paintComponent from within the paintComponent method. The reason for this is that it will (among other things) clear all graphics that you've earlier displayed. So, for example, if your program draws a circle moving across the screen, each iteration of the drawing will also contain a trail of circles from the previous drawings unless you call super.paintComponent.
Using a Timer
To get the FPS rate that you want, modify the class to include a Swing timer, as such:
// Include these imports:
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyGameDisplay extends JComponent {
private Timer t;
public MyGameDisplay() {
ActionListener al = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
}
t = new Timer(1000 / 30 /* frame rate */, al);
t.start();
}
}
Override getPreferredSize
The reason for overriding getPreferredSize is so that layout managers will know how to properly size the container.
While writing the actual logic to calculate the size may be difficult, overriding getPreferredSize in itself isn't. Do so like this:
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400); // for example
}
When you're all done, you can just run the following code:
import javax.swing.JFrame;
public class Test {
public static void main(String[] args) {
JFrame frame = new JFrame();
MyGameDisplay mgd = new MyGameDisplay();
frame.add(mgd);
frame.pack();
frame.setVisible(true);
}
}