Force JComponent to Resize - java

I have a JPanel that has some JLabels on it. They've been set to specific sizes based on the size of the JPanel. However, when I resize the JPanel, they don't get resized. What I would like to do is force them to get resized. Here was my attempt:
public void componentResized(ComponentEvent e) {
//just test values
int height = 10;
int width = 10;
Dimension d = new Dimension(width, height);
//whenever we detect this event, we'll resize the draft components
for(int i = 0; i < jpPack.getComponentCount(); i++){
jpPack.getComponent(i).setPreferredSize(d);
}
jpPack.revalidate();
jpPack.repaint();
}
However, this didn't work, I didn't get any results. I also tried with setSize(d) but nothing either. The way I've created it right now, the size of the JLabel is set at creation based on the size of the JPanel.
Essentially, all I want to do is tell the JLabel "the GUI has resized, here is your new size, now repaint yourself to be this size". Would I perhaps have to change my JLabel's paint method to handle this? If so, how would I go about that?
Thanks.
EDIT: Here is some additional information to try to help. I'm sorry I can't provide a working example, but there's just so much code involved in making this work and it isn't feasible (it's not a small project, it's a minor bug in a large project).
ImageLabels are my custom JLabel to use for my cards. There isn't a lot in there besides some stored data and compare methods. The paintComponent method when drawing the cards in the images below should just be calling the super method.
My JPanel containing the labels is defined as follows:
jpCards = new JPanel(new MigLayout("insets 0, gapx 0, gapy 0, rtl", "grow"));
When I add images, they are added as follows:
for(int i = 0; i < imgJLabels.length; i++){
BufferedImage img;
try{
img = ImageIO.read(new URL(XML.getXML().getCardURLByName(pack[i], set)));
}
catch (java.net.MalformedURLException e){
}
int height = (jpPack.getHeight() / 2);
if(height > 310) height = 310;
float temp = (float)img.getWidth() / img.getHeight();
int width = Math.round(height * temp);
if(width > 223) width = 223;
ImageIcon imgIcon = getScaledImage(img, height, width);
imgJLabels[i] = new ImageLabel(img, imgIcon, pack[i]);
jpPack.add(imgJLabels[i]);
imgJLabels[i].addMouseListener(this);
}
And my scaling method is as follows:
private ImageIcon getScaledImage(Image srcImg, int height, int width){
BufferedImage resizedImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, width, height, null);
g2.dispose();
return new ImageIcon(resizedImg);
}
Here you can see the results. As I resize the GUI to be bigger, the ImageLabels don't increase in size. The same applies when I make the GUI smaller, the cards don't become smaller.
I do have a maximum size for the cards set, as you can see. However, the large card image on the right is set to the same maximum, so my cards are definitely not hitting the maximum. The GUI is just not resizing the elements.
This issue has been driving me nuts for a long time and I have no idea what to do. Thanks again.

Related

Copy the contents of a JPanel onto a BufferedImage

On the first time through, I insert BufferedImages from a list onto my JPanel from my extended class:
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
if (controlWhichImage == 1){
for (BufferedImage eachImage : docList){
g.drawImage(eachImage, 0,inty,imageWidth,imageHeight,null);
intx += eachImage.getWidth();
inty += eachImage.getHeight() * zoomAdd;
}
if (intx >= this.getWidth() || inty >= this.getHeight()){
inty = 0;
}
The next time I want to copy the contents of the JPanel to a BufferedImage:
public void recordImage(){
controlWhichImage = 2;
this.createdImage = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);
Image halfWay = this.createImage(this.getWidth(), this.getHeight());
//now cast it from Image to bufferedImage
this.createdImage = (BufferedImage) halfWay;
}
And then, take the modified BufferedImage and draw it back onto the JPanel:
if (controlWhichImage == 2){
g.drawImage(this.createdImage,0,inty,this.getWidth(),this.getHeight(),null);
}
This second time I get a blank panel.
I hope this is clear, any help gratefully received.
Sorry for my poor explanation. I will try to make myself clearer.
On each iteration the user is able to draw on the image in the Jpanel.
What I want to do is copy the user altered jpanel into a buffered image which will then be in the Jpanel to be edited again by the user.
This continues until the user selects print.
So apart from the code that I have put here are the controls for drawing by the user, at the moment I am struggling with putting the initial updated image from the original Jpanel into a bufferedImage and then back to the JPanel.
Hope this makes it somewhat clearer
To draw to a BufferedImage, you would do something similar to what you already do in your paintComponent method, but with your BufferedImage. Perhaps a method like:
// imgW and imgH are the width and height of the desired ultimate image
public BufferedImage combineImages(List<BufferedImage> docList, int imgW, int imgH) {
// first create the main image that you want to draw to
BufferedImage mainImg = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
// get its Graphics context
Graphics g = mainImage.getGraphics();
int intx = 0;
int inty = 0;
// draw your List of images onto this main image however you want to do this
for (BufferedImage eachImage : docList){
g.drawImage(eachImage, 0,inty,imageWidth,imageHeight,null);
intx += eachImage.getWidth();
inty += eachImage.getHeight() * zoomAdd;
}
}
// anything else that you need to do
g.dispose(); // dispose of this graphics context to save resources
return mainImg;
}
You could then store the image returned into a varaible and then draw it in your JPanel if desired, or write it to disk.
If this doesn't answer your question, then again you'll want to tell more and show us your MCVE.

OutOfMemoryError: Jave heap space when jtable saved as Image

Currently I am saving a jtable as jpeg using the below method, when the dimension of the jtable became 2590, 126181, java.lang.OutOfMemoryError: Java heap space exception occurs at "BufferedImage constructor", when the size of the table is small the image gets saved successfully.
public BufferedImage saveComponentAsJPEG(JTable table, String filename) {
Dimension size = table.getSize();
BufferedImage myImage =
new BufferedImage(size.width, size.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = myImage.createGraphics();
table.paint(g2);
return myImage;
}
How to save a jtable with bigger size in pdf or jpeg image?
Updated Info:
You asked how to "split the JTable into different small images":
As you go through my code below please read my comments, they help explain what is happening and will help you grasp a better understanding of how a JTable/JComponent can be painted to lots of small images. At the heart my code is similar to yours, but there are two key points:
1) Rather than create a single large BufferedImage, I create a single small image that is then used multiple times, therefore leaving a very small memory footprint.
2) With the single image, I use Graphics.translate() to paint a small part of the JTable each time.
The following code was tested with a large JComponents (2590 x 126181) and a tile size of 200x200, and the whole process did not exceed 60mb of memory:
//width = width of tile in pixels, for minimal memory usage try 200
//height = height of tile in pixels, for minimal memory usage try 200
//saveFileLocation = folder to save image tiles
//component = The JComponent to save as tiles
public static boolean saveComponentTiles(int width, int height, String saveFileLocation, JComponent component)
{
try
{
//Calculate tile sizes
int componentWidth = component.getWidth();
int componentHeight = component.getHeight();
int horizontalTiles = (int) Math.ceil((double)componentWidth / width); //use (double) so Math.ceil works correctly.
int verticalTiles = (int) Math.ceil((double)componentHeight / height); //use (double) so Math.ceil works correctly.
System.out.println("Tiles Required (H, W): "+horizontalTiles+", verticalTiles: "+verticalTiles);
//preset arguments
BufferedImage image;
//Loop through vertical and horizontal tiles
//Draw part of the component to the image
//Save image to file
for (int h = 0; h < verticalTiles; h++)
{
for (int w = 0; w < horizontalTiles; w++)
{
//check tile size, if area to paint is smaller than image then shrink image
int imageHeight = height;
int imageWidth = width;
if (h + 1 == verticalTiles)
{
imageHeight = componentHeight - (h * height);
}
if (w + 1 == horizontalTiles)
{
imageWidth = componentWidth - (w * width);
}
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
//translate image graphics so that only the correct part of the component is panted to the image
g.translate(-(w * width), -(h * height));
component.paint(g);
//In my example I am saving the image to file, however you could throw your PDF processing code here
//Files are named as "Image.[h].[w]"
//Example: Image 8 down and 2 accross would save as "Image.8.2.png"
ImageIO.write(image, "png", new File(saveFileLocation + "Image." + h +"."+ w + ".png"));
//tidy up
g.dispose();
}
}
return true;
}
catch (IOException ex)
{
return false;
}
}
Just call it like so:
boolean result = saveComponentTiles(200, 200, saveFileLocation, jTable1);
Also if you haven't done it already, you should only call the method from a different thread because it will hang your application when dealing with large components.
If you have not picked a PDF library yet, then I highly recommend looking at iText.
Original Post:
The process you are looking for is quite simple, however it may take some work.
You were on the right track thinking about parts, but as David
mentioned you shouldn't mess with the jTable, instead you will need a
to make use of the TiledImage class, or do something yourself with
RenderedImage and Rasters.
This sort of method basically uses HDD space instead of memory and
lets you create a large image in lots of smaller parts/tiles, then
when its done you can save it all to a single image file.
This answer may also help: https://stackoverflow.com/a/14069551/1270000

Good practice for drawing and zooming image?

I have a JScrollPane with a JPanel where I can draw by mouse and code.
I need the possibility to zoom on details in my drawing.
But soon I get a outOfMemoryError. I think because I make my drawing to big while zooming.
This is my code:
private BufferedImage _bufferedImage;
private int _panelWidth = 2000, _panelHeight = 1500;
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
if(_bufferedImage != null){
g.drawImage(_bufferedImage, 0, 0, this);
}
}
public void draw(float zoomFactor){
try {
int width = (int)(_panelWidth * zoomFactor);
int height = (int)(_panelHeight * zoomFactor);
_bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = _bufferedImage.createGraphics();
g2.setBackground(Color.WHITE);
g2.setPaint(Color.BLACK);
g2.scale(zoomFactor, zoomFactor);
drawHouse(g2); ...
g2.dispose();
}
catch (Exception e) {
e.printStackTrace();
}
repaint();
}
There must be better practice then what I did.
I can just draw the area of the scrollpane, but then I can't use the scrollbars,
then I have to use buttons with arrow up, right, left, down to scroll in my drawing.
Anyone who can me give a hint?
but then I can't use the scrollbars
Scrollbars work when the preferred size of the component is greater than the size of the scrollpane. If you are zooming the image in the paintComponent() method then you would also need to override the getPreferredSize() method to return the appropriate size of the image that takes into account the zooming factor.
So in your case the preferred size would be the size of your image.
If you want to zoom in, I am assuming you are no trying to make "bigger pixels", but to draw the same figures at a higher scale. In that case, you should not be using a BufferedImage at all -- instead, you should draw to a suitably scaled JPanel or similar. You can always take a snapshot of whatever you are rendering whenever you need it; but rendering to a BufferedImage without need is wasteful (of time and memory).
See this answer for details.

Double Buffering image in JFrame with other swing controls

I have a JFrame with an JLabel that houses an ImageIcon containing a BufferedImage. The BufferedImage's graphis are drawn on with several different graphics calls, such as drawOval(), drawRectangle(), etc, with many drawn shapes on it. As time passes ,or the user pans/zooms on the panel, the graphics are redrawn, so we could be redrawing several times per second.
Before attempting to add double buffering, the image repaint was slow, but my JComponents would render OK. The image would redraw multiple times as second as the user could drag and resize the label. I decided to attempt double buffering.
With my current double-buffered implementation below, the JLabel/Graphics redraw and show very smooth. However, The JFrame has other controls (JMenuBar, JSlider, JComboBox, etc.) that do not render properly and flicker a lot. I have to manually repaint() them, but then they flicker. What am I doing wrong with Double buffering? How can I get my image to repaint smoothly, but also allow my JComponetns to not flicker?
The view sets himself up to do double buffering. I also tried setting setIgnoreRepaint(true) as a way to fix my issue.
this.createBufferStrategy(2);
...
m_graphicsLabel.setIcon(new ImageIcon(bufferedImage));
This method below is called by the helper class when new graphics are available. I am manually repainting the JComponents, otherwise they wouldn't show up at all. But they flicker as this method can be called multiple times per second.
public void newViewGraphicsAvailable() {
m_xAxisZoomSlider.repaint();
m_yAxisZoomSlider.repaint();
lblZoom.repaint();
lblYZoom.repaint();
m_comboBox.repaint();
m_menuBar.repaint();
m_layersMenu.repaint();
}
This is the helper class that manipulates the graphics object with calls to graphics.drawOval() etc.
private void drawGraphics(){
BufferedImage blankImage = createBlankImage();
Graphics g = null;
try {
g = m_bufferedStrategy.getDrawGraphics();
g.drawImage(blankImage, 0, 0, null);
m_imageDrawer.draw((Graphics2D) g);
} finally {
g.dispose();
}
m_bufferedStrategy.show();
Toolkit.getDefaultToolkit().sync();
view.newGraphicsAvailable();
}
private BufferedImage createBlankImage()
{
short[] backgroundPixels = new short[Width * Height];
for(int index = 0; index < backgroundPixels.length; index++) {
backgroundPixels[index] = 0;
}
DataBuffer dbuf = new DataBufferUShort(backgroundPixels, WaterfallHeight * WaterfallWidth, 0);
int bitMasks[] = new int[]{0xFFFF};
SampleModel sampleModel = new SinglePixelPackedSampleModel(
DataBuffer.TYPE_USHORT, WaterfallWidth, WaterfallHeight, bitMasks);
WritableRaster raster = Raster.createWritableRaster(sampleModel, dbuf, null);
return new BufferedImage(m_indexColorModel, raster, false, null);
}

java applet: is there a simple way to draw and erase in a two-layered 2D scene?

I have an applet which displays some data using circles and lines. As the data continually changes, the display is updated, which means that sometimes the circles and lines must be erased, so I just draw them in white (my background color) to erase them. (There are a lot of them, so erasing everything and then recomputing and redrawing everything except the erased item would be a horribly slow way to erase a single item.)
The logic of the situation is that there are two layers that need to be displayed, and I need to be able to erase an object in one layer without affecting the other layer. I suppose the upper layer would need to have a background color of "transparent", but then how would I erase an object, since drawing in a transparent color has no effect.
What distinguishes this situation from all the transparency-related help on the web is that I want to be able to erase lines and circles one-by-one from the transparent layer, overwriting their pixels with the "fully transparent" color.
Currently my applet draws (using just a single layer) by doing this in start():
screenBuffer = createImage(640, 480);
screenBufferGraphics = screenBuffer.getGraphics();
and this in paint():
g.drawImage(screenBuffer, 0, 0, this);
and objects are rendered (or "erased" by drawing in white) by commands like:
screenBufferGraphics.drawLine(x1,y1,x2,y2);
Is it easy to somehow make a second screen buffer with a transparent background and then be able to draw and erase objects in that buffer and render it over the first buffer?
This seems fairly quick, so long as the rendered image area remains around 640x480, the code can achieve from 125-165 FPS. The code tracks 2000 semi-transparent lines of width 4px, and moves them around in an area 8 times the size of the rendered image.
import java.awt.image.BufferedImage;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.*;
import javax.swing.*;
import java.util.Random;
class LineAnimator {
public static void main(String[] args) {
final int w = 640;
final int h = 480;
final RenderingHints hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
);
hints.put(
RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY
);
final BufferedImage bi = new BufferedImage(w,h, BufferedImage.TYPE_INT_RGB);
final JLabel l = new JLabel(new ImageIcon(bi));
final BouncingLine[] lines = new BouncingLine[20000];
for (int ii=0; ii<lines.length; ii++) {
lines[ii] = new BouncingLine(w*8,h*8);
}
final Font font = new Font("Arial", Font.BOLD, 30);
ActionListener al = new ActionListener() {
int count = 0;
long lastTime;
String fps = "";
public void actionPerformed(ActionEvent ae) {
count++;
Graphics2D g = bi.createGraphics();
g.setRenderingHints(hints);
g.clearRect(0,0,w,h);
for (int ii=0; ii<lines.length; ii++) {
lines[ii].move();
lines[ii].paint(g);
}
if ( System.currentTimeMillis()-lastTime>1000 ) {
lastTime = System.currentTimeMillis();
fps = count + " FPS";
count = 0;
}
g.setColor(Color.YELLOW);
g.setFont(font);
g.drawString(fps,10,30);
l.repaint();
g.dispose();
}
};
Timer timer = new Timer(1,al);
timer.start();
JOptionPane.showMessageDialog(null, l);
System.exit(0);
}
}
class BouncingLine {
private final Color color;
private static final BasicStroke stroke = new BasicStroke(4);
private static final Random random = new Random();
Line2D line;
int w;
int h;
int x1;
int y1;
int x2;
int y2;
BouncingLine(int w, int h) {
line = new Line2D.Double(random.nextInt(w),random.nextInt(h),random.nextInt(w),random.nextInt(h));
this.w = w;
this.h = h;
this.color = new Color(
128+random.nextInt(127),
128+random.nextInt(127),
128+random.nextInt(127),
85
);
x1 = (random.nextBoolean() ? 1 : -1);
y1 = (random.nextBoolean() ? 1 : -1);
x2 = -x1;
y2 = -y1;
}
public void move() {
int tx1 = 0;
if (line.getX1()+x1>0 && line.getX1()+x1<w) {
tx1 = (int)line.getX1()+x1;
} else {
x1 = -x1;
tx1 = (int)line.getX1()+x1;
}
int ty1 = 0;
if (line.getY1()+y1>0 && line.getY1()+y1<h) {
ty1 = (int)line.getY1()+y1;
} else {
y1 = -y1;
ty1 = (int)line.getY1()+y1;
}
int tx2 = 0;
if (line.getX2()+x2>0 && line.getX2()+x2<w) {
tx2 = (int)line.getX2()+x2;
} else {
x2 = -x2;
tx2 = (int)line.getX2()+x2;
}
int ty2 = 0;
if (line.getY2()+y2>0 && line.getY2()+y2<h) {
ty2 = (int)line.getY2()+y2;
} else {
y2 = -y2;
ty2 = (int)line.getY2()+y2;
}
line.setLine(tx1,ty1,tx2,ty2);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setColor(color);
g2.setStroke(stroke);
//line.set
g2.draw(line);
}
}
Update 1
When I posted that code, I thought you said 100s to 1000s, rather than 1000s to 100,000s! At 20,000 lines the rate drops to around 16-18 FPS.
Update 2
..is this optimized approach, using layers, possible in Java?
Sure. I use that technique in DukeBox - which shows a funky plot of the sound it is playing. It keeps a number of buffered images.
Background. A solid color in a non-transparent image.
Old Traces. The older sound traces as stretched or faded from the original positions. Has transparency, to allow the BG to show.
Latest Trace. Drawn on top of the other two. Has transparency.
After a day of no proposed solutions, I started to think that Java Graphics cannot erase individual items back to a transparent color. But it turns out that the improved Graphics2D, together with BufferedImage and AlphaComposite, provide pretty much exactly the functionality I was looking for, allowing me to both draw shapes and erase shapes (back to full transparency) in various layers.
Now I do the following in start():
screenBuffer = new BufferedImage(640, 480, BufferedImage.TYPE_INT_ARGB);
screenBufferGraphics = screenBuffer.createGraphics();
overlayBuffer = new BufferedImage(640, 480, BufferedImage.TYPE_INT_ARGB);
overlayBufferGraphics = overlayBuffer.createGraphics();
I have to use new BufferedImage() instead of createImage() because I need to ask for alpha. (Even for screenBuffer, although it is the background -- go figure!) I use createGraphics() instead of getGraphics() just because my variable screenBufferGraphics is now a Graphics2D object instead of just a Graphics object. (Although casting back and forth works fine too.)
The code in paint() is barely different:
g.drawImage(screenBuffer, 0, 0, null);
g.drawImage(overlayBuffer, 0, 0, null);
And objects are rendered (or erased) like this:
// render to background
screenBufferGraphics.setColor(Color.red);
screenBufferGraphics.fillOval(80,80, 40,40);
// render to overlay
overlayBufferGraphics.setComposite(AlphaComposite.SrcOver);
overlayBufferGraphics.setColor(Color.green);
overlayBufferGraphics.fillOval(90,70, 20,60);
// render invisibility onto overlay
overlayBufferGraphics.setComposite(AlphaComposite.DstOut);
overlayBufferGraphics.setColor(Color.blue);
overlayBufferGraphics.fillOval(70,90, 30,20);
// and flush just this locally changed region
repaint(60,60, 80,80);
The final Color.blue yields transparency, not blueness -- it can be any color that has no transparency.
As a final note, if you are rendering in a different thread from the AWT-EventQueue thread (which you probably are if you spend a lot of time rendering but also need to have a responsive interface), then you will want to synchronize the above code in paint() with your rendering routine; otherwise the display can wind up in a half-drawn state.
If you are rendering in more than one thread, you will need to synchronize the rendering routine anyway so that the Graphics2D state changes do not interfere with each other. (Or maybe each thread could have its own Graphics2D object drawing onto the same BufferedImage -- I didn't try that.)
It looks so simple, it's hard to believe how long it took to figure out how to do this!

Categories