|
| 1 | +#!/usr/bin/env python |
| 2 | +# author: Hua Liang [ Stupid ET ] |
| 3 | + |
| 4 | +# website: http://EverET.org |
| 5 | +# |
| 6 | + |
| 7 | +import random, string, md5, time |
| 8 | +import Image, ImageDraw, ImageFont, ImageChops, ImageFilter |
| 9 | +import StringIO |
| 10 | +import math, operator |
| 11 | + |
| 12 | +# Filters that input Image and output Image |
| 13 | +# |
| 14 | + |
| 15 | +class Effect(object): |
| 16 | + '''The base Effect |
| 17 | + All Effects have a member function called "filter" |
| 18 | + which receives a PIL Image and return a PIL Image |
| 19 | + ''' |
| 20 | + name = 'Base Effect' |
| 21 | + empty_color = (128, 128, 128, 255) |
| 22 | + def __init__(self): |
| 23 | + pass |
| 24 | + |
| 25 | + def __call__(self, img): |
| 26 | + '''The effect can act like a filter function''' |
| 27 | + return self.filter(img) |
| 28 | + |
| 29 | + def filter(self, img): |
| 30 | + return img |
| 31 | + |
| 32 | + |
| 33 | +class GlobalWarpEffect(Effect): |
| 34 | + '''Warping Effect Base class. |
| 35 | + provide basic warping framework |
| 36 | + ''' |
| 37 | + name = 'Warp Effect' |
| 38 | + |
| 39 | + def __init__(self, formula, antialias=2): |
| 40 | + self.formula = formula |
| 41 | + self.antialias = antialias |
| 42 | + |
| 43 | + def filter(self, img): |
| 44 | + '''Effect Kernel of radius based Effect. |
| 45 | + @param formula is a function like f(x, y) => (x', y'), -1 <= x <= 1 and -1 <= y <= 1 |
| 46 | + ''' |
| 47 | + width, height = img.size |
| 48 | + nx, ny = width, height |
| 49 | + new_img = img.copy() |
| 50 | + nband = len(img.getpixel((0, 0))) |
| 51 | + for j in range(height): |
| 52 | + for i in range(width): |
| 53 | + found = 0 |
| 54 | + psum = (0, ) * nband |
| 55 | + new_img.putpixel((i, j), Effect.empty_color) |
| 56 | + # antialias |
| 57 | + for ai in range(self.antialias): |
| 58 | + x = 2 * (i + ai / float(self.antialias)) / width - 1 |
| 59 | + for aj in range(self.antialias): |
| 60 | + y = 2 * (j + aj / float(self.antialias)) / height - 1 |
| 61 | + |
| 62 | + # distortion |
| 63 | + xnew, ynew = self.formula(x, y) |
| 64 | + |
| 65 | + i2 = int(round(0.5 * nx * (xnew + 1))) |
| 66 | + j2 = int(round(0.5 * ny * (ynew + 1))) |
| 67 | + |
| 68 | + if not (0 <= i2 < nx and 0 <= j2 < ny): |
| 69 | + continue |
| 70 | + |
| 71 | + pt = img.getpixel((i2, j2)) |
| 72 | + psum = map(operator.add, psum, pt) |
| 73 | + found += 1 |
| 74 | + |
| 75 | + if found > 0: |
| 76 | + psum = map(operator.div, psum, (self.antialias * self.antialias, ) * len(psum)) |
| 77 | + new_img.putpixel((i, j), tuple(psum)) |
| 78 | + |
| 79 | + return new_img |
| 80 | + |
| 81 | + |
| 82 | +class RadianFormulaEffect(Effect): |
| 83 | + '''Transform the Image according to the input formula |
| 84 | + @note The formula is a function like f(r, phi) => (r, phi) |
| 85 | + which r is radius and phi is radian angel. |
| 86 | + ''' |
| 87 | + name = 'Radian Formula Effect' |
| 88 | + |
| 89 | + def __init__(self, formula, antialias=2): |
| 90 | + self.formula = formula |
| 91 | + self.antialias = antialias |
| 92 | + |
| 93 | + def filter(self, img): |
| 94 | + def radian_formula(x, y): |
| 95 | + '''transform formula |
| 96 | + func is a function that like f(r, phi) => (r, phi) |
| 97 | + ''' |
| 98 | + r = math.sqrt(x ** 2 + y ** 2) |
| 99 | + phi = math.atan2(y, x) |
| 100 | + |
| 101 | + r, phi = self.formula(r, phi) |
| 102 | + |
| 103 | + xnew = r * math.cos(phi) |
| 104 | + ynew = r * math.sin(phi) |
| 105 | + |
| 106 | + return xnew, ynew |
| 107 | + |
| 108 | + warp = GlobalWarpEffect(radian_formula, self.antialias) |
| 109 | + return warp(img) |
| 110 | + |
| 111 | + |
| 112 | +class RadianSqrtEffect(RadianFormulaEffect): |
| 113 | + name = 'r = sqrt(r)' |
| 114 | + def __init__(self): |
| 115 | + super(RadianSqrtEffect, self).__init__( |
| 116 | + lambda r, phi: (math.sqrt(r), phi)) |
| 117 | + |
| 118 | + |
| 119 | +class WaveEffect(Effect): |
| 120 | + name = 'Wave Effect' |
| 121 | + def __init__(self, vertical_delta=0.1, horizon_delta=0, box=None): |
| 122 | + self.vertical_delta = vertical_delta |
| 123 | + self.horizon_delta = horizon_delta |
| 124 | + self.box = box |
| 125 | + |
| 126 | + def filter(self, img): |
| 127 | + if self.box == None: |
| 128 | + left, top = 0, 0 |
| 129 | + right, bottom = img.size[0] - 1, img.size[1] - 1 |
| 130 | + else: |
| 131 | + left, top, right, bottom = self.box |
| 132 | + |
| 133 | + width, height = img.size |
| 134 | + |
| 135 | + mid_x = (right + left) / 2.0 |
| 136 | + mid_y = (top + bottom) / 2.0 |
| 137 | + |
| 138 | + new_img = img.copy() |
| 139 | + height_delta = (bottom - top + 1) * self.vertical_delta |
| 140 | + width_delta = 2 * math.pi / (right - left + 1) * (self.horizon_delta + 1) |
| 141 | + for x in range(left, right): |
| 142 | + degree = x * width_delta |
| 143 | + for y in range(top, bottom): |
| 144 | + h = math.sin(degree) * height_delta * ((bottom - top) / 2 - math.sqrt((y - mid_y) ** 2 + (x - mid_x) ** 2)) / mid_y |
| 145 | + offset = int(round(h)) |
| 146 | + if 0 < x < width and 0 < y + offset < height: |
| 147 | + new_img.putpixel((x, y), img.getpixel((x, y + offset))) |
| 148 | + else: |
| 149 | + new_img.putpixel((x, y), Effect.empty_color) |
| 150 | + |
| 151 | + return new_img |
| 152 | + |
| 153 | + |
| 154 | +class EffectGlue(Effect): |
| 155 | + name = 'Effect Glue' |
| 156 | + def __init__(self, name=None): |
| 157 | + self.name = name if name else EffectGlue.name |
| 158 | + self.effect_pipeline = [] |
| 159 | + |
| 160 | + def append(self, effect): |
| 161 | + self.effect_pipeline.append(effect) |
| 162 | + |
| 163 | + def remove(self, effect): |
| 164 | + self.effect_pipeline.remove(effect) |
| 165 | + |
| 166 | + def insert(self, index, effect): |
| 167 | + self.effect_pipeline.insert(index, Effect) |
| 168 | + |
| 169 | + def pop(self): |
| 170 | + return self.effect_pipeline.pop() |
| 171 | + |
| 172 | + def filter(self, img): |
| 173 | + for f in self.effect_pipeline: |
| 174 | + img = f.filter(img) |
| 175 | + return img |
| 176 | + |
| 177 | + |
| 178 | +class GridMaker(Effect): |
| 179 | + name = 'Grid Maker' |
| 180 | + def __init__(self, width_offset, height_offset, color=(0, 0, 0, 255)): |
| 181 | + self.width_offset = width_offset |
| 182 | + self.height_offset = height_offset |
| 183 | + self.color = color |
| 184 | + |
| 185 | + def filter(self, img): |
| 186 | + width, height = img.size |
| 187 | + |
| 188 | + # draw grid |
| 189 | + draw = ImageDraw.Draw(img) |
| 190 | + for x in range(0, width, self.width_offset): |
| 191 | + draw.line((x, 0, x, height), self.color) |
| 192 | + for y in range(0, height, self.height_offset): |
| 193 | + draw.line((0, y, width, y), self.color) |
| 194 | + |
| 195 | + del draw |
| 196 | + return img |
| 197 | + |
| 198 | + |
| 199 | +class TextWriter(Effect): |
| 200 | + name = 'Text Writer' |
| 201 | + def __init__(self, (x, y), text, color=(0, 0, 0, 255), font=None): |
| 202 | + self.x = x |
| 203 | + self.y = y |
| 204 | + self.text = text |
| 205 | + self.color = color |
| 206 | + |
| 207 | + def filter(self, img): |
| 208 | + draw = ImageDraw.Draw(img) |
| 209 | + draw.text((self.x, self.y), self.text, self.color) |
| 210 | + del draw |
| 211 | + return img |
| 212 | + |
| 213 | + |
| 214 | +def GenerateImage(effect): |
| 215 | + # draw img |
| 216 | + # img = Image.open('z.jpg') |
| 217 | + img = Image.new("RGBA", (300, 300), (255, 255, 255, 255)) |
| 218 | + |
| 219 | + width, height = img.size |
| 220 | + |
| 221 | + grid = GridMaker(20, 20) |
| 222 | + text = TextWriter((10, 100), string.letters) |
| 223 | + |
| 224 | + img = grid.filter(text.filter(img)) |
| 225 | + |
| 226 | + old = img.copy() |
| 227 | + |
| 228 | + img = effect(img) |
| 229 | + |
| 230 | + out = Image.new("RGBA", (width * 2, height)) |
| 231 | + out.paste(old, (0, 0)) |
| 232 | + out.paste(img, (width, 0)) |
| 233 | + draw = ImageDraw.Draw(out) |
| 234 | + |
| 235 | + draw.line((width, 0, width, height), (255, 0, 0, 255)) |
| 236 | + |
| 237 | + out.save('haha.png') |
| 238 | + |
| 239 | +def sign(v): |
| 240 | + return 1 if v >= 0 else -1 |
| 241 | + |
| 242 | +if __name__ == '__main__': |
| 243 | + wave = WaveEffect(0.2, 0.5, (100, 50, 200, 200)) |
| 244 | + sqrt = RadianSqrtEffect() |
| 245 | + effect = RadianFormulaEffect(lambda r, phi: (r ** 1.2, phi)) |
| 246 | + effect = RadianFormulaEffect(lambda r, phi: (r ** 1.5 * math.cos(r), phi)) |
| 247 | + effect = RadianFormulaEffect(lambda r, phi: (r, phi + 0.5)) |
| 248 | + effect = GlobalWarpEffect(lambda x, y: (x + 0.1, y)) |
| 249 | + effect = GlobalWarpEffect(lambda x, y: (math.sin(x * math.pi / 2), math.sin(y * math.pi / 2))) |
| 250 | + effect = GlobalWarpEffect(lambda x, y: (sign(x) * x ** 2, sign(y) * y ** 2)) |
| 251 | + effect = RadianFormulaEffect(lambda r, phi: (r ** 2, phi), 4) |
| 252 | + GenerateImage(effect) |
0 commit comments