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