summaryrefslogtreecommitdiff
path: root/unrpyc/renpy/display/im.py
diff options
context:
space:
mode:
Diffstat (limited to 'unrpyc/renpy/display/im.py')
-rw-r--r--unrpyc/renpy/display/im.py1562
1 files changed, 0 insertions, 1562 deletions
diff --git a/unrpyc/renpy/display/im.py b/unrpyc/renpy/display/im.py
deleted file mode 100644
index d90fb51..0000000
--- a/unrpyc/renpy/display/im.py
+++ /dev/null
@@ -1,1562 +0,0 @@
-# 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.
-
-# This file contains the new image code, which includes provisions for
-# size-based caching and constructing images from operations (like
-# cropping and scaling).
-
-import renpy.display
-
-import math
-import zipfile
-import io
-import threading
-import time
-
-
-# This is an entry in the image cache.
-class CacheEntry(object):
-
- def __init__(self, what, surf):
-
- # The object that is being cached (which needs to be
- # hashable and comparable).
- self.what = what
-
- # The pygame surface corresponding to the cached object.
- self.surf = surf
-
- # The size of this image.
- w, h = surf.get_size()
- self.size = w * h
-
- # The time when this cache entry was last used.
- self.time = 0
-
-# This is the singleton image cache.
-class Cache(object):
-
- def __init__(self):
-
- # The current arbitrary time. (Increments by one for each
- # interaction.)
- self.time = 0
-
- # A map from Image object to CacheEntry.
- self.cache = { }
-
- # A list of Image objects that we want to preload.
- self.preloads = [ ]
-
- # False if this is not the first preload in this tick.
- self.first_preload_in_tick = True
-
- # The total size of the current generation of images.
- self.size_of_current_generation = 0
-
- # The total size of everything in the cache.
- self.total_cache_size = 0
-
- # A lock that must be held when updating the cache.
- self.lock = threading.Condition()
-
- # A lock that mist be held to notify the preload thread.
- self.preload_lock = threading.Condition()
-
- # Is the preload_thread alive?
- self.keep_preloading = True
-
- # A map from image object to surface, only for objects that have
- # been pinned into memory.
- self.pin_cache = { }
-
- # Images that we tried, and failed, to preload.
- self.preload_blacklist = set()
-
- # The size of the cache, in pixels.
- self.cache_limit = 0
-
- # The preload thread.
- self.preload_thread = threading.Thread(target=self.preload_thread_main, name="preloader")
- self.preload_thread.setDaemon(True)
- self.preload_thread.start()
-
- # Have we been added this tick?
- self.added = set()
-
- # A list of (time, filename, preload) tuples. This is updated when
- # config.developer is True and an image is loaded. Preload is a
- # flag that is true if the image was loaded from the preload
- # thread. The log is limited to 100 entries, and the newest entry
- # is first.
- #
- # This is only updated when config.developer is True.
- self.load_log = [ ]
-
-
- def init(self):
- """
- Updates the cache object to make use of settings that might be provided
- by the game-maker.
- """
-
- self.cache_limit = renpy.config.image_cache_size * renpy.config.screen_width * renpy.config.screen_height
-
- def quit(self): #@ReservedAssignment
- if not self.preload_thread.isAlive():
- return
-
- with self.preload_lock:
- self.keep_preloading = False
- self.preload_lock.notify()
-
- self.preload_thread.join()
-
-
- # Clears out the cache.
- def clear(self):
-
- self.lock.acquire()
-
- self.preloads = [ ]
- self.pin_cache = { }
- self.cache = { }
- self.first_preload_in_tick = True
- self.size_of_current_generation = 0
- self.total_cache_size = 0
-
- self.added.clear()
-
- self.lock.release()
-
- # Increments time, and clears the list of images to be
- # preloaded.
- def tick(self):
-
- with self.lock:
- self.time += 1
- self.preloads = [ ]
- self.first_preload_in_tick = True
- self.size_of_current_generation = 0
- self.added.clear()
-
- if renpy.config.debug_image_cache:
- renpy.display.ic_log.write("----")
- filename, line = renpy.exports.get_filename_line()
- renpy.display.ic_log.write("%s %d", filename, line)
-
- # The preload thread can deal with this update, so we don't need
- # to lock things.
- def end_tick(self):
- self.preloads = [ ]
-
-
- # This returns the pygame surface corresponding to the provided
- # image. It also takes care of updating the age of images in the
- # cache to be current, and maintaining the size of the current
- # generation of images.
- def get(self, image, predict=False):
-
- if not isinstance(image, ImageBase):
- raise Exception("Expected an image of some sort, but got" + str(image) + ".")
-
- if not image.cache:
- surf = image.load()
- renpy.display.render.mutated_surface(surf)
- return surf
-
- # First try to grab the image out of the cache without locking it.
- ce = self.cache.get(image, None)
-
- # Otherwise, we load the image ourselves.
- if ce is None:
-
- try:
- if image in self.pin_cache:
- surf = self.pin_cache[image]
- else:
- surf = image.load()
-
- except:
- raise
-
- with self.lock:
-
- ce = CacheEntry(image, surf)
-
- if image not in self.cache:
- self.total_cache_size += ce.size
-
- self.cache[image] = ce
-
- # Indicate that this surface had changed.
- renpy.display.render.mutated_surface(ce.surf)
-
- if renpy.config.debug_image_cache:
- if predict:
- renpy.display.ic_log.write("Added %r (%.02f%%)", ce.what, 100.0 * self.total_cache_size / self.cache_limit)
- else:
- renpy.display.ic_log.write("Total Miss %r", ce.what)
-
- renpy.display.draw.load_texture(ce.surf)
-
-
- # Move it into the current generation. This isn't protected by
- # a lock, so in certain circumstances we could have an
- # inaccurate size - but that will be cured at the end of the
- # current generation.
-
- if ce.time != self.time:
- ce.time = self.time
- self.size_of_current_generation += ce.size
-
- # Done... return the surface.
- return ce.surf
-
-
- # This kills off a given cache entry.
- def kill(self, ce):
-
- # Should never happen... but...
- if ce.time == self.time:
- self.size_of_current_generation -= ce.size
-
- self.total_cache_size -= ce.size
- del self.cache[ce.what]
-
- if renpy.config.debug_image_cache:
- renpy.display.ic_log.write("Removed %r", ce.what)
-
- def cleanout(self):
- """
- Cleans out the cache, if it's gotten too large. Returns True
- if the cache is smaller than the size limit, or False if it's
- bigger and we don't want to continue preloading.
- """
-
- # If we're within the limit, return.
- if self.total_cache_size <= self.cache_limit:
- return True
-
- # If we're outside the cache limit, we need to go and start
- # killing off some of the entries until we're back inside it.
-
- for ce in sorted(iter(self.cache.values()), key=lambda a : a.time):
-
- if ce.time == self.time:
- # If we're bigger than the limit, and there's nothing
- # to remove, we should stop the preloading right away.
- return False
-
- # Otherwise, kill off the given cache entry.
- self.kill(ce)
-
- # If we're in the limit, we're done.
- if self.total_cache_size <= self.cache_limit:
- break
-
- return True
-
-
- # Called to report that a given image would like to be preloaded.
- def preload_image(self, im):
-
- if not isinstance(im, ImageBase):
- return
-
- with self.lock:
-
- if im in self.added:
- return
-
- self.added.add(im)
-
- if im in self.cache:
- self.get(im)
- in_cache = True
- else:
- self.preloads.append(im)
- in_cache = False
-
- if not in_cache:
-
- with self.preload_lock:
- self.preload_lock.notify()
-
- if in_cache and renpy.config.debug_image_cache:
- renpy.display.ic_log.write("Kept %r", im)
-
-
- def start_prediction(self):
- """
- Called at the start of prediction, to ensure the thread runs
- at least once to clean out the cache.
- """
-
- with self.preload_lock:
- self.preload_lock.notify()
-
- def preload_thread_main(self):
-
- while self.keep_preloading:
-
- self.preload_lock.acquire()
- self.preload_lock.wait()
- self.preload_lock.release()
-
- while self.preloads and self.keep_preloading:
-
- start = time.time()
-
- # If the size of the current generation is bigger than the
- # total cache size, stop preloading.
- with self.lock:
-
- # If the cache is overfull, clean it out.
- if not self.cleanout():
-
- if renpy.config.debug_image_cache:
- for i in self.preloads:
- renpy.display.ic_log.write("Overfull %r", i)
-
- self.preloads = [ ]
-
- break
-
- try:
- image = self.preloads.pop(0)
-
- if image not in self.preload_blacklist:
- try:
- self.get(image, True)
- except:
- self.preload_blacklist.add(image)
- except:
- pass
-
- with self.lock:
- self.cleanout()
-
- # If we have time, preload pinned images.
- if self.keep_preloading and not renpy.game.less_memory:
-
- workset = set(renpy.store._cache_pin_set)
-
- # Remove things that are not in the workset from the pin cache,
- # and remove things that are in the workset from pin cache.
- for i in list(self.pin_cache.keys()):
-
- if i in workset:
- workset.remove(i)
- else:
- surf = self.pin_cache[i]
-
- del self.pin_cache[i]
-
-
- # For each image in the worklist...
- for image in workset:
-
- if image in self.preload_blacklist:
- continue
-
- # If we have normal preloads, break out.
- if self.preloads:
- break
-
- try:
- surf = image.load()
- self.pin_cache[image] = surf
- renpy.display.draw.load_texture(surf)
- except:
- self.preload_blacklist.add(image)
-
- def add_load_log(self, filename):
-
- if not renpy.config.developer:
- return
-
- preload = (threading.current_thread() is self.preload_thread)
-
- self.load_log.insert(0, (time.time(), filename, preload))
-
- while len(self.load_log) > 100:
- self.load_log.pop()
-
-
-
-# The cache object.
-cache = Cache()
-
-def free_memory():
- """
- Frees some memory.
- """
-
- renpy.display.draw.free_memory()
- cache.clear()
-
-
-class ImageBase(renpy.display.core.Displayable):
- """
- This is the base class for all of the various kinds of images that
- we can possibly have.
- """
-
- __version__ = 1
-
- def after_upgrade(self, version):
- if version < 1:
- self.cache = True
-
- def __init__(self, *args, **properties):
-
- self.rle = properties.pop('rle', None)
- self.cache = properties.pop('cache', True)
-
- properties.setdefault('style', 'image')
-
- super(ImageBase, self).__init__(**properties)
- self.identity = (type(self).__name__, ) + args
-
-
- def __hash__(self):
- return hash(self.identity)
-
- def __eq__(self, other):
-
- if not isinstance(other, ImageBase):
- return False
-
- return self.identity == other.identity
-
- def __repr__(self):
- return "<" + " ".join([repr(i) for i in self.identity]) + ">"
-
- def load(self):
- """
- This function is called by the image cache code to cause this
- image to be loaded. It's expected that children of this class
- would override this.
- """
-
- assert False
-
- def render(self, w, h, st, at):
-
- im = cache.get(self)
- texture = renpy.display.draw.load_texture(im)
-
- w, h = im.get_size()
- rv = renpy.display.render.Render(w, h)
- rv.blit(texture, (0, 0))
- return rv
-
- def predict_one(self):
- renpy.display.predict.image(self)
-
- def predict_files(self):
- """
- Returns a list of files that will be accessed when this image
- operation is performed.
- """
-
- return [ ]
-
-class Image(ImageBase):
- """
- This image manipulator loads an image from a file.
- """
-
- def __init__(self, filename, **properties):
- """
- @param filename: The filename that the image will be loaded from.
- """
-
- super(Image, self).__init__(filename, **properties)
- self.filename = filename
-
- def get_mtime(self):
- return renpy.loader.get_mtime(self.filename)
-
- def load(self, unscaled=False):
-
- cache.add_load_log(self.filename)
-
- try:
-
- if unscaled:
- surf = renpy.display.pgrender.load_image_unscaled(renpy.loader.load(self.filename), self.filename)
- else:
- surf = renpy.display.pgrender.load_image(renpy.loader.load(self.filename), self.filename)
-
- return surf
-
- except Exception as e:
-
- if renpy.config.missing_image_callback:
- im = renpy.config.missing_image_callback(self.filename)
- if im is None:
- raise e
-
- return im.load()
-
- raise
-
- def predict_files(self):
-
- if renpy.loader.loadable(self.filename):
- return [ self.filename ]
- else:
- if renpy.config.missing_image_callback:
- im = renpy.config.missing_image_callback(self.filename)
- if im is not None:
- return im.predict_files()
-
- return [ self.filename ]
-
-class ZipFileImage(ImageBase):
-
- def __init__(self, zipfilename, filename, mtime=0, **properties):
- super(ZipFileImage, self).__init__(zipfilename, filename, mtime, **properties)
-
- self.zipfilename = zipfilename
- self.filename = filename
-
- def load(self):
- try:
- zf = zipfile.ZipFile(self.zipfilename, 'r')
- data = zf.read(self.filename)
- sio = io.StringIO(data)
- rv = renpy.display.pgrender.load_image(sio, self.filename)
- zf.close()
- return rv
- except:
- return renpy.display.pgrender.surface((2, 2), True)
-
-
-
- def predict_files(self):
- return [ ]
-
-
-
-class Composite(ImageBase):
- """
- :doc: im_im
-
- This image manipulator composites multiple images together to
- form a single image.
-
- The `size` should be a (width, height) tuple giving the size
- of the composed image.
-
- The remaining positional arguments are interpreted as groups of
- two. The first argument in a group should be an (x, y) tuple,
- while the second should be an image manipulator. The image
- produced by the image manipulator is composited at the location
- given by the tuple.
-
- ::
-
- image girl clothed happy = im.Composite(
- (300, 600),
- (0, 0), "girl_body.png",
- (0, 0), "girl_clothes.png",
- (100, 100), "girl_happy.png"
- )
-
- """
-
- def __init__(self, size, *args, **properties):
-
- super(Composite, self).__init__(size, *args, **properties)
-
- if len(args) % 2 != 0:
- raise Exception("Composite requires an odd number of arguments.")
-
- self.size = size
- self.positions = args[0::2]
- self.images = [ image(i) for i in args[1::2] ]
-
- def get_mtime(self):
- return min(i.get_mtime() for i in self.images)
-
- def load(self):
-
- if self.size:
- size = self.size
- else:
- size = cache.get(self.images[0]).get_size()
-
- rv = renpy.display.pgrender.surface(size, True)
-
- for pos, im in zip(self.positions, self.images):
- rv.blit(cache.get(im), pos)
-
- return rv
-
- def predict_files(self):
-
- rv = [ ]
-
- for i in self.images:
- rv.extend(i.predict_files())
-
- return rv
-
-class Scale(ImageBase):
- """
- :doc: im_im
-
- An image manipulator that scales `im` (an image manipulator) to
- `width` and `height`.
-
- If `bilinear` is true, then bilinear interpolation is used for
- the scaling. Otherwise, nearest neighbor interpolation is used.
-
- ::
-
- image logo scale = im.Scale("logo.png", 100, 150)
- """
-
- def __init__(self, im, width, height, bilinear=True, **properties):
-
- im = image(im)
- super(Scale, self).__init__(im, width, height, bilinear, **properties)
-
- self.image = im
- self.width = int(width)
- self.height = int(height)
- self.bilinear = bilinear
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- child = cache.get(self.image)
-
- if self.bilinear:
- try:
- renpy.display.render.blit_lock.acquire()
- rv = renpy.display.scale.smoothscale(child, (self.width, self.height))
- finally:
- renpy.display.render.blit_lock.release()
- else:
- try:
- renpy.display.render.blit_lock.acquire()
- rv = renpy.display.pgrender.transform_scale(child, (self.width, self.height))
- finally:
- renpy.display.render.blit_lock.release()
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-class FactorScale(ImageBase):
- """
- :doc: im_im
-
- An image manipulator that scales `im` (a second image manipulator)
- to `width` times its original `width`, and `height` times its
- original height. If `height` is ommitted, it defaults to `width`.
-
- If `bilinear` is true, then bilinear interpolation is used for
- the scaling. Otherwise, nearest neighbor interpolation is used.
-
- ::
-
- image logo doubled = im.FactorScale("logo.png", 1.5)
- """
-
-
- def __init__(self, im, width, height=None, bilinear=True, **properties):
-
- if height is None:
- height = width
-
- im = image(im)
- super(FactorScale, self).__init__(im, width, height, bilinear, **properties)
-
- self.image = im
- self.width = width
- self.height = height
- self.bilinear = bilinear
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- surf = cache.get(self.image)
- width, height = surf.get_size()
-
- width = int(width * self.width)
- height = int(height * self.height)
-
- if self.bilinear:
- try:
- renpy.display.render.blit_lock.acquire()
- rv = renpy.display.scale.smoothscale(surf, (width, height))
- finally:
- renpy.display.render.blit_lock.release()
-
- else:
- try:
- renpy.display.render.blit_lock.acquire()
- rv = renpy.display.pgrender.transform_scale(surf, (width, height))
- finally:
- renpy.display.render.blit_lock.release()
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-
-class Flip(ImageBase):
- """
- :doc: im_im
-
- An image manipulator that flips `im` (an image manipulator)
- vertically or horizontally. `vertical` and `horizontal` control
- the directions in which the image is flipped.
-
- ::
-
- image eileen flip = im.Flip("eileen_happy.png", vertical=True)
- """
-
- def __init__(self, im, horizontal=False, vertical=False, **properties):
-
- if not (horizontal or vertical):
- raise Exception("im.Flip must be called with a true value for horizontal or vertical.")
-
- im = image(im)
- super(Flip, self).__init__(im, horizontal, vertical, **properties)
-
- self.image = im
- self.horizontal = horizontal
- self.vertical = vertical
-
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- child = cache.get(self.image)
-
- try:
- renpy.display.render.blit_lock.acquire()
- rv = renpy.display.pgrender.flip(child, self.horizontal, self.vertical)
- finally:
- renpy.display.render.blit_lock.release()
-
- return rv
-
-
- def predict_files(self):
- return self.image.predict_files()
-
-
-
-class Rotozoom(ImageBase):
- """
- This is an image manipulator that is a smooth rotation and zoom of another image manipulator.
- """
-
- def __init__(self, im, angle, zoom, **properties):
- """
- @param im: The image to be rotozoomed.
-
- @param angle: The number of degrees counterclockwise the image is
- to be rotated.
-
- @param zoom: The zoom factor. Numbers that are greater than 1.0
- lead to the image becoming larger.
- """
-
- im = image(im)
- super(Rotozoom, self).__init__(im, angle, zoom, **properties)
-
- self.image = im
- self.angle = angle
- self.zoom = zoom
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- child = cache.get(self.image)
-
- try:
- renpy.display.render.blit_lock.acquire()
- rv = renpy.display.pgrender.rotozoom(child, self.angle, self.zoom)
- finally:
- renpy.display.render.blit_lock.release()
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-
-
-class Crop(ImageBase):
- """
- :doc: im_im
- :args: (im, rect)
-
- An image manipulator that crops `rect`, a (x, y, width, height) tuple,
- out of `im`, an image manipulator.
-
- ::
-
- image logo crop = im.Crop("logo.png", (0, 0, 100, 307))
- """
-
- def __init__(self, im, x, y=None, w=None, h=None, **properties):
-
- im = image(im)
-
- if y is None:
- (x, y, w, h) = x
-
- super(Crop, self).__init__(im, x, y, w, h, **properties)
-
- self.image = im
- self.x = x
- self.y = y
- self.w = w
- self.h = h
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
- return cache.get(self.image).subsurface((self.x, self.y,
- self.w, self.h))
-
- def predict_files(self):
- return self.image.predict_files()
-
-
-ramp_cache = { }
-
-
-def ramp(start, end):
- """
- Returns a 256 character linear ramp, where the first character has
- the value start and the last character has the value end. Such a
- ramp can be used as a map argument of im.Map.
- """
-
- rv = ramp_cache.get((start, end), None)
- if rv is None:
-
- chars = [ ]
-
- for i in range(0, 256):
- i = i / 255.0
- chars.append(chr(int( end * i + start * (1.0 - i) ) ) )
-
- rv = "".join(chars)
- ramp_cache[start, end] = rv
-
- return rv
-
-identity = ramp(0, 255)
-
-class Map(ImageBase):
- """
- This adjusts the colors of the image that is its child. It takes
- as arguments 4 256 character strings. If a pixel channel has a
- value of 192, then the value of the 192nd character in the string
- is used for the mapped pixel component.
- """
-
- def __init__(self, im, rmap=identity, gmap=identity, bmap=identity,
- amap=identity, force_alpha=False, **properties):
-
- im = image(im)
-
- super(Map, self).__init__(im, rmap, gmap, bmap, amap, force_alpha, **properties)
-
- self.image = im
- self.rmap = rmap
- self.gmap = gmap
- self.bmap = bmap
- self.amap = amap
-
- self.force_alpha = force_alpha
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- surf = cache.get(self.image)
-
- rv = renpy.display.pgrender.surface(surf.get_size(), True)
-
- renpy.display.module.map(surf, rv,
- self.rmap, self.gmap, self.bmap, self.amap)
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-class Twocolor(ImageBase):
- """
- This takes as arguments two colors, white and black. The image is
- mapped such that pixels in white have the white color, pixels in
- black have the black color, and shades of gray are linearly
- interpolated inbetween. The alpha channel is mapped linearly
- between 0 and the alpha found in the white color, the black
- color's alpha is ignored.
- """
-
- def __init__(self, im, white, black, force_alpha=False, **properties):
-
- white = renpy.easy.color(white)
- black = renpy.easy.color(black)
-
- im = image(im)
-
- super(Twocolor, self).__init__(im, white, black, force_alpha, **properties)
-
- self.image = im
- self.white = white
- self.black = black
-
- self.force_alpha = force_alpha
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- surf = cache.get(self.image)
-
- rv = renpy.display.pgrender.surface(surf.get_size(), True)
-
- renpy.display.module.twomap(surf, rv,
- self.white, self.black)
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-
-class Recolor(ImageBase):
- """
- This adjusts the colors of the image that is its child. It takes as an
- argument 4 numbers between 0 and 255, and maps each channel of the image
- linearly between 0 and the supplied color.
- """
-
- def __init__(self, im, rmul=255, gmul=255, bmul=255,
- amul=255, force_alpha=False, **properties):
-
- im = image(im)
-
- super(Recolor, self).__init__(im, rmul, gmul, bmul, amul, force_alpha, **properties)
-
- self.image = im
- self.rmul = rmul + 1
- self.gmul = gmul + 1
- self.bmul = bmul + 1
- self.amul = amul + 1
-
- self.force_alpha = force_alpha
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- surf = cache.get(self.image)
-
- rv = renpy.display.pgrender.surface(surf.get_size(), True)
-
- renpy.display.module.linmap(surf, rv,
- self.rmul, self.gmul, self.bmul, self.amul)
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-class MatrixColor(ImageBase):
- """
- :doc: im_matrixcolor
-
- An image operator that uses `matrix` to linearly transform the
- image manipulator `im`.
-
- `Matrix` should be a list, tuple, or :func:`im.matrix` that is 20
- or 25 elements long. If the object has 25 elements, then elements
- past the 20th are ignored.
-
- When the four components of the source color are R, G, B, and A,
- which range from 0.0 to 1.0; the four components of the transformed
- color are R', G', B', and A', with the same range; and the elements
- of the matrix are named::
-
- [ a, b, c, d, e,
- f, g, h, i, j,
- k, l, m, n, o,
- p, q, r, s, t ]
-
- the transformed colors can be computed with the formula::
-
- R' = (a * R) + (b * G) + (c * B) + (d * A) + e
- G' = (f * R) + (g * G) + (h * B) + (i * A) + j
- B' = (k * R) + (l * G) + (m * B) + (n * A) + o
- A' = (p * R) + (q * G) + (r * B) + (s * A) + t
-
- The components of the transformed color are clamped to the
- range [0.0, 1.0].
- """
-
- def __init__(self, im, matrix, **properties):
-
- im = image(im)
-
- if len(matrix) != 20 and len(matrix) != 25:
- raise Exception("ColorMatrix expects a 20 or 25 element matrix, got %d elements." % len(matrix))
-
- matrix = tuple(matrix)
- super(MatrixColor, self).__init__(im, matrix, **properties)
-
- self.image = im
- self.matrix = matrix
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- surf = cache.get(self.image)
-
- rv = renpy.display.pgrender.surface(surf.get_size(), True)
-
- renpy.display.module.colormatrix(surf, rv, self.matrix)
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-class matrix(tuple):
- """
- :doc: im_matrixcolor
-
- Constructs an im.matrix object from `matrix`. im.matrix objects
- support The operations supported are matrix multiplication, scalar
- multiplication, element-wise addition, and element-wise
- subtraction. These operations are invoked using the standard
- mathematical operators (\\*, \\*, +, and -, respectively). If two
- im.matrix objects are multiplied, matrix multiplication is
- performed, otherwise scalar multiplication is used.
-
- `matrix` is a 20 or 25 element list or tuple. If it is 20 elements
- long, it is padded with (0, 0, 0, 0, 1) to make a 5x5 matrix,
- suitable for multiplication.
- """
-
- def __new__(cls, *args):
-
- if len(args) == 1:
- args = tuple(args[0])
-
- if len(args) == 20:
- args = args + (0, 0, 0, 0, 1)
-
- if len(args) != 25:
- raise Exception("Matrix expects to be given 20 or 25 entries, not %d." % len(args))
-
- return tuple.__new__(cls, args)
-
- def mul(self, a, b):
-
- if not isinstance(a, matrix):
- a = matrix(a)
-
- if not isinstance(b, matrix):
- b = matrix(b)
-
- result = [ 0 ] * 25
- for y in range(0, 5):
- for x in range(0, 5):
- for i in range(0, 5):
- result[x + y * 5] += a[x + i * 5] * b[i + y * 5]
-
- return matrix(result)
-
- def scalar_mul(self, other):
- other = float(other)
- return matrix([ i * other for i in self ])
-
- def vector_mul(self, o):
-
- return (o[0]*self[0] + o[1]*self[1] + o[2]*self[2] + o[3]*self[3] + self[4],
- o[0]*self[5] + o[1]*self[6] + o[2]*self[7] + o[3]*self[8] + self[9],
- o[0]*self[10] + o[1]*self[11] + o[2]*self[12] + o[3]*self[13] + self[14],
- o[0]*self[15] + o[1]*self[16] + o[2]*self[17] + o[3]*self[18] + self[19],
- 1)
-
-
- def __add__(self, other):
- if isinstance(other, (int, float)):
- other = float(other)
- return matrix([ i + other for i in self ])
-
- other = matrix(other)
- return matrix([ i + j for i, j in zip(self, other)])
-
- __radd__ = __add__
-
- def __sub__(self, other):
- return self + other * -1
-
- def __rsub__(self, other):
- return self * -1 + other
-
- def __mul__(self, other):
- if isinstance(other, (int, float)):
- return self.scalar_mul(other)
-
- return self.mul(self, other)
-
- def __rmul__(self, other):
- if isinstance(other, (int, float)):
- return self.scalar_mul(other)
-
- return self.mul(other, self)
-
- def __repr__(self):
- return """\
-im.matrix(%f, %f, %f, %f, %f.
- %f, %f, %f, %f, %f,
- %f, %f, %f, %f, %f,
- %f, %f, %f, %f, %f,
- %f, %f, %f, %f, %f)""" % self
-
-
- @staticmethod
- def identity():
- """
- :doc: im_matrixcolor
- :name: im.matrix.identity
-
- Returns an identity matrix, one that does not change color or
- alpha.
- """
-
- return matrix(1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0)
- @staticmethod
- def saturation(level, desat=(0.2126, 0.7152, 0.0722)):
- """
- :doc: im_matrixcolor
- :name: im.matrix.saturation
-
- Returns an im.matrix that alters the saturation of an
- image. The alpha channel is untouched.
-
- `level`
- The amount of saturation in the resulting image. 1.0 is
- the unaltered image, while 0.0 is grayscale.
-
- `desat`
- This is a 3-element tuple that controls how much of the
- red, green, and blue channels will be placed into all
- three channels of a fully desaturated image. The default
- is based on the constants used for the luminance channel
- of an NTSC television signal. Since the human eye is
- mostly sensitive to green, more of the green channel is
- kept then the other two channels.
- """
-
- r, g, b = desat
-
- def I(a, b):
- return a + (b - a) * level
-
- return matrix(I(r, 1), I(g, 0), I(b, 0), 0, 0,
- I(r, 0), I(g, 1), I(b, 0), 0, 0,
- I(r, 0), I(g, 0), I(b, 1), 0, 0,
- 0, 0, 0, 1, 0)
-
- @staticmethod
- def desaturate():
- """
- :doc: im_matrixcolor
- :name: im.matrix.desaturate
-
- Returns an im.matrix that desaturates the image (makes it
- grayscale). This is equivalent to calling
- im.matrix.saturation(0).
- """
-
- return matrix.saturation(0.0)
-
- @staticmethod
- def tint(r, g, b):
- """
- :doc: im_matrixcolor
- :name: im.matrix.tint
-
- Returns an im.matrix that tints an image, without changing
- the alpha channel. `r`, `g`, and `b` should be numbers between
- 0 and 1, and control what fraction of the given channel is
- placed into the final image. (For example, if `r` is .5, and
- the value of the red channel is 100, the transformed color
- will have a red value of 50.)
- """
-
- return matrix(r, 0, 0, 0, 0,
- 0, g, 0, 0, 0,
- 0, 0, b, 0, 0,
- 0, 0, 0, 1, 0)
-
- @staticmethod
- def invert():
- """
- :doc: im_matrixcolor
- :name: im.matrix.invert
-
- Returns an im.matrix that inverts the red, green, and blue
- channels of the image without changing the alpha channel.
- """
-
- return matrix(-1, 0, 0, 0, 1,
- 0, -1, 0, 0, 1,
- 0, 0, -1, 0, 1,
- 0, 0, 0, 1, 0)
-
- @staticmethod
- def brightness(b):
- """
- :doc: im_matrixcolor
- :name: im.matrix.brightness
-
- Returns an im.matrix that alters the brightness of an image.
-
- `b`
- The amount of change in image brightness. This should be
- a number between -1 and 1, with -1 the darkest possible
- image and 1 the brightest.
- """
-
- return matrix(1, 0, 0, 0, b,
- 0, 1, 0, 0, b,
- 0, 0, 1, 0, b,
- 0, 0, 0, 1, 0)
-
- @staticmethod
- def opacity(o):
- """
- :doc: im_matrixcolor
- :name: im.matrix.opacity
-
- Returns an im.matrix that alters the opacity of an image. An
- `o` of 0.0 is fully transparent, while 1.0 is fully opaque.
- """
-
- return matrix(1, 0, 0, 0, 0,
- 0, 1, 0, 0, 0,
- 0, 0, 1, 0, 0,
- 0, 0, 0, o, 0)
-
- @staticmethod
- def contrast(c):
- """
- :doc: im_matrixcolor
- :name: im.matrix.contrast
-
- Returns an im.matrix that alters the contrast of an image. `c` should
- be greater than 0.0, with values between 0.0 and 1.0 decreasing contrast, and
- values greater than 1.0 increasing contrast.
- """
-
- return matrix.brightness(-.5) * matrix.tint(c, c, c) * matrix.brightness(.5)
-
- # from http://www.gskinner.com/blog/archives/2005/09/flash_8_source.html
- @staticmethod
- def hue(h):
- """
- :doc: im_matrixcolor
- :name: im.matrix.hue
-
- Returns an im.matrix that rotates the hue by `h` degrees, while
- preserving luminosity.
- """
-
- h = h * math.pi / 180
- cosVal = math.cos(h)
- sinVal = math.sin(h)
- lumR = 0.213
- lumG = 0.715
- lumB = 0.072
- return matrix(
- lumR+cosVal*(1-lumR)+sinVal*(-lumR),lumG+cosVal*(-lumG)+sinVal*(-lumG),lumB+cosVal*(-lumB)+sinVal*(1-lumB),0,0,
- lumR+cosVal*(-lumR)+sinVal*(0.143),lumG+cosVal*(1-lumG)+sinVal*(0.140),lumB+cosVal*(-lumB)+sinVal*(-0.283),0,0,
- lumR+cosVal*(-lumR)+sinVal*(-(1-lumR)),lumG+cosVal*(-lumG)+sinVal*(lumG),lumB+cosVal*(1-lumB)+sinVal*(lumB),0,0,
- 0,0,0,1,0,
- 0,0,0,0,1
- )
-
- @staticmethod
- def colorize(black_color, white_color):
- """
- :doc: im_matrixcolor
- :name: im.matrix.colorize
-
- Returns an im.matrix that colorizes a black and white image.
- `black_color` and `white_color` are Ren'Py style colors, so
- they may be specfied as strings or tuples of (0-255) color
- values. ::
-
- # This makes black colors red, and white colors blue.
- image logo colored = im.MatrixColor(
- "bwlogo.png",
- im.matrix.colorize("#f00", "#00f"))
-
- """
-
- (r0, g0, b0, _a0) = renpy.easy.color(black_color)
- (r1, g1, b1, _a1) = renpy.easy.color(white_color)
-
- r0 /= 255.0
- g0 /= 255.0
- b0 /= 255.0
- r1 /= 255.0
- g1 /= 255.0
- b1 /= 255.0
-
- return matrix((r1-r0), 0, 0, 0, r0,
- 0, (g1-g0), 0, 0, g0,
- 0, 0, (b1-b0), 0, b0,
- 0, 0, 0, 1, 0)
-
-
-
-def Grayscale(im, desat=(0.2126, 0.7152, 0.0722), **properties):
- """
- :doc: im_im
- :args: (im, **properties)
-
- An image manipulator that creats a desaturated version of the image
- manipulator `im`.
- """
-
- return MatrixColor(im, matrix.saturation(0.0, desat), **properties)
-
-
-def Sepia(im, tint=(1.0, .94, .76), desat=(0.2126, 0.7152, 0.0722), **properties):
- """
- :doc: im_im
- :args: (im, **properties)
-
- An image manipulator that creates a sepia-toned version of the image
- manipulator `im`.
- """
-
- return MatrixColor(im, matrix.saturation(0.0, desat) * matrix.tint(tint[0], tint[1], tint[2]), **properties)
-
-
-def Color(im, color):
- """
- This recolors the supplied image, mapping colors such that black is
- black and white is the supplied color.
- """
-
- r, g, b, a = renpy.easy.color(color)
-
- return Recolor(im, r, g, b, a)
-
-
-def Alpha(image, alpha, **properties):
- """
- Returns an alpha-mapped version of the image. Alpha is the maximum
- alpha that this image can have, a number between 0.0 (fully
- transparent) and 1.0 (opaque).
-
- If an image already has an alpha channel, values in that alpha
- channel are reduced as appropriate.
- """
-
- return Recolor(image, 255, 255, 255, int(255 * alpha), force_alpha=True, **properties)
-
-class Tile(ImageBase):
- """
- :doc: im_im
-
- An image manipulator that tiles the image manipulator `im`, until
- it is `size`.
-
- `size`
- If not None, a (width, height) tuple. If None, this defaults to
- (:var:`config.screen_width`, :var:`config.screen_height`).
- """
-
- def __init__(self, im, size=None, **properties):
-
- im = image(im)
-
- super(Tile, self).__init__(im, size, **properties)
- self.image = im
- self.size = size
-
- def get_mtime(self):
- return self.image.get_mtime()
-
- def load(self):
-
- size = self.size
-
- if size is None:
- size = (renpy.config.screen_width, renpy.config.screen_height)
-
- surf = cache.get(self.image)
-
- rv = renpy.display.pgrender.surface(size, True)
-
- width, height = size
- sw, sh = surf.get_size()
-
- for y in range(0, height, sh):
- for x in range(0, width, sw):
- rv.blit(surf, (x, y))
-
- return rv
-
- def predict_files(self):
- return self.image.predict_files()
-
-class AlphaMask(ImageBase):
- """
- :doc: im_im
-
- An image manipulator that takes two image manipulators, `base` and
- `mask`, as arguments. It replaces the alpha channel of `base` with
- the red channel of `mask`.
-
- This is used to provide an image's alpha channel in a second
- image, like having one jpeg for color data, and a second one
- for alpha. In some cases, two jpegs can be smaller than a
- single png file.
- """
-
- def __init__(self, base, mask, **properties):
- super(AlphaMask, self).__init__(base, mask, **properties)
-
- self.base = image(base)
- self.mask = image(mask)
-
- def get_mtime(self):
- return max(self.base.get_mtime(), self.image.get_mtime())
-
- def load(self):
-
- basesurf = cache.get(self.base)
- masksurf = cache.get(self.mask)
-
- if basesurf.get_size() != masksurf.get_size():
- raise Exception("AlphaMask surfaces must be the same size.")
-
- # Used to copy the surface.
- rv = renpy.display.pgrender.copy_surface(basesurf)
- renpy.display.module.alpha_munge(masksurf, rv, identity)
-
- return rv
-
- def predict_files(self):
- return self.base.predict_files() + self.mask.predict_files()
-
-def image(arg, loose=False, **properties):
- """
- :doc: im_image
- :name: Image
- :args: (filename, **properties)
-
- Loads an image from a file. `filename` is a
- string giving the name of the file.
-
- `filename` should be a JPEG or PNG file with an appropriate
- extension.
- """
-
- """
- (Actually, the user documentation is a bit misleading, as
- this tries for compatibility with several older forms of
- image specification.)
-
- If the loose argument is False, then this will report an error if an
- arbitrary argument is given. If it's True, then the argument is passed
- through unchanged.
- """
-
- if isinstance(arg, ImageBase):
- return arg
-
- elif isinstance(arg, str):
- return Image(arg, **properties)
-
- elif isinstance(arg, renpy.display.image.ImageReference):
- arg.find_target()
- return image(arg.target, loose=loose, **properties)
-
- elif isinstance(arg, tuple):
- params = [ ]
-
- for i in arg:
- params.append((0, 0))
- params.append(i)
-
- return Composite(None, *params)
-
- elif loose:
- return arg
-
- if isinstance(arg, renpy.display.core.Displayable):
- raise Exception("Expected an image, but got a general displayable.")
- else:
- raise Exception("Could not construct image from argument.")
-
-
-def load_image(fn):
- """
- This loads an image from the given filename, using the cache.
- """
-
- surf = cache.get(image(fn))
- return renpy.display.draw.load_texture(surf)