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 time import time 9from xpra.log import Logger 10log = Logger("decoder", "spng") 11 12from libc.stdint cimport uintptr_t, uint32_t, uint8_t 13from xpra.buffers.membuf cimport makebuf, MemBuf, buffer_context #pylint: disable=syntax-error 14from xpra.util import envbool 15from xpra.net.compression import Compressed 16 17SAVE_TO_FILE = envbool("XPRA_SAVE_TO_FILE", False) 18 19 20cdef extern from "zconf.h": 21 int MAX_MEM_LEVEL 22 23cdef extern from "zlib.h": 24 int Z_HUFFMAN_ONLY 25 26cdef extern from "spng.h": 27 int SPNG_VERSION_MAJOR 28 int SPNG_VERSION_MINOR 29 int SPNG_VERSION_PATCH 30 31 int SPNG_CTX_ENCODER 32 33 int SPNG_FILTER_NONE 34 int SPNG_INTERLACE_NONE 35 36 enum spng_encode_flags: 37 SPNG_ENCODE_PROGRESSIVE 38 SPNG_ENCODE_FINALIZE 39 40 enum spng_filter_choice: 41 SPNG_DISABLE_FILTERING 42 SPNG_FILTER_CHOICE_NONE 43 SPNG_FILTER_CHOICE_SUB 44 SPNG_FILTER_CHOICE_UP 45 SPNG_FILTER_CHOICE_AVG 46 SPNG_FILTER_CHOICE_PAETH 47 SPNG_FILTER_CHOICE_ALL 48 49 enum spng_option: 50 SPNG_ENCODE_TO_BUFFER 51 SPNG_KEEP_UNKNOWN_CHUNKS 52 53 SPNG_IMG_COMPRESSION_LEVEL 54 55 SPNG_IMG_WINDOW_BITS 56 SPNG_IMG_MEM_LEVEL 57 SPNG_IMG_COMPRESSION_STRATEGY 58 59 SPNG_TEXT_COMPRESSION_LEVEL 60 SPNG_TEXT_WINDOW_BITS 61 SPNG_TEXT_MEM_LEVEL 62 SPNG_TEXT_COMPRESSION_STRATEGY 63 64 SPNG_FILTER_CHOICE 65 66 enum spng_format: 67 SPNG_FMT_RGBA8 68 SPNG_FMT_RGBA16 69 SPNG_FMT_RGB8 70 # Partially implemented, see documentation 71 SPNG_FMT_GA8 72 SPNG_FMT_GA16 73 SPNG_FMT_G8 74 SPNG_FMT_PNG #host-endian 75 SPNG_FMT_RAW #big-endian 76 77 enum spng_color_type: 78 SPNG_COLOR_TYPE_GRAYSCALE 79 SPNG_COLOR_TYPE_TRUECOLOR 80 SPNG_COLOR_TYPE_INDEXED 81 SPNG_COLOR_TYPE_GRAYSCALE_ALPHA 82 SPNG_COLOR_TYPE_TRUECOLOR_ALPHA 83 84 ctypedef struct spng_ctx: 85 pass 86 87 cdef struct spng_ihdr: 88 uint32_t height 89 uint32_t width 90 uint8_t bit_depth 91 uint8_t color_type 92 uint8_t compression_method 93 uint8_t filter_method 94 uint8_t interlace_method 95 96 const char *spng_version_string() 97 const char *spng_strerror(int err) 98 spng_ctx *spng_ctx_new(int flags) 99 void spng_ctx_free(spng_ctx *ctx) 100 int spng_set_option(spng_ctx *ctx, spng_option option, int value) 101 int spng_set_ihdr(spng_ctx *ctx, spng_ihdr *ihdr) 102 int spng_encode_image(spng_ctx *ctx, const void *img, size_t len, int fmt, int flags) nogil 103 void *spng_get_png_buffer(spng_ctx *ctx, size_t *len, int *error) 104 105 106 107COLOR_TYPE_STR = { 108 SPNG_COLOR_TYPE_GRAYSCALE : "GRAYSCALE", 109 SPNG_COLOR_TYPE_TRUECOLOR : "TRUECOLOR", 110 SPNG_COLOR_TYPE_INDEXED : "INDEXED", 111 SPNG_COLOR_TYPE_GRAYSCALE_ALPHA : "GRAYSCALE_ALPHA", 112 SPNG_COLOR_TYPE_TRUECOLOR_ALPHA : "TRUECOLOR_ALPHA", 113 } 114 115 116def get_version(): 117 return (SPNG_VERSION_MAJOR, SPNG_VERSION_MINOR, SPNG_VERSION_PATCH) 118 119def get_type(): 120 return "spng" 121 122def get_encodings(): 123 return ("png", ) 124 125def get_error_str(int r): 126 b = spng_strerror(r) 127 return b.decode() 128 129def check_error(int r, msg): 130 if r: 131 log_error(r, msg) 132 return r 133 134def log_error(int r, msg): 135 log.error("Error: %s", msg) 136 log.error(" code %i: %s", r, get_error_str(r)) 137 138INPUT_FORMATS = "RGBA", "RGB" 139 140def encode(coding, image, options=None): 141 assert coding=="png" 142 options = options or {} 143 cdef int grayscale = options.get("grayscale", 0) 144 cdef int speed = options.get("speed", 50) 145 cdef int width = image.get_width() 146 cdef int height = image.get_height() 147 cdef int rowstride = image.get_rowstride() 148 cdef int scaled_width = options.get("scaled-width", width) 149 cdef int scaled_height = options.get("scaled-height", height) 150 cdef char resize = scaled_width!=width or scaled_height!=height 151 152 rgb_format = image.get_pixel_format() 153 alpha = options.get("alpha", True) 154 if rgb_format not in INPUT_FORMATS or (resize and len(rgb_format)!=4) or rowstride!=width*len(rgb_format): 155 #best to restride before byte-swapping to trim extra unused data: 156 if rowstride!=width*len(rgb_format): 157 image.restride(width*len(rgb_format)) 158 from xpra.codecs.argb.argb import argb_swap #@UnresolvedImport 159 if not argb_swap(image, INPUT_FORMATS, supports_transparency=alpha): 160 log("spng: argb_swap failed to convert %s to a suitable format: %s" % ( 161 rgb_format, INPUT_FORMATS)) 162 log("spng converted %s to %s", rgb_format, image) 163 rgb_format = image.get_pixel_format() 164 rowstride = image.get_rowstride() 165 166 if resize: 167 from xpra.codecs.argb.scale import scale_image 168 image = scale_image(image, scaled_width, scaled_height) 169 log("jpeg scaled image: %s", image) 170 171 assert rgb_format in ("RGB", "RGBA", "RGBX"), "unsupported input pixel format %s" % rgb_format 172 173 cdef spng_ctx *ctx = spng_ctx_new(SPNG_CTX_ENCODER) 174 if ctx==NULL: 175 raise Exception("failed to instantiate an spng context") 176 177 cdef spng_ihdr ihdr 178 ihdr.width = scaled_width 179 ihdr.height = scaled_height 180 ihdr.bit_depth = 8 181 ihdr.color_type = SPNG_COLOR_TYPE_TRUECOLOR 182 if rgb_format=="RGBA": 183 ihdr.color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA 184 ihdr.compression_method = 0 185 ihdr.filter_method = 0 186 ihdr.interlace_method = SPNG_INTERLACE_NONE 187 if check_error(spng_set_ihdr(ctx, &ihdr), 188 "failed to set encode-to-buffer option"): 189 spng_ctx_free(ctx) 190 return None 191 192 cdef int clevel = 1 193 if check_error(spng_set_option(ctx, SPNG_IMG_COMPRESSION_LEVEL, clevel), 194 "failed to set compression level"): 195 spng_ctx_free(ctx) 196 return None 197 if check_error(spng_set_option(ctx, SPNG_TEXT_WINDOW_BITS, 15), 198 "failed to set window bits"): 199 spng_ctx_free(ctx) 200 return None 201 if check_error(spng_set_option(ctx, SPNG_IMG_COMPRESSION_STRATEGY, Z_HUFFMAN_ONLY), 202 "failed to set compression strategy"): 203 spng_ctx_free(ctx) 204 return None 205 if check_error(spng_set_option(ctx, SPNG_IMG_MEM_LEVEL, MAX_MEM_LEVEL), 206 "failed to set mem level"): 207 spng_ctx_free(ctx) 208 return None 209 cdef int filter = SPNG_FILTER_CHOICE_NONE 210 if speed<30: 211 filter |= SPNG_FILTER_CHOICE_SUB 212 if check_error(spng_set_option(ctx, SPNG_FILTER_CHOICE, filter), 213 "failed to set filter choice"): 214 spng_ctx_free(ctx) 215 return None 216 217 if check_error(spng_set_option(ctx, SPNG_ENCODE_TO_BUFFER, 1), 218 "failed to set encode-to-buffer option"): 219 spng_ctx_free(ctx) 220 return None 221 222 cdef spng_format fmt = SPNG_FMT_PNG #SPNG_FMT_PNG 223 cdef int flags = SPNG_ENCODE_FINALIZE 224 cdef size_t data_len = 0 225 cdef uintptr_t data_ptr 226 cdef int r = 0 227 pixels = image.get_pixels() 228 assert len(pixels)>=ihdr.width*ihdr.height*len(rgb_format), \ 229 "pixel buffer is too small, expected %i bytes but got %i" % ( 230 ihdr.width*ihdr.height*4, len(pixels)) 231 with buffer_context(pixels) as bc: 232 data_ptr = <uintptr_t> int(bc) 233 data_len = len(bc) 234 #log("spng encode buffer %#x, len=%#x", <uintptr_t> data_ptr, data_len) 235 with nogil: 236 r = spng_encode_image(ctx, <const void*> data_ptr, data_len, fmt, flags) 237 if check_error(r, "failed to encode image"): 238 log.error(" %i bytes of %s pixel data", data_len, rgb_format) 239 log.error(" for %s", image) 240 spng_ctx_free(ctx) 241 return None 242 243 cdef int error 244 cdef size_t png_len 245 cdef void *png_data = spng_get_png_buffer(ctx, &png_len, &error) 246 if check_error(error, 247 "failed get png buffer"): 248 spng_ctx_free(ctx) 249 return None 250 if png_data==NULL: 251 log.error("Error: spng buffer is NULL") 252 spng_ctx_free(ctx) 253 return None 254 255 cdef membuf = makebuf(png_data, png_len) 256 spng_ctx_free(ctx) 257 cdata = memoryview(membuf) 258 if SAVE_TO_FILE: # pragma: no cover 259 filename = "./%s.png" % time() 260 with open(filename, "wb") as f: 261 f.write(cdata) 262 log.info("saved png to %s", filename) 263 return coding, Compressed(coding, cdata), {}, width, height, 0, len(rgb_format)*8 264 265 266def selftest(full=False): 267 log("spng version %s selftest" % (get_version(),)) 268 from xpra.codecs.codec_checks import make_test_image 269 for rgb_format in ("RGBA", "RGB", "BGRA", "BGRX"): 270 image = make_test_image(rgb_format, 1024, 768) 271 assert encode("png", image) 272