SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
XdgThumbnailer/main.c
1 #include <errno.h>
2 #include <gdk-pixbuf/gdk-pixbuf.h>
3 #include <glib.h>
4 #include <stdbool.h>
5 #include <stdint.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9
10 #include "emulate.h"
11
12 static const char dmg_only_resource_path[] = "/thumbnailer/CartridgeTemplate.png";
13 static const char dual_resource_path[] = "/thumbnailer/UniversalCartridgeTemplate.png";
14 static const char cgb_only_resource_path[] = "/thumbnailer/ColorCartridgeTemplate.png";
15
16 static GdkPixbuf *generate_thumbnail(const char *input_path)
17 {
18 uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT];
19 uint8_t cgb_flag = emulate(input_path, screen_raw);
20
21 // Generate the thumbnail from `screen_raw` and `cgb_flag`.
22
23 // `screen_raw` is properly formatted for this operation; see the comment in `rgb_encode` for a
24 // discussion of why and how.
25 GdkPixbuf *screen = gdk_pixbuf_new_from_data((uint8_t *)screen_raw, GDK_COLORSPACE_RGB,
26 true, // Yes, we have alpha!
27 8, // bpp
28 GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size.
29 GB_SCREEN_WIDTH * sizeof(screen_raw[0]), // Row stride.
30 NULL, NULL); // Do not free the buffer.
31 // Scale the screen and position it in the appropriate place for compositing the cartridge templates.
32 GdkPixbuf *scaled_screen = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 1024, 1024);
33 gdk_pixbuf_scale(screen, // Source.
34 scaled_screen, // Destination.
35 192, 298, // Match the displacement below.
36 GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // How the scaled rectangle should be cropped.
37 192, 298, // Displace the scaled screen so it lines up with the template.
38 4, 4, // Scaling factors.
39 GDK_INTERP_NEAREST);
40 g_object_unref(screen);
41
42 GError *error = NULL;
43 GdkPixbuf *template;
44 switch (cgb_flag) {
45 case 0xC0:
46 template = gdk_pixbuf_new_from_resource(cgb_only_resource_path, &error);
47 break;
48 case 0x80:
49 template = gdk_pixbuf_new_from_resource(dual_resource_path, &error);
50 break;
51 default:
52 template = gdk_pixbuf_new_from_resource(dmg_only_resource_path, &error);
53 break;
54 }
55 g_assert_no_error(error);
56 g_assert_cmpint(gdk_pixbuf_get_width(template), ==, 1024);
57 g_assert_cmpint(gdk_pixbuf_get_height(template), ==, 1024);
58 gdk_pixbuf_composite(template, // Source.
59 scaled_screen, // Destination.
60 0, 0, // Match the displacement below.
61 1024, 1024, // Crop of the scaled rectangle.
62 0, 0, // Displacement of the scaled rectangle.
63 1, 1, // Scaling factors.
64 GDK_INTERP_NEAREST, // Doesn't really matter, but should be a little faster.
65 255); // Blending factor of the source onto the destination.
66 g_object_unref(template);
67
68 return scaled_screen;
69 }
70
71 static GdkPixbuf *enforce_max_size(GdkPixbuf *thumbnail, unsigned max_size)
72 {
73 g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, gdk_pixbuf_get_height(thumbnail));
74 g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, 1024);
75 // This is only a *max* size; don't bother scaling up.
76 // (This also prevents any overflow errors—notice that the scale function takes `int` size parameters!)
77 if (max_size > 1024) return thumbnail;
78 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(thumbnail, max_size, max_size, GDK_INTERP_BILINEAR);
79 g_object_unref(thumbnail);
80 return scaled;
81 }
82
83 static void write_thumbnail(GdkPixbuf *thumbnail, const char *output_path)
84 {
85 GError *error = NULL;
86 // Intentionally be "not a good citizen":
87 // - Write directly to the provided path, instead of atomically replacing it with a fully-formed file;
88 // this is necessary for at least Tumbler (XFCE's thumbnailer daemon), which creates the file **and** keeps the
89 // returned FD—which keeps pointing to the deleted file... which is still empty!
90 // - Do not save any metadata to the PNG, since the thumbnailer daemon (again, at least XFCE's, the only one I have
91 // tested with) appears to read the PNG's pixels, and write a new one with the appropriate metadata.
92 // (Thank you! Saves me all that work.)
93 gdk_pixbuf_save(thumbnail, output_path, "png", &error, NULL);
94 if (error) {
95 g_error("Failed to save thumbnail: %s", error->message);
96 // NOTREACHED
97 }
98 }
99
100 int main(int argc, char *argv[])
101 {
102 if (argc != 3 && argc != 4) {
103 g_error("Usage: %s <input path> <output path> [<size>]", argv[0] ? argv[0] : "sameboy-thumbnailer");
104 // NOTREACHED
105 }
106 const char *input_path = argv[1];
107 char *output_path = argv[2]; // Gets mutated in-place.
108 const char *max_size = argv[3]; // May be NULL.
109
110 g_debug("%s -> %s [%s]", input_path, output_path, max_size ? max_size : "(none)");
111
112 GdkPixbuf *thumbnail = generate_thumbnail(input_path);
113 if (max_size) {
114 char *endptr;
115 errno = 0;
116 /* This will implicitly truncate, but enforce_max_size will cap size to 1024 anyway.
117 (Not that 4 billion pixels wide icons make sense to begin with)*/
118 unsigned size = strtoul(max_size, &endptr, 10);
119 if (errno != 0 || *max_size == '\0' || *endptr != '\0') {
120 g_error("Invalid size parameter \"%s\": %s", max_size, strerror(errno == 0 ? EINVAL : errno));
121 // NOTREACHED
122 }
123
124 thumbnail = enforce_max_size(thumbnail, size);
125 }
126 write_thumbnail(thumbnail, output_path);
127 g_object_unref(thumbnail);
128
129 return 0;
130 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.