Skip to content

Commit

Permalink
#3457 gl paint from yuv
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Feb 15, 2022
1 parent 90d026d commit 6e3bc9e
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 8 deletions.
14 changes: 14 additions & 0 deletions xpra/client/gl/gl_window_backing_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1071,6 +1071,20 @@ def paint_webp(self, img_data, x : int, y : int, width : int, height : int, opti
return
super().paint_webp(img_data, x, y, width, height, options, callbacks)

def paint_avif(self, img_data, x, y, width, height, options, callbacks):
alpha = options.boolget("alpha")
img = self.avif_decoder.decompress(img_data, options, yuv=not alpha)
pixel_format = img.get_pixel_format()
flush = options.intget("flush", 0)
w = img.get_width()
h = img.get_height()
if pixel_format.startswith("YUV"):
self.idle_add(self.gl_paint_planar, YUV2RGB_FULL_SHADER, flush, "avif", img,
x, y, w, h, width, height, options, callbacks)
else:
self.idle_add(self.do_paint_rgb, pixel_format, img.get_pixels(), x, y, w, h, width, height,
img.get_rowstride(), options, callbacks)

def do_paint_rgb(self, rgb_format, img_data,
x : int, y : int, width : int, height : int, render_width : int, render_height : int,
rowstride, options, callbacks):
Expand Down
23 changes: 19 additions & 4 deletions xpra/codecs/avif/decoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# later version. See the file COPYING for details.

from xpra.codecs.image_wrapper import ImageWrapper
from xpra.codecs.codec_constants import get_subsampling_divs
from xpra.codecs.codec_debug import may_save_image

from libc.string cimport memset #pylint: disable=syntax-error
Expand Down Expand Up @@ -68,7 +69,7 @@ cdef check(avifResult r, message):
err = avifResultToString(r).decode("latin1") or AVIF_RESULT.get(r, r)
raise Exception("%s : %s" % (message, err))

def decompress(data, options=None):
def decompress(data, options=None, yuv=False):
cdef avifRGBImage rgb
memset(&rgb, 0, sizeof(avifRGBImage))
cdef avifDecoder * decoder = avifDecoderCreate()
Expand All @@ -82,7 +83,7 @@ def decompress(data, options=None):
cdef avifResult r
cdef size_t data_len
cdef const uint8_t* data_buf
cdef uint32_t width, height, stride
cdef uint32_t width, height, stride, ydiv, size
cdef uint8_t bpp = 32
cdef MemBuf pixels
cdef avifImage *image = NULL
Expand All @@ -108,12 +109,26 @@ def decompress(data, options=None):
# * overall image sequence timing (including per-frame timing with avifDecoderNthImageTiming())
width = image.width
height = image.height
log("avif parsed: %ux%u (%ubpc)\n", width, height, image.depth)
r = avifDecoderNextImage(decoder)
check(r, "failed to get next image")
# Now available (for this frame):
# * All decoder->image YUV pixel data (yuvFormat, yuvPlanes, yuvRange, yuvChromaSamplePosition, yuvRowBytes)
log("yuvFormat=%s, yuvRange=%s", AVIF_PIXEL_FORMAT.get(image.yuvFormat), AVIF_RANGE.get(image.yuvRange))
log("avif parsed: %ux%u (%ubpc) yuvFormat=%s, yuvRange=%s",
width, height, image.depth, AVIF_PIXEL_FORMAT.get(image.yuvFormat), AVIF_RANGE.get(image.yuvRange))
if yuv:
#do we still need to copy if image.imageOwnsYUVPlanes?
#could we keep the decoder context alive until the image is freed instead?
yuv_format = "%sP" % AVIF_PIXEL_FORMAT.get(image.yuvFormat) #ie: YUV420P
divs = get_subsampling_divs(yuv_format)
planes = []
strides = []
for i in range(3):
ydiv = divs[i][1]
size = image.yuvRowBytes[i]*height//ydiv
planes.append(image.yuvPlanes[i][:size])
strides.append(image.yuvRowBytes[i])
return ImageWrapper(0, 0, width, height, planes, yuv_format, 24, strides, ImageWrapper.PLANAR_3)

# * decoder->image alpha data (alphaRange, alphaPlane, alphaRowBytes)
# * this frame's sequence timing
avifRGBImageSetDefaults(&rgb, image)
Expand Down
11 changes: 7 additions & 4 deletions xpra/codecs/avif/encoder.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -127,21 +127,25 @@ def encode(coding, image, options=None):
qrange = 20
cdef int minq = AVIF_QUANTIZER_WORST_QUALITY - AVIF_QUANTIZER_WORST_QUALITY*100//quality
cdef int maxq = max(0, minq-qrange)
client_options = {"quality" : quality}

with buffer_context(pixels) as bc:
rgb.pixels = <uint8_t*> (<uintptr_t> int(bc))
log("avif.encode(%s, %s, %s) pixels=%#x", coding, image, options, int(bc))

try:
avif_image.yuvRange = AVIF_RANGE_FULL
if grayscale:
avif_image.yuvFormat = AVIF_PIXEL_FORMAT_YUV400
client_options["subsampling"] = "YUV400"
elif quality>80:
avif_image.yuvFormat = AVIF_PIXEL_FORMAT_YUV444
client_options["subsampling"] = "YUV444"
elif quality>50:
avif_image.yuvFormat = AVIF_PIXEL_FORMAT_YUV422
client_options["subsampling"] = "YUV422"
else:
avif_image.yuvFormat = AVIF_PIXEL_FORMAT_YUV420
client_options["subsampling"] = "YUV420"
r = avifImageRGBToYUV(avif_image, &rgb)
log("avifImageRGBToYUV()=%i for %s", r, AVIF_PIXEL_FORMAT.get(avif_image.yuvFormat))
check(r, "Failed to convert to YUV(A)")
Expand All @@ -156,6 +160,8 @@ def encode(coding, image, options=None):
encoder.minQuantizer = minq
encoder.maxQuantizer = maxq
if alpha>0:
#we need this client side to know if we can use planar gl paint:
client_options["alpha"] = alpha
encoder.minQuantizerAlpha = minq
encoder.maxQuantizerAlpha = maxq
# * tileRowsLog2
Expand All @@ -170,9 +176,6 @@ def encode(coding, image, options=None):
log("avifEncoderFinish()=%i", r)
check(r, "Failed to finish encode")

client_options = {"quality" : quality}
if alpha:
client_options["alpha"] = alpha
cdata = avifOutput.data[:avifOutput.size]
log("avif: got %i bytes", avifOutput.size)
may_save_image("avif", cdata)
Expand Down

0 comments on commit 6e3bc9e

Please sign in to comment.