From 8063ea0952a495cce33bb14a8198cb738a953a01 Mon Sep 17 00:00:00 2001 From: Jorge Rodriguez Date: Thu, 31 Jul 2014 17:20:00 -0700 Subject: [PATCH] Specify a channel as having premultiplied alpha and use it to un-premultiply all other channels before resampling. --- stb_resample.h | 66 +++++++++++++++++++++++++++++++++++++++++-------- tests/resample_test.cpp | 45 +++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 15 deletions(-) diff --git a/stb_resample.h b/stb_resample.h index 73dd28d..b76988c 100644 --- a/stb_resample.h +++ b/stb_resample.h @@ -51,12 +51,13 @@ // result = stbr_resize_arbitrary(input_data, input_w, input_h, input_stride_in_bytes, // output_data, output_w, output_h, output_stride_in_bytes, // s0, t0, s1, t1, -// channels, STBR_TYPE_UINT8, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, STBR_COLORSPACE_SRGB, +// channels, premultiplied_alpha_channel, STBR_TYPE_UINT8, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, STBR_COLORSPACE_SRGB, // extra_memory, memory_required); // free(extra_memory); // // input_stride_in_bytes and output_stride_in_bytes can be 0. If so they will be automatically calculated as width * channels. // s0, t0, s1, t1 are the top-left and bottom right corner (uv addressing style: [0, 1]x[0, 1]) of a region of the input image to use. +// premultiplied_alpha_channel is 0 if there are no premultiplied alpha channels. If nonzero, the specified channel will be divided from all other channels before resampling, then multiplied back in after. // Returned result is 1 for success or 0 in case of an error. In the case of an error an assert with be triggered, #define STBR_ASSERT() to see it. // stbr_resize_arbitrary() will not allocate any memory, it will use the memory you pass in to do its work. @@ -155,7 +156,7 @@ extern "C" { STBRDEF int stbr_resize_arbitrary(const void* input_data, int input_w, int input_h, int input_stride_in_bytes, void* output_data, int output_w, int output_h, int output_stride_in_bytes, float s0, float t0, float s1, float t1, - int channels, stbr_type type, stbr_filter filter, stbr_edge edge, stbr_colorspace colorspace, + int channels, int premultiplied_alpha_channel, stbr_type type, stbr_filter filter, stbr_edge edge, stbr_colorspace colorspace, void* tempmem, stbr_size_t tempmem_size_in_bytes); @@ -256,6 +257,7 @@ typedef struct float vertical_scale; int channels; + int premul_alpha_channel; stbr_type type; stbr_filter filter; stbr_edge edge; @@ -708,6 +710,7 @@ static void stbr__decode_scanline(stbr__info* stbr_info, int n) { int x, c; int channels = stbr_info->channels; + int premul_alpha_channel = stbr_info->premul_alpha_channel; int type = stbr_info->type; int colorspace = stbr_info->colorspace; int input_w = stbr_info->input_w; @@ -770,6 +773,17 @@ static void stbr__decode_scanline(stbr__info* stbr_info, int n) STBR_UNIMPLEMENTED("Unknown type/colorspace/channels combination."); break; } + + if (premul_alpha_channel) + { + for (c = 0; c < channels; c++) + { + if (c == premul_alpha_channel) + continue; + + decode_buffer[decode_texel_index + c] /= decode_buffer[decode_texel_index + premul_alpha_channel]; + } + } } } @@ -920,9 +934,21 @@ static float* stbr__get_ring_buffer_scanline(int get_scanline, float* ring_buffe } -static stbr_inline void stbr__encode_scanline(void* output_buffer, int output_texel_index, float* encode_buffer, int encode_texel_index, int channels, int decode) +static stbr_inline void stbr__encode_scanline(void* output_buffer, int output_texel_index, float* encode_buffer, int encode_texel_index, int channels, int premul_alpha_channel, int decode) { int n; + + if (premul_alpha_channel) + { + for (n = 0; n < channels; n++) + { + if (n == premul_alpha_channel) + continue; + + encode_buffer[encode_texel_index + n] *= encode_buffer[encode_texel_index + premul_alpha_channel]; + } + } + switch (decode) { case STBR__DECODE(STBR_TYPE_UINT8, STBR_COLORSPACE_LINEAR): @@ -969,6 +995,18 @@ static stbr_inline void stbr__encode_scanline(void* output_buffer, int output_te STBR_UNIMPLEMENTED("Unknown type/colorspace/channels combination."); break; } + + // Put it back the way it was in case this is a ring buffer. + if (premul_alpha_channel) + { + for (n = 0; n < channels; n++) + { + if (n == premul_alpha_channel) + continue; + + encode_buffer[encode_texel_index + n] /= encode_buffer[encode_texel_index + premul_alpha_channel]; + } + } } static void stbr__resample_vertical_upsample(stbr__info* stbr_info, int n, int in_first_scanline, int in_last_scanline, float in_center_of_out) @@ -978,6 +1016,7 @@ static void stbr__resample_vertical_upsample(stbr__info* stbr_info, int n, int i stbr__contributors* vertical_contributors = &stbr_info->vertical_contributors; float* vertical_coefficients = stbr_info->vertical_coefficients; int channels = stbr_info->channels; + int premul_alpha_channel = stbr_info->premul_alpha_channel; int type = stbr_info->type; int colorspace = stbr_info->colorspace; int kernel_texel_width = stbr__get_filter_texel_width_vertical(stbr_info); @@ -1023,7 +1062,7 @@ static void stbr__resample_vertical_upsample(stbr__info* stbr_info, int n, int i encode_buffer[c] += ring_buffer_entry[in_texel_index + c] * coefficient; } - stbr__encode_scanline(output_data, out_texel_index, encode_buffer, 0, channels, decode); + stbr__encode_scanline(output_data, out_texel_index, encode_buffer, 0, channels, premul_alpha_channel, decode); } } @@ -1130,6 +1169,7 @@ static void stbr__empty_ring_buffer(stbr__info* stbr_info, int first_necessary_s { int output_stride = stbr_info->output_stride_bytes / stbr__type_size[stbr_info->type]; int channels = stbr_info->channels; + int premul_alpha_channel = stbr_info->premul_alpha_channel; int type = stbr_info->type; int colorspace = stbr_info->colorspace; int output_w = stbr_info->output_w; @@ -1156,7 +1196,7 @@ static void stbr__empty_ring_buffer(stbr__info* stbr_info, int first_necessary_s int ring_texel_index = texel_index; int output_texel_index = output_row + texel_index; - stbr__encode_scanline(output_data, output_texel_index, ring_buffer_entry, ring_texel_index, channels, decode); + stbr__encode_scanline(output_data, output_texel_index, ring_buffer_entry, ring_texel_index, channels, premul_alpha_channel, decode); } } @@ -1221,7 +1261,7 @@ static void stbr__buffer_loop_downsample(stbr__info* stbr_info) STBRDEF int stbr_resize_arbitrary(const void* input_data, int input_w, int input_h, int input_stride_in_bytes, void* output_data, int output_w, int output_h, int output_stride_in_bytes, float s0, float t0, float s1, float t1, - int channels, stbr_type type, stbr_filter filter, stbr_edge edge, stbr_colorspace colorspace, + int channels, int premul_alpha_channel, stbr_type type, stbr_filter filter, stbr_edge edge, stbr_colorspace colorspace, void* tempmem, stbr_size_t tempmem_size_in_bytes) { stbr_size_t memory_required = stbr_calculate_memory(input_w, input_h, output_w, output_h, s0, t0, s1, t1, channels, filter); @@ -1260,6 +1300,11 @@ STBRDEF int stbr_resize_arbitrary(const void* input_data, int input_w, int input if (s1 > 1 || s0 < 0 || t1 > 1 || t0 < 0) return 0; + STBR_ASSERT(premul_alpha_channel >= 0 && premul_alpha_channel < channels); + + if (premul_alpha_channel < 0 || premul_alpha_channel >= channels) + return 0; + STBR_ASSERT(tempmem); if (!tempmem) @@ -1296,6 +1341,7 @@ STBRDEF int stbr_resize_arbitrary(const void* input_data, int input_w, int input stbr_info->vertical_shift = t0 * input_h / (t1 - t0); stbr_info->channels = channels; + stbr_info->premul_alpha_channel = premul_alpha_channel; stbr_info->type = type; stbr_info->filter = filter; stbr_info->edge = edge; @@ -1396,7 +1442,7 @@ STBRDEF int stbr_resize_srgb_uint8(const stbr_uint8* input_data, int input_w, in if (!extra_memory) return 0; - int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, STBR_TYPE_UINT8, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); + int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, 0, STBR_TYPE_UINT8, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); free(extra_memory); @@ -1413,7 +1459,7 @@ STBRDEF int stbr_resize_srgb_uint16(const stbr_uint16* input_data, int input_w, if (!extra_memory) return 0; - int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, STBR_TYPE_UINT16, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); + int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, 0, STBR_TYPE_UINT16, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); free(extra_memory); @@ -1430,7 +1476,7 @@ STBRDEF int stbr_resize_srgb_uint32(const stbr_uint32* input_data, int input_w, if (!extra_memory) return 0; - int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, STBR_TYPE_UINT32, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); + int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, 0, STBR_TYPE_UINT32, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); free(extra_memory); @@ -1447,7 +1493,7 @@ STBRDEF int stbr_resize_srgb_float(const float* input_data, int input_w, int inp if (!extra_memory) return 0; - int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, STBR_TYPE_FLOAT, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); + int result = stbr_resize_arbitrary(input_data, input_w, input_h, 0, output_data, output_w, output_h, 0, 0, 0, 1, 1, channels, 0, STBR_TYPE_FLOAT, filter, edge, STBR_COLORSPACE_SRGB, extra_memory, memory_required); free(extra_memory); diff --git a/tests/resample_test.cpp b/tests/resample_test.cpp index 3857d54..d4be392 100644 --- a/tests/resample_test.cpp +++ b/tests/resample_test.cpp @@ -91,7 +91,7 @@ int main(int argc, char** argv) printf("Average: %dms\n", average); #else - stbr_resize_arbitrary(input_data + w * border * n + border * n, in_w, in_h, w*n, output_data, out_w, out_h, out_stride, s0, t0, s1, t1, n, STBR_TYPE_UINT8, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, STBR_COLORSPACE_SRGB, extra_memory, memory_required); + stbr_resize_arbitrary(input_data + w * border * n + border * n, in_w, in_h, w*n, output_data, out_w, out_h, out_stride, s0, t0, s1, t1, n, 0, STBR_TYPE_UINT8, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, STBR_COLORSPACE_SRGB, extra_memory, memory_required); #endif free(extra_memory); @@ -123,7 +123,7 @@ void resize_image(const char* filename, float width_percent, float height_percen size_t memory_required = stbr_calculate_memory(w, h, out_w, out_h, 0, 0, 1, 1, n, filter); void* extra_memory = malloc(memory_required); - stbr_resize_arbitrary(input_data, w, h, 0, output_data, out_w, out_h, 0, 0, 0, 1, 1, n, STBR_TYPE_UINT8, filter, edge, colorspace, extra_memory, memory_required); + stbr_resize_arbitrary(input_data, w, h, 0, output_data, out_w, out_h, 0, 0, 0, 1, 1, n, 0, STBR_TYPE_UINT8, filter, edge, colorspace, extra_memory, memory_required); free(extra_memory); stbi_image_free(input_data); @@ -157,7 +157,7 @@ void test_format(const char* file, float width_percent, float height_percent, st size_t required = stbr_calculate_memory(w, h, new_w, new_h, 0, 0, 1, 1, n, STBR_FILTER_CATMULLROM); void* extra_memory = malloc(required); - stbr_resize_arbitrary(T_data, w, h, 0, output_data, new_w, new_h, 0, 0, 0, 1, 1, n, type, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, colorspace, extra_memory, required); + stbr_resize_arbitrary(T_data, w, h, 0, output_data, new_w, new_h, 0, 0, 0, 1, 1, n, 0, type, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, colorspace, extra_memory, required); free(extra_memory); free(T_data); @@ -201,7 +201,7 @@ void test_float(const char* file, float width_percent, float height_percent, stb size_t required = stbr_calculate_memory(w, h, new_w, new_h, 0, 0, 1, 1, n, STBR_FILTER_CATMULLROM); void* extra_memory = malloc(required); - stbr_resize_arbitrary(T_data, w, h, 0, output_data, new_w, new_h, 0, 0, 0, 1, 1, n, type, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, colorspace, extra_memory, required); + stbr_resize_arbitrary(T_data, w, h, 0, output_data, new_w, new_h, 0, 0, 0, 1, 1, n, 0, type, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, colorspace, extra_memory, required); free(extra_memory); free(T_data); @@ -267,7 +267,7 @@ void test_subpixel(const char* file, float width_percent, float height_percent, size_t tempmem_size = stbr_calculate_memory(w, h, new_w, new_h, 0, 0, s1, t1, n, STBR_FILTER_CATMULLROM); void* tempmem = malloc(tempmem_size); - stbr_resize_arbitrary(input_data, w, h, 0, output_data, new_w, new_h, 0, 0, 0, s1, t1, n, STBR_TYPE_UINT8, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, STBR_COLORSPACE_SRGB, tempmem, tempmem_size); + stbr_resize_arbitrary(input_data, w, h, 0, output_data, new_w, new_h, 0, 0, 0, s1, t1, n, 0, STBR_TYPE_UINT8, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, STBR_COLORSPACE_SRGB, tempmem, tempmem_size); free(tempmem); @@ -280,8 +280,43 @@ void test_subpixel(const char* file, float width_percent, float height_percent, free(output_data); } +void test_premul(const char* file) +{ + int w, h, n; + unsigned char* input_data = stbi_load(file, &w, &h, &n, 4); + n = 4; + + // Premultiply the first texel. + input_data[0] /= 2; + input_data[1] /= 2; + input_data[2] /= 2; + input_data[3] = 255 / 2; + + int new_w = (int)(w * .5); + int new_h = (int)(h * .5); + + unsigned char* output_data = (unsigned char*)malloc(new_w * new_h * n * sizeof(unsigned char)); + + size_t tempmem_size = stbr_calculate_memory(w, h, new_w, new_h, 0, 0, 1, 1, n, STBR_FILTER_CATMULLROM); + void* tempmem = malloc(tempmem_size); + + stbr_resize_arbitrary(input_data, w, h, 0, output_data, new_w, new_h, 0, 0, 0, 1, 1, n, 3, STBR_TYPE_UINT8, STBR_FILTER_CATMULLROM, STBR_EDGE_CLAMP, STBR_COLORSPACE_SRGB, tempmem, tempmem_size); + + free(tempmem); + + stbi_image_free(input_data); + + char output[200]; + sprintf(output, "test-output/premul-%s", file); + stbi_write_png(output, new_w, new_h, n, output_data, 0); + + free(output_data); +} + void test_suite() { + test_premul("barbara.png"); + for (int i = 0; i < 10; i++) test_subpixel("barbara.png", 0.5f, 0.5f, (float)i / 10, 1);