From 66da2d165481d3e0c5c153a788b26aa23cc0ad97 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 13 Jun 2021 22:15:05 +0200 Subject: Fix hole transparency in aperture macros --- gerbonara/gerber/primitives.py | 3 - gerbonara/gerber/render/cairo_backend.py | 12 ++++ .../tests/golden/example_am_exposure_modifier.png | Bin 10091 -> 6198 bytes .../resources/example_am_exposure_modifier.gbr | 16 +++--- gerbonara/gerber/tests/test_cairo_backend.py | 61 ++++++++++++++++++--- 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 Binary files a/gerbonara/gerber/tests/golden/example_am_exposure_modifier.png and b/gerbonara/gerber/tests/golden/example_am_exposure_modifier.png 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 -- cgit