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