1#!/usr/local/bin/python 2 3# Gimp-Python - allows the writing of Gimp plugins in Python. 4# Copyright (C) 1997 James Henstridge <james@daa.com.au> 5# 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 3 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <https://www.gnu.org/licenses/>. 18 19# Algorithms stolen from the whirl and pinch plugin distributed with Gimp, 20# by Federico Mena Quintero and Scott Goehring 21# 22# This version does the same thing, except there is no preview, and it is 23# written in python and is slower. 24 25import math, struct 26from gimpfu import * 27 28class pixel_fetcher: 29 def __init__(self, drawable): 30 self.col = -1 31 self.row = -1 32 self.img_width = drawable.width 33 self.img_height = drawable.height 34 self.img_bpp = drawable.bpp 35 self.img_has_alpha = drawable.has_alpha 36 self.tile_width = gimp.tile_width() 37 self.tile_height = gimp.tile_height() 38 self.bg_colour = '\0\0\0\0' 39 self.bounds = drawable.mask_bounds 40 self.drawable = drawable 41 self.tile = None 42 def set_bg_colour(self, r, g, b, a): 43 self.bg_colour = struct.pack('BBB', r,g,b) 44 if self.img_has_alpha: 45 self.bg_colour = self.bg_colour + chr(a) 46 def get_pixel(self, x, y): 47 sel_x1, sel_y1, sel_x2, sel_y2 = self.bounds 48 if x < sel_x1 or x >= sel_x2 or y < sel_y1 or y >= sel_y2: 49 return self.bg_colour 50 col = x / self.tile_width 51 coloff = x % self.tile_width 52 row = y / self.tile_height 53 rowoff = y % self.tile_height 54 55 if col != self.col or row != self.row or self.tile == None: 56 self.tile = self.drawable.get_tile(False, row, col) 57 self.col = col 58 self.row = row 59 return self.tile[coloff, rowoff] 60 61class Dummy: 62 pass 63 64def whirl_pinch(image, drawable, whirl, pinch, radius): 65 self = Dummy() 66 self.width = drawable.width 67 self.height = drawable.height 68 self.bpp = drawable.bpp 69 self.has_alpha = drawable.has_alpha 70 self.bounds = drawable.mask_bounds 71 self.sel_x1, self.sel_y1, self.sel_x2, self.sel_y2 = \ 72 drawable.mask_bounds 73 self.sel_w = self.sel_x2 - self.sel_x1 74 self.sel_h = self.sel_y2 - self.sel_y1 75 self.cen_x = (self.sel_x1 + self.sel_x2 - 1) / 2.0 76 self.cen_y = (self.sel_y1 + self.sel_y2 - 1) / 2.0 77 xhsiz = (self.sel_w - 1) / 2.0 78 yhsiz = (self.sel_h - 1) / 2.0 79 80 if xhsiz < yhsiz: 81 self.scale_x = yhsiz / xhsiz 82 self.scale_y = 1.0 83 elif xhsiz > yhsiz: 84 self.scale_x = 1.0 85 self.scale_y = xhsiz / yhsiz 86 else: 87 self.scale_x = 1.0 88 self.scale_y = 1.0 89 90 self.radius = max(xhsiz, yhsiz); 91 92 if not drawable.is_rgb and not drawable.is_grey: 93 return 94 95 gimp.tile_cache_ntiles(2 * (1 + self.width / gimp.tile_width())) 96 97 whirl = whirl * math.pi / 180 98 dest_rgn = drawable.get_pixel_rgn(self.sel_x1, self.sel_y1, 99 self.sel_w, self.sel_h, True, True) 100 pft = pixel_fetcher(drawable) 101 pfb = pixel_fetcher(drawable) 102 103 bg_colour = gimp.get_background() 104 105 pft.set_bg_colour(bg_colour[0], bg_colour[1], bg_colour[2], 0) 106 pfb.set_bg_colour(bg_colour[0], bg_colour[1], bg_colour[2], 0) 107 108 progress = 0 109 max_progress = self.sel_w * self.sel_h 110 111 gimp.progress_init("Whirling and pinching") 112 113 self.radius2 = self.radius * self.radius * radius 114 pixel = ['', '', '', ''] 115 values = [0,0,0,0] 116 117 for row in range(self.sel_y1, (self.sel_y1+self.sel_y2)/2+1): 118 top_p = '' 119 bot_p = '' 120 for col in range(self.sel_x1, self.sel_x2): 121 q, cx, cy = calc_undistorted_coords(self, col, 122 row, whirl, pinch, 123 radius) 124 if q: 125 if cx >= 0: ix = int(cx) 126 else: ix = -(int(-cx) + 1) 127 if cy >= 0: iy = int(cy) 128 else: iy = -(int(-cy) + 1) 129 pixel[0] = pft.get_pixel(ix, iy) 130 pixel[1] = pft.get_pixel(ix+1, iy) 131 pixel[2] = pft.get_pixel(ix, iy+1) 132 pixel[3] = pft.get_pixel(ix+1, iy+1) 133 for i in range(self.bpp): 134 values[0] = ord(pixel[0][i]) 135 values[1] = ord(pixel[1][i]) 136 values[2] = ord(pixel[2][i]) 137 values[3] = ord(pixel[3][i]) 138 top_p = top_p + bilinear(cx,cy, values) 139 cx = self.cen_x + (self.cen_x - cx) 140 cy = self.cen_y + (self.cen_y - cy) 141 if cx >= 0: ix = int(cx) 142 else: ix = -(int(-cx) + 1) 143 if cy >= 0: iy = int(cy) 144 else: iy = -(int(-cy) + 1) 145 pixel[0] = pfb.get_pixel(ix, iy) 146 pixel[1] = pfb.get_pixel(ix+1, iy) 147 pixel[2] = pfb.get_pixel(ix, iy+1) 148 pixel[3] = pfb.get_pixel(ix+1, iy+1) 149 tmp = '' 150 for i in range(self.bpp): 151 values[0] = ord(pixel[0][i]) 152 values[1] = ord(pixel[1][i]) 153 values[2] = ord(pixel[2][i]) 154 values[3] = ord(pixel[3][i]) 155 tmp = tmp + bilinear(cx,cy, values) 156 bot_p = tmp + bot_p 157 else: 158 top_p = top_p + pft.get_pixel(col, row) 159 bot_p = pfb.get_pixel((self.sel_x2 - 1) - 160 (col - self.sel_x1), (self.sel_y2-1) - 161 (row - self.sel_y1)) + bot_p 162 163 dest_rgn[self.sel_x1:self.sel_x2, row] = top_p 164 dest_rgn[self.sel_x1:self.sel_x2, (self.sel_y2 - 1) 165 - (row - self.sel_y1)] = bot_p 166 167 progress = progress + self.sel_w * 2 168 gimp.progress_update(float(progress) / max_progress) 169 170 drawable.flush() 171 drawable.merge_shadow(True) 172 drawable.update(self.sel_x1,self.sel_y1,self.sel_w,self.sel_h) 173 174def calc_undistorted_coords(self, wx, wy, whirl, pinch, radius): 175 dx = (wx - self.cen_x) * self.scale_x 176 dy = (wy - self.cen_y) * self.scale_y 177 d = dx * dx + dy * dy 178 inside = d < self.radius2 179 180 if inside: 181 dist = math.sqrt(d / radius) / self.radius 182 if (d == 0.0): 183 factor = 1.0 184 else: 185 factor = math.pow(math.sin(math.pi / 2 * dist), 186 -pinch) 187 dx = dx * factor 188 dy = dy * factor 189 factor = 1 - dist 190 ang = whirl * factor * factor 191 sina = math.sin(ang) 192 cosa = math.cos(ang) 193 x = (cosa * dx - sina * dy) / self.scale_x + self.cen_x 194 y = (sina * dx + cosa * dy) / self.scale_y + self.cen_y 195 else: 196 x = wx 197 y = wy 198 return inside, float(x), float(y) 199 200def bilinear(x, y, values): 201 x = x % 1.0 202 y = y % 1.0 203 m0 = values[0] + x * (values[1] - values[0]) 204 m1 = values[2] + x * (values[3] - values[2]) 205 return chr(int(m0 + y * (m1 - m0))) 206 207 208register( 209 "python-fu-whirl-pinch", 210 "Distorts an image by whirling and pinching", 211 "Distorts an image by whirling and pinching", 212 "James Henstridge (translated from C plugin)", 213 "James Henstridge", 214 "1997-1999", 215 "_Whirl and Pinch...", 216 "RGB*, GRAY*", 217 [ 218 (PF_IMAGE, "image", "Input image", None), 219 (PF_DRAWABLE, "drawable", "Input drawable", None), 220 (PF_SLIDER, "whirl", "Whirl angle", 90, (-360, 360, 1)), 221 (PF_FLOAT, "pinch", "Pinch amount", 0), 222 (PF_FLOAT, "radius", "radius", 1) 223 ], 224 [], 225 whirl_pinch, menu="<Image>/Filters/Distorts") 226 227main() 228