From 5eefd41059855ccc91d7b836ed678f6c8d70ff67 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 12 Aug 2020 07:48:47 +0530 Subject: [PATCH] Add support for displaying correct colors with PNG files that contain embedded ICC color profiles --- .github/workflows/ci.py | 2 +- bypy/linux/__main__.py | 2 +- bypy/macos/__main__.py | 1 + bypy/sources.json | 9 ++++++++ docs/build.rst | 1 + docs/changelog.rst | 7 ++++++ docs/graphics-protocol.rst | 7 +++--- kitty/png-reader.c | 45 +++++++++++++++++++++++++++++++++++--- setup.py | 4 +++- 9 files changed, 69 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.py b/.github/workflows/ci.py index 100f23933..534ba5536 100644 --- a/.github/workflows/ci.py +++ b/.github/workflows/ci.py @@ -36,7 +36,7 @@ def install_deps(): run('sudo apt-get update') run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev' ' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev' - ' libpng-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev') + ' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev') if is_bundle: install_bundle() else: diff --git a/bypy/linux/__main__.py b/bypy/linux/__main__.py index bbd9ab423..5ec85ac2f 100644 --- a/bypy/linux/__main__.py +++ b/bypy/linux/__main__.py @@ -26,7 +26,7 @@ kitty_constants = iv['kitty_constants'] def binary_includes(): return tuple(map(get_dll_path, ( - 'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', + 'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', # dont include freetype because fontconfig is closely coupled to it # and also distros often patch freetype diff --git a/bypy/macos/__main__.py b/bypy/macos/__main__.py index 21cc34422..4eac88666 100644 --- a/bypy/macos/__main__.py +++ b/bypy/macos/__main__.py @@ -262,6 +262,7 @@ class Freeze(object): 'z.1', 'harfbuzz.0', 'png16.16', + 'lcms2.2', 'crypto.1.0.0', 'ssl.1.0.0', ): diff --git a/bypy/sources.json b/bypy/sources.json index 842baf95f..06ba961d2 100644 --- a/bypy/sources.json +++ b/bypy/sources.json @@ -153,6 +153,15 @@ } }, + { + "name": "lcms2", + "unix": { + "filename": "lcms2-2.11.tar.gz", + "hash": "sha256:478c9c3938d7a91b1171de4616f8b04308a8676d73eadc19505b7ace41327f28", + "urls": ["https://github.com/mm2/Little-CMS/archive/2.11/{filename}"] + } + }, + { "name": "graphite", "os": "linux", diff --git a/docs/build.rst b/docs/build.rst index edacdb10f..36a4575f1 100644 --- a/docs/build.rst +++ b/docs/build.rst @@ -18,6 +18,7 @@ Run-time dependencies: * harfbuzz >= 1.5.0 * zlib * libpng + * liblcms2 * freetype (not needed on macOS) * fontconfig (not needed on macOS) * libcanberra (not needed on macOS) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6db282512..47d2cc3c8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,13 @@ Changelog |kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator. To update |kitty|, :doc:`follow the instructions `. +0.18.4 [2020-08-11] +------------------- + +- Add support for displaying correct colors with non-sRGB PNG files (Adds a + dependency on liblcms2) + + 0.18.3 [2020-08-11] ------------------- diff --git a/docs/graphics-protocol.rst b/docs/graphics-protocol.rst index 4c3abd01d..03c53b3f1 100644 --- a/docs/graphics-protocol.rst +++ b/docs/graphics-protocol.rst @@ -161,9 +161,10 @@ of transmitting paletted images. RGB and RGBA data ~~~~~~~~~~~~~~~~~~~ -In these formats the pixel data is stored directly as 3 or 4 bytes per pixel, respectively. -When specifying images in this format, the image dimensions **must** be sent in the control data. -For example:: +In these formats the pixel data is stored directly as 3 or 4 bytes per pixel, +respectively. The colors in the data **must** be in the *sRGB color space*. When +specifying images in this format, the image dimensions **must** be sent in the +control data. For example:: _Gf=24,s=10,v=20;\ diff --git a/kitty/png-reader.c b/kitty/png-reader.c index 93910f0cf..ecc5d65d3 100644 --- a/kitty/png-reader.c +++ b/kitty/png-reader.c @@ -7,8 +7,10 @@ #include "png-reader.h" #include "state.h" +#include +static cmsHPROFILE srgb_profile = NULL; struct fake_file { const uint8_t *buf; size_t sz, cur; }; static void @@ -64,13 +66,30 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz) { bit_depth = png_get_bit_depth(png, info); double image_gamma; int intent; + cmsHPROFILE input_profile = NULL; + cmsHTRANSFORM colorspace_transform = NULL; if (png_get_sRGB(png, info, &intent)) { // do nothing since we output sRGB } else if (png_get_gAMA(png, info, &image_gamma)) { if (image_gamma != 0 && fabs(image_gamma - 1.0/2.2) > 0.0001) png_set_gamma(png, 2.2, image_gamma); } else { - // do nothing since we don't know what gamma the source image is in - // TODO: handle images with color profiles + // Look for an embedded color profile + png_charp name; + int compression_type; + png_bytep profdata; + png_uint_32 proflen; + if (png_get_iCCP(png, info, &name, &compression_type, &profdata, &proflen) & PNG_INFO_iCCP) { + input_profile = cmsOpenProfileFromMem(profdata, proflen); + if (input_profile) { + if (!srgb_profile) { + srgb_profile = cmsCreate_sRGBProfile(); + if (!srgb_profile) ABRT(ENOMEM, "Out of memory allocating sRGB colorspace profile"); + } + colorspace_transform = cmsCreateTransform( + input_profile, TYPE_RGBA_8, srgb_profile, TYPE_RGBA_8, INTENT_PERCEPTUAL, 0); + + } + } } // Ensure we get RGBA data out of libpng @@ -93,9 +112,17 @@ inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz) { if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG"); d->row_pointers = malloc(d->height * sizeof(png_bytep)); if (d->row_pointers == NULL) ABRT(ENOMEM, "Out of memory allocating row_pointers buffer for PNG"); - for (int i = 0; i < d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes; + for (int i = 0; i < d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes * sizeof(png_byte); png_read_image(png, d->row_pointers); + if (colorspace_transform) { + for (int i = 0; i < d->height; i++) { + cmsDoTransform(colorspace_transform, d->row_pointers[i], d->row_pointers[i], d->width); + } + cmsDeleteTransform(colorspace_transform); + } + if (input_profile) cmsCloseProfile(input_profile); + d->ok = true; err: if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL); @@ -130,8 +157,20 @@ static PyMethodDef module_methods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; + +static void +unload(void) { + if (srgb_profile) cmsCloseProfile(srgb_profile); + srgb_profile = NULL; +} + bool init_png_reader(PyObject *module) { if (PyModule_AddFunctions(module, module_methods) != 0) return false; + if (Py_AtExit(unload) != 0) { + PyErr_SetString(PyExc_RuntimeError, "Failed to register the PNG library at exit handler"); + return false; + } + return true; } diff --git a/setup.py b/setup.py index 6e2d05b66..305608c34 100755 --- a/setup.py +++ b/setup.py @@ -329,6 +329,7 @@ def kitty_env() -> Env: cppflags.append('-DSECONDARY_VERSION={}'.format(version[1])) at_least_version('harfbuzz', 1, 5) cflags.extend(pkg_config('libpng', '--cflags-only-I')) + cflags.extend(pkg_config('lcms2', '--cflags-only-I')) if is_macos: font_libs = ['-framework', 'CoreText', '-framework', 'CoreGraphics'] # Apple deprecated OpenGL in Mojave (10.14) silence the endless @@ -342,7 +343,8 @@ def kitty_env() -> Env: pylib = get_python_flags(cflags) gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs') libpng = pkg_config('libpng', '--libs') - ans.ldpaths += pylib + font_libs + gl_libs + libpng + lcms2 = pkg_config('lcms2', '--libs') + ans.ldpaths += pylib + font_libs + gl_libs + libpng + lcms2 if is_macos: ans.ldpaths.extend('-framework Cocoa'.split()) elif not is_openbsd: