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; | |
210 | i_fsample_t fsamp; | |
faa9b3e7 | 211 | |
999f4294 TC |
212 | #ifdef IM_EIGHT_BIT |
213 | if (backp) { | |
214 | back = *backp; | |
215 | } | |
216 | else if (fbackp) { | |
217 | for (ch = 0; ch < src->channels; ++ch) { | |
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) { | |
289d65f4 TC |
255 | |
256 | ROT_DEBUG(fprintf(stderr, "map " i_DFp " to %g,%g\n", i_DFcp(x, y), sx, sy)); | |
999f4294 TC |
257 | if (sx != (i_img_dim)sx) { |
258 | if (sy != (i_img_dim)sy) { | |
259 | IM_COLOR c[2][2]; | |
260 | IM_COLOR ci2[2]; | |
289d65f4 | 261 | ROT_DEBUG(fprintf(stderr, " both non-int\n")); |
999f4294 TC |
262 | for (i = 0; i < 2; ++i) |
263 | for (j = 0; j < 2; ++j) | |
264 | if (IM_GPIX(src, floor(sx)+i, floor(sy)+j, &c[j][i])) | |
265 | c[j][i] = back; | |
266 | for (j = 0; j < 2; ++j) | |
267 | ci2[j] = interp_i_color(c[j][0], c[j][1], sx, src->channels); | |
268 | vals[x] = interp_i_color(ci2[0], ci2[1], sy, src->channels); | |
269 | } | |
270 | else { | |
271 | IM_COLOR ci2[2]; | |
289d65f4 | 272 | ROT_DEBUG(fprintf(stderr, " y int, x non-int\n")); |
999f4294 TC |
273 | for (i = 0; i < 2; ++i) |
274 | if (IM_GPIX(src, floor(sx)+i, sy, ci2+i)) | |
275 | ci2[i] = back; | |
276 | vals[x] = interp_i_color(ci2[0], ci2[1], sx, src->channels); | |
277 | } | |
278 | } | |
279 | else { | |
280 | if (sy != (i_img_dim)sy) { | |
281 | IM_COLOR ci2[2]; | |
289d65f4 | 282 | ROT_DEBUG(fprintf(stderr, " x int, y non-int\n")); |
999f4294 TC |
283 | for (i = 0; i < 2; ++i) |
284 | if (IM_GPIX(src, sx, floor(sy)+i, ci2+i)) | |
285 | ci2[i] = back; | |
286 | vals[x] = interp_i_color(ci2[0], ci2[1], sy, src->channels); | |
287 | } | |
288 | else { | |
289d65f4 | 289 | ROT_DEBUG(fprintf(stderr, " both int\n")); |
999f4294 TC |
290 | /* all the world's an integer */ |
291 | if (IM_GPIX(src, sx, sy, vals+x)) | |
292 | vals[x] = back; | |
293 | } | |
294 | } | |
295 | } | |
296 | else { | |
297 | vals[x] = back; | |
298 | } | |
faa9b3e7 | 299 | } |
999f4294 | 300 | IM_PLIN(result, 0, xsize, y, vals); |
faa9b3e7 | 301 | } |
999f4294 TC |
302 | myfree(vals); |
303 | #undef interp_i_color | |
304 | #/code | |
faa9b3e7 TC |
305 | } |
306 | else { | |
307 | /* don't interpolate for a palette based image */ | |
308 | i_palidx *vals = mymalloc(xsize * sizeof(i_palidx)); | |
0d3b936e | 309 | i_palidx back = 0; |
faa9b3e7 | 310 | i_color min; |
0d3b936e | 311 | int minval = 256 * 4; |
8d14daab | 312 | i_img_dim ix, iy; |
0d3b936e TC |
313 | i_color want_back; |
314 | i_fsample_t fsamp; | |
faa9b3e7 | 315 | |
0d3b936e TC |
316 | if (backp) { |
317 | want_back = *backp; | |
318 | } | |
319 | else if (fbackp) { | |
320 | for (ch = 0; ch < src->channels; ++ch) { | |
321 | fsamp = fbackp->channel[ch]; | |
322 | want_back.channel[ch] = fsamp < 0 ? 0 : fsamp > 1 ? 255 : fsamp * 255; | |
323 | } | |
324 | } | |
325 | else { | |
326 | for (ch = 0; ch < src->channels; ++ch) | |
327 | want_back.channel[ch] = 0; | |
328 | } | |
329 | ||
330 | /* find the closest color */ | |
331 | for (i = 0; i < i_colorcount(src); ++i) { | |
faa9b3e7 TC |
332 | i_color temp; |
333 | int tempval; | |
334 | i_getcolors(src, i, &temp, 1); | |
335 | tempval = 0; | |
336 | for (ch = 0; ch < src->channels; ++ch) { | |
0d3b936e | 337 | tempval += abs(want_back.channel[ch] - temp.channel[ch]); |
faa9b3e7 TC |
338 | } |
339 | if (tempval < minval) { | |
0d3b936e | 340 | back = i; |
faa9b3e7 TC |
341 | min = temp; |
342 | minval = tempval; | |
343 | } | |
344 | } | |
345 | ||
346 | for (y = 0; y < ysize; ++y) { | |
347 | for (x = 0; x < xsize; ++x) { | |
348 | /* dividing by sz gives us the ability to do perspective | |
349 | transforms */ | |
350 | sz = x * matrix[6] + y * matrix[7] + matrix[8]; | |
351 | if (abs(sz) > 0.0000001) { | |
352 | sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz; | |
353 | sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz; | |
354 | } | |
e4bf9335 TC |
355 | else { |
356 | sx = sy = 0; | |
357 | } | |
faa9b3e7 TC |
358 | |
359 | /* anything outside these ranges is either a broken co-ordinate | |
360 | or outside the source */ | |
361 | if (abs(sz) > 0.0000001 | |
362 | && sx >= -0.5 && sx < src->xsize-0.5 | |
363 | && sy >= -0.5 && sy < src->ysize-0.5) { | |
364 | ||
365 | /* all the world's an integer */ | |
8d14daab TC |
366 | ix = (i_img_dim)(sx+0.5); |
367 | iy = (i_img_dim)(sy+0.5); | |
0d3b936e TC |
368 | if (!i_gpal(src, ix, ix+1, iy, vals+x)) |
369 | vals[i] = back; | |
faa9b3e7 TC |
370 | } |
371 | else { | |
0d3b936e | 372 | vals[x] = back; |
faa9b3e7 TC |
373 | } |
374 | } | |
375 | i_ppal(result, 0, xsize, y, vals); | |
376 | } | |
377 | myfree(vals); | |
378 | } | |
379 | ||
380 | return result; | |
381 | } | |
382 | ||
8d14daab | 383 | i_img *i_matrix_transform(i_img *src, i_img_dim xsize, i_img_dim ysize, const double *matrix) { |
0d3b936e TC |
384 | return i_matrix_transform_bg(src, xsize, ysize, matrix, NULL, NULL); |
385 | } | |
386 | ||
35891892 | 387 | static void |
97ac0a96 | 388 | i_matrix_mult(double *dest, const double *left, const double *right) { |
faa9b3e7 TC |
389 | int i, j, k; |
390 | double accum; | |
391 | ||
392 | for (i = 0; i < 3; ++i) { | |
393 | for (j = 0; j < 3; ++j) { | |
394 | accum = 0.0; | |
395 | for (k = 0; k < 3; ++k) { | |
396 | accum += left[3*i+k] * right[3*k+j]; | |
397 | } | |
398 | dest[3*i+j] = accum; | |
399 | } | |
400 | } | |
401 | } | |
402 | ||
289d65f4 TC |
403 | #define numfmt "%23g" |
404 | ||
405 | ROT_DEBUG(static dump_mat(const char *name, double *f) { | |
406 | fprintf(stderr, "%s:\n " numfmt " " numfmt " " numfmt "\n" | |
407 | " " numfmt " " numfmt " " numfmt "\n" | |
408 | " " numfmt " " numfmt " " numfmt "\n", | |
409 | name, f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8]); | |
410 | }) | |
411 | ||
0d3b936e | 412 | i_img *i_rotate_exact_bg(i_img *src, double amount, |
97ac0a96 | 413 | const i_color *backp, const i_fcolor *fbackp) { |
faa9b3e7 TC |
414 | double xlate1[9] = { 0 }; |
415 | double rotate[9]; | |
416 | double xlate2[9] = { 0 }; | |
417 | double temp[9], matrix[9]; | |
8d14daab | 418 | i_img_dim x1, x2, y1, y2, newxsize, newysize; |
faa9b3e7 TC |
419 | |
420 | /* first translate the centre of the image to (0,0) */ | |
421 | xlate1[0] = 1; | |
289d65f4 | 422 | xlate1[2] = (src->xsize-1)/2.0; |
faa9b3e7 | 423 | xlate1[4] = 1; |
289d65f4 | 424 | xlate1[5] = (src->ysize-1)/2.0; |
faa9b3e7 TC |
425 | xlate1[8] = 1; |
426 | ||
289d65f4 TC |
427 | ROT_DEBUG(dump_mat("xlate1", xlate1)); |
428 | ||
faa9b3e7 TC |
429 | /* rotate around (0.0) */ |
430 | rotate[0] = cos(amount); | |
431 | rotate[1] = sin(amount); | |
432 | rotate[2] = 0; | |
433 | rotate[3] = -rotate[1]; | |
434 | rotate[4] = rotate[0]; | |
435 | rotate[5] = 0; | |
436 | rotate[6] = 0; | |
437 | rotate[7] = 0; | |
438 | rotate[8] = 1; | |
439 | ||
289d65f4 TC |
440 | ROT_DEBUG(dump_mat("rotate", rotate)); |
441 | ||
442 | ROT_DEBUG(fprintf(stderr, "cos %g sin %g\n", rotate[0], rotate[1])); | |
443 | ||
444 | x1 = ceil(fabs(src->xsize * rotate[0] + src->ysize * rotate[1]) - 0.0001); | |
445 | x2 = ceil(fabs(src->xsize * rotate[0] - src->ysize * rotate[1]) - 0.0001); | |
446 | y1 = ceil(fabs(src->xsize * rotate[3] + src->ysize * rotate[4]) - 0.0001); | |
447 | y2 = ceil(fabs(src->xsize * rotate[3] - src->ysize * rotate[4]) - 0.0001); | |
448 | ROT_DEBUG(fprintf(stderr, "x1 y1 " i_DFp " x2 y2 " i_DFp "\n", i_DFcp(x1, y1), i_DFcp(x2, y2))); | |
faa9b3e7 TC |
449 | newxsize = x1 > x2 ? x1 : x2; |
450 | newysize = y1 > y2 ? y1 : y2; | |
451 | /* translate the centre back to the center of the image */ | |
452 | xlate2[0] = 1; | |
289d65f4 | 453 | xlate2[2] = -(newxsize-1)/2.0; |
faa9b3e7 | 454 | xlate2[4] = 1; |
289d65f4 | 455 | xlate2[5] = -(newysize-1)/2.0; |
faa9b3e7 | 456 | xlate2[8] = 1; |
289d65f4 TC |
457 | |
458 | ROT_DEBUG(dump_mat("xlate2", xlate2)); | |
459 | ||
faa9b3e7 TC |
460 | i_matrix_mult(temp, xlate1, rotate); |
461 | i_matrix_mult(matrix, temp, xlate2); | |
462 | ||
289d65f4 TC |
463 | ROT_DEBUG(dump_mat("matrxi", matrix)); |
464 | ||
0d3b936e | 465 | return i_matrix_transform_bg(src, newxsize, newysize, matrix, backp, fbackp); |
faa9b3e7 | 466 | } |
b8c2033e | 467 | |
0d3b936e TC |
468 | i_img *i_rotate_exact(i_img *src, double amount) { |
469 | return i_rotate_exact_bg(src, amount, NULL, NULL); | |
470 | } | |
471 | ||
472 | ||
b8c2033e AMH |
473 | /* |
474 | =back | |
475 | ||
476 | =head1 AUTHOR | |
477 | ||
478 | Tony Cook <tony@develop-help.com> | |
479 | ||
480 | =head1 SEE ALSO | |
481 | ||
482 | Imager(3) | |
483 | ||
484 | =cut | |
485 | */ |