Commit | Line | Data |
---|---|---|
faa9b3e7 TC |
1 | /* |
2 | =head1 NAME | |
3 | ||
999f4294 | 4 | rotate.im - implements image rotations |
faa9b3e7 TC |
5 | |
6 | =head1 SYNOPSIS | |
7 | ||
8 | i_img *i_rotate90(i_img *src, int degrees) | |
9 | ||
10 | =head1 DESCRIPTION | |
11 | ||
12 | Implements basic 90 degree rotations of an image. | |
13 | ||
14 | Other rotations will be added as tuits become available. | |
15 | ||
16 | =cut | |
17 | */ | |
18 | ||
92bda632 | 19 | #include "imager.h" |
13c9a303 | 20 | #include "imageri.h" |
faa9b3e7 TC |
21 | #include <math.h> /* for floor() */ |
22 | ||
23 | i_img *i_rotate90(i_img *src, int degrees) { | |
24 | i_img *targ; | |
8d14daab | 25 | i_img_dim x, y; |
faa9b3e7 TC |
26 | |
27 | i_clear_error(); | |
28 | ||
29 | if (degrees == 180) { | |
30 | /* essentially the same as flipxy(..., 2) except that it's not | |
31 | done in place */ | |
32 | targ = i_sametype(src, src->xsize, src->ysize); | |
33 | if (src->type == i_direct_type) { | |
999f4294 TC |
34 | #code src->bits <= 8 |
35 | IM_COLOR *vals = mymalloc(src->xsize * sizeof(IM_COLOR)); | |
36 | for (y = 0; y < src->ysize; ++y) { | |
37 | IM_COLOR tmp; | |
38 | IM_GLIN(src, 0, src->xsize, y, vals); | |
39 | for (x = 0; x < src->xsize/2; ++x) { | |
40 | tmp = vals[x]; | |
41 | vals[x] = vals[src->xsize - x - 1]; | |
42 | vals[src->xsize - x - 1] = tmp; | |
43 | } | |
44 | IM_PLIN(targ, 0, src->xsize, src->ysize - y - 1, vals); | |
faa9b3e7 | 45 | } |
999f4294 TC |
46 | myfree(vals); |
47 | #/code | |
faa9b3e7 TC |
48 | } |
49 | else { | |
50 | i_palidx *vals = mymalloc(src->xsize * sizeof(i_palidx)); | |
51 | ||
52 | for (y = 0; y < src->ysize; ++y) { | |
53 | i_palidx tmp; | |
54 | i_gpal(src, 0, src->xsize, y, vals); | |
55 | for (x = 0; x < src->xsize/2; ++x) { | |
56 | tmp = vals[x]; | |
57 | vals[x] = vals[src->xsize - x - 1]; | |
58 | vals[src->xsize - x - 1] = tmp; | |
59 | } | |
60 | i_ppal(targ, 0, src->xsize, src->ysize - y - 1, vals); | |
61 | } | |
62 | ||
63 | myfree(vals); | |
64 | } | |
65 | ||
66 | return targ; | |
67 | } | |
68 | else if (degrees == 270 || degrees == 90) { | |
8d14daab TC |
69 | i_img_dim tx, txstart, txinc; |
70 | i_img_dim ty, tystart, tyinc; | |
faa9b3e7 TC |
71 | |
72 | if (degrees == 270) { | |
73 | txstart = 0; | |
74 | txinc = 1; | |
75 | tystart = src->xsize-1; | |
76 | tyinc = -1; | |
77 | } | |
78 | else { | |
79 | txstart = src->ysize-1; | |
80 | txinc = -1; | |
81 | tystart = 0; | |
82 | tyinc = 1; | |
83 | } | |
84 | targ = i_sametype(src, src->ysize, src->xsize); | |
85 | if (src->type == i_direct_type) { | |
999f4294 TC |
86 | #code src->bits <= 8 |
87 | IM_COLOR *vals = mymalloc(src->xsize * sizeof(IM_COLOR)); | |
88 | ||
89 | tx = txstart; | |
90 | for (y = 0; y < src->ysize; ++y) { | |
91 | IM_GLIN(src, 0, src->xsize, y, vals); | |
92 | ty = tystart; | |
93 | for (x = 0; x < src->xsize; ++x) { | |
94 | IM_PPIX(targ, tx, ty, vals+x); | |
95 | ty += tyinc; | |
96 | } | |
97 | tx += txinc; | |
faa9b3e7 | 98 | } |
999f4294 TC |
99 | myfree(vals); |
100 | #/code | |
faa9b3e7 TC |
101 | } |
102 | else { | |
103 | i_palidx *vals = mymalloc(src->xsize * sizeof(i_palidx)); | |
104 | ||
105 | tx = txstart; | |
106 | for (y = 0; y < src->ysize; ++y) { | |
107 | i_gpal(src, 0, src->xsize, y, vals); | |
108 | ty = tystart; | |
109 | for (x = 0; x < src->xsize; ++x) { | |
110 | i_ppal(targ, tx, tx+1, ty, vals+x); | |
111 | ty += tyinc; | |
112 | } | |
113 | tx += txinc; | |
114 | } | |
115 | myfree(vals); | |
116 | } | |
117 | return targ; | |
118 | } | |
119 | else { | |
120 | i_push_error(0, "i_rotate90() only rotates at 90, 180, or 270 degrees"); | |
121 | return NULL; | |
122 | } | |
123 | } | |
124 | ||
faa9b3e7 TC |
125 | /* linear interpolation */ |
126 | static i_color interp_i_color(i_color before, i_color after, double pos, | |
127 | int channels) { | |
128 | i_color out; | |
129 | int ch; | |
130 | ||
131 | pos -= floor(pos); | |
13c9a303 TC |
132 | if (channels == 1 || channels == 3) { |
133 | for (ch = 0; ch < channels; ++ch) | |
134 | out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch]; | |
135 | } | |
136 | else { | |
137 | int total_cover = (1-pos) * before.channel[channels-1] | |
138 | + pos * after.channel[channels-1]; | |
139 | ||
140 | total_cover = I_LIMIT_8(total_cover); | |
141 | if (total_cover) { | |
142 | double before_alpha = before.channel[channels-1] / 255.0; | |
143 | double after_alpha = after.channel[channels-1] / 255.0; | |
144 | double total_alpha = before_alpha * (1-pos) + after_alpha * pos; | |
145 | ||
146 | for (ch = 0; ch < channels-1; ++ch) { | |
147 | int out_level = ((1-pos) * before.channel[ch] * before_alpha + | |
ef74691a | 148 | pos * after.channel[ch] * after_alpha) / total_alpha + 0.5; |
13c9a303 TC |
149 | |
150 | out.channel[ch] = I_LIMIT_8(out_level); | |
151 | } | |
152 | } | |
153 | ||
154 | out.channel[channels-1] = total_cover; | |
155 | } | |
faa9b3e7 TC |
156 | |
157 | return out; | |
158 | } | |
159 | ||
160 | /* hopefully this will be inlined (it is with -O3 with gcc 2.95.4) */ | |
161 | /* linear interpolation */ | |
162 | static i_fcolor interp_i_fcolor(i_fcolor before, i_fcolor after, double pos, | |
163 | int channels) { | |
164 | i_fcolor out; | |
165 | int ch; | |
166 | ||
167 | pos -= floor(pos); | |
13c9a303 TC |
168 | if (channels == 1 || channels == 3) { |
169 | for (ch = 0; ch < channels; ++ch) | |
170 | out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch]; | |
171 | } | |
172 | else { | |
173 | double total_cover = (1-pos) * before.channel[channels-1] | |
174 | + pos * after.channel[channels-1]; | |
175 | ||
176 | total_cover = I_LIMIT_DOUBLE(total_cover); | |
177 | if (total_cover) { | |
178 | double before_alpha = before.channel[channels-1]; | |
179 | double after_alpha = after.channel[channels-1]; | |
180 | double total_alpha = before_alpha * (1-pos) + after_alpha * pos; | |
181 | ||
182 | for (ch = 0; ch < channels-1; ++ch) { | |
183 | double out_level = ((1-pos) * before.channel[ch] * before_alpha + | |
184 | pos * after.channel[ch] * after_alpha) / total_alpha; | |
185 | ||
186 | out.channel[ch] = I_LIMIT_DOUBLE(out_level); | |
187 | } | |
188 | } | |
189 | ||
190 | out.channel[channels-1] = total_cover; | |
191 | } | |
faa9b3e7 TC |
192 | |
193 | return out; | |
194 | } | |
195 | ||
8d14daab | 196 | i_img *i_matrix_transform_bg(i_img *src, i_img_dim xsize, i_img_dim ysize, const double *matrix, |
97ac0a96 | 197 | const i_color *backp, const i_fcolor *fbackp) { |
faa9b3e7 | 198 | i_img *result = i_sametype(src, xsize, ysize); |
8d14daab | 199 | i_img_dim x, y; |
faa9b3e7 | 200 | int ch; |
8d14daab | 201 | i_img_dim i, j; |
faa9b3e7 | 202 | double sx, sy, sz; |
faa9b3e7 TC |
203 | |
204 | if (src->type == i_direct_type) { | |
999f4294 TC |
205 | #code src->bits <= 8 |
206 | IM_COLOR *vals = mymalloc(xsize * sizeof(IM_COLOR)); | |
207 | IM_COLOR back; | |
208 | i_fsample_t fsamp; | |
faa9b3e7 | 209 | |
999f4294 TC |
210 | #ifdef IM_EIGHT_BIT |
211 | if (backp) { | |
212 | back = *backp; | |
213 | } | |
214 | else if (fbackp) { | |
215 | for (ch = 0; ch < src->channels; ++ch) { | |
216 | fsamp = fbackp->channel[ch]; | |
217 | back.channel[ch] = fsamp < 0 ? 0 : fsamp > 1 ? 255 : fsamp * 255; | |
faa9b3e7 | 218 | } |
faa9b3e7 | 219 | } |
999f4294 TC |
220 | #else |
221 | #define interp_i_color interp_i_fcolor | |
222 | if (fbackp) { | |
223 | back = *fbackp; | |
224 | } | |
225 | else if (backp) { | |
226 | for (ch = 0; ch < src->channels; ++ch) | |
227 | back.channel[ch] = backp->channel[ch] / 255.0; | |
228 | } | |
229 | #endif | |
faa9b3e7 | 230 | else { |
999f4294 TC |
231 | for (ch = 0; ch < src->channels; ++ch) |
232 | back.channel[ch] = 0; | |
233 | } | |
faa9b3e7 | 234 | |
999f4294 TC |
235 | for (y = 0; y < ysize; ++y) { |
236 | for (x = 0; x < xsize; ++x) { | |
237 | /* dividing by sz gives us the ability to do perspective | |
238 | transforms */ | |
239 | sz = x * matrix[6] + y * matrix[7] + matrix[8]; | |
240 | if (fabs(sz) > 0.0000001) { | |
241 | sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz; | |
242 | sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz; | |
243 | } | |
244 | else { | |
245 | sx = sy = 0; | |
246 | } | |
247 | ||
248 | /* anything outside these ranges is either a broken co-ordinate | |
249 | or outside the source */ | |
250 | if (fabs(sz) > 0.0000001 | |
251 | && sx >= -1 && sx < src->xsize | |
252 | && sy >= -1 && sy < src->ysize) { | |
253 | ||
254 | if (sx != (i_img_dim)sx) { | |
255 | if (sy != (i_img_dim)sy) { | |
256 | IM_COLOR c[2][2]; | |
257 | IM_COLOR ci2[2]; | |
258 | for (i = 0; i < 2; ++i) | |
259 | for (j = 0; j < 2; ++j) | |
260 | if (IM_GPIX(src, floor(sx)+i, floor(sy)+j, &c[j][i])) | |
261 | c[j][i] = back; | |
262 | for (j = 0; j < 2; ++j) | |
263 | ci2[j] = interp_i_color(c[j][0], c[j][1], sx, src->channels); | |
264 | vals[x] = interp_i_color(ci2[0], ci2[1], sy, src->channels); | |
265 | } | |
266 | else { | |
267 | IM_COLOR ci2[2]; | |
268 | for (i = 0; i < 2; ++i) | |
269 | if (IM_GPIX(src, floor(sx)+i, sy, ci2+i)) | |
270 | ci2[i] = back; | |
271 | vals[x] = interp_i_color(ci2[0], ci2[1], sx, src->channels); | |
272 | } | |
273 | } | |
274 | else { | |
275 | if (sy != (i_img_dim)sy) { | |
276 | IM_COLOR ci2[2]; | |
277 | for (i = 0; i < 2; ++i) | |
278 | if (IM_GPIX(src, sx, floor(sy)+i, ci2+i)) | |
279 | ci2[i] = back; | |
280 | vals[x] = interp_i_color(ci2[0], ci2[1], sy, src->channels); | |
281 | } | |
282 | else { | |
283 | /* all the world's an integer */ | |
284 | if (IM_GPIX(src, sx, sy, vals+x)) | |
285 | vals[x] = back; | |
286 | } | |
287 | } | |
288 | } | |
289 | else { | |
290 | vals[x] = back; | |
291 | } | |
faa9b3e7 | 292 | } |
999f4294 | 293 | IM_PLIN(result, 0, xsize, y, vals); |
faa9b3e7 | 294 | } |
999f4294 TC |
295 | myfree(vals); |
296 | #undef interp_i_color | |
297 | #/code | |
faa9b3e7 TC |
298 | } |
299 | else { | |
300 | /* don't interpolate for a palette based image */ | |
301 | i_palidx *vals = mymalloc(xsize * sizeof(i_palidx)); | |
0d3b936e | 302 | i_palidx back = 0; |
faa9b3e7 | 303 | i_color min; |
0d3b936e | 304 | int minval = 256 * 4; |
8d14daab | 305 | i_img_dim ix, iy; |
0d3b936e TC |
306 | i_color want_back; |
307 | i_fsample_t fsamp; | |
faa9b3e7 | 308 | |
0d3b936e TC |
309 | if (backp) { |
310 | want_back = *backp; | |
311 | } | |
312 | else if (fbackp) { | |
313 | for (ch = 0; ch < src->channels; ++ch) { | |
314 | fsamp = fbackp->channel[ch]; | |
315 | want_back.channel[ch] = fsamp < 0 ? 0 : fsamp > 1 ? 255 : fsamp * 255; | |
316 | } | |
317 | } | |
318 | else { | |
319 | for (ch = 0; ch < src->channels; ++ch) | |
320 | want_back.channel[ch] = 0; | |
321 | } | |
322 | ||
323 | /* find the closest color */ | |
324 | for (i = 0; i < i_colorcount(src); ++i) { | |
faa9b3e7 TC |
325 | i_color temp; |
326 | int tempval; | |
327 | i_getcolors(src, i, &temp, 1); | |
328 | tempval = 0; | |
329 | for (ch = 0; ch < src->channels; ++ch) { | |
0d3b936e | 330 | tempval += abs(want_back.channel[ch] - temp.channel[ch]); |
faa9b3e7 TC |
331 | } |
332 | if (tempval < minval) { | |
0d3b936e | 333 | back = i; |
faa9b3e7 TC |
334 | min = temp; |
335 | minval = tempval; | |
336 | } | |
337 | } | |
338 | ||
339 | for (y = 0; y < ysize; ++y) { | |
340 | for (x = 0; x < xsize; ++x) { | |
341 | /* dividing by sz gives us the ability to do perspective | |
342 | transforms */ | |
343 | sz = x * matrix[6] + y * matrix[7] + matrix[8]; | |
344 | if (abs(sz) > 0.0000001) { | |
345 | sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz; | |
346 | sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz; | |
347 | } | |
e4bf9335 TC |
348 | else { |
349 | sx = sy = 0; | |
350 | } | |
faa9b3e7 TC |
351 | |
352 | /* anything outside these ranges is either a broken co-ordinate | |
353 | or outside the source */ | |
354 | if (abs(sz) > 0.0000001 | |
355 | && sx >= -0.5 && sx < src->xsize-0.5 | |
356 | && sy >= -0.5 && sy < src->ysize-0.5) { | |
357 | ||
358 | /* all the world's an integer */ | |
8d14daab TC |
359 | ix = (i_img_dim)(sx+0.5); |
360 | iy = (i_img_dim)(sy+0.5); | |
0d3b936e TC |
361 | if (!i_gpal(src, ix, ix+1, iy, vals+x)) |
362 | vals[i] = back; | |
faa9b3e7 TC |
363 | } |
364 | else { | |
0d3b936e | 365 | vals[x] = back; |
faa9b3e7 TC |
366 | } |
367 | } | |
368 | i_ppal(result, 0, xsize, y, vals); | |
369 | } | |
370 | myfree(vals); | |
371 | } | |
372 | ||
373 | return result; | |
374 | } | |
375 | ||
8d14daab | 376 | i_img *i_matrix_transform(i_img *src, i_img_dim xsize, i_img_dim ysize, const double *matrix) { |
0d3b936e TC |
377 | return i_matrix_transform_bg(src, xsize, ysize, matrix, NULL, NULL); |
378 | } | |
379 | ||
35891892 | 380 | static void |
97ac0a96 | 381 | i_matrix_mult(double *dest, const double *left, const double *right) { |
faa9b3e7 TC |
382 | int i, j, k; |
383 | double accum; | |
384 | ||
385 | for (i = 0; i < 3; ++i) { | |
386 | for (j = 0; j < 3; ++j) { | |
387 | accum = 0.0; | |
388 | for (k = 0; k < 3; ++k) { | |
389 | accum += left[3*i+k] * right[3*k+j]; | |
390 | } | |
391 | dest[3*i+j] = accum; | |
392 | } | |
393 | } | |
394 | } | |
395 | ||
0d3b936e | 396 | i_img *i_rotate_exact_bg(i_img *src, double amount, |
97ac0a96 | 397 | const i_color *backp, const i_fcolor *fbackp) { |
faa9b3e7 TC |
398 | double xlate1[9] = { 0 }; |
399 | double rotate[9]; | |
400 | double xlate2[9] = { 0 }; | |
401 | double temp[9], matrix[9]; | |
8d14daab | 402 | i_img_dim x1, x2, y1, y2, newxsize, newysize; |
faa9b3e7 TC |
403 | |
404 | /* first translate the centre of the image to (0,0) */ | |
405 | xlate1[0] = 1; | |
406 | xlate1[2] = src->xsize/2.0; | |
407 | xlate1[4] = 1; | |
408 | xlate1[5] = src->ysize/2.0; | |
409 | xlate1[8] = 1; | |
410 | ||
411 | /* rotate around (0.0) */ | |
412 | rotate[0] = cos(amount); | |
413 | rotate[1] = sin(amount); | |
414 | rotate[2] = 0; | |
415 | rotate[3] = -rotate[1]; | |
416 | rotate[4] = rotate[0]; | |
417 | rotate[5] = 0; | |
418 | rotate[6] = 0; | |
419 | rotate[7] = 0; | |
420 | rotate[8] = 1; | |
421 | ||
ef74691a TC |
422 | x1 = ceil(fabs(src->xsize * rotate[0] + src->ysize * rotate[1])); |
423 | x2 = ceil(fabs(src->xsize * rotate[0] - src->ysize * rotate[1])); | |
424 | y1 = ceil(fabs(src->xsize * rotate[3] + src->ysize * rotate[4])); | |
425 | y2 = ceil(fabs(src->xsize * rotate[3] - src->ysize * rotate[4])); | |
faa9b3e7 TC |
426 | newxsize = x1 > x2 ? x1 : x2; |
427 | newysize = y1 > y2 ? y1 : y2; | |
428 | /* translate the centre back to the center of the image */ | |
429 | xlate2[0] = 1; | |
13c9a303 | 430 | xlate2[2] = -newxsize/2.0; |
faa9b3e7 | 431 | xlate2[4] = 1; |
13c9a303 | 432 | xlate2[5] = -newysize/2.0; |
faa9b3e7 TC |
433 | xlate2[8] = 1; |
434 | i_matrix_mult(temp, xlate1, rotate); | |
435 | i_matrix_mult(matrix, temp, xlate2); | |
436 | ||
0d3b936e | 437 | return i_matrix_transform_bg(src, newxsize, newysize, matrix, backp, fbackp); |
faa9b3e7 | 438 | } |
b8c2033e | 439 | |
0d3b936e TC |
440 | i_img *i_rotate_exact(i_img *src, double amount) { |
441 | return i_rotate_exact_bg(src, amount, NULL, NULL); | |
442 | } | |
443 | ||
444 | ||
b8c2033e AMH |
445 | /* |
446 | =back | |
447 | ||
448 | =head1 AUTHOR | |
449 | ||
450 | Tony Cook <tony@develop-help.com> | |
451 | ||
452 | =head1 SEE ALSO | |
453 | ||
454 | Imager(3) | |
455 | ||
456 | =cut | |
457 | */ |