dither.c revision f766d2f6
1/*
2 * Copyright 2012, Red Hat, Inc.
3 * Copyright 2012, Soren Sandmann
4 * Copyright 2018, Basile Clement
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 * DEALINGS IN THE SOFTWARE.
24 */
25#ifdef HAVE_CONFIG_H
26#include "pixman-config.h"
27#endif
28#include <math.h>
29#include <gtk/gtk.h>
30#include <stdlib.h>
31#include "utils.h"
32#include "gtk-utils.h"
33
34#define WIDTH 1024
35#define HEIGHT 640
36
37typedef struct
38{
39    GtkBuilder *         builder;
40    pixman_image_t *	 original;
41    pixman_format_code_t format;
42    pixman_dither_t      dither;
43    int                  width;
44    int                  height;
45} app_t;
46
47static GtkWidget *
48get_widget (app_t *app, const char *name)
49{
50    GtkWidget *widget = GTK_WIDGET (gtk_builder_get_object (app->builder, name));
51
52    if (!widget)
53	g_error ("Widget %s not found\n", name);
54
55    return widget;
56}
57
58typedef struct
59{
60    char	name [20];
61    int		value;
62} named_int_t;
63
64static const named_int_t formats[] =
65{
66    { "a8r8g8b8",  PIXMAN_a8r8g8b8      },
67    { "rgb",       PIXMAN_rgb_float     },
68    { "sRGB",      PIXMAN_a8r8g8b8_sRGB },
69    { "r5g6b5",    PIXMAN_r5g6b5        },
70    { "a4r4g4b4",  PIXMAN_a4r4g4b4      },
71    { "a2r2g2b2",  PIXMAN_a2r2g2b2      },
72    { "r3g3b2",    PIXMAN_r3g3b2        },
73    { "r1g2b1",    PIXMAN_r1g2b1        },
74    { "a1r1g1b1",  PIXMAN_a1r1g1b1      },
75};
76
77static const named_int_t dithers[] =
78{
79    { "None",                   PIXMAN_REPEAT_NONE },
80    { "Bayer 8x8",              PIXMAN_DITHER_ORDERED_BAYER_8 },
81    { "Blue noise 64x64",       PIXMAN_DITHER_ORDERED_BLUE_NOISE_64 },
82};
83
84static int
85get_value (app_t *app, const named_int_t table[], const char *box_name)
86{
87    GtkComboBox *box = GTK_COMBO_BOX (get_widget (app, box_name));
88
89    return table[gtk_combo_box_get_active (box)].value;
90}
91
92static void
93rescale (GtkWidget *may_be_null, app_t *app)
94{
95    app->dither = get_value (app, dithers, "dithering_combo_box");
96    app->format = get_value (app, formats, "target_format_combo_box");
97
98    gtk_widget_set_size_request (
99	get_widget (app, "drawing_area"), app->width + 0.5, app->height + 0.5);
100
101    gtk_widget_queue_draw (
102	get_widget (app, "drawing_area"));
103}
104
105static gboolean
106on_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data)
107{
108    app_t *app = user_data;
109    GdkRectangle area;
110    cairo_surface_t *surface;
111    pixman_image_t *tmp, *final;
112    uint32_t *pixels;
113
114    gdk_cairo_get_clip_rectangle(cr, &area);
115
116    tmp = pixman_image_create_bits (
117	app->format, area.width, area.height, NULL, 0);
118    pixman_image_set_dither (tmp, app->dither);
119
120    pixman_image_composite (
121	PIXMAN_OP_SRC,
122	app->original, NULL, tmp,
123	area.x, area.y, 0, 0, 0, 0,
124	app->width - area.x,
125	app->height - area.y);
126
127    pixels = calloc (1, area.width * area.height * 4);
128    final = pixman_image_create_bits (
129	PIXMAN_a8r8g8b8, area.width, area.height, pixels, area.width * 4);
130
131    pixman_image_composite (
132	PIXMAN_OP_SRC,
133	tmp, NULL, final,
134	area.x, area.y, 0, 0, 0, 0,
135	app->width - area.x,
136	app->height - area.y);
137
138    surface = cairo_image_surface_create_for_data (
139	(uint8_t *)pixels, CAIRO_FORMAT_ARGB32,
140	area.width, area.height, area.width * 4);
141
142    cairo_set_source_surface (cr, surface, area.x, area.y);
143
144    cairo_paint (cr);
145
146    cairo_surface_destroy (surface);
147    free (pixels);
148    pixman_image_unref (final);
149    pixman_image_unref (tmp);
150
151    return TRUE;
152}
153
154static void
155set_up_combo_box (app_t *app, const char *box_name,
156		  int n_entries, const named_int_t table[])
157{
158    GtkWidget *widget = get_widget (app, box_name);
159    GtkListStore *model;
160    GtkCellRenderer *cell;
161    int i;
162
163    model = gtk_list_store_new (1, G_TYPE_STRING);
164
165    cell = gtk_cell_renderer_text_new ();
166    gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (widget), cell, TRUE);
167    gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (widget), cell,
168				    "text", 0,
169				    NULL);
170
171    gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (model));
172
173    for (i = 0; i < n_entries; ++i)
174    {
175	const named_int_t *info = &(table[i]);
176	GtkTreeIter iter;
177
178	gtk_list_store_append (model, &iter);
179	gtk_list_store_set (model, &iter, 0, info->name, -1);
180    }
181
182    gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
183
184    g_signal_connect (widget, "changed", G_CALLBACK (rescale), app);
185}
186
187static app_t *
188app_new (pixman_image_t *original)
189{
190    GtkWidget *widget;
191    app_t *app = g_malloc (sizeof *app);
192    GError *err = NULL;
193
194    app->builder = gtk_builder_new ();
195    app->original = original;
196
197    if (original->type == BITS)
198    {
199	app->width = pixman_image_get_width (original);
200	app->height = pixman_image_get_height (original);
201    }
202    else
203    {
204	app->width = WIDTH;
205	app->height = HEIGHT;
206    }
207
208    if (!gtk_builder_add_from_file (app->builder, "dither.ui", &err))
209	g_error ("Could not read file dither.ui: %s", err->message);
210
211    widget = get_widget (app, "drawing_area");
212    g_signal_connect (widget, "draw", G_CALLBACK (on_draw), app);
213
214    set_up_combo_box (app, "target_format_combo_box",
215		      G_N_ELEMENTS (formats), formats);
216    set_up_combo_box (app, "dithering_combo_box",
217		      G_N_ELEMENTS (dithers), dithers);
218
219    app->dither = get_value (app, dithers, "dithering_combo_box");
220    app->format = get_value (app, formats, "target_format_combo_box");
221
222    rescale (NULL, app);
223
224    return app;
225}
226
227int
228main (int argc, char **argv)
229{
230    GtkWidget *window;
231    pixman_image_t *image;
232    app_t *app;
233
234    gtk_init (&argc, &argv);
235
236    if (argc < 2)
237    {
238	pixman_gradient_stop_t stops[] = {
239	    /* These colors make it very obvious that dithering
240	     * is useful even for 8-bit gradients
241	     */
242	    { 0x00000, { 0x1b1b, 0x5d5d, 0x7c7c, 0xffff } },
243	    { 0x10000, { 0x3838, 0x3232, 0x1010, 0xffff } },
244	};
245	pixman_point_fixed_t p1, p2;
246
247	p1.x = p1.y = 0x0000;
248	p2.x = WIDTH << 16;
249	p2.y = HEIGHT << 16;
250
251	if (!(image = pixman_image_create_linear_gradient (
252		  &p1, &p2, stops, ARRAY_LENGTH (stops))))
253	{
254	    printf ("Could not create gradient\n");
255	    return -1;
256	}
257    }
258    else if (!(image = pixman_image_from_file (argv[1], PIXMAN_a8r8g8b8)))
259    {
260	printf ("Could not load image \"%s\"\n", argv[1]);
261	return -1;
262    }
263
264    app = app_new (image);
265
266    window = get_widget (app, "main");
267
268    g_signal_connect (window, "delete_event", G_CALLBACK (gtk_main_quit), NULL);
269
270    gtk_window_set_default_size (GTK_WINDOW (window), 1024, 768);
271
272    gtk_widget_show_all (window);
273
274    gtk_main ();
275
276    return 0;
277}
278