File: renderer.py

package info (click to toggle)
pysdl2 0.9.17%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,328 kB
  • sloc: python: 24,685; makefile: 36; sh: 8
file content (902 lines) | stat: -rw-r--r-- 35,527 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
from ctypes import byref, c_int, c_float

from .. import blendmode, surface, rect, video, render, error, dll, hints
from ..stdinc import Uint8, Uint32

from .color import convert_to_color
from .err import raise_sdl_err
from .compat import deprecated, stringify, byteify, isiterable
from .sprite import SoftwareSprite, TextureSprite
from .surface import _get_target_surface
from .window import Window

__all__ = ["set_texture_scale_quality", "Renderer", "Texture"]


# NOTE: The following 3 utility functions probably belong in a separate module

def is_numeric(x):
    try:
        return float(x) == x
    except (TypeError, ValueError):
        return False

def _is_point(x):
    if isiterable(x):
        return len(x) == 2
    if isinstance(x, rect.SDL_Point) or isinstance(x, rect.SDL_FPoint):
        return True
    return False

def _is_rect(x):
    if isiterable(x):
        return len(x) == 4
    if isinstance(x, rect.SDL_Rect) or isinstance(x, rect.SDL_FRect):
        return True
    return False

def _sanitize_points(points):
    # If first item is numeric, assume flat list of points
    if isinstance(points, rect.SDL_Point) or isinstance(points, rect.SDL_FPoint):
        points = [points]
    elif is_numeric(points[0]):
        if len(points) % 2 != 0:
            raise ValueError("Flat x/y coordinate lists must have even length")
        chunked = []
        for i in range(0, len(points), 2):
            p = (points[i], points[i+1])
            chunked.append(p)
        points = chunked

    type_err = (
        "Points must be specified as either (x, y) tuples or "
        "SDL_Point objects (Got unsupported format '{0}')"
    )
    pts = []
    if not isiterable(points):
        points = [points]
    for p in points:
        if isinstance(p, rect.SDL_Point) or isinstance(p, rect.SDL_FPoint):
            p = (p.x, p.y)
        elif len(p) != 2:
            raise ValueError(type_err.format(str(p)))
        if dll.version < 2010 and any([int(v) != v for v in p]):
            e = "Floating point rendering requires SDL 2.0.10 or newer"
            raise RuntimeError(e + " (got '{0}')".format(str(p)))
        pts.append((p[0], p[1]))
    return pts


def _sanitize_rects(rects):
    type_err = (
        "Rectangles must be specified as either (x, y, w, h) tuples or "
        "SDL_Rect objects (Got unsupported format '{0}')"
    )
    out = []
    if not (isiterable(rects) and _is_rect(rects[0])):
        rects = [rects]
    for r in rects:
        if isinstance(r, rect.SDL_Rect) or isinstance(r, rect.SDL_FRect):
            r = (r.x, r.y, r.w, r.h)
        elif len(r) != 4:
            raise ValueError(type_err.format(str(r)))
        if dll.version < 2010 and any([int(v) != v for v in r]):
            e = "Floating point rendering requires SDL 2.0.10 or newer"
            raise RuntimeError(e + " (got '{0}')".format(str(r)))
        out.append((r[0], r[1], r[2], r[3]))
    return out


def _get_texture_size(texture):
    flags = Uint32()
    access, w, h = (c_int(), c_int(), c_int())
    ret = render.SDL_QueryTexture(
        texture, byref(flags), byref(access), byref(w), byref(h)
    )
    if ret < 0:
        raise_sdl_err("getting texture attributes")
    return (w.value, h.value)



def set_texture_scale_quality(method):
    """Sets the default scaling quailty for :obj:`~sdl2.ext.Texture` objects.

    By default, SDL2 uses low-quality nearest-neighbour scaling for all new
    textures. This method lets you change the default scaling method to one of
    the following options:
    
    ========= =============================================================
    Method    Description
    ========= =============================================================
    Nearest   Nearest-neighbour pixel scaling (no filtering)
    Linear    Linear filtering
    Best      Anisotropic filtering (falls back to linear if not available)
    ========= =============================================================

    This function does not apply retroactively, and will only affect textures
    created after it is called.

    Args:
        method (str): The default scaling method to use for SDL textures. Must
            be one of 'nearest', 'linear', or 'best'.

    """
    method = byteify(method.lower())
    if method not in [b"nearest", b"linear", b"best"]:
        raise ValueError(
            "Texture scaling method must be 'nearest', 'linear', or 'best'."
        )
    hints.SDL_SetHint(hints.SDL_HINT_RENDER_SCALE_QUALITY, method)



class Texture(object):
    """A 2D texture to be used with a :obj:`~sdl2.ext.Renderer`.

    In SDL2, textures are 2D images that have been prepared for fast rendering
    with a given renderer. For example, if an SDL surface is converted into a
    texture with a :obj:`~sdl2.ext.Renderer` using the OpenGL backend, the pixel
    data for the surface will internally be converted into an OpenGL texture.

    Once a surface has been converted into a :obj:`Texture`, the surface can be
    safely deleted if no longer needed.

    Args:
        renderer (:obj:`~sdl2.ext.Renderer`): The renderer associated with the
            texture.
        surface (:obj:`~sdl2.SDL_Surface`): An SDL surface from which the
            texture will be created.
    
    """
    def __init__(self, renderer, surface):
        # Validate and get reference to the parent renderer
        self._renderer_ref = None
        if isinstance(renderer, Renderer):
            self._renderer_ref = renderer._renderer_ref
        elif hasattr(renderer, "contents"):
            if isinstance(renderer.contents, render.SDL_Renderer):
                self._renderer_ref = [renderer]
        if self._renderer_ref is None:
            raise TypeError(
                "'renderer' must be a valid Renderer object or a pointer to "
                "an SDL_Renderer."
            )
        # Convert the passed surface into a texture
        surface = _get_target_surface(surface, "surface")
        self._tx = render.SDL_CreateTextureFromSurface(self._renderer, surface)
        if not self._tx:
            raise_sdl_err("creating the texture")
        # Cache the size of the texture for future use
        self._size = _get_texture_size(self.tx)

    def __del__(self):
        if hasattr(self, "_tx"):
            self.destroy()

    @property
    def _renderer(self):
        # NOTE: This is sort of a hack to get around a ctypes problem. Basically,
        # if the parent renderer of a texture is destroyed before the texture
        # itself, destroying or doing anything with that texture will result in a
        # segfault. By wrapping the renderer in a List, we can overwrite it with
        # None when destroyed (within the Renderer class) and detect that in any
        # child textures to avoid any memory-unsafe behaviour.
        return self._renderer_ref[0]

    @property
    def tx(self):
        """:obj:`~sdl2.SDL_Texture`: The underlying base SDL texture object.
        Can be used to perform operations with the texture using the base
        PySDL2 bindings.

        """
        if self._tx is None:
            raise RuntimeError(
                "Cannot use a texture after it has been destroyed."
            )
        elif self._renderer_ref[0] is None:
            raise RuntimeError(
                "Cannot use a texture after its parent renderer has been "
                "destroyed."
            )
        return self._tx

    @property
    def size(self):
        """tuple: The width and height (in pixels) of the texture."""
        tx = self.tx # Makes sure texture is still usable
        return self._size

    def destroy(self):
        """Deletes the texture and frees its associated memory.
        
        When a texture is no longer needed, it should be destroyed using this
        method to free up memory for new textures. After being destroyed, a
        texture can no longer be used.

        """
        if self._tx and self._renderer_ref[0]:
            render.SDL_DestroyTexture(self._tx)
            self._tx = None

    @property
    def scale_mode(self):
        """str: The current scaling mode to use for rendering the texture. Can
        be 'nearest', 'linear', 'best', or 'unknown'.
        
        See :func:`set_texture_scale_quality` for more information.

        .. note::
           For SDL versions older than 2.0.12, the scaling mode will always be
           'unknown'.

        """
        if dll.version < 2012:
            return "unknown"
        modes = ["nearest", "linear", "best"]
        mode_num = c_int()
        ret = render.SDL_GetTextureScaleMode(self.tx, byref(mode_num))
        if ret < 0:
            raise_sdl_err("retrieving the texture scaling mode")
        try:
            return modes[mode_num.value]
        except IndexError:
            return "unknown"

    def set_scale_mode(self, mode):
        """Sets a custom scaling method to use for rendering the texture.

        This method overrides the default texture scaling method specified by
        :func:`set_texture_scale_quality`, the documentation for which describes
        the different possible scaling modes.

        .. note::
           Support for custom per-texture scaling modes is only available in
           SDL2 2.0.12 and up. As such, this method has no effect when used with
           earlier releases of SDL2.

        Args:
            mode (str): The scaling method to use for the current texture. Must
                be one of 'nearest', 'linear', or 'best'.

        """
        if dll.version < 2012:
            return None
        modes = {
            "nearest": render.SDL_ScaleModeNearest,
            "linear": render.SDL_ScaleModeLinear,
            "best": render.SDL_ScaleModeBest,
        }
        mode = mode.lower()
        if mode not in modes.keys():
            raise ValueError(
                "Texture scaling mode must be either 'nearest', 'linear', "
                "or 'best'."
            )
        ret = render.SDL_SetTextureScaleMode(self.tx, modes[mode])
        if ret < 0:
            raise_sdl_err("setting the texture scaling mode")



class Renderer(object):
    """A rendering context for SDL2 windows and sprites.

    A ``Renderer`` can be created from an SDL window, an SDL Surface, or a
    :obj:`~sdl2.ext.SoftwareSprite`. Depending on the settings and operating
    system, this rendering context can use either hardware or software
    acceleration.

    A useful feature of SDL renderers is that that the logical size of the
    rendering context can be different than the actual size (in pixels) of its
    target window or surface. For example, the renderer for a 1024x768 window
    can be set to have a logical size of 640x480, improving performance at the
    cost of image quality. The rendered content will be automatically scaled to
    fit the target, and will be centered with black bars on either side in the
    case of an aspect ratio mismatch.

    If creating a rendering context from a window, you can customize the
    renderer using flags to request different settings:

    =============================== ===========================================
    Flag                            Description
    =============================== ===========================================
    ``SDL_RENDERER_SOFTWARE``       Requests a software-accelerated renderer.
    ``SDL_RENDERER_ACCELERATED``    Requests a hardware-accelerated renderer.
    ``SDL_RENDERER_PRESENTVSYNC``   Enables vsync support for :meth:`present`.
    ``SDL_RENDERER_TARGETTEXTURE``  Requests support for rendering to texture.
    =============================== ===========================================

    To combine multiple flags, you can use a bitwise OR to combine two or more
    together before passing them to the `flags` argument::

      render_flags = (
          sdl2.SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC
      )
      sdl2.ext.Renderer(window, flags=render_flags)

    By default, SDL2 will choose the first renderer backend that supports all
    the requested flags. However, you can also request a specific rendering
    backend by name (e.g. 'opengl', 'opengles2', 'metal', 'direct3d', etc.),
    giving you more control but likely making your code less cross-platform.

    Args:
        target (:obj:`~sdl2.ext.Window`, :obj:`~sdl2.SDL_Surface`): The target
            window or surface from which to create the rendering context.
        backend (str or int, optional): The name of the specific backend to use
            for the new rendering context (e.g. 'opengl'). Defaults to letting
            SDL2 decide. If ``target`` is not an SDL window, this argument has
            no effect.
        logical_size (tuple, optional): The initial logical size (in pixels) of
            the rendering context as a ``(w, h)`` tuple. Defaults to the size
            of the target window or surface.
        flags (int, optional): The requested features and settings for the
            new rendering context. Defaults to requesting a hardware-accelerated
            context. If ``target`` is not an SDL window, this argument has no
            effect.
 
    Raises:
        RuntimeError: If a requested rendering backend is not available.

    """

    def __init__(self, target, backend=-1, logical_size=None,
                 flags=render.SDL_RENDERER_ACCELERATED):
        self._renderer_ref = None
        self.rendertarget = None

        available = self._get_render_drivers()
        if isinstance(backend, str):
            if backend.lower() in available:
                index = available.index(backend.lower())
            else:
                e1 = "'{0}' is not a supported renderer on this system. "
                e2 = "The renderer backend must be one of the following: "
                raise RuntimeError(e1.format(backend) + e2 + str(available))
        else:
            index = backend

        _size = None
        if isinstance(target, Window):
            _renderer = render.SDL_CreateRenderer(target.window, index, flags)
            _size = target.size
        elif isinstance(target, video.SDL_Window):
            _renderer = render.SDL_CreateRenderer(target, index, flags)
            w, h = c_int(0), c_int(0)
            video.SDL_GetWindowSize(target, byref(w), byref(h))
            _size = (w.value, h.value)
        elif isinstance(target, SoftwareSprite):
            _renderer = render.SDL_CreateSoftwareRenderer(target.surface)
            _size = target.size
        elif isinstance(target, surface.SDL_Surface):
            _renderer = render.SDL_CreateSoftwareRenderer(target)
            _size = (target.w, target.h)
        elif "SDL_Surface" in str(type(target)):
            _renderer = render.SDL_CreateSoftwareRenderer(target.contents)
            _size = (target.contents.w, target.contents.h)
        else:
            raise TypeError("unsupported target type")
        if not _renderer:
            raise_sdl_err("creating the SDL renderer")
        error.SDL_ClearError()  # Clear any errors from renderer selection
        self._renderer_ref = [_renderer]

        self.rendertarget = target
        self.color = (0, 0, 0, 0)  # Set black as the default draw color
        self.logical_size = _size
        self._original_logical_size = self.logical_size
        if logical_size is not None:
            self.logical_size = logical_size

    def __del__(self):
        if self._renderer_ref is not None:
            self.destroy()

    def _get_render_drivers(self):
        renderers = []
        drivers = render.SDL_GetNumRenderDrivers()
        for x in range(drivers):
            info = render.SDL_RendererInfo()
            ret = render.SDL_GetRenderDriverInfo(x, info)
            if ret == 0:
                renderers.append(stringify(info.name))
            error.SDL_ClearError()
        return renderers

    @property
    def sdlrenderer(self):
        """:obj:`~sdl2.SDL_Renderer`: The underlying base SDL renderer object.
        Can be used to perform operations with the renderer using the base
        PySDL2 bindings.

        """
        if self._renderer_ref[0] is None:
            raise RuntimeError(
                "Cannot use a renderer after it has been destroyed."
            )
        return self._renderer_ref[0]

    @property
    @deprecated
    def renderer(self):
        return self.sdlrenderer

    @property
    def logical_size(self):
        """tuple: The logical size of the rendering context (in pixels), as a
        ``(width, height)`` tuple.

        """
        w, h = c_int(0), c_int(0)
        render.SDL_RenderGetLogicalSize(self.sdlrenderer, byref(w), byref(h))
        return w.value, h.value

    @logical_size.setter
    def logical_size(self, size):
        width, height = size
        ret = render.SDL_RenderSetLogicalSize(self.sdlrenderer, width, height)
        if ret != 0:
            raise_sdl_err("setting the logical size of the renderer")

    @property
    def color(self):
        """:obj:`~sdl2.ext.Color`: The current drawing color of the renderer."""
        r, g, b, a = Uint8(0), Uint8(0), Uint8(0), Uint8(0)
        ret = render.SDL_GetRenderDrawColor(self.sdlrenderer, byref(r), byref(g),
                                            byref(b), byref(a))
        if ret < 0:
            raise_sdl_err("retrieving the drawing color of the renderer")
        return convert_to_color((r.value, g.value, b.value, a.value))

    @color.setter
    def color(self, value):
        c = convert_to_color(value)
        ret = render.SDL_SetRenderDrawColor(self.sdlrenderer, c.r, c.g, c.b, c.a)
        if ret < 0:
            raise_sdl_err("setting the drawing color of the renderer")

    @property
    def blendmode(self):
        """int: The blend mode used for :meth:`fill` and :meth:`line` drawing
        operations. This value can be any of the following constants:
        
        ========================= ====================================
        Flag                      Description
        ========================= ====================================
        ``SDL_BLENDMODE_NONE``    No blending
        ``SDL_BLENDMODE_BLEND``   Alpha channel blending
        ``SDL_BLENDMODE_ADD``     Additive blending
        ``SDL_BLENDMODE_MOD``     Color modulation
        ``SDL_BLENDMODE_MUL``     Color multiplication (SDL >= 2.0.12)
        ========================= ====================================

        """
        mode = blendmode.SDL_BlendMode()
        ret = render.SDL_GetRenderDrawBlendMode(self.sdlrenderer, byref(mode))
        if ret < 0:
            raise_sdl_err("retrieving the blend mode for the renderer")
        return mode

    @blendmode.setter
    def blendmode(self, value):
        ret = render.SDL_SetRenderDrawBlendMode(self.sdlrenderer, value)
        if ret < 0:
            raise_sdl_err("setting the blend mode for the renderer")

    @property
    def scale(self):
        """tuple: The x/y scaling factors applied to all drawing coordinates
        before rendering, in the format ``(scale_x, scale_y)``. These can be
        used to facilitate resolution-independent drawing.
        
        """
        sx = c_float(0.0)
        sy = c_float(0.0)
        render.SDL_RenderGetScale(self.sdlrenderer, byref(sx), byref(sy))
        return sx.value, sy.value

    @scale.setter
    def scale(self, value):
        if any([s <= 0 for s in value]):
            raise ValueError("Scaling factors must be greater than zero.")
        sx, sy = value
        ret = render.SDL_RenderSetScale(self.sdlrenderer, sx, sy)
        if ret != 0:
            raise_sdl_err("setting the scaling factors for the renderer")

    def destroy(self):
        """Destroys the renderer and any associated textures.

        When a renderer is no longer needed, it should be destroyed using this
        method to free up its associated memory. After being destroyed, a
        renderer can no longer be used.
        
        """
        if self._renderer_ref[0]:
            render.SDL_DestroyRenderer(self._renderer_ref[0])
            self._renderer_ref[0] = None
            self.rendertarget = None

    def reset_logical_size(self):
        """Resets the logical size of the renderer to its original value."""
        self.logical_size = self._original_logical_size

    def clear(self, color=None):
        """Clears the rendering surface with a given color.

        Args:
            color (:obj:`~sdl2.ext.Color`, optional): The color with which to
                clear the entire rendering context. If not specified, the
                renderer's current :attr:`color` will be used.

        """
        if color is None:
            ret = render.SDL_RenderClear(self.sdlrenderer)
        else:
            tmp = self.color
            self.color = color
            ret = render.SDL_RenderClear(self.sdlrenderer)
            self.color = tmp
        if ret < 0:
           raise_sdl_err("clearing the rendering context")

    def copy(self, src, srcrect=None, dstrect=None, angle=0, center=None,
             flip=render.SDL_FLIP_NONE):
        """Copies (blits) a texture to the rendering context.

        If the source texture is an :obj:`~sdl2.SDL_Surface`, you will need
        to convert it into a :obj:`~sdl2.ext.Texture` first before it can be
        copied to the rendering surface.

        The source texture can be flipped horizontally or vertically when
        being copied to the rendering context using one of the following
        flags:

        ========================= ===================================
        Flag                      Description
        ========================= ===================================
        ``SDL_FLIP_NONE``         Does not flip the source (default)
        ``SDL_FLIP_HORIZONTAL``   Flips the source horizontally
        ``SDL_FLIP_VERTICAL``     Flips the source vertically
        ========================= ===================================

        .. note::
           Subpixel rendering (i.e. using floats as pixel coordinates) requires
           SDL 2.0.10 or newer.

        Args:
            src (:obj:`~sdl2.ext.Texture`, :obj:`~sdl2.SDL_Texture`): The source
                texture to copy to the rendering surface.
            srcrect (tuple, optional): An ``(x, y, w, h)`` rectangle defining
                the subset of the source texture to copy to the rendering
                surface. Defaults to copying the entire source texture.
            dstrect (tuple, optional): An ``(x, y, w, h)`` rectangle
                defining the region of the rendering surface to which the 
                source texture will be copied. Alternatively, if only
                ``(x, y)`` coordinates are provided, the width and height of
                the source rectangle will be used. Defaults to stretching
                the source across the entire rendering context.
            angle (float, optional): The clockwise rotation (in degrees) to
                apply to the destination rectangle. Defaults to no rotation.
            center (tuple, optional): The point around with the destination
                rectangle will be rotated. Defaults to the center of the
                destination rectangle.
            flip (int, optional): A flag indicating whether the source should
                be flipped (horizontally or vertically) when rendering to the
                render context. Defaults to no flipping.

        """
        if dll.version < 2010:
            render_copy = render.SDL_RenderCopyEx
            Point = rect.SDL_Point
            Rect = rect.SDL_Rect
        else:
            render_copy = render.SDL_RenderCopyExF
            Point = rect.SDL_FPoint
            Rect = rect.SDL_FRect

        w, h = (None, None)
        if isinstance(src, TextureSprite):
            texture = src.texture
            angle = angle if angle != 0 else src.angle
            center = center if center else src.center
            flip = flip if flip != 0 else src.flip
            w, h = src.size
        elif isinstance(src, Texture):
            texture = src.tx
            w, h = src.size
        elif isinstance(src, render.SDL_Texture):
            texture = src
        else:
            raise TypeError("src must be a Texture object or an SDL_Texture")

        if srcrect:
            x, y, w, h = _sanitize_rects([srcrect])[0]
            srcrect = rect.SDL_Rect(int(x), int(y), int(w), int(h))

        if dstrect:
            if _is_point(dstrect):
                x, y = dstrect
                if srcrect:
                    w, h = (srcrect.w, srcrect.h)
                elif w is None:
                    w, h = _get_texture_size(texture)
            elif _is_rect(dstrect):
                x, y, w, h = dstrect
            else:
                err = "'dstrect' must be a valid point or rectangle."
                raise ValueError(err)
            dstrect = Rect(x, y, w, h)

        if center:
            x, y = _sanitize_points(center)[0]
            center = Point(x, y)

        ret = render_copy(
            self.sdlrenderer, texture, srcrect, dstrect, angle, center, flip
        )
        if ret < 0:
            raise_sdl_err("copying the texture to the rendering context")

    def blit(self, src, srcrect=None, dstrect=None, angle=0, center=None,
             flip=render.SDL_FLIP_NONE):
        """Copies a texture to the rendering context.
        
        An alias for the :meth:`copy` method.

        """
        self.copy(src, srcrect, dstrect, angle, center, flip)

    def rcopy(self, src, loc, size=None, align=(0.0, 0.0), srcrect=None):
        """Copies a texture to the rendering context with relative alignment.

        This method draws a texture to the rendering context using two things:
        a location on the renderer surface, and a point on the texture to align
        to that location. For example, you may want to draw a texture such that
        its center is aligned with the middle of the screen::

           screen_c = [int(n / 2) for n in renderer.logical_size]
           renderer.rcopy(img, loc=screen_c, align=(0.5, 0.5))

        Alignments are specified with an (x, y) tuple of values from 0.0 to 1.0,
        inclusive, indicating the point on the texture to place at the given
        location. ``(0, 0)`` represents the top-left corner of the texture, and
        ``(1, 1)`` represents the bottom right. An alignment of ``(0.5, 0.5)``
        represents the midpoint of the surface.

        Args:
            src (:obj:`~sdl2.ext.Texture`, :obj:`~sdl2.SDL_Texture`): The source
                texture to copy to the rendering surface.
            loc (tuple): The (x, y) pixel coordinates at which to place the
                texture on the surface.
            size (tuple, optional): The scaled ``(width, height)`` output size
                in pixels of the texture on the surface. If not specified, the
                texture will be drawn with its original size.
            align (tuple, optional): The point on the source texture to align to
                the given location. Values can range from ``(0.0, 0.0)``
                (top-left corner) to ``(1.0, 1.0)`` (bottom-right corner).
            srcrect (tuple, optional): An ``(x, y, w, h)`` rectangle defining
                the subset of the source texture to copy to the rendering
                surface. Defaults to copying the entire source texture.

        """
        # Do initial type checking
        if not _is_point(loc):
            raise ValueError("'loc' must be a valid set of (x, y) coordinates")
        if size and not (isiterable(size) and len(size) == 2):
            raise ValueError("'size' must be a valid set of (width, height) values")

        # If using subset of surface and size not given, get size from srcrect
        if srcrect and not size:
            x, y, w, h = _sanitize_rects([srcrect])[0]
            size = (w, h)

        if isinstance(src, TextureSprite):
            texture = src.texture
            w, h = size if size else src.size
        elif isinstance(src, Texture):
            texture = src.tx
            w, h = size if size else src.size
        elif isinstance(src, render.SDL_Texture):
            texture = src
            w, h = size if size else _get_texture_size(texture)
        else:
            raise TypeError("src must be a Texture object or an SDL_Texture")

        # Calcuate the destination rect for the given loc/size/alignment
        loc_x, loc_y = loc
        align_x, align_y = align
        left = loc_x - int(w * align_x)
        top = loc_y - int(h * align_y)

        self.copy(src, srcrect, (left, top, w, h), 0, None, 0)

    def present(self):
        """Presents the current rendering surface to the screen.

        Because SDL renderers use batch rendering (i.e. they have a separate
        backbuffer that is drawn to and buffer shown on screen which are
        switched when this function is called), any drawing operations
        performed with the renderer will not take effect until this method
        is called.

        It is recommended that you clear and redraw the contents of the
        rendering context every time before this method is called, as the
        contents of the buffers are not guaranteed to remain the same between
        repeat presentations.
        
        """
        render.SDL_RenderPresent(self.sdlrenderer)

    def draw_line(self, points, color=None):
        """Draws one or more connected lines on the rendering context.

        .. note::
           Subpixel rendering (i.e. using floats as pixel coordinates) requires
           SDL 2.0.10 or newer.

        Args:
            points (list): A list of 2 or more ``(x, y)`` coordinates or
                :obj:`~sdl2.SDL_Point` objects defining the set of connected
                lines to draw.
            color (:obj:`~sdl2.ext.Color`, optional): The color with which to
                draw the lines. If not specified, the renderer's current
                :attr:`color` will be used.

        """
        if dll.version < 2010:
            draw_lines = render.SDL_RenderDrawLines
            Point = rect.SDL_Point
        else:
            draw_lines = render.SDL_RenderDrawLinesF
            Point = rect.SDL_FPoint

        points = _sanitize_points(points)
        if len(points) < 2:
            raise ValueError("At least two (x, y) points are required.")
        sdlpts = []
        for p in points:
            x, y = p
            sdlpts.append(Point(x, y))
        points_ptr = (Point * len(points))(*sdlpts)

        if color is None:
            ret = draw_lines(self.sdlrenderer, points_ptr, len(points))
        else:
            tmp = self.color
            self.color = color
            ret = draw_lines(self.sdlrenderer, points_ptr, len(points))
            self.color = tmp

        if ret < 0:
            raise_sdl_err("drawing lines to the renderer")

    def draw_point(self, points, color=None):
        """Draws one or more points on the rendering context.

        .. note::
           Subpixel rendering (i.e. using floats as pixel coordinates) requires
           SDL 2.0.10 or newer.

        Args:
            points (list): A list of ``(x, y)`` coordinates or
                :obj:`~sdl2.SDL_Point` objects defining the set of points to
                draw.
            color (:obj:`~sdl2.ext.Color`, optional): The color with which to
                draw the points. If not specified, the renderer's current
                :attr:`color` will be used.

        """
        if dll.version < 2010:
            draw_points = render.SDL_RenderDrawPoints
            Point = rect.SDL_Point
        else:
            draw_points = render.SDL_RenderDrawPointsF
            Point = rect.SDL_FPoint

        points = _sanitize_points(points)
        sdlpts = []
        for p in points:
            x, y = p
            sdlpts.append(Point(x, y))
        points_ptr = (Point * len(points))(*sdlpts)

        if color is None:
            ret = draw_points(self.sdlrenderer, points_ptr, len(points))
        else:
            tmp = self.color
            self.color = color
            ret = draw_points(self.sdlrenderer, points_ptr, len(points))
            self.color = tmp

        if ret < 0:
            raise_sdl_err("drawing points to the renderer")

    def draw_rect(self, rects, color=None):
        """Draws one or more rectangles on the rendering context.

        Rectangles can be specified as 4-item ``(x, y, w, h)`` tuples,
        :obj:`~sdl2.rect.SDL_Rect` objects, or a list containing multiple
        rectangles in either format.

        .. note::
           Subpixel rendering (i.e. using floats as pixel coordinates) requires
           SDL 2.0.10 or newer.

        Args:
            rects (tuple, :obj:`~sdl2.SDL_Rect`, list): The rectangle(s) to draw
                to the rendering context.
            color (:obj:`~sdl2.ext.Color`, optional): The color with which to
                draw the rectangle(s). If not specified, the renderer's current
                :attr:`color` will be used.

        """
        if dll.version < 2010:
            draw_rects = render.SDL_RenderDrawRects
            Rect = rect.SDL_Rect
        else:
            draw_rects = render.SDL_RenderDrawRectsF
            Rect = rect.SDL_FRect

        rects = _sanitize_rects(rects)
        sdlrects = []
        for r in rects:
            x, y, w, h = r
            sdlrects.append(Rect(x, y, w, h))
        rects_ptr = (Rect * len(rects))(*sdlrects)

        if color is None:
            ret = draw_rects(self.sdlrenderer, rects_ptr, len(rects))
        else:
            tmp = self.color
            self.color = color
            ret = draw_rects(self.sdlrenderer, rects_ptr, len(rects))
            self.color = tmp
    
        if ret < 0:
            raise_sdl_err("drawing rectangles to the renderer")

    def fill(self, rects, color=None):
        """Fills one or more rectangular regions the rendering context.

        Fill regions can be specified as 4-item ``(x, y, w, h)`` tuples,
        :obj:`~sdl2.rect.SDL_Rect` objects, or a list containing multiple
        rectangles in either format.

        .. note::
           Subpixel rendering (i.e. using floats as pixel coordinates) requires
           SDL 2.0.10 or newer.

        Args:
            rects (tuple, :obj:`~sdl2.SDL_Rect`, list): The rectangle(s) to fill
                within the rendering context.
            color (:obj:`~sdl2.ext.Color`, optional): The color with which to
                fill the rectangle(s). If not specified, the renderer's current
                :attr:`color` will be used.

        """
        if dll.version < 2010:
            fill_rects = render.SDL_RenderFillRects
            Rect = rect.SDL_Rect
        else:
            fill_rects = render.SDL_RenderFillRectsF
            Rect = rect.SDL_FRect

        rects = _sanitize_rects(rects)
        sdlrects = []
        for r in rects:
            x, y, w, h = r
            sdlrects.append(Rect(x, y, w, h))
        rects_ptr = (Rect * len(rects))(*sdlrects)

        if color is None:
            ret = fill_rects(self.sdlrenderer, rects_ptr, len(rects))
        else:
            tmp = self.color
            self.color = color
            ret = fill_rects(self.sdlrenderer, rects_ptr, len(rects))
            self.color = tmp
    
        if ret < 0:
            raise_sdl_err("filling rectangles in the renderer")