clients: screenshot: YUV support

If any output uses a YUV format, instead of trying to composite into a
png, write the raw data into .yuv files for each output.

These files don't contain any metadata yet, thus one may want to convert
them into y4m files with e.g. a command like the this:
ffmpeg -s 1024x768 -r 1 -pix_fmt yuv420p -i ~/wayland-screenshot-output-0-2025-08-01_15-58-24.yuv -c:v copy screenshot.y4m

Note that this may only work for certain pixel formats, such as
DRM_FORMAT_YUV4[20|22|44], and not for e.g. DRM_FORMAT_P010.

Signed-off-by: Robert Mader <robert.mader@collabora.com>
This commit is contained in:
Robert Mader 2025-08-28 16:36:35 +02:00
parent a9fc1fc0f7
commit 8e30a7efc7

View file

@ -442,6 +442,59 @@ screenshot_write_png(const struct buffer_size *buff_size,
pixman_image_unref(shot); pixman_image_unref(shot);
} }
static void
screenshot_write_yuv(const struct buffer_size *buff_size,
struct wl_list *output_list)
{
struct screenshooter_output *output;
int i = 0;
wl_list_for_each(output, output_list, link) {
struct screenshooter_buffer *buffer = output->buffer;
char filepath[PATH_MAX];
char filepath_prefix[100];
int write_offset = 0;
FILE *fp;
sprintf(filepath_prefix, "wayland-screenshot-output-%d-", i++);
fp = file_create_dated(getenv("XDG_PICTURES_DIR"),
filepath_prefix, ".yuv", filepath,
sizeof(filepath));
if (!fp) {
fprintf(stderr, "Writing yuv file for output %d failed\n", i);
return;
}
client_buffer_util_maybe_sync_dmabuf_start(buffer->buf);
for (unsigned int j = 0; j < pixel_format_get_plane_count(buffer->buf->fmt); j++) {
int plane_height =
buffer->buf->height / pixel_format_hsub(buffer->buf->fmt, j);
for (int k = 0; k < plane_height; k++) {
size_t lines_written;
lines_written = fwrite(buffer->buf->data + write_offset,
buffer->buf->bytes_per_line[j],
1, fp);
if (lines_written != 1) {
fprintf(stderr,
"Writing yuv file for output %d " \
"failed during write(): %s\n",
i, strerror(errno));
return;
}
write_offset += buffer->buf->strides[j];
}
}
fclose (fp);
client_buffer_util_maybe_sync_dmabuf_end(buffer->buf);
}
}
static int static int
screenshot_set_buffer_size(struct buffer_size *buff_size, screenshot_set_buffer_size(struct buffer_size *buff_size,
struct wl_list *output_list) struct wl_list *output_list)
@ -487,6 +540,28 @@ received_formats_for_all_outputs(struct screenshooter_app *app)
return true; return true;
} }
static bool
all_output_formats_are_yuv(struct wl_list *output_list)
{
struct screenshooter_output *output;
int color_model = -1;
wl_list_for_each(output, output_list, link) {
if (color_model == -1) {
color_model = output->buffer->buf->fmt->color_model;
continue;
}
if ((int)output->buffer->buf->fmt->color_model != color_model) {
fprintf(stderr, "Mixing of RGB and YUV output formats not supported\n");
exit(1);
}
}
assert(color_model == (int)COLOR_MODEL_RGB ||
color_model == (int)COLOR_MODEL_YUV);
return color_model == COLOR_MODEL_YUV;
}
static void static void
print_usage_and_exit(void) print_usage_and_exit(void)
{ {
@ -497,6 +572,10 @@ print_usage_and_exit(void)
"\n\t\tprint additional output\n" "\n\t\tprint additional output\n"
"\t'-f,--format=<>'" "\t'-f,--format=<>'"
"\n\t\tthe DRM format name to use without the DRM_FORMAT_ prefix, e.g. RGBA8888 or NV12\n" "\n\t\tthe DRM format name to use without the DRM_FORMAT_ prefix, e.g. RGBA8888 or NV12\n"
"\n\t\tIn case of YCbCr formats like NV12, instead of a single .png, the output will consist of raw .yuv files for each output."
"\n\t\tThese files do not contain any metadata, however that can be added by converting to .y4m with a command like:"
"\n\t\tffmpeg -s 1024x768 -r 1 -pix_fmt yuv420p -i ~/wayland-screenshot-output-0-2025-08-01_15-58-24.yuv -c:v copy screenshot.y4m\n"
"\n\t\tNote that this may not work for all YCbCr pixel formats.\n"
"\t'-s,--source-type=<>'" "\t'-s,--source-type=<>'"
"\n\t\tframebuffer to use framebuffer source (default), " "\n\t\tframebuffer to use framebuffer source (default), "
"\n\t\twriteback to use writeback source\n" "\n\t\twriteback to use writeback source\n"
@ -642,6 +721,10 @@ main(int argc, char *argv[])
if (!app.failed) { if (!app.failed) {
if (screenshot_set_buffer_size(&buff_size, &app.output_list) < 0) if (screenshot_set_buffer_size(&buff_size, &app.output_list) < 0)
return -1; return -1;
if (all_output_formats_are_yuv(&app.output_list))
screenshot_write_yuv(&buff_size, &app.output_list);
else
screenshot_write_png(&buff_size, &app.output_list); screenshot_write_png(&buff_size, &app.output_list);
} else { } else {
fprintf(stderr, "Error: screenshot or protocol failure\n"); fprintf(stderr, "Error: screenshot or protocol failure\n");