I am just about to have this app ready to distribute. However i still haven't found out a way to make a app suitable for whatever screen. In fact I am using pngs file for background and buttons (mainly) into JLabels, and this is a huge problem i think. What i would like to achieve is to have the JFrame and its components resizable but maintaining the proportions, just like for a responsive website!
Other ways i was planning of creating pngs of the originals but smaller and then create new frames and while booting the app ask the system what frame should suite the screen best and use it (but i know i would not be great and then of course os sometimes use screen resolution in different ways making is appear smaller or larger, if you know what i mean).
(I am using Netbeans).
Thank you a lot, i am looking foreword to discuss with you this issue that i am sure will concern many others.
Toolkit.getDefaultToolkit() has methods that provide you with what you need, including getting the current screen size:
private void makeFrameFullSize(JFrame aFrame)
{
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
aFrame.setSize(screenSize.width, screenSize.height);
}
This post discusses Resize image while keeping aspect ratio in Java.
Now that you have the screen size, you can calculate the ratio of image height/width to current frame size and scale on the % difference of current frame size to full screen size.
private Dimension get getFrameToScreenRatio(Frame aFrame){
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
return dimension.setSize(aFrame.getWidth()/dimension.getWidth(), aFrame.getHeight()/dimension.getHeight());
}
Here is an example utility class that can scale an image to fit a canvas (like a background image) and scales it to fit:
package net.codejava.graphics;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;
/**
* This utility class draws and scales an image to fit canvas of a component.
* if the image is smaller than the canvas, it is kept as it is.
*
* #author www.codejava.net
*
*/
public class ImageDrawer {
public static void drawScaledImage(Image image, Component canvas, Graphics g) {
int imgWidth = image.getWidth(null);
int imgHeight = image.getHeight(null);
double imgAspect = (double) imgHeight / imgWidth;
int canvasWidth = canvas.getWidth();
int canvasHeight = canvas.getHeight();
double canvasAspect = (double) canvasHeight / canvasWidth;
int x1 = 0; // top left X position
int y1 = 0; // top left Y position
int x2 = 0; // bottom right X position
int y2 = 0; // bottom right Y position
if (imgWidth < canvasWidth && imgHeight < canvasHeight) {
// the image is smaller than the canvas
x1 = (canvasWidth - imgWidth) / 2;
y1 = (canvasHeight - imgHeight) / 2;
x2 = imgWidth + x1;
y2 = imgHeight + y1;
} else {
if (canvasAspect > imgAspect) {
y1 = canvasHeight;
// keep image aspect ratio
canvasHeight = (int) (canvasWidth * imgAspect);
y1 = (y1 - canvasHeight) / 2;
} else {
x1 = canvasWidth;
// keep image aspect ratio
canvasWidth = (int) (canvasHeight / imgAspect);
x1 = (x1 - canvasWidth) / 2;
}
x2 = canvasWidth + x1;
y2 = canvasHeight + y1;
}
g.drawImage(image, x1, y1, x2, y2, 0, 0, imgWidth, imgHeight, null);
}
Related
I'm trying to create a BufferedImage from an arbitrary image file and then center that image in the background of a JPanel. I don't have any problems with square images, but I can't figure out how to handle non-square images.
Some debugging indicates that the (immediate) problem is that when I use ImageIO to create a BufferedImage from a rectangular input file, say one that's 256x128, BufferedImage.getHeight() returns 256 rather than 128.
Here's a snippet approximating my code:
class ExtendedPanel extends JPanel {
static final int WIDTH = 400;
static final int HEIGHT = 400;
BufferedImage image;
public ExtendedPanel(File f) {
super();
setPreferredSize(new Dimension(WIDTH,HEIGHT));
image = ImageIO.read(f);
}
#Override
public void paintComponent(Graphics g) {
int x = (WIDTH - image.getWidth())/2;
int y = (HEIGHT - image.getHeight())/2;
Graphics2D g2d = (Graphics2d)g;
g2d.drawRenderedImage(image,AffineTransform.getTranslateInstance(x,y));
}
}
As I said, this is fine for square image files. But with rectangular images that are wider than they are tall, the image is displayed higher than it should be. I haven't tried it yet with images taller than they are wide but I'm afraid that it that case the image would be displayed too far to the left. What can I do?
It is more a problem of (understanding) the right calculation.
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2d)g;
// How to scale the image:
double xscale = ((double)WIDTH) / image.getWidth();
double yscale = ((double)HEIGHT) / image.getHeight());
// When scaling proportionally:
double scale = Math.min(xscale, yscale); // max for covering entire panel.
xscale = scale;
yscale = scale;
double w = scalex * image.getWidth();
double h = scaley * image.getHeight();
double x = (getWidth() - w) / 2;
double y = (getHeight() - h) / 2;
g.drawImage(img, (int)x, (int)y, (int)w, (int)h, Color.BLACK, null);
//g2d.translate(x, y);
//g2d.scale(xscale, yscale);
//g2d.draw...;
}
Using the simple (scaling) version of drawImage what is needed is entirely clear.
To be considered is proportionally scaling, filling entirely (loss of image part) or upto maximal size (seeing background).
I've found something weird when splitting a translate operation around a scaling one with Java Swing. Maybe I'm doing something stupid but I'm not sure where.
In the first version I center the image, scale it and then translate it to the desired position.
In the second version I directly scale the image and then translate to the desired position compensating for having a non centered image.
The two solutions should be equivalent. Also this is important when considering rotations around a point and motion in another.. I've code that does that too... but why this does not work?
Here are the two versions of the code. They are supposed to do the exact same thing but they are not. Here are the screenshots:
First produces: screenshot1
Second produces: screenshot2
I think that the two translation operations in draw1 surrounding the scale operation should be equivalent to the scale translate operation in draw2.
Any suggestion?
MCVE:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.net.URL;
public class Asteroid extends JComponent implements ActionListener {
public static final Dimension FRAME_SIZE = new Dimension(640, 480);
public double x = 200;
public double y = 200;
public int radius = 40;
private AffineTransform bgTransfo;
private final BufferedImage im2;
private JCheckBox draw1Check = new JCheckBox("Draw 1", true);
Asteroid() {
BufferedImage img = null;
try {
img = ImageIO.read(new URL("https://i.stack.imgur.com/CWJdo.png"));
} catch (Exception e) {
e.printStackTrace();
}
im2 = img;
initUI();
}
private final void initUI() {
draw1Check.addActionListener(this);
JFrame frame = new JFrame("FrameDemo");
frame.add(BorderLayout.CENTER, this);
frame.add(BorderLayout.PAGE_START, draw1Check);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
Asteroid asteroid = new Asteroid();
}
#Override
public Dimension getPreferredSize() {
return FRAME_SIZE;
}
#Override
public void paintComponent(Graphics g0) {
Graphics2D g = (Graphics2D) g0;
g.setColor(Color.white);
g.fillRect(0, 0, 640, 480);
if (draw1Check.isSelected()) {
draw1(g);
} else {
draw2(g);
}
}
public void draw1(Graphics2D g) {//Draw method - draws asteroid
double imWidth = im2.getWidth();
double imHeight = im2.getHeight();
double stretchx = (2.0 * radius) / imWidth;
double stretchy = (2.0 * radius) / imHeight;
bgTransfo = new AffineTransform();
//centering
bgTransfo.translate(-imWidth / 2.0, -imHeight / 2.0);
//scaling
bgTransfo.scale(stretchx, stretchy);
//translation
bgTransfo.translate(x / stretchx, y / stretchy);
//draw correct position
g.setColor(Color.CYAN);
g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));
//draw sprite
g.drawImage(im2, bgTransfo, this);
}
public void draw2(Graphics2D g) {//Draw method - draws asteroid
double imWidth = im2.getWidth();
double imHeight = im2.getHeight();
double stretchx = (2.0 * radius) / imWidth;
double stretchy = (2.0 * radius) / imHeight;
bgTransfo = new AffineTransform();
//scale
bgTransfo.scale(stretchx, stretchy);
//translate and center
bgTransfo.translate((x - radius) / stretchx, (y - radius) / stretchy);
//draw correct position
g.setColor(Color.CYAN);
g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));
//draw sprite
g.drawImage(im2, bgTransfo, this);
}
#Override
public void actionPerformed(ActionEvent e) {
repaint();
}
}
Not sure if this question is still really open. Anyway here is my answer.
I think the crucial part to understand this behavior is the difference between AffineTransform.concatenate and AffineTransform.preConcatenate methods. The thing is that resulting transformation depends on the order the sub-transformations are applied.
To quote the concatenate JavaDoc
Concatenates an AffineTransform Tx to this AffineTransform Cx in the most commonly useful way to provide a new user space that is mapped to the former user space by Tx. Cx is updated to perform the combined transformation. Transforming a point p by the updated transform Cx' is equivalent to first transforming p by Tx and then transforming the result by the original transform Cx like this: Cx'(p) = Cx(Tx(p))
compare this with preConcatenate:
Concatenates an AffineTransform Tx to this AffineTransform Cx in a less commonly used way such that Tx modifies the coordinate transformation relative to the absolute pixel space rather than relative to the existing user space. Cx is updated to perform the combined transformation. Transforming a point p by the updated transform Cx' is equivalent to first transforming p by the original transform Cx and then transforming the result by Tx like this: Cx'(p) = Tx(Cx(p))
The scale and translate methods are effectively concatenate. Lets call 3 transformations in your draw1 method C (center), S (scale), and T (translate). So your compound transformation is effectively C(S(T(p))). Particularly it means that S is applied to the T but not to the C so your C does not really center the image. A simple fix would be to change the order of S and C but I think that a more proper fix would be something like this:
public void draw3(Graphics2D g) {
//Draw method - draws asteroid
double imWidth = im2.getWidth();
double imHeight = im2.getHeight();
double stretchx = (2.0 * radius) / imWidth;
double stretchy = (2.0 * radius) / imHeight;
AffineTransform bgTransfo = new AffineTransform();
//translation
bgTransfo.translate(x, y);
//scaling
bgTransfo.scale(stretchx, stretchy);
//centering
bgTransfo.translate(-imWidth / 2.0, -imHeight / 2.0);
//draw correct position
g.setColor(Color.CYAN);
g.fillOval((int) (x - radius), (int) (y - radius), (int) (2 * radius), (int) (2 * radius));
//draw sprite
g.drawImage(im2, bgTransfo, this);
}
I think the big advantage of this method is that you don't have to re-calculate the T using stretchx/stretchy
I had a quick question, and wondered if anyone had any ideas or libraries I could use for this. I am making a java game, and need to make 2d images concave. The problem is, 1: I don't know how to make an image concave. 2: I need the concave effect to be somewhat of a post process, think Oculus Rift. Everything is normal, but the camera of the player distorts the normal 2d images to look 3d. I am a Sophmore, so I don't know very complex math to accomplish this.
Thanks,
-Blue
If you're not using any 3D libraries or anything like that, just implement it as a simple 2D distortion. It doesn't have to be 100% mathematically correct as long as it looks OK. You can create a couple of arrays to store the distorted texture co-ordinates for your bitmap, which means you can pre-calculate the distortion once (which will be slow but only happens once) and then render multiple times using the pre-calculated values (which will be faster).
Here's a simple function using a power formula to generate a distortion field. There's nothing 3D about it, it just sucks in the center of the image to give a concave look:
int distortionU[][];
int distortionV[][];
public void computeDistortion(int width, int height)
{
// this will be really slow but you only have to call it once:
int halfWidth = width / 2;
int halfHeight = height / 2;
// work out the distance from the center in the corners:
double maxDistance = Math.sqrt((double)((halfWidth * halfWidth) + (halfHeight * halfHeight)));
// allocate arrays to store the distorted co-ordinates:
distortionU = new int[width][height];
distortionV = new int[width][height];
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// work out the distortion at this pixel:
// find distance from the center:
int xDiff = x - halfWidth;
int yDiff = y - halfHeight;
double distance = Math.sqrt((double)((xDiff * xDiff) + (yDiff * yDiff)));
// distort the distance using a power function
double invDistance = 1.0 - (distance / maxDistance);
double distortedDistance = (1.0 - Math.pow(invDistance, 1.7)) * maxDistance;
distortedDistance *= 0.7; // zoom in a little bit to avoid gaps at the edges
// work out how much to multiply xDiff and yDiff by:
double distortionFactor = distortedDistance / distance;
xDiff = (int)((double)xDiff * distortionFactor);
yDiff = (int)((double)yDiff * distortionFactor);
// save the distorted co-ordinates
distortionU[x][y] = halfWidth + xDiff;
distortionV[x][y] = halfHeight + yDiff;
// clamp
if(distortionU[x][y] < 0)
distortionU[x][y] = 0;
if(distortionU[x][y] >= width)
distortionU[x][y] = width - 1;
if(distortionV[x][y] < 0)
distortionV[x][y] = 0;
if(distortionV[x][y] >= height)
distortionV[x][y] = height - 1;
}
}
}
Call it once passing the size of the bitmap that you want to distort. You can play around with the values or use a totally different formula to get the effect you want. Using an exponent less than one for the pow() function should give the image a convex look.
Then when you render your bitmap, or copy it to another bitmap, use the values in distortionU and distortionV to distort your bitmap, e.g.:
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// int pixelColor = bitmap.getPixel(x, y); // gets undistorted value
int pixelColor = bitmap.getPixel(distortionU[x][y], distortionV[x][y]); // gets distorted value
canvas.drawPixel(x + offsetX, y + offsetY, pixelColor);
}
}
I don't know what your actual function for drawing a pixel to the canvas is called, the above is just pseudo-code.
i have a use case like after adding JLabelComponent to pallet which i have to resize to custom level(re sizing bcz data is very large ) the added label component.Once i am done with re-sizing setting the component.setBounds all the coordinates. when i try to rotate the re sized label component to 90 degrees i am not getting proper shape. its head are cut off. please suggest
Here is my code:
if (selectedComponent instanceof LabelComponent) {
LabelComponent lbls = (LabelComponent) selectedComponent;
lbls.setAngle(Integer.parseInt(value));
lbls.repaint();
lbls.setSize(lbls.getPreferredSize());
and my paint method is
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
AffineTransform aT = g2.getTransform();
double sin = Math.abs(Math.sin(getAngle()));
double cos = Math.abs(Math.cos(getAngle()));
int originalWidth = getWidth();
int originalHeight = getHeight();
int newWidth = (int) Math.floor(originalWidth * cos + riginalHeight * sin);
int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
if(getAngle() == Integer.parseInt("90"))
{
g2.translate((newWidth-originalWidth)/2, (newHeight-orginalHeight)/2);
}
g2.rotate(Math.toRadians(getAngle()), originalWidth/2, originalHeight/2);
super.paint(g);
}
when i try to rotate the re sized label component to 90 degrees i am not getting proper shape.
One problem is that you need to reset the preferred size of the label. That is the width and height change because of the rotation.
Instead of doing custom painting you could try to use the Rotated Icon.
This is not a homework problem. I am only going over a freely available course from Stanford. I am using Ubuntu Linux with Eclipse.
Problem and Question:
I am drawing rectangles by calling add() on a acm.program.GraphicsProgram object. I am drawing certain number of rectangles which have a certain fixed width. However I am seeing that my rectangles are flowing off the visible area. I have tried setting a big enough width and height for both the GraphicsProgram object and the GCanvas object but still my rectangles are falling off the visible area. I always get the same height for GraphicsProgram object no matter what height I set. Any pointers as to what am I doing wrong?
import acm.graphics.*;
import acm.program.*;
import java.awt.*;
public class Pyramid extends GraphicsProgram {
/** Width of each brick in pixels */
private static final int BRICK_WIDTH = 30;
/** Width of each brick in pixels */
private static final int BRICK_HEIGHT = 12;
/** Number of bricks in the base of the pyramid */
private static final int BRICKS_IN_BASE = 14;
public void run() {
setWindowSize();
this.createPyramid();
}
private void createPyramid()
{
int centerX = findCenter();
int startingX = centerX - (BRICKS_IN_BASE / 2) * BRICK_WIDTH;
int startingY = BRICK_HEIGHT;
for(int numBricks = BRICKS_IN_BASE; numBricks>= 1; numBricks--)
{
this.layBricks(startingX,startingY , numBricks);
startingX = startingX + BRICK_WIDTH / 2;
startingY = (BRICKS_IN_BASE - numBricks + 2) * BRICK_HEIGHT;
}
}
private void layBricks(int x, int y, int numOfBricks)
{
for(int i = 0; i < numOfBricks; i++)
{
add(new GRect(x,y,this.BRICK_WIDTH, this.BRICK_HEIGHT));
x+=this.BRICK_WIDTH;
}
}
private void setWindowSize()
{
int width = BRICK_WIDTH * BRICKS_IN_BASE * 2;
int height = BRICKS_IN_BASE * BRICK_HEIGHT * 2;
this.setSize(width, height);
//this.setForeground(Color.GREEN);
//this.setBackground(Color.BLUE);
//this.getGCanvas().setBounds(0, 0, width, height);
//this.getGCanvas().add(new GRect(0,0,300,30));
//this.getGCanvas().setBackground(Color.WHITE);
System.out.println(this.getHeight());
System.out.println(this.getWidth());
System.out.println(this.getGCanvas().getHeight());
System.out.println(this.getGCanvas().getWidth());
}
private int findCenter()
{
return this.getWidth() / 2;
}
}
I'm working through the same Stanford course online and ran into the same problem. The setSize method would resize the display but not the values returned by getWidth and getHeight.
You can change the width and height by going to Project > Properties > Run/Debug Settings > Edit > Parameters tab.
I assume there is something more direct or code-based, but this is an easy solution.
Start by not hard coding the brick sizes like this
int brick_width = (getWidth() / BRICKS_IN_BASE) - (getWidth() / 50);
int brick_height = (brick_width / 3);
That way your pyramid will always be drawn within whatever the screen size happens to be.
It should also be centered, once your brick sizes are relative to window size, guaranteeing that the bricks will always be centered becomes easier. The only wrench I have found are weird
window sizes such as 50 x 500, but that doesn't happen too often.
Here is a look at my solution
import acm.graphics.*;
import acm.program.*;
import java.awt.*;
public class Pyramid1 extends GraphicsProgram {
public void run(){
int brick_width = (getWidth() / BRICKS_IN_BASE) - (getWidth() / 50);
int brick_height = (brick_width / 3);
for(int n = 0; n < BRICKS_IN_BASE; n++ ){
//make a row at level n of bricks that is BRICKS_IN_BASE - n bricks wide.
int bricks_in_level = BRICKS_IN_BASE - n;
int x = ((getWidth() / 2) - ((bricks_in_level * brick_width) / 2)); //find the center then offset to farthest left.
int y = (((getHeight() / 2) + ((BRICKS_IN_BASE / 2) * brick_height )) - ((n + 1) * brick_height)); //start at the 1/2 and move down half max stack height and move up a brick each round.
GRect brick = new GRect(x, y, brick_width, brick_height);
brick.setFilled(true);
brick.setFillColor(Color.RED);
add(brick);
if(bricks_in_level > 1){ //If there are 2 or more bricks needed in this level
for(int needed_bricks = bricks_in_level - 1; needed_bricks > 0; needed_bricks -= 1){
x += brick_width;
GRect needed_brick = new GRect(x, y, brick_width, brick_height);
needed_brick.setFilled(true);
needed_brick.setFillColor(Color.RED);
add(needed_brick);
}
}
}
}
private static final int BRICKS_IN_BASE = 12;
}
The main problem was that I was using open-java-jdk and not sun-java-jdk. After changing the jre my Applet is behaving in a more predictable way.