Rev 304 | Rev 399 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
221 | daniel-mar | 1 | Implementation detail differences |
2 | ================================= |
||
141 | dmarschall | 3 | |
304 | daniel-mar | 4 | Filter Foundry tries to be as compatible with Filter Factory as possible. |
5 | However, there are some differences that are explained in this documentation. |
||
141 | dmarschall | 6 | |
294 | daniel-mar | 7 | Various implementations |
8 | ----------------------- |
||
141 | dmarschall | 9 | |
294 | daniel-mar | 10 | In the source-code file funcs.c, some functions are implemented twice: |
11 | One instance is the default implementation (older Filter Foundry versions used), |
||
12 | and one instance is a 100% replica of the Filter Factory code, obtained |
||
13 | from the "OPER" resource. |
||
14 | (More information at https://misc.daniel-marschall.de/projects/filter_factory/res_oper.html ) |
||
297 | daniel-mar | 15 | If required, the compiler-definitions `use_filterfactory_implementation_*` |
294 | daniel-mar | 16 | can be set or unset to select the implementation. |
17 | |||
304 | daniel-mar | 18 | In Filter Foundry 1.7.0.8, the following functions have been updated to the Filter Factory replica: |
297 | daniel-mar | 19 | - `rnd(x)` |
20 | - `cos(x)` |
||
21 | - `sin(x)` |
||
22 | - `tan(x)` |
||
23 | - `r2x(d,m)` |
||
24 | - `r2y(d,m)` |
||
25 | - `rad(d,m,z)` |
||
26 | - `c2d(x,y)` |
||
27 | - `c2m(x,y)` |
||
28 | - `sqr(x)` |
||
29 | - `d` |
||
30 | - `m` |
||
31 | - `M` |
||
294 | daniel-mar | 32 | |
33 | |||
34 | sqr(x) |
||
35 | ------ |
||
36 | |||
37 | Filter Factory: |
||
38 | |||
39 | sqr(x)=x for x < 0 |
||
295 | daniel-mar | 40 | |
304 | daniel-mar | 41 | Can be tested with the following expression: |
295 | daniel-mar | 42 | sqr(-20)+21 == 1 |
294 | daniel-mar | 43 | |
44 | Filter Foundry (prior to 1.7.0.8): |
||
45 | |||
46 | sqr(x)=0 for x < 0 |
||
47 | |||
304 | daniel-mar | 48 | Beginning with Filter Foundry 1.7.0.8, the behavior of Filter Factory was implemented. |
297 | daniel-mar | 49 | |
304 | daniel-mar | 50 | |
158 | dmarschall | 51 | i, u, v (Testcase iuv.afs) |
141 | dmarschall | 52 | ------- |
53 | |||
54 | Filter Foundry <1.7 uses the same formulas as in Filter Factory: |
||
55 | |||
221 | daniel-mar | 56 | i=((76*r)+(150*g)+(29*b))/256 // Output range is 0..254 |
57 | u=((-19*r)+(-37*g)+(56*b))/256 // Output range is -55..55 |
||
58 | v=((78*r)+(-65*g)+(-13*b))/256 // Output range is -77..77 |
||
141 | dmarschall | 59 | |
60 | Filter Foundry 1.7 uses more accurate formulas: |
||
61 | |||
221 | daniel-mar | 62 | i=(299*r+587*g+114*b)/1000 // Output range is 0..255 |
63 | u=(-147407*r-289391*g+436798*b)/2000000 // Output range is -55..55 |
||
64 | v=614777*r-514799*g-99978*b)/2000000 // Output range is -78..78 |
||
141 | dmarschall | 65 | |
192 | daniel-mar | 66 | Both formulas follow the same YUV standard but have different accuracy. |
141 | dmarschall | 67 | |
68 | |||
304 | daniel-mar | 69 | I, U, V, imin, umin, vmin (Testcase iuv_minmax.afs) |
70 | ------------------------- |
||
71 | |||
72 | In Filter Foundry 1.7.0.8, the previously undocumented variables I, U, V as well as imin, umin, vmin |
||
73 | have been changed to represent the actual results of the i,u,v variables: |
||
74 | |||
335 | daniel-mar | 75 | imax = 255 (stayed the same) |
76 | umax = 55 (was 255 in Filter Factory) |
||
77 | vmax = 78 (was 255 in Filter Factory) |
||
304 | daniel-mar | 78 | |
79 | imin = 0 (stayed the same) |
||
80 | umin = -55 (was 0 in Filter Factory) |
||
81 | vmin = -78 (was 0 in Filter Factory) |
||
82 | |||
335 | daniel-mar | 83 | It is questionable if `I` was meant to be a synonym of `imax`, or if `I` was meant to be `I := imax - imin`. |
84 | We have chosen the latter in Filter Foundry 1.7.0.9. Same thing with `U` and `V`. |
||
304 | daniel-mar | 85 | |
335 | daniel-mar | 86 | Therefore: |
87 | |||
88 | I := imax-imin = 255 |
||
89 | U := umax-umin = 110 |
||
90 | V := vmax-vmin = 156 |
||
91 | |||
92 | |||
93 | dmin, D (Testcase d_minmax.afs) |
||
94 | ------- |
||
95 | |||
96 | **The Filter Factory manual writes:** |
||
97 | |||
98 | |||
99 | 256 to the 6 o'clock position, |
||
100 | 512 to the 9 o'clock position, |
||
101 | 768 to the 12 o'clock position, |
||
102 | and 1024 to the full rotation back to the 3 o'clock position |
||
103 | |||
104 | But this does not match the Windows implementation of Filter Factory |
||
105 | (maybe it is true to the Mac implementation?) |
||
106 | |||
107 | **In the original Windows implementation we can observe:** |
||
108 | |||
109 | d=-512 is at 9 o'clock position |
||
110 | d=-256 is at 12 o'clock position |
||
111 | d=0 is at 3 o'clock position |
||
112 | d=256 is at 6 o'clock position |
||
113 | d=512 is the full rotation back to 3 o'clock position |
||
114 | |||
115 | Therefore, `dmin` has been changed from 0 to -512, |
||
116 | and `D`, `dmax` has been changed from 1024 to 512. |
||
117 | |||
118 | |||
141 | dmarschall | 119 | get(i) (Testcase getput.afs) |
120 | ------ |
||
121 | |||
122 | Filter Foundry: |
||
123 | |||
221 | daniel-mar | 124 | get(x)=0 if x>255 or x<0 |
125 | |||
141 | dmarschall | 126 | Filter Factory: |
127 | |||
221 | daniel-mar | 128 | get(x)=x if x>255 or x<0 |
141 | dmarschall | 129 | |
221 | daniel-mar | 130 | Note: The result "x" was most likely not intended but a result of an undefined behavior |
131 | |||
132 | |||
141 | dmarschall | 133 | r, g, b at empty canvas (Testcase emptycanvas.afs) |
134 | ----------------------- |
||
135 | |||
221 | daniel-mar | 136 | In Filter Factory, an empty (transparent) canvas of a new file is initialized as `r=g=b=0` |
141 | dmarschall | 137 | |
221 | daniel-mar | 138 | Filter Foundry initializes it as `r=g=b=255` |
141 | dmarschall | 139 | |
140 | |||
294 | daniel-mar | 141 | rnd(a,b) (Testcases rnd*) |
289 | daniel-mar | 142 | -------- |
143 | |||
144 | Filter Factory uses Donald E. Knuth's subtractive random number generator algorithm, |
||
145 | which has been published in "The Art of Computer Programming, volume 2: Seminumerical Algorithms". |
||
146 | Addison-Wesley, Reading, MA, second edition, 1981. |
||
147 | |||
148 | Beginning with Filter Foundry 1.7.0.8, the same PRNG was implemented, |
||
149 | so that the output of rnd(a,b) is exactly the same now. |
||
150 | |||
151 | |||
294 | daniel-mar | 152 | rst(i) (Testcases rnd*.afs and rst_*.afs) |
153 | ------ |
||
141 | dmarschall | 154 | |
289 | daniel-mar | 155 | Filter Factory contains an undocumented function that sets the seed for the random number generator. |
141 | dmarschall | 156 | |
304 | daniel-mar | 157 | Filter Factory and Filter Foundry beginning with 1.7.0.8 accept a seed between 0 and 32767, inclusively. |
158 | If the argument is not within this range, the operation lowest 15 bits are taken. |
||
141 | dmarschall | 159 | |
304 | daniel-mar | 160 | There are many differences in the implementation between Filter Factory and Filter Foundry in regards rst(i): |
141 | dmarschall | 161 | |
289 | daniel-mar | 162 | **Filter Factory:** |
143 | dmarschall | 163 | |
289 | daniel-mar | 164 | If rst(i) is called in Filter Factory, an internal Seed-Variable is set. |
165 | It does NOT influence any calls of rnd(a,b), because a lookup-table needs to be built first. |
||
166 | The building of the lookup-table is probably done before the processing of the first pixel (x,y,z=0). |
||
167 | It is suspected that the call of rst(i) will take effect on the next calculation. |
||
168 | Due to a bug (or feature?), the random state is not reset to its initial state (0) before the |
||
169 | filter is applied. The preview image processing will modify the random state, and once the filter |
||
170 | is actually applied (pressing "OK"), the random state that was set in the preview picture, will be used. |
||
171 | This could be considered as a bug, but it is probably required, otherwise the call of rst(i) |
||
172 | (inside the preview calculation) won't affect the rnd(a,b) in the real run. |
||
173 | However, in a standalone filter without dialog/preview, there is no preview that could set |
||
174 | the internal seed, so the rnd(a,b) functions will always work using the default seed 0, |
||
175 | and only the subsequent calls will use the rst(i) of the previous call. |
||
221 | daniel-mar | 176 | |
289 | daniel-mar | 177 | **Filter Foundry:** |
143 | dmarschall | 178 | |
289 | daniel-mar | 179 | In Filter Foundry, the function rnd(a,b) retrieves a random number in "realtime"; therefore, if the |
180 | seed is changed via rst(i), there is an immediate effect on the next call of the rnd(a,b) function. |
||
181 | |||
304 | daniel-mar | 182 | For example, the following filter would generate an one-colored picture without any randomness: |
221 | daniel-mar | 183 | R: rst(123), rnd(0,255) |
184 | G: rnd(0,255) |
||
185 | B: rnd(0,255) |
||
144 | dmarschall | 186 | |
289 | daniel-mar | 187 | If you want to generate a random pixel image with a non-default seed, you need to make sure |
188 | that rst(i) is called only once at the beginning (channel 0, coordinate 0|0): |
||
294 | daniel-mar | 189 | R: (x== 0 && y ==0) ? rst(123) : 0, rnd(0,255) |
221 | daniel-mar | 190 | G: rnd(0,255) |
191 | B: rnd(0,255) |
||
144 | dmarschall | 192 | |
289 | daniel-mar | 193 | In Filter Foundry, rst(i) can be called by branches and variables/sliders can |
194 | be used as arguments of rst(i). |
||
144 | dmarschall | 195 | |
196 | |||
143 | dmarschall | 197 | Evaluation of conditional branches |
198 | ---------------------------------- |
||
199 | |||
221 | daniel-mar | 200 | **Filter Foundry:** |
144 | dmarschall | 201 | |
221 | daniel-mar | 202 | Only the branches which will be chosen due to the conditional expression will be evaluated. |
203 | |||
304 | daniel-mar | 204 | This means that the following filter would generate a black canvas: (Testcase conditional_eval_1.afs) |
221 | daniel-mar | 205 | |
206 | R: 1==0 ? put(255,0) : 0 |
||
207 | G: get(0) |
||
208 | B: 0 |
||
209 | |||
210 | In boolean expressions, the evaluation will be aborted if the result is already determined. |
||
211 | |||
212 | So, this will also generate a black canvas: (Testcase conditional_eval_2.afs) |
||
213 | |||
214 | R: 1==0 && put(255,0) ? 0: 0 |
||
215 | G: get(0) |
||
216 | B: 0 |
||
217 | |||
218 | This will also generate a black canvas: (Testcase conditional_eval_3.afs) |
||
219 | |||
220 | R: 1==1 || put(255,0) ? 0 : 0 |
||
221 | G: get(0) |
||
222 | B: 0 |
||
223 | |||
224 | **Filter Factory:** |
||
225 | |||
304 | daniel-mar | 226 | Each branch inside an if-then-else expression will be evaluated. |
227 | This means that the following filter would generate a green canvas: (Testcase conditional_eval_1.afs) |
||
221 | daniel-mar | 228 | |
229 | R: 1==0 ? put(255,0) : 0 |
||
230 | G: get(0) |
||
231 | B: 0 |
||
232 | |||
304 | daniel-mar | 233 | Also, all arguments of a boolean expression will be fully evaluated. |
221 | daniel-mar | 234 | So, this will also generate a green canvas: (Testcase conditional_eval_2.afs) |
235 | |||
236 | R: 1==0 && put(255,0) ? 0: 0 |
||
237 | G: get(0) |
||
238 | B: 0 |
||
239 | |||
240 | This will also generate a green canvas: (Testcase conditional_eval_3.afs) |
||
241 | |||
242 | R: 1==1 || put(255,0) ? 0 : 0 |
||
243 | G: get(0) |
||
244 | B: 0 |