Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package grim for openSUSE:Factory checked in at 2022-02-11 23:07:42 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/grim (Old) and /work/SRC/openSUSE:Factory/.grim.new.1956 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "grim" Fri Feb 11 23:07:42 2022 rev:6 rq:953373 version:1.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/grim/grim.changes 2021-04-19 21:06:59.300118333 +0200 +++ /work/SRC/openSUSE:Factory/.grim.new.1956/grim.changes 2022-02-11 23:09:22.990941402 +0100 @@ -1,0 +2,22 @@ +Fri Feb 11 07:32:39 UTC 2022 - Michael Vetter <mvet...@suse.com> + +- Update to 1.4.0: + * Read XDG_PICTURES_DIR from user-dirs.dirs file + * add bash completion + * render: use pixman to compose output buffers + * render: use convolution filter when downscaling + * Allow setting compression level for PNG output + * write_png: fix out of bounds read + * render: add support for ??? 24bpp pixel formats + * Use alternate Wayland to Pixman format conversion on big-endian + * Rename cairo_{jpg,ppm} to write_{jpg,ppm} + * Use stream-type functions to save images + * Remove cairo dependency + * man: fix wrong section headers + * Add example for taking screenshot of active window + * Make get_output_dir return value const + * Replace sprintf with snprintf + * Make output_filename const + * Stop using PATH_MAX + +------------------------------------------------------------------- Old: ---- v1.3.2.tar.gz New: ---- v1.4.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ grim.spec ++++++ --- /var/tmp/diff_new_pack.qZhkaF/_old 2022-02-11 23:09:23.594943148 +0100 +++ /var/tmp/diff_new_pack.qZhkaF/_new 2022-02-11 23:09:23.602943172 +0100 @@ -1,7 +1,7 @@ # # spec file for package grim # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,7 +17,7 @@ Name: grim -Version: 1.3.2 +Version: 1.4.0 Release: 0 Summary: Wayland compositor image grabber License: MIT @@ -27,8 +27,9 @@ BuildRequires: meson BuildRequires: pkgconfig BuildRequires: scdoc -BuildRequires: pkgconfig(cairo) BuildRequires: pkgconfig(libjpeg) +BuildRequires: pkgconfig(libpng) +BuildRequires: pkgconfig(pixman-1) BuildRequires: pkgconfig(wayland-client) BuildRequires: pkgconfig(wayland-protocols) >= 1.14 ++++++ v1.3.2.tar.gz -> v1.4.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/.build.yml new/grim-1.4.0/.build.yml --- old/grim-1.3.2/.build.yml 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/.build.yml 2022-02-09 00:20:30.000000000 +0100 @@ -3,7 +3,8 @@ - meson - wayland - wayland-protocols - - cairo + - pixman + - libpng - libjpeg-turbo sources: - https://github.com/emerison/grim diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/README.md new/grim-1.4.0/README.md --- old/grim-1.3.2/README.md 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/README.md 2022-02-09 00:20:30.000000000 +0100 @@ -47,6 +47,13 @@ grim -o $(swaymsg -t get_outputs | jq -r '.[] | select(.focused) | .name') ``` +Grab a screenshot from the focused window under Sway, using `swaymsg` and +`jq`: + +```sh +grim -g "$(swaymsg -t get_tree | jq -j '.. | select(.type?) | select(.focused).rect | "\(.x),\(.y) \(.width)x\(.height)"')" +``` + Pick a color, using ImageMagick: ```sh @@ -59,7 +66,8 @@ * meson * wayland -* cairo +* pixman +* libpng * libjpeg (optional) Then run: @@ -77,7 +85,7 @@ Either [send GitHub pull requests][github] or [send patches on the mailing list][ml]. -Join the IRC channel: ##emersion on Freenode. +Join the IRC channel: #emersion on Libera Chat. ## License diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/cairo_jpg.c new/grim-1.4.0/cairo_jpg.c --- old/grim-1.3.2/cairo_jpg.c 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/cairo_jpg.c 1970-01-01 01:00:00.000000000 +0100 @@ -1,126 +0,0 @@ -/** - * @author Bernhard R. Fischer, 4096R/8E24F29D b...@abenteuerland.at - * @license This code is free software. Do whatever you like to do with it. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <assert.h> -#include <fcntl.h> -#include <limits.h> -#include <unistd.h> -#include <cairo.h> -#include <jpeglib.h> - -#include "cairo_jpg.h" - -cairo_status_t cairo_surface_write_to_jpeg_mem(cairo_surface_t *sfc, - unsigned char **data, unsigned long *len, int quality) { - struct jpeg_compress_struct cinfo; - struct jpeg_error_mgr jerr; - JSAMPROW row_pointer[1]; - cairo_surface_t *other = NULL; - - if (cairo_surface_get_type(sfc) != CAIRO_SURFACE_TYPE_IMAGE || - (cairo_image_surface_get_format(sfc) != CAIRO_FORMAT_ARGB32 && - cairo_image_surface_get_format(sfc) != CAIRO_FORMAT_RGB24)) { - double x1, y1, x2, y2; - other = sfc; - cairo_t *ctx = cairo_create(other); - cairo_clip_extents(ctx, &x1, &y1, &x2, &y2); - cairo_destroy(ctx); - - sfc = cairo_surface_create_similar_image(other, CAIRO_FORMAT_RGB24, - x2 - x1, y2 - y1); - if (cairo_surface_status(sfc) != CAIRO_STATUS_SUCCESS) { - return CAIRO_STATUS_INVALID_FORMAT; - } - - ctx = cairo_create(sfc); - cairo_set_source_surface(ctx, other, 0, 0); - cairo_paint(ctx); - cairo_destroy(ctx); - } - - cairo_surface_flush(sfc); - - cinfo.err = jpeg_std_error(&jerr); - jpeg_create_compress(&cinfo); - - jpeg_mem_dest(&cinfo, data, len); - cinfo.image_width = cairo_image_surface_get_width(sfc); - cinfo.image_height = cairo_image_surface_get_height(sfc); - if (cairo_image_surface_get_format(sfc) == CAIRO_FORMAT_ARGB32) { - cinfo.in_color_space = JCS_EXT_BGRA; - } else { - cinfo.in_color_space = JCS_EXT_BGRX; - } - cinfo.input_components = 4; - - jpeg_set_defaults(&cinfo); - jpeg_set_quality(&cinfo, quality, TRUE); - - jpeg_start_compress(&cinfo, TRUE); - - while (cinfo.next_scanline < cinfo.image_height) { - row_pointer[0] = cairo_image_surface_get_data(sfc) + (cinfo.next_scanline - * cairo_image_surface_get_stride(sfc)); - (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); - } - - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - - if (other != NULL) - cairo_surface_destroy(sfc); - - return CAIRO_STATUS_SUCCESS; -} - - -static cairo_status_t cj_write(void *closure, const unsigned char *data, - unsigned int length) { - if (write((long) closure, data, length) < (ssize_t) length) { - return CAIRO_STATUS_WRITE_ERROR; - } else { - return CAIRO_STATUS_SUCCESS; - } -} - -cairo_status_t cairo_surface_write_to_jpeg_stream(cairo_surface_t *sfc, - cairo_write_func_t write_func, void *closure, int quality) { - cairo_status_t e; - unsigned char *data = NULL; - unsigned long len = 0; - - e = cairo_surface_write_to_jpeg_mem(sfc, &data, &len, quality); - if (e == CAIRO_STATUS_SUCCESS) { - assert(sizeof(unsigned long) <= sizeof(size_t) - || !(len >> (sizeof(size_t) * CHAR_BIT))); - e = write_func(closure, data, len); - free(data); - } - - return e; -} - -cairo_status_t cairo_surface_write_to_jpeg(cairo_surface_t *sfc, - const char *filename, int quality) { - cairo_status_t e; - int outfile; - - outfile = open(filename, - O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - - if (outfile == -1) { - return CAIRO_STATUS_DEVICE_ERROR; - } - - e = cairo_surface_write_to_jpeg_stream(sfc, cj_write, - (void*) (long) outfile, quality); - - close(outfile); - return e; -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/cairo_ppm.c new/grim-1.4.0/cairo_ppm.c --- old/grim-1.3.2/cairo_ppm.c 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/cairo_ppm.c 1970-01-01 01:00:00.000000000 +0100 @@ -1,96 +0,0 @@ -#include <assert.h> -#include <fcntl.h> -#include <limits.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> -#include <cairo.h> - -#include "cairo_ppm.h" - -cairo_status_t cairo_surface_write_to_ppm_mem(cairo_surface_t *sfc, - unsigned char **data, unsigned long *len) { - // 256 bytes ought to be enough for everyone - char header[256]; - - int width = cairo_image_surface_get_width(sfc); - int height = cairo_image_surface_get_height(sfc); - - int header_len = snprintf(header, sizeof(header), "P6\n%d %d\n255\n", width, height); - assert(header_len <= (int)sizeof(header)); - - *len = header_len + width * height * 3; - unsigned char *buffer = malloc(*len); - *data = buffer; - - // We _do_not_ include the null byte - memcpy(buffer, header, header_len); - buffer += header_len; - - cairo_format_t cformat = cairo_image_surface_get_format(sfc); - assert(cformat == CAIRO_FORMAT_RGB24 || cformat == CAIRO_FORMAT_ARGB32); - - // Both formats are native-endian 32-bit ints - uint32_t *pixels = (uint32_t *)cairo_image_surface_get_data(sfc); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - uint32_t p = *pixels++; - // RGB order - *buffer++ = (p >> 16) & 0xff; - *buffer++ = (p >> 8) & 0xff; - *buffer++ = (p >> 0) & 0xff; - } - } - - return CAIRO_STATUS_SUCCESS; -} - - -static cairo_status_t cj_write(void *closure, const unsigned char *data, - unsigned int length) { - if (write((long) closure, data, length) < (ssize_t) length) { - return CAIRO_STATUS_WRITE_ERROR; - } else { - return CAIRO_STATUS_SUCCESS; - } -} - -cairo_status_t cairo_surface_write_to_ppm_stream(cairo_surface_t *sfc, - cairo_write_func_t write_func, void *closure) { - cairo_status_t e; - unsigned char *data = NULL; - unsigned long len = 0; - - e = cairo_surface_write_to_ppm_mem(sfc, &data, &len); - if (e == CAIRO_STATUS_SUCCESS) { - assert(sizeof(unsigned long) <= sizeof(size_t) - || !(len >> (sizeof(size_t) * CHAR_BIT))); - e = write_func(closure, data, len); - free(data); - } - - return e; -} - -cairo_status_t cairo_surface_write_to_ppm(cairo_surface_t *sfc, - const char *filename) { - cairo_status_t e; - int outfile; - - outfile = open(filename, - O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - - if (outfile == -1) { - return CAIRO_STATUS_DEVICE_ERROR; - } - - e = cairo_surface_write_to_ppm_stream(sfc, cj_write, - (void*) (long) outfile); - - close(outfile); - return e; -} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/contrib/completions/bash/grim.bash new/grim-1.4.0/contrib/completions/bash/grim.bash --- old/grim-1.3.2/contrib/completions/bash/grim.bash 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.4.0/contrib/completions/bash/grim.bash 2022-02-09 00:20:30.000000000 +0100 @@ -0,0 +1,27 @@ +_grim() { + _init_completion || return + + CUR="${COMP_WORDS[COMP_CWORD]}" + PREV="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ "$PREV" == "-t" ]]; then + COMPREPLY=($(compgen -W "png ppm jpeg" -- "$CUR")) + return + elif [[ "$PREV" == "-o" ]]; then + OUTPUTS="$(swaymsg -t get_outputs 2>/dev/null | \ + jq -r '.[] | select(.active) | "\(.name)\t\(.make) \(.model)"' 2>/dev/null)" + + COMPREPLY=($(compgen -W "$OUTPUTS" -- "$CUR")) + return + fi + + if [[ "$CUR" == -* ]]; then + COMPREPLY=($(compgen -W "-h -s -g -t -q -o -c" -- "$CUR")) + return + fi + + # fall back to completing filenames + _filedir +} + +complete -F _grim grim diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/contrib/completions/meson.build new/grim-1.4.0/contrib/completions/meson.build --- old/grim-1.3.2/contrib/completions/meson.build 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/contrib/completions/meson.build 2022-02-09 00:20:30.000000000 +0100 @@ -1,12 +1,20 @@ -fish_comp = dependency('fish', required: false) - if get_option('fish-completions') fish_files = files('fish/grim.fish') + + fish_comp = dependency('fish', required: false) if fish_comp.found() - fish_install_dir = fish_comp.get_pkgconfig_variable('completionsdir') + fish_install_dir = fish_comp.get_variable('completionsdir') else datadir = get_option('datadir') fish_install_dir = join_paths(datadir, 'fish', 'vendor_completions.d') endif install_data(fish_files, install_dir: fish_install_dir) endif + + +if get_option('bash-completions') + bash_comp = dependency('bash-completion') + bash_files = files('bash/grim.bash') + bash_install_dir = bash_comp.get_variable('completionsdir') + install_data(bash_files, install_dir: bash_install_dir) +endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/grim.1.scd new/grim-1.4.0/grim.1.scd --- old/grim-1.3.2/grim.1.scd 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/grim.1.scd 2022-02-09 00:20:30.000000000 +0100 @@ -4,11 +4,11 @@ grim - grab images from a Wayland compositor -# DESCRIPTION +# SYNOPSIS *grim* [options...] [output-file] -# SYNOPSIS +# DESCRIPTION grim is a command-line utility to take screenshots of Wayland desktops. For now it requires support for the screencopy protocol to work. Support for the @@ -42,6 +42,14 @@ Set the output jpeg's filetype compression rate to _quality_. By default, the jpeg quality is *80*, valid values are between 0-100. +*-l* <level> + Set the output PNG's filetype compression level to _level_. By default, + the PNG compression level is 6 on a scale from 0 to 9. Level 9 gives + the highest compression ratio, but may be slow; level 1 gives a lower + compression ratio, but is faster. Level 0 does no compression at all, + and produces very large files; it can be useful when grim is used + in a pipeline with other commands. + *-o* <output> Set the output name to capture. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/include/cairo_jpg.h new/grim-1.4.0/include/cairo_jpg.h --- old/grim-1.3.2/include/cairo_jpg.h 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/include/cairo_jpg.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -#ifndef _CAIRO_JPEG_H -#define _CAIRO_JPEG_H - -#include <cairo.h> - -cairo_status_t cairo_surface_write_to_jpeg_mem(cairo_surface_t *sfc, unsigned char **data, unsigned long *len, int quality); -cairo_status_t cairo_surface_write_to_jpeg_stream(cairo_surface_t *sfc, cairo_write_func_t write_func, void *closure, int quality); -cairo_status_t cairo_surface_write_to_jpeg(cairo_surface_t *sfc, const char *filename, int quality); - -#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/include/cairo_ppm.h new/grim-1.4.0/include/cairo_ppm.h --- old/grim-1.3.2/include/cairo_ppm.h 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/include/cairo_ppm.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -#ifndef _CAIRO_PPM_H -#define _CAIRO_PPM_H - -#include <cairo.h> - -cairo_status_t cairo_surface_write_to_ppm_mem(cairo_surface_t *sfc, unsigned char **data, unsigned long *len); -cairo_status_t cairo_surface_write_to_ppm_stream(cairo_surface_t *sfc, cairo_write_func_t write_func, void *closure); -cairo_status_t cairo_surface_write_to_ppm(cairo_surface_t *sfc, const char *filename); - -#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/include/render.h new/grim-1.4.0/include/render.h --- old/grim-1.3.2/include/render.h 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/include/render.h 2022-02-09 00:20:30.000000000 +0100 @@ -1,11 +1,11 @@ #ifndef _RENDER_H #define _RENDER_H -#include <cairo.h> +#include <pixman.h> #include "grim.h" -cairo_surface_t *render(struct grim_state *state, struct grim_box *geometry, +pixman_image_t *render(struct grim_state *state, struct grim_box *geometry, double scale); #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/include/write_jpg.h new/grim-1.4.0/include/write_jpg.h --- old/grim-1.3.2/include/write_jpg.h 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.4.0/include/write_jpg.h 2022-02-09 00:20:30.000000000 +0100 @@ -0,0 +1,9 @@ +#ifndef _WRITE_JPEG_H +#define _WRITE_JPEG_H + +#include <pixman.h> +#include <stdio.h> + +int write_to_jpeg_stream(pixman_image_t *image, FILE *stream, int quality); + +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/include/write_png.h new/grim-1.4.0/include/write_png.h --- old/grim-1.3.2/include/write_png.h 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.4.0/include/write_png.h 2022-02-09 00:20:30.000000000 +0100 @@ -0,0 +1,9 @@ +#ifndef _WRITE_PNG_H +#define _WRITE_PNG_H + +#include <pixman.h> +#include <stdio.h> + +int write_to_png_stream(pixman_image_t *image, FILE *stream, int comp_level); + +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/include/write_ppm.h new/grim-1.4.0/include/write_ppm.h --- old/grim-1.3.2/include/write_ppm.h 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.4.0/include/write_ppm.h 2022-02-09 00:20:30.000000000 +0100 @@ -0,0 +1,9 @@ +#ifndef _WRITE_PPM_H +#define _WRITE_PPM_H + +#include <pixman.h> +#include <stdio.h> + +int write_to_ppm_stream(pixman_image_t *image, FILE *stream); + +#endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/main.c new/grim-1.4.0/main.c --- old/grim-1.3.2/main.c 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/main.c 2022-02-09 00:20:30.000000000 +0100 @@ -1,23 +1,25 @@ #define _POSIX_C_SOURCE 200809L #include <assert.h> -#include <cairo.h> #include <errno.h> #include <limits.h> +#include <pixman.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <unistd.h> +#include <wordexp.h> #include "buffer.h" #include "grim.h" #include "output-layout.h" #include "render.h" -#include "cairo_ppm.h" +#include "write_ppm.h" #ifdef HAVE_JPEG -#include "cairo_jpg.h" +#include "write_jpg.h" #endif +#include "write_png.h" static void screencopy_frame_handle_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format, uint32_t width, @@ -182,19 +184,7 @@ .global_remove = handle_global_remove, }; -static cairo_status_t write_func(void *data, const unsigned char *buf, - unsigned int len) { - FILE *f = data; - - size_t written = fwrite(buf, sizeof(unsigned char), len, f); - if (written < len) { - return CAIRO_STATUS_WRITE_ERROR; - } - - return CAIRO_STATUS_SUCCESS; -} - -bool default_filename(char *filename, size_t n, int filetype) { +static bool default_filename(char *filename, size_t n, int filetype) { time_t time_epoch = time(NULL); struct tm *time = localtime(&time_epoch); if (time == NULL) { @@ -234,25 +224,86 @@ return path && access(path, R_OK) != -1; } -char *get_output_dir(void) { - static const char *output_dirs[] = { - "GRIM_DEFAULT_DIR", - "XDG_PICTURES_DIR", - }; - - for (size_t i = 0; i < sizeof(output_dirs) / sizeof(char *); ++i) { - char *path = getenv(output_dirs[i]); - if (path_exists(path)) { - return path; +char *get_xdg_pictures_dir(void) { + const char *home_dir = getenv("HOME"); + if (home_dir == NULL) { + return NULL; + } + + char *config_file; + const char user_dirs_file[] = "user-dirs.dirs"; + const char config_home_fallback[] = ".config"; + const char *config_home = getenv("XDG_CONFIG_HOME"); + if (config_home == NULL || config_home[0] == 0) { + size_t size = strlen(home_dir) + strlen("/") + + strlen(config_home_fallback) + strlen("/") + strlen(user_dirs_file) + 1; + config_file = malloc(size); + if (config_file == NULL) { + return NULL; + } + snprintf(config_file, size, "%s/%s/%s", home_dir, config_home_fallback, user_dirs_file); + } else { + size_t size = strlen(config_home) + strlen("/") + strlen(user_dirs_file) + 1; + config_file = malloc(size); + if (config_file == NULL) { + return NULL; } + snprintf(config_file, size, "%s/%s", config_home, user_dirs_file); } - return "."; + FILE *file = fopen(config_file, "r"); + free(config_file); + if (file == NULL) { + return NULL; + } + + char *line = NULL; + size_t line_size = 0; + ssize_t nread; + char *pictures_dir = NULL; + while ((nread = getline(&line, &line_size, file)) != -1) { + if (nread > 0 && line[nread - 1] == '\n') { + line[nread - 1] = '\0'; + } + + if (strlen(line) == 0 || line[0] == '#') { + continue; + } + + size_t i = 0; + while (line[i] == ' ') { + i++; + } + const char prefix[] = "XDG_PICTURES_DIR="; + if (strncmp(&line[i], prefix, strlen(prefix)) == 0) { + const char *line_remaining = &line[i] + strlen(prefix); + wordexp_t p; + if (wordexp(line_remaining, &p, WRDE_UNDEF) == 0) { + free(pictures_dir); + pictures_dir = strdup(p.we_wordv[0]); + wordfree(&p); + } + } + } + free(line); + fclose(file); + return pictures_dir; } -void filepath(char *output_path, const char *filename) { - char *output_dir = get_output_dir(); - sprintf(output_path, "%s/%s", output_dir, filename); +char *get_output_dir(void) { + const char *grim_default_dir = getenv("GRIM_DEFAULT_DIR"); + if (path_exists(grim_default_dir)) { + return strdup(grim_default_dir); + } + + char *xdg_fallback_dir = get_xdg_pictures_dir(); + if (path_exists(xdg_fallback_dir)) { + return xdg_fallback_dir; + } else { + free(xdg_fallback_dir); + } + + return strdup("."); } static const char usage[] = @@ -264,6 +315,7 @@ " -g <geometry> Set the region to capture.\n" " -t png|ppm|jpeg Set the output filetype. Defaults to png.\n" " -q <quality> Set the JPEG filetype quality 0-100. Defaults to 80.\n" + " -l <level> Set the PNG filetype compression level 0-9. Defaults to 6.\n" " -o <output> Set the output name to capture.\n" " -c Include cursors in the screenshot.\n"; @@ -274,9 +326,10 @@ char *geometry_output = NULL; enum grim_filetype output_filetype = GRIM_FILETYPE_PNG; int jpeg_quality = 80; + int png_level = 6; // current default png/zlib compression level bool with_cursor = false; int opt; - while ((opt = getopt(argc, argv, "hs:g:t:q:o:c")) != -1) { + while ((opt = getopt(argc, argv, "hs:g:t:q:l:o:c")) != -1) { switch (opt) { case 'h': printf("%s", usage); @@ -347,6 +400,24 @@ } } break; + case 'l': + if (output_filetype != GRIM_FILETYPE_PNG) { + fprintf(stderr, "compression level is used only for png files\n"); + return EXIT_FAILURE; + } else { + char *endptr = NULL; + errno = 0; + png_level = strtol(optarg, &endptr, 10); + if (*endptr != '\0' || errno) { + fprintf(stderr, "level must be a integer\n"); + return EXIT_FAILURE; + } + if (png_level < 0 || png_level > 9) { + fprintf(stderr, "compression level valid values are between 0-9\n"); + return EXIT_FAILURE; + } + } + break; case 'o': free(geometry_output); geometry_output = strdup(optarg); @@ -359,23 +430,28 @@ } } - char output_filepath[PATH_MAX]; - char *output_filename; + const char *output_filename; + char *output_filepath; char tmp[64]; if (optind >= argc) { - if (default_filename(tmp, sizeof(tmp), output_filetype) != true) { + if (!default_filename(tmp, sizeof(tmp), output_filetype)) { fprintf(stderr, "failed to generate default filename\n"); return EXIT_FAILURE; } output_filename = tmp; - filepath(output_filepath, output_filename); - } else { - output_filename = argv[optind]; - if (strlen(output_filename) >= PATH_MAX) { - fprintf(stderr, "'%s': filepath too long\n", output_filename); + + char *output_dir = get_output_dir(); + int len = snprintf(NULL, 0, "%s/%s", output_dir, output_filename); + if (len < 0) { + perror("snprintf failed"); return EXIT_FAILURE; } - strcpy(output_filepath, output_filename); + output_filepath = malloc(len + 1); + snprintf(output_filepath, len + 1, "%s/%s", output_dir, output_filename); + free(output_dir); + } else { + output_filename = argv[optind]; + output_filepath = strdup(output_filename); } struct grim_state state = {0}; @@ -480,56 +556,50 @@ get_output_layout_extents(&state, geometry); } - cairo_surface_t *surface = render(&state, geometry, scale); - if (surface == NULL) { + pixman_image_t *image = render(&state, geometry, scale); + if (image == NULL) { return EXIT_FAILURE; } - cairo_status_t status = CAIRO_STATUS_INVALID_STATUS; + FILE *file; if (strcmp(output_filename, "-") == 0) { - switch (output_filetype) { - case GRIM_FILETYPE_PPM: - status = cairo_surface_write_to_ppm_stream(surface, write_func, stdout); - break; - case GRIM_FILETYPE_PNG: - status = cairo_surface_write_to_png_stream(surface, write_func, stdout); - break; - case GRIM_FILETYPE_JPEG: -#if HAVE_JPEG - status = cairo_surface_write_to_jpeg_stream(surface, write_func, - stdout, jpeg_quality); - break; -#else - abort(); -#endif - } + file = stdout; } else { - switch (output_filetype) { - case GRIM_FILETYPE_PPM: - status = cairo_surface_write_to_ppm(surface, output_filepath); - break; - case GRIM_FILETYPE_PNG: - status = cairo_surface_write_to_png(surface, output_filepath); - break; - case GRIM_FILETYPE_JPEG: + file = fopen(output_filepath, "w"); + if (!file) { + fprintf(stderr, "Failed to open file '%s' for writing: %s\n", + output_filepath, strerror(errno)); + return EXIT_FAILURE; + } + } + + int ret = 0; + switch (output_filetype) { + case GRIM_FILETYPE_PPM: + ret = write_to_ppm_stream(image, file); + break; + case GRIM_FILETYPE_PNG: + ret = write_to_png_stream(image, file, png_level); + break; + case GRIM_FILETYPE_JPEG: #if HAVE_JPEG - status = cairo_surface_write_to_jpeg( - surface, output_filepath, jpeg_quality); - break; + ret = write_to_jpeg_stream(image, file, jpeg_quality); + break; #else - abort(); + abort(); #endif - } } - if (status != CAIRO_STATUS_SUCCESS) { - fprintf(stderr, "%s\n", cairo_status_to_string(status)); - if (status == CAIRO_STATUS_WRITE_ERROR && strlen(output_filepath) > NAME_MAX) { - fprintf(stderr, "Hint: Output filepath length may be too long for your filesystem."); - } + if (ret == -1) { + // Error messages will be printed at the source return EXIT_FAILURE; } - cairo_surface_destroy(surface); + if (strcmp(output_filename, "-") != 0) { + fclose(file); + } + + free(output_filepath); + pixman_image_unref(image); struct grim_output *output_tmp; wl_list_for_each_safe(output, output_tmp, &state.outputs, link) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/meson.build new/grim-1.4.0/meson.build --- old/grim-1.3.2/meson.build 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/meson.build 2022-02-09 00:20:30.000000000 +0100 @@ -1,10 +1,10 @@ project( - 'grim', - 'c', -version : '1.3.2', -license : 'MIT', -meson_version : '>=0.48.0', -default_options : ['c_std=c11', 'warning_level=3', 'werror=true'] + 'grim', + 'c', + version: '1.4.0', + license: 'MIT', + meson_version: '>=0.59.0', + default_options: ['c_std=c11', 'warning_level=3', 'werror=true'], ) add_project_arguments('-Wno-unused-parameter', language: 'c') @@ -13,9 +13,10 @@ cc = meson.get_compiler('c') -cairo = dependency('cairo') +png = dependency('libpng') jpeg = dependency('libjpeg', required: get_option('jpeg')) math = cc.find_library('m') +pixman = dependency('pixman-1') realtime = cc.find_library('rt') wayland_client = dependency('wayland-client') wayland_protos = dependency('wayland-protocols', version: '>=1.14') @@ -24,6 +25,9 @@ add_project_arguments('-DHAVE_JPEG', language: 'c') endif +is_le = host_machine.endian() == 'little' +add_project_arguments('-DGRIM_LITTLE_ENDIAN=@0@'.format(is_le.to_int()), language: 'c') + subdir('contrib/completions') subdir('protocol') @@ -33,19 +37,21 @@ 'main.c', 'output-layout.c', 'render.c', - 'cairo_ppm.c', + 'write_ppm.c', + 'write_png.c', ] grim_deps = [ - cairo, client_protos, math, + pixman, + png, realtime, wayland_client, ] if jpeg.found() - grim_files += ['cairo_jpg.c'] + grim_files += ['write_jpg.c'] grim_deps += [jpeg] endif @@ -60,12 +66,8 @@ scdoc = find_program('scdoc', required: get_option('man-pages')) if scdoc.found() - sh = find_program('sh') - man_pages = ['grim.1.scd'] - mandir = get_option('mandir') - foreach src : man_pages topic = src.split('.')[0] section = src.split('.')[1] @@ -75,11 +77,11 @@ output, input: src, output: output, - command: [ - sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.path(), output) - ], + command: scdoc, + feed: true, + capture: true, install: true, - install_dir: '@0@/man@1@'.format(mandir, section) + install_dir: '@0@/man@1@'.format(get_option('mandir'), section), ) endforeach endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/meson_options.txt new/grim-1.4.0/meson_options.txt --- old/grim-1.3.2/meson_options.txt 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/meson_options.txt 2022-02-09 00:20:30.000000000 +0100 @@ -1,3 +1,4 @@ option('jpeg', type: 'feature', value: 'auto', description: 'Enable JPEG support') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('fish-completions', type: 'boolean', value: false, description: 'Install fish completions') +option('bash-completions', type: 'boolean', value: false, description: 'Install bash completions') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/protocol/meson.build new/grim-1.4.0/protocol/meson.build --- old/grim-1.3.2/protocol/meson.build 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/protocol/meson.build 2022-02-09 00:20:30.000000000 +0100 @@ -1,4 +1,4 @@ -wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') +wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/render.c new/grim-1.4.0/render.c --- old/grim-1.3.2/render.c 2021-04-17 19:41:14.000000000 +0200 +++ new/grim-1.4.0/render.c 2022-02-09 00:20:30.000000000 +0100 @@ -1,69 +1,139 @@ #include <assert.h> +#include <math.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include <pixman.h> #include "buffer.h" #include "output-layout.h" #include "render.h" -static bool convert_buffer(struct grim_buffer *buffer) { - // Formats are little-endian - switch (buffer->format) { +static pixman_format_code_t get_pixman_format(enum wl_shm_format wl_fmt) { + switch (wl_fmt) { +#if GRIM_LITTLE_ENDIAN + case WL_SHM_FORMAT_RGB332: + return PIXMAN_r3g3b2; + case WL_SHM_FORMAT_BGR233: + return PIXMAN_b2g3r3; + case WL_SHM_FORMAT_ARGB4444: + return PIXMAN_a4r4g4b4; + case WL_SHM_FORMAT_XRGB4444: + return PIXMAN_x4r4g4b4; + case WL_SHM_FORMAT_ABGR4444: + return PIXMAN_a4b4g4r4; + case WL_SHM_FORMAT_XBGR4444: + return PIXMAN_x4b4g4r4; + case WL_SHM_FORMAT_ARGB1555: + return PIXMAN_a1r5g5b5; + case WL_SHM_FORMAT_XRGB1555: + return PIXMAN_x1r5g5b5; + case WL_SHM_FORMAT_ABGR1555: + return PIXMAN_a1b5g5r5; + case WL_SHM_FORMAT_XBGR1555: + return PIXMAN_x1b5g5r5; + case WL_SHM_FORMAT_RGB565: + return PIXMAN_r5g6b5; + case WL_SHM_FORMAT_BGR565: + return PIXMAN_b5g6r5; + case WL_SHM_FORMAT_RGB888: + return PIXMAN_r8g8b8; + case WL_SHM_FORMAT_BGR888: + return PIXMAN_b8g8r8; case WL_SHM_FORMAT_ARGB8888: + return PIXMAN_a8r8g8b8; case WL_SHM_FORMAT_XRGB8888: - // Natively supported by Cairo - return true; + return PIXMAN_x8r8g8b8; case WL_SHM_FORMAT_ABGR8888: - case WL_SHM_FORMAT_XBGR8888:; - // ABGR -> ARGB - uint8_t *data = buffer->data; - for (int i = 0; i < buffer->height; ++i) { - for (int j = 0; j < buffer->width; ++j) { - uint32_t *px = (uint32_t *)(data + i * buffer->stride + j * 4); - uint8_t a = (*px >> 24) & 0xFF; - uint8_t b = (*px >> 16) & 0xFF; - uint8_t g = (*px >> 8) & 0xFF; - uint8_t r = *px & 0xFF; - *px = (a << 24) | (r << 16) | (g << 8) | b; - } - } - if (buffer->format == WL_SHM_FORMAT_ABGR8888) { - buffer->format = WL_SHM_FORMAT_ARGB8888; - } else { - buffer->format = WL_SHM_FORMAT_XRGB8888; - } - return true; + return PIXMAN_a8b8g8r8; + case WL_SHM_FORMAT_XBGR8888: + return PIXMAN_x8b8g8r8; + case WL_SHM_FORMAT_BGRA8888: + return PIXMAN_b8g8r8a8; + case WL_SHM_FORMAT_BGRX8888: + return PIXMAN_b8g8r8x8; + case WL_SHM_FORMAT_RGBA8888: + return PIXMAN_r8g8b8a8; + case WL_SHM_FORMAT_RGBX8888: + return PIXMAN_r8g8b8x8; + case WL_SHM_FORMAT_ARGB2101010: + return PIXMAN_a2r10g10b10; + case WL_SHM_FORMAT_ABGR2101010: + return PIXMAN_a2b10g10r10; + case WL_SHM_FORMAT_XRGB2101010: + return PIXMAN_x2r10g10b10; + case WL_SHM_FORMAT_XBGR2101010: + return PIXMAN_x2b10g10r10; +#else + case WL_SHM_FORMAT_ARGB8888: + return PIXMAN_b8g8r8a8; + case WL_SHM_FORMAT_XRGB8888: + return PIXMAN_b8g8r8x8; + case WL_SHM_FORMAT_ABGR8888: + return PIXMAN_r8g8b8a8; + case WL_SHM_FORMAT_XBGR8888: + return PIXMAN_r8g8b8x8; + case WL_SHM_FORMAT_BGRA8888: + return PIXMAN_a8r8g8b8; + case WL_SHM_FORMAT_BGRX8888: + return PIXMAN_x8r8g8b8; + case WL_SHM_FORMAT_RGBA8888: + return PIXMAN_a8b8g8r8; + case WL_SHM_FORMAT_RGBX8888: + return PIXMAN_x8b8g8r8; +#endif default: - fprintf(stderr, "unsupported format %d\n", buffer->format); - return false; + return 0; } - abort(); } -static cairo_format_t get_cairo_format(enum wl_shm_format wl_fmt) { - switch (wl_fmt) { - case WL_SHM_FORMAT_ARGB8888: - return CAIRO_FORMAT_ARGB32; - case WL_SHM_FORMAT_XRGB8888: - return CAIRO_FORMAT_RGB24; - default: - return CAIRO_FORMAT_INVALID; +static void compute_composite_region(const struct pixman_f_transform *out2com, + int output_width, int output_height, struct grim_box *dest, + bool *grid_aligned) { + struct pixman_transform o2c_fixedpt; + pixman_transform_from_pixman_f_transform(&o2c_fixedpt, out2com); + + pixman_fixed_t w = pixman_int_to_fixed(output_width); + pixman_fixed_t h = pixman_int_to_fixed(output_height); + struct pixman_vector corners[4] = { + {{0, 0, pixman_fixed_1}}, + {{w, 0, pixman_fixed_1}}, + {{0, h, pixman_fixed_1}}, + {{w, h, pixman_fixed_1}}, + }; + + pixman_fixed_t x_min = INT32_MAX, x_max = INT32_MIN, + y_min = INT32_MAX, y_max = INT32_MIN; + for (int i = 0; i < 4; i++) { + pixman_transform_point(&o2c_fixedpt, &corners[i]); + x_min = corners[i].vector[0] < x_min ? corners[i].vector[0] : x_min; + x_max = corners[i].vector[0] > x_max ? corners[i].vector[0] : x_max; + y_min = corners[i].vector[1] < y_min ? corners[i].vector[1] : y_min; + y_max = corners[i].vector[1] > y_max ? corners[i].vector[1] : y_max; } - abort(); + + *grid_aligned = pixman_fixed_frac(x_min) == 0 && + pixman_fixed_frac(x_max) == 0 && + pixman_fixed_frac(y_min) == 0 && + pixman_fixed_frac(y_max) == 0; + + int32_t x1 = pixman_fixed_to_int(pixman_fixed_floor(x_min)); + int32_t x2 = pixman_fixed_to_int(pixman_fixed_ceil(x_max)); + int32_t y1 = pixman_fixed_to_int(pixman_fixed_floor(y_min)); + int32_t y2 = pixman_fixed_to_int(pixman_fixed_ceil(y_max)); + *dest = (struct grim_box) { + .x = x1, + .y = y1, + .width = x2 - x1, + .height = y2 - y1 + }; } -cairo_surface_t *render(struct grim_state *state, struct grim_box *geometry, +pixman_image_t *render(struct grim_state *state, struct grim_box *geometry, double scale) { - cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, - geometry->width * scale, geometry->height * scale); - cairo_t *cairo = cairo_create(surface); - - // Clear - cairo_save(cairo); - cairo_set_source_rgba(cairo, 0, 0, 0, 0); - cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); - cairo_paint(cairo); - cairo_restore(cairo); + pixman_image_t *common_image = pixman_image_create_bits(PIXMAN_a8r8g8b8, + geometry->width * scale, geometry->height * scale, + NULL, 0); struct grim_output *output; wl_list_for_each(output, &state->outputs, link) { @@ -72,13 +142,13 @@ continue; } - if (!convert_buffer(buffer)) { + pixman_format_code_t pixman_fmt = get_pixman_format(buffer->format); + if (!pixman_fmt) { + fprintf(stderr, "unsupported format %d = 0x%08x\n", + buffer->format, buffer->format); return NULL; } - cairo_format_t cairo_fmt = get_cairo_format(buffer->format); - assert(cairo_fmt != CAIRO_FORMAT_INVALID); - int32_t output_x = output->logical_geometry.x - geometry->x; int32_t output_y = output->logical_geometry.y - geometry->y; int32_t output_width = output->logical_geometry.width; @@ -93,40 +163,92 @@ int output_flipped_y = output->screencopy_frame_flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT ? -1 : 1; - cairo_surface_t *output_surface = cairo_image_surface_create_for_data( - buffer->data, cairo_fmt, buffer->width, buffer->height, - buffer->stride); - cairo_pattern_t *output_pattern = - cairo_pattern_create_for_surface(output_surface); - - // All transformations are in pattern-local coordinates - cairo_matrix_t matrix; - cairo_matrix_init_identity(&matrix); - cairo_matrix_translate(&matrix, - (double)output->geometry.width / 2, - (double)output->geometry.height / 2); - cairo_matrix_scale(&matrix, - (double)raw_output_width / output_width, - (double)raw_output_height / output_height * output_flipped_y); - cairo_matrix_rotate(&matrix, -get_output_rotation(output->transform)); - cairo_matrix_scale(&matrix, output_flipped_x, 1); - cairo_matrix_translate(&matrix, - -(double)output_width / 2, - -(double)output_height / 2); - cairo_matrix_translate(&matrix, -output_x, -output_y); - cairo_matrix_scale(&matrix, 1 / scale, 1 / scale); - cairo_pattern_set_matrix(output_pattern, &matrix); - - cairo_pattern_set_filter(output_pattern, CAIRO_FILTER_BEST); + pixman_image_t *output_image = pixman_image_create_bits( + pixman_fmt, buffer->width, buffer->height, + buffer->data, buffer->stride); + if (!output_image) { + fprintf(stderr, "Failed to create image\n"); + return NULL; + } - cairo_set_source(cairo, output_pattern); - cairo_pattern_destroy(output_pattern); + // The transformation `out2com` will send a pixel in the output_image + // to one in the common_image + struct pixman_f_transform out2com; + pixman_f_transform_init_identity(&out2com); + pixman_f_transform_translate(&out2com, NULL, + -(double)output->geometry.width / 2, + -(double)output->geometry.height / 2); + pixman_f_transform_scale(&out2com, NULL, + (double)output_width / raw_output_width, + (double)output_height * output_flipped_y / raw_output_height); + pixman_f_transform_rotate(&out2com, NULL, + round(cos(get_output_rotation(output->transform))), + round(sin(get_output_rotation(output->transform)))); + pixman_f_transform_scale(&out2com, NULL, output_flipped_x, 1); + pixman_f_transform_translate(&out2com, NULL, + (double)output_width / 2, + (double)output_height / 2); + pixman_f_transform_translate(&out2com, NULL, output_x, output_y); + pixman_f_transform_scale(&out2com, NULL, scale, scale); + + struct grim_box composite_dest; + bool grid_aligned; + compute_composite_region(&out2com, buffer->width, + buffer->height, &composite_dest, &grid_aligned); + + pixman_f_transform_translate(&out2com, NULL, + -composite_dest.x, -composite_dest.y); + + struct pixman_f_transform com2out; + pixman_f_transform_invert(&com2out, &out2com); + struct pixman_transform c2o_fixedpt; + pixman_transform_from_pixman_f_transform(&c2o_fixedpt, &com2out); + pixman_image_set_transform(output_image, &c2o_fixedpt); + + double x_scale = fmax(fabs(out2com.m[0][0]), fabs(out2com.m[0][1])); + double y_scale = fmax(fabs(out2com.m[1][0]), fabs(out2com.m[1][1])); + if (x_scale >= 0.75 && y_scale >= 0.75) { + // Bilinear scaling is relatively fast and gives decent + // results for upscaling and light downscaling + pixman_image_set_filter(output_image, + PIXMAN_FILTER_BILINEAR, NULL, 0); + } else { + // When downscaling, convolve the output_image so that each + // pixel in the common_image collects colors from a region + // of size roughly 1/x_scale*1/y_scale in the output_image + int n_values = 0; + pixman_fixed_t *conv = pixman_filter_create_separable_convolution( + &n_values, + pixman_double_to_fixed(fmax(1., 1. / x_scale)), + pixman_double_to_fixed(fmax(1., 1. / y_scale)), + PIXMAN_KERNEL_IMPULSE, PIXMAN_KERNEL_IMPULSE, + PIXMAN_KERNEL_LANCZOS2, PIXMAN_KERNEL_LANCZOS2, + 2, 2); + pixman_image_set_filter(output_image, + PIXMAN_FILTER_SEPARABLE_CONVOLUTION, conv, n_values); + free(conv); + } - cairo_paint(cairo); + bool overlapping = false; + struct grim_output *other_output; + wl_list_for_each(other_output, &state->outputs, link) { + if (output != other_output && intersect_box(&output->logical_geometry, + &other_output->logical_geometry)) { + overlapping = true; + } + } + /* OP_SRC copies the image instead of blending it, and is much + * faster, but this a) is incorrect in the weird case where + * logical outputs overlap and are partially transparent b) + * can draw the edge between two outputs incorrectly if that + * edge is not exactly grid aligned in the common image */ + pixman_op_t op = (grid_aligned && !overlapping) ? PIXMAN_OP_SRC : PIXMAN_OP_OVER; + pixman_image_composite32(op, output_image, NULL, common_image, + 0, 0, 0, 0, composite_dest.x, composite_dest.y, + composite_dest.width, composite_dest.height); - cairo_surface_destroy(output_surface); + pixman_image_unref(output_image); } - cairo_destroy(cairo); - return surface; + return common_image; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/write_jpg.c new/grim-1.4.0/write_jpg.c --- old/grim-1.3.2/write_jpg.c 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.4.0/write_jpg.c 2022-02-09 00:20:30.000000000 +0100 @@ -0,0 +1,63 @@ +/** + * @author Bernhard R. Fischer, 4096R/8E24F29D b...@abenteuerland.at + * @license This code is free software. Do whatever you like to do with it. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> +#include <fcntl.h> +#include <limits.h> +#include <unistd.h> +#include <jpeglib.h> + +#include "write_jpg.h" + +int write_to_jpeg_stream(pixman_image_t *image, FILE *stream, int quality) { + pixman_format_code_t format = pixman_image_get_format(image); + assert(format == PIXMAN_a8r8g8b8 || format == PIXMAN_x8r8g8b8); + + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + JSAMPROW row_pointer[1]; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + + unsigned char *data = NULL; + unsigned long len = 0; + jpeg_mem_dest(&cinfo, &data, &len); + cinfo.image_width = pixman_image_get_width(image); + cinfo.image_height = pixman_image_get_height(image); + if (format == PIXMAN_a8r8g8b8) { + cinfo.in_color_space = JCS_EXT_BGRA; + } else { + cinfo.in_color_space = JCS_EXT_BGRX; + } + cinfo.input_components = 4; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + + jpeg_start_compress(&cinfo, TRUE); + + while (cinfo.next_scanline < cinfo.image_height) { + row_pointer[0] = (unsigned char *)pixman_image_get_data(image) + + (cinfo.next_scanline * pixman_image_get_stride(image)); + (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + size_t written = fwrite(data, 1, len, stream); + if (written < len) { + free(data); + fprintf(stderr, "Failed to write jpg; only %zu of %zu bytes written\n", + written, len); + return -1; + } + free(data); + return 0; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/write_png.c new/grim-1.4.0/write_png.c --- old/grim-1.3.2/write_png.c 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.4.0/write_png.c 2022-02-09 00:20:30.000000000 +0100 @@ -0,0 +1,123 @@ +#include <assert.h> +#include <png.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "write_png.h" + +static void pack_row32(uint8_t *restrict row_out, const uint32_t *restrict row_in, + size_t width, bool fully_opaque) { + for (size_t x = 0; x < width; x++) { + uint8_t b = (row_in[x] >> 0) & 0xff; + uint8_t g = (row_in[x] >> 8) & 0xff; + uint8_t r = (row_in[x] >> 16) & 0xff; + uint8_t a = (row_in[x] >> 24) & 0xff; + + // Unpremultiply pixels, if necessary. In practice, few images + // made by grim will have many pixels with fractional alpha + if (!fully_opaque && (a != 0 && a != 255)) { + uint32_t inv = (0xff << 16) / a; + uint32_t sr = r * inv; + r = sr > (0xff << 16) ? 0xff : (sr >> 16); + uint32_t sg = g * inv; + g = sg > (0xff << 16) ? 0xff : (sg >> 16); + uint32_t sb = b * inv; + b = sb > (0xff << 16) ? 0xff : (sb >> 16); + } + + *row_out++ = r; + *row_out++ = g; + *row_out++ = b; + if (!fully_opaque) { + *row_out++ = a; + } + } +} + +int write_to_png_stream(pixman_image_t *image, FILE *stream, + int comp_level) { + pixman_format_code_t format = pixman_image_get_format(image); + assert(format == PIXMAN_a8r8g8b8 || format == PIXMAN_x8r8g8b8); + + int width = pixman_image_get_width(image); + int height = pixman_image_get_height(image); + int stride = pixman_image_get_stride(image); + const unsigned char *data = (unsigned char *)pixman_image_get_data(image); + + bool fully_opaque = true; + if (format == PIXMAN_a8r8g8b8) { + for (int y = 0; y < height; y++) { + const uint32_t *row = (const uint32_t *)(data + y * stride); + for (int x = 0; x < width; x++) { + if ((row[x] >> 24) != 0xff) { + fully_opaque = false; + } + } + } + } + int color_type = fully_opaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA; + int bit_depth = 8; + + uint8_t *tmp_row = calloc(width, 4); + if (!tmp_row) { + fprintf(stderr, "failed to allocate temp row\n"); + return -1; + } + + int ret = 0; + png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + png_info *info = NULL; + if (!png) { + fprintf(stderr, "failed to allocate png struct\n"); + ret = -1; + goto cleanup; + } + info = png_create_info_struct(png); + if (!info) { + fprintf(stderr, "failed to allocate png write struct\n"); + ret = -1; + goto cleanup; + } + +#ifdef PNG_SETJMP_SUPPORTED + if (setjmp(png_jmpbuf(png))) { + fprintf(stderr, "failed to write png\n"); + ret = -1; + goto cleanup; + } +#endif + + png_init_io(png, stream); + + png_set_IHDR(png, info, width, height, bit_depth, color_type, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + png_write_info(png, info); + + // If the level is zero (no compression), filtering will be unnecessary + png_set_compression_level(png, comp_level); + if (comp_level == 0) { + png_set_filter(png, 0, PNG_NO_FILTERS); + } else { + png_set_filter(png, 0, PNG_ALL_FILTERS); + } + + for (int y = 0; y < height; y++) { + const uint32_t *row = (const uint32_t *)(data + y * stride); + pack_row32(tmp_row, row, width, fully_opaque); + png_write_row(png, tmp_row); + } + + png_write_end(png, NULL); + +cleanup: + if (info) { + png_destroy_info_struct(png, &info); + } + if (png) { + png_destroy_write_struct(&png, NULL); + } + free(tmp_row); + return ret; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/grim-1.3.2/write_ppm.c new/grim-1.4.0/write_ppm.c --- old/grim-1.3.2/write_ppm.c 1970-01-01 01:00:00.000000000 +0100 +++ new/grim-1.4.0/write_ppm.c 2022-02-09 00:20:30.000000000 +0100 @@ -0,0 +1,56 @@ +#include <assert.h> +#include <fcntl.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "write_ppm.h" + +int write_to_ppm_stream(pixman_image_t *image, FILE *stream) { + // 256 bytes ought to be enough for everyone + char header[256]; + + int width = pixman_image_get_width(image); + int height = pixman_image_get_height(image); + + int header_len = snprintf(header, sizeof(header), "P6\n%d %d\n255\n", width, height); + assert(header_len <= (int)sizeof(header)); + + size_t len = header_len + width * height * 3; + unsigned char *data = malloc(len); + unsigned char *buffer = data; + + // We _do_not_ include the null byte + memcpy(buffer, header, header_len); + buffer += header_len; + + pixman_format_code_t format = pixman_image_get_format(image); + assert(format == PIXMAN_a8r8g8b8 || format == PIXMAN_x8r8g8b8); + + // Both formats are native-endian 32-bit ints + uint32_t *pixels = pixman_image_get_data(image); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + uint32_t p = *pixels++; + // RGB order + *buffer++ = (p >> 16) & 0xff; + *buffer++ = (p >> 8) & 0xff; + *buffer++ = (p >> 0) & 0xff; + } + } + + size_t written = fwrite(data, 1, len, stream); + if (written < len) { + free(data); + fprintf(stderr, "Failed to write ppm; only %zu of %zu bytes written\n", + written, len); + return -1; + } + free(data); + return 0; +}