Example #1
0
    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
Example #2
0
class ImageNode(Node):
    """
    ImageNode is the bootstrapped node type for the 'elem image' type.

    ImageNode contains a main matrix, main image. A processed image and a processed matrix.
    The processed matrix must be concated with the main matrix to be accurate.
    """

    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 __copy__(self):
        return ImageNode(
            image=self.image,
            matrix=copy(self.matrix),
            overscan=self.overscan,
            direction=self.direction,
            dpi=self.dpi,
            operations=self.operations,
            **self.settings,
        )

    def __repr__(self):
        return "%s('%s', %s, %s)" % (
            self.__class__.__name__,
            self.type,
            str(self.image),
            str(self._parent),
        )

    @property
    def active_image(self):
        if self.processed_image is not None:
            return self.processed_image
        else:
            return self.image

    @property
    def active_matrix(self):
        if self.processed_matrix is None:
            return self.matrix
        return self.processed_matrix * self.matrix

    def preprocess(self, context, matrix, commands):
        """
        Preprocess step during the cut planning stages.

        We require a context to calculate the correct step values relative to the device
        """
        self.step_x, self.step_y = context.device.dpi_to_steps(self.dpi)
        self.matrix *= matrix
        self._bounds_dirty = True
        self.process_image()

    @property
    def bounds(self):
        if self._bounds_dirty:
            image_width, image_height = self.active_image.size
            matrix = self.active_matrix
            x0, y0 = matrix.point_in_matrix_space((0, 0))
            x1, y1 = matrix.point_in_matrix_space((image_width, image_height))
            x2, y2 = matrix.point_in_matrix_space((0, image_height))
            x3, y3 = matrix.point_in_matrix_space((image_width, 0))
            self._bounds_dirty = False
            self._bounds = (
                min(x0, x1, x2, x3),
                min(y0, y1, y2, y3),
                max(x0, x1, x2, x3),
                max(y0, y1, y2, y3),
            )
        return self._bounds

    def default_map(self, default_map=None):
        default_map = super(ImageNode, self).default_map(default_map=default_map)
        default_map.update(self.settings)
        image = self.active_image
        default_map["width"] = image.width
        default_map["height"] = image.height
        default_map["element_type"] = "Image"
        default_map["matrix"] = self.matrix
        default_map["dpi"] = self.dpi
        default_map["overscan"] = self.overscan
        default_map["direction"] = self.direction
        return default_map

    def drop(self, drag_node, modify=True):
        # Dragging element into element.
        if drag_node.type.startswith("elem"):
            if modify:
                self.insert_sibling(drag_node)
            return True
        return False

    def revalidate_points(self):
        bounds = self.bounds
        if bounds is None:
            return
        if len(self._points) < 9:
            self._points.extend([None] * (9 - len(self._points)))
        self._points[0] = [bounds[0], bounds[1], "bounds top_left"]
        self._points[1] = [bounds[2], bounds[1], "bounds top_right"]
        self._points[2] = [bounds[0], bounds[3], "bounds bottom_left"]
        self._points[3] = [bounds[2], bounds[3], "bounds bottom_right"]
        cx = (bounds[0] + bounds[2]) / 2
        cy = (bounds[1] + bounds[3]) / 2
        self._points[4] = [cx, cy, "bounds center_center"]
        self._points[5] = [cx, bounds[1], "bounds top_center"]
        self._points[6] = [cx, bounds[3], "bounds bottom_center"]
        self._points[7] = [bounds[0], cy, "bounds center_left"]
        self._points[8] = [bounds[2], cy, "bounds center_right"]

    def update_point(self, index, point):
        return False

    def add_point(self, point, index=None):
        return False

    def update(self, context):
        self._needs_update = True
        self.text = "Processing..."
        context.signal("refresh_scene", "Scene")
        if self._update_thread is None:

            def clear(result):
                if self.process_image_failed:
                    self.text = "Process image could not exist in memory."
                else:
                    self.text = None
                self._needs_update = False
                self._update_thread = None
                context.signal("refresh_scene", "Scene")
                context.signal("image updated", self)

            self.processed_image = None
            self.processed_matrix = None
            self._update_thread = context.threaded(
                self.process_image_thread, result=clear, daemon=True
            )

    def process_image_thread(self):
        while self._needs_update:
            self._needs_update = False
            self.process_image()
            # Unset cache.
            self.wx_bitmap_image = None
            self.cache = None

    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

    @staticmethod
    def line(p):
        N = len(p) - 1
        try:
            m = [(p[i + 1][1] - p[i][1]) / (p[i + 1][0] - p[i][0]) for i in range(0, N)]
        except ZeroDivisionError:
            m = [1] * N
        # b = y - mx
        b = [p[i][1] - (m[i] * p[i][0]) for i in range(0, N)]
        r = list()
        for i in range(0, p[0][0]):
            r.append(0)
        for i in range(len(p) - 1):
            x0 = p[i][0]
            x1 = p[i + 1][0]
            range_list = [int(round((m[i] * x) + b[i])) for x in range(x0, x1)]
            r.extend(range_list)
        for i in range(p[-1][0], 256):
            r.append(255)
        r.append(round(int(p[-1][1])))
        return r

    @staticmethod
    def spline(p):
        """
        Spline interpreter.

        Returns all integer locations between different spline interpolation values
        @param p: points to be quad spline interpolated.
        @return: integer y values for given spline points.
        """
        try:
            N = len(p) - 1
            w = [(p[i + 1][0] - p[i][0]) for i in range(0, N)]
            h = [(p[i + 1][1] - p[i][1]) / w[i] for i in range(0, N)]
            ftt = (
                [0]
                + [3 * (h[i + 1] - h[i]) / (w[i + 1] + w[i]) for i in range(0, N - 1)]
                + [0]
            )
            A = [(ftt[i + 1] - ftt[i]) / (6 * w[i]) for i in range(0, N)]
            B = [ftt[i] / 2 for i in range(0, N)]
            C = [h[i] - w[i] * (ftt[i + 1] + 2 * ftt[i]) / 6 for i in range(0, N)]
            D = [p[i][1] for i in range(0, N)]
        except ZeroDivisionError:
            return list(range(256))
        r = list()
        for i in range(0, p[0][0]):
            r.append(0)
        for i in range(len(p) - 1):
            a = p[i][0]
            b = p[i + 1][0]
            r.extend(
                int(
                    round(
                        A[i] * (x - a) ** 3
                        + B[i] * (x - a) ** 2
                        + C[i] * (x - a)
                        + D[i]
                    )
                )
                for x in range(a, b)
            )
        for i in range(p[-1][0], 256):
            r.append(255)
        r.append(round(int(p[-1][1])))
        return r

    def as_path(self):
        image_width, image_height = self.active_image.size
        matrix = self.active_matrix
        x0, y0 = matrix.point_in_matrix_space((0, 0))
        x1, y1 = matrix.point_in_matrix_space((0, image_height))
        x2, y2 = matrix.point_in_matrix_space((image_width, image_height))
        x3, y3 = matrix.point_in_matrix_space((image_width, 0))
        return abs(Path(Polygon((x0,y0), (x1,y1), (x2,y2), (x3,y3), (x0,y0))))
Example #3
0
class Widget(list):
    """
    Widgets are drawable, interaction objects within the scene. They have their own space, matrix, orientation, and
    processing of events.
    """
    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 __str__(self):
        return "Widget(%f, %f, %f, %f)" % (self.left, self.top, self.right,
                                           self.bottom)

    def __repr__(self):
        return "%s(%f, %f, %f, %f)" % (
            type(self).__name__,
            self.left,
            self.top,
            self.right,
            self.bottom,
        )

    def hit(self):
        """
        Default hit state delegates to child-widgets within the current object.
        """
        return HITCHAIN_DELEGATE

    def draw(self, gc):
        """
        Widget.draw() routine which concat's the widgets matrix and call the process_draw() function.
        """
        # Concat if this is a thing.
        matrix = self.matrix
        gc.PushState()
        if matrix is not None and not matrix.is_identity():
            gc.ConcatTransform(
                wx.GraphicsContext.CreateMatrix(gc, ZMatrix(matrix)))
        self.process_draw(gc)
        for i in range(len(self) - 1, -1, -1):
            widget = self[i]
            if not widget is None:
                widget.draw(gc)
        gc.PopState()

    def process_draw(self, gc):
        """
        Overloaded function by derived widgets to process the drawing of this widget.
        """
        pass

    def contains(self, x, y=None):
        """
        Query whether the current point is contained within the current widget.
        """
        if y is None:
            y = x.y
            x = x.x
        return self.left <= x <= self.right and self.top <= y <= self.bottom

    def event(self,
              window_pos=None,
              space_pos=None,
              event_type=None,
              nearest_snap=None):
        """
        Default event which simply chains the event to the next hittable object.
        """
        return RESPONSE_CHAIN

    def notify_added_to_parent(self, parent):
        """
        Widget notify that calls scene notify.
        """
        self.scene.notify_added_to_parent(parent)

    def notify_added_child(self, child):
        """
        Widget notify that calls scene notify.
        """
        self.scene.notify_added_child(child)

    def notify_removed_from_parent(self, parent):
        """
        Widget notify that calls scene notify.
        """
        self.scene.notify_removed_from_parent(parent)

    def notify_removed_child(self, child):
        """
        Widget notify that calls scene notify.
        """
        self.scene.notify_removed_child(child)

    def notify_moved_child(self, child):
        """
        Widget notify that calls scene notify.
        """
        self.scene.notify_moved_child(child)

    def add_widget(self, index=-1, widget=None, properties=0):
        """
        Add a widget to the current widget.

        Adds at the particular index according to the properties.

        The properties can be used to trigger particular layouts or properties for the added widget.
        """
        if len(self) == 0:
            last = self
        else:
            last = self[-1]
        if 0 <= index < len(self):
            self.insert(index, widget)
        else:
            self.append(widget)
        widget.parent = self
        self.layout_by_orientation(widget, last, properties)
        self.notify_added_to_parent(self)
        self.notify_added_child(widget)

    def translate(self, dx, dy):
        """
        Move the current widget and all child widgets.
        """
        if dx == 0 and dy == 0:
            return
        if isnan(dx) or isnan(dy) or isinf(dx) or isinf(dy):
            return
        self.translate_loop(dx, dy)

    def translate_loop(self, dx, dy):
        """
        Loop the translation call to all child objects.
        """
        if self.properties & ORIENTATION_ABSOLUTE != 0:
            return  # Do not translate absolute oriented widgets.
        self.translate_self(dx, dy)
        for w in self:
            w.translate_loop(dx, dy)

    def translate_self(self, dx, dy):
        """
        Perform the local translation of the current widget
        """
        self.left += dx
        self.right += dx
        self.top += dy
        self.bottom += dy
        if self.parent is not None:
            self.notify_moved_child(self)

    def union_children_bounds(self, bounds=None):
        """
        Find the bounds of the current widget and all child widgets.
        """
        if bounds is None:
            bounds = [self.left, self.top, self.right, self.bottom]
        else:
            if bounds[0] > self.left:
                bounds[0] = self.left
            if bounds[1] > self.top:
                bounds[1] = self.top
            if bounds[2] < self.right:
                bounds[2] = self.left
            if bounds[3] < self.bottom:
                bounds[3] = self.bottom
        for w in self:
            w.union_children_bounds(bounds)
        return bounds

    @property
    def height(self):
        """
        Height of the current widget.
        """
        return self.bottom - self.top

    @property
    def width(self):
        """
        Width of the current widget.
        """
        return self.right - self.left

    def layout_by_orientation(self, widget, last, properties):
        """
        Perform specific layout based on the properties given.
        ORIENTATION_ABSOLUTE places the widget exactly in the scene.
        ORIENTATION_NO_BUFFER nullifies any buffer between objects being laid out.
        ORIENTATION_RELATIVE lays out the added widget relative to the parent.
        ORIENTATION_GRID lays out the added widget in a DIM_MASK grid.
        ORIENTATION_VERTICAL lays the added widget below the reference widget.
        ORIENTATION_HORIZONTAL lays the added widget to the right of the reference widget.
        ORIENTATION_CENTERED lays out the added widget and within the parent and all child centered.
        """
        if properties & ORIENTATION_ABSOLUTE != 0:
            return
        if properties & ORIENTATION_NO_BUFFER != 0:
            buffer = 0
        else:
            buffer = BUFFER
        if (properties & ORIENTATION_MODE_MASK) == ORIENTATION_RELATIVE:
            widget.translate(self.left, self.top)
            return
        elif last is None:  # orientation = origin
            widget.translate(self.left - widget.left, self.top - widget.top)
        elif (properties & ORIENTATION_GRID) != 0:
            dim = properties & ORIENTATION_DIM_MASK
            if (properties & ORIENTATION_VERTICAL) != 0:
                if dim == 0:  # Vertical
                    if self.height >= last.bottom - self.top + widget.height:
                        # add to line
                        widget.translate(last.left - widget.left,
                                         last.bottom - widget.top)
                    else:
                        # line return
                        widget.translate(last.right - widget.left + buffer,
                                         self.top - widget.top)
            else:
                if dim == 0:  # Horizontal
                    if self.width >= last.right - self.left + widget.width:
                        # add to line
                        widget.translate(last.right - widget.left + buffer,
                                         last.top - widget.top)
                    else:
                        # line return
                        widget.translate(self.left - widget.left,
                                         last.bottom - widget.top + buffer)
        elif (properties & ORIENTATION_HORIZONTAL) != 0:
            widget.translate(last.right - widget.left + buffer,
                             last.top - widget.top)
        elif (properties & ORIENTATION_VERTICAL) != 0:
            widget.translate(last.left - widget.left,
                             last.bottom - widget.top + buffer)
        if properties & ORIENTATION_CENTERED:
            self.center_children()

    def center_children(self):
        """
        Centers the children of the current widget within the current widget.
        """
        child_bounds = self.union_children_bounds()
        dx = self.left - (child_bounds[0] + child_bounds[2]) / 2.0
        dy = self.top - (child_bounds[1] + child_bounds[3]) / 2.0
        if dx != 0 and dy != 0:
            for w in self:
                w.translate_loop(dx, dy)

    def center_widget(self, x, y=None):
        """
        Moves the current widget to center within the bounds of the children.
        """
        if y is None:
            y = x.y
            x = x.x
        child_bounds = self.union_children_bounds()
        cx = (child_bounds[0] + child_bounds[2]) / 2.0
        cy = (child_bounds[1] + child_bounds[3]) / 2.0
        self.translate(x - cx, y - cy)

    def set_position(self, x, y=None):
        """
        Sets the absolute position of this widget by moving it from its current position
        to given position.
        """
        if y is None:
            y = x.y
            x = x.x
        dx = x - self.left
        dy = y - self.top
        self.translate(dx, dy)

    def remove_all_widgets(self):
        """
        Remove all widgets from the current widget.
        """
        for w in self:
            if w is None:
                continue
            w.parent = None
            w.notify_removed_from_parent(self)
            self.notify_removed_child(w)
        self.clear()
        try:
            self.scene.notify_tree_changed()
        except AttributeError:
            pass

    def remove_widget(self, widget=None):
        """
        Remove the given widget from being a child of the current widget.
        """
        if widget is None:
            return
        if isinstance(widget, Widget):
            self.remove(widget)
        elif isinstance(widget, int):
            index = widget
            widget = self[index]
            del self[index]
        widget.parent = None
        widget.notify_removed_from_parent(self)
        self.notify_removed_child(widget)
        try:
            self.scene.notify_tree_changed()
        except AttributeError:
            pass

    def set_widget(self, index, widget):
        """
        Sets the given widget at the index to replace the child currently at the position of that widget.
        """
        w = self[index]
        self[index] = widget
        widget.parent = self
        widget.notify_added_to_parent(self)
        self.notify_removed_child(w)
        try:
            self.scene.notify_tree_changed()
        except AttributeError:
            pass

    def on_matrix_change(self):
        """
        Notification of a changed matrix.
        """
        pass

    def scene_matrix_reset(self):
        """
        Resets the scene matrix.
        """
        self.matrix.reset()
        self.on_matrix_change()

    def scene_post_scale(self, sx, sy=None, ax=0, ay=0):
        """
        Adds a post_scale to the matrix.
        """
        self.matrix.post_scale(sx, sy, ax, ay)
        self.on_matrix_change()

    def scene_post_pan(self, px, py):
        """
        Adds a post_pan to the matrix.
        """
        self.matrix.post_translate(px, py)
        self.on_matrix_change()

    def scene_post_rotate(self, angle, rx=0, ry=0):
        """
        Adds a post_rotate to the matrix.
        """
        self.matrix.post_rotate(angle, rx, ry)
        self.on_matrix_change()

    def scene_pre_scale(self, sx, sy=None, ax=0, ay=0):
        """
        Adds a pre_scale to the matrix()
        """
        self.matrix.pre_scale(sx, sy, ax, ay)
        self.on_matrix_change()

    def scene_pre_pan(self, px, py):
        """
        Adds a pre_pan to the matrix()
        """
        self.matrix.pre_translate(px, py)
        self.on_matrix_change()

    def scene_pre_rotate(self, angle, rx=0, ry=0):
        """
        Adds a pre_rotate to the matrix()
        """
        self.matrix.pre_rotate(angle, rx, ry)
        self.on_matrix_change()

    def get_scale_x(self):
        """
        Gets the scale_x of the current matrix
        """
        return self.matrix.value_scale_x()

    def get_scale_y(self):
        """
        Gets the scale_y of the current matrix
        """
        return self.matrix.value_scale_y()

    def get_skew_x(self):
        """
        Gets the skew_x of the current matrix()
        """
        return self.matrix.value_skew_x()

    def get_skew_y(self):
        """
        Gets the skew_y of the current matrix()
        """
        return self.matrix.value_skew_y()

    def get_translate_x(self):
        """
        Gets the translate_x of the current matrix()
        """
        return self.matrix.value_trans_x()

    def get_translate_y(self):
        """
        Gets the translate_y of the current matrix()
        """
        return self.matrix.value_trans_y()