Skip to content

Commit

Permalink
Fix antialiasing for downsampling
Browse files Browse the repository at this point in the history
Use the average color filter to smooth the image in downsampling mode.

Resolves #183.

Signed-off-by: Artem Senichev <[email protected]>
  • Loading branch information
artemsen committed Sep 15, 2024
1 parent fecd704 commit d170c3c
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 7 deletions.
10 changes: 8 additions & 2 deletions src/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ void image_thumbnail(struct image* image, size_t size, bool fill,
size_t thumb_width = scale * full->width;
size_t thumb_height = scale * full->height;
ssize_t offset_x, offset_y;
const enum pixmap_scale sf = antialias ? pixmap_bicubic : pixmap_nearest;
enum pixmap_scale scaler;

if (antialias) {
scaler = (scale > 1.0) ? pixmap_bicubic : pixmap_average;
} else {
scaler = pixmap_nearest;
}

if (fill) {
offset_x = size / 2 - thumb_width / 2;
Expand All @@ -80,7 +86,7 @@ void image_thumbnail(struct image* image, size_t size, bool fill,
if (!pixmap_create(&thumb, thumb_width, thumb_height)) {
return;
}
pixmap_scale(sf, full, &thumb, offset_x, offset_y, scale, image->alpha);
pixmap_scale(scaler, full, &thumb, offset_x, offset_y, scale, image->alpha);

image_free_frames(image);
frame = image_create_frames(image, 1);
Expand Down
76 changes: 73 additions & 3 deletions src/pixmap.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,63 @@ static void scale_nearest(struct scale_param* sp, size_t start, size_t step)
}
}

/** Average scale filter, see `scale_fn` for details. */
static void scale_average(struct scale_param* sp, size_t start, size_t step)
{
const ssize_t left = max(0, sp->x);
const ssize_t top = max(0, sp->y);
const ssize_t right =
min((ssize_t)sp->dst->width, sp->x + sp->scale * sp->src->width);
const ssize_t bottom =
min((ssize_t)sp->dst->height, sp->y + sp->scale * sp->src->height);

const ssize_t iscale = (1.0 / sp->scale) / 2;
const ssize_t delta_x = left - sp->x;
const ssize_t delta_y = top - sp->y;

for (ssize_t dst_y = top + start; dst_y < bottom; dst_y += step) {
ssize_t src_y = (double)(dst_y - top + delta_y) / sp->scale;
const ssize_t src_y0 = max(0, src_y - iscale);
const ssize_t src_y1 = min((ssize_t)sp->src->height, src_y + iscale);
argb_t* dst_line = &sp->dst->data[dst_y * sp->dst->width];

for (ssize_t dst_x = left; dst_x < right; ++dst_x) {
ssize_t src_x = (double)(dst_x - left + delta_x) / sp->scale;
const ssize_t src_x0 = max(0, src_x - iscale);
const ssize_t src_x1 = min((ssize_t)sp->src->width, src_x + iscale);

size_t a = 0, r = 0, g = 0, b = 0;
size_t count = 0;

for (src_y = src_y0; src_y <= src_y1; ++src_y) {
for (src_x = src_x0; src_x <= src_x1; ++src_x) {
const argb_t color =
sp->src->data[src_y * sp->src->width + src_x];
a += ARGB_GET_A(color);
r += ARGB_GET_R(color);
g += ARGB_GET_G(color);
b += ARGB_GET_B(color);
++count;
}
}
if (count) {
a /= count;
r /= count;
g /= count;
b /= count;
}

const argb_t color = ARGB(a, r, g, b);

if (sp->alpha) {
alpha_blend(color, &dst_line[dst_x]);
} else {
dst_line[dst_x] = ARGB_SET_A(0xff) | color;
}
}
}
}

/** Bicubic scale filter, see `scale_fn` for details. */
static void scale_bicubic(struct scale_param* sp, size_t start, size_t step)
{
Expand Down Expand Up @@ -449,9 +506,7 @@ void pixmap_scale(enum pixmap_scale scaler, const struct pixmap* src,
const size_t threads_num = min(16, max(cpus, 1)) - 1;
struct scale_task* tasks = NULL;

const scale_fn scaler_fn =
scaler == pixmap_nearest ? scale_nearest : scale_bicubic;

// scaling parameters
struct scale_param sp = {
.src = src,
.dst = dst,
Expand All @@ -460,6 +515,21 @@ void pixmap_scale(enum pixmap_scale scaler, const struct pixmap* src,
.scale = scale,
.alpha = alpha,
};
scale_fn scaler_fn;

switch (scaler) {
case pixmap_nearest:
scaler_fn = scale_nearest;
break;
case pixmap_bicubic:
scaler_fn = scale_bicubic;
break;
case pixmap_average:
scaler_fn = scale_average;
break;
default:
return;
}

// create task for each CPU core
if (threads_num) {
Expand Down
1 change: 1 addition & 0 deletions src/pixmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ struct pixmap {
enum pixmap_scale {
pixmap_nearest, ///< Nearest filter, poor quality but fast
pixmap_bicubic, ///< Bicubic filter, good quality but slow
pixmap_average, ///< Average color, downsampling image
};

/**
Expand Down
10 changes: 8 additions & 2 deletions src/viewer.c
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,14 @@ static void draw_image(struct pixmap* wnd)
if (ctx.scale == 1.0) {
pixmap_copy(img_pm, wnd, ctx.img_x, ctx.img_y, img->alpha);
} else {
pixmap_scale(ctx.antialiasing ? pixmap_bicubic : pixmap_nearest, img_pm,
wnd, ctx.img_x, ctx.img_y, ctx.scale, img->alpha);
enum pixmap_scale scaler;
if (ctx.antialiasing) {
scaler = (ctx.scale > 1.0) ? pixmap_bicubic : pixmap_average;
} else {
scaler = pixmap_nearest;
}
pixmap_scale(scaler, img_pm, wnd, ctx.img_x, ctx.img_y, ctx.scale,
img->alpha);
}
}

Expand Down

0 comments on commit d170c3c

Please sign in to comment.