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