summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2021-06-13 22:15:05 +0200
committerjaseg <git@jaseg.de>2021-06-13 22:15:05 +0200
commit66da2d165481d3e0c5c153a788b26aa23cc0ad97 (patch)
treeaa4f2b85ad14cc64d63f6b53fa2e8a4724b6f291
parentd0f836ecfadacbaea20fc6a3ceebd455e96e1307 (diff)
downloadgerbonara-66da2d165481d3e0c5c153a788b26aa23cc0ad97.tar.gz
gerbonara-66da2d165481d3e0c5c153a788b26aa23cc0ad97.tar.bz2
gerbonara-66da2d165481d3e0c5c153a788b26aa23cc0ad97.zip
Fix hole transparency in aperture macros
-rw-r--r--gerbonara/gerber/primitives.py3
-rw-r--r--gerbonara/gerber/render/cairo_backend.py12
-rw-r--r--gerbonara/gerber/tests/golden/example_am_exposure_modifier.pngbin10091 -> 6198 bytes
-rw-r--r--gerbonara/gerber/tests/resources/example_am_exposure_modifier.gbr16
-rw-r--r--gerbonara/gerber/tests/test_cairo_backend.py61
5 files changed, 74 insertions, 18 deletions
diff --git a/gerbonara/gerber/primitives.py b/gerbonara/gerber/primitives.py
index fa067a1..7d15e8a 100644
--- a/gerbonara/gerber/primitives.py
+++ b/gerbonara/gerber/primitives.py
@@ -1064,9 +1064,6 @@ class RoundRectangle(Primitive):
class Obround(Primitive):
- """
- """
-
def __init__(self, position, width, height, hole_diameter=0,
hole_width=0,hole_height=0, **kwargs):
super(Obround, self).__init__(**kwargs)
diff --git a/gerbonara/gerber/render/cairo_backend.py b/gerbonara/gerber/render/cairo_backend.py
index d9c197d..6af3348 100644
--- a/gerbonara/gerber/render/cairo_backend.py
+++ b/gerbonara/gerber/render/cairo_backend.py
@@ -513,9 +513,21 @@ class GerberCairoContext(GerberContext):
self.ctx.mask_surface(mask.surface, self.origin_in_pixels[0])
def _render_amgroup(self, amgroup, color):
+
+ mask_surface = cairo.SVGSurface(None, self.size_in_pixels[0], self.size_in_pixels[1])
+ mask_ctx = cairo.Context(mask_surface)
+ mask_ctx.set_matrix(self.ctx.get_matrix())
+
+ old_surface, self.surface = self.surface, mask_surface
+ old_ctx, self.ctx = self.ctx, mask_ctx
+
for primitive in amgroup.primitives:
self.render(primitive)
+ old_ctx.mask_surface(mask_surface, self.origin_in_pixels[0])
+ mask_surface.finish()
+ self.surface, self.ctx = old_surface, old_ctx
+
def _render_test_record(self, primitive, color):
position = [pos + origin for pos, origin in
zip(primitive.position, self.origin_in_inch)]
diff --git a/gerbonara/gerber/tests/golden/example_am_exposure_modifier.png b/gerbonara/gerber/tests/golden/example_am_exposure_modifier.png
index dac951f..5ba3d7b 100644
--- a/gerbonara/gerber/tests/golden/example_am_exposure_modifier.png
+++ b/gerbonara/gerber/tests/golden/example_am_exposure_modifier.png
Binary files differ
diff --git a/gerbonara/gerber/tests/resources/example_am_exposure_modifier.gbr b/gerbonara/gerber/tests/resources/example_am_exposure_modifier.gbr
index 5f3f3dd..3f0b46f 100644
--- a/gerbonara/gerber/tests/resources/example_am_exposure_modifier.gbr
+++ b/gerbonara/gerber/tests/resources/example_am_exposure_modifier.gbr
@@ -1,16 +1,16 @@
G04 Umaco example for exposure modifier and clearing area*
%FSLAX26Y26*%
-%MOIN*%
-%AMSQUAREWITHHOLE*
-21,0.1,1,1,0,0,0*
-1,0,0.5,0,0*%
-%ADD10SQUAREWITHHOLE*%
+%MOMM*%
+%AMSquareWithHole*
+21,1,10,10,0,0,0*
+1,0,5,0,0*%
+%ADD10SquareWithHole*%
%ADD11C,1*%
G01*
%LPD*%
D11*
-X-1000000Y-250000D02*
-X1000000Y250000D01*
+X-08939393Y-2500000D02*
+X08939393Y2500000D01*
D10*
X0Y0D03*
-M02* \ No newline at end of file
+M02*
diff --git a/gerbonara/gerber/tests/test_cairo_backend.py b/gerbonara/gerber/tests/test_cairo_backend.py
index b4b8ce3..d0386d8 100644
--- a/gerbonara/gerber/tests/test_cairo_backend.py
+++ b/gerbonara/gerber/tests/test_cairo_backend.py
@@ -7,6 +7,7 @@ import shutil
import io
import tempfile
import uuid
+import cv2
from pathlib import Path
import pytest
@@ -201,12 +202,16 @@ def test_holes_dont_clear():
)
-def _DISABLED_test_render_am_exposure_modifier():
+def test_render_am_exposure_modifier():
"""Umaco example that an aperture macro with a hole does not clear the area"""
_test_render(
"resources/example_am_exposure_modifier.gbr",
"golden/example_am_exposure_modifier.png",
+ scale = 50,
+ autocrop_golden = True,
+ auto_contrast = True,
+ max_delta = 0.005 # Take artifacts due to differences in anti-aliasing and thresholding into account
)
@@ -252,19 +257,59 @@ def test_fine_lines_x():
def _resolve_path(path):
return os.path.join(os.path.dirname(__file__), path)
-def images_match(reference, output, max_delta):
+def images_match(reference, output, max_delta, autocrop_golden=False, auto_contrast=False):
global output_dir
ref, out = Image.open(reference), Image.open(output)
+ if ref.mode == 'P': # palette mode
+ ref = ref.convert('RGB')
+
ref, out = np.array(ref), np.array(out)
# convert to grayscale
ref, out = ref.astype(float).mean(axis=2), out.astype(float).mean(axis=2)
+
+ if autocrop_golden:
+ rows = ref.sum(axis=1)
+ cols = ref.sum(axis=0)
+
+ x0 = np.argmax(cols > 0)
+ y0 = np.argmax(rows > 0)
+ x1 = len(cols) - np.argmax(cols[::-1] > 0)
+ y1 = len(rows) - np.argmax(rows[::-1] > 0)
+ print(f'{x0=} {y0=} {x1=} {y1=}')
+
+ ref = ref[y0:y1, x0:x1]
+ ref = cv2.resize(ref, dsize=out.shape[::-1], interpolation=cv2.INTER_LINEAR)
+
+ def print_stats(name, ref):
+ print(name, 'stats:', ref.min(), ref.mean(), ref.max(), 'std:', ref.std())
+
+ if auto_contrast:
+ print_stats('ref pre proc', ref)
+ print_stats('out pre proc', out)
+
+ ref -= ref.min()
+ ref /= ref.max()
+ ref *= 255
+
+ out -= out.min()
+ out /= out.max()
+ out *= 255
+
+ def write_refout():
+ nonlocal autocrop_golden, ref
+ if autocrop_golden:
+ global output_dir
+ with output_dir.create(suffix='.png') as ref_out:
+ cv2.imwrite(str(ref_out), ref)
+ print('Processed reference image:', ref_out)
if ref.shape != out.shape:
- print(f'Rendering image size mismatch')
+ print(f'Rendering image size mismatch: {ref.shape} != {out.shape}')
print(f'Reference image: {Path(reference).absolute()}')
print(f'Actual output: {output}')
+ write_refout()
output_dir.keep()
return False
@@ -275,11 +320,13 @@ def images_match(reference, output, max_delta):
print(f'Renderings mismatch: {delta.mean()=}, {max_delta=}')
print(f'Reference image: {Path(reference).absolute()}')
print(f'Actual output: {output}')
- def print_stats(name, ref):
- print(name, 'stats:', ref.min(), ref.mean(), ref.max(), 'std:', ref.std())
+ with output_dir.create(suffix='.png') as proc_out:
+ cv2.imwrite(str(proc_out), out)
+ print('Processed output image:', proc_out)
print_stats('reference', ref)
print_stats('actual', out)
+ write_refout()
output_dir.keep()
return False
@@ -287,7 +334,7 @@ def images_match(reference, output, max_delta):
return True
-def _test_render(gerber_path, png_expected_path, max_delta=1e-6, scale=300):
+def _test_render(gerber_path, png_expected_path, max_delta=1e-6, scale=300, autocrop_golden=False, auto_contrast=False):
"""Render the gerber file and compare to the expected PNG output.
Parameters
@@ -314,7 +361,7 @@ def _test_render(gerber_path, png_expected_path, max_delta=1e-6, scale=300):
with output_dir.create(suffix='.png') as outfile:
actual_bytes = ctx.dump(outfile)
- assert images_match(png_expected_path, outfile, max_delta)
+ assert images_match(png_expected_path, outfile, max_delta, autocrop_golden, auto_contrast)
return gerber