def test_numpath_transform(self): numpath = Numpath() numpath.polyline( ( complex(0.05, 0.05), complex(0.95, 0.05), complex(0.95, 0.95), complex(0.05, 0.95), complex(0.05, 0.05), ) ) numpath.polyline( ( complex(0.25, 0.25), complex(0.75, 0.25), complex(0.75, 0.75), complex(0.25, 0.75), complex(0.25, 0.25), ) ) numpath.uscale(10000) c = copy(numpath) numpath.rotate(tau * 0.25) c.transform(Matrix("rotate(.25turn)")) t = numpath.segments == c.segments self.assertTrue(np.all(t))
def validate(self): if self.point is None: self.point = Point( float(self.settings.get("x", 0)), float(self.settings.get("y", 0)) ) if self.matrix is None: self.matrix = Matrix()
def __init__( self, path=None, matrix=None, fill=None, stroke=None, stroke_width=None, linecap=Linecap.CAP_BUTT, linejoin=Linejoin.JOIN_MITER, fillrule=Fillrule.FILLRULE_NONZERO, *args, **kwargs, ): super().__init__(*args, **kwargs) self._formatter = "{element_type} {id} {stroke}" self.settings.update(kwargs) self.path = path if matrix is None: matrix = Matrix() self.matrix = matrix self.fill = fill self.stroke = stroke self.stroke_width = stroke_width self.linecap = linecap self.linejoin = linejoin self.fillrule = fillrule self.lock = False
def test_fill_hatch2(self): kernel = bootstrap.bootstrap() try: kernel.console("operation* delete\n") kernel.console("rect 0 0 1in 1in\n") kernel.console("rect 3in 0 1in 1in\n") kernel.console("hatch\n") hatch = list(kernel.elements.ops())[0] hatch.hatch_type = "eulerian" rect0 = list(kernel.elements.elems())[0] hatch.add_node(copy(rect0)) rect1 = list(kernel.elements.elems())[1] hatch.add_node(copy(rect1)) commands = list() # kernel.console("tree list\n") hatch.preprocess(kernel.root, Matrix(), commands) for command in commands: command() # kernel.console("tree list\n") polyline_node0 = hatch.children[0] shape0 = polyline_node0.shape self.assertEqual(len(shape0), 77) # print(shape0) polyline_node1 = hatch.children[1] shape1 = polyline_node1.shape self.assertEqual(len(shape1), 50) # print(shape1) finally: kernel.shutdown()
def rebuild_hit_chain(self, current_widget, current_matrix=None): """ Iterates through the hit chain to find elements which respond to their hit() function that they are HITCHAIN_HIT and registers this within the hittable_elements list if they are able to hit at the current time. Given the dimensions of the widget and the current matrix within the widget tree. HITCHAIN_HIT means that this is a hit value and should the termination of this branch of the widget tree. HITCHAIN_DELEGATE means that this is not a hittable widget and should not receive mouse events. HITCHAIN_HIT_AND_DELEGATE means that this is a hittable widget, but other widgets within it might also matter. HITCHAIN_DELEGATE_AND_HIT means that other widgets in the tree should be checked first, but after those this widget should be checked. The hitchain is the current matrix and current widget in the order of depth. """ # If there is a matrix for the widget concatenate it. if current_widget.matrix is not None: matrix_within_scene = Matrix(current_widget.matrix) matrix_within_scene.post_cat(current_matrix) else: matrix_within_scene = Matrix(current_matrix) # Add to list and recurse for children based on response. response = current_widget.hit() if response == HITCHAIN_HIT: self.hittable_elements.append( (current_widget, matrix_within_scene)) # elif response == HITCHAIN_HIT_WITH_PRIORITY: # self.hittable_elements.insert(0, (current_widget, matrix_within_scene)) elif response == HITCHAIN_DELEGATE: for w in current_widget: self.rebuild_hit_chain(w, matrix_within_scene) elif response == HITCHAIN_HIT_AND_DELEGATE: self.hittable_elements.append( (current_widget, matrix_within_scene)) for w in current_widget: self.rebuild_hit_chain(w, matrix_within_scene) elif response == HITCHAIN_DELEGATE_AND_HIT: for w in current_widget: self.rebuild_hit_chain(w, matrix_within_scene) self.hittable_elements.append( (current_widget, matrix_within_scene))
def test_actualize_circle_step3_direct_black(self): """ Test for edge pixel error. Black Empty. :return: """ image = Image.new("RGBA", (256, 256), "black") draw = ImageDraw.Draw(image) draw.ellipse((100, 100, 150, 150), "white") for step in range(1, 20): transform = Matrix() actual, transform = actualize( image, transform, step_x=step, step_y=step, crop=False, inverted=True ) self.assertEqual(actual.getpixel((-1, -1)), 0) # Note: inverted flag not set. White edge pixel is correct. actual, transform = actualize(image, Matrix(), step_x=3, step_y=3, crop=False) self.assertEqual(actual.getpixel((-1, -1)), 255)
def test_plotplanner_walk_raster(self): """ Test plotplanner operation of walking to a raster. PLOT_FINISH = 256 PLOT_RAPID = 4 PLOT_JOG = 2 PLOT_SETTING = 128 PLOT_AXIS = 64 PLOT_DIRECTION = 32 PLOT_LEFT_UPPER = 512 PLOT_RIGHT_LOWER = 1024 1 means cut. 0 means move. :return: """ rasterop = RasterOpNode() image = Image.new("RGBA", (256, 256)) draw = ImageDraw.Draw(image) draw.ellipse((0, 0, 255, 255), "black") image = image.convert("L") inode = ImageNode(image=image, dpi=1000.0, matrix=Matrix()) inode.step_x = 1 inode.step_y = 1 inode.process_image() rasterop.add_node(inode) rasterop.raster_step_x = 1 rasterop.raster_step_y = 1 vectorop = EngraveOpNode() vectorop.add_node( PathNode(path=Path(Circle(cx=127, cy=127, r=128)), fill="black")) cutcode = CutCode() cutcode.extend(vectorop.as_cutobjects()) cutcode.extend(rasterop.as_cutobjects()) settings = {"power": 500} plan = PlotPlanner(settings) for c in cutcode.flat(): plan.push(c) setting_changed = False for x, y, on in plan.gen(): if on > 2: if setting_changed: # Settings change happens at vector to raster switch and must repost the axis. self.assertEqual(on, PLOT_AXIS) if on == PLOT_SETTING: setting_changed = True else: setting_changed = False
def apply_rotary_scale(*args, **kwargs): sx = self.scale_x sy = self.scale_y x, y = self.device.current matrix = Matrix("scale(%f, %f, %f, %f)" % (sx, sy, x, y)) for node in self.elements.elems(): if hasattr(node, "rotary_scale"): # This element is already scaled return try: node.rotary_scale = sx, sy node.matrix *= matrix node.modified() except AttributeError: pass
def eulerian_fill(settings, outlines, matrix, limit=None): """ Applies optimized Eulerian fill @return: """ if matrix is None: matrix = Matrix() settings = dict(settings) h_dist = settings.get("hatch_distance", "1mm") h_angle = settings.get("hatch_angle", "0deg") distance_y = float(Length(h_dist)) if isinstance(h_angle, float): angle = Angle.degrees(h_angle) else: angle = Angle.parse(h_angle) rotate = Matrix.rotate(angle) counter_rotate = Matrix.rotate(-angle) def mx_rotate(pt): if pt is None: return None return ( pt[0] * rotate.a + pt[1] * rotate.c + 1 * rotate.e, pt[0] * rotate.b + pt[1] * rotate.d + 1 * rotate.f, ) def mx_counter(pt): if pt is None: return None return ( pt[0] * counter_rotate.a + pt[1] * counter_rotate.c + 1 * counter_rotate.e, pt[0] * counter_rotate.b + pt[1] * counter_rotate.d + 1 * counter_rotate.f, ) transformed_vector = matrix.transform_vector([0, distance_y]) distance = abs(complex(transformed_vector[0], transformed_vector[1])) efill = EulerianFill(distance) for sp in outlines: sp = list(map(mx_rotate, sp)) efill += sp if limit and efill.estimate() > limit: return [] points = efill.get_fill() points = list(map(mx_counter, points)) return points
def test_actualize_circle_step3_direct_white(self): """ Test for edge pixel error. White empty. :return: """ image = Image.new("RGBA", (256, 256), "white") draw = ImageDraw.Draw(image) draw.ellipse((100, 100, 150, 150), "black") for step in range(1, 20): transform = Matrix() actual, transform = actualize( image, transform, step_x=step, step_y=step, crop=False ) self.assertEqual(actual.getpixel((-1, -1)), 255)
def vectrace(data, **kwargs): elements = kernel.root.elements path = Path(fill="black", stroke="blue") paths = [] for node in data: matrix = node.matrix image = node.image width, height = node.image.size if image.mode != "L": image = image.convert("L") image = image.point(lambda e: int(e > 127) * 255) for points in _vectrace(image.load(), width, height): path += Polygon(*points) paths.append( elements.elem_branch.add(path=path, matrix=Matrix(matrix), type="elem path")) return "elements", paths
def __init__( self, scene, left: float = None, top: float = None, right: float = None, bottom: float = None, all: bool = False, ): """ All produces a widget of infinite space rather than finite space. """ assert scene.__class__.__name__ == "Scene" list.__init__(self) self.matrix = Matrix() self.scene = scene self.parent = None self.properties = ORIENTATION_RELATIVE if all: # contains all points self.left = -float("inf") self.top = -float("inf") self.right = float("inf") self.bottom = float("inf") else: # contains no points self.left = float("inf") self.top = float("inf") self.right = -float("inf") self.bottom = -float("inf") if left is not None: self.left = left if right is not None: self.right = right if top is not None: self.top = top if bottom is not None: self.bottom = bottom
def test_actualize_pureblack(self): """ Test that a pure black image does not crash. :return: """ kernel = bootstrap.bootstrap() try: kernel_root = kernel.get_context("/") # kernel_root("channel print console\n") image = Image.new("RGBA", (256, 256), "black") elements = kernel_root.elements node = elements.elem_branch.add( image=image, dpi=1000.0, matrix=Matrix(), type="elem image" ) node.emphasized = True kernel_root("image resample\n") for element in kernel_root.elements.elems(): if node.type == "elem image": self.assertEqual(element.image.size, (256, 256)) self.assertEqual(element.matrix.value_trans_x(), 0) self.assertEqual(element.matrix.value_trans_y(), 0) finally: kernel.shutdown()
def event(self, window_pos=None, space_pos=None, event_type=None, nearest_snap=None): response = RESPONSE_CHAIN if event_type == "leftclick": if nearest_snap is None: point = Point(space_pos[0], space_pos[1]) else: point = Point(nearest_snap[0], nearest_snap[1]) elements = self.scene.context.elements node = elements.elem_branch.add(point=point, matrix=Matrix(), type="elem point") if self.scene.context.elements.default_stroke is not None: node.stroke = self.scene.context.elements.default_stroke if self.scene.context.elements.default_fill is not None: node.fill = self.scene.context.elements.default_fill if elements.classify_new: elements.classify([node]) self.notify_created(node) response = RESPONSE_CONSUME return response
def scanline_fill(settings, outlines, matrix, limit=None): """ Applies optimized scanline fill @return: """ if matrix is None: matrix = Matrix() settings = dict(settings) h_dist = settings.get("hatch_distance", "1mm") h_angle = settings.get("hatch_angle", "0deg") distance_y = float(Length(h_dist)) if isinstance(h_angle, float): angle = Angle.degrees(h_angle) else: angle = Angle.parse(h_angle) rotate = Matrix.rotate(angle) counter_rotate = Matrix.rotate(-angle) def mx_rotate(pt): if pt is None: return None return ( pt[0] * rotate.a + pt[1] * rotate.c + 1 * rotate.e, pt[0] * rotate.b + pt[1] * rotate.d + 1 * rotate.f, ) def mx_counter(pt): if pt is None: return None return ( pt[0] * counter_rotate.a + pt[1] * counter_rotate.c + 1 * counter_rotate.e, pt[0] * counter_rotate.b + pt[1] * counter_rotate.d + 1 * counter_rotate.f, ) transformed_vector = matrix.transform_vector([0, distance_y]) distance = abs(complex(transformed_vector[0], transformed_vector[1])) vm = VectorMontonizer() for outline in outlines: pts = list(map(Point, map(mx_rotate, outline))) vm.add_cluster(pts) vm.sort_clusters() y_max = vm.clusters[-1][0] y_min = vm.clusters[0][0] height = y_max - y_min try: count = height / distance except ZeroDivisionError: return [] if limit and count > limit: return [] vm.valid_low_value = y_min - distance vm.valid_high_value = y_max + distance vm.scanline(y_min - distance) points = list() forward = True while vm.valid_range(): vm.next_intercept(distance) vm.sort_actives() y = vm.current for i in ( range(1, len(vm.actives), 2) if forward else range(len(vm.actives) - 1, 0, -2) ): left_segment = vm.actives[i - 1] right_segment = vm.actives[i] left_segment_x = vm.intercept(left_segment, y) right_segment_x = vm.intercept(right_segment, y) if forward: points.append((left_segment_x, y)) points.append((right_segment_x, y)) else: points.append((right_segment_x, y)) points.append((left_segment_x, y)) points.append(None) forward = not forward points = list(map(mx_counter, points)) return points
def make_raster( self, nodes, bounds, width=None, height=None, bitmap=False, step_x=1, step_y=1, keep_ratio=False, recursion=0, ): """ Make Raster turns an iterable of elements and a bounds into an image of the designated size, taking into account the step size. The physical pixels in the image is reduced by the step size then the matrix for the element is scaled up by the same amount. This makes step size work like inverse dpi and correctly sets the image scale to the step scale for 1:1 sizes independent of the scale. This function requires both wxPython and Pillow. @param nodes: elements to render. @param bounds: bounds of those elements for the viewport. @param width: desired width of the resulting raster @param height: desired height of the resulting raster @param bitmap: bitmap to use rather than provisioning @param step: raster step rate, int scale rate of the image. @param keepratio: get a picture with the same height / width ratio as the original @return: """ if bounds is None: return None xxmin = float("inf") yymin = float("inf") xxmax = -float("inf") yymax = -float("inf") # print ("Recursion=%d" % recursion) if not isinstance(nodes, (tuple, list)): mynodes = [nodes] else: mynodes = nodes if recursion == 0: # Do it only once... textnodes = [] for item in mynodes: if item.type == "elem text": if item.text.width == 0 or item.text.height == 0: textnodes.append(item) if len(textnodes) > 0: # print ("Invalid textnodes found, call me again...") self.make_raster( nodes=textnodes, bounds=bounds, width=width, height=height, bitmap=bitmap, step_x=step_x, step_y=step_y, keep_ratio=keep_ratio, recursion=1, ) for item in mynodes: bb = item.bounds # if item.type == "elem text": # print ("Bounds for text: %.1f, %.1f, %.1f, %.1f, w=%.1f, h=%.1f)" % (bb[0], bb[1], bb[2], bb[3], item.text.width, item.text.height)) if bb[0] < xxmin: xxmin = bb[0] if bb[1] < yymin: yymin = bb[1] if bb[2] > xxmax: xxmax = bb[2] if bb[3] > yymax: yymax = bb[3] xmin = xxmin ymin = yymin xmax = xxmax ymax = yymax xmax = ceil(xmax) ymax = ceil(ymax) xmin = floor(xmin) ymin = floor(ymin) # print ("Bounds: %.1f, %.1f, %.1f, %.1f, Mine: %.1f, %.1f, %.1f, %.1f)" % (xmin, ymin, xmax, ymax, xxmin, yymin, xxmax, yymax)) image_width = int(xmax - xmin) if image_width == 0: image_width = 1 image_height = int(ymax - ymin) if image_height == 0: image_height = 1 if width is None: width = image_width if height is None: height = image_height # Scale physical image down by step amount. width /= float(step_x) height /= float(step_y) width = int(ceil(abs(width))) height = int(ceil(abs(height))) if width <= 0: width = 1 if height <= 0: height = 1 bmp = wx.Bitmap(width, height, 32) dc = wx.MemoryDC() dc.SelectObject(bmp) dc.SetBackground(wx.WHITE_BRUSH) dc.Clear() matrix = Matrix() matrix.post_translate(-xmin, -ymin) # Scale affine matrix up by step amount scaled down. scale_x = width / float(image_width) scale_y = height / float(image_height) if keep_ratio: scale_x = min(scale_x, scale_y) scale_y = scale_x matrix.post_scale(scale_x, scale_y) gc = wx.GraphicsContext.Create(dc) gc.SetInterpolationQuality(wx.INTERPOLATION_BEST) gc.PushState() if not matrix.is_identity(): gc.ConcatTransform( wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix))) if not isinstance(nodes, (list, tuple)): nodes = [nodes] gc.SetBrush(wx.WHITE_BRUSH) gc.DrawRectangle(xmin - 1, ymin - 1, xmax + 1, ymax + 1) self.render(nodes, gc, draw_mode=DRAW_MODE_CACHE | DRAW_MODE_VARIABLES) img = bmp.ConvertToImage() buf = img.GetData() image = Image.frombuffer("RGB", tuple(bmp.GetSize()), bytes(buf), "raw", "RGB", 0, 1) gc.PopState() dc.SelectObject(wx.NullBitmap) gc.Destroy() del dc if bitmap: return bmp # for item in mynodes: # bb = item.bounds # if item.type == "elem text": # print ("Afterwards Bounds for text: %.1f, %.1f, %.1f, %.1f, w=%.1f, h=%.1f)" % (bb[0], bb[1], bb[2], bb[3], item.text.width, item.text.height)) return image
def calculate_matrices(self): self._matrix = Matrix(self.scene_to_device_matrix()) self._imatrix = Matrix(self._matrix) self._imatrix.inverse()
def aspect_matrix(self): """ Specifically view the scene with the given Viewbox. """ if self._frame and self._view and self.aspect: self.scene_widget.matrix = Matrix(self._view.transform(self._frame))
def test_cutcode_image(self): """ Convert CutCode from Image operation Test image-based crosshatched setting :return: """ laserop = ImageOpNode() # Add Path initial = "M 0,0 L 100,100 L 0,0 M 50,-50 L 100,-100 M 0,0 Q 100,100 200,0" path = Path(initial) laserop.add_node(PathNode(path)) # Add SVG Image1 image = Image.new("RGBA", (256, 256), (255, 255, 255, 0)) draw = ImageDraw.Draw(image) draw.ellipse((50, 50, 150, 150), "white") draw.ellipse((100, 100, 105, 105), "black") inode1 = ImageNode(image=image, matrix=Matrix(), dpi=1000.0 / 3.0) inode1.step_x = 3 inode1.step_y = 3 inode1.process_image() laserop.add_node(inode1) # Add SVG Image2 image2 = Image.new("RGBA", (256, 256), (255, 255, 255, 0)) draw = ImageDraw.Draw(image2) draw.ellipse((50, 50, 150, 150), "white") draw.ellipse((80, 80, 120, 120), "black") inode2 = ImageNode(image=image2, matrix=Matrix(), dpi=500, direction=4) inode2.step_x = 2 inode2.step_y = 2 inode2.process_image() laserop.add_node(inode2) # crosshatch for i in range(2): # Check for knockon cutcode = CutCode(laserop.as_cutobjects()) self.assertEqual(len(cutcode), 3) rastercut = cutcode[0] self.assertTrue(isinstance(rastercut, RasterCut)) self.assertEqual(rastercut.offset_x, 100) self.assertEqual(rastercut.offset_y, 100) image = rastercut.image self.assertTrue(isinstance(image, Image.Image)) self.assertIn(image.mode, ("L", "1")) self.assertEqual(image.size, (2, 2)) # step value 2, 6/2 self.assertEqual( rastercut.path, "M 100,100 L 100,106 L 106,106 L 106,100 Z" ) rastercut1 = cutcode[1] self.assertTrue(isinstance(rastercut1, RasterCut)) self.assertEqual(rastercut1.offset_x, 80) self.assertEqual(rastercut1.offset_y, 80) image1 = rastercut1.image self.assertTrue(isinstance(image1, Image.Image)) self.assertIn(image1.mode, ("L", "1")) self.assertEqual(image1.size, (21, 21)) # default step value 2, 40/2 + 1 self.assertEqual(rastercut1.path, "M 80,80 L 80,122 L 122,122 L 122,80 Z") rastercut2 = cutcode[2] self.assertTrue(isinstance(rastercut2, RasterCut)) self.assertEqual(rastercut2.offset_x, 80) self.assertEqual(rastercut2.offset_y, 80) image2 = rastercut2.image self.assertTrue(isinstance(image2, Image.Image)) self.assertIn(image2.mode, ("L", "1")) self.assertEqual(image2.size, (21, 21)) # default step value 2, 40/2 + 1 self.assertEqual(rastercut2.path, "M 80,80 L 80,122 L 122,122 L 122,80 Z")
def process_image(self): if self.step_x is None: step = UNITS_PER_INCH / self.dpi self.step_x = step self.step_y = step from PIL import Image, ImageEnhance, ImageFilter, ImageOps from meerk40t.image.actualize import actualize from meerk40t.image.imagetools import dither image = self.image main_matrix = self.matrix r = self.red * 0.299 g = self.green * 0.587 b = self.blue * 0.114 v = self.lightness c = r + g + b try: c /= v r = r / c g = g / c b = b / c except ZeroDivisionError: pass if image.mode != "L": image = image.convert("RGB") image = image.convert("L", matrix=[r, g, b, 1.0]) if self.invert: image = image.point(lambda e: 255 - e) # Calculate device real step. step_x, step_y = self.step_x, self.step_y if ( main_matrix.a != step_x or main_matrix.b != 0.0 or main_matrix.c != 0.0 or main_matrix.d != step_y ): try: image, actualized_matrix = actualize( image, main_matrix, step_x=step_x, step_y=step_y, inverted=self.invert, ) except (MemoryError, DecompressionBombError): self.process_image_failed = True return else: actualized_matrix = Matrix(main_matrix) if self.invert: empty_mask = image.convert("L").point(lambda e: 0 if e == 0 else 255) else: empty_mask = image.convert("L").point(lambda e: 0 if e == 255 else 255) # Process operations. for op in self.operations: name = op["name"] if name == "crop": try: if op["enable"] and op["bounds"] is not None: crop = op["bounds"] left = int(crop[0]) upper = int(crop[1]) right = int(crop[2]) lower = int(crop[3]) image = image.crop((left, upper, right, lower)) except KeyError: pass elif name == "edge_enhance": try: if op["enable"]: if image.mode == "P": image = image.convert("L") image = image.filter(filter=ImageFilter.EDGE_ENHANCE) except KeyError: pass elif name == "auto_contrast": try: if op["enable"]: if image.mode not in ("RGB", "L"): # Auto-contrast raises NotImplementedError if P # Auto-contrast raises OSError if not RGB, L. image = image.convert("L") image = ImageOps.autocontrast(image, cutoff=op["cutoff"]) except KeyError: pass elif name == "tone": try: if op["enable"] and op["values"] is not None: if image.mode == "L": image = image.convert("P") tone_values = op["values"] if op["type"] == "spline": spline = ImageNode.spline(tone_values) else: tone_values = [q for q in tone_values if q is not None] spline = ImageNode.line(tone_values) if len(spline) < 256: spline.extend([255] * (256 - len(spline))) if len(spline) > 256: spline = spline[:256] image = image.point(spline) if image.mode != "L": image = image.convert("L") except KeyError: pass elif name == "contrast": try: if op["enable"]: if op["contrast"] is not None and op["brightness"] is not None: contrast = ImageEnhance.Contrast(image) c = (op["contrast"] + 128.0) / 128.0 image = contrast.enhance(c) brightness = ImageEnhance.Brightness(image) b = (op["brightness"] + 128.0) / 128.0 image = brightness.enhance(b) except KeyError: pass elif name == "gamma": try: if op["enable"] and op["factor"] is not None: if image.mode == "L": gamma_factor = float(op["factor"]) def crimp(px): px = int(round(px)) if px < 0: return 0 if px > 255: return 255 return px if gamma_factor == 0: gamma_lut = [0] * 256 else: gamma_lut = [ crimp(pow(i / 255, (1.0 / gamma_factor)) * 255) for i in range(256) ] image = image.point(gamma_lut) if image.mode != "L": image = image.convert("L") except KeyError: pass elif name == "unsharp_mask": try: if ( op["enable"] and op["percent"] is not None and op["radius"] is not None and op["threshold"] is not None ): unsharp = ImageFilter.UnsharpMask( radius=op["radius"], percent=op["percent"], threshold=op["threshold"], ) image = image.filter(unsharp) except (KeyError, ValueError): # Value error if wrong type of image. pass elif name == "halftone": try: if op["enable"]: image = RasterScripts.halftone( image, sample=op["sample"], angle=op["angle"], oversample=op["oversample"], black=op["black"], ) except KeyError: pass if empty_mask is not None: background = Image.new(image.mode, image.size, "white") background.paste(image, mask=empty_mask) image = background # Mask exists use it to remove any pixels that were pure reject. if self.dither and self.dither_type is not None: if self.dither_type != "Floyd-Steinberg": image = dither(image, self.dither_type) image = image.convert("1") inverted_main_matrix = Matrix(main_matrix).inverse() self.processed_matrix = actualized_matrix * inverted_main_matrix self.processed_image = image # self.matrix = actualized_matrix self.altered() self.process_image_failed = False
def __init__( self, image=None, matrix=None, overscan=None, direction=None, dpi=500, operations=None, **kwargs, ): super(ImageNode, self).__init__(type="elem image", **kwargs) self.__formatter = "{element_type} {width}x{height}" if "href" in kwargs: self.matrix = Matrix() try: from PIL import Image as PILImage self.image = PILImage.open(kwargs["href"]) if "x" in kwargs: self.matrix.post_translate_x(kwargs["x"]) if "y" in kwargs: self.matrix.post_translate_x(kwargs["y"]) real_width, real_height = self.image.size declared_width, declared_height = real_width, real_height if "width" in kwargs: declared_width = kwargs["width"] if "height" in kwargs: declared_height = kwargs["height"] try: sx = declared_width / real_width sy = declared_height / real_height self.matrix.post_scale(sx, sy) except ZeroDivisionError: pass except ImportError: self.image = None else: self.image = image self.matrix = matrix self.processed_image = None self.processed_matrix = None self.process_image_failed = False self.text = None self._needs_update = False self._update_thread = None self._update_lock = threading.Lock() self.settings = kwargs self.overscan = overscan self.direction = direction self.dpi = dpi self.step_x = None self.step_y = None self.lock = False self.invert = False self.red = 1.0 self.green = 1.0 self.blue = 1.0 self.lightness = 1.0 self.view_invert = False self.dither = True self.dither_type = "Floyd-Steinberg" if operations is None: operations = list() self.operations = operations
def test_cutcode_image_crosshatch(self): """ Convert CutCode from Image Operation. Test ImageOp Crosshatch Setting :return: """ laserop = ImageOpNode(raster_direction=4) # Add Path initial = "M 0,0 L 100,100 L 0,0 M 50,-50 L 100,-100 M 0,0 Q 100,100 200,0" path = Path(initial) laserop.add_node(PathNode(path)) # Add SVG Image1 image1 = Image.new("RGBA", (256, 256), (255, 255, 255, 0)) draw = ImageDraw.Draw(image1) draw.ellipse((50, 50, 150, 150), "white") draw.ellipse((100, 100, 105, 105), "black") inode = ImageNode(image=image1, matrix=Matrix(), dpi=1000.0 / 3.0) inode.step_x = 3 inode.step_y = 3 inode.process_image() laserop.add_node(inode) # Add SVG Image2 image2 = Image.new("RGBA", (256, 256), (255, 255, 255, 0)) draw = ImageDraw.Draw(image2) draw.ellipse((50, 50, 150, 150), "white") draw.ellipse((80, 80, 120, 120), "black") inode = ImageNode(image=image2, matrix=Matrix(), dpi=500.0) inode.step_x = 2 inode.step_y = 2 inode.process_image() laserop.add_node(inode) # Add SVG Image3 inode = ImageNode(image=image2, matrix=Matrix(), dpi=1000.0 / 3.0) inode.step_x = 3 inode.step_y = 3 inode.process_image() laserop.add_node(inode) cutcode = CutCode(laserop.as_cutobjects()) self.assertEqual(len(cutcode), 6) rastercut1_0 = cutcode[0] self.assertTrue(isinstance(rastercut1_0, RasterCut)) self.assertEqual(rastercut1_0.offset_x, 100) self.assertEqual(rastercut1_0.offset_y, 100) image = rastercut1_0.image self.assertTrue(isinstance(image, Image.Image)) self.assertIn(image.mode, ("L", "1")) self.assertEqual(image.size, (2, 2)) # step value 2, 6/2 self.assertEqual(rastercut1_0.path, "M 100,100 L 100,106 L 106,106 L 106,100 Z") rastercut1_1 = cutcode[1] self.assertTrue(isinstance(rastercut1_1, RasterCut)) self.assertEqual(rastercut1_1.offset_x, 100) self.assertEqual(rastercut1_1.offset_y, 100) image = rastercut1_1.image self.assertTrue(isinstance(image, Image.Image)) self.assertIn(image.mode, ("L", "1")) self.assertEqual(image.size, (2, 2)) # step value 2, 6/2 self.assertEqual(rastercut1_1.path, "M 100,100 L 100,106 L 106,106 L 106,100 Z") rastercut2_0 = cutcode[2] self.assertTrue(isinstance(rastercut2_0, RasterCut)) self.assertEqual(rastercut2_0.offset_x, 80) self.assertEqual(rastercut2_0.offset_y, 80) image1 = rastercut2_0.image self.assertTrue(isinstance(image1, Image.Image)) self.assertIn(image1.mode, ("L", "1")) self.assertEqual(image1.size, (21, 21)) # default step value 2, 40/2 + 1 self.assertEqual(rastercut2_0.path, "M 80,80 L 80,122 L 122,122 L 122,80 Z") rastercut2_1 = cutcode[3] self.assertTrue(isinstance(rastercut2_1, RasterCut)) self.assertEqual(rastercut2_1.offset_x, 80) self.assertEqual(rastercut2_1.offset_y, 80) image2 = rastercut2_1.image self.assertTrue(isinstance(image2, Image.Image)) self.assertIn(image2.mode, ("L", "1")) self.assertEqual(image2.size, (21, 21)) # default step value 2, 40/2 + 1 self.assertEqual(rastercut2_0.path, "M 80,80 L 80,122 L 122,122 L 122,80 Z") rastercut3_0 = cutcode[4] self.assertTrue(isinstance(rastercut3_0, RasterCut)) self.assertEqual(rastercut3_0.offset_x, 80) self.assertEqual(rastercut3_0.offset_y, 80) image3 = rastercut3_0.image self.assertTrue(isinstance(image3, Image.Image)) self.assertIn(image3.mode, ("L", "1")) self.assertEqual(image3.size, (14, 14)) # default step value 3, ceil(40/3) + 1 self.assertEqual(rastercut3_0.path, "M 80,80 L 80,122 L 122,122 L 122,80 Z") rastercut3_1 = cutcode[5] self.assertTrue(isinstance(rastercut3_1, RasterCut)) self.assertEqual(rastercut3_1.offset_x, 80) self.assertEqual(rastercut3_1.offset_y, 80) image4 = rastercut3_1.image self.assertTrue(isinstance(image4, Image.Image)) self.assertIn(image4.mode, ("L", "1")) self.assertEqual(image4.size, (14, 14)) # default step value 3, ceil(40/3) + 1 self.assertEqual(rastercut2_0.path, "M 80,80 L 80,122 L 122,122 L 122,80 Z")
HITCHAIN_DELEGATE_AND_HIT, HITCHAIN_HIT, HITCHAIN_HIT_AND_DELEGATE, ORIENTATION_RELATIVE, RESPONSE_ABORT, RESPONSE_CHAIN, RESPONSE_CONSUME, RESPONSE_DROP, ) from meerk40t.gui.scene.scenespacewidget import SceneSpaceWidget from meerk40t.kernel import Job, Module from meerk40t.svgelements import Matrix, Point # TODO: _buffer can be updated partially rather than fully rewritten, especially with some layering. _reused_identity_widget = Matrix() XCELLS = 15 YCELLS = 15 class SceneToast: """ SceneToast is drawn directly by the Scene. It creates an text message in a box that animates a fade. """ def __init__(self, scene, left, top, right, bottom): self.scene = scene self.left = left self.top = top self.right = right self.bottom = bottom self.countdown = 0