I am jumping back into an old bunch of code and my Java is very rough. Please be kind.
Problem: I have an application that draws on the canvas. The placement of the screen objects works well. Even Text attached to other objects. However when I place a Text object on the canvas the scale of the canvas halves. I have fiddled off and on for months and can't seem to find the resolution. Any advice would be helpful.
Below is the code to draw the text on screen it is in a class Visualise2D with the other drawing method. All other objects use the same scale etc. This only occurred since I upgraded to Java 15, last java I used was java 8 and it worked fine.
//TEXT
public void paintText(Graphics2D t2D, Color color,Text t, Font font, double bearing, Rectangle2D bounds, double scale, boolean selected, boolean isRotationTool, double enhance) {
//Draws text where ever the user clicks
FontMetrics fm = t2D.getFontMetrics();
t2D.setFont(default_FONT);
AffineTransform at = new AffineTransform();
int x = (int) ((t.getX() - bounds.getX())*(scale));
int y = (int) ((bounds.getHeight() + bounds.getY() - t.getY()) *(scale));
at.setToRotation(Math.toRadians(bearing+270), x,y);
FontRenderContext frc = t2D.getFontRenderContext();
TextLayout layout = new TextLayout(t.getText(), t2D.getFont(), frc);
t2D.setTransform(at);
if (!(selected)) {
t2D.setColor(color);
}
else
{
//pixel size of the circle
float size = 20;//(float) (fm.stringWidth(t.getText())*0.5);
t2D.setColor(p_selectedObjectsColour);
t2D.setStroke(LINE_100);
//Highlight and origin indicator when selected - START
t2D.setColor(p_selectedObjectsColour);
t2D.draw(new Ellipse2D.Double((((t.getX() - bounds.getX())*scale) - size), (((bounds.getHeight() + bounds.getY() - t.getY())*scale) - size), (size*2), (size*2)));
if(isRotationTool){
t2D.drawString(" : \uu27f3 "+dec1P.format(bearing)+"\u00b0",(float) (x + (fm.stringWidth(t.getText())*1.05)),y);
}
t2D.setColor(p_selectedObjectsColour);
t2D.draw(new Rectangle2D.Double(
(t.getX() - bounds.getX())* scale,
((bounds.getHeight() + bounds.getY() - t.getY())*scale)-fm.getStringBounds(t.toString(), t2D).getHeight(),
t.getBounds().getWidth(),
t.getBounds().getHeight()
));
t2D.drawLine((int) (((t.getX() - bounds.getX())) * scale),
(int)(((bounds.getHeight() + bounds.getY())-(t.getY()))*scale),
(int)(((t.getX())- bounds.getX())*scale)+fm.stringWidth(t.getText()),
(int)(((bounds.getHeight() + bounds.getY())-(t.getY()))*scale));
}
t2D.setColor(color);
//t2D.drawString(t.getText(), x, y);
layout.draw(t2D, x, y);
at.setToRotation(0, x, y);
t2D.setTransform(at);
//This error is to remind you that the Affine transform is not working and the text is in the collection still after it is moved.
}
Below are two images that describe the issue.
Image 1 is the Normal View at Normal Scale
Image 2 is the Alter after Text addition Scale.
If the text is deleted the Scale returns to the first image.
Normal Scale:
Added Text Scale Changes:
Related
I'm working on something that involves clicking specific points on a buffered image in a JPanel. I had issues with this earlier in the project (affine transform translation not working properly), but nothing I found fixed it so I decided I would come back to it later.
I'm not entirely sure how to trouble shoot it since I'm a novice, but I think it's reading my y coordinates too low. I made a mouse input listener that tracks the number of times the user has clicked and gets the mouse pointer's location for functions I haven't made yet. For testing I have it output the coordinates and number of clicks then make a circle centered where the mouse clicks.
#Override
public void mouseClicked(MouseEvent e) {
Point mouseCursor = MouseInfo.getPointerInfo().getLocation();
panel.drawCenteredCircle(mouseCursor.getX(), mouseCursor.getY(), 100);
System.out.println(String.valueOf(mouseCursor));
System.out.println(String.valueOf(clickCount));
clickCount++;
}
Here is drawCenteredCircle in my custom panel class:
public void drawCenteredCircle(double x, double y, int r) {
imgG2 = image.createGraphics();
imgG2.setPaint(Color.RED);
x = (x-r/2.0);
y = (y-r/2.0);
imgG2.fillOval((int)Math.round(x), (int)Math.round(y), r, r);
this.repaint();
imgG2.dispose();
}
I tried taking a screenshot to show what happens, but the circle properly centers on the x coordinate, but not the y coordinate. Instead it draws the circle with the pointer at the top center edge.
I overrided the paintComponent of my JPanel to implement a zoom feature:
#Override
protected void paintComponent(Graphics g) {
//Implimenting zoom
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
/*Supposed to counter the movement from the scale, not working properly
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
double x = (w - scale * imageWidth)/2;
double y = (h - scale * imageHeight)/2;*/
AffineTransform at = new AffineTransform()/*.getTranslateInstance(x, y) */;
at.scale(scale, scale);
g2.drawRenderedImage(image, at);
//g2.dispose(); I was told to put this, but I'm not sure if it's necessary or what it does entirely
}
My confused notes are because I got this code from an example someone made and, as I said earlier, the affine translation wasn't working (I took the actual translation out). They're irrelevant to the question.
The reason I put this is because I initially had code that was meant to fit the image to the screen/frame depending if it was fullscreen or not:
int x = image.getWidth();
int y = image.getHeight();
double frameW = frame.getBounds().getWidth();
double frameH = frame.getBounds().getHeight();
//Rectangle winSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
double screenW = Toolkit.getDefaultToolkit().getScreenSize().getWidth();
double screenH = Toolkit.getDefaultToolkit().getScreenSize().getHeight();
if (!isFullScreen) {
if (x/y > frameW/frameH) {
scale = frameW/x;
} else {
scale = frameH/y;
}
} else {
if (x/y > screenW/screenH) {
scale = screenW/x;
} else {
scale = screenH/y;
}
}
It uses my zoom function which scales the image with the double "scale." I noticed that when I zoomed in or out, it would change where the dots would appear relative to the pointer. It wasn't until I removed the code for the image to start fitted to the window and had it start at 100% that I received the result of the pointer being at the top center of the circle.
I also tried removing the part that's supposed to center the circle and the result was the pointer being on the left side and having a gap between it and the top of the circle.
Sorry if this is too much stuff. I'm pretty novice and learned just as much about java (the only coding language I know) working on this project as I knew when I first started it. I'm not sure what information I have that could be helpful in this, so I just threw in everything I thought could help. I appreciate any help, even irrelevant to my question.
My app, which uses ZXing to scan QR codes, can't read a QR Code unless the phone is VERY far away from the code (see picture, 6-7+ inches away and still not reading). The code is centered and well within the framingRect, but the camera seems to only be picking up result points from the top 2 positioning squares. I have increased the size of the framing rectangle through some code which I found here, which does yield a much better result.
Code: (replaces getFramingRect from zxing.camera.cameramanager.Java)
public Rect getFramingRect() {
if (framingRect == null) {
if (camera == null) {
return null;
}
Point screenResolution = configManager.getScreenResolution();
int width = screenResolution.x * 3 / 4;
int height = screenResolution.y * 3 / 4;
Log.v("Framing rect is : ", "width is "+width+" and height is "+height);
int leftOffset = (screenResolution.x - width) / 2;
int topOffset = (screenResolution.y - height) / 2;
framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
Log.d(TAG, "Calculated framing rect: " + framingRect);
}
return framingRect;
}
For reasons beyond my comprehension, with this new larger framing rectangle, codes can be read as soon as they fit inside the rect width, whereas previously the code had to occupy a small region at the center of the rect (see pic).
My Question:
How can I make code scan as soon as it is within the bounds of the framing rect, without increasing the size of the rectangle? Why Is this happening?
Increase the width and height to 4/4 (just leave them as the screen resolution) and then change the framing rect visual representation to make it seem as if the scanner is only inside that. Worked for my app.
I am drawing a series of Body objects on a Slick2D screen from a dyn4j World (World.getBodies()) and getting a very low frame rate, i.e. 6-10 FPS. Here is the part of the render loop in Slick2D that draws the dyn4j bodies:
for (int i = 0; i < space.getBodies().size(); i++) { // Iterate through World bodies
if (space.getBodies().get(i) instanceof lander.ShipFragmentPool.Fragment) {
lander.ShipFragmentPool.Fragment fragment = (lander.ShipFragmentPool.Fragment) space.getBodies().get(i);
float x = (float) (fragment.getWorldCenter().x * scale); // Extract fragementco-ordinates
float y = (float) (fragment.getWorldCenter().y * scale);
float tempX = x % bWIDTH;
if (tempX > bufferX // Only draw fragments that are on screen
&& tempX < bufferX + sWIDTH) {
float radius = (float) (fragment.getRadius() * scale); // Get radius
float diameter = (float) (radius * 2); // And diameter
float drawX = (tempX - bufferX) - radius; // Co-ordinates to draw on screen
float drawY = ((bHEIGHT - bufferY) - y) - radius;
g.setColor(Color.white);
g.drawOval(drawX, drawY, diameter, diameter);
}
}
}
I am guessing that the main issue that I am iterating over 36 bodies in the World object and having to test for object type in each case. The returned Body objects are of different types and so I have to test each body to see if it an instance of the type I want to render (ship fragments after an explosion). Is there a better (i.e. faster) way to construct this loop? Or is there another issue that I am missing?
Note:
in the example above bufferY is fixed (the screen doesn't move when the explosion is happening)
bWIDTH/bHEIGHT are the width and height of the background image, sWIDTH is the width of the screen
I am iterating over a set of Body objects in dyn4J because I want to the individual explosion fragments to interact with the landscape, e.g. bounce, slide, etc.
the Body objects have a single Fixture and this is a Circle
Thanks
Turns out it isn't the graphics drawing routine... it is the dyn4j iteration of the fragments during the World.update() method. Changing the fragment Fixtures to sensors (Fixture.setSensor(true)) so they detect collisions, but don't respond to them, resolves the performance issues and I get about 130FPS if I run the game unchecked. Shame, given that it's only 36 objects. I didn't think that would be too many for dyn4j to handle... I suspect I am missing some setting somewhere to make this work effectively. :/
ps. the for (Object spaceBody : spaceBodies) in my previous comment does work. Not sure what happened there, but a clean and build sorted it out.
I'm making a game in slick2D and i wanted the game to adjust resolutions acording to the screen being used, so when i initialize my AppGameContainer i call it as the following shows
AppGameContainer appgc;
try{
appgc = new AppGameContainer(appgc.getScreenWidth(), appgc.getScreenHeight(), true);
}catch....
So my question is when using this method will i have to draw objects and do collision detection using appgc.getScreenWidth() and appgc.getScreenWidth() for example if i wanted to draw a square in the middle of the screen would i have to do it like so ?
g.fillRect(appgc.getScreenWidth, appgc.getScreenHeight(), 50, 50);
or is there some easier way around this, as this could get confusing with collision later on as opposed to just an X and a Y axis?
thanks for any helpful answers in advance.
If you want to draw a square in the middle of the screen using fillRect you have to do that :
int square_width = 50 ;
int = 50 ;
g.fillRect((appgc.getScreenWidth+square_width)/2, (appgc.getScreenHeight+square_height)/2, square_width, square_height);
Indeed there is the function :
fillRect(float x1, float y1, float width, float height)
x1 - The x coordinate of the top left corner
y1 - The y coordinate of the top left corner
width - The width of the rectangle to fill
height - The height of the rectangle to fill
I know it's a simple concept but I'm struggling with the font metrics. Centering horizontally isn't too hard but vertically seems a bit difficult.
I've tried using the FontMetrics getAscent, getLeading, getXXXX methods in various combinations but no matter what I've tried the text is always off by a few pixels. Is there a way to measure the exact height of the text so that it is exactly centered.
Note, you do need to consider precisely what you mean by vertical centering.
Fonts are rendered on a baseline, running along the bottom of the text. The vertical space is allocated as follows:
---
^
| leading
|
--
^ Y Y
| Y Y
| Y Y
| ascent Y y y
| Y y y
| Y y y
-- baseline ______Y________y_________
| y
v descent yy
--
The leading is simply the font's recommended space between lines. For the sake of centering vertically between two points, you should ignore leading (it's ledding, BTW, not leeding; in general typography it is/was the lead spacing inserted between lines in a printing plate).
So for centering the text ascenders and descenders, you want the
baseline=(top+((bottom+1-top)/2) - ((ascent + descent)/2) + ascent;
Without the final "+ ascent", you have the position for the top of the font; therefore adding the ascent goes from the top to the baseline.
Also, note that the font height should include leading, but some fonts don't include it, and due to rounding differences, the font height may not exactly equal (leading + ascent + descent).
I found a recipe here.
The crucial methods seem to be getStringBounds() and getAscent()
// Find the size of string s in font f in the current Graphics context g.
FontMetrics fm = g.getFontMetrics(f);
java.awt.geom.Rectangle2D rect = fm.getStringBounds(s, g);
int textHeight = (int)(rect.getHeight());
int textWidth = (int)(rect.getWidth());
int panelHeight= this.getHeight();
int panelWidth = this.getWidth();
// Center text horizontally and vertically
int x = (panelWidth - textWidth) / 2;
int y = (panelHeight - textHeight) / 2 + fm.getAscent();
g.drawString(s, x, y); // Draw the string.
(note: above code is covered by the MIT License as noted on the page.)
Not sure this helps, but drawString(s, x, y) sets the baseline of the text at y.
I was working with doing some vertical centering and couldn't get the text to look right until I noticed that behavior mentioned in the docs. I was assuming the bottom of the font was at y.
For me, the fix was to subtract fm.getDescent() from the y-coordinate.
Another option is the getBounds method from the TextLayout class.
Font f;
// code to create f
String TITLE = "Text to center in a panel.";
FontRenderContext context = g2.getFontRenderContext();
TextLayout txt = new TextLayout(TITLE, f, context);
Rectangle2D bounds = txt.getBounds();
int xString = (int) ((getWidth() - bounds.getWidth()) / 2.0 );
int yString = (int) ((getHeight() + bounds.getHeight()) / 2.0);
// g2 is the graphics object
g2.setFont(f);
g2.drawString(TITLE, xString, yString);