Hand-drawn graphics with JAVA - java

Is there a way (a library) with which one can give the (contour) lines of AWT-2D-objects a "hand-drawn" look (somehow shaky: not exactly following the "official" path, not having perfect line contours)
(The official path should be followed almost perfectly, with just some "random" noise. The line contours should be almost perfect, with just some "random" noise.)

Use the SloppyStroke from here: http://www.java2s.com/Code/Java/2D-Graphics-GUI/CustomStrokes.htm
/**
* This Stroke implementation randomly perturbs the line and curve segments that
* make up a Shape, and then strokes that perturbed shape. It uses PathIterator
* to loop through the Shape and GeneralPath to build up the modified shape.
* Finally, it uses a BasicStroke to stroke the modified shape. The result is a
* "sloppy" looking shape.
*/
class SloppyStroke implements Stroke {
BasicStroke stroke;
float sloppiness;
public SloppyStroke(float width, float sloppiness) {
this.stroke = new BasicStroke(width); // Used to stroke modified shape
this.sloppiness = sloppiness; // How sloppy should we be?
}
public Shape createStrokedShape(Shape shape) {
GeneralPath newshape = new GeneralPath(); // Start with an empty shape
// Iterate through the specified shape, perturb its coordinates, and
// use them to build up the new shape.
float[] coords = new float[6];
for (PathIterator i = shape.getPathIterator(null); !i.isDone(); i
.next()) {
int type = i.currentSegment(coords);
switch (type) {
case PathIterator.SEG_MOVETO:
perturb(coords, 2);
newshape.moveTo(coords[0], coords[1]);
break;
case PathIterator.SEG_LINETO:
perturb(coords, 2);
newshape.lineTo(coords[0], coords[1]);
break;
case PathIterator.SEG_QUADTO:
perturb(coords, 4);
newshape.quadTo(coords[0], coords[1], coords[2], coords[3]);
break;
case PathIterator.SEG_CUBICTO:
perturb(coords, 6);
newshape.curveTo(coords[0], coords[1], coords[2], coords[3],
coords[4], coords[5]);
break;
case PathIterator.SEG_CLOSE:
newshape.closePath();
break;
}
}
// Finally, stroke the perturbed shape and return the result
return stroke.createStrokedShape(newshape);
}
// Randomly modify the specified number of coordinates, by an amount
// specified by the sloppiness field.
void perturb(float[] coords, int numCoords) {
for (int i = 0; i < numCoords; i++)
coords[i] += (float) ((Math.random() * 2 - 1.0) * sloppiness);
}
}

Once I tried to do something similar with jhlabs. Please take a look at the WobbleStroke sample at: http://www.jhlabs.com/java/java2d/strokes/
In general - you have to implement the java.awt.Stroke class. So better search for appropriate implementations.
Here is a good sample of a brush stroke as well:
http://javagraphics.blogspot.com/2007/04/strokes-brush-stroke.html

Related

Calculate Center of gravity of several triangles

I cant wrap my mind about how to solve this problem. I have created 3 triangles:
I have the XY coordinates of all the individual triangles. I want to calculate the center of gravity if they are put together as shown, but only using the filled part, and wherever they intersec, you do not count the mass twice. How can I go about doing this in java?
Can I somehow combine these into 1 of some kind of object, and then do a numeric calculation of each area and find a middle ground, or is there a better way?
Convert your shape to a single polygon (there are two intersections to be computed).
Then use the centroid formula for polygons.
Another option is to fill all triangles in the same color (by polygon or seed filling), then seed fill the resultant area, while accumulating the X and Y coordinates on the fly.
First, you have to determine for what you actually want to compute the center of gravity. Obviously, when the triangles intersect (and the overlapping area should not be counted twice), then you are not computing the center of gravity of triangles, but the center of gravity of their intersection area.
Fortunately, such an intersection area can easily be computed with the Area class. And according to one comment, you already have an Area describing this area.
So one option to compute the center of gravity of this area is to compute the average of all border points of the Area.
Note that this only works when the Area does not have holes.
Otherwise, you have to compute the area of the Area.
Here is a possible implementation:
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class CenterOfGravity
{
public static void main(String[] args)
{
Path2D p0 = new Path2D.Double();
p0.moveTo(100, 100);
p0.lineTo(200, 100);
p0.lineTo(150, 50);
p0.closePath();
Path2D p1 = new Path2D.Double();
p1.moveTo(150, 100);
p1.lineTo(250, 100);
p1.lineTo(200, 50);
p1.closePath();
Area a = new Area();
a.add(new Area(p0));
a.intersect(new Area(p1));
Point2D cog = computeCenterOfGravity(a);
System.out.println(cog);
}
private static Point2D computeCenterOfGravity(Shape shape)
{
return computeAverage(computePoints(shape, 1.0));
}
private static Point2D computeAverage(
Collection<? extends Point2D> points)
{
double x = 0;
double y = 0;
for (Point2D point : points)
{
x += point.getX();
y += point.getY();
}
if (!points.isEmpty())
{
x /= points.size();
y /= points.size();
}
return new Point2D.Double(x, y);
}
public static List<Point2D> computePoints(
Shape shape, double flatness)
{
List<Point2D> result = new ArrayList<Point2D>();
PathIterator pi = shape.getPathIterator(null, flatness);
double[] coords = new double[6];
while (!pi.isDone())
{
int segment = pi.currentSegment(coords);
switch (segment)
{
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
result.add(new Point2D.Double(coords[0], coords[1]));
break;
case PathIterator.SEG_CLOSE:
break;
case PathIterator.SEG_QUADTO:
case PathIterator.SEG_CUBICTO:
default:
throw new AssertionError(
"Invalid segment in flattened path!");
}
pi.next();
}
return result;
}
}

Checking collisions with a line and a rectangle

Is it possible to check a collision with a line using an Area object?
Currently the way I am doing it is not working:
This returns false when you run it. But the line is very clearing touching the rectangle, in fact its completely inside it.
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
public class collision {
public static void main(String[] args) {
Area area1 = new Area(new Rectangle(0, 0, 100, 100));
Area area2 = new Area(new Line2D.Double(0, 0, 100, 100));
System.out.println(isColliding(area1, area2));
}
public static boolean isColliding(Area area1, Area area2) {
if (area2 != null) {
Area collide1 = new Area(area1);
collide1.subtract(area2);
if (!collide1.equals(area1)) {
return true;
}
}
return false;
}
}
I was able to solve this problem by making a recursive function that checks points along the thing.
The higher you set the depth the more accurately it will check, but will take longer to complete. I have been using 10 as my depth (which I believe checks 2047 points along the line) and I encountered no performance loss. Unless your Area object contains really thin parts I don't believe you will need more than this.
Someone feel free to comment and revise my method if you believe you can improve it in any way :)
Thanks to ajb for his suggestion of using a PathIterator which gave me the idea to check points along the line.
public static boolean findPoints(Area area1, Line2D line1, int depth) {
Point p1 = new Point((int) (line1.getX2() + line1.getX1()) / 2,
(int) (line1.getY2() + line1.getY1()) / 2);
if (depth == 0) {
return false;
}
pointMiddle = new Point(p1);
if (area1.contains(p1)) {
return true;
} else {
return findPoints(area1, new Line2D.Double(p1, line1.getP2()),
depth - 1)
|| findPoints(area1, new Line2D.Double(line1.getP1(), p1),
depth - 1);
}
}
If, as you say in the comments, you know that you will always be checking a Line2D and a Rectangle for collision, you can use the intersects method of Line2D (see javadoc):
public static void main(String[] args) {
Rectangle rect1 = new Rectangle(0, 0, 100, 100);
Line2D line2 = new Line2D.Double(0, 0, 100, 100);
System.out.println(isColliding(rect1, line2));
}
public static boolean isColliding(Rectangle2D rect1, Line2D line2) {
if (line2 != null) {
return line2.intersects(rect1);
}
return false;
}
To test if a line collides with a more general Area is more difficult; I don't think there's a method in the library for this. If the Area is built up of rectangles, you could try saving an array (or List) of the Rectangle objects, and testing whether the line intersects with any of them. Another approach: You can try using the contains method of Area, which tests whether an area contains a point. If either endpoint of the Line2D is contained by the Area, then the line and the area collide. If both endpoints are outside the area, but the area consists of straight lines (area.isPolygonal()), you could retrieve each line segment from the polygon and test whether the line intersects each line segment. You can do this with area.getPathIterator(null), and then using something like this on the resulting path iterator:
double[] coords = new double[6];
double moveX, moveY, prevX, prevY, newX, newY;
while (!pathIterator.isDone()) {
switch (pathIterator.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
moveX = coords[0]; moveY = coords[1];
prevX = moveX; prevY = moveY;
break;
case PathIterator.SEG_LINETO:
newX = coords[0]; newY = coords[1];
if ([line2 intersects the line from (prevX,prevY) to (newX,newY)]) {
return true;
}
prevX = newX; prevY = newY;
break;
case PathIterator.SEG_QUADTO:
case PathIterator.SEG_CUBICTO:
throw new RuntimeException("What is a curve doing in my rectangle?");
case PathIterator.SEG_CLOSE:
// go back to the last SEG_MOVETO point, usually the first point
if ([line2 intersects the line from (prevX,prevY) to (moveX,moveY)]) {
return true;
}
prevX = newX; prevY = newY;
break;
}
}
There are methods in Line2D to test whether a line segment intersects another.
Note: I have not tested this at all. I hope it works.

Libgdx - Get Intersection Rectangle from Rectangle.overlap(Rectangle)

Is there any way to know the intersection Rectangle area between two Rectangles in libgdx like the Rectangle in c# http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.rectangle.intersect.aspx ?
I need to get the intersection rectangle area between that two rectangles but the overlap method in libgdx only return boolean value whether two rectangles are intersect or not.
I have read Intersector class but it provides nothing to do that.
Indeed, LibGDX does not have this functionality built in, so I would do something like this:
/** Determines whether the supplied rectangles intersect and, if they do,
* sets the supplied {#code intersection} rectangle to the area of overlap.
*
* #return whether the rectangles intersect
*/
static public boolean intersect(Rectangle rectangle1, Rectangle rectangle2, Rectangle intersection) {
if (rectangle1.overlaps(rectangle2)) {
intersection.x = Math.max(rectangle1.x, rectangle2.x);
intersection.width = Math.min(rectangle1.x + rectangle1.width, rectangle2.x + rectangle2.width) - intersection.x;
intersection.y = Math.max(rectangle1.y, rectangle2.y);
intersection.height = Math.min(rectangle1.y + rectangle1.height, rectangle2.y + rectangle2.height) - intersection.y;
return true;
}
return false;
}
You can use the Intersector class.
import com.badlogic.gdx.math.Intersector;
Intersector.intersectRectangles(rectangle1, rectangle2, intersection);
I'd like to add a slight variation to the answer of nEx Software. This one will work even if you want to store the resulting value in one of the source rectangles:
public static boolean intersect(Rectangle r1, Rectangle r2, Rectangle intersection) {
if (!r1.overlaps(r2)) {
return false;
}
float x = Math.max(r1.x, r2.x);
float y = Math.max(r1.y, r2.y);
float width = Math.min(r1.x + r1.width, r2.x + r2.width) - x;
float height = Math.min(r1.y + r1.height, r2.y + r2.height) - y;
intersection.set(x, y, width, height);
return true;
}
Here is an example usege:
Rectangle r1 = new Rectangle();
Rectangle r2 = new Rectangle();
// ...
intersect(r1, r2, r1);

custom shape rotation issue

I am trying to rotate a custom shape around its center, but can not get the result as expected.
what i want is
*shape should be rotated around its center without moving itself.*
what my solution is currently doing is
rotating a whole shape around its center , by every rotation its changing its position.
I have multiple shapes so i have created a class to encapsulate a shape with its transform in following class
public abstract class Shoe implements Shape, ShoeShape {
// variable declaration
/**
*
*/
public Shoe() {
position = new Point();
lastPosition = new Point();
}
public void draw(Graphics2D g2, AffineTransform transform, boolean firstTime) {
AffineTransform af = firstTime ? getInitTransform()
: getCompositeTransform();
if (af != null) {
Shape s = af.createTransformedShape(this);
if (getFillColor() != null) {
g2.setColor(getFillColor());
g2.fill(s);
} else {
g2.draw(s);
}
}
}
}
public AffineTransform getCompositeTransform() {
AffineTransform af = new AffineTransform();
af.setToIdentity();
af.translate(position.getX(), position.getY());
Point2D centerP = calculateShapeCenter();
af.rotate(orientation, centerP.getX(), centerP.getY());
return af;
}
public void onMouseDrag(MouseEvent me, Rectangle2D canvasBoundary,
int selectionOperation) {
// shape operation can be either resize , rotate , translate ,
switch (selectionOperation) {
case MmgShoeViewer.SHAPE_OPERATION_MOVE:
// MOVEMENT
break;
case MmgShoeViewer.SHAPE_OPERATION_ROTATE:
Point2D origin = calculateShapeCenter();
Point2D.Double starting = new Point2D.Double(me.getX(), me.getY());
currentAngle = RotationHelper.getAngle(origin, starting);
rotationAngle = currentAngle - startingAngle;
rotate(rotationAngle);
break;
case MmgShoeViewer.SHAPE_OPERATION_RESIZE:
break;
default:
System.out.println(" invalid select operation");
}
}
public void onMousePress(MouseEvent me, Rectangle2D canvasBoundary,
int selectionOperation) {
// shape operation can be either resize , rotate , translate ,
switch (selectionOperation) {
case MmgShoeViewer.SHAPE_OPERATION_MOVE:
break;
case MmgShoeViewer.SHAPE_OPERATION_ROTATE:
Point2D origin = calculateShapeCenter();
Point2D.Double starting = new Point2D.Double(me.getX(), me.getY());
startingAngle = RotationHelper.getAngle(origin, starting);
setShapeOperation(selectionOperation);
break;
case MmgShoeViewer.SHAPE_OPERATION_RESIZE:
break;
default:
System.out.println(" invalid select operation");
}
}
public void onMouseRelease(MouseEvent me, Rectangle2D canvasBoundary,
int selectionOperation) {
// shape operation can be either resize , rotate , translate ,
switch (selectionOperation) {
case MmgShoeViewer.SHAPE_OPERATION_MOVE:
break;
case MmgShoeViewer.SHAPE_OPERATION_ROTATE:
// FIXME rotation angle computation
setShapeOperation(-1);
break;
case MmgShoeViewer.SHAPE_OPERATION_RESIZE:
break;
default:
System.out.println(" invalid select operation");
}
}
public void rotate(double angle) {
orientation = (float) angle;
}
public void translate(double deltaX, double deltaY) {
position.setLocation(deltaX, deltaY);
lastPosition.setLocation(deltaX, deltaY);
}
// another getter and setter
I am calculating angle of rotation using following method
public static double getAngle(Point2D origin, Point2D other) {
double dy = other.getY() - origin.getY();
double dx = other.getX() - origin.getX();
double angle;
if (dx == 0) {// special case
angle = dy >= 0 ? Math.PI / 2 : -Math.PI / 2;
} else {
angle = Math.atan(dy / dx);
if (dx < 0) // hemisphere correction
angle += Math.PI;
}
// all between 0 and 2PI
if (angle < 0) // between -PI/2 and 0
angle += 2 * Math.PI;
return angle;
}
in mouse press event of the canvas mouse listener
selectedShape.onMousePress(me, canvasBoundary, shoeViewer
.getShapeOperation());
i am just calling selected shape's onMousePress method
and in my mouse drag method of the canvas mouse listener , i am just calling the selected shape's onMouseDrag method which updates the rotation angle as you can see from the very first class
selectedShape.onMouseDrag(me, canvasBoundary, shoeViewer
.getShapeOperation());
and you can see the draw method of the individual shape , to draw the shape according to current transform , i am calling from paintComponent like
Iterator<Shoe> shoeIter = shoeShapeMap.values().iterator();
while (shoeIter.hasNext()) {
Shoe shoe = shoeIter.next();
shoe.draw(g2, firstTime);
}
where shoeShapeMap contains all of the custom shapes currently on the canvas.
is i am doing mistake in calculating angle or determining anchor point ? my current solution rotates shape 360 degree by checking all the conditions[90 degree etc.] as you can see in the above mentioned method.
i want the shape should be rotated around its center without resizing its positions ?
in the word it is difficult to explain , so please suggest me any better way to show here what i want to accomplish ?
i think i have mentioned all the things related to this issue. if you have any doubts please feel free to ask me.
i found 2 related posts here but i could not find much information from them.
I think that the solution may be to (either/and):
invert the order of operations on your AffineTransform, put translate after rotate
use -x and -y for your translation values

How to calculate the area of a java.awt.geom.Area?

I am looking for a way to calculate the area, in pixels, of an arbitrary instance of java.awt.geom.Area.
The background: I have Shapes in my applications that may overlap. I want to know how much one Shape overlaps another. The Shapes may be skewed, rotated, etc. If I had a function area(Shape) (or Area), I could use the intersection of two Shapes like so:
double fractionObscured(Shape bottom, Shape top) {
Area intersection = new Area(bottom);
intersection.intersect(new Area(top));
return area(intersection) / area(bottom);
}
To find the area of a polygon using the following snippet:
int sum = 0;
for (int i = 0; i < n -1; i++)
{
sum = sum + x[i]*y[i+1] - y[i]*x[i+1];
}
// (sum / 2) is your area.
System.out.println("The area is : " + (sum / 2));
Here n is the total number of vertices and x[i] and y[i] are the x and y coordinates of a vertex i.
Note that for this algorithm to work, the polygon must be closed. It doesent work on open polygons.
You can find mathematical alogrithms related to polygons here. You need to convert it to code yourself:)
I've used this class to approximate the area of a shape in one of my projects. It's slow but at high resolution it may still be faster than counting pixels (because the cost of counting pixels grows quadratically with resolution, but the number of line segments on the perimeter grows linearly.)
import static java.lang.Double.NaN;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.FlatteningPathIterator;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
public abstract class Areas {
public static double approxArea(Area area, double flatness, int limit) {
PathIterator i =
new FlatteningPathIterator(area.getPathIterator(identity),
flatness,
limit);
return approxArea(i);
}
public static double approxArea(Area area, double flatness) {
PathIterator i = area.getPathIterator(identity, flatness);
return approxArea(i);
}
public static double approxArea(PathIterator i) {
double a = 0.0;
double[] coords = new double[6];
double startX = NaN, startY = NaN;
Line2D segment = new Line2D.Double(NaN, NaN, NaN, NaN);
while (! i.isDone()) {
int segType = i.currentSegment(coords);
double x = coords[0], y = coords[1];
switch (segType) {
case PathIterator.SEG_CLOSE:
segment.setLine(segment.getX2(), segment.getY2(), startX, startY);
a += hexArea(segment);
startX = startY = NaN;
segment.setLine(NaN, NaN, NaN, NaN);
break;
case PathIterator.SEG_LINETO:
segment.setLine(segment.getX2(), segment.getY2(), x, y);
a += hexArea(segment);
break;
case PathIterator.SEG_MOVETO:
startX = x;
startY = y;
segment.setLine(NaN, NaN, x, y);
break;
default:
throw new IllegalArgumentException("PathIterator contains curved segments");
}
i.next();
}
if (Double.isNaN(a)) {
throw new IllegalArgumentException("PathIterator contains an open path");
} else {
return 0.5 * Math.abs(a);
}
}
private static double hexArea(Line2D seg) {
return seg.getX1() * seg.getY2() - seg.getX2() * seg.getY1();
}
private static final AffineTransform identity =
AffineTransform.getQuadrantRotateInstance(0);
}
One approach would be to fill() each scaled and transformed Shape with a different color using a suitable AlphaComposite and count the overlapping pixels in the underlying Raster.
Addendum 1: Using this calculator to see the effect of AlphaComposite.Xor shows that the intersetion of any two opaque colors is zero.
Addendum 2: Counting pixels may have performance problems; sampling may help. If each Shape is reasonably convex, it may be possible to estimate the overlap from the ratio of the intersect() area to the sum of the areas of the Shapes' getBounds2D(). For example,
Shape s1, s2 ...
Rectangle2D r1 = s1.getBounds2D();
Rectangle2D r2 = s2.getBounds2D();
Rectangle2D r3 = new Rectangle2D.Double();
Rectangle2D.intersect(r1, r2, r3);
double overlap = area(r3) / (area(r1) + area(r2));
...
private double area(Rectangle2D r) {
return r.getWidth() * r.getHeight();
}
You may need to validate the results empirically.
I would comment if I could. Suraj, your algorithm is correct, but the code should be
int sum = 0;
for (int i = 0; i < npoints ; i++)
{
sum = sum + Xs[i]*Ys[(i+1)%npoints] - Ys[i]*Xs[(i+1)%npoints];
}
return Math.abs(sum / 2);
In your code last vertice is not taken into account. Just a small edit :)
The given answer is not accurate , I have found that following solution gives much better results
private int calcAreaSize(Area area){
int sum = 0;
float xBegin=0, yBegin=0, xPrev=0, yPrev=0, coords[] = new float[6];
for (PathIterator iterator1 = area.getPathIterator(null, 0.1); !iterator1.isDone(); iterator1.next()){
switch (iterator1.currentSegment(coords))
{
case PathIterator.SEG_MOVETO:
xBegin = coords[0]; yBegin = coords[1];
break;
case PathIterator.SEG_LINETO:
// the well-known trapez-formula
sum += (coords[0] - xPrev) * (coords[1] + yPrev) / 2.0;
break;
case PathIterator.SEG_CLOSE:
sum += (xBegin - xPrev) * (yBegin + yPrev) / 2.0;
break;
default:
// curved segments cannot occur, because we have a flattened ath
throw new InternalError();
}
xPrev = coords[0]; yPrev = coords[1];
}
return sum;
}

Categories

Resources