1# This file is part of Xpra. 2# Copyright (C) 2021 Antoine Martin <antoine@xpra.org> 3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 4# later version. See the file COPYING for details. 5 6#cython: wraparound=False 7 8from xpra.log import Logger 9log = Logger("decoder", "spng") 10 11from libc.stdint cimport uintptr_t, uint32_t, uint8_t 12from xpra.buffers.membuf cimport getbuf, MemBuf #pylint: disable=syntax-error 13from xpra.util import envint 14 15MAX_SIZE = envint("XPRA_SPNG_MAX_SIZE", 8192*8192) 16 17 18cdef extern from "Python.h": 19 int PyObject_GetBuffer(object obj, Py_buffer *view, int flags) 20 void PyBuffer_Release(Py_buffer *view) 21 int PyBUF_ANY_CONTIGUOUS 22 23cdef extern from "spng.h": 24 int SPNG_VERSION_MAJOR 25 int SPNG_VERSION_MINOR 26 int SPNG_VERSION_PATCH 27 28 int SPNG_DECODE_TRNS 29 30 enum spng_format: 31 SPNG_FMT_RGBA8 32 SPNG_FMT_RGBA16 33 SPNG_FMT_RGB8 34 35 enum spng_color_type: 36 SPNG_COLOR_TYPE_GRAYSCALE 37 SPNG_COLOR_TYPE_TRUECOLOR 38 SPNG_COLOR_TYPE_INDEXED 39 SPNG_COLOR_TYPE_GRAYSCALE_ALPHA 40 SPNG_COLOR_TYPE_TRUECOLOR_ALPHA 41 42 ctypedef struct spng_ctx: 43 pass 44 45 cdef struct spng_ihdr: 46 uint32_t height 47 uint32_t width 48 uint8_t bit_depth 49 uint8_t color_type 50 uint8_t compression_method 51 uint8_t filter_method 52 uint8_t interlace_method 53 54 const char *spng_version_string() 55 const char *spng_strerror(int err) 56 spng_ctx *spng_ctx_new(int flags) 57 void spng_ctx_free(spng_ctx *ctx) 58 int spng_get_ihdr(spng_ctx *ctx, spng_ihdr *ihdr) 59 int spng_set_png_buffer(spng_ctx *ctx, const void *buf, size_t size) nogil 60 int spng_decoded_image_size(spng_ctx *ctx, int fmt, size_t *len) nogil 61 int spng_decode_image(spng_ctx *ctx, void *out, size_t len, int fmt, int flags) nogil 62 63 64COLOR_TYPE_STR = { 65 SPNG_COLOR_TYPE_GRAYSCALE : "GRAYSCALE", 66 SPNG_COLOR_TYPE_TRUECOLOR : "TRUECOLOR", 67 SPNG_COLOR_TYPE_INDEXED : "INDEXED", 68 SPNG_COLOR_TYPE_GRAYSCALE_ALPHA : "GRAYSCALE_ALPHA", 69 SPNG_COLOR_TYPE_TRUECOLOR_ALPHA : "TRUECOLOR_ALPHA", 70 } 71 72 73def get_version(): 74 return (SPNG_VERSION_MAJOR, SPNG_VERSION_MINOR, SPNG_VERSION_PATCH) 75 76def get_encodings(): 77 return ("png", ) 78 79def get_error_str(int r): 80 s = spng_strerror(r) 81 return str(s) 82 83def check_error(int r, msg): 84 if r: 85 log_error(r, msg) 86 return r 87 88def log_error(int r, msg): 89 log.error("Error: %s", msg) 90 log.error(" code %i: %s", r, get_error_str(r)) 91 92def decompress(data): 93 cdef spng_ctx *ctx = spng_ctx_new(0) 94 if ctx==NULL: 95 raise Exception("failed to instantiate an spng context") 96 97 cdef Py_buffer py_buf 98 if PyObject_GetBuffer(data, &py_buf, PyBUF_ANY_CONTIGUOUS): 99 spng_ctx_free(ctx) 100 raise Exception("failed to read compressed data from %s" % type(data)) 101 102 cdef int r 103 def close(): 104 PyBuffer_Release(&py_buf) 105 spng_ctx_free(ctx) 106 107 if check_error(spng_set_png_buffer(ctx, py_buf.buf, py_buf.len), 108 "failed to set png buffer"): 109 close() 110 return None 111 112 cdef spng_ihdr ihdr 113 if check_error(spng_get_ihdr(ctx, &ihdr), 114 "failed to get ihdr"): 115 close() 116 return None 117 118 log("ihdr: %ix%i-%i, color-type=%s, compression-method=%#x, filter-method=%#x, interlace-method=%#x", 119 ihdr.width, ihdr.height, ihdr.bit_depth, COLOR_TYPE_STR.get(ihdr.color_type, ihdr.color_type), 120 ihdr.compression_method, ihdr.filter_method, ihdr.interlace_method) 121 122 cdef int flags = 0 123 cdef size_t out_size 124 cdef int fmt 125 if ihdr.color_type==SPNG_COLOR_TYPE_TRUECOLOR: 126 fmt = SPNG_FMT_RGB8 127 rgb_format = "RGB" 128 elif ihdr.color_type==SPNG_COLOR_TYPE_TRUECOLOR_ALPHA: 129 fmt = SPNG_FMT_RGBA8 130 rgb_format = "RGBA" 131 flags = SPNG_DECODE_TRNS 132 else: 133 raise ValueError("cannot handle color type %s" % COLOR_TYPE_STR.get(ihdr.color_type, ihdr.color_type)) 134 135 if check_error(spng_decoded_image_size(ctx, fmt, &out_size), 136 "failed to get decoded image size"): 137 close() 138 return None 139 if out_size>MAX_SIZE: 140 log.error("Error: spng image size %i is too big", out_size) 141 log.error(" maximum size supported is %i", MAX_SIZE) 142 close() 143 return None 144 145 cdef MemBuf membuf = getbuf(out_size) 146 cdef uintptr_t ptr = <uintptr_t> membuf.get_mem() 147 with nogil: 148 r = spng_decode_image(ctx, <void *> ptr, out_size, fmt, flags) 149 if check_error(r, "failed to decode image"): 150 close() 151 return None 152 153 close() 154 return memoryview(membuf), rgb_format, ihdr.width, ihdr.height 155 156 157def selftest(full=False): 158 log("spng version %s selftest" % (get_version(),)) 159 import binascii 160 from xpra.codecs.codec_checks import TEST_PICTURES #pylint: disable=import-outside-toplevel 161 for hexdata in TEST_PICTURES["png"]: 162 cdata = binascii.unhexlify(hexdata) 163 assert decompress(cdata) 164