From 5aaf18889c3cdc31ae61b9593bf5848bc57ec09a Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Thu, 9 Jul 2015 03:54:47 -0300 Subject: Initial patch to unify our render towards cairo This branch allows a pure cairo based render for both PNG and SVG. Cairo backend is mostly the same but with improved support for configurable scale, orientation and inverted color drawing. API is not yet final. --- gerber/render/cairo_backend.py | 87 ++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 37 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index fa1aecc..939863b 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -16,53 +16,44 @@ # limitations under the License. from .render import GerberContext -from operator import mul + import cairocffi as cairo + +from operator import mul import math +import tempfile from ..primitives import * -SCALE = 4000. - - class GerberCairoContext(GerberContext): - def __init__(self, surface=None, size=(10000, 10000)): + def __init__(self, scale=300): GerberContext.__init__(self) - if surface is None: - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, - size[0], size[1]) - else: - self.surface = surface + self.scale = (scale, scale) + self.surface = None + self.ctx = None + + def set_bounds(self, bounds): + origin_in_inch = (bounds[0][0], bounds[1][0]) + size_in_inch = (abs(bounds[0][1] - bounds[0][0]), abs(bounds[1][1] - bounds[1][0])) + size_in_pixels = map(mul, size_in_inch, self.scale) + + self.surface_buffer = tempfile.NamedTemporaryFile() + + self.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) self.ctx = cairo.Context(self.surface) - self.size = size - self.ctx.translate(0, self.size[1]) - self.scale = (SCALE,SCALE) + self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) self.ctx.scale(1, -1) - self.apertures = {} - self.background = False - - def set_bounds(self, bounds): - if not self.background: - xbounds, ybounds = bounds - width = SCALE * (xbounds[1] - xbounds[0]) - height = SCALE * (ybounds[1] - ybounds[0]) - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(width), int(height)) - self.ctx = cairo.Context(self.surface) - self.ctx.translate(0, height) - self.scale = (SCALE,SCALE) - self.ctx.scale(1, -1) - self.ctx.rectangle(SCALE * xbounds[0], SCALE * ybounds[0], width, height) - self.ctx.set_source_rgb(0,0,0) - self.ctx.fill() - self.background = True + self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) + # self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), -origin_in_inch[1]*self.scale[1]) def _render_line(self, line, color): start = map(mul, line.start, self.scale) end = map(mul, line.end, self.scale) if isinstance(line.aperture, Circle): - width = line.aperture.diameter if line.aperture.diameter != 0 else 0.001 + width = line.aperture.diameter self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_line_width(width * SCALE) + self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) self.ctx.line_to(*end) @@ -70,6 +61,7 @@ class GerberCairoContext(GerberContext): elif isinstance(line.aperture, Rectangle): points = [tuple(map(mul, x, self.scale)) for x in line.vertices] self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (line.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) for point in points[1:]: @@ -80,12 +72,13 @@ class GerberCairoContext(GerberContext): center = map(mul, arc.center, self.scale) start = map(mul, arc.start, self.scale) end = map(mul, arc.end, self.scale) - radius = SCALE * arc.radius + radius = self.scale * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_line_width(width * SCALE) + self.ctx.set_operator(cairo.OPERATOR_OVER if (arc.level_polarity == "dark" and not self.invert)else cairo.OPERATOR_CLEAR) + self.ctx.set_line_width(width * self.scale[0]) self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) self.ctx.move_to(*start) # You actually have to do this... if arc.direction == 'counterclockwise': @@ -97,6 +90,7 @@ class GerberCairoContext(GerberContext): def _render_region(self, region, color): points = [tuple(map(mul, point, self.scale)) for point in region.points] self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.move_to(*points[0]) for point in points[1:]: @@ -106,14 +100,16 @@ class GerberCairoContext(GerberContext): def _render_circle(self, circle, color): center = tuple(map(mul, circle.position, self.scale)) self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (circle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.arc(*center, radius=circle.radius * SCALE, angle1=0, angle2=2 * math.pi) + self.ctx.arc(*center, radius=circle.radius * self.scale[0], angle1=0, angle2=2 * math.pi) self.ctx.fill() def _render_rectangle(self, rectangle, color): ll = map(mul, rectangle.lower_left, self.scale) width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale))) self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) self.ctx.rectangle(*ll,width=width, height=height) self.ctx.fill() @@ -131,10 +127,27 @@ class GerberCairoContext(GerberContext): self.ctx.set_font_size(200) self._render_circle(Circle(primitive.position, 0.01), color) self.ctx.set_source_rgb(*color) - self.ctx.move_to(*[SCALE * (coord + 0.01) for coord in primitive.position]) + self.ctx.set_operator(cairo.OPERATOR_OVER if (primitive.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.move_to(*[self.scale[0] * (coord + 0.01) for coord in primitive.position]) self.ctx.scale(1, -1) self.ctx.show_text(primitive.net_name) self.ctx.scale(1, -1) + def _paint_inverted_layer(self): + self.ctx.set_source_rgba(*self.background_color) + self.ctx.set_operator(cairo.OPERATOR_OVER) + self.ctx.paint() + self.ctx.set_operator(cairo.OPERATOR_CLEAR) + def dump(self, filename): - self.surface.write_to_png(filename) + is_svg = filename.lower().endswith(".svg") + + if is_svg: + self.surface.finish() + self.surface_buffer.flush() + + with open(filename, "w") as f: + f.write(open(self.surface_buffer.name, "r").read()) + f.flush() + else: + self.surface.write_to_png(filename) -- cgit From b3f6ec558ca35a19bd60440f2a114eb98c0a4263 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Thu, 9 Jul 2015 04:05:15 -0300 Subject: Fix arcs and ackground painting --- gerber/render/cairo_backend.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 939863b..16638f5 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -72,7 +72,7 @@ class GerberCairoContext(GerberContext): center = map(mul, arc.center, self.scale) start = map(mul, arc.start, self.scale) end = map(mul, arc.end, self.scale) - radius = self.scale * arc.radius + radius = self.scale[0] * arc.radius angle1 = arc.start_angle angle2 = arc.end_angle width = arc.aperture.diameter if arc.aperture.diameter != 0 else 0.001 @@ -139,6 +139,10 @@ class GerberCairoContext(GerberContext): self.ctx.paint() self.ctx.set_operator(cairo.OPERATOR_CLEAR) + def _paint_background(self): + self.ctx.set_source_rgba(*self.background_color) + self.ctx.paint() + def dump(self, filename): is_svg = filename.lower().endswith(".svg") -- cgit From 39726e3936c5fa5c50158727e8eb7f5d01cb1b49 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 22 Jul 2015 22:13:09 -0400 Subject: Fix multiple layer issue in cairo-unification branch (see #33) --- gerber/render/cairo_backend.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 16638f5..2791d76 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -31,20 +31,21 @@ class GerberCairoContext(GerberContext): self.scale = (scale, scale) self.surface = None self.ctx = None + self.bg = False def set_bounds(self, bounds): origin_in_inch = (bounds[0][0], bounds[1][0]) size_in_inch = (abs(bounds[0][1] - bounds[0][0]), abs(bounds[1][1] - bounds[1][0])) size_in_pixels = map(mul, size_in_inch, self.scale) - self.surface_buffer = tempfile.NamedTemporaryFile() - - self.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) - self.ctx = cairo.Context(self.surface) - self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) - self.ctx.scale(1, -1) - self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) - # self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), -origin_in_inch[1]*self.scale[1]) + if self.surface is None: + self.surface_buffer = tempfile.NamedTemporaryFile() + self.surface = cairo.SVGSurface(self.surface_buffer, size_in_pixels[0], size_in_pixels[1]) + self.ctx = cairo.Context(self.surface) + self.ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) + self.ctx.scale(1, -1) + self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), (-origin_in_inch[1]*self.scale[0]) - size_in_pixels[1]) + # self.ctx.translate(-(origin_in_inch[0] * self.scale[0]), -origin_in_inch[1]*self.scale[1]) def _render_line(self, line, color): start = map(mul, line.start, self.scale) @@ -140,8 +141,10 @@ class GerberCairoContext(GerberContext): self.ctx.set_operator(cairo.OPERATOR_CLEAR) def _paint_background(self): - self.ctx.set_source_rgba(*self.background_color) - self.ctx.paint() + if not self.bg: + self.bg = True + self.ctx.set_source_rgba(*self.background_color) + self.ctx.paint() def dump(self, filename): is_svg = filename.lower().endswith(".svg") -- cgit From d4a870570855265b9b37f1609dd2bc9f49699bb6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 25 Jul 2015 09:48:58 -0400 Subject: Fix windows permission error per #33 the issue was trying to re-open the temporary file. it works on everything but windows. I've changed it to seek to the beginning and read from the file without re-opening, which should fix the issue. --- gerber/render/cairo_backend.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 2791d76..0ae5d40 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -154,7 +154,9 @@ class GerberCairoContext(GerberContext): self.surface_buffer.flush() with open(filename, "w") as f: - f.write(open(self.surface_buffer.name, "r").read()) + self.surface_buffer.seek(0) + f.write(self.surface_buffer.read()) f.flush() + else: self.surface.write_to_png(filename) -- cgit From cb2fa34e881a389cf8a4bc98fd12be662ff687f8 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 9 Aug 2015 15:11:13 -0400 Subject: Add support for arcs in regions. This fixes the circular cutout issue described in #32. Regions were previously stored as a collection of points, now they are stored as a collection of line and arc primitives. --- gerber/render/cairo_backend.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'gerber/render/cairo_backend.py') diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 0ae5d40..a97e552 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -89,13 +89,25 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*end) # ...lame def _render_region(self, region, color): - points = [tuple(map(mul, point, self.scale)) for point in region.points] self.ctx.set_source_rgba(*color, alpha=self.alpha) - self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) + self.ctx.set_operator(cairo.OPERATOR_OVER if (region.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR) self.ctx.set_line_width(0) - self.ctx.move_to(*points[0]) - for point in points[1:]: - self.ctx.line_to(*point) + self.ctx.set_line_cap(cairo.LINE_CAP_ROUND) + self.ctx.move_to(*tuple(map(mul, region.primitives[0].start, self.scale))) + for p in region.primitives: + if isinstance(p, Line): + self.ctx.line_to(*tuple(map(mul, p.end, self.scale))) + else: + center = map(mul, p.center, self.scale) + start = map(mul, p.start, self.scale) + end = map(mul, p.end, self.scale) + radius = self.scale[0] * p.radius + angle1 = p.start_angle + angle2 = p.end_angle + if p.direction == 'counterclockwise': + self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2) + else: + self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2) self.ctx.fill() def _render_circle(self, circle, color): -- cgit