[rt #69261] fix incorrect image size and color calculations for rotate()
[imager.git] / rotate.im
1 /*
2 =head1 NAME
3
4   rotate.im - implements image rotations
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
19 #include "imager.h"
20 #include "imageri.h"
21 #include <math.h> /* for floor() */
22
23 i_img *i_rotate90(i_img *src, int degrees) {
24   i_img *targ;
25   i_img_dim x, y;
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) {
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);
45       }
46       myfree(vals);
47 #/code
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) {
69     i_img_dim tx, txstart, txinc;
70     i_img_dim ty, tystart, tyinc;
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) {
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;
98       }
99       myfree(vals);
100 #/code
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
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);
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 + 
148                          pos * after.channel[ch] * after_alpha) / total_alpha + 0.5;
149
150         out.channel[ch] = I_LIMIT_8(out_level);
151       }
152     }
153
154     out.channel[channels-1] = total_cover;
155   }
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);
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   }
192
193   return out;
194 }
195
196 i_img *i_matrix_transform_bg(i_img *src, i_img_dim xsize, i_img_dim ysize, const double *matrix,
197                              const i_color *backp, const i_fcolor *fbackp) {
198   i_img *result = i_sametype(src, xsize, ysize);
199   i_img_dim x, y;
200   int ch;
201   i_img_dim i, j;
202   double sx, sy, sz;
203
204   if (src->type == i_direct_type) {
205 #code src->bits <= 8
206     IM_COLOR *vals = mymalloc(xsize * sizeof(IM_COLOR));
207     IM_COLOR back;
208     i_fsample_t fsamp;
209
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;
218       }
219     }
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
230     else {
231       for (ch = 0; ch < src->channels; ++ch)
232         back.channel[ch] = 0;
233     }
234
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         }
292       }
293       IM_PLIN(result, 0, xsize, y, vals);
294     }
295     myfree(vals);
296 #undef interp_i_color
297 #/code
298   }
299   else {
300     /* don't interpolate for a palette based image */
301     i_palidx *vals = mymalloc(xsize * sizeof(i_palidx));
302     i_palidx back = 0;
303     i_color min;
304     int minval = 256 * 4;
305     i_img_dim ix, iy;
306     i_color want_back;
307     i_fsample_t fsamp;
308
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) {
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) {
330         tempval += abs(want_back.channel[ch] - temp.channel[ch]);
331       }
332       if (tempval < minval) {
333         back = i;
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         }
348         else {
349           sx = sy = 0;
350         }
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 */
359           ix = (i_img_dim)(sx+0.5);
360           iy = (i_img_dim)(sy+0.5);
361           if (!i_gpal(src, ix, ix+1, iy, vals+x))
362             vals[i] = back;
363         }
364         else {
365           vals[x] = back;
366         }
367       }
368       i_ppal(result, 0, xsize, y, vals);
369     }
370     myfree(vals);
371   }
372
373   return result;
374 }
375
376 i_img *i_matrix_transform(i_img *src, i_img_dim xsize, i_img_dim ysize, const double *matrix) {
377   return i_matrix_transform_bg(src, xsize, ysize, matrix, NULL, NULL);
378 }
379
380 static void
381 i_matrix_mult(double *dest, const double *left, const double *right) {
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
396 i_img *i_rotate_exact_bg(i_img *src, double amount, 
397                          const i_color *backp, const i_fcolor *fbackp) {
398   double xlate1[9] = { 0 };
399   double rotate[9];
400   double xlate2[9] = { 0 };
401   double temp[9], matrix[9];
402   i_img_dim x1, x2, y1, y2, newxsize, newysize;
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
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]));
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;
430   xlate2[2] = -newxsize/2.0;
431   xlate2[4] = 1;
432   xlate2[5] = -newysize/2.0;
433   xlate2[8] = 1;
434   i_matrix_mult(temp, xlate1, rotate);
435   i_matrix_mult(matrix, temp, xlate2);
436
437   return i_matrix_transform_bg(src, newxsize, newysize, matrix, backp, fbackp);
438 }
439
440 i_img *i_rotate_exact(i_img *src, double amount) {
441   return i_rotate_exact_bg(src, amount, NULL, NULL);
442 }
443
444
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 */