// Fixed-point version of magick.c
static fixed_t BoxFixed(const fixed_t x,const fixed_t ARGUNUSED(support))
{
if (x < -FIXED_HALF)
return 0;
if (x < FIXED_HALF)
return FIXED_1;
return 0;
}
static fixed_t TriangleFixed(const fixed_t x,const fixed_t ARGUNUSED(support))
{
if (x < -FIXED_1)
return 0;
if (x < 0)
return(FIXED_1+x);
if (x < FIXED_1)
return(FIXED_1-x);
return 0;
}
// Other filters could be ported but they are increasingly more complex and
// include a lot of multiplication, divisison, and/or trig functions
static void
image_downsize_gm_horizontal_filter_fixed_point(image *im, ImageInfo *source, ImageInfo *destination,
const fixed_t x_factor, const FilterInfoFixed *filter_info, ContributionInfoFixed *contribution, int rotate)
{
fixed_t scale, support;
int x;
int dstX = 0;
int dstW = destination->columns;
if (im->width_padding) {
dstX = im->width_padding;
dstW = im->width_inner;
}
scale = MAX(fixed_div(FIXED_1, x_factor), FIXED_1);
support = fixed_mul(scale, filter_info->support);
if (support <= FIXED_HALF) {
// Reduce to point sampling
support = FIXED_HALF + FIXED_EPSILON;
scale = FIXED_1;
}
scale = fixed_div(FIXED_1, scale);
for (x = dstX; (x < dstX + dstW); x++) {
fixed_t center, density;
int n, start, stop, y;
center = fixed_div(int_to_fixed(x - dstX) + FIXED_HALF, x_factor);
start = fixed_to_int(MAX(center - support + FIXED_HALF, 0));
stop = fixed_to_int(MIN(center + support + FIXED_HALF, int_to_fixed(source->columns)));
density = 0;
//DEBUG_TRACE("x %d: center %.2f, start %d, stop %d\n", x, center, start, stop);
for (n = 0; n < (stop - start); n++) {
contribution[n].pixel = start + n;
contribution[n].weight = filter_info->function(fixed_mul(scale, (int_to_fixed(start) + int_to_fixed(n) - center + FIXED_HALF)), filter_info->support);
density += contribution[n].weight;
//DEBUG_TRACE(" contribution[%d].pixel %d, weight %.2f, density %.2f\n", n, contribution[n].pixel, contribution[n].weight, density);
}
if ((density != 0) && (density != FIXED_1)) {
// Normalize
int i;
density = fixed_div(FIXED_1, density);
for (i = 0; i < n; i++) {
contribution[i].weight = fixed_mul(contribution[i].weight, density);
//DEBUG_TRACE(" normalize contribution[%d].weight to %.2f\n", i, fixed_to_float(contribution[i].weight));
}
}
for (y = 0; y < destination->rows; y++) {
fixed_t weight;
fixed_t red = 0, green = 0, blue = 0, alpha = 0;
pix p;
int j;
register int i;
//DEBUG_TRACE("y %d:\n", y);
if (im->has_alpha) {
fixed_t normalize = 0;
for (i = 0; i < n; i++) {
j = (int)((y * source->columns) + contribution[i].pixel);
weight = contribution[i].weight;
p = source->buf[j];
// XXX The original GM code weighted based on transparency for some reason,
// but this produces bad results, so we use only the weight
//transparency_coeff = weight * ((float)COL_ALPHA(p) / 255);
/*
DEBUG_TRACE(" merging with pix (%d, %d) @ %d (%d %d %d %d) weight %.2f\n",
x, contribution[i].pixel, j,
COL_RED(p), COL_GREEN(p), COL_BLUE(p), COL_ALPHA(p),
fixed_to_float(weight));
*/
red += fixed_mul(weight, int_to_fixed(COL_RED(p)));
green += fixed_mul(weight, int_to_fixed(COL_GREEN(p)));
blue += fixed_mul(weight, int_to_fixed(COL_BLUE(p)));
alpha += fixed_mul(weight, int_to_fixed(COL_ALPHA(p)));
normalize += weight;
}
normalize = fixed_div(FIXED_1, (ABS(normalize) <= FIXED_EPSILON ? FIXED_1 : normalize));
red = fixed_mul(red, normalize);
green = fixed_mul(green, normalize);
blue = fixed_mul(blue, normalize);
}
else {
for (i = 0; i < n; i++) {
j = (int)((y * source->columns) + contribution[i].pixel);
weight = contribution[i].weight;
p = source->buf[j];
/*
DEBUG_TRACE(" merging with pix (%d, %d) @ %d (%d %d %d) weight %.2f\n",
contribution[i].pixel, y, j,
COL_RED(p), COL_GREEN(p), COL_BLUE(p),
weight);
*/
red += fixed_mul(weight, int_to_fixed(COL_RED(p)));
green += fixed_mul(weight, int_to_fixed(COL_GREEN(p)));
blue += fixed_mul(weight, int_to_fixed(COL_BLUE(p)));
}
alpha = FIXED_255;
}
/*
DEBUG_TRACE(" -> (%d, %d) @ %d (%d %d %d %d)\n",
x, y, (y * destination->columns) + x,
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha));
*/
if (rotate && im->orientation != ORIENTATION_NORMAL) {
int ox, oy; // new destination pixel coordinates after rotating
image_get_rotated_coords(im, x, y, &ox, &oy);
if (im->orientation >= 5) {
// 90 and 270 rotations, width/height are swapped
destination->buf[(oy * destination->rows) + ox] = COL_FULL(
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha)
);
}
else {
destination->buf[(oy * destination->columns) + ox] = COL_FULL(
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha)
);
}
}
else {
destination->buf[(y * destination->columns) + x] = COL_FULL(
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha)
);
}
}
}
}
static void
image_downsize_gm_vertical_filter_fixed_point(image *im, ImageInfo *source, ImageInfo *destination,
const fixed_t y_factor, const FilterInfoFixed *filter_info, ContributionInfoFixed *contribution, int rotate)
{
fixed_t scale, support;
int y;
int dstY = 0;
int dstH = destination->rows;
if (im->height_padding) {
dstY = im->height_padding;
dstH = im->height_inner;
}
//DEBUG_TRACE("y_factor %.2f\n", y_factor);
// Apply filter to resize vertically from source to destination
scale = MAX(fixed_div(FIXED_1, y_factor), FIXED_1);
support = fixed_mul(scale, filter_info->support);
if (support <= FIXED_HALF) {
// Reduce to point sampling
support = FIXED_HALF + FIXED_EPSILON;
scale = FIXED_1;
}
scale = fixed_div(FIXED_1, scale);
for (y = dstY; (y < dstY + dstH); y++) {
fixed_t center, density;
int n, start, stop, x;
center = fixed_div(int_to_fixed(y - dstY) + FIXED_HALF, y_factor);
start = fixed_to_int(MAX(center - support + FIXED_HALF, 0));
stop = fixed_to_int(MIN(center + support + FIXED_HALF, int_to_fixed(source->rows)));
density = 0;
//DEBUG_TRACE("y %d: center %.2f, start %d, stop %d\n", y, fixed_to_float(center), start, stop);
for (n = 0; n < (stop - start); n++) {
contribution[n].pixel = start + n;
contribution[n].weight = filter_info->function(fixed_mul(scale, (int_to_fixed(start) + int_to_fixed(n) - center + FIXED_HALF)), filter_info->support);
density += contribution[n].weight;
//DEBUG_TRACE(" contribution[%d].pixel %d, weight %.2f, density %.2f\n", n, contribution[n].pixel, contribution[n].weight, density);
}
if ((density != 0) && (density != FIXED_1)) {
// Normalize
int i;
density = fixed_div(FIXED_1, density);
for (i = 0; i < n; i++) {
contribution[i].weight = fixed_mul(contribution[i].weight, density);
//DEBUG_TRACE(" normalize contribution[%d].weight to %.2f\n", i, fixed_to_float(contribution[i].weight));
}
}
for (x = 0; x < destination->columns; x++) {
fixed_t weight;
fixed_t red = 0, green = 0, blue = 0, alpha = 0;
pix p;
int j;
register int i;
//DEBUG_TRACE("x %d:\n", x);
if (im->has_alpha) {
fixed_t normalize = 0;
for (i = 0; i < n; i++) {
j = (int)((contribution[i].pixel * source->columns) + x);
weight = contribution[i].weight;
p = source->buf[j];
// XXX The original GM code weighted based on transparency for some reason,
// but this produces bad results, so we use only the weight
//transparency_coeff = weight * ((float)COL_ALPHA(p) / 255);
/*
DEBUG_TRACE(" merging with pix (%d, %d) @ %d (%d %d %d %d) weight %.2f\n",
x, contribution[i].pixel, j,
COL_RED(p), COL_GREEN(p), COL_BLUE(p), COL_ALPHA(p),
fixed_to_float(weight));
*/
red += fixed_mul(weight, int_to_fixed(COL_RED(p)));
green += fixed_mul(weight, int_to_fixed(COL_GREEN(p)));
blue += fixed_mul(weight, int_to_fixed(COL_BLUE(p)));
alpha += fixed_mul(weight, int_to_fixed(COL_ALPHA(p)));
normalize += weight;
}
normalize = fixed_div(FIXED_1, (ABS(normalize) <= FIXED_EPSILON ? FIXED_1 : normalize));
red = fixed_mul(red, normalize);
green = fixed_mul(green, normalize);
blue = fixed_mul(blue, normalize);
}
else {
for (i = 0; i < n; i++) {
j = (int)((contribution[i].pixel * source->columns) + x);
weight = contribution[i].weight;
p = source->buf[j];
/*
DEBUG_TRACE(" merging with pix (%d, %d) @ %d (%d %d %d) weight %.2f\n",
x, contribution[i].pixel, j,
COL_RED(p), COL_GREEN(p), COL_BLUE(p),
fixed_to_float(weight));
*/
red += fixed_mul(weight, int_to_fixed(COL_RED(p)));
green += fixed_mul(weight, int_to_fixed(COL_GREEN(p)));
blue += fixed_mul(weight, int_to_fixed(COL_BLUE(p)));
}
alpha = FIXED_255;
}
/*
DEBUG_TRACE(" -> (%d, %d) @ %d (%d %d %d %d)\n",
x, y, (y * destination->columns) + x,
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha));
*/
if (rotate && im->orientation != ORIENTATION_NORMAL) {
int ox, oy; // new destination pixel coordinates after rotating
image_get_rotated_coords(im, x, y, &ox, &oy);
if (im->orientation >= 5) {
// 90 and 270 rotations, width/height are swapped
destination->buf[(oy * destination->rows) + ox] = COL_FULL(
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha)
);
}
else {
destination->buf[(oy * destination->columns) + ox] = COL_FULL(
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha)
);
}
}
else {
destination->buf[(y * destination->columns) + x] = COL_FULL(
ROUND_FIXED_TO_INT(red),
ROUND_FIXED_TO_INT(green),
ROUND_FIXED_TO_INT(blue),
ROUND_FIXED_TO_INT(alpha)
);
}
}
}
}
void
image_downsize_gm_fixed_point(image *im)
{
// This intentionally still uses floating-point because these variables are only calculated once
float x_factor, y_factor;
float support, x_support, y_support;
int columns, rows;
int order;
int filter;
ContributionInfoFixed *contribution;
ImageInfo source, destination;
static const FilterInfoFixed
filters[SincFilter+1] =
{
{ BoxFixed, 0 },
{ BoxFixed, 0 },
{ BoxFixed, FIXED_HALF },
{ TriangleFixed, FIXED_1 },
// Triangle seems good enough
};
columns = im->target_width;
rows = im->target_height;
filter = TriangleFilter; // Force Triangle for fixed-point mode, best quality and performance
DEBUG_TRACE("Resizing with filter %d\n", filter);
// Determine which dimension to resize first
order = (((float)columns * (im->height + rows)) >
((float)rows * (im->width + columns)));
if (im->width_padding)
x_factor = (float)im->width_inner / im->width;
else
x_factor = (float)im->target_width / im->width;
if (im->height_padding)
y_factor = (float)im->height_inner / im->height;
else
y_factor = (float)im->target_height / im->height;
x_support = BLUR * MAX(1.0 / x_factor, 1.0) * fixed_to_int(filters[filter].support);
y_support = BLUR * MAX(1.0 / y_factor, 1.0) * fixed_to_int(filters[filter].support);
support = MAX(x_support, y_support);
if (support < fixed_to_int(filters[filter].support))
support = fixed_to_int(filters[filter].support);
DEBUG_TRACE("ContributionInfoFixed allocated for %ld items\n", (size_t)(2.0 * MAX(support, 0.5) + 3));
New(0, contribution, (size_t)(2.0 * MAX(support, 0.5) + 3), ContributionInfoFixed);
DEBUG_TRACE("order %d, x_factor %f, y_factor %f, support %f\n", order, x_factor, y_factor, support);
source.rows = im->height;
source.columns = im->width;
source.buf = im->pixbuf;
if (order) {
DEBUG_TRACE("Allocating temporary buffer size %ld\n", im->target_width * im->height * sizeof(pix));
New(0, im->tmpbuf, im->target_width * im->height, pix);
// Fill new space with the bgcolor or zeros
image_bgcolor_fill(im->tmpbuf, im->target_width * im->height, im->bgcolor);
// Resize horizontally from source -> tmp
destination.rows = im->height;
destination.columns = im->target_width;
destination.buf = im->tmpbuf;
image_downsize_gm_horizontal_filter_fixed_point(im, &source, &destination, float_to_fixed(x_factor), &filters[filter], contribution, 0);
// Resize vertically from tmp -> out
source.rows = destination.rows;
source.columns = destination.columns;
source.buf = destination.buf;
destination.rows = im->target_height;
destination.buf = im->outbuf;
image_downsize_gm_vertical_filter_fixed_point(im, &source, &destination, float_to_fixed(y_factor), &filters[filter], contribution, 1);
}
else {
DEBUG_TRACE("Allocating temporary buffer size %ld\n", im->width * im->target_height * sizeof(pix));
New(0, im->tmpbuf, im->width * im->target_height, pix);
// Fill new space with the bgcolor or zeros
image_bgcolor_fill(im->tmpbuf, im->width * im->target_height, im->bgcolor);
// Resize vertically from source -> tmp
destination.rows = im->target_height;
destination.columns = im->width;
destination.buf = im->tmpbuf;
image_downsize_gm_vertical_filter_fixed_point(im, &source, &destination, float_to_fixed(y_factor), &filters[filter], contribution, 0);
// Resize horizontally from tmp -> out
source.rows = destination.rows;
source.columns = destination.columns;
source.buf = destination.buf;
destination.columns = im->target_width;
destination.buf = im->outbuf;
image_downsize_gm_horizontal_filter_fixed_point(im, &source, &destination, float_to_fixed(x_factor), &filters[filter], contribution, 1);
}
Safefree(im->tmpbuf);
Safefree(contribution);
}