Related
I started remaking my graphics engine and i have a problem with repainting the VolatileImage on the JFrame, i added an FPS counter and when it is in full-screen it only gets to around 250 or 280 FPS even though i have the loop in a new thread and I'm only drawing 2 dots on the screen.
Here is the code for the init and render functions in the Window class, might be a bit unorganized:
`
public int render() {
frame.getGraphics().drawImage(vImg, 0, 0, frame.getWidth(), frame.getHeight(), null);
totalFrames++;
if (System.nanoTime() > lastFPScheck + 1000000000) {
lastFPScheck = System.nanoTime();
currentFPS = totalFrames;
totalFrames = 0;
}
if (vImg.validate(gc) == VolatileImage.IMAGE_OK) {
vImg = gc.createCompatibleVolatileImage(frame.getWidth(), frame.getHeight());
}
if (debugging()) {
frame.setTitle(title + "[FPS: " + currentFPS + "]");
}
graphics = (Graphics2D)vImg.getGraphics();
graphics.setColor(bg_color);
graphics.fillRect(0, 0, frame.getWidth(), frame.getHeight());
return currentFPS;
}
public void repaint_buffer() {
frame.getContentPane().getGraphics().drawImage(vImg, 0, 0, canvasWidth, canvasHeight, null);
}
public void init(int width,
int height,
String title,
Color bg_color,
Consumer<Void> onDestroy) {
this.canvasWidth = width;
this.canvasHeight = height;
this.is_open = true;
this.title = title;
this.bg_color = bg_color;
this.frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
onDestroy.accept(null);
}
});
this.frame.addWindowListener(new WindowAdapter() {
public void windowResized(WindowEvent e) {
vImg = gc.createCompatibleVolatileImage(frame.getWidth(), frame.getHeight());
}
});
this.frame.setLayout(null);
this.frame.setSize(canvasWidth, canvasHeight);
this.frame.addKeyListener(keyInput);
this.frame.setResizable(true);
this.frame.setLocation(-7, 0);
this.frame.setTitle(title);
this.frame.setVisible(true);
gc = this.frame.getGraphicsConfiguration();
vImg = gc.createCompatibleVolatileImage(this.frame.getWidth(), this.frame.getHeight());
this.graphics = (Graphics2D) vImg.getGraphics();
}
`
and here is the code from the Main class, the GraphicsManager object only contains the set_pixel function which sets the color and draws a rectangle at the given position with a size of 1x1 pixels:
package obsidian.core;
import obsidian.core.Window;
import java.awt.*;
public class Main {
private static void on_destroy() {
System.exit(0);
}
public static void main(String[] args) {
Window window = new Window();
window.init(Window.get_max_size().width, Window.get_max_size().height, "title", new Color(0, 0, 0), destroy -> on_destroy());
GraphicsManager gm = new GraphicsManager(window);
Thread t = new Thread(new Runnable() {
#Override
public void run() {
while(window.is_open()) {
window.render();
gm.draw_pixel(0.5, 0.5, new Color(255, 0, 0));
gm.draw_pixel(0, 0, new Color(255, 255, 255));
System.out.println(window.get_current_fps());
}
}
});
t.start();
}
}
I tried removing the VolatileImage and directly drawing on the frame but the filling of the image makes it glitchy but it got to about 2000 FPS and IntelliJ IDEA couldn't keep up. The only solution I could find to make it run faster is removing the VolatileImage and draw directly to the frame. Didn't work ;-;
Can anyone help me with this? Thank you
Swing "passive" painting
This makes use of a Swing Timer
~170fps
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Timer timer;
private Instant lastTick;
private int fps = 0;
public TestPane() {
}
#Override
public void addNotify() {
super.addNotify();
if (timer != null) {
timer.stop();
}
timer = new Timer(5, new ActionListener() {
private int frameCount = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (lastTick != null) {
Duration duration = Duration.between(lastTick, Instant.now());
if (duration.toMillis() >= 1000) {
lastTick = Instant.now();
fps = frameCount;
frameCount = 0;
} else {
frameCount++;
}
} else {
lastTick = Instant.now();
}
repaint();
}
});
timer.start();
}
#Override
public void removeNotify() {
if (timer != null) {
timer.stop();
}
timer = null;
super.removeNotify();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.fillOval(20, 20, 20, 20);
g2d.fillOval(40, 40, 20, 20);
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(fps);
g2d.drawString(text, 10, fm.getAscent());
g2d.dispose();
}
}
}
"Active" painting via a BufferStrategy
~680fps
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicBoolean;
public class Other {
public static void main(String[] args) {
new Other();
}
public Other() {
Frame frame = new Frame();
MainCanvas canvas = new MainCanvas();
frame.setBackground(Color.BLUE);
frame.add(canvas);
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
public class MainCanvas extends Canvas {
private Thread thread;
private AtomicBoolean running = new AtomicBoolean(false);
private int fps;
public MainCanvas() {
}
#Override
public void addNotify() {
super.addNotify();
createBufferStrategy(3);
start();
}
#Override
public void removeNotify() {
stop();
super.removeNotify();
}
public void start() {
stop();
run();
}
protected void stop() {
if (running.get()) {
running.set(false);
}
thread = null;
}
protected void run() {
stop();
running.set(true);
thread = new Thread(new Runnable() {
private Instant lastTick;
private int frameCount;
#Override
public void run() {
while (running.get()) {
if (lastTick != null) {
Duration duration = Duration.between(lastTick, Instant.now());
if (duration.toMillis() >= 1000) {
lastTick = Instant.now();
fps = frameCount;
frameCount = 0;
} else {
frameCount++;
}
} else {
lastTick = Instant.now();
}
render();
// Comment out these lines and see where it takes you
try {
Thread.sleep(1);
} catch (InterruptedException ex) {
java.util.logging.Logger.getLogger(Other.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
}
}
});
thread.start();
}
protected void render() {
BufferStrategy bs = getBufferStrategy();
while (bs == null) {
bs = getBufferStrategy();
}
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics2D g2d = (Graphics2D) bs.getDrawGraphics();
g2d.setColor(Color.GREEN);
g2d.fillRect(0, 0, getWidth(), getHeight());
// Render to graphics
// ...
g2d.setColor(Color.RED);
g2d.fillOval(20, 20, 20, 20);
g2d.fillOval(40, 40, 20, 20);
FontMetrics fm = g2d.getFontMetrics();
String text = Integer.toString(fps);
g2d.drawString(text, 10, fm.getAscent());
// Dispose the graphics
g2d.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (bs.contentsRestored());
// Display the buffer
bs.show();
// Repeat the rendering if the drawing buffer was lost
} while (bs.contentsLost());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
}
Disclaimer
This is by far a "scientific" example and is intended simply as a demonstration between passive and active rendering workflows
I used the first answer of Zoom box for area around mouse location on screen to create a Zoom Box Window around mouse location that would zoom into images as the mouse moves.
Now, I wanna know how to activate this Zoom Box View when a JCheckBox is checked and desactivate it if it's unchecked.
I modified the classes ZoomBoxWindow and ZoomPane written by #MadProgrammer by adding the lines of code for activating and desactivating the ZoomBoxView, but this doesn't seem to work. Could you please tell me what am I doing wrong?
Here is the code:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.JCheckBox;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
import java.awt.event.KeyEvent;
public class ZoomPane extends JPanel {
protected static final int ZOOM_AREA = 40;
private JComponent parent;
private JWindow popup;
private Boolean zoomBoxActivated = false;
private BufferedImage buffer;
private float zoomLevel = 2f;
public ZoomPane(JComponent parent, Boolean zba) {
this.parent = parent;
this.zoomBoxActivated=zba;
popup = new JWindow();
popup.setLayout(new BorderLayout());
popup.add(this);
popup.pack();
popup.setAlwaysOnTop(true);
MouseAdapter ma = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
Point pos = e.getLocationOnScreen();
updateBuffer(p);
popup.setLocation(pos.x - 20, pos.y + 20);
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
if(zoomBoxActivated){
popup.setVisible(true);
}
else {
popup.setVisible(false);
}
}
#Override
public void mouseExited(MouseEvent e) {
popup.setVisible(false);
}
};
parent.addMouseListener(ma);
parent.addMouseMotionListener(ma);
}
protected void updateBuffer(Point p) {
int width = Math.round(ZOOM_AREA);
int height = Math.round(ZOOM_AREA);
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffer.createGraphics();
AffineTransform at = new AffineTransform();
int xPos = (ZOOM_AREA / 2) - p.x;
int yPos = (ZOOM_AREA / 2) - p.y;
if (xPos > 0) {
xPos = 0;
}
if (yPos > 0) {
yPos = 0;
}
if ((xPos * -1) + ZOOM_AREA > parent.getWidth()) {
xPos = (parent.getWidth() - ZOOM_AREA) * -1;
}
if ((yPos * -1) + ZOOM_AREA > parent.getHeight()) {
yPos = (parent.getHeight()- ZOOM_AREA) * -1;
}
at.translate(xPos, yPos);
g2d.setTransform(at);
parent.paint(g2d);
g2d.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(Math.round(ZOOM_AREA * zoomLevel), Math.round(ZOOM_AREA * zoomLevel));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (buffer != null) {
AffineTransform at = g2d.getTransform();
g2d.setTransform(AffineTransform.getScaleInstance(zoomLevel, zoomLevel));
g2d.drawImage(buffer, 0, 0, this);
g2d.setTransform(at);
}
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
public class TestPane extends JPanel {
private BufferedImage img;
public TestPane() {
try {
img = ImageIO.read(new File("satellite-image-of-spain.jpg"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
g2d.dispose();
}
}
}
public class ZoomBoxWindow {
ZoomPane zoomPane;
public static void main(String[] args) {
new ZoomBoxWindow();
}
public ZoomBoxWindow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane pane = new TestPane();
zoomPane = new ZoomPane(pane, false);
JPanel buttonPanel = new JPanel(new BorderLayout());
JCheckBox zoomBoxChkBox = new JCheckBox("Zoom Box");
zoomBoxChkBox.setMnemonic(KeyEvent.VK_Z);
zoomBoxChkBox.setSelected(false);
zoomBoxChkBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
// TODO Auto-generated method stub
if ( e.getStateChange() == ItemEvent.SELECTED) {
zoomPane = new ZoomPane(pane,true);
}
else {
zoomPane = new ZoomPane(pane,false);
}
}
});
buttonPanel.add(zoomBoxChkBox);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane,BorderLayout.CENTER);
frame.add(buttonPanel, BorderLayout.PAGE_START);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Thank you for your answer.
So,
zoomBoxChkBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
// TODO Auto-generated method stub
if (e.getStateChange() == ItemEvent.SELECTED) {
zoomPane = new ZoomPane(pane, true);
} else {
zoomPane = new ZoomPane(pane, false);
}
}
});
Isn't really doing anything, you're just create a new instance of ZoomPane. Apart from adding a bunch of MouseListeners to the parent component, which could cause you no end of issues.
Instead, I'd add a new method to ZoomPane
public class ZoomPane extends JPanel {
private boolean isAutoDisplayEnabled = false;
//...
public void setShowZoomPopup(boolean show) {
popup.setVisible(show);
isAutoDisplayEnabled = show;
}
This now allows you to control the visibility state of the popup externally.
Now, the isAutoDisplayEnabled flag is simply used to determine if the popup should be displayed when the mouseEntered event is triggered, for example...
MouseAdapter ma = new MouseAdapter() {
//...
#Override
public void mouseEntered(MouseEvent e) {
if (isAutoDisplayEnabled) {
popup.setVisible(true);
}
}
Now your ItemListener can control the state of the popup
zoomBoxChkBox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
// TODO Auto-generated method stub
if (e.getStateChange() == ItemEvent.SELECTED) {
zoomPane.setShowZoomPopup(true);
} else {
zoomPane.setShowZoomPopup(false);
}
}
});
You could also add a check to see if the mouse is currently within the bounds of the image pane, so you don't show it needlessly, but I'll leave that to you to try and figure out ;)
the popup is flickering as it is being moved
This is, because every time the popup is displayed, it triggers a mouseExit event, which triggers the popup to be hidden, which then triggers a mouseEnter event and so on and so forth...
This is a slightly different take on the same idea, but, instead of a separate window, this paints the zoom as part of the image pane itself.
This does mean that, if the zoom falls beyond the bounds of the panel, it will be truncated, but I've spent some time so that you can resize the panel larger than the image and the zoom effect will still work
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ZoomBoxWindow {
public static void main(String[] args) {
new ZoomBoxWindow();
}
public ZoomBoxWindow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane pane = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage img;
private Point zoomPoint;
private boolean zoomEnabled = true;
private int zoomArea = 80;
private float zoom = 2.0f;
public TestPane() {
try {
img = ImageIO.read(new File("/Volumes/Big Fat Extension/Dropbox/MegaTokyo/_cg_1009___Afraid___by_Serena_Clearwater.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
zoomPoint = e.getPoint();
repaint();
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
zoomPoint = null;
repaint();
}
});
}
public float getZoom() {
return zoom;
}
public void setZoom(float zoom) {
this.zoom = zoom;
repaint();
}
public int getZoomArea() {
return zoomArea;
}
public void setZoomArea(int zoomArea) {
this.zoomArea = zoomArea;
repaint();
}
public boolean isZoomEnabled() {
return zoomEnabled;
}
public void setZoomEnabled(boolean zoomEnabled) {
this.zoomEnabled = zoomEnabled;
repaint();
}
#Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
protected Point getOffset() {
if (img == null) {
return new Point(0, 0);
}
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
return new Point(x, y);
}
protected Rectangle getImageBounds() {
Rectangle bounds = new Rectangle(0, 0, 0, 0);
if (img != null) {
bounds.setLocation(getOffset());
bounds.setSize(img.getWidth(), img.getHeight());
}
return bounds;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
Point offset = getOffset();
g2d.drawImage(img, offset.x, offset.y, this);
if (zoomPoint != null) {
BufferedImage zoomBuffer = updateBuffer(zoomPoint);
if (zoomBuffer != null) {
Rectangle bounds = getZoomBounds();
g2d.drawImage(zoomBuffer, bounds.x, bounds.y, this);
g2d.setColor(Color.RED);
g2d.draw(bounds);
}
}
g2d.dispose();
}
}
protected Rectangle getZoomBounds() {
Rectangle bounds = null;
if (zoomPoint != null && img != null) {
int zoomArea = getZoomArea();
int xPos = zoomPoint.x - (zoomArea / 2);
int yPos = zoomPoint.y - (zoomArea / 2);
Rectangle zoomBounds = new Rectangle(xPos, yPos, zoomArea, zoomArea);
Rectangle imageBounds = getImageBounds();
bounds = imageBounds.intersection(zoomBounds);
System.out.println(bounds);
}
return bounds;
}
protected BufferedImage updateBuffer(Point p) {
if (zoomPoint == null) {
return null;
}
Rectangle bounds = getZoomBounds();
Point offset = getOffset();
bounds.translate(-offset.x, -offset.y);
if (bounds.x < 0 || bounds.y < 0 || bounds.width <= 0 || bounds.height <= 0) {
return null;
}
BufferedImage zoomBuffer = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = zoomBuffer.createGraphics();
BufferedImage sample = img.getSubimage(bounds.x, bounds.y, bounds.width, bounds.height);
double zoom = getZoom();
Image scaled = sample.getScaledInstance((int) (bounds.width * zoom), (int) (bounds.height * zoom), Image.SCALE_SMOOTH);
g2d.drawImage(scaled, 0, 0, this);
g2d.dispose();
return zoomBuffer;
}
}
}
Assigning a new ZoomPane to the zoomPane field will do nothing on the actual displayed ZoomPane.
What you want is to tell the existing ZoomPane to switch its zoom policy.
You could do that by adding the following method to ZoomPane :
void switchZoomBoxActivated() {
zoomBoxActivated = !zoomBoxActivated;
}
Then in the ItemListener, just call it this way :
#Override
public void itemStateChanged(final ItemEvent e) {
zoomPane.switchZoomBoxActivated();
}
I'd like to do JButton with nice transition effect. I write a class which extend by JButton and add to it custom MouseAdapter. It almost works, but if opacity should have 0 my one BufferedImage don't vanish.
Here my all source code:
public class ImageHoverButton extends JButton {
public class MouseListener extends MouseAdapter
{
public void mouseExited(MouseEvent me)
{
new Thread(new Runnable()
{
public void run()
{
for (float i = 1f; i >= 0f; i -= .03f)
{
setOpacity(i);
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
}
}
}).start();
}
public void mouseEntered(MouseEvent me)
{
new Thread(new Runnable()
{
public void run()
{
for (float i = 0f; i <= 1f; i += .03f)
{
setOpacity(i);
try
{
Thread.sleep(10);
}
catch (Exception e)
{
}
}
}
}).start();
}
public void mousePressed(MouseEvent me)
{
new Thread(new Runnable()
{
public void run()
{
for (float i = 1f; i >= 0.6f; i -= .1f)
{
setOpacity(i);
try
{
Thread.sleep(1);
}
catch (Exception e)
{
}
}
}
}).start();
}
}
private static final long serialVersionUID = 1L;
private BufferedImage imgBottom;
private BufferedImage imgHover;
private BufferedImage imgHoverRGB;
// filter to imgInActive
float[] scales = { 1f, 1f, 1f, 0f};
float[] offsets = new float[4];
RescaleOp rop = new RescaleOp(scales, offsets, null);
/**
* Constructor for image path
* #param img
* #param x
* #param y
*/
public ImageHoverButton(String imgBottomPath, String imgHoverPath, int x, int y) {
try {
this.imgBottom = ImageIO.read(new File(imgBottomPath));
this.imgHover = ImageIO.read(new File(imgHoverPath));
imgHoverRGB = new BufferedImage(imgHover.getWidth(null),
imgHover.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics g = imgHoverRGB.getGraphics();
g.drawImage(imgHover, 0, 0, null);
} catch (IOException e) {
}
this.setBounds(x, y, imgBottom.getWidth() + 40 , imgBottom.getHeight() + 50);
addMouseListener(new MouseListener());
setOpacity(0f);
setOpaque(false);
setBorderPainted(false);
setRolloverEnabled(false);
setCursor(new Cursor(Cursor.HAND_CURSOR));
setLayout(null);
}
public void setOpacity(float opacity) {
scales[3] = opacity;
rop = new RescaleOp(scales, offsets, null);
repaint();
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(imgBottom, 50, 50, null);
g2d.drawImage(imgHoverRGB, rop, 0, 0);
}
}
Have any idea how to improve this?
I'm not so familiar with RescaleOp, and can't remember having used this before. But it seems like the results of applying it in this case are somewhat unexpected.
As an alternative, you might consider an AlphaComposite. The minimum modification that is necessary to achieve the desired effect would then be to change the line
g2d.drawImage(imgHoverRGB, rop, 0, 0);
to
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, scales[3]));
g2d.drawImage(imgHoverRGB, 0, 0, null);
However, there are several other issues with the code:
don't override paint. Instead, override paintComponent
don't call setBounds on a component (particlularly not in a constructor). The placement should be done by a layout manager
don't swallow Exceptions silently
don't load the images in the constructor of the button
implement getPreferredSize properly
don't spawn hundreds of threads due to mouse movement. (When you quickly move the mouse in and out, you'll have several threads running - some of them increasing the opacity, and some of them decreasing the opacity)
I created an example showing one possible approach: It contains an OpacityAnimator that allows a transition between two opacities, with a predefined delay in milliseconds. This animator is used to increase the opacity of the foreground image when the button is hovered with the mouse, and to decrease it when the mouse leaves the button.
(Note that this could be generalized further, and there are many possible "configuration settings" (like the transition delay) that could be exposed, but this is just intended as an example)
import java.awt.AlphaComposite;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class HoverButtonTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
try
{
createAndShowGUI();
}
catch (IOException e)
{
e.printStackTrace();
}
}
});
}
private static void createAndShowGUI() throws IOException
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
BufferedImage backgroundImage = loadImage("background.png");
BufferedImage foregroundImage = loadImage("foreground.png");
f.getContentPane().setLayout(new FlowLayout());
f.getContentPane().add(
new ImageHoverButton(backgroundImage, foregroundImage));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static BufferedImage loadImage(String path) throws IOException
{
return convertToARGB(ImageIO.read(new File(path)));
}
public static BufferedImage convertToARGB(BufferedImage image)
{
BufferedImage newImage = new BufferedImage(image.getWidth(),
image.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return newImage;
}
}
class ImageHoverButton extends JButton
{
private class MouseHoverListener extends MouseAdapter
{
#Override
public void mouseExited(MouseEvent me)
{
opacityAnimator.changeOpacity(0.0f, 250);
}
#Override
public void mouseEntered(MouseEvent me)
{
opacityAnimator.changeOpacity(1.0f, 1000);
}
#Override
public void mousePressed(MouseEvent me)
{
opacityAnimator.changeOpacity(0.5f, 50);
}
}
private class OpacityAnimator
{
private final int DELAY_MS = 10;
private final Timer timer;
private float targetOpacity;
private float currentOpacity;
private float opacityStep;
OpacityAnimator()
{
timer = new Timer(DELAY_MS, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if (currentOpacity > targetOpacity)
{
currentOpacity += opacityStep;
currentOpacity = Math.max(
currentOpacity, targetOpacity);
}
else if (currentOpacity < targetOpacity)
{
currentOpacity += opacityStep;
currentOpacity = Math.min(
currentOpacity, targetOpacity);
}
if (currentOpacity == targetOpacity)
{
timer.stop();
}
setOpacity(currentOpacity);
}
});
}
void changeOpacity(float targetOpacity, int durationMs)
{
timer.stop();
this.targetOpacity = targetOpacity;
float delta = targetOpacity - currentOpacity;
if (durationMs > 0)
{
opacityStep = (delta / durationMs) * DELAY_MS;
}
else
{
opacityStep = delta;
}
timer.start();
}
}
private final OpacityAnimator opacityAnimator;
private final BufferedImage backgroundImage;
private final BufferedImage foregroundImage;
private float opacity = 0.0f;
public ImageHoverButton(BufferedImage backgroundImage,
BufferedImage foregroundImage)
{
this.backgroundImage = backgroundImage;
this.foregroundImage = foregroundImage;
this.opacityAnimator = new OpacityAnimator();
addMouseListener(new MouseHoverListener());
setOpaque(false);
setBorderPainted(false);
setRolloverEnabled(false);
setCursor(new Cursor(Cursor.HAND_CURSOR));
}
#Override
public Dimension getPreferredSize()
{
if (super.isPreferredSizeSet())
{
return super.getPreferredSize();
}
int w = Math
.max(backgroundImage.getWidth(), foregroundImage.getWidth());
int h = Math.max(backgroundImage.getHeight(),
foregroundImage.getHeight());
return new Dimension(w, h);
}
public void setOpacity(float opacity)
{
this.opacity = opacity;
repaint();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.drawImage(backgroundImage, 0, 0, null);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
opacity));
g.drawImage(foregroundImage, 0, 0, null);
}
}
Don't access Swing components from other threads. Use a swing Timer instead.
See How to use swing timers
I have a jLabel with an Icon that I should print. However, I can't get the jLabel's icon to full size.
Here's some of the code that I think that it is affecting the print size.
public static void printComponentToFile(Component comp, boolean fill) throws PrinterException {
Paper paper = new Paper();
paper.setSize(8.3 * 72, 11.7 * 72); //here
paper.setImageableArea(18, 18, 100, 300); //and here
PageFormat pf = new PageFormat();
pf.setPaper(paper);
pf.setOrientation(PageFormat.LANDSCAPE);
BufferedImage img = new BufferedImage(
(int) Math.round(pf.getWidth()),
(int) Math.round(pf.getHeight()),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fill(new Rectangle(0, 0, img.getWidth(), img.getHeight()));
ComponentPrinter cp = new ComponentPrinter(comp, fill);
try {
cp.print(g2d, pf, 0);
} finally {
g2d.dispose();
}
try {
ImageIO.write(img, "png", new File("Page-" + (fill ? "Filled" : "") + ".png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static class ComponentPrinter implements Printable {
private Component comp;
private boolean fill;
public ComponentPrinter(Component comp, boolean fill) {
this.comp = comp;
this.fill = fill;
}
#Override
public int print(Graphics g, PageFormat format, int page_index) throws PrinterException {
if (page_index > 0) {
return Printable.NO_SUCH_PAGE;
}
Graphics2D g2 = (Graphics2D) g;
g2.translate(format.getImageableX(), format.getImageableY());
double width = (int) Math.floor(format.getImageableWidth()); // here too
double height = (int) Math.floor(format.getImageableHeight()); // and here
if (!fill) {
width = Math.min(width, comp.getPreferredSize().width); // here
height = Math.min(height, comp.getPreferredSize().height); // here
}
comp.setBounds(0, 0, (int) Math.floor(width), (int) Math.floor(height));
if (comp.getParent() == null) {
comp.addNotify();
}
comp.validate();
comp.doLayout();
comp.printAll(g2);
if (comp.getParent() != null) {
comp.removeNotify();
}
return Printable.PAGE_EXISTS;
}
}
So what should I change with those? Also, how can I put a radiobutton on the printing process? It's because I want to print the radiobutton altogether with the label.
This is how I print a label using a button:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
try {
printComponent(jLabel2, false);
} catch (PrinterException ex) {
Logger.getLogger(MappingScreen.class.getName()).log(Level.SEVERE, null, ex);
}
}
Can I make it like this?:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
try {
printComponent(jLabel2, false);
printComponent(jRadioBtn1, false); //change
} catch (PrinterException ex) {
Logger.getLogger(M.class.getName()).log(Level.SEVERE, null, ex);
}
}
UPDATE:
I think I have to add something here to print another component:
public static void main(String args[]) {
try {
JLabel label = new JLabel(
"This is a test",
new ImageIcon("/adv/mapp.jpg"),
JLabel.CENTER);
printComponentToFile(label, true);
printComponentToFile(label, false);
} catch (PrinterException exp) {
exp.printStackTrace();
}
Please help. Thanks
So, based on the concept presented in Printing a JFrame and its components, I've been able to produce these two examples...
Which used the following JPanel as the base component...
public static class PrintForm extends JPanel {
public PrintForm() {
setLayout(new GridBagLayout());
JLabel label = new JLabel("This is a label");
label.setVerticalTextPosition(JLabel.BOTTOM);
label.setHorizontalTextPosition(JLabel.CENTER);
try {
label.setIcon(new ImageIcon(ImageIO.read(new File("C:\\hold\\thumbnails\\_cg_1009___Afraid___by_Serena_Clearwater.png"))));
} catch (IOException ex) {
ex.printStackTrace();
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
add(label, gbc);
JRadioButton rb = new JRadioButton("Open to suggestions");
rb.setSelected(true);
gbc.gridy++;
gbc.weightx = 1;
gbc.fill = GridBagConstraints.NONE;
add(rb, gbc);
}
}
And based on the concept presented in Fit/Scale JComponent to page being printed I was able to take an image of 7680x4800 and scale it down to print within an area of 842x598.
Now note. JLabel DOES NOT support scaling. If your image does not fit within the available space, you are going to have to scale it yourself some how. The following solution scales the entire component, having said that, with a little bit of clever re-arranging, it would be possible to make the TestPane scale it's image instead and use the above example instead...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ScalablePrintingTest {
public static void main(String[] args) {
new ScalablePrintingTest();
}
public ScalablePrintingTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
final TestPane imagePane = new TestPane();
JButton print = new JButton("Print");
print.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
//printComponent(imagePane);
printComponentToFile(imagePane);
} catch (PrinterException ex) {
ex.printStackTrace();
}
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(imagePane);
frame.add(print, BorderLayout.SOUTH);
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage bg;
public TestPane() {
try {
bg = ImageIO.read(new File("/path/to/a/image"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return bg == null ? new Dimension(200, 200) : new Dimension(bg.getWidth(), bg.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (bg != null) {
int x = (getWidth() - bg.getWidth()) / 2;
int y = (getHeight() - bg.getHeight()) / 2;
g2d.drawImage(bg, x, y, this);
}
g2d.dispose();
}
}
public void printComponent(Component comp) {
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setJobName(" Print Component ");
pj.setPrintable(new ComponentPrintable(comp));
if (!pj.printDialog()) {
return;
}
try {
pj.print();
} catch (PrinterException ex) {
System.out.println(ex);
}
}
public static void printComponentToFile(Component comp) throws PrinterException {
Paper paper = new Paper();
paper.setSize(8.3 * 72, 11.7 * 72);
paper.setImageableArea(18, 18, 559, 783);
PageFormat pf = new PageFormat();
pf.setPaper(paper);
pf.setOrientation(PageFormat.LANDSCAPE);
BufferedImage img = new BufferedImage(
(int) Math.round(pf.getWidth()),
(int) Math.round(pf.getHeight()),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fill(new Rectangle(0, 0, img.getWidth(), img.getHeight()));
ComponentPrintable cp = new ComponentPrintable(comp);
try {
cp.print(g2d, pf, 0);
} finally {
g2d.dispose();
}
try {
ImageIO.write(img, "png", new File("Page-Scaled.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static class ComponentPrintable implements Printable {
private Component comp;
private ComponentPrintable(Component comp) {
this.comp = comp;
}
#Override
public int print(Graphics g, PageFormat pf, int pageNumber)
throws PrinterException {
// TODO Auto-generated method stub
if (pageNumber > 0) {
return Printable.NO_SUCH_PAGE;
}
// Get the preferred size ofthe component...
Dimension compSize = comp.getPreferredSize();
// Make sure we size to the preferred size
comp.setSize(compSize);
// Get the the print size
Dimension printSize = new Dimension();
printSize.setSize(pf.getImageableWidth(), pf.getImageableHeight());
// Calculate the scale factor
double scaleFactor = getScaleFactorToFit(compSize, printSize);
// Don't want to scale up, only want to scale down
if (scaleFactor > 1d) {
scaleFactor = 1d;
}
// Calcaulte the scaled size...
double scaleWidth = compSize.width * scaleFactor;
double scaleHeight = compSize.height * scaleFactor;
// Create a clone of the graphics context. This allows us to manipulate
// the graphics context without begin worried about what effects
// it might have once we're finished
Graphics2D g2 = (Graphics2D) g.create();
// Calculate the x/y position of the component, this will center
// the result on the page if it can
double x = ((pf.getImageableWidth() - scaleWidth) / 2d) + pf.getImageableX();
double y = ((pf.getImageableHeight() - scaleHeight) / 2d) + pf.getImageableY();
// Create a new AffineTransformation
AffineTransform at = new AffineTransform();
// Translate the offset to out "center" of page
at.translate(x, y);
// Set the scaling
at.scale(scaleFactor, scaleFactor);
// Apply the transformation
g2.transform(at);
// Print the component
comp.printAll(g2);
// Dispose of the graphics context, freeing up memory and discarding
// our changes
g2.dispose();
comp.revalidate();
return Printable.PAGE_EXISTS;
}
}
public static double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = 1;
if (iMasterSize > iTargetSize) {
dScale = (double) iTargetSize / (double) iMasterSize;
} else {
dScale = (double) iTargetSize / (double) iMasterSize;
}
return dScale;
}
}
I was going to update my previous answer, but it's been accepted...
This example basically combines the solutions presented in Fit/Scale JComponent to page being printed and Printing a JFrame and its components and demonstrates the difference between scaling the component when it's printed as apposed to have the component capable of scaling itself...
It creates a interface chain of the Printables, which I hope will help clear up any confusion...well, less then there was...
So starting with...
The panel on the left is static, that is, the image is not scaled by the component. The panel on the right is dynamic, that is, the component will scale the image as the size of the component is changed.
The static panel when printed using the scaled approach. You will note that both the label and radio button have been scaled as well
Compared to the dynamic panel when using the filled approach. You will note that the label and radio button are not scaled, but remain at there normal size, but the background image is scaled instead...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PrintComponentTest {
public static void main(String[] args) {
new PrintComponentTest();
}
public PrintComponentTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private StaticImagePane staticImagePane;
private DynamicImagePane dynamicImagePane;
public TestPane() {
JPanel content = new JPanel(new GridLayout());
content.add(new JScrollPane(preparePane(staticImagePane = new StaticImagePane())));
content.add(preparePane(dynamicImagePane = new DynamicImagePane()));
setLayout(new BorderLayout());
add(content);
JPanel options = new JPanel();
JButton normal = new JButton("Print");
normal.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
printComponentToFile(new FillableCompentPrintable(staticImagePane, false), new File("Static-Normal.png"));
printComponentToFile(new FillableCompentPrintable(staticImagePane, true), new File("Static-Filled.png"));
printComponentToFile(new ScalableCompentPrintable(staticImagePane), new File("Static-Scaled.png"));
printComponentToFile(new FillableCompentPrintable(dynamicImagePane, false), new File("Dynamic-Normal.png"));
printComponentToFile(new FillableCompentPrintable(dynamicImagePane, true), new File("Dynamic-Filled.png"));
printComponentToFile(new ScalableCompentPrintable(dynamicImagePane), new File("Dynamic-Scaled.png"));
staticImagePane.invalidate();
dynamicImagePane.invalidate();
staticImagePane.revalidate();
dynamicImagePane.revalidate();
invalidate();
revalidate();
} catch (PrinterException ex) {
ex.printStackTrace();
}
}
});
options.add(normal);
add(options, BorderLayout.SOUTH);
}
protected JPanel preparePane(JPanel panel) {
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
JLabel label = new JLabel("This is some text on a label");
label.setForeground(Color.WHITE);
panel.add(label, gbc);
JRadioButton btn = new JRadioButton("Do you agree", true);
btn.setOpaque(false);
btn.setForeground(Color.WHITE);
panel.add(btn, gbc);
return panel;
}
}
public void printComponent(ComponentPrintable printable) {
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setJobName(" Print Component ");
pj.setPrintable(printable);
if (!pj.printDialog()) {
return;
}
try {
pj.print();
} catch (PrinterException ex) {
System.out.println(ex);
}
}
public static void printComponentToFile(ComponentPrintable printable, File file) throws PrinterException {
Paper paper = new Paper();
paper.setSize(8.3 * 72, 11.7 * 72);
paper.setImageableArea(18, 18, 559, 783);
PageFormat pf = new PageFormat();
pf.setPaper(paper);
pf.setOrientation(PageFormat.LANDSCAPE);
BufferedImage img = new BufferedImage(
(int) Math.round(pf.getWidth()),
(int) Math.round(pf.getHeight()),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = img.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fill(new Rectangle(0, 0, img.getWidth(), img.getHeight()));
try {
printable.print(g2d, pf, 0);
} finally {
g2d.dispose();
}
try {
ImageIO.write(img, "png", file);
} catch (IOException ex) {
ex.printStackTrace();
}
}
public static interface ComponentPrintable extends Printable {
public Component getComponent();
}
public abstract static class AbstractComponentPrintable implements ComponentPrintable {
private final Component component;
public AbstractComponentPrintable(Component component) {
this.component = component;
}
#Override
public Component getComponent() {
return component;
}
protected void beforePrinting(Component comp) {
if (comp.getParent() == null) {
comp.addNotify();
}
comp.invalidate();
comp.revalidate();
comp.doLayout();
}
protected void afterPrinting(Component comp) {
if (comp.getParent() != null) {
comp.removeNotify();
} else {
comp.invalidate();
comp.revalidate();
}
}
#Override
public int print(Graphics g, PageFormat format, int page_index) throws PrinterException {
if (page_index > 0) {
return Printable.NO_SUCH_PAGE;
}
Graphics2D g2 = (Graphics2D) g;
printComponent(g2, format, getComponent());
return Printable.PAGE_EXISTS;
}
protected abstract void printComponent(Graphics2D g, PageFormat pf, Component comp);
}
public static class FillableCompentPrintable extends AbstractComponentPrintable {
private boolean fill;
public FillableCompentPrintable(Component component, boolean fill) {
super(component);
this.fill = fill;
}
public boolean isFill() {
return fill;
}
#Override
protected void printComponent(Graphics2D g2, PageFormat pf, Component comp) {
g2.translate(pf.getImageableX(), pf.getImageableY());
double width = (int) Math.floor(pf.getImageableWidth());
double height = (int) Math.floor(pf.getImageableHeight());
if (!isFill()) {
width = Math.min(width, comp.getPreferredSize().width);
height = Math.min(height, comp.getPreferredSize().height);
}
comp.setBounds(0, 0, (int) Math.floor(width), (int) Math.floor(height));
beforePrinting(comp);
comp.printAll(g2);
afterPrinting(comp);
// Debug purposes only...
g2.translate(-pf.getImageableX(), -pf.getImageableY());
g2.setColor(Color.RED);
g2.drawRect(0, 0, (int) pf.getWidth() - 1, (int) pf.getHeight() - 1);
}
}
public static class ScalableCompentPrintable extends AbstractComponentPrintable {
public ScalableCompentPrintable(Component component) {
super(component);
}
#Override
protected void printComponent(Graphics2D g2d, PageFormat pf, Component comp) {
// Get the preferred size ofthe component...
Dimension compSize = comp.getPreferredSize();
// Make sure we size to the preferred size
comp.setSize(compSize);
// Get the the print size
Dimension printSize = new Dimension();
printSize.setSize(pf.getImageableWidth(), pf.getImageableHeight());
// Calculate the scale factor
double scaleFactor = getScaleFactorToFit(compSize, printSize);
// Don't want to scale up, only want to scale down
if (scaleFactor > 1d) {
scaleFactor = 1d;
}
// Calcaulte the scaled size...
double scaleWidth = compSize.width * scaleFactor;
double scaleHeight = compSize.height * scaleFactor;
// Create a clone of the graphics context. This allows us to manipulate
// the graphics context without begin worried about what effects
// it might have once we're finished
Graphics2D g2 = (Graphics2D) g2d.create();
// Calculate the x/y position of the component, this will center
// the result on the page if it can
double x = ((pf.getImageableWidth() - scaleWidth) / 2d) + pf.getImageableX();
double y = ((pf.getImageableHeight() - scaleHeight) / 2d) + pf.getImageableY();
// Create a new AffineTransformation
AffineTransform at = new AffineTransform();
// Translate the offset to out "center" of page
at.translate(x, y);
// Set the scaling
at.scale(scaleFactor, scaleFactor);
// Apply the transformation
g2.transform(at);
// Print the component
beforePrinting(comp);
comp.printAll(g2);
afterPrinting(comp);
// Dispose of the graphics context, freeing up memory and discarding
// our changes
g2.dispose();
// Debug purposes only...
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, (int) pf.getWidth() - 1, (int) pf.getHeight() - 1);
}
public static double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public static double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = 1;
if (iMasterSize > iTargetSize) {
dScale = (double) iTargetSize / (double) iMasterSize;
} else {
dScale = (double) iTargetSize / (double) iMasterSize;
}
return dScale;
}
}
public class StaticImagePane extends JPanel {
private BufferedImage image;
public StaticImagePane() {
try {
setImage(ImageIO.read(new File("path\to\a\image")));
} catch (IOException ex) {
ex.printStackTrace();
}
}
public BufferedImage getImage() {
return image;
}
public void setImage(BufferedImage image) {
this.image = image;
repaint();
}
#Override
public Dimension getPreferredSize() {
BufferedImage bg = getImage();
return bg == null ? new Dimension(200, 200) : new Dimension(bg.getWidth(), bg.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
paintBackground(g2d);
g2d.dispose();
}
protected void paintBackground(Graphics2D g2d) {
paintBackground(g2d, getImage());
}
protected void paintBackground(Graphics2D g2d, Image bg) {
if (bg != null) {
int x = (getWidth() - bg.getWidth(this)) / 2;
int y = (getHeight() - bg.getHeight(this)) / 2;
g2d.drawImage(bg, x, y, this);
}
}
}
public class DynamicImagePane extends StaticImagePane {
private Image scaled;
public DynamicImagePane() {
try {
setImage(ImageIO.read(new File("path\to\a\image")));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public void invalidate() {
super.invalidate();
if (getImage() != null) {
int width = getWidth();
int height = getHeight();
if (width > 0 && height > 0) {
// Keep the aspect ratio
if (width > height) {
width = -1;
} else if (height > width) {
height = -1;
}
scaled = getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
}
}
}
public Image getScaledImage() {
return scaled;
}
#Override
protected void paintBackground(Graphics2D g2d) {
paintBackground(g2d, getScaledImage());
}
}
}
Note, this is just an example, there is plenty of work that needs to be done.
One issue is with the scaling, see The Perils of Image.getScaledInstance() for details and Quality of Image after resize very low -- Java for a better approach...
To make it work, I changed it to something like this:
//OLD:
public void printComponent(Component comp) {
//NEW:
public void printComponent(JComponent comp, boolean fill) throws PrinterException {
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setJobName(" Print Component ");
pj.setPrintable(new ComponentPrintable(comp));
if (!pj.printDialog()) {
return;
}
try {
pj.print();
} catch (PrinterException ex) {
System.out.println(ex);
}
}
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 9 years ago.
Questions concerning problems with code you've written must describe the specific problem — and include valid code to reproduce it — in the question itself. See SSCCE.org for guidance.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Improve this question
Is there any way to create a dynamic Zoom Box in Java which will include e. g. 20x20pix area around the cursor (but even when cursor will move beyond the Frame of app) and which will be shown for example in a small JPanel?
I'm asking in context of a Color Chooser program. The last functionality need to be implemented is just that Zoom Box.
I'm sure there are a number of different ways that this could be achieved.
This basically uses a separate component, which acts as the "zoom box". You supply it a component that you want to "zoom" on. It adds a mouse listener so it can monitor mouse motion events and enter and exit events.
These are used to determine when the "popup" window should be shown, where the popup window should be shown and the area that should be "painted".
This uses the "component to be zoomed" paint method to paint a region to of it to a backing buffer, which is then scaled and painted to the "zoom box"...simple
I've not played around with the zoom factor, so there may still be some quirks, but you should get the basic idea...
While I've presented a image to act as the background, this should work on any component
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ZoomBoxWindow {
public static void main(String[] args) {
new ZoomBoxWindow();
}
public ZoomBoxWindow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane pane = new TestPane();
ZoomPane zoomPane = new ZoomPane(pane);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(pane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class ZoomPane extends JPanel {
protected static final int ZOOM_AREA = 40;
private JComponent parent;
private JWindow popup;
private BufferedImage buffer;
private float zoomLevel = 2f;
public ZoomPane(JComponent parent) {
this.parent = parent;
popup = new JWindow();
popup.setLayout(new BorderLayout());
popup.add(this);
popup.pack();
MouseAdapter ma = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
Point p = e.getPoint();
Point pos = e.getLocationOnScreen();
updateBuffer(p);
popup.setLocation(pos.x + 16, pos.y + 16);
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
popup.setVisible(true);
}
#Override
public void mouseExited(MouseEvent e) {
popup.setVisible(false);
}
};
parent.addMouseListener(ma);
parent.addMouseMotionListener(ma);
}
protected void updateBuffer(Point p) {
int width = Math.round(ZOOM_AREA);
int height = Math.round(ZOOM_AREA);
buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = buffer.createGraphics();
AffineTransform at = new AffineTransform();
int xPos = (ZOOM_AREA / 2) - p.x;
int yPos = (ZOOM_AREA / 2) - p.y;
if (xPos > 0) {
xPos = 0;
}
if (yPos > 0) {
yPos = 0;
}
if ((xPos * -1) + ZOOM_AREA > parent.getWidth()) {
xPos = (parent.getWidth() - ZOOM_AREA) * -1;
}
if ((yPos * -1) + ZOOM_AREA > parent.getHeight()) {
yPos = (parent.getHeight()- ZOOM_AREA) * -1;
}
at.translate(xPos, yPos);
g2d.setTransform(at);
parent.paint(g2d);
g2d.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(Math.round(ZOOM_AREA * zoomLevel), Math.round(ZOOM_AREA * zoomLevel));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (buffer != null) {
AffineTransform at = g2d.getTransform();
g2d.setTransform(AffineTransform.getScaleInstance(zoomLevel, zoomLevel));
g2d.drawImage(buffer, 0, 0, this);
g2d.setTransform(at);
}
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
public class TestPane extends JPanel {
private BufferedImage img;
public TestPane() {
try {
img = ImageIO.read(new File("/path/to/your/image"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
g2d.dispose();
}
}
}
}
Updated with "screen" version
This version will allow you to display a "zoom window" any where on the screen.
This has a minor issue in the fact that you need to hide the zoom window before you capture the screen, then re-show it.
I might be tempted to change the process so that when the updateBuffer method detected that the mouse position hadn't changed, it updated the buffer and showed the zoom window. When the mouse position changes, it would hide the window again...but that's me ;)
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.Action;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import static zoomboxwindow.ZoomBoxWindow.ZoomPane.ZOOM_AREA;
public class GlobalZoomBox {
public static void main(String[] args) {
new GlobalZoomBox();
}
public GlobalZoomBox() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Zoomer zoomer = new Zoomer();
zoomer.setZoomWinodwVisible(true);
}
});
}
public class Zoomer extends JPanel {
protected static final int ZOOM_AREA = 40;
private JWindow popup;
private BufferedImage buffer;
private Robot bot;
private float zoomLevel = 2f;
private Point lastPoint;
private final Timer timer;
public Zoomer() {
popup = new JWindow();
popup.setLayout(new BorderLayout());
popup.add(this);
popup.pack();
try {
bot = new Robot();
} catch (AWTException ex) {
ex.printStackTrace();
}
timer = new Timer(125, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateBuffer();
}
});
timer.setCoalesce(true);
timer.setInitialDelay(0);
}
public void setZoomWinodwVisible(boolean value) {
if (value && !popup.isVisible()) {
timer.start();
popup.setVisible(true);
} else {
timer.stop();
popup.setVisible(false);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(Math.round(ZOOM_AREA * zoomLevel), Math.round(ZOOM_AREA * zoomLevel));
}
protected void updateBuffer() {
if (bot != null) {
PointerInfo info = MouseInfo.getPointerInfo();
Point p = info.getLocation();
if (lastPoint == null || !lastPoint.equals(p)) {
int x = p.x - (ZOOM_AREA / 2);
int y = p.y - (ZOOM_AREA / 2);
popup.setLocation(p.x + 16, p.y + 16);
popup.setVisible(false);
buffer = bot.createScreenCapture(new Rectangle(x, y, ZOOM_AREA, ZOOM_AREA));
popup.setVisible(true);
lastPoint = p;
repaint();
}
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (buffer != null) {
AffineTransform at = g2d.getTransform();
g2d.setTransform(AffineTransform.getScaleInstance(zoomLevel, zoomLevel));
g2d.drawImage(buffer, 0, 0, this);
g2d.setTransform(at);
}
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
}
Updated with "tooltip" style popup
The main problems with the second example is the fact that you need to hide the popup in order to grab a screen shoot. This is done to prevent the popup from begin captured as well. This makes the popup "flash" every time the mouse is moved.
You "could" get around this ensuring the popup is positioned out side the range of the capture, but as you increase the capture area, the popup will move further away from the cursor.
This would, of course, be a great solution for fixed position display (ie, you had a panel fixed on a JFrame instead of a floating box)
This is an additional update that uses a second timer to display the zoom box after the user has stopped moving the mouse.
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.Action;
import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import static zoomboxwindow.ZoomBoxWindow.ZoomPane.ZOOM_AREA;
public class GlobalZoomBox {
public static void main(String[] args) {
new GlobalZoomBox();
}
public GlobalZoomBox() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
Zoomer zoomer = new Zoomer();
zoomer.setZoomWinodwVisible(true);
}
});
}
public class Zoomer extends JPanel {
protected static final int ZOOM_AREA = 80;
private JWindow popup;
private BufferedImage buffer;
private Robot bot;
private float zoomLevel = 2f;
private Point lastPoint;
private final Timer timer;
private final Timer popupTimer;
public Zoomer() {
popup = new JWindow();
popup.setLayout(new BorderLayout());
popup.add(this);
popup.pack();
try {
bot = new Robot();
} catch (AWTException ex) {
ex.printStackTrace();
}
timer = new Timer(125, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
updateBuffer();
}
});
timer.setCoalesce(true);
timer.setInitialDelay(0);
popupTimer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (lastPoint != null) {
System.out.println("lastPoint = " + lastPoint);
popup.setVisible(false);
Point p = lastPoint;
int x = p.x - (ZOOM_AREA / 2);
int y = p.y - (ZOOM_AREA / 2);
popup.setLocation(p.x + 16, p.y + 16);
buffer = bot.createScreenCapture(new Rectangle(x, y, ZOOM_AREA, ZOOM_AREA));
repaint();
popup.setVisible(true);
}
}
});
popupTimer.setRepeats(false);
}
public void setZoomWinodwVisible(boolean value) {
if (value && !popup.isVisible()) {
timer.start();
popup.setVisible(true);
} else {
timer.stop();
popup.setVisible(false);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(Math.round(ZOOM_AREA * zoomLevel), Math.round(ZOOM_AREA * zoomLevel));
}
protected void updateBuffer() {
if (bot != null) {
PointerInfo info = MouseInfo.getPointerInfo();
Point p = info.getLocation();
if (lastPoint == null || !lastPoint.equals(p)) {
lastPoint = p;
popupTimer.stop();
popup.setVisible(false);
} else {
if (!popup.isVisible()) {
popupTimer.start();
}
}
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (buffer != null) {
AffineTransform at = g2d.getTransform();
g2d.setTransform(AffineTransform.getScaleInstance(zoomLevel, zoomLevel));
g2d.drawImage(buffer, 0, 0, this);
g2d.setTransform(at);
}
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.dispose();
}
}
}
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.*;
class ZoomOnMouse {
public static void main(String[] args) throws AWTException {
final Robot robot = new Robot();
Runnable r = new Runnable() {
#Override
public void run() {
final int size = 256;
final BufferedImage bi = new BufferedImage(
size, size, BufferedImage.TYPE_INT_RGB);
final JLabel gui = new JLabel(new ImageIcon(bi));
ActionListener zoomListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
BufferedImage temp = robot.createScreenCapture(
new Rectangle(p.x-(size/4), p.y-(size/4),
(size/2), (size/2)));
Graphics g = bi.getGraphics();
g.drawImage(temp, 0, 0, size, size, null);
g.dispose();
gui.repaint();
}
};
Timer t = new Timer(40, zoomListener);
t.start();
JOptionPane.showMessageDialog(null, gui);
t.stop();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}