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