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;
+}

Reply via email to