From 7630cd03442a99971e2bfbcd9572d9d384fa7069 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 18 May 2014 12:27:40 +0000 Subject: [PATCH] #487: implement a webp decoder using the advanced API so we can get premultiplied alpha git-svn-id: https://xpra.org/svn/Xpra/trunk@6509 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/setup.py | 4 + src/xpra/client/ui_client_base.py | 2 +- src/xpra/client/window_backing_base.py | 20 +++ src/xpra/codecs/loader.py | 8 +- src/xpra/codecs/webp/decode.pyx | 213 +++++++++++++++++++++++++ 5 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 src/xpra/codecs/webp/decode.pyx diff --git a/src/setup.py b/src/setup.py index fc78db4e45..d4d09b3b58 100755 --- a/src/setup.py +++ b/src/setup.py @@ -724,6 +724,7 @@ def pkgconfig(*pkgs_options, **ekw): "xpra/codecs/enc_x264/encoder.c", "xpra/codecs/enc_x265/encoder.c", "xpra/codecs/webp/encode.c", + "xpra/codecs/webp/decode.c", "xpra/codecs/dec_avcodec/decoder.c", "xpra/codecs/dec_avcodec/constants.pxi", "xpra/codecs/dec_avcodec2/decoder.c", @@ -1550,6 +1551,9 @@ def cython_add(*args, **kwargs): cython_add(Extension("xpra.codecs.webp.encode", ["xpra/codecs/webp/encode.pyx", buffers_c], **webp_pkgconfig)) + cython_add(Extension("xpra.codecs.webp.decode", + ["xpra/codecs/webp/decode.pyx"]+membuffers_c, + **webp_pkgconfig)) toggle_packages(dec_avcodec_ENABLED, "xpra.codecs.dec_avcodec") if dec_avcodec_ENABLED: diff --git a/src/xpra/client/ui_client_base.py b/src/xpra/client/ui_client_base.py index 13e179ff6a..99495dbae8 100644 --- a/src/xpra/client/ui_client_base.py +++ b/src/xpra/client/ui_client_base.py @@ -442,7 +442,7 @@ def do_get_core_encodings(self): core_encodings = ["rgb24"] #PIL: core_encodings += get_PIL_decodings(get_codec("PIL")) - if has_codec("dec_webm") and "webp" not in core_encodings: + if (has_codec("dec_webm") or has_codec("dec_webp")) and "webp" not in core_encodings: core_encodings.append("webp") #we enable all the video decoders we know about, #what will actually get used by the server will still depend on the csc modes supported diff --git a/src/xpra/client/window_backing_base.py b/src/xpra/client/window_backing_base.py index 8e255804a4..670b5acb52 100644 --- a/src/xpra/client/window_backing_base.py +++ b/src/xpra/client/window_backing_base.py @@ -235,6 +235,26 @@ def paint_image(self, coding, img_data, x, y, width, height, options, callbacks) return False def paint_webp(self, img_data, x, y, width, height, options, callbacks): + dec_webp = get_codec("dec_webp") + if dec_webp: + return self.paint_webp_using_cwebp(img_data, x, y, width, height, options, callbacks) + return self.paint_webp_using_webm(img_data, x, y, width, height, options, callbacks) + + def paint_webp_using_cwebp(self, img_data, x, y, width, height, options, callbacks): + dec_webp = get_codec("dec_webp") + has_alpha = options.get("has_alpha", False) + buffer_wrapper, width, height, stride, has_alpha, rgb_format = dec_webp.decompress(img_data, has_alpha) + options["rgb_format"] = rgb_format + def free_buffer(*args): + buffer_wrapper.free() + callbacks.append(free_buffer) + data = buffer_wrapper.get_pixels() + if has_alpha: + return self.paint_rgb32(data, x, y, width, height, stride, options, callbacks) + else: + return self.paint_rgb24(data, x, y, width, height, stride, options, callbacks) + + def paint_webp_using_webm(self, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ dec_webm = get_codec("dec_webm") assert dec_webm is not None, "webp decoder not found" diff --git a/src/xpra/codecs/loader.py b/src/xpra/codecs/loader.py index 780a9d47b6..b241a519bc 100755 --- a/src/xpra/codecs/loader.py +++ b/src/xpra/codecs/loader.py @@ -110,7 +110,10 @@ def load_codecs(): #webp via cython: codec_import_check("enc_webp", "webp encoder", "xpra.codecs.webp", "xpra.codecs.webp.encode", "compress") - add_codec_version("webp", "xpra.codecs.webp.encode") + add_codec_version("enc_webp", "xpra.codecs.webp.encode") + + codec_import_check("dec_webp", "webp decoder", "xpra.codecs.webp", "xpra.codecs.webp.decode", "decompress") + add_codec_version("dec_webp", "xpra.codecs.webp.decode") #no bytearray (python 2.6 or later) or no bitmap handlers, no webm: from xpra.os_util import builtins @@ -164,7 +167,8 @@ def has_codec(name): "dec_avcodec", "dec_avcodec2", \ "enc_webm", \ "dec_webm", \ - "enc_webp" + "enc_webp", \ + "dec_webp" #note: this is just for defining the order of encodings, #so we have both core encodings (rgb24/rgb32) and regular encodings (rgb) in here: diff --git a/src/xpra/codecs/webp/decode.pyx b/src/xpra/codecs/webp/decode.pyx new file mode 100644 index 0000000000..50e4f997f4 --- /dev/null +++ b/src/xpra/codecs/webp/decode.pyx @@ -0,0 +1,213 @@ +# This file is part of Xpra. +# Copyright (C) 2014 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +import time +import os + +from xpra.log import Logger +log = Logger("encoder", "webp") + + +from libc.stdint cimport uint8_t, uint32_t + +cdef extern from *: + ctypedef unsigned long size_t + +cdef extern from "stdlib.h": + void free(void *ptr) + +cdef extern from "Python.h": + ctypedef int Py_ssize_t + +cdef extern from "../buffers/memalign.h": + void *xmemalign(size_t size) + +cdef extern from "../buffers/buffers.h": + object memory_as_pybuffer(void* ptr, Py_ssize_t buf_len, int readonly) + +cdef extern from "webp/decode.h": + + int WebPGetDecoderVersion() + + ctypedef int VP8StatusCode + VP8StatusCode VP8_STATUS_OK + VP8StatusCode VP8_STATUS_OUT_OF_MEMORY + VP8StatusCode VP8_STATUS_INVALID_PARAM + VP8StatusCode VP8_STATUS_BITSTREAM_ERROR + VP8StatusCode VP8_STATUS_UNSUPPORTED_FEATURE + VP8StatusCode VP8_STATUS_SUSPENDED + VP8StatusCode VP8_STATUS_USER_ABORT + VP8StatusCode VP8_STATUS_NOT_ENOUGH_DATA + + ctypedef int WEBP_CSP_MODE + WEBP_CSP_MODE MODE_RGB + WEBP_CSP_MODE MODE_RGBA + WEBP_CSP_MODE MODE_BGR + WEBP_CSP_MODE MODE_BGRA + WEBP_CSP_MODE MODE_ARGB + WEBP_CSP_MODE MODE_RGBA_4444 + WEBP_CSP_MODE MODE_RGB_565 + #RGB-premultiplied transparent modes (alpha value is preserved) + WEBP_CSP_MODE MODE_rgbA + WEBP_CSP_MODE MODE_bgrA + WEBP_CSP_MODE MODE_Argb + WEBP_CSP_MODE MODE_rgbA_4444 + #YUV modes must come after RGB ones. + WEBP_CSP_MODE MODE_YUV + WEBP_CSP_MODE MODE_YUVA #yuv 4:2:0 + + + ctypedef struct WebPDecoderOptions: + int bypass_filtering #if true, skip the in-loop filtering + int no_fancy_upsampling #if true, use faster pointwise upsampler + int use_cropping #if true, cropping is applied _first_ + int crop_left + int crop_top #top-left position for cropping. + #Will be snapped to even values. + int crop_width + int crop_height #dimension of the cropping area + int use_scaling #if true, scaling is applied _afterward_ + int scaled_width, scaled_height #final resolution + int use_threads #if true, use multi-threaded decoding + + int force_rotation #forced rotation (to be applied _last_) + int no_enhancement #if true, discard enhancement layer + uint32_t pad[6] #padding for later use + + ctypedef struct WebPBitstreamFeatures: + int width #Width in pixels, as read from the bitstream. + int height #Height in pixels, as read from the bitstream. + int has_alpha #True if the bitstream contains an alpha channel. + int has_animation #True if the bitstream is an animation. + #Unused for now: + int bitstream_version #should be 0 for now. TODO(later) + int no_incremental_decoding #if true, using incremental decoding is not recommended. + int rotate #TODO(later) + int uv_sampling #should be 0 for now. TODO(later) + uint32_t pad[2] #padding for later use + + ctypedef struct WebPRGBABuffer: #view as RGBA + uint8_t* rgba #pointer to RGBA samples + int stride #stride in bytes from one scanline to the next. + size_t size #total size of the *rgba buffer. + + ctypedef struct WebPYUVABuffer: #view as YUVA + uint8_t* y #pointer to luma + uint8_t* u #pointer to chroma U + uint8_t* v #pointer to chroma V + uint8_t* a #pointer to alpha samples + int y_stride #luma stride + int u_stride, v_stride #chroma strides + int a_stride #alpha stride + size_t y_size #luma plane size + size_t u_size, v_size #chroma planes size + size_t a_size #alpha-plane size + + ctypedef struct u: + WebPRGBABuffer RGBA + WebPYUVABuffer YUVA + + ctypedef struct WebPDecBuffer: + WEBP_CSP_MODE colorspace #Colorspace. + int width, height #Dimensions. + int is_external_memory #If true, 'internal_memory' pointer is not used. + u u + uint32_t pad[4] #padding for later use + uint8_t* private_memory #Internally allocated memory (only when + #is_external_memory is false). Should not be used + #externally, but accessed via the buffer union. + + ctypedef struct WebPDecoderConfig: + WebPBitstreamFeatures input #Immutable bitstream features (optional) + WebPDecBuffer output #Output buffer (can point to external mem) + WebPDecoderOptions options #Decoding options + + + VP8StatusCode WebPGetFeatures(const uint8_t* data, size_t data_size, + WebPBitstreamFeatures* features) + + int WebPInitDecoderConfig(WebPDecoderConfig* config) + VP8StatusCode WebPDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config) + + +ERROR_TO_NAME = { +#VP8_STATUS_OK + VP8_STATUS_OUT_OF_MEMORY : "out of memory", + VP8_STATUS_INVALID_PARAM : "invalid parameter", + VP8_STATUS_BITSTREAM_ERROR : "bitstream error", + VP8_STATUS_UNSUPPORTED_FEATURE : "unsupported feature", + VP8_STATUS_SUSPENDED : "suspended", + VP8_STATUS_USER_ABORT : "user abort", + VP8_STATUS_NOT_ENOUGH_DATA : "not enough data", + } + +def get_version(): + version = WebPGetDecoderVersion() + return (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff + +def webp_check(int ret): + if ret==0: + return + err = ERROR_TO_NAME.get(ret, ret) + raise Exception("error: %s" % err) + + +cdef class WebpBufferWrapper: + """ + Opaque object wrapping the buffer, + calling free will free the underlying memory. + """ + + cdef unsigned long buffer_ptr + cdef size_t size + + def __cinit__(self, unsigned long buffer_ptr, size_t size): + self.buffer_ptr = buffer_ptr + self.size = size + + def __del__(self): + assert self.buffer_ptr==0, "WebpBufferWrapper out of scope before being freed!" + + def get_pixels(self): + assert self.buffer_ptr>0, "WebpBufferWrapper has already been freed!" + return memory_as_pybuffer( self.buffer_ptr, self.size, False) + + def free(self): #@DuplicatedSignature + if self.buffer_ptr!=0: + free(self.buffer_ptr) + self.buffer_ptr = 0 + + +def decompress(data, has_alpha): + """ + This returns a WebpBufferWrapper, you MUST call free() on it + once the pixel buffer can be freed. + """ + cdef WebPDecoderConfig config + config.options.use_threads = 1 + WebPInitDecoderConfig(&config) + webp_check(WebPGetFeatures(data, len(data), &config.input)) + log("webp decompress found features: width=%s, height=%s, has_alpha=%s", config.input.width, config.input.height, config.input.has_alpha) + + cdef int stride = 4 * config.input.width + if has_alpha: + rgb_format = "BGRA" + config.output.colorspace = MODE_bgrA + else: + rgb_format = "RGB" + config.output.colorspace = MODE_RGB + cdef size_t size = stride * config.input.height + #allocate the buffer: + cdef uint8_t *buffer = xmemalign(size + stride) #add one line of padding + cdef WebpBufferWrapper b = WebpBufferWrapper( buffer, size) + config.output.u.RGBA.rgba = buffer + config.output.u.RGBA.stride = stride + config.output.u.RGBA.size = size + config.output.is_external_memory = 1 + + webp_check(WebPDecode(data, len(data), &config)) + + return b, config.input.width, config.input.height, stride, has_alpha and config.input.has_alpha, rgb_format