Swing normally combines multiple repaint() requests, in order to drive the paintComponent() method only once and thus enhance performance. Normally, this is what you want.
In my application, however, this is undesirable. My component is a large screen full of text (like a text editor). Each character includes attributes like foreground/background color and styles (like BOLD). Repainting the entire screen (which repaint(() does by default) is an expensive operation, and introduces unnecessary latency, when only updating a few disparate characters on-screen.
Therefore, I call repaint(x, y, width, height) multiple times, but only for those areas that have actually changed (typically, 50 characters or less, which may be scattered about the screen in two or three separate, contiguous areas).
The problem is, typically I will have characters change at the top and bottom of the screen. I call repaint for each of the affected areas, but Swing combines my multiple repaint(...) requests into a single repaint(...) request (this is default behavior) by computing the union of the areas to be repainted. This results in a single call to paintComponent() but with a large clipping rectangle (as returned by getClipBounds()) which encompasses all areas to be updated. Thus, I wind up repainting a large area of the screen, anyway (based on the ClipBounds), which is counter-productive and what I wish to avoid, since only a small portion of the screen has actually been modified and therefore needs to be repainted.
Question: Is there any way to mitigate this behavior, so that my paintComponent() method repaints only those areas that have been modified, and avoids unnecessary repainting of unmodified areas?
Additional detail, as requested:
The class is derived from JComponent. Basically, it's just an "empty" window on which I draw text characters using Swing's API. The window's maximum size is about 83 rows by 320 columns per row (this is on an Apple Cinema display), so for an 8x16 font, that's 2560x1328.
Basically, the application is a text editor (think: vi). The bottom line of the display is a Status Line. In a worst-case scenario, i might move the cursor one position to the right at the top of the window. This would cause the Status Line at the bottom of the window to be updated to reflect the new cursor position (row, column). So, we have a few locations changing at the top of the window, and a few locations changing at the bottom of the window. I would issue 2 repaint(...) requests, one for each modified area of the window. However, repaint() will combine those two requests, and call paintComponent() once with a bounding rectangle (which defines the area to be repainted) that is the union of the two areas that were actually updated. The resultant rectangle will be very large, extending from the top of the window to the bottom. Thus, in paintComponent(), I wind up repainting a large part of the screen, even thought the vast majority of it was not modified. That is what I'm trying to avoid.
Is there any way to mitigate this behavior, so that my paintComponent() method repaints only those areas that have been modified,
You could try using the paintImmediately(...) method of the JComponent class.
This will result in multiple smaller paint requests being done right away without the benefit of the RepaintManager. Only you can decide is multiple smaller requests performs better than a single larger paint request.
Related
I'm currently working on some Rendering Code, and I wondered if Graphics2D.draw(Shape) is performed the exact same way onscreen as offscreen.
If it is, is there a way to check if my shape (a Line2D) is onscreen?
Depends on what you mean by "exactly the same way".
As far as calls to draw-something() from your code are concerned, they take place normally. What is different, somewhere in the rendering chain the condition that the shape does not actually fall into the actual rendering area is detected and no pixels are actually output for the shape.
As for detecting if something is "on screen", that depends what you define the screen as. Reasonably you would define it as the area described by your Graphics object (that may well be only part of the screen, or completely off screen, heck it could be a printer!).
Provided you have not changed the clip area of a Graphics instance, getClipBounds() gives you the rectangle where graphics are actually rendered. If your graphics comes from a Swing component, you can also substitute the components dimensions for the rendering area.
Once you have the actual Rectangle, you can make use of the methods provided by Shape to detect collisions (contains(), intersects() etc.).
This might be a weird question but what is the right way to think about how images are projected onto the screen?
if we have an image already on the screen and we render another image onto the screen does the new image go ON TOP of the old one, thus making the old one not visible anymore. or does it go behind the old one thus making the new one not visible till we clear the screen off of the old one.
I used to think that the new image goes on top of the old one, however, after working with buffers for a little bit (ie BufferedImage and BufferedStrategy) I come to think that is not the right way it happens.
so can someone please clarify this matter
thanks
Generally speaking, you can think of it like a painters canvas. Every time you paint something new you are painting over the top of what is there.
When dealing with buffers, the process is the same, but know you are dealing with whatever was painted last to THAT buffer.
So if you have three buffers, [1] is on the screen, you are painting [2] which gets pushed to the screen, it doesn't have the content of [1], but what was ever painted to [2] when it was painted last...
So you have [1][2][3], then you have [2][3][1], then [3][1][2]. Each buffer will be out of date by at least two paint cycles.
This is why it's important to clear you buffers and rebuild them from scratch each time, as you don't know the last time a buffer was painted
It depends on what and how you are drawing.
The screen "refreshes" itself at a certain rate, (for example 60 times a second). At that point whatever the graphics card has in its memory gets drawn.
There are lots of different ways of putting things into that graphics card memory though and they have different behaviour.
BufferedImage does not directly go to the screen. Instead it's "copied" into the graphics card and that is used to draw to the screen.
Swing hides all that away from you though - you just need to worry about the repaint and it will handle the rest. The actual handling of this comes from the Control you are using, for example JButton, JPanel, etc.
I got me 90 odd images that form an explosion animation. When I come to 'play' the animation, I use a Swing Timer to set a new Icon for a JLabel, and cycle through until I reach the last image.
My question is this: is it better to have loaded and stored all the ImageIcons in some static form somewhere, or load them from file every time I need them?
Explosions are quite common in the game, and I have two sizes of them (scaling each image for every size of explosion seemed rather inefficient when I want them to swap very quickly) which makes for 200 images (roundabout).
Store or load?
For a game, using Swing components to represent game objects (Sprites) is probably not the best way. I would use a single JPanel as the rendering component and override its paintComponent() method to do rendering for my own game objects.
This way you avoid dealing with Swing components intricacies (e.g. they may add unwanted gaps / offsets where they really paint, as well they make it harder to control the painting order - important when two objects overlap).
I wouldn't use any type of Icon, just use java.awt.Image, or you own type to represent an image (e.g. to render part of an underlying image - as sprite sheet requires). Icon doesn't really add any value for a game.
If the image data fits easily into memory, I would load all the images before even starting. If there are too many images to preload them all, I would use some sort of cache that retains as many images as possible - the cache may use an LRU based scheme to decide which images to retain when it gets full.
I use a SwingTimer to set a new Icon for a JLabel, and cycle through until I reach the last image.
You may want to consider using an Animated Icon. It can be used anywhere an Icon can be used.
The AnimatedIcon class uses loaded Icons, so I guess my answer would be to pre-load for ease of use and CPU efficiency.
I'm developing on a machine from 2008, and unfortunately cannot upgrade at the moment. I really need to optimize performance in this tool I am building.
My entire project is fairly large, and I'm currently creating my own Map Editor for use to create Maps for my main project later on down the road. It's fairly simple, and is being designed to load, save, and edit Arrays of 'Tiles' possessed by a Map. It's basically a 2D-Map Editor, customized for my needs.
When a Map is loaded into the Editor, its Tileset (one large BufferedImage), is broken down into each individual Tile (with a smaller BufferedImage), loaded into the right side of the GUI into a large bunch of JLabels with icons. I've used various Swing LayoutManagers to achieve my desired positioning.
Inside the MapCanvas area, essentially the same thing is done. Each Tile, whether empty or not, has a JLabel with an Icon, loaded onto the Grid. I've asked around before about whether using Swing components would be more efficient than using Java2D to design and draw, and the consensus was that it didn't really matter (and to be honest, Swing probably does it better than what I would write myself).
I believe my problem is stemming from the way that I interconnect each JLabel and their icons.
My goal was to reduce the number of duplicate Icons I have to create, which in theory should reduce memory usage and CPU usage. Tiles in the Editor are interacted with as follows:
If no tile is selected to be 'stamped,' there will just be an alpha composite of a color painted over the JLabel's icon to denote that it is being hovered, and will revert upon a MouseExit
If there is a tile on the active Stamp tool, that tile's image will replace the JLabel's icon -temporarily- to denote that it is being hovered, and will revert upon a MouseExit
If there is a tile on the active Stamp tool, and a JLabel is clicked, that JLabel's icon will be set to the active Stamp tile "permanently" unless reverted
If the option to have Grid Lines is toggled on, each JLabel's icon will be processed through the ImageFactory to have dashed lines running along the top and left edges (forms a grid, as seen in the picture)
If the Zoom level is changed, each JLabel's preferredSize is set to accomodate, and the icon has to be resized in the ImageFactory (grid lines applied depending)
Upon loading a Map into the Editor, each JLabel is created, given its corresponding BufferedImage (be that empty or not), and thrown into the Map's field ArrayList. The preferredSize is set so the Layout looks right, and it is added to the MapCanvas (which is just a JPanel).
I created various ArrayList and HashMap objects, so that I could pass a JLabel as a Key, and get back a BufferedImage. That way, when hovering iver a JLabel, the MouseEvent can send the event's source back to the listener, and it can grab the correct BufferedImage based on the JLabel it receives. The image can then be processed by my ImageFactory class in one of the ways listed above.
So basically when a JLabel recieves a MouseEvent, this happens:
MouseEvent sends source JLabel to ActionListener
Listener uses source JLabel as Key to grab desired BufferedImage chunk
BufferedImage chunk is then processed accordingly (which could be a color Alpha composite, a Zoom factor applied to it, or grid lines added to it, or a combination of these)
Is there any better way to do this? This doesn't take any noticeable toll on my 2 year old Laptop, but there is very noticeable lag when the Mouse interacts with the JLabels on my older Desktop machine.
Seeing as I will be actively developing on both, I want the environment to run smoothly, and if that means I have to be stricter on memory and CPU usage, then so be it.
But I can't really seem to come up with a system that is more memory efficient.
Are the ArrayLists and HashMaps the problems? Are the many image operations the problems?
I've taken advantage of JLabel's "setName(String)" method in various ways already (for example, each JLabel has a name containing its location in the single-dimension Array of JLabels, and its x-and-y coordintes on the graph, i.e. "maptile#24:1,9"), but that requires iterating through Arrays, and my hope was that specifically grabbing the BufferedImages with Keys would be more efficient.
Java collections are dynamic, meaning they resize as you add data. The actual model for their sizing function (used by ArrayList and presumably HashMap) is something similar to when they reach capacity they double their capacity, which consists of declaring a new block in memory and copying across all the stored data.
If you know the maximum possible size for each of your HashMaps and ArrayLists you may benefit from using the constructors that take an initial capacity. Here & Here This will prevent them using more memory than necessary. And will prevent any possible delays from increasing capacity.
You may also benefit from reading the top few paragraphs of the documentation for each type as they can provide good explanations of efficient usage (e.g. HashMaps loadfactor).
It may also be useful for you to consider other types of List and Map (if you are not aware of the difference in their implementations), e.g LinkedList has far more efficient delete and insert operations than ArrayList, however it is much slower for random access to an index than an ArrayList as it must traverse each prior index to reach the desired index, whereas an ArrayList can directly access the desired index.
However it's hard to guess how noticeable any changes you make based on these recommendations would be.
This program will have an infinite canvas (ie as long as the user scrolls, it becomes bigger) with a tiled background image, and you can drag and drop blocks and draw arrows between blocks. Obviously I won't use a layout manager for placing blocks and lines, since they will be absolutely positioned (any link on this, possibily with a snapping feature?). The problem arises with blocks and lines. Basically I'll have two options:
Using a simple layout for each building block. This is the simplest and clearest approach, but does it scale well when you have hundreds of objects? This may not be uncommon, just imagine a database with 50 tables and dozens of relationships
Drawing everything with primitives (rectangles, bitmaps, etc). This seems too complicated (especially things like text padding and alignment) but may be more scalable if you have a large number of objects. Also there won't be any event handler
Please give me some hints based on your experience. I have never drawn with Java before - well I did something rather basic with PHP and on Android. Here is a simple preview
DISCLAIMER
You are not forced to answer this. I am looking for someone who did something like this before, what's the use of writing I can check an open source project? Do you know how difficult it is to understand someone else's code? I'm talking about implementations details here... Moreover, there is no guarantee that he's right. This project is just for study and will be funny, I don't want to sell it or anything and I don't need your authorization to start it.
Measuring and drawing text isn't such a pain, since java has built in classes for doing that. you may want to take a look at the 2D Text Tutorial for more information. In fact, I did some text drawing computations with a different graphics engine which is much more primitive, and in the end it was rather easy (at least for the single-line drawing, for going multiline see the previous link).
For the infinite canvas problem, that's also something I always wanted to be able to do. A quick search here at stackoverflow gives this which sounds nice, althought I'm not sure I like it. What you can do, is use the way GIMP has a scroll area that can extend as you move - catch the click of the middle mouse button for marking the initial intention to move the viewport. Then, when the mouse is dragged (while the button is clicked) move the viewport of the jscrollpane by the offset between the initial click and the current position. If we moved outside the bounds of the canvas, then you should simply enlarge the canvas.
In case you are still afraid of some of the manual drawing, you can actually have a JPanel as your canvas, with a fixed layout. Then you can override it's paint method for drawing the connectors, while having child components (such as buttongs and text areas) for other interaction (and each component may override it's own paint method in case it wants to have a custom-painted rect).
In my last drawing test in java, I made an application for drawing bezier curves (which are basically curves made of several control points). It was a JPanel with overidden paint method that drew the curve itself, and buttons with custom painting placed on the location of the control points. Clicking on the control point actually was clicking on a button, so it was easy to detect the matching control point (since each button had one control point associated with it). This is bad in terms of efficiency (manual hit detection may be faster) but it was easy in terms of programming.
Anyway, This idea can be extended by having one child JPanel for each class rectangle - this will provide easy click detection and custom painting, while the parent will draw the connectors.
So in short - go for nested JPanels with custom drawing, so that you can also place "on-canvas" widgets (and use real swing widgets such as text labels to do some ready drawing) while also having custom drawing (by overriding the paint method of the panels). Note that the con of this method is that some swing look-and-feel's may interfere with your drawing, so may need to mess a bit with that (as far as I remember, the metal and nimbus look-and-feel's were ok, and they are both cross-platform).