From 66da2d165481d3e0c5c153a788b26aa23cc0ad97 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 13 Jun 2021 22:15:05 +0200 Subject: [PATCH] Fix hole transparency in aperture macros --- gerbonara/gerber/primitives.py | 3 - gerbonara/gerber/render/cairo_backend.py | 12 ++++ .../golden/example_am_exposure_modifier.png | Bin 10091 -> 6198 bytes .../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 dac951ff31fd7c189f48f8c1d046b13ecf5e47ca..5ba3d7b41520be0e050a8d8f7e2e4d632211f9a5 100644 GIT binary patch literal 6198 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 FngF@HKTQAt literal 10091 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 MboFyt=akR{02P6IBme*a 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