diff --git a/src/xpra/codecs/image_wrapper.py b/src/xpra/codecs/image_wrapper.py index ac1c17a060..031241fe93 100644 --- a/src/xpra/codecs/image_wrapper.py +++ b/src/xpra/codecs/image_wrapper.py @@ -36,6 +36,7 @@ def __init__(self, x, y, width, height, pixels, pixel_format, depth, rowstride, self.thread_safe = thread_safe self.freed = False self.timestamp = int(time.time()*1000) + self.palette = None def _cn(self): try: @@ -79,6 +80,9 @@ def get_pixels(self): def get_planes(self): return self.planes + def get_palette(self): + return self.palette + def is_thread_safe(self): """ if True, free() and clone_pixel_data() can be called from any thread, if False, free() and clone_pixel_data() must be called from the same thread. diff --git a/src/xpra/codecs/pillow/encode.py b/src/xpra/codecs/pillow/encode.py index 74b0b8a29b..78063ebf0b 100644 --- a/src/xpra/codecs/pillow/encode.py +++ b/src/xpra/codecs/pillow/encode.py @@ -12,7 +12,7 @@ from xpra.os_util import BytesIOClass, memoryview_to_bytes, _buffer from xpra.net.compression import Compressed -from PIL import Image #@UnresolvedImport +from PIL import Image, ImagePalette #@UnresolvedImport from xpra.codecs.pillow import PIL_VERSION PIL_can_optimize = PIL_VERSION and PIL_VERSION>="2.2" @@ -54,14 +54,16 @@ def get_info(): def encode(coding, image, quality, speed, supports_transparency): pixel_format = image.get_pixel_format() + palette = None w = image.get_width() h = image.get_height() rgb = { - "XRGB" : "RGB", - "BGRX" : "RGB", - "RGBA" : "RGBA", - "BGRA" : "RGBA", - }.get(pixel_format, pixel_format) + "RLE8" : "P", + "XRGB" : "RGB", + "BGRX" : "RGB", + "RGBA" : "RGBA", + "BGRA" : "RGBA", + }.get(pixel_format, pixel_format) bpp = 32 #remove transparency if it cannot be handled: try: @@ -92,6 +94,15 @@ def encode(coding, image, quality, speed, supports_transparency): pixel_format = "RGB" rgb = "RGB" bpp = 24 + elif pixel_format=="RLE8": + pixel_format = "P" + palette = image.get_palette() + rp, gp, bp = [], [], [] + for r, g, b in palette: + rp.append((r>>8) & 0xFF) + gp.append((g>>8) & 0xFF) + bp.append((b>>8) & 0xFF) + palette = rp+gp+bp #PIL cannot use the memoryview directly: if type(pixels)!=_buffer: pixels = memoryview_to_bytes(pixels) @@ -99,6 +110,9 @@ def encode(coding, image, quality, speed, supports_transparency): #calls below will not convert and modify the data in place #and we save the compressed data then discard the image im = Image.frombuffer(rgb, (w, h), pixels, "raw", pixel_format, image.get_rowstride(), 1) + if palette: + im.putpalette(palette) + im.palette = ImagePalette.ImagePalette("RGB", palette = palette, size = len(palette)) if coding.startswith("png") and not supports_transparency and rgb=="RGBA": im = im.convert("RGB") rgb = "RGB" diff --git a/src/xpra/server/window/window_source.py b/src/xpra/server/window/window_source.py index aafeed58c5..f71ee69e99 100644 --- a/src/xpra/server/window/window_source.py +++ b/src/xpra/server/window/window_source.py @@ -753,7 +753,10 @@ def get_best_encoding_impl(self): return self.encoding_is_mmap elif self.encoding=="png/L": #(png/L would look awful if we mixed it with something else) - return self.get_strict_encoding + return self.encoding_is_pngL + elif self.image_depth==8: + #no other option: + return self.encoding_is_pngP elif self.strict and self.encoding!="auto": #honour strict flag if self.encoding=="rgb": @@ -792,6 +795,12 @@ def get_best_encoding_impl_default(self): def encoding_is_mmap(self, *args): return "mmap" + def encoding_is_pngL(self, *args): + return "png/L" + + def encoding_is_pngP(self, *args): + return "png/P" + def encoding_is_rgb32(self, *args): return "rgb32" diff --git a/src/xpra/server/window/window_video_source.py b/src/xpra/server/window/window_video_source.py index 9f39faee02..64a2ac248b 100644 --- a/src/xpra/server/window/window_video_source.py +++ b/src/xpra/server/window/window_video_source.py @@ -428,6 +428,8 @@ def get_best_nonvideo_encoding(self, pixel_count, ww, wh, speed, quality, curren #if we're here, then the window has no alpha (or the client cannot handle alpha) #and we can ignore the current encoding options = options or self.non_video_encodings + if self.image_depth==8: + return "png/P" if pixel_count pixels self.timestamp = int(time.time()*1000) + self.palette = palette cdef set_image(self, XImage* image): assert not self.sub @@ -269,6 +299,8 @@ cdef class XImageWrapper(object): self.pixel_format = RGB565 else: self.pixel_format = BGR565 + elif self.depth==8: + self.pixel_format = RLE8 elif self.depth==32: if image.byte_order==MSBFirst: self.pixel_format = ARGB @@ -303,6 +335,9 @@ cdef class XImageWrapper(object): def get_rowstride(self): return self.rowstride + def get_palette(self): + return self.palette + def get_planes(self): return self.planes @@ -332,7 +367,7 @@ cdef class XImageWrapper(object): raise Exception("source image does not have pixels!") cdef unsigned char Bpp = BYTESPERPIXEL(self.depth) cdef uintptr_t sub_ptr = ( src) + x*Bpp + y*self.rowstride - return XImageWrapper(self.x+x, self.y+y, w, h, sub_ptr, self.pixel_format, self.depth, self.rowstride, 0, True, True) + return XImageWrapper(self.x+x, self.y+y, w, h, sub_ptr, self.pixel_format, self.depth, self.rowstride, 0, True, True, self.palette) cdef void *get_pixels_ptr(self): if self.pixels!=NULL: @@ -353,6 +388,9 @@ cdef class XImageWrapper(object): return self.timestamp + def set_palette(self, palette): + self.palette = palette + def set_timestamp(self, timestamp): self.timestamp = timestamp @@ -572,9 +610,45 @@ cdef class XShmWrapper(object): imageWrapper = XShmImageWrapper(x, y, w, h) imageWrapper.set_image(self.image) imageWrapper.set_free_callback(self.free_image_callback) + if self.depth==8: + imageWrapper.set_palette(self.read_palette()) xshmdebug("XShmWrapper.get_image(%#x, %i, %i, %i, %i)=%s (ref_count=%i)", drawable, x, y, w, h, imageWrapper, self.ref_count) return imageWrapper + def read_palette(self): + #FIXME: we assume screen is zero + cdef Colormap colormap = 0 + cdef XWindowAttributes attrs + cdef VisualID visualid + cdef XVisualInfo vinfo_template + cdef XVisualInfo *vinfo + cdef int count = 0 + if not XGetWindowAttributes(self.display, self.window, &attrs): + return None + colormap = attrs.colormap + visualid = XVisualIDFromVisual(attrs.visual) + vinfo_template.visualid = visualid + vinfo = XGetVisualInfo(self.display, VisualIDMask, &vinfo_template, &count) + if count!=1 or vinfo==NULL: + log.error("Error: visual %i not found, count=%i, vinfo=%#x", visualid, count, vinfo) + if vinfo: + XFree(vinfo) + return None + log("visual: depth=%i, red mask=%#10x, green mask=%#10x, blue mask=%#10x, size=%i, bits per rgb=%i", vinfo.depth, vinfo.red_mask, vinfo.green_mask, vinfo.blue_mask, vinfo.colormap_size, vinfo.bits_per_rgb) + cdef unsigned int size = vinfo.colormap_size + XFree(vinfo) + if size>256: + log.error("invalid colormap size: %i", size) + return None + cdef XColor[256] colors + cdef int i + for i in range(size): + colors[i].flags = DoRed | DoGreen | DoBlue + colors[i].pixel = i + XQueryColors(self.display, colormap, colors, size) + palette = [(colors[i].red, colors[i].green, colors[i].blue) for i in range(256)] + return palette + def discard(self): #force next get_image call to get a new image from the server self.got_image = False