diff options
Diffstat (limited to 'unrpyc/renpy/display/render.pyx')
-rw-r--r-- | unrpyc/renpy/display/render.pyx | 1174 |
1 files changed, 1174 insertions, 0 deletions
diff --git a/unrpyc/renpy/display/render.pyx b/unrpyc/renpy/display/render.pyx new file mode 100644 index 0000000..5aa45a0 --- /dev/null +++ b/unrpyc/renpy/display/render.pyx @@ -0,0 +1,1174 @@ +#cython: profile=False +# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us> +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import collections +import pygame +import threading +import renpy +import gc + +# We grab the blit lock each time it is necessary to blit +# something. This allows call to the pygame.transform functions to +# disable blitting, should it prove necessary. +blit_lock = threading.Condition() + +# This is a dictionary containing all the renders that we know of. It's a +# map from displayable to dictionaries containing the render of that +# displayable. +render_cache = collections.defaultdict(dict) + +# The queue of redraws. A list of (time, displayable) pairs. +redraw_queue = [ ] + +# The render returned from render_screen. +screen_render = None + +# A list of renders the system knows about, and thinks are still alive. +cdef list live_renders +live_renders = [ ] + +# A copy of renpy.display.interface.frame_time, for speed reasons. +cdef double frame_time +frame_time = 0 + +def free_memory(): + """ + Frees memory used by the render system. + """ + + global screen_render + screen_render = None + + mark_sweep() + + render_cache.clear() + + # This can hang onto a render. + renpy.display.interface.surftree = None + + +def check_at_shutdown(): + """ + This is called at shutdown time to check that everything went okay. + The big thing it checks for is memory leaks. + """ + + if not renpy.config.developer: + return + + free_memory() + + gc.collect() + l = gc.get_objects() + + count = 0 + objects = gc.get_objects() + + for i in objects: + if isinstance(i, Render): + count += 1 + + if count: + raise Exception("%d Renders are alive at shutdown. This is probably a memory leak bug in Ren'Py." % count) + + + +cpdef render(d, object widtho, object heighto, double st, double at): + """ + :doc: udd_utility + :args: (d, width, height, st, at) + + Causes a displayable to be rendered, and a renpy.Render object to + be returned. + + `d` + The displayable to render. + + `width`, `height` + The width and height available for the displayable to render into. + + `st`, `at` + The shown and animation timebases. + + Renders returned by this object may be cached, and should not be modified + once they have been retrieved. + """ + + cdef float width, height + cdef float orig_width, orig_height + cdef tuple orig_wh, wh + cdef dict render_cache_d + cdef Render rv + + orig_wh = (widtho, heighto, frame_time-st, frame_time-at) + + render_cache_d = render_cache[d] + rv = render_cache_d.get(orig_wh, None) + + if rv is not None: + return rv + + orig_width = width = widtho + orig_height = height = heighto + + style = d.style + xmaximum = style.xmaximum + ymaximum = style.ymaximum + + if xmaximum is not None: + if isinstance(xmaximum, float): + width = width * xmaximum + else: + width = min(xmaximum, width) + + if ymaximum is not None: + if isinstance(ymaximum, float): + height = height * ymaximum + else: + height = min(ymaximum, height) + + if width < 0: + width = 0 + if height < 0: + height = 0 + + if orig_width != width or orig_height != height: + widtho = width + heighto = height + wh = (widtho, heighto, frame_time-st, frame_time-at) + rv = render_cache_d.get(wh, None) + + if rv is not None: + return rv + + else: + wh = orig_wh + + rv = d.render(widtho, heighto, st, at) + + rv.render_of.append(d) + + if style.clipping: + rv = rv.subsurface((0, 0, rv.width, rv.height), focus=True) + rv.render_of.append(d) + + render_cache_d[wh] = rv + + if wh is not orig_wh: + render_cache_d[orig_wh] = rv + + return rv + + +# This is true if something has been invalidated, and a redraw needs +# to occur. It's automatically cleared to False at the end of each +# redraw. +invalidated = False + +def invalidate(d): + """ + Removes d from the render cache. If we're not in a redraw, triggers + a redraw to start. + """ + + global invalidated + + if d in render_cache: + for v in render_cache[d].values(): + v.kill_cache() + + invalidated = True + + +def process_redraws(): + """ + Called to determine if any redraws are pending. Returns true if we + need to redraw the screen now, false otherwise. + """ + + global redraw_queue + + redraw_queue.sort() + + now = renpy.display.core.get_time() + rv = invalidated + + new_redraw_queue = [ ] + seen = set() + + for t in redraw_queue: + when, d = t + + if d in seen: + continue + + seen.add(d) + + if d not in render_cache: + continue + + if when <= now: + # Remove this displayable and all its parents from the + # render cache. But don't kill them yet, as that will kill the + # children that we want to reuse. + + for v in render_cache[d].values(): + v.kill_cache() + + rv = True + + else: + new_redraw_queue.append(t) + + redraw_queue = new_redraw_queue + + return rv + + +def redraw_time(): + """ + Returns the time at which the next redraw is scheduled. + """ + + if redraw_queue: + return redraw_queue[0][0] + + return None + + +def redraw(d, when): + """ + :doc: udd_utility + + Causes the displayable `d` to be redrawn after `when` seconds have + elapsed. + """ + + if not renpy.game.interface: + return + + redraw_queue.append((when + renpy.game.interface.frame_time, d)) + + +cdef class Matrix2D: + """ + This represents a 2d matrix that can be used to transform + points and things like that. + """ + + def __getstate__(self): + return dict( + xdx = self.xdx, + xdy = self.xdy, + ydx = self.ydx, + ydy = self.ydy) + + def __setstate__(self, state): + self.xdx = state['xdx'] + self.xdy = state['xdy'] + self.ydx = state['ydx'] + self.ydy = state['ydy'] + + def __init__(Matrix2D self, double xdx, double xdy, double ydx, double ydy): + self.xdx = xdx + self.xdy = xdy + self.ydx = ydx + self.ydy = ydy + + cpdef tuple transform(Matrix2D self, double x, double y): + return (x * self.xdx + y * self.xdy), (x * self.ydx + y * self.ydy) + + def __mul__(Matrix2D self, Matrix2D other): + return Matrix2D( + other.xdx * self.xdx + other.xdy * self.ydx, + other.xdx * self.xdy + other.xdy * self.ydy, + other.ydx * self.xdx + other.ydy * self.ydx, + other.ydx * self.xdy + other.ydy * self.ydy) + + def __repr__(self): + return "Matrix2D(xdx=%f, xdy=%f, ydx=%f, ydy=%f)" % (self.xdx, self.xdy, self.ydx, self.ydy) + +IDENTITY = Matrix2D(1, 0, 0, 1) + +def take_focuses(focuses): + """ + Adds a list of rectangular focus regions to the focuses list. + """ + + screen_render.take_focuses( + 0, 0, screen_render.width, screen_render.height, + IDENTITY, 0, 0, focuses) + +# The result of focus_at_point for a modal render. This overrides any +# specific focus from below us. +Modal = object() + +def focus_at_point(x, y): + """ + Returns a focus object corresponding to the uppermost displayable + at point, or None if nothing focusable is at point. + """ + + if screen_render is None: + return None + + cf = screen_render.focus_at_point(x, y) + if cf is None or cf is Modal: + return None + else: + d, arg = cf + return renpy.display.focus.Focus(d, arg, None, None, None, None) + + +def mutated_surface(surf): + """ + Called to indicate that the given surface has changed. + """ + + renpy.display.draw.mutated_surface(surf) + + +def render_screen(root, width, height): + """ + Renders `root` (a displayable) as the root of a screen with the given + `width` and `height`. + """ + + + + global old_screen_render + global screen_render + global invalidated + global frame_time + + frame_time = renpy.display.interface.frame_time + + rv = render(root, width, height, 0, 0) + screen_render = rv + + invalidated = False + + rv.is_opaque() + + return rv + +def mark_sweep(): + """ + This performs mark-and-sweep garbage collection on the live_renders + list. + """ + + global live_renders + + cdef list worklist + cdef int i + cdef Render r, j + + worklist = [ ] + + if screen_render is not None: + worklist.append(screen_render) + + i = 0 + + while i < len(worklist): + r = worklist[i] + + for j in r.depends_on_list: + if not j.mark: + j.mark = True + worklist.append(j) + + i += 1 + + for r in live_renders: + if not r.mark: + r.kill_cache() + else: + r.mark = False + + live_renders = worklist + +def compute_subline(sx0, sw, cx0, cw): + """ + Given a source line (start sx0, width sw) and a crop line (cx0, cw), + return three things: + + * The offset of the portion of the source line that overlaps with + the crop line, relative to the crop line. + * The offset of the portion of the source line that overlaps with the + the crop line, relative to the source line. + * The length of the overlap in pixels. (can be <= 0) + """ + + sx1 = sx0 + sw + cx1 = cx0 + cw + + if sx0 > cx0: + start = sx0 + else: + start = cx0 + + offset = start - cx0 + crop = start - sx0 + + if sx1 < cx1: + width = sx1 - start + else: + width = cx1 - start + + + return offset, crop, width + + + + +# Possible operations that can be done as part of a render. +BLIT = 0 +DISSOLVE = 1 +IMAGEDISSOLVE = 2 +PIXELLATE = 3 + +cdef class Render: + + def __init__(Render self, float width, float height, draw_func=None, layer_name=None, bint opaque=None): #@DuplicatedSignature + """ + Creates a new render corresponding to the given widget with + the specified width and height. + + If `layer_name` is given, then this render corresponds to a + layer. + """ + + # The mark bit, used for mark/sweep-style garbage collection of + # renders. + self.mark = False + + # Is has this render been removed from the cache? + self.cache_killed = False + + + self.width = width + self.height = height + + self.layer_name = layer_name + + # A list of (surface/render, xoffset, yoffset, focus, main) tuples, ordered from + # back to front. + self.children = [ ] + + # The set of renders that either have us as children, or depend on + # us. + self.parents = set() + + # The renders we depend on, including our children. + self.depends_on_list = [ ] + + # The operation we're performing. (BLIT, DISSOLVE, OR IMAGE_DISSOLVE) + self.operation = BLIT + + # The fraction of the operation that is complete. + self.operation_complete = 0.0 + + # Should the dissolve operations preserve alpha? + self.operation_alpha = False + + # The parameter to the operation. + self.operation_parameter = 0 + + # Forward is used to transform from screen coordinates to child + # coordinates. + # Reverse is used to transform from child coordinates to screen + # coordinates. + # + # For performance reasons, these aren't used to transform the + # x and y offsets found in self.children. Those offsets should + # be of the (0, 0) point in the child coordinate space. + self.forward = None + self.reverse = None + + # This is used to adjust the alpha of children of this render. + self.alpha = 1 + + # A list of focus regions in this displayable. + self.focuses = None + + # Other renders that we should pass focus onto. + self.pass_focuses = None + + # The displayable(s) that this is a render of. (Set by render) + self.render_of = [ ] + + # If set, this is a function that's called to draw this render + # instead of the default. + self.draw_func = draw_func + + # Is this displayable opaque? (May be set on init, or later on + # if we have opaque children.) This may be True, False, or None + # to indicate we don't know yet. + self.opaque = opaque + + # A list of our visible children. (That is, children above and + # including our uppermost opaque child.) If nothing is opaque, + # includes all children. + self.visible_children = self.children + + # Should children be clipped to a rectangle? + self.clipping = False + + # Caches of the texture created by rendering this surface. + self.surface = None + self.alpha_surface = None + + # Cache of the texture created by rendering this surface at half size. + # (This is set in gldraw.) + self.half_cache = None + + # Are we modal? + self.modal = False + + live_renders.append(self) + + def __repr__(self): #@DuplicatedSignature + return "<Render %x of %r>" % (id(self), self.render_of) + + def __getstate__(self): #@DuplicatedSignature + if renpy.config.developer: + raise Exception("Can't pickle a Render.") + else: + return { } + + def __setstate__(self, state): #@DuplicatedSignature + return + + cpdef int blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None): + """ + Blits `source` (a Render or Surface) to this Render, offset by + xo and yo. + + If `focus` is true, then focuses are added from the child to the + parent. + + This will only blit on integer pixel boundaries. + """ + + (xo, yo) = pos + + if source is self: + raise Exception("Blitting to self.") + + xo = int(xo) + yo = int(yo) + + if index is None: + self.children.append((source, xo, yo, focus, main)) + else: + self.children.insert(index, (source, xo, yo, focus, main)) + + if isinstance(source, Render): + self.depends_on_list.append(source) + source.parents.add(self) + + return 0 + + cpdef int subpixel_blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None): + """ + Blits `source` (a Render or Surface) to this Render, offset by + xo and yo. + + If `focus` is true, then focuses are added from the child to the + parent. + + This blits at fractional pixel boundaries. + """ + + (xo, yo) = pos + + xo = float(xo) + yo = float(yo) + + if index is None: + self.children.append((source, xo, yo, focus, main)) + else: + self.children.insert(index, (source, xo, yo, focus, main)) + + if isinstance(source, Render): + self.depends_on_list.append(source) + source.parents.add(self) + + return 0 + + def get_size(self): + """ + Returns the size of this Render, a mostly ficticious value + that's taken from the inputs to the constructor. (As in, we + don't clip to this size.) + """ + + return self.width, self.height + + + def render_to_texture(self, alpha=True): + """ + Returns a texture constructed from this render. This may return + a cached textue, if one has already been rendered. + + `alpha` is a hint that controls if the surface should have + alpha or not. + """ + + if alpha: + if self.alpha_surface is not None: + return self.alpha_surface + else: + if self.surface is not None: + return self.surface + + rv = None + + opaque = self.is_opaque() + + # If we can, reuse a child's texture. + if opaque or alpha: + + if not self.forward and len(self.children) == 1: + child, x, y, focus, main = self.children[0] + cw, ch = child.get_size() + if x <= 0 and y <= 0 and cw + x >= self.width and ch + y >= self.height: + # Our single child overlaps us. + if isinstance(child, Render): + child = child.render_to_texture(alpha) + + if x != 0 or y != 0 or cw != self.width or ch != self.height: + rv = child.subsurface((-x, -y, self.width, self.height)) + else: + rv = child + + # Otherwise, render to a texture. + if rv is None: + # is_opaque has already been called. + rv = renpy.display.draw.render_to_texture(self, alpha) + + # Stash and return the surface. + if alpha: + self.alpha_surface = rv + else: + self.surface = rv + + return rv + + pygame_surface = render_to_texture + + def subsurface(self, rect, focus=False): + """ + Returns a subsurface of this render. If `focus` is true, then + the focuses are copied from this render to the child. + """ + + (x, y, w, h) = rect + rv = Render(w, h) + + reverse = self.reverse + + # This doesn't actually make a subsurface, as we can't easily do + # so for non-rectangle-aligned renders. + if (reverse is not None) and ( + reverse.xdx != 1.0 or + reverse.xdy != 0.0 or + reverse.ydx != 0.0 or + reverse.ydy != 1.0): + + rv.clipping = True + rv.blit(self, (-x, -y), focus=focus, main=True) + return rv + + # This is the path that executes for rectangle-aligned surfaces, + # making an actual subsurface. + + for child, cx, cy, cfocus, cmain in self.children: + + cw, ch = child.get_size() + xo, cx, cw = compute_subline(cx, cw, x, w) + yo, cy, ch = compute_subline(cy, ch, y, h) + + if cw <= 0 or ch <= 0: + continue + + crop = (cx, cy, cw, ch) + offset = (xo, yo) + + if isinstance(child, Render): + newchild = child.subsurface(crop, focus=focus) + newchild.render_of = child.render_of[:] + else: + newchild = child.subsurface(crop) + renpy.display.draw.mutated_surface(newchild) + + rv.blit(newchild, offset, focus=cfocus, main=cmain) + + if focus and self.focuses: + + for (d, arg, xo, yo, fw, fh, mx, my, mask) in self.focuses: + + if xo is None: + rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask) + continue + + xo, cx, fw = compute_subline(xo, fw, x, w) + yo, cy, fh = compute_subline(yo, fh, y, h) + + if cw <= 0 or ch <= 0: + continue + + if mx is not None: + + mw, mh = mask.get_size() + + mx, mcx, mw = compute_subline(mx, mw, x, w) + my, mcy, mh = compute_subline(my, mh, y, h) + + if mw <= 0 or mh <= 0: + mx = None + my = None + mask = None + else: + mask = mask.subsurface((mcx, mcy, mw, mh)) + + rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask) + + rv.depends_on(self) + rv.alpha = self.alpha + rv.operation = self.operation + rv.operation_alpha = self.operation_alpha + rv.operation_complete = self.operation_complete + + return rv + + + def depends_on(self, source, focus=False): + """ + Used to indicate that this render depends on another + render. Useful, for example, if we use pygame_surface to make + a surface, and then blit that surface into another render. + """ + + if source is self: + raise Exception("Render depends on itself.") + + self.depends_on_list.append(source) + source.parents.add(self) + + if focus: + if self.pass_focuses is None: + self.pass_focuses = [ source ] + else: + self.pass_focuses.append(source) + + + def kill_cache(self): + """ + Removes this render and its transitive parents from the cache. + """ + + if self.cache_killed: + return + + self.cache_killed = True + + for i in self.parents: + i.kill_cache() + + self.parents.clear() + + for i in self.depends_on_list: + if not i.cache_killed: + i.parents.discard(self) + + for ro in self.render_of: + cache = render_cache[ro] + for k, v in cache.items(): + if v is self: + del cache[k] + + if not cache: + del render_cache[ro] + + def kill(self): + """ + Retained for compatibility. + """ + + def add_focus(self, d, arg=None, x=0, y=0, w=None, h=None, mx=None, my=None, mask=None): + """ + This is called to indicate a region of the screen that can be + focused. + + `d` - the displayable that is being focused. + `arg` - an argument. + + The rest of the parameters are a rectangle giving the portion of + this region corresponding to the focus. If they are all None, than + this focus is assumed to be the singular full-screen focus. + """ + + if mask is not None and mask is not self: + self.depends_on(mask) + + t = (d, arg, x, y, w, h, mx, my, mask) + + if self.focuses is None: + self.focuses = [ t ] + else: + self.focuses.append(t) + + def take_focuses(self, cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses): #@DuplicatedSignature + """ + This adds to focuses Focus objects corresponding to the focuses + added to this object and its children, transformed into screen + coordinates. + + `cminx`, `cminy`, `cmaxx`, `cmaxy` - The clipping rectangle. + `reverse` - The transform from render to screen coordinates. + `x`, `y` - The offset of the upper-left corner of the render. + `focuses` - The list of focuses to add to. + """ + + if self.modal: + focuses[:] = [ ] + + if self.reverse: + reverse = reverse * self.reverse + + if self.focuses: + + for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses: + + if xo is None: + focuses.append(renpy.display.focus.Focus(d, arg, None, None, None, None)) + continue + + x1, y1 = reverse.transform(xo, yo) + x2, y2 = reverse.transform(xo + w, yo + h) + + minx = min(x1, x2) + x + miny = min(y1, y2) + y + maxx = max(x1, x2) + x + maxy = max(y1, y2) + y + + minx = max(minx, cminx) + miny = max(miny, cminy) + maxx = min(maxx, cmaxx) + maxy = min(maxy, cmaxy) + + if minx >= maxx or miny >= maxy: + continue + + focuses.append(renpy.display.focus.Focus(d, arg, minx, miny, maxx - minx, maxy - miny)) + + if self.clipping: + cminx = max(cminx, x) + cminy = max(cminy, y) + cmaxx = min(cmaxx, x + self.width) + cmaxy = min(cmaxx, x + self.height) + + for child, xo, yo, focus, main in self.children: + if not focus or not isinstance(child, Render): + continue + + xo, yo = reverse.transform(xo, yo) + child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x + xo, y + yo, focuses) + + if self.pass_focuses: + for child in self.pass_focuses: + child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses) + + def focus_at_point(self, x, y): #@DuplicatedSignature + """ + This returns the focus of this object at the given point. + """ + + if self.clipping: + if x < 0 or x >= self.width or y < 0 or y >= self.height: + return None + + rv = None + + if self.focuses: + for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses: + + if xo is None: + continue + + elif mx is not None: + cx = x - mx + cy = y - my + + if self.forward: + cx, cy = self.forward.transform(cx, cy) + + if mask.is_pixel_opaque(cx, cy): + rv = d, arg + + elif xo <= x < xo + w and yo <= y < yo + h: + rv = d, arg + + for child, xo, yo, focus, main in self.children: + + if not focus or not isinstance(child, Render): + continue + + cx = x - xo + cy = y - yo + + if self.forward: + cx, cy = self.forward.transform(cx, cy) + + cf = child.focus_at_point(cx, cy) + if cf is not None: + rv = cf + + if self.pass_focuses: + for child in self.pass_focuses: + cf = child.focus_at_point(x, y) + if cf is not None: + rv = cf + + if rv is None and self.modal: + rv = Modal + + return rv + + + def main_displayables_at_point(self, x, y, layers, depth=None): + """ + Returns the displayable at `x`, `y` on one of the layers in + the set or list `layers`. + """ + + rv = [ ] + + if x < 0 or y < 0 or x >= self.width or y >= self.height: + return rv + + if depth is not None: + for d in self.render_of: + rv.append((depth, self.width, self.height, d)) + depth += 1 + elif self.layer_name in layers: + depth = 0 + + for (child, xo, yo, focus, main) in self.children: + if not main or not isinstance(child, Render): + continue + + cx = x - xo + cy = y - yo + + if self.forward: + cx, cy = self.forward.transform(cx, cy) + + cf = child.main_displayables_at_point(cx, cy, layers, depth) + rv.extend(cf) + + return rv + + + def is_opaque(self): + """ + Returns true if this displayable is opaque, or False otherwise. + Also sets self.visible_children. + """ + + if self.opaque is not None: + return self.opaque + + # A rotated image is never opaque. (This isn't actually true, but it + # saves us from the expensive calculations require to prove it is.) + if self.forward: + self.opaque = False + return False + + rv = False + vc = [ ] + + for i in self.children: + child, xo, yo, focus, main = i + + if xo <= 0 and yo <= 0: + cw, ch = child.get_size() + if cw + xo < self.width or ch + yo < self.height: + if child.is_opaque(): + vc = [ ] + rv = True + + vc.append(i) + + self.visible_children = vc + self.opaque = rv + return rv + + + def is_pixel_opaque(self, x, y): + """ + Determine if the pixel at x and y is opaque or not. + """ + + if x < 0 or y < 0 or x >= self.width or y >= self.height: + return False + + if self.is_opaque(): + return True + + return renpy.display.draw.is_pixel_opaque(self, x, y) + + + def fill(self, color): + """ + Fills this Render with the given color. + """ + + color = renpy.easy.color(color) + solid = renpy.display.imagelike.Solid(color) + surf = render(solid, self.width, self.height, 0, 0) + self.blit(surf, (0, 0), focus=False, main=False) + + + def canvas(self): + """ + Returns a canvas object that draws to this Render. + """ + + surf = renpy.display.pgrender.surface((self.width, self.height), True) + + mutated_surface(surf) + + self.blit(surf, (0, 0)) + + return Canvas(surf) + + +class Canvas(object): + + def __init__(self, surf): #@DuplicatedSignature + self.surf = surf + + def rect(self, color, rect, width=0): + + try: + blit_lock.acquire() + pygame.draw.rect(self.surf, + renpy.easy.color(color), + rect, + width) + finally: + blit_lock.release() + + def polygon(self, color, pointlist, width=0): + try: + blit_lock.acquire() + pygame.draw.polygon(self.surf, + renpy.easy.color(color), + pointlist, + width) + finally: + blit_lock.release() + + def circle(self, color, pos, radius, width=0): + + try: + blit_lock.acquire() + pygame.draw.circle(self.surf, + renpy.easy.color(color), + pos, + radius, + width) + + finally: + blit_lock.release() + + def ellipse(self, color, rect, width=0): + try: + blit_lock.acquire() + pygame.draw.ellipse(self.surf, + renpy.easy.color(color), + rect, + width) + finally: + blit_lock.release() + + + def arc(self, color, rect, start_angle, stop_angle, width=1): + try: + blit_lock.acquire() + pygame.draw.arc(self.surf, + renpy.easy.color(color), + rect, + start_angle, + stop_angle, + width) + finally: + blit_lock.release() + + + def line(self, color, start_pos, end_pos, width=1): + try: + blit_lock.acquire() + pygame.draw.line(self.surf, + renpy.easy.color(color), + start_pos, + end_pos, + width) + finally: + blit_lock.release() + + def lines(self, color, closed, pointlist, width=1): + try: + blit_lock.acquire() + pygame.draw.lines(self.surf, + renpy.easy.color(color), + closed, + pointlist, + width) + finally: + blit_lock.release() + + def aaline(self, color, startpos, endpos, blend=1): + try: + blit_lock.acquire() + pygame.draw.aaline(self.surf, + renpy.easy.color(color), + startpos, + endpos, + blend) + finally: + blit_lock.release() + + def aalines(self, color, closed, pointlist, blend=1): + try: + blit_lock.acquire() + pygame.draw.aalines(self.surf, + renpy.easy.color(color), + closed, + pointlist, + blend) + finally: + blit_lock.release() |