/* =head1 NAME rotate.im - implements image rotations =head1 SYNOPSIS i_img *i_rotate90(i_img *src, int degrees) =head1 DESCRIPTION Implements basic 90 degree rotations of an image. Other rotations will be added as tuits become available. =cut */ #include "imager.h" #include "imageri.h" #include /* for floor() */ #define ROT_DEBUG(x) i_img *i_rotate90(i_img *src, int degrees) { i_img *targ; i_img_dim x, y; i_clear_error(); if (degrees == 180) { /* essentially the same as flipxy(..., 2) except that it's not done in place */ targ = i_sametype(src, src->xsize, src->ysize); if (src->type == i_direct_type) { #code src->bits <= 8 IM_COLOR *vals = mymalloc(src->xsize * sizeof(IM_COLOR)); for (y = 0; y < src->ysize; ++y) { IM_COLOR tmp; IM_GLIN(src, 0, src->xsize, y, vals); for (x = 0; x < src->xsize/2; ++x) { tmp = vals[x]; vals[x] = vals[src->xsize - x - 1]; vals[src->xsize - x - 1] = tmp; } IM_PLIN(targ, 0, src->xsize, src->ysize - y - 1, vals); } myfree(vals); #/code } else { i_palidx *vals = mymalloc(src->xsize * sizeof(i_palidx)); for (y = 0; y < src->ysize; ++y) { i_palidx tmp; i_gpal(src, 0, src->xsize, y, vals); for (x = 0; x < src->xsize/2; ++x) { tmp = vals[x]; vals[x] = vals[src->xsize - x - 1]; vals[src->xsize - x - 1] = tmp; } i_ppal(targ, 0, src->xsize, src->ysize - y - 1, vals); } myfree(vals); } return targ; } else if (degrees == 270 || degrees == 90) { i_img_dim tx, txstart, txinc; i_img_dim ty, tystart, tyinc; if (degrees == 270) { txstart = 0; txinc = 1; tystart = src->xsize-1; tyinc = -1; } else { txstart = src->ysize-1; txinc = -1; tystart = 0; tyinc = 1; } targ = i_sametype(src, src->ysize, src->xsize); if (src->type == i_direct_type) { #code src->bits <= 8 IM_COLOR *vals = mymalloc(src->xsize * sizeof(IM_COLOR)); tx = txstart; for (y = 0; y < src->ysize; ++y) { IM_GLIN(src, 0, src->xsize, y, vals); ty = tystart; for (x = 0; x < src->xsize; ++x) { IM_PPIX(targ, tx, ty, vals+x); ty += tyinc; } tx += txinc; } myfree(vals); #/code } else { i_palidx *vals = mymalloc(src->xsize * sizeof(i_palidx)); tx = txstart; for (y = 0; y < src->ysize; ++y) { i_gpal(src, 0, src->xsize, y, vals); ty = tystart; for (x = 0; x < src->xsize; ++x) { i_ppal(targ, tx, tx+1, ty, vals+x); ty += tyinc; } tx += txinc; } myfree(vals); } return targ; } else { i_push_error(0, "i_rotate90() only rotates at 90, 180, or 270 degrees"); return NULL; } } /* linear interpolation */ static i_color interp_i_color(i_color before, i_color after, double pos, int channels) { i_color out; int ch; if (channels == 1 || channels == 3) { for (ch = 0; ch < channels; ++ch) out.channel[ch] = ((1-pos) * before.channel[ch] + pos * after.channel[ch]) + 0.5; } else { int total_cover = (1-pos) * before.channel[channels-1] + pos * after.channel[channels-1]; total_cover = I_LIMIT_8(total_cover); if (total_cover) { double before_alpha = before.channel[channels-1] / 255.0; double after_alpha = after.channel[channels-1] / 255.0; double total_alpha = before_alpha * (1-pos) + after_alpha * pos; for (ch = 0; ch < channels-1; ++ch) { int out_level = ((1-pos) * before.channel[ch] * before_alpha + pos * after.channel[ch] * after_alpha) / total_alpha + 0.5; out.channel[ch] = I_LIMIT_8(out_level); } } else { for (ch = 0; ch < channels-1; ++ch) out.channel[ch] = 0; } out.channel[channels-1] = total_cover; } return out; } /* hopefully this will be inlined (it is with -O3 with gcc 2.95.4) */ /* linear interpolation */ static i_fcolor interp_i_fcolor(i_fcolor before, i_fcolor after, double pos, int channels) { i_fcolor out; int ch; if (channels == 1 || channels == 3) { for (ch = 0; ch < channels; ++ch) out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch]; } else { double total_cover = (1-pos) * before.channel[channels-1] + pos * after.channel[channels-1]; total_cover = I_LIMIT_DOUBLE(total_cover); if (total_cover) { double before_alpha = before.channel[channels-1]; double after_alpha = after.channel[channels-1]; double total_alpha = before_alpha * (1-pos) + after_alpha * pos; for (ch = 0; ch < channels-1; ++ch) { double out_level = ((1-pos) * before.channel[ch] * before_alpha + pos * after.channel[ch] * after_alpha) / total_alpha; out.channel[ch] = I_LIMIT_DOUBLE(out_level); } } else { for (ch = 0; ch < channels-1; ++ch) out.channel[ch] = 0; } out.channel[channels-1] = total_cover; } return out; } i_img *i_matrix_transform_bg(i_img *src, i_img_dim xsize, i_img_dim ysize, const double *matrix, const i_color *backp, const i_fcolor *fbackp) { i_img *result = i_sametype(src, xsize, ysize); i_img_dim x, y; int ch; i_img_dim i, j; double sx, sy, sz; if (src->type == i_direct_type) { #code src->bits <= 8 IM_COLOR *vals = mymalloc(xsize * sizeof(IM_COLOR)); IM_COLOR back; #ifdef IM_EIGHT_BIT if (backp) { back = *backp; } else if (fbackp) { for (ch = 0; ch < src->channels; ++ch) { i_fsample_t fsamp; fsamp = fbackp->channel[ch]; back.channel[ch] = fsamp < 0 ? 0 : fsamp > 1 ? 255 : fsamp * 255; } } #else #define interp_i_color interp_i_fcolor if (fbackp) { back = *fbackp; } else if (backp) { for (ch = 0; ch < src->channels; ++ch) back.channel[ch] = backp->channel[ch] / 255.0; } #endif else { for (ch = 0; ch < src->channels; ++ch) back.channel[ch] = 0; } for (y = 0; y < ysize; ++y) { for (x = 0; x < xsize; ++x) { /* dividing by sz gives us the ability to do perspective transforms */ sz = x * matrix[6] + y * matrix[7] + matrix[8]; if (fabs(sz) > 0.0000001) { sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz; sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz; } else { sx = sy = 0; } /* anything outside these ranges is either a broken co-ordinate or outside the source */ if (fabs(sz) > 0.0000001 && sx >= -1 && sx < src->xsize && sy >= -1 && sy < src->ysize) { double fsx = floor(sx); double fsy = floor(sy); i_img_dim bx = fsx; i_img_dim by = fsy; ROT_DEBUG(fprintf(stderr, "map " i_DFp " to %g,%g\n", i_DFcp(x, y), sx, sy)); if (sx != fsx) { double dx = sx - fsx; if (sy != fsy) { IM_COLOR c[2][2]; IM_COLOR ci2[2]; double dy = sy - fsy; ROT_DEBUG(fprintf(stderr, " both non-int\n")); for (i = 0; i < 2; ++i) for (j = 0; j < 2; ++j) if (IM_GPIX(src, bx+i, by+j, &c[j][i])) c[j][i] = back; for (j = 0; j < 2; ++j) ci2[j] = interp_i_color(c[j][0], c[j][1], dx, src->channels); vals[x] = interp_i_color(ci2[0], ci2[1], dy, src->channels); } else { IM_COLOR ci2[2]; ROT_DEBUG(fprintf(stderr, " y int, x non-int\n")); for (i = 0; i < 2; ++i) if (IM_GPIX(src, bx+i, sy, ci2+i)) ci2[i] = back; vals[x] = interp_i_color(ci2[0], ci2[1], dx, src->channels); } } else { if (sy != fsy) { IM_COLOR ci2[2]; double dy = sy - fsy; ROT_DEBUG(fprintf(stderr, " x int, y non-int\n")); for (i = 0; i < 2; ++i) if (IM_GPIX(src, bx, by+i, ci2+i)) ci2[i] = back; vals[x] = interp_i_color(ci2[0], ci2[1], dy, src->channels); } else { ROT_DEBUG(fprintf(stderr, " both int\n")); /* all the world's an integer */ if (IM_GPIX(src, bx, by, vals+x)) vals[x] = back; } } } else { vals[x] = back; } } IM_PLIN(result, 0, xsize, y, vals); } myfree(vals); #undef interp_i_color #/code } else { /* don't interpolate for a palette based image */ i_palidx *vals = mymalloc(xsize * sizeof(i_palidx)); i_palidx back = 0; int minval = 256 * 4; i_img_dim ix, iy; i_color want_back; i_fsample_t fsamp; if (backp) { want_back = *backp; } else if (fbackp) { for (ch = 0; ch < src->channels; ++ch) { fsamp = fbackp->channel[ch]; want_back.channel[ch] = fsamp < 0 ? 0 : fsamp > 1 ? 255 : fsamp * 255; } } else { for (ch = 0; ch < src->channels; ++ch) want_back.channel[ch] = 0; } /* find the closest color */ for (i = 0; i < i_colorcount(src); ++i) { i_color temp; int tempval; i_getcolors(src, i, &temp, 1); tempval = 0; for (ch = 0; ch < src->channels; ++ch) { tempval += abs(want_back.channel[ch] - temp.channel[ch]); } if (tempval < minval) { back = i; minval = tempval; } } for (y = 0; y < ysize; ++y) { for (x = 0; x < xsize; ++x) { /* dividing by sz gives us the ability to do perspective transforms */ sz = x * matrix[6] + y * matrix[7] + matrix[8]; if (abs(sz) > 0.0000001) { sx = (x * matrix[0] + y * matrix[1] + matrix[2]) / sz; sy = (x * matrix[3] + y * matrix[4] + matrix[5]) / sz; } else { sx = sy = 0; } /* anything outside these ranges is either a broken co-ordinate or outside the source */ if (abs(sz) > 0.0000001 && sx >= -0.5 && sx < src->xsize-0.5 && sy >= -0.5 && sy < src->ysize-0.5) { /* all the world's an integer */ ix = (i_img_dim)(sx+0.5); iy = (i_img_dim)(sy+0.5); if (!i_gpal(src, ix, ix+1, iy, vals+x)) vals[i] = back; } else { vals[x] = back; } } i_ppal(result, 0, xsize, y, vals); } myfree(vals); } return result; } i_img *i_matrix_transform(i_img *src, i_img_dim xsize, i_img_dim ysize, const double *matrix) { return i_matrix_transform_bg(src, xsize, ysize, matrix, NULL, NULL); } static void i_matrix_mult(double *dest, const double *left, const double *right) { int i, j, k; double accum; for (i = 0; i < 3; ++i) { for (j = 0; j < 3; ++j) { accum = 0.0; for (k = 0; k < 3; ++k) { accum += left[3*i+k] * right[3*k+j]; } dest[3*i+j] = accum; } } } #define numfmt "%23g" ROT_DEBUG(static void dump_mat(const char *name, double *f) { fprintf(stderr, "%s:\n " numfmt " " numfmt " " numfmt "\n" " " numfmt " " numfmt " " numfmt "\n" " " numfmt " " numfmt " " numfmt "\n", name, f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8]); }) i_img *i_rotate_exact_bg(i_img *src, double amount, const i_color *backp, const i_fcolor *fbackp) { double xlate1[9] = { 0 }; double rotate[9]; double xlate2[9] = { 0 }; double temp[9], matrix[9]; i_img_dim x1, x2, y1, y2, newxsize, newysize; ROT_DEBUG(fprintf(stderr, "rotate angle %.20g\n", amount)); /* first translate the centre of the image to (0,0) */ xlate1[0] = 1; xlate1[2] = (src->xsize-1)/2.0; xlate1[4] = 1; xlate1[5] = (src->ysize-1)/2.0; xlate1[8] = 1; ROT_DEBUG(dump_mat("xlate1", xlate1)); /* rotate around (0.0) */ rotate[0] = cos(amount); rotate[1] = sin(amount); rotate[2] = 0; rotate[3] = -rotate[1]; rotate[4] = rotate[0]; rotate[5] = 0; rotate[6] = 0; rotate[7] = 0; rotate[8] = 1; ROT_DEBUG(dump_mat("rotate", rotate)); ROT_DEBUG(fprintf(stderr, "cos %g sin %g\n", rotate[0], rotate[1])); x1 = ceil(fabs(src->xsize * rotate[0] + src->ysize * rotate[1]) - 0.0001); x2 = ceil(fabs(src->xsize * rotate[0] - src->ysize * rotate[1]) - 0.0001); y1 = ceil(fabs(src->xsize * rotate[3] + src->ysize * rotate[4]) - 0.0001); y2 = ceil(fabs(src->xsize * rotate[3] - src->ysize * rotate[4]) - 0.0001); ROT_DEBUG(fprintf(stderr, "x1 y1 " i_DFp " x2 y2 " i_DFp "\n", i_DFcp(x1, y1), i_DFcp(x2, y2))); newxsize = x1 > x2 ? x1 : x2; newysize = y1 > y2 ? y1 : y2; /* translate the centre back to the center of the image */ xlate2[0] = 1; xlate2[2] = -(newxsize-1)/2.0; xlate2[4] = 1; xlate2[5] = -(newysize-1)/2.0; xlate2[8] = 1; ROT_DEBUG(dump_mat("xlate2", xlate2)); i_matrix_mult(temp, xlate1, rotate); i_matrix_mult(matrix, temp, xlate2); ROT_DEBUG(dump_mat("matrxi", matrix)); return i_matrix_transform_bg(src, newxsize, newysize, matrix, backp, fbackp); } i_img *i_rotate_exact(i_img *src, double amount) { return i_rotate_exact_bg(src, amount, NULL, NULL); } /* =back =head1 AUTHOR Tony Cook =head1 SEE ALSO Imager(3) =cut */