From 216d3f60dd2f1d790cb17aaac45b947c6d3c78c9 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 22 Apr 2015 19:07:03 +0200 Subject: [PATCH 01/39] Clean curve package --- curve/arc.go | 4 +- curve/cubic_float64.go | 49 ++- curve/cubic_float64_others.go | 696 ---------------------------------- curve/curve_test.go | 156 +------- curve/quad_float64.go | 18 +- curve/tracer.go | 6 + 6 files changed, 65 insertions(+), 864 deletions(-) delete mode 100644 curve/cubic_float64_others.go create mode 100644 curve/tracer.go diff --git a/curve/arc.go b/curve/arc.go index 583c158..9c8fb82 100644 --- a/curve/arc.go +++ b/curve/arc.go @@ -1,12 +1,14 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 21/11/2010 by Laurent Le Goff + package curve import ( "math" ) -func SegmentArc(t LineTracer, x, y, rx, ry, start, angle, scale float64) { +// TraceArc trace an arc using a LineTracer +func TraceArc(t LineTracer, x, y, rx, ry, start, angle, scale float64) { end := start + angle clockWise := true if angle < 0 { diff --git a/curve/cubic_float64.go b/curve/cubic_float64.go index 64a7ac6..8c19eb2 100644 --- a/curve/cubic_float64.go +++ b/curve/cubic_float64.go @@ -1,5 +1,7 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 17/05/2011 by Laurent Le Goff + +// Package curve implements Bezier Curve Subdivision using De Casteljau's algorithm package curve import ( @@ -10,38 +12,48 @@ const ( CurveRecursionLimit = 32 ) -// X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64 +// x1, y1, cpx1, cpx2, cpx2, cpy2, x2, y2 float64 type CubicCurveFloat64 [8]float64 -type LineTracer interface { - LineTo(x, y float64) -} - -func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) { - // Calculate all the mid-points of the line segments - //---------------------- +// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves. +// c1 and c2 parameters are the resulting curves +func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) { + // First point of c is the first point of c1 c1[0], c1[1] = c[0], c[1] + // Last point of c is the last point of c2 c2[6], c2[7] = c[6], c[7] + + // Subdivide segment using midpoints c1[2] = (c[0] + c[2]) / 2 c1[3] = (c[1] + c[3]) / 2 - x23 = (c[2] + c[4]) / 2 - y23 = (c[3] + c[5]) / 2 + + midX := (c[2] + c[4]) / 2 + midY := (c[3] + c[5]) / 2 + c2[4] = (c[4] + c[6]) / 2 c2[5] = (c[5] + c[7]) / 2 - c1[4] = (c1[2] + x23) / 2 - c1[5] = (c1[3] + y23) / 2 - c2[2] = (x23 + c2[4]) / 2 - c2[3] = (y23 + c2[5]) / 2 + + c1[4] = (c1[2] + midX) / 2 + c1[5] = (c1[3] + midY) / 2 + + c2[2] = (midX + c2[4]) / 2 + c2[3] = (midY + c2[5]) / 2 + c1[6] = (c1[4] + c2[2]) / 2 c1[7] = (c1[5] + c2[3]) / 2 + + // Last Point of c1 is equal to the first point of c2 c2[0], c2[1] = c1[6], c1[7] - return } -func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) { +// Trace generate lines subdividing the curve using a LineTracer +// flattening_threshold helps determines the flattening expectation of the curve +func (curve *CubicCurveFloat64) Trace(t LineTracer, flattening_threshold float64) { + // Allocation curves var curves [CurveRecursionLimit]CubicCurveFloat64 curves[0] = *curve i := 0 + // current curve var c *CubicCurveFloat64 @@ -52,9 +64,10 @@ func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float dx = c[6] - c[0] dy = c[7] - c[1] - d2 = math.Abs(((c[2]-c[6])*dy - (c[3]-c[7])*dx)) - d3 = math.Abs(((c[4]-c[6])*dy - (c[5]-c[7])*dx)) + d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx) + d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx) + // if it's flat then trace a line if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { t.LineTo(c[6], c[7]) i-- diff --git a/curve/cubic_float64_others.go b/curve/cubic_float64_others.go deleted file mode 100644 index 497f1d3..0000000 --- a/curve/cubic_float64_others.go +++ /dev/null @@ -1,696 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 17/05/2011 by Laurent Le Goff -package curve - -import ( - "math" -) - -const ( - CurveCollinearityEpsilon = 1e-30 - CurveAngleToleranceEpsilon = 0.01 -) - -//mu ranges from 0 to 1, start to end of curve -func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) { - - mum1 := 1 - mu - mum13 := mum1 * mum1 * mum1 - mu3 := mu * mu * mu - - x = mum13*c[0] + 3*mu*mum1*mum1*c[2] + 3*mu*mu*mum1*c[4] + mu3*c[6] - y = mum13*c[1] + 3*mu*mum1*mum1*c[3] + 3*mu*mu*mum1*c[5] + mu3*c[7] - return -} - -func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) (x23, y23 float64) { - inv_t := (1 - t) - c1[0], c1[1] = c[0], c[1] - c2[6], c2[7] = c[6], c[7] - - c1[2] = inv_t*c[0] + t*c[2] - c1[3] = inv_t*c[1] + t*c[3] - - x23 = inv_t*c[2] + t*c[4] - y23 = inv_t*c[3] + t*c[5] - - c2[4] = inv_t*c[4] + t*c[6] - c2[5] = inv_t*c[5] + t*c[7] - - c1[4] = inv_t*c1[2] + t*x23 - c1[5] = inv_t*c1[3] + t*y23 - - c2[2] = inv_t*x23 + t*c2[4] - c2[3] = inv_t*y23 + t*c2[5] - - c1[6] = inv_t*c1[4] + t*c2[2] - c1[7] = inv_t*c1[5] + t*c2[3] - - c2[0], c2[1] = c1[6], c1[7] - return -} - -func (c *CubicCurveFloat64) EstimateDistance() float64 { - dx1 := c[2] - c[0] - dy1 := c[3] - c[1] - dx2 := c[4] - c[2] - dy2 := c[5] - c[3] - dx3 := c[6] - c[4] - dy3 := c[7] - c[5] - return math.Sqrt(dx1*dx1+dy1*dy1) + math.Sqrt(dx2*dx2+dy2*dy2) + math.Sqrt(dx3*dx3+dy3*dy3) -} - -// subdivide the curve in straight lines using line approximation and Casteljau recursive subdivision -func (c *CubicCurveFloat64) SegmentRec(t LineTracer, flattening_threshold float64) { - c.segmentRec(t, flattening_threshold) - t.LineTo(c[6], c[7]) -} - -func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float64) { - var c1, c2 CubicCurveFloat64 - c.Subdivide(&c1, &c2) - - // Try to approximate the full cubic curve by a single straight line - //------------------ - dx := c[6] - c[0] - dy := c[7] - c[1] - - d2 := math.Abs(((c[2]-c[6])*dy - (c[3]-c[7])*dx)) - d3 := math.Abs(((c[4]-c[6])*dy - (c[5]-c[7])*dx)) - - if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) { - t.LineTo(c[6], c[7]) - return - } - // Continue subdivision - //---------------------- - c1.segmentRec(t, flattening_threshold) - c2.segmentRec(t, flattening_threshold) -} - -/* - The function has the following parameters: - approximationScale : - Eventually determines the approximation accuracy. In practice we need to transform points from the World coordinate system to the Screen one. - It always has some scaling coefficient. - The curves are usually processed in the World coordinates, while the approximation accuracy should be eventually in pixels. - Usually it looks as follows: - curved.approximationScale(transform.scale()); - where transform is the affine matrix that includes all the transformations, including viewport and zoom. - angleTolerance : - You set it in radians. - The less this value is the more accurate will be the approximation at sharp turns. - But 0 means that we don't consider angle conditions at all. - cuspLimit : - An angle in radians. - If 0, only the real cusps will have bevel cuts. - If more than 0, it will restrict the sharpness. - The more this value is the less sharp turns will be cut. - Typically it should not exceed 10-15 degrees. -*/ -func (c *CubicCurveFloat64) AdaptiveSegmentRec(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) { - cuspLimit = computeCuspLimit(cuspLimit) - distanceToleranceSquare := 0.5 / approximationScale - distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare - c.adaptiveSegmentRec(t, 0, distanceToleranceSquare, angleTolerance, cuspLimit) - t.LineTo(c[6], c[7]) -} - -func computeCuspLimit(v float64) (r float64) { - if v == 0.0 { - r = 0.0 - } else { - r = math.Pi - v - } - return -} - -func squareDistance(x1, y1, x2, y2 float64) float64 { - dx := x2 - x1 - dy := y2 - y1 - return dx*dx + dy*dy -} - -/** - * http://www.antigrain.com/research/adaptive_bezier/index.html - */ -func (c *CubicCurveFloat64) adaptiveSegmentRec(t LineTracer, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) { - if level > CurveRecursionLimit { - return - } - var c1, c2 CubicCurveFloat64 - x23, y23 := c.Subdivide(&c1, &c2) - - // Try to approximate the full cubic curve by a single straight line - //------------------ - dx := c[6] - c[0] - dy := c[7] - c[1] - - d2 := math.Abs(((c[2]-c[6])*dy - (c[3]-c[7])*dx)) - d3 := math.Abs(((c[4]-c[6])*dy - (c[5]-c[7])*dx)) - switch { - case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon: - // All collinear OR p1==p4 - //---------------------- - k := dx*dx + dy*dy - if k == 0 { - d2 = squareDistance(c[0], c[1], c[2], c[3]) - d3 = squareDistance(c[6], c[7], c[4], c[5]) - } else { - k = 1 / k - da1 := c[2] - c[0] - da2 := c[3] - c[1] - d2 = k * (da1*dx + da2*dy) - da1 = c[4] - c[0] - da2 = c[5] - c[1] - d3 = k * (da1*dx + da2*dy) - if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 { - // Simple collinear case, 1---2---3---4 - // We can leave just two endpoints - return - } - if d2 <= 0 { - d2 = squareDistance(c[2], c[3], c[0], c[1]) - } else if d2 >= 1 { - d2 = squareDistance(c[2], c[3], c[6], c[7]) - } else { - d2 = squareDistance(c[2], c[3], c[0]+d2*dx, c[1]+d2*dy) - } - - if d3 <= 0 { - d3 = squareDistance(c[4], c[5], c[0], c[1]) - } else if d3 >= 1 { - d3 = squareDistance(c[4], c[5], c[6], c[7]) - } else { - d3 = squareDistance(c[4], c[5], c[0]+d3*dx, c[1]+d3*dy) - } - } - if d2 > d3 { - if d2 < distanceToleranceSquare { - t.LineTo(c[2], c[3]) - return - } - } else { - if d3 < distanceToleranceSquare { - t.LineTo(c[4], c[5]) - return - } - } - - case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon: - // p1,p2,p4 are collinear, p3 is significant - //---------------------- - if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) { - if angleTolerance < CurveAngleToleranceEpsilon { - t.LineTo(x23, y23) - return - } - - // Angle Condition - //---------------------- - da1 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - math.Atan2(c[5]-c[3], c[4]-c[2])) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - - if da1 < angleTolerance { - t.LineTo(c[2], c[3]) - t.LineTo(c[4], c[5]) - return - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - t.LineTo(c[4], c[5]) - return - } - } - } - - case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon: - // p1,p3,p4 are collinear, p2 is significant - //---------------------- - if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) { - if angleTolerance < CurveAngleToleranceEpsilon { - t.LineTo(x23, y23) - return - } - - // Angle Condition - //---------------------- - da1 := math.Abs(math.Atan2(c[5]-c[3], c[4]-c[2]) - math.Atan2(c[3]-c[1], c[2]-c[0])) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - - if da1 < angleTolerance { - t.LineTo(c[2], c[3]) - t.LineTo(c[4], c[5]) - return - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - t.LineTo(c[2], c[3]) - return - } - } - } - - case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon: - // Regular case - //----------------- - if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) { - // If the curvature doesn't exceed the distanceTolerance value - // we tend to finish subdivisions. - //---------------------- - if angleTolerance < CurveAngleToleranceEpsilon { - t.LineTo(x23, y23) - return - } - - // Angle & Cusp Condition - //---------------------- - k := math.Atan2(c[5]-c[3], c[4]-c[2]) - da1 := math.Abs(k - math.Atan2(c[3]-c[1], c[2]-c[0])) - da2 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - k) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - if da2 >= math.Pi { - da2 = 2*math.Pi - da2 - } - - if da1+da2 < angleTolerance { - // Finally we can stop the recursion - //---------------------- - t.LineTo(x23, y23) - return - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - t.LineTo(c[2], c[3]) - return - } - - if da2 > cuspLimit { - t.LineTo(c[4], c[5]) - return - } - } - } - } - - // Continue subdivision - //---------------------- - c1.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit) - c2.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit) - -} - -func (curve *CubicCurveFloat64) AdaptiveSegment(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) { - cuspLimit = computeCuspLimit(cuspLimit) - distanceToleranceSquare := 0.5 / approximationScale - distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare - - var curves [CurveRecursionLimit]CubicCurveFloat64 - curves[0] = *curve - i := 0 - // current curve - var c *CubicCurveFloat64 - var c1, c2 CubicCurveFloat64 - var dx, dy, d2, d3, k, x23, y23 float64 - for i >= 0 { - c = &curves[i] - x23, y23 = c.Subdivide(&c1, &c2) - - // Try to approximate the full cubic curve by a single straight line - //------------------ - dx = c[6] - c[0] - dy = c[7] - c[1] - - d2 = math.Abs(((c[2]-c[6])*dy - (c[3]-c[7])*dx)) - d3 = math.Abs(((c[4]-c[6])*dy - (c[5]-c[7])*dx)) - switch { - case i == len(curves)-1: - t.LineTo(c[6], c[7]) - i-- - continue - case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon: - // All collinear OR p1==p4 - //---------------------- - k = dx*dx + dy*dy - if k == 0 { - d2 = squareDistance(c[0], c[1], c[2], c[3]) - d3 = squareDistance(c[6], c[7], c[4], c[5]) - } else { - k = 1 / k - da1 := c[2] - c[0] - da2 := c[3] - c[1] - d2 = k * (da1*dx + da2*dy) - da1 = c[4] - c[0] - da2 = c[5] - c[1] - d3 = k * (da1*dx + da2*dy) - if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 { - // Simple collinear case, 1---2---3---4 - // We can leave just two endpoints - i-- - continue - } - if d2 <= 0 { - d2 = squareDistance(c[2], c[3], c[0], c[1]) - } else if d2 >= 1 { - d2 = squareDistance(c[2], c[3], c[6], c[7]) - } else { - d2 = squareDistance(c[2], c[3], c[0]+d2*dx, c[1]+d2*dy) - } - - if d3 <= 0 { - d3 = squareDistance(c[4], c[5], c[0], c[1]) - } else if d3 >= 1 { - d3 = squareDistance(c[4], c[5], c[6], c[7]) - } else { - d3 = squareDistance(c[4], c[5], c[0]+d3*dx, c[1]+d3*dy) - } - } - if d2 > d3 { - if d2 < distanceToleranceSquare { - t.LineTo(c[2], c[3]) - i-- - continue - } - } else { - if d3 < distanceToleranceSquare { - t.LineTo(c[4], c[5]) - i-- - continue - } - } - - case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon: - // p1,p2,p4 are collinear, p3 is significant - //---------------------- - if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) { - if angleTolerance < CurveAngleToleranceEpsilon { - t.LineTo(x23, y23) - i-- - continue - } - - // Angle Condition - //---------------------- - da1 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - math.Atan2(c[5]-c[3], c[4]-c[2])) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - - if da1 < angleTolerance { - t.LineTo(c[2], c[3]) - t.LineTo(c[4], c[5]) - i-- - continue - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - t.LineTo(c[4], c[5]) - i-- - continue - } - } - } - - case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon: - // p1,p3,p4 are collinear, p2 is significant - //---------------------- - if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) { - if angleTolerance < CurveAngleToleranceEpsilon { - t.LineTo(x23, y23) - i-- - continue - } - - // Angle Condition - //---------------------- - da1 := math.Abs(math.Atan2(c[5]-c[3], c[4]-c[2]) - math.Atan2(c[3]-c[1], c[2]-c[0])) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - - if da1 < angleTolerance { - t.LineTo(c[2], c[3]) - t.LineTo(c[4], c[5]) - i-- - continue - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - t.LineTo(c[2], c[3]) - i-- - continue - } - } - } - - case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon: - // Regular case - //----------------- - if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) { - // If the curvature doesn't exceed the distanceTolerance value - // we tend to finish subdivisions. - //---------------------- - if angleTolerance < CurveAngleToleranceEpsilon { - t.LineTo(x23, y23) - i-- - continue - } - - // Angle & Cusp Condition - //---------------------- - k := math.Atan2(c[5]-c[3], c[4]-c[2]) - da1 := math.Abs(k - math.Atan2(c[3]-c[1], c[2]-c[0])) - da2 := math.Abs(math.Atan2(c[7]-c[5], c[6]-c[4]) - k) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - if da2 >= math.Pi { - da2 = 2*math.Pi - da2 - } - - if da1+da2 < angleTolerance { - // Finally we can stop the recursion - //---------------------- - t.LineTo(x23, y23) - i-- - continue - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - t.LineTo(c[2], c[3]) - i-- - continue - } - - if da2 > cuspLimit { - t.LineTo(c[4], c[5]) - i-- - continue - } - } - } - } - - // Continue subdivision - //---------------------- - curves[i+1], curves[i] = c1, c2 - i++ - } - t.LineTo(curve[6], curve[7]) -} - -/********************** Ahmad thesis *******************/ - -/************************************************************************************** -* This code is the implementation of the Parabolic Approximation (PA). Although * -* it uses recursive subdivision as a safe net for the failing cases, this is an * -* iterative routine and reduces considerably the number of vertices (point) * -* generation. * -**************************************************************************************/ - -func (c *CubicCurveFloat64) ParabolicSegment(t LineTracer, flattening_threshold float64) { - estimatedIFP := c.numberOfInflectionPoints() - if estimatedIFP == 0 { - // If no inflection points then apply PA on the full Bezier segment. - c.doParabolicApproximation(t, flattening_threshold) - return - } - // If one or more inflection point then we will have to subdivide the curve - numOfIfP, t1, t2 := c.findInflectionPoints() - if numOfIfP == 2 { - // Case when 2 inflection points then divide at the smallest one first - var sub1, tmp1, sub2, sub3 CubicCurveFloat64 - c.SubdivideAt(&sub1, &tmp1, t1) - // Now find the second inflection point in the second curve an subdivide - numOfIfP, t1, t2 = tmp1.findInflectionPoints() - if numOfIfP == 2 { - tmp1.SubdivideAt(&sub2, &sub3, t2) - } else if numOfIfP == 1 { - tmp1.SubdivideAt(&sub2, &sub3, t1) - } else { - return - } - // Use PA for first subsegment - sub1.doParabolicApproximation(t, flattening_threshold) - // Use RS for the second (middle) subsegment - sub2.Segment(t, flattening_threshold) - // Drop the last point in the array will be added by the PA in third subsegment - //noOfPoints--; - // Use PA for the third curve - sub3.doParabolicApproximation(t, flattening_threshold) - } else if numOfIfP == 1 { - // Case where there is one inflection point, subdivide once and use PA on - // both subsegments - var sub1, sub2 CubicCurveFloat64 - c.SubdivideAt(&sub1, &sub2, t1) - sub1.doParabolicApproximation(t, flattening_threshold) - //noOfPoints--; - sub2.doParabolicApproximation(t, flattening_threshold) - } else { - // Case where there is no inflection USA PA directly - c.doParabolicApproximation(t, flattening_threshold) - } -} - -// Find the third control point deviation form the axis -func (c *CubicCurveFloat64) thirdControlPointDeviation() float64 { - dx := c[2] - c[0] - dy := c[3] - c[1] - l2 := dx*dx + dy*dy - if l2 == 0 { - return 0 - } - l := math.Sqrt(l2) - r := (c[3] - c[1]) / l - s := (c[0] - c[2]) / l - u := (c[2]*c[1] - c[0]*c[3]) / l - return math.Abs(r*c[4] + s*c[5] + u) -} - -// Find the number of inflection point -func (c *CubicCurveFloat64) numberOfInflectionPoints() int { - dx21 := (c[2] - c[0]) - dy21 := (c[3] - c[1]) - dx32 := (c[4] - c[2]) - dy32 := (c[5] - c[3]) - dx43 := (c[6] - c[4]) - dy43 := (c[7] - c[5]) - if ((dx21*dy32 - dy21*dx32) * (dx32*dy43 - dy32*dx43)) < 0 { - return 1 // One inflection point - } else if ((dx21*dy32 - dy21*dx32) * (dx21*dy43 - dy21*dx43)) > 0 { - return 0 // No inflection point - } else { - // Most cases no inflection point - b1 := (dx21*dx32 + dy21*dy32) > 0 - b2 := (dx32*dx43 + dy32*dy43) > 0 - if b1 || b2 && !(b1 && b2) { // xor!! - return 0 - } - } - return -1 // cases where there in zero or two inflection points -} - -// This is the main function where all the work is done -func (curve *CubicCurveFloat64) doParabolicApproximation(tracer LineTracer, flattening_threshold float64) { - var c *CubicCurveFloat64 - c = curve - var d, t, dx, dy, d2, d3 float64 - for { - dx = c[6] - c[0] - dy = c[7] - c[1] - - d2 = math.Abs(((c[2]-c[6])*dy - (c[3]-c[7])*dx)) - d3 = math.Abs(((c[4]-c[6])*dy - (c[5]-c[7])*dx)) - - if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) { - // If the subsegment deviation satisfy the flatness then store the last - // point and stop - tracer.LineTo(c[6], c[7]) - break - } - // Find the third control point deviation and the t values for subdivision - d = c.thirdControlPointDeviation() - t = 2 * math.Sqrt(flattening_threshold/d/3) - if t > 1 { - // Case where the t value calculated is invalid so using RS - c.Segment(tracer, flattening_threshold) - break - } - // Valid t value to subdivide at that calculated value - var b1, b2 CubicCurveFloat64 - c.SubdivideAt(&b1, &b2, t) - // First subsegment should have its deviation equal to flatness - dx = b1[6] - b1[0] - dy = b1[7] - b1[1] - - d2 = math.Abs(((b1[2]-b1[6])*dy - (b1[3]-b1[7])*dx)) - d3 = math.Abs(((b1[4]-b1[6])*dy - (b1[5]-b1[7])*dx)) - - if (d2+d3)*(d2+d3) > flattening_threshold*(dx*dx+dy*dy) { - // if not then use RS to handle any mathematical errors - b1.Segment(tracer, flattening_threshold) - } else { - tracer.LineTo(b1[6], b1[7]) - } - // repeat the process for the left over subsegment. - c = &b2 - } -} - -// Find the actual inflection points and return the number of inflection points found -// if 2 inflection points found, the first one returned will be with smaller t value. -func (curve *CubicCurveFloat64) findInflectionPoints() (int, firstIfp, secondIfp float64) { - // For Cubic Bezier curve with equation P=a*t^3 + b*t^2 + c*t + d - // slope of the curve dP/dt = 3*a*t^2 + 2*b*t + c - // a = (float)(-bez.p1 + 3*bez.p2 - 3*bez.p3 + bez.p4); - // b = (float)(3*bez.p1 - 6*bez.p2 + 3*bez.p3); - // c = (float)(-3*bez.p1 + 3*bez.p2); - ax := (-curve[0] + 3*curve[2] - 3*curve[4] + curve[6]) - bx := (3*curve[0] - 6*curve[2] + 3*curve[4]) - cx := (-3*curve[0] + 3*curve[2]) - ay := (-curve[1] + 3*curve[3] - 3*curve[5] + curve[7]) - by := (3*curve[1] - 6*curve[3] + 3*curve[5]) - cy := (-3*curve[1] + 3*curve[3]) - a := (3 * (ay*bx - ax*by)) - b := (3 * (ay*cx - ax*cy)) - c := (by*cx - bx*cy) - r2 := (b*b - 4*a*c) - firstIfp = 0.0 - secondIfp = 0.0 - if r2 >= 0.0 && a != 0.0 { - r := math.Sqrt(r2) - firstIfp = ((-b + r) / (2 * a)) - secondIfp = ((-b - r) / (2 * a)) - if (firstIfp > 0.0 && firstIfp < 1.0) && (secondIfp > 0.0 && secondIfp < 1.0) { - if firstIfp > secondIfp { - tmp := firstIfp - firstIfp = secondIfp - secondIfp = tmp - } - if secondIfp-firstIfp > 0.00001 { - return 2, firstIfp, secondIfp - } else { - return 1, firstIfp, secondIfp - } - } else if firstIfp > 0.0 && firstIfp < 1.0 { - return 1, firstIfp, secondIfp - } else if secondIfp > 0.0 && secondIfp < 1.0 { - firstIfp = secondIfp - return 1, firstIfp, secondIfp - } - return 0, firstIfp, secondIfp - } - return 0, firstIfp, secondIfp -} diff --git a/curve/curve_test.go b/curve/curve_test.go index c101e6a..2b4bb6a 100644 --- a/curve/curve_test.go +++ b/curve/curve_test.go @@ -1,16 +1,15 @@ package curve import ( - "bufio" "fmt" "image" "image/color" "image/draw" - "image/png" "log" "os" "testing" + "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/raster" ) @@ -51,7 +50,8 @@ func (p *Path) LineTo(x, y float64) { } func init() { - f, err := os.Create("_test.html") + os.Mkdir("test_results", 0666) + f, err := os.Create("test_results/_test.html") if err != nil { log.Println(err) os.Exit(1) @@ -60,7 +60,7 @@ func init() { log.Printf("Create html viewer") f.Write([]byte("")) for i := 0; i < len(testsCubicFloat64); i++ { - f.Write([]byte(fmt.Sprintf("
\n\n\n\n\n
\n", i, i, i, i, i))) + f.Write([]byte(fmt.Sprintf("
\n", i))) } for i := 0; i < len(testsQuadFloat64); i++ { f.Write([]byte(fmt.Sprintf("
\n
\n", i))) @@ -69,28 +69,8 @@ func init() { } -func savepng(filePath string, m image.Image) { - f, err := os.Create(filePath) - if err != nil { - log.Println(err) - os.Exit(1) - } - defer f.Close() - b := bufio.NewWriter(f) - err = png.Encode(b, m) - if err != nil { - log.Println(err) - os.Exit(1) - } - err = b.Flush() - if err != nil { - log.Println(err) - os.Exit(1) - } -} - func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image { - /*for i := 0; i < len(s); i += 2 { + for i := 0; i < len(s); i += 2 { x, y := int(s[i]+0.5), int(s[i+1]+0.5) img.Set(x, y, c) img.Set(x, y+1, c) @@ -102,85 +82,21 @@ func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image { img.Set(x-1, y+1, c) img.Set(x-1, y-1, c) - }*/ - return img -} - -func TestCubicCurveRec(t *testing.T) { - for i, curve := range testsCubicFloat64 { - var p Path - p.LineTo(curve[0], curve[1]) - curve.SegmentRec(&p, flattening_threshold) - img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) - raster.PolylineBresenham(img, image.Black, p.points...) - //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - savepng(fmt.Sprintf("_testRec%d.png", i), img) - log.Printf("Num of points: %d\n", len(p.points)) } - fmt.Println() + return img } func TestCubicCurve(t *testing.T) { for i, curve := range testsCubicFloat64 { var p Path p.LineTo(curve[0], curve[1]) - curve.Segment(&p, flattening_threshold) + curve.Trace(&p, flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) raster.PolylineBresenham(img, image.Black, p.points...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - savepng(fmt.Sprintf("_test%d.png", i), img) - log.Printf("Num of points: %d\n", len(p.points)) - } - fmt.Println() -} - -func TestCubicCurveAdaptiveRec(t *testing.T) { - for i, curve := range testsCubicFloat64 { - var p Path - p.LineTo(curve[0], curve[1]) - curve.AdaptiveSegmentRec(&p, 1, 0, 0) - img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) - raster.PolylineBresenham(img, image.Black, p.points...) - //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - savepng(fmt.Sprintf("_testAdaptiveRec%d.png", i), img) - log.Printf("Num of points: %d\n", len(p.points)) - } - fmt.Println() -} - -func TestCubicCurveAdaptive(t *testing.T) { - for i, curve := range testsCubicFloat64 { - var p Path - p.LineTo(curve[0], curve[1]) - curve.AdaptiveSegment(&p, 1, 0, 0) - img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) - raster.PolylineBresenham(img, image.Black, p.points...) - //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - savepng(fmt.Sprintf("_testAdaptive%d.png", i), img) - log.Printf("Num of points: %d\n", len(p.points)) - } - fmt.Println() -} - -func TestCubicCurveParabolic(t *testing.T) { - for i, curve := range testsCubicFloat64 { - var p Path - p.LineTo(curve[0], curve[1]) - curve.ParabolicSegment(&p, flattening_threshold) - img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) - raster.PolylineBresenham(img, image.Black, p.points...) - //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - savepng(fmt.Sprintf("_testParabolic%d.png", i), img) + draw2d.SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i), img) log.Printf("Num of points: %d\n", len(p.points)) } fmt.Println() @@ -190,74 +106,24 @@ func TestQuadCurve(t *testing.T) { for i, curve := range testsQuadFloat64 { var p Path p.LineTo(curve[0], curve[1]) - curve.Segment(&p, flattening_threshold) + curve.Trace(&p, flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) raster.PolylineBresenham(img, image.Black, p.points...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - savepng(fmt.Sprintf("_testQuad%d.png", i), img) + draw2d.SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) log.Printf("Num of points: %d\n", len(p.points)) } fmt.Println() } -func BenchmarkCubicCurveRec(b *testing.B) { - for i := 0; i < b.N; i++ { - for _, curve := range testsCubicFloat64 { - p := Path{make([]float64, 0, 32)} - p.LineTo(curve[0], curve[1]) - curve.SegmentRec(&p, flattening_threshold) - } - } -} - func BenchmarkCubicCurve(b *testing.B) { for i := 0; i < b.N; i++ { for _, curve := range testsCubicFloat64 { p := Path{make([]float64, 0, 32)} p.LineTo(curve[0], curve[1]) - curve.Segment(&p, flattening_threshold) - } - } -} - -func BenchmarkCubicCurveAdaptiveRec(b *testing.B) { - for i := 0; i < b.N; i++ { - for _, curve := range testsCubicFloat64 { - p := Path{make([]float64, 0, 32)} - p.LineTo(curve[0], curve[1]) - curve.AdaptiveSegmentRec(&p, 1, 0, 0) - } - } -} - -func BenchmarkCubicCurveAdaptive(b *testing.B) { - for i := 0; i < b.N; i++ { - for _, curve := range testsCubicFloat64 { - p := Path{make([]float64, 0, 32)} - p.LineTo(curve[0], curve[1]) - curve.AdaptiveSegment(&p, 1, 0, 0) - } - } -} - -func BenchmarkCubicCurveParabolic(b *testing.B) { - for i := 0; i < b.N; i++ { - for _, curve := range testsCubicFloat64 { - p := Path{make([]float64, 0, 32)} - p.LineTo(curve[0], curve[1]) - curve.ParabolicSegment(&p, flattening_threshold) - } - } -} - -func BenchmarkQuadCurve(b *testing.B) { - for i := 0; i < b.N; i++ { - for _, curve := range testsQuadFloat64 { - p := Path{make([]float64, 0, 32)} - p.LineTo(curve[0], curve[1]) - curve.Segment(&p, flattening_threshold) + curve.Trace(&p, flattening_threshold) } } } diff --git a/curve/quad_float64.go b/curve/quad_float64.go index bd72aff..6cb2891 100644 --- a/curve/quad_float64.go +++ b/curve/quad_float64.go @@ -1,19 +1,25 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 17/05/2011 by Laurent Le Goff + package curve import ( "math" ) -//X1, Y1, X2, Y2, X3, Y3 float64 +//x1, y1, cpx1, cpy2, x2, y2 float64 type QuadCurveFloat64 [6]float64 +// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves. +// c1 and c2 parameters are the resulting curves func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) { - // Calculate all the mid-points of the line segments - //---------------------- + + // First point of c is the first point of c1 c1[0], c1[1] = c[0], c[1] + // Last point of c is the last point of c2 c2[4], c2[5] = c[4], c[5] + + // Subdivide segment using midpoints c1[2] = (c[0] + c[2]) / 2 c1[3] = (c[1] + c[3]) / 2 c2[2] = (c[2] + c[4]) / 2 @@ -24,7 +30,10 @@ func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) { return } -func (curve *QuadCurveFloat64) Segment(t LineTracer, flattening_threshold float64) { +// Trace generate lines subdividing the curve using a LineTracer +// flattening_threshold helps determines the flattening expectation of the curve +func (curve *QuadCurveFloat64) Trace(t LineTracer, flattening_threshold float64) { + // Allocates curves stack var curves [CurveRecursionLimit]QuadCurveFloat64 curves[0] = *curve i := 0 @@ -39,6 +48,7 @@ func (curve *QuadCurveFloat64) Segment(t LineTracer, flattening_threshold float6 d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx)) + // if it's flat then trace a line if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { t.LineTo(c[4], c[5]) i-- diff --git a/curve/tracer.go b/curve/tracer.go new file mode 100644 index 0000000..e80ce1d --- /dev/null +++ b/curve/tracer.go @@ -0,0 +1,6 @@ +package curve + +// LineTracer is an interface that help segmenting curve into small lines +type LineTracer interface { + LineTo(x, y float64) +} From 4b3ba53f4c46f15cd095e50aa9c811489df63471 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 10:05:48 +0200 Subject: [PATCH 02/39] migrating to new curve package --- arc.go | 2 +- curve/arc.go | 4 +- curve/{cubic_float64.go => cubic.go} | 22 ++--- curve/curve_test.go | 95 +++++++++++++--------- curve/{quad_float64.go => quad.go} | 21 +++-- curve/tracer.go | 3 +- curves.go | 34 ++++---- dasher.go | 12 +-- demux_converter.go | 4 +- path_adder.go | 2 +- path_converter.go | 117 +++++++++++++-------------- path_storage.go | 24 +++--- raster/raster_test.go | 16 ++-- stroker.go | 8 +- transform.go | 4 +- vertex2d.go | 2 +- 16 files changed, 192 insertions(+), 178 deletions(-) rename curve/{cubic_float64.go => cubic.go} (75%) rename curve/{quad_float64.go => quad.go} (74%) diff --git a/arc.go b/arc.go index 1bfef06..a40c8b8 100644 --- a/arc.go +++ b/arc.go @@ -33,7 +33,7 @@ func arc(t VertexConverter, x, y, rx, ry, start, angle, scale float64) (lastX, l curY = y + math.Sin(angle)*ry angle += da - t.Vertex(curX, curY) + t.AddPoint(curX, curY) } return curX, curY } diff --git a/curve/arc.go b/curve/arc.go index 9c8fb82..1ed1426 100644 --- a/curve/arc.go +++ b/curve/arc.go @@ -32,7 +32,7 @@ func TraceArc(t LineTracer, x, y, rx, ry, start, angle, scale float64) { curY = y + math.Sin(angle)*ry angle += da - t.LineTo(curX, curY) + t.AddPoint(curX, curY) } - t.LineTo(curX, curY) + t.AddPoint(curX, curY) } diff --git a/curve/cubic_float64.go b/curve/cubic.go similarity index 75% rename from curve/cubic_float64.go rename to curve/cubic.go index 8c19eb2..af7995d 100644 --- a/curve/cubic_float64.go +++ b/curve/cubic.go @@ -12,12 +12,12 @@ const ( CurveRecursionLimit = 32 ) -// x1, y1, cpx1, cpx2, cpx2, cpy2, x2, y2 float64 -type CubicCurveFloat64 [8]float64 +// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64 +// type Cubic []float64 // Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves. // c1 and c2 parameters are the resulting curves -func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) { +func SubdivideCubic(c, c1, c2 []float64) { // First point of c is the first point of c1 c1[0], c1[1] = c[0], c[1] // Last point of c is the last point of c2 @@ -46,21 +46,21 @@ func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) { c2[0], c2[1] = c1[6], c1[7] } -// Trace generate lines subdividing the curve using a LineTracer +// TraceCubic generate lines subdividing the cubic curve using a LineTracer // flattening_threshold helps determines the flattening expectation of the curve -func (curve *CubicCurveFloat64) Trace(t LineTracer, flattening_threshold float64) { +func TraceCubic(t LineTracer, cubic []float64, flattening_threshold float64) { // Allocation curves - var curves [CurveRecursionLimit]CubicCurveFloat64 - curves[0] = *curve + var curves [CurveRecursionLimit * 8]float64 + copy(curves[0:8], cubic[0:8]) i := 0 // current curve - var c *CubicCurveFloat64 + var c []float64 var dx, dy, d2, d3 float64 for i >= 0 { - c = &curves[i] + c = curves[i*8:] dx = c[6] - c[0] dy = c[7] - c[1] @@ -69,11 +69,11 @@ func (curve *CubicCurveFloat64) Trace(t LineTracer, flattening_threshold float64 // if it's flat then trace a line if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.LineTo(c[6], c[7]) + t.AddPoint(c[6], c[7]) i-- } else { // second half of bezier go lower onto the stack - c.Subdivide(&curves[i+1], &curves[i]) + SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:]) i++ } } diff --git a/curve/curve_test.go b/curve/curve_test.go index 2b4bb6a..e480a93 100644 --- a/curve/curve_test.go +++ b/curve/curve_test.go @@ -1,35 +1,36 @@ package curve import ( + "bufio" "fmt" "image" "image/color" "image/draw" + "image/png" "log" "os" "testing" - "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/raster" ) var ( flattening_threshold float64 = 0.5 - testsCubicFloat64 = []CubicCurveFloat64{ - CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200}, - CubicCurveFloat64{100, 100, 300, 200, 200, 200, 300, 100}, - CubicCurveFloat64{100, 100, 0, 300, 200, 0, 300, 300}, - CubicCurveFloat64{150, 290, 10, 10, 290, 10, 150, 290}, - CubicCurveFloat64{10, 290, 10, 10, 290, 10, 290, 290}, - CubicCurveFloat64{100, 290, 290, 10, 10, 10, 200, 290}, + testsCubicFloat64 = []float64{ + 100, 100, 200, 100, 100, 200, 200, 200, + 100, 100, 300, 200, 200, 200, 300, 100, + 100, 100, 0, 300, 200, 0, 300, 300, + 150, 290, 10, 10, 290, 10, 150, 290, + 10, 290, 10, 10, 290, 10, 290, 290, + 100, 290, 290, 10, 10, 10, 200, 290, } - testsQuadFloat64 = []QuadCurveFloat64{ - QuadCurveFloat64{100, 100, 200, 100, 200, 200}, - QuadCurveFloat64{100, 100, 290, 200, 290, 100}, - QuadCurveFloat64{100, 100, 0, 290, 200, 290}, - QuadCurveFloat64{150, 290, 10, 10, 290, 290}, - QuadCurveFloat64{10, 290, 10, 10, 290, 290}, - QuadCurveFloat64{100, 290, 290, 10, 120, 290}, + testsQuadFloat64 = []float64{ + 100, 100, 200, 100, 200, 200, + 100, 100, 290, 200, 290, 100, + 100, 100, 0, 290, 200, 290, + 150, 290, 10, 10, 290, 290, + 10, 290, 10, 10, 290, 290, + 100, 290, 290, 10, 120, 290, } ) @@ -37,16 +38,8 @@ type Path struct { points []float64 } -func (p *Path) LineTo(x, y float64) { - if len(p.points)+2 > cap(p.points) { - points := make([]float64, len(p.points)+2, len(p.points)+32) - copy(points, p.points) - p.points = points - } else { - p.points = p.points[0 : len(p.points)+2] - } - p.points[len(p.points)-2] = x - p.points[len(p.points)-1] = y +func (p *Path) AddPoint(x, y float64) { + p.points = append(p.points, x, y) } func init() { @@ -59,7 +52,7 @@ func init() { defer f.Close() log.Printf("Create html viewer") f.Write([]byte("")) - for i := 0; i < len(testsCubicFloat64); i++ { + for i := 0; i < len(testsCubicFloat64)/8; i++ { f.Write([]byte(fmt.Sprintf("
\n", i))) } for i := 0; i < len(testsQuadFloat64); i++ { @@ -87,32 +80,32 @@ func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image { } func TestCubicCurve(t *testing.T) { - for i, curve := range testsCubicFloat64 { + for i := 0; i < len(testsCubicFloat64); i += 8 { var p Path - p.LineTo(curve[0], curve[1]) - curve.Trace(&p, flattening_threshold) + p.AddPoint(testsCubicFloat64[i], testsCubicFloat64[i+1]) + TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) + raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...) raster.PolylineBresenham(img, image.Black, p.points...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - draw2d.SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i), img) + SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), img) log.Printf("Num of points: %d\n", len(p.points)) } fmt.Println() } func TestQuadCurve(t *testing.T) { - for i, curve := range testsQuadFloat64 { + for i := 0; i < len(testsQuadFloat64); i += 6 { var p Path - p.LineTo(curve[0], curve[1]) - curve.Trace(&p, flattening_threshold) + p.AddPoint(testsQuadFloat64[i], testsQuadFloat64[i+1]) + TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, curve[:]...) + raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...) raster.PolylineBresenham(img, image.Black, p.points...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) - draw2d.SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) + SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) log.Printf("Num of points: %d\n", len(p.points)) } fmt.Println() @@ -120,10 +113,32 @@ func TestQuadCurve(t *testing.T) { func BenchmarkCubicCurve(b *testing.B) { for i := 0; i < b.N; i++ { - for _, curve := range testsCubicFloat64 { - p := Path{make([]float64, 0, 32)} - p.LineTo(curve[0], curve[1]) - curve.Trace(&p, flattening_threshold) + for i := 0; i < len(testsCubicFloat64); i += 8 { + var p Path + p.AddPoint(testsCubicFloat64[i], testsCubicFloat64[i+1]) + TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) } } } + +// SaveToPngFile create and save an image to a file using PNG format +func SaveToPngFile(filePath string, m image.Image) error { + // Create the file + f, err := os.Create(filePath) + if err != nil { + return err + } + defer f.Close() + // Create Writer from file + b := bufio.NewWriter(f) + // Write the image into the buffer + err = png.Encode(b, m) + if err != nil { + return err + } + err = b.Flush() + if err != nil { + return err + } + return nil +} diff --git a/curve/quad_float64.go b/curve/quad.go similarity index 74% rename from curve/quad_float64.go rename to curve/quad.go index 6cb2891..6feb669 100644 --- a/curve/quad_float64.go +++ b/curve/quad.go @@ -7,13 +7,12 @@ import ( "math" ) -//x1, y1, cpx1, cpy2, x2, y2 float64 -type QuadCurveFloat64 [6]float64 +// x1, y1, cpx1, cpy2, x2, y2 float64 +// type Quad [6]float64 // Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves. // c1 and c2 parameters are the resulting curves -func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) { - +func SubdivideQuad(c, c1, c2 []float64) { // First point of c is the first point of c1 c1[0], c1[1] = c[0], c[1] // Last point of c is the last point of c2 @@ -32,17 +31,17 @@ func (c *QuadCurveFloat64) Subdivide(c1, c2 *QuadCurveFloat64) { // Trace generate lines subdividing the curve using a LineTracer // flattening_threshold helps determines the flattening expectation of the curve -func (curve *QuadCurveFloat64) Trace(t LineTracer, flattening_threshold float64) { +func TraceQuad(t LineTracer, quad []float64, flattening_threshold float64) { // Allocates curves stack - var curves [CurveRecursionLimit]QuadCurveFloat64 - curves[0] = *curve + var curves [CurveRecursionLimit * 6]float64 + copy(curves[0:6], quad[0:6]) i := 0 // current curve - var c *QuadCurveFloat64 + var c []float64 var dx, dy, d float64 for i >= 0 { - c = &curves[i] + c = curves[i*6:] dx = c[4] - c[0] dy = c[5] - c[1] @@ -50,11 +49,11 @@ func (curve *QuadCurveFloat64) Trace(t LineTracer, flattening_threshold float64) // if it's flat then trace a line if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.LineTo(c[4], c[5]) + t.AddPoint(c[4], c[5]) i-- } else { // second half of bezier go lower onto the stack - c.Subdivide(&curves[i+1], &curves[i]) + SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:]) i++ } } diff --git a/curve/tracer.go b/curve/tracer.go index e80ce1d..7cfb997 100644 --- a/curve/tracer.go +++ b/curve/tracer.go @@ -2,5 +2,6 @@ package curve // LineTracer is an interface that help segmenting curve into small lines type LineTracer interface { - LineTo(x, y float64) + // AddPoint a point + AddPoint(x, y float64) } diff --git a/curves.go b/curves.go index 98c9a97..7b8c0eb 100644 --- a/curves.go +++ b/curves.go @@ -88,7 +88,7 @@ func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 fl // we tend to finish subdivisions. //---------------------- if angleTolerance < CurveAngleToleranceEpsilon { - v.Vertex(x123, y123) + v.AddPoint(x123, y123) return } @@ -102,7 +102,7 @@ func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 fl if da < angleTolerance { // Finally we can stop the recursion //---------------------- - v.Vertex(x123, y123) + v.AddPoint(x123, y123) return } } @@ -128,7 +128,7 @@ func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 fl } } if d < distanceToleranceSquare { - v.Vertex(x2, y2) + v.AddPoint(x2, y2) return } } @@ -209,12 +209,12 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa } if d2 > d3 { if d2 < distanceToleranceSquare { - v.Vertex(x2, y2) + v.AddPoint(x2, y2) return } } else { if d3 < distanceToleranceSquare { - v.Vertex(x3, y3) + v.AddPoint(x3, y3) return } } @@ -225,7 +225,7 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa //---------------------- if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) { if angleTolerance < CurveAngleToleranceEpsilon { - v.Vertex(x23, y23) + v.AddPoint(x23, y23) return } @@ -237,14 +237,14 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa } if da1 < angleTolerance { - v.Vertex(x2, y2) - v.Vertex(x3, y3) + v.AddPoint(x2, y2) + v.AddPoint(x3, y3) return } if cuspLimit != 0.0 { if da1 > cuspLimit { - v.Vertex(x3, y3) + v.AddPoint(x3, y3) return } } @@ -256,7 +256,7 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa //---------------------- if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) { if angleTolerance < CurveAngleToleranceEpsilon { - v.Vertex(x23, y23) + v.AddPoint(x23, y23) return } @@ -268,14 +268,14 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa } if da1 < angleTolerance { - v.Vertex(x2, y2) - v.Vertex(x3, y3) + v.AddPoint(x2, y2) + v.AddPoint(x3, y3) return } if cuspLimit != 0.0 { if da1 > cuspLimit { - v.Vertex(x2, y2) + v.AddPoint(x2, y2) return } } @@ -290,7 +290,7 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa // we tend to finish subdivisions. //---------------------- if angleTolerance < CurveAngleToleranceEpsilon { - v.Vertex(x23, y23) + v.AddPoint(x23, y23) return } @@ -309,18 +309,18 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa if da1+da2 < angleTolerance { // Finally we can stop the recursion //---------------------- - v.Vertex(x23, y23) + v.AddPoint(x23, y23) return } if cuspLimit != 0.0 { if da1 > cuspLimit { - v.Vertex(x2, y2) + v.AddPoint(x2, y2) return } if da2 > cuspLimit { - v.Vertex(x3, y3) + v.AddPoint(x3, y3) return } } diff --git a/dasher.go b/dasher.go index 5210299..b4d4977 100644 --- a/dasher.go +++ b/dasher.go @@ -28,7 +28,7 @@ func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) { } } -func (dasher *DashVertexConverter) Vertex(x, y float64) { +func (dasher *DashVertexConverter) AddPoint(x, y float64) { switch dasher.command { case VertexStartCommand: dasher.start(x, y) @@ -40,7 +40,7 @@ func (dasher *DashVertexConverter) Vertex(x, y float64) { func (dasher *DashVertexConverter) start(x, y float64) { dasher.next.NextCommand(VertexStartCommand) - dasher.next.Vertex(x, y) + dasher.next.AddPoint(x, y) dasher.x, dasher.y = x, y dasher.distance = dasher.dashOffset dasher.currentDash = 0 @@ -60,12 +60,12 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) { ly := dasher.y + k*(y-dasher.y) if dasher.currentDash%2 == 0 { // line - dasher.next.Vertex(lx, ly) + dasher.next.AddPoint(lx, ly) } else { // gap dasher.next.NextCommand(VertexStopCommand) dasher.next.NextCommand(VertexStartCommand) - dasher.next.Vertex(lx, ly) + dasher.next.AddPoint(lx, ly) } d = d - rest dasher.x, dasher.y = lx, ly @@ -75,12 +75,12 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) { dasher.distance = d if dasher.currentDash%2 == 0 { // line - dasher.next.Vertex(x, y) + dasher.next.AddPoint(x, y) } else { // gap dasher.next.NextCommand(VertexStopCommand) dasher.next.NextCommand(VertexStartCommand) - dasher.next.Vertex(x, y) + dasher.next.AddPoint(x, y) } if dasher.distance >= dasher.dash[dasher.currentDash] { dasher.distance = dasher.distance - dasher.dash[dasher.currentDash] diff --git a/demux_converter.go b/demux_converter.go index b5c871d..98524c8 100644 --- a/demux_converter.go +++ b/demux_converter.go @@ -16,8 +16,8 @@ func (dc *DemuxConverter) NextCommand(cmd VertexCommand) { converter.NextCommand(cmd) } } -func (dc *DemuxConverter) Vertex(x, y float64) { +func (dc *DemuxConverter) AddPoint(x, y float64) { for _, converter := range dc.converters { - converter.Vertex(x, y) + converter.AddPoint(x, y) } } diff --git a/path_adder.go b/path_adder.go index c5efd2b..c40c039 100644 --- a/path_adder.go +++ b/path_adder.go @@ -20,7 +20,7 @@ func (vertexAdder *VertexAdder) NextCommand(cmd VertexCommand) { vertexAdder.command = cmd } -func (vertexAdder *VertexAdder) Vertex(x, y float64) { +func (vertexAdder *VertexAdder) AddPoint(x, y float64) { switch vertexAdder.command { case VertexStartCommand: vertexAdder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) diff --git a/path_converter.go b/path_converter.go index 0ef96b8..b1bd9a4 100644 --- a/path_converter.go +++ b/path_converter.go @@ -4,74 +4,73 @@ package draw2d import ( + "github.com/llgcode/draw2d/curve" "math" ) type PathConverter struct { - converter VertexConverter - ApproximationScale, AngleTolerance, CuspLimit float64 - startX, startY, x, y float64 + converter VertexConverter + ApproximationScale float64 + startX, startY, x, y float64 } func NewPathConverter(converter VertexConverter) *PathConverter { - return &PathConverter{converter, 1, 0, 0, 0, 0, 0, 0} + return &PathConverter{converter, 1, 0, 0, 0, 0} } func (c *PathConverter) Convert(paths ...*PathStorage) { for _, path := range paths { - j := 0 + i := 0 for _, cmd := range path.commands { - j = j + c.ConvertCommand(cmd, path.vertices[j:]...) + switch cmd { + case MoveTo: + c.x, c.y = path.vertices[i], path.vertices[i+1] + c.startX, c.startY = c.x, c.y + c.converter.NextCommand(VertexStopCommand) + c.converter.NextCommand(VertexStartCommand) + c.converter.AddPoint(c.x, c.y) + i += 2 + case LineTo: + c.x, c.y = path.vertices[i], path.vertices[i+1] + if c.startX == c.x && c.startY == c.y { + c.converter.NextCommand(VertexCloseCommand) + } + c.converter.AddPoint(c.x, c.y) + c.converter.NextCommand(VertexJoinCommand) + i += 2 + case QuadCurveTo: + curve.TraceQuad(c.converter, path.vertices[i-2:], 0.5) + c.x, c.y = path.vertices[i+2], path.vertices[i+3] + if c.startX == c.x && c.startY == c.y { + c.converter.NextCommand(VertexCloseCommand) + } + c.converter.AddPoint(c.x, c.y) + i += 4 + case CubicCurveTo: + curve.TraceCubic(c.converter, path.vertices[i-2:], 0.5) + c.x, c.y = path.vertices[i+4], path.vertices[i+5] + if c.startX == c.x && c.startY == c.y { + c.converter.NextCommand(VertexCloseCommand) + } + c.converter.AddPoint(c.x, c.y) + i += 6 + case ArcTo: + c.x, c.y = arc(c.converter, path.vertices[i], path.vertices[i+1], path.vertices[i+2], path.vertices[i+3], path.vertices[i+4], path.vertices[i+5], c.ApproximationScale) + if c.startX == c.x && c.startY == c.y { + c.converter.NextCommand(VertexCloseCommand) + } + c.converter.AddPoint(c.x, c.y) + i += 6 + case Close: + c.converter.NextCommand(VertexCloseCommand) + c.converter.AddPoint(c.startX, c.startY) + } } c.converter.NextCommand(VertexStopCommand) } } -func (c *PathConverter) ConvertCommand(cmd PathCmd, vertices ...float64) int { - switch cmd { - case MoveTo: - c.x, c.y = vertices[0], vertices[1] - c.startX, c.startY = c.x, c.y - c.converter.NextCommand(VertexStopCommand) - c.converter.NextCommand(VertexStartCommand) - c.converter.Vertex(c.x, c.y) - return 2 - case LineTo: - c.x, c.y = vertices[0], vertices[1] - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) - } - c.converter.Vertex(c.x, c.y) - c.converter.NextCommand(VertexJoinCommand) - return 2 - case QuadCurveTo: - quadraticBezier(c.converter, c.x, c.y, vertices[0], vertices[1], vertices[2], vertices[3], c.ApproximationScale, c.AngleTolerance) - c.x, c.y = vertices[2], vertices[3] - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) - } - c.converter.Vertex(c.x, c.y) - return 4 - case CubicCurveTo: - cubicBezier(c.converter, c.x, c.y, vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], c.ApproximationScale, c.AngleTolerance, c.CuspLimit) - c.x, c.y = vertices[4], vertices[5] - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) - } - c.converter.Vertex(c.x, c.y) - return 6 - case ArcTo: - c.x, c.y = arc(c.converter, vertices[0], vertices[1], vertices[2], vertices[3], vertices[4], vertices[5], c.ApproximationScale) - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) - } - c.converter.Vertex(c.x, c.y) - return 6 - case Close: - c.converter.NextCommand(VertexCloseCommand) - c.converter.Vertex(c.startX, c.startY) - return 0 - } +func (c *PathConverter) convertCommand(cmd PathCmd, vertices ...float64) int { return 0 } @@ -80,7 +79,7 @@ func (c *PathConverter) MoveTo(x, y float64) *PathConverter { c.startX, c.startY = c.x, c.y c.converter.NextCommand(VertexStopCommand) c.converter.NextCommand(VertexStartCommand) - c.converter.Vertex(c.x, c.y) + c.converter.AddPoint(c.x, c.y) return c } @@ -94,7 +93,7 @@ func (c *PathConverter) LineTo(x, y float64) *PathConverter { if c.startX == c.x && c.startY == c.y { c.converter.NextCommand(VertexCloseCommand) } - c.converter.Vertex(c.x, c.y) + c.converter.AddPoint(c.x, c.y) c.converter.NextCommand(VertexJoinCommand) return c } @@ -105,12 +104,12 @@ func (c *PathConverter) RLineTo(dx, dy float64) *PathConverter { } func (c *PathConverter) QuadCurveTo(cx, cy, x, y float64) *PathConverter { - quadraticBezier(c.converter, c.x, c.y, cx, cy, x, y, c.ApproximationScale, c.AngleTolerance) + curve.TraceQuad(c.converter, []float64{c.x, c.y, cx, cy, x, y}, 0.5) c.x, c.y = x, y if c.startX == c.x && c.startY == c.y { c.converter.NextCommand(VertexCloseCommand) } - c.converter.Vertex(c.x, c.y) + c.converter.AddPoint(c.x, c.y) return c } @@ -120,12 +119,12 @@ func (c *PathConverter) RQuadCurveTo(dcx, dcy, dx, dy float64) *PathConverter { } func (c *PathConverter) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathConverter { - cubicBezier(c.converter, c.x, c.y, cx1, cy1, cx2, cy2, x, y, c.ApproximationScale, c.AngleTolerance, c.CuspLimit) + curve.TraceCubic(c.converter, []float64{c.x, c.y, cx1, cy1, cx2, cy2, x, y}, 0.5) c.x, c.y = x, y if c.startX == c.x && c.startY == c.y { c.converter.NextCommand(VertexCloseCommand) } - c.converter.Vertex(c.x, c.y) + c.converter.AddPoint(c.x, c.y) return c } @@ -157,7 +156,7 @@ func (c *PathConverter) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathCo if c.startX == c.x && c.startY == c.y { c.converter.NextCommand(VertexCloseCommand) } - c.converter.Vertex(c.x, c.y) + c.converter.AddPoint(c.x, c.y) return c } @@ -168,6 +167,6 @@ func (c *PathConverter) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *Pat func (c *PathConverter) Close() *PathConverter { c.converter.NextCommand(VertexCloseCommand) - c.converter.Vertex(c.startX, c.startY) + c.converter.AddPoint(c.startX, c.startY) return c } diff --git a/path_storage.go b/path_storage.go index c2a8870..b773d2b 100644 --- a/path_storage.go +++ b/path_storage.go @@ -39,18 +39,8 @@ func (p *PathStorage) Clear() { } func (p *PathStorage) appendToPath(cmd PathCmd, vertices ...float64) { - if cap(p.vertices) <= len(p.vertices)+6 { - a := make([]PathCmd, len(p.commands), cap(p.commands)+256) - b := make([]float64, len(p.vertices), cap(p.vertices)+256) - copy(a, p.commands) - p.commands = a - copy(b, p.vertices) - p.vertices = b - } - p.commands = p.commands[0 : len(p.commands)+1] - p.commands[len(p.commands)-1] = cmd - copy(p.vertices[len(p.vertices):len(p.vertices)+len(vertices)], vertices) - p.vertices = p.vertices[0 : len(p.vertices)+len(vertices)] + p.commands = append(p.commands, cmd) + p.vertices = append(p.vertices, vertices...) } func (src *PathStorage) Copy() (dest *PathStorage) { @@ -77,6 +67,7 @@ func (p *PathStorage) Close() *PathStorage { func (p *PathStorage) MoveTo(x, y float64) *PathStorage { p.appendToPath(MoveTo, x, y) + p.x = x p.y = y return p @@ -89,6 +80,9 @@ func (p *PathStorage) RMoveTo(dx, dy float64) *PathStorage { } func (p *PathStorage) LineTo(x, y float64) *PathStorage { + if len(p.commands) == 0 { //special case when no move has been done + p.MoveTo(0, 0) + } p.appendToPath(LineTo, x, y) p.x = x p.y = y @@ -102,6 +96,9 @@ func (p *PathStorage) RLineTo(dx, dy float64) *PathStorage { } func (p *PathStorage) QuadCurveTo(cx, cy, x, y float64) *PathStorage { + if len(p.commands) == 0 { //special case when no move has been done + p.MoveTo(0, 0) + } p.appendToPath(QuadCurveTo, cx, cy, x, y) p.x = x p.y = y @@ -115,6 +112,9 @@ func (p *PathStorage) RQuadCurveTo(dcx, dcy, dx, dy float64) *PathStorage { } func (p *PathStorage) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathStorage { + if len(p.commands) == 0 { //special case when no move has been done + p.MoveTo(0, 0) + } p.appendToPath(CubicCurveTo, cx1, cy1, cx2, cy2, x, y) p.x = x p.y = y diff --git a/raster/raster_test.go b/raster/raster_test.go index 7d595cc..ac1dd57 100644 --- a/raster/raster_test.go +++ b/raster/raster_test.go @@ -55,7 +55,7 @@ func TestFreetype(t *testing.T) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} @@ -77,7 +77,7 @@ func TestFreetypeNonZeroWinding(t *testing.T) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} @@ -100,7 +100,7 @@ func TestRasterizer(t *testing.T) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} tr := [6]float64{1, 0, 0, 1, 0, 0} @@ -116,7 +116,7 @@ func TestRasterizerNonZeroWinding(t *testing.T) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} tr := [6]float64{1, 0, 0, 1, 0, 0} @@ -131,7 +131,7 @@ func BenchmarkFreetype(b *testing.B) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} @@ -152,7 +152,7 @@ func BenchmarkFreetypeNonZeroWinding(b *testing.B) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} @@ -174,7 +174,7 @@ func BenchmarkRasterizerNonZeroWinding(b *testing.B) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} tr := [6]float64{1, 0, 0, 1, 0, 0} @@ -189,7 +189,7 @@ func BenchmarkRasterizer(b *testing.B) { var p Path p.LineTo(10, 190) c := curve.CubicCurveFloat64{10, 190, 10, 10, 190, 10, 190, 190} - c.Segment(&p, flattening_threshold) + c.Trace(&p, flattening_threshold) poly := Polygon(p.points) color := color.RGBA{0, 0, 0, 0xff} tr := [6]float64{1, 0, 0, 1, 0, 0} diff --git a/stroker.go b/stroker.go index 9e40361..b46586c 100644 --- a/stroker.go +++ b/stroker.go @@ -47,16 +47,16 @@ func (l *LineStroker) NextCommand(command VertexCommand) { if command == VertexStopCommand { l.Next.NextCommand(VertexStartCommand) for i, j := 0, 1; j < len(l.vertices); i, j = i+2, j+2 { - l.Next.Vertex(l.vertices[i], l.vertices[j]) + l.Next.AddPoint(l.vertices[i], l.vertices[j]) l.Next.NextCommand(VertexNoCommand) } for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 { l.Next.NextCommand(VertexNoCommand) - l.Next.Vertex(l.rewind[i], l.rewind[j]) + l.Next.AddPoint(l.rewind[i], l.rewind[j]) } if len(l.vertices) > 1 { l.Next.NextCommand(VertexNoCommand) - l.Next.Vertex(l.vertices[0], l.vertices[1]) + l.Next.AddPoint(l.vertices[0], l.vertices[1]) } l.Next.NextCommand(VertexStopCommand) // reinit vertices @@ -66,7 +66,7 @@ func (l *LineStroker) NextCommand(command VertexCommand) { } } -func (l *LineStroker) Vertex(x, y float64) { +func (l *LineStroker) AddPoint(x, y float64) { switch l.command { case VertexNoCommand: l.line(l.x, l.y, x, y) diff --git a/transform.go b/transform.go index 61d3f35..3766a22 100644 --- a/transform.go +++ b/transform.go @@ -266,10 +266,10 @@ func (vmt *VertexMatrixTransform) NextCommand(command VertexCommand) { vmt.Next.NextCommand(command) } -func (vmt *VertexMatrixTransform) Vertex(x, y float64) { +func (vmt *VertexMatrixTransform) AddPoint(x, y float64) { u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5] - vmt.Next.Vertex(u, v) + vmt.Next.AddPoint(u, v) } // this adder apply a Matrix transformation to points diff --git a/vertex2d.go b/vertex2d.go index 4e4d4fd..9cf928a 100644 --- a/vertex2d.go +++ b/vertex2d.go @@ -15,5 +15,5 @@ const ( type VertexConverter interface { NextCommand(cmd VertexCommand) - Vertex(x, y float64) + AddPoint(x, y float64) } From ceb331894dcb7c9d4df4c09f212e4dae96a51161 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 15:36:56 +0200 Subject: [PATCH 03/39] Rename LineTracer and VertexConverter to LineBuilder --- arc.go | 4 +-- curve/arc.go | 8 +++--- curve/cubic.go | 6 ++--- curve/curve_test.go | 12 ++++++--- curve/line.go | 7 +++++ curve/quad.go | 6 ++--- curve/tracer.go | 7 ----- curves.go | 42 +++++++++++++++--------------- dasher.go | 42 +++++++++++++----------------- demux_converter.go | 17 +++++++++---- path_adder.go | 23 ++++++++--------- path_converter.go | 62 ++++++++++++++++++++++----------------------- path_storage.go | 33 ++++++++---------------- stroker.go | 50 +++++++++++++++++++----------------- transform.go | 16 ++++++++---- vertex2d.go | 22 +++++++++------- 16 files changed, 180 insertions(+), 177 deletions(-) create mode 100644 curve/line.go delete mode 100644 curve/tracer.go diff --git a/arc.go b/arc.go index a40c8b8..03213a6 100644 --- a/arc.go +++ b/arc.go @@ -9,7 +9,7 @@ import ( "code.google.com/p/freetype-go/freetype/raster" ) -func arc(t VertexConverter, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { +func arc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { end := start + angle clockWise := true if angle < 0 { @@ -33,7 +33,7 @@ func arc(t VertexConverter, x, y, rx, ry, start, angle, scale float64) (lastX, l curY = y + math.Sin(angle)*ry angle += da - t.AddPoint(curX, curY) + t.LineTo(curX, curY) } return curX, curY } diff --git a/curve/arc.go b/curve/arc.go index 1ed1426..733bf3e 100644 --- a/curve/arc.go +++ b/curve/arc.go @@ -7,8 +7,8 @@ import ( "math" ) -// TraceArc trace an arc using a LineTracer -func TraceArc(t LineTracer, x, y, rx, ry, start, angle, scale float64) { +// TraceArc trace an arc using a LineBuilder +func TraceArc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) { end := start + angle clockWise := true if angle < 0 { @@ -32,7 +32,7 @@ func TraceArc(t LineTracer, x, y, rx, ry, start, angle, scale float64) { curY = y + math.Sin(angle)*ry angle += da - t.AddPoint(curX, curY) + t.LineTo(curX, curY) } - t.AddPoint(curX, curY) + t.LineTo(curX, curY) } diff --git a/curve/cubic.go b/curve/cubic.go index af7995d..89d6575 100644 --- a/curve/cubic.go +++ b/curve/cubic.go @@ -46,9 +46,9 @@ func SubdivideCubic(c, c1, c2 []float64) { c2[0], c2[1] = c1[6], c1[7] } -// TraceCubic generate lines subdividing the cubic curve using a LineTracer +// TraceCubic generate lines subdividing the cubic curve using a LineBuilder // flattening_threshold helps determines the flattening expectation of the curve -func TraceCubic(t LineTracer, cubic []float64, flattening_threshold float64) { +func TraceCubic(t LineBuilder, cubic []float64, flattening_threshold float64) { // Allocation curves var curves [CurveRecursionLimit * 8]float64 copy(curves[0:8], cubic[0:8]) @@ -69,7 +69,7 @@ func TraceCubic(t LineTracer, cubic []float64, flattening_threshold float64) { // if it's flat then trace a line if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.AddPoint(c[6], c[7]) + t.LineTo(c[6], c[7]) i-- } else { // second half of bezier go lower onto the stack diff --git a/curve/curve_test.go b/curve/curve_test.go index e480a93..f3a8fd0 100644 --- a/curve/curve_test.go +++ b/curve/curve_test.go @@ -38,7 +38,11 @@ type Path struct { points []float64 } -func (p *Path) AddPoint(x, y float64) { +func (p *Path) MoveTo(x, y float64) { + p.points = append(p.points, x, y) +} + +func (p *Path) LineTo(x, y float64) { p.points = append(p.points, x, y) } @@ -82,7 +86,7 @@ func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image { func TestCubicCurve(t *testing.T) { for i := 0; i < len(testsCubicFloat64); i += 8 { var p Path - p.AddPoint(testsCubicFloat64[i], testsCubicFloat64[i+1]) + p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...) @@ -98,7 +102,7 @@ func TestCubicCurve(t *testing.T) { func TestQuadCurve(t *testing.T) { for i := 0; i < len(testsQuadFloat64); i += 6 { var p Path - p.AddPoint(testsQuadFloat64[i], testsQuadFloat64[i+1]) + p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1]) TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...) @@ -115,7 +119,7 @@ func BenchmarkCubicCurve(b *testing.B) { for i := 0; i < b.N; i++ { for i := 0; i < len(testsCubicFloat64); i += 8 { var p Path - p.AddPoint(testsCubicFloat64[i], testsCubicFloat64[i+1]) + p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) } } diff --git a/curve/line.go b/curve/line.go new file mode 100644 index 0000000..177ba98 --- /dev/null +++ b/curve/line.go @@ -0,0 +1,7 @@ +package curve + +// LineBuilder is an interface that help segmenting curve into small lines +type LineBuilder interface { + // LineTo a point + LineTo(x, y float64) +} diff --git a/curve/quad.go b/curve/quad.go index 6feb669..12f66c5 100644 --- a/curve/quad.go +++ b/curve/quad.go @@ -29,9 +29,9 @@ func SubdivideQuad(c, c1, c2 []float64) { return } -// Trace generate lines subdividing the curve using a LineTracer +// Trace generate lines subdividing the curve using a LineBuilder // flattening_threshold helps determines the flattening expectation of the curve -func TraceQuad(t LineTracer, quad []float64, flattening_threshold float64) { +func TraceQuad(t LineBuilder, quad []float64, flattening_threshold float64) { // Allocates curves stack var curves [CurveRecursionLimit * 6]float64 copy(curves[0:6], quad[0:6]) @@ -49,7 +49,7 @@ func TraceQuad(t LineTracer, quad []float64, flattening_threshold float64) { // if it's flat then trace a line if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.AddPoint(c[4], c[5]) + t.LineTo(c[4], c[5]) i-- } else { // second half of bezier go lower onto the stack diff --git a/curve/tracer.go b/curve/tracer.go deleted file mode 100644 index 7cfb997..0000000 --- a/curve/tracer.go +++ /dev/null @@ -1,7 +0,0 @@ -package curve - -// LineTracer is an interface that help segmenting curve into small lines -type LineTracer interface { - // AddPoint a point - AddPoint(x, y float64) -} diff --git a/curves.go b/curves.go index 7b8c0eb..4accdb1 100644 --- a/curves.go +++ b/curves.go @@ -33,7 +33,7 @@ var ( The more this value is the less sharp turns will be cut. Typically it should not exceed 10-15 degrees. */ -func cubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float64) { +func cubicBezier(v LineBuilder, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float64) { cuspLimit = computeCuspLimit(cuspLimit) distanceToleranceSquare := 0.5 / approximationScale distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare @@ -43,7 +43,7 @@ func cubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4, approximatio /* * see cubicBezier comments for approximationScale and angleTolerance definition */ -func quadraticBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float64) { +func quadraticBezier(v LineBuilder, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float64) { distanceToleranceSquare := 0.5 / approximationScale distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare @@ -62,7 +62,7 @@ func computeCuspLimit(v float64) (r float64) { /** * http://www.antigrain.com/research/adaptive_bezier/index.html */ -func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 float64, level int, distanceToleranceSquare, angleTolerance float64) { +func recursiveQuadraticBezierBezier(v LineBuilder, x1, y1, x2, y2, x3, y3 float64, level int, distanceToleranceSquare, angleTolerance float64) { if level > CurveRecursionLimit { return } @@ -88,7 +88,7 @@ func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 fl // we tend to finish subdivisions. //---------------------- if angleTolerance < CurveAngleToleranceEpsilon { - v.AddPoint(x123, y123) + v.LineTo(x123, y123) return } @@ -102,7 +102,7 @@ func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 fl if da < angleTolerance { // Finally we can stop the recursion //---------------------- - v.AddPoint(x123, y123) + v.LineTo(x123, y123) return } } @@ -128,7 +128,7 @@ func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 fl } } if d < distanceToleranceSquare { - v.AddPoint(x2, y2) + v.LineTo(x2, y2) return } } @@ -142,7 +142,7 @@ func recursiveQuadraticBezierBezier(v VertexConverter, x1, y1, x2, y2, x3, y3 fl /** * http://www.antigrain.com/research/adaptive_bezier/index.html */ -func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 float64, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) { +func recursiveCubicBezier(v LineBuilder, x1, y1, x2, y2, x3, y3, x4, y4 float64, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) { if level > CurveRecursionLimit { return } @@ -209,12 +209,12 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa } if d2 > d3 { if d2 < distanceToleranceSquare { - v.AddPoint(x2, y2) + v.LineTo(x2, y2) return } } else { if d3 < distanceToleranceSquare { - v.AddPoint(x3, y3) + v.LineTo(x3, y3) return } } @@ -225,7 +225,7 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa //---------------------- if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) { if angleTolerance < CurveAngleToleranceEpsilon { - v.AddPoint(x23, y23) + v.LineTo(x23, y23) return } @@ -237,14 +237,14 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa } if da1 < angleTolerance { - v.AddPoint(x2, y2) - v.AddPoint(x3, y3) + v.LineTo(x2, y2) + v.LineTo(x3, y3) return } if cuspLimit != 0.0 { if da1 > cuspLimit { - v.AddPoint(x3, y3) + v.LineTo(x3, y3) return } } @@ -256,7 +256,7 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa //---------------------- if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) { if angleTolerance < CurveAngleToleranceEpsilon { - v.AddPoint(x23, y23) + v.LineTo(x23, y23) return } @@ -268,14 +268,14 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa } if da1 < angleTolerance { - v.AddPoint(x2, y2) - v.AddPoint(x3, y3) + v.LineTo(x2, y2) + v.LineTo(x3, y3) return } if cuspLimit != 0.0 { if da1 > cuspLimit { - v.AddPoint(x2, y2) + v.LineTo(x2, y2) return } } @@ -290,7 +290,7 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa // we tend to finish subdivisions. //---------------------- if angleTolerance < CurveAngleToleranceEpsilon { - v.AddPoint(x23, y23) + v.LineTo(x23, y23) return } @@ -309,18 +309,18 @@ func recursiveCubicBezier(v VertexConverter, x1, y1, x2, y2, x3, y3, x4, y4 floa if da1+da2 < angleTolerance { // Finally we can stop the recursion //---------------------- - v.AddPoint(x23, y23) + v.LineTo(x23, y23) return } if cuspLimit != 0.0 { if da1 > cuspLimit { - v.AddPoint(x2, y2) + v.LineTo(x2, y2) return } if da2 > cuspLimit { - v.AddPoint(x3, y3) + v.LineTo(x3, y3) return } } diff --git a/dasher.go b/dasher.go index b4d4977..ba2686b 100644 --- a/dasher.go +++ b/dasher.go @@ -4,15 +4,15 @@ package draw2d type DashVertexConverter struct { - command VertexCommand - next VertexConverter + command LineMarker + next LineBuilder x, y, distance float64 dash []float64 currentDash int dashOffset float64 } -func NewDashConverter(dash []float64, dashOffset float64, converter VertexConverter) *DashVertexConverter { +func NewDashConverter(dash []float64, dashOffset float64, converter LineBuilder) *DashVertexConverter { var dasher DashVertexConverter dasher.dash = dash dasher.currentDash = 0 @@ -21,26 +21,20 @@ func NewDashConverter(dash []float64, dashOffset float64, converter VertexConver return &dasher } -func (dasher *DashVertexConverter) NextCommand(cmd VertexCommand) { +func (dasher *DashVertexConverter) NextCommand(cmd LineMarker) { dasher.command = cmd - if dasher.command == VertexStopCommand { - dasher.next.NextCommand(VertexStopCommand) + if dasher.command == LineEndMarker { + dasher.next.NextCommand(LineEndMarker) } } -func (dasher *DashVertexConverter) AddPoint(x, y float64) { - switch dasher.command { - case VertexStartCommand: - dasher.start(x, y) - default: - dasher.lineTo(x, y) - } - dasher.command = VertexNoCommand +func (dasher *DashVertexConverter) LineTo(x, y float64) { + dasher.lineTo(x, y) + dasher.command = LineNoneMarker } -func (dasher *DashVertexConverter) start(x, y float64) { - dasher.next.NextCommand(VertexStartCommand) - dasher.next.AddPoint(x, y) +func (dasher *DashVertexConverter) MoveTo(x, y float64) { + dasher.next.MoveTo(x, y) dasher.x, dasher.y = x, y dasher.distance = dasher.dashOffset dasher.currentDash = 0 @@ -60,12 +54,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) { ly := dasher.y + k*(y-dasher.y) if dasher.currentDash%2 == 0 { // line - dasher.next.AddPoint(lx, ly) + dasher.next.LineTo(lx, ly) } else { // gap - dasher.next.NextCommand(VertexStopCommand) - dasher.next.NextCommand(VertexStartCommand) - dasher.next.AddPoint(lx, ly) + dasher.next.NextCommand(LineEndMarker) + dasher.next.MoveTo(lx, ly) } d = d - rest dasher.x, dasher.y = lx, ly @@ -75,12 +68,11 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) { dasher.distance = d if dasher.currentDash%2 == 0 { // line - dasher.next.AddPoint(x, y) + dasher.next.LineTo(x, y) } else { // gap - dasher.next.NextCommand(VertexStopCommand) - dasher.next.NextCommand(VertexStartCommand) - dasher.next.AddPoint(x, y) + dasher.next.NextCommand(LineEndMarker) + dasher.next.MoveTo(x, y) } if dasher.distance >= dasher.dash[dasher.currentDash] { dasher.distance = dasher.distance - dasher.dash[dasher.currentDash] diff --git a/demux_converter.go b/demux_converter.go index 98524c8..6315842 100644 --- a/demux_converter.go +++ b/demux_converter.go @@ -4,20 +4,27 @@ package draw2d type DemuxConverter struct { - converters []VertexConverter + converters []LineBuilder } -func NewDemuxConverter(converters ...VertexConverter) *DemuxConverter { +func NewDemuxConverter(converters ...LineBuilder) *DemuxConverter { return &DemuxConverter{converters} } -func (dc *DemuxConverter) NextCommand(cmd VertexCommand) { +func (dc *DemuxConverter) NextCommand(cmd LineMarker) { for _, converter := range dc.converters { converter.NextCommand(cmd) } } -func (dc *DemuxConverter) AddPoint(x, y float64) { + +func (dc *DemuxConverter) MoveTo(x, y float64) { for _, converter := range dc.converters { - converter.AddPoint(x, y) + converter.MoveTo(x, y) + } +} + +func (dc *DemuxConverter) LineTo(x, y float64) { + for _, converter := range dc.converters { + converter.LineTo(x, y) } } diff --git a/path_adder.go b/path_adder.go index c40c039..aa85b9d 100644 --- a/path_adder.go +++ b/path_adder.go @@ -8,26 +8,23 @@ import ( ) type VertexAdder struct { - command VertexCommand - adder raster.Adder + adder raster.Adder } func NewVertexAdder(adder raster.Adder) *VertexAdder { - return &VertexAdder{VertexNoCommand, adder} + return &VertexAdder{adder} } -func (vertexAdder *VertexAdder) NextCommand(cmd VertexCommand) { - vertexAdder.command = cmd +func (vertexAdder *VertexAdder) NextCommand(cmd LineMarker) { + } -func (vertexAdder *VertexAdder) AddPoint(x, y float64) { - switch vertexAdder.command { - case VertexStartCommand: - vertexAdder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) - default: - vertexAdder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) - } - vertexAdder.command = VertexNoCommand +func (vertexAdder *VertexAdder) MoveTo(x, y float64) { + vertexAdder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) +} + +func (vertexAdder *VertexAdder) LineTo(x, y float64) { + vertexAdder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) } type PathAdder struct { diff --git a/path_converter.go b/path_converter.go index b1bd9a4..1c88695 100644 --- a/path_converter.go +++ b/path_converter.go @@ -9,12 +9,12 @@ import ( ) type PathConverter struct { - converter VertexConverter + converter LineBuilder ApproximationScale float64 startX, startY, x, y float64 } -func NewPathConverter(converter VertexConverter) *PathConverter { +func NewPathConverter(converter LineBuilder) *PathConverter { return &PathConverter{converter, 1, 0, 0, 0, 0} } @@ -26,47 +26,48 @@ func (c *PathConverter) Convert(paths ...*PathStorage) { case MoveTo: c.x, c.y = path.vertices[i], path.vertices[i+1] c.startX, c.startY = c.x, c.y - c.converter.NextCommand(VertexStopCommand) - c.converter.NextCommand(VertexStartCommand) - c.converter.AddPoint(c.x, c.y) + if i != 0 { + c.converter.NextCommand(LineEndMarker) + } + c.converter.MoveTo(c.x, c.y) i += 2 case LineTo: c.x, c.y = path.vertices[i], path.vertices[i+1] if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) - c.converter.NextCommand(VertexJoinCommand) + c.converter.LineTo(c.x, c.y) + c.converter.NextCommand(LineJoinMarker) i += 2 case QuadCurveTo: curve.TraceQuad(c.converter, path.vertices[i-2:], 0.5) c.x, c.y = path.vertices[i+2], path.vertices[i+3] if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) + c.converter.LineTo(c.x, c.y) i += 4 case CubicCurveTo: curve.TraceCubic(c.converter, path.vertices[i-2:], 0.5) c.x, c.y = path.vertices[i+4], path.vertices[i+5] if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) + c.converter.LineTo(c.x, c.y) i += 6 case ArcTo: c.x, c.y = arc(c.converter, path.vertices[i], path.vertices[i+1], path.vertices[i+2], path.vertices[i+3], path.vertices[i+4], path.vertices[i+5], c.ApproximationScale) if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) + c.converter.LineTo(c.x, c.y) i += 6 case Close: - c.converter.NextCommand(VertexCloseCommand) - c.converter.AddPoint(c.startX, c.startY) + c.converter.NextCommand(LineCloseMarker) + c.converter.LineTo(c.startX, c.startY) } } - c.converter.NextCommand(VertexStopCommand) + c.converter.NextCommand(LineEndMarker) } } @@ -77,9 +78,8 @@ func (c *PathConverter) convertCommand(cmd PathCmd, vertices ...float64) int { func (c *PathConverter) MoveTo(x, y float64) *PathConverter { c.x, c.y = x, y c.startX, c.startY = c.x, c.y - c.converter.NextCommand(VertexStopCommand) - c.converter.NextCommand(VertexStartCommand) - c.converter.AddPoint(c.x, c.y) + c.converter.NextCommand(LineEndMarker) + c.converter.MoveTo(c.x, c.y) return c } @@ -91,10 +91,10 @@ func (c *PathConverter) RMoveTo(dx, dy float64) *PathConverter { func (c *PathConverter) LineTo(x, y float64) *PathConverter { c.x, c.y = x, y if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) - c.converter.NextCommand(VertexJoinCommand) + c.converter.LineTo(c.x, c.y) + c.converter.NextCommand(LineJoinMarker) return c } @@ -107,9 +107,9 @@ func (c *PathConverter) QuadCurveTo(cx, cy, x, y float64) *PathConverter { curve.TraceQuad(c.converter, []float64{c.x, c.y, cx, cy, x, y}, 0.5) c.x, c.y = x, y if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) + c.converter.LineTo(c.x, c.y) return c } @@ -122,9 +122,9 @@ func (c *PathConverter) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathConv curve.TraceCubic(c.converter, []float64{c.x, c.y, cx1, cy1, cx2, cy2, x, y}, 0.5) c.x, c.y = x, y if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) + c.converter.LineTo(c.x, c.y) return c } @@ -154,9 +154,9 @@ func (c *PathConverter) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathCo c.MoveTo(startX, startY) c.x, c.y = arc(c.converter, cx, cy, rx, ry, startAngle, angle, c.ApproximationScale) if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(VertexCloseCommand) + c.converter.NextCommand(LineCloseMarker) } - c.converter.AddPoint(c.x, c.y) + c.converter.LineTo(c.x, c.y) return c } @@ -166,7 +166,7 @@ func (c *PathConverter) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *Pat } func (c *PathConverter) Close() *PathConverter { - c.converter.NextCommand(VertexCloseCommand) - c.converter.AddPoint(c.startX, c.startY) + c.converter.NextCommand(LineCloseMarker) + c.converter.LineTo(c.startX, c.startY) return c } diff --git a/path_storage.go b/path_storage.go index b773d2b..5fc8890 100644 --- a/path_storage.go +++ b/path_storage.go @@ -60,74 +60,65 @@ func (p *PathStorage) IsEmpty() bool { return len(p.commands) == 0 } -func (p *PathStorage) Close() *PathStorage { +func (p *PathStorage) Close() { p.appendToPath(Close) - return p } -func (p *PathStorage) MoveTo(x, y float64) *PathStorage { +func (p *PathStorage) MoveTo(x, y float64) { p.appendToPath(MoveTo, x, y) p.x = x p.y = y - return p } -func (p *PathStorage) RMoveTo(dx, dy float64) *PathStorage { +func (p *PathStorage) RMoveTo(dx, dy float64) { x, y := p.LastPoint() p.MoveTo(x+dx, y+dy) - return p } -func (p *PathStorage) LineTo(x, y float64) *PathStorage { +func (p *PathStorage) LineTo(x, y float64) { if len(p.commands) == 0 { //special case when no move has been done p.MoveTo(0, 0) } p.appendToPath(LineTo, x, y) p.x = x p.y = y - return p } -func (p *PathStorage) RLineTo(dx, dy float64) *PathStorage { +func (p *PathStorage) RLineTo(dx, dy float64) { x, y := p.LastPoint() p.LineTo(x+dx, y+dy) - return p } -func (p *PathStorage) QuadCurveTo(cx, cy, x, y float64) *PathStorage { +func (p *PathStorage) QuadCurveTo(cx, cy, x, y float64) { if len(p.commands) == 0 { //special case when no move has been done p.MoveTo(0, 0) } p.appendToPath(QuadCurveTo, cx, cy, x, y) p.x = x p.y = y - return p } -func (p *PathStorage) RQuadCurveTo(dcx, dcy, dx, dy float64) *PathStorage { +func (p *PathStorage) RQuadCurveTo(dcx, dcy, dx, dy float64) { x, y := p.LastPoint() p.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy) - return p } -func (p *PathStorage) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathStorage { +func (p *PathStorage) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { if len(p.commands) == 0 { //special case when no move has been done p.MoveTo(0, 0) } p.appendToPath(CubicCurveTo, cx1, cy1, cx2, cy2, x, y) p.x = x p.y = y - return p } -func (p *PathStorage) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) *PathStorage { +func (p *PathStorage) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) { x, y := p.LastPoint() p.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy) - return p } -func (p *PathStorage) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathStorage { +func (p *PathStorage) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { endAngle := startAngle + angle clockWise := true if angle < 0 { @@ -153,13 +144,11 @@ func (p *PathStorage) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathStor p.appendToPath(ArcTo, cx, cy, rx, ry, startAngle, angle) p.x = cx + math.Cos(endAngle)*rx p.y = cy + math.Sin(endAngle)*ry - return p } -func (p *PathStorage) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *PathStorage { +func (p *PathStorage) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) { x, y := p.LastPoint() p.ArcTo(x+dcx, y+dcy, rx, ry, startAngle, angle) - return p } func (p *PathStorage) String() string { diff --git a/stroker.go b/stroker.go index b46586c..ef693a8 100644 --- a/stroker.go +++ b/stroker.go @@ -20,17 +20,17 @@ const ( ) type LineStroker struct { - Next VertexConverter + Next LineBuilder HalfLineWidth float64 Cap Cap Join Join vertices []float64 rewind []float64 x, y, nx, ny float64 - command VertexCommand + command LineMarker } -func NewLineStroker(c Cap, j Join, converter VertexConverter) *LineStroker { +func NewLineStroker(c Cap, j Join, converter LineBuilder) *LineStroker { l := new(LineStroker) l.Next = converter l.HalfLineWidth = 0.5 @@ -38,27 +38,29 @@ func NewLineStroker(c Cap, j Join, converter VertexConverter) *LineStroker { l.rewind = make([]float64, 0, 256) l.Cap = c l.Join = j - l.command = VertexNoCommand + l.command = LineNoneMarker return l } -func (l *LineStroker) NextCommand(command VertexCommand) { +func (l *LineStroker) NextCommand(command LineMarker) { l.command = command - if command == VertexStopCommand { - l.Next.NextCommand(VertexStartCommand) - for i, j := 0, 1; j < len(l.vertices); i, j = i+2, j+2 { - l.Next.AddPoint(l.vertices[i], l.vertices[j]) - l.Next.NextCommand(VertexNoCommand) + if command == LineEndMarker { + if len(l.vertices) > 1 { + l.Next.MoveTo(l.vertices[0], l.vertices[1]) + for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 { + l.Next.LineTo(l.vertices[i], l.vertices[j]) + l.Next.NextCommand(LineNoneMarker) + } } for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 { - l.Next.NextCommand(VertexNoCommand) - l.Next.AddPoint(l.rewind[i], l.rewind[j]) + l.Next.NextCommand(LineNoneMarker) + l.Next.LineTo(l.rewind[i], l.rewind[j]) } if len(l.vertices) > 1 { - l.Next.NextCommand(VertexNoCommand) - l.Next.AddPoint(l.vertices[0], l.vertices[1]) + l.Next.NextCommand(LineNoneMarker) + l.Next.LineTo(l.vertices[0], l.vertices[1]) } - l.Next.NextCommand(VertexStopCommand) + l.Next.NextCommand(LineEndMarker) // reinit vertices l.vertices = l.vertices[0:0] l.rewind = l.rewind[0:0] @@ -66,20 +68,22 @@ func (l *LineStroker) NextCommand(command VertexCommand) { } } -func (l *LineStroker) AddPoint(x, y float64) { +func (l *LineStroker) MoveTo(x, y float64) { + l.x, l.y = x, y +} + +func (l *LineStroker) LineTo(x, y float64) { switch l.command { - case VertexNoCommand: - l.line(l.x, l.y, x, y) - case VertexJoinCommand: + case LineJoinMarker: l.joinLine(l.x, l.y, l.nx, l.ny, x, y) - case VertexStartCommand: - l.x, l.y = x, y - case VertexCloseCommand: + case LineCloseMarker: l.line(l.x, l.y, x, y) l.joinLine(l.x, l.y, l.nx, l.ny, x, y) l.closePolygon() + default: + l.line(l.x, l.y, x, y) } - l.command = VertexNoCommand + l.command = LineNoneMarker } func (l *LineStroker) appendVertex(vertices ...float64) { diff --git a/transform.go b/transform.go index 3766a22..ac34224 100644 --- a/transform.go +++ b/transform.go @@ -254,22 +254,28 @@ func fequals(float1, float2 float64) bool { // this VertexConverter apply the Matrix transformation tr type VertexMatrixTransform struct { tr MatrixTransform - Next VertexConverter + Next LineBuilder } -func NewVertexMatrixTransform(tr MatrixTransform, converter VertexConverter) *VertexMatrixTransform { +func NewVertexMatrixTransform(tr MatrixTransform, converter LineBuilder) *VertexMatrixTransform { return &VertexMatrixTransform{tr, converter} } // Vertex Matrix Transform -func (vmt *VertexMatrixTransform) NextCommand(command VertexCommand) { +func (vmt *VertexMatrixTransform) NextCommand(command LineMarker) { vmt.Next.NextCommand(command) } -func (vmt *VertexMatrixTransform) AddPoint(x, y float64) { +func (vmt *VertexMatrixTransform) MoveTo(x, y float64) { u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5] - vmt.Next.AddPoint(u, v) + vmt.Next.MoveTo(u, v) +} + +func (vmt *VertexMatrixTransform) LineTo(x, y float64) { + u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] + v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5] + vmt.Next.LineTo(u, v) } // this adder apply a Matrix transformation to points diff --git a/vertex2d.go b/vertex2d.go index 9cf928a..f07f5e8 100644 --- a/vertex2d.go +++ b/vertex2d.go @@ -3,17 +3,21 @@ package draw2d -type VertexCommand byte +type LineMarker byte const ( - VertexNoCommand VertexCommand = iota - VertexStartCommand - VertexJoinCommand - VertexCloseCommand - VertexStopCommand + LineNoneMarker LineMarker = iota + // Mark the current point of the line as a join to it can draw some specific join Bevel, Miter, Rount + LineJoinMarker + // Mark the current point of the line as closed so it draw a line from the current + // position to the point specified by the last start marker. + LineCloseMarker + // Mark the current point of the line as finished. This ending maker allow caps to be drawn + LineEndMarker ) -type VertexConverter interface { - NextCommand(cmd VertexCommand) - AddPoint(x, y float64) +type LineBuilder interface { + NextCommand(cmd LineMarker) + MoveTo(x, y float64) + LineTo(x, y float64) } From 4fa829a373f7302065dd651a7baa77bfc0e8b7a1 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 16:18:21 +0200 Subject: [PATCH 04/39] replace LineEndMarker by End() Method --- dasher.go | 11 +++++----- demux_converter.go | 30 -------------------------- draw2dgl/gc.go | 2 +- image.go | 2 +- line.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++ path_adder.go | 3 +++ path_converter.go | 6 +++--- stroker.go | 40 ++++++++++++++++++---------------- transform.go | 4 ++++ vertex2d.go | 23 -------------------- 10 files changed, 93 insertions(+), 82 deletions(-) delete mode 100644 demux_converter.go create mode 100644 line.go delete mode 100644 vertex2d.go diff --git a/dasher.go b/dasher.go index ba2686b..d1c49af 100644 --- a/dasher.go +++ b/dasher.go @@ -21,11 +21,12 @@ func NewDashConverter(dash []float64, dashOffset float64, converter LineBuilder) return &dasher } +func (dasher *DashVertexConverter) End() { + dasher.next.End() +} + func (dasher *DashVertexConverter) NextCommand(cmd LineMarker) { dasher.command = cmd - if dasher.command == LineEndMarker { - dasher.next.NextCommand(LineEndMarker) - } } func (dasher *DashVertexConverter) LineTo(x, y float64) { @@ -57,7 +58,7 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) { dasher.next.LineTo(lx, ly) } else { // gap - dasher.next.NextCommand(LineEndMarker) + dasher.next.End() dasher.next.MoveTo(lx, ly) } d = d - rest @@ -71,7 +72,7 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) { dasher.next.LineTo(x, y) } else { // gap - dasher.next.NextCommand(LineEndMarker) + dasher.next.End() dasher.next.MoveTo(x, y) } if dasher.distance >= dasher.dash[dasher.currentDash] { diff --git a/demux_converter.go b/demux_converter.go deleted file mode 100644 index 6315842..0000000 --- a/demux_converter.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 13/12/2010 by Laurent Le Goff - -package draw2d - -type DemuxConverter struct { - converters []LineBuilder -} - -func NewDemuxConverter(converters ...LineBuilder) *DemuxConverter { - return &DemuxConverter{converters} -} - -func (dc *DemuxConverter) NextCommand(cmd LineMarker) { - for _, converter := range dc.converters { - converter.NextCommand(cmd) - } -} - -func (dc *DemuxConverter) MoveTo(x, y float64) { - for _, converter := range dc.converters { - converter.MoveTo(x, y) - } -} - -func (dc *DemuxConverter) LineTo(x, y float64) { - for _, converter := range dc.converters { - converter.LineTo(x, y) - } -} diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index 2833dcc..b0a0a07 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -223,7 +223,7 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) { stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer))) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - demux := draw2d.NewDemuxConverter(filler, stroker) + demux := draw2d.NewLineBuilders(filler, stroker) paths = append(paths, gc.Current.Path) pathConverter := draw2d.NewPathConverter(demux) pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code diff --git a/image.go b/image.go index 9f2ee20..952dc2c 100644 --- a/image.go +++ b/image.go @@ -317,7 +317,7 @@ func (gc *ImageGraphicContext) FillStroke(paths ...*PathStorage) { stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer))) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - demux := NewDemuxConverter(filler, stroker) + demux := NewLineBuilders(filler, stroker) paths = append(paths, gc.Current.Path) pathConverter := NewPathConverter(demux) pathConverter.ApproximationScale = gc.Current.Tr.GetScale() diff --git a/line.go b/line.go new file mode 100644 index 0000000..85a2142 --- /dev/null +++ b/line.go @@ -0,0 +1,54 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 21/11/2010 by Laurent Le Goff + +package draw2d + +type LineMarker byte + +const ( + LineNoneMarker LineMarker = iota + // Mark the current point of the line as a join to it can draw some specific join Bevel, Miter, Rount + LineJoinMarker + // Mark the current point of the line as closed so it draw a line from the current + // position to the point specified by the last start marker. + LineCloseMarker +) + +type LineBuilder interface { + NextCommand(cmd LineMarker) + MoveTo(x, y float64) + LineTo(x, y float64) + End() +} + +type LineBuilders struct { + builders []LineBuilder +} + +func NewLineBuilders(builders ...LineBuilder) *LineBuilders { + return &LineBuilders{builders} +} + +func (dc *LineBuilders) NextCommand(cmd LineMarker) { + for _, converter := range dc.builders { + converter.NextCommand(cmd) + } +} + +func (dc *LineBuilders) MoveTo(x, y float64) { + for _, converter := range dc.builders { + converter.MoveTo(x, y) + } +} + +func (dc *LineBuilders) LineTo(x, y float64) { + for _, converter := range dc.builders { + converter.LineTo(x, y) + } +} + +func (dc *LineBuilders) End() { + for _, converter := range dc.builders { + converter.End() + } +} diff --git a/path_adder.go b/path_adder.go index aa85b9d..19bc48f 100644 --- a/path_adder.go +++ b/path_adder.go @@ -27,6 +27,9 @@ func (vertexAdder *VertexAdder) LineTo(x, y float64) { vertexAdder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) } +func (vertexAdder *VertexAdder) End() { +} + type PathAdder struct { adder raster.Adder firstPoint raster.Point diff --git a/path_converter.go b/path_converter.go index 1c88695..3b22c1c 100644 --- a/path_converter.go +++ b/path_converter.go @@ -27,7 +27,7 @@ func (c *PathConverter) Convert(paths ...*PathStorage) { c.x, c.y = path.vertices[i], path.vertices[i+1] c.startX, c.startY = c.x, c.y if i != 0 { - c.converter.NextCommand(LineEndMarker) + c.converter.End() } c.converter.MoveTo(c.x, c.y) i += 2 @@ -67,7 +67,7 @@ func (c *PathConverter) Convert(paths ...*PathStorage) { c.converter.LineTo(c.startX, c.startY) } } - c.converter.NextCommand(LineEndMarker) + c.converter.End() } } @@ -78,7 +78,7 @@ func (c *PathConverter) convertCommand(cmd PathCmd, vertices ...float64) int { func (c *PathConverter) MoveTo(x, y float64) *PathConverter { c.x, c.y = x, y c.startX, c.startY = c.x, c.y - c.converter.NextCommand(LineEndMarker) + c.converter.End() c.converter.MoveTo(c.x, c.y) return c } diff --git a/stroker.go b/stroker.go index ef693a8..1871349 100644 --- a/stroker.go +++ b/stroker.go @@ -44,28 +44,30 @@ func NewLineStroker(c Cap, j Join, converter LineBuilder) *LineStroker { func (l *LineStroker) NextCommand(command LineMarker) { l.command = command - if command == LineEndMarker { - if len(l.vertices) > 1 { - l.Next.MoveTo(l.vertices[0], l.vertices[1]) - for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 { - l.Next.LineTo(l.vertices[i], l.vertices[j]) - l.Next.NextCommand(LineNoneMarker) - } - } - for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 { +} + +func (l *LineStroker) End() { + if len(l.vertices) > 1 { + l.Next.MoveTo(l.vertices[0], l.vertices[1]) + for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 { + l.Next.LineTo(l.vertices[i], l.vertices[j]) l.Next.NextCommand(LineNoneMarker) - l.Next.LineTo(l.rewind[i], l.rewind[j]) } - if len(l.vertices) > 1 { - l.Next.NextCommand(LineNoneMarker) - l.Next.LineTo(l.vertices[0], l.vertices[1]) - } - l.Next.NextCommand(LineEndMarker) - // reinit vertices - l.vertices = l.vertices[0:0] - l.rewind = l.rewind[0:0] - l.x, l.y, l.nx, l.ny = 0, 0, 0, 0 } + for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 { + l.Next.NextCommand(LineNoneMarker) + l.Next.LineTo(l.rewind[i], l.rewind[j]) + } + if len(l.vertices) > 1 { + l.Next.NextCommand(LineNoneMarker) + l.Next.LineTo(l.vertices[0], l.vertices[1]) + } + l.Next.End() + // reinit vertices + l.vertices = l.vertices[0:0] + l.rewind = l.rewind[0:0] + l.x, l.y, l.nx, l.ny = 0, 0, 0, 0 + } func (l *LineStroker) MoveTo(x, y float64) { diff --git a/transform.go b/transform.go index ac34224..21e3ed5 100644 --- a/transform.go +++ b/transform.go @@ -278,6 +278,10 @@ func (vmt *VertexMatrixTransform) LineTo(x, y float64) { vmt.Next.LineTo(u, v) } +func (vmt *VertexMatrixTransform) End() { + vmt.Next.End() +} + // this adder apply a Matrix transformation to points type MatrixTransformAdder struct { tr MatrixTransform diff --git a/vertex2d.go b/vertex2d.go deleted file mode 100644 index f07f5e8..0000000 --- a/vertex2d.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -type LineMarker byte - -const ( - LineNoneMarker LineMarker = iota - // Mark the current point of the line as a join to it can draw some specific join Bevel, Miter, Rount - LineJoinMarker - // Mark the current point of the line as closed so it draw a line from the current - // position to the point specified by the last start marker. - LineCloseMarker - // Mark the current point of the line as finished. This ending maker allow caps to be drawn - LineEndMarker -) - -type LineBuilder interface { - NextCommand(cmd LineMarker) - MoveTo(x, y float64) - LineTo(x, y float64) -} From 42d0eb260f0cdbb6b7e27c4bfc341cd82d2baec5 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 17:08:20 +0200 Subject: [PATCH 05/39] Replace LineCloseMarker by Close() Method --- dasher.go | 12 ++++++++---- line.go | 14 +++++++++++--- path_adder.go | 3 +++ path_converter.go | 29 ++--------------------------- stroker.go | 20 ++++++++++---------- transform.go | 4 ++++ 6 files changed, 38 insertions(+), 44 deletions(-) diff --git a/dasher.go b/dasher.go index d1c49af..ea122ec 100644 --- a/dasher.go +++ b/dasher.go @@ -21,10 +21,6 @@ func NewDashConverter(dash []float64, dashOffset float64, converter LineBuilder) return &dasher } -func (dasher *DashVertexConverter) End() { - dasher.next.End() -} - func (dasher *DashVertexConverter) NextCommand(cmd LineMarker) { dasher.command = cmd } @@ -41,6 +37,14 @@ func (dasher *DashVertexConverter) MoveTo(x, y float64) { dasher.currentDash = 0 } +func (dasher *DashVertexConverter) Close() { + dasher.next.Close() +} + +func (dasher *DashVertexConverter) End() { + dasher.next.End() +} + func (dasher *DashVertexConverter) lineTo(x, y float64) { rest := dasher.dash[dasher.currentDash] - dasher.distance for rest < 0 { diff --git a/line.go b/line.go index 85a2142..ff1be5d 100644 --- a/line.go +++ b/line.go @@ -9,15 +9,17 @@ const ( LineNoneMarker LineMarker = iota // Mark the current point of the line as a join to it can draw some specific join Bevel, Miter, Rount LineJoinMarker - // Mark the current point of the line as closed so it draw a line from the current - // position to the point specified by the last start marker. - LineCloseMarker ) type LineBuilder interface { NextCommand(cmd LineMarker) + // MoveTo Start a New line from the point (x, y) MoveTo(x, y float64) + // LineTo Draw a line from the current position to the point (x, y) LineTo(x, y float64) + // Close add the most recent starting point to close the path to create a polygon + Close() + // End mark the current line as finished so we can draw caps End() } @@ -47,6 +49,12 @@ func (dc *LineBuilders) LineTo(x, y float64) { } } +func (dc *LineBuilders) Close() { + for _, converter := range dc.builders { + converter.Close() + } +} + func (dc *LineBuilders) End() { for _, converter := range dc.builders { converter.End() diff --git a/path_adder.go b/path_adder.go index 19bc48f..0037269 100644 --- a/path_adder.go +++ b/path_adder.go @@ -27,6 +27,9 @@ func (vertexAdder *VertexAdder) LineTo(x, y float64) { vertexAdder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) } +func (vertexAdder *VertexAdder) Close() { +} + func (vertexAdder *VertexAdder) End() { } diff --git a/path_converter.go b/path_converter.go index 3b22c1c..29356a5 100644 --- a/path_converter.go +++ b/path_converter.go @@ -33,38 +33,26 @@ func (c *PathConverter) Convert(paths ...*PathStorage) { i += 2 case LineTo: c.x, c.y = path.vertices[i], path.vertices[i+1] - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) c.converter.NextCommand(LineJoinMarker) i += 2 case QuadCurveTo: curve.TraceQuad(c.converter, path.vertices[i-2:], 0.5) c.x, c.y = path.vertices[i+2], path.vertices[i+3] - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) i += 4 case CubicCurveTo: curve.TraceCubic(c.converter, path.vertices[i-2:], 0.5) c.x, c.y = path.vertices[i+4], path.vertices[i+5] - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) i += 6 case ArcTo: c.x, c.y = arc(c.converter, path.vertices[i], path.vertices[i+1], path.vertices[i+2], path.vertices[i+3], path.vertices[i+4], path.vertices[i+5], c.ApproximationScale) - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) i += 6 case Close: - c.converter.NextCommand(LineCloseMarker) c.converter.LineTo(c.startX, c.startY) + c.converter.Close() } } c.converter.End() @@ -90,9 +78,6 @@ func (c *PathConverter) RMoveTo(dx, dy float64) *PathConverter { func (c *PathConverter) LineTo(x, y float64) *PathConverter { c.x, c.y = x, y - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) c.converter.NextCommand(LineJoinMarker) return c @@ -106,9 +91,6 @@ func (c *PathConverter) RLineTo(dx, dy float64) *PathConverter { func (c *PathConverter) QuadCurveTo(cx, cy, x, y float64) *PathConverter { curve.TraceQuad(c.converter, []float64{c.x, c.y, cx, cy, x, y}, 0.5) c.x, c.y = x, y - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) return c } @@ -121,9 +103,6 @@ func (c *PathConverter) RQuadCurveTo(dcx, dcy, dx, dy float64) *PathConverter { func (c *PathConverter) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathConverter { curve.TraceCubic(c.converter, []float64{c.x, c.y, cx1, cy1, cx2, cy2, x, y}, 0.5) c.x, c.y = x, y - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) return c } @@ -153,9 +132,6 @@ func (c *PathConverter) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathCo startY := cy + math.Sin(startAngle)*ry c.MoveTo(startX, startY) c.x, c.y = arc(c.converter, cx, cy, rx, ry, startAngle, angle, c.ApproximationScale) - if c.startX == c.x && c.startY == c.y { - c.converter.NextCommand(LineCloseMarker) - } c.converter.LineTo(c.x, c.y) return c } @@ -166,7 +142,6 @@ func (c *PathConverter) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *Pat } func (c *PathConverter) Close() *PathConverter { - c.converter.NextCommand(LineCloseMarker) - c.converter.LineTo(c.startX, c.startY) + c.converter.Close() return c } diff --git a/stroker.go b/stroker.go index 1871349..462c9b7 100644 --- a/stroker.go +++ b/stroker.go @@ -78,16 +78,22 @@ func (l *LineStroker) LineTo(x, y float64) { switch l.command { case LineJoinMarker: l.joinLine(l.x, l.y, l.nx, l.ny, x, y) - case LineCloseMarker: - l.line(l.x, l.y, x, y) - l.joinLine(l.x, l.y, l.nx, l.ny, x, y) - l.closePolygon() + // case LineCloseMarker: + // l.line(l.x, l.y, x, y) + // l.joinLine(l.x, l.y, l.nx, l.ny, x, y) + // l.closePolygon() default: l.line(l.x, l.y, x, y) } l.command = LineNoneMarker } +func (l *LineStroker) Close() { + if len(l.vertices) > 1 { + l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1]) + } +} + func (l *LineStroker) appendVertex(vertices ...float64) { s := len(vertices) / 2 if len(l.vertices)+s >= cap(l.vertices) { @@ -106,12 +112,6 @@ func (l *LineStroker) appendVertex(vertices ...float64) { } -func (l *LineStroker) closePolygon() { - if len(l.vertices) > 1 { - l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1]) - } -} - func (l *LineStroker) line(x1, y1, x2, y2 float64) { dx := (x2 - x1) dy := (y2 - y1) diff --git a/transform.go b/transform.go index 21e3ed5..1f4ffa0 100644 --- a/transform.go +++ b/transform.go @@ -278,6 +278,10 @@ func (vmt *VertexMatrixTransform) LineTo(x, y float64) { vmt.Next.LineTo(u, v) } +func (vmt *VertexMatrixTransform) Close() { + vmt.Next.Close() +} + func (vmt *VertexMatrixTransform) End() { vmt.Next.End() } From 5df1705bb4cdeed8324421e5964bde98866762cc Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 17:14:16 +0200 Subject: [PATCH 06/39] Remove LineNoMarker --- dasher.go | 1 - line.go | 3 +-- stroker.go | 5 ----- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/dasher.go b/dasher.go index ea122ec..ac70396 100644 --- a/dasher.go +++ b/dasher.go @@ -27,7 +27,6 @@ func (dasher *DashVertexConverter) NextCommand(cmd LineMarker) { func (dasher *DashVertexConverter) LineTo(x, y float64) { dasher.lineTo(x, y) - dasher.command = LineNoneMarker } func (dasher *DashVertexConverter) MoveTo(x, y float64) { diff --git a/line.go b/line.go index ff1be5d..b6b6c47 100644 --- a/line.go +++ b/line.go @@ -6,9 +6,8 @@ package draw2d type LineMarker byte const ( - LineNoneMarker LineMarker = iota // Mark the current point of the line as a join to it can draw some specific join Bevel, Miter, Rount - LineJoinMarker + LineJoinMarker LineMarker = iota ) type LineBuilder interface { diff --git a/stroker.go b/stroker.go index 462c9b7..b666143 100644 --- a/stroker.go +++ b/stroker.go @@ -38,7 +38,6 @@ func NewLineStroker(c Cap, j Join, converter LineBuilder) *LineStroker { l.rewind = make([]float64, 0, 256) l.Cap = c l.Join = j - l.command = LineNoneMarker return l } @@ -51,15 +50,12 @@ func (l *LineStroker) End() { l.Next.MoveTo(l.vertices[0], l.vertices[1]) for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 { l.Next.LineTo(l.vertices[i], l.vertices[j]) - l.Next.NextCommand(LineNoneMarker) } } for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 { - l.Next.NextCommand(LineNoneMarker) l.Next.LineTo(l.rewind[i], l.rewind[j]) } if len(l.vertices) > 1 { - l.Next.NextCommand(LineNoneMarker) l.Next.LineTo(l.vertices[0], l.vertices[1]) } l.Next.End() @@ -85,7 +81,6 @@ func (l *LineStroker) LineTo(x, y float64) { default: l.line(l.x, l.y, x, y) } - l.command = LineNoneMarker } func (l *LineStroker) Close() { From 565dfa9eb9bdbd05bab9e547aec3116ed56b4386 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 17:24:41 +0200 Subject: [PATCH 07/39] Replace copy things by append --- stroker.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/stroker.go b/stroker.go index b666143..63f779c 100644 --- a/stroker.go +++ b/stroker.go @@ -74,10 +74,6 @@ func (l *LineStroker) LineTo(x, y float64) { switch l.command { case LineJoinMarker: l.joinLine(l.x, l.y, l.nx, l.ny, x, y) - // case LineCloseMarker: - // l.line(l.x, l.y, x, y) - // l.joinLine(l.x, l.y, l.nx, l.ny, x, y) - // l.closePolygon() default: l.line(l.x, l.y, x, y) } @@ -91,20 +87,8 @@ func (l *LineStroker) Close() { func (l *LineStroker) appendVertex(vertices ...float64) { s := len(vertices) / 2 - if len(l.vertices)+s >= cap(l.vertices) { - v := make([]float64, len(l.vertices), cap(l.vertices)+128) - copy(v, l.vertices) - l.vertices = v - v = make([]float64, len(l.rewind), cap(l.rewind)+128) - copy(v, l.rewind) - l.rewind = v - } - - copy(l.vertices[len(l.vertices):len(l.vertices)+s], vertices[:s]) - l.vertices = l.vertices[0 : len(l.vertices)+s] - copy(l.rewind[len(l.rewind):len(l.rewind)+s], vertices[s:]) - l.rewind = l.rewind[0 : len(l.rewind)+s] - + l.vertices = append(l.vertices, vertices[:s]...) + l.rewind = append(l.rewind, vertices[s:]...) } func (l *LineStroker) line(x1, y1, x2, y2 float64) { From 41809b913202ced89ab24f320bf08d35be0fca59 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 17:43:26 +0200 Subject: [PATCH 08/39] remove LineMarker --- dasher.go | 9 +++--- line.go | 23 ++++++-------- path_adder.go | 7 ++--- path_converter.go | 4 +-- stroker.go | 78 +++++++++++++++++------------------------------ transform.go | 9 +++--- 6 files changed, 50 insertions(+), 80 deletions(-) diff --git a/dasher.go b/dasher.go index ac70396..a9a1ce2 100644 --- a/dasher.go +++ b/dasher.go @@ -4,7 +4,6 @@ package draw2d type DashVertexConverter struct { - command LineMarker next LineBuilder x, y, distance float64 dash []float64 @@ -21,10 +20,6 @@ func NewDashConverter(dash []float64, dashOffset float64, converter LineBuilder) return &dasher } -func (dasher *DashVertexConverter) NextCommand(cmd LineMarker) { - dasher.command = cmd -} - func (dasher *DashVertexConverter) LineTo(x, y float64) { dasher.lineTo(x, y) } @@ -36,6 +31,10 @@ func (dasher *DashVertexConverter) MoveTo(x, y float64) { dasher.currentDash = 0 } +func (dasher *DashVertexConverter) LineJoin() { + dasher.next.LineJoin() +} + func (dasher *DashVertexConverter) Close() { dasher.next.Close() } diff --git a/line.go b/line.go index b6b6c47..5838aad 100644 --- a/line.go +++ b/line.go @@ -3,19 +3,14 @@ package draw2d -type LineMarker byte - -const ( - // Mark the current point of the line as a join to it can draw some specific join Bevel, Miter, Rount - LineJoinMarker LineMarker = iota -) - +// LineBuilder defines drawing line methods type LineBuilder interface { - NextCommand(cmd LineMarker) // MoveTo Start a New line from the point (x, y) MoveTo(x, y float64) // LineTo Draw a line from the current position to the point (x, y) LineTo(x, y float64) + // LineJoin add the most recent starting point to close the path to create a polygon + LineJoin() // Close add the most recent starting point to close the path to create a polygon Close() // End mark the current line as finished so we can draw caps @@ -30,12 +25,6 @@ func NewLineBuilders(builders ...LineBuilder) *LineBuilders { return &LineBuilders{builders} } -func (dc *LineBuilders) NextCommand(cmd LineMarker) { - for _, converter := range dc.builders { - converter.NextCommand(cmd) - } -} - func (dc *LineBuilders) MoveTo(x, y float64) { for _, converter := range dc.builders { converter.MoveTo(x, y) @@ -48,6 +37,12 @@ func (dc *LineBuilders) LineTo(x, y float64) { } } +func (dc *LineBuilders) LineJoin() { + for _, converter := range dc.builders { + converter.LineJoin() + } +} + func (dc *LineBuilders) Close() { for _, converter := range dc.builders { converter.Close() diff --git a/path_adder.go b/path_adder.go index 0037269..696cf89 100644 --- a/path_adder.go +++ b/path_adder.go @@ -15,10 +15,6 @@ func NewVertexAdder(adder raster.Adder) *VertexAdder { return &VertexAdder{adder} } -func (vertexAdder *VertexAdder) NextCommand(cmd LineMarker) { - -} - func (vertexAdder *VertexAdder) MoveTo(x, y float64) { vertexAdder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) } @@ -27,6 +23,9 @@ func (vertexAdder *VertexAdder) LineTo(x, y float64) { vertexAdder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) } +func (vertexAdder *VertexAdder) LineJoin() { +} + func (vertexAdder *VertexAdder) Close() { } diff --git a/path_converter.go b/path_converter.go index 29356a5..90bf415 100644 --- a/path_converter.go +++ b/path_converter.go @@ -34,7 +34,7 @@ func (c *PathConverter) Convert(paths ...*PathStorage) { case LineTo: c.x, c.y = path.vertices[i], path.vertices[i+1] c.converter.LineTo(c.x, c.y) - c.converter.NextCommand(LineJoinMarker) + c.converter.LineJoin() i += 2 case QuadCurveTo: curve.TraceQuad(c.converter, path.vertices[i-2:], 0.5) @@ -79,7 +79,7 @@ func (c *PathConverter) RMoveTo(dx, dy float64) *PathConverter { func (c *PathConverter) LineTo(x, y float64) *PathConverter { c.x, c.y = x, y c.converter.LineTo(c.x, c.y) - c.converter.NextCommand(LineJoinMarker) + c.converter.LineJoin() return c } diff --git a/stroker.go b/stroker.go index 63f779c..e140254 100644 --- a/stroker.go +++ b/stroker.go @@ -27,7 +27,6 @@ type LineStroker struct { vertices []float64 rewind []float64 x, y, nx, ny float64 - command LineMarker } func NewLineStroker(c Cap, j Join, converter LineBuilder) *LineStroker { @@ -41,8 +40,34 @@ func NewLineStroker(c Cap, j Join, converter LineBuilder) *LineStroker { return l } -func (l *LineStroker) NextCommand(command LineMarker) { - l.command = command +func (l *LineStroker) MoveTo(x, y float64) { + l.x, l.y = x, y +} + +func (l *LineStroker) LineTo(x, y float64) { + l.line(l.x, l.y, x, y) +} + +func (l *LineStroker) LineJoin() { + +} + +func (l *LineStroker) line(x1, y1, x2, y2 float64) { + dx := (x2 - x1) + dy := (y2 - y1) + d := vectorDistance(dx, dy) + if d != 0 { + nx := dy * l.HalfLineWidth / d + ny := -(dx * l.HalfLineWidth / d) + l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny) + l.x, l.y, l.nx, l.ny = x2, y2, nx, ny + } +} + +func (l *LineStroker) Close() { + if len(l.vertices) > 1 { + l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1]) + } } func (l *LineStroker) End() { @@ -66,55 +91,8 @@ func (l *LineStroker) End() { } -func (l *LineStroker) MoveTo(x, y float64) { - l.x, l.y = x, y -} - -func (l *LineStroker) LineTo(x, y float64) { - switch l.command { - case LineJoinMarker: - l.joinLine(l.x, l.y, l.nx, l.ny, x, y) - default: - l.line(l.x, l.y, x, y) - } -} - -func (l *LineStroker) Close() { - if len(l.vertices) > 1 { - l.appendVertex(l.vertices[0], l.vertices[1], l.rewind[0], l.rewind[1]) - } -} - func (l *LineStroker) appendVertex(vertices ...float64) { s := len(vertices) / 2 l.vertices = append(l.vertices, vertices[:s]...) l.rewind = append(l.rewind, vertices[s:]...) } - -func (l *LineStroker) line(x1, y1, x2, y2 float64) { - dx := (x2 - x1) - dy := (y2 - y1) - d := vectorDistance(dx, dy) - if d != 0 { - nx := dy * l.HalfLineWidth / d - ny := -(dx * l.HalfLineWidth / d) - l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny) - l.x, l.y, l.nx, l.ny = x2, y2, nx, ny - } -} - -func (l *LineStroker) joinLine(x1, y1, nx1, ny1, x2, y2 float64) { - dx := (x2 - x1) - dy := (y2 - y1) - d := vectorDistance(dx, dy) - - if d != 0 { - nx := dy * l.HalfLineWidth / d - ny := -(dx * l.HalfLineWidth / d) - /* l.join(x1, y1, x1 + nx, y1 - ny, nx, ny, x1 + ny2, y1 + nx2, nx2, ny2) - l.join(x1, y1, x1 - ny1, y1 - nx1, nx1, ny1, x1 - ny2, y1 - nx2, nx2, ny2)*/ - - l.appendVertex(x1+nx, y1+ny, x2+nx, y2+ny, x1-nx, y1-ny, x2-nx, y2-ny) - l.x, l.y, l.nx, l.ny = x2, y2, nx, ny - } -} diff --git a/transform.go b/transform.go index 1f4ffa0..f2c2927 100644 --- a/transform.go +++ b/transform.go @@ -261,11 +261,6 @@ func NewVertexMatrixTransform(tr MatrixTransform, converter LineBuilder) *Vertex return &VertexMatrixTransform{tr, converter} } -// Vertex Matrix Transform -func (vmt *VertexMatrixTransform) NextCommand(command LineMarker) { - vmt.Next.NextCommand(command) -} - func (vmt *VertexMatrixTransform) MoveTo(x, y float64) { u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5] @@ -278,6 +273,10 @@ func (vmt *VertexMatrixTransform) LineTo(x, y float64) { vmt.Next.LineTo(u, v) } +func (vmt *VertexMatrixTransform) LineJoin() { + vmt.Next.LineJoin() +} + func (vmt *VertexMatrixTransform) Close() { vmt.Next.Close() } From fef72651456546ef604497e028cce74713871741 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 18:09:41 +0200 Subject: [PATCH 09/39] Remove unecessary method in Path interface --- path.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/path.go b/path.go index 7167a7c..8d69dd0 100644 --- a/path.go +++ b/path.go @@ -3,25 +3,26 @@ package draw2d +// PathBuilder define method that create path type Path interface { - // Return the current point of the path + // Return the current point of the current path LastPoint() (x, y float64) - // Create a new subpath that start at the specified point - MoveTo(x, y float64) - // Create a new subpath that start at the specified point - // relative to the current point - RMoveTo(dx, dy float64) - // Add a line to the current subpath - LineTo(x, y float64) - // Add a line to the current subpath - // relative to the current point - RLineTo(dx, dy float64) + // MoveTo start a new path at (x, y) position + MoveTo(x, y float64) + + // LineTo add a line to the current path + LineTo(x, y float64) + + // QuadCurveTo add a quadratic curve to the current path QuadCurveTo(cx, cy, x, y float64) - RQuadCurveTo(dcx, dcy, dx, dy float64) + + // CubicCurveTo add a cubic bezier curve to the current path CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) - RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) + + // ArcTo add an arc to the path ArcTo(cx, cy, rx, ry, startAngle, angle float64) - RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) + + // Close the current path Close() } From 06178b5d2de8d18b081ae7f49917809d21c73f90 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 23 Apr 2015 18:12:31 +0200 Subject: [PATCH 10/39] rename Path to PathBuilder --- advanced_path.go | 8 ++++---- draw2dgl/gc.go | 6 +++--- gc.go | 8 ++++---- image.go | 6 +++--- path.go | 2 +- path_adder.go | 2 +- path_converter.go | 2 +- path_storage.go | 42 +++++++++++++++++++++--------------------- stack_gc.go | 2 +- 9 files changed, 39 insertions(+), 39 deletions(-) diff --git a/advanced_path.go b/advanced_path.go index 68f1d78..c5d8984 100644 --- a/advanced_path.go +++ b/advanced_path.go @@ -9,7 +9,7 @@ import ( //high level path creation -func Rect(path Path, x1, y1, x2, y2 float64) { +func Rect(path PathBuilder, x1, y1, x2, y2 float64) { path.MoveTo(x1, y1) path.LineTo(x2, y1) path.LineTo(x2, y2) @@ -17,7 +17,7 @@ func Rect(path Path, x1, y1, x2, y2 float64) { path.Close() } -func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) { +func RoundRect(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) { arcWidth = arcWidth / 2 arcHeight = arcHeight / 2 path.MoveTo(x1, y1+arcHeight) @@ -31,12 +31,12 @@ func RoundRect(path Path, x1, y1, x2, y2, arcWidth, arcHeight float64) { path.Close() } -func Ellipse(path Path, cx, cy, rx, ry float64) { +func Ellipse(path PathBuilder, cx, cy, rx, ry float64) { path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) path.Close() } -func Circle(path Path, cx, cy, radius float64) { +func Circle(path PathBuilder, cx, cy, radius float64) { path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) path.Close() } diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index b0a0a07..24faa69 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -182,7 +182,7 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color gc.painter.Flush() } -func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) { +func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true @@ -202,7 +202,7 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.PathStorage) { gc.Current.Path.Clear() } -func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) { +func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() @@ -214,7 +214,7 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.PathStorage) { gc.Current.Path.Clear() } -func (gc *GraphicContext) FillStroke(paths ...*draw2d.PathStorage) { +func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.strokeRasterizer.UseNonZeroWinding = true diff --git a/gc.go b/gc.go index 66dc508..c00cba0 100644 --- a/gc.go +++ b/gc.go @@ -16,7 +16,7 @@ const ( ) type GraphicContext interface { - Path + PathBuilder // Create a new path BeginPath() GetMatrixTransform() MatrixTransform @@ -49,7 +49,7 @@ type GraphicContext interface { FillStringAt(text string, x, y float64) (cursor float64) StrokeString(text string) (cursor float64) StrokeStringAt(text string, x, y float64) (cursor float64) - Stroke(paths ...*PathStorage) - Fill(paths ...*PathStorage) - FillStroke(paths ...*PathStorage) + Stroke(paths ...*Path) + Fill(paths ...*Path) + FillStroke(paths ...*Path) } diff --git a/image.go b/image.go index 952dc2c..73dcb49 100644 --- a/image.go +++ b/image.go @@ -275,7 +275,7 @@ func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color color. } /**** second method ****/ -func (gc *ImageGraphicContext) Stroke(paths ...*PathStorage) { +func (gc *ImageGraphicContext) Stroke(paths ...*Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true @@ -295,7 +295,7 @@ func (gc *ImageGraphicContext) Stroke(paths ...*PathStorage) { } /**** second method ****/ -func (gc *ImageGraphicContext) Fill(paths ...*PathStorage) { +func (gc *ImageGraphicContext) Fill(paths ...*Path) { paths = append(paths, gc.Current.Path) gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() @@ -308,7 +308,7 @@ func (gc *ImageGraphicContext) Fill(paths ...*PathStorage) { } /* second method */ -func (gc *ImageGraphicContext) FillStroke(paths ...*PathStorage) { +func (gc *ImageGraphicContext) FillStroke(paths ...*Path) { gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.strokeRasterizer.UseNonZeroWinding = true diff --git a/path.go b/path.go index 8d69dd0..232cf76 100644 --- a/path.go +++ b/path.go @@ -4,7 +4,7 @@ package draw2d // PathBuilder define method that create path -type Path interface { +type PathBuilder interface { // Return the current point of the current path LastPoint() (x, y float64) diff --git a/path_adder.go b/path_adder.go index 696cf89..a958a57 100644 --- a/path_adder.go +++ b/path_adder.go @@ -42,7 +42,7 @@ func NewPathAdder(adder raster.Adder) *PathAdder { return &PathAdder{adder, raster.Point{0, 0}, 1} } -func (pathAdder *PathAdder) Convert(paths ...*PathStorage) { +func (pathAdder *PathAdder) Convert(paths ...*Path) { for _, path := range paths { j := 0 for _, cmd := range path.commands { diff --git a/path_converter.go b/path_converter.go index 90bf415..dd96853 100644 --- a/path_converter.go +++ b/path_converter.go @@ -18,7 +18,7 @@ func NewPathConverter(converter LineBuilder) *PathConverter { return &PathConverter{converter, 1, 0, 0, 0, 0} } -func (c *PathConverter) Convert(paths ...*PathStorage) { +func (c *PathConverter) Convert(paths ...*Path) { for _, path := range paths { i := 0 for _, cmd := range path.commands { diff --git a/path_storage.go b/path_storage.go index 5fc8890..3725730 100644 --- a/path_storage.go +++ b/path_storage.go @@ -19,32 +19,32 @@ const ( Close ) -type PathStorage struct { +type Path struct { commands []PathCmd vertices []float64 x, y float64 } -func NewPathStorage() (p *PathStorage) { - p = new(PathStorage) +func NewPathStorage() (p *Path) { + p = new(Path) p.commands = make([]PathCmd, 0, 256) p.vertices = make([]float64, 0, 256) return } -func (p *PathStorage) Clear() { +func (p *Path) Clear() { p.commands = p.commands[0:0] p.vertices = p.vertices[0:0] return } -func (p *PathStorage) appendToPath(cmd PathCmd, vertices ...float64) { +func (p *Path) appendToPath(cmd PathCmd, vertices ...float64) { p.commands = append(p.commands, cmd) p.vertices = append(p.vertices, vertices...) } -func (src *PathStorage) Copy() (dest *PathStorage) { - dest = new(PathStorage) +func (src *Path) Copy() (dest *Path) { + dest = new(Path) dest.commands = make([]PathCmd, len(src.commands)) copy(dest.commands, src.commands) dest.vertices = make([]float64, len(src.vertices)) @@ -52,31 +52,31 @@ func (src *PathStorage) Copy() (dest *PathStorage) { return dest } -func (p *PathStorage) LastPoint() (x, y float64) { +func (p *Path) LastPoint() (x, y float64) { return p.x, p.y } -func (p *PathStorage) IsEmpty() bool { +func (p *Path) IsEmpty() bool { return len(p.commands) == 0 } -func (p *PathStorage) Close() { +func (p *Path) Close() { p.appendToPath(Close) } -func (p *PathStorage) MoveTo(x, y float64) { +func (p *Path) MoveTo(x, y float64) { p.appendToPath(MoveTo, x, y) p.x = x p.y = y } -func (p *PathStorage) RMoveTo(dx, dy float64) { +func (p *Path) RMoveTo(dx, dy float64) { x, y := p.LastPoint() p.MoveTo(x+dx, y+dy) } -func (p *PathStorage) LineTo(x, y float64) { +func (p *Path) LineTo(x, y float64) { if len(p.commands) == 0 { //special case when no move has been done p.MoveTo(0, 0) } @@ -85,12 +85,12 @@ func (p *PathStorage) LineTo(x, y float64) { p.y = y } -func (p *PathStorage) RLineTo(dx, dy float64) { +func (p *Path) RLineTo(dx, dy float64) { x, y := p.LastPoint() p.LineTo(x+dx, y+dy) } -func (p *PathStorage) QuadCurveTo(cx, cy, x, y float64) { +func (p *Path) QuadCurveTo(cx, cy, x, y float64) { if len(p.commands) == 0 { //special case when no move has been done p.MoveTo(0, 0) } @@ -99,12 +99,12 @@ func (p *PathStorage) QuadCurveTo(cx, cy, x, y float64) { p.y = y } -func (p *PathStorage) RQuadCurveTo(dcx, dcy, dx, dy float64) { +func (p *Path) RQuadCurveTo(dcx, dcy, dx, dy float64) { x, y := p.LastPoint() p.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy) } -func (p *PathStorage) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { +func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { if len(p.commands) == 0 { //special case when no move has been done p.MoveTo(0, 0) } @@ -113,12 +113,12 @@ func (p *PathStorage) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { p.y = y } -func (p *PathStorage) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) { +func (p *Path) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) { x, y := p.LastPoint() p.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy) } -func (p *PathStorage) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { +func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { endAngle := startAngle + angle clockWise := true if angle < 0 { @@ -146,12 +146,12 @@ func (p *PathStorage) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { p.y = cy + math.Sin(endAngle)*ry } -func (p *PathStorage) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) { +func (p *Path) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) { x, y := p.LastPoint() p.ArcTo(x+dcx, y+dcy, rx, ry, startAngle, angle) } -func (p *PathStorage) String() string { +func (p *Path) String() string { s := "" j := 0 for _, cmd := range p.commands { diff --git a/stack_gc.go b/stack_gc.go index 3741328..ee26f61 100644 --- a/stack_gc.go +++ b/stack_gc.go @@ -16,7 +16,7 @@ type StackGraphicContext struct { type ContextStack struct { Tr MatrixTransform - Path *PathStorage + Path *Path LineWidth float64 Dash []float64 DashOffset float64 From d6812fd8e6911427d37d9ac0788f25c30efbe436 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Mon, 27 Apr 2015 12:14:34 +0200 Subject: [PATCH 11/39] Start path package --- path/drawing_kit.go | 44 +++++++++++ path/interpret.go | 63 ++++++++++++++++ path/path.go | 176 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 path/drawing_kit.go create mode 100644 path/interpret.go create mode 100644 path/path.go diff --git a/path/drawing_kit.go b/path/drawing_kit.go new file mode 100644 index 0000000..89fc3c7 --- /dev/null +++ b/path/drawing_kit.go @@ -0,0 +1,44 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 13/12/2010 by Laurent Le Goff + +package path + +import ( + "math" +) + +// Rectangle draw a rectangle using a PathBuilder +func Rectangle(path PathBuilder, x1, y1, x2, y2 float64) { + path.MoveTo(x1, y1) + path.LineTo(x2, y1) + path.LineTo(x2, y2) + path.LineTo(x1, y2) + path.Close() +} + +// RoundedRectangle draw a rounded rectangle using a PathBuilder +func RoundedRectangle(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) { + arcWidth = arcWidth / 2 + arcHeight = arcHeight / 2 + path.MoveTo(x1, y1+arcHeight) + path.QuadCurveTo(x1, y1, x1+arcWidth, y1) + path.LineTo(x2-arcWidth, y1) + path.QuadCurveTo(x2, y1, x2, y1+arcHeight) + path.LineTo(x2, y2-arcHeight) + path.QuadCurveTo(x2, y2, x2-arcWidth, y2) + path.LineTo(x1+arcWidth, y2) + path.QuadCurveTo(x1, y2, x1, y2-arcHeight) + path.Close() +} + +// Ellipse draw an ellipse using a PathBuilder +func Ellipse(path PathBuilder, cx, cy, rx, ry float64) { + path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) + path.Close() +} + +// Circle draw an circle using a PathBuilder +func Circle(path PathBuilder, cx, cy, radius float64) { + path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) + path.Close() +} diff --git a/path/interpret.go b/path/interpret.go new file mode 100644 index 0000000..86de6e8 --- /dev/null +++ b/path/interpret.go @@ -0,0 +1,63 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 06/12/2010 by Laurent Le Goff + +package path + +import ( + "github.com/llgcode/draw2d/curve" +) + +type PathConverter struct { + converter LineBuilder + ApproximationScale float64 +} + +func NewPathConverter(converter LineBuilder) *PathConverter { + return &PathConverter{converter, 1, 0, 0, 0, 0} +} + +// may not been in path instead put it in a troke package thing +func (c *PathConverter) Interpret(liner LineBuilder, scale float64, paths ...*Path) { + // First Point + var startX, startY float64 = 0, 0 + // Current Point + var x, y float64 = 0, 0 + for _, path := range paths { + i := 0 + for _, cmd := range path.Components { + switch cmd { + case MoveToCmp: + x, y = path.Points[i], path.Points[i+1] + startX, startY = x, y + if i != 0 { + liner.End() + } + liner.MoveTo(x, y) + i += 2 + case LineToCmp: + x, y = path.Points[i], path.Points[i+1] + liner.LineTo(x, y) + liner.LineJoin() + i += 2 + case QuadCurveToCmp: + curve.TraceQuad(liner, path.Points[i-2:], 0.5) + x, y = path.Points[i+2], path.Points[i+3] + liner.LineTo(x, y) + i += 4 + case CubicCurveToCmp: + curve.TraceCubic(liner, path.Points[i-2:], 0.5) + x, y = path.Points[i+4], path.Points[i+5] + liner.LineTo(x, y) + i += 6 + case ArcToCmp: + x, y = arc(liner, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) + liner.LineTo(x, y) + i += 6 + case CloseCmp: + liner.LineTo(startX, startY) + liner.Close() + } + } + liner.End() + } +} diff --git a/path/path.go b/path/path.go new file mode 100644 index 0000000..b902466 --- /dev/null +++ b/path/path.go @@ -0,0 +1,176 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 21/11/2010 by Laurent Le Goff + +// Package path implements function to build path +package path + +import ( + "fmt" + "math" +) + +// PathBuilder define method that create path +type PathBuilder interface { + // Return the current point of the current path + LastPoint() (x, y float64) + + // MoveTo start a new path at (x, y) position + MoveTo(x, y float64) + + // LineTo add a line to the current path + LineTo(x, y float64) + + // QuadCurveTo add a quadratic curve to the current path + QuadCurveTo(cx, cy, x, y float64) + + // CubicCurveTo add a cubic bezier curve to the current path + CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) + + // ArcTo add an arc to the path + ArcTo(cx, cy, rx, ry, startAngle, angle float64) + + // Close the current path + Close() +} + +// Component represent components of a path +type Component int + +const ( + MoveToCmp Component = iota + LineToCmp + QuadCurveToCmp + CubicCurveToCmp + ArcToCmp + CloseCmp +) + +// Type Path store path +type Path struct { + Components []Component + Points []float64 + x, y float64 +} + +func (p *Path) Clear() { + p.Components = p.Components[0:0] + p.Points = p.Points[0:0] + return +} + +func (p *Path) appendToPath(cmd Component, points ...float64) { + p.Components = append(p.Components, cmd) + p.Points = append(p.Points, points...) +} + +// Copy make a clone of the current path and return it +func (src *Path) Copy() (dest *Path) { + dest = new(Path) + dest.Components = make([]Component, len(src.Components)) + copy(dest.Components, src.Components) + dest.Points = make([]float64, len(src.Points)) + copy(dest.Points, src.Points) + dest.x, dest.y = src.x, src.y + return dest +} + +func (p *Path) LastPoint() (x, y float64) { + return p.x, p.y +} + +func (p *Path) IsEmpty() bool { + return len(p.Components) == 0 +} + +func (p *Path) Close() { + p.appendToPath(CloseCmp) +} + +func (p *Path) MoveTo(x, y float64) { + p.appendToPath(MoveToCmp, x, y) + + p.x = x + p.y = y +} + +func (p *Path) LineTo(x, y float64) { + if len(p.Components) == 0 { //special case when no move has been done + p.MoveTo(0, 0) + } + p.appendToPath(LineToCmp, x, y) + p.x = x + p.y = y +} + +func (p *Path) QuadCurveTo(cx, cy, x, y float64) { + if len(p.Components) == 0 { //special case when no move has been done + p.MoveTo(0, 0) + } + p.appendToPath(QuadCurveToCmp, cx, cy, x, y) + p.x = x + p.y = y +} + +func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { + if len(p.Components) == 0 { //special case when no move has been done + p.MoveTo(0, 0) + } + p.appendToPath(CubicCurveToCmp, cx1, cy1, cx2, cy2, x, y) + p.x = x + p.y = y +} + +func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { + endAngle := startAngle + angle + clockWise := true + if angle < 0 { + clockWise = false + } + // normalize + if clockWise { + for endAngle < startAngle { + endAngle += math.Pi * 2.0 + } + } else { + for startAngle < endAngle { + startAngle += math.Pi * 2.0 + } + } + startX := cx + math.Cos(startAngle)*rx + startY := cy + math.Sin(startAngle)*ry + if len(p.Components) > 0 { + p.LineTo(startX, startY) + } else { + p.MoveTo(startX, startY) + } + p.appendToPath(ArcToCmp, cx, cy, rx, ry, startAngle, angle) + p.x = cx + math.Cos(endAngle)*rx + p.y = cy + math.Sin(endAngle)*ry +} + +func (p *Path) String() string { + s := "" + j := 0 + for _, cmd := range p.Components { + switch cmd { + case MoveToCmp: + s += fmt.Sprintf("MoveTo: %f, %f\n", p.Points[j], p.Points[j+1]) + j = j + 2 + case LineToCmp: + s += fmt.Sprintf("LineTo: %f, %f\n", p.Points[j], p.Points[j+1]) + j = j + 2 + case QuadCurveToCmp: + s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3]) + j = j + 4 + case CubicCurveToCmp: + s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5]) + j = j + 6 + case ArcToCmp: + s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.Points[j], p.Points[j+1], p.Points[j+2], p.Points[j+3], p.Points[j+4], p.Points[j+5]) + j = j + 6 + case CloseCmp: + s += "Close\n" + } + } + return s +} From 61a6e03fdbde8069eea134e96a358bddac1d59a9 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Mon, 27 Apr 2015 12:16:18 +0200 Subject: [PATCH 12/39] Start path package --- gc.go | 9 +++++---- image.go | 7 ++++--- path_adder.go | 29 +++++++++++++++-------------- stack_gc.go | 3 ++- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/gc.go b/gc.go index c00cba0..f510a8c 100644 --- a/gc.go +++ b/gc.go @@ -4,6 +4,7 @@ package draw2d import ( + "github.com/llgcode/draw2d/path" "image" "image/color" ) @@ -16,7 +17,7 @@ const ( ) type GraphicContext interface { - PathBuilder + path.PathBuilder // Create a new path BeginPath() GetMatrixTransform() MatrixTransform @@ -49,7 +50,7 @@ type GraphicContext interface { FillStringAt(text string, x, y float64) (cursor float64) StrokeString(text string) (cursor float64) StrokeStringAt(text string, x, y float64) (cursor float64) - Stroke(paths ...*Path) - Fill(paths ...*Path) - FillStroke(paths ...*Path) + Stroke(paths ...*path.Path) + Fill(paths ...*path.Path) + FillStroke(paths ...*path.Path) } diff --git a/image.go b/image.go index 73dcb49..6b83f2b 100644 --- a/image.go +++ b/image.go @@ -5,6 +5,7 @@ package draw2d import ( "errors" + "github.com/llgcode/draw2d/path" "image" "image/color" "image/draw" @@ -275,7 +276,7 @@ func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color color. } /**** second method ****/ -func (gc *ImageGraphicContext) Stroke(paths ...*Path) { +func (gc *ImageGraphicContext) Stroke(paths ...*path.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true @@ -295,7 +296,7 @@ func (gc *ImageGraphicContext) Stroke(paths ...*Path) { } /**** second method ****/ -func (gc *ImageGraphicContext) Fill(paths ...*Path) { +func (gc *ImageGraphicContext) Fill(paths ...*path.Path) { paths = append(paths, gc.Current.Path) gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() @@ -308,7 +309,7 @@ func (gc *ImageGraphicContext) Fill(paths ...*Path) { } /* second method */ -func (gc *ImageGraphicContext) FillStroke(paths ...*Path) { +func (gc *ImageGraphicContext) FillStroke(paths ...*path.Path) { gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.strokeRasterizer.UseNonZeroWinding = true diff --git a/path_adder.go b/path_adder.go index a958a57..0dd8c3f 100644 --- a/path_adder.go +++ b/path_adder.go @@ -5,6 +5,7 @@ package draw2d import ( "code.google.com/p/freetype-go/freetype/raster" + "github.com/llgcode/draw2d/path" ) type VertexAdder struct { @@ -42,29 +43,29 @@ func NewPathAdder(adder raster.Adder) *PathAdder { return &PathAdder{adder, raster.Point{0, 0}, 1} } -func (pathAdder *PathAdder) Convert(paths ...*Path) { - for _, path := range paths { +func (pathAdder *PathAdder) Convert(paths ...*path.Path) { + for _, apath := range paths { j := 0 - for _, cmd := range path.commands { + for _, cmd := range apath.Components { switch cmd { - case MoveTo: - pathAdder.firstPoint = raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)} + case path.MoveToCmp: + pathAdder.firstPoint = raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)} pathAdder.adder.Start(pathAdder.firstPoint) j += 2 - case LineTo: - pathAdder.adder.Add1(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}) + case path.LineToCmp: + pathAdder.adder.Add1(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}) j += 2 - case QuadCurveTo: - pathAdder.adder.Add2(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.vertices[j+2] * 256), raster.Fix32(path.vertices[j+3] * 256)}) + case path.QuadCurveToCmp: + pathAdder.adder.Add2(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)}) j += 4 - case CubicCurveTo: - pathAdder.adder.Add3(raster.Point{raster.Fix32(path.vertices[j] * 256), raster.Fix32(path.vertices[j+1] * 256)}, raster.Point{raster.Fix32(path.vertices[j+2] * 256), raster.Fix32(path.vertices[j+3] * 256)}, raster.Point{raster.Fix32(path.vertices[j+4] * 256), raster.Fix32(path.vertices[j+5] * 256)}) + case path.CubicCurveToCmp: + pathAdder.adder.Add3(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)}, raster.Point{raster.Fix32(apath.Points[j+4] * 256), raster.Fix32(apath.Points[j+5] * 256)}) j += 6 - case ArcTo: - lastPoint := arcAdder(pathAdder.adder, path.vertices[j], path.vertices[j+1], path.vertices[j+2], path.vertices[j+3], path.vertices[j+4], path.vertices[j+5], pathAdder.ApproximationScale) + case path.ArcToCmp: + lastPoint := arcAdder(pathAdder.adder, apath.Points[j], apath.Points[j+1], apath.Points[j+2], apath.Points[j+3], apath.Points[j+4], apath.Points[j+5], pathAdder.ApproximationScale) pathAdder.adder.Add1(lastPoint) j += 6 - case Close: + case path.CloseCmp: pathAdder.adder.Add1(pathAdder.firstPoint) } } diff --git a/stack_gc.go b/stack_gc.go index ee26f61..3d83154 100644 --- a/stack_gc.go +++ b/stack_gc.go @@ -4,6 +4,7 @@ package draw2d import ( + "github.com/llgcode/draw2d/path" "image" "image/color" @@ -16,7 +17,7 @@ type StackGraphicContext struct { type ContextStack struct { Tr MatrixTransform - Path *Path + Path *path.Path LineWidth float64 Dash []float64 DashOffset float64 From 1d191b3eaf4efc86a3b2736799f867765b57c267 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Mon, 27 Apr 2015 12:16:50 +0200 Subject: [PATCH 13/39] Start path package --- advanced_path.go | 42 ----------- path.go | 28 -------- path_converter.go | 147 ------------------------------------- path_storage.go | 179 ---------------------------------------------- 4 files changed, 396 deletions(-) delete mode 100644 advanced_path.go delete mode 100644 path.go delete mode 100644 path_converter.go delete mode 100644 path_storage.go diff --git a/advanced_path.go b/advanced_path.go deleted file mode 100644 index c5d8984..0000000 --- a/advanced_path.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 13/12/2010 by Laurent Le Goff - -package draw2d - -import ( - "math" -) - -//high level path creation - -func Rect(path PathBuilder, x1, y1, x2, y2 float64) { - path.MoveTo(x1, y1) - path.LineTo(x2, y1) - path.LineTo(x2, y2) - path.LineTo(x1, y2) - path.Close() -} - -func RoundRect(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) { - arcWidth = arcWidth / 2 - arcHeight = arcHeight / 2 - path.MoveTo(x1, y1+arcHeight) - path.QuadCurveTo(x1, y1, x1+arcWidth, y1) - path.LineTo(x2-arcWidth, y1) - path.QuadCurveTo(x2, y1, x2, y1+arcHeight) - path.LineTo(x2, y2-arcHeight) - path.QuadCurveTo(x2, y2, x2-arcWidth, y2) - path.LineTo(x1+arcWidth, y2) - path.QuadCurveTo(x1, y2, x1, y2-arcHeight) - path.Close() -} - -func Ellipse(path PathBuilder, cx, cy, rx, ry float64) { - path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) - path.Close() -} - -func Circle(path PathBuilder, cx, cy, radius float64) { - path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) - path.Close() -} diff --git a/path.go b/path.go deleted file mode 100644 index 232cf76..0000000 --- a/path.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -// PathBuilder define method that create path -type PathBuilder interface { - // Return the current point of the current path - LastPoint() (x, y float64) - - // MoveTo start a new path at (x, y) position - MoveTo(x, y float64) - - // LineTo add a line to the current path - LineTo(x, y float64) - - // QuadCurveTo add a quadratic curve to the current path - QuadCurveTo(cx, cy, x, y float64) - - // CubicCurveTo add a cubic bezier curve to the current path - CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) - - // ArcTo add an arc to the path - ArcTo(cx, cy, rx, ry, startAngle, angle float64) - - // Close the current path - Close() -} diff --git a/path_converter.go b/path_converter.go deleted file mode 100644 index dd96853..0000000 --- a/path_converter.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 06/12/2010 by Laurent Le Goff - -package draw2d - -import ( - "github.com/llgcode/draw2d/curve" - "math" -) - -type PathConverter struct { - converter LineBuilder - ApproximationScale float64 - startX, startY, x, y float64 -} - -func NewPathConverter(converter LineBuilder) *PathConverter { - return &PathConverter{converter, 1, 0, 0, 0, 0} -} - -func (c *PathConverter) Convert(paths ...*Path) { - for _, path := range paths { - i := 0 - for _, cmd := range path.commands { - switch cmd { - case MoveTo: - c.x, c.y = path.vertices[i], path.vertices[i+1] - c.startX, c.startY = c.x, c.y - if i != 0 { - c.converter.End() - } - c.converter.MoveTo(c.x, c.y) - i += 2 - case LineTo: - c.x, c.y = path.vertices[i], path.vertices[i+1] - c.converter.LineTo(c.x, c.y) - c.converter.LineJoin() - i += 2 - case QuadCurveTo: - curve.TraceQuad(c.converter, path.vertices[i-2:], 0.5) - c.x, c.y = path.vertices[i+2], path.vertices[i+3] - c.converter.LineTo(c.x, c.y) - i += 4 - case CubicCurveTo: - curve.TraceCubic(c.converter, path.vertices[i-2:], 0.5) - c.x, c.y = path.vertices[i+4], path.vertices[i+5] - c.converter.LineTo(c.x, c.y) - i += 6 - case ArcTo: - c.x, c.y = arc(c.converter, path.vertices[i], path.vertices[i+1], path.vertices[i+2], path.vertices[i+3], path.vertices[i+4], path.vertices[i+5], c.ApproximationScale) - c.converter.LineTo(c.x, c.y) - i += 6 - case Close: - c.converter.LineTo(c.startX, c.startY) - c.converter.Close() - } - } - c.converter.End() - } -} - -func (c *PathConverter) convertCommand(cmd PathCmd, vertices ...float64) int { - return 0 -} - -func (c *PathConverter) MoveTo(x, y float64) *PathConverter { - c.x, c.y = x, y - c.startX, c.startY = c.x, c.y - c.converter.End() - c.converter.MoveTo(c.x, c.y) - return c -} - -func (c *PathConverter) RMoveTo(dx, dy float64) *PathConverter { - c.MoveTo(c.x+dx, c.y+dy) - return c -} - -func (c *PathConverter) LineTo(x, y float64) *PathConverter { - c.x, c.y = x, y - c.converter.LineTo(c.x, c.y) - c.converter.LineJoin() - return c -} - -func (c *PathConverter) RLineTo(dx, dy float64) *PathConverter { - c.LineTo(c.x+dx, c.y+dy) - return c -} - -func (c *PathConverter) QuadCurveTo(cx, cy, x, y float64) *PathConverter { - curve.TraceQuad(c.converter, []float64{c.x, c.y, cx, cy, x, y}, 0.5) - c.x, c.y = x, y - c.converter.LineTo(c.x, c.y) - return c -} - -func (c *PathConverter) RQuadCurveTo(dcx, dcy, dx, dy float64) *PathConverter { - c.QuadCurveTo(c.x+dcx, c.y+dcy, c.x+dx, c.y+dy) - return c -} - -func (c *PathConverter) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) *PathConverter { - curve.TraceCubic(c.converter, []float64{c.x, c.y, cx1, cy1, cx2, cy2, x, y}, 0.5) - c.x, c.y = x, y - c.converter.LineTo(c.x, c.y) - return c -} - -func (c *PathConverter) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) *PathConverter { - c.CubicCurveTo(c.x+dcx1, c.y+dcy1, c.x+dcx2, c.y+dcy2, c.x+dx, c.y+dy) - return c -} - -func (c *PathConverter) ArcTo(cx, cy, rx, ry, startAngle, angle float64) *PathConverter { - endAngle := startAngle + angle - clockWise := true - if angle < 0 { - clockWise = false - } - // normalize - if clockWise { - for endAngle < startAngle { - endAngle += math.Pi * 2.0 - } - } else { - for startAngle < endAngle { - startAngle += math.Pi * 2.0 - } - } - startX := cx + math.Cos(startAngle)*rx - startY := cy + math.Sin(startAngle)*ry - c.MoveTo(startX, startY) - c.x, c.y = arc(c.converter, cx, cy, rx, ry, startAngle, angle, c.ApproximationScale) - c.converter.LineTo(c.x, c.y) - return c -} - -func (c *PathConverter) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) *PathConverter { - c.ArcTo(c.x+dcx, c.y+dcy, rx, ry, startAngle, angle) - return c -} - -func (c *PathConverter) Close() *PathConverter { - c.converter.Close() - return c -} diff --git a/path_storage.go b/path_storage.go deleted file mode 100644 index 3725730..0000000 --- a/path_storage.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -import ( - "fmt" - "math" -) - -type PathCmd int - -const ( - MoveTo PathCmd = iota - LineTo - QuadCurveTo - CubicCurveTo - ArcTo - Close -) - -type Path struct { - commands []PathCmd - vertices []float64 - x, y float64 -} - -func NewPathStorage() (p *Path) { - p = new(Path) - p.commands = make([]PathCmd, 0, 256) - p.vertices = make([]float64, 0, 256) - return -} - -func (p *Path) Clear() { - p.commands = p.commands[0:0] - p.vertices = p.vertices[0:0] - return -} - -func (p *Path) appendToPath(cmd PathCmd, vertices ...float64) { - p.commands = append(p.commands, cmd) - p.vertices = append(p.vertices, vertices...) -} - -func (src *Path) Copy() (dest *Path) { - dest = new(Path) - dest.commands = make([]PathCmd, len(src.commands)) - copy(dest.commands, src.commands) - dest.vertices = make([]float64, len(src.vertices)) - copy(dest.vertices, src.vertices) - return dest -} - -func (p *Path) LastPoint() (x, y float64) { - return p.x, p.y -} - -func (p *Path) IsEmpty() bool { - return len(p.commands) == 0 -} - -func (p *Path) Close() { - p.appendToPath(Close) -} - -func (p *Path) MoveTo(x, y float64) { - p.appendToPath(MoveTo, x, y) - - p.x = x - p.y = y -} - -func (p *Path) RMoveTo(dx, dy float64) { - x, y := p.LastPoint() - p.MoveTo(x+dx, y+dy) -} - -func (p *Path) LineTo(x, y float64) { - if len(p.commands) == 0 { //special case when no move has been done - p.MoveTo(0, 0) - } - p.appendToPath(LineTo, x, y) - p.x = x - p.y = y -} - -func (p *Path) RLineTo(dx, dy float64) { - x, y := p.LastPoint() - p.LineTo(x+dx, y+dy) -} - -func (p *Path) QuadCurveTo(cx, cy, x, y float64) { - if len(p.commands) == 0 { //special case when no move has been done - p.MoveTo(0, 0) - } - p.appendToPath(QuadCurveTo, cx, cy, x, y) - p.x = x - p.y = y -} - -func (p *Path) RQuadCurveTo(dcx, dcy, dx, dy float64) { - x, y := p.LastPoint() - p.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy) -} - -func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { - if len(p.commands) == 0 { //special case when no move has been done - p.MoveTo(0, 0) - } - p.appendToPath(CubicCurveTo, cx1, cy1, cx2, cy2, x, y) - p.x = x - p.y = y -} - -func (p *Path) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) { - x, y := p.LastPoint() - p.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy) -} - -func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { - endAngle := startAngle + angle - clockWise := true - if angle < 0 { - clockWise = false - } - // normalize - if clockWise { - for endAngle < startAngle { - endAngle += math.Pi * 2.0 - } - } else { - for startAngle < endAngle { - startAngle += math.Pi * 2.0 - } - } - startX := cx + math.Cos(startAngle)*rx - startY := cy + math.Sin(startAngle)*ry - if len(p.commands) > 0 { - p.LineTo(startX, startY) - } else { - p.MoveTo(startX, startY) - } - p.appendToPath(ArcTo, cx, cy, rx, ry, startAngle, angle) - p.x = cx + math.Cos(endAngle)*rx - p.y = cy + math.Sin(endAngle)*ry -} - -func (p *Path) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) { - x, y := p.LastPoint() - p.ArcTo(x+dcx, y+dcy, rx, ry, startAngle, angle) -} - -func (p *Path) String() string { - s := "" - j := 0 - for _, cmd := range p.commands { - switch cmd { - case MoveTo: - s += fmt.Sprintf("MoveTo: %f, %f\n", p.vertices[j], p.vertices[j+1]) - j = j + 2 - case LineTo: - s += fmt.Sprintf("LineTo: %f, %f\n", p.vertices[j], p.vertices[j+1]) - j = j + 2 - case QuadCurveTo: - s += fmt.Sprintf("QuadCurveTo: %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3]) - j = j + 4 - case CubicCurveTo: - s += fmt.Sprintf("CubicCurveTo: %f, %f, %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5]) - j = j + 6 - case ArcTo: - s += fmt.Sprintf("ArcTo: %f, %f, %f, %f, %f, %f\n", p.vertices[j], p.vertices[j+1], p.vertices[j+2], p.vertices[j+3], p.vertices[j+4], p.vertices[j+5]) - j = j + 6 - case Close: - s += "Close\n" - } - } - return s -} From 0b7a049f3e917c4be457bc688b10a015e978ffa5 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 10:28:05 +0200 Subject: [PATCH 14/39] refactoring mv path things in path package --- arc.go | 68 ------- curve/arc.go | 38 +++- curves.go | 336 -------------------------------- image.go => ftgc.go | 70 +++---- gc.go | 4 +- math.go | 14 -- dasher.go => path/dasher.go | 2 +- line.go => path/flattening.go | 4 +- path_adder.go => path/ftpath.go | 40 ++-- path/interpret.go | 63 ------ path/path.go | 45 +++++ stroker.go => path/stroker.go | 28 ++- path/utils.go | 13 ++ stack_gc.go | 38 +--- transform.go | 5 +- 15 files changed, 185 insertions(+), 583 deletions(-) delete mode 100644 arc.go delete mode 100644 curves.go rename image.go => ftgc.go (85%) rename dasher.go => path/dasher.go (99%) rename line.go => path/flattening.go (95%) rename path_adder.go => path/ftpath.go (60%) delete mode 100644 path/interpret.go rename stroker.go => path/stroker.go (81%) create mode 100644 path/utils.go diff --git a/arc.go b/arc.go deleted file mode 100644 index 03213a6..0000000 --- a/arc.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -import ( - "math" - - "code.google.com/p/freetype-go/freetype/raster" -) - -func arc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { - end := start + angle - clockWise := true - if angle < 0 { - clockWise = false - } - ra := (math.Abs(rx) + math.Abs(ry)) / 2 - da := math.Acos(ra/(ra+0.125/scale)) * 2 - //normalize - if !clockWise { - da = -da - } - angle = start + da - var curX, curY float64 - for { - if (angle < end-da/4) != clockWise { - curX = x + math.Cos(end)*rx - curY = y + math.Sin(end)*ry - return curX, curY - } - curX = x + math.Cos(angle)*rx - curY = y + math.Sin(angle)*ry - - angle += da - t.LineTo(curX, curY) - } - return curX, curY -} - -func arcAdder(adder raster.Adder, x, y, rx, ry, start, angle, scale float64) raster.Point { - end := start + angle - clockWise := true - if angle < 0 { - clockWise = false - } - ra := (math.Abs(rx) + math.Abs(ry)) / 2 - da := math.Acos(ra/(ra+0.125/scale)) * 2 - //normalize - if !clockWise { - da = -da - } - angle = start + da - var curX, curY float64 - for { - if (angle < end-da/4) != clockWise { - curX = x + math.Cos(end)*rx - curY = y + math.Sin(end)*ry - return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)} - } - curX = x + math.Cos(angle)*rx - curY = y + math.Sin(angle)*ry - - angle += da - adder.Add1(raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)}) - } - return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)} -} diff --git a/curve/arc.go b/curve/arc.go index 733bf3e..1d84d86 100644 --- a/curve/arc.go +++ b/curve/arc.go @@ -5,10 +5,12 @@ package curve import ( "math" + + "code.google.com/p/freetype-go/freetype/raster" ) // TraceArc trace an arc using a LineBuilder -func TraceArc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) { +func TraceArc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { end := start + angle clockWise := true if angle < 0 { @@ -26,7 +28,7 @@ func TraceArc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) { if (angle < end-da/4) != clockWise { curX = x + math.Cos(end)*rx curY = y + math.Sin(end)*ry - break + return curX, curY } curX = x + math.Cos(angle)*rx curY = y + math.Sin(angle)*ry @@ -34,5 +36,35 @@ func TraceArc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) { angle += da t.LineTo(curX, curY) } - t.LineTo(curX, curY) + return curX, curY +} + +// TraceArc trace an arc using a Freetype +func TraceArcFt(adder raster.Adder, x, y, rx, ry, start, angle, scale float64) raster.Point { + end := start + angle + clockWise := true + if angle < 0 { + clockWise = false + } + ra := (math.Abs(rx) + math.Abs(ry)) / 2 + da := math.Acos(ra/(ra+0.125/scale)) * 2 + //normalize + if !clockWise { + da = -da + } + angle = start + da + var curX, curY float64 + for { + if (angle < end-da/4) != clockWise { + curX = x + math.Cos(end)*rx + curY = y + math.Sin(end)*ry + return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)} + } + curX = x + math.Cos(angle)*rx + curY = y + math.Sin(angle)*ry + + angle += da + adder.Add1(raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)}) + } + return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)} } diff --git a/curves.go b/curves.go deleted file mode 100644 index 4accdb1..0000000 --- a/curves.go +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -import ( - "math" -) - -var ( - CurveRecursionLimit = 32 - CurveCollinearityEpsilon = 1e-30 - CurveAngleToleranceEpsilon = 0.01 -) - -/* - The function has the following parameters: - approximationScale : - Eventually determines the approximation accuracy. In practice we need to transform points from the World coordinate system to the Screen one. - It always has some scaling coefficient. - The curves are usually processed in the World coordinates, while the approximation accuracy should be eventually in pixels. - Usually it looks as follows: - curved.approximationScale(transform.scale()); - where transform is the affine matrix that includes all the transformations, including viewport and zoom. - angleTolerance : - You set it in radians. - The less this value is the more accurate will be the approximation at sharp turns. - But 0 means that we don't consider angle conditions at all. - cuspLimit : - An angle in radians. - If 0, only the real cusps will have bevel cuts. - If more than 0, it will restrict the sharpness. - The more this value is the less sharp turns will be cut. - Typically it should not exceed 10-15 degrees. -*/ -func cubicBezier(v LineBuilder, x1, y1, x2, y2, x3, y3, x4, y4, approximationScale, angleTolerance, cuspLimit float64) { - cuspLimit = computeCuspLimit(cuspLimit) - distanceToleranceSquare := 0.5 / approximationScale - distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare - recursiveCubicBezier(v, x1, y1, x2, y2, x3, y3, x4, y4, 0, distanceToleranceSquare, angleTolerance, cuspLimit) -} - -/* - * see cubicBezier comments for approximationScale and angleTolerance definition - */ -func quadraticBezier(v LineBuilder, x1, y1, x2, y2, x3, y3, approximationScale, angleTolerance float64) { - distanceToleranceSquare := 0.5 / approximationScale - distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare - - recursiveQuadraticBezierBezier(v, x1, y1, x2, y2, x3, y3, 0, distanceToleranceSquare, angleTolerance) -} - -func computeCuspLimit(v float64) (r float64) { - if v == 0.0 { - r = 0.0 - } else { - r = math.Pi - v - } - return -} - -/** - * http://www.antigrain.com/research/adaptive_bezier/index.html - */ -func recursiveQuadraticBezierBezier(v LineBuilder, x1, y1, x2, y2, x3, y3 float64, level int, distanceToleranceSquare, angleTolerance float64) { - if level > CurveRecursionLimit { - return - } - - // Calculate all the mid-points of the line segments - //---------------------- - x12 := (x1 + x2) / 2 - y12 := (y1 + y2) / 2 - x23 := (x2 + x3) / 2 - y23 := (y2 + y3) / 2 - x123 := (x12 + x23) / 2 - y123 := (y12 + y23) / 2 - - dx := x3 - x1 - dy := y3 - y1 - d := math.Abs(((x2-x3)*dy - (y2-y3)*dx)) - - if d > CurveCollinearityEpsilon { - // Regular case - //----------------- - if d*d <= distanceToleranceSquare*(dx*dx+dy*dy) { - // If the curvature doesn't exceed the distanceTolerance value - // we tend to finish subdivisions. - //---------------------- - if angleTolerance < CurveAngleToleranceEpsilon { - v.LineTo(x123, y123) - return - } - - // Angle & Cusp Condition - //---------------------- - da := math.Abs(math.Atan2(y3-y2, x3-x2) - math.Atan2(y2-y1, x2-x1)) - if da >= math.Pi { - da = 2*math.Pi - da - } - - if da < angleTolerance { - // Finally we can stop the recursion - //---------------------- - v.LineTo(x123, y123) - return - } - } - } else { - // Collinear case - //------------------ - da := dx*dx + dy*dy - if da == 0 { - d = squareDistance(x1, y1, x2, y2) - } else { - d = ((x2-x1)*dx + (y2-y1)*dy) / da - if d > 0 && d < 1 { - // Simple collinear case, 1---2---3 - // We can leave just two endpoints - return - } - if d <= 0 { - d = squareDistance(x2, y2, x1, y1) - } else if d >= 1 { - d = squareDistance(x2, y2, x3, y3) - } else { - d = squareDistance(x2, y2, x1+d*dx, y1+d*dy) - } - } - if d < distanceToleranceSquare { - v.LineTo(x2, y2) - return - } - } - - // Continue subdivision - //---------------------- - recursiveQuadraticBezierBezier(v, x1, y1, x12, y12, x123, y123, level+1, distanceToleranceSquare, angleTolerance) - recursiveQuadraticBezierBezier(v, x123, y123, x23, y23, x3, y3, level+1, distanceToleranceSquare, angleTolerance) -} - -/** - * http://www.antigrain.com/research/adaptive_bezier/index.html - */ -func recursiveCubicBezier(v LineBuilder, x1, y1, x2, y2, x3, y3, x4, y4 float64, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) { - if level > CurveRecursionLimit { - return - } - - // Calculate all the mid-points of the line segments - //---------------------- - x12 := (x1 + x2) / 2 - y12 := (y1 + y2) / 2 - x23 := (x2 + x3) / 2 - y23 := (y2 + y3) / 2 - x34 := (x3 + x4) / 2 - y34 := (y3 + y4) / 2 - x123 := (x12 + x23) / 2 - y123 := (y12 + y23) / 2 - x234 := (x23 + x34) / 2 - y234 := (y23 + y34) / 2 - x1234 := (x123 + x234) / 2 - y1234 := (y123 + y234) / 2 - - // Try to approximate the full cubic curve by a single straight line - //------------------ - dx := x4 - x1 - dy := y4 - y1 - - d2 := math.Abs(((x2-x4)*dy - (y2-y4)*dx)) - d3 := math.Abs(((x3-x4)*dy - (y3-y4)*dx)) - - switch { - case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon: - // All collinear OR p1==p4 - //---------------------- - k := dx*dx + dy*dy - if k == 0 { - d2 = squareDistance(x1, y1, x2, y2) - d3 = squareDistance(x4, y4, x3, y3) - } else { - k = 1 / k - da1 := x2 - x1 - da2 := y2 - y1 - d2 = k * (da1*dx + da2*dy) - da1 = x3 - x1 - da2 = y3 - y1 - d3 = k * (da1*dx + da2*dy) - if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 { - // Simple collinear case, 1---2---3---4 - // We can leave just two endpoints - return - } - if d2 <= 0 { - d2 = squareDistance(x2, y2, x1, y1) - } else if d2 >= 1 { - d2 = squareDistance(x2, y2, x4, y4) - } else { - d2 = squareDistance(x2, y2, x1+d2*dx, y1+d2*dy) - } - - if d3 <= 0 { - d3 = squareDistance(x3, y3, x1, y1) - } else if d3 >= 1 { - d3 = squareDistance(x3, y3, x4, y4) - } else { - d3 = squareDistance(x3, y3, x1+d3*dx, y1+d3*dy) - } - } - if d2 > d3 { - if d2 < distanceToleranceSquare { - v.LineTo(x2, y2) - return - } - } else { - if d3 < distanceToleranceSquare { - v.LineTo(x3, y3) - return - } - } - break - - case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon: - // p1,p2,p4 are collinear, p3 is significant - //---------------------- - if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) { - if angleTolerance < CurveAngleToleranceEpsilon { - v.LineTo(x23, y23) - return - } - - // Angle Condition - //---------------------- - da1 := math.Abs(math.Atan2(y4-y3, x4-x3) - math.Atan2(y3-y2, x3-x2)) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - - if da1 < angleTolerance { - v.LineTo(x2, y2) - v.LineTo(x3, y3) - return - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - v.LineTo(x3, y3) - return - } - } - } - break - - case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon: - // p1,p3,p4 are collinear, p2 is significant - //---------------------- - if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) { - if angleTolerance < CurveAngleToleranceEpsilon { - v.LineTo(x23, y23) - return - } - - // Angle Condition - //---------------------- - da1 := math.Abs(math.Atan2(y3-y2, x3-x2) - math.Atan2(y2-y1, x2-x1)) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - - if da1 < angleTolerance { - v.LineTo(x2, y2) - v.LineTo(x3, y3) - return - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - v.LineTo(x2, y2) - return - } - } - } - break - - case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon: - // Regular case - //----------------- - if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) { - // If the curvature doesn't exceed the distanceTolerance value - // we tend to finish subdivisions. - //---------------------- - if angleTolerance < CurveAngleToleranceEpsilon { - v.LineTo(x23, y23) - return - } - - // Angle & Cusp Condition - //---------------------- - k := math.Atan2(y3-y2, x3-x2) - da1 := math.Abs(k - math.Atan2(y2-y1, x2-x1)) - da2 := math.Abs(math.Atan2(y4-y3, x4-x3) - k) - if da1 >= math.Pi { - da1 = 2*math.Pi - da1 - } - if da2 >= math.Pi { - da2 = 2*math.Pi - da2 - } - - if da1+da2 < angleTolerance { - // Finally we can stop the recursion - //---------------------- - v.LineTo(x23, y23) - return - } - - if cuspLimit != 0.0 { - if da1 > cuspLimit { - v.LineTo(x2, y2) - return - } - - if da2 > cuspLimit { - v.LineTo(x3, y3) - return - } - } - } - break - } - - // Continue subdivision - //---------------------- - recursiveCubicBezier(v, x1, y1, x12, y12, x123, y123, x1234, y1234, level+1, distanceToleranceSquare, angleTolerance, cuspLimit) - recursiveCubicBezier(v, x1234, y1234, x234, y234, x34, y34, x4, y4, level+1, distanceToleranceSquare, angleTolerance, cuspLimit) - -} diff --git a/image.go b/ftgc.go similarity index 85% rename from image.go rename to ftgc.go index 6b83f2b..86647cf 100644 --- a/image.go +++ b/ftgc.go @@ -275,56 +275,64 @@ func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color color. gc.Current.Path.Clear() } -/**** second method ****/ func (gc *ImageGraphicContext) Stroke(paths ...*path.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer))) + stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var pathConverter *PathConverter + + var liner path.LineBuilder if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { - dasher := NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) - pathConverter = NewPathConverter(dasher) + liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { - pathConverter = NewPathConverter(stroker) + liner = stroker + } + for _, p := range paths { + p.Flatten(liner, gc.Current.Tr.GetScale()) } - pathConverter.ApproximationScale = gc.Current.Tr.GetScale() - pathConverter.Convert(paths...) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) } -/**** second method ****/ func (gc *ImageGraphicContext) Fill(paths ...*path.Path) { paths = append(paths, gc.Current.Path) gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() /**** first method ****/ - pathConverter := NewPathConverter(NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer))) - pathConverter.ApproximationScale = gc.Current.Tr.GetScale() - pathConverter.Convert(paths...) + flattener := NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) + for _, p := range paths { + p.Flatten(flattener, gc.Current.Tr.GetScale()) + } gc.paint(gc.fillRasterizer, gc.Current.FillColor) } -/* second method */ func (gc *ImageGraphicContext) FillStroke(paths ...*path.Path) { + paths = append(paths, gc.Current.Path) gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.strokeRasterizer.UseNonZeroWinding = true - filler := NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.fillRasterizer)) + flattener := NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) - stroker := NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, NewVertexAdder(gc.strokeRasterizer))) + stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - demux := NewLineBuilders(filler, stroker) - paths = append(paths, gc.Current.Path) - pathConverter := NewPathConverter(demux) - pathConverter.ApproximationScale = gc.Current.Tr.GetScale() - pathConverter.Convert(paths...) + var liner path.LineBuilder + if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { + liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) + } else { + liner = stroker + } + demux := path.NewLineBuilders(flattener, liner) + for _, p := range paths { + p.Flatten(demux, gc.Current.Tr.GetScale()) + } + + // Fill gc.paint(gc.fillRasterizer, gc.Current.FillColor) + // Stroke gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) } @@ -337,25 +345,3 @@ func (f FillRule) UseNonZeroWinding() bool { } return false } - -func (c Cap) Convert() raster.Capper { - switch c { - case RoundCap: - return raster.RoundCapper - case ButtCap: - return raster.ButtCapper - case SquareCap: - return raster.SquareCapper - } - return raster.RoundCapper -} - -func (j Join) Convert() raster.Joiner { - switch j { - case RoundJoin: - return raster.RoundJoiner - case BevelJoin: - return raster.BevelJoiner - } - return raster.RoundJoiner -} diff --git a/gc.go b/gc.go index f510a8c..6b91f9a 100644 --- a/gc.go +++ b/gc.go @@ -30,8 +30,8 @@ type GraphicContext interface { SetFillColor(c color.Color) SetFillRule(f FillRule) SetLineWidth(lineWidth float64) - SetLineCap(cap Cap) - SetLineJoin(join Join) + SetLineCap(cap path.Cap) + SetLineJoin(join path.Join) SetLineDash(dash []float64, dashOffset float64) SetFontSize(fontSize float64) GetFontSize() float64 diff --git a/math.go b/math.go index c4bb761..91b5482 100644 --- a/math.go +++ b/math.go @@ -3,20 +3,6 @@ package draw2d -import ( - "math" -) - -func distance(x1, y1, x2, y2 float64) float64 { - dx := x2 - x1 - dy := y2 - y1 - return float64(math.Sqrt(dx*dx + dy*dy)) -} - -func vectorDistance(dx, dy float64) float64 { - return float64(math.Sqrt(dx*dx + dy*dy)) -} - func squareDistance(x1, y1, x2, y2 float64) float64 { dx := x2 - x1 dy := y2 - y1 diff --git a/dasher.go b/path/dasher.go similarity index 99% rename from dasher.go rename to path/dasher.go index a9a1ce2..8e5c115 100644 --- a/dasher.go +++ b/path/dasher.go @@ -1,7 +1,7 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -package draw2d +package path type DashVertexConverter struct { next LineBuilder diff --git a/line.go b/path/flattening.go similarity index 95% rename from line.go rename to path/flattening.go index 5838aad..9f18061 100644 --- a/line.go +++ b/path/flattening.go @@ -1,7 +1,7 @@ // Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff +// created: 06/12/2010 by Laurent Le Goff -package draw2d +package path // LineBuilder defines drawing line methods type LineBuilder interface { diff --git a/path_adder.go b/path/ftpath.go similarity index 60% rename from path_adder.go rename to path/ftpath.go index 0dd8c3f..d2b40d5 100644 --- a/path_adder.go +++ b/path/ftpath.go @@ -1,36 +1,36 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -package draw2d +package path import ( "code.google.com/p/freetype-go/freetype/raster" - "github.com/llgcode/draw2d/path" + "github.com/llgcode/draw2d/curve" ) -type VertexAdder struct { +type FtLineBuilder struct { adder raster.Adder } -func NewVertexAdder(adder raster.Adder) *VertexAdder { - return &VertexAdder{adder} +func NewFtLineBuilder(adder raster.Adder) *FtLineBuilder { + return &FtLineBuilder{adder} } -func (vertexAdder *VertexAdder) MoveTo(x, y float64) { - vertexAdder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) +func (FtLineBuilder *FtLineBuilder) MoveTo(x, y float64) { + FtLineBuilder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) } -func (vertexAdder *VertexAdder) LineTo(x, y float64) { - vertexAdder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) +func (FtLineBuilder *FtLineBuilder) LineTo(x, y float64) { + FtLineBuilder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) } -func (vertexAdder *VertexAdder) LineJoin() { +func (FtLineBuilder *FtLineBuilder) LineJoin() { } -func (vertexAdder *VertexAdder) Close() { +func (FtLineBuilder *FtLineBuilder) Close() { } -func (vertexAdder *VertexAdder) End() { +func (FtLineBuilder *FtLineBuilder) End() { } type PathAdder struct { @@ -43,29 +43,29 @@ func NewPathAdder(adder raster.Adder) *PathAdder { return &PathAdder{adder, raster.Point{0, 0}, 1} } -func (pathAdder *PathAdder) Convert(paths ...*path.Path) { +func (pathAdder *PathAdder) Convert(paths ...*Path) { for _, apath := range paths { j := 0 for _, cmd := range apath.Components { switch cmd { - case path.MoveToCmp: + case MoveToCmp: pathAdder.firstPoint = raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)} pathAdder.adder.Start(pathAdder.firstPoint) j += 2 - case path.LineToCmp: + case LineToCmp: pathAdder.adder.Add1(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}) j += 2 - case path.QuadCurveToCmp: + case QuadCurveToCmp: pathAdder.adder.Add2(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)}) j += 4 - case path.CubicCurveToCmp: + case CubicCurveToCmp: pathAdder.adder.Add3(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)}, raster.Point{raster.Fix32(apath.Points[j+4] * 256), raster.Fix32(apath.Points[j+5] * 256)}) j += 6 - case path.ArcToCmp: - lastPoint := arcAdder(pathAdder.adder, apath.Points[j], apath.Points[j+1], apath.Points[j+2], apath.Points[j+3], apath.Points[j+4], apath.Points[j+5], pathAdder.ApproximationScale) + case ArcToCmp: + lastPoint := curve.TraceArcFt(pathAdder.adder, apath.Points[j], apath.Points[j+1], apath.Points[j+2], apath.Points[j+3], apath.Points[j+4], apath.Points[j+5], pathAdder.ApproximationScale) pathAdder.adder.Add1(lastPoint) j += 6 - case path.CloseCmp: + case CloseCmp: pathAdder.adder.Add1(pathAdder.firstPoint) } } diff --git a/path/interpret.go b/path/interpret.go deleted file mode 100644 index 86de6e8..0000000 --- a/path/interpret.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 06/12/2010 by Laurent Le Goff - -package path - -import ( - "github.com/llgcode/draw2d/curve" -) - -type PathConverter struct { - converter LineBuilder - ApproximationScale float64 -} - -func NewPathConverter(converter LineBuilder) *PathConverter { - return &PathConverter{converter, 1, 0, 0, 0, 0} -} - -// may not been in path instead put it in a troke package thing -func (c *PathConverter) Interpret(liner LineBuilder, scale float64, paths ...*Path) { - // First Point - var startX, startY float64 = 0, 0 - // Current Point - var x, y float64 = 0, 0 - for _, path := range paths { - i := 0 - for _, cmd := range path.Components { - switch cmd { - case MoveToCmp: - x, y = path.Points[i], path.Points[i+1] - startX, startY = x, y - if i != 0 { - liner.End() - } - liner.MoveTo(x, y) - i += 2 - case LineToCmp: - x, y = path.Points[i], path.Points[i+1] - liner.LineTo(x, y) - liner.LineJoin() - i += 2 - case QuadCurveToCmp: - curve.TraceQuad(liner, path.Points[i-2:], 0.5) - x, y = path.Points[i+2], path.Points[i+3] - liner.LineTo(x, y) - i += 4 - case CubicCurveToCmp: - curve.TraceCubic(liner, path.Points[i-2:], 0.5) - x, y = path.Points[i+4], path.Points[i+5] - liner.LineTo(x, y) - i += 6 - case ArcToCmp: - x, y = arc(liner, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) - liner.LineTo(x, y) - i += 6 - case CloseCmp: - liner.LineTo(startX, startY) - liner.Close() - } - } - liner.End() - } -} diff --git a/path/path.go b/path/path.go index b902466..203837a 100644 --- a/path/path.go +++ b/path/path.go @@ -6,6 +6,7 @@ package path import ( "fmt" + "github.com/llgcode/draw2d/curve" "math" ) @@ -174,3 +175,47 @@ func (p *Path) String() string { } return s } + +// Flatten convert curves in straight segments keeping join segements +func (path *Path) Flatten(liner LineBuilder, scale float64) { + // First Point + var startX, startY float64 = 0, 0 + // Current Point + var x, y float64 = 0, 0 + i := 0 + for _, cmd := range path.Components { + switch cmd { + case MoveToCmp: + x, y = path.Points[i], path.Points[i+1] + startX, startY = x, y + if i != 0 { + liner.End() + } + liner.MoveTo(x, y) + i += 2 + case LineToCmp: + x, y = path.Points[i], path.Points[i+1] + liner.LineTo(x, y) + liner.LineJoin() + i += 2 + case QuadCurveToCmp: + curve.TraceQuad(liner, path.Points[i-2:], 0.5) + x, y = path.Points[i+2], path.Points[i+3] + liner.LineTo(x, y) + i += 4 + case CubicCurveToCmp: + curve.TraceCubic(liner, path.Points[i-2:], 0.5) + x, y = path.Points[i+4], path.Points[i+5] + liner.LineTo(x, y) + i += 6 + case ArcToCmp: + x, y = curve.TraceArc(liner, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) + liner.LineTo(x, y) + i += 6 + case CloseCmp: + liner.LineTo(startX, startY) + liner.Close() + } + } + liner.End() +} diff --git a/stroker.go b/path/stroker.go similarity index 81% rename from stroker.go rename to path/stroker.go index e140254..60dfac4 100644 --- a/stroker.go +++ b/path/stroker.go @@ -1,7 +1,11 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -package draw2d +package path + +import ( + "code.google.com/p/freetype-go/freetype/raster" +) type Cap int @@ -11,6 +15,18 @@ const ( SquareCap ) +func (c Cap) Convert() raster.Capper { + switch c { + case RoundCap: + return raster.RoundCapper + case ButtCap: + return raster.ButtCapper + case SquareCap: + return raster.SquareCapper + } + return raster.RoundCapper +} + type Join int const ( @@ -19,6 +35,16 @@ const ( MiterJoin ) +func (j Join) Convert() raster.Joiner { + switch j { + case RoundJoin: + return raster.RoundJoiner + case BevelJoin: + return raster.BevelJoiner + } + return raster.RoundJoiner +} + type LineStroker struct { Next LineBuilder HalfLineWidth float64 diff --git a/path/utils.go b/path/utils.go new file mode 100644 index 0000000..68dbc35 --- /dev/null +++ b/path/utils.go @@ -0,0 +1,13 @@ +package path + +import ( + "math" +) + +func distance(x1, y1, x2, y2 float64) float64 { + return vectorDistance(x2-x1, y2-y1) +} + +func vectorDistance(dx, dy float64) float64 { + return float64(math.Sqrt(dx*dx + dy*dy)) +} diff --git a/stack_gc.go b/stack_gc.go index 3d83154..ea34e79 100644 --- a/stack_gc.go +++ b/stack_gc.go @@ -24,8 +24,8 @@ type ContextStack struct { StrokeColor color.Color FillColor color.Color FillRule FillRule - Cap Cap - Join Join + Cap path.Cap + Join path.Join FontSize float64 FontData FontData @@ -44,13 +44,13 @@ func NewStackGraphicContext() *StackGraphicContext { gc := &StackGraphicContext{} gc.Current = new(ContextStack) gc.Current.Tr = NewIdentityMatrix() - gc.Current.Path = NewPathStorage() + gc.Current.Path = new(path.Path) gc.Current.LineWidth = 1.0 gc.Current.StrokeColor = image.Black gc.Current.FillColor = image.White - gc.Current.Cap = RoundCap + gc.Current.Cap = path.RoundCap gc.Current.FillRule = FillRuleEvenOdd - gc.Current.Join = RoundJoin + gc.Current.Join = path.RoundJoin gc.Current.FontSize = 10 gc.Current.FontData = defaultFontData return gc @@ -96,12 +96,12 @@ func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) { gc.Current.LineWidth = LineWidth } -func (gc *StackGraphicContext) SetLineCap(Cap Cap) { - gc.Current.Cap = Cap +func (gc *StackGraphicContext) SetLineCap(cap path.Cap) { + gc.Current.Cap = cap } -func (gc *StackGraphicContext) SetLineJoin(Join Join) { - gc.Current.Join = Join +func (gc *StackGraphicContext) SetLineJoin(join path.Join) { + gc.Current.Join = join } func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) { @@ -141,42 +141,22 @@ func (gc *StackGraphicContext) MoveTo(x, y float64) { gc.Current.Path.MoveTo(x, y) } -func (gc *StackGraphicContext) RMoveTo(dx, dy float64) { - gc.Current.Path.RMoveTo(dx, dy) -} - func (gc *StackGraphicContext) LineTo(x, y float64) { gc.Current.Path.LineTo(x, y) } -func (gc *StackGraphicContext) RLineTo(dx, dy float64) { - gc.Current.Path.RLineTo(dx, dy) -} - func (gc *StackGraphicContext) QuadCurveTo(cx, cy, x, y float64) { gc.Current.Path.QuadCurveTo(cx, cy, x, y) } -func (gc *StackGraphicContext) RQuadCurveTo(dcx, dcy, dx, dy float64) { - gc.Current.Path.RQuadCurveTo(dcx, dcy, dx, dy) -} - func (gc *StackGraphicContext) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { gc.Current.Path.CubicCurveTo(cx1, cy1, cx2, cy2, x, y) } -func (gc *StackGraphicContext) RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy float64) { - gc.Current.Path.RCubicCurveTo(dcx1, dcy1, dcx2, dcy2, dx, dy) -} - func (gc *StackGraphicContext) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { gc.Current.Path.ArcTo(cx, cy, rx, ry, startAngle, angle) } -func (gc *StackGraphicContext) RArcTo(dcx, dcy, rx, ry, startAngle, angle float64) { - gc.Current.Path.RArcTo(dcx, dcy, rx, ry, startAngle, angle) -} - func (gc *StackGraphicContext) Close() { gc.Current.Path.Close() } diff --git a/transform.go b/transform.go index f2c2927..00d177f 100644 --- a/transform.go +++ b/transform.go @@ -7,6 +7,7 @@ import ( "math" "code.google.com/p/freetype-go/freetype/raster" + "github.com/llgcode/draw2d/path" ) type MatrixTransform [6]float64 @@ -254,10 +255,10 @@ func fequals(float1, float2 float64) bool { // this VertexConverter apply the Matrix transformation tr type VertexMatrixTransform struct { tr MatrixTransform - Next LineBuilder + Next path.LineBuilder } -func NewVertexMatrixTransform(tr MatrixTransform, converter LineBuilder) *VertexMatrixTransform { +func NewVertexMatrixTransform(tr MatrixTransform, converter path.LineBuilder) *VertexMatrixTransform { return &VertexMatrixTransform{tr, converter} } From 79f25c1ea20a4b07493535ed9595f22e1da60df2 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 11:16:03 +0200 Subject: [PATCH 15/39] Refactoring path things --- draw2dgl/gc.go | 56 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index 24faa69..5297a2b 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -9,6 +9,7 @@ import ( "code.google.com/p/freetype-go/freetype/raster" "github.com/go-gl/gl/v2.1/gl" "github.com/llgcode/draw2d" + "github.com/llgcode/draw2d/path" ) func init() { @@ -180,56 +181,67 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color rasterizer.Rasterize(gc.painter) rasterizer.Clear() gc.painter.Flush() + gc.Current.Path.Clear() } -func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { +func (gc *GraphicContext) Stroke(paths ...*path.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer))) + stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var pathConverter *draw2d.PathConverter + + var liner path.LineBuilder if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { - dasher := draw2d.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) - pathConverter = draw2d.NewPathConverter(dasher) + liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { - pathConverter = draw2d.NewPathConverter(stroker) + liner = stroker + } + + for _, p := range paths { + p.Flatten(liner, gc.Current.Tr.GetScale()) } - pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code - pathConverter.Convert(paths...) gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) - gc.Current.Path.Clear() } -func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { +func (gc *GraphicContext) Fill(paths ...*path.Path) { paths = append(paths, gc.Current.Path) gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() - pathConverter := draw2d.NewPathConverter(draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer))) - pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code - pathConverter.Convert(paths...) + flattener := draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) + for _, p := range paths { + p.Flatten(flattener, gc.Current.Tr.GetScale()) + } gc.paint(gc.fillRasterizer, gc.Current.FillColor) gc.Current.Path.Clear() } -func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { +func (gc *GraphicContext) FillStroke(paths ...*path.Path) { + paths = append(paths, gc.Current.Path) gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() gc.strokeRasterizer.UseNonZeroWinding = true - filler := draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.fillRasterizer)) + flattener := draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) - stroker := draw2d.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, draw2d.NewVertexAdder(gc.strokeRasterizer))) + stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - demux := draw2d.NewLineBuilders(filler, stroker) - paths = append(paths, gc.Current.Path) - pathConverter := draw2d.NewPathConverter(demux) - pathConverter.ApproximationScale = gc.Current.Tr.GetScale() // From agg code - pathConverter.Convert(paths...) + var liner path.LineBuilder + if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { + liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) + } else { + liner = stroker + } + demux := path.NewLineBuilders(flattener, liner) + for _, p := range paths { + p.Flatten(demux, gc.Current.Tr.GetScale()) + } + + // Fill gc.paint(gc.fillRasterizer, gc.Current.FillColor) + // Stroke gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) - gc.Current.Path = draw2d.NewPathStorage() } From ce6fbe94f3df97e3256e9bef284fb6dc528d8b43 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 11:24:09 +0200 Subject: [PATCH 16/39] Remove old functions --- math.go | 27 --------------------------- transform.go | 8 ++++---- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/math.go b/math.go index 91b5482..8e15c14 100644 --- a/math.go +++ b/math.go @@ -3,36 +3,9 @@ package draw2d -func squareDistance(x1, y1, x2, y2 float64) float64 { - dx := x2 - x1 - dy := y2 - y1 - return dx*dx + dy*dy -} - -func min(x, y float64) float64 { - if x < y { - return x - } - return y -} - -func max(x, y float64) float64 { - if x > y { - return x - } - return y -} - func minMax(x, y float64) (min, max float64) { if x > y { return y, x } return x, y } - -func minUint32(a, b uint32) uint32 { - if a < b { - return a - } - return b -} diff --git a/transform.go b/transform.go index 00d177f..e3a8a86 100644 --- a/transform.go +++ b/transform.go @@ -49,10 +49,10 @@ func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 *float64) { *y0, y1 = minMax(*y0, y1) *y2, y3 = minMax(*y2, y3) - *x0 = min(*x0, *x2) - *y0 = min(*y0, *y2) - *x2 = max(x1, x3) - *y2 = max(y1, y3) + *x0 = math.Min(*x0, *x2) + *y0 = math.Min(*y0, *y2) + *x2 = math.Max(x1, x3) + *y2 = math.Max(y1, y3) } func (tr MatrixTransform) TransformRasterPoint(points ...*raster.Point) { From 24d62b9aa7248243b202e39b700199166a759e4d Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 11:27:59 +0200 Subject: [PATCH 17/39] remove unused file --- paint.go | 92 -------------------------------------------------------- 1 file changed, 92 deletions(-) delete mode 100644 paint.go diff --git a/paint.go b/paint.go deleted file mode 100644 index 885d993..0000000 --- a/paint.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -/* -import ( - "image/draw" - "image" - "freetype-go.googlecode.com/hg/freetype/raster" -)*/ - -const M = 1<<16 - 1 - -/* -type NRGBAPainter struct { - // The image to compose onto. - Image *image.NRGBA - // The Porter-Duff composition operator. - Op draw.Op - // The 16-bit color to paint the spans. - cr, cg, cb, ca uint32 -} - -// Paint satisfies the Painter interface by painting ss onto an image.RGBA. -func (r *NRGBAPainter) Paint(ss []raster.Span, done bool) { - b := r.Image.Bounds() - for _, s := range ss { - if s.Y < b.Min.Y { - continue - } - if s.Y >= b.Max.Y { - return - } - if s.X0 < b.Min.X { - s.X0 = b.Min.X - } - if s.X1 > b.Max.X { - s.X1 = b.Max.X - } - if s.X0 >= s.X1 { - continue - } - base := s.Y * r.Image.Stride - p := r.Image.Pix[base+s.X0 : base+s.X1] - // This code is duplicated from drawGlyphOver in $GOROOT/src/pkg/image/draw/draw.go. - // TODO(nigeltao): Factor out common code into a utility function, once the compiler - // can inline such function calls. - ma := s.A >> 16 - if r.Op == draw.Over { - for i, nrgba := range p { - dr, dg, db, da := nrgba. - a := M - (r.ca*ma)/M - da = (da*a + r.ca*ma) / M - if da != 0 { - dr = minUint32(M, (dr*a+r.cr*ma)/da) - dg = minUint32(M, (dg*a+r.cg*ma)/da) - db = minUint32(M, (db*a+r.cb*ma)/da) - } else { - dr, dg, db = 0, 0, 0 - } - p[i] = image.NRGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)} - } - } else { - for i, nrgba := range p { - dr, dg, db, da := nrgba.RGBA() - a := M - ma - da = (da*a + r.ca*ma) / M - if da != 0 { - dr = minUint32(M, (dr*a+r.cr*ma)/da) - dg = minUint32(M, (dg*a+r.cg*ma)/da) - db = minUint32(M, (db*a+r.cb*ma)/da) - } else { - dr, dg, db = 0, 0, 0 - } - p[i] = image.NRGBAColor{uint8(dr >> 8), uint8(dg >> 8), uint8(db >> 8), uint8(da >> 8)} - } - } - } - -} - -// SetColor sets the color to paint the spans. -func (r *NRGBAPainter) SetColor(c image.Color) { - r.cr, r.cg, r.cb, r.ca = c.RGBA() -} - -// NewRGBAPainter creates a new RGBAPainter for the given image. -func NewNRGBAPainter(m *image.NRGBA) *NRGBAPainter { - return &NRGBAPainter{Image: m} -} -*/ From 966a9b73f79707d21089030b25161f4cdf2b5c5a Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 14:33:32 +0200 Subject: [PATCH 18/39] remove path package and create draw2dimg package --- curve.go | 161 ++++++++++++++++++ curve/arc.go | 70 -------- curve/cubic.go | 80 --------- curve/line.go | 7 - curve/quad.go | 60 ------- curve/curve_test.go => curve_test.go | 32 ++-- {path => draw2dbase}/dasher.go | 14 +- draw2dbase/demux_flattener.go | 39 +++++ draw2dbase/ftpath.go | 29 ++++ stack_gc.go => draw2dbase/stack_gc.go | 86 +++++----- {path => draw2dbase}/stroker.go | 50 +++--- draw2dgl/gc.go | 47 ++--- fileutil.go => draw2dimg/fileutil.go | 2 +- ftgc.go => draw2dimg/ftgc.go | 111 ++++++------ .../rgba_interpolation.go | 6 +- path/drawing_kit.go => drawing_kit.go | 2 +- flattener.go | 59 +++++++ gc.go | 13 +- math.go | 11 -- path/path.go => path.go | 31 ++-- path/flattening.go | 56 ------ path/ftpath.go | 73 -------- path/utils.go | 13 -- transform.go | 52 +++--- 24 files changed, 506 insertions(+), 598 deletions(-) create mode 100644 curve.go delete mode 100644 curve/arc.go delete mode 100644 curve/cubic.go delete mode 100644 curve/line.go delete mode 100644 curve/quad.go rename curve/curve_test.go => curve_test.go (84%) rename {path => draw2dbase}/dasher.go (90%) create mode 100644 draw2dbase/demux_flattener.go create mode 100644 draw2dbase/ftpath.go rename stack_gc.go => draw2dbase/stack_gc.go (61%) rename {path => draw2dbase}/stroker.go (77%) rename fileutil.go => draw2dimg/fileutil.go (97%) rename ftgc.go => draw2dimg/ftgc.go (66%) rename rgba_interpolation.go => draw2dimg/rgba_interpolation.go (96%) rename path/drawing_kit.go => drawing_kit.go (98%) create mode 100644 flattener.go delete mode 100644 math.go rename path/path.go => path.go (89%) delete mode 100644 path/flattening.go delete mode 100644 path/ftpath.go delete mode 100644 path/utils.go diff --git a/curve.go b/curve.go new file mode 100644 index 0000000..3599144 --- /dev/null +++ b/curve.go @@ -0,0 +1,161 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 17/05/2011 by Laurent Le Goff + +package draw2d + +import ( + "math" +) + +const ( + CurveRecursionLimit = 32 +) + +// Cubic +// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64 + +// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves. +// c1 and c2 parameters are the resulting curves +func SubdivideCubic(c, c1, c2 []float64) { + // First point of c is the first point of c1 + c1[0], c1[1] = c[0], c[1] + // Last point of c is the last point of c2 + c2[6], c2[7] = c[6], c[7] + + // Subdivide segment using midpoints + c1[2] = (c[0] + c[2]) / 2 + c1[3] = (c[1] + c[3]) / 2 + + midX := (c[2] + c[4]) / 2 + midY := (c[3] + c[5]) / 2 + + c2[4] = (c[4] + c[6]) / 2 + c2[5] = (c[5] + c[7]) / 2 + + c1[4] = (c1[2] + midX) / 2 + c1[5] = (c1[3] + midY) / 2 + + c2[2] = (midX + c2[4]) / 2 + c2[3] = (midY + c2[5]) / 2 + + c1[6] = (c1[4] + c2[2]) / 2 + c1[7] = (c1[5] + c2[3]) / 2 + + // Last Point of c1 is equal to the first point of c2 + c2[0], c2[1] = c1[6], c1[7] +} + +// TraceCubic generate lines subdividing the cubic curve using a Flattener +// flattening_threshold helps determines the flattening expectation of the curve +func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) { + // Allocation curves + var curves [CurveRecursionLimit * 8]float64 + copy(curves[0:8], cubic[0:8]) + i := 0 + + // current curve + var c []float64 + + var dx, dy, d2, d3 float64 + + for i >= 0 { + c = curves[i*8:] + dx = c[6] - c[0] + dy = c[7] - c[1] + + d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx) + d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx) + + // if it's flat then trace a line + if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { + t.LineTo(c[6], c[7]) + i-- + } else { + // second half of bezier go lower onto the stack + SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:]) + i++ + } + } +} + +// Quad +// x1, y1, cpx1, cpy2, x2, y2 float64 + +// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves. +// c1 and c2 parameters are the resulting curves +func SubdivideQuad(c, c1, c2 []float64) { + // First point of c is the first point of c1 + c1[0], c1[1] = c[0], c[1] + // Last point of c is the last point of c2 + c2[4], c2[5] = c[4], c[5] + + // Subdivide segment using midpoints + c1[2] = (c[0] + c[2]) / 2 + c1[3] = (c[1] + c[3]) / 2 + c2[2] = (c[2] + c[4]) / 2 + c2[3] = (c[3] + c[5]) / 2 + c1[4] = (c1[2] + c2[2]) / 2 + c1[5] = (c1[3] + c2[3]) / 2 + c2[0], c2[1] = c1[4], c1[5] + return +} + +// Trace generate lines subdividing the curve using a Flattener +// flattening_threshold helps determines the flattening expectation of the curve +func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) { + // Allocates curves stack + var curves [CurveRecursionLimit * 6]float64 + copy(curves[0:6], quad[0:6]) + i := 0 + // current curve + var c []float64 + var dx, dy, d float64 + + for i >= 0 { + c = curves[i*6:] + dx = c[4] - c[0] + dy = c[5] - c[1] + + d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx)) + + // if it's flat then trace a line + if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { + t.LineTo(c[4], c[5]) + i-- + } else { + // second half of bezier go lower onto the stack + SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:]) + i++ + } + } +} + +// TraceArc trace an arc using a Flattener +func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { + end := start + angle + clockWise := true + if angle < 0 { + clockWise = false + } + ra := (math.Abs(rx) + math.Abs(ry)) / 2 + da := math.Acos(ra/(ra+0.125/scale)) * 2 + //normalize + if !clockWise { + da = -da + } + angle = start + da + var curX, curY float64 + for { + if (angle < end-da/4) != clockWise { + curX = x + math.Cos(end)*rx + curY = y + math.Sin(end)*ry + return curX, curY + } + curX = x + math.Cos(angle)*rx + curY = y + math.Sin(angle)*ry + + angle += da + t.LineTo(curX, curY) + } + return curX, curY +} diff --git a/curve/arc.go b/curve/arc.go deleted file mode 100644 index 1d84d86..0000000 --- a/curve/arc.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package curve - -import ( - "math" - - "code.google.com/p/freetype-go/freetype/raster" -) - -// TraceArc trace an arc using a LineBuilder -func TraceArc(t LineBuilder, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { - end := start + angle - clockWise := true - if angle < 0 { - clockWise = false - } - ra := (math.Abs(rx) + math.Abs(ry)) / 2 - da := math.Acos(ra/(ra+0.125/scale)) * 2 - //normalize - if !clockWise { - da = -da - } - angle = start + da - var curX, curY float64 - for { - if (angle < end-da/4) != clockWise { - curX = x + math.Cos(end)*rx - curY = y + math.Sin(end)*ry - return curX, curY - } - curX = x + math.Cos(angle)*rx - curY = y + math.Sin(angle)*ry - - angle += da - t.LineTo(curX, curY) - } - return curX, curY -} - -// TraceArc trace an arc using a Freetype -func TraceArcFt(adder raster.Adder, x, y, rx, ry, start, angle, scale float64) raster.Point { - end := start + angle - clockWise := true - if angle < 0 { - clockWise = false - } - ra := (math.Abs(rx) + math.Abs(ry)) / 2 - da := math.Acos(ra/(ra+0.125/scale)) * 2 - //normalize - if !clockWise { - da = -da - } - angle = start + da - var curX, curY float64 - for { - if (angle < end-da/4) != clockWise { - curX = x + math.Cos(end)*rx - curY = y + math.Sin(end)*ry - return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)} - } - curX = x + math.Cos(angle)*rx - curY = y + math.Sin(angle)*ry - - angle += da - adder.Add1(raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)}) - } - return raster.Point{raster.Fix32(curX * 256), raster.Fix32(curY * 256)} -} diff --git a/curve/cubic.go b/curve/cubic.go deleted file mode 100644 index 89d6575..0000000 --- a/curve/cubic.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 17/05/2011 by Laurent Le Goff - -// Package curve implements Bezier Curve Subdivision using De Casteljau's algorithm -package curve - -import ( - "math" -) - -const ( - CurveRecursionLimit = 32 -) - -// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64 -// type Cubic []float64 - -// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves. -// c1 and c2 parameters are the resulting curves -func SubdivideCubic(c, c1, c2 []float64) { - // First point of c is the first point of c1 - c1[0], c1[1] = c[0], c[1] - // Last point of c is the last point of c2 - c2[6], c2[7] = c[6], c[7] - - // Subdivide segment using midpoints - c1[2] = (c[0] + c[2]) / 2 - c1[3] = (c[1] + c[3]) / 2 - - midX := (c[2] + c[4]) / 2 - midY := (c[3] + c[5]) / 2 - - c2[4] = (c[4] + c[6]) / 2 - c2[5] = (c[5] + c[7]) / 2 - - c1[4] = (c1[2] + midX) / 2 - c1[5] = (c1[3] + midY) / 2 - - c2[2] = (midX + c2[4]) / 2 - c2[3] = (midY + c2[5]) / 2 - - c1[6] = (c1[4] + c2[2]) / 2 - c1[7] = (c1[5] + c2[3]) / 2 - - // Last Point of c1 is equal to the first point of c2 - c2[0], c2[1] = c1[6], c1[7] -} - -// TraceCubic generate lines subdividing the cubic curve using a LineBuilder -// flattening_threshold helps determines the flattening expectation of the curve -func TraceCubic(t LineBuilder, cubic []float64, flattening_threshold float64) { - // Allocation curves - var curves [CurveRecursionLimit * 8]float64 - copy(curves[0:8], cubic[0:8]) - i := 0 - - // current curve - var c []float64 - - var dx, dy, d2, d3 float64 - - for i >= 0 { - c = curves[i*8:] - dx = c[6] - c[0] - dy = c[7] - c[1] - - d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx) - d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx) - - // if it's flat then trace a line - if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.LineTo(c[6], c[7]) - i-- - } else { - // second half of bezier go lower onto the stack - SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:]) - i++ - } - } -} diff --git a/curve/line.go b/curve/line.go deleted file mode 100644 index 177ba98..0000000 --- a/curve/line.go +++ /dev/null @@ -1,7 +0,0 @@ -package curve - -// LineBuilder is an interface that help segmenting curve into small lines -type LineBuilder interface { - // LineTo a point - LineTo(x, y float64) -} diff --git a/curve/quad.go b/curve/quad.go deleted file mode 100644 index 12f66c5..0000000 --- a/curve/quad.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 17/05/2011 by Laurent Le Goff - -package curve - -import ( - "math" -) - -// x1, y1, cpx1, cpy2, x2, y2 float64 -// type Quad [6]float64 - -// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves. -// c1 and c2 parameters are the resulting curves -func SubdivideQuad(c, c1, c2 []float64) { - // First point of c is the first point of c1 - c1[0], c1[1] = c[0], c[1] - // Last point of c is the last point of c2 - c2[4], c2[5] = c[4], c[5] - - // Subdivide segment using midpoints - c1[2] = (c[0] + c[2]) / 2 - c1[3] = (c[1] + c[3]) / 2 - c2[2] = (c[2] + c[4]) / 2 - c2[3] = (c[3] + c[5]) / 2 - c1[4] = (c1[2] + c2[2]) / 2 - c1[5] = (c1[3] + c2[3]) / 2 - c2[0], c2[1] = c1[4], c1[5] - return -} - -// Trace generate lines subdividing the curve using a LineBuilder -// flattening_threshold helps determines the flattening expectation of the curve -func TraceQuad(t LineBuilder, quad []float64, flattening_threshold float64) { - // Allocates curves stack - var curves [CurveRecursionLimit * 6]float64 - copy(curves[0:6], quad[0:6]) - i := 0 - // current curve - var c []float64 - var dx, dy, d float64 - - for i >= 0 { - c = curves[i*6:] - dx = c[4] - c[0] - dy = c[5] - c[1] - - d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx)) - - // if it's flat then trace a line - if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.LineTo(c[4], c[5]) - i-- - } else { - // second half of bezier go lower onto the stack - SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:]) - i++ - } - } -} diff --git a/curve/curve_test.go b/curve_test.go similarity index 84% rename from curve/curve_test.go rename to curve_test.go index f3a8fd0..c4d2603 100644 --- a/curve/curve_test.go +++ b/curve_test.go @@ -1,4 +1,4 @@ -package curve +package draw2d import ( "bufio" @@ -34,18 +34,6 @@ var ( } ) -type Path struct { - points []float64 -} - -func (p *Path) MoveTo(x, y float64) { - p.points = append(p.points, x, y) -} - -func (p *Path) LineTo(x, y float64) { - p.points = append(p.points, x, y) -} - func init() { os.Mkdir("test_results", 0666) f, err := os.Create("test_results/_test.html") @@ -85,32 +73,32 @@ func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image { func TestCubicCurve(t *testing.T) { for i := 0; i < len(testsCubicFloat64); i += 8 { - var p Path + var p SegmentedPath p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...) - raster.PolylineBresenham(img, image.Black, p.points...) + raster.PolylineBresenham(img, image.Black, p.Points...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) + drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), img) - log.Printf("Num of points: %d\n", len(p.points)) + log.Printf("Num of points: %d\n", len(p.Points)) } fmt.Println() } func TestQuadCurve(t *testing.T) { for i := 0; i < len(testsQuadFloat64); i += 6 { - var p Path + var p SegmentedPath p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1]) TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...) - raster.PolylineBresenham(img, image.Black, p.points...) + raster.PolylineBresenham(img, image.Black, p.Points...) //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.points...) + drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) - log.Printf("Num of points: %d\n", len(p.points)) + log.Printf("Num of points: %d\n", len(p.Points)) } fmt.Println() } @@ -118,7 +106,7 @@ func TestQuadCurve(t *testing.T) { func BenchmarkCubicCurve(b *testing.B) { for i := 0; i < b.N; i++ { for i := 0; i < len(testsCubicFloat64); i += 8 { - var p Path + var p SegmentedPath p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) } diff --git a/path/dasher.go b/draw2dbase/dasher.go similarity index 90% rename from path/dasher.go rename to draw2dbase/dasher.go index 8e5c115..d0da022 100644 --- a/path/dasher.go +++ b/draw2dbase/dasher.go @@ -1,17 +1,21 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -package path +package draw2dbase + +import ( + "github.com/llgcode/draw2d" +) type DashVertexConverter struct { - next LineBuilder + next draw2d.Flattener x, y, distance float64 dash []float64 currentDash int dashOffset float64 } -func NewDashConverter(dash []float64, dashOffset float64, converter LineBuilder) *DashVertexConverter { +func NewDashConverter(dash []float64, dashOffset float64, converter draw2d.Flattener) *DashVertexConverter { var dasher DashVertexConverter dasher.dash = dash dasher.currentDash = 0 @@ -83,3 +87,7 @@ func (dasher *DashVertexConverter) lineTo(x, y float64) { } dasher.x, dasher.y = x, y } + +func distance(x1, y1, x2, y2 float64) float64 { + return vectorDistance(x2-x1, y2-y1) +} diff --git a/draw2dbase/demux_flattener.go b/draw2dbase/demux_flattener.go new file mode 100644 index 0000000..6537797 --- /dev/null +++ b/draw2dbase/demux_flattener.go @@ -0,0 +1,39 @@ +package draw2dbase + +import ( + "github.com/llgcode/draw2d" +) + +type DemuxFlattener struct { + Flatteners []draw2d.Flattener +} + +func (dc DemuxFlattener) MoveTo(x, y float64) { + for _, flattener := range dc.Flatteners { + flattener.MoveTo(x, y) + } +} + +func (dc DemuxFlattener) LineTo(x, y float64) { + for _, flattener := range dc.Flatteners { + flattener.LineTo(x, y) + } +} + +func (dc DemuxFlattener) LineJoin() { + for _, flattener := range dc.Flatteners { + flattener.LineJoin() + } +} + +func (dc DemuxFlattener) Close() { + for _, flattener := range dc.Flatteners { + flattener.Close() + } +} + +func (dc DemuxFlattener) End() { + for _, flattener := range dc.Flatteners { + flattener.End() + } +} diff --git a/draw2dbase/ftpath.go b/draw2dbase/ftpath.go new file mode 100644 index 0000000..8e5601c --- /dev/null +++ b/draw2dbase/ftpath.go @@ -0,0 +1,29 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 13/12/2010 by Laurent Le Goff + +package draw2dbase + +import ( + "code.google.com/p/freetype-go/freetype/raster" +) + +type FtLineBuilder struct { + Adder raster.Adder +} + +func (liner FtLineBuilder) MoveTo(x, y float64) { + liner.Adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) +} + +func (liner FtLineBuilder) LineTo(x, y float64) { + liner.Adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) +} + +func (liner FtLineBuilder) LineJoin() { +} + +func (liner FtLineBuilder) Close() { +} + +func (liner FtLineBuilder) End() { +} diff --git a/stack_gc.go b/draw2dbase/stack_gc.go similarity index 61% rename from stack_gc.go rename to draw2dbase/stack_gc.go index ea34e79..6170d42 100644 --- a/stack_gc.go +++ b/draw2dbase/stack_gc.go @@ -1,40 +1,42 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 21/11/2010 by Laurent Le Goff -package draw2d +package draw2dbase import ( - "github.com/llgcode/draw2d/path" + "github.com/llgcode/draw2d" "image" "image/color" "code.google.com/p/freetype-go/freetype/truetype" ) +var DefaultFontData = draw2d.FontData{"luxi", draw2d.FontFamilySans, draw2d.FontStyleNormal} + type StackGraphicContext struct { Current *ContextStack } type ContextStack struct { - Tr MatrixTransform - Path *path.Path + Tr draw2d.MatrixTransform + Path *draw2d.Path LineWidth float64 Dash []float64 DashOffset float64 StrokeColor color.Color FillColor color.Color - FillRule FillRule - Cap path.Cap - Join path.Join + FillRule draw2d.FillRule + Cap draw2d.LineCap + Join draw2d.LineJoin FontSize float64 - FontData FontData + FontData draw2d.FontData - font *truetype.Font + Font *truetype.Font // fontSize and dpi are used to calculate scale. scale is the number of // 26.6 fixed point units in 1 em. - scale int32 + Scale int32 - previous *ContextStack + Previous *ContextStack } /** @@ -43,41 +45,41 @@ type ContextStack struct { func NewStackGraphicContext() *StackGraphicContext { gc := &StackGraphicContext{} gc.Current = new(ContextStack) - gc.Current.Tr = NewIdentityMatrix() - gc.Current.Path = new(path.Path) + gc.Current.Tr = draw2d.NewIdentityMatrix() + gc.Current.Path = new(draw2d.Path) gc.Current.LineWidth = 1.0 gc.Current.StrokeColor = image.Black gc.Current.FillColor = image.White - gc.Current.Cap = path.RoundCap - gc.Current.FillRule = FillRuleEvenOdd - gc.Current.Join = path.RoundJoin + gc.Current.Cap = draw2d.RoundCap + gc.Current.FillRule = draw2d.FillRuleEvenOdd + gc.Current.Join = draw2d.RoundJoin gc.Current.FontSize = 10 - gc.Current.FontData = defaultFontData + gc.Current.FontData = DefaultFontData return gc } -func (gc *StackGraphicContext) GetMatrixTransform() MatrixTransform { +func (gc *StackGraphicContext) GetMatrixTransform() draw2d.MatrixTransform { return gc.Current.Tr } -func (gc *StackGraphicContext) SetMatrixTransform(Tr MatrixTransform) { +func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.MatrixTransform) { gc.Current.Tr = Tr } -func (gc *StackGraphicContext) ComposeMatrixTransform(Tr MatrixTransform) { +func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.MatrixTransform) { gc.Current.Tr = Tr.Multiply(gc.Current.Tr) } func (gc *StackGraphicContext) Rotate(angle float64) { - gc.Current.Tr = NewRotationMatrix(angle).Multiply(gc.Current.Tr) + gc.Current.Tr = draw2d.NewRotationMatrix(angle).Multiply(gc.Current.Tr) } func (gc *StackGraphicContext) Translate(tx, ty float64) { - gc.Current.Tr = NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr) + gc.Current.Tr = draw2d.NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr) } func (gc *StackGraphicContext) Scale(sx, sy float64) { - gc.Current.Tr = NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr) + gc.Current.Tr = draw2d.NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr) } func (gc *StackGraphicContext) SetStrokeColor(c color.Color) { @@ -88,40 +90,40 @@ func (gc *StackGraphicContext) SetFillColor(c color.Color) { gc.Current.FillColor = c } -func (gc *StackGraphicContext) SetFillRule(f FillRule) { +func (gc *StackGraphicContext) SetFillRule(f draw2d.FillRule) { gc.Current.FillRule = f } -func (gc *StackGraphicContext) SetLineWidth(LineWidth float64) { - gc.Current.LineWidth = LineWidth +func (gc *StackGraphicContext) SetLineWidth(lineWidth float64) { + gc.Current.LineWidth = lineWidth } -func (gc *StackGraphicContext) SetLineCap(cap path.Cap) { +func (gc *StackGraphicContext) SetLineCap(cap draw2d.LineCap) { gc.Current.Cap = cap } -func (gc *StackGraphicContext) SetLineJoin(join path.Join) { +func (gc *StackGraphicContext) SetLineJoin(join draw2d.LineJoin) { gc.Current.Join = join } -func (gc *StackGraphicContext) SetLineDash(Dash []float64, DashOffset float64) { - gc.Current.Dash = Dash - gc.Current.DashOffset = DashOffset +func (gc *StackGraphicContext) SetLineDash(dash []float64, dashOffset float64) { + gc.Current.Dash = dash + gc.Current.DashOffset = dashOffset } -func (gc *StackGraphicContext) SetFontSize(FontSize float64) { - gc.Current.FontSize = FontSize +func (gc *StackGraphicContext) SetFontSize(fontSize float64) { + gc.Current.FontSize = fontSize } func (gc *StackGraphicContext) GetFontSize() float64 { return gc.Current.FontSize } -func (gc *StackGraphicContext) SetFontData(FontData FontData) { - gc.Current.FontData = FontData +func (gc *StackGraphicContext) SetFontData(fontData draw2d.FontData) { + gc.Current.FontData = fontData } -func (gc *StackGraphicContext) GetFontData() FontData { +func (gc *StackGraphicContext) GetFontData() draw2d.FontData { return gc.Current.FontData } @@ -174,17 +176,17 @@ func (gc *StackGraphicContext) Save() { context.Cap = gc.Current.Cap context.Join = gc.Current.Join context.Path = gc.Current.Path.Copy() - context.font = gc.Current.font - context.scale = gc.Current.scale + context.Font = gc.Current.Font + context.Scale = gc.Current.Scale copy(context.Tr[:], gc.Current.Tr[:]) - context.previous = gc.Current + context.Previous = gc.Current gc.Current = context } func (gc *StackGraphicContext) Restore() { - if gc.Current.previous != nil { + if gc.Current.Previous != nil { oldContext := gc.Current - gc.Current = gc.Current.previous - oldContext.previous = nil + gc.Current = gc.Current.Previous + oldContext.Previous = nil } } diff --git a/path/stroker.go b/draw2dbase/stroker.go similarity index 77% rename from path/stroker.go rename to draw2dbase/stroker.go index 60dfac4..c61c65a 100644 --- a/path/stroker.go +++ b/draw2dbase/stroker.go @@ -1,66 +1,50 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -package path +package draw2dbase import ( "code.google.com/p/freetype-go/freetype/raster" + "github.com/llgcode/draw2d" + "math" ) -type Cap int - -const ( - RoundCap Cap = iota - ButtCap - SquareCap -) - -func (c Cap) Convert() raster.Capper { +func toFtCap(c draw2d.LineCap) raster.Capper { switch c { - case RoundCap: + case draw2d.RoundCap: return raster.RoundCapper - case ButtCap: + case draw2d.ButtCap: return raster.ButtCapper - case SquareCap: + case draw2d.SquareCap: return raster.SquareCapper } return raster.RoundCapper } -type Join int - -const ( - BevelJoin Join = iota - RoundJoin - MiterJoin -) - -func (j Join) Convert() raster.Joiner { +func toFtJoin(j draw2d.LineJoin) raster.Joiner { switch j { - case RoundJoin: + case draw2d.RoundJoin: return raster.RoundJoiner - case BevelJoin: + case draw2d.BevelJoin: return raster.BevelJoiner } return raster.RoundJoiner } type LineStroker struct { - Next LineBuilder + Next draw2d.Flattener HalfLineWidth float64 - Cap Cap - Join Join + Cap draw2d.LineCap + Join draw2d.LineJoin vertices []float64 rewind []float64 x, y, nx, ny float64 } -func NewLineStroker(c Cap, j Join, converter LineBuilder) *LineStroker { +func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener draw2d.Flattener) *LineStroker { l := new(LineStroker) - l.Next = converter + l.Next = flattener l.HalfLineWidth = 0.5 - l.vertices = make([]float64, 0, 256) - l.rewind = make([]float64, 0, 256) l.Cap = c l.Join = j return l @@ -122,3 +106,7 @@ func (l *LineStroker) appendVertex(vertices ...float64) { l.vertices = append(l.vertices, vertices[:s]...) l.rewind = append(l.rewind, vertices[s:]...) } + +func vectorDistance(dx, dy float64) float64 { + return float64(math.Sqrt(dx*dx + dy*dy)) +} diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index 5297a2b..cc9bdc5 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -9,7 +9,7 @@ import ( "code.google.com/p/freetype-go/freetype/raster" "github.com/go-gl/gl/v2.1/gl" "github.com/llgcode/draw2d" - "github.com/llgcode/draw2d/path" + "github.com/llgcode/draw2d/draw2dbase" ) func init() { @@ -113,7 +113,7 @@ func NewPainter() *Painter { } type GraphicContext struct { - *draw2d.StackGraphicContext + *draw2dbase.StackGraphicContext painter *Painter fillRasterizer *raster.Rasterizer strokeRasterizer *raster.Rasterizer @@ -122,7 +122,7 @@ type GraphicContext struct { // NewGraphicContext creates a new Graphic context from an image. func NewGraphicContext(width, height int) *GraphicContext { gc := &GraphicContext{ - draw2d.NewStackGraphicContext(), + draw2dbase.NewStackGraphicContext(), NewPainter(), raster.NewRasterizer(width, height), raster.NewRasterizer(width, height), @@ -184,20 +184,19 @@ func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color gc.Current.Path.Clear() } -func (gc *GraphicContext) Stroke(paths ...*path.Path) { +func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner path.LineBuilder + var liner draw2d.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { - liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) + liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } - for _, p := range paths { p.Flatten(liner, gc.Current.Tr.GetScale()) } @@ -205,37 +204,37 @@ func (gc *GraphicContext) Stroke(paths ...*path.Path) { gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) } -func (gc *GraphicContext) Fill(paths ...*path.Path) { +func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) - gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() + gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) - flattener := draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) + /**** first method ****/ + flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} for _, p := range paths { p.Flatten(flattener, gc.Current.Tr.GetScale()) } gc.paint(gc.fillRasterizer, gc.Current.FillColor) - gc.Current.Path.Clear() } -func (gc *GraphicContext) FillStroke(paths ...*path.Path) { +func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) - gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() + gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) gc.strokeRasterizer.UseNonZeroWinding = true - flattener := draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) + flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} - stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner path.LineBuilder + var liner draw2d.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { - liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) + liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } - demux := path.NewLineBuilders(flattener, liner) + demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}} for _, p := range paths { p.Flatten(demux, gc.Current.Tr.GetScale()) } @@ -245,3 +244,13 @@ func (gc *GraphicContext) FillStroke(paths ...*path.Path) { // Stroke gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) } + +func useNonZeroWinding(f draw2d.FillRule) bool { + switch f { + case draw2d.FillRuleEvenOdd: + return false + case draw2d.FillRuleWinding: + return true + } + return false +} diff --git a/fileutil.go b/draw2dimg/fileutil.go similarity index 97% rename from fileutil.go rename to draw2dimg/fileutil.go index a97d590..b6dccb9 100644 --- a/fileutil.go +++ b/draw2dimg/fileutil.go @@ -1,4 +1,4 @@ -package draw2d +package draw2dimg import ( "bufio" diff --git a/ftgc.go b/draw2dimg/ftgc.go similarity index 66% rename from ftgc.go rename to draw2dimg/ftgc.go index 86647cf..9982d92 100644 --- a/ftgc.go +++ b/draw2dimg/ftgc.go @@ -1,11 +1,12 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 21/11/2010 by Laurent Le Goff -package draw2d +package draw2dimg import ( "errors" - "github.com/llgcode/draw2d/path" + "github.com/llgcode/draw2d" + "github.com/llgcode/draw2d/draw2dbase" "image" "image/color" "image/draw" @@ -21,12 +22,8 @@ type Painter interface { SetColor(color color.Color) } -var ( - defaultFontData = FontData{"luxi", FontFamilySans, FontStyleNormal} -) - -type ImageGraphicContext struct { - *StackGraphicContext +type GraphicContext struct { + *draw2dbase.StackGraphicContext img draw.Image painter Painter fillRasterizer *raster.Rasterizer @@ -38,7 +35,7 @@ type ImageGraphicContext struct { /** * Create a new Graphic context from an image */ -func NewGraphicContext(img draw.Image) *ImageGraphicContext { +func NewGraphicContext(img draw.Image) *GraphicContext { var painter Painter switch selectImage := img.(type) { case *image.RGBA: @@ -50,11 +47,11 @@ func NewGraphicContext(img draw.Image) *ImageGraphicContext { } // Create a new Graphic context from an image and a Painter (see Freetype-go) -func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphicContext { +func NewGraphicContextWithPainter(img draw.Image, painter Painter) *GraphicContext { width, height := img.Bounds().Dx(), img.Bounds().Dy() dpi := 92 - gc := &ImageGraphicContext{ - NewStackGraphicContext(), + gc := &GraphicContext{ + draw2dbase.NewStackGraphicContext(), img, painter, raster.NewRasterizer(width, height), @@ -65,48 +62,48 @@ func NewGraphicContextWithPainter(img draw.Image, painter Painter) *ImageGraphic return gc } -func (gc *ImageGraphicContext) GetDPI() int { +func (gc *GraphicContext) GetDPI() int { return gc.DPI } -func (gc *ImageGraphicContext) Clear() { +func (gc *GraphicContext) Clear() { width, height := gc.img.Bounds().Dx(), gc.img.Bounds().Dy() gc.ClearRect(0, 0, width, height) } -func (gc *ImageGraphicContext) ClearRect(x1, y1, x2, y2 int) { +func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) { imageColor := image.NewUniform(gc.Current.FillColor) draw.Draw(gc.img, image.Rect(x1, y1, x2, y2), imageColor, image.ZP, draw.Over) } -func (gc *ImageGraphicContext) DrawImage(img image.Image) { +func (gc *GraphicContext) DrawImage(img image.Image) { DrawImage(img, gc.img, gc.Current.Tr, draw.Over, BilinearFilter) } -func (gc *ImageGraphicContext) FillString(text string) (cursor float64) { +func (gc *GraphicContext) FillString(text string) (cursor float64) { return gc.FillStringAt(text, 0, 0) } -func (gc *ImageGraphicContext) FillStringAt(text string, x, y float64) (cursor float64) { +func (gc *GraphicContext) FillStringAt(text string, x, y float64) (cursor float64) { width := gc.CreateStringPath(text, x, y) gc.Fill() return width } -func (gc *ImageGraphicContext) StrokeString(text string) (cursor float64) { +func (gc *GraphicContext) StrokeString(text string) (cursor float64) { return gc.StrokeStringAt(text, 0, 0) } -func (gc *ImageGraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) { +func (gc *GraphicContext) StrokeStringAt(text string, x, y float64) (cursor float64) { width := gc.CreateStringPath(text, x, y) gc.Stroke() return width } -func (gc *ImageGraphicContext) loadCurrentFont() (*truetype.Font, error) { - font := GetFont(gc.Current.FontData) +func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) { + font := draw2d.GetFont(gc.Current.FontData) if font == nil { - font = GetFont(defaultFontData) + font = draw2d.GetFont(draw2dbase.DefaultFontData) } if font == nil { return nil, errors.New("No font set, and no default font available.") @@ -129,7 +126,7 @@ func pointToF64Point(p truetype.Point) (x, y float64) { } // drawContour draws the given closed contour at the given sub-pixel offset. -func (gc *ImageGraphicContext) drawContour(ps []truetype.Point, dx, dy float64) { +func (gc *GraphicContext) drawContour(ps []truetype.Point, dx, dy float64) { if len(ps) == 0 { return } @@ -164,8 +161,8 @@ func (gc *ImageGraphicContext) drawContour(ps []truetype.Point, dx, dy float64) } } -func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { - if err := gc.glyphBuf.Load(gc.Current.font, gc.Current.scale, glyph, truetype.NoHinting); err != nil { +func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { + if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, glyph, truetype.NoHinting); err != nil { return err } e0 := 0 @@ -182,7 +179,7 @@ func (gc *ImageGraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) e // above and to the right of the point, but some may be below or to the left. // For example, drawing a string that starts with a 'J' in an italic font may // affect pixels below and left of the point. -func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64 { +func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 { font, err := gc.loadCurrentFont() if err != nil { log.Println(err) @@ -193,14 +190,14 @@ func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64 for _, rune := range s { index := font.Index(rune) if hasPrev { - x += fUnitsToFloat64(font.Kerning(gc.Current.scale, prev, index)) + x += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index)) } err := gc.drawGlyph(index, x, y) if err != nil { log.Println(err) return startx - x } - x += fUnitsToFloat64(font.HMetric(gc.Current.scale, index).AdvanceWidth) + x += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth) prev, hasPrev = index, true } return x - startx @@ -210,7 +207,7 @@ func (gc *ImageGraphicContext) CreateStringPath(s string, x, y float64) float64 // The the left edge of the em square of the first character of s // and the baseline intersect at 0, 0 in the returned coordinates. // Therefore the top and left coordinates may well be negative. -func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { +func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { font, err := gc.loadCurrentFont() if err != nil { log.Println(err) @@ -222,9 +219,9 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott for _, rune := range s { index := font.Index(rune) if hasPrev { - cursor += fUnitsToFloat64(font.Kerning(gc.Current.scale, prev, index)) + cursor += fUnitsToFloat64(font.Kerning(gc.Current.Scale, prev, index)) } - if err := gc.glyphBuf.Load(gc.Current.font, gc.Current.scale, index, truetype.NoHinting); err != nil { + if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, index, truetype.NoHinting); err != nil { log.Println(err) return 0, 0, 0, 0 } @@ -239,7 +236,7 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott right = math.Max(right, x+cursor) } } - cursor += fUnitsToFloat64(font.HMetric(gc.Current.scale, index).AdvanceWidth) + cursor += fUnitsToFloat64(font.HMetric(gc.Current.Scale, index).AdvanceWidth) prev, hasPrev = index, true } return left, top, right, bottom @@ -247,44 +244,44 @@ func (gc *ImageGraphicContext) GetStringBounds(s string) (left, top, right, bott // recalc recalculates scale and bounds values from the font size, screen // resolution and font metrics, and invalidates the glyph cache. -func (gc *ImageGraphicContext) recalc() { - gc.Current.scale = int32(gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)) +func (gc *GraphicContext) recalc() { + gc.Current.Scale = int32(gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)) } // SetDPI sets the screen resolution in dots per inch. -func (gc *ImageGraphicContext) SetDPI(dpi int) { +func (gc *GraphicContext) SetDPI(dpi int) { gc.DPI = dpi gc.recalc() } // SetFont sets the font used to draw text. -func (gc *ImageGraphicContext) SetFont(font *truetype.Font) { - gc.Current.font = font +func (gc *GraphicContext) SetFont(font *truetype.Font) { + gc.Current.Font = font } // SetFontSize sets the font size in points (as in ``a 12 point font''). -func (gc *ImageGraphicContext) SetFontSize(fontSize float64) { +func (gc *GraphicContext) SetFontSize(fontSize float64) { gc.Current.FontSize = fontSize gc.recalc() } -func (gc *ImageGraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { +func (gc *GraphicContext) paint(rasterizer *raster.Rasterizer, color color.Color) { gc.painter.SetColor(color) rasterizer.Rasterize(gc.painter) rasterizer.Clear() gc.Current.Path.Clear() } -func (gc *ImageGraphicContext) Stroke(paths ...*path.Path) { +func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner path.LineBuilder + var liner draw2d.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { - liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) + liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } @@ -295,12 +292,12 @@ func (gc *ImageGraphicContext) Stroke(paths ...*path.Path) { gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) } -func (gc *ImageGraphicContext) Fill(paths ...*path.Path) { +func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) - gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() + gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) /**** first method ****/ - flattener := NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) + flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} for _, p := range paths { p.Flatten(flattener, gc.Current.Tr.GetScale()) } @@ -308,24 +305,24 @@ func (gc *ImageGraphicContext) Fill(paths ...*path.Path) { gc.paint(gc.fillRasterizer, gc.Current.FillColor) } -func (gc *ImageGraphicContext) FillStroke(paths ...*path.Path) { +func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) - gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule.UseNonZeroWinding() + gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) gc.strokeRasterizer.UseNonZeroWinding = true - flattener := NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.fillRasterizer)) + flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} - stroker := path.NewLineStroker(gc.Current.Cap, gc.Current.Join, NewVertexMatrixTransform(gc.Current.Tr, path.NewFtLineBuilder(gc.strokeRasterizer))) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner path.LineBuilder + var liner draw2d.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { - liner = path.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) + liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } - demux := path.NewLineBuilders(flattener, liner) + demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}} for _, p := range paths { p.Flatten(demux, gc.Current.Tr.GetScale()) } @@ -336,11 +333,11 @@ func (gc *ImageGraphicContext) FillStroke(paths ...*path.Path) { gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) } -func (f FillRule) UseNonZeroWinding() bool { +func useNonZeroWinding(f draw2d.FillRule) bool { switch f { - case FillRuleEvenOdd: + case draw2d.FillRuleEvenOdd: return false - case FillRuleWinding: + case draw2d.FillRuleWinding: return true } return false diff --git a/rgba_interpolation.go b/draw2dimg/rgba_interpolation.go similarity index 96% rename from rgba_interpolation.go rename to draw2dimg/rgba_interpolation.go index 92534e7..fc59581 100644 --- a/rgba_interpolation.go +++ b/draw2dimg/rgba_interpolation.go @@ -2,9 +2,10 @@ // created: 21/11/2010 by Laurent Le Goff // see http://pippin.gimp.org/image_processing/chap_resampling.html -package draw2d +package draw2dimg import ( + "github.com/llgcode/draw2d" "image" "image/color" "image/draw" @@ -17,6 +18,7 @@ const ( LinearFilter ImageFilter = iota BilinearFilter BicubicFilter + M = 1<<16 - 1 ) //see http://pippin.gimp.org/image_processing/chap_resampling.html @@ -103,7 +105,7 @@ func cubic(offset, v0, v1, v2, v3 float64) uint32 { (-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0) } -func DrawImage(src image.Image, dest draw.Image, tr MatrixTransform, op draw.Op, filter ImageFilter) { +func DrawImage(src image.Image, dest draw.Image, tr draw2d.MatrixTransform, op draw.Op, filter ImageFilter) { bounds := src.Bounds() x0, y0, x1, y1 := float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y) tr.TransformRectangle(&x0, &y0, &x1, &y1) diff --git a/path/drawing_kit.go b/drawing_kit.go similarity index 98% rename from path/drawing_kit.go rename to drawing_kit.go index 89fc3c7..fe496af 100644 --- a/path/drawing_kit.go +++ b/drawing_kit.go @@ -1,7 +1,7 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -package path +package draw2d import ( "math" diff --git a/flattener.go b/flattener.go new file mode 100644 index 0000000..d948ae5 --- /dev/null +++ b/flattener.go @@ -0,0 +1,59 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 06/12/2010 by Laurent Le Goff + +package draw2d + +// Flattener receive segment definition +type Flattener interface { + // MoveTo Start a New line from the point (x, y) + MoveTo(x, y float64) + // LineTo Draw a line from the current position to the point (x, y) + LineTo(x, y float64) + // LineJoin add the most recent starting point to close the path to create a polygon + LineJoin() + // Close add the most recent starting point to close the path to create a polygon + Close() + // End mark the current line as finished so we can draw caps + End() +} + +type SegmentedPath struct { + Points []float64 +} + +func (p *SegmentedPath) MoveTo(x, y float64) { + p.Points = append(p.Points, x, y) + // TODO need to mark this point as moveto +} + +func (p *SegmentedPath) LineTo(x, y float64) { + p.Points = append(p.Points, x, y) +} + +func (p *SegmentedPath) LineJoin() { + // TODO need to mark the current point as linejoin +} + +func (p *SegmentedPath) Close() { + // TODO Close +} + +func (p *SegmentedPath) End() { + // Nothing to do +} + +type LineCap int + +const ( + RoundCap LineCap = iota + ButtCap + SquareCap +) + +type LineJoin int + +const ( + BevelJoin LineJoin = iota + RoundJoin + MiterJoin +) diff --git a/gc.go b/gc.go index 6b91f9a..0388708 100644 --- a/gc.go +++ b/gc.go @@ -4,7 +4,6 @@ package draw2d import ( - "github.com/llgcode/draw2d/path" "image" "image/color" ) @@ -17,7 +16,7 @@ const ( ) type GraphicContext interface { - path.PathBuilder + PathBuilder // Create a new path BeginPath() GetMatrixTransform() MatrixTransform @@ -30,8 +29,8 @@ type GraphicContext interface { SetFillColor(c color.Color) SetFillRule(f FillRule) SetLineWidth(lineWidth float64) - SetLineCap(cap path.Cap) - SetLineJoin(join path.Join) + SetLineCap(cap LineCap) + SetLineJoin(join LineJoin) SetLineDash(dash []float64, dashOffset float64) SetFontSize(fontSize float64) GetFontSize() float64 @@ -50,7 +49,7 @@ type GraphicContext interface { FillStringAt(text string, x, y float64) (cursor float64) StrokeString(text string) (cursor float64) StrokeStringAt(text string, x, y float64) (cursor float64) - Stroke(paths ...*path.Path) - Fill(paths ...*path.Path) - FillStroke(paths ...*path.Path) + Stroke(paths ...*Path) + Fill(paths ...*Path) + FillStroke(paths ...*Path) } diff --git a/math.go b/math.go deleted file mode 100644 index 8e15c14..0000000 --- a/math.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -func minMax(x, y float64) (min, max float64) { - if x > y { - return y, x - } - return x, y -} diff --git a/path/path.go b/path.go similarity index 89% rename from path/path.go rename to path.go index 203837a..7a5b38e 100644 --- a/path/path.go +++ b/path.go @@ -2,11 +2,10 @@ // created: 21/11/2010 by Laurent Le Goff // Package path implements function to build path -package path +package draw2d import ( "fmt" - "github.com/llgcode/draw2d/curve" "math" ) @@ -177,7 +176,7 @@ func (p *Path) String() string { } // Flatten convert curves in straight segments keeping join segements -func (path *Path) Flatten(liner LineBuilder, scale float64) { +func (path *Path) Flatten(flattener Flattener, scale float64) { // First Point var startX, startY float64 = 0, 0 // Current Point @@ -189,33 +188,33 @@ func (path *Path) Flatten(liner LineBuilder, scale float64) { x, y = path.Points[i], path.Points[i+1] startX, startY = x, y if i != 0 { - liner.End() + flattener.End() } - liner.MoveTo(x, y) + flattener.MoveTo(x, y) i += 2 case LineToCmp: x, y = path.Points[i], path.Points[i+1] - liner.LineTo(x, y) - liner.LineJoin() + flattener.LineTo(x, y) + flattener.LineJoin() i += 2 case QuadCurveToCmp: - curve.TraceQuad(liner, path.Points[i-2:], 0.5) + TraceQuad(flattener, path.Points[i-2:], 0.5) x, y = path.Points[i+2], path.Points[i+3] - liner.LineTo(x, y) + flattener.LineTo(x, y) i += 4 case CubicCurveToCmp: - curve.TraceCubic(liner, path.Points[i-2:], 0.5) + TraceCubic(flattener, path.Points[i-2:], 0.5) x, y = path.Points[i+4], path.Points[i+5] - liner.LineTo(x, y) + flattener.LineTo(x, y) i += 6 case ArcToCmp: - x, y = curve.TraceArc(liner, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) - liner.LineTo(x, y) + x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) + flattener.LineTo(x, y) i += 6 case CloseCmp: - liner.LineTo(startX, startY) - liner.Close() + flattener.LineTo(startX, startY) + flattener.Close() } } - liner.End() + flattener.End() } diff --git a/path/flattening.go b/path/flattening.go deleted file mode 100644 index 9f18061..0000000 --- a/path/flattening.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 06/12/2010 by Laurent Le Goff - -package path - -// LineBuilder defines drawing line methods -type LineBuilder interface { - // MoveTo Start a New line from the point (x, y) - MoveTo(x, y float64) - // LineTo Draw a line from the current position to the point (x, y) - LineTo(x, y float64) - // LineJoin add the most recent starting point to close the path to create a polygon - LineJoin() - // Close add the most recent starting point to close the path to create a polygon - Close() - // End mark the current line as finished so we can draw caps - End() -} - -type LineBuilders struct { - builders []LineBuilder -} - -func NewLineBuilders(builders ...LineBuilder) *LineBuilders { - return &LineBuilders{builders} -} - -func (dc *LineBuilders) MoveTo(x, y float64) { - for _, converter := range dc.builders { - converter.MoveTo(x, y) - } -} - -func (dc *LineBuilders) LineTo(x, y float64) { - for _, converter := range dc.builders { - converter.LineTo(x, y) - } -} - -func (dc *LineBuilders) LineJoin() { - for _, converter := range dc.builders { - converter.LineJoin() - } -} - -func (dc *LineBuilders) Close() { - for _, converter := range dc.builders { - converter.Close() - } -} - -func (dc *LineBuilders) End() { - for _, converter := range dc.builders { - converter.End() - } -} diff --git a/path/ftpath.go b/path/ftpath.go deleted file mode 100644 index d2b40d5..0000000 --- a/path/ftpath.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 13/12/2010 by Laurent Le Goff - -package path - -import ( - "code.google.com/p/freetype-go/freetype/raster" - "github.com/llgcode/draw2d/curve" -) - -type FtLineBuilder struct { - adder raster.Adder -} - -func NewFtLineBuilder(adder raster.Adder) *FtLineBuilder { - return &FtLineBuilder{adder} -} - -func (FtLineBuilder *FtLineBuilder) MoveTo(x, y float64) { - FtLineBuilder.adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) -} - -func (FtLineBuilder *FtLineBuilder) LineTo(x, y float64) { - FtLineBuilder.adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) -} - -func (FtLineBuilder *FtLineBuilder) LineJoin() { -} - -func (FtLineBuilder *FtLineBuilder) Close() { -} - -func (FtLineBuilder *FtLineBuilder) End() { -} - -type PathAdder struct { - adder raster.Adder - firstPoint raster.Point - ApproximationScale float64 -} - -func NewPathAdder(adder raster.Adder) *PathAdder { - return &PathAdder{adder, raster.Point{0, 0}, 1} -} - -func (pathAdder *PathAdder) Convert(paths ...*Path) { - for _, apath := range paths { - j := 0 - for _, cmd := range apath.Components { - switch cmd { - case MoveToCmp: - pathAdder.firstPoint = raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)} - pathAdder.adder.Start(pathAdder.firstPoint) - j += 2 - case LineToCmp: - pathAdder.adder.Add1(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}) - j += 2 - case QuadCurveToCmp: - pathAdder.adder.Add2(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)}) - j += 4 - case CubicCurveToCmp: - pathAdder.adder.Add3(raster.Point{raster.Fix32(apath.Points[j] * 256), raster.Fix32(apath.Points[j+1] * 256)}, raster.Point{raster.Fix32(apath.Points[j+2] * 256), raster.Fix32(apath.Points[j+3] * 256)}, raster.Point{raster.Fix32(apath.Points[j+4] * 256), raster.Fix32(apath.Points[j+5] * 256)}) - j += 6 - case ArcToCmp: - lastPoint := curve.TraceArcFt(pathAdder.adder, apath.Points[j], apath.Points[j+1], apath.Points[j+2], apath.Points[j+3], apath.Points[j+4], apath.Points[j+5], pathAdder.ApproximationScale) - pathAdder.adder.Add1(lastPoint) - j += 6 - case CloseCmp: - pathAdder.adder.Add1(pathAdder.firstPoint) - } - } - } -} diff --git a/path/utils.go b/path/utils.go deleted file mode 100644 index 68dbc35..0000000 --- a/path/utils.go +++ /dev/null @@ -1,13 +0,0 @@ -package path - -import ( - "math" -) - -func distance(x1, y1, x2, y2 float64) float64 { - return vectorDistance(x2-x1, y2-y1) -} - -func vectorDistance(dx, dy float64) float64 { - return float64(math.Sqrt(dx*dx + dy*dy)) -} diff --git a/transform.go b/transform.go index e3a8a86..e1d82fe 100644 --- a/transform.go +++ b/transform.go @@ -7,7 +7,6 @@ import ( "math" "code.google.com/p/freetype-go/freetype/raster" - "github.com/llgcode/draw2d/path" ) type MatrixTransform [6]float64 @@ -38,6 +37,13 @@ func (tr MatrixTransform) TransformArray(points []float64) { } } +func minMax(x, y float64) (min, max float64) { + if x > y { + return y, x + } + return x, y +} + func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 *float64) { x1 := *x2 y1 := *y0 @@ -252,38 +258,34 @@ func fequals(float1, float2 float64) bool { return math.Abs(float1-float2) <= epsilon } -// this VertexConverter apply the Matrix transformation tr -type VertexMatrixTransform struct { - tr MatrixTransform - Next path.LineBuilder +// Transformer apply the Matrix transformation tr +type Transformer struct { + Tr MatrixTransform + Flattener Flattener } -func NewVertexMatrixTransform(tr MatrixTransform, converter path.LineBuilder) *VertexMatrixTransform { - return &VertexMatrixTransform{tr, converter} +func (t Transformer) MoveTo(x, y float64) { + u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] + v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] + t.Flattener.MoveTo(u, v) } -func (vmt *VertexMatrixTransform) MoveTo(x, y float64) { - u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] - v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5] - vmt.Next.MoveTo(u, v) +func (t Transformer) LineTo(x, y float64) { + u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] + v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] + t.Flattener.LineTo(u, v) } -func (vmt *VertexMatrixTransform) LineTo(x, y float64) { - u := x*vmt.tr[0] + y*vmt.tr[2] + vmt.tr[4] - v := x*vmt.tr[1] + y*vmt.tr[3] + vmt.tr[5] - vmt.Next.LineTo(u, v) +func (t Transformer) LineJoin() { + t.Flattener.LineJoin() } -func (vmt *VertexMatrixTransform) LineJoin() { - vmt.Next.LineJoin() +func (t Transformer) Close() { + t.Flattener.Close() } -func (vmt *VertexMatrixTransform) Close() { - vmt.Next.Close() -} - -func (vmt *VertexMatrixTransform) End() { - vmt.Next.End() +func (t Transformer) End() { + t.Flattener.End() } // this adder apply a Matrix transformation to points @@ -292,10 +294,6 @@ type MatrixTransformAdder struct { next raster.Adder } -func NewMatrixTransformAdder(tr MatrixTransform, adder raster.Adder) *MatrixTransformAdder { - return &MatrixTransformAdder{tr, adder} -} - // Start starts a new curve at the given point. func (mta MatrixTransformAdder) Start(a raster.Point) { mta.tr.TransformRasterPoint(&a) From 511954196b750963b374f3ab779a92266c622689 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 16:19:49 +0200 Subject: [PATCH 19/39] clean Transform --- draw2dimg/rgba_interpolation.go | 5 +- transform.go | 112 +++++++++++--------------------- 2 files changed, 39 insertions(+), 78 deletions(-) diff --git a/draw2dimg/rgba_interpolation.go b/draw2dimg/rgba_interpolation.go index fc59581..3b5fd66 100644 --- a/draw2dimg/rgba_interpolation.go +++ b/draw2dimg/rgba_interpolation.go @@ -107,8 +107,7 @@ func cubic(offset, v0, v1, v2, v3 float64) uint32 { func DrawImage(src image.Image, dest draw.Image, tr draw2d.MatrixTransform, op draw.Op, filter ImageFilter) { bounds := src.Bounds() - x0, y0, x1, y1 := float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y) - tr.TransformRectangle(&x0, &y0, &x1, &y1) + x0, y0, x1, y1 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y)) var x, y, u, v float64 var c1, c2, cr color.Color var r, g, b, a, ia, r1, g1, b1, a1, r2, g2, b2, a2 uint32 @@ -117,7 +116,7 @@ func DrawImage(src image.Image, dest draw.Image, tr draw2d.MatrixTransform, op d for y = y0; y < y1; y++ { u = x v = y - tr.InverseTransform(&u, &v) + u, v = tr.InverseTransformPoint(u, v) if bounds.Min.X <= int(u) && bounds.Max.X > int(u) && bounds.Min.Y <= int(v) && bounds.Max.Y > int(v) { c1 = dest.At(int(x), int(y)) switch filter { diff --git a/transform.go b/transform.go index e1d82fe..ef2235c 100644 --- a/transform.go +++ b/transform.go @@ -5,8 +5,6 @@ package draw2d import ( "math" - - "code.google.com/p/freetype-go/freetype/raster" ) type MatrixTransform [6]float64 @@ -19,16 +17,8 @@ func (tr MatrixTransform) Determinant() float64 { return tr[0]*tr[3] - tr[1]*tr[2] } -func (tr MatrixTransform) Transform(points ...*float64) { - for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { - x := *points[i] - y := *points[j] - *points[i] = x*tr[0] + y*tr[2] + tr[4] - *points[j] = x*tr[1] + y*tr[3] + tr[5] - } -} - -func (tr MatrixTransform) TransformArray(points []float64) { +// Transform apply the Affine Matrix to points. It modify the points passed in parameter. +func (tr MatrixTransform) Transform(points []float64) { for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { x := points[i] y := points[j] @@ -37,6 +27,12 @@ func (tr MatrixTransform) TransformArray(points []float64) { } } +func (tr MatrixTransform) TransformPoint(x, y float64) (xres, yres float64) { + xres = x*tr[0] + y*tr[2] + tr[4] + yres = x*tr[1] + y*tr[3] + tr[5] + return xres, yres +} + func minMax(x, y float64) (min, max float64) { if x > y { return y, x @@ -44,50 +40,46 @@ func minMax(x, y float64) (min, max float64) { return x, y } -func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 *float64) { - x1 := *x2 - y1 := *y0 - x3 := *x0 - y3 := *y2 - tr.Transform(x0, y0, &x1, &y1, x2, y2, &x3, &y3) - *x0, x1 = minMax(*x0, x1) - *x2, x3 = minMax(*x2, x3) - *y0, y1 = minMax(*y0, y1) - *y2, y3 = minMax(*y2, y3) +func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) { + points := []float64{x0, y0, x2, y0, x2, y2, x0, y2} + tr.Transform(points) + points[0], points[2] = minMax(points[0], points[2]) + points[4], points[6] = minMax(points[4], points[6]) + points[1], points[3] = minMax(points[1], points[3]) + points[5], points[7] = minMax(points[5], points[7]) - *x0 = math.Min(*x0, *x2) - *y0 = math.Min(*y0, *y2) - *x2 = math.Max(x1, x3) - *y2 = math.Max(y1, y3) + nx0 = math.Min(points[0], points[4]) + ny0 = math.Min(points[1], points[5]) + nx2 = math.Max(points[2], points[6]) + ny2 = math.Max(points[3], points[7]) + return nx0, ny0, nx2, ny2 } -func (tr MatrixTransform) TransformRasterPoint(points ...*raster.Point) { - for _, point := range points { - x := float64(point.X) / 256 - y := float64(point.Y) / 256 - point.X = raster.Fix32((x*tr[0] + y*tr[2] + tr[4]) * 256) - point.Y = raster.Fix32((x*tr[1] + y*tr[3] + tr[5]) * 256) - } -} - -func (tr MatrixTransform) InverseTransform(points ...*float64) { +func (tr MatrixTransform) InverseTransform(points []float64) { d := tr.Determinant() // matrix determinant for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { - x := *points[i] - y := *points[j] - *points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d - *points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d + x := points[i] + y := points[j] + points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d + points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d } } +func (tr MatrixTransform) InverseTransformPoint(x, y float64) (xres, yres float64) { + d := tr.Determinant() // matrix determinant + xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d + yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d + return xres, yres +} + // ******************** Vector transformations ******************** -func (tr MatrixTransform) VectorTransform(points ...*float64) { +func (tr MatrixTransform) VectorTransform(points []float64) { for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { - x := *points[i] - y := *points[j] - *points[i] = x*tr[0] + y*tr[2] - *points[j] = x*tr[1] + y*tr[3] + x := points[i] + y := points[j] + points[i] = x*tr[0] + y*tr[2] + points[j] = x*tr[1] + y*tr[3] } } @@ -287,33 +279,3 @@ func (t Transformer) Close() { func (t Transformer) End() { t.Flattener.End() } - -// this adder apply a Matrix transformation to points -type MatrixTransformAdder struct { - tr MatrixTransform - next raster.Adder -} - -// Start starts a new curve at the given point. -func (mta MatrixTransformAdder) Start(a raster.Point) { - mta.tr.TransformRasterPoint(&a) - mta.next.Start(a) -} - -// Add1 adds a linear segment to the current curve. -func (mta MatrixTransformAdder) Add1(b raster.Point) { - mta.tr.TransformRasterPoint(&b) - mta.next.Add1(b) -} - -// Add2 adds a quadratic segment to the current curve. -func (mta MatrixTransformAdder) Add2(b, c raster.Point) { - mta.tr.TransformRasterPoint(&b, &c) - mta.next.Add2(b, c) -} - -// Add3 adds a cubic segment to the current curve. -func (mta MatrixTransformAdder) Add3(b, c, d raster.Point) { - mta.tr.TransformRasterPoint(&b, &c, &d) - mta.next.Add3(b, c, d) -} From 409365e40f6be3a9e6c6bc0b28809a0195177802 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 16:37:07 +0200 Subject: [PATCH 20/39] transform clean up --- transform.go | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/transform.go b/transform.go index ef2235c..fb70941 100644 --- a/transform.go +++ b/transform.go @@ -13,11 +13,12 @@ const ( epsilon = 1e-6 ) +// Determinant compute the determinant of the matrix func (tr MatrixTransform) Determinant() float64 { return tr[0]*tr[3] - tr[1]*tr[2] } -// Transform apply the Affine Matrix to points. It modify the points passed in parameter. +// Transform apply the transformation matrix to points. It modify the points passed in parameter. func (tr MatrixTransform) Transform(points []float64) { for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { x := points[i] @@ -27,6 +28,7 @@ func (tr MatrixTransform) Transform(points []float64) { } } +// TransformPoint apply the transformation matrix to point. It returns the point the transformed point. func (tr MatrixTransform) TransformPoint(x, y float64) (xres, yres float64) { xres = x*tr[0] + y*tr[2] + tr[4] yres = x*tr[1] + y*tr[3] + tr[5] @@ -40,6 +42,7 @@ func minMax(x, y float64) (min, max float64) { return x, y } +// Transform apply the transformation matrix to the rectangle represented by the min and the max point of the rectangle func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) { points := []float64{x0, y0, x2, y0, x2, y2, x0, y2} tr.Transform(points) @@ -55,6 +58,7 @@ func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, return nx0, ny0, nx2, ny2 } +// InverseTransform apply the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle func (tr MatrixTransform) InverseTransform(points []float64) { d := tr.Determinant() // matrix determinant for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { @@ -65,6 +69,7 @@ func (tr MatrixTransform) InverseTransform(points []float64) { } } +// InverseTransformPoint apply the transformation inverse matrix to point. It returns the point the transformed point. func (tr MatrixTransform) InverseTransformPoint(x, y float64) (xres, yres float64) { d := tr.Determinant() // matrix determinant xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d @@ -72,8 +77,8 @@ func (tr MatrixTransform) InverseTransformPoint(x, y float64) (xres, yres float6 return xres, yres } -// ******************** Vector transformations ******************** - +// VectorTransform apply the transformation matrix to points without using the translation parameter of the affine matrix. +// It modify the points passed in parameter. func (tr MatrixTransform) VectorTransform(points []float64) { for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { x := points[i] @@ -83,41 +88,30 @@ func (tr MatrixTransform) VectorTransform(points []float64) { } } -// ******************** Transformations creation ******************** - -/** Creates an identity transformation. */ +// NewIdentityMatrix creates an identity transformation matrix. func NewIdentityMatrix() MatrixTransform { return [6]float64{1, 0, 0, 1, 0, 0} } -/** - * Creates a transformation with a translation, that, - * transform point1 into point2. - */ +// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter func NewTranslationMatrix(tx, ty float64) MatrixTransform { return [6]float64{1, 0, 0, 1, tx, ty} } -/** - * Creates a transformation with a sx, sy scale factor - */ +// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor func NewScaleMatrix(sx, sy float64) MatrixTransform { return [6]float64{sx, 0, 0, sy, 0, 0} } -/** - * Creates a rotation transformation. - */ +// NewRotationMatrix creates a rotation transformation matrix. angle is in radian func NewRotationMatrix(angle float64) MatrixTransform { c := math.Cos(angle) s := math.Sin(angle) return [6]float64{c, s, -s, c, 0, 0} } -/** - * Creates a transformation, combining a scale and a translation, that transform rectangle1 into rectangle2. - */ -func NewMatrixTransform(rectangle1, rectangle2 [4]float64) MatrixTransform { +// NewMatrixTransform creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2. +func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) MatrixTransform { xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0]) yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1]) xOffset := rectangle2[0] - (rectangle1[0] * xScale) @@ -125,12 +119,8 @@ func NewMatrixTransform(rectangle1, rectangle2 [4]float64) MatrixTransform { return [6]float64{xScale, 0, 0, yScale, xOffset, yOffset} } -// ******************** Transformations operations ******************** - -/** - * Returns a transformation that is the inverse of the given transformation. - */ -func (tr MatrixTransform) GetInverseTransformation() MatrixTransform { +// Inverse returns a matrix that is the inverse of the given matrix. +func (tr MatrixTransform) Inverse() MatrixTransform { d := tr.Determinant() // matrix determinant return [6]float64{ tr[3] / d, @@ -141,6 +131,7 @@ func (tr MatrixTransform) GetInverseTransformation() MatrixTransform { (tr[1]*tr[4] - tr[0]*tr[5]) / d} } +// Multiply Compose Matrix tr1 with tr2 returns the resulting matrix func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform { return [6]float64{ tr1[0]*tr2[0] + tr1[1]*tr2[2], @@ -151,6 +142,7 @@ func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform { tr1[5]*tr2[3] + tr1[4]*tr2[1] + tr2[5]} } +// Scale add a scale to the matrix func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform { tr[0] = sx * tr[0] tr[1] = sx * tr[1] @@ -159,12 +151,14 @@ func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform { return tr } +// Translate add a translation to the matrix func (tr *MatrixTransform) Translate(tx, ty float64) *MatrixTransform { tr[4] = tx*tr[0] + ty*tr[2] + tr[4] tr[5] = ty*tr[3] + tx*tr[1] + tr[5] return tr } +// Rotate add a rotation to the matrix. angle is in radian func (tr *MatrixTransform) Rotate(angle float64) *MatrixTransform { c := math.Cos(angle) s := math.Sin(angle) @@ -179,14 +173,17 @@ func (tr *MatrixTransform) Rotate(angle float64) *MatrixTransform { return tr } +// GetTranslation func (tr MatrixTransform) GetTranslation() (x, y float64) { return tr[4], tr[5] } +// GetScaling func (tr MatrixTransform) GetScaling() (x, y float64) { return tr[0], tr[3] } +// GetScale computes the scale of the matrix func (tr MatrixTransform) GetScale() float64 { x := 0.707106781*tr[0] + 0.707106781*tr[1] y := 0.707106781*tr[2] + 0.707106781*tr[3] From ee83fedb1068227b079ccfa8745f5dd23f0a2a57 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 16:53:36 +0200 Subject: [PATCH 21/39] Document path --- path.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/path.go b/path.go index 7a5b38e..6ff5ccd 100644 --- a/path.go +++ b/path.go @@ -11,33 +11,33 @@ import ( // PathBuilder define method that create path type PathBuilder interface { - // Return the current point of the current path + // LastPoint returns the current point of the current path LastPoint() (x, y float64) - // MoveTo start a new path at (x, y) position + // MoveTo starts a new path at (x, y) position MoveTo(x, y float64) - // LineTo add a line to the current path + // LineTo adds a line to the current path LineTo(x, y float64) - // QuadCurveTo add a quadratic curve to the current path + // QuadCurveTo adds a quadratic bezier curve to the current path QuadCurveTo(cx, cy, x, y float64) - // CubicCurveTo add a cubic bezier curve to the current path + // CubicCurveTo adds a cubic bezier curve to the current path CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) - // ArcTo add an arc to the path + // ArcTo adds an arc to the path ArcTo(cx, cy, rx, ry, startAngle, angle float64) - // Close the current path + // Close closes the current path Close() } -// Component represent components of a path -type Component int +// PathCmp represent components of a path +type PathCmp int const ( - MoveToCmp Component = iota + MoveToCmp PathCmp = iota LineToCmp QuadCurveToCmp CubicCurveToCmp @@ -45,9 +45,9 @@ const ( CloseCmp ) -// Type Path store path +// Path stores points type Path struct { - Components []Component + Components []PathCmp Points []float64 x, y float64 } @@ -58,7 +58,7 @@ func (p *Path) Clear() { return } -func (p *Path) appendToPath(cmd Component, points ...float64) { +func (p *Path) appendToPath(cmd PathCmp, points ...float64) { p.Components = append(p.Components, cmd) p.Points = append(p.Points, points...) } @@ -66,7 +66,7 @@ func (p *Path) appendToPath(cmd Component, points ...float64) { // Copy make a clone of the current path and return it func (src *Path) Copy() (dest *Path) { dest = new(Path) - dest.Components = make([]Component, len(src.Components)) + dest.Components = make([]PathCmp, len(src.Components)) copy(dest.Components, src.Components) dest.Points = make([]float64, len(src.Points)) copy(dest.Points, src.Points) @@ -175,7 +175,7 @@ func (p *Path) String() string { return s } -// Flatten convert curves in straight segments keeping join segements +// Flatten convert curves into straight segments keeping join segments info func (path *Path) Flatten(flattener Flattener, scale float64) { // First Point var startX, startY float64 = 0, 0 From 82ef300f1d8f4b7fdb6f47ea457f63e30e4b6336 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 17:16:15 +0200 Subject: [PATCH 22/39] move flattening code in draw2dbase --- draw2dbase/curve.go | 161 ++++++++++++++++++++++++++++++++++ draw2dbase/curve_test.go | 136 ++++++++++++++++++++++++++++ draw2dbase/dasher.go | 10 +-- draw2dbase/demux_flattener.go | 6 +- draw2dbase/flattener.go | 121 +++++++++++++++++++++++++ draw2dbase/stroker.go | 16 ++-- draw2dgl/gc.go | 20 ++--- draw2dimg/ftgc.go | 20 ++--- drawing_kit.go | 8 +- flattener.go | 59 ------------- gc.go | 16 ++++ path.go | 48 +--------- transform.go | 30 ------- 13 files changed, 472 insertions(+), 179 deletions(-) create mode 100644 draw2dbase/curve.go create mode 100644 draw2dbase/curve_test.go create mode 100644 draw2dbase/flattener.go delete mode 100644 flattener.go diff --git a/draw2dbase/curve.go b/draw2dbase/curve.go new file mode 100644 index 0000000..2b10ba7 --- /dev/null +++ b/draw2dbase/curve.go @@ -0,0 +1,161 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 17/05/2011 by Laurent Le Goff + +package draw2dbase + +import ( + "math" +) + +const ( + CurveRecursionLimit = 32 +) + +// Cubic +// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64 + +// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves. +// c1 and c2 parameters are the resulting curves +func SubdivideCubic(c, c1, c2 []float64) { + // First point of c is the first point of c1 + c1[0], c1[1] = c[0], c[1] + // Last point of c is the last point of c2 + c2[6], c2[7] = c[6], c[7] + + // Subdivide segment using midpoints + c1[2] = (c[0] + c[2]) / 2 + c1[3] = (c[1] + c[3]) / 2 + + midX := (c[2] + c[4]) / 2 + midY := (c[3] + c[5]) / 2 + + c2[4] = (c[4] + c[6]) / 2 + c2[5] = (c[5] + c[7]) / 2 + + c1[4] = (c1[2] + midX) / 2 + c1[5] = (c1[3] + midY) / 2 + + c2[2] = (midX + c2[4]) / 2 + c2[3] = (midY + c2[5]) / 2 + + c1[6] = (c1[4] + c2[2]) / 2 + c1[7] = (c1[5] + c2[3]) / 2 + + // Last Point of c1 is equal to the first point of c2 + c2[0], c2[1] = c1[6], c1[7] +} + +// TraceCubic generate lines subdividing the cubic curve using a Flattener +// flattening_threshold helps determines the flattening expectation of the curve +func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) { + // Allocation curves + var curves [CurveRecursionLimit * 8]float64 + copy(curves[0:8], cubic[0:8]) + i := 0 + + // current curve + var c []float64 + + var dx, dy, d2, d3 float64 + + for i >= 0 { + c = curves[i*8:] + dx = c[6] - c[0] + dy = c[7] - c[1] + + d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx) + d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx) + + // if it's flat then trace a line + if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { + t.LineTo(c[6], c[7]) + i-- + } else { + // second half of bezier go lower onto the stack + SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:]) + i++ + } + } +} + +// Quad +// x1, y1, cpx1, cpy2, x2, y2 float64 + +// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves. +// c1 and c2 parameters are the resulting curves +func SubdivideQuad(c, c1, c2 []float64) { + // First point of c is the first point of c1 + c1[0], c1[1] = c[0], c[1] + // Last point of c is the last point of c2 + c2[4], c2[5] = c[4], c[5] + + // Subdivide segment using midpoints + c1[2] = (c[0] + c[2]) / 2 + c1[3] = (c[1] + c[3]) / 2 + c2[2] = (c[2] + c[4]) / 2 + c2[3] = (c[3] + c[5]) / 2 + c1[4] = (c1[2] + c2[2]) / 2 + c1[5] = (c1[3] + c2[3]) / 2 + c2[0], c2[1] = c1[4], c1[5] + return +} + +// Trace generate lines subdividing the curve using a Flattener +// flattening_threshold helps determines the flattening expectation of the curve +func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) { + // Allocates curves stack + var curves [CurveRecursionLimit * 6]float64 + copy(curves[0:6], quad[0:6]) + i := 0 + // current curve + var c []float64 + var dx, dy, d float64 + + for i >= 0 { + c = curves[i*6:] + dx = c[4] - c[0] + dy = c[5] - c[1] + + d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx)) + + // if it's flat then trace a line + if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { + t.LineTo(c[4], c[5]) + i-- + } else { + // second half of bezier go lower onto the stack + SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:]) + i++ + } + } +} + +// TraceArc trace an arc using a Flattener +func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { + end := start + angle + clockWise := true + if angle < 0 { + clockWise = false + } + ra := (math.Abs(rx) + math.Abs(ry)) / 2 + da := math.Acos(ra/(ra+0.125/scale)) * 2 + //normalize + if !clockWise { + da = -da + } + angle = start + da + var curX, curY float64 + for { + if (angle < end-da/4) != clockWise { + curX = x + math.Cos(end)*rx + curY = y + math.Sin(end)*ry + return curX, curY + } + curX = x + math.Cos(angle)*rx + curY = y + math.Sin(angle)*ry + + angle += da + t.LineTo(curX, curY) + } + return curX, curY +} diff --git a/draw2dbase/curve_test.go b/draw2dbase/curve_test.go new file mode 100644 index 0000000..b737862 --- /dev/null +++ b/draw2dbase/curve_test.go @@ -0,0 +1,136 @@ +package draw2dbase + +import ( + "bufio" + "fmt" + "image" + "image/color" + "image/draw" + "image/png" + "log" + "os" + "testing" + + "github.com/llgcode/draw2d/raster" +) + +var ( + flattening_threshold float64 = 0.5 + testsCubicFloat64 = []float64{ + 100, 100, 200, 100, 100, 200, 200, 200, + 100, 100, 300, 200, 200, 200, 300, 100, + 100, 100, 0, 300, 200, 0, 300, 300, + 150, 290, 10, 10, 290, 10, 150, 290, + 10, 290, 10, 10, 290, 10, 290, 290, + 100, 290, 290, 10, 10, 10, 200, 290, + } + testsQuadFloat64 = []float64{ + 100, 100, 200, 100, 200, 200, + 100, 100, 290, 200, 290, 100, + 100, 100, 0, 290, 200, 290, + 150, 290, 10, 10, 290, 290, + 10, 290, 10, 10, 290, 290, + 100, 290, 290, 10, 120, 290, + } +) + +func init() { + os.Mkdir("test_results", 0666) + f, err := os.Create("test_results/_test.html") + if err != nil { + log.Println(err) + os.Exit(1) + } + defer f.Close() + log.Printf("Create html viewer") + f.Write([]byte("")) + for i := 0; i < len(testsCubicFloat64)/8; i++ { + f.Write([]byte(fmt.Sprintf("
\n", i))) + } + for i := 0; i < len(testsQuadFloat64); i++ { + f.Write([]byte(fmt.Sprintf("
\n
\n", i))) + } + f.Write([]byte("")) + +} + +func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image { + for i := 0; i < len(s); i += 2 { + x, y := int(s[i]+0.5), int(s[i+1]+0.5) + img.Set(x, y, c) + img.Set(x, y+1, c) + img.Set(x, y-1, c) + img.Set(x+1, y, c) + img.Set(x+1, y+1, c) + img.Set(x+1, y-1, c) + img.Set(x-1, y, c) + img.Set(x-1, y+1, c) + img.Set(x-1, y-1, c) + + } + return img +} + +func TestCubicCurve(t *testing.T) { + for i := 0; i < len(testsCubicFloat64); i += 8 { + var p SegmentedPath + p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) + TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) + img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) + raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...) + raster.PolylineBresenham(img, image.Black, p.Points...) + //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) + drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) + SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), img) + log.Printf("Num of points: %d\n", len(p.Points)) + } + fmt.Println() +} + +func TestQuadCurve(t *testing.T) { + for i := 0; i < len(testsQuadFloat64); i += 6 { + var p SegmentedPath + p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1]) + TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold) + img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) + raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...) + raster.PolylineBresenham(img, image.Black, p.Points...) + //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) + drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) + SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) + log.Printf("Num of points: %d\n", len(p.Points)) + } + fmt.Println() +} + +func BenchmarkCubicCurve(b *testing.B) { + for i := 0; i < b.N; i++ { + for i := 0; i < len(testsCubicFloat64); i += 8 { + var p SegmentedPath + p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) + TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) + } + } +} + +// SaveToPngFile create and save an image to a file using PNG format +func SaveToPngFile(filePath string, m image.Image) error { + // Create the file + f, err := os.Create(filePath) + if err != nil { + return err + } + defer f.Close() + // Create Writer from file + b := bufio.NewWriter(f) + // Write the image into the buffer + err = png.Encode(b, m) + if err != nil { + return err + } + err = b.Flush() + if err != nil { + return err + } + return nil +} diff --git a/draw2dbase/dasher.go b/draw2dbase/dasher.go index d0da022..6f8260c 100644 --- a/draw2dbase/dasher.go +++ b/draw2dbase/dasher.go @@ -3,24 +3,20 @@ package draw2dbase -import ( - "github.com/llgcode/draw2d" -) - type DashVertexConverter struct { - next draw2d.Flattener + next Flattener x, y, distance float64 dash []float64 currentDash int dashOffset float64 } -func NewDashConverter(dash []float64, dashOffset float64, converter draw2d.Flattener) *DashVertexConverter { +func NewDashConverter(dash []float64, dashOffset float64, flattener Flattener) *DashVertexConverter { var dasher DashVertexConverter dasher.dash = dash dasher.currentDash = 0 dasher.dashOffset = dashOffset - dasher.next = converter + dasher.next = flattener return &dasher } diff --git a/draw2dbase/demux_flattener.go b/draw2dbase/demux_flattener.go index 6537797..13b6c40 100644 --- a/draw2dbase/demux_flattener.go +++ b/draw2dbase/demux_flattener.go @@ -1,11 +1,7 @@ package draw2dbase -import ( - "github.com/llgcode/draw2d" -) - type DemuxFlattener struct { - Flatteners []draw2d.Flattener + Flatteners []Flattener } func (dc DemuxFlattener) MoveTo(x, y float64) { diff --git a/draw2dbase/flattener.go b/draw2dbase/flattener.go new file mode 100644 index 0000000..63c3823 --- /dev/null +++ b/draw2dbase/flattener.go @@ -0,0 +1,121 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 06/12/2010 by Laurent Le Goff + +package draw2dbase + +import ( + "github.com/llgcode/draw2d" +) + +// Flattener receive segment definition +type Flattener interface { + // MoveTo Start a New line from the point (x, y) + MoveTo(x, y float64) + // LineTo Draw a line from the current position to the point (x, y) + LineTo(x, y float64) + // LineJoin add the most recent starting point to close the path to create a polygon + LineJoin() + // Close add the most recent starting point to close the path to create a polygon + Close() + // End mark the current line as finished so we can draw caps + End() +} + +// Flatten convert curves into straight segments keeping join segments info +func Flatten(path *draw2d.Path, flattener Flattener, scale float64) { + // First Point + var startX, startY float64 = 0, 0 + // Current Point + var x, y float64 = 0, 0 + i := 0 + for _, cmp := range path.Components { + switch cmp { + case draw2d.MoveToCmp: + x, y = path.Points[i], path.Points[i+1] + startX, startY = x, y + if i != 0 { + flattener.End() + } + flattener.MoveTo(x, y) + i += 2 + case draw2d.LineToCmp: + x, y = path.Points[i], path.Points[i+1] + flattener.LineTo(x, y) + flattener.LineJoin() + i += 2 + case draw2d.QuadCurveToCmp: + TraceQuad(flattener, path.Points[i-2:], 0.5) + x, y = path.Points[i+2], path.Points[i+3] + flattener.LineTo(x, y) + i += 4 + case draw2d.CubicCurveToCmp: + TraceCubic(flattener, path.Points[i-2:], 0.5) + x, y = path.Points[i+4], path.Points[i+5] + flattener.LineTo(x, y) + i += 6 + case draw2d.ArcToCmp: + x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) + flattener.LineTo(x, y) + i += 6 + case draw2d.CloseCmp: + flattener.LineTo(startX, startY) + flattener.Close() + } + } + flattener.End() +} + +// Transformer apply the Matrix transformation tr +type Transformer struct { + Tr draw2d.MatrixTransform + Flattener Flattener +} + +func (t Transformer) MoveTo(x, y float64) { + u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] + v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] + t.Flattener.MoveTo(u, v) +} + +func (t Transformer) LineTo(x, y float64) { + u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] + v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] + t.Flattener.LineTo(u, v) +} + +func (t Transformer) LineJoin() { + t.Flattener.LineJoin() +} + +func (t Transformer) Close() { + t.Flattener.Close() +} + +func (t Transformer) End() { + t.Flattener.End() +} + +type SegmentedPath struct { + Points []float64 +} + +func (p *SegmentedPath) MoveTo(x, y float64) { + p.Points = append(p.Points, x, y) + // TODO need to mark this point as moveto +} + +func (p *SegmentedPath) LineTo(x, y float64) { + p.Points = append(p.Points, x, y) +} + +func (p *SegmentedPath) LineJoin() { + // TODO need to mark the current point as linejoin +} + +func (p *SegmentedPath) Close() { + // TODO Close +} + +func (p *SegmentedPath) End() { + // Nothing to do +} diff --git a/draw2dbase/stroker.go b/draw2dbase/stroker.go index c61c65a..4d9be86 100644 --- a/draw2dbase/stroker.go +++ b/draw2dbase/stroker.go @@ -32,7 +32,7 @@ func toFtJoin(j draw2d.LineJoin) raster.Joiner { } type LineStroker struct { - Next draw2d.Flattener + Flattener Flattener HalfLineWidth float64 Cap draw2d.LineCap Join draw2d.LineJoin @@ -41,9 +41,9 @@ type LineStroker struct { x, y, nx, ny float64 } -func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener draw2d.Flattener) *LineStroker { +func NewLineStroker(c draw2d.LineCap, j draw2d.LineJoin, flattener Flattener) *LineStroker { l := new(LineStroker) - l.Next = flattener + l.Flattener = flattener l.HalfLineWidth = 0.5 l.Cap = c l.Join = j @@ -82,18 +82,18 @@ func (l *LineStroker) Close() { func (l *LineStroker) End() { if len(l.vertices) > 1 { - l.Next.MoveTo(l.vertices[0], l.vertices[1]) + l.Flattener.MoveTo(l.vertices[0], l.vertices[1]) for i, j := 2, 3; j < len(l.vertices); i, j = i+2, j+2 { - l.Next.LineTo(l.vertices[i], l.vertices[j]) + l.Flattener.LineTo(l.vertices[i], l.vertices[j]) } } for i, j := len(l.rewind)-2, len(l.rewind)-1; j > 0; i, j = i-2, j-2 { - l.Next.LineTo(l.rewind[i], l.rewind[j]) + l.Flattener.LineTo(l.rewind[i], l.rewind[j]) } if len(l.vertices) > 1 { - l.Next.LineTo(l.vertices[0], l.vertices[1]) + l.Flattener.LineTo(l.vertices[0], l.vertices[1]) } - l.Next.End() + l.Flattener.End() // reinit vertices l.vertices = l.vertices[0:0] l.rewind = l.rewind[0:0] diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index cc9bdc5..d9a055c 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -188,17 +188,17 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner draw2d.Flattener + var liner draw2dbase.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } for _, p := range paths { - p.Flatten(liner, gc.Current.Tr.GetScale()) + draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale()) } gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) @@ -209,9 +209,9 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) /**** first method ****/ - flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} + flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} for _, p := range paths { - p.Flatten(flattener, gc.Current.Tr.GetScale()) + draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale()) } gc.paint(gc.fillRasterizer, gc.Current.FillColor) @@ -222,21 +222,21 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) gc.strokeRasterizer.UseNonZeroWinding = true - flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} + flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner draw2d.Flattener + var liner draw2dbase.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } - demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}} + demux := draw2dbase.DemuxFlattener{[]draw2dbase.Flattener{flattener, liner}} for _, p := range paths { - p.Flatten(demux, gc.Current.Tr.GetScale()) + draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale()) } // Fill diff --git a/draw2dimg/ftgc.go b/draw2dimg/ftgc.go index 9982d92..2e0da66 100644 --- a/draw2dimg/ftgc.go +++ b/draw2dimg/ftgc.go @@ -276,17 +276,17 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner draw2d.Flattener + var liner draw2dbase.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } for _, p := range paths { - p.Flatten(liner, gc.Current.Tr.GetScale()) + draw2dbase.Flatten(p, liner, gc.Current.Tr.GetScale()) } gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) @@ -297,9 +297,9 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) /**** first method ****/ - flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} + flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} for _, p := range paths { - p.Flatten(flattener, gc.Current.Tr.GetScale()) + draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale()) } gc.paint(gc.fillRasterizer, gc.Current.FillColor) @@ -310,21 +310,21 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) gc.strokeRasterizer.UseNonZeroWinding = true - flattener := draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} + flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2d.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 - var liner draw2d.Flattener + var liner draw2dbase.Flattener if gc.Current.Dash != nil && len(gc.Current.Dash) > 0 { liner = draw2dbase.NewDashConverter(gc.Current.Dash, gc.Current.DashOffset, stroker) } else { liner = stroker } - demux := draw2dbase.DemuxFlattener{[]draw2d.Flattener{flattener, liner}} + demux := draw2dbase.DemuxFlattener{[]draw2dbase.Flattener{flattener, liner}} for _, p := range paths { - p.Flatten(demux, gc.Current.Tr.GetScale()) + draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale()) } // Fill diff --git a/drawing_kit.go b/drawing_kit.go index fe496af..f18d518 100644 --- a/drawing_kit.go +++ b/drawing_kit.go @@ -7,7 +7,7 @@ import ( "math" ) -// Rectangle draw a rectangle using a PathBuilder +// Rectangle draws a rectangle using a PathBuilder func Rectangle(path PathBuilder, x1, y1, x2, y2 float64) { path.MoveTo(x1, y1) path.LineTo(x2, y1) @@ -16,7 +16,7 @@ func Rectangle(path PathBuilder, x1, y1, x2, y2 float64) { path.Close() } -// RoundedRectangle draw a rounded rectangle using a PathBuilder +// RoundedRectangle draws a rounded rectangle using a PathBuilder func RoundedRectangle(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) { arcWidth = arcWidth / 2 arcHeight = arcHeight / 2 @@ -31,13 +31,13 @@ func RoundedRectangle(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight floa path.Close() } -// Ellipse draw an ellipse using a PathBuilder +// Ellipse draws an ellipse using a PathBuilder func Ellipse(path PathBuilder, cx, cy, rx, ry float64) { path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) path.Close() } -// Circle draw an circle using a PathBuilder +// Circle draws a circle using a PathBuilder func Circle(path PathBuilder, cx, cy, radius float64) { path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) path.Close() diff --git a/flattener.go b/flattener.go deleted file mode 100644 index d948ae5..0000000 --- a/flattener.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 06/12/2010 by Laurent Le Goff - -package draw2d - -// Flattener receive segment definition -type Flattener interface { - // MoveTo Start a New line from the point (x, y) - MoveTo(x, y float64) - // LineTo Draw a line from the current position to the point (x, y) - LineTo(x, y float64) - // LineJoin add the most recent starting point to close the path to create a polygon - LineJoin() - // Close add the most recent starting point to close the path to create a polygon - Close() - // End mark the current line as finished so we can draw caps - End() -} - -type SegmentedPath struct { - Points []float64 -} - -func (p *SegmentedPath) MoveTo(x, y float64) { - p.Points = append(p.Points, x, y) - // TODO need to mark this point as moveto -} - -func (p *SegmentedPath) LineTo(x, y float64) { - p.Points = append(p.Points, x, y) -} - -func (p *SegmentedPath) LineJoin() { - // TODO need to mark the current point as linejoin -} - -func (p *SegmentedPath) Close() { - // TODO Close -} - -func (p *SegmentedPath) End() { - // Nothing to do -} - -type LineCap int - -const ( - RoundCap LineCap = iota - ButtCap - SquareCap -) - -type LineJoin int - -const ( - BevelJoin LineJoin = iota - RoundJoin - MiterJoin -) diff --git a/gc.go b/gc.go index 0388708..463f6a8 100644 --- a/gc.go +++ b/gc.go @@ -15,6 +15,22 @@ const ( FillRuleWinding ) +type LineCap int + +const ( + RoundCap LineCap = iota + ButtCap + SquareCap +) + +type LineJoin int + +const ( + BevelJoin LineJoin = iota + RoundJoin + MiterJoin +) + type GraphicContext interface { PathBuilder // Create a new path diff --git a/path.go b/path.go index 6ff5ccd..4f9b1b5 100644 --- a/path.go +++ b/path.go @@ -9,7 +9,7 @@ import ( "math" ) -// PathBuilder define method that create path +// PathBuilder defines methods that creates path type PathBuilder interface { // LastPoint returns the current point of the current path LastPoint() (x, y float64) @@ -33,7 +33,7 @@ type PathBuilder interface { Close() } -// PathCmp represent components of a path +// PathCmp represents component of a path type PathCmp int const ( @@ -174,47 +174,3 @@ func (p *Path) String() string { } return s } - -// Flatten convert curves into straight segments keeping join segments info -func (path *Path) Flatten(flattener Flattener, scale float64) { - // First Point - var startX, startY float64 = 0, 0 - // Current Point - var x, y float64 = 0, 0 - i := 0 - for _, cmd := range path.Components { - switch cmd { - case MoveToCmp: - x, y = path.Points[i], path.Points[i+1] - startX, startY = x, y - if i != 0 { - flattener.End() - } - flattener.MoveTo(x, y) - i += 2 - case LineToCmp: - x, y = path.Points[i], path.Points[i+1] - flattener.LineTo(x, y) - flattener.LineJoin() - i += 2 - case QuadCurveToCmp: - TraceQuad(flattener, path.Points[i-2:], 0.5) - x, y = path.Points[i+2], path.Points[i+3] - flattener.LineTo(x, y) - i += 4 - case CubicCurveToCmp: - TraceCubic(flattener, path.Points[i-2:], 0.5) - x, y = path.Points[i+4], path.Points[i+5] - flattener.LineTo(x, y) - i += 6 - case ArcToCmp: - x, y = TraceArc(flattener, path.Points[i], path.Points[i+1], path.Points[i+2], path.Points[i+3], path.Points[i+4], path.Points[i+5], scale) - flattener.LineTo(x, y) - i += 6 - case CloseCmp: - flattener.LineTo(startX, startY) - flattener.Close() - } - } - flattener.End() -} diff --git a/transform.go b/transform.go index fb70941..9d924f3 100644 --- a/transform.go +++ b/transform.go @@ -246,33 +246,3 @@ func (tr MatrixTransform) IsTranslation() bool { func fequals(float1, float2 float64) bool { return math.Abs(float1-float2) <= epsilon } - -// Transformer apply the Matrix transformation tr -type Transformer struct { - Tr MatrixTransform - Flattener Flattener -} - -func (t Transformer) MoveTo(x, y float64) { - u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] - v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] - t.Flattener.MoveTo(u, v) -} - -func (t Transformer) LineTo(x, y float64) { - u := x*t.Tr[0] + y*t.Tr[2] + t.Tr[4] - v := x*t.Tr[1] + y*t.Tr[3] + t.Tr[5] - t.Flattener.LineTo(u, v) -} - -func (t Transformer) LineJoin() { - t.Flattener.LineJoin() -} - -func (t Transformer) Close() { - t.Flattener.Close() -} - -func (t Transformer) End() { - t.Flattener.End() -} From 74e6b9b1ec2c235fafcd3c52e399edd2609b1a99 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 17:16:44 +0200 Subject: [PATCH 23/39] move flattening code in draw2dbase --- curve.go | 161 -------------------------------------------------- curve_test.go | 136 ------------------------------------------ 2 files changed, 297 deletions(-) delete mode 100644 curve.go delete mode 100644 curve_test.go diff --git a/curve.go b/curve.go deleted file mode 100644 index 3599144..0000000 --- a/curve.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 17/05/2011 by Laurent Le Goff - -package draw2d - -import ( - "math" -) - -const ( - CurveRecursionLimit = 32 -) - -// Cubic -// x1, y1, cpx1, cpy1, cpx2, cpy2, x2, y2 float64 - -// Subdivide a Bezier cubic curve in 2 equivalents Bezier cubic curves. -// c1 and c2 parameters are the resulting curves -func SubdivideCubic(c, c1, c2 []float64) { - // First point of c is the first point of c1 - c1[0], c1[1] = c[0], c[1] - // Last point of c is the last point of c2 - c2[6], c2[7] = c[6], c[7] - - // Subdivide segment using midpoints - c1[2] = (c[0] + c[2]) / 2 - c1[3] = (c[1] + c[3]) / 2 - - midX := (c[2] + c[4]) / 2 - midY := (c[3] + c[5]) / 2 - - c2[4] = (c[4] + c[6]) / 2 - c2[5] = (c[5] + c[7]) / 2 - - c1[4] = (c1[2] + midX) / 2 - c1[5] = (c1[3] + midY) / 2 - - c2[2] = (midX + c2[4]) / 2 - c2[3] = (midY + c2[5]) / 2 - - c1[6] = (c1[4] + c2[2]) / 2 - c1[7] = (c1[5] + c2[3]) / 2 - - // Last Point of c1 is equal to the first point of c2 - c2[0], c2[1] = c1[6], c1[7] -} - -// TraceCubic generate lines subdividing the cubic curve using a Flattener -// flattening_threshold helps determines the flattening expectation of the curve -func TraceCubic(t Flattener, cubic []float64, flattening_threshold float64) { - // Allocation curves - var curves [CurveRecursionLimit * 8]float64 - copy(curves[0:8], cubic[0:8]) - i := 0 - - // current curve - var c []float64 - - var dx, dy, d2, d3 float64 - - for i >= 0 { - c = curves[i*8:] - dx = c[6] - c[0] - dy = c[7] - c[1] - - d2 = math.Abs((c[2]-c[6])*dy - (c[3]-c[7])*dx) - d3 = math.Abs((c[4]-c[6])*dy - (c[5]-c[7])*dx) - - // if it's flat then trace a line - if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.LineTo(c[6], c[7]) - i-- - } else { - // second half of bezier go lower onto the stack - SubdivideCubic(c, curves[(i+1)*8:], curves[i*8:]) - i++ - } - } -} - -// Quad -// x1, y1, cpx1, cpy2, x2, y2 float64 - -// Subdivide a Bezier quad curve in 2 equivalents Bezier quad curves. -// c1 and c2 parameters are the resulting curves -func SubdivideQuad(c, c1, c2 []float64) { - // First point of c is the first point of c1 - c1[0], c1[1] = c[0], c[1] - // Last point of c is the last point of c2 - c2[4], c2[5] = c[4], c[5] - - // Subdivide segment using midpoints - c1[2] = (c[0] + c[2]) / 2 - c1[3] = (c[1] + c[3]) / 2 - c2[2] = (c[2] + c[4]) / 2 - c2[3] = (c[3] + c[5]) / 2 - c1[4] = (c1[2] + c2[2]) / 2 - c1[5] = (c1[3] + c2[3]) / 2 - c2[0], c2[1] = c1[4], c1[5] - return -} - -// Trace generate lines subdividing the curve using a Flattener -// flattening_threshold helps determines the flattening expectation of the curve -func TraceQuad(t Flattener, quad []float64, flattening_threshold float64) { - // Allocates curves stack - var curves [CurveRecursionLimit * 6]float64 - copy(curves[0:6], quad[0:6]) - i := 0 - // current curve - var c []float64 - var dx, dy, d float64 - - for i >= 0 { - c = curves[i*6:] - dx = c[4] - c[0] - dy = c[5] - c[1] - - d = math.Abs(((c[2]-c[4])*dy - (c[3]-c[5])*dx)) - - // if it's flat then trace a line - if (d*d) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 { - t.LineTo(c[4], c[5]) - i-- - } else { - // second half of bezier go lower onto the stack - SubdivideQuad(c, curves[(i+1)*6:], curves[i*6:]) - i++ - } - } -} - -// TraceArc trace an arc using a Flattener -func TraceArc(t Flattener, x, y, rx, ry, start, angle, scale float64) (lastX, lastY float64) { - end := start + angle - clockWise := true - if angle < 0 { - clockWise = false - } - ra := (math.Abs(rx) + math.Abs(ry)) / 2 - da := math.Acos(ra/(ra+0.125/scale)) * 2 - //normalize - if !clockWise { - da = -da - } - angle = start + da - var curX, curY float64 - for { - if (angle < end-da/4) != clockWise { - curX = x + math.Cos(end)*rx - curY = y + math.Sin(end)*ry - return curX, curY - } - curX = x + math.Cos(angle)*rx - curY = y + math.Sin(angle)*ry - - angle += da - t.LineTo(curX, curY) - } - return curX, curY -} diff --git a/curve_test.go b/curve_test.go deleted file mode 100644 index c4d2603..0000000 --- a/curve_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package draw2d - -import ( - "bufio" - "fmt" - "image" - "image/color" - "image/draw" - "image/png" - "log" - "os" - "testing" - - "github.com/llgcode/draw2d/raster" -) - -var ( - flattening_threshold float64 = 0.5 - testsCubicFloat64 = []float64{ - 100, 100, 200, 100, 100, 200, 200, 200, - 100, 100, 300, 200, 200, 200, 300, 100, - 100, 100, 0, 300, 200, 0, 300, 300, - 150, 290, 10, 10, 290, 10, 150, 290, - 10, 290, 10, 10, 290, 10, 290, 290, - 100, 290, 290, 10, 10, 10, 200, 290, - } - testsQuadFloat64 = []float64{ - 100, 100, 200, 100, 200, 200, - 100, 100, 290, 200, 290, 100, - 100, 100, 0, 290, 200, 290, - 150, 290, 10, 10, 290, 290, - 10, 290, 10, 10, 290, 290, - 100, 290, 290, 10, 120, 290, - } -) - -func init() { - os.Mkdir("test_results", 0666) - f, err := os.Create("test_results/_test.html") - if err != nil { - log.Println(err) - os.Exit(1) - } - defer f.Close() - log.Printf("Create html viewer") - f.Write([]byte("")) - for i := 0; i < len(testsCubicFloat64)/8; i++ { - f.Write([]byte(fmt.Sprintf("
\n", i))) - } - for i := 0; i < len(testsQuadFloat64); i++ { - f.Write([]byte(fmt.Sprintf("
\n
\n", i))) - } - f.Write([]byte("")) - -} - -func drawPoints(img draw.Image, c color.Color, s ...float64) image.Image { - for i := 0; i < len(s); i += 2 { - x, y := int(s[i]+0.5), int(s[i+1]+0.5) - img.Set(x, y, c) - img.Set(x, y+1, c) - img.Set(x, y-1, c) - img.Set(x+1, y, c) - img.Set(x+1, y+1, c) - img.Set(x+1, y-1, c) - img.Set(x-1, y, c) - img.Set(x-1, y+1, c) - img.Set(x-1, y-1, c) - - } - return img -} - -func TestCubicCurve(t *testing.T) { - for i := 0; i < len(testsCubicFloat64); i += 8 { - var p SegmentedPath - p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) - TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) - img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...) - raster.PolylineBresenham(img, image.Black, p.Points...) - //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) - SaveToPngFile(fmt.Sprintf("test_results/_test%d.png", i/8), img) - log.Printf("Num of points: %d\n", len(p.Points)) - } - fmt.Println() -} - -func TestQuadCurve(t *testing.T) { - for i := 0; i < len(testsQuadFloat64); i += 6 { - var p SegmentedPath - p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1]) - TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold) - img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) - raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...) - raster.PolylineBresenham(img, image.Black, p.Points...) - //drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve[:]...) - drawPoints(img, color.NRGBA{0, 0, 0, 0xff}, p.Points...) - SaveToPngFile(fmt.Sprintf("test_results/_testQuad%d.png", i), img) - log.Printf("Num of points: %d\n", len(p.Points)) - } - fmt.Println() -} - -func BenchmarkCubicCurve(b *testing.B) { - for i := 0; i < b.N; i++ { - for i := 0; i < len(testsCubicFloat64); i += 8 { - var p SegmentedPath - p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) - TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) - } - } -} - -// SaveToPngFile create and save an image to a file using PNG format -func SaveToPngFile(filePath string, m image.Image) error { - // Create the file - f, err := os.Create(filePath) - if err != nil { - return err - } - defer f.Close() - // Create Writer from file - b := bufio.NewWriter(f) - // Write the image into the buffer - err = png.Encode(b, m) - if err != nil { - return err - } - err = b.Flush() - if err != nil { - return err - } - return nil -} From 47f90d341484a398487672b954fa5aaa833a4798 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 17:16:55 +0200 Subject: [PATCH 24/39] remove old .project file --- .project | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .project diff --git a/.project b/.project deleted file mode 100644 index 6a8aaf2..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - draw2d - - - - - - com.googlecode.goclipse.goBuilder - - - - - - goclipse.goNature - - From 9012e5e58009446e7a8f6be6555414e00a63a206 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 17:32:28 +0200 Subject: [PATCH 25/39] move helpers to draw2dkit --- draw2dkit/draw2dkit.go | 46 ++++++++++++++++++++++++++++++++++ draw2dkit/droid.go | 50 +++++++++++++++++++++++++++++++++++++ draw2dkit/gopher.go | 56 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 draw2dkit/draw2dkit.go create mode 100644 draw2dkit/droid.go create mode 100644 draw2dkit/gopher.go diff --git a/draw2dkit/draw2dkit.go b/draw2dkit/draw2dkit.go new file mode 100644 index 0000000..1cb3820 --- /dev/null +++ b/draw2dkit/draw2dkit.go @@ -0,0 +1,46 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 13/12/2010 by Laurent Le Goff + +// Package draw2dkit procides helpers to draw common figure and draw using a Path or a GraphicContext +package draw2dkit + +import ( + "github.com/llgcode/draw2d" + "math" +) + +// Rectangle draws a rectangle using a PathBuilder +func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) { + path.MoveTo(x1, y1) + path.LineTo(x2, y1) + path.LineTo(x2, y2) + path.LineTo(x1, y2) + path.Close() +} + +// RoundedRectangle draws a rounded rectangle using a PathBuilder +func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) { + arcWidth = arcWidth / 2 + arcHeight = arcHeight / 2 + path.MoveTo(x1, y1+arcHeight) + path.QuadCurveTo(x1, y1, x1+arcWidth, y1) + path.LineTo(x2-arcWidth, y1) + path.QuadCurveTo(x2, y1, x2, y1+arcHeight) + path.LineTo(x2, y2-arcHeight) + path.QuadCurveTo(x2, y2, x2-arcWidth, y2) + path.LineTo(x1+arcWidth, y2) + path.QuadCurveTo(x1, y2, x1, y2-arcHeight) + path.Close() +} + +// Ellipse draws an ellipse using a PathBuilder +func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) { + path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) + path.Close() +} + +// Circle draws a circle using a PathBuilder +func Circle(path draw2d.PathBuilder, cx, cy, radius float64) { + path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) + path.Close() +} diff --git a/draw2dkit/droid.go b/draw2dkit/droid.go new file mode 100644 index 0000000..5bd62dd --- /dev/null +++ b/draw2dkit/droid.go @@ -0,0 +1,50 @@ +package draw2dkit + +import ( + "github.com/llgcode/draw2d" + "math" +) + +func Droid(gc draw2d.GraphicContext, x, y float64) { + gc.SetLineCap(draw2d.RoundCap) + gc.SetLineWidth(5) + + // head + gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) + gc.FillStroke() + gc.MoveTo(x+60, y+25) + gc.LineTo(x+50, y+10) + gc.MoveTo(x+100, y+25) + gc.LineTo(x+110, y+10) + gc.Stroke() + + // left eye + draw2d.Circle(gc, x+60, y+45, 5) + gc.FillStroke() + + // right eye + draw2d.Circle(gc, x+100, y+45, 5) + gc.FillStroke() + + // body + draw2d.RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) + gc.FillStroke() + draw2d.Rectangle(gc, x+30, y+75, x+30+100, y+75+80) + gc.FillStroke() + + // left arm + draw2d.RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) + gc.FillStroke() + + // right arm + draw2d.RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) + gc.FillStroke() + + // left leg + draw2d.RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) + gc.FillStroke() + + // right leg + draw2d.RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) + gc.FillStroke() +} diff --git a/draw2dkit/gopher.go b/draw2dkit/gopher.go new file mode 100644 index 0000000..f2b743a --- /dev/null +++ b/draw2dkit/gopher.go @@ -0,0 +1,56 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 21/11/2010 by Laurent Le Goff + +package draw2dkit + +import ( + "image/color" + + "github.com/llgcode/draw2d" +) + +// Gopher draw a gopher using a gc thanks to https://github.com/golang-samples/gopher-vector/ +func Gopher(gc draw2d.GraphicContext, x, y, w, h float64) { + // Initialize Stroke Attribute + gc.SetLineWidth(3) + gc.SetLineCap(draw2d.RoundCap) + gc.SetStrokeColor(color.Black) + + // Left hand + // + gc.SetFillColor(color.RGBA{0xF6, 0xD2, 0xA2, 0xff}) + gc.MoveTo(10.634, 300.493) + rCubicCurveTo(gc, 0.764, 15.751, 16.499, 8.463, 23.626, 3.539) + rCubicCurveTo(gc, 6.765, -4.675, 8.743, -0.789, 9.337, -10.015) + rCubicCurveTo(gc, 0.389, -6.064, 1.088, -12.128, 0.744, -18.216) + rCubicCurveTo(gc, -10.23, -0.927, -21.357, 1.509, -29.744, 7.602) + gc.CubicCurveTo(10.277, 286.542, 2.177, 296.561, 10.634, 300.493) + gc.FillStroke() + + // + gc.MoveTo(10.634, 300.493) + rCubicCurveTo(gc, 2.29, -0.852, 4.717, -1.457, 6.271, -3.528) + gc.Stroke() + + // Left Ear + // + gc.MoveTo(46.997, 112.853) + gc.CubicCurveTo(-13.3, 95.897, 31.536, 19.189, 79.956, 50.74) + gc.LineTo(46.997, 112.853) + gc.Close() + gc.Stroke() +} + +func rQuadCurveTo(path draw2d.PathBuilder, dcx, dcy, dx, dy float64) { + x, y := path.LastPoint() + path.QuadCurveTo(x+dcx, y+dcy, x+dx, y+dy) +} + +func rCubicCurveTo(path draw2d.PathBuilder, dcx1, dcy1, dcx2, dcy2, dx, dy float64) { + x, y := path.LastPoint() + path.CubicCurveTo(x+dcx1, y+dcy1, x+dcx2, y+dcy2, x+dx, y+dy) +} From 94ef483cbd5566ab8abb721099b5d6cd48457d8f Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 17:49:18 +0200 Subject: [PATCH 26/39] draw2dkit --- draw2dkit/droid.go | 16 ++++++++-------- drawing_kit.go | 44 -------------------------------------------- 2 files changed, 8 insertions(+), 52 deletions(-) delete mode 100644 drawing_kit.go diff --git a/draw2dkit/droid.go b/draw2dkit/droid.go index 5bd62dd..7114149 100644 --- a/draw2dkit/droid.go +++ b/draw2dkit/droid.go @@ -19,32 +19,32 @@ func Droid(gc draw2d.GraphicContext, x, y float64) { gc.Stroke() // left eye - draw2d.Circle(gc, x+60, y+45, 5) + Circle(gc, x+60, y+45, 5) gc.FillStroke() // right eye - draw2d.Circle(gc, x+100, y+45, 5) + Circle(gc, x+100, y+45, 5) gc.FillStroke() // body - draw2d.RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) + RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) gc.FillStroke() - draw2d.Rectangle(gc, x+30, y+75, x+30+100, y+75+80) + Rectangle(gc, x+30, y+75, x+30+100, y+75+80) gc.FillStroke() // left arm - draw2d.RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) + RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) gc.FillStroke() // right arm - draw2d.RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) + RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) gc.FillStroke() // left leg - draw2d.RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) + RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) gc.FillStroke() // right leg - draw2d.RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) + RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) gc.FillStroke() } diff --git a/drawing_kit.go b/drawing_kit.go deleted file mode 100644 index f18d518..0000000 --- a/drawing_kit.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 13/12/2010 by Laurent Le Goff - -package draw2d - -import ( - "math" -) - -// Rectangle draws a rectangle using a PathBuilder -func Rectangle(path PathBuilder, x1, y1, x2, y2 float64) { - path.MoveTo(x1, y1) - path.LineTo(x2, y1) - path.LineTo(x2, y2) - path.LineTo(x1, y2) - path.Close() -} - -// RoundedRectangle draws a rounded rectangle using a PathBuilder -func RoundedRectangle(path PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) { - arcWidth = arcWidth / 2 - arcHeight = arcHeight / 2 - path.MoveTo(x1, y1+arcHeight) - path.QuadCurveTo(x1, y1, x1+arcWidth, y1) - path.LineTo(x2-arcWidth, y1) - path.QuadCurveTo(x2, y1, x2, y1+arcHeight) - path.LineTo(x2, y2-arcHeight) - path.QuadCurveTo(x2, y2, x2-arcWidth, y2) - path.LineTo(x1+arcWidth, y2) - path.QuadCurveTo(x1, y2, x1, y2-arcHeight) - path.Close() -} - -// Ellipse draws an ellipse using a PathBuilder -func Ellipse(path PathBuilder, cx, cy, rx, ry float64) { - path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) - path.Close() -} - -// Circle draws a circle using a PathBuilder -func Circle(path PathBuilder, cx, cy, radius float64) { - path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) - path.Close() -} From cf01bf30262ad5e6bfe947d8f672bf9b2ee39e59 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 17:52:43 +0200 Subject: [PATCH 27/39] document --- draw2dkit/draw2dkit.go | 10 +++++----- draw2dkit/droid.go | 1 + draw2dkit/gopher.go | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/draw2dkit/draw2dkit.go b/draw2dkit/draw2dkit.go index 1cb3820..968fb80 100644 --- a/draw2dkit/draw2dkit.go +++ b/draw2dkit/draw2dkit.go @@ -1,7 +1,7 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -// Package draw2dkit procides helpers to draw common figure and draw using a Path or a GraphicContext +// Package draw2dkit provides helpers to draw common figures using a Path or a GraphicContext package draw2dkit import ( @@ -9,7 +9,7 @@ import ( "math" ) -// Rectangle draws a rectangle using a PathBuilder +// Rectangle draws a rectangle using a path func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) { path.MoveTo(x1, y1) path.LineTo(x2, y1) @@ -18,7 +18,7 @@ func Rectangle(path draw2d.PathBuilder, x1, y1, x2, y2 float64) { path.Close() } -// RoundedRectangle draws a rounded rectangle using a PathBuilder +// RoundedRectangle draws a rounded rectangle using a path func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeight float64) { arcWidth = arcWidth / 2 arcHeight = arcHeight / 2 @@ -33,13 +33,13 @@ func RoundedRectangle(path draw2d.PathBuilder, x1, y1, x2, y2, arcWidth, arcHeig path.Close() } -// Ellipse draws an ellipse using a PathBuilder +// Ellipse draws an ellipse using a path func Ellipse(path draw2d.PathBuilder, cx, cy, rx, ry float64) { path.ArcTo(cx, cy, rx, ry, 0, -math.Pi*2) path.Close() } -// Circle draws a circle using a PathBuilder +// Circle draws a circle using a path func Circle(path draw2d.PathBuilder, cx, cy, radius float64) { path.ArcTo(cx, cy, radius, radius, 0, -math.Pi*2) path.Close() diff --git a/draw2dkit/droid.go b/draw2dkit/droid.go index 7114149..0adfe42 100644 --- a/draw2dkit/droid.go +++ b/draw2dkit/droid.go @@ -5,6 +5,7 @@ import ( "math" ) +// Droid draws a droid at specified position func Droid(gc draw2d.GraphicContext, x, y float64) { gc.SetLineCap(draw2d.RoundCap) gc.SetLineWidth(5) diff --git a/draw2dkit/gopher.go b/draw2dkit/gopher.go index f2b743a..b46a38e 100644 --- a/draw2dkit/gopher.go +++ b/draw2dkit/gopher.go @@ -9,7 +9,7 @@ import ( "github.com/llgcode/draw2d" ) -// Gopher draw a gopher using a gc thanks to https://github.com/golang-samples/gopher-vector/ +// Gopher draw a gopher using a GraphicContext thanks to https://github.com/golang-samples/gopher-vector/ func Gopher(gc draw2d.GraphicContext, x, y, w, h float64) { // Initialize Stroke Attribute gc.SetLineWidth(3) From c686a7fcf6406f6de2e504317c1cb1874dc99c85 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 17:59:39 +0200 Subject: [PATCH 28/39] add comments on path --- path.go | 60 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/path.go b/path.go index 4f9b1b5..c0bb7af 100644 --- a/path.go +++ b/path.go @@ -52,40 +52,17 @@ type Path struct { x, y float64 } -func (p *Path) Clear() { - p.Components = p.Components[0:0] - p.Points = p.Points[0:0] - return -} - func (p *Path) appendToPath(cmd PathCmp, points ...float64) { p.Components = append(p.Components, cmd) p.Points = append(p.Points, points...) } -// Copy make a clone of the current path and return it -func (src *Path) Copy() (dest *Path) { - dest = new(Path) - dest.Components = make([]PathCmp, len(src.Components)) - copy(dest.Components, src.Components) - dest.Points = make([]float64, len(src.Points)) - copy(dest.Points, src.Points) - dest.x, dest.y = src.x, src.y - return dest -} - +// LastPoint returns the current point of the current path func (p *Path) LastPoint() (x, y float64) { return p.x, p.y } -func (p *Path) IsEmpty() bool { - return len(p.Components) == 0 -} - -func (p *Path) Close() { - p.appendToPath(CloseCmp) -} - +// MoveTo starts a new path at (x, y) position func (p *Path) MoveTo(x, y float64) { p.appendToPath(MoveToCmp, x, y) @@ -93,6 +70,7 @@ func (p *Path) MoveTo(x, y float64) { p.y = y } +// LineTo adds a line to the current path func (p *Path) LineTo(x, y float64) { if len(p.Components) == 0 { //special case when no move has been done p.MoveTo(0, 0) @@ -102,6 +80,7 @@ func (p *Path) LineTo(x, y float64) { p.y = y } +// QuadCurveTo adds a quadratic bezier curve to the current path func (p *Path) QuadCurveTo(cx, cy, x, y float64) { if len(p.Components) == 0 { //special case when no move has been done p.MoveTo(0, 0) @@ -111,6 +90,7 @@ func (p *Path) QuadCurveTo(cx, cy, x, y float64) { p.y = y } +// CubicCurveTo adds a cubic bezier curve to the current path func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { if len(p.Components) == 0 { //special case when no move has been done p.MoveTo(0, 0) @@ -120,6 +100,7 @@ func (p *Path) CubicCurveTo(cx1, cy1, cx2, cy2, x, y float64) { p.y = y } +// ArcTo adds an arc to the path func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { endAngle := startAngle + angle clockWise := true @@ -148,6 +129,35 @@ func (p *Path) ArcTo(cx, cy, rx, ry, startAngle, angle float64) { p.y = cy + math.Sin(endAngle)*ry } +// Close closes the current path +func (p *Path) Close() { + p.appendToPath(CloseCmp) +} + +// Copy make a clone of the current path and return it +func (src *Path) Copy() (dest *Path) { + dest = new(Path) + dest.Components = make([]PathCmp, len(src.Components)) + copy(dest.Components, src.Components) + dest.Points = make([]float64, len(src.Points)) + copy(dest.Points, src.Points) + dest.x, dest.y = src.x, src.y + return dest +} + +// Clear reset the path +func (p *Path) Clear() { + p.Components = p.Components[0:0] + p.Points = p.Points[0:0] + return +} + +// IsEmpty returns true if the path is empty +func (p *Path) IsEmpty() bool { + return len(p.Components) == 0 +} + +// String returns a debug text view of the path func (p *Path) String() string { s := "" j := 0 From c7ef18681a3a588d8fe873acafe7309b0cefd61e Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 18:09:07 +0200 Subject: [PATCH 29/39] comment --- gc.go | 16 ++++++++++++++- transform.go | 58 +++++++++++++--------------------------------------- 2 files changed, 29 insertions(+), 45 deletions(-) diff --git a/gc.go b/gc.go index 463f6a8..a4e671c 100644 --- a/gc.go +++ b/gc.go @@ -33,21 +33,35 @@ const ( type GraphicContext interface { PathBuilder - // Create a new path + // BeginPath creates a new path BeginPath() + // GetMatrixTransform returns the current transformation matrix GetMatrixTransform() MatrixTransform + // SetMatrixTransform sets the current transformation matrix SetMatrixTransform(tr MatrixTransform) + // ComposeMatrixTransform composes the current transformation matrix with tr ComposeMatrixTransform(tr MatrixTransform) + // Rotate applies a rotation to the current transformation matrix. angle is in radian. Rotate(angle float64) + // Translate applies a translation to the current transformation matrix. Translate(tx, ty float64) + // Scale applies a scale to the current transformation matrix. Scale(sx, sy float64) + // SetStrokeColor sets the current stroke color SetStrokeColor(c color.Color) + // SetStrokeColor sets the current fill color SetFillColor(c color.Color) + // SetFillRule sets the current fill rule SetFillRule(f FillRule) + // SetLineWidth sets the current line width SetLineWidth(lineWidth float64) + // SetLineCap sets the current line cap SetLineCap(cap LineCap) + // SetLineJoin sets the current line join SetLineJoin(join LineJoin) + // SetLineJoin sets the current dash SetLineDash(dash []float64, dashOffset float64) + // SetFontSize SetFontSize(fontSize float64) GetFontSize() float64 SetFontData(fontData FontData) diff --git a/transform.go b/transform.go index 9d924f3..a648d9e 100644 --- a/transform.go +++ b/transform.go @@ -18,7 +18,7 @@ func (tr MatrixTransform) Determinant() float64 { return tr[0]*tr[3] - tr[1]*tr[2] } -// Transform apply the transformation matrix to points. It modify the points passed in parameter. +// Transform applies the transformation matrix to points. It modify the points passed in parameter. func (tr MatrixTransform) Transform(points []float64) { for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { x := points[i] @@ -28,7 +28,7 @@ func (tr MatrixTransform) Transform(points []float64) { } } -// TransformPoint apply the transformation matrix to point. It returns the point the transformed point. +// TransformPoint applies the transformation matrix to point. It returns the point the transformed point. func (tr MatrixTransform) TransformPoint(x, y float64) (xres, yres float64) { xres = x*tr[0] + y*tr[2] + tr[4] yres = x*tr[1] + y*tr[3] + tr[5] @@ -42,7 +42,7 @@ func minMax(x, y float64) (min, max float64) { return x, y } -// Transform apply the transformation matrix to the rectangle represented by the min and the max point of the rectangle +// Transform applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) { points := []float64{x0, y0, x2, y0, x2, y2, x0, y2} tr.Transform(points) @@ -58,7 +58,7 @@ func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, return nx0, ny0, nx2, ny2 } -// InverseTransform apply the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle +// InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle func (tr MatrixTransform) InverseTransform(points []float64) { d := tr.Determinant() // matrix determinant for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { @@ -69,7 +69,7 @@ func (tr MatrixTransform) InverseTransform(points []float64) { } } -// InverseTransformPoint apply the transformation inverse matrix to point. It returns the point the transformed point. +// InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point. func (tr MatrixTransform) InverseTransformPoint(x, y float64) (xres, yres float64) { d := tr.Determinant() // matrix determinant xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d @@ -77,7 +77,7 @@ func (tr MatrixTransform) InverseTransformPoint(x, y float64) (xres, yres float6 return xres, yres } -// VectorTransform apply the transformation matrix to points without using the translation parameter of the affine matrix. +// VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix. // It modify the points passed in parameter. func (tr MatrixTransform) VectorTransform(points []float64) { for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { @@ -131,7 +131,7 @@ func (tr MatrixTransform) Inverse() MatrixTransform { (tr[1]*tr[4] - tr[0]*tr[5]) / d} } -// Multiply Compose Matrix tr1 with tr2 returns the resulting matrix +// Multiply composes Matrix tr1 with tr2 returns the resulting matrix func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform { return [6]float64{ tr1[0]*tr2[0] + tr1[1]*tr2[2], @@ -142,7 +142,7 @@ func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform { tr1[5]*tr2[3] + tr1[4]*tr2[1] + tr2[5]} } -// Scale add a scale to the matrix +// Scale adds a scale to the matrix func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform { tr[0] = sx * tr[0] tr[1] = sx * tr[1] @@ -151,14 +151,14 @@ func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform { return tr } -// Translate add a translation to the matrix +// Translate adds a translation to the matrix func (tr *MatrixTransform) Translate(tx, ty float64) *MatrixTransform { tr[4] = tx*tr[0] + ty*tr[2] + tr[4] tr[5] = ty*tr[3] + tx*tr[1] + tr[5] return tr } -// Rotate add a rotation to the matrix. angle is in radian +// Rotate adds a rotation to the matrix. angle is in radian func (tr *MatrixTransform) Rotate(angle float64) *MatrixTransform { c := math.Cos(angle) s := math.Sin(angle) @@ -190,30 +190,9 @@ func (tr MatrixTransform) GetScale() float64 { return math.Sqrt(x*x + y*y) } -func (tr MatrixTransform) GetMaxAbsScaling() (s float64) { - sx := math.Abs(tr[0]) - sy := math.Abs(tr[3]) - if sx > sy { - return sx - } - return sy -} - -func (tr MatrixTransform) GetMinAbsScaling() (s float64) { - sx := math.Abs(tr[0]) - sy := math.Abs(tr[3]) - if sx > sy { - return sy - } - return sx -} - // ******************** Testing ******************** -/** - * Tests if a two transformation are equal. A tolerance is applied when - * comparing matrix elements. - */ +// Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements. func (tr1 MatrixTransform) Equals(tr2 MatrixTransform) bool { for i := 0; i < 6; i = i + 1 { if !fequals(tr1[i], tr2[i]) { @@ -223,26 +202,17 @@ func (tr1 MatrixTransform) Equals(tr2 MatrixTransform) bool { return true } -/** - * Tests if a transformation is the identity transformation. A tolerance - * is applied when comparing matrix elements. - */ +// IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements. func (tr MatrixTransform) IsIdentity() bool { return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation() } -/** - * Tests if a transformation is is a pure translation. A tolerance - * is applied when comparing matrix elements. - */ +// IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements. func (tr MatrixTransform) IsTranslation() bool { return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1) } -/** - * Compares two floats. - * return true if the distance between the two floats is less than epsilon, false otherwise - */ +// fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise func fequals(float1, float2 float64) bool { return math.Abs(float1-float2) <= epsilon } From 0345095002ab8213ad6d1a11836ef13ee5e6d34c Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Wed, 29 Apr 2015 22:06:59 +0200 Subject: [PATCH 30/39] Starts a non Contextual Graphics API --- gc.go | 23 ------------ graphics.go | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 graphics.go diff --git a/gc.go b/gc.go index a4e671c..ee3dcb4 100644 --- a/gc.go +++ b/gc.go @@ -8,29 +8,6 @@ import ( "image/color" ) -type FillRule int - -const ( - FillRuleEvenOdd FillRule = iota - FillRuleWinding -) - -type LineCap int - -const ( - RoundCap LineCap = iota - ButtCap - SquareCap -) - -type LineJoin int - -const ( - BevelJoin LineJoin = iota - RoundJoin - MiterJoin -) - type GraphicContext interface { PathBuilder // BeginPath creates a new path diff --git a/graphics.go b/graphics.go new file mode 100644 index 0000000..c8387e9 --- /dev/null +++ b/graphics.go @@ -0,0 +1,103 @@ +package draw2d + +import ( + "image/color" +) + +// FillRule defines the fill rule used when fill +type FillRule int + +const ( + // FillRuleEvenOdd determines the "insideness" of a point in the shape + // by drawing a ray from that point to infinity in any direction + // and counting the number of path segments from the given shape that the ray crosses. + // If this number is odd, the point is inside; if even, the point is outside. + FillRuleEvenOdd FillRule = iota + // FillRuleWinding determines the "insideness" of a point in the shape + // by drawing a ray from that point to infinity in any direction + // and then examining the places where a segment of the shape crosses the ray. + // Starting with a count of zero, add one each time a path segment crosses + // the ray from left to right and subtract one each time + // a path segment crosses the ray from right to left. After counting the crossings, + // if the result is zero then the point is outside the path. Otherwise, it is inside. + FillRuleWinding +) + +// LineCap is the style of line extremities +type LineCap int + +const ( + // RoundCap defines a rounded shape at the end of the line + RoundCap LineCap = iota + // ButtCap defines a squared shape exactly at the end of the line + ButtCap + // SquareCap defines a squared shape at the end of the line + SquareCap +) + +// LineJoin is the style of segments joint +type LineJoin int + +const ( + // BevelJoin represents cut segments joint + BevelJoin LineJoin = iota + // RoundJoin represents rounded segments joint + RoundJoin + // MiterJoin represents peaker segments joint + MiterJoin +) + +// StrokeStyle keeps stroke style attributes +// that is used by the Stroke method of a Drawer +type StrokeStyle struct { + // Color defines the color of stroke + Color color.Color + // Line width + Width float64 + // Line cap style rounded, butt or square + LineCap LineCap + // Line join style bevel, round or miter + LineJoin LineJoin + // offset of the first dash + dashOffset float64 + // array represented dash length pair values are plain dash and impair are space between dash + // if empty display plain line + dash []float64 +} + +// FillStyle +type FillStyle struct { +} + +// SolidFillStyle define style attributes for a solid fill style +type SolidFillStyle struct { + FillStyle + // Color defines the line color + Color color.Color + // FillRule defines the file rule to used + FillRule FillRule +} + +type VerticalAlign int + +type HorizontalAlign int + +// TextStyle +type TextStyle struct { + // Color defines the color of text + Color color.Color + // The font to use + Font FontData + // Horizontal Alignment of the text + HorizontalAlign HorizontalAlign + // Vertical Alignment of the text + VerticalAlign VerticalAlign +} + +// Drawer can fill and stroke a path +type Drawer interface { + setMatrix(MatrixTransform) + Fill(FillStyle, Path) + Stroke(StrokeStyle, Path) + Text(TextStyle, text string, x, y float64) +} From 383fef0d7dc3598cce4ffc8401161efcbfc1760e Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 30 Apr 2015 12:20:22 +0200 Subject: [PATCH 31/39] clean Api --- graphics.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/graphics.go b/graphics.go index c8387e9..47117f5 100644 --- a/graphics.go +++ b/graphics.go @@ -78,26 +78,84 @@ type SolidFillStyle struct { FillRule FillRule } -type VerticalAlign int +// Vertical Alignment of the text +type Valign int -type HorizontalAlign int +const ( + ValignTop Valign = iota + ValignTopCenter + ValignTopBottom + ValignTopBaseline +) + +// Horizontal Alignment of the text +type Halign int + +const ( + HalignLeft = iota + HalignCenter + HalignRight +) + +type ScalingPolicy int + +const ( + // ScalingNone no scaling applied + ScalingNone = iota + // ScalingStretch the image is stretched so that its width and height are exactly the given width and height + ScalingStretch + // ScalingWidth the image is scaled so that its width is exactly the given width + ScalingWidth + // ScalingHeight the image is scaled so that its height is exactly the given height + ScalingHeight + // ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height + ScalingFit + // ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height + ScalingSameArea + // ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height + ScalingFill +) // TextStyle type TextStyle struct { // Color defines the color of text Color color.Color + // Size font size + Size float64 // The font to use Font FontData // Horizontal Alignment of the text - HorizontalAlign HorizontalAlign + Halign Halign // Vertical Alignment of the text - VerticalAlign VerticalAlign + Valign Valign +} + +// ImageStyle style attributes used to display the image +type ImageStyle struct { + // Horizontal Alignment of the image + Halign Halign + // Vertical Alignment of the image + Valign Valign + // Width Height used by scaling policy + Width, Height float64 + // ScalingPolicy defines the scaling policy to applied to the image + ScalingPolicy ScalingPolicy +} + +// Style defines properties that +type Style struct { + MatrixTransform MatrixTransform + StrokeStyle StrokeStyle + FillStyle FillStyle + TextStyle TextStyle + ImageStyle TextStyle } // Drawer can fill and stroke a path type Drawer interface { - setMatrix(MatrixTransform) - Fill(FillStyle, Path) - Stroke(StrokeStyle, Path) - Text(TextStyle, text string, x, y float64) + Style() *Style + Fill(Path) + Stroke(Path) + Text(text string, x, y float64) + Image(image image.Image, x, y float64) } From 72643a28b20825dcbd4a51b305a94a59ce42980c Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 30 Apr 2015 14:11:23 +0200 Subject: [PATCH 32/39] Rename MatrixTransform to Matrix --- draw2dbase/flattener.go | 2 +- draw2dbase/stack_gc.go | 16 +-- draw2dimg/rgba_interpolation.go | 2 +- gc.go | 6 +- graphics.go | 13 +- matrix.go | 222 ++++++++++++++++++++++++++++++++ 6 files changed, 242 insertions(+), 19 deletions(-) create mode 100644 matrix.go diff --git a/draw2dbase/flattener.go b/draw2dbase/flattener.go index 63c3823..70ec305 100644 --- a/draw2dbase/flattener.go +++ b/draw2dbase/flattener.go @@ -67,7 +67,7 @@ func Flatten(path *draw2d.Path, flattener Flattener, scale float64) { // Transformer apply the Matrix transformation tr type Transformer struct { - Tr draw2d.MatrixTransform + Tr draw2d.Matrix Flattener Flattener } diff --git a/draw2dbase/stack_gc.go b/draw2dbase/stack_gc.go index 6170d42..27327ae 100644 --- a/draw2dbase/stack_gc.go +++ b/draw2dbase/stack_gc.go @@ -18,7 +18,7 @@ type StackGraphicContext struct { } type ContextStack struct { - Tr draw2d.MatrixTransform + Tr draw2d.Matrix Path *draw2d.Path LineWidth float64 Dash []float64 @@ -58,28 +58,28 @@ func NewStackGraphicContext() *StackGraphicContext { return gc } -func (gc *StackGraphicContext) GetMatrixTransform() draw2d.MatrixTransform { +func (gc *StackGraphicContext) GetMatrixTransform() draw2d.Matrix { return gc.Current.Tr } -func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.MatrixTransform) { +func (gc *StackGraphicContext) SetMatrixTransform(Tr draw2d.Matrix) { gc.Current.Tr = Tr } -func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.MatrixTransform) { - gc.Current.Tr = Tr.Multiply(gc.Current.Tr) +func (gc *StackGraphicContext) ComposeMatrixTransform(Tr draw2d.Matrix) { + gc.Current.Tr.Compose(Tr) } func (gc *StackGraphicContext) Rotate(angle float64) { - gc.Current.Tr = draw2d.NewRotationMatrix(angle).Multiply(gc.Current.Tr) + gc.Current.Tr.Rotate(angle) } func (gc *StackGraphicContext) Translate(tx, ty float64) { - gc.Current.Tr = draw2d.NewTranslationMatrix(tx, ty).Multiply(gc.Current.Tr) + gc.Current.Tr.Translate(tx, ty) } func (gc *StackGraphicContext) Scale(sx, sy float64) { - gc.Current.Tr = draw2d.NewScaleMatrix(sx, sy).Multiply(gc.Current.Tr) + gc.Current.Tr.Scale(sx, sy) } func (gc *StackGraphicContext) SetStrokeColor(c color.Color) { diff --git a/draw2dimg/rgba_interpolation.go b/draw2dimg/rgba_interpolation.go index 3b5fd66..8c2b399 100644 --- a/draw2dimg/rgba_interpolation.go +++ b/draw2dimg/rgba_interpolation.go @@ -105,7 +105,7 @@ func cubic(offset, v0, v1, v2, v3 float64) uint32 { (-9*v0+9*v2))*offset + (v0 + 16*v1 + v2)) / 18.0) } -func DrawImage(src image.Image, dest draw.Image, tr draw2d.MatrixTransform, op draw.Op, filter ImageFilter) { +func DrawImage(src image.Image, dest draw.Image, tr draw2d.Matrix, op draw.Op, filter ImageFilter) { bounds := src.Bounds() x0, y0, x1, y1 := tr.TransformRectangle(float64(bounds.Min.X), float64(bounds.Min.Y), float64(bounds.Max.X), float64(bounds.Max.Y)) var x, y, u, v float64 diff --git a/gc.go b/gc.go index ee3dcb4..22e89d2 100644 --- a/gc.go +++ b/gc.go @@ -13,11 +13,11 @@ type GraphicContext interface { // BeginPath creates a new path BeginPath() // GetMatrixTransform returns the current transformation matrix - GetMatrixTransform() MatrixTransform + GetMatrixTransform() Matrix // SetMatrixTransform sets the current transformation matrix - SetMatrixTransform(tr MatrixTransform) + SetMatrixTransform(tr Matrix) // ComposeMatrixTransform composes the current transformation matrix with tr - ComposeMatrixTransform(tr MatrixTransform) + ComposeMatrixTransform(tr Matrix) // Rotate applies a rotation to the current transformation matrix. angle is in radian. Rotate(angle float64) // Translate applies a translation to the current transformation matrix. diff --git a/graphics.go b/graphics.go index 47117f5..48ca305 100644 --- a/graphics.go +++ b/graphics.go @@ -1,6 +1,7 @@ package draw2d import ( + "image" "image/color" ) @@ -101,7 +102,7 @@ type ScalingPolicy int const ( // ScalingNone no scaling applied - ScalingNone = iota + ScalingNone ScalingPolicy = iota // ScalingStretch the image is stretched so that its width and height are exactly the given width and height ScalingStretch // ScalingWidth the image is scaled so that its width is exactly the given width @@ -144,11 +145,11 @@ type ImageStyle struct { // Style defines properties that type Style struct { - MatrixTransform MatrixTransform - StrokeStyle StrokeStyle - FillStyle FillStyle - TextStyle TextStyle - ImageStyle TextStyle + Matrix Matrix + StrokeStyle StrokeStyle + FillStyle FillStyle + TextStyle TextStyle + ImageStyle TextStyle } // Drawer can fill and stroke a path diff --git a/matrix.go b/matrix.go new file mode 100644 index 0000000..55f5e38 --- /dev/null +++ b/matrix.go @@ -0,0 +1,222 @@ +// Copyright 2010 The draw2d Authors. All rights reserved. +// created: 21/11/2010 by Laurent Le Goff + +package draw2d + +import ( + "math" +) + +// Matrix represents an affine transformation +type Matrix [6]float64 + +const ( + epsilon = 1e-6 +) + +// Determinant compute the determinant of the matrix +func (tr Matrix) Determinant() float64 { + return tr[0]*tr[3] - tr[1]*tr[2] +} + +// Transform applies the transformation matrix to points. It modify the points passed in parameter. +func (tr Matrix) Transform(points []float64) { + for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { + x := points[i] + y := points[j] + points[i] = x*tr[0] + y*tr[2] + tr[4] + points[j] = x*tr[1] + y*tr[3] + tr[5] + } +} + +// TransformPoint applies the transformation matrix to point. It returns the point the transformed point. +func (tr Matrix) TransformPoint(x, y float64) (xres, yres float64) { + xres = x*tr[0] + y*tr[2] + tr[4] + yres = x*tr[1] + y*tr[3] + tr[5] + return xres, yres +} + +func minMax(x, y float64) (min, max float64) { + if x > y { + return y, x + } + return x, y +} + +// Transform applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle +func (tr Matrix) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) { + points := []float64{x0, y0, x2, y0, x2, y2, x0, y2} + tr.Transform(points) + points[0], points[2] = minMax(points[0], points[2]) + points[4], points[6] = minMax(points[4], points[6]) + points[1], points[3] = minMax(points[1], points[3]) + points[5], points[7] = minMax(points[5], points[7]) + + nx0 = math.Min(points[0], points[4]) + ny0 = math.Min(points[1], points[5]) + nx2 = math.Max(points[2], points[6]) + ny2 = math.Max(points[3], points[7]) + return nx0, ny0, nx2, ny2 +} + +// InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle +func (tr Matrix) InverseTransform(points []float64) { + d := tr.Determinant() // matrix determinant + for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { + x := points[i] + y := points[j] + points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d + points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d + } +} + +// InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point. +func (tr Matrix) InverseTransformPoint(x, y float64) (xres, yres float64) { + d := tr.Determinant() // matrix determinant + xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d + yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d + return xres, yres +} + +// VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix. +// It modify the points passed in parameter. +func (tr Matrix) VectorTransform(points []float64) { + for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { + x := points[i] + y := points[j] + points[i] = x*tr[0] + y*tr[2] + points[j] = x*tr[1] + y*tr[3] + } +} + +// NewIdentityMatrix creates an identity transformation matrix. +func NewIdentityMatrix() Matrix { + return Matrix{1, 0, 0, 1, 0, 0} +} + +// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter +func NewTranslationMatrix(tx, ty float64) Matrix { + return Matrix{1, 0, 0, 1, tx, ty} +} + +// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor +func NewScaleMatrix(sx, sy float64) Matrix { + return Matrix{sx, 0, 0, sy, 0, 0} +} + +// NewRotationMatrix creates a rotation transformation matrix. angle is in radian +func NewRotationMatrix(angle float64) Matrix { + c := math.Cos(angle) + s := math.Sin(angle) + return Matrix{c, s, -s, c, 0, 0} +} + +// NewMatrixFromRects creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2. +func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) Matrix { + xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0]) + yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1]) + xOffset := rectangle2[0] - (rectangle1[0] * xScale) + yOffset := rectangle2[1] - (rectangle1[1] * yScale) + return Matrix{xScale, 0, 0, yScale, xOffset, yOffset} +} + +// Inverse computes the inverse matrix +func (tr *Matrix) Inverse() { + d := tr.Determinant() // matrix determinant + tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5] + tr[0] = tr3 / d + tr[1] = -tr1 / d + tr[2] = -tr2 / d + tr[3] = tr0 / d + tr[4] = (tr2*tr5 - tr3*tr4) / d + tr[5] = (tr1*tr4 - tr0*tr5) / d +} + +func (tr Matrix) Copy() Matrix { + var result Matrix + copy(result[:], tr[:]) + return result +} + +// Compose multiplies trToConcat x tr +func (tr *Matrix) Compose(trToCompose Matrix) { + tr0, tr1, tr2, tr3, tr4, tr5 := tr[0], tr[1], tr[2], tr[3], tr[4], tr[5] + tr[0] = trToCompose[0]*tr0 + trToCompose[1]*tr2 + tr[1] = trToCompose[1]*tr3 + trToCompose[0]*tr1 + tr[2] = trToCompose[2]*tr0 + trToCompose[3]*tr2 + tr[3] = trToCompose[3]*tr3 + trToCompose[2]*tr1 + tr[4] = trToCompose[4]*tr0 + trToCompose[5]*tr2 + tr4 + tr[5] = trToCompose[5]*tr3 + trToCompose[4]*tr1 + tr5 +} + +// Scale adds a scale to the matrix +func (tr *Matrix) Scale(sx, sy float64) { + tr[0] = sx * tr[0] + tr[1] = sx * tr[1] + tr[2] = sy * tr[2] + tr[3] = sy * tr[3] +} + +// Translate adds a translation to the matrix +func (tr *Matrix) Translate(tx, ty float64) { + tr[4] = tx*tr[0] + ty*tr[2] + tr[4] + tr[5] = ty*tr[3] + tx*tr[1] + tr[5] +} + +// Rotate adds a rotation to the matrix. angle is in radian +func (tr *Matrix) Rotate(angle float64) { + c := math.Cos(angle) + s := math.Sin(angle) + t0 := c*tr[0] + s*tr[2] + t1 := s*tr[3] + c*tr[1] + t2 := c*tr[2] - s*tr[0] + t3 := c*tr[3] - s*tr[1] + tr[0] = t0 + tr[1] = t1 + tr[2] = t2 + tr[3] = t3 +} + +// GetTranslation +func (tr Matrix) GetTranslation() (x, y float64) { + return tr[4], tr[5] +} + +// GetScaling +func (tr Matrix) GetScaling() (x, y float64) { + return tr[0], tr[3] +} + +// GetScale computes a scale for the matrix +func (tr Matrix) GetScale() float64 { + x := 0.707106781*tr[0] + 0.707106781*tr[1] + y := 0.707106781*tr[2] + 0.707106781*tr[3] + return math.Sqrt(x*x + y*y) +} + +// ******************** Testing ******************** + +// Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements. +func (tr1 Matrix) Equals(tr2 Matrix) bool { + for i := 0; i < 6; i = i + 1 { + if !fequals(tr1[i], tr2[i]) { + return false + } + } + return true +} + +// IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements. +func (tr Matrix) IsIdentity() bool { + return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation() +} + +// IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements. +func (tr Matrix) IsTranslation() bool { + return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1) +} + +// fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise +func fequals(float1, float2 float64) bool { + return math.Abs(float1-float2) <= epsilon +} From f2563306e4182daaa78b60f297f31cbe5b91e119 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 30 Apr 2015 16:27:23 +0200 Subject: [PATCH 33/39] Start implementing drawer --- draw2d.go | 150 ++++++++++++++++++++++++++++++ draw2dimg/drawer.go | 79 ++++++++++++++++ draw2dkit/droid.go | 74 +++++++++------ graphics.go | 162 -------------------------------- transform.go | 218 -------------------------------------------- 5 files changed, 277 insertions(+), 406 deletions(-) create mode 100644 draw2dimg/drawer.go delete mode 100644 graphics.go delete mode 100644 transform.go diff --git a/draw2d.go b/draw2d.go index 424abf4..94c8983 100644 --- a/draw2d.go +++ b/draw2d.go @@ -3,3 +3,153 @@ // Package draw2d provides a Graphic Context that can draw vector form on canvas. package draw2d + +import ( + "image" + "image/color" +) + +// FillRule defines the fill rule used when fill +type FillRule int + +const ( + // FillRuleEvenOdd determines the "insideness" of a point in the shape + // by drawing a ray from that point to infinity in any direction + // and counting the number of path segments from the given shape that the ray crosses. + // If this number is odd, the point is inside; if even, the point is outside. + FillRuleEvenOdd FillRule = iota + // FillRuleWinding determines the "insideness" of a point in the shape + // by drawing a ray from that point to infinity in any direction + // and then examining the places where a segment of the shape crosses the ray. + // Starting with a count of zero, add one each time a path segment crosses + // the ray from left to right and subtract one each time + // a path segment crosses the ray from right to left. After counting the crossings, + // if the result is zero then the point is outside the path. Otherwise, it is inside. + FillRuleWinding +) + +// LineCap is the style of line extremities +type LineCap int + +const ( + // RoundCap defines a rounded shape at the end of the line + RoundCap LineCap = iota + // ButtCap defines a squared shape exactly at the end of the line + ButtCap + // SquareCap defines a squared shape at the end of the line + SquareCap +) + +// LineJoin is the style of segments joint +type LineJoin int + +const ( + // BevelJoin represents cut segments joint + BevelJoin LineJoin = iota + // RoundJoin represents rounded segments joint + RoundJoin + // MiterJoin represents peaker segments joint + MiterJoin +) + +// StrokeStyle keeps stroke style attributes +// that is used by the Stroke method of a Drawer +type StrokeStyle struct { + // Color defines the color of stroke + Color color.Color + // Line width + Width float64 + // Line cap style rounded, butt or square + LineCap LineCap + // Line join style bevel, round or miter + LineJoin LineJoin + // offset of the first dash + DashOffset float64 + // array represented dash length pair values are plain dash and impair are space between dash + // if empty display plain line + Dash []float64 +} + +type FillStyle interface { +} + +// SolidFillStyle define style attributes for a solid fill style +type SolidFillStyle struct { + // Color defines the line color + Color color.Color + // FillRule defines the file rule to used + FillRule FillRule +} + +// Vertical Alignment of the text +type Valign int + +const ( + ValignTop Valign = iota + ValignTopCenter + ValignTopBottom + ValignTopBaseline +) + +// Horizontal Alignment of the text +type Halign int + +const ( + HalignLeft = iota + HalignCenter + HalignRight +) + +// TextStyle +type TextStyle struct { + // Color defines the color of text + Color color.Color + // Size font size + Size float64 + // The font to use + Font FontData + // Horizontal Alignment of the text + Halign Halign + // Vertical Alignment of the text + Valign Valign +} + +type ScalingPolicy int + +const ( + // ScalingNone no scaling applied + ScalingNone ScalingPolicy = iota + // ScalingStretch the image is stretched so that its width and height are exactly the given width and height + ScalingStretch + // ScalingWidth the image is scaled so that its width is exactly the given width + ScalingWidth + // ScalingHeight the image is scaled so that its height is exactly the given height + ScalingHeight + // ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height + ScalingFit + // ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height + ScalingSameArea + // ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height + ScalingFill +) + +// ImageScaling style attributes used to display the image +type ImageScaling struct { + // Horizontal Alignment of the image + Halign Halign + // Vertical Alignment of the image + Valign Valign + // Width Height used by scaling policy + Width, Height float64 + // ScalingPolicy defines the scaling policy to applied to the image + ScalingPolicy ScalingPolicy +} + +// Drawer can fill and stroke a path +type Drawer interface { + Matrix() *Matrix + Fill(path *Path, style FillStyle) + Stroke(path *Path, style StrokeStyle) + Text(text string, x, y float64, style TextStyle) + Image(image image.Image, x, y float64, scaling ImageScaling) +} diff --git a/draw2dimg/drawer.go b/draw2dimg/drawer.go new file mode 100644 index 0000000..a2a8b25 --- /dev/null +++ b/draw2dimg/drawer.go @@ -0,0 +1,79 @@ +package draw2dimg + +import ( + "code.google.com/p/freetype-go/freetype/raster" + "code.google.com/p/freetype-go/freetype/truetype" + "github.com/llgcode/draw2d" + "github.com/llgcode/draw2d/draw2dbase" + "image" + "image/draw" +) + +type Drawer struct { + matrix draw2d.Matrix + img draw.Image + painter Painter + fillRasterizer *raster.Rasterizer + strokeRasterizer *raster.Rasterizer + glyphBuf *truetype.GlyphBuf +} + +func NewDrawer(img *image.RGBA) *Drawer { + width, height := img.Bounds().Dx(), img.Bounds().Dy() + return &Drawer{ + draw2d.NewIdentityMatrix(), + img, + raster.NewRGBAPainter(img), + raster.NewRasterizer(width, height), + raster.NewRasterizer(width, height), + truetype.NewGlyphBuf(), + } +} + +func (d *Drawer) Matrix() *draw2d.Matrix { + return d.Matrix() +} + +func (d *Drawer) Fill(path *draw2d.Path, style draw2d.FillStyle) { + switch fillStyle := style.(type) { + case draw2d.SolidFillStyle: + d.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(fillStyle.FillRule) + d.painter.SetColor(fillStyle.Color) + default: + panic("FillStyle not supported") + } + + flattener := draw2dbase.Transformer{d.matrix, draw2dbase.FtLineBuilder{d.fillRasterizer}} + + draw2dbase.Flatten(path, flattener, d.matrix.GetScale()) + + d.fillRasterizer.Rasterize(d.painter) + d.fillRasterizer.Clear() +} + +func (d *Drawer) Stroke(path *draw2d.Path, style draw2d.StrokeStyle) { + d.strokeRasterizer.UseNonZeroWinding = true + + stroker := draw2dbase.NewLineStroker(style.LineCap, style.LineJoin, draw2dbase.Transformer{d.matrix, draw2dbase.FtLineBuilder{d.strokeRasterizer}}) + stroker.HalfLineWidth = style.Width / 2 + + var liner draw2dbase.Flattener + if style.Dash != nil && len(style.Dash) > 0 { + liner = draw2dbase.NewDashConverter(style.Dash, style.DashOffset, stroker) + } else { + liner = stroker + } + + draw2dbase.Flatten(path, liner, d.matrix.GetScale()) + + d.painter.SetColor(style.Color) + d.strokeRasterizer.Rasterize(d.painter) + d.strokeRasterizer.Clear() +} + +func (d *Drawer) Text(text string, x, y float64, style draw2d.TextStyle) { + +} + +func (d *Drawer) Image(image image.Image, x, y float64, scaling draw2d.ImageScaling) { +} diff --git a/draw2dkit/droid.go b/draw2dkit/droid.go index 0adfe42..79edaf5 100644 --- a/draw2dkit/droid.go +++ b/draw2dkit/droid.go @@ -6,46 +6,68 @@ import ( ) // Droid draws a droid at specified position -func Droid(gc draw2d.GraphicContext, x, y float64) { - gc.SetLineCap(draw2d.RoundCap) - gc.SetLineWidth(5) +func Droid(drawer draw2d.Drawer, x, y float64, fillStyle draw2d.FillStyle, strokeStyle draw2d.StrokeStyle) { + strokeStyle.LineCap = draw2d.RoundCap + strokeStyle.Width = 5 + + path := &draw2d.Path{} // head - gc.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) - gc.FillStroke() - gc.MoveTo(x+60, y+25) - gc.LineTo(x+50, y+10) - gc.MoveTo(x+100, y+25) - gc.LineTo(x+110, y+10) - gc.Stroke() + path.ArcTo(x+80, y+70, 50, 50, 180*(math.Pi/180), 360*(math.Pi/180)) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) + + path.Clear() + path.MoveTo(x+60, y+25) + path.LineTo(x+50, y+10) + path.MoveTo(x+100, y+25) + path.LineTo(x+110, y+10) + drawer.Stroke(path, strokeStyle) // left eye - Circle(gc, x+60, y+45, 5) - gc.FillStroke() + path.Clear() + Circle(path, x+60, y+45, 5) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // right eye - Circle(gc, x+100, y+45, 5) - gc.FillStroke() + path.Clear() + Circle(path, x+100, y+45, 5) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // body - RoundedRectangle(gc, x+30, y+75, x+30+100, y+75+90, 10, 10) - gc.FillStroke() - Rectangle(gc, x+30, y+75, x+30+100, y+75+80) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+30, y+75, x+30+100, y+75+90, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) + + path.Clear() + Rectangle(path, x+30, y+75, x+30+100, y+75+80) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // left arm - RoundedRectangle(gc, x+5, y+80, x+5+20, y+80+70, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+5, y+80, x+5+20, y+80+70, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // right arm - RoundedRectangle(gc, x+135, y+80, x+135+20, y+80+70, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+135, y+80, x+135+20, y+80+70, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // left leg - RoundedRectangle(gc, x+50, y+150, x+50+20, y+150+50, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+50, y+150, x+50+20, y+150+50, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) // right leg - RoundedRectangle(gc, x+90, y+150, x+90+20, y+150+50, 10, 10) - gc.FillStroke() + path.Clear() + RoundedRectangle(path, x+90, y+150, x+90+20, y+150+50, 10, 10) + drawer.Fill(path, fillStyle) + drawer.Stroke(path, strokeStyle) } diff --git a/graphics.go b/graphics.go deleted file mode 100644 index 48ca305..0000000 --- a/graphics.go +++ /dev/null @@ -1,162 +0,0 @@ -package draw2d - -import ( - "image" - "image/color" -) - -// FillRule defines the fill rule used when fill -type FillRule int - -const ( - // FillRuleEvenOdd determines the "insideness" of a point in the shape - // by drawing a ray from that point to infinity in any direction - // and counting the number of path segments from the given shape that the ray crosses. - // If this number is odd, the point is inside; if even, the point is outside. - FillRuleEvenOdd FillRule = iota - // FillRuleWinding determines the "insideness" of a point in the shape - // by drawing a ray from that point to infinity in any direction - // and then examining the places where a segment of the shape crosses the ray. - // Starting with a count of zero, add one each time a path segment crosses - // the ray from left to right and subtract one each time - // a path segment crosses the ray from right to left. After counting the crossings, - // if the result is zero then the point is outside the path. Otherwise, it is inside. - FillRuleWinding -) - -// LineCap is the style of line extremities -type LineCap int - -const ( - // RoundCap defines a rounded shape at the end of the line - RoundCap LineCap = iota - // ButtCap defines a squared shape exactly at the end of the line - ButtCap - // SquareCap defines a squared shape at the end of the line - SquareCap -) - -// LineJoin is the style of segments joint -type LineJoin int - -const ( - // BevelJoin represents cut segments joint - BevelJoin LineJoin = iota - // RoundJoin represents rounded segments joint - RoundJoin - // MiterJoin represents peaker segments joint - MiterJoin -) - -// StrokeStyle keeps stroke style attributes -// that is used by the Stroke method of a Drawer -type StrokeStyle struct { - // Color defines the color of stroke - Color color.Color - // Line width - Width float64 - // Line cap style rounded, butt or square - LineCap LineCap - // Line join style bevel, round or miter - LineJoin LineJoin - // offset of the first dash - dashOffset float64 - // array represented dash length pair values are plain dash and impair are space between dash - // if empty display plain line - dash []float64 -} - -// FillStyle -type FillStyle struct { -} - -// SolidFillStyle define style attributes for a solid fill style -type SolidFillStyle struct { - FillStyle - // Color defines the line color - Color color.Color - // FillRule defines the file rule to used - FillRule FillRule -} - -// Vertical Alignment of the text -type Valign int - -const ( - ValignTop Valign = iota - ValignTopCenter - ValignTopBottom - ValignTopBaseline -) - -// Horizontal Alignment of the text -type Halign int - -const ( - HalignLeft = iota - HalignCenter - HalignRight -) - -type ScalingPolicy int - -const ( - // ScalingNone no scaling applied - ScalingNone ScalingPolicy = iota - // ScalingStretch the image is stretched so that its width and height are exactly the given width and height - ScalingStretch - // ScalingWidth the image is scaled so that its width is exactly the given width - ScalingWidth - // ScalingHeight the image is scaled so that its height is exactly the given height - ScalingHeight - // ScalingFit the image is scaled to the largest scale that allow the image to fit within a rectangle width x height - ScalingFit - // ScalingSameArea the image is scaled so that its area is exactly the area of the given rectangle width x height - ScalingSameArea - // ScalingFill the image is scaled to the smallest scale that allow the image to fully cover a rectangle width x height - ScalingFill -) - -// TextStyle -type TextStyle struct { - // Color defines the color of text - Color color.Color - // Size font size - Size float64 - // The font to use - Font FontData - // Horizontal Alignment of the text - Halign Halign - // Vertical Alignment of the text - Valign Valign -} - -// ImageStyle style attributes used to display the image -type ImageStyle struct { - // Horizontal Alignment of the image - Halign Halign - // Vertical Alignment of the image - Valign Valign - // Width Height used by scaling policy - Width, Height float64 - // ScalingPolicy defines the scaling policy to applied to the image - ScalingPolicy ScalingPolicy -} - -// Style defines properties that -type Style struct { - Matrix Matrix - StrokeStyle StrokeStyle - FillStyle FillStyle - TextStyle TextStyle - ImageStyle TextStyle -} - -// Drawer can fill and stroke a path -type Drawer interface { - Style() *Style - Fill(Path) - Stroke(Path) - Text(text string, x, y float64) - Image(image image.Image, x, y float64) -} diff --git a/transform.go b/transform.go deleted file mode 100644 index a648d9e..0000000 --- a/transform.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2010 The draw2d Authors. All rights reserved. -// created: 21/11/2010 by Laurent Le Goff - -package draw2d - -import ( - "math" -) - -type MatrixTransform [6]float64 - -const ( - epsilon = 1e-6 -) - -// Determinant compute the determinant of the matrix -func (tr MatrixTransform) Determinant() float64 { - return tr[0]*tr[3] - tr[1]*tr[2] -} - -// Transform applies the transformation matrix to points. It modify the points passed in parameter. -func (tr MatrixTransform) Transform(points []float64) { - for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { - x := points[i] - y := points[j] - points[i] = x*tr[0] + y*tr[2] + tr[4] - points[j] = x*tr[1] + y*tr[3] + tr[5] - } -} - -// TransformPoint applies the transformation matrix to point. It returns the point the transformed point. -func (tr MatrixTransform) TransformPoint(x, y float64) (xres, yres float64) { - xres = x*tr[0] + y*tr[2] + tr[4] - yres = x*tr[1] + y*tr[3] + tr[5] - return xres, yres -} - -func minMax(x, y float64) (min, max float64) { - if x > y { - return y, x - } - return x, y -} - -// Transform applies the transformation matrix to the rectangle represented by the min and the max point of the rectangle -func (tr MatrixTransform) TransformRectangle(x0, y0, x2, y2 float64) (nx0, ny0, nx2, ny2 float64) { - points := []float64{x0, y0, x2, y0, x2, y2, x0, y2} - tr.Transform(points) - points[0], points[2] = minMax(points[0], points[2]) - points[4], points[6] = minMax(points[4], points[6]) - points[1], points[3] = minMax(points[1], points[3]) - points[5], points[7] = minMax(points[5], points[7]) - - nx0 = math.Min(points[0], points[4]) - ny0 = math.Min(points[1], points[5]) - nx2 = math.Max(points[2], points[6]) - ny2 = math.Max(points[3], points[7]) - return nx0, ny0, nx2, ny2 -} - -// InverseTransform applies the transformation inverse matrix to the rectangle represented by the min and the max point of the rectangle -func (tr MatrixTransform) InverseTransform(points []float64) { - d := tr.Determinant() // matrix determinant - for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { - x := points[i] - y := points[j] - points[i] = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d - points[j] = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d - } -} - -// InverseTransformPoint applies the transformation inverse matrix to point. It returns the point the transformed point. -func (tr MatrixTransform) InverseTransformPoint(x, y float64) (xres, yres float64) { - d := tr.Determinant() // matrix determinant - xres = ((x-tr[4])*tr[3] - (y-tr[5])*tr[2]) / d - yres = ((y-tr[5])*tr[0] - (x-tr[4])*tr[1]) / d - return xres, yres -} - -// VectorTransform applies the transformation matrix to points without using the translation parameter of the affine matrix. -// It modify the points passed in parameter. -func (tr MatrixTransform) VectorTransform(points []float64) { - for i, j := 0, 1; j < len(points); i, j = i+2, j+2 { - x := points[i] - y := points[j] - points[i] = x*tr[0] + y*tr[2] - points[j] = x*tr[1] + y*tr[3] - } -} - -// NewIdentityMatrix creates an identity transformation matrix. -func NewIdentityMatrix() MatrixTransform { - return [6]float64{1, 0, 0, 1, 0, 0} -} - -// NewTranslationMatrix creates a transformation matrix with a translation tx and ty translation parameter -func NewTranslationMatrix(tx, ty float64) MatrixTransform { - return [6]float64{1, 0, 0, 1, tx, ty} -} - -// NewScaleMatrix creates a transformation matrix with a sx, sy scale factor -func NewScaleMatrix(sx, sy float64) MatrixTransform { - return [6]float64{sx, 0, 0, sy, 0, 0} -} - -// NewRotationMatrix creates a rotation transformation matrix. angle is in radian -func NewRotationMatrix(angle float64) MatrixTransform { - c := math.Cos(angle) - s := math.Sin(angle) - return [6]float64{c, s, -s, c, 0, 0} -} - -// NewMatrixTransform creates a transformation matrix, combining a scale and a translation, that transform rectangle1 into rectangle2. -func NewMatrixFromRects(rectangle1, rectangle2 [4]float64) MatrixTransform { - xScale := (rectangle2[2] - rectangle2[0]) / (rectangle1[2] - rectangle1[0]) - yScale := (rectangle2[3] - rectangle2[1]) / (rectangle1[3] - rectangle1[1]) - xOffset := rectangle2[0] - (rectangle1[0] * xScale) - yOffset := rectangle2[1] - (rectangle1[1] * yScale) - return [6]float64{xScale, 0, 0, yScale, xOffset, yOffset} -} - -// Inverse returns a matrix that is the inverse of the given matrix. -func (tr MatrixTransform) Inverse() MatrixTransform { - d := tr.Determinant() // matrix determinant - return [6]float64{ - tr[3] / d, - -tr[1] / d, - -tr[2] / d, - tr[0] / d, - (tr[2]*tr[5] - tr[3]*tr[4]) / d, - (tr[1]*tr[4] - tr[0]*tr[5]) / d} -} - -// Multiply composes Matrix tr1 with tr2 returns the resulting matrix -func (tr1 MatrixTransform) Multiply(tr2 MatrixTransform) MatrixTransform { - return [6]float64{ - tr1[0]*tr2[0] + tr1[1]*tr2[2], - tr1[1]*tr2[3] + tr1[0]*tr2[1], - tr1[2]*tr2[0] + tr1[3]*tr2[2], - tr1[3]*tr2[3] + tr1[2]*tr2[1], - tr1[4]*tr2[0] + tr1[5]*tr2[2] + tr2[4], - tr1[5]*tr2[3] + tr1[4]*tr2[1] + tr2[5]} -} - -// Scale adds a scale to the matrix -func (tr *MatrixTransform) Scale(sx, sy float64) *MatrixTransform { - tr[0] = sx * tr[0] - tr[1] = sx * tr[1] - tr[2] = sy * tr[2] - tr[3] = sy * tr[3] - return tr -} - -// Translate adds a translation to the matrix -func (tr *MatrixTransform) Translate(tx, ty float64) *MatrixTransform { - tr[4] = tx*tr[0] + ty*tr[2] + tr[4] - tr[5] = ty*tr[3] + tx*tr[1] + tr[5] - return tr -} - -// Rotate adds a rotation to the matrix. angle is in radian -func (tr *MatrixTransform) Rotate(angle float64) *MatrixTransform { - c := math.Cos(angle) - s := math.Sin(angle) - t0 := c*tr[0] + s*tr[2] - t1 := s*tr[3] + c*tr[1] - t2 := c*tr[2] - s*tr[0] - t3 := c*tr[3] - s*tr[1] - tr[0] = t0 - tr[1] = t1 - tr[2] = t2 - tr[3] = t3 - return tr -} - -// GetTranslation -func (tr MatrixTransform) GetTranslation() (x, y float64) { - return tr[4], tr[5] -} - -// GetScaling -func (tr MatrixTransform) GetScaling() (x, y float64) { - return tr[0], tr[3] -} - -// GetScale computes the scale of the matrix -func (tr MatrixTransform) GetScale() float64 { - x := 0.707106781*tr[0] + 0.707106781*tr[1] - y := 0.707106781*tr[2] + 0.707106781*tr[3] - return math.Sqrt(x*x + y*y) -} - -// ******************** Testing ******************** - -// Equals tests if a two transformation are equal. A tolerance is applied when comparing matrix elements. -func (tr1 MatrixTransform) Equals(tr2 MatrixTransform) bool { - for i := 0; i < 6; i = i + 1 { - if !fequals(tr1[i], tr2[i]) { - return false - } - } - return true -} - -// IsIdentity tests if a transformation is the identity transformation. A tolerance is applied when comparing matrix elements. -func (tr MatrixTransform) IsIdentity() bool { - return fequals(tr[4], 0) && fequals(tr[5], 0) && tr.IsTranslation() -} - -// IsTranslation tests if a transformation is is a pure translation. A tolerance is applied when comparing matrix elements. -func (tr MatrixTransform) IsTranslation() bool { - return fequals(tr[0], 1) && fequals(tr[1], 0) && fequals(tr[2], 0) && fequals(tr[3], 1) -} - -// fequals compares two floats. return true if the distance between the two floats is less than epsilon, false otherwise -func fequals(float1, float2 float64) bool { - return math.Abs(float1-float2) <= epsilon -} From f6e1ada0f2bffb80ca79a5585b290697224f0fd2 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 30 Apr 2015 16:30:50 +0200 Subject: [PATCH 34/39] Fix bug --- draw2dimg/drawer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draw2dimg/drawer.go b/draw2dimg/drawer.go index a2a8b25..7e06b4c 100644 --- a/draw2dimg/drawer.go +++ b/draw2dimg/drawer.go @@ -31,7 +31,7 @@ func NewDrawer(img *image.RGBA) *Drawer { } func (d *Drawer) Matrix() *draw2d.Matrix { - return d.Matrix() + return &d.matrix } func (d *Drawer) Fill(path *draw2d.Path, style draw2d.FillStyle) { From b14683a5523c4dd25d71ed97ab263799c64114c4 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Thu, 30 Apr 2015 18:06:25 +0200 Subject: [PATCH 35/39] Start working on text --- draw2dimg/ftgc.go | 46 +-------------------------- draw2dimg/text.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 draw2dimg/text.go diff --git a/draw2dimg/ftgc.go b/draw2dimg/ftgc.go index 2e0da66..d7f668e 100644 --- a/draw2dimg/ftgc.go +++ b/draw2dimg/ftgc.go @@ -113,53 +113,9 @@ func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) { return font, nil } -func fUnitsToFloat64(x int32) float64 { - scaled := x << 2 - return float64(scaled/256) + float64(scaled%256)/256.0 -} - // p is a truetype.Point measured in FUnits and positive Y going upwards. // The returned value is the same thing measured in floating point and positive Y // going downwards. -func pointToF64Point(p truetype.Point) (x, y float64) { - return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y) -} - -// drawContour draws the given closed contour at the given sub-pixel offset. -func (gc *GraphicContext) drawContour(ps []truetype.Point, dx, dy float64) { - if len(ps) == 0 { - return - } - startX, startY := pointToF64Point(ps[0]) - gc.MoveTo(startX+dx, startY+dy) - q0X, q0Y, on0 := startX, startY, true - for _, p := range ps[1:] { - qX, qY := pointToF64Point(p) - on := p.Flags&0x01 != 0 - if on { - if on0 { - gc.LineTo(qX+dx, qY+dy) - } else { - gc.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy) - } - } else { - if on0 { - // No-op. - } else { - midX := (q0X + qX) / 2 - midY := (q0Y + qY) / 2 - gc.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy) - } - } - q0X, q0Y, on0 = qX, qY, on - } - // Close the curve. - if on0 { - gc.LineTo(startX+dx, startY+dy) - } else { - gc.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy) - } -} func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { if err := gc.glyphBuf.Load(gc.Current.Font, gc.Current.Scale, glyph, truetype.NoHinting); err != nil { @@ -167,7 +123,7 @@ func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error } e0 := 0 for _, e1 := range gc.glyphBuf.End { - gc.drawContour(gc.glyphBuf.Point[e0:e1], dx, dy) + DrawContour(gc, gc.glyphBuf.Point[e0:e1], dx, dy) e0 = e1 } return nil diff --git a/draw2dimg/text.go b/draw2dimg/text.go new file mode 100644 index 0000000..c858c1d --- /dev/null +++ b/draw2dimg/text.go @@ -0,0 +1,80 @@ +package draw2dimg + +import ( + "code.google.com/p/freetype-go/freetype/truetype" + "github.com/llgcode/draw2d" +) + +// drawContour draws the given closed contour at the given sub-pixel offset. +func DrawContour(path draw2d.PathBuilder, ps []truetype.Point, dx, dy float64) { + if len(ps) == 0 { + return + } + startX, startY := pointToF64Point(ps[0]) + path.MoveTo(startX+dx, startY+dy) + q0X, q0Y, on0 := startX, startY, true + for _, p := range ps[1:] { + qX, qY := pointToF64Point(p) + on := p.Flags&0x01 != 0 + if on { + if on0 { + path.LineTo(qX+dx, qY+dy) + } else { + path.QuadCurveTo(q0X+dx, q0Y+dy, qX+dx, qY+dy) + } + } else { + if on0 { + // No-op. + } else { + midX := (q0X + qX) / 2 + midY := (q0Y + qY) / 2 + path.QuadCurveTo(q0X+dx, q0Y+dy, midX+dx, midY+dy) + } + } + q0X, q0Y, on0 = qX, qY, on + } + // Close the curve. + if on0 { + path.LineTo(startX+dx, startY+dy) + } else { + path.QuadCurveTo(q0X+dx, q0Y+dy, startX+dx, startY+dy) + } +} + +func pointToF64Point(p truetype.Point) (x, y float64) { + return fUnitsToFloat64(p.X), -fUnitsToFloat64(p.Y) +} + +func fUnitsToFloat64(x int32) float64 { + scaled := x << 2 + return float64(scaled/256) + float64(scaled%256)/256.0 +} + +// FontExtents contains font metric information. +type FontExtents struct { + // Ascent is the distance that the text + // extends above the baseline. + Ascent float64 + + // Descent is the distance that the text + // extends below the baseline. The descent + // is given as a negative value. + Descent float64 + + // Height is the distance from the lowest + // descending point to the highest ascending + // point. + Height float64 +} + +// TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro +// Extents returns the FontExtents for a font. +func Extents(font *truetype.Font, size float64) FontExtents { + bounds := font.Bounds(font.FUnitsPerEm()) + scale := size / float64(font.FUnitsPerEm()) + return FontExtents{ + Ascent: float64(bounds.YMax) * scale, + Descent: float64(bounds.YMin) * scale, + Height: float64(bounds.YMax-bounds.YMin) * scale, + } +} From 9b55e34990af6032ccd3f38f1d2271aee22a1f52 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Fri, 14 Aug 2015 22:00:33 +0200 Subject: [PATCH 36/39] Change file permission --- test | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 test diff --git a/test b/test old mode 100644 new mode 100755 From 3b19ab855ee3e5eefe99d3ddcbad56e526fd1898 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Fri, 14 Aug 2015 22:07:02 +0200 Subject: [PATCH 37/39] use key fields --- draw2dbase/ftpath.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/draw2dbase/ftpath.go b/draw2dbase/ftpath.go index 8e5601c..50aab78 100644 --- a/draw2dbase/ftpath.go +++ b/draw2dbase/ftpath.go @@ -12,11 +12,11 @@ type FtLineBuilder struct { } func (liner FtLineBuilder) MoveTo(x, y float64) { - liner.Adder.Start(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) + liner.Adder.Start(raster.Point{X: raster.Fix32(x * 256), Y: raster.Fix32(y * 256)}) } func (liner FtLineBuilder) LineTo(x, y float64) { - liner.Adder.Add1(raster.Point{raster.Fix32(x * 256), raster.Fix32(y * 256)}) + liner.Adder.Add1(raster.Point{X: raster.Fix32(x * 256), Y: raster.Fix32(y * 256)}) } func (liner FtLineBuilder) LineJoin() { From 1e0467b8fc7b7b5b157b00170b1100675cb0a946 Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Fri, 14 Aug 2015 22:22:01 +0200 Subject: [PATCH 38/39] move freetype dependency to draw2dimg --- draw2dbase/stroker.go | 26 ++----------------------- draw2dgl/gc.go | 9 +++++---- draw2dimg/ftgc.go | 30 +++++++++++++++++++++++++---- {draw2dbase => draw2dimg}/ftpath.go | 2 +- 4 files changed, 34 insertions(+), 33 deletions(-) rename {draw2dbase => draw2dimg}/ftpath.go (96%) diff --git a/draw2dbase/stroker.go b/draw2dbase/stroker.go index 4d9be86..640b228 100644 --- a/draw2dbase/stroker.go +++ b/draw2dbase/stroker.go @@ -4,33 +4,11 @@ package draw2dbase import ( - "code.google.com/p/freetype-go/freetype/raster" - "github.com/llgcode/draw2d" "math" + + "github.com/llgcode/draw2d" ) -func toFtCap(c draw2d.LineCap) raster.Capper { - switch c { - case draw2d.RoundCap: - return raster.RoundCapper - case draw2d.ButtCap: - return raster.ButtCapper - case draw2d.SquareCap: - return raster.SquareCapper - } - return raster.RoundCapper -} - -func toFtJoin(j draw2d.LineJoin) raster.Joiner { - switch j { - case draw2d.RoundJoin: - return raster.RoundJoiner - case draw2d.BevelJoin: - return raster.BevelJoiner - } - return raster.RoundJoiner -} - type LineStroker struct { Flattener Flattener HalfLineWidth float64 diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index d9a055c..d47ff0e 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -10,6 +10,7 @@ import ( "github.com/go-gl/gl/v2.1/gl" "github.com/llgcode/draw2d" "github.com/llgcode/draw2d/draw2dbase" + "github.com/llgcode/draw2d/draw2dimg" ) func init() { @@ -188,7 +189,7 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 var liner draw2dbase.Flattener @@ -209,7 +210,7 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) /**** first method ****/ - flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} + flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}} for _, p := range paths { draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale()) } @@ -222,9 +223,9 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) gc.strokeRasterizer.UseNonZeroWinding = true - flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.fillRasterizer}} + flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dimg.FtLineBuilder{gc.fillRasterizer}} - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dbase.FtLineBuilder{gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dimg.FtLineBuilder{gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 var liner draw2dbase.Flattener diff --git a/draw2dimg/ftgc.go b/draw2dimg/ftgc.go index 415e3d4..2e8c52d 100644 --- a/draw2dimg/ftgc.go +++ b/draw2dimg/ftgc.go @@ -244,7 +244,7 @@ func (gc *GraphicContext) Stroke(paths ...*draw2d.Path) { paths = append(paths, gc.Current.Path) gc.strokeRasterizer.UseNonZeroWinding = true - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 var liner draw2dbase.Flattener @@ -266,7 +266,7 @@ func (gc *GraphicContext) Fill(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding /**** first method ****/ - flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.fillRasterizer}} + flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}} for _, p := range paths { draw2dbase.Flatten(p, flattener, gc.Current.Tr.GetScale()) } @@ -280,9 +280,9 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = gc.Current.FillRule == draw2d.FillRuleWinding gc.strokeRasterizer.UseNonZeroWinding = true - flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.fillRasterizer}} + flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.fillRasterizer}} - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dbase.FtLineBuilder{Adder: gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: FtLineBuilder{Adder: gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 var liner draw2dbase.Flattener @@ -302,3 +302,25 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { // Stroke gc.paint(gc.strokeRasterizer, gc.Current.StrokeColor) } + +func toFtCap(c draw2d.LineCap) raster.Capper { + switch c { + case draw2d.RoundCap: + return raster.RoundCapper + case draw2d.ButtCap: + return raster.ButtCapper + case draw2d.SquareCap: + return raster.SquareCapper + } + return raster.RoundCapper +} + +func toFtJoin(j draw2d.LineJoin) raster.Joiner { + switch j { + case draw2d.RoundJoin: + return raster.RoundJoiner + case draw2d.BevelJoin: + return raster.BevelJoiner + } + return raster.RoundJoiner +} diff --git a/draw2dbase/ftpath.go b/draw2dimg/ftpath.go similarity index 96% rename from draw2dbase/ftpath.go rename to draw2dimg/ftpath.go index 50aab78..ce6c422 100644 --- a/draw2dbase/ftpath.go +++ b/draw2dimg/ftpath.go @@ -1,7 +1,7 @@ // Copyright 2010 The draw2d Authors. All rights reserved. // created: 13/12/2010 by Laurent Le Goff -package draw2dbase +package draw2dimg import ( "code.google.com/p/freetype-go/freetype/raster" From 7e94968f5ec157f6b99f6968f770e6864d1d25ef Mon Sep 17 00:00:00 2001 From: Laurent Le Goff Date: Fri, 14 Aug 2015 22:38:18 +0200 Subject: [PATCH 39/39] Correct some golint --- draw2d.go | 17 +++++++++++++---- draw2dbase/curve_test.go | 10 +++++----- draw2dbase/stack_gc.go | 2 +- draw2dgl/gc.go | 6 +++--- samples/helloworld/helloworld.go | 2 +- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/draw2d.go b/draw2d.go index 25b0d12..d71158a 100644 --- a/draw2d.go +++ b/draw2d.go @@ -150,6 +150,7 @@ type StrokeStyle struct { Dash []float64 } +// FillStyle is an empty interface you may want to use SolidFillStyle type FillStyle interface { } @@ -165,22 +166,29 @@ type SolidFillStyle struct { type Valign int const ( + // ValignTop top align text ValignTop Valign = iota - ValignTopCenter - ValignTopBottom - ValignTopBaseline + // ValignCenter centered text + ValignCenter + // ValignBottom bottom aligned text + ValignBottom + // ValignBaseline align text with the baseline of the font + ValignBaseline ) // Halign Horizontal Alignment of the text type Halign int const ( + // HalignLeft Horizontally align to left HalignLeft = iota + // HalignCenter Horizontally align to center HalignCenter + // HalignRight Horizontally align to right HalignRight ) -// TextStyle +// TextStyle describe text property type TextStyle struct { // Color defines the color of text Color color.Color @@ -194,6 +202,7 @@ type TextStyle struct { Valign Valign } +// ScalingPolicy is a constant to define how to scale an image type ScalingPolicy int const ( diff --git a/draw2dbase/curve_test.go b/draw2dbase/curve_test.go index 750521a..d04b9fb 100644 --- a/draw2dbase/curve_test.go +++ b/draw2dbase/curve_test.go @@ -15,8 +15,8 @@ import ( ) var ( - flattening_threshold float64 = 0.5 - testsCubicFloat64 = []float64{ + flatteningThreshold = 0.5 + testsCubicFloat64 = []float64{ 100, 100, 200, 100, 100, 200, 200, 200, 100, 100, 300, 200, 200, 200, 300, 100, 100, 100, 0, 300, 200, 0, 300, 300, @@ -75,7 +75,7 @@ func TestCubicCurve(t *testing.T) { for i := 0; i < len(testsCubicFloat64); i += 8 { var p SegmentedPath p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) - TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) + TraceCubic(&p, testsCubicFloat64[i:], flatteningThreshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsCubicFloat64[i:i+8]...) raster.PolylineBresenham(img, image.Black, p.Points...) @@ -91,7 +91,7 @@ func TestQuadCurve(t *testing.T) { for i := 0; i < len(testsQuadFloat64); i += 6 { var p SegmentedPath p.MoveTo(testsQuadFloat64[i], testsQuadFloat64[i+1]) - TraceQuad(&p, testsQuadFloat64[i:], flattening_threshold) + TraceQuad(&p, testsQuadFloat64[i:], flatteningThreshold) img := image.NewNRGBA(image.Rect(0, 0, 300, 300)) raster.PolylineBresenham(img, color.NRGBA{0xff, 0, 0, 0xff}, testsQuadFloat64[i:i+6]...) raster.PolylineBresenham(img, image.Black, p.Points...) @@ -108,7 +108,7 @@ func BenchmarkCubicCurve(b *testing.B) { for i := 0; i < len(testsCubicFloat64); i += 8 { var p SegmentedPath p.MoveTo(testsCubicFloat64[i], testsCubicFloat64[i+1]) - TraceCubic(&p, testsCubicFloat64[i:], flattening_threshold) + TraceCubic(&p, testsCubicFloat64[i:], flatteningThreshold) } } } diff --git a/draw2dbase/stack_gc.go b/draw2dbase/stack_gc.go index b2a56c7..ff8f4f8 100644 --- a/draw2dbase/stack_gc.go +++ b/draw2dbase/stack_gc.go @@ -12,7 +12,7 @@ import ( "code.google.com/p/freetype-go/freetype/truetype" ) -var DefaultFontData = draw2d.FontData{"luxi", draw2d.FontFamilySans, draw2d.FontStyleNormal} +var DefaultFontData = draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilySans, Style: draw2d.FontStyleNormal} type StackGraphicContext struct { Current *ContextStack diff --git a/draw2dgl/gc.go b/draw2dgl/gc.go index d47ff0e..b738a5e 100644 --- a/draw2dgl/gc.go +++ b/draw2dgl/gc.go @@ -223,9 +223,9 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { gc.fillRasterizer.UseNonZeroWinding = useNonZeroWinding(gc.Current.FillRule) gc.strokeRasterizer.UseNonZeroWinding = true - flattener := draw2dbase.Transformer{gc.Current.Tr, draw2dimg.FtLineBuilder{gc.fillRasterizer}} + flattener := draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.fillRasterizer}} - stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{gc.Current.Tr, draw2dimg.FtLineBuilder{gc.strokeRasterizer}}) + stroker := draw2dbase.NewLineStroker(gc.Current.Cap, gc.Current.Join, draw2dbase.Transformer{Tr: gc.Current.Tr, Flattener: draw2dimg.FtLineBuilder{Adder: gc.strokeRasterizer}}) stroker.HalfLineWidth = gc.Current.LineWidth / 2 var liner draw2dbase.Flattener @@ -235,7 +235,7 @@ func (gc *GraphicContext) FillStroke(paths ...*draw2d.Path) { liner = stroker } - demux := draw2dbase.DemuxFlattener{[]draw2dbase.Flattener{flattener, liner}} + demux := draw2dbase.DemuxFlattener{Flatteners: []draw2dbase.Flattener{flattener, liner}} for _, p := range paths { draw2dbase.Flatten(p, demux, gc.Current.Tr.GetScale()) } diff --git a/samples/helloworld/helloworld.go b/samples/helloworld/helloworld.go index f9bf410..af5b47e 100644 --- a/samples/helloworld/helloworld.go +++ b/samples/helloworld/helloworld.go @@ -31,7 +31,7 @@ func Draw(gc draw2d.GraphicContext, text string) { gc.FillStroke() // Set the font luximbi.ttf - gc.SetFontData(draw2d.FontData{"luxi", draw2d.FontFamilyMono, draw2d.FontStyleBold | draw2d.FontStyleItalic}) + gc.SetFontData(draw2d.FontData{Name: "luxi", Family: draw2d.FontFamilyMono, Style: draw2d.FontStyleBold | draw2d.FontStyleItalic}) // Set the fill text color to black gc.SetFillColor(image.Black) gc.SetFontSize(14)