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.
This commit is contained in:
Paulo Henrique Silva 2015-07-09 03:54:47 -03:00
parent 9e36d7e21d
commit 5aaf18889c
3 changed files with 63 additions and 40 deletions

View file

@ -253,8 +253,9 @@ class CamFile(object):
filename : string <optional>
If provided, save the rendered image to `filename`
"""
bounds = [tuple([x * 1.2, y*1.2]) for x, y in self.bounds]
ctx.set_bounds(bounds)
if ctx.invert:
ctx._paint_inverted_layer()
for p in self.primitives:
ctx.render(p)
if filename is not None:

View file

@ -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.ctx = cairo.Context(self.surface)
self.size = size
self.ctx.translate(0, self.size[1])
self.scale = (SCALE,SCALE)
self.ctx.scale(1, -1)
self.apertures = {}
self.background = False
self.scale = (scale, scale)
self.surface = None
self.ctx = None
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
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])
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)

View file

@ -62,6 +62,7 @@ class GerberContext(object):
self._drill_color = (0.25, 0.25, 0.25)
self._background_color = (0.0, 0.0, 0.0)
self._alpha = 1.0
self._invert = False
@property
def units(self):
@ -122,6 +123,14 @@ class GerberContext(object):
raise ValueError('Alpha must be between 0.0 and 1.0')
self._alpha = alpha
@property
def invert(self):
return self._invert
@invert.setter
def invert(self, invert):
self._invert = invert
def render(self, primitive):
color = (self.color if primitive.level_polarity == 'dark'
else self.background_color)