Java Swing rotate and zoom - java

I have a JPanel surrounded with JScrollPane. This JPanel is used to display an image. I need to provide functionality like zoomIn, zoomOut, clockwiseRotate and antiClockwiseRotate. All these functionalities are working fine individually. For zoom, I call scale of graphics object. It happens on top left to bottom right basis. For rotation, I reset the scale, and translate, rotate & translate back the graphics object. But when I combine zoom with rotate, it behaves differently. For instance, I rotate clockwise and the image gets rotated to 1.57079633 radians (approx 90 degree). Now when I press zoom, the image gets zoomed but the zooming happens based on top right and bottom left basis instead of top left and bottom right basis. If I again rotate the image in clockwise direction, I reset the zoom, that is I scale the image to its original size and call translate, rotate and translate back on graphics object. Now if I press zoom again, it zooms in based on bottom right and top left basis.
Hence the problem is the Image's coordinates are not getting changed when panels coordinates are changed. Can somebody help me out in changing the coordinates of the image?
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D)g;
g2d.translate((tempWidth/2), (tempHeight/2));
g2d.rotate(m_rotate);
g2d.translate(-(tempWidth/2), -(tempHeight/2));
g2d.scale(m_zoom, m_zoom);
if(this.image != null && this.image.getHeight(null) > 0)
{
g2d.drawImage(this.image, 0, 0,302,312,this);
}
else
{
g2d.drawString("View Image Here!. ", 20, 20);
}
}

Thanks for your help. It made me think in a new dimension which helped me to solve the issue. All I did is just scaling the image before rotating and it runs well now.
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D)g;
g2d.scale(m_zoom, m_zoom);//This is what made the magic
g2d.translate((tempWidth/2), (tempHeight/2));
g2d.rotate(m_rotate);
g2d.translate(-(tempWidth/2), -(tempHeight/2));
/*g2d.scale(m_zoom, m_zoom); removed this and placed above */
if(this.image != null && this.image.getHeight(null) > 0)
{
g2d.drawImage(this.image, 0, 0,302,312,this);
}
else
{
g2d.drawString("View Image Here!. ", 20, 20);
}
}

Related

How to flip entire awt.Canvas vertically

I am sitting with my son, trying to implement a school homework. The task is to write a program that draws X and Y axis and functions, e.g. Sinus or x² into a awt.Canvas. The issue we are struggeling with is that the root, Point(0,0) of the Canvas is designed to be in the upper left corner. The cartesian coordinate system that we have to have, has the origin in the lower left corner. So we tried to apply a AffineTransform and translate in the paint method of the Canvas, which in essence works but has two issues:
1st, for whatever reason the related translation doesn't really moves the origin to the bottom but about 100 pixels to high (see image).
When we put in the below code an additional offset of about 100 pixels with tx.translate(0, -(getHeight()+100)); it looks about right.Same issue seems to be true on the right side. There is also unintended free space. We colored the background of the containing Frame in black and the Canvas in grey to exclude an artefact between these two containers. But doesn't seem to be the case.
2nd, and that concerns us more, is the side effect that all text, when e.g. adding values to the axes will also be fliped, as you see at our debug info in the plotAxes method.
Here is what we have done so far..
public class PlotterView extends Canvas {
protected int MINWIDTH = 500;
protected int MINHEIGHT = 400;
Point[][] lines;
public PlotterView() {
Dimension dim = new Dimension(MINWIDTH, MINHEIGHT);
setPreferredSize(dim);
setBackground(Color.LIGHT_GRAY);
}
protected void plotAxes(Graphics2D g) {
Color defaultColor = g.getColor(); // save to restore defaults in the end
int originX = 5; // x origin of both axes - shift right
int originY = 5; // y origin of both axis - shift up
// Debug info to compare
g.setColor(Color.BLACK);
g.drawString("X: " + originX + "; Y: " + originY, originX, originY);
// X-Axis
g.setColor(Color.RED);
g.drawLine(originX, originY, MINWIDTH-20, originY);
g.drawLine(MINWIDTH-20, originY, MINWIDTH-30, originY-5);
g.drawLine(MINWIDTH-20, originY, MINWIDTH-30, originY+5);
// Y-Axis
g.setColor(Color.BLUE);
g.drawLine(originX, originY, originX, MINHEIGHT-20);
g.drawLine(originX, MINHEIGHT-20, originX-5, MINHEIGHT-30);
g.drawLine(originX, MINHEIGHT-20, originX+5, MINHEIGHT-30);
// Restore defaults
g.setColor(defaultColor);
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -getHeight());
g2.setTransform(tx);
plotAxes(g2);
}
}
One alternative would be to implement a method that "adjusts" every x-value from upper left to lower left, but that feels like a kind of botch job.
How to solve this right? Thank you in advance
Thank you for the feedback. I figured out that issue number 1 is born by using the AffineTransform. If I apply the scale and translate on the g2 directly the issue disappears.
Issue number 2 is a conflict of interest. We couldn't manage to find and apply the "three magic lines of code" (or whatever number would be required) to from thereon programm in a cartesian coordinate system. Instead we are converting all y-values into this top-level-origin coordinate system. Makes the code hard to read, but with the help debugging we managed.

Miserable text when drawing offscreen images in Java

I am stuck (beyond the limits of fun) at trying to fix text quality with offscreen image double buffering.
Screen capture worth a thousand words.
The ugly String is drawn to an offscreen image, and then copied to the paintComponent's Graphics argument.
The good looking String is written directly to the paintComponent's Graphics argument, bypassing the offscreen image.
Both Graphics instances (onscreen and offscreen) are identically setup in terms of rendering quality, antialiasing, and so on...
Thank you very much in advance for your wisdom.
The very simple code follows:
public class AcceleratedPanel extends JPanel {
private Dimension osd; //offscreen dimension
private BufferedImage osi; //offscreen image
private Graphics osg; //offscreen graphic
public AcceleratedPanel() {
super();
}
#Override
public final void paintComponent(Graphics g) {
super.paintComponent(g);
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
osg2D.drawString("Offscreen painting", 10, 20);
//Dump offscreen buffer to screen
g.drawImage(osi, 0, 0, this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
/*
To make sure same settings are used in different Graphics instances,
a unique setup procedure is used.
*/
private void setupGraphics(Graphics2D g) {
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
private Graphics2D getOffscreenGraphics() {
//Graphics Acceleration
Dimension currentDimension = getSize();
if (osi == null || !currentDimension.equals(osd)) {
osi = (BufferedImage)createImage(currentDimension.width, currentDimension.height);
osg = osi.createGraphics();
osd = currentDimension;
}
return (Graphics2D) osg;
}
} //End of mistery
You are not drawing your two strings with the same color. The default color for the offscreen Graphics is rgb(0, 0, 0) (that is, pure black), while Swing will set the color of a Graphics object to the look-and-feel’s default color—which, for me on Windows 7, using the default theme, is rgb(51, 51, 51), or dark gray.
Try placing g.setColor(Color.BLACK); in your setupGraphics method, to ensure both strings are drawn with the same color.
Thanks for the replies.
With mentioning DPI, MadProgrammer has lead me to a working fix which I offer here more as workaround than as a 'clean' solution to be proud of. It solves the issue, anyway.
I noticed that while my screen resolution is 2880x1800 (Retina Display), MouseEvent's getPoint() method reads x=1440, y=900 at the lower right corner of the screen. Then, the JPanel size is half the screen resolution, although it covers the full screen.
This seen, the solution is as follows:
first, create an offscreen image matching the screen resolution, not the JPanel.getSize() as suggested in dozens of double buffering articles.
then, draw in the offscreen image applying a magnifying transform, bigger than needed, in particular scaling by r = screen dimension / panel dimension ratio.
finally, copy a down scaled version of the offscreen image back into the screen, applying a shrinking factor of r (or scaling factor 1/r).
The solution implementation is split into two methods:
An ammended version of the initial paintComponent posted earlier,
a helper method getDPIFactor() explained afterwards.
The ammended paintComponent method follows:
public final void paintComponent(Graphics g) {
super.paintComponent(g);
double dpiFactor = getDPIFactor();
// --------------------------------------
//OffScreen painting
Graphics2D osg2D = getOffscreenGraphics();
setupGraphics(osg2D);
//Paint stuff bigger than needed
osg2D.setTransform(AffineTransform.getScaleInstance(dpiFactor, dpiFactor));
//Custom painting
performPainting(osg2D);
//Shrink offscreen buffer to screen.
((Graphics2D)g).drawImage(
osi,
AffineTransform.getScaleInstance(1.0/dpiFactor, 1.0/dpiFactor),
this);
// --------------------------------------
// OnScreen painting
Graphics2D gg = (Graphics2D)g;
setupGraphics(gg);
gg.drawString("Direct painting", 10, 35);
}
To complete the task, the screen resolution must be obtained.
A call to Toolkit.getDefaultToolkit().getScreenResolution() doesn't solve the problem, as it returns the size of a JPanel covering the whole screen. As seen above, this figure doesn't match the actual screen size in physical dots.
The way to get this datum is cleared by Sarge Bosch in this stackoverflow post.
I have adapted his code to implement the last part of the puzzle, getDPIFactor().
/*
* Adapted from Sarge Bosch post in StackOverflow.
* https://stackoverflow.com/questions/40399643/how-to-find-real-display-density-dpi-from-java-code
*/
private Double getDPIFactor() {
GraphicsDevice defaultScreenDevice =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice();
// on OS X, it would be CGraphicsDevice
if (defaultScreenDevice instanceof CGraphicsDevice) {
CGraphicsDevice device = (CGraphicsDevice) defaultScreenDevice;
// this is the missing correction factor.
// It's equal to 2 on HiDPI a.k.a. Retina displays
int scaleFactor = device.getScaleFactor();
// now we can compute the real DPI of the screen
return scaleFactor * (device.getXResolution() + device.getYResolution()) / 2
/ Toolkit.getDefaultToolkit().getScreenResolution();
} else
return 1.0;
}
This code solves the issue for Mac Retina displays, but I am affraid nowhere else, since CGraphicsDevice is an explicit mention to a proprietary implementation of GraphicsDevice.
I do not have other HDPI hardware with which to play around to have a chance to offer a wider solution.

How do I avoid "anti-aliasing effect" when creating a PGraphics object that's smaller than the window, then scale it up to the windows size?

When ran, the program displays a 3D sphere rendered in a P3D environment in the PGraphics object 'g', which is shown by taking the rendered PGraphics object and displaying it through the image() method in the main graphics context, which happens to be P2D.
The purpose of the program is to show how window size doesn't always correlate with render size. If you play an old Widows98 game in full screen, the game most likely will be rendered at 480p no matter what, so taking it into full screen just decreases the pixels per inch, plus making the image appear blurry. Which is fine, since fullscreen at 480p is preferred over windowed mode ( esp. if you're on 4K X_X )
the mouse's y position in the window changes the 3d camera's field of view, and the x position changes the rendering resolution of the P3D context used to display the sphere. Additionally, the P3D context is drawn in the main (P2D) context through the image() method, and is 'forcefully'. displayed at the size of the window. So if the P3D render resolution is smaller than the window, then it will start to look blurry and more pixelated, and if the render resolution is larger, you get a strange sharpening effect.
Now, my program works fine as it is, but. Another purpose of the program is shadowed by this issue, it's how the 'crispness' of the sphere fades as the render resolution decreases. You might say that it's clearly shown, but what I'm looking for is an image where there is no "anti-alias" effect going on. I want the image to preserve the pixels as the resolution gets smaller, so you can see the actual shape of the sphere at say, 50 x 50 pixels.
The noSmooth() method doesn't seem to work, and before you tell me to just do
g.loadPixels();
and then do a double for loop to draw the raw pixels to the 2d context. No, it's sloppy. I know that there must be some reason why this blurring is going on. I'm hoping that it's the image() method and that I should be using a different method or I should add another method before it to remove image blurring.
PGraphics g;
void setup(){
size(1000,1000,P2D);
frameRate(1000);
noSmooth();
}
void draw(){
background(200);
float res = map(mouseX,0,width,0.75,128);
if (res==0) {
res=1;
}
g = createGraphics((int)(width/res),(int)(height/res),P3D);
g.noSmooth(); // is this thing working?????
float cameraZ = ((height/2.0) / tan(PI*60.0/360.0));
g.beginDraw();
g.perspective(radians(map(mouseY,0,height,0.1,160)), width/height, cameraZ/10.0, cameraZ*10.0);
g.camera(g.width/2.0, g.height/2.0, (height/2.0) / tan(PI*30.0 / 180.0), g.width/2.0, g.height/2.0, 0, 0, 1, 0);
g.background(200);
g.translate(g.width/2 ,g.height/2);
g.sphere(100);
g.endDraw();
image(g, 0, 0, width, height); // this is where it all comes together
text("rendering resolution: "+g.width+" x "+g.height,0,14);
text("fps: "+frameRate,0,14*2);
}
Replace g.noSmooth() with ((PGraphicsOpenGL)g).textureSampling(2);
Credits go to Vallentin as I oddly enough had the same question with the P3D renderer
(Edit: This solution fixes the problem in the default renderer, but the OP is using the P2D renderer. The solution should be similar, so if somebody knows how to change the image interpolation mode in opengl, that's the answer.)
This is not really caused by anti-aliasing. It's caused by image scaling.
Also, it's much easier to help if you provide a MCVE, like this one:
PGraphics buffer;
void setup() {
size(1000, 1000);
buffer = createGraphics(100, 100);
buffer.noSmooth();
buffer.beginDraw();
buffer.background(255);
buffer.line(0, 0, width, height);
buffer.endDraw();
}
void draw() {
background(0);
image(buffer, 0, 0, mouseX, mouseY);
}
This code exhibits the same problem, but it's much easier to understand and work with.
Anyway, tracing through Processing's code, we can see that the image() function eventually calls the imageImpl() function in the PGraphics class here.
This function then draws your image using this code:
beginShape(QUADS);
texture(img);
vertex(x1, y1, u1, v1);
vertex(x1, y2, u1, v2);
vertex(x2, y2, u2, v2);
vertex(x2, y1, u2, v1);
endShape();
The endShape() function is then implemented in the renderer, specifically the PGraphicsJava2D class, which calls the drawShape() function here:
protected void drawShape(Shape s) {
if (fillGradient) {
g2.setPaint(fillGradientObject);
g2.fill(s);
} else if (fill) {
g2.setColor(fillColorObject);
g2.fill(s);
}
if (strokeGradient) {
g2.setPaint(strokeGradientObject);
g2.draw(s);
} else if (stroke) {
g2.setColor(strokeColorObject);
g2.draw(s);
}
}
Finally, that shows us that the Graphics2D.fill() function is being called, which is what actually draws your function.
The "problem" is that Graphics2D.fill() is scaling your image using an algorithm that causes some blurriness. We can consult the Java API and Google to figure out how to fix that though.
Specifically, this tutorial shows you how to set various rendering hints to change the scaling algorithm. We can use that in Processing like this:
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import processing.awt.PGraphicsJava2D;
PGraphics buffer;
void setup() {
size(1000, 1000);
buffer = createGraphics(100, 100);
buffer.noSmooth();
buffer.beginDraw();
buffer.background(255);
buffer.line(0, 0, width, height);
buffer.endDraw();
}
void draw() {
if (mousePressed) {
Graphics2D g2d = ((PGraphicsJava2D)g).g2;
g2d.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
}
background(0);
image(buffer, 0, 0, mouseX, mouseY);
}
First, we import the classes we're going to need. Then we get to the Graphics2D instance in the renderer, and finally we call its setRenderingHint() function. I wrapped it in an if(mousePressed) so you could easily see the difference. When you click the mouse, interpolation is set to nearest neighbor, and you no longer see the blurriness.
Also notice that my code uses the g variable that's inherited from the PApplet superclass, so you would have to change your g variable so it's no longer hiding it.

Drawing a gradient in Libgdx

Ok I have this code
#Override
public void render() {
// do not update game world when paused
if (!paused) {
// Update game world by the time that has passed
// since last render frame
worldController.update(Gdx.graphics.getDeltaTime());
}
// Sets the clear screen color to: Cornflower Blue
Gdx.gl.glClearColor(0x64/255.0f, 0x95/255.0f, 0xed/255.0f,
0xff/255.0f);
// Clears the screen
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
// Render game world to screen
worldRenderer.render();
}
And it draws a light blue background onto the screen. I am attempting to create a gradient that goes from a dark blue at the top, to a light blue towards the bottom. Is there a simple way to do this? I'm new to Libgdx, and OpenGL so i'm trying to learn from a book but I can't seem to find the answer to this one. I've heard of drawing a big square and having the vertices different colors, but I'm unsure of how to do this.
In libGDX, the ShapeRenderer object contains a drawRect() method that takes arguments for its position and size as well as four colors. Those colors are converted to a 4-corners gradient. If you want a vertical gradient, just make the top corners one color and the bottom corners another color. Something like this:
shapeRenderer.filledRect(x, y, width, height, lightBlue, lightBlue, darkBlue, darkBlue);
From the API for ShapeRenderer:
The 4 color parameters specify the color for the bottom left, bottom right, top right and top left corner of the rectangle, allowing you to create gradients.
It seems ShapeRenderer.filledRect method has been removed in late libGDX versions. Now the way to do this is as follows:
shapeRenderer.set(ShapeRenderer.ShapeType.Filled);
shapeRenderer.rect(
x,
y,
width,
height,
Color.DARK_GRAY,
Color.DARK_GRAY,
Color.LIGHT_GRAY,
Color.LIGHT_GRAY
);
The parameters for rect method work in the same way as those in filledRect used to do, like in Kevin Workman answer.
There are some further details worth bearing in mind before comitting to ShapeRenderer. I for one will be sticking with stretching and tinting Texture.
private Color topCol = new Color(0xd0000000);
private Color btmCol = new Color(0xd0000000);
#Override
public void render(float delta) {
...
batch.end(); //Must end your "regular" batch first.
myRect.setColor(Color.YELLOW); // Must be called, I don't see yellow, but nice to know.
myRect.begin(ShapeRenderer.ShapeType.Filled); //Everyone else was saying call `set`.
//Exception informed me I needed `begin`. Adding `set` after was a NOP.
myRect.rect(
10, 400,
//WORLD_H - 300, // WORLD_H assumed 1920. But ShapeRenderer uses actual pixels.
420,
300,
btmCol, btmCol, topCol, topCol
);
myRect.end();
I was hoping to change transparency dynamically as player health declines. The btmCol and topCol had no effect on transparency, hence I'll stick to Textures. Translating pixel space is no biggie, but this is much more than the proferred single or double line above.

Drawing a dot grid

I'm new to graphics programming. I'm trying to create a program that allows you to draw directed graphs. For a start I have managed to draw a set of rectangles (representing the nodes) and have made pan and zoom capabilities by overriding the paint method in Java.
This all seems to work reasonably well while there aren't too many nodes. My problem is when it comes to trying to draw a dot grid. I used a simple bit of test code at first that overlayed a dot grid using two nested for loops:
int iPanX = (int) panX;
int iPanY = (int) panY;
int a = this.figure.getWidth() - iPanX;
int b = this.figure.getHeight() - (int) iPanY;
for (int i = -iPanX; i < a; i += 10) {
for (int j = -iPanY; j < b; j += 10) {
g.drawLine(i, j, i, j);
}
}
This allows me to pan the grid but not zoom. However, the performance when panning is terrible! I've done a lot of searching but I feel that I must be missing something obvious because I can't find anything on the subject.
Any help or pointers would be greatly appreciated.
--Stephen
Use a BufferedImage for the dot grid. Initialize it once and later only paint the image instead of drawing the grid over and over.
private init(){
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
// then draw your grid into g
}
public void paint(Graphics g) {
g.drawImage(image, 0, 0, null);
// then draw the graphs
}
And zooming is easily achieved using this:
g.drawImage(image, 0, 0, null); // so you paint the grid at a 1:1 resolution
Graphics2D g2 = (Graphics2D) g;
g2.scale(zoom, zoom);
// then draw the rest into g2 instead of g
Drawing into the zoomed Graphics will lead to proportionally larger line width, etc.
I think re-drawing all your dots every time the mouse moves is going to give you performance problems. Perhaps you should look into taking a snapshot of the view as a bitmap and panning that around, redrawing the view 'properly' when the user releases the mouse button?

Categories

Resources