예제 #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
예제 #2
0
파일: widget.py 프로젝트: meerk40t/meerk40t
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()