AffineTransform without transforming Stroke? - java

When using the Graphics2D scale() function with two different parameters (scaling by different ratios in x- and y-direction), everything drawn later on this Graphics2D object is scaled too. This has the strange effect that lines drawn in one direction are thicker than those in another direction. The following program produces this effect, it shows this window:
public class StrokeExample extends JPanel {
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
g.setStroke(new BasicStroke(0.2f));
int height = getHeight();
int width = getWidth();
g.scale(width/7.0, height/4.0);
g.setColor(Color.BLACK);
g.draw(new Rectangle( 2, 1, 4, 2));
}
public static void main(String[] params) {
EventQueue.invokeLater(new Runnable(){public void run() {
StrokeExample example = new StrokeExample();
JFrame f = new JFrame("StrokeExample");
f.setSize(100, 300);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(example);
f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
f.setVisible(true);
}});
}
}
I'm using this coordinate transform to avoid having to manually transform my application model coordinates (the (2,1, 2,4) in this example) to screen (or component) pixel coordinates, but I don't want this stroke distortion. In other words, I want to have all lines the same width, independent of current x- and y-scale-factors.
I know what produces this effect (the Stroke object creates a stroked shape of the rectangle to be painted in user coordinates, which then are translated to screen coordinates), but I'm not sure on how to solve this.
Should I create a new Stroke implementation which strokes Shapes differently in X- and Y-direction (thereby undoing the distortion here)? (Or does anyone already knows such an implementation?)
Should I transform my shapes to screen coordinates and stroke there?
Any other (better) ideas?

Turns out my question was not so horrible difficult, and that my two ideas given in the question are actually the same idea. Here is a TransformedStroke class which implements a distorted Stroke by transforming the Shape.
import java.awt.*;
import java.awt.geom.*;
/**
* A implementation of {#link Stroke} which transforms another Stroke
* with an {#link AffineTransform} before stroking with it.
*
* This class is immutable as long as the underlying stroke is
* immutable.
*/
public class TransformedStroke
implements Stroke
{
/**
* To make this serializable without problems.
*/
private static final long serialVersionUID = 1;
/**
* the AffineTransform used to transform the shape before stroking.
*/
private AffineTransform transform;
/**
* The inverse of {#link #transform}, used to transform
* back after stroking.
*/
private AffineTransform inverse;
/**
* Our base stroke.
*/
private Stroke stroke;
/**
* Creates a TransformedStroke based on another Stroke
* and an AffineTransform.
*/
public TransformedStroke(Stroke base, AffineTransform at)
throws NoninvertibleTransformException
{
this.transform = new AffineTransform(at);
this.inverse = transform.createInverse();
this.stroke = base;
}
/**
* Strokes the given Shape with this stroke, creating an outline.
*
* This outline is distorted by our AffineTransform relative to the
* outline which would be given by the base stroke, but only in terms
* of scaling (i.e. thickness of the lines), as translation and rotation
* are undone after the stroking.
*/
public Shape createStrokedShape(Shape s) {
Shape sTrans = transform.createTransformedShape(s);
Shape sTransStroked = stroke.createStrokedShape(sTrans);
Shape sStroked = inverse.createTransformedShape(sTransStroked);
return sStroked;
}
}
My paint-method using it then looks like this:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
int height = getHeight();
int width = getWidth();
g.scale(width/4.0, height/7.0);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
g.getTransform()));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
Then my window looks like this:
I'm quite content with this, but if someone has more ideas, feel free to answer nevertheless.
Attention: This g.getTransform() is returning the complete transformation of g relative to the device space, not only the transformation applied after the .create(). So, if someone did some scaling before giving the Graphics to my component, this would still draw with a 2-device-pixel width stroke, not 2 pixels of the grapics given to my method. If this would be a problem, use it like this:
public void paintComponent(Graphics context) {
super.paintComponent(context);
Graphics2D g = (Graphics2D)context.create();
AffineTransform trans = new AffineTransform();
int height = getHeight();
int width = getWidth();
trans.scale(width/4.0, height/7.0);
g.transform(trans);
try {
g.setStroke(new TransformedStroke(new BasicStroke(2f),
trans));
}
catch(NoninvertibleTransformException ex) {
// should not occur if width and height > 0
ex.printStackTrace();
}
g.setColor(Color.BLACK);
g.draw(new Rectangle( 1, 2, 2, 4));
}
In Swing normally your Graphics given to the paintComponent is only translated (so (0,0) is the upper left corner of your component), not scaled, so there is no difference.

There is a simpler and less 'hacky' solution than the original TransformedStroke answer.
I got the idea when I read how the rendering pipeline works:
(from http://docs.oracle.com/javase/7/docs/technotes/guides/2d/spec/j2d-awt.html)
If the Shape is to be stroked, the Stroke attribute in the Graphics2D context is used to generate a new Shape that encompasses the stroked path.
The coordinates of the Shape’s path are transformed from user space into device space according to the transform attribute in the Graphics2D context.
The Shape’s path is clipped using the clip attribute in the Graphics2D context.
The remaining Shape, if any, is filled using the Paint and Composite attributes in the Graphics2D context.
What you, and I, ideally seek is a way to swap the first two steps.
If you look closely at the second step, TransformedStroke already contains part of the solution.
Shape sTrans = transform.createTransformedShape(s);
solution
In stead of:
g.scale(...), g.transform(...), whatever,
g.draw(new Rectangle( 1, 2, 2, 4));
Or, using TransformedStroke:
g.setStroke(new TransformedStroke(new BasicStroke(2f), g.getTransform());
g.draw(new Rectangle( 1, 2, 2, 4));
I propose you do:
transform =whatever,
g.draw(transform.createTransformedShape(new Rectangle( 1, 2, 2, 4));
Don't transform g anymore. Ever. Transform the shapes instead, using a transform that you make and modify yourself.
discussion
TransformedStroke feels more like a 'hack' than a way the authors of Stroke meant the interface to be used. It also requires an extra class.
This solution keeps a separate Transform around and modifies the Shape instead of transforming the Graphics object. This is however in no way a hack, because I'm not abusing existing functionality but using API functionality exactly how it's meant to be used. I'm just using the more explicit parts of the API instead of the 'shortcut'/'convenience' methods of the API (g.scale() etc.).
Performance-wise, this solution can only be more efficient. Effectively one step is now skipped. In the original solution, TransformedStroke transforms the shape twice and strokes the shape once. This solution transforms the shape explicitly and the *current* stroke strokes the shape once.

Have you just tried to make the int x and int y on the application bigger like int x = 500 int y = 900??? Also my suggestion is that with out rewritten the whole code is to implement where the recs are thicker when the app is closer together more like doubling the rectangle on the top and the bottom but when the app is extended the recs on the top and bottom go back to normal...

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.

Can I apply 2D transforms to shapes on a JavaFX 8 Canvas?

I'm porting a class that that was previously done in Swing to JavaFX 8. It displays a UI element that looks like an analog electric voltage meter with a half circle surrounded by a collection of "tic marks" at regular intervals. In the Swing version the class was an extension of JPanel and the tic marks were drawn in paintComponent(Graphics g) as follows:
private Line2D ticLine = new Line2D.Float(0, LINE_ROOT_Y, TIC_LENGTH, LINE_ROOT_Y);
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// Draw tic marks
if (ticCount > 0)
{
g2.draw(ticLine); // First tic
AffineTransform ticTrans = new AffineTransform();
// Draw all additional tics rotated around half circle
for (int i = 1; i < ticCount; i++)
{
ticTrans.rotate(Math.toRadians(ticGap),
METER_MIDDLE, METER_BASE_Y);
g2.draw(ticTrans.createTransformedShape(ticLine));
}
}
}
This worked fine.
Now with JavaFX I'm using a class extending VBox. It contains 2 stacked Canvas objects. One of which will draw the static elements like the half circle and tic marks, and the other for the regularly moving meter line. On that first Canvas I was hoping to use a similar loop as I did in the Swing version to easily redraw the first tic mark in a ticCount # of additional positions around the half circle. So I tried the following which compiled and ran but only drew that first tic mark:
// Called from the constructor:
MeterGC = MeterCanvas.getGraphicsContext2D();
Line ticLine = new Line(0, LINE_ROOT_Y, TIC_LENGTH, LINE_ROOT_Y);
// Draw tic marks
if (ticCount > 1)
{
MeterGC.setStroke(Color.GRAY);
MeterGC.setLineWidth(BASIC_LINE_WIDTH);
MeterGC.strokeLine(ticLine.getStartX(), ticLine.getStartY(),
ticLine.getEndX(), ticLine.getEndY());
Rotate ticTrans = new Rotate(Math.toRadians(ticGap), METER_MIDDLE, METER_BASE_Y);
for (int i = 1; i < ticCount; i++)
{
ticLine.getTransforms().add(ticTrans);
MeterGC.strokeLine(ticLine.getStartX(), ticLine.getStartY(),
ticLine.getEndX(), ticLine.getEndY());
}
}
It may be that trying to Transform a Shape object like this only works when drawing to a scene and not on a Canvas. Or that I have to do something after the "ticLine.getTransforms().add(ticTrans)" line to get them to apply to the line. Am I at least close? Or is there a much better way to do what I'm trying here?
What you are doing wrong
In your sample code you are applying the transform to a Line object (which you never display).
How to fix it
You need to set the transform on the canvas GraphicsContext before you stroke the line on the canvas.
Sample code
For an example, see:
How to draw image rotated on JavaFX Canvas?
/**
* Sets the transform for the GraphicsContext to rotate around a pivot point.
*
* #param gc the graphics context the transform to applied to.
* #param angle the angle of rotation.
* #param px the x pivot co-ordinate for the rotation (in canvas co-ordinates).
* #param py the y pivot co-ordinate for the rotation (in canvas co-ordinates).
*/
private void rotate(GraphicsContext gc, double angle, double px, double py) {
Rotate r = new Rotate(angle, px, py);
gc.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy());
}
This is my revised for-loop and it works perfectly. Noticing that converting the angle in Rotate to radians is no longer necessary in JavaFX was also key in getting it to work.
for (int i = 1; i < ticCount; i++)
{
Rotate ticTrans = new Rotate(ticGap * i, METER_MIDDLE, METER_BASE_Y);
MeterGC.setTransform(ticTrans.getMxx(), ticTrans.getMyx(), ticTrans.getMxy(),
ticTrans.getMyy(), ticTrans.getTx(), ticTrans.getTy());
MeterGC.strokeLine(ticLine.getStartX(), ticLine.getStartY(),
ticLine.getEndX(), ticLine.getEndY());
}

JFreeChart Boxplot Outlier and Farout appearance

I am using JFreeChart with Java to evaluate experimental results using the boxplot chart. I want to change the color and shape of the outliers and the farout entries.
This is how my plots currently look like when I use the normal BoxAndWhiskerRenderer:
I set up the renderer like this:
BoxAndWhiskerRenderer renderer = new BoxAndWhiskerRenderer();
renderer.setFillBox(true);
renderer.setSeriesPaint(0, Color.DARK_GRAY);
renderer.setSeriesPaint(1, Color.LIGHT_GRAY);
renderer.setSeriesOutlinePaint(0, Color.BLACK);
renderer.setSeriesOutlinePaint(1, Color.BLACK);
renderer.setUseOutlinePaintForWhiskers(true);
Font legendFont = new Font("SansSerif", Font.PLAIN, 15);
renderer.setLegendTextFont(0, legendFont);
renderer.setLegendTextFont(1, legendFont);
renderer.setMeanVisible(false);
Here, I cannot change the color and shape of the outliers. I would want them in black, not in the color of their series. And I would want them to look like small crosses rather than these big empty circles.
Also no farout values are shown at all and it seems like one of the outliers is cut off.
Then I found the ExtendedBoxAndWhiskerRenderer which allows to edit the color and shape of both outliers and farouts. This is what that looks like:
I set up the renderer like before, but I added two lines to set the color for the outliers and the farout entries:
renderer.setOutlierPaint(Color.BLACK); renderer.setFaroutPaint(Color.LIGHT_GRAY);
I also experimented with the shape of the outliers by reducing the cirle raduis in the extended renderer's implementation to 1.0 instead of 2.0:
private Shape createEllipse(Point2D point, double oRadius) {
Ellipse2D dot = new Ellipse2D.Double(point.getX(), point.getY(), oRadius*1.0, oRadius*1.0);
return dot;
}
However, I don't like these plots too much either. The Whiskers/Outlines of my plots aren't black anymore even though I set them to black. The mean is visible again even though I set it to invisible. And the huge number of outliers looks kind of ridiculous and makes me wonder why there are no farouts at the plots with the normal renderer at all.
If anyone could help me with these smaller appearance problems, that would be very nice. Otherwise, I will just take the current plots with the weird looking outliers and missing farouts...
While ExtendedBoxAndWhiskerRenderer is exemplary, it is somewhat dated, and much of its functionality has been incorporated into the mainline version. Your experiment suggests that the old renderer and new dataset are incompatible.
Because the outlier rendering methods are private, an alternative approach is to override the relevant draw*Item() method and let it invoke your own variations. You'll need to recapitulate the existing code, using the public accessors as required. In outline, the following variations demonstrate using Color.black, illustrated below.
plot.setRenderer(new BoxAndWhiskerRenderer() {
#Override
public void drawVerticalItem(Graphics2D g2, …) {
// existing code that calls the methods below
}
private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
Paint temp = g2.getPaint();
g2.setColor(Color.black);
Ellipse2D dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
point.getY(), oRadius, oRadius);
g2.draw(dot);
g2.setPaint(temp);
}
private void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
double m) {
Paint temp = g2.getPaint();
g2.setColor(Color.black);
double side = aRadius * 2;
g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
g2.setPaint(temp);
}
}

Make oval/rectangle using float/double values

I want to draw a figure using float or double values, to be precise.
I use:
g.drawOval(0, 0, 10, 10);
to draw a circle, but I only can use integer values.
Is there any statement that use float/double values that do the same?
Here is a picture: Problem
The circles have to be centered, and I can't. Any solution?
Code:
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JPanel;
public class Bulls_EYE extends JPanel
{
int red, green, blue;
int height, width;
int heightOval = 475, widthOval = 475;
Random rValue = new Random();
public void paint (Graphics g)
{
super.paint(g);
for (int idx = 0; idx < 100; idx++)
{
g.setColor(new Color(red = 1 + rValue.nextInt(255), green = 1 + rValue.nextInt(255), blue = 1 + rValue.nextInt(255)));
g.fillOval(width+2*idx, height+2*idx, widthOval-5*idx, heightOval-5*idx);
}
}
}
I think it's an interesting question but needs more context. Drawing primitives are usually expressed in pixel coordinates so fractions of a pixel do not make much sense.
If you want precision like a CAD application note that what is displayed on the screen is only an approximation of the underlying model due to the limitations of the display.
You can represent your models precisely in memory (with limitations in floating point representation) and draw the approximation on the screen.
Update
Based on your last update:
We know from the JavaDoc that fillOval takes as parameters (x, y, w, h) where x, y are the upper left coordinates, and w, h are the width and height.
If for each concentric circle you move the upper left coordinates inward, in this case by 2 px, to keep them centered, you must also reduce the width and height by twice that amount. Change the following line:
g.fillOval(width+2*idx, height+2*idx, widthOval-5*idx, heightOval-5*idx);
To
int dx, dy, dw, dh;
dx = 2*idx;
dy = 2*idx;
dw = 2*dx; // note this is 4*idx not 5*idx like you have currently
dh = 2*dy;
g.fillOval(width+dx, height+dy, widthOval-dw, heightOval-dh);
Note that your width and height variables being used in the first and second parameters really doesn't have anything to do with width and height but instead are providing a beginning offset from the origin where the oval is drawn.
There is no reason you should do this, because when drawing an oval with the given coordinates, they are referred to pixels on the screen. Since you can't draw between pixels, 1 is the smallest unit you can use. If you want to round the values before drawing, you can use
g.drawOval(Math.round(a),Math.round(b),Math.round(x),Math.round(y)
which will round the float a, b, x and y before drawing the oval. The only reason I can see is that you calculate the coordinates and the result is a float, then you need to round it like above.
You can use the Arc2D class for drawing circles with float/double precision, since it is a Shape and the Graphics2D class can draw shapes.
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Shape circle = new Arc2D.Double(
// Same values as used in the fillOval function,
// but with double precision.
x, y, width, height,
// Draw a full circle (yes, in degrees).
0, 360,
// Connect the endpoint with the startpoint.
Arc2D.CORD
);
// Paint the circle.
g2d.fill(circle);
}
In a similar way, you can draw rectangles by using the Rectangle2D class.
Also, please use the paintComponent function instead of the paint function, as explained here.

Categories

Resources