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