git.y1.nz

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.