From 3656fd8617ac6f3ba19a32771e060781ad25e5c8 Mon Sep 17 00:00:00 2001 From: Samuel Hornus Date: Tue, 11 May 2021 23:42:02 +0200 Subject: [PATCH 1/5] Initial support for sweep gradients in Cairo and CoreGraphics backends --- Lib/blackrenderer/backends/base.py | 54 ++++++++++++++++++++++ Lib/blackrenderer/backends/cairo.py | 30 ++++++++++-- Lib/blackrenderer/backends/coregraphics.py | 27 ++++++++++- Tests/test_canvas_api.py | 20 ++++---- 4 files changed, 117 insertions(+), 14 deletions(-) diff --git a/Lib/blackrenderer/backends/base.py b/Lib/blackrenderer/backends/base.py index eb7ce02..3d6b063 100644 --- a/Lib/blackrenderer/backends/base.py +++ b/Lib/blackrenderer/backends/base.py @@ -1,6 +1,8 @@ from abc import ABC, abstractmethod from contextlib import contextmanager +from math import pi, ceil, sin, cos +from fontTools.misc.vector import Vector class Canvas(ABC): @abstractmethod @@ -94,6 +96,58 @@ def _rectPath(self, rect): path.closePath() return path + def _buildSweepGradientPatches( + self, + colorLine, + center, + radius, + startAngle, + endAngle, + useGouraudShading, + ): + patches = [] + # generate a fan of 'triangular' bezier patches, with center 'center' and radius 'radius' + degToRad = pi/180.0 + if useGouraudShading: + maxAngle = pi/360.0 + radius = 1.05 * radius # we will use straight-edged triangles + else: + maxAngle = pi/10.0 + n = len(colorLine) + center = Vector(center) + for i in range(n-1): + a0, col0 = colorLine[i+0] + a1, col1 = colorLine[i+1] + col0 = Vector(col0) + col1 = Vector(col1) + a0 = degToRad * (startAngle + a0 * (endAngle - startAngle)) + a1 = degToRad * (startAngle + a1 * (endAngle - startAngle)) + numSplits = int(ceil((a1 - a0) / maxAngle)) + p0 = Vector((cos(a0), sin(a0))) + color0 = col0 + for a in range(numSplits): + k = ((a + 1.0) / numSplits) + angle1 = a0 + k * (a1 - a0) + color1 = col0 + k * (col1 - col0) + p1 = Vector((cos(angle1), sin(angle1))) + P0 = center[0] + radius * p0[0], center[1] + radius * p0[1] + P1 = center[0] + radius * p1[0], center[1] + radius * p1[1] + # draw patch + if useGouraudShading: + patches.append(((P0, color0), (P1, color1))) + else: + # compute cubic Bezier antennas (control points) so as to approximate the circular arc p0-p1 + A = (p0 + p1).normalized() + U = Vector((-A[1], A[0])) # tangent to circle at A + C0 = A + ((p0 - A).dot(p0) / U.dot(p0)) * U + C1 = A + ((p1 - A).dot(p1) / U.dot(p1)) * U + C0 = center + radius * (C0 + 0.33333 * (C0 - p0)) + C1 = center + radius * (C1 + 0.33333 * (C1 - p1)) + patches.append(((P0, color0), C0, C1, (P1, color1))) + # move to next patch + p0 = p1 + color0 = color1 + return patches class Surface(ABC): fileExtension = ".png" diff --git a/Lib/blackrenderer/backends/cairo.py b/Lib/blackrenderer/backends/cairo.py index 1383105..176627e 100644 --- a/Lib/blackrenderer/backends/cairo.py +++ b/Lib/blackrenderer/backends/cairo.py @@ -5,7 +5,7 @@ from fontTools.ttLib.tables.otTables import ExtendMode import cairo from .base import Canvas, Surface - +from math import sqrt _extendModeMap = { ExtendMode.PAD: cairo.Extend.PAD, @@ -106,9 +106,30 @@ def drawPathSweepGradient( extendMode, gradientTransform, ): - self.drawPathSolid(path, colorLine[0][1]) - - # TODO: blendMode for PaintComposite + # alloc the mesh pattern + pat = cairo.MeshPattern() + # find current path' extent + x1, y1, x2, y2 = self.context.clip_extents() + maxX = max(d * d for d in (x1 - center[0], x2 - center[0])) + maxY = max(d * d for d in (y1 - center[1], y2 - center[1])) + R = sqrt(maxX + maxY) + patches = self._buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=False) + for (P0, color0), C0, C1, (P1, color1) in patches: + # draw patch + pat.begin_patch() + pat.move_to(center[0], center[1]) + pat.line_to(P0[0], P0[1]) + pat.curve_to(C0[0], C0[1], C1[0], C1[1], P1[0], P1[1]) + pat.line_to(center[0], center[1]) + pat.set_corner_color_rgba(0, *color0) + pat.set_corner_color_rgba(1, *color0) + pat.set_corner_color_rgba(2, *color1) + pat.set_corner_color_rgba(3, *color1) + pat.end_patch() + self.context.set_source(pat) + self.context.paint() + + # TODO: blendMode for PaintComposite) def _drawGradient(self, path, gradient, gradientTransform): self.context.new_path() @@ -119,7 +140,6 @@ def _drawGradient(self, path, gradient, gradientTransform): self.context.fill() self.context.restore() - class CairoPixelSurface(Surface): fileExtension = ".png" diff --git a/Lib/blackrenderer/backends/coregraphics.py b/Lib/blackrenderer/backends/coregraphics.py index 08cf220..16c50a7 100644 --- a/Lib/blackrenderer/backends/coregraphics.py +++ b/Lib/blackrenderer/backends/coregraphics.py @@ -125,7 +125,32 @@ def drawPathSweepGradient( extendMode, gradientTransform, ): - self.drawPathSolid(path, colorLine[0][1]) + from math import sqrt + if self.clipIsEmpty or CG.CGPathGetBoundingBox(path.path) == CG.CGRectNull: + return + with self.savedState(): + CG.CGContextAddPath(self.context, path.path) + CG.CGContextClip(self.context) + self.transform(gradientTransform) + # find current path' extent + bb = CG.CGContextGetClipBoundingBox(self.context) + x1, y1 = bb.origin.x, bb.origin.y + x2 = x1 + bb.size.width + y2 = y1 + bb.size.height + maxX = max(d * d for d in (x1 - center[0], x2 - center[0])) + maxY = max(d * d for d in (y1 - center[1], y2 - center[1])) + R = sqrt(maxX + maxY) + # compute the triangle fan approximating the sweep gradient + patches = self._buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=True) + CG.CGContextSetAllowsAntialiasing(self.context, False) + for (P0, color0), (P1, color1) in patches: + color = 0.5 * (color0 + color1) + CG.CGContextMoveToPoint(self.context, center[0], center[1]) + CG.CGContextAddLineToPoint(self.context, P0[0], P0[1]) + CG.CGContextAddLineToPoint(self.context, P1[0], P1[1]) + CG.CGContextSetRGBFillColor(self.context, *color) + CG.CGContextFillPath(self.context) + CG.CGContextSetAllowsAntialiasing(self.context, True) # TODO: blendMode for PaintComposite diff --git a/Tests/test_canvas_api.py b/Tests/test_canvas_api.py index bacf86e..4212b1f 100644 --- a/Tests/test_canvas_api.py +++ b/Tests/test_canvas_api.py @@ -54,18 +54,22 @@ def test_colorStops(backendName, surfaceFactory, stopOffsets, extend): @pytest.mark.parametrize("extend", test_extendModes) @pytest.mark.parametrize("backendName, surfaceFactory", backends) def test_sweepGradient(backendName, surfaceFactory, extend): - surface = surfaceFactory(0, 0, 200, 200) + H, W = 400, 400 + surface = surfaceFactory(0, 0, H, W) canvas = surface.canvas - center = (100, 100) + center = (H / 2, W / 2) startAngle = 45 endAngle = 315 - color1 = (1, 0, 0, 1) - color2 = (0, 0, 1, 1) - stopOffsets = [0, 1] - stop1, stop2 = stopOffsets - colorLine = [(stop1, color1), (stop2, color2)] + colors = [ + (1, 0, 0, 1), + (0, 1, 0, 1), + (1, 1, 0, 1), + (0, 0, 1, 1), + ] + stopOffsets = [0, 0.5, 0.6, 1] + colorLine = list(zip(stopOffsets, colors)) canvas.drawRectSweepGradient( - (0, 0, 200, 200), colorLine, center, startAngle, endAngle, extend, Identity + (0, 0, H, W), colorLine, center, startAngle, endAngle, extend, Identity ) ext = surface.fileExtension From 989bd65bfc4a647825a9e88e3202f4422829b118 Mon Sep 17 00:00:00 2001 From: Samuel Hornus Date: Wed, 12 May 2021 10:19:09 +0200 Subject: [PATCH 2/5] move sweep gradient support code to separate file. --- Lib/blackrenderer/backends/base.py | 56 -------------------- Lib/blackrenderer/backends/cairo.py | 5 +- Lib/blackrenderer/backends/coregraphics.py | 4 +- Lib/blackrenderer/backends/sweepGradient.py | 58 +++++++++++++++++++++ 4 files changed, 63 insertions(+), 60 deletions(-) create mode 100644 Lib/blackrenderer/backends/sweepGradient.py diff --git a/Lib/blackrenderer/backends/base.py b/Lib/blackrenderer/backends/base.py index 3d6b063..9dc95ee 100644 --- a/Lib/blackrenderer/backends/base.py +++ b/Lib/blackrenderer/backends/base.py @@ -1,9 +1,6 @@ from abc import ABC, abstractmethod from contextlib import contextmanager -from math import pi, ceil, sin, cos -from fontTools.misc.vector import Vector - class Canvas(ABC): @abstractmethod def __init__(self, context): @@ -96,59 +93,6 @@ def _rectPath(self, rect): path.closePath() return path - def _buildSweepGradientPatches( - self, - colorLine, - center, - radius, - startAngle, - endAngle, - useGouraudShading, - ): - patches = [] - # generate a fan of 'triangular' bezier patches, with center 'center' and radius 'radius' - degToRad = pi/180.0 - if useGouraudShading: - maxAngle = pi/360.0 - radius = 1.05 * radius # we will use straight-edged triangles - else: - maxAngle = pi/10.0 - n = len(colorLine) - center = Vector(center) - for i in range(n-1): - a0, col0 = colorLine[i+0] - a1, col1 = colorLine[i+1] - col0 = Vector(col0) - col1 = Vector(col1) - a0 = degToRad * (startAngle + a0 * (endAngle - startAngle)) - a1 = degToRad * (startAngle + a1 * (endAngle - startAngle)) - numSplits = int(ceil((a1 - a0) / maxAngle)) - p0 = Vector((cos(a0), sin(a0))) - color0 = col0 - for a in range(numSplits): - k = ((a + 1.0) / numSplits) - angle1 = a0 + k * (a1 - a0) - color1 = col0 + k * (col1 - col0) - p1 = Vector((cos(angle1), sin(angle1))) - P0 = center[0] + radius * p0[0], center[1] + radius * p0[1] - P1 = center[0] + radius * p1[0], center[1] + radius * p1[1] - # draw patch - if useGouraudShading: - patches.append(((P0, color0), (P1, color1))) - else: - # compute cubic Bezier antennas (control points) so as to approximate the circular arc p0-p1 - A = (p0 + p1).normalized() - U = Vector((-A[1], A[0])) # tangent to circle at A - C0 = A + ((p0 - A).dot(p0) / U.dot(p0)) * U - C1 = A + ((p1 - A).dot(p1) / U.dot(p1)) * U - C0 = center + radius * (C0 + 0.33333 * (C0 - p0)) - C1 = center + radius * (C1 + 0.33333 * (C1 - p1)) - patches.append(((P0, color0), C0, C1, (P1, color1))) - # move to next patch - p0 = p1 - color0 = color1 - return patches - class Surface(ABC): fileExtension = ".png" diff --git a/Lib/blackrenderer/backends/cairo.py b/Lib/blackrenderer/backends/cairo.py index 176627e..bc36154 100644 --- a/Lib/blackrenderer/backends/cairo.py +++ b/Lib/blackrenderer/backends/cairo.py @@ -1,11 +1,12 @@ from contextlib import contextmanager import os +from math import sqrt from fontTools.pens.basePen import BasePen from fontTools.pens.recordingPen import RecordingPen from fontTools.ttLib.tables.otTables import ExtendMode import cairo from .base import Canvas, Surface -from math import sqrt +from .sweepGradient import buildSweepGradientPatches _extendModeMap = { ExtendMode.PAD: cairo.Extend.PAD, @@ -113,7 +114,7 @@ def drawPathSweepGradient( maxX = max(d * d for d in (x1 - center[0], x2 - center[0])) maxY = max(d * d for d in (y1 - center[1], y2 - center[1])) R = sqrt(maxX + maxY) - patches = self._buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=False) + patches = buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=False) for (P0, color0), C0, C1, (P1, color1) in patches: # draw patch pat.begin_patch() diff --git a/Lib/blackrenderer/backends/coregraphics.py b/Lib/blackrenderer/backends/coregraphics.py index 16c50a7..f706b62 100644 --- a/Lib/blackrenderer/backends/coregraphics.py +++ b/Lib/blackrenderer/backends/coregraphics.py @@ -4,7 +4,7 @@ from fontTools.ttLib.tables.otTables import ExtendMode import Quartz as CG from .base import Canvas, Surface - +from .sweepGradient import buildSweepGradientPatches class CoreGraphicsPathPen(BasePen): def __init__(self): @@ -141,7 +141,7 @@ def drawPathSweepGradient( maxY = max(d * d for d in (y1 - center[1], y2 - center[1])) R = sqrt(maxX + maxY) # compute the triangle fan approximating the sweep gradient - patches = self._buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=True) + patches = buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=True) CG.CGContextSetAllowsAntialiasing(self.context, False) for (P0, color0), (P1, color1) in patches: color = 0.5 * (color0 + color1) diff --git a/Lib/blackrenderer/backends/sweepGradient.py b/Lib/blackrenderer/backends/sweepGradient.py new file mode 100644 index 0000000..563055e --- /dev/null +++ b/Lib/blackrenderer/backends/sweepGradient.py @@ -0,0 +1,58 @@ +from math import pi, ceil, sin, cos +from fontTools.misc.vector import Vector + + +def buildSweepGradientPatches( + colorLine, + center, + radius, + startAngle, + endAngle, + useGouraudShading, +): + """Provides patches of colors to mimic a sweep gradient, for use, in + particular, in the Cairo and CoreGraphics backends, since these libraries + lack the sweep gradient feature.""" + patches = [] + # generate a fan of 'triangular' bezier patches, with center 'center' and radius 'radius' + degToRad = pi/180.0 + if useGouraudShading: + maxAngle = pi/360.0 + radius = 1.05 * radius # we will use straight-edged triangles + else: + maxAngle = pi/10.0 + n = len(colorLine) + center = Vector(center) + for i in range(n-1): + a0, col0 = colorLine[i+0] + a1, col1 = colorLine[i+1] + col0 = Vector(col0) + col1 = Vector(col1) + a0 = degToRad * (startAngle + a0 * (endAngle - startAngle)) + a1 = degToRad * (startAngle + a1 * (endAngle - startAngle)) + numSplits = int(ceil((a1 - a0) / maxAngle)) + p0 = Vector((cos(a0), sin(a0))) + color0 = col0 + for a in range(numSplits): + k = ((a + 1.0) / numSplits) + angle1 = a0 + k * (a1 - a0) + color1 = col0 + k * (col1 - col0) + p1 = Vector((cos(angle1), sin(angle1))) + P0 = center[0] + radius * p0[0], center[1] + radius * p0[1] + P1 = center[0] + radius * p1[0], center[1] + radius * p1[1] + # draw patch + if useGouraudShading: + patches.append(((P0, color0), (P1, color1))) + else: + # compute cubic Bezier antennas (control points) so as to approximate the circular arc p0-p1 + A = (p0 + p1).normalized() + U = Vector((-A[1], A[0])) # tangent to circle at A + C0 = A + ((p0 - A).dot(p0) / U.dot(p0)) * U + C1 = A + ((p1 - A).dot(p1) / U.dot(p1)) * U + C0 = center + radius * (C0 + 0.33333 * (C0 - p0)) + C1 = center + radius * (C1 + 0.33333 * (C1 - p1)) + patches.append(((P0, color0), C0, C1, (P1, color1))) + # move to next patch + p0 = p1 + color0 = color1 + return patches From 2451721197f2493c7b7b9d6c4c268794998c8bb9 Mon Sep 17 00:00:00 2001 From: Samuel Hornus Date: Wed, 12 May 2021 10:24:43 +0200 Subject: [PATCH 3/5] black --- Lib/blackrenderer/backends/base.py | 2 ++ Lib/blackrenderer/backends/cairo.py | 5 ++++- Lib/blackrenderer/backends/coregraphics.py | 6 +++++- Lib/blackrenderer/backends/sweepGradient.py | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Lib/blackrenderer/backends/base.py b/Lib/blackrenderer/backends/base.py index 9dc95ee..eb7ce02 100644 --- a/Lib/blackrenderer/backends/base.py +++ b/Lib/blackrenderer/backends/base.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod from contextlib import contextmanager + class Canvas(ABC): @abstractmethod def __init__(self, context): @@ -93,6 +94,7 @@ def _rectPath(self, rect): path.closePath() return path + class Surface(ABC): fileExtension = ".png" diff --git a/Lib/blackrenderer/backends/cairo.py b/Lib/blackrenderer/backends/cairo.py index bc36154..208cfe4 100644 --- a/Lib/blackrenderer/backends/cairo.py +++ b/Lib/blackrenderer/backends/cairo.py @@ -114,7 +114,9 @@ def drawPathSweepGradient( maxX = max(d * d for d in (x1 - center[0], x2 - center[0])) maxY = max(d * d for d in (y1 - center[1], y2 - center[1])) R = sqrt(maxX + maxY) - patches = buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=False) + patches = buildSweepGradientPatches( + colorLine, center, R, startAngle, endAngle, useGouraudShading=False + ) for (P0, color0), C0, C1, (P1, color1) in patches: # draw patch pat.begin_patch() @@ -141,6 +143,7 @@ def _drawGradient(self, path, gradient, gradientTransform): self.context.fill() self.context.restore() + class CairoPixelSurface(Surface): fileExtension = ".png" diff --git a/Lib/blackrenderer/backends/coregraphics.py b/Lib/blackrenderer/backends/coregraphics.py index f706b62..921fa55 100644 --- a/Lib/blackrenderer/backends/coregraphics.py +++ b/Lib/blackrenderer/backends/coregraphics.py @@ -6,6 +6,7 @@ from .base import Canvas, Surface from .sweepGradient import buildSweepGradientPatches + class CoreGraphicsPathPen(BasePen): def __init__(self): super().__init__(None) @@ -126,6 +127,7 @@ def drawPathSweepGradient( gradientTransform, ): from math import sqrt + if self.clipIsEmpty or CG.CGPathGetBoundingBox(path.path) == CG.CGRectNull: return with self.savedState(): @@ -141,7 +143,9 @@ def drawPathSweepGradient( maxY = max(d * d for d in (y1 - center[1], y2 - center[1])) R = sqrt(maxX + maxY) # compute the triangle fan approximating the sweep gradient - patches = buildSweepGradientPatches(colorLine, center, R, startAngle, endAngle, useGouraudShading=True) + patches = buildSweepGradientPatches( + colorLine, center, R, startAngle, endAngle, useGouraudShading=True + ) CG.CGContextSetAllowsAntialiasing(self.context, False) for (P0, color0), (P1, color1) in patches: color = 0.5 * (color0 + color1) diff --git a/Lib/blackrenderer/backends/sweepGradient.py b/Lib/blackrenderer/backends/sweepGradient.py index 563055e..1eaee1d 100644 --- a/Lib/blackrenderer/backends/sweepGradient.py +++ b/Lib/blackrenderer/backends/sweepGradient.py @@ -10,22 +10,22 @@ def buildSweepGradientPatches( endAngle, useGouraudShading, ): - """Provides patches of colors to mimic a sweep gradient, for use, in + """Provides colorful patches that mimic a sweep gradient, for use, in particular, in the Cairo and CoreGraphics backends, since these libraries lack the sweep gradient feature.""" patches = [] # generate a fan of 'triangular' bezier patches, with center 'center' and radius 'radius' - degToRad = pi/180.0 + degToRad = pi / 180.0 if useGouraudShading: - maxAngle = pi/360.0 - radius = 1.05 * radius # we will use straight-edged triangles + maxAngle = pi / 360.0 + radius = 1.05 * radius # we will use straight-edged triangles else: - maxAngle = pi/10.0 + maxAngle = pi / 10.0 n = len(colorLine) center = Vector(center) - for i in range(n-1): - a0, col0 = colorLine[i+0] - a1, col1 = colorLine[i+1] + for i in range(n - 1): + a0, col0 = colorLine[i + 0] + a1, col1 = colorLine[i + 1] col0 = Vector(col0) col1 = Vector(col1) a0 = degToRad * (startAngle + a0 * (endAngle - startAngle)) @@ -34,7 +34,7 @@ def buildSweepGradientPatches( p0 = Vector((cos(a0), sin(a0))) color0 = col0 for a in range(numSplits): - k = ((a + 1.0) / numSplits) + k = (a + 1.0) / numSplits angle1 = a0 + k * (a1 - a0) color1 = col0 + k * (col1 - col0) p1 = Vector((cos(angle1), sin(angle1))) @@ -46,7 +46,7 @@ def buildSweepGradientPatches( else: # compute cubic Bezier antennas (control points) so as to approximate the circular arc p0-p1 A = (p0 + p1).normalized() - U = Vector((-A[1], A[0])) # tangent to circle at A + U = Vector((-A[1], A[0])) # tangent to circle at A C0 = A + ((p0 - A).dot(p0) / U.dot(p0)) * U C1 = A + ((p1 - A).dot(p1) / U.dot(p1)) * U C0 = center + radius * (C0 + 0.33333 * (C0 - p0)) From bef1ca6f1b655fe51f1bbaea72f5495119f24592 Mon Sep 17 00:00:00 2001 From: Samuel Hornus Date: Wed, 12 May 2021 15:53:40 +0200 Subject: [PATCH 4/5] sweeep gradients: radians() and maxAngle as parameter --- Lib/blackrenderer/backends/sweepGradient.py | 43 ++++++++++++++++----- Tests/test_canvas_api.py | 3 +- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/Lib/blackrenderer/backends/sweepGradient.py b/Lib/blackrenderer/backends/sweepGradient.py index 1eaee1d..ae3d7d4 100644 --- a/Lib/blackrenderer/backends/sweepGradient.py +++ b/Lib/blackrenderer/backends/sweepGradient.py @@ -1,4 +1,4 @@ -from math import pi, ceil, sin, cos +from math import pi, ceil, sin, cos, radians from fontTools.misc.vector import Vector @@ -9,27 +9,50 @@ def buildSweepGradientPatches( startAngle, endAngle, useGouraudShading, + maxAngle=-1, ): - """Provides colorful patches that mimic a sweep gradient, for use, in - particular, in the Cairo and CoreGraphics backends, since these libraries - lack the sweep gradient feature.""" + """Provides colorful triangular patches that mimic a sweep gradient. + + Together the patches approximate an angular section of a disk of center + 'center' and radius 'radius'. + The patches respect the color line provided by 'colorLine'. + The angular section is beetween 'startAngle' and 'endAngle'. + + For use, in particular, in the Cairo and CoreGraphics backends, since these + libraries lack the sweep gradient feature. + + useGouraudShading -- If True, build a lot of skinny triangles, expected to + be constant or Gouraud shaded. If False, build fewer degenerate Coons patches + (outer boundary is rounded), expected to be used in a "mesh gradient" + + Optional keyword arguments: + maxAngle -- largest desired angular extent of a single triangular patch.""" patches = [] # generate a fan of 'triangular' bezier patches, with center 'center' and radius 'radius' degToRad = pi / 180.0 - if useGouraudShading: - maxAngle = pi / 360.0 - radius = 1.05 * radius # we will use straight-edged triangles + if maxAngle == -1: + if useGouraudShading: + maxAngle = pi / 360.0 + else: + maxAngle = pi / 2.0 else: - maxAngle = pi / 10.0 + maxAngle = max(min(maxAngle, pi / 2), pi / 360) + if useGouraudShading: + # Use a slightly larger radius to make sure that disk with the original + # radius completely fits within the straight-edged triangles that we + # will generate + radius = radius / cos(maxAngle/2) n = len(colorLine) center = Vector(center) for i in range(n - 1): a0, col0 = colorLine[i + 0] a1, col1 = colorLine[i + 1] + if a0 == a1: + continue # two equal stopOffset are used to add color discontinuities. Nothing too draw col0 = Vector(col0) col1 = Vector(col1) - a0 = degToRad * (startAngle + a0 * (endAngle - startAngle)) - a1 = degToRad * (startAngle + a1 * (endAngle - startAngle)) + a0 = radians(startAngle + a0 * (endAngle - startAngle)) + a1 = radians(startAngle + a1 * (endAngle - startAngle)) numSplits = int(ceil((a1 - a0) / maxAngle)) p0 = Vector((cos(a0), sin(a0))) color0 = col0 diff --git a/Tests/test_canvas_api.py b/Tests/test_canvas_api.py index 4212b1f..6c06284 100644 --- a/Tests/test_canvas_api.py +++ b/Tests/test_canvas_api.py @@ -64,9 +64,10 @@ def test_sweepGradient(backendName, surfaceFactory, extend): (1, 0, 0, 1), (0, 1, 0, 1), (1, 1, 0, 1), + (1, 0.5, 1, 1), (0, 0, 1, 1), ] - stopOffsets = [0, 0.5, 0.6, 1] + stopOffsets = [0, 0.5, 0.5, 0.6, 1] colorLine = list(zip(stopOffsets, colors)) canvas.drawRectSweepGradient( (0, 0, H, W), colorLine, center, startAngle, endAngle, extend, Identity From 257ee4ae93466abe0c9e7bbce5527e2d647260b4 Mon Sep 17 00:00:00 2001 From: Samuel Hornus Date: Wed, 12 May 2021 16:01:09 +0200 Subject: [PATCH 5/5] typo --- Lib/blackrenderer/backends/sweepGradient.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/blackrenderer/backends/sweepGradient.py b/Lib/blackrenderer/backends/sweepGradient.py index ae3d7d4..adb8223 100644 --- a/Lib/blackrenderer/backends/sweepGradient.py +++ b/Lib/blackrenderer/backends/sweepGradient.py @@ -9,7 +9,7 @@ def buildSweepGradientPatches( startAngle, endAngle, useGouraudShading, - maxAngle=-1, + maxAngle=None, ): """Provides colorful triangular patches that mimic a sweep gradient. @@ -29,19 +29,18 @@ def buildSweepGradientPatches( maxAngle -- largest desired angular extent of a single triangular patch.""" patches = [] # generate a fan of 'triangular' bezier patches, with center 'center' and radius 'radius' - degToRad = pi / 180.0 - if maxAngle == -1: + if maxAngle is None: if useGouraudShading: maxAngle = pi / 360.0 else: - maxAngle = pi / 2.0 + maxAngle = pi / 8.0 else: maxAngle = max(min(maxAngle, pi / 2), pi / 360) if useGouraudShading: # Use a slightly larger radius to make sure that disk with the original # radius completely fits within the straight-edged triangles that we # will generate - radius = radius / cos(maxAngle/2) + radius = radius / cos(maxAngle / 2) n = len(colorLine) center = Vector(center) for i in range(n - 1):