I'm writing a method for the Java Helper Library to show a "windowless" swing component (image) which can be used as a way to show a progress wheel or something. I asked how to do this a while ago and received a good answer which I'm using. It works great except the animated gif is not animated. (I'm not using the image itself because seeing it the whole time you're reading this might make you sick...) It's not animated as in it's not moving. It's seemingly paused or something. The answer on the other question said animated gifs would work fine. Is the answer-er wrong or am I implementing this wrong?:
public static void main(String[] args) throws IOException, InterruptedException {
Image image = SwingHelper.resizeImageFromResourceBy(TestClass.class, progressImageLocation, 32, true); //This just gets the image in the right size I want it.
JWindow window = SwingHelper.getProgressWheelWindow(new ImageIcon(image), .9f, 600, 400);
window.setVisible(true);
Thread.sleep(3000); //Just so the image only shows up for a short period of time.
window.setVisible(false);
SwingHelper.centerAndPack(window); //A method to center and pack the window
}
/**
* Returns a window with a partially opaque progress Icon
*
* #param icon the icon to set in the progress window
* #param opacity a float value from 0-1
* #param x the x location of the window
* #param y the y location of the window
* #return a jWindow of the progress wheel
*/
public static JWindow getProgressWheelWindow(final Icon icon, final Float opacity, final int x, final int y) {
JWindow jWindow = new JWindow() {
{
setOpacity(opacity);
setLocation(x, y);
setSize(icon.getIconWidth(), icon.getIconHeight());
add(new JLabel(icon));
pack();
}
};
return jWindow;
}
SwingHelper.resizeImageFromResourceBy(
TestClass.class, progressImageLocation, 32, true);
//This just gets the image in the right size I want it.
My best guess short of an SSCCE, is that the helper method returns a static (resized) version of the original image.
Note that by using HTML in the label, an animated GIF can be resized 'on-the-fly', however the effect is less than optimal. It is better to design the image the right size.
Related
Is there any way to make fullscreen(and if possible resizing too) to instead of rearranging everything (actually what it does is to rearrange the elements like resizing but to the whole screen) to make an actual fullscreen mode? (like games that what usually do is change screen resolution), so that buttons and text grows accordingly to the size of the screen/window
Also how can I remove the message and the effect on click the "esc" key to exit the fullscreen mode?
EDIT: use this way to make resizeable
#Override public void start(Stage stage) throws Exception{
final int initWidth = 720; //initial width
final int initHeight = 1080; //initial height
final Pane root = new Pane(); //necessary evil
Pane controller = new CtrlMainMenu(); //initial view
controller.setPrefWidth(initWidth); //if not initialized
controller.setPrefHeight(initHeight); //if not initialized
root.getChildren().add(controller); //necessary evil
Scale scale = new Scale(1, 1, 0, 0);
scale.xProperty().bind(root.widthProperty().divide(initWidth)); //must match with the one in the controller
scale.yProperty().bind(root.heightProperty().divide(initHeight)); //must match with the one in the controller
root.getTransforms().add(scale);
final Scene scene = new Scene(root, initWidth, initHeight);
stage.setScene(scene);
stage.setResizable(true);
stage.show();
//add listener for the use of scene.setRoot()
scene.rootProperty().addListener(new ChangeListener<Parent>(){
#Override public void changed(ObservableValue<? extends Parent> arg0, Parent oldValue, Parent newValue){
scene.rootProperty().removeListener(this);
scene.setRoot(root);
((Region)newValue).setPrefWidth(initWidth); //make sure is a Region!
((Region)newValue).setPrefHeight(initHeight); //make sure is a Region!
root.getChildren().clear();
root.getChildren().add(newValue);
scene.rootProperty().addListener(this);
}
});
}
There are a couple of ways to resize your UI.
Scale by Font Size
You can scale all controls by setting -fx-font-size in the .root of your scene's style sheet.
For example, if you apply the following stylesheet to your scene, then all controls will be doubled in size (because the default font size is 13px).
.root {
-fx-font-size: 26px;
}
The above will work to scale controls, which is fine for things which are completely control based, but not so good for things which are graphic and shape based.
Scale by Transform
Apply a Scale transform pivoted at (0,0) to your scene's root node.
Scale scale = new Scale(scaleFactor, scaleFactor);
scale.setPivotX(0);
scale.setPivotY(0);
scene.getRoot().getTransforms().setAll(scale);
To scale a game I developed which includes graphics and various shapes, I used a letter boxing technique which sized the game window to a constant aspect ratio, (similar to the letter boxing you see when you watch a 4:3 tv show on a 16:9 screen).
The SceneSizeChangeListener in the code below listens for changes to the scene size and scales the content of the scene appropriate to the available scene size.
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.transform.Scale;
import javafx.stage.Stage;
import org.jewelsea.games.supersnake.layout.LayoutController;
import java.io.IOException;
import java.util.ResourceBundle;
/* Main JavaFX application class */
public class SuperSnake extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(final Stage stage) throws IOException {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("layout/layout.fxml"),
ResourceBundle.getBundle("org.jewelsea.games.supersnake.layout.text")
);
Pane root = (Pane) loader.load();
GameManager.instance().setLayoutController(loader.<LayoutController>getController());
Scene scene = new Scene(new Group(root));
stage.setScene(scene);
stage.show();
GameManager.instance().showMenu();
letterbox(scene, root);
stage.setFullScreen(true);
}
private void letterbox(final Scene scene, final Pane contentPane) {
final double initWidth = scene.getWidth();
final double initHeight = scene.getHeight();
final double ratio = initWidth / initHeight;
SceneSizeChangeListener sizeListener = new SceneSizeChangeListener(scene, ratio, initHeight, initWidth, contentPane);
scene.widthProperty().addListener(sizeListener);
scene.heightProperty().addListener(sizeListener);
}
private static class SceneSizeChangeListener implements ChangeListener<Number> {
private final Scene scene;
private final double ratio;
private final double initHeight;
private final double initWidth;
private final Pane contentPane;
public SceneSizeChangeListener(Scene scene, double ratio, double initHeight, double initWidth, Pane contentPane) {
this.scene = scene;
this.ratio = ratio;
this.initHeight = initHeight;
this.initWidth = initWidth;
this.contentPane = contentPane;
}
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
final double newWidth = scene.getWidth();
final double newHeight = scene.getHeight();
double scaleFactor =
newWidth / newHeight > ratio
? newHeight / initHeight
: newWidth / initWidth;
if (scaleFactor >= 1) {
Scale scale = new Scale(scaleFactor, scaleFactor);
scale.setPivotX(0);
scale.setPivotY(0);
scene.getRoot().getTransforms().setAll(scale);
contentPane.setPrefWidth (newWidth / scaleFactor);
contentPane.setPrefHeight(newHeight / scaleFactor);
} else {
contentPane.setPrefWidth (Math.max(initWidth, newWidth));
contentPane.setPrefHeight(Math.max(initHeight, newHeight));
}
}
}
}
Here is a screenshot where you can see the letterboxing and scaling taking effect. The green grass in the middle is the main game content screen and scales up and down to fit the available screen area. The wood texture around the outside provides a flexibly sized border which fills in the area where the black letterbox bars would normally be if you were watching a tv program at a different aspect ratio to your screen. Note that the background in the screenshot below is blurry at the title page because I make it so, when the game starts, the blur effect is removed and the view is crisp regardless of the size.
Windowed version:
Scaled full screen version:
You might think that the scaling method above might make everything go all blocky and pixelated, but it doesn't. All font's and controls scale smoothly. All standard drawing and graphic commands and css based styles scale smoothly as they are all vector based. Even bitmapped images scale well because JavaFX uses fairly high quality filters when scaling the images.
One trick to get good scaling on images is to provide high resolution images, so that when the screen scales up, the JavaFX system has more raw data to work from. For example, if the preferred window size for an app is quarter of the screen size and it contains a 64x64 icon, instead use a 128x128 icon, so that when the app is put in full screen and all elements scaled, the scaler has more raw pixel data samples to use for interpolating values.
The scaling is also fast as it is hardware accelerated.
how can I remove the message and the effect on click the "esc" key to exit the fullscreen mode?
It's not possible to remove the full screen exit message in JavaFX 2.2, it will be possible in JavaFX 8:
RT-15314 Allow trusted apps to disable the fullscreen overlay warning and disable the "Exit on ESC" behavior
It will be nice when that is done, because then my games won't have that "look at me - I look like a beta" feel about them.
"Also how can I remove the message and the effect on click the "esc" key to exit the fullscreen mode?"
Use this code :
stage.setFullScreenExitHint("");
It will change the string message "Press Esc to quit Fullscreen mode" into empty string so it will not show up.
You may copy this into JavaFXApplication
Dimension resolution = Toolkit.getDefaultToolkit().getScreenSize();
double width = resolution.getWidth();
double height = resolution.getHeight();
double w = width/1280; // your window width
double h = height/720; // your window height
Scale scale = new Scale(w, h, 0, 0);
root.getTransforms().add(scale);
Hey :) So I'm making buttons for a game I'm making. The graphics work, at least to the extent that I don't have to fix them yet. However, click detection is a bit iffy. For example pressing where the black line is in the first picture below, triggers a response. Now obviously that point is not on the button. I have tested the buttons bounding box by drawing a rectangle around it, using it's getBounds() method (which is also used for click detection) and it draws a perfect rectangle around it. So then I tested the mouse click points and it turns out that even though the button is placed at y = 100, at the black line, the mouse point is also equal to 100... Now I have no idea why that is happening, especially because, if I place the button in the top left corner, the mouse detection correctly detects the top pixels and there is no offset...
This is rather interesting, and during my times in have had similar problems. This all really depends on why the Mouse Listener is attached to. Many people attach the listener to the frame, but draw on a panel. This can have the effects you are describing so it is usually better to either draw directly onto the frame, or attach the listener to the panel. In 99.99% of cases, I would always choose the latter. Really, no one should ever choose the latter UNLESS it's something very small.
Panels are exactly that; they're boxes which hold things, hence 'panel'. In my experiences it has always been more effective to use a panel. Frames are just the container to hold multiple panels.
Hope I could help, report your findings in a comment and/or post update.
Jarod.
Got bored so I whipped up an example of what I think is going on.
In essence, I do full rendering to a buffer (BufferedImage here). And then draw the render to the canvas. This may or may not be what you do, but I did it merely for example.
Seeing as you did say that it works fine in the top-left corner, I came to the hypothesis that scaling is the issue, since the x,y-values near the top left approach 0, and 0 * scale = 0, even a scaling of 1000 won't have any offset. The issue is when those components are not at the top-left corner, which you demonstrated for us.
Hopefully this answers your question. As for solving it, you can either accommodate for scaling, or use a letterboxing technique. Beyond those two, there are certainly many other ways to deal with this (such as fixing the screen size).
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* #author Obicere
*/
public class GraphicScale {
public GraphicScale(){
final JFrame frame = new JFrame("Graphic Scale Example");
final MyPanel panel = new MyPanel();
final Timer repaintTimer = new Timer(50, e -> frame.repaint());
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
repaintTimer.start();
}
public static void main(final String[] args){
SwingUtilities.invokeLater(GraphicScale::new);
}
public class MyPanel extends JPanel {
private final Rectangle box = new Rectangle(100, 100, 100, 50);
private final Dimension size = new Dimension(500, 500);
private final BufferedImage render = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
render.flush();
render();
g.drawImage(render, 0, 0, getWidth(), getHeight(), this); // Trick is that this gets rescaled!
}
public void render(){
final Graphics2D g = (Graphics2D) render.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, render.getWidth(), render.getHeight());
final Point mouse = getMousePosition();
if(mouse != null){
if(box.contains(mouse)) {
g.setColor(Color.GREEN);
g.fill(box);
}
g.setColor(Color.DARK_GRAY);
g.drawOval(mouse.x - 3, mouse.y - 3, 6, 6);
}
g.setColor(Color.BLACK);
g.draw(box);
}
#Override
public Dimension getPreferredSize(){
return size;
}
}
}
Ok, so it turns out that there was some scaling going on with the frame, however I have no idea where it came from. I prepped the game to be scalable so I did all the painting to the optimal size BufferedImage and then I scale that image to the frame. However, even when I removed that the mouse location was still offset. In the end I overcame it by finishing the scaling optimization which required finding the scale of the frame by dividing the current width and height by the optimal width and height. And then dividing the mouse location by that value.I just figured this out. Setting the size of a component and packing the frame after adding the component, results in the actual frame being that size (counting the border), yet when you retrieve the size of the frame, it disregards the border... Why does this happen?
Solved
When I did the game screen scaling, I used the actual frame's height and width to scale the screen, instead of the canvas's height and width. I changed that and now it works perfectly!
I am using a JasperViewer to display the report inside of a Java desktop application.
The report consists of 2 pages - each of them represents an image.
The problem is, when user scrolls the page inside the viewer, there are huge freezes.
The size of image isn't so big, about 1000x1000.
The image is generated in this way:
private BufferedImage createImage(Component panel) {
int w = (int) panel.getSize().getWidth();
int h = (int) panel.getSize().getHeight();
BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = bi.createGraphics();
panel.paint(g);
g.dispose();
return bi;
}
You have two choices
1) put your image as Icon to JLabel
2) for Swing JComponets is there paintComponent() instead of paint(),
please read tutorial about Graphics
tons of examples on this forum (Swing tagged),
The issue is resolved. There is a parameter in the JRViewer:
//Maximum size (in pixels) of a buffered image that would be used by {#link JRViewer JRViewer} to render a report page.
//If rendering a report page would require an image larger than this threshold
//(i.e. image width x image height > maximum size), the report page will be rendered directly on the viewer component.
//If this property is zero or negative, buffered images will never be user to render a report page.
//By default, this property is set to 0.
public static final String VIEWER_RENDER_BUFFER_MAX_SIZE
So, if this parameter is set, the reports is drawn as an ImageIcon on a JLabel. Otherwise, it's drawn using JRGraphics2DExporter that is much more slower when working with big images.
So the solution is to set the specified property in the property file or using way like this:
/**
* This number represents maximum size of an image ( x*y )
* So this value cover up to 300% zoom for an image 1000x1000 pixels
*/
public static final String MAX_PIXELS_NUMBER = "10000000";
static {
try {
JRProperties.setProperty(JRViewer.VIEWER_RENDER_BUFFER_MAX_SIZE, MAX_PIXELS_NUMBER);
} catch (Exception e) {
System.err.println("Cannot set the VIEWER_RENDER_BUFFER_MAX_SIZE property. Reports will be rendered slowly.");
}
}
I'm attempting to sort of "highlight" a tile object within a game I'm making (Mahjong Solitaire). To do this, I'm drawing a Rectangle2D object in the same position as the tile and trying to have it display when the mouse is clicked.
I'm able to get the mouse click event to work and recognize when tiles are selected, but for some reason, the rectangle is not drawn when I'm within the mousePressed function. I can't seem to figure out why...
Here is what I consider the relevant code - I can expand it if necessary!
/* Above this, the positions of tiles are set */
if (content[i][y][x].isVisible()) {
/* Draws the image to screen at the appropriate location */
graphics.drawImage(image, x*TILEW+TILEW/2+i*TILESKEW, (y+1)*TILEH/2-i*TILESKEW,null);
}
/* Represents the area around a tile, so that you can determine
* whether appropriate area pressed within a tile */
final Rectangle2D rect = new Rectangle2D.Double(x*TILEW+TILEW/2+i*TILESKEW,(y+1)*TILEH/2-i*TILESKEW, image.getWidth(null), image.getHeight(null));
/* Set colour of border rectangle */
graphics.setColor(Color.red);
/* Store positions and sizes of tile objects */
final int xPos = x*TILEW+TILEW/2+i*TILESKEW;
final int yPos = (y+1)*TILEH/2-i*TILESKEW;
final int height = image.getHeight(null)+2;
/* This works - outside of the mouse event */
//graphics.drawRoundRect(xPos, yPos, width, height, 7, 7);
/* Mouse event */
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
/* Draw the rectangle to the screen -- Doesn't display! */
graphics.drawRoundRect(xPos, yPos, width, height, 7, 7);
}
The "graphics" Graphic object is passed to the function:
public void paintComponent(final Graphics graphics) { ... }
Any suggestions would be greatly appreciated!
Thank you in advance for your help!
Your program structure sounds off in that you usually shouldn't have the MouseListener directly manipulating the Graphics object that is passed into paintComponent. The reason for this is that the Graphics object obtained this way won't persist. Usually you'll have the MouseAdapter (both MouseListener and MouseMotionListener) alter class fields and then call repaint() on the component. Then paintComponent uses the fields set by the mouse adapter to draw the rectangle.
Edit 1
For example, please see my sample program here: drawing-a-rectangle-over-an-existing-graphics-page
I have a field which extends BitmapField (called AnimatedGIFField) and an AnimatorThread (extending Thread) which does the work of looping through the GIF frames, incrementing the current frame and invalidating the field, which calls the paint() method to draw the next frame (resulting in animation). The animation code works fine, but my issue is in the paint() method of the AnimatedGIFField class. I'm calling 'graphics.drawImage()' and I'm having trouble getting proper positions for x and y (the first two args to drawImage()). Positioning the AnimatedGIFField is working and is accomplished by overriding 'getPreferredWidth()' and 'getPreferredHeight()'. The relevant code is here:
public class AnimatedGIFField extends BitmapField {
/**
The current frame in the animation sequence; the AnimatorThread
increments this so paint() knows which frame to draw.
*/
private int currentFrame;
public AnimatedGIFField(GIFEncodedImage image, long style) {
super(image.getBitmap(), style);
this.image = image;
this.preferredWidth = this.image.getWidth();
this.preferredHeight = -(this.image.getHeight() * 4);
}
protected void paint(Graphics graphics) {
// Calling super draws the first background frame.
super.paint(graphics);
// Don't redraw if this is the first frame.
if (this.currentFrame != 0) {
// Draw the animation frame.
/* getFrameLeft() and getFrameTop() both return the top left-most position (0, 0). */
/* graphics.drawImage(this.image.getFrameLeft(this.currentFrame), */
/* this.image.getFrameTop(this.currentFrame), */
/* this.image.getFrameWidth(this.currentFrame), */
/* this.image.getFrameHeight(this.currentFrame), */
/* this.image, this.currentFrame, 0, 0); */
/*
Currently trying some hackish nonsense like this to position the frame, although
it probably won't scale properly across different devices/screen sizes.
*/
int x = (this.getManager().getWidth() / 2) - 45;
int y = (this.getManager().getHeight() / 2) + 83;
graphics.drawImage(x, y,
this.image.getFrameWidth(this.currentFrame),
this.image.getFrameHeight(this.currentFrame),
this.image, this.currentFrame, 0, 0);
}
}
}
What about something like this?
graphics.drawImage(this.image.getFrameLeft(this.currentFrame,
this.image.getFrameTop(this.currentFrame),
this.image.getFrameWidth(this.currentFrame),
this.image.getFrameHeight(this.currentFrame),
this.image, this.currentFrame, 0, 0);
Fixed the problem by stripping out all getPreferredWidth/Height stuff, removed the sublayout() method on my custom field and just overwrote layout() inside of my custom manager to position all fields (just the one) properly. That caused image.getFrameLeft() and image.getFrameTop() return proper values, which is where I had my positioning hacks before.
Thanks for the responses. I was making it way more complicated than it needed to be.