PaletteOptimiser

optimiserlib.py

83:3f96e51bfacb
2015-10-10 Paul Boddie Made a new module for Shedskin to compile as an extension module. simpleimage-shedskin
     1 #!/usr/bin/env python     2      3 """     4 Convert and optimise images for display in an Acorn Electron MODE 1 variant     5 with four colours per line but eight colours available for selection on each     6 line.     7      8 Copyright (C) 2015 Paul Boddie <paul@boddie.org.uk>     9     10 This program is free software; you can redistribute it and/or modify it under    11 the terms of the GNU General Public License as published by the Free Software    12 Foundation; either version 3 of the License, or (at your option) any later    13 version.    14     15 This program is distributed in the hope that it will be useful, but WITHOUT ANY    16 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A    17 PARTICULAR PURPOSE.  See the GNU General Public License for more details.    18     19 You should have received a copy of the GNU General Public License along    20 with this program.  If not, see <http://www.gnu.org/licenses/>.    21 """    22     23 from random import random, randrange    24     25 corners = [    26     (0, 0, 0), (255, 0, 0), (0, 255, 0), (255, 255, 0),    27     (0, 0, 255), (255, 0, 255), (0, 255, 255), (255, 255, 255)    28     ]    29     30 # Basic colour operations.    31     32 def within(v, lower, upper):    33     return min(max(v, lower), upper)    34     35 def clip(v):    36     return int(within(v, 0, 255))    37     38 def restore(srgb):    39     r, g, b = srgb    40     return int(r * 255.0), int(g * 255.0), int(b * 255.0)    41     42 def scale(rgb):    43     r, g, b = rgb    44     return r / 255.0, g / 255.0, b / 255.0    45     46 def invert(srgb):    47     r, g, b = srgb    48     return 1.0 - r, 1.0 - g, 1.0 - b    49     50 scaled_corners = map(scale, corners)    51 zipped_corners = zip(corners, scaled_corners)    52     53 # Colour distribution functions.    54     55 def combination(rgb):    56     57     "Return the colour distribution for 'rgb'."    58     59     # Get the colour with components scaled from 0 to 1, plus the inverted    60     # component values.    61     62     srgb = scale(rgb)    63     rgbi = invert(srgb)    64     pairs = zip(rgbi, srgb)    65     66     # For each corner of the colour cube (primary and secondary colours plus    67     # black and white), calculate the corner value's contribution to the    68     # input colour.    69     70     d = []    71     for corner, scaled in zipped_corners:    72         rs, gs, bs = scaled    73     74         # Obtain inverted channel values where corner channels are low;    75         # obtain original channel values where corner channels are high.    76     77         d.append((pairs[0][int(rs)] * pairs[1][int(gs)] * pairs[2][int(bs)], corner))    78     79     # Balance the corner contributions.    80     81     return balance(d)    82     83 def complements(rgb):    84     85     "Return 'rgb' and its complement."    86     87     r, g, b = rgb    88     return rgb, restore(invert(scale(rgb)))    89     90 bases = [(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255)]    91 base_complements = map(complements, bases)    92     93 def balance(d):    94     95     """    96     Balance distribution 'd', cancelling opposing values and their complements    97     and replacing their common contributions with black and white contributions.    98     """    99    100     dd = dict([(value, f) for f, value in d])   101     for primary, secondary in base_complements:   102         common = min(dd[primary], dd[secondary])   103         dd[primary] -= common   104         dd[secondary] -= common   105     return [(f, value) for value, f in dd.items()]   106    107 def combine(d):   108    109     "Combine distribution 'd' to get a colour value."   110    111     out = [0, 0, 0]   112     for v, rgb in d:   113         out[0] += v * rgb[0]   114         out[1] += v * rgb[1]   115         out[2] += v * rgb[2]   116     return tuple(map(int, out))   117    118 def pattern(rgb, chosen=None):   119    120     """   121     Obtain a sorted colour distribution for 'rgb', optionally limited to any   122     specified 'chosen' colours.   123     """   124    125     l = [(f, value) for f, value in combination(rgb) if not chosen or value in chosen]   126     l.sort(reverse=True)   127     return l   128    129 def get_value(rgb, chosen=None, fail=False):   130    131     """   132     Get an output colour for 'rgb', optionally limited to any specified 'chosen'   133     colours. If 'fail' is set to a true value, return None if the colour cannot   134     be expressed using any of the chosen colours.   135     """   136    137     l = pattern(rgb, chosen)   138     limit = sum([f for f, c in l])   139     if not limit:   140         if fail:   141             return None   142         else:   143             return l[randrange(0, len(l))][1]   144    145     choose = random() * limit   146     threshold = 0   147     for f, c in l:   148         threshold += f   149         if choose < threshold:   150             return c   151     return c   152    153 # Colour processing operations.   154    155 def sign(x):   156     if x >= 0:   157         return 1   158     else:   159         return -1   160    161 def saturate_rgb(rgb, exp):   162     r, g, b = rgb   163     return saturate_value(r, exp), saturate_value(g, exp), saturate_value(b, exp)   164    165 def saturate_value(x, exp):   166     return int(127.5 + sign(x - 127.5) * 127.5 * pow(abs(x - 127.5) / 127.5, exp))   167    168 def amplify_rgb(rgb, exp):   169     r, g, b = rgb   170     return amplify_value(r, exp), amplify_value(g, exp), amplify_value(b, exp)   171    172 def amplify_value(x, exp):   173     return int(pow(x / 255.0, exp) * 255.0)   174    175 # Exercise functions for Shedskin.   176    177 if __name__ == "__main__":   178     rgb = (200, 100, 50)   179     saturate_rgb(rgb, 1.0)   180     amplify_rgb(rgb, 1.0)   181     get_value(rgb)   182     get_value(rgb, [(255, 255, 255), (255, 0, 0), (255, 255, 0), (0, 0, 0)])   183     combine([(1.0, (255, 0, 0)), (0.0, (0, 0, 0))])   184     clip(200.0)   185    186 # vim: tabstop=4 expandtab shiftwidth=4