From 6379bc59629349427bd1d2729adb1428c973e3de Mon Sep 17 00:00:00 2001 From: Akko Date: Wed, 17 May 2023 16:27:52 +0200 Subject: [PATCH] Major refactors --- .gitignore | 1 + gradientmesh/gmtypes.py | 346 +++++++------------ gradientmesh/test.py | 202 ++++------- gradientmesh/test_data/meme.png | Bin 2661 -> 0 bytes gradientmesh/test_data/monotone_quad.png | Bin 2336 -> 0 bytes gradientmesh/test_data/multiple_quads.png | Bin 23945 -> 0 bytes gradientmesh/test_data/optimize_test.png | Bin 4884 -> 0 bytes gradientmesh/test_data/optimize_test.png.png | Bin 4863 -> 0 bytes gradientmesh/test_data/random_quad.png | Bin 2940 -> 0 bytes gradientmesh/util.py | 13 + 10 files changed, 209 insertions(+), 353 deletions(-) delete mode 100644 gradientmesh/test_data/meme.png delete mode 100644 gradientmesh/test_data/monotone_quad.png delete mode 100644 gradientmesh/test_data/multiple_quads.png delete mode 100644 gradientmesh/test_data/optimize_test.png delete mode 100644 gradientmesh/test_data/optimize_test.png.png delete mode 100644 gradientmesh/test_data/random_quad.png diff --git a/.gitignore b/.gitignore index bb2ce51..cf10f5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +gradientmesh/test_data/examples apps/results apps/files apps/__pycache__ diff --git a/gradientmesh/gmtypes.py b/gradientmesh/gmtypes.py index c8c97be..20543fb 100644 --- a/gradientmesh/gmtypes.py +++ b/gradientmesh/gmtypes.py @@ -4,36 +4,21 @@ import torch from random import uniform import math -import pydiffvg +from util import rgb2hex, any_map -from util import rgb2hex class Point: """2D point, optionally with control points; alternatively, 2-vector""" def __init__(self, x: float, y: float, - controls: list[Point] = None, round=False): - if isinstance(x, torch.Tensor): - # Convert from tensor - x = x.item() - y = y.item() - - self.id = id(self) self.x = x self.y = y - self.controls = controls or [] if round: self.round() - def add_control(self, control: Point): - self.controls.append(control) - - def add_to_patch(self, patch: Patch): - self.patches.append(patch) - def as_xy(self): return [self.x, self.y] @@ -41,27 +26,19 @@ class Point: self.x = int(self.x * 100) / 100.0 self.y = int(self.y * 100) / 100.0 - def replace(self, pt: Point): - """"Replace (x,y) coordinates of point while maintaining pointer.""" - self.x = pt.x - self.y = pt.y + return self def add(self, pt: Point): self.x += pt.x self.y += pt.y - for cp in self.controls: - cp.x += pt.x - cp.y += pt.y + + return self def mult(self, pt: Point): self.x *= pt.x self.y *= pt.y - for cp in self.controls: - cp.x *= pt.x - cp.y *= pt.y - def equalize(self, other): - self.id = other.id + return self @classmethod def random(cls, rx=(0, 1), ry=(0, 1)): @@ -73,67 +50,48 @@ class Point: out.round() return out - def __eq__(self, other): - return self.id == other.id - def __hash__(self): - return self.id + # Used for removing duplicate points + return id(self) def __repr__(self): - return f"P<({self.x}, {self.y})[{len(self.controls)}]>" + return f"P<({self.x}, {self.y})>" def __str__(self): return self.__repr__() class Patch: - """Cubic patch.""" - def __init__(self, points: list[Point], color=(0.2, 0.5, 0.7, 1.0)): + """Bicubic patch.""" + def __init__(self, + points: list[Point], + controls: list[list[Point]], + color=(0.2, 0.5, 0.7, 1.0)): self.points = points + self.controls = controls self.color = color def translate(self, pt: Point): for p in self.points: p.add(pt) + for q in self.controls: + for p in q: + p.add(pt) + def scale(self, pt: Point): for p in self.points: p.mult(pt) - def as_path(self, width=256, height=256) -> pydiffvg.Path: - ppoints = [] - for pt in self.points: - ppoints.append([pt.x * width, pt.y * height]) - for cpt in pt.controls: - ppoints.append([cpt.x * width, cpt.y * height]) - - return pydiffvg.Path( - num_control_points=torch.tensor( - [len(p.controls) for p in self.points] - ), - points=torch.tensor(ppoints, requires_grad=True), - is_closed=True - ) - - def as_shape_group(self, color=None) -> pydiffvg.ShapeGroup: - # TODO proper id handling - return pydiffvg.ShapeGroup( - shape_ids=torch.tensor([0]), - fill_color=torch.tensor(color or self.color, requires_grad=True) - ) - - def get_points(self): - out = [] - for p in self.points: - out.append([p.x, p.y]) - for cp in p.controls: - out.append([cp.x, cp.y]) - - return out + for q in self.controls: + for p in q: + p.mult(pt) @classmethod def random(cls, degree=4, num_control_points=2): num_control_points = [num_control_points] * degree + """Returns a random Patch with `degree` vertices + and `num_control_points` control points per edge.""" # Random tweaks to regular polygon base angle = 2 * math.pi / degree @@ -156,6 +114,7 @@ class Patch: points.append(pt) + control_points = [] for i in range(len(num_control_points)): pt = points[i] npt = points[i+1 if i+1 < degree else 0] @@ -164,15 +123,14 @@ class Patch: dx = (npt.x - pt.x) / (ncp + 1) dy = (npt.y - pt.y) / (ncp + 1) - for j in range(1, ncp+1): - midpoint = Point( + control_points.append([ + Point( pt.x + j * dx * uniform(0.8, 1.2) + uniform(0, 0.2), pt.y + j * dy * uniform(0.8, 1.2) + uniform(0, 0.2) - ) + ) for j in range(1, ncp+1) + ]) - pt.add_control(midpoint) - - out = cls(points) + out = cls(points, control_points) out.color = ( uniform(0, 1), uniform(0, 1), @@ -189,8 +147,11 @@ class Patch: class Quad(Patch): + """Quadrilateral bicubic patch.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + + # Assign 4 vertices as self.{top, bototm, left, right} self.top, self.right, self.bottom, self.left = self.points by_y = sorted(self.points, key=lambda pt: pt.y) self.top, self.bottom = by_y[0], by_y[-1] @@ -199,184 +160,141 @@ class Quad(Patch): key=lambda pt: pt.x) self.left, self.right = by_x - for pt in by_y: - pt.round() + # Assign 4 edges as self.{northeat, southeast, southwest, northwest} + self.northeast = self.controls[self.points.index(self.top)] + self.southeast = self.controls[self.points.index(self.right)] + self.southwest = self.controls[self.points.index(self.bottom)] + self.northwest = self.controls[self.points.index(self.left)] - @classmethod - def from_path_points(cls, pp, color=None): - pt = None - points = [] - for i in range(len(pp)): - if i % (len(pp) // 4) == 0: - if pt: - points.append(pt) - pt = Point(*pp[i]) - else: - pt.add_control(Point(*pp[i])) + self.set_points() - points.append(pt) - return cls(points, color) + def set_points(self): + """Reset self.points and self.controls; used after mutating + e.g. self.top, self.southwest &c.""" + self.points = [self.left, self.top, + self.right, self.bottom] - def to_path_points(self): + self.controls = [self.northwest, self.northeast, + self.southeast, self.southwest] + + +class PointMapping: + """Mapping of unique points in a mesh to separate shapes for diffvg.""" + def __init__(self, points, controls, raw_points, colors): + self.points = points + self.controls = controls + self.raw_points = raw_points + self.data = torch.tensor( + [pt.as_xy() for pt in raw_points], requires_grad=True + ) + self.colors = colors + + def as_shapes(self): out = [] - for pt in self.points: - out.append(pt.as_xy()) - for cp in pt.controls: - out.append(cp.as_xy()) - return out + for i in range(len(self.points)): + quad = self.points[i] + + quadpoints = [] + for j in range(len(quad)): + quadpoints.append(quad[j]) + quadpoints += self.controls[i][j] + + out.append(quadpoints) + + return [torch.stack(x) for x in + any_map(lambda idx: self.data[idx], out)] class GradientMesh: + """Bicubic quadrilateral mesh.""" def __init__(self, *quads: Quad): self.quads = quads - def as_shape_groups(self): - sg = [quad.as_shape_group() for quad in self.quads] - for i in range(len(sg)): - sg[i].shape_ids = torch.tensor([i]) - return sg - - def as_shapes(self, width, height): - return [quad.as_path(width, height) for quad in self.quads] - - def to_numbers(self): + def as_mapping(self): + """Convert GradientMesh to PointMapping""" points = [] controls = [] - for quad in self.quads: - qp = [] - qcp = [] - for p in quad.points: - qp.append([p.x, p.y]) - qcp.append([[cp.x, cp.y] for cp in p.controls]) - points.append(qp) - controls.append(qcp) + raw_points = [] - return [points, controls, [q.color for q in self.quads]] - - def from_numbers(self, numbers): - points, controls, colors = numbers - for q in range(4): - self.quads[q].color = colors[q] - pts = points[q] - ctrls = controls[q] - for p in range(4): - self.quads[q].points[p].replace(Point(*pts[p])) - for c in range(len(self.quads[q].points[p].controls)): - self.quads[q].points[p].controls[c].replace( - Point(*ctrls[p][c]) - ) - - @classmethod - def from_path_points(cls, pp, colors): - a, b, c, d = [Quad.from_path_points(pp[x], colors[x]) - for x in range(len(pp))] - join_quads(a, b, c, d, scale=False, translate=False) - - return cls(a, b, c, d) - - def to_path_points(self): - out = [] for q in self.quads: - out.append(q.to_path_points()) - return out + points.append(q.points) + controls.append(q.controls) + raw_points += q.points + for cp in q.controls: + raw_points += cp - def to_point_map(self): - # XXX this doesn't work - # because of control points - pts: list[Point] = [] - template: list[list[int]] = [] + raw_points = list(set(raw_points)) - for quad in self.quads: - for pt in quad.points: - pts.append(pt) + points = any_map(lambda p: raw_points.index(p), points) + controls = any_map(lambda p: raw_points.index(p), controls) - pts = list(dict.fromkeys(pts)) + colors = torch.tensor([q.color for q in self.quads], + requires_grad=True) - for idx in range(len(self.quads)): - template.append([ - pts.index(pt) for pt in self.quads[idx].points - ]) + return PointMapping(points, controls, raw_points, colors) - return (pts, template) - - def from_point_map(self, pts, template): - # XXX this not either - mapped = [] - for q in template: - mapped.append([ - pts[x] for x in q - ]) - - return mapped + def from_mapping(self, mapping: PointMapping): + pass -def average_points(points: list[Point]) -> Point: +def average_points(*points: list[Point]) -> Point: + """Average (i.e. geometric center) of points.""" x = sum([pt.x for pt in points]) / len(points) y = sum([pt.y for pt in points]) / len(points) return Point(x, y) -def equalize_points(points: list[Point]): - first = points[0] - for pt in points: - pt.equalize(first) +def join_quads(a: Quad, b: Quad, c: Quad, d: Quad, + scale=True, translate=True, + step=10): + """Join 4 quadrilaterals so that they form a mesh with a center point.""" + def combine_cp(cpa, cpb): + return [average_points(a, b) for a, b in zip(cpa, cpb)] -def equalize_cp(points: list[Point]): - first = points[0].controls[0] - for pt in points: - for cp in pt.controls: - cp.equalize(first) - - -def merge_points(points: list[Point]): - merged = average_points(points) - for pt in points: - pt.replace(merged) - - -def merge_cp(points: list[Point]): - ct1 = points[0].controls - ct2 = points[1].controls[::-1] - for i in range(len(ct1)): - merged = average_points([ct1[i], ct2[i]]) - ct1[i].replace(merged) - ct2[i].replace(merged) - - -def join_quads(a: Quad, b: Quad, c: Quad, d: Quad, scale=True, translate=True): - if translate: - b.translate(a.top) - c.translate(a.right) - d.translate(a.bottom) - - merge_points([a.right, b.bottom, c.left, d.top]) - - merge_points([a.top, b.left]) - merge_points([a.bottom, d.left]) - merge_points([b.right, c.top]) - merge_points([c.bottom, d.right]) - + # If each quad occupies full space, make it so that they occupy 1/4 of space if scale: a.scale(Point(0.5, 0.5)) b.scale(Point(0.5, 0.5)) c.scale(Point(0.5, 0.5)) d.scale(Point(0.5, 0.5)) - merge_cp([a.right, d.left]) - merge_cp([a.top, b.bottom]) - merge_cp([c.left, b.right]) - merge_cp([c.bottom, d.top]) + # If quads are on top of each other, translate so they are not + if translate: + b.translate(a.top) + c.translate(a.right) + d.translate(a.bottom) - equalize_points([a.right, b.bottom, c.left, d.top]) - equalize_points([a.top, b.left]) - equalize_points([a.bottom, d.left]) - equalize_points([b.right, c.top]) - equalize_points([c.bottom, d.right]) + # Equalize centerpoint + a.right, b.bottom, c.left, d.top = [ + average_points(a.right, b.bottom, c.left, d.top) + ] * 4 - equalize_cp([a.right, d.left]) - equalize_cp([a.top, b.bottom]) - equalize_cp([c.left, b.right]) - equalize_cp([c.bottom, d.top]) + # Equalize non-center shared points + a.top, b.left = [average_points(a.top, b.left)] * 2 + a.bottom, d.left = [average_points(a.bottom, d.left)] * 2 + b.right, c.top = [average_points(b.right, c.top)] * 2 + c.bottom, d.right = [average_points(c.bottom, d.right)] * 2 + # Equalize edges + a.northeast, b.southwest = ( + (comb := combine_cp(a.northeast, b.southwest)), + comb[::-1] + ) + b.southeast, c.northwest = ( + (comb := combine_cp(b.southeast, c.northwest)), + comb[::-1] + ) + c.southwest, d.northeast = ( + (comb := combine_cp(c.southwest, d.northeast)), + comb[::-1] + ) + d.northwest, a.southeast = ( + (comb := combine_cp(d.northwest, a.southeast)), + comb[::-1] + ) + + # Update points + for q in [a, b, c, d]: + q.set_points() diff --git a/gradientmesh/test.py b/gradientmesh/test.py index 5f15de8..a6f42c5 100644 --- a/gradientmesh/test.py +++ b/gradientmesh/test.py @@ -7,171 +7,95 @@ import torch import random from random import uniform -from gmtypes import GradientMesh, Quad, Patch, Point, join_quads +from gmtypes import GradientMesh, Quad, PointMapping, join_quads + +def get_mesh() -> GradientMesh: + """Helper function to get a random mesh.""" + a, b, c, d = [Quad.random() for _ in range(4)] + join_quads(a, b, c, d) + return GradientMesh(a, b, c, d) -def quads(): - return [ - Quad.random(), - Quad.random(), - Quad.random(), - Quad.random(), - ] +def render_mesh(mesh: PointMapping, + filename='test_data/mesh.png', + width=1024, + height=1024, + num_control_points=2, + seed=None): + random.seed(seed) -def rand_quad_test(filename='random_quad.png', width=256, height=256, - degree=4, num_control_points=2): pydiffvg.set_use_gpu(torch.cuda.is_available()) render = pydiffvg.RenderFunction.apply + ncp = torch.tensor([num_control_points] * len(mesh.points)) - patch = Patch.random() + # Scale + # TODO non-uniform scaling + points = [x * width for x in mesh.as_shapes()] - shape_groups = [patch.as_shape_group()] - shapes = [patch.as_path(width, height)] + shapes = [ + pydiffvg.Path(num_control_points=ncp, + points=pts, + is_closed=True) + for pts in points + ] - scene_args = pydiffvg.RenderFunction.serialize_scene(width, height, - shapes, shape_groups) + shape_groups = [ + pydiffvg.ShapeGroup(shape_ids=torch.tensor([i]), + fill_color=mesh.colors[i]) + for i in range(len(mesh.points)) + ] - img = render(width, height, 2, 2, 0, None, *scene_args) - pydiffvg.imwrite(img.cpu(), f"test_data/{filename}", gamma=2.2) + scene_args = pydiffvg.RenderFunction.serialize_scene( + width, + height, + shapes, + shape_groups + ) + + img = render(width, + height, + 2, # num smaples x + 2, # num samples y + 0, # seed + None, + *scene_args) + pydiffvg.imwrite(img.cpu(), filename, gamma=2.2) return img -def mult_quad_test(filename='multiple_quads.png', width=1024, - height=1024, num_control_points=None, mask=None, seed=None): - random.seed(seed) - mask = mask or [1, 1, 1, 1] - pydiffvg.set_use_gpu(torch.cuda.is_available()) - render = pydiffvg.RenderFunction.apply - - a, b, c, d = quads() - join_quads(a, b, c, d) - - to_render = [a, b, c, d] - to_render = [x for x in to_render if mask[to_render.index(x)]] - - shape_groups = [patch.as_shape_group(color=( - uniform(0, 1), - uniform(0, 1), - uniform(0, 1), - 0.8 - )) for patch in to_render] - - for i in range(len(to_render)): - shape_groups[i].shape_ids = torch.tensor([i]) - shapes = [patch.as_path(width, height) for patch in to_render] - - scene_args = pydiffvg.RenderFunction.serialize_scene(width, height, - shapes, shape_groups) - - img = render(width, height, 2, 2, 0, None, *scene_args) - pydiffvg.imwrite(img.cpu(), f"test_data/{filename}", gamma=2.2) - return img.clone() +def test_render(filename='test_data/target.png', + width=1024, + height=1024): + return render_mesh(get_mesh().as_mapping(), + width=width, + height=height, + filename=filename) -def om(): - filename = 'optimize_test.png' - pydiffvg.set_use_gpu(torch.cuda.is_available()) - render = pydiffvg.RenderFunction.apply +def optimize(): + width, height = 256, 256 + target = test_render(width=width, height=height).clone() - target = mult_quad_test(width=256, height=256) + mesh = get_mesh().as_mapping() - squad = quads() + optimizer = torch.optim.Adam([mesh.data, mesh.colors], lr=1e-2) - join_quads(*squad) - - gm = GradientMesh(*squad) - - points_n = [] - for s in squad: - out = [] - for pt in s.points: - out.append([pt.x, pt.y]) - for cpt in pt.controls: - out.append([cpt.x, cpt.y]) - points_n.append(out) - - points_n = torch.tensor(points_n, requires_grad=True) - color = torch.tensor([s.color for s in squad], requires_grad=True) - - paths = [s.as_path() for s in squad] - path_groups = [pydiffvg.ShapeGroup(shape_ids=torch.tensor([i]), - fill_color=torch.tensor(squad[i].color)) - for i in range(len(squad))] - scene_args = pydiffvg.RenderFunction.serialize_scene( - 256, 256, paths, path_groups - ) - img = render(256, # width - 256, # height - 2, # num_samples_x - 2, # num_samples_y - 1, # seed - None, - *scene_args) - - points, controls, color = [torch.tensor(x, requires_grad=True) - for x in gm.to_numbers()] - - optimizer = torch.optim.Adam([points, color, points_n], lr=1e-2) - - for t in range(180): + for t in range(150): print(f"iteration {t}") optimizer.zero_grad() - points_n.data = torch.tensor( - GradientMesh.from_path_points(points_n, color).to_path_points() - ) - - for i in range(len(paths)): - paths[i].points = points_n[i] * 256 - - for i in range(len(path_groups)): - path_groups[i].fill_color = color[i] - - scene_args = pydiffvg.RenderFunction.serialize_scene( - 256, 256, paths, path_groups) - - img = render(256, # width - 256, # height - 2, # num_samples_x - 2, # num_samples_y - t+1, # seed - None, - *scene_args) - - pydiffvg.imwrite(img.cpu(), - f'test_data/test_curve/iter_{filename}_' - f'{str(t).zfill(5)}.png', - gamma=2.2) + img = render_mesh(mesh, + filename=f"test_data/mesh_optim_{str(t).zfill(3)}.png", + width=width, + height=height) loss = (img - target).pow(2).sum() - loss.backward() + # FIXME no need to retain graph + loss.backward(retain_graph=True) print(f'loss: {loss}') - print(f'points.grad {points.grad}') - print(f'color.grad {color.grad}') optimizer.step() - - -def slideshow(n=30, s=1, do_mask=False): - mask = None - for i in range(n): - if do_mask: - mask = [1] * 4 - print(i % n) - mask[i % 4] = 0 - print(mask) - - mult_quad_test(mask=mask) - sleep(s) - - -def get_mesh(): - a, b, c, d = quads() - join_quads(a,b,c,d) - - gm = GradientMesh(a, b, c, d) - return gm diff --git a/gradientmesh/test_data/meme.png b/gradientmesh/test_data/meme.png deleted file mode 100644 index 9c41291c252bac21b3cdfc20f7318d1b31ca607e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2661 zcmZWrc|6o>7k_@EnPG&;gegmPwV=peugp}G-B>1+W$vq7WUn+~n%}+k+LbFpWGPFH zWu_=D4I?Q_QcRW@F`~LumW*YXd4Iis-uL+vIZ0%v6LL5L4ger$ zYh&dK0E9pYNMjJ=9d$nd0Gyt!)e(1E{#SlQ)Hd5Km@%WR3-TyKmutnj_jmA)#qTpU zRGQnmCzW>`$N4?!&-6LAcG!u;WVJ&_pQB=ueW(KsW#x_UvYDO3OWW-y?9&<+dfax` zJlcsA_~!|1v-U11Z^B$z*|&&JA4MESYM%Z&tI@AH)uU$egJg%3Si<=}$2L!^S#h)2 ze_cS^q8O=pX+b5@aJuPRZ1T&tJadu%8wn)b1$5rZ3BqQhrRugOI?vUA#+>V%PEA}k za(epRaQr{*WV0OBgpes3O)<}<-$<}tnodll7=_5VLo1uW=2IT0Si0HggnKPtt=6fR z%*FmKd*6jIWr>ufk942k!#O{11kj{^>%dap&nGXN-PAXGMk~LdvCB6;{l7SQJ(;9m)afg`j1##9Y%C+J8 zusCWRDRn7b?+Y-F~o~9nAaBFYl;6%;sv&uJ8cmpjlO!$kj3Q$x`Sw2^-)SgX; z`hsHk@2+#>4w{A#9I>Qp-A4}A90-iN!X@%w@IOh%qNDEOR11Np;E9$#6OLRNubwTW zErrK14L}3(SM+XeP;ljk?)O^Kvf^Uom(c*VKP4G|DFCM1)afP>W;Qjxs3~;{w z*c#8ivkS$&O8n}A4k=FpMUK&NmaHT&dpt1vY)dy&0i2}{>#c{Kqyw5wNvPAN(aKd| zAT@TF?YjsSiEijnIZP?;d!jmp8%)8%LxtDrPz41c&$tyNEpdAcfv$YQ4`&Jnu4=BF z;1xAj7=p~0kh?0g_E}KTMAxnY<+aiVKo&1-(?vDAtAO4+p|rv^vJc2vABxu*OJJh7 zvYq?gp)544lIe4Qi!Kgy6NPVAv2E^~0h@IzT)jdDOcF)GD;zgRMA#vZE?pF51B|sW zcdx*{JgEpYW~tKqPC{*1Qi?udB5-c|BY{*=n@U<_EzOr3{y=|>T*aC&d`g@sS&n>I zbypX;%|Gx9opPZ!cU`R&dF7+ehcq9s{BhPE0C~tZbU9vmX^-L_Q&2r2?3~hk(cAgJ zwGBhk)!!6U>xx!Z0|jgTxP1Teh_@+Kzm84LnTI(?z&w>pTp)a~1@O zUG6HyLS7h<$*k4UY6KB8X4<+{xc2?P&YtXfvNzVekplRPU~;Ds%8LTDvz*wrdv(3M z&?MW^%^|Lx4~`&kFVT4LxD1743Xz{F$VR(7OGY3t>0DQx3pte4AmQnJ!b5nFs`qJl zSPxb7!#|69GasqC0qL{!&Hpdvm&8Sq~3bv;9B8}e~jI@INLkr6 zcyl90wrDNk*O8a}HJ_)Ki9bI4>lAwIqo3+)&qk>evS7D_%YKpL9*T}Xn(Bg1-2$Q| zEwk~yUR$++o}|S*epKDu4tR>M&TSl$Rn-Qrj6sMO(vE4|rKKFuzWEwf0ZdB_nu?h- zEmGW7qJw{8NAxUUuVMRs4%A}Zu&Wq&l9AoWv`r_t~;_4zpgk`pm< zMz?F~TYCP~@`8|0|Cc8i6w7{@Y1F5(0efAg_x*2vTrG&Yq*+qev&*6akdx>IikkrW zPoGOzc_ko}9J7oLYFAJK{gUmPjqXqwRCILsB!tDnc+lSSq-!2QB1U_kX^-@_wYOAD zvF!Zf$Zpwbh}2e4fs)68B}sbSrvhbnh%{-c&O1*Af#9gvlmcN*AQMo$t%C3ea;0Jg z6WL+XJm1MK(?2x7QlzG{%4y!)+U8~~zfFA*Obh%+;=C3`LV2K-CaTXXd@;0!B-X!& z6FyJRsDszwTBJqWgdo;JuYonNac1e*+UZy^)HWO`%um8$%IvTudtwvpl*p@-WorEUy~L;ymyk zeR>v-decfmmXXI_hy1(FQU@ZwNLD6U19FMt47;W2Nd{}HIls_P!)ACR^U~bZ`o$67 z#k?avL8}MCn)D=fmNi3uw-U diff --git a/gradientmesh/test_data/monotone_quad.png b/gradientmesh/test_data/monotone_quad.png deleted file mode 100644 index e247ba66e9ec3b31dcca43f3f898be210df6524b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2336 zcmb_e`8(8Y7r(zVW3o)D(JO;$O!SN;%UDWT##S^#Y7m8{gcMn(o+!&#^(0<}k!6sE zh$3Za8S6-r#7L!-WqOJc$z-g9dcM8Szwn-4&V8EJ#(03@=T9p3VFX#9EJh z*xp~N)W#0O;S(9qbVY>Z_Vfkt`CK@d-_&$wxoJ;Ct%p2y(RV`9xskPadhyrBso3SA zCDEg*6io6k7tcMD*5R@lOnlG<(OsP*pWd0s6!`4&2Mr0uQ1>MgM69HMEjr4;C8Aat z!`;$~qLO5(CuR>|>AoJ5aY)6XsJY3O4RR_Sg9Lue_gYb-$fE4ZuubWqS9oSF0V*9D zgt$X%r}5+U1K$=U@jq~xkmgX%`2Y;p_W~BqXJNQ5H!-liIWYZjj$mjWJ^gNq_{P5m zM_hPlF&ehFw9N(*R{F#nXvtD7cXooaBCn8ST+8%kIgser%EhM3z)x$4^a-vzT=Y$Y z*rj|iNc|dg?dKa`(cMlv!+0GEoKFYO+})AoZ8hI9&w-cK`!N;)%0lh^yoDllIGkQ9 zFV0TStC&!O!`;>ae4xS;Pen!W@r;aOvlq&+UdI>?Ig2vPySe>4QgMp?`gB2H-&d&9BWZWZYASDLEYg z-(u^!I~^h}oN%F{Z=1dsKEi>CP#tTr>nQk zdMAMby%*^1hC@772#1sM8X2D}EZ?OzO9IpYJ5+b=WYt;A85Z?!k_lAeE8pMROcK}h zm7~6qWceqnSAoP6&zV^yi2E=DZbQ(*(luHdAW`7KC=a!2lo-h{N71%4V^i6owRL}$ z^cM3=9w#LiA9Rxwr#1rGem(GJyMj1RQf_u0(A@Pv&3*j+j{nu_y(*-^AucLM5|d=$ zUJultUZRPetC`wJG%0DReIv-(7xxP$^6}5XpQ|yR=Z~;}Q*Jd7R2FBO>wdrlpVk&@ zFQ2sS11vWaP*cBl{#v5}(--Dd;vOeR%F9<9>N=qL{i^XPyyyOb$1d~8A29ThKH2Ex z5h9C=*_MhpBAU!!^QGoaBvx#`HMhsQ`cD_tLibiQAFmo;=c!X<+De0ua{T#bD{T)L zIVy!+Rf=C6eWcuhEgc|2%nBrm=$3t;rU~?TtHi0fiKTr+OIVZlqO%0SM1pkTE>GYy zr}gOiuACR51mfD^&!d{@|a_C$IOwCE4ig+sKPP zON#0$h4>M|c`1#CL2WRWmh-ImHP^7gYL;LW{lPn5}*ZVZ$M!@^8q^#>y!_f5g z=Eb;Uw73TxtLA#TGWe}=2$ye3m({y06C_4b9Wb$@{i{@PFy-zBlaoD+zzj&Dp?mCg z5yW>BT5u@AZAFl?ar}3E>f80N3k;;3qVF^wzoT+&b9!W zRdDfZYTi{qb;qe+DN6!e95bsikxazhOae@u)U46!3-(lUaS})+qqik z^cvra9Ka-B)(8odPYf;yG+zDR?oj5~uef}7{Rqy{K7V^t0<{pwRix=?+>r-~0mRn} zsdMAJCh;n5$W!l}k`4~c@y2m7Mwm@)zwXuIiF>iGF2A&IXwrTb4Hehoh-ormkF`w2 zyGtqH6mKwFj=6Dcp~cM$rTGHni|Fy6-8m57FZxB+{tz&qI(Nb!SPdt*hsYswWsPm}eh^xG0*1X<%25HiY-*yS)?&_8SwZN8XJGq`R>6z?r)LKkH@&d%QK_9Bef-t=z6{WSY};2f5s*Zu?z-Xe z(hTj$zA+_9@tZ~0&({R>LPWwJvXMW6-hM+t#_!D5ti~G=HkPw z8@*dd=+X7La_{q6`t@{x=3=O7M6wy{;bV{*t$=cxaw8f(Xv6DKK=7jJ{-)@x>Y3Sh h^oiepmH(3&&si7GZJjSGoD%)M(aFKZzQUGy{vU+(4kG{n diff --git a/gradientmesh/test_data/multiple_quads.png b/gradientmesh/test_data/multiple_quads.png deleted file mode 100644 index ebd964e2a5262f67dfda36cccbb4d162b91ab07a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23945 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 diff --git a/gradientmesh/test_data/optimize_test.png b/gradientmesh/test_data/optimize_test.png deleted file mode 100644 index a331f6b6af6bf7100131ab10d2e41fe233163b3b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4884 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 b;a9iS846dKXTO(l>}y@tG0-m2v<~?nIRocx diff --git a/gradientmesh/test_data/optimize_test.png.png b/gradientmesh/test_data/optimize_test.png.png deleted file mode 100644 index 1bda8ebfa214ca40dba6b4fa380343f392f1aa63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4863 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 diff --git a/gradientmesh/test_data/random_quad.png b/gradientmesh/test_data/random_quad.png deleted file mode 100644 index 30ba6c70255d280bdc6a89453bce20a19ef024a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2940 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 diff --git a/gradientmesh/util.py b/gradientmesh/util.py index 842a379..21860dd 100644 --- a/gradientmesh/util.py +++ b/gradientmesh/util.py @@ -1,4 +1,6 @@ #!/usr/bin/env ipython +from types import FunctionType + def clamp(val, low, high): return min(high, max(low, val)) @@ -10,3 +12,14 @@ def rgb2hex(r, g, b, a=None): int(b * 255) ) return hex_value + + +def any_map(f: FunctionType, lst: list): + result = [] + for itm in lst: + if isinstance(itm, list): + result.append(any_map(f, itm)) + else: + result.append(f(itm)) + + return result