Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/codecs/image_wrapper.py : 100%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.
7from xpra.util import roundup
8from xpra.os_util import memoryview_to_bytes, monotonic_time
10def clone_plane(plane):
11 if isinstance(plane, memoryview):
12 return plane.tobytes()
13 return plane[:]
16class ImageWrapper:
18 PACKED = 0
19 PLANAR_2 = 2
20 PLANAR_3 = 3
21 PLANAR_4 = 4
22 PLANE_OPTIONS = (PACKED, PLANAR_2, PLANAR_3, PLANAR_4)
23 PLANE_NAMES = {
24 PACKED : "PACKED",
25 PLANAR_3 : "3_PLANES",
26 PLANAR_4 : "4_PLANES",
27 }
29 def __init__(self, x : int, y : int, width : int, height : int, pixels, pixel_format, depth : int, rowstride : int,
30 bytesperpixel : int=4, planes : int=PACKED, thread_safe : bool=True, palette=None):
31 self.x = x
32 self.y = y
33 self.target_x = x
34 self.target_y = y
35 self.width = width
36 self.height = height
37 self.pixels = pixels
38 self.pixel_format = pixel_format
39 self.depth = depth
40 self.rowstride = rowstride
41 self.bytesperpixel = bytesperpixel
42 self.planes = planes
43 self.thread_safe = thread_safe
44 self.freed = False
45 self.timestamp = int(monotonic_time()*1000)
46 self.palette = palette
47 assert x>=0 and y>=0 and width>0 and height>0
49 def _cn(self):
50 try:
51 return type(self).__name__
52 except AttributeError: # pragma: no cover
53 return type(self)
55 def __repr__(self):
56 return "%s(%s:%s:%s)" % (self._cn(), self.pixel_format, self.get_geometry(),
57 ImageWrapper.PLANE_NAMES.get(self.planes))
59 def get_geometry(self):
60 return self.x, self.y, self.width, self.height, self.depth
62 def get_x(self) -> int:
63 return self.x
65 def get_y(self) -> int:
66 return self.y
68 def get_target_x(self) -> int:
69 return self.target_x
71 def get_target_y(self) -> int:
72 return self.target_y
74 def set_target_x(self, target_x : int):
75 self.target_x = target_x
77 def set_target_y(self, target_y : int):
78 self.target_y = target_y
80 def get_width(self) -> int:
81 return self.width
83 def get_height(self) -> int:
84 return self.height
86 def get_rowstride(self) -> int:
87 return self.rowstride
89 def get_depth(self) -> int:
90 return self.depth
92 def get_bytesperpixel(self) -> int:
93 return self.bytesperpixel
95 def get_size(self) -> int:
96 return self.rowstride * self.height
98 def get_pixel_format(self):
99 return self.pixel_format
101 def get_pixels(self):
102 return self.pixels
104 def get_planes(self) -> int:
105 return self.planes
107 def get_palette(self):
108 return self.palette
110 def get_gpu_buffer(self):
111 return None
113 def has_pixels(self) -> bool:
114 return bool(self.pixels)
116 def is_thread_safe(self) -> bool:
117 """ if True, free() and clone_pixel_data() can be called from any thread,
118 if False, free() and clone_pixel_data() must be called from the same thread.
119 Used by XImageWrapper to ensure X11 images are freed from the UI thread.
120 """
121 return self.thread_safe
123 def get_timestamp(self) -> int:
124 """ time in millis """
125 return self.timestamp
128 def set_timestamp(self, timestamp : int):
129 self.timestamp = timestamp
131 def set_planes(self, planes : int):
132 self.planes = planes
134 def set_rowstride(self, rowstride : int):
135 self.rowstride = rowstride
137 def set_pixel_format(self, pixel_format):
138 self.pixel_format = pixel_format
140 def set_palette(self, palette):
141 self.palette = palette
143 def set_pixels(self, pixels):
144 assert not self.freed
145 self.pixels = pixels
147 def allocate_buffer(self, _buf_len, _free_existing=1):
148 assert not self.freed
149 #only defined for XImage wrappers:
150 return 0
152 def may_restride(self) -> bool:
153 newstride = roundup(self.width*self.bytesperpixel, 4)
154 if self.rowstride>newstride:
155 return self.restride(newstride)
156 return False
158 def restride(self, rowstride : int) -> bool:
159 assert not self.freed
160 if self.planes>0:
161 #not supported yet for planar images
162 return False
163 pixels = self.pixels
164 assert pixels, "no pixel data to restride"
165 oldstride = self.rowstride
166 pos = 0
167 lines = []
168 for _ in range(self.height):
169 lines.append(memoryview_to_bytes(pixels[pos:pos+rowstride]))
170 pos += oldstride
171 if self.height>0 and oldstride<rowstride:
172 #the last few lines may need padding if the new rowstride is bigger
173 #(usually just the last line)
174 #we do this here to avoid slowing down the main loop above
175 #as this should be a rarer case
176 for h in range(self.height):
177 i = -(1+h)
178 line = lines[i]
179 if len(line)<rowstride:
180 lines[i] = line + b"\0"*(rowstride-len(line))
181 else:
182 break
183 self.rowstride = rowstride
184 self.pixels = b"".join(lines)
185 return True
187 def freeze(self) -> bool:
188 assert not self.freed
189 #some wrappers (XShm) need to be told to stop updating the pixel buffer
190 return False
192 def clone_pixel_data(self):
193 assert not self.freed
194 pixels = self.pixels
195 planes = self.planes
196 assert pixels, "no pixel data to clone"
197 if planes == 0:
198 #no planes, simple buffer:
199 self.pixels = clone_plane(pixels)
200 else:
201 assert planes>0
202 self.pixels = [clone_plane(pixels[i]) for i in range(planes)]
203 self.thread_safe = True
204 if self.freed: # pragma: no cover
205 #could be a race since this can run threaded
206 self.free()
208 def get_sub_image(self, x : int, y : int, w : int, h : int):
209 #raise NotImplementedError("no sub-images for %s" % type(self))
210 assert w>0 and h>0, "invalid sub-image size: %ix%i" % (w, h)
211 if x+w>self.width:
212 raise Exception("invalid sub-image width: %i+%i greater than image width %i" % (x, w, self.width))
213 if y+h>self.height:
214 raise Exception("invalid sub-image height: %i+%i greater than image height %i" % (y, h, self.height))
215 assert self.planes==0, "cannot sub-divide planar images!"
216 if x==0 and y==0 and w==self.width and h==self.height:
217 #same dimensions, use the same wrapper
218 return self
219 #copy to local variables:
220 pixels = self.pixels
221 oldstride = self.rowstride
222 pos = y*oldstride + x*self.bytesperpixel
223 newstride = w*self.bytesperpixel
224 lines = []
225 for _ in range(h):
226 lines.append(memoryview_to_bytes(pixels[pos:pos+newstride]))
227 pos += oldstride
228 image = ImageWrapper(self.x+x, self.y+y, w, h, b"".join(lines), self.pixel_format, self.depth, newstride,
229 planes=self.planes, thread_safe=True, palette=self.palette)
230 image.set_target_x(self.target_x+x)
231 image.set_target_y(self.target_y+y)
232 return image
234 def __del__(self):
235 #print("ImageWrapper.__del__() calling %s" % self.free)
236 self.free()
238 def free(self):
239 #print("ImageWrapper.free()")
240 if not self.freed:
241 self.freed = True
242 self.planes = None
243 self.pixels = None
244 self.pixel_format = None