drawArc() rendering is inconsistent when the arc length changes - java

I need to draw an arc around a volume knob, the arc length represents the volume.
I tried with Graphics.drawArc() and Arc2D.Double, but the result is not good: the path of the arc is not exactly the same for different extent values.
public class ArcComponent extends JComponent
{
private ImageIcon knobImage = new ImageIcon(ArcComponent.class.getResource("Knob-w37.png"));
int arcLength = 220; // Degrees
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Paint knob image
knobImage.paintIcon(this, g2, 20, 20);
// Add arc
var arc = new Arc2D.Double(22, 22, 32, 32, 220 - arcLength, arcLength, Arc2D.OPEN);
g2.setColor(Color.CYAN);
g2.setStroke(new BasicStroke(2));
g2.draw(arc);
g2.dispose();
}
void shorter()
{
arcLength -= 5;
if (arcLength < 0)
{
arcLength = 260;
}
repaint();
}
void longer()
{
arcLength += 5;
if (arcLength > 260)
{
arcLength = 0;
}
repaint();
}
}
How could I ensure that the path of the arc is always the same?
EDIT: added knob image painting

A dirty trick: draw the complete circle and ovewrite the part that you don't need with a shape, same color of background.
Something like this:
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Shape circle=new Ellipse2D.Double(20, 20, 30, 30);
g2.setColor(Color.CYAN);
g2.setStroke(new BasicStroke(2f));
g2.draw(circle);
Shape arc=new Arc2D.Double(10,10,50,50,230,360-arcLength,Arc2D.PIE);
g2.setColor(getBackground());
g2.fill(arc);
g2.dispose();
}
Unfortunately this requires the arc to be drawn before any other thing in the same area, but you can probably deal with this.
EDIT: For the additional constraint, the inconsistent rendering is actually due to the stroke, so try creating a thick shape and fill it, example:
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
double thickness=8.0; //This replaces your stroke width
GeneralPath shape=new GeneralPath();
shape.append(new Arc2D.Double(20-thickness/2,20-thickness/2, 30+thickness, 30+thickness, 230, -arcLength, Arc2D.OPEN),false);
shape.append(new Arc2D.Double(20+thickness/2,20+thickness/2, 30-thickness, 30-thickness, 230-arcLength, arcLength, Arc2D.OPEN),true);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.CYAN);
g2.fill(shape);
g2.dispose();
}

Simply draw a full circle and clip on the arc shape (with some padding to ensure everything gets drawn).
Graphics2D g2 = (Graphics2D) g.create();
Ellipse2D circle = new Ellipse2D.Double(20, 20, 30, 30);
int d = 2;
Arc2D arc = new Arc2D.Double(20 - d, 20 - d, 30 + 2 * d, 30 + 2 * d, 230, -arcLength, Arc2D.PIE);
g2.setColor(Color.CYAN);
g2.setClip(arc);
g2.setStroke(new BasicStroke(2f));
g2.draw(circle);
knobImage.paintIcon(this, g, 20, 20);

Related

The angle of rotation of the object is wrong

I'm trying to rotate an object in java but i noticed that something is wrong.
When i rotate it of 180 degrees,i get a value of the angle of '90°',so in order to get an angle of 360 degrees i have to rotate it twice.
What's wrong?
0°,90°,180°
The code:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
if (rotate == true) {
ship.increaseDegress();
}
ship.draw(g);
}
Ship.java
public void increaseDegress() {
rotationAngle += 10;
if(rotationAngle>360) {
rotationAngle = 0;
}
}
public void draw(Graphics g) {
this.g = g;
Graphics2D g2 = (Graphics2D) g;
AffineTransform at = new AffineTransform();
Rectangle rect = this.getBounds();
at.rotate(Math.toRadians(rotationAngle), rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight() / 2);
g2.setColor(Color.BLUE);
g2.setTransform(at);
g2.draw(at.createTransformedShape(this));
}
So, let's take a quick look at your code...
public void draw(Graphics g) {
this.g = g;
Graphics2D g2 = (Graphics2D) g;
AffineTransform at = new AffineTransform();
Rectangle rect = this.getBounds();
at.rotate(Math.toRadians(rotationAngle), rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight() / 2);
g2.setColor(Color.BLUE);
g2.setTransform(at);
g2.draw(at.createTransformedShape(this));
}
So,
First you create AffineTransform, nice
You rotate the transformer, also nice
You apply the transformer to the Graphics context ... okay, there's a problem here, but let's move on
You apply the transformer to the shape!
So, on a single draw pass, you will rotate the shape by rotationAngle * 2! So, when the angle is 10°, the shape will be rendered at 20°, when it's 20°, it will rendered at 40°!
Okay, but there's another problem. Transformations applied to a Graphics context are compounding, this means, based on the available code, each time you call draw, the Graphics context is been rotated by rotationAngle. So if rotationAngle is 10°
On pass #1, the shape will be rotated to 20°
On pass #2, the shape will be rotated to 30°
On pass #3, the shape will be rotated to 40°
... so on and so forth ...
So, what's the answer?
When ever I pass a Graphics context off to some other method, I first create a copy it, because I don't trust anybody!
Graphics gCopy = g.create();
shape.draw(gCopy);
gCopy.dispose();
This ensures that the state of the Graphics context is returned to the state it was before I called draw, that will get rid of the compounding transformation.
The other solution is, don't transform the Graphics context. If you're transforming the shape, what's the point?
public void draw(Graphics g) {
this.g = g;
Graphics2D g2 = (Graphics2D) g;
AffineTransform at = new AffineTransform();
Rectangle rect = this.getBounds();
at.rotate(Math.toRadians(rotationAngle), rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight() / 2);
g2.setColor(Color.BLUE);
g2.draw(at.createTransformedShape(this));
}

How to hide JToolTip background when exceeding borders?

I've created this custom JToolTip for my application. When the tooltip is entirely diplayed inside a JFrame, no background is visible (expected), but when the tooltip is displayed outside the JFrame, the background will be visible. How can I have it removed either way?
I've tried setBackground(new Color(255, 255, 255, 0)); with the '0' alpha value to make sure the background is transparent, but that didn't do the trick.
The tooltip inside the frame, as expected:
The tooltip exceeding the JFrame, with the unwanted background:
The custom JTooltip:
public class DefaultToolTip extends JToolTip {
public DefaultToolTip() {
setOpaque(false);
setPreferredSize(new Dimension(275, 30));
setBackground(new Color(255, 255, 255, 0));
}
#Override
public void addNotify() {
super.addNotify();
setOpaque(false);
Component parent = this.getParent();
if (parent != null) {
if (parent instanceof JComponent) {
JComponent jparent = (JComponent) parent;
jparent.setOpaque(false);
}
}
}
#Override
public void paint(Graphics g) {
String text = getComponent().getToolTipText();
addNotify();
Graphics2D g2d = drawComponent(g);
drawText(text, g2d);
g2d.dispose();
}
private void drawText(String text, Graphics2D g2d) {
//Draw the text
int cHeight = getComponent().getHeight();
FontMetrics fm = g2d.getFontMetrics();
g2d.setColor(Color.WHITE);
if (cHeight > getHeight())
g2d.drawString(text, (getWidth() - fm.stringWidth(text)) / 2, (getHeight() + fm.getAscent()) / 2 + 2);
else
g2d.drawString(text, (getWidth() - fm.stringWidth(text)) / 2, (cHeight + fm.getAscent()) / 2 + 2);
}
private Graphics2D drawComponent(Graphics g) {
//Create a round rectangle
Shape round = new RoundRectangle2D.Float(0, 8, getWidth(), getHeight(), 8, 8);
//Draw the background
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.GRAY);
g2d.fill(round);
//Draw the left triangle
Point p1 = new Point(getWidth() / 2, getHeight() - 30);
Point p2 = new Point(getWidth() / 2 + 8, getHeight() - 20);
Point p3 = new Point(getWidth() / 2 - 8, getHeight() - 20);
int[] xs = {p1.x, p2.x, p3.x};
int[] ys = {p1.y, p2.y, p3.y};
Polygon triangle = new Polygon(xs, ys, xs.length);
g2d.fillPolygon(triangle);
return g2d;
}
}
Solution
A few things have changed to have the tooltip behave as expected. the paint method has been replaced by the paintComponent method, the addNotify call was removed, the method updated to fetch the window of the component and to give it a transparent background. setBorder(BorderFactory.createEmptyBorder()); was also needed to remove the components default border.
public class DefaultToolTip extends JToolTip {
public DefaultToolTip() {
setOpaque(false);
setPreferredSize(new Dimension(275, 30));
setBackground(new Color(255, 255, 255, 0));
setBorder(BorderFactory.createEmptyBorder());
}
#Override
public void addNotify() {
super.addNotify();
setOpaque(false);
Component parent = this.getParent();
if (parent != null) {
if (parent instanceof JComponent) {
JComponent jparent = (JComponent) parent;
jparent.setOpaque(false);
}
}
Window window = SwingUtilities.windowForComponent(this);
try {
window.setBackground(new Color(255, 255, 255, 0));
} catch (IllegalComponentStateException e) {
//Do nothing
}
}
#Override
public void paintComponent(Graphics g) {
//super.paintComponent(g);
String text = getComponent().getToolTipText();
Graphics2D g2d = drawComponent(g);
drawText(text, g2d);
g2d.dispose();
}
private void drawText(String text, Graphics2D g2d) {
//Draw the text
int cHeight = getComponent().getHeight();
FontMetrics fm = g2d.getFontMetrics();
g2d.setColor(Color.WHITE);
if (cHeight > getHeight())
g2d.drawString(text, (getWidth() - fm.stringWidth(text)) / 2, (getHeight() + fm.getAscent()) / 2 + 2);
else
g2d.drawString(text, (getWidth() - fm.stringWidth(text)) / 2, (cHeight + fm.getAscent()) / 2 + 2);
}
private Graphics2D drawComponent(Graphics g) {
//Create a round rectangle
Shape round = new RoundRectangle2D.Float(0, 8, getWidth(), getHeight(), 8, 8);
//Draw the background
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.GRAY);
g2d.fill(round);
//Draw the left triangle
Point p1 = new Point(getWidth() / 2, getHeight() - 30);
Point p2 = new Point(getWidth() / 2 + 8, getHeight() - 20);
Point p3 = new Point(getWidth() / 2 - 8, getHeight() - 20);
int[] xs = {p1.x, p2.x, p3.x};
int[] ys = {p1.y, p2.y, p3.y};
Polygon triangle = new Polygon(xs, ys, xs.length);
g2d.fillPolygon(triangle);
return g2d;
}
}
A note however, super.paintComponent(g) was commented out, since it would draw text another time.
Don't know if any of these will help but:
Don't override paint(...). Custom painting is done by overriding paintComponent(...).
Invoke super.paintComponent(...) as the first statement
Don't invoke addNotify() in a painting method. A painting method is for painting only.
with the '0' alpha value to make sure the background is transparent,
Swing components don't know how to handle transparent backgrounds. Just make the component non-opaque.
When the tooltip overlaps the component. The tooltip is actually added to a JWindow before it is displayed. So in your addNotify() logic, you can search for the window and make it transparent.
Check out:
Window window = SwingUtilities.windowForComponent(...);

Java Animation Rotation

I have very little experience with Java, and I am an amateur programmer. So mind my vocabulary.
I want to be able to stick a static rectangle on top of a rotating rectangle.
So far when I try to add another object it spins with the other image. I have tried setting the rotation to zero but that doesn't seem to work. I have also tried to create another class that draws components separately and added them to the frame using frame.add. I have also tried creating another part to the Draw class that has no effect on the GUI. Here is my current Draw class. Any help is appreciated.
class DrawRectangle extends JPanel {
#Override
public void paintComponent(Graphics g) {
int h = this.getHeight();
int w = this.getWidth();
Graphics2D g2 = (Graphics2D) g;
//draw background
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, w, h);
//draw roatiing rectangle
g2.setColor(Color.CYAN);
Rectangle rRec = new Rectangle(w / 4, h / 4, 2 * w / 4, 2 * h / 4);
double wr = rRec.getX() + rRec.getWidth() / 2;
double hr = rRec.getY() + rRec.getHeight() / 2;
g2.rotate(Math.toRadians(count), wr, hr);
g2.fill(rRec);
g2.fillRect(w / 3, h / 3, 2 * w / 3, 2 * h / 3);
}
public void paintComponent2(Graphics g) {
int h = this.getHeight();
int w = this.getWidth();
Graphics2D g2 = (Graphics2D) g;
}
}
So far when I try to add another object it spins with the other image.
Create a separate Graphics object to do the rotation so you don't affect the properties of the Graphics object passed into the painting method:
//Graphics2D g2 = (Graphics2D) g;
Graphics2D g2 = (Graphics2D)g.create();
// painting code
g2.dispose();
Move your g2.fill(rRec); BEFORE the rotate call, and it should work (I just tested it out).
This way, you will draw your static rectangle before the rotation, perform the rotation, THEN draw your second rectangle. Assuming your count variable is incremented somewhere, it should show the second rectangle being rotated.

Current Coordinate of bufferedImage Object in java

Is there anyway to find the current position of a buffered image on jpanel?
I draw an image on buffer, named it currentImage. Now I want to move it around a Panel by using affine transform.
To do translation by using mouse, I have to know the current position, new position and update the new position.
But I have trouble in getting the current position of the BufferedImage. There is no method in the IDE's suggestion working.
Can anyone give me an idea how to do this ?? Thanks!
EDIT
this is my draw and paintComponent method:
private void draw(){
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC);
int w = this.getWidth(); int h = this.getHeight();
if (currentImage == null || width != w || height != h){
width = w;
height = h;
currentImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
graphics = currentImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
drawClearAndGradedArea(graphics);
drawActualRunway(graphics, width, height, width, height);
drawLeftToRight(graphics);
drawRightToLeft(graphics);
drawCentralLine(graphics);
graphics.setComposite(ac);
}
}
paintComponent()
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.clearRect(0,0, this.getWidth(), this.getHeight());
RenderingHints rh = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHints(rh);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
/*
* main components on panel
*/
this.draw();
this.animation();
g2d.drawImage(currentImage, transform, this);
drawNote(g2d);
g2d.dispose();
}

Draw rectangle border thickness

Is it possible to do draw a rectangle with a given border thickness in an easy way?
If you are drawing on a Graphics2D object, you can use the setStroke() method:
Graphics2D g2;
double thickness = 2;
Stroke oldStroke = g2.getStroke();
g2.setStroke(new BasicStroke(thickness));
g2.drawRect(x, y, width, height);
g2.setStroke(oldStroke);
If this is being done on a Swing component and you are being passed a Graphics object, you can downcast it to a Graphics2D.
Graphics2D g2 = (Graphics2D) g;
Here's how to do this : Border with colored line with thickness 5.
Border linebor = BorderFactory.createLineBorder(new Color(0xAD85FF), 5);
**Tested code with buffered image with different thickness values**:
Graphics2D g = bufferedImage.createGraphics();
int height = //image height
int width = //image height
int borderWidth = //border thickness
int borderControl = 1;
//set border color
g.setColor(Color.BLACK);
//set border thickness
g.setStroke(new BasicStroke(borderWidth));
//to fix issue for even numbers
if(borderWidth%2 == 0){
borderControl = 0;
}
g.drawLine(0, 0, 0, height);
g.drawLine(0, 0, width, 0);
g.drawLine(0, height – borderControl, width, height – borderControl);
g.drawLine(width – borderControl, height – borderControl, width – borderControl, 0);

Categories

Resources