1# Copyright 2014 Patrick Dawson <pat@dw.is>
2# Copyright 2014 Tom Rothamel <tom@rothamel.us>
3#
4# This software is provided 'as-is', without any express or implied
5# warranty.  In no event will the authors be held liable for any damages
6# arising from the use of this software.
7#
8# Permission is granted to anyone to use this software for any purpose,
9# including commercial applications, and to alter it and redistribute it
10# freely, subject to the following restrictions:
11#
12# 1. The origin of this software must not be misrepresented; you must not
13#    claim that you wrote the original software. If you use this software
14#    in a product, an acknowledgment in the product documentation would be
15#    appreciated but is not required.
16# 2. Altered source versions must be plainly marked as such, and must not be
17#    misrepresented as being the original software.
18# 3. This notice may not be removed or altered from any source distribution.
19
20from libc.string cimport memmove
21from sdl2 cimport *
22
23from pygame_sdl2.color cimport map_color, get_color
24from pygame_sdl2.rect cimport to_sdl_rect
25from pygame_sdl2.rect import Rect
26
27from pygame_sdl2.error import error
28from pygame_sdl2.locals import SRCALPHA
29import pygame_sdl2
30
31import warnings
32
33cdef extern from "src/surface.h" nogil:
34    int pygame_Blit (SDL_Surface * src, SDL_Rect * srcrect,
35                 SDL_Surface * dst, SDL_Rect * dstrect, int the_args);
36
37cdef void move_pixels(Uint8 *src, Uint8 *dst, int h, int span, int srcpitch, int dstpitch) nogil:
38    if src < dst:
39        src += (h - 1) * srcpitch;
40        dst += (h - 1) * dstpitch;
41        srcpitch = -srcpitch;
42        dstpitch = -dstpitch;
43
44    while h:
45        h -= 1
46        memmove(dst, src, span);
47        src += srcpitch;
48        dst += dstpitch;
49
50# The total size of all allocated surfaces
51total_size = 0
52
53cdef class Surface:
54
55    def __cinit__(self):
56        self.surface = NULL
57        self.owns_surface = False
58        self.window_surface = False
59        self.has_alpha = False
60
61    def __dealloc__(self):
62        global total_size
63
64        if self.surface and self.owns_surface:
65            if total_size:
66                total_size -= self.surface.pitch * self.surface.h
67
68            SDL_FreeSurface(self.surface)
69            return
70        elif self.window_surface:
71            return
72        elif self.parent:
73            SDL_FreeSurface(self.surface)
74            return
75
76        warnings.warn("Memory leak via Surface in pygame_sdl2.")
77
78    def __sizeof__(self):
79        if self.surface and self.owns_surface:
80            return self.surface.pitch * self.surface.h
81        else:
82            return 0
83
84    def __init__(self, size, flags=0, depth=32, masks=None):
85
86        self.locklist = None
87        self.parent = None
88
89        self.offset_x = 0
90        self.offset_y = 0
91
92        self.get_window_flags = None
93
94        # When size is an empty tuple, we do not create a surface, and
95        # expect our caller to set this object up.
96        if size == ():
97            return
98
99        cdef int w
100        cdef int h
101
102        w, h = size
103        assert w >= 0
104        assert h >= 0
105
106        cdef Uint32 Rmask, Gmask, Bmask, Amask
107        cdef SDL_Surface *sample
108        cdef Surface pysample
109        cdef int depth_int
110
111        if masks is not None:
112            Rmask, Gmask, Bmask, Amask = masks
113            depth_int = depth
114
115        elif isinstance(depth, Surface):
116
117            pysample = depth
118            sample = pysample.surface
119            Rmask = sample.format.Rmask
120            Gmask = sample.format.Gmask
121            Bmask = sample.format.Bmask
122            Amask = sample.format.Amask
123            depth_int = sample.format.BitsPerPixel
124
125        else:
126
127            pysample = pygame_sdl2.display.get_surface()
128
129            if pysample and pysample.surface.format.BitsPerPixel == 32:
130                sample = pysample.surface
131                Rmask = sample.format.Rmask
132                Gmask = sample.format.Gmask
133                Bmask = sample.format.Bmask
134                Amask = sample.format.Amask
135
136            else:
137
138                # RGB(A)
139                if SDL_BYTEORDER == SDL_BIG_ENDIAN:
140                    Rmask = 0xff000000
141                    Gmask = 0x00ff0000
142                    Bmask = 0x0000ff00
143                    Amask = 0
144                else:
145                    Rmask = 0x000000ff
146                    Gmask = 0x0000ff00
147                    Bmask = 0x00ff0000
148                    Amask = 0
149
150            if (flags & SRCALPHA):
151                if not Amask:
152                    Amask = 0xffffffff & ~(Rmask | Gmask | Bmask)
153            else:
154                Amask = 0
155
156            depth_int = 32
157
158        cdef SDL_Surface *surface
159
160        with nogil:
161            surface = SDL_CreateRGBSurface(0, w, h, depth_int, Rmask, Gmask, Bmask, Amask)
162
163        if not surface:
164            raise error()
165
166        self.take_surface(surface)
167
168    cdef void take_surface(self, SDL_Surface *surface):
169        if not surface:
170            raise error("A null pointer was passed in.")
171
172        self.surface = surface
173        self.owns_surface = True
174
175        global total_size
176
177        total_size += self.surface.pitch * self.surface.h
178
179    def __repr__(self):
180        return "<Surface({}x{}x{})>".format(self.surface.w, self.surface.h, self.surface.format.BitsPerPixel)
181
182    def blit(self, Surface source, dest, area=None, int special_flags=0):
183        cdef SDL_Rect dest_rect
184        cdef SDL_Rect area_rect
185        cdef SDL_Rect *area_ptr = NULL
186
187        cdef Surface temp
188
189        cdef int err
190        cdef Uint32 key
191        cdef Uint8 alpha
192        cdef bint colorkey
193
194        colorkey = (SDL_GetColorKey(source.surface, &key) == 0)
195
196        if not source.surface.format.Amask:
197            if SDL_GetSurfaceAlphaMod(source.surface, &alpha):
198                raise error()
199
200            if alpha != 255 and (self.surface.format.Amask or colorkey):
201
202                if area:
203                    source = source.subsurface(area)
204                    area = None
205
206                if SDL_SetSurfaceBlendMode(source.surface, SDL_BLENDMODE_NONE):
207                    raise error()
208                temp = Surface(source.get_size(), SRCALPHA)
209
210                with nogil:
211                    err = SDL_UpperBlit(source.surface, NULL, temp.surface, NULL)
212
213                if err:
214                    raise error()
215
216                source = temp
217                colorkey = False
218
219        if colorkey:
220            if SDL_SetSurfaceBlendMode(source.surface, SDL_BLENDMODE_NONE):
221                raise error()
222        else:
223            if SDL_SetSurfaceBlendMode(source.surface, SDL_BLENDMODE_BLEND):
224                raise error()
225
226        to_sdl_rect(dest, &dest_rect, "dest")
227
228        if area is not None:
229            to_sdl_rect(area, &area_rect, "area")
230            area_ptr = &area_rect
231
232        with nogil:
233
234            if source.surface.format.Amask or special_flags:
235                err = pygame_Blit(source.surface, area_ptr, self.surface, &dest_rect, special_flags)
236            else:
237                err = SDL_UpperBlit(source.surface, area_ptr, self.surface, &dest_rect)
238
239        if err:
240            raise error()
241
242        dirty = Rect(dest[0], dest[1], source.surface.w, source.surface.h)
243        return dirty.clip(self.get_rect())
244
245    def convert(self, surface=None):
246        if not isinstance(surface, Surface):
247            surface = pygame_sdl2.display.get_surface()
248
249        cdef SDL_PixelFormat *sample_format
250
251        if surface is None:
252            sample_format = SDL_AllocFormat(SDL_PIXELFORMAT_BGRX8888)
253        else:
254            sample_format = (<Surface> surface).surface.format
255
256        cdef Uint32 amask
257        cdef Uint32 rmask
258        cdef Uint32 gmask
259        cdef Uint32 bmask
260
261        cdef Uint32 pixel_format
262        cdef SDL_Surface *new_surface
263
264        # If the sample surface has alpha, use it.
265        if not sample_format.Amask:
266            use_format = sample_format
267
268            with nogil:
269                new_surface = SDL_ConvertSurface(self.surface, sample_format, 0)
270
271            if not new_surface:
272                raise error()
273
274        else:
275
276            rmask = sample_format.Rmask
277            gmask = sample_format.Gmask
278            bmask = sample_format.Bmask
279            amask = 0
280
281            pixel_format = SDL_MasksToPixelFormatEnum(32, rmask, gmask, bmask, amask)
282
283            with nogil:
284                new_surface = SDL_ConvertSurfaceFormat(self.surface, pixel_format, 0)
285
286            if not new_surface:
287                raise error()
288
289        cdef Surface rv = Surface(())
290        rv.take_surface(new_surface)
291
292        return rv
293
294    def convert_alpha(self, Surface surface=None):
295        if surface is None:
296            surface = pygame_sdl2.display.get_surface()
297
298        cdef SDL_PixelFormat *sample_format
299
300        if surface is None:
301            sample_format = SDL_AllocFormat(SDL_PIXELFORMAT_BGRA8888)
302        else:
303            sample_format = (<Surface> surface).surface.format
304
305        cdef Uint32 amask = 0xff000000
306        cdef Uint32 rmask = 0x00ff0000
307        cdef Uint32 gmask = 0x0000ff00
308        cdef Uint32 bmask = 0x000000ff
309
310        cdef Uint32 pixel_format
311        cdef SDL_Surface *new_surface
312
313        # If the sample surface has alpha, use it.
314        if sample_format.Amask:
315            use_format = sample_format
316
317            with nogil:
318                new_surface = SDL_ConvertSurface(self.surface, sample_format, 0)
319
320            if not new_surface:
321                raise error()
322
323        else:
324
325            if sample_format.BytesPerPixel == 4:
326                rmask = sample_format.Rmask
327                gmask = sample_format.Gmask
328                bmask = sample_format.Bmask
329                amask = 0xffffffff & ~(rmask | gmask | bmask)
330
331            pixel_format = SDL_MasksToPixelFormatEnum(32, rmask, gmask, bmask, amask)
332
333            with nogil:
334                new_surface = SDL_ConvertSurfaceFormat(self.surface, pixel_format, 0)
335
336            if not new_surface:
337                raise error()
338
339        cdef Surface rv = Surface(())
340        rv.take_surface(new_surface)
341
342        return rv
343
344    def copy(self):
345        if self.surface.format.Amask:
346            return self.convert_alpha(self)
347        else:
348            return self.convert(self)
349
350    def fill(self, color, rect=None, special_flags=0):
351
352        cdef SDL_Rect sdl_rect
353        cdef Uint32 pixel = map_color(self.surface, color)
354        cdef int err
355
356        if rect is not None:
357            to_sdl_rect(rect, &sdl_rect)
358
359            if sdl_rect.x < 0:
360                sdl_rect.w = sdl_rect.w + sdl_rect.x
361                sdl_rect.x = 0
362
363            if sdl_rect.y < 0:
364                sdl_rect.w = sdl_rect.h + sdl_rect.y
365                sdl_rect.y = 0
366
367            if sdl_rect.w <= 0 or sdl_rect.h <= 0:
368                return Rect(0, 0, 0, 0)
369
370            with nogil:
371                err = SDL_FillRect(self.surface, &sdl_rect, pixel)
372
373            if err:
374                raise error()
375
376            return Rect(sdl_rect.x, sdl_rect.y, sdl_rect.w, sdl_rect.h)
377
378        else:
379            with nogil:
380                err = SDL_FillRect(self.surface, NULL, pixel)
381
382            if err:
383                raise error()
384
385            return Rect(0, 0, self.surface.w, self.surface.h)
386
387
388    def scroll(self, int dx=0, int dy=0):
389        cdef int srcx, destx, move_width
390        cdef int srcy, desty, move_height
391
392        cdef int width = self.surface.w
393        cdef int height = self.surface.h
394
395        cdef int per_pixel = self.surface.format.BytesPerPixel
396
397        if dx >= 0:
398            srcx = 0
399            destx = dx
400            move_width = width - dx
401        else:
402            srcx = -dx
403            destx = 0
404            move_width = width + dx
405
406        if dy >= 0:
407            srcy = 0
408            desty = dy
409            move_height = height - dy
410        else:
411            srcy = -dy
412            desty = 0
413            move_height = height + dy
414
415        cdef Uint8 *srcptr = <Uint8 *> self.surface.pixels
416        cdef Uint8 *destptr = <Uint8 *> self.surface.pixels
417
418        srcptr += srcy * self.surface.pitch + srcx * per_pixel
419        destptr += desty * self.surface.pitch + destx * per_pixel
420
421        self.lock()
422
423        with nogil:
424            move_pixels(
425                srcptr,
426                destptr,
427                move_height,
428                move_width * per_pixel,
429                self.surface.pitch,
430                self.surface.pitch)
431
432        self.unlock()
433
434    def set_colorkey(self, color, flags=0):
435
436        if color is None:
437            if SDL_SetColorKey(self.surface, SDL_FALSE, 0):
438                raise error()
439
440        else:
441            if SDL_SetColorKey(self.surface, SDL_TRUE, map_color(self.surface, color)):
442                raise error()
443
444    def get_colorkey(self):
445        cdef Uint32 key
446
447        if SDL_GetColorKey(self.surface, &key):
448            return None
449
450        return get_color(key, self.surface)
451
452    def set_alpha(self, value, flags=0):
453        if value is None:
454            value = 255
455            self.has_alpha = False
456        else:
457            self.has_alpha = True
458
459        if SDL_SetSurfaceAlphaMod(self.surface, value):
460            raise error()
461
462    def get_alpha(self):
463        cdef Uint8 rv
464
465        if self.has_alpha or self.surface.format.Amask:
466
467            if SDL_GetSurfaceAlphaMod(self.surface, &rv):
468                raise error()
469
470            return rv
471
472        else:
473            return None
474
475    def lock(self, lock=None):
476        cdef Surface root = self
477
478        while root.parent:
479            root = root.parent
480
481        if lock is None:
482            lock = self
483
484        if root.locklist is None:
485            root.locklist = [ ]
486
487        root.locklist.append(lock)
488
489        if SDL_LockSurface(root.surface):
490            raise error()
491
492    def unlock(self, lock=None):
493        cdef Surface root = self
494
495        while root.parent:
496            root = root.parent
497
498        if lock is None:
499            lock = self
500
501        if root.locklist is None:
502            root.locklist = [ ]
503
504        root.locklist.remove(lock)
505
506        SDL_UnlockSurface(root.surface)
507
508    def mustlock(self):
509        cdef Surface root = self
510
511        while root.parent:
512            root = root.parent
513
514        return SDL_MUSTLOCK(root.surface)
515
516    def get_locked(self):
517        if self.locklist:
518            return True
519
520    def get_locks(self):
521        cdef Surface root = self
522
523        while root.parent:
524            root = root.parent
525
526        if root.locklist is None:
527            root.locklist = [ ]
528
529        return root.locklist
530
531    def get_at(self, pos):
532        cdef int x, y
533        cdef Uint8 *p
534
535        x, y = pos
536
537        if not (0 <= x < self.surface.w) or not (0 <= y < self.surface.h):
538            raise IndexError("Position outside surface.")
539
540        if self.surface.format.BytesPerPixel != 4:
541            raise error("Surface has unsupported bytesize.")
542
543        self.lock()
544
545        p = <Uint8 *> self.surface.pixels
546        p += y * self.surface.pitch
547        p += x * 4
548
549        cdef Uint32 pixel = (<Uint32 *> p)[0]
550
551        self.unlock()
552
553        return get_color(pixel, self.surface)
554
555    def set_at(self, pos, color):
556        cdef int x, y
557        cdef Uint8 *p
558        cdef Uint32 pixel
559
560        x, y = pos
561
562        if not (0 <= x < self.surface.w) or not (0 <= y < self.surface.h):
563            raise ValueError("Position outside surface.")
564
565        if self.surface.format.BytesPerPixel != 4:
566            raise error("Surface has unsupported bytesize.")
567
568        pixel = map_color(self.surface, color)
569
570        self.lock()
571
572        p = <Uint8 *> self.surface.pixels
573        p += y * self.surface.pitch
574        p += x * 4
575
576        (<Uint32 *> p)[0] = pixel
577
578        self.unlock()
579
580    def get_at_mapped(self, pos):
581        cdef int x, y
582        cdef Uint8 *p
583
584        x, y = pos
585
586        if not (0 <= x < self.surface.w) or not (0 <= y < self.surface.h):
587            raise ValueError("Position outside surface.")
588
589        if self.surface.format.BytesPerPixel != 4:
590            raise error("Surface has unsupported bytesize.")
591
592        self.lock()
593
594        p = <Uint8 *> self.surface.pixels
595        p += y * self.surface.pitch
596        p += x * 4
597
598        cdef Uint32 pixel = (<Uint32 *> p)[0]
599
600        self.unlock()
601
602        return pixel
603
604    def map_rgb(self, color):
605        return map_color(self.surface, color)
606
607    def unmap_rgb(self, pixel):
608        return get_color(pixel, self.surface)
609
610    def set_clip(self, rect):
611        cdef SDL_Rect sdl_rect
612
613        if rect is None:
614            SDL_SetClipRect(self.surface, NULL)
615        else:
616            to_sdl_rect(rect, &sdl_rect)
617            SDL_SetClipRect(self.surface, &sdl_rect)
618
619    def get_clip(self):
620        cdef SDL_Rect sdl_rect
621
622        SDL_GetClipRect(self.surface, &sdl_rect)
623
624        return (sdl_rect.x, sdl_rect.y, sdl_rect.w, sdl_rect.h)
625
626    def subsurface(self, *args):
627        cdef SDL_Rect sdl_rect
628
629        if len(args) == 1:
630            to_sdl_rect(args[0], &sdl_rect)
631        else:
632            to_sdl_rect(args, &sdl_rect)
633
634        if sdl_rect.w < 0 or sdl_rect.h < 0:
635            raise error("subsurface size must be non-negative.")
636
637        if ((sdl_rect.x < 0)
638            or (sdl_rect.y < 0)
639            or (sdl_rect.x + sdl_rect.w > self.surface.w)
640            or (sdl_rect.y + sdl_rect.h > self.surface.h)):
641
642            raise error("subsurface rectangle outside surface area.")
643
644        cdef Uint8 *pixels = <Uint8 *> self.surface.pixels
645        pixels += sdl_rect.y * self.surface.pitch
646        pixels += sdl_rect.x * self.surface.format.BytesPerPixel
647
648        cdef SDL_Surface *new_surface = SDL_CreateRGBSurfaceFrom(
649            pixels,
650            sdl_rect.w,
651            sdl_rect.h,
652            self.surface.format.BitsPerPixel,
653            self.surface.pitch,
654            self.surface.format.Rmask,
655            self.surface.format.Gmask,
656            self.surface.format.Bmask,
657            self.surface.format.Amask)
658
659        if not new_surface:
660            raise error()
661
662        cdef Surface rv = Surface(())
663
664        rv.surface = new_surface
665        rv.parent = self
666        rv.offset_x = sdl_rect.x
667        rv.offset_y = sdl_rect.y
668
669        if self.has_alpha:
670            rv.set_alpha(self.get_alpha())
671
672        return rv
673
674    def get_parent(self):
675        return self.parent
676
677    def get_abs_parent(self):
678        rv = self
679
680        while rv.parent:
681            rv = rv.parent
682
683        return rv
684
685    def get_offset(self):
686        return (self.offset_x, self.offset_y)
687
688    def get_abs_offset(self):
689        cdef Surface surf = self
690
691        cdef int offset_x = 0
692        cdef int offset_y = 0
693
694        while surf:
695            offset_x += surf.offset_x
696            offset_y += surf.offset_y
697            surf = surf.parent
698
699        return (offset_x, offset_y)
700
701    def get_size(self):
702        return self.surface.w, self.surface.h
703
704    def get_width(self):
705        return self.surface.w
706
707    def get_height(self):
708        return self.surface.h
709
710    def get_rect(self, **kwargs):
711        rv = Rect((0, 0, self.surface.w, self.surface.h))
712
713        for k, v in kwargs.items():
714            setattr(rv, k, v)
715
716        return rv
717
718    def get_bitsize(self):
719        return self.surface.format.BitsPerPixel
720
721    def get_bytesize(self):
722        return self.surface.format.BytesPerPixel
723
724    def get_flags(self):
725
726        if self.get_window_flags:
727            rv = self.get_window_flags()
728        else:
729            rv = 0
730
731        if self.surface.format.Amask or self.has_alpha:
732            rv = rv | SRCALPHA
733
734        return rv
735
736    def get_pitch(self):
737        return self.surface.pitch
738
739    def get_masks(self):
740        cdef SDL_PixelFormat *format = self.surface.format
741        return (format.Rmask, format.Gmask, format.Bmask, format.Amask)
742
743    def set_masks(self, masks):
744        warnings.warn("Surface.set_masks is not supported.")
745
746    def get_shifts(self):
747        cdef SDL_PixelFormat *format = self.surface.format
748        return (format.Rshift, format.Gshift, format.Bshift, format.Ashift)
749
750    def set_shifts(self, shifts):
751        warnings.warn("Surface.set_shifts is not supported.")
752
753    def get_shifts(self):
754        cdef SDL_PixelFormat *format = self.surface.format
755        return (format.Rshift, format.Gshift, format.Bshift, format.Ashift)
756
757    def get_losses(self):
758        cdef SDL_PixelFormat *format = self.surface.format
759        return (format.Rloss, format.Gloss, format.Bloss, format.Aloss)
760
761    def get_bounding_rect(self, min_alpha=1):
762
763        cdef Uint32 amask = self.surface.format.Amask
764        cdef Uint32 amin = (0x01010101 * min_alpha) & amask
765
766        cdef int x
767        cdef int y
768        cdef int w
769        cdef int h
770
771        cdef int minx = self.surface.w - 1
772        cdef int maxx = 0
773        cdef int miny = self.surface.h - 1
774        cdef int maxy = 0
775
776        cdef Uint32 *row
777
778        cdef Uint32 topleft
779        cdef Uint32 botright
780
781        if (not amask) or (self.surface.w == 0) or (self.surface.h == 0):
782            return Rect((0, 0, self.surface.w, self.surface.h))
783
784        self.lock()
785
786        cdef Uint8 *pixels = <Uint8 *> self.surface.pixels
787
788        with nogil:
789
790            topleft = (<Uint32*> pixels)[0]
791            botright = (<Uint32*> (pixels + self.surface.pitch * (self.surface.h - 1)))[self.surface.w - 1]
792
793            if ((topleft & amask) > amin) and ((botright & amask) > amin):
794
795                # Bounding box covers image.
796
797                minx = 0
798                miny = 0
799                maxx = self.surface.w - 1
800                maxy = self.surface.h - 1
801
802            else:
803
804                # Bounding box is smaller than image.
805
806                for 0 <= y < self.surface.h:
807                    row = <Uint32*> (pixels + self.surface.pitch * y)
808
809                    for 0 <= x < self.surface.w:
810
811                        if (row[x] & amask) >= amin:
812
813                            if minx > x:
814                                minx = x
815                            if miny > y:
816                                miny = y
817                            if maxx < x:
818                                maxx = x
819                            if maxy < y:
820                                maxy = y
821
822        self.unlock()
823
824        # Totally empty surface.
825        if minx > maxx:
826            return Rect((0, 0, 0, 0))
827
828        x = minx
829        y = miny
830        w = min(maxx - minx + 1, self.surface.w - x)
831        h = min(maxy - miny + 1, self.surface.h - y)
832
833        return Rect((x, y, w, h))
834
835    def get_view(self, kind='2'):
836        raise error("Surface.get_view is not supported.")
837
838    def get_buffer(self):
839        cdef Uint8 *pixels = <Uint8 *> self.surface.pixels
840        return pixels[self.surface.h * self.surface.pitch]
841
842    property _pixels_address:
843        def __get__(self):
844            return <Uint64> self.surface.pixels
845
846
847cdef api SDL_Surface *PySurface_AsSurface(surface):
848    return (<Surface> surface).surface
849
850cdef api object PySurface_New(SDL_Surface *surf):
851    cdef Surface rv = Surface(())
852    rv.take_surface(surf)
853    return rv
854