What is the best way to draw a crisp, opaque hairline in JavaFX 2.2?
The documentation says, that with a strokeWidth of 0.0d it'll be a hairline, but it's just not visible at all. Values > 0.0d and <1.0d show lines that are pretty fine, but also don't show an opaque behaviour. When one line cuts another, the intersecting points are lighter than the rest of the line (i'd expect this behaviour from a line with some transparency). Finally, 1.0d draws a white line with several pixels width.
That's my test code:
LineBuilder.create().startX(i*gridSize).startY(0).endX(i*gridSize).endY(height).smooth(false).stroke(Color.WHITE).strokeWidth(0.5d).fill(Color.WHITE).build();
You can use a Region subclass, such as a Pane for your Parent, with snapToPixel set to true.
Additionally, refer to the Node documentation on the co-ordinate system.
At the device pixel level, integer coordinates map onto the corners
and cracks between the pixels and the centers of the pixels appear at
the midpoints between integer pixel locations. Because all coordinate
values are specified with floating point numbers, coordinates can
precisely point to these corners (when the floating point values have
exact integer values) or to any location on the pixel. For example, a
coordinate of (0.5, 0.5) would point to the center of the upper left
pixel on the Stage. Similarly, a rectangle at (0, 0) with dimensions
of 10 by 10 would span from the upper left corner of the upper left
pixel on the Stage to the lower right corner of the 10th pixel on the
10th scanline. The pixel center of the last pixel inside that
rectangle would be at the coordinates (9.5, 9.5).
Also see the Shape documentation:
Most nodes tend to have only integer translations applied to them and
quite often they are defined using integer coordinates as well. For
this common case, fills of shapes with straight line edges tend to be
crisp since they line up with the cracks between pixels that fall on
integer device coordinates and thus tend to naturally cover entire
pixels. On the other hand, stroking those same shapes can often lead
to fuzzy outlines because the default stroking attributes specify both
that the default stroke width is 1.0 coordinates which often maps to
exactly 1 device pixel and also that the stroke should straddle the
border of the shape, falling half on either side of the border. Since
the borders in many common shapes tend to fall directly on integer
coordinates and those integer coordinates often map precisely to
integer device locations, the borders tend to result in 50% coverage
over the pixel rows and columns on either side of the border of the
shape rather than 100% coverage on one or the other. Thus, fills may
typically be crisp, but strokes are often fuzzy.
Two common solutions to avoid these fuzzy outlines are to use wider
strokes that cover more pixels completely - typically a stroke width
of 2.0 will achieve this if there are no scale transforms in effect -
or to specify either the StrokeType.INSIDE or StrokeType.OUTSIDE
stroke styles - which will bias the default single unit stroke onto
one of the full pixel rows or columns just inside or outside the
border of the shape.
So, if you leave your Nodes in a Group or Region which does not snapToPixel, you can follow the above instructions from the Shape documentation.
Here is some sample code:
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineBuilder;
import javafx.scene.shape.StrokeType;
import javafx.scene.text.Text;
import javafx.stage.Stage;
/** http://stackoverflow.com/questions/11886230/how-to-draw-a-crisp-opaque-hairline-in-javafx-2-2 */
public class LineWidths extends Application {
public static void main(String[] args) { launch(args); }
#Override public void start(Stage stage) {
Line fuzzyline = LineBuilder.create()
.startX(5).startY(50)
.endX(90).endY(50)
.stroke(Color.BLACK).strokeWidth(1)
.build();
Line hairline = LineBuilder.create()
.startX(4.5).startY(99.5)
.endX(89.5).endY(99.5)
.stroke(Color.BLACK).strokeWidth(1)
.build();
Line fatline = LineBuilder.create()
.startX(5).startY(150)
.endX(90).endY(150)
.stroke(Color.BLACK).strokeWidth(1).strokeType(StrokeType.OUTSIDE)
.build();
Pane snappedPane = new Pane();
Line insideline = LineBuilder.create()
.startX(5).startY(25)
.endX(90).endY(25)
.stroke(Color.BLACK).strokeWidth(1)
.build();
snappedPane.setSnapToPixel(true);
snappedPane.getChildren().add(insideline);
snappedPane.setPrefSize(100, 50);
snappedPane.relocate(-0.5, 174.5);
stage.setScene(
new Scene(
new Group(
fuzzyline, hairline, fatline, snappedPane,
new Text(10, 40, "fuzzyline"),
new Text(10, 90, "hairline"),
new Text(10, 140, "fatline"),
new Text(10, 190, "snappedPane")
), 100, 250
)
);
stage.show();
}
}
Related
I'm trying to find a way to identify an archery target and all of its rings on a photo which might be made of different perspectives:
My goal is to identify the target and later on also where the arrows hit the target to automatically count their score. Presumptions are as follows:
The camera's position is not fixed and might change
The archery target might also move or rotate slightly
The target might be of different size and have different amount of circles
There might be many holes (sometimes big scratches) in the target
I have already tried OpenCV to find contours, but even with preprocessing (grayscale -> blur (-> threshold) -> edge detection) I still find a few houndred contours which are all distracted by the arrows or other obstacles (holes) on the target, so it is impossible to find a nice circular line. Using Hough to find circles doesn't work either as it will give me weired results as Hough will only find perfect circles and not ellipses.
With preprocessing the image this is my best result so far:
I was thinking about ellipse and circle fitting, but as I don't know radius, position and pose of the target this might be a very cpu consuming task. Another thought was about using recognition from a template, but the position and rotation of the target changes often.
Now I have the idea to follow every line on the image to check if it is a curve and then guess which curves belong together to form a circle/ellipse (ellipse because of the perspective). The problem is that the lines might be intersected by arrows or holes in a short distance so the line would be too short to check if it is a curve. With the smaller circles on the target the chance is high that it isn't recognised at all. Also, as you can see, circle 8, 7 and 6 have no clear line on the left side.
I think it is not neccessary to do perspective correction to achieve this task as long as I can clearly identify all the rings in the target.
I googled a long time and found some thesis which are all not exactly focussed on this specific task and also too mathematical for me to understand.
Is it by any chance possible to achieve this task? Could you share with me an idea how to solve this problem? Anything is very appreciated.
I'm doing this in Java, but the programming language is secondary. Please let me know if you need more details.
for starters see
Detecting circles and shots from paper target.
If you are using standardized target as on the image ( btw. I use these same too for my bow :) ) then do not cut off the color. You can select the regions of blue red and yellow pixels to ease up the detection. see:
footprint fitting
From that you need to fit the circles. But as you got perspective then the objects are not circles nor ellipses. You got 2 options:
Perspective correction
Use right bottom table rectangle area as marker (or the whole target). It is rectangle with known aspect ratio. so measure it on image and construct transformation that will change the image so it became rectangle again. There are tons of stuff about this: 3D scene reconstruction so google/read/implement. The basic are based just on De-skew + scaling.
Approximate circles by ellipses (not axis aligned!)
so fit ellipses to found edges instead circles. This will not be as precise but still close enough. see:
ellipse fitting
[Edit1] sorry did not have time/mood for this for a while
As you were unable to adapt my approach yourself here it is:
remove noise
you need to recolor your image to remove noise to ease up the rest... I convert it to HSV and detect your 4 colors (circles+paper) by simple tresholding and recolor the image to 4 colors (circles,paper,background) back into RGB space.
fill the gaps
in some temp image I fill the gaps in circles created by arrows and stuff. It is simple just scan pixels from opposite sides of image (in each line/row) and stop if hit selected circle color (you need to go from outer circles to inner not to overwrite the previous ones...). Now just fill the space between these two points with your selected circle color. (I start with paper, then blue,red and yellow last):
now you can use the linked approach
So find avg point of each color, that is approx circle center. Then do a histogram of radius-es and chose the biggest one. From here just cast lines out of the circle and find where the circle really stops and compute the ellipse semi-axises from it and also update the center (that handles the perspective distortions). To visually check I render cross and circle for each circle into the image from #1:
As you can see it is pretty close. If you need even better match then cast more lines (not just 90 degree H,V lines) to obtain more points and compute ellipse algebraically or fit it by approximation (second link)
C++ code (for explanations look into first link):
picture pic0,pic1,pic2;
// pic0 - source
// pic1 - output
// pic2 - temp
DWORD c0;
int x,y,i,j,n,m,r,*hist;
int x0,y0,rx,ry; // ellipse
const int colors[4]=// color sequence from center
{
0x00FFFF00, // RGB yelow
0x00FF0000, // RGB red
0x000080FF, // RGB blue
0x00FFFFFF, // RGB White
};
// init output as source image and resize temp to same size
pic1=pic0;
pic2=pic0; pic2.clear(0);
// recolor image (in HSV space -> RGB) to avoid noise and select target pixels
pic1.rgb2hsv();
for (y=0;y<pic1.ys;y++)
for (x=0;x<pic1.xs;x++)
{
color c;
int h,s,v;
c=pic1.p[y][x];
h=c.db[picture::_h];
s=c.db[picture::_s];
v=c.db[picture::_v];
if (v>100) // bright enough pixels?
{
i=25; // treshold
if (abs(h- 40)+abs(s-225)<i) c.dd=colors[0]; // RGB yelow
else if (abs(h-250)+abs(s-165)<i) c.dd=colors[1]; // RGB red
else if (abs(h-145)+abs(s-215)<i) c.dd=colors[2]; // RGB blue
else if (abs(h-145)+abs(s- 10)<i) c.dd=colors[3]; // RGB white
else c.dd=0x00000000; // RGB black means unselected pixels
} else c.dd=0x00000000; // RGB black
pic1.p[y][x]=c;
}
pic1.save("out0.png");
// fit ellipses:
pic1.bmp->Canvas->Pen->Width=3;
pic1.bmp->Canvas->Pen->Color=0x0000FF00;
pic1.bmp->Canvas->Brush->Style=bsClear;
m=(pic1.xs+pic1.ys)*2;
hist=new int[m]; if (hist==NULL) return;
for (j=3;j>=0;j--)
{
// select color per pass
c0=colors[j];
// fill the gaps with H,V lines into temp pic2
for (y=0;y<pic1.ys;y++)
{
for (x= 0;(x<pic1.xs)&&(pic1.p[y][x].dd!=c0);x++); x0=x;
for (x=pic1.xs-1;(x> x0)&&(pic1.p[y][x].dd!=c0);x--);
for (;x0<x;x0++) pic2.p[y][x0].dd=c0;
}
for (x=0;x<pic1.xs;x++)
{
for (y= 0;(y<pic1.ys)&&(pic1.p[y][x].dd!=c0);y++); y0=y;
for (y=pic1.ys-1;(y> y0)&&(pic1.p[y][x].dd!=c0);y--);
for (;y0<y;y0++) pic2.p[y0][x].dd=c0;
}
if (j==3) continue; // do not continue for border
// avg point (possible center)
x0=0; y0=0; n=0;
for (y=0;y<pic2.ys;y++)
for (x=0;x<pic2.xs;x++)
if (pic2.p[y][x].dd==c0)
{ x0+=x; y0+=y; n++; }
if (!n) continue; // no points found
x0/=n; y0/=n; // center
// histogram of radius
for (i=0;i<m;i++) hist[i]=0;
n=0;
for (y=0;y<pic2.ys;y++)
for (x=0;x<pic2.xs;x++)
if (pic2.p[y][x].dd==c0)
{
r=sqrt(((x-x0)*(x-x0))+((y-y0)*(y-y0))); n++;
hist[r]++;
}
// select most occurent radius (biggest)
for (r=0,i=0;i<m;i++)
if (hist[r]<hist[i])
r=i;
// cast lines from possible center to find edges (and recompute rx,ry)
for (x=x0-r,y=y0;(x>= 0)&&(pic2.p[y][x].dd==c0);x--); rx=x; // scan left
for (x=x0+r,y=y0;(x<pic2.xs)&&(pic2.p[y][x].dd==c0);x++); // scan right
x0=(rx+x)>>1; rx=(x-rx)>>1;
for (x=x0,y=y0-r;(y>= 0)&&(pic2.p[y][x].dd==c0);y--); ry=y; // scan up
for (x=x0,y=y0+r;(y<pic2.ys)&&(pic2.p[y][x].dd==c0);y++); // scan down
y0=(ry+y)>>1; ry=(y-ry)>>1;
i=10;
pic1.bmp->Canvas->MoveTo(x0-i,y0);
pic1.bmp->Canvas->LineTo(x0+i,y0);
pic1.bmp->Canvas->MoveTo(x0,y0-i);
pic1.bmp->Canvas->LineTo(x0,y0+i);
//rx=r; ry=r;
pic1.bmp->Canvas->Ellipse(x0-rx,y0-ry,x0+rx,y0+ry);
}
pic2.save("out1.png");
pic1.save("out2.png");
pic1.bmp->Canvas->Pen->Width=1;
pic1.bmp->Canvas->Brush->Style=bsSolid;
delete[] hist;
The Situation:
In PDFBox, PDRectangle objects' default origin (0,0) seems to be the lower-left corner of a page.
For example, the following code gives you a square at the lower-left corner of a page, and each side is 100 units long.
PDRectangle rectangle = new PDRectangle(0, 0, 100, 100);
The Question:
Is it possible to change the origin to the UPPER-LEFT corner, so that, for example, the code above will give you the same square at the UPPER-LEFT corner of the page instead?
The reason I ask:
I was using PDFTextStripper to get the coordinates of the text (by using the getX() and getY() methods of the extracted TextPosition objects). The coordinates retrieved from TextPosition objects seem have an origin (0,0) at the UPPER-LEFT CORNER. I want the coordinates of my PDRectangle objects have the same origin as the coordinates of my TextPosition objects.
I have tried to adjust the Y-coordinates of my PDRectangle by "page height minus Y-coordinate". This gives me the desired result, but it's not elegant. I want an elegant solution.
Note:
Someone has asked a similar question. The answer is what I tried, which is not the most elegant.
how to change the coordiantes of a text in a pdf page from lower left to upper left
You can change coordinate systems somewhat but most likely things won't get more elegant in the end.
To start with...
First of all let's clear up some misconception:
You assume
In PDFBox, PDRectangle objects' default origin (0,0) seems to be the lower-left corner of a page.
This is not true for all cases, merely often.
The area containing the displayed page area (on paper or on screen) usually is defined by the CropBox entry of the page in question:
CropBox rectangle (Optional; inheritable) A rectangle, expressed in default user space units, that shall define the visible region of default user space.
When the page is displayed or printed, its contents shall be clipped (cropped) to this rectangle and then shall be imposed on the output medium in some implementation-defined manner.
... The positive x axis extends horizontally to the right and the positive y axis vertically upward, as in standard mathematical practice (subject to alteration by the Rotate entry in the page dictionary).
... In PostScript, the origin of default user space always corresponds to the lower-left corner of the output medium. While this convention is common in PDF documents as well, it is not required; the page dictionary’s CropBox entry can specify any rectangle of default user space to be made visible on the medium.
Thus, the origin (0,0) can literally be anywhere, it may be at the lower left, at the upper left, in the middle of the page or even far outside the displayed page area.
And by means of the Rotate entry, that area can even be rotated (by 90°, 180°, or 270°).
Putting the origin (as you seem to have observed) in the lower left merely is done by convention.
Furthermore you seem to think that the coordinate system is constant. This also is not the case, there are operations by which you can transform the user space coordinate system drastically, you can translate, rotate, mirror, skew, and/or scale it!
Thus, even if at the beginning the coordinate system is the usual one, origin in lower left, x-axis going right, y-axis going up, it may be changed to something weird some way into the page content description. Drawing your rectangle new PDRectangle(0, 0, 100, 100) there might produce some rhomboid form just right of the page center.
What you can do...
As you see coordinates in PDF user space are a very dynamic matter. what you can do to tame the situation, depends on the context you use your rectangle in.
Unfortunately you were quite vague in the description of what you do. Thus, this will be somewhat vague, too.
Coordinates in the page content
If you want to draw some rectangle on an existing page, you first of all need a page content stream to write to, i.e. a PDPageContentStream instance, and it should be prepared in a manner guaranteeing that the original user space coordinate system has not been disturbed. You get such an instance by using the constructor with three boolean arguments setting all them to true:
PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true, true);
Then you can apply a transformation to the coordinate system. You want the top left to be the origin and the y-value increasing downwards. If the crop box of the page tells you the top left has coordinates (xtl, ytl), therefore, you apply
contentStream.concatenate2CTM(new AffineTransform(1, 0, 0, -1, xtl, ytl));
and from here on you have a coordinate system you wanted, origin top left and y coordinates mirrored.
Be aware of one thing, though: If you are going to draw text, too, not only the text insertion point y coordinate is mirrored but also the text itself unless you counteract that by adding an also mirroring text matrix! If you want to add much text, therefore, this may not be as elegant as you want.
Coordinates for annotations
If you don't want to use the rectangle in the content stream but instead for adding annotations, you are not subject to the transformations mentioned above but you can not make use of it, either.
Thus, in this context you have to take the crop box as it is and transform your rectangle accordingly.
Why PDFBox text extraction coordinates are as they are
Essentially for putting lines of text together in the right order and sorting the lines correctly, you don't want such a weird situation but instead a simple stable coordinate system. Some PDFBox developers chose the top-left-origin, y-increasing-downwards variant for that, and so the TextPosition coordinates have been normalized to that scheme.
In my opinion a better choice would have been to use the default user space coordinates for easier re-use of the coordinates. You might, therefore, want to try working with textPosition.getTextMatrix().getTranslateX(), textPosition.getTextMatrix().getTranslateY() for a TextPosition textPosition
The following seems to be the best way to "adjust" the TextPosition coordinates:
x_adjusted = x_original + page.findCropBox().getLowerLeftX();
y_adjusted = -y_original + page.findCropBox().getUpperRightY();
where page is the PDPage on which the TextPosition object is located
The accepted answer created some problems for me. Also, text being mirrored and adjusting for that just didn't seem like the right solution for me. So here's what I came up with and so far, this has worked pretty smoothly.
Solution (example available below):
Call the getAdjustedPoints(...) method with your original points as you are drawing on paper where x=0 and y=0 is top left corner.
This method will return float array (length 4) that can be used to draw rect
Array order is x, y, width and height. Just pass that addRect(...) method
private float[] getAdjustedPoints(PDPage page, float x, float y, float width, float height) {
float resizedWidth = getSizeFromInches(width);
float resizedHeight = getSizeFromInches(height);
return new float[] {
getAdjustedX(page, getSizeFromInches(x)),
getAdjustedY(page, getSizeFromInches(y)) - resizedHeight,
resizedWidth, resizedHeight
};
}
private float getSizeFromInches(float inches) {
// 72 is POINTS_PER_INCH - it's defined in the PDRectangle class
return inches * 72f;
}
private float getAdjustedX(PDPage page, float x) {
return x + page.getCropBox().getLowerLeftX();
}
private float getAdjustedY(PDPage page, float y) {
return -y + page.getCropBox().getUpperRightY();
}
Example:
private PDPage drawPage1(PDDocument document) {
PDPage page = new PDPage(PDRectangle.LETTER);
try {
// Gray Color Box
PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, false, false);
contentStream.setNonStrokingColor(Color.decode(MyColors.Gallery));
float [] p1 = getAdjustedPoints(page, 0f, 0f, 8.5f, 1f);
contentStream.addRect(p1[0], p1[1], p1[2], p1[3]);
contentStream.fill();
// Disco Color Box
contentStream.setNonStrokingColor(Color.decode(MyColors.Disco));
p1 = getAdjustedPoints(page, 4.5f, 1f, 4, 0.25f);
contentStream.addRect(p1[0], p1[1], p1[2], p1[3]);
contentStream.fill();
contentStream.close();
} catch (Exception e) { }
return page;
}
As you can see, I've drawn 2 rectangle boxes.
To draw this, I used the the following coordinates which assumes that x=0 and y=0 is top left.
Gray Color Box: x=0, y=0, w=8.5, h=1
Disco Color Box: x=4.5 y=1, w=4, h=0.25
Here's an image of my result.
Add the height of the PDF (Easiest Solution)
I'm working on a project, and I need to be able to detect collisions between circles. I already found a mathematical formula for that : http://cgp.wikidot.com/circle-to-circle-collision-detection
But I've got a question, how can I detect if there is a rectangle in this area ? Or just a part of a rectangle inside ?
I've got : coordinates of the center of a circle and the radius, and for the rectanble I've got a x and y coordinate, and width an height. I guess that x and y are just a point and with that I'm able to guess the form with the width and the height.
Any idea ?
Thanks a lot !
Write a method to check whether a point lies within a circle or not.
Call that method for all corner points of the rectangle (calculated from x, y, width and height) on both circles.
Use your existing circle intersection detector method to prune calls.
Hope this helps.
Good luck.
You can use java.awt.geom.Area class.
It has a constructor to create an Area from Shape
public Area(Shape s)
So create simple areas for your circles and rectangle. Then you can either combine areas using the methods to obtain new areas.
public void add(Area rhs)
public void subtract(Area rhs)
And check whether area intersects or contains another area via
public void intersect(Area rhs)
public boolean contains(Rectangle2D r)
This sounds like a variation of the technique described in this answer.
The same two cases apply (either the circle's centre is in the region, or one or more of the rectangle's edges intersects the region)... the difference is that instead of considering the circle in general, you need to consider the intersection of the circle.
The first case is easy because you can swap centre points. If the rectangle's centre point is in the intersection of the circles, then the rectangle is partly inside. This is easy to determine: find the centre point of the rectangle, see if it's in the first circle, see if it's in the second circle.
The second case is complicated, because it requires you to calculate the curves where the circles intersect. If the edges of the rectangle intersect either of those curves then the rectangle overlaps the intersection. As a special case, if one circle lies completely inside the other one, then the line to check is the border of the smaller circle.
If you don't need an exact answer, then the second case can be approximated. First, find the points where the two circles intersect (or use the method you've already come up with, if you can). These two points can be used to construct a bounding rectangle (they are either the top left/bottom right or top right/bottom left points of a rectangle). If this bounding rectangle intersects with your rectangle, then your rectangle probably overlaps the circle intersection.
All in all, this is fairly complicated if you want to an exact answer that works properly with all of the special cases (one circle completely inside the other, the rectangle intersects both circles but not their intersection, etc). I hope this helps a little.
A library I've used before called the JTS topology suite might be appropriate for your needs. It's orientated more towards GIS operations than pure euclidean geometry, but it can easily do all of these calculations for you once you've got the shapes defined:
import com.vividsolutions.jts.util.*
GeometricShapeFactory gsf = new GeometricShapeFactory();
gsf.setSize(100);
gsf.setNumPoints(100);
gsf.setBase(new Coordinate(100, 100));
//configure the circle as appropriate
Polygon circleA = gsf.createCircle();
//configure again and create a separate circle
Polygon circleB = gsf.createCircle();
//configure a rectangle this time
Polygon rectangle = gsf.createRectangle();
Geometry circleIntersection = circleA.intersection(circleB);
return rectangle.intersects(circleIntersection);
I'm creating some some shapes and everything seems to be blurred, like anti-aliased despite no effects applied.
For example, a white line drawn on a black backgroud with 1 pixel width, is rendered grey! Changing the width to 2px results in white, but not well-defined.
When searching, the method setSmooth(false) on shapes returned, but calling it makes no difference.
What should I change or disable on Stage or Scene?
See the Shape documentation:
Most nodes tend to have only integer translations applied to them and
quite often they are defined using integer coordinates as well. For
this common case, fills of shapes with straight line edges tend to be
crisp since they line up with the cracks between pixels that fall on
integer device coordinates and thus tend to naturally cover entire
pixels.
On the other hand, stroking those same shapes can often lead to fuzzy
outlines because the default stroking attributes specify both that the
default stroke width is 1.0 coordinates which often maps to exactly 1
device pixel and also that the stroke should straddle the border of
the shape, falling half on either side of the border. Since the
borders in many common shapes tend to fall directly on integer
coordinates and those integer coordinates often map precisely to
integer device locations, the borders tend to result in 50% coverage
over the pixel rows and columns on either side of the border of the
shape rather than 100% coverage on one or the other. Thus, fills may
typically be crisp, but strokes are often fuzzy.
Two common solutions to avoid these fuzzy outlines are to use wider
strokes that cover more pixels completely - typically a stroke width
of 2.0 will achieve this if there are no scale transforms in effect -
or to specify either the StrokeType.INSIDE or StrokeType.OUTSIDE
stroke styles - which will bias the default single unit stroke onto
one of the full pixel rows or columns just inside or outside the
border of the shape.
And see also the documentation of Node:
At the device pixel level, integer coordinates map onto the corners
and cracks between the pixels and the centers of the pixels appear at
the midpoints between integer pixel locations. Because all coordinate
values are specified with floating point numbers, coordinates can
precisely point to these corners (when the floating point values have
exact integer values) or to any location on the pixel. For example, a
coordinate of (0.5, 0.5) would point to the center of the upper left
pixel on the Stage. Similarly, a rectangle at (0, 0) with dimensions
of 10 by 10 would span from the upper left corner of the upper left
pixel on the Stage to the lower right corner of the 10th pixel on the
10th scanline. The pixel center of the last pixel inside that
rectangle would be at the coordinates (9.5, 9.5).
So your options for clean lines when you have an odd stroke width are:
Use a StrokeType.INSIDE or StrokeType.OUTSIDE stroke style.
Offset the co-ordinates of shapes by 0.5 of a pixel so that the strokes line up on the lines rather than the cracks between lines.
Just use the next even number up as the stroke width, e.g. 1 => 2, 3 => 4, etc.
As to why setSmooth(false) does not work, I don't know exactly, my guess is that the antialiasing it refers to is independent of the antialiasing styles performed when strokes are centered on the cracks between pixels, but I would not know why that would be.
So I've got an assignment that takes two inputs, males and females, and outputs matingPairs, the product of the two.
In addition to that, the instructions ask to draw a shape using one of those variables.
I've decided to draw circles for each value.
I first draw matingPairs, followed by the smaller male and female circles on top of the original, larger matingPairs circle.
The problem I'm running in to is obviously representing the graphic in the applet. If the numbers go higher than say 100, the graphic becomes too large for the applet.
I'm looking for a way to basically have the matingPairs circle always fill the applet, then have males and females dynamically adjust so their size is scaled relative to the matingPairs circle size. I'm using JApplet.
Thank you very much for any guidance. I'm really looking for a solution, rather a push in the right direction.
May be you should provide more instruction about how are you drawing the circles in the Graphics object.
The idea is to manage two bi-dimensional spaces with different scales; the first one is the input data and the second one represents the available area to draw such data. The first one can have data on any location, such (5, 5), (0.2, 0.3)or (1200, 3400). The key is to map the original coordinates of the first space into the second, using the proper transformation: scale + translation.
This transformation must be calculated prior to start drawing and applies to any point drawn.
The idea is to map the rectangle where input data resides to the available area in the graphics. If the graphics area is 200x200 pixels and the data could be from (0, 0) to (400, 400), just divide by 2 the coordinates of the points to draw. If the original data is not centered in (0, 0), use a translation.
So, do you need to know how to get the size of the applets canvas or how to scale the male/female circles accordingly?
Edit:
Drawing a circle to fill the 600x600 area should be easy. Just keep in mind that you often specify the top left corner of the circle and the width and height (i.e. the diameter) when calling drawOval() / fillOval() or similar methods.
The next question is: what does represent the size of the input (males/females) and output (pairs), the area or the radius of the circles? Whatever it is, it should be easy to calculate the input/output ratio and then multiply the fixed size of the output circle with it in order to get the size of the input circle.