1# This file is part of Xpra. 2# Copyright (C) 2014-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 6import os 7from time import monotonic, time 8 9from libc.stdint cimport uint8_t, uint32_t, uintptr_t #pylint: disable=syntax-error 10from libc.stdlib cimport free #pylint: disable=syntax-error 11from libc.string cimport memset #pylint: disable=syntax-error 12from xpra.buffers.membuf cimport buffer_context 13 14from xpra.net.compression import Compressed 15from xpra.util import envbool, envint 16from xpra.log import Logger 17log = Logger("encoder", "webp") 18 19 20cdef int SAVE_TO_FILE = envbool("XPRA_SAVE_TO_FILE") 21cdef int LOG_CONFIG = envbool("XPRA_WEBP_LOG_CONFIG", False) 22cdef int WEBP_THREADING = envbool("XPRA_WEBP_THREADING", True) 23cdef int SUBSAMPLING_THRESHOLD = envint("XPRA_WEBP_SUBSAMPLING_THRESHOLD", 80) 24 25cdef inline int MIN(int a, int b): 26 if a<=b: 27 return a 28 return b 29cdef inline int MAX(int a, int b): 30 if a>=b: 31 return a 32 return b 33 34 35cdef extern from *: 36 ctypedef unsigned long size_t 37 38 39DEF WEBP_MAX_DIMENSION = 16383 40 41cdef extern from "webp/encode.h": 42 43 int WebPGetEncoderVersion() 44 45 ctypedef int WebPImageHint 46 # WebPImageHint: 47 WebPImageHint WEBP_HINT_DEFAULT #default preset 48 WebPImageHint WEBP_HINT_PICTURE #digital picture, like portrait, inner shot 49 WebPImageHint WEBP_HINT_PHOTO #outdoor photograph, with natural lighting 50 WebPImageHint WEBP_HINT_GRAPH #Discrete tone image (graph, map-tile etc). 51 52 ctypedef int WebPPreset 53 WebPPreset WEBP_PRESET_DEFAULT #default preset. 54 WebPPreset WEBP_PRESET_PICTURE #digital picture, like portrait, inner shot 55 WebPPreset WEBP_PRESET_PHOTO #outdoor photograph, with natural lighting 56 WebPPreset WEBP_PRESET_DRAWING #hand or line drawing, with high-contrast details 57 WebPPreset WEBP_PRESET_ICON #small-sized colorful images 58 WebPPreset WEBP_PRESET_TEXT #text-like 59 60 ctypedef int WebPEncCSP 61 #chroma sampling 62 WebPEncCSP WEBP_YUV420 #4:2:0 63 WebPEncCSP WEBP_CSP_UV_MASK #bit-mask to get the UV sampling factors 64 WebPEncCSP WEBP_CSP_ALPHA_BIT #bit that is set if alpha is present 65 66 ctypedef int WebPEncodingError 67 WebPEncodingError VP8_ENC_OK 68 WebPEncodingError VP8_ENC_ERROR_OUT_OF_MEMORY 69 WebPEncodingError VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY 70 WebPEncodingError VP8_ENC_ERROR_NULL_PARAMETER 71 WebPEncodingError VP8_ENC_ERROR_INVALID_CONFIGURATION 72 WebPEncodingError VP8_ENC_ERROR_BAD_DIMENSION 73 WebPEncodingError VP8_ENC_ERROR_PARTITION0_OVERFLOW 74 WebPEncodingError VP8_ENC_ERROR_PARTITION_OVERFLOW 75 WebPEncodingError VP8_ENC_ERROR_BAD_WRITE 76 WebPEncodingError VP8_ENC_ERROR_FILE_TOO_BIG 77 WebPEncodingError VP8_ENC_ERROR_USER_ABORT 78 WebPEncodingError VP8_ENC_ERROR_LAST 79 80 ctypedef struct WebPConfig: 81 int lossless #Lossless encoding (0=lossy(default), 1=lossless). 82 float quality #between 0 (smallest file) and 100 (biggest) 83 int method #quality/speed trade-off (0=fast, 6=slower-better) 84 85 WebPImageHint image_hint #Hint for image type (lossless only for now). 86 87 #Parameters related to lossy compression only: 88 int target_size #if non-zero, set the desired target size in bytes. 89 #Takes precedence over the 'compression' parameter. 90 float target_PSNR #if non-zero, specifies the minimal distortion to 91 #try to achieve. Takes precedence over target_size. 92 int segments #maximum number of segments to use, in [1..4] 93 int sns_strength #Spatial Noise Shaping. 0=off, 100=maximum. 94 int filter_strength #range: [0 = off .. 100 = strongest] 95 int filter_sharpness #range: [0 = off .. 7 = least sharp] 96 int filter_type #filtering type: 0 = simple, 1 = strong (only used 97 #if filter_strength > 0 or autofilter > 0) 98 int autofilter #Auto adjust filter's strength [0 = off, 1 = on] 99 int alpha_compression #Algorithm for encoding the alpha plane (0 = none, 100 #1 compressed with WebP lossless). Default is 1. 101 int alpha_filtering #Predictive filtering method for alpha plane. 102 #0: none, 1: fast, 2: best. Default if 1. 103 int alpha_quality #Between 0 (smallest size) and 100 (lossless). 104 #Default is 100. 105 int _pass "pass" #number of entropy-analysis passes (in [1..10]). 106 107 int show_compressed #if true, export the compressed picture back. 108 #In-loop filtering is not applied. 109 int preprocessing #preprocessing filter (0=none, 1=segment-smooth) 110 int partitions #log2(number of token partitions) in [0..3]. Default 111 #is set to 0 for easier progressive decoding. 112 int partition_limit #quality degradation allowed to fit the 512k limit 113 #on prediction modes coding (0: no degradation, 114 #100: maximum possible degradation). 115 int emulate_jpeg_size #If true, compression parameters will be remapped 116 #to better match the expected output size from 117 #JPEG compression. Generally, the output size will 118 #be similar but the degradation will be lower. 119 int thread_level #If non-zero, try and use multi-threaded encoding. 120 int low_memory #If set, reduce memory usage (but increase CPU use). 121 122 int near_lossless #Near lossless encoding [0 = max loss .. 100 = off 123 #(default) 124 int exact #if non-zero, preserve the exact RGB values under 125 #transparent area. Otherwise, discard this invisible 126 #RGB information for better compression. The default 127 #value is 0. 128 uint32_t pad[3] #padding for later use 129 130 ctypedef struct WebPMemoryWriter: 131 uint8_t* mem #final buffer (of size 'max_size', larger than 'size'). 132 size_t size #final size 133 size_t max_size #total capacity 134 uint32_t pad[1] 135 136 ctypedef void *WebPWriterFunction 137 ctypedef void *WebPProgressHook 138 ctypedef void *WebPAuxStats 139 140 ctypedef struct WebPPicture: 141 # INPUT 142 # Main flag for encoder selecting between ARGB or YUV input. 143 # It is recommended to use ARGB input (*argb, argb_stride) for lossless 144 # compression, and YUV input (*y, *u, *v, etc.) for lossy compression 145 # since these are the respective native colorspace for these formats. 146 int use_argb 147 148 # YUV input (mostly used for input to lossy compression) 149 WebPEncCSP colorspace #colorspace: should be YUV420 for now (=Y'CbCr). 150 int width, height #dimensions (less or equal to WEBP_MAX_DIMENSION) 151 uint8_t *y 152 uint8_t *u 153 uint8_t *v 154 int y_stride, uv_stride #luma/chroma strides. 155 uint8_t* a #pointer to the alpha plane 156 int a_stride #stride of the alpha plane 157 uint32_t pad1[2] #padding for later use 158 159 # ARGB input (mostly used for input to lossless compression) 160 uint32_t* argb #Pointer to argb (32 bit) plane. 161 int argb_stride #This is stride in pixels units, not bytes. 162 uint32_t pad2[3] #padding for later use 163 164 # OUTPUT 165 # Byte-emission hook, to store compressed bytes as they are ready. 166 WebPWriterFunction writer #can be NULL 167 void* custom_ptr #can be used by the writer. 168 169 # map for extra information (only for lossy compression mode) 170 int extra_info_type #1: intra type, 2: segment, 3: quant 171 #4: intra-16 prediction mode, 172 #5: chroma prediction mode, 173 #6: bit cost, 7: distortion 174 uint8_t* extra_info #if not NULL, points to an array of size 175 # ((width + 15) / 16) * ((height + 15) / 16) that 176 #will be filled with a macroblock map, depending 177 #on extra_info_type. 178 179 # STATS AND REPORTS 180 # Pointer to side statistics (updated only if not NULL) 181 WebPAuxStats* stats 182 183 # Error code for the latest error encountered during encoding 184 WebPEncodingError error_code 185 186 #If not NULL, report progress during encoding. 187 WebPProgressHook progress_hook 188 189 void* user_data #this field is free to be set to any value and 190 #used during callbacks (like progress-report e.g.). 191 192 uint32_t pad3[3] #padding for later use 193 194 # Unused for now: original samples (for non-YUV420 modes) 195 uint8_t *u0 196 uint8_t *v0 197 int uv0_stride 198 199 uint32_t pad4[7] #padding for later use 200 201 # PRIVATE FIELDS 202 void* memory_ #row chunk of memory for yuva planes 203 void* memory_argb_ #and for argb too. 204 void* pad5[2] #padding for later use 205 206 void WebPMemoryWriterInit(WebPMemoryWriter* writer) 207 int WebPMemoryWrite(const uint8_t* data, size_t data_size, const WebPPicture* picture) nogil 208 209 int WebPConfigInit(WebPConfig* config) 210 int WebPConfigPreset(WebPConfig* config, WebPPreset preset, float quality) 211 int WebPValidateConfig(const WebPConfig* config) 212 int WebPPictureInit(WebPPicture* picture) 213 void WebPPictureFree(WebPPicture* picture) 214 215 # Colorspace conversion function to import RGB samples. 216 # Previous buffer will be free'd, if any. 217 # *rgb buffer should have a size of at least height * rgb_stride. 218 # Returns false in case of memory error. 219 int WebPPictureImportRGB(WebPPicture* picture, const uint8_t* rgb, int rgb_stride) nogil 220 # Same, but for RGBA buffer. 221 int WebPPictureImportRGBA(WebPPicture* picture, const uint8_t* rgba, int rgba_stride) nogil 222 # Same, but for RGBA buffer. Imports the RGB direct from the 32-bit format 223 # input buffer ignoring the alpha channel. Avoids needing to copy the data 224 # to a temporary 24-bit RGB buffer to import the RGB only. 225 int WebPPictureImportRGBX(WebPPicture* picture, const uint8_t* rgbx, int rgbx_stride) nogil 226 227 # Variants of the above, but taking BGR(A|X) input. 228 int WebPPictureImportBGR(WebPPicture* picture, const uint8_t* bgr, int bgr_stride) nogil 229 int WebPPictureImportBGRA(WebPPicture* picture, const uint8_t* bgra, int bgra_stride) nogil 230 int WebPPictureImportBGRX(WebPPicture* picture, const uint8_t* bgrx, int bgrx_stride) nogil 231 232 # Converts picture->argb data to the YUVA format specified by 'colorspace'. 233 # Upon return, picture->use_argb is set to false. The presence of real 234 # non-opaque transparent values is detected, and 'colorspace' will be 235 # adjusted accordingly. Note that this method is lossy. 236 # Returns false in case of error. 237 int WebPPictureARGBToYUVA(WebPPicture* picture, WebPEncCSP colorspace) nogil 238 239 # Converts picture->yuv to picture->argb and sets picture->use_argb to true. 240 # The input format must be YUV_420 or YUV_420A. 241 # Note that the use of this method is discouraged if one has access to the 242 # raw ARGB samples, since using YUV420 is comparatively lossy. Also, the 243 # conversion from YUV420 to ARGB incurs a small loss too. 244 # Returns false in case of error. 245 int WebPPictureYUVAToARGB(WebPPicture* picture) nogil 246 247 # Helper function: given a width x height plane of YUV(A) samples 248 # (with stride 'stride'), clean-up the YUV samples under fully transparent 249 # area, to help compressibility (no guarantee, though). 250 void WebPCleanupTransparentArea(WebPPicture* picture) nogil 251 252 # Scan the picture 'picture' for the presence of non fully opaque alpha values. 253 # Returns true in such case. Otherwise returns false (indicating that the 254 # alpha plane can be ignored altogether e.g.). 255 int WebPPictureHasTransparency(const WebPPicture* picture) nogil 256 257 # Main encoding call, after config and picture have been initialized. 258 # 'picture' must be less than 16384x16384 in dimension (cf WEBP_MAX_DIMENSION), 259 # and the 'config' object must be a valid one. 260 # Returns false in case of error, true otherwise. 261 # In case of error, picture->error_code is updated accordingly. 262 # 'picture' can hold the source samples in both YUV(A) or ARGB input, depending 263 # on the value of 'picture->use_argb'. It is highly recommended to use 264 # the former for lossy encoding, and the latter for lossless encoding 265 # (when config.lossless is true). Automatic conversion from one format to 266 # another is provided but they both incur some loss. 267 int WebPEncode(const WebPConfig* config, WebPPicture* picture) nogil 268 269 # Rescale a picture to new dimension width x height. 270 # If either 'width' or 'height' (but not both) is 0 the corresponding 271 # dimension will be calculated preserving the aspect ratio. 272 # No gamma correction is applied. 273 # Returns false in case of error (invalid parameter or insufficient memory). 274 int WebPPictureRescale(WebPPicture* pic, int width, int height) nogil 275 276 277ERROR_TO_NAME = { 278#VP8_ENC_OK 279 VP8_ENC_ERROR_OUT_OF_MEMORY : "memory error allocating objects", 280 VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY : "memory error while flushing bits", 281 VP8_ENC_ERROR_NULL_PARAMETER : "a pointer parameter is NULL", 282 VP8_ENC_ERROR_INVALID_CONFIGURATION : "configuration is invalid", 283 VP8_ENC_ERROR_BAD_DIMENSION : "picture has invalid width/height", 284 VP8_ENC_ERROR_PARTITION0_OVERFLOW : "partition is bigger than 512k", 285 VP8_ENC_ERROR_PARTITION_OVERFLOW : "partition is bigger than 16M", 286 VP8_ENC_ERROR_BAD_WRITE : "error while flushing bytes", 287 VP8_ENC_ERROR_FILE_TOO_BIG : "file is bigger than 4G", 288 VP8_ENC_ERROR_USER_ABORT : "abort request by user", 289 } 290 291PRESETS = { 292 WEBP_PRESET_DEFAULT : "default", 293 WEBP_PRESET_PICTURE : "picture", 294 WEBP_PRESET_PHOTO : "photo", 295 WEBP_PRESET_DRAWING : "drawing", 296 WEBP_PRESET_ICON : "icon", 297 WEBP_PRESET_TEXT : "text", 298 } 299PRESET_NAME_TO_CONSTANT = {} 300for k,v in PRESETS.items(): 301 PRESET_NAME_TO_CONSTANT[v] = k 302 303CONTENT_TYPE_PRESET = { 304 "picture" : WEBP_PRESET_PICTURE, 305 "text" : WEBP_PRESET_TEXT, 306 "browser" : WEBP_PRESET_TEXT, 307 } 308 309IMAGE_HINT = { 310 WEBP_HINT_DEFAULT : "default", 311 WEBP_HINT_PICTURE : "picture", 312 WEBP_HINT_PHOTO : "photo", 313 WEBP_HINT_GRAPH : "graph", 314 } 315HINT_NAME_TO_CONSTANT = {} 316for k,v in IMAGE_HINT.items(): 317 HINT_NAME_TO_CONSTANT[v] = k 318 319CONTENT_TYPE_HINT = { 320 "picture" : WEBP_HINT_PICTURE, 321 } 322 323cdef WebPImageHint DEFAULT_IMAGE_HINT = HINT_NAME_TO_CONSTANT.get(os.environ.get("XPRA_WEBP_IMAGE_HINT", "default").lower(), WEBP_HINT_DEFAULT) 324cdef WebPPreset DEFAULT_PRESET = PRESET_NAME_TO_CONSTANT.get(os.environ.get("XPRA_WEBP_PRESET", "default").lower(), WEBP_PRESET_DEFAULT) 325cdef WebPPreset PRESET_SMALL = PRESET_NAME_TO_CONSTANT.get(os.environ.get("XPRA_WEBP_PRESET_SMALL", "icon").lower(), WEBP_PRESET_ICON) 326 327 328def get_type(): 329 return "webp" 330 331def get_encodings(): 332 return ("webp", ) 333 334def get_version(): 335 cdef int version = WebPGetEncoderVersion() 336 log("WebPGetEncoderVersion()=%#x", version) 337 return (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff 338 339def get_info(): 340 return { 341 "version" : get_version(), 342 "encodings" : get_encodings(), 343 "threading" : bool(WEBP_THREADING), 344 "image-hint" : DEFAULT_IMAGE_HINT, 345 "image-hints" : tuple(IMAGE_HINT.values()), 346 "preset" : DEFAULT_PRESET, 347 "preset-small" : PRESET_SMALL, 348 "presets" : tuple(PRESETS.values()), 349 } 350 351def webp_check(int ret): 352 if ret==0: 353 return 354 err = ERROR_TO_NAME.get(ret, ret) 355 raise Exception("error: %s" % err) 356 357cdef float fclamp(int v): 358 if v<0: 359 v = 0 360 elif v>100: 361 v = 100 362 return <float> v 363 364 365cdef get_config_info(WebPConfig *config): 366 return { 367 "lossless" : config.lossless, 368 "method" : config.method, 369 "image_hint" : IMAGE_HINT.get(config.image_hint, config.image_hint), 370 "target_size" : config.target_size, 371 "target_PSNR" : config.target_PSNR, 372 "segments" : config.segments, 373 "sns_strength" : config.sns_strength, 374 "filter_strength" : config.filter_strength, 375 "filter_sharpness" : config.filter_sharpness, 376 "filter_type" : config.filter_type, 377 "autofilter" : config.autofilter, 378 "alpha_compression" : config.alpha_compression, 379 "alpha_filtering" : config.alpha_filtering, 380 "alpha_quality" : config.alpha_quality, 381 "pass" : config._pass, 382 "show_compressed" : config.show_compressed, 383 "preprocessing" : config.preprocessing, 384 "partitions" : config.partitions, 385 "partition_limit" : config.partition_limit, 386 "emulate_jpeg_size" : config.emulate_jpeg_size, 387 "thread_level" : config.thread_level, 388 "low_memory" : config.low_memory, 389 } 390 391def encode(coding, image, options=None): 392 log("webp.encode(%s, %s, %s)", coding, image, options) 393 assert coding=="webp" 394 pixel_format = image.get_pixel_format() 395 if pixel_format not in ("RGBX", "RGBA", "BGRX", "BGRA", "RGB", "BGR"): 396 raise Exception("unsupported pixel format %s" % pixel_format) 397 options = options or {} 398 cdef unsigned int width = image.get_width() 399 cdef unsigned int height = image.get_height() 400 assert width<16384 and height<16384, "invalid image dimensions: %ix%i" % (width, height) 401 cdef unsigned int scaled_width = options.get("scaled-width", width) 402 cdef unsigned int scaled_height = options.get("scaled-height", height) 403 assert scaled_width<16384 and scaled_height<16384, "invalid image dimensions: %ix%i" % (width, height) 404 resize = None 405 if scaled_width!=width or scaled_height!=height: 406 resize = (scaled_width, scaled_height) 407 cdef unsigned int stride = image.get_rowstride() 408 cdef unsigned int Bpp = len(pixel_format) #ie: "BGRA" -> 4 409 cdef int size = stride * height 410 pixels = image.get_pixels() 411 cdef int supports_alpha = options.get("alpha", False) 412 cdef int alpha_int = supports_alpha and pixel_format.find("A")>=0 413 cdef int quality = options.get("quality", 50) 414 cdef int yuv420p = quality<SUBSAMPLING_THRESHOLD 415 416 cdef WebPConfig config 417 cdef WebPPreset preset = DEFAULT_PRESET 418 #only use icon for small squarish rectangles 419 if width*height<=2304 and abs(width-height)<=16: 420 preset = PRESET_SMALL 421 content_type = options.get("content-type", None) 422 preset = CONTENT_TYPE_PRESET.get(content_type, preset) 423 cdef WebPImageHint image_hint = CONTENT_TYPE_HINT.get(content_type, DEFAULT_IMAGE_HINT) 424 425 cdef int ret = WebPConfigInit(&config) 426 if not ret: 427 raise Exception("failed to initialize webp config") 428 429 ret = WebPConfigPreset(&config, preset, fclamp(quality)) 430 if not ret: 431 raise Exception("failed to set webp preset") 432 433 cdef int speed = options.get("speed", 50) 434 #tune it: 435 config.lossless = quality>=100 and not resize 436 if config.lossless: 437 #'quality' actually controls the speed 438 #and anything above zero is just too slow: 439 config.quality = 0 440 config.autofilter = 1 441 else: 442 #normalize quality: webp quality is much higher than jpeg's 443 #so we can go lower, 444 #[0,10,...,90,100] maps to: 445 #[0, 1, 3, 5, 9, 14, 23, 34, 50, 71, 99] 446 config.quality = fclamp((quality//4+((quality+15)**4//(100**3)))//2) 447 config.segments = 1 448 config.sns_strength = 0 449 config.filter_strength = 0 450 config.filter_sharpness = 7-quality//15 451 config.filter_type = 0 452 config.autofilter = 0 453 #"method" takes values from 0 to 6, 454 #but anything higher than 1 is dreadfully slow, 455 #so only use method=1 when speed is already very low 456 config.method = int(speed<10) 457 config.alpha_compression = alpha_int 458 config.alpha_filtering = MAX(0, MIN(2, speed/50)) * alpha_int 459 config.alpha_quality = quality * alpha_int 460 config.emulate_jpeg_size = 1 461 config._pass = MAX(1, MIN(10, (40-speed)//10)) 462 config.preprocessing = int(speed<30) 463 config.image_hint = image_hint 464 config.thread_level = WEBP_THREADING 465 config.partitions = 3 466 config.partition_limit = MAX(0, MIN(100, 100-quality)) 467 468 log("webp.compress config: lossless=%-5s, quality=%3i, method=%i, alpha=%3i,%3i,%3i, preset=%-8s, image hint=%s", config.lossless, config.quality, config.method, 469 config.alpha_compression, config.alpha_filtering, config.alpha_quality, PRESETS.get(preset, preset), IMAGE_HINT.get(image_hint, image_hint)) 470 ret = WebPValidateConfig(&config) 471 if not ret: 472 info = get_config_info(&config) 473 raise Exception("invalid webp configuration: %s" % info) 474 475 client_options = { 476 #no need to expose speed 477 #(not used for anything downstream) 478 #"speed" : speed, 479 "rgb_format" : pixel_format, 480 } 481 482 cdef WebPPicture pic 483 memset(&pic, 0, sizeof(WebPPicture)) 484 ret = WebPPictureInit(&pic) 485 if not ret: 486 raise Exception("failed to initialise webp picture") 487 pic.width = width 488 pic.height = height 489 pic.use_argb = 1 490 pic.argb_stride = stride//Bpp 491 492 cdef double start = monotonic() 493 cdef double end 494 cdef const uint8_t* src 495 with buffer_context(pixels) as bc: 496 log("webp.compress(%s, %i, %i, %s, %s) buf=%#x", image, width, height, supports_alpha, content_type, <uintptr_t> int(bc)) 497 assert len(bc)>=size, "pixel buffer is too small: expected at least %s bytes but got %s" % (size, len(bc)) 498 src = <const uint8_t*> (<uintptr_t> int(bc)) 499 500 #import the pixel data into WebPPicture 501 if pixel_format=="RGB": 502 with nogil: 503 ret = WebPPictureImportRGB(&pic, src, stride) 504 elif pixel_format=="BGR": 505 with nogil: 506 ret = WebPPictureImportBGR(&pic, src, stride) 507 elif pixel_format=="RGBX" or (pixel_format=="RGBA" and not supports_alpha): 508 with nogil: 509 ret = WebPPictureImportRGBX(&pic, src, stride) 510 elif pixel_format=="RGBA": 511 with nogil: 512 ret = WebPPictureImportRGBA(&pic, src, stride) 513 elif pixel_format=="BGRX" or (pixel_format=="BGRA" and not supports_alpha): 514 with nogil: 515 ret = WebPPictureImportBGRX(&pic, src, stride) 516 else: 517 assert pixel_format=="BGRA" 518 with nogil: 519 ret = WebPPictureImportBGRA(&pic, src, stride) 520 if not ret: 521 WebPPictureFree(&pic) 522 raise Exception("WebP importing image failed: %s, config=%s" % (ERROR_TO_NAME.get(pic.error_code, pic.error_code), get_config_info(&config))) 523 end = monotonic() 524 log("webp %s import took %.1fms", pixel_format, 1000*(end-start)) 525 526 if resize: 527 start = monotonic() 528 with nogil: 529 ret = WebPPictureRescale(&pic, scaled_width, scaled_height) 530 if not ret: 531 WebPPictureFree(&pic) 532 raise Exception("WebP failed to resize %s to %s" % (image, resize)) 533 end = monotonic() 534 log("webp %s resizing took %.1fms", 1000*(end-start)) 535 536 if yuv420p: 537 start = monotonic() 538 with nogil: 539 ret = WebPPictureARGBToYUVA(&pic, WEBP_YUV420) 540 if not ret: 541 raise Exception("WebPPictureARGBToYUVA failed for %s" % image) 542 end = monotonic() 543 log("webp YUVA subsampling took %.1fms", 1000*(end-start)) 544 client_options["subsampling"] = "YUV420P" 545 546 cdef WebPMemoryWriter memory_writer 547 memset(&memory_writer, 0, sizeof(WebPMemoryWriter)) 548 try: 549 #TODO: custom writer that over-allocates memory 550 WebPMemoryWriterInit(&memory_writer) 551 pic.writer = <WebPWriterFunction> WebPMemoryWrite 552 pic.custom_ptr = <void*> &memory_writer 553 start = monotonic() 554 with nogil: 555 ret = WebPEncode(&config, &pic) 556 if not ret: 557 raise Exception("WebPEncode failed: %s, config=%s" % (ERROR_TO_NAME.get(pic.error_code, pic.error_code), get_config_info(&config))) 558 end = monotonic() 559 log("webp encode took %.1fms", 1000*(end-start)) 560 561 cdata = memory_writer.mem[:memory_writer.size] 562 finally: 563 if memory_writer.mem: 564 free(memory_writer.mem) 565 WebPPictureFree(&pic) 566 567 if config.lossless: 568 client_options["quality"] = 100 569 else: 570 client_options["quality"] = max(0, min(99, quality)) 571 if alpha_int: 572 client_options["has_alpha"] = True 573 log("webp.compress ratio=%i%%, client-options=%s", 100*memory_writer.size//size, client_options) 574 if LOG_CONFIG>0: 575 log("webp.compress used config: %s", get_config_info(&config)) 576 if SAVE_TO_FILE: # pragma: no cover 577 filename = "./%s.webp" % time() 578 with open(filename, "wb") as f: 579 f.write(cdata) 580 log.info("saved %i bytes to %s", len(cdata), filename) 581 return "webp", Compressed("webp", cdata), client_options, width, height, 0, len(pixel_format.replace("A", ""))*8 582 583 584def selftest(full=False): 585 #fake empty buffer: 586 from xpra.codecs.codec_checks import make_test_image 587 w, h = 24, 16 588 for has_alpha in (True, False): 589 img = make_test_image("BGR%s" % ["X", "A"][has_alpha], w, h) 590 for q in (10, 50, 90): 591 r = encode("webp", img, {"quality" : q, "speed" : 50, "alpha" : has_alpha}) 592 assert len(r)>0 593 #import binascii 594 #print("compressed data(%s)=%s" % (has_alpha, binascii.hexlify(r))) 595