1# -*- coding: utf-8 -*- 2# This file is part of Xpra. 3# Copyright (C) 2013-2018 Antoine Martin <antoine@xpra.org> 4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5# later version. See the file COPYING for details. 6 7from time import monotonic 8from xpra.util import roundup 9from xpra.os_util import memoryview_to_bytes 10 11def clone_plane(plane): 12 if isinstance(plane, memoryview): 13 return plane.tobytes() 14 return plane[:] 15 16 17class ImageWrapper: 18 19 PACKED = 0 20 PLANAR_2 = 2 21 PLANAR_3 = 3 22 PLANAR_4 = 4 23 PLANE_OPTIONS = (PACKED, PLANAR_2, PLANAR_3, PLANAR_4) 24 PLANE_NAMES = { 25 PACKED : "PACKED", 26 PLANAR_3 : "3_PLANES", 27 PLANAR_4 : "4_PLANES", 28 } 29 30 def __init__(self, x : int, y : int, width : int, height : int, pixels, pixel_format, depth : int, rowstride : int, 31 bytesperpixel : int=4, planes : int=PACKED, thread_safe : bool=True, palette=None): 32 self.x = x 33 self.y = y 34 self.target_x = x 35 self.target_y = y 36 self.width = width 37 self.height = height 38 self.pixels = pixels 39 self.pixel_format = pixel_format 40 self.depth = depth 41 self.rowstride = rowstride 42 self.bytesperpixel = bytesperpixel 43 self.planes = planes 44 self.thread_safe = thread_safe 45 self.freed = False 46 self.timestamp = int(monotonic()*1000) 47 self.palette = palette 48 assert x>=0 and y>=0 and width>0 and height>0 49 50 def _cn(self): 51 try: 52 return type(self).__name__ 53 except AttributeError: # pragma: no cover 54 return type(self) 55 56 def __repr__(self): 57 return "%s(%s:%s:%s)" % (self._cn(), self.pixel_format, self.get_geometry(), 58 ImageWrapper.PLANE_NAMES.get(self.planes)) 59 60 def get_geometry(self): 61 return self.x, self.y, self.width, self.height, self.depth 62 63 def get_x(self) -> int: 64 return self.x 65 66 def get_y(self) -> int: 67 return self.y 68 69 def get_target_x(self) -> int: 70 return self.target_x 71 72 def get_target_y(self) -> int: 73 return self.target_y 74 75 def set_target_x(self, target_x : int): 76 self.target_x = target_x 77 78 def set_target_y(self, target_y : int): 79 self.target_y = target_y 80 81 def get_width(self) -> int: 82 return self.width 83 84 def get_height(self) -> int: 85 return self.height 86 87 def get_rowstride(self) -> int: 88 return self.rowstride 89 90 def get_depth(self) -> int: 91 return self.depth 92 93 def get_bytesperpixel(self) -> int: 94 return self.bytesperpixel 95 96 def get_size(self) -> int: 97 return self.rowstride * self.height 98 99 def get_pixel_format(self): 100 return self.pixel_format 101 102 def get_pixels(self): 103 return self.pixels 104 105 def get_planes(self) -> int: 106 return self.planes 107 108 def get_palette(self): 109 return self.palette 110 111 def get_gpu_buffer(self): 112 return None 113 114 def has_pixels(self) -> bool: 115 return bool(self.pixels) 116 117 def is_thread_safe(self) -> bool: 118 """ if True, free() and clone_pixel_data() can be called from any thread, 119 if False, free() and clone_pixel_data() must be called from the same thread. 120 Used by XImageWrapper to ensure X11 images are freed from the UI thread. 121 """ 122 return self.thread_safe 123 124 def get_timestamp(self) -> int: 125 """ time in millis """ 126 return self.timestamp 127 128 129 def set_timestamp(self, timestamp : int): 130 self.timestamp = timestamp 131 132 def set_planes(self, planes : int): 133 self.planes = planes 134 135 def set_rowstride(self, rowstride : int): 136 self.rowstride = rowstride 137 138 def set_pixel_format(self, pixel_format): 139 self.pixel_format = pixel_format 140 141 def set_palette(self, palette): 142 self.palette = palette 143 144 def set_pixels(self, pixels): 145 assert not self.freed 146 self.pixels = pixels 147 148 def allocate_buffer(self, _buf_len, _free_existing=1): 149 assert not self.freed 150 #only defined for XImage wrappers: 151 return 0 152 153 def may_restride(self) -> bool: 154 newstride = roundup(self.width*self.bytesperpixel, 4) 155 if self.rowstride>newstride: 156 return self.restride(newstride) 157 return False 158 159 def restride(self, rowstride : int) -> bool: 160 assert not self.freed 161 if self.planes>0: 162 #not supported yet for planar images 163 return False 164 pixels = self.pixels 165 assert pixels, "no pixel data to restride" 166 oldstride = self.rowstride 167 pos = 0 168 lines = [] 169 for _ in range(self.height): 170 lines.append(memoryview_to_bytes(pixels[pos:pos+rowstride])) 171 pos += oldstride 172 if self.height>0 and oldstride<rowstride: 173 #the last few lines may need padding if the new rowstride is bigger 174 #(usually just the last line) 175 #we do this here to avoid slowing down the main loop above 176 #as this should be a rarer case 177 for h in range(self.height): 178 i = -(1+h) 179 line = lines[i] 180 if len(line)<rowstride: 181 lines[i] = line + b"\0"*(rowstride-len(line)) 182 else: 183 break 184 self.rowstride = rowstride 185 self.pixels = b"".join(lines) 186 return True 187 188 def freeze(self) -> bool: 189 assert not self.freed 190 #some wrappers (XShm) need to be told to stop updating the pixel buffer 191 return False 192 193 def clone_pixel_data(self): 194 assert not self.freed 195 pixels = self.pixels 196 planes = self.planes 197 assert pixels, "no pixel data to clone" 198 if planes == 0: 199 #no planes, simple buffer: 200 self.pixels = clone_plane(pixels) 201 else: 202 assert planes>0 203 self.pixels = [clone_plane(pixels[i]) for i in range(planes)] 204 self.thread_safe = True 205 if self.freed: # pragma: no cover 206 #could be a race since this can run threaded 207 self.free() 208 209 def get_sub_image(self, x : int, y : int, w : int, h : int): 210 #raise NotImplementedError("no sub-images for %s" % type(self)) 211 assert w>0 and h>0, "invalid sub-image size: %ix%i" % (w, h) 212 if x+w>self.width: 213 raise Exception("invalid sub-image width: %i+%i greater than image width %i" % (x, w, self.width)) 214 if y+h>self.height: 215 raise Exception("invalid sub-image height: %i+%i greater than image height %i" % (y, h, self.height)) 216 assert self.planes==0, "cannot sub-divide planar images!" 217 if x==0 and y==0 and w==self.width and h==self.height: 218 #same dimensions, use the same wrapper 219 return self 220 #copy to local variables: 221 pixels = self.pixels 222 oldstride = self.rowstride 223 pos = y*oldstride + x*self.bytesperpixel 224 newstride = w*self.bytesperpixel 225 lines = [] 226 for _ in range(h): 227 lines.append(memoryview_to_bytes(pixels[pos:pos+newstride])) 228 pos += oldstride 229 image = ImageWrapper(self.x+x, self.y+y, w, h, b"".join(lines), self.pixel_format, self.depth, newstride, 230 planes=self.planes, thread_safe=True, palette=self.palette) 231 image.set_target_x(self.target_x+x) 232 image.set_target_y(self.target_y+y) 233 return image 234 235 def __del__(self): 236 #print("ImageWrapper.__del__() calling %s" % self.free) 237 self.free() 238 239 def free(self): 240 #print("ImageWrapper.free()") 241 if not self.freed: 242 self.freed = True 243 self.planes = None 244 self.pixels = None 245 self.pixel_format = None 246