Rev 198 | Rev 206 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
2 | toby | 1 | /* |
18 | toby | 2 | This file is part of "Filter Foundry", a filter plugin for Adobe Photoshop |
192 | daniel-mar | 3 | Copyright (C) 2003-2009 Toby Thain, toby@telegraphics.com.au |
4 | Copyright (C) 2018-2019 Daniel Marschall, ViaThinkSoft |
||
2 | toby | 5 | |
6 | This program is free software; you can redistribute it and/or modify |
||
106 | dmarschall | 7 | it under the terms of the GNU General Public License as published by |
2 | toby | 8 | the Free Software Foundation; either version 2 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 | |||
106 | dmarschall | 16 | You should have received a copy of the GNU General Public License |
2 | toby | 17 | along with this program; if not, write to the Free Software |
18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
||
19 | */ |
||
20 | |||
21 | #include "ff.h" |
||
94 | toby | 22 | |
2 | toby | 23 | #include "symtab.h" |
24 | #include "node.h" |
||
25 | #include "funcs.h" |
||
8 | toby | 26 | #include "y.tab.h" |
2 | toby | 27 | |
128 | dmarschall | 28 | // Strict compatibility to Filter Factory |
29 | //#define YUV_FILTER_FACTORY |
||
30 | |||
2 | toby | 31 | extern value_type var[]; |
94 | toby | 32 | extern int nplanes,varused[],cnvused; |
2 | toby | 33 | extern struct node *tree[]; |
66 | toby | 34 | |
35 | // points to first row, first column of selection image data |
||
36 | // this is used by src() and cnv() functions to access pixels |
||
37 | unsigned char *image_ptr; |
||
38 | |||
198 | daniel-mar | 39 | extern int needinput; |
150 | dmarschall | 40 | int state_changing_funcs_used; |
2 | toby | 41 | |
42 | /* get prepared to evaluate expression trees-- |
||
43 | this assumes that tree[] array is already set up |
||
44 | return TRUE if we're ready to go |
||
45 | */ |
||
46 | |||
94 | toby | 47 | // minimum setup required when formulae have not changed, |
48 | // and a new preview is to be generated. (Called by recalc_preview()) |
||
49 | void evalinit(){ |
||
142 | dmarschall | 50 | int i; |
51 | |||
94 | toby | 52 | INITRANDSEED(); |
142 | dmarschall | 53 | |
54 | for (i=0; i<NUM_CELLS; ++i) { |
||
55 | cell[i] = 0; |
||
56 | } |
||
94 | toby | 57 | } |
58 | |||
59 | // full setup for evaluation, called when formulae have changed. |
||
2 | toby | 60 | Boolean setup(FilterRecordPtr pb){ |
61 | int i; |
||
62 | |||
117 | dmarschall | 63 | // Attention: If you introduce new variables, please define them also in lexer.l |
53 | toby | 64 | var['X'] = pb->filterRect.right - pb->filterRect.left; |
65 | var['Y'] = pb->filterRect.bottom - pb->filterRect.top; |
||
2 | toby | 66 | var['Z'] = nplanes; |
67 | var['D'] = 1024; |
||
68 | var['M'] = ff_c2m(var['X'],var['Y'])/2; |
||
69 | |||
70 | /* initialise flags for tracking special variable usage */ |
||
71 | toby | 71 | for(i = 0; i < 0x100; i++) |
2 | toby | 72 | varused[i] = 0; |
94 | toby | 73 | needall = cnvused = 0; |
71 | toby | 74 | for(i = 0; i < nplanes; ++i){ |
2 | toby | 75 | //char s[100];sprintf(s,"expr[%d]=%#x",i,expr[i]);dbg(s); |
71 | toby | 76 | if( tree[i] || (tree[i] = parseexpr(expr[i])) ) |
150 | dmarschall | 77 | checkvars(tree[i],varused,&cnvused,&needall,&state_changing_funcs_used); |
2 | toby | 78 | else |
79 | break; |
||
80 | } |
||
94 | toby | 81 | needinput = ( cnvused || needall |
71 | toby | 82 | || varused['r'] || varused['g'] || varused['b'] || varused['a'] |
83 | || varused['i'] || varused['u'] || varused['v'] || varused['c'] ); |
||
106 | dmarschall | 84 | |
166 | dmarschall | 85 | /* |
86 | * Workaround for PSPI for GIMP: |
||
87 | * Filters will only fill the bottom of the picture, not the whole canvas. |
||
88 | * The reason is that OnContinue/main.c:RequestNext() processes the image in chunks, |
||
89 | * and probably due to a bug, PSPI only applies the image data of the last chunk. |
||
182 | dmarschall | 90 | * Workaround applied in FF 1.7: If the host is GIMP, then we set |
166 | dmarschall | 91 | * needall=1 to disable chunked processing. |
92 | */ |
||
183 | dmarschall | 93 | if (pb->hostSig == HOSTSIG_GIMP) needall = true; |
182 | dmarschall | 94 | |
94 | toby | 95 | evalinit(); |
2 | toby | 96 | return i==nplanes; /* all required expressions parse OK */ |
97 | } |
||
98 | |||
99 | void evalpixel(unsigned char *outp,unsigned char *inp){ |
||
100 | int f,k; |
||
101 | |||
102 | if(needinput){ |
||
103 | var['r'] = inp[0]; |
||
104 | var['g'] = nplanes > 1 ? inp[1] : 0; |
||
105 | var['b'] = nplanes > 2 ? inp[2] : 0; |
||
106 | var['a'] = nplanes > 3 ? inp[3] : 0; |
||
106 | dmarschall | 107 | |
128 | dmarschall | 108 | #ifdef YUV_FILTER_FACTORY |
109 | if(varused['i']) var['i'] = (( 76L*var['r'])+(150L*var['g'])+( 29L*var['b']))/256; // range: [0..254] |
||
110 | if(varused['u']) var['u'] = ((-19L*var['r'])+(-37L*var['g'])+( 56L*var['b']))/256; // range: [-55..55] |
||
111 | if(varused['v']) var['v'] = (( 78L*var['r'])+(-65L*var['g'])+(-13L*var['b']))/256; // range: [-77..77] |
||
112 | #else |
||
113 | // These formulas are more accurate, e.g. pure white has now i=255 instead of 254 |
||
114 | |||
115 | // For Y, the definition is Y := 0.299R + 0.587G + 0.114B |
||
116 | if(varused['i']) var['i'] = (( 299L*var['r'])+( 587L*var['g'])+( 114L*var['b']))/1000; // range: [0..255] |
||
117 | |||
118 | // For U, the definition is U := (B-Y) * 0.493; the range would be [-111..111] |
||
119 | // Filter Factory divided it by 2, resulting in a range of [-55..55]. |
||
120 | // Due to compatibility reasons, we adopt that behavior. |
||
121 | if(varused['u']) var['u'] = ((-147407L*var['r'])+(-289391L*var['g'])+( 436798L*var['b']))/2000000; // range: [-55..55] |
||
122 | |||
123 | // For V, the definition is V := (R-Y) * 0.877; the range would be [-156..156] |
||
124 | // Filter Factory divided it by 2, resulting in a range of [-78..78]. |
||
125 | // Due to compatibility reasons, we adopt that behavior. |
||
126 | if(varused['v']) var['v'] = (( 614777L*var['r'])+(-514799L*var['g'])+(- 99978L*var['b']))/2000000; // range: [-78..78] |
||
127 | #endif |
||
128 | |||
2 | toby | 129 | } |
201 | daniel-mar | 130 | if(varused['d']) var['d'] = ff_c2d_negated(var['X']/2 - var['x'], var['Y']/2 - var['y']); |
66 | toby | 131 | if(varused['m']) var['m'] = ff_c2m(var['X']/2 - var['x'], var['Y']/2 - var['y']); |
106 | dmarschall | 132 | |
66 | toby | 133 | for(k = 0; k < nplanes; ++k){ |
2 | toby | 134 | if(needinput) |
135 | var['c'] = inp[k]; |
||
136 | var['z'] = k; |
||
117 | dmarschall | 137 | var['p'] = k; // undocumented alias of z |
2 | toby | 138 | f = eval(tree[k]); |
150 | dmarschall | 139 | if (outp) |
140 | outp[k] = f<0 ? 0 : (f>255 ? 255 : f); // clamp channel value to 0-255 |
||
2 | toby | 141 | } |
142 | } |
||
143 | |||
53 | toby | 144 | /* Zoom and filter image. |
66 | toby | 145 | * Parameters: pb - Photoshop Filter parameter block |
146 | * progress - whether to use Photoshop's progress bar |
||
53 | toby | 147 | * (not appropriate during preview) |
66 | toby | 148 | * filterRect - rectangle (within pb->inRect) |
53 | toby | 149 | * of area to be filtered. This may not correspond |
150 | * to pb->filterRect, it may be just a piece. |
||
66 | toby | 151 | * outPiece - rectangle defining scaled output buffer. |
152 | * In case of zoomed preview, this is physically |
||
153 | * scaled FROM filterRect (or equal to filterRect |
||
154 | * for unscaled 1:1 filtering). |
||
53 | toby | 155 | * outData - pointer to output data buffer |
156 | * outRowBytes - row stride of output data buffer |
||
106 | dmarschall | 157 | * zoom - pixel scale factor (both horiz & vert) |
53 | toby | 158 | * e.g. 2.0 means 1 output pixel per 2 input pixels. |
159 | */ |
||
2 | toby | 160 | |
154 | dmarschall | 161 | //#define PROCESS_SCALED_GAP_DEBUG 1 |
162 | |||
53 | toby | 163 | OSErr process_scaled(FilterRecordPtr pb, Boolean progress, |
66 | toby | 164 | Rect *filterPiece, Rect *outPiece, |
53 | toby | 165 | void *outData, long outRowBytes, double zoom){ |
2 | toby | 166 | unsigned char *inrow,*outrow,*outp; |
154 | dmarschall | 167 | int j,i; |
2 | toby | 168 | long t,ticks = TICKCOUNT(); |
154 | dmarschall | 169 | double x,y,k; |
66 | toby | 170 | |
154 | dmarschall | 171 | #ifdef PROCESS_SCALED_GAP_DEBUG |
172 | char s[0x200]; |
||
173 | int last_good_x, last_good_y; |
||
174 | last_good_y = -1; |
||
175 | #endif |
||
176 | |||
106 | dmarschall | 177 | // find base pointer to selection image data |
178 | image_ptr = (unsigned char*)pb->inData |
||
179 | + (long)pb->inRowBytes*(pb->filterRect.top - pb->inRect.top) |
||
180 | + (long)nplanes*(pb->filterRect.left - pb->inRect.left); |
||
66 | toby | 181 | |
150 | dmarschall | 182 | if (state_changing_funcs_used) { |
154 | dmarschall | 183 | // Fill gap between selection/filter top border and top preview zoomed border |
184 | for (y = 0; y < filterPiece->top - pb->filterRect.top; ++y) { |
||
185 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
186 | if (state_changing_funcs_used && last_good_y != (int)floor(y-1)) { sprintf(s, "Non calculated Y gap, type 1: %f, last good %d, zoom %f\n", y, last_good_y, zoom); simplealert(s); } last_good_y = (int)floor(y); |
||
187 | #endif |
||
188 | |||
185 | dmarschall | 189 | var['y'] = (value_type)y; |
150 | dmarschall | 190 | inrow = image_ptr + (long)(y)*pb->inRowBytes; |
154 | dmarschall | 191 | |
192 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
193 | last_good_x = -1; |
||
194 | #endif |
||
195 | |||
196 | for (x = 0; x < pb->filterRect.right - pb->filterRect.left; ++x) { |
||
197 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
198 | if (state_changing_funcs_used && last_good_x != (int)floor(x-1)) { sprintf(s, "Non calculated X gap, type 1a: %f, last good %d, zoom %f\n", x, last_good_x, zoom); simplealert(s); } last_good_x = (int)floor(x); |
||
199 | #endif |
||
200 | |||
185 | dmarschall | 201 | var['x'] = (value_type)x; |
150 | dmarschall | 202 | evalpixel(NULL,inrow + (long)(x)*nplanes); |
203 | } |
||
154 | dmarschall | 204 | |
205 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
206 | if (var['x'] != var['X']-1) { sprintf(s, "X not at right border #1: x=%d, X=%d\n", var['x'], var['X']); simplealert(s); } |
||
207 | #endif |
||
150 | dmarschall | 208 | } |
209 | } |
||
210 | |||
106 | dmarschall | 211 | // j indexes scaled output rows |
212 | for( j = outPiece->top, outrow = (unsigned char*)outData, y = filterPiece->top - pb->filterRect.top ; |
||
213 | j < outPiece->bottom ; ++j, outrow += outRowBytes, y += zoom ) |
||
214 | { |
||
154 | dmarschall | 215 | #ifdef PROCESS_SCALED_GAP_DEBUG |
216 | if (state_changing_funcs_used && last_good_y != (int)floor(y-1)) { sprintf(s, "Non calculated Y gap, type 1: %f, last good %d, zoom %f\n", y, last_good_y, zoom); simplealert(s); } last_good_y = (int)floor(y); |
||
217 | #endif |
||
218 | |||
185 | dmarschall | 219 | var['y'] = (value_type)y; // index of corresponding *input* row, top of selection == 0 |
106 | dmarschall | 220 | inrow = image_ptr + (long)y*pb->inRowBytes; |
66 | toby | 221 | |
154 | dmarschall | 222 | #ifdef PROCESS_SCALED_GAP_DEBUG |
223 | last_good_x = -1; |
||
224 | #endif |
||
225 | |||
150 | dmarschall | 226 | if (state_changing_funcs_used) { |
154 | dmarschall | 227 | // Fill gap between left selection/image border and left border of the preview-area |
228 | for (x = 0; x < filterPiece->left - pb->filterRect.left; ++x) { |
||
229 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
230 | if (state_changing_funcs_used && last_good_x != (int)floor(x-1)) { sprintf(s, "Non calculated X gap, type 2a: %f, last good %d, zoom %f\n", x, last_good_x, zoom); simplealert(s); } last_good_x = (int)floor(x); |
||
231 | #endif |
||
232 | |||
185 | dmarschall | 233 | var['x'] = (value_type)x; |
150 | dmarschall | 234 | evalpixel(NULL,inrow + (long)(x)*nplanes); |
235 | } |
||
236 | } |
||
237 | |||
106 | dmarschall | 238 | // i indexes scaled output columns |
239 | for( outp = outrow, i = outPiece->left, x = filterPiece->left - pb->filterRect.left ; |
||
240 | i < outPiece->right ; ++i, outp += nplanes, x += zoom ) |
||
53 | toby | 241 | { |
154 | dmarschall | 242 | #ifdef PROCESS_SCALED_GAP_DEBUG |
243 | if (state_changing_funcs_used && last_good_x != (int)floor(x-1)) { sprintf(s, "Non calculated X gap, type 2b: %f, last good %d, zoom %f\n", x, last_good_x, zoom); simplealert(s); } last_good_x = (int)floor(x); |
||
244 | #endif |
||
245 | |||
185 | dmarschall | 246 | var['x'] = (value_type)x; // index of corresponding *input* column, left of selection == 0 |
154 | dmarschall | 247 | evalpixel(outp,inrow + (long)(x)*nplanes); /* var['x'] & var['y'] are implicit parameters */ |
150 | dmarschall | 248 | |
249 | if (state_changing_funcs_used) { |
||
154 | dmarschall | 250 | // Fill gap between each X-preview-pixel (discarded pixels due to zoom level) |
251 | for (k = x+1; floor(k) < floor(x + zoom); ++k) { |
||
252 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
253 | if (state_changing_funcs_used && last_good_x != (int)floor(k-1)) { sprintf(s, "Non calculated X gap, type 2c: %f (x=%f), last good %d, zoom %f\n", k, x, last_good_x, zoom); simplealert(s); } last_good_x = (int)floor(k); |
||
254 | #endif |
||
255 | |||
185 | dmarschall | 256 | var['x'] = (value_type)k; |
154 | dmarschall | 257 | if (var['x'] >= var['X']) break; |
150 | dmarschall | 258 | evalpixel(NULL,inrow + (long)(k)*nplanes); |
259 | } |
||
260 | } |
||
106 | dmarschall | 261 | } |
66 | toby | 262 | |
154 | dmarschall | 263 | if (state_changing_funcs_used) { |
264 | // Fill gap between right border of preview-area and right border of selection/image border |
||
265 | |||
266 | for (x = var['x']+1; x < pb->filterRect.right - pb->filterRect.left; ++x) { |
||
267 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
268 | if (state_changing_funcs_used && last_good_x != (int)floor(x-1)) { sprintf(s, "Non calculated X gap, type 2d: %f, last good %d, zoom %f\n", x, last_good_x, zoom); simplealert(s); } last_good_x = (int)floor(x); |
||
269 | #endif |
||
270 | |||
185 | dmarschall | 271 | var['x'] = (value_type)x; |
154 | dmarschall | 272 | evalpixel(NULL,inrow + (long)(x)*nplanes); |
150 | dmarschall | 273 | } |
154 | dmarschall | 274 | |
275 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
276 | if (var['x'] != var['X']-1) { sprintf(s, "X not at right border #2: x=%d, X=%d\n", var['x'], var['X']); simplealert(s);} |
||
277 | #endif |
||
278 | |||
155 | dmarschall | 279 | // Fill gap between each Y-preview-pixel (discarded pixels due to zoom level), |
280 | // but not for the very last line, since we are then done drawing our preview picture |
||
281 | for (k = y+1; floor(k) < floor(y + zoom) && (j < outPiece->bottom-1); ++k) { |
||
154 | dmarschall | 282 | #ifdef PROCESS_SCALED_GAP_DEBUG |
283 | if (state_changing_funcs_used && last_good_y != (int)floor(k-1)) { sprintf(s, "Non calculated Y gap, type 3a: %f (y=%f), last good %d, zoom %f\n", k, y, last_good_y, zoom); simplealert(s); } last_good_y = (int)floor(k); |
||
284 | #endif |
||
285 | |||
185 | dmarschall | 286 | var['y'] = (value_type)k; |
154 | dmarschall | 287 | if (var['y'] >= var['Y']) break; |
150 | dmarschall | 288 | inrow = image_ptr + (long)(k)*pb->inRowBytes; |
154 | dmarschall | 289 | |
290 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
291 | last_good_x = -1; |
||
292 | #endif |
||
293 | |||
294 | for (x = 0; x < pb->filterRect.right - pb->filterRect.left; ++x) { |
||
295 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
296 | if (state_changing_funcs_used && last_good_x != (int)floor(x-1)) { sprintf(s, "Non calculated X gap, type 3b: %f, last good %d, zoom %f\n", x, last_good_x, zoom); simplealert(s); } last_good_x = (int)floor(x); |
||
297 | #endif |
||
298 | |||
185 | dmarschall | 299 | var['x'] = (value_type)x; |
150 | dmarschall | 300 | evalpixel(NULL,inrow + (long)(x)*nplanes); |
301 | } |
||
154 | dmarschall | 302 | |
303 | #ifdef PROCESS_SCALED_GAP_DEBUG |
||
304 | if (var['x'] != var['X']-1) {sprintf(s, "X not at right border #3: x=%d, X=%d\n", var['x'], var['X']); simplealert(s);} |
||
305 | #endif |
||
150 | dmarschall | 306 | } |
307 | } |
||
308 | |||
106 | dmarschall | 309 | if(progress){ |
310 | if((t = TICKCOUNT()) > ticks){ |
||
311 | ticks = t + TICKS_SEC/4; |
||
312 | if(pb->abortProc()) |
||
313 | return userCanceledErr; |
||
314 | else |
||
315 | pb->progressProc((int)y - pb->filterRect.top,pb->filterRect.bottom - pb->filterRect.top); |
||
2 | toby | 316 | } |
106 | dmarschall | 317 | } |
2 | toby | 318 | #ifdef MAC_ENV |
106 | dmarschall | 319 | else{ |
320 | /* to stop delays during typing of expressions, |
||
321 | immediately abort preview calculation if a key or mouse has been pressed. */ |
||
322 | EventRecord event; |
||
323 | if(EventAvail(mDownMask|keyDownMask|autoKeyMask,&event)) |
||
324 | return userCanceledErr; |
||
325 | } |
||
2 | toby | 326 | #endif |
106 | dmarschall | 327 | } |
2 | toby | 328 | |
154 | dmarschall | 329 | // Note for state_changing_funcs_used: We will not evaluate the gap between bottom border |
330 | // of preview area and the bottom border of the selection/filter, because there are no |
||
331 | // preview output pixels left that could be affected by these gap evaluations. |
||
332 | |||
2 | toby | 333 | return noErr; |
334 | } |