mirror of https://github.com/LIJI32/SameBoy.git
131 lines
6.0 KiB
C
131 lines
6.0 KiB
C
#include <errno.h>
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
#include <glib.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "emulate.h"
|
|
|
|
static const char dmg_only_resource_path[] = "/thumbnailer/CartridgeTemplate.png";
|
|
static const char dual_resource_path[] = "/thumbnailer/UniversalCartridgeTemplate.png";
|
|
static const char cgb_only_resource_path[] = "/thumbnailer/ColorCartridgeTemplate.png";
|
|
|
|
static GdkPixbuf *generate_thumbnail(const char *input_path)
|
|
{
|
|
uint32_t screen_raw[GB_SCREEN_WIDTH * GB_SCREEN_HEIGHT];
|
|
uint8_t cgb_flag = emulate(input_path, screen_raw);
|
|
|
|
// Generate the thumbnail from `screen_raw` and `cgb_flag`.
|
|
|
|
// `screen_raw` is properly formatted for this operation; see the comment in `rgb_encode` for a
|
|
// discussion of why and how.
|
|
GdkPixbuf *screen = gdk_pixbuf_new_from_data((uint8_t *)screen_raw, GDK_COLORSPACE_RGB,
|
|
true, // Yes, we have alpha!
|
|
8, // bpp
|
|
GB_SCREEN_WIDTH, GB_SCREEN_HEIGHT, // Size.
|
|
GB_SCREEN_WIDTH * sizeof(screen_raw[0]), // Row stride.
|
|
NULL, NULL); // Do not free the buffer.
|
|
// Scale the screen and position it in the appropriate place for compositing the cartridge templates.
|
|
GdkPixbuf *scaled_screen = gdk_pixbuf_new(GDK_COLORSPACE_RGB, true, 8, 1024, 1024);
|
|
gdk_pixbuf_scale(screen, // Source.
|
|
scaled_screen, // Destination.
|
|
192, 298, // Match the displacement below.
|
|
GB_SCREEN_WIDTH * 4, GB_SCREEN_HEIGHT * 4, // How the scaled rectangle should be cropped.
|
|
192, 298, // Displace the scaled screen so it lines up with the template.
|
|
4, 4, // Scaling factors.
|
|
GDK_INTERP_NEAREST);
|
|
g_object_unref(screen);
|
|
|
|
GError *error = NULL;
|
|
GdkPixbuf *template;
|
|
switch (cgb_flag) {
|
|
case 0xC0:
|
|
template = gdk_pixbuf_new_from_resource(cgb_only_resource_path, &error);
|
|
break;
|
|
case 0x80:
|
|
template = gdk_pixbuf_new_from_resource(dual_resource_path, &error);
|
|
break;
|
|
default:
|
|
template = gdk_pixbuf_new_from_resource(dmg_only_resource_path, &error);
|
|
break;
|
|
}
|
|
g_assert_no_error(error);
|
|
g_assert_cmpint(gdk_pixbuf_get_width(template), ==, 1024);
|
|
g_assert_cmpint(gdk_pixbuf_get_height(template), ==, 1024);
|
|
gdk_pixbuf_composite(template, // Source.
|
|
scaled_screen, // Destination.
|
|
0, 0, // Match the displacement below.
|
|
1024, 1024, // Crop of the scaled rectangle.
|
|
0, 0, // Displacement of the scaled rectangle.
|
|
1, 1, // Scaling factors.
|
|
GDK_INTERP_NEAREST, // Doesn't really matter, but should be a little faster.
|
|
255); // Blending factor of the source onto the destination.
|
|
g_object_unref(template);
|
|
|
|
return scaled_screen;
|
|
}
|
|
|
|
static GdkPixbuf *enforce_max_size(GdkPixbuf *thumbnail, unsigned max_size)
|
|
{
|
|
g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, gdk_pixbuf_get_height(thumbnail));
|
|
g_assert_cmpuint(gdk_pixbuf_get_width(thumbnail), ==, 1024);
|
|
// This is only a *max* size; don't bother scaling up.
|
|
// (This also prevents any overflow errors—notice that the scale function takes `int` size parameters!)
|
|
if (max_size > 1024) return thumbnail;
|
|
GdkPixbuf *scaled = gdk_pixbuf_scale_simple(thumbnail, max_size, max_size, GDK_INTERP_BILINEAR);
|
|
g_object_unref(thumbnail);
|
|
return scaled;
|
|
}
|
|
|
|
static void write_thumbnail(GdkPixbuf *thumbnail, const char *output_path)
|
|
{
|
|
GError *error = NULL;
|
|
// Intentionally be "not a good citizen":
|
|
// - Write directly to the provided path, instead of atomically replacing it with a fully-formed file;
|
|
// this is necessary for at least Tumbler (XFCE's thumbnailer daemon), which creates the file **and** keeps the
|
|
// returned FD—which keeps pointing to the deleted file... which is still empty!
|
|
// - Do not save any metadata to the PNG, since the thumbnailer daemon (again, at least XFCE's, the only one I have
|
|
// tested with) appears to read the PNG's pixels, and write a new one with the appropriate metadata.
|
|
// (Thank you! Saves me all that work.)
|
|
gdk_pixbuf_save(thumbnail, output_path, "png", &error, NULL);
|
|
if (error) {
|
|
g_error("Failed to save thumbnail: %s", error->message);
|
|
// NOTREACHED
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
if (argc != 3 && argc != 4) {
|
|
g_error("Usage: %s <input path> <output path> [<size>]", argv[0] ? argv[0] : "sameboy-thumbnailer");
|
|
// NOTREACHED
|
|
}
|
|
const char *input_path = argv[1];
|
|
char *output_path = argv[2]; // Gets mutated in-place.
|
|
const char *max_size = argv[3]; // May be NULL.
|
|
|
|
g_debug("%s -> %s [%s]", input_path, output_path, max_size ? max_size : "(none)");
|
|
|
|
GdkPixbuf *thumbnail = generate_thumbnail(input_path);
|
|
if (max_size) {
|
|
char *endptr;
|
|
errno = 0;
|
|
/* This will implicitly truncate, but enforce_max_size will cap size to 1024 anyway.
|
|
(Not that 4 billion pixels wide icons make sense to begin with)*/
|
|
unsigned size = strtoul(max_size, &endptr, 10);
|
|
if (errno != 0 || *max_size == '\0' || *endptr != '\0') {
|
|
g_error("Invalid size parameter \"%s\": %s", max_size, strerror(errno == 0 ? EINVAL : errno));
|
|
// NOTREACHED
|
|
}
|
|
|
|
thumbnail = enforce_max_size(thumbnail, size);
|
|
}
|
|
write_thumbnail(thumbnail, output_path);
|
|
g_object_unref(thumbnail);
|
|
|
|
return 0;
|
|
}
|