# HG changeset patch # User Paul Boddie # Date 1444484208 -7200 # Node ID 3f96e51bfacb811ae5d749ad14130e8a9c79ea29 # Parent 7d63ab284ab8ae3019392227a8e4a040dac7516d Made a new module for Shedskin to compile as an extension module. diff -r 7d63ab284ab8 -r 3f96e51bfacb optimiser.py --- a/optimiser.py Sat Oct 10 15:25:12 2015 +0200 +++ b/optimiser.py Sat Oct 10 15:36:48 2015 +0200 @@ -20,160 +20,13 @@ with this program. If not, see . """ -from random import random, randrange +from optimiserlib import * from os.path import split, splitext import EXIF import PIL.Image import itertools import sys -corners = [ - (0, 0, 0), (255, 0, 0), (0, 255, 0), (255, 255, 0), - (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255) - ] - -# Basic colour operations. - -def within(v, lower, upper): - return min(max(v, lower), upper) - -def clip(v): - return int(within(v, 0, 255)) - -def restore(srgb): - r, g, b = srgb - return int(r * 255.0), int(g * 255.0), int(b * 255.0) - -def scale(rgb): - r, g, b = rgb - return r / 255.0, g / 255.0, b / 255.0 - -def invert(srgb): - r, g, b = srgb - return 1.0 - r, 1.0 - g, 1.0 - b - -scaled_corners = map(scale, corners) -zipped_corners = zip(corners, scaled_corners) - -# Colour distribution functions. - -def combination(rgb): - - "Return the colour distribution for 'rgb'." - - # Get the colour with components scaled from 0 to 1, plus the inverted - # component values. - - srgb = scale(rgb) - rgbi = invert(srgb) - pairs = zip(rgbi, srgb) - - # For each corner of the colour cube (primary and secondary colours plus - # black and white), calculate the corner value's contribution to the - # input colour. - - d = [] - for corner, scaled in zipped_corners: - rs, gs, bs = scaled - - # Obtain inverted channel values where corner channels are low; - # obtain original channel values where corner channels are high. - - d.append((pairs[0][int(rs)] * pairs[1][int(gs)] * pairs[2][int(bs)], corner)) - - # Balance the corner contributions. - - return balance(d) - -def complements(rgb): - - "Return 'rgb' and its complement." - - r, g, b = rgb - return rgb, restore(invert(scale(rgb))) - -bases = [(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255)] -base_complements = map(complements, bases) - -def balance(d): - - """ - Balance distribution 'd', cancelling opposing values and their complements - and replacing their common contributions with black and white contributions. - """ - - d = dict([(value, f) for f, value in d]) - for primary, secondary in base_complements: - common = min(d[primary], d[secondary]) - d[primary] -= common - d[secondary] -= common - return [(f, value) for value, f in d.items()] - -def combine(d): - - "Combine distribution 'd' to get a colour value." - - out = [0, 0, 0] - for v, rgb in d: - out[0] += v * rgb[0] - out[1] += v * rgb[1] - out[2] += v * rgb[2] - return tuple(map(int, out)) - -def pattern(rgb, chosen=None): - - """ - Obtain a sorted colour distribution for 'rgb', optionally limited to any - specified 'chosen' colours. - """ - - l = [(f, value) for f, value in combination(rgb) if not chosen or value in chosen] - l.sort(reverse=True) - return l - -def get_value(rgb, chosen=None, fail=False): - - """ - Get an output colour for 'rgb', optionally limited to any specified 'chosen' - colours. If 'fail' is set to a true value, return None if the colour cannot - be expressed using any of the chosen colours. - """ - - l = pattern(rgb, chosen) - limit = sum([f for f, c in l]) - if not limit: - if fail: - return None - else: - return l[randrange(0, len(l))][1] - - choose = random() * limit - threshold = 0 - for f, c in l: - threshold += f - if choose < threshold: - return c - return c - -# Colour processing operations. - -def sign(x): - return x >= 0 and 1 or -1 - -def saturate_rgb(rgb, exp): - r, g, b = rgb - return saturate_value(r, exp), saturate_value(g, exp), saturate_value(b, exp) - -def saturate_value(x, exp): - return int(127.5 + sign(x - 127.5) * 127.5 * pow(abs(x - 127.5) / 127.5, exp)) - -def amplify_rgb(rgb, exp): - r, g, b = rgb - return amplify_value(r, exp), amplify_value(g, exp), amplify_value(b, exp) - -def amplify_value(x, exp): - return int(pow(x / 255.0, exp) * 255.0) - # Image operations. def get_colours(im, y): diff -r 7d63ab284ab8 -r 3f96e51bfacb optimiserlib.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/optimiserlib.py Sat Oct 10 15:36:48 2015 +0200 @@ -0,0 +1,186 @@ +#!/usr/bin/env python + +""" +Convert and optimise images for display in an Acorn Electron MODE 1 variant +with four colours per line but eight colours available for selection on each +line. + +Copyright (C) 2015 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . +""" + +from random import random, randrange + +corners = [ + (0, 0, 0), (255, 0, 0), (0, 255, 0), (255, 255, 0), + (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255) + ] + +# Basic colour operations. + +def within(v, lower, upper): + return min(max(v, lower), upper) + +def clip(v): + return int(within(v, 0, 255)) + +def restore(srgb): + r, g, b = srgb + return int(r * 255.0), int(g * 255.0), int(b * 255.0) + +def scale(rgb): + r, g, b = rgb + return r / 255.0, g / 255.0, b / 255.0 + +def invert(srgb): + r, g, b = srgb + return 1.0 - r, 1.0 - g, 1.0 - b + +scaled_corners = map(scale, corners) +zipped_corners = zip(corners, scaled_corners) + +# Colour distribution functions. + +def combination(rgb): + + "Return the colour distribution for 'rgb'." + + # Get the colour with components scaled from 0 to 1, plus the inverted + # component values. + + srgb = scale(rgb) + rgbi = invert(srgb) + pairs = zip(rgbi, srgb) + + # For each corner of the colour cube (primary and secondary colours plus + # black and white), calculate the corner value's contribution to the + # input colour. + + d = [] + for corner, scaled in zipped_corners: + rs, gs, bs = scaled + + # Obtain inverted channel values where corner channels are low; + # obtain original channel values where corner channels are high. + + d.append((pairs[0][int(rs)] * pairs[1][int(gs)] * pairs[2][int(bs)], corner)) + + # Balance the corner contributions. + + return balance(d) + +def complements(rgb): + + "Return 'rgb' and its complement." + + r, g, b = rgb + return rgb, restore(invert(scale(rgb))) + +bases = [(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255)] +base_complements = map(complements, bases) + +def balance(d): + + """ + Balance distribution 'd', cancelling opposing values and their complements + and replacing their common contributions with black and white contributions. + """ + + dd = dict([(value, f) for f, value in d]) + for primary, secondary in base_complements: + common = min(dd[primary], dd[secondary]) + dd[primary] -= common + dd[secondary] -= common + return [(f, value) for value, f in dd.items()] + +def combine(d): + + "Combine distribution 'd' to get a colour value." + + out = [0, 0, 0] + for v, rgb in d: + out[0] += v * rgb[0] + out[1] += v * rgb[1] + out[2] += v * rgb[2] + return tuple(map(int, out)) + +def pattern(rgb, chosen=None): + + """ + Obtain a sorted colour distribution for 'rgb', optionally limited to any + specified 'chosen' colours. + """ + + l = [(f, value) for f, value in combination(rgb) if not chosen or value in chosen] + l.sort(reverse=True) + return l + +def get_value(rgb, chosen=None, fail=False): + + """ + Get an output colour for 'rgb', optionally limited to any specified 'chosen' + colours. If 'fail' is set to a true value, return None if the colour cannot + be expressed using any of the chosen colours. + """ + + l = pattern(rgb, chosen) + limit = sum([f for f, c in l]) + if not limit: + if fail: + return None + else: + return l[randrange(0, len(l))][1] + + choose = random() * limit + threshold = 0 + for f, c in l: + threshold += f + if choose < threshold: + return c + return c + +# Colour processing operations. + +def sign(x): + if x >= 0: + return 1 + else: + return -1 + +def saturate_rgb(rgb, exp): + r, g, b = rgb + return saturate_value(r, exp), saturate_value(g, exp), saturate_value(b, exp) + +def saturate_value(x, exp): + return int(127.5 + sign(x - 127.5) * 127.5 * pow(abs(x - 127.5) / 127.5, exp)) + +def amplify_rgb(rgb, exp): + r, g, b = rgb + return amplify_value(r, exp), amplify_value(g, exp), amplify_value(b, exp) + +def amplify_value(x, exp): + return int(pow(x / 255.0, exp) * 255.0) + +# Exercise functions for Shedskin. + +if __name__ == "__main__": + rgb = (200, 100, 50) + saturate_rgb(rgb, 1.0) + amplify_rgb(rgb, 1.0) + get_value(rgb) + get_value(rgb, [(255, 255, 255), (255, 0, 0), (255, 255, 0), (0, 0, 0)]) + combine([(1.0, (255, 0, 0)), (0.0, (0, 0, 0))]) + clip(200.0) + +# vim: tabstop=4 expandtab shiftwidth=4