On Mon, 2008-04-21 at 20:36 +0100, Øyvind Kolås wrote: > You need to do the following: > [...] Thanks for all the help! I wrote an operation to do color reduction to a specified number of bits per channel, employing one out of a couple of potential color compensation strategies. I'm attaching it in case you want to use it for something, or re-use parts of the code in a better operation. I put a test image up at http://hpjansson.org/temp/meadow-dithered.png The picture has one bit per channel for a total of 8 colors, making it a true retro experience. From left to right -- original, thresholding, Bayer, F-S, covariant random and random dithering. -- Hans Petter Jansson <hpj@xxxxxxxxxxx>
/* This file is an image processing operation for GEGL * * GEGL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * GEGL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with GEGL; if not, see <http://www.gnu.org/licenses/>. * * Copyright 2008 Hans Petter Jansson <hpj@xxxxxxxxxxx> */ #ifdef GEGL_CHANT_PROPERTIES gegl_chant_int (red_bits, "Red bits", 1, 16, 16, "Number of bits for red channel") gegl_chant_int (green_bits, "Green bits", 1, 16, 16, "Number of bits for green channel") gegl_chant_int (blue_bits, "Blue bits", 1, 16, 16, "Number of bits for blue channel") gegl_chant_int (alpha_bits, "Alpha bits", 1, 16, 16, "Number of bits for alpha channel") gegl_chant_string (dither_type, "Dither", "none", "Dithering strategy (none, random, random-covariant, bayer, floyd-steinberg)") #else #define GEGL_CHANT_TYPE_FILTER #define GEGL_CHANT_C_FILE "color-reduction.c" #include "gegl-chant.h" static void prepare (GeglOperation *operation) { gegl_operation_set_format (operation, "input", babl_format ("RGBA u16")); gegl_operation_set_format (operation, "output", babl_format ("RGBA u16")); } static void generate_channel_masks (guint *channel_bits, guint *channel_mask) { gint i; for (i = 0; i < 4; i++) channel_mask [i] = ~((1 << (16 - channel_bits [i])) - 1); } static guint quantize_value (guint value, guint n_bits, guint mask) { gint i; value &= mask; for (i = n_bits; i < 16; i += n_bits) value |= value >> i; return value; } static void process_floyd_steinberg (GeglBuffer *input, GeglBuffer *output, guint *channel_bits) { const GeglRectangle *input_rect; GeglRectangle line_rect; guint16 *line_buf; gdouble *error_buf [2]; guint channel_mask [4]; gint y; input_rect = gegl_buffer_get_extent (input); line_rect.x = input_rect->x; line_rect.y = input_rect->y; line_rect.width = input_rect->width; line_rect.height = 1; line_buf = g_new (guint16, line_rect.width * 4); error_buf [0] = g_new0 (gdouble, line_rect.width * 4); error_buf [1] = g_new0 (gdouble, line_rect.width * 4); generate_channel_masks (channel_bits, channel_mask); for (y = 0; y < input_rect->height; y++) { gdouble *error_buf_swap; gint step; gint start_x; gint end_x; gint x; /* Serpentine scanning; reverse direction every row */ if (y & 1) { start_x = input_rect->width - 1; end_x = -1; step = -1; } else { start_x = 0; end_x = input_rect->width; step = 1; } /* Pull input row */ gegl_buffer_get (input, 1.0, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); /* Process the row */ for (x = start_x; x != end_x; x += step) { guint16 *pixel = &line_buf [x * 4]; guint ch; for (ch = 0; ch < 4; ch++) { gdouble value; gdouble value_clamped; gdouble quantized; gdouble qerror; value = pixel [ch] + error_buf [0] [x * 4 + ch]; value_clamped = CLAMP (value, 0.0, 65535.0); quantized = quantize_value ((guint) (value_clamped + 0.5), channel_bits [ch], channel_mask [ch]); qerror = value - quantized; pixel [ch] = (guint16) quantized; /* Distribute the error */ error_buf [1] [x * 4 + ch] += qerror * 5.0 / 16.0; /* Down */ if (x + step >= 0 && x + step < input_rect->width) { error_buf [0] [(x + step) * 4 + ch] += qerror * 6.0 / 16.0; /* Ahead */ error_buf [1] [(x + step) * 4 + ch] += qerror * 1.0 / 16.0; /* Down, ahead */ } if (x - step >= 0 && x - step < input_rect->width) { error_buf [1] [(x - step) * 4 + ch] += qerror * 3.0 / 16.0; /* Down, behind */ } } } /* Swap error accumulation rows */ error_buf_swap = error_buf [0]; error_buf [0] = error_buf [1]; error_buf [1] = error_buf_swap; /* Clear error buffer for next-plus-one line */ memset (error_buf [1], 0, line_rect.width * 4 * sizeof (gdouble)); /* Push output row */ gegl_buffer_set (output, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); line_rect.y++; } g_free (line_buf); g_free (error_buf [0]); g_free (error_buf [1]); } static const gdouble bayer_matrix_8x8 [] = { 1, 49, 13, 61, 4, 52, 16, 64, 33, 17, 45, 29, 36, 20, 48, 32, 9, 57, 5, 53, 12, 60, 8, 56, 41, 25, 37, 21, 44, 28, 40, 24, 3, 51, 15, 63, 2, 50, 14, 62, 35, 19, 47, 31, 34, 18, 46, 30, 11, 59, 7, 55, 10, 58, 6, 54, 43, 27, 39, 23, 42, 26, 38, 22 }; static void process_bayer (GeglBuffer *input, GeglBuffer *output, guint *channel_bits) { const GeglRectangle *input_rect; GeglRectangle line_rect; guint16 *line_buf; guint channel_mask [4]; guint y; input_rect = gegl_buffer_get_extent (input); line_rect.x = input_rect->x; line_rect.y = input_rect->y; line_rect.width = input_rect->width; line_rect.height = 1; line_buf = g_new (guint16, line_rect.width * 4); generate_channel_masks (channel_bits, channel_mask); for (y = 0; y < input_rect->height; y++) { guint x; gegl_buffer_get (input, 1.0, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); for (x = 0; x < input_rect->width; x++) { guint16 *pixel = &line_buf [x * 4]; guint ch; for (ch = 0; ch < 4; ch++) { gdouble value; gdouble value_clamped; gdouble quantized; value = pixel [ch] + ((bayer_matrix_8x8 [(y % 8) * 8 + (x % 8)] - 32) * 65536.0 / 65.0) / (1 << (channel_bits [ch] - 1)); value_clamped = CLAMP (value, 0.0, 65535.0); quantized = quantize_value ((guint) (value_clamped + 0.5), channel_bits [ch], channel_mask [ch]); pixel [ch] = (guint16) quantized; } } gegl_buffer_set (output, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); line_rect.y++; } g_free (line_buf); } static void process_random_covariant (GeglBuffer *input, GeglBuffer *output, guint *channel_bits) { const GeglRectangle *input_rect; GeglRectangle line_rect; guint16 *line_buf; guint channel_mask [4]; guint y; input_rect = gegl_buffer_get_extent (input); line_rect.x = input_rect->x; line_rect.y = input_rect->y; line_rect.width = input_rect->width; line_rect.height = 1; line_buf = g_new (guint16, line_rect.width * 4); generate_channel_masks (channel_bits, channel_mask); for (y = 0; y < input_rect->height; y++) { guint x; gegl_buffer_get (input, 1.0, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); for (x = 0; x < input_rect->width; x++) { guint16 *pixel = &line_buf [x * 4]; guint ch; gint r = g_random_int_range (-65536, 65536); for (ch = 0; ch < 4; ch++) { gdouble value; gdouble value_clamped; gdouble quantized; value = pixel [ch] + (r / (1 << channel_bits [ch])); value_clamped = CLAMP (value, 0.0, 65535.0); quantized = quantize_value ((guint) (value_clamped + 0.5), channel_bits [ch], channel_mask [ch]); pixel [ch] = (guint16) quantized; } } gegl_buffer_set (output, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); line_rect.y++; } g_free (line_buf); } static void process_random (GeglBuffer *input, GeglBuffer *output, guint *channel_bits) { const GeglRectangle *input_rect; GeglRectangle line_rect; guint16 *line_buf; guint channel_mask [4]; guint y; input_rect = gegl_buffer_get_extent (input); line_rect.x = input_rect->x; line_rect.y = input_rect->y; line_rect.width = input_rect->width; line_rect.height = 1; line_buf = g_new (guint16, line_rect.width * 4); generate_channel_masks (channel_bits, channel_mask); for (y = 0; y < input_rect->height; y++) { guint x; gegl_buffer_get (input, 1.0, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); for (x = 0; x < input_rect->width; x++) { guint16 *pixel = &line_buf [x * 4]; guint ch; for (ch = 0; ch < 4; ch++) { gdouble value; gdouble value_clamped; gdouble quantized; value = pixel [ch] + (g_random_int_range (-65536, 65536) / (1 << channel_bits [ch])); value_clamped = CLAMP (value, 0.0, 65535.0); quantized = quantize_value ((guint) (value_clamped + 0.5), channel_bits [ch], channel_mask [ch]); pixel [ch] = (guint16) quantized; } } gegl_buffer_set (output, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); line_rect.y++; } g_free (line_buf); } static void process_no_dither (GeglBuffer *input, GeglBuffer *output, guint *channel_bits) { const GeglRectangle *input_rect; GeglRectangle line_rect; guint16 *line_buf; guint channel_mask [4]; guint y; input_rect = gegl_buffer_get_extent (input); line_rect.x = input_rect->x; line_rect.y = input_rect->y; line_rect.width = input_rect->width; line_rect.height = 1; line_buf = g_new (guint16, line_rect.width * 4); generate_channel_masks (channel_bits, channel_mask); for (y = 0; y < input_rect->height; y++) { guint x; gegl_buffer_get (input, 1.0, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); for (x = 0; x < input_rect->width; x++) { guint16 *pixel = &line_buf [x * 4]; guint ch; for (ch = 0; ch < 4; ch++) { pixel [ch] = quantize_value (pixel [ch], channel_bits [ch], channel_mask [ch]); } } gegl_buffer_set (output, &line_rect, babl_format ("RGBA u16"), line_buf, GEGL_AUTO_ROWSTRIDE); line_rect.y++; } g_free (line_buf); } static GeglRectangle get_required_for_output (GeglOperation *self, const gchar *input_pad, const GeglRectangle *roi) { return *gegl_operation_source_get_bounding_box (self, "input"); } static GeglRectangle get_cached_region (GeglOperation *self, const GeglRectangle *roi) { return *gegl_operation_source_get_bounding_box (self, "input"); } static gboolean process (GeglOperation *operation, GeglBuffer *input, GeglBuffer *output, const GeglRectangle *result) { GeglChantO *o = GEGL_CHANT_PROPERTIES (operation); guint channel_bits [4]; channel_bits [0] = o->red_bits; channel_bits [1] = o->green_bits; channel_bits [2] = o->blue_bits; channel_bits [3] = o->alpha_bits; if (!o->dither_type) process_no_dither (input, output, channel_bits); else if (!strcasecmp (o->dither_type, "random")) process_random (input, output, channel_bits); else if (!strcasecmp (o->dither_type, "random-covariant")) process_random_covariant (input, output, channel_bits); else if (!strcasecmp (o->dither_type, "bayer")) process_bayer (input, output, channel_bits); else if (!strcasecmp (o->dither_type, "floyd-steinberg")) process_floyd_steinberg (input, output, channel_bits); else process_no_dither (input, output, channel_bits); return TRUE; } static void gegl_chant_class_init (GeglChantClass *klass) { GeglOperationClass *operation_class; GeglOperationFilterClass *filter_class; operation_class = GEGL_OPERATION_CLASS (klass); filter_class = GEGL_OPERATION_FILTER_CLASS (klass); operation_class->prepare = prepare; operation_class->get_required_for_output = get_required_for_output; operation_class->get_cached_region = get_cached_region; filter_class->process = process; operation_class->name = "color-reduction"; operation_class->categories = "misc"; operation_class->description = "Reduces the number of bits per channel (colors and alpha), with optional dithering."; } #endif
_______________________________________________ Gegl-developer mailing list Gegl-developer@xxxxxxxxxxxxxxxxxxxxxx https://lists.XCF.Berkeley.EDU/mailman/listinfo/gegl-developer