Skip to content

Commit

Permalink
#3337 define a common encode() method interface
Browse files Browse the repository at this point in the history
so all the picture codecs can be called generically with the same first 4 arguments: coding, image, quality, speed
  • Loading branch information
totaam committed Nov 22, 2021
1 parent 2dab4cc commit 28ac1af
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 29 deletions.
5 changes: 3 additions & 2 deletions xpra/codecs/jpeg/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@ def get_error_str():
cdef char *err = tjGetErrorStr()
return bytestostr(err)

def encode(image, int quality=50, int speed=50):
def encode(coding, image, int quality=50, int speed=50):
assert coding=="jpeg"
#100 would mean lossless, so cap it at 99:
client_options = {
"quality" : min(99, quality),
Expand Down Expand Up @@ -369,5 +370,5 @@ def selftest(full=False):
from xpra.codecs.codec_checks import make_test_image
img = make_test_image("BGRA", 32, 32)
for q in (0, 50, 100):
v = encode(img, q, 100)
v = encode("jpeg", img, q, 100)
assert v, "encode output was empty!"
12 changes: 7 additions & 5 deletions xpra/codecs/nvjpeg/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ from xpra.buffers.membuf cimport getbuf, MemBuf #pylint: disable=syntax-error

from pycuda import driver

from xpra.util import envbool, typedict, roundup
from xpra.net.compression import Compressed
from xpra.util import envbool, typedict
from xpra.os_util import bytestostr

from xpra.log import Logger
Expand Down Expand Up @@ -511,7 +512,7 @@ def compress_file(filename, save_to="./out.jpeg"):
from xpra.codecs.image_wrapper import ImageWrapper
image = ImageWrapper(0, 0, w, h, data, rgb_format,
len(rgb_format)*8, stride, len(rgb_format), ImageWrapper.PACKED, True, None)
jpeg_data = encode(image)[0]
jpeg_data = encode("jpeg", image)[0]
with open(save_to, "wb") as f:
f.write(jpeg_data)

Expand All @@ -523,7 +524,8 @@ cdef nvjpegChromaSubsampling_t get_subsampling(int quality):
return NVJPEG_CSS_420


def encode(image, int quality=50, speed=50):
def encode(coding, image, int quality=50, speed=50):
assert coding=="jpeg"
from xpra.codecs.cuda_common.cuda_context import select_device, cuda_device_context
cdef double start = monotonic()
cuda_device_id, cuda_device = select_device()
Expand Down Expand Up @@ -563,7 +565,7 @@ def device_encode(device_context, image, int quality=50, speed=50):
with open(filename, "wb") as f:
f.write(cdata)
log.info("saved %i bytes to %s", len(cdata), filename)
return cdata, width, height, stride, options
return "jpeg", Compressed("jpeg", cdata, False), options, width, height, 0, 24
except NVJPEG_Exception as e:
errors.append(str(e))
return None
Expand All @@ -577,5 +579,5 @@ def selftest(full=False):
for size in (32, 256):
img = make_test_image("BGR", size, size)
log("testing with %s", img)
v = encode(img)
v = encode("jpeg", img)
assert v, "failed to compress test image"
7 changes: 5 additions & 2 deletions xpra/codecs/pillow/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def do_get_encodings():
if stripped in Image.SAVE:
encodings.append(encoding)
log("do_get_encodings()=%s", encodings)
return encodings
return tuple(encodings)

def get_encodings():
return ENCODINGS
Expand All @@ -49,7 +49,10 @@ def get_info() -> dict:
}


def encode(coding : str, image, quality : int, speed : int, supports_transparency : bool, grayscale : bool=False, resize=None):
def encode(coding : str, image, quality : int=50, speed : int=50,
supports_transparency : bool=True,
grayscale : bool=False,
resize=None):
log("pillow.encode%s", (coding, image, quality, speed, supports_transparency, grayscale, resize))
assert coding in ("jpeg", "webp", "png", "png/P", "png/L"), "unsupported encoding: %s" % coding
assert image, "no image to encode"
Expand Down
26 changes: 18 additions & 8 deletions xpra/codecs/webp/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ from libc.stdlib cimport free #pylint: disable=syntax-error
from libc.string cimport memset #pylint: disable=syntax-error
from xpra.buffers.membuf cimport buffer_context

from xpra.net.compression import Compressed
from xpra.util import envbool, envint
from xpra.log import Logger
log = Logger("encoder", "webp")
Expand Down Expand Up @@ -318,7 +319,7 @@ cdef WebPPreset PRESET_SMALL = PRESET_NAME_TO_CONSTANT.get(os.environ.get("XPRA_


def get_encodings():
return ["webp"]
return ("webp", )

def get_version():
cdef int version = WebPGetEncoderVersion()
Expand Down Expand Up @@ -377,15 +378,19 @@ cdef get_config_info(WebPConfig *config):
"low_memory" : config.low_memory,
}

def encode(image, int quality=50, int speed=50, supports_alpha=False, content_type=""):
def encode(coding, image, int quality=50, int speed=50,
supports_alpha=False,
content_type=""):
assert coding=="webp"
log("webp.encode(%s, %i, %i, %s, %s)", image, quality, speed, supports_alpha, content_type)
pixel_format = image.get_pixel_format()
if pixel_format not in ("RGBX", "RGBA", "BGRX", "BGRA"):
if pixel_format not in ("RGBX", "RGBA", "BGRX", "BGRA", "RGB", "BGR"):
raise Exception("unsupported pixel format %s" % pixel_format)
cdef unsigned int width = image.get_width()
cdef unsigned int height = image.get_height()
assert width<16384 and height<16384, "invalid image dimensions: %ix%i" % (width, height)
cdef unsigned int stride = image.get_rowstride()
cdef unsigned int Bpp = len(pixel_format)
cdef unsigned int Bpp = len(pixel_format) #ie: "BGRA" -> 4
cdef int size = stride * height
if width>WEBP_MAX_DIMENSION or height>WEBP_MAX_DIMENSION:
raise Exception("this image is too big for webp: %ix%i" % (width, height))
Expand All @@ -394,7 +399,8 @@ def encode(image, int quality=50, int speed=50, supports_alpha=False, content_ty
#we can only use YUV420P subsampling if we are 100% certain
#that the RGB pixel data contains a valid alpha component
#because WebPPictureARGBToYUVA will set the alpha channel if it finds ANY value
cdef int use_argb = quality>=SUBSAMPLING_THRESHOLD or pixel_format.find("A")<0
#and we can't specify the rowstride, so it has to be a multiple already:
cdef int use_argb = True #quality>=SUBSAMPLING_THRESHOLD or pixel_format!="BGRA" or stride!=Bpp*width

cdef WebPConfig config
cdef WebPPreset preset = DEFAULT_PRESET
Expand Down Expand Up @@ -480,7 +486,11 @@ def encode(image, int quality=50, int speed=50, supports_alpha=False, content_ty

#import the pixel data into WebPPicture
if use_argb:
if pixel_format=="RGBX" or (pixel_format=="RGBA" and not supports_alpha):
if pixel_format=="RGB":
ret = WebPPictureImportRGB(&pic, src, stride)
elif pixel_format=="BGR":
ret = WebPPictureImportBGR(&pic, src, stride)
elif pixel_format=="RGBX" or (pixel_format=="RGBA" and not supports_alpha):
ret = WebPPictureImportRGBX(&pic, src, stride)
elif pixel_format=="RGBA":
ret = WebPPictureImportRGBA(&pic, src, stride)
Expand Down Expand Up @@ -528,7 +538,7 @@ def encode(image, int quality=50, int speed=50, supports_alpha=False, content_ty
with open(filename, "wb") as f:
f.write(cdata)
log.info("saved %i bytes to %s", len(cdata), filename)
return cdata, client_options
return "webp", Compressed("webp", cdata), client_options, width, height, 0, len(pixel_format.replace("A", ""))*8


def selftest(full=False):
Expand All @@ -538,7 +548,7 @@ def selftest(full=False):
for has_alpha in (True, False):
img = make_test_image("BGR%s" % ["X", "A"][has_alpha], w, h)
for q in (10, 50, 90):
r = encode(img, quality=q, speed=50, supports_alpha=has_alpha)
r = encode("webp", img, quality=q, speed=50, supports_alpha=has_alpha)
assert len(r)>0
#import binascii
#print("compressed data(%s)=%s" % (has_alpha, binascii.hexlify(r)))
13 changes: 7 additions & 6 deletions xpra/server/picture_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,16 @@
WEBP_PILLOW = envbool("XPRA_WEBP_PILLOW", False)


def webp_encode(image, supports_transparency, quality, speed, content_type):
def webp_encode(coding, image, quality : int=50, speed : int=50,
supports_transparency : bool=True,
content_type=""):
stride = image.get_rowstride()
pixel_format = image.get_pixel_format()
enc_webp = get_codec("enc_webp")
#log("WEBP_PILLOW=%s, enc_webp=%s, stride=%s, pixel_format=%s", WEBP_PILLOW, enc_webp, stride, pixel_format)
if not WEBP_PILLOW and enc_webp and stride>0 and stride%4==0 and pixel_format in ("BGRA", "BGRX", "RGBA", "RGBX"):
log("WEBP_PILLOW=%s, enc_webp=%s, stride=%s, pixel_format=%s", WEBP_PILLOW, enc_webp, stride, pixel_format)
if not WEBP_PILLOW and enc_webp and pixel_format in ("BGRA", "BGRX", "RGBA", "RGBX", "RGB", "BGR"):
#prefer Cython module:
cdata, client_options = enc_webp.encode(image, quality, speed, supports_transparency, content_type)
return "webp", compression.Compressed("webp", cdata), client_options, image.get_width(), image.get_height(), 0, 24
return enc_webp.encode(coding, image, quality, speed, supports_transparency, content_type)
#fallback using Pillow:
enc_pillow = get_codec("enc_pillow")
if enc_pillow:
Expand Down Expand Up @@ -113,7 +114,7 @@ def rgb_encode(coding, image, rgb_formats, supports_transparency, speed, rgb_zli
else:
bpp = 24
log("rgb_encode using level=%s for %5i bytes at %3i speed, %s compressed %4sx%-4s in %s/%s: %5s bytes down to %5s",
level, l, speed, algo, image.get_width(), image.get_height(), coding, pixel_format, len(pixels), len(cwrapper.data))
level, l, speed, algo, width, height, coding, pixel_format, len(pixels), len(cwrapper.data))
#wrap it using "Compressed" so the network layer receiving it
#won't decompress it (leave it to the client's draw thread)
return coding, cwrapper, options, width, height, stride, bpp
Expand Down
9 changes: 3 additions & 6 deletions xpra/server/window/window_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -2540,7 +2540,6 @@ def make_draw_packet(self, x, y, outw, outh, coding, data, outstride, client_opt


def webp_encode(self, coding, image, options):
assert coding=="webp"
r, image = self.may_scale(coding, image, options)
if r:
return r
Expand All @@ -2555,7 +2554,7 @@ def webp_encode(self, coding, image, options):
pixel_format, self.rgb_formats))
q = options.get("quality", self._current_quality)
s = options.get("speed", self._current_speed)
return webp_encode(image, self.supports_transparency, q, s, self.content_type)
return webp_encode(coding, image, q, s, self.supports_transparency, self.content_type)

def rgb_encode(self, coding, image, options):
s = options.get("speed") or self._current_speed
Expand All @@ -2568,14 +2567,13 @@ def no_r210(self, image, rgb_formats):
argb_swap(image, rgb_formats, self.supports_transparency)

def jpeg_encode(self, coding, image, options):
assert coding=="jpeg"
r, image = self.may_scale(coding, image, options)
if r:
return r
self.no_r210(image, ["RGB"])
q = options.get("quality", self._current_quality)
s = options.get("speed", self._current_speed)
return self.enc_jpeg.encode(image, q, s)
return self.enc_jpeg.encode(coding, image, q, s)

def may_scale(self, coding, image, options):
if self.encoding=="grayscale" or FORCE_PILLOW:
Expand Down Expand Up @@ -2642,8 +2640,7 @@ def fallback(reason):
log(" %s", e)
self.enc_nvjpeg = None
return fallback("nvjpeg is now disabled")
data, w, h, stride, options = r
return "jpeg", Compressed(coding, data, False), options, w, h, stride, 24
return r

def pillow_encode(self, coding, image, options):
#for more information on pixel formats supported by PIL / Pillow, see:
Expand Down

0 comments on commit 28ac1af

Please sign in to comment.