1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#include "SDL_sysvideo.h"
24#include "SDL_video_c.h"
25#include "SDL_RLEaccel_c.h"
26#include "SDL_pixels_c.h"
27#include "SDL_stb_c.h"
28#include "SDL_yuv_c.h"
29#include "../render/SDL_sysrender.h"
30
31#include "SDL_surface_c.h"
32
33
34// Check to make sure we can safely check multiplication of surface w and pitch and it won't overflow size_t
35SDL_COMPILE_TIME_ASSERT(surface_size_assumptions,
36 sizeof(int) == sizeof(Sint32) && sizeof(size_t) >= sizeof(Sint32));
37
38SDL_COMPILE_TIME_ASSERT(can_indicate_overflow, SDL_SIZE_MAX > SDL_MAX_SINT32);
39
40// Magic!
41static char SDL_surface_magic;
42
43// Public routines
44
45bool SDL_SurfaceValid(SDL_Surface *surface)
46{
47 return (surface && surface->reserved == &SDL_surface_magic);
48}
49
50void SDL_UpdateSurfaceLockFlag(SDL_Surface *surface)
51{
52 if (SDL_SurfaceHasRLE(surface)) {
53 surface->flags |= SDL_SURFACE_LOCK_NEEDED;
54 } else {
55 surface->flags &= ~SDL_SURFACE_LOCK_NEEDED;
56 }
57}
58
59/*
60 * Calculate the pad-aligned scanline width of a surface.
61 *
62 * for FOURCC, use SDL_CalculateYUVSize()
63 */
64static bool SDL_CalculateRGBSize(Uint32 format, size_t width, size_t height, size_t *size, size_t *pitch, bool minimal)
65{
66 if (SDL_BITSPERPIXEL(format) >= 8) {
67 if (!SDL_size_mul_check_overflow(width, SDL_BYTESPERPIXEL(format), pitch)) {
68 return SDL_SetError("width * bpp would overflow");
69 }
70 } else {
71 if (!SDL_size_mul_check_overflow(width, SDL_BITSPERPIXEL(format), pitch)) {
72 return SDL_SetError("width * bpp would overflow");
73 }
74 if (!SDL_size_add_check_overflow(*pitch, 7, pitch)) {
75 return SDL_SetError("aligning pitch would overflow");
76 }
77 *pitch /= 8;
78 }
79 if (!minimal) {
80 // 4-byte aligning for speed
81 if (!SDL_size_add_check_overflow(*pitch, 3, pitch)) {
82 return SDL_SetError("aligning pitch would overflow");
83 }
84 *pitch &= ~3;
85 }
86
87 if (!SDL_size_mul_check_overflow(height, *pitch, size)) {
88 return SDL_SetError("height * pitch would overflow");
89 }
90
91 return true;
92}
93
94bool SDL_CalculateSurfaceSize(SDL_PixelFormat format, int width, int height, size_t *size, size_t *pitch, bool minimalPitch)
95{
96 size_t p = 0, sz = 0;
97
98 if (size) {
99 *size = 0;
100 }
101
102 if (pitch) {
103 *pitch = 0;
104 }
105
106 if (SDL_ISPIXELFORMAT_FOURCC(format)) {
107 if (format == SDL_PIXELFORMAT_MJPG) {
108 // We don't know in advance what it will be, we'll figure it out later.
109 return true;
110 }
111
112 if (!SDL_CalculateYUVSize(format, width, height, &sz, &p)) {
113 // Overflow...
114 return false;
115 }
116 } else {
117 if (!SDL_CalculateRGBSize(format, width, height, &sz, &p, minimalPitch)) {
118 // Overflow...
119 return false;
120 }
121 }
122
123 if (size) {
124 *size = sz;
125 }
126
127 if (pitch) {
128 *pitch = p;
129 }
130
131 return true;
132}
133
134static bool SDL_InitializeSurface(SDL_Surface *surface, int width, int height, SDL_PixelFormat format, SDL_Colorspace colorspace, SDL_PropertiesID props, void *pixels, int pitch, bool onstack)
135{
136 SDL_zerop(surface);
137
138 surface->flags = SDL_SURFACE_PREALLOCATED;
139 surface->format = format;
140 surface->w = width;
141 surface->h = height;
142 surface->pixels = pixels;
143 surface->pitch = pitch;
144 surface->reserved = &SDL_surface_magic;
145
146 if (onstack) {
147 surface->internal_flags |= SDL_INTERNAL_SURFACE_STACK;
148 }
149
150 surface->fmt = SDL_GetPixelFormatDetails(format);
151 if (!surface->fmt) {
152 SDL_DestroySurface(surface);
153 return false;
154 }
155
156 // Initialize the clip rect
157 surface->clip_rect.w = width;
158 surface->clip_rect.h = height;
159
160 // Allocate an empty mapping
161 surface->map.info.r = 0xFF;
162 surface->map.info.g = 0xFF;
163 surface->map.info.b = 0xFF;
164 surface->map.info.a = 0xFF;
165
166 if (colorspace == SDL_COLORSPACE_UNKNOWN) {
167 surface->colorspace = SDL_GetDefaultColorspaceForFormat(format);
168 } else {
169 surface->colorspace = colorspace;
170 }
171
172 if (props) {
173 if (!SDL_CopyProperties(props, SDL_GetSurfaceProperties(surface))) {
174 SDL_DestroySurface(surface);
175 return false;
176 }
177 }
178
179 // By default surfaces with an alpha mask are set up for blending
180 if (SDL_ISPIXELFORMAT_ALPHA(surface->format)) {
181 SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
182 }
183
184 // The surface is ready to go
185 surface->refcount = 1;
186 return true;
187}
188
189/*
190 * Create an empty surface of the appropriate depth using the given format
191 */
192SDL_Surface *SDL_CreateSurface(int width, int height, SDL_PixelFormat format)
193{
194 size_t pitch, size;
195 SDL_Surface *surface;
196
197 if (width < 0) {
198 SDL_InvalidParamError("width");
199 return NULL;
200 }
201
202 if (height < 0) {
203 SDL_InvalidParamError("height");
204 return NULL;
205 }
206
207 if (!SDL_CalculateSurfaceSize(format, width, height, &size, &pitch, false /* not minimal pitch */)) {
208 // Overflow...
209 return NULL;
210 }
211
212 // Allocate and initialize the surface
213 surface = (SDL_Surface *)SDL_malloc(sizeof(*surface));
214 if (!surface) {
215 return NULL;
216 }
217
218 if (!SDL_InitializeSurface(surface, width, height, format, SDL_COLORSPACE_UNKNOWN, 0, NULL, (int)pitch, false)) {
219 return NULL;
220 }
221
222 if (surface->w && surface->h && format != SDL_PIXELFORMAT_MJPG) {
223 surface->flags &= ~SDL_SURFACE_PREALLOCATED;
224 surface->pixels = SDL_aligned_alloc(SDL_GetSIMDAlignment(), size);
225 if (!surface->pixels) {
226 SDL_DestroySurface(surface);
227 return NULL;
228 }
229 surface->flags |= SDL_SURFACE_SIMD_ALIGNED;
230
231 // This is important for bitmaps
232 SDL_memset(surface->pixels, 0, size);
233 }
234 return surface;
235}
236
237/*
238 * Create an RGB surface from an existing memory buffer using the given
239 * enum SDL_PIXELFORMAT_* format
240 */
241SDL_Surface *SDL_CreateSurfaceFrom(int width, int height, SDL_PixelFormat format, void *pixels, int pitch)
242{
243 if (width < 0) {
244 SDL_InvalidParamError("width");
245 return NULL;
246 }
247
248 if (height < 0) {
249 SDL_InvalidParamError("height");
250 return NULL;
251 }
252
253 if (pitch == 0 && !pixels) {
254 // The application will fill these in later with valid values
255 } else {
256 size_t minimalPitch;
257
258 if (!SDL_CalculateSurfaceSize(format, width, height, NULL, &minimalPitch, true /* minimal pitch */)) {
259 // Overflow...
260 return NULL;
261 }
262
263 if (pitch < 0 || (size_t)pitch < minimalPitch) {
264 SDL_InvalidParamError("pitch");
265 return NULL;
266 }
267 }
268
269 // Allocate and initialize the surface
270 SDL_Surface *surface = (SDL_Surface *)SDL_malloc(sizeof(*surface));
271 if (!surface ||
272 !SDL_InitializeSurface(surface, width, height, format, SDL_COLORSPACE_UNKNOWN, 0, pixels, pitch, false)) {
273 return NULL;
274 }
275 return surface;
276}
277
278SDL_PropertiesID SDL_GetSurfaceProperties(SDL_Surface *surface)
279{
280 if (!SDL_SurfaceValid(surface)) {
281 SDL_InvalidParamError("surface");
282 return 0;
283 }
284
285 if (!surface->props) {
286 surface->props = SDL_CreateProperties();
287 }
288 return surface->props;
289}
290
291bool SDL_SetSurfaceColorspace(SDL_Surface *surface, SDL_Colorspace colorspace)
292{
293 if (!SDL_SurfaceValid(surface)) {
294 return SDL_InvalidParamError("surface");
295 }
296
297 surface->colorspace = colorspace;
298 return true;
299}
300
301SDL_Colorspace SDL_GetSurfaceColorspace(SDL_Surface *surface)
302{
303 if (!SDL_SurfaceValid(surface)) {
304 return SDL_COLORSPACE_UNKNOWN;
305 }
306
307 return surface->colorspace;
308}
309
310float SDL_GetDefaultSDRWhitePoint(SDL_Colorspace colorspace)
311{
312 return SDL_GetSurfaceSDRWhitePoint(NULL, colorspace);
313}
314
315float SDL_GetSurfaceSDRWhitePoint(SDL_Surface *surface, SDL_Colorspace colorspace)
316{
317 SDL_TransferCharacteristics transfer = SDL_COLORSPACETRANSFER(colorspace);
318
319 if (transfer == SDL_TRANSFER_CHARACTERISTICS_LINEAR ||
320 transfer == SDL_TRANSFER_CHARACTERISTICS_PQ) {
321 SDL_PropertiesID props;
322 float default_value = 1.0f;
323
324 if (SDL_SurfaceValid(surface)) {
325 props = surface->props;
326 } else {
327 props = 0;
328 }
329 if (transfer == SDL_TRANSFER_CHARACTERISTICS_PQ) {
330 /* The older standards use an SDR white point of 100 nits.
331 * ITU-R BT.2408-6 recommends using an SDR white point of 203 nits.
332 * This is the default Chrome uses, and what a lot of game content
333 * assumes, so we'll go with that.
334 */
335 const float DEFAULT_PQ_SDR_WHITE_POINT = 203.0f;
336 default_value = DEFAULT_PQ_SDR_WHITE_POINT;
337 }
338 return SDL_GetFloatProperty(props, SDL_PROP_SURFACE_SDR_WHITE_POINT_FLOAT, default_value);
339 }
340 return 1.0f;
341}
342
343float SDL_GetDefaultHDRHeadroom(SDL_Colorspace colorspace)
344{
345 return SDL_GetSurfaceHDRHeadroom(NULL, colorspace);
346}
347
348float SDL_GetSurfaceHDRHeadroom(SDL_Surface *surface, SDL_Colorspace colorspace)
349{
350 SDL_TransferCharacteristics transfer = SDL_COLORSPACETRANSFER(colorspace);
351
352 if (transfer == SDL_TRANSFER_CHARACTERISTICS_LINEAR ||
353 transfer == SDL_TRANSFER_CHARACTERISTICS_PQ) {
354 SDL_PropertiesID props;
355 float default_value = 0.0f;
356
357 if (SDL_SurfaceValid(surface)) {
358 props = surface->props;
359 } else {
360 props = 0;
361 }
362 return SDL_GetFloatProperty(props, SDL_PROP_SURFACE_HDR_HEADROOM_FLOAT, default_value);
363 }
364 return 1.0f;
365}
366
367SDL_Palette *SDL_CreateSurfacePalette(SDL_Surface *surface)
368{
369 SDL_Palette *palette;
370
371 if (!SDL_SurfaceValid(surface)) {
372 SDL_InvalidParamError("surface");
373 return NULL;
374 }
375
376 if (!SDL_ISPIXELFORMAT_INDEXED(surface->format)) {
377 SDL_SetError("The surface is not indexed format");
378 return NULL;
379 }
380
381 palette = SDL_CreatePalette((1 << SDL_BITSPERPIXEL(surface->format)));
382 if (!palette) {
383 return NULL;
384 }
385
386 if (palette->ncolors == 2) {
387 // Create a black and white bitmap palette
388 palette->colors[0].r = 0xFF;
389 palette->colors[0].g = 0xFF;
390 palette->colors[0].b = 0xFF;
391 palette->colors[1].r = 0x00;
392 palette->colors[1].g = 0x00;
393 palette->colors[1].b = 0x00;
394 }
395
396 if (!SDL_SetSurfacePalette(surface, palette)) {
397 SDL_DestroyPalette(palette);
398 return NULL;
399 }
400
401 // The surface has retained the palette, we can remove the reference here
402 SDL_assert(palette->refcount == 2);
403 SDL_DestroyPalette(palette);
404 return palette;
405}
406
407bool SDL_SetSurfacePalette(SDL_Surface *surface, SDL_Palette *palette)
408{
409 if (!SDL_SurfaceValid(surface)) {
410 return SDL_InvalidParamError("surface");
411 }
412
413 if (palette && palette->ncolors > (1 << SDL_BITSPERPIXEL(surface->format))) {
414 return SDL_SetError("SDL_SetSurfacePalette() passed a palette that doesn't match the surface format");
415 }
416
417 if (palette != surface->palette) {
418 if (surface->palette) {
419 SDL_DestroyPalette(surface->palette);
420 }
421
422 surface->palette = palette;
423
424 if (surface->palette) {
425 ++surface->palette->refcount;
426 }
427 }
428
429 SDL_InvalidateMap(&surface->map);
430
431 return true;
432}
433
434SDL_Palette *SDL_GetSurfacePalette(SDL_Surface *surface)
435{
436 if (!SDL_SurfaceValid(surface)) {
437 return NULL;
438 }
439
440 return surface->palette;
441}
442
443bool SDL_AddSurfaceAlternateImage(SDL_Surface *surface, SDL_Surface *image)
444{
445 if (!SDL_SurfaceValid(surface)) {
446 return SDL_InvalidParamError("surface");
447 }
448
449 if (!SDL_SurfaceValid(image)) {
450 return SDL_InvalidParamError("image");
451 }
452
453 SDL_Surface **images = (SDL_Surface **)SDL_realloc(surface->images, (surface->num_images + 1) * sizeof(*images));
454 if (!images) {
455 return false;
456 }
457 images[surface->num_images] = image;
458 surface->images = images;
459 ++surface->num_images;
460 ++image->refcount;
461 return true;
462}
463
464bool SDL_SurfaceHasAlternateImages(SDL_Surface *surface)
465{
466 if (!SDL_SurfaceValid(surface)) {
467 return false;
468 }
469
470 return (surface->num_images > 0);
471}
472
473SDL_Surface **SDL_GetSurfaceImages(SDL_Surface *surface, int *count)
474{
475 if (count) {
476 *count = 0;
477 }
478
479 if (!SDL_SurfaceValid(surface)) {
480 SDL_InvalidParamError("surface");
481 return NULL;
482 }
483
484 int num_images = 1 + surface->num_images;
485 SDL_Surface **images = (SDL_Surface **)SDL_malloc((num_images + 1) * sizeof(*images));
486 if (!images) {
487 return NULL;
488 }
489 images[0] = surface;
490 if (surface->num_images > 0) {
491 SDL_memcpy(&images[1], surface->images, (surface->num_images * sizeof(images[1])));
492 }
493 images[num_images] = NULL;
494
495 if (count) {
496 *count = num_images;
497 }
498 return images;
499}
500
501SDL_Surface *SDL_GetSurfaceImage(SDL_Surface *surface, float display_scale)
502{
503 if (!SDL_SurfaceValid(surface)) {
504 SDL_InvalidParamError("surface");
505 return NULL;
506 }
507
508 if (!SDL_SurfaceHasAlternateImages(surface)) {
509 ++surface->refcount;
510 return surface;
511 }
512
513 // This surface has high DPI images, pick the best one available, or scale one to the correct size
514 SDL_Surface **images = SDL_GetSurfaceImages(surface, NULL);
515 if (!images) {
516 // Failure, fall back to the existing surface
517 ++surface->refcount;
518 return surface;
519 }
520
521 // Find closest image. Images that are larger than the
522 // desired size are preferred over images that are smaller.
523 SDL_Surface *closest = NULL;
524 int desired_w = (int)SDL_round(surface->w * display_scale);
525 int desired_h = (int)SDL_round(surface->h * display_scale);
526 int desired_size = desired_w * desired_h;
527 int closest_distance = -1;
528 int closest_size = -1;
529 for (int i = 0; images[i]; ++i) {
530 SDL_Surface *candidate = images[i];
531 int size = candidate->w * candidate->h;
532 int delta_w = (candidate->w - desired_w);
533 int delta_h = (candidate->h - desired_h);
534 int distance = (delta_w * delta_w) + (delta_h * delta_h);
535 if (closest_distance < 0 || distance < closest_distance ||
536 (size > desired_size && closest_size < desired_size)) {
537 closest = candidate;
538 closest_distance = distance;
539 closest_size = size;
540 }
541 }
542 SDL_free(images);
543 SDL_assert(closest != NULL); // We should always have at least one surface
544
545 if (closest->w == desired_w && closest->h == desired_h) {
546 ++closest->refcount;
547 return closest;
548 }
549
550 // We need to scale the image to the correct size. To maintain good image quality, downscaling
551 // is done in steps, never reducing the width and height by more than half each time.
552 SDL_Surface *scaled = closest;
553 do {
554 int next_scaled_w = SDL_max(desired_w, (scaled->w + 1) / 2);
555 int next_scaled_h = SDL_max(desired_h, (scaled->h + 1) / 2);
556 SDL_Surface *next_scaled = SDL_ScaleSurface(scaled, next_scaled_w, next_scaled_h, SDL_SCALEMODE_LINEAR);
557 if (scaled != closest) {
558 SDL_DestroySurface(scaled);
559 }
560 scaled = next_scaled;
561 if (!scaled) {
562 // Failure, fall back to the closest surface
563 ++closest->refcount;
564 return closest;
565 }
566 } while (scaled->w != desired_w || scaled->h != desired_h);
567
568 return scaled;
569}
570
571void SDL_RemoveSurfaceAlternateImages(SDL_Surface *surface)
572{
573 if (!SDL_SurfaceValid(surface)) {
574 return;
575 }
576
577 if (surface->num_images > 0) {
578 for (int i = 0; i < surface->num_images; ++i) {
579 SDL_DestroySurface(surface->images[i]);
580 }
581 SDL_free(surface->images);
582 surface->images = NULL;
583 surface->num_images = 0;
584 }
585}
586
587bool SDL_SetSurfaceRLE(SDL_Surface *surface, bool enabled)
588{
589 int flags;
590
591 if (!SDL_SurfaceValid(surface)) {
592 return SDL_InvalidParamError("surface");
593 }
594
595 flags = surface->map.info.flags;
596 if (enabled) {
597 surface->map.info.flags |= SDL_COPY_RLE_DESIRED;
598 } else {
599 surface->map.info.flags &= ~SDL_COPY_RLE_DESIRED;
600 }
601 if (surface->map.info.flags != flags) {
602 SDL_InvalidateMap(&surface->map);
603 }
604 SDL_UpdateSurfaceLockFlag(surface);
605 return true;
606}
607
608bool SDL_SurfaceHasRLE(SDL_Surface *surface)
609{
610 if (!SDL_SurfaceValid(surface)) {
611 return false;
612 }
613
614 if (!(surface->map.info.flags & SDL_COPY_RLE_DESIRED)) {
615 return false;
616 }
617
618 return true;
619}
620
621bool SDL_SetSurfaceColorKey(SDL_Surface *surface, bool enabled, Uint32 key)
622{
623 int flags;
624
625 if (!SDL_SurfaceValid(surface)) {
626 return SDL_InvalidParamError("surface");
627 }
628
629 if (surface->palette && key >= ((Uint32)surface->palette->ncolors)) {
630 return SDL_InvalidParamError("key");
631 }
632
633 flags = surface->map.info.flags;
634 if (enabled) {
635 surface->map.info.flags |= SDL_COPY_COLORKEY;
636 surface->map.info.colorkey = key;
637 } else {
638 surface->map.info.flags &= ~SDL_COPY_COLORKEY;
639 }
640 if (surface->map.info.flags != flags) {
641 SDL_InvalidateMap(&surface->map);
642 }
643
644 return true;
645}
646
647bool SDL_SurfaceHasColorKey(SDL_Surface *surface)
648{
649 if (!SDL_SurfaceValid(surface)) {
650 return false;
651 }
652
653 if (!(surface->map.info.flags & SDL_COPY_COLORKEY)) {
654 return false;
655 }
656
657 return true;
658}
659
660bool SDL_GetSurfaceColorKey(SDL_Surface *surface, Uint32 *key)
661{
662 if (key) {
663 *key = 0;
664 }
665
666 if (!SDL_SurfaceValid(surface)) {
667 return SDL_InvalidParamError("surface");
668 }
669
670 if (!(surface->map.info.flags & SDL_COPY_COLORKEY)) {
671 return SDL_SetError("Surface doesn't have a colorkey");
672 }
673
674 if (key) {
675 *key = surface->map.info.colorkey;
676 }
677 return true;
678}
679
680/* This is a fairly slow function to switch from colorkey to alpha
681 NB: it doesn't handle bpp 1 or 3, because they have no alpha channel */
682static void SDL_ConvertColorkeyToAlpha(SDL_Surface *surface, bool ignore_alpha)
683{
684 int x, y, bpp;
685
686 if (!SDL_SurfaceValid(surface)) {
687 return;
688 }
689
690 if (!(surface->map.info.flags & SDL_COPY_COLORKEY) ||
691 !SDL_ISPIXELFORMAT_ALPHA(surface->format)) {
692 return;
693 }
694
695 bpp = SDL_BYTESPERPIXEL(surface->format);
696
697 SDL_LockSurface(surface);
698
699 if (bpp == 2) {
700 Uint16 *row, *spot;
701 Uint16 ckey = (Uint16)surface->map.info.colorkey;
702 Uint16 mask = (Uint16)(~surface->fmt->Amask);
703
704 // Ignore, or not, alpha in colorkey comparison
705 if (ignore_alpha) {
706 ckey &= mask;
707 row = (Uint16 *)surface->pixels;
708 for (y = surface->h; y--;) {
709 spot = row;
710 for (x = surface->w; x--;) {
711 if ((*spot & mask) == ckey) {
712 *spot &= mask;
713 }
714 ++spot;
715 }
716 row += surface->pitch / 2;
717 }
718 } else {
719 row = (Uint16 *)surface->pixels;
720 for (y = surface->h; y--;) {
721 spot = row;
722 for (x = surface->w; x--;) {
723 if (*spot == ckey) {
724 *spot &= mask;
725 }
726 ++spot;
727 }
728 row += surface->pitch / 2;
729 }
730 }
731 } else if (bpp == 4) {
732 Uint32 *row, *spot;
733 Uint32 ckey = surface->map.info.colorkey;
734 Uint32 mask = ~surface->fmt->Amask;
735
736 // Ignore, or not, alpha in colorkey comparison
737 if (ignore_alpha) {
738 ckey &= mask;
739 row = (Uint32 *)surface->pixels;
740 for (y = surface->h; y--;) {
741 spot = row;
742 for (x = surface->w; x--;) {
743 if ((*spot & mask) == ckey) {
744 *spot &= mask;
745 }
746 ++spot;
747 }
748 row += surface->pitch / 4;
749 }
750 } else {
751 row = (Uint32 *)surface->pixels;
752 for (y = surface->h; y--;) {
753 spot = row;
754 for (x = surface->w; x--;) {
755 if (*spot == ckey) {
756 *spot &= mask;
757 }
758 ++spot;
759 }
760 row += surface->pitch / 4;
761 }
762 }
763 }
764
765 SDL_UnlockSurface(surface);
766
767 SDL_SetSurfaceColorKey(surface, false, 0);
768 SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
769}
770
771bool SDL_SetSurfaceColorMod(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b)
772{
773 int flags;
774
775 if (!SDL_SurfaceValid(surface)) {
776 return SDL_InvalidParamError("surface");
777 }
778
779 surface->map.info.r = r;
780 surface->map.info.g = g;
781 surface->map.info.b = b;
782
783 flags = surface->map.info.flags;
784 if (r != 0xFF || g != 0xFF || b != 0xFF) {
785 surface->map.info.flags |= SDL_COPY_MODULATE_COLOR;
786 } else {
787 surface->map.info.flags &= ~SDL_COPY_MODULATE_COLOR;
788 }
789 if (surface->map.info.flags != flags) {
790 SDL_InvalidateMap(&surface->map);
791 }
792 return true;
793}
794
795bool SDL_GetSurfaceColorMod(SDL_Surface *surface, Uint8 *r, Uint8 *g, Uint8 *b)
796{
797 if (!SDL_SurfaceValid(surface)) {
798 if (r) {
799 *r = 255;
800 }
801 if (g) {
802 *g = 255;
803 }
804 if (b) {
805 *b = 255;
806 }
807 return SDL_InvalidParamError("surface");
808 }
809
810 if (r) {
811 *r = surface->map.info.r;
812 }
813 if (g) {
814 *g = surface->map.info.g;
815 }
816 if (b) {
817 *b = surface->map.info.b;
818 }
819 return true;
820}
821
822bool SDL_SetSurfaceAlphaMod(SDL_Surface *surface, Uint8 alpha)
823{
824 int flags;
825
826 if (!SDL_SurfaceValid(surface)) {
827 return SDL_InvalidParamError("surface");
828 }
829
830 surface->map.info.a = alpha;
831
832 flags = surface->map.info.flags;
833 if (alpha != 0xFF) {
834 surface->map.info.flags |= SDL_COPY_MODULATE_ALPHA;
835 } else {
836 surface->map.info.flags &= ~SDL_COPY_MODULATE_ALPHA;
837 }
838 if (surface->map.info.flags != flags) {
839 SDL_InvalidateMap(&surface->map);
840 }
841 return true;
842}
843
844bool SDL_GetSurfaceAlphaMod(SDL_Surface *surface, Uint8 *alpha)
845{
846 if (!SDL_SurfaceValid(surface)) {
847 if (alpha) {
848 *alpha = 255;
849 }
850 return SDL_InvalidParamError("surface");
851 }
852
853 if (alpha) {
854 *alpha = surface->map.info.a;
855 }
856 return true;
857}
858
859bool SDL_SetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode blendMode)
860{
861 int flags;
862 bool result = true;
863
864 if (!SDL_SurfaceValid(surface)) {
865 return SDL_InvalidParamError("surface");
866 }
867
868 if (blendMode == SDL_BLENDMODE_INVALID) {
869 return SDL_InvalidParamError("blendMode");
870 }
871
872 flags = surface->map.info.flags;
873 surface->map.info.flags &= ~SDL_COPY_BLEND_MASK;
874 switch (blendMode) {
875 case SDL_BLENDMODE_NONE:
876 break;
877 case SDL_BLENDMODE_BLEND:
878 surface->map.info.flags |= SDL_COPY_BLEND;
879 break;
880 case SDL_BLENDMODE_BLEND_PREMULTIPLIED:
881 surface->map.info.flags |= SDL_COPY_BLEND_PREMULTIPLIED;
882 break;
883 case SDL_BLENDMODE_ADD:
884 surface->map.info.flags |= SDL_COPY_ADD;
885 break;
886 case SDL_BLENDMODE_ADD_PREMULTIPLIED:
887 surface->map.info.flags |= SDL_COPY_ADD_PREMULTIPLIED;
888 break;
889 case SDL_BLENDMODE_MOD:
890 surface->map.info.flags |= SDL_COPY_MOD;
891 break;
892 case SDL_BLENDMODE_MUL:
893 surface->map.info.flags |= SDL_COPY_MUL;
894 break;
895 default:
896 result = SDL_Unsupported();
897 break;
898 }
899
900 if (surface->map.info.flags != flags) {
901 SDL_InvalidateMap(&surface->map);
902 }
903
904 return result;
905}
906
907bool SDL_GetSurfaceBlendMode(SDL_Surface *surface, SDL_BlendMode *blendMode)
908{
909 if (blendMode) {
910 *blendMode = SDL_BLENDMODE_INVALID;
911 }
912
913 if (!SDL_SurfaceValid(surface)) {
914 return SDL_InvalidParamError("surface");
915 }
916
917 if (!blendMode) {
918 return true;
919 }
920
921 switch (surface->map.info.flags & SDL_COPY_BLEND_MASK) {
922 case SDL_COPY_BLEND:
923 *blendMode = SDL_BLENDMODE_BLEND;
924 break;
925 case SDL_COPY_BLEND_PREMULTIPLIED:
926 *blendMode = SDL_BLENDMODE_BLEND_PREMULTIPLIED;
927 break;
928 case SDL_COPY_ADD:
929 *blendMode = SDL_BLENDMODE_ADD;
930 break;
931 case SDL_COPY_ADD_PREMULTIPLIED:
932 *blendMode = SDL_BLENDMODE_ADD_PREMULTIPLIED;
933 break;
934 case SDL_COPY_MOD:
935 *blendMode = SDL_BLENDMODE_MOD;
936 break;
937 case SDL_COPY_MUL:
938 *blendMode = SDL_BLENDMODE_MUL;
939 break;
940 default:
941 *blendMode = SDL_BLENDMODE_NONE;
942 break;
943 }
944 return true;
945}
946
947bool SDL_SetSurfaceClipRect(SDL_Surface *surface, const SDL_Rect *rect)
948{
949 SDL_Rect full_rect;
950
951 // Don't do anything if there's no surface to act on
952 if (!SDL_SurfaceValid(surface)) {
953 return false;
954 }
955
956 // Set up the full surface rectangle
957 full_rect.x = 0;
958 full_rect.y = 0;
959 full_rect.w = surface->w;
960 full_rect.h = surface->h;
961
962 // Set the clipping rectangle
963 if (!rect) {
964 surface->clip_rect = full_rect;
965 return true;
966 }
967 return SDL_GetRectIntersection(rect, &full_rect, &surface->clip_rect);
968}
969
970bool SDL_GetSurfaceClipRect(SDL_Surface *surface, SDL_Rect *rect)
971{
972 if (!SDL_SurfaceValid(surface)) {
973 if (rect) {
974 SDL_zerop(rect);
975 }
976 return SDL_InvalidParamError("surface");
977 }
978 if (!rect) {
979 return SDL_InvalidParamError("rect");
980 }
981 *rect = surface->clip_rect;
982 return true;
983}
984
985/*
986 * Set up a blit between two surfaces -- split into three parts:
987 * The upper part, SDL_BlitSurface(), performs clipping and rectangle
988 * verification. The lower part is a pointer to a low level
989 * accelerated blitting function.
990 *
991 * These parts are separated out and each used internally by this
992 * library in the optimum places. They are exported so that if
993 * you know exactly what you are doing, you can optimize your code
994 * by calling the one(s) you need.
995 */
996bool SDL_BlitSurfaceUnchecked(SDL_Surface *src, const SDL_Rect *srcrect,
997 SDL_Surface *dst, const SDL_Rect *dstrect)
998{
999 // Check to make sure the blit mapping is valid
1000 if (!SDL_ValidateMap(src, dst)) {
1001 return false;
1002 }
1003 return src->map.blit(src, srcrect, dst, dstrect);
1004}
1005
1006bool SDL_BlitSurface(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)
1007{
1008 SDL_Rect r_src, r_dst;
1009
1010 // Make sure the surfaces aren't locked
1011 if (!SDL_SurfaceValid(src)) {
1012 return SDL_InvalidParamError("src");
1013 } else if (!SDL_SurfaceValid(dst)) {
1014 return SDL_InvalidParamError("dst");
1015 } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) {
1016 return SDL_SetError("Surfaces must not be locked during blit");
1017 }
1018
1019 // Full src surface
1020 r_src.x = 0;
1021 r_src.y = 0;
1022 r_src.w = src->w;
1023 r_src.h = src->h;
1024
1025 if (dstrect) {
1026 r_dst.x = dstrect->x;
1027 r_dst.y = dstrect->y;
1028 } else {
1029 r_dst.x = 0;
1030 r_dst.y = 0;
1031 }
1032
1033 // clip the source rectangle to the source surface
1034 if (srcrect) {
1035 SDL_Rect tmp;
1036 if (SDL_GetRectIntersection(srcrect, &r_src, &tmp) == false) {
1037 return true;
1038 }
1039
1040 // Shift dstrect, if srcrect origin has changed
1041 r_dst.x += tmp.x - srcrect->x;
1042 r_dst.y += tmp.y - srcrect->y;
1043
1044 // Update srcrect
1045 r_src = tmp;
1046 }
1047
1048 // There're no dstrect.w/h parameters. It's the same as srcrect
1049 r_dst.w = r_src.w;
1050 r_dst.h = r_src.h;
1051
1052 // clip the destination rectangle against the clip rectangle
1053 {
1054 SDL_Rect tmp;
1055 if (SDL_GetRectIntersection(&r_dst, &dst->clip_rect, &tmp) == false) {
1056 return true;
1057 }
1058
1059 // Shift srcrect, if dstrect has changed
1060 r_src.x += tmp.x - r_dst.x;
1061 r_src.y += tmp.y - r_dst.y;
1062 r_src.w = tmp.w;
1063 r_src.h = tmp.h;
1064
1065 // Update dstrect
1066 r_dst = tmp;
1067 }
1068
1069 if (r_dst.w <= 0 || r_dst.h <= 0) {
1070 // No-op.
1071 return true;
1072 }
1073
1074 // Switch back to a fast blit if we were previously stretching
1075 if (src->map.info.flags & SDL_COPY_NEAREST) {
1076 src->map.info.flags &= ~SDL_COPY_NEAREST;
1077 SDL_InvalidateMap(&src->map);
1078 }
1079
1080 return SDL_BlitSurfaceUnchecked(src, &r_src, dst, &r_dst);
1081}
1082
1083bool SDL_BlitSurfaceScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)
1084{
1085 SDL_Rect *clip_rect;
1086 double src_x0, src_y0, src_x1, src_y1;
1087 double dst_x0, dst_y0, dst_x1, dst_y1;
1088 SDL_Rect final_src, final_dst;
1089 double scaling_w, scaling_h;
1090 int src_w, src_h;
1091 int dst_w, dst_h;
1092
1093 // Make sure the surfaces aren't locked
1094 if (!SDL_SurfaceValid(src)) {
1095 return SDL_InvalidParamError("src");
1096 } else if (!SDL_SurfaceValid(dst)) {
1097 return SDL_InvalidParamError("dst");
1098 } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) {
1099 return SDL_SetError("Surfaces must not be locked during blit");
1100 }
1101
1102 switch (scaleMode) {
1103 case SDL_SCALEMODE_NEAREST:
1104 break;
1105 case SDL_SCALEMODE_LINEAR:
1106 break;
1107 case SDL_SCALEMODE_PIXELART:
1108 scaleMode = SDL_SCALEMODE_NEAREST;
1109 break;
1110 default:
1111 return SDL_InvalidParamError("scaleMode");
1112 }
1113
1114 if (!srcrect) {
1115 src_w = src->w;
1116 src_h = src->h;
1117 } else {
1118 src_w = srcrect->w;
1119 src_h = srcrect->h;
1120 }
1121
1122 if (!dstrect) {
1123 dst_w = dst->w;
1124 dst_h = dst->h;
1125 } else {
1126 dst_w = dstrect->w;
1127 dst_h = dstrect->h;
1128 }
1129
1130 if (dst_w == src_w && dst_h == src_h) {
1131 // No scaling, defer to regular blit
1132 return SDL_BlitSurface(src, srcrect, dst, dstrect);
1133 }
1134
1135 scaling_w = (double)dst_w / src_w;
1136 scaling_h = (double)dst_h / src_h;
1137
1138 if (!dstrect) {
1139 dst_x0 = 0;
1140 dst_y0 = 0;
1141 dst_x1 = dst_w;
1142 dst_y1 = dst_h;
1143 } else {
1144 dst_x0 = dstrect->x;
1145 dst_y0 = dstrect->y;
1146 dst_x1 = dst_x0 + dst_w;
1147 dst_y1 = dst_y0 + dst_h;
1148 }
1149
1150 if (!srcrect) {
1151 src_x0 = 0;
1152 src_y0 = 0;
1153 src_x1 = src_w;
1154 src_y1 = src_h;
1155 } else {
1156 src_x0 = srcrect->x;
1157 src_y0 = srcrect->y;
1158 src_x1 = src_x0 + src_w;
1159 src_y1 = src_y0 + src_h;
1160
1161 // Clip source rectangle to the source surface
1162
1163 if (src_x0 < 0) {
1164 dst_x0 -= src_x0 * scaling_w;
1165 src_x0 = 0;
1166 }
1167
1168 if (src_x1 > src->w) {
1169 dst_x1 -= (src_x1 - src->w) * scaling_w;
1170 src_x1 = src->w;
1171 }
1172
1173 if (src_y0 < 0) {
1174 dst_y0 -= src_y0 * scaling_h;
1175 src_y0 = 0;
1176 }
1177
1178 if (src_y1 > src->h) {
1179 dst_y1 -= (src_y1 - src->h) * scaling_h;
1180 src_y1 = src->h;
1181 }
1182 }
1183
1184 // Clip destination rectangle to the clip rectangle
1185 clip_rect = &dst->clip_rect;
1186
1187 // Translate to clip space for easier calculations
1188 dst_x0 -= clip_rect->x;
1189 dst_x1 -= clip_rect->x;
1190 dst_y0 -= clip_rect->y;
1191 dst_y1 -= clip_rect->y;
1192
1193 if (dst_x0 < 0) {
1194 src_x0 -= dst_x0 / scaling_w;
1195 dst_x0 = 0;
1196 }
1197
1198 if (dst_x1 > clip_rect->w) {
1199 src_x1 -= (dst_x1 - clip_rect->w) / scaling_w;
1200 dst_x1 = clip_rect->w;
1201 }
1202
1203 if (dst_y0 < 0) {
1204 src_y0 -= dst_y0 / scaling_h;
1205 dst_y0 = 0;
1206 }
1207
1208 if (dst_y1 > clip_rect->h) {
1209 src_y1 -= (dst_y1 - clip_rect->h) / scaling_h;
1210 dst_y1 = clip_rect->h;
1211 }
1212
1213 // Translate back to surface coordinates
1214 dst_x0 += clip_rect->x;
1215 dst_x1 += clip_rect->x;
1216 dst_y0 += clip_rect->y;
1217 dst_y1 += clip_rect->y;
1218
1219 final_src.x = (int)SDL_round(src_x0);
1220 final_src.y = (int)SDL_round(src_y0);
1221 final_src.w = (int)SDL_round(src_x1 - src_x0);
1222 final_src.h = (int)SDL_round(src_y1 - src_y0);
1223
1224 final_dst.x = (int)SDL_round(dst_x0);
1225 final_dst.y = (int)SDL_round(dst_y0);
1226 final_dst.w = (int)SDL_round(dst_x1 - dst_x0);
1227 final_dst.h = (int)SDL_round(dst_y1 - dst_y0);
1228
1229 // Clip again
1230 {
1231 SDL_Rect tmp;
1232 tmp.x = 0;
1233 tmp.y = 0;
1234 tmp.w = src->w;
1235 tmp.h = src->h;
1236 SDL_GetRectIntersection(&tmp, &final_src, &final_src);
1237 }
1238
1239 // Clip again
1240 SDL_GetRectIntersection(clip_rect, &final_dst, &final_dst);
1241
1242 if (final_dst.w == 0 || final_dst.h == 0 ||
1243 final_src.w < 0 || final_src.h < 0) {
1244 // No-op.
1245 return true;
1246 }
1247
1248 return SDL_BlitSurfaceUncheckedScaled(src, &final_src, dst, &final_dst, scaleMode);
1249}
1250
1251/**
1252 * This is a semi-private blit function and it performs low-level surface
1253 * scaled blitting only.
1254 */
1255bool SDL_BlitSurfaceUncheckedScaled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect, SDL_ScaleMode scaleMode)
1256{
1257 static const Uint32 complex_copy_flags = (SDL_COPY_MODULATE_MASK | SDL_COPY_BLEND_MASK | SDL_COPY_COLORKEY);
1258
1259 if (srcrect->w > SDL_MAX_UINT16 || srcrect->h > SDL_MAX_UINT16 ||
1260 dstrect->w > SDL_MAX_UINT16 || dstrect->h > SDL_MAX_UINT16) {
1261 return SDL_SetError("Size too large for scaling");
1262 }
1263
1264 if (!(src->map.info.flags & SDL_COPY_NEAREST)) {
1265 src->map.info.flags |= SDL_COPY_NEAREST;
1266 SDL_InvalidateMap(&src->map);
1267 }
1268
1269 if (scaleMode == SDL_SCALEMODE_NEAREST || scaleMode == SDL_SCALEMODE_PIXELART) {
1270 if (!(src->map.info.flags & complex_copy_flags) &&
1271 src->format == dst->format &&
1272 !SDL_ISPIXELFORMAT_INDEXED(src->format) &&
1273 SDL_BYTESPERPIXEL(src->format) <= 4) {
1274 return SDL_StretchSurface(src, srcrect, dst, dstrect, SDL_SCALEMODE_NEAREST);
1275 } else if (SDL_BITSPERPIXEL(src->format) < 8) {
1276 // Scaling bitmap not yet supported, convert to RGBA for blit
1277 bool result = false;
1278 SDL_Surface *tmp = SDL_ConvertSurface(src, SDL_PIXELFORMAT_ARGB8888);
1279 if (tmp) {
1280 result = SDL_BlitSurfaceUncheckedScaled(tmp, srcrect, dst, dstrect, SDL_SCALEMODE_NEAREST);
1281 SDL_DestroySurface(tmp);
1282 }
1283 return result;
1284 } else {
1285 return SDL_BlitSurfaceUnchecked(src, srcrect, dst, dstrect);
1286 }
1287 } else {
1288 if (!(src->map.info.flags & complex_copy_flags) &&
1289 src->format == dst->format &&
1290 !SDL_ISPIXELFORMAT_INDEXED(src->format) &&
1291 SDL_BYTESPERPIXEL(src->format) == 4 &&
1292 src->format != SDL_PIXELFORMAT_ARGB2101010) {
1293 // fast path
1294 return SDL_StretchSurface(src, srcrect, dst, dstrect, SDL_SCALEMODE_LINEAR);
1295 } else if (SDL_BITSPERPIXEL(src->format) < 8) {
1296 // Scaling bitmap not yet supported, convert to RGBA for blit
1297 bool result = false;
1298 SDL_Surface *tmp = SDL_ConvertSurface(src, SDL_PIXELFORMAT_ARGB8888);
1299 if (tmp) {
1300 result = SDL_BlitSurfaceUncheckedScaled(tmp, srcrect, dst, dstrect, scaleMode);
1301 SDL_DestroySurface(tmp);
1302 }
1303 return result;
1304 } else {
1305 // Use intermediate surface(s)
1306 SDL_Surface *tmp1 = NULL;
1307 bool result;
1308 SDL_Rect srcrect2;
1309 int is_complex_copy_flags = (src->map.info.flags & complex_copy_flags);
1310
1311 Uint8 r, g, b;
1312 Uint8 alpha;
1313 SDL_BlendMode blendMode;
1314
1315 // Save source infos
1316 SDL_GetSurfaceColorMod(src, &r, &g, &b);
1317 SDL_GetSurfaceAlphaMod(src, &alpha);
1318 SDL_GetSurfaceBlendMode(src, &blendMode);
1319 srcrect2.x = srcrect->x;
1320 srcrect2.y = srcrect->y;
1321 srcrect2.w = srcrect->w;
1322 srcrect2.h = srcrect->h;
1323
1324 // Change source format if not appropriate for scaling
1325 if (SDL_BYTESPERPIXEL(src->format) != 4 || src->format == SDL_PIXELFORMAT_ARGB2101010) {
1326 SDL_Rect tmprect;
1327 SDL_PixelFormat fmt;
1328 tmprect.x = 0;
1329 tmprect.y = 0;
1330 tmprect.w = src->w;
1331 tmprect.h = src->h;
1332 if (SDL_BYTESPERPIXEL(dst->format) == 4 && dst->format != SDL_PIXELFORMAT_ARGB2101010) {
1333 fmt = dst->format;
1334 } else {
1335 fmt = SDL_PIXELFORMAT_ARGB8888;
1336 }
1337 tmp1 = SDL_CreateSurface(src->w, src->h, fmt);
1338 SDL_BlitSurfaceUnchecked(src, srcrect, tmp1, &tmprect);
1339
1340 srcrect2.x = 0;
1341 srcrect2.y = 0;
1342 SDL_SetSurfaceColorMod(tmp1, r, g, b);
1343 SDL_SetSurfaceAlphaMod(tmp1, alpha);
1344 SDL_SetSurfaceBlendMode(tmp1, blendMode);
1345
1346 src = tmp1;
1347 }
1348
1349 // Intermediate scaling
1350 if (is_complex_copy_flags || src->format != dst->format) {
1351 SDL_Rect tmprect;
1352 SDL_Surface *tmp2 = SDL_CreateSurface(dstrect->w, dstrect->h, src->format);
1353 SDL_StretchSurface(src, &srcrect2, tmp2, NULL, SDL_SCALEMODE_LINEAR);
1354
1355 SDL_SetSurfaceColorMod(tmp2, r, g, b);
1356 SDL_SetSurfaceAlphaMod(tmp2, alpha);
1357 SDL_SetSurfaceBlendMode(tmp2, blendMode);
1358
1359 tmprect.x = 0;
1360 tmprect.y = 0;
1361 tmprect.w = dstrect->w;
1362 tmprect.h = dstrect->h;
1363 result = SDL_BlitSurfaceUnchecked(tmp2, &tmprect, dst, dstrect);
1364 SDL_DestroySurface(tmp2);
1365 } else {
1366 result = SDL_StretchSurface(src, &srcrect2, dst, dstrect, SDL_SCALEMODE_LINEAR);
1367 }
1368
1369 SDL_DestroySurface(tmp1);
1370 return result;
1371 }
1372 }
1373}
1374
1375bool SDL_BlitSurfaceTiled(SDL_Surface *src, const SDL_Rect *srcrect, SDL_Surface *dst, const SDL_Rect *dstrect)
1376{
1377 SDL_Rect r_src, r_dst;
1378
1379 // Make sure the surfaces aren't locked
1380 if (!SDL_SurfaceValid(src)) {
1381 return SDL_InvalidParamError("src");
1382 } else if (!SDL_SurfaceValid(dst)) {
1383 return SDL_InvalidParamError("dst");
1384 } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) {
1385 return SDL_SetError("Surfaces must not be locked during blit");
1386 }
1387
1388 // Full src surface
1389 r_src.x = 0;
1390 r_src.y = 0;
1391 r_src.w = src->w;
1392 r_src.h = src->h;
1393
1394 if (dstrect) {
1395 r_dst.x = dstrect->x;
1396 r_dst.y = dstrect->y;
1397 r_dst.w = dstrect->w;
1398 r_dst.h = dstrect->h;
1399 } else {
1400 r_dst.x = 0;
1401 r_dst.y = 0;
1402 r_dst.w = dst->w;
1403 r_dst.h = dst->h;
1404 }
1405
1406 // clip the source rectangle to the source surface
1407 if (srcrect) {
1408 if (SDL_GetRectIntersection(srcrect, &r_src, &r_src) == false) {
1409 return true;
1410 }
1411
1412 // For tiling we don't adjust the destination rectangle
1413 }
1414
1415 // clip the destination rectangle against the clip rectangle
1416 {
1417 if (SDL_GetRectIntersection(&r_dst, &dst->clip_rect, &r_dst) == false) {
1418 return true;
1419 }
1420
1421 // For tiling we don't adjust the source rectangle
1422 }
1423
1424 // Switch back to a fast blit if we were previously stretching
1425 if (src->map.info.flags & SDL_COPY_NEAREST) {
1426 src->map.info.flags &= ~SDL_COPY_NEAREST;
1427 SDL_InvalidateMap(&src->map);
1428 }
1429
1430 int rows = r_dst.h / r_src.h;
1431 int cols = r_dst.w / r_src.w;
1432 int remaining_w = r_dst.w % r_src.w;
1433 int remaining_h = r_dst.h % r_src.h;
1434 SDL_Rect curr_src, curr_dst;
1435
1436 SDL_copyp(&curr_src, &r_src);
1437 curr_dst.y = r_dst.y;
1438 curr_dst.w = r_src.w;
1439 curr_dst.h = r_src.h;
1440 for (int y = 0; y < rows; ++y) {
1441 curr_dst.x = r_dst.x;
1442 for (int x = 0; x < cols; ++x) {
1443 if (!SDL_BlitSurfaceUnchecked(src, &curr_src, dst, &curr_dst)) {
1444 return false;
1445 }
1446 curr_dst.x += curr_dst.w;
1447 }
1448 if (remaining_w) {
1449 curr_src.w = remaining_w;
1450 curr_dst.w = remaining_w;
1451 if (!SDL_BlitSurfaceUnchecked(src, &curr_src, dst, &curr_dst)) {
1452 return false;
1453 }
1454 curr_src.w = r_src.w;
1455 curr_dst.w = r_src.w;
1456 }
1457 curr_dst.y += curr_dst.h;
1458 }
1459 if (remaining_h) {
1460 curr_src.h = remaining_h;
1461 curr_dst.h = remaining_h;
1462 curr_dst.x = r_dst.x;
1463 for (int x = 0; x < cols; ++x) {
1464 if (!SDL_BlitSurfaceUnchecked(src, &curr_src, dst, &curr_dst)) {
1465 return false;
1466 }
1467 curr_dst.x += curr_dst.w;
1468 }
1469 if (remaining_w) {
1470 curr_src.w = remaining_w;
1471 curr_dst.w = remaining_w;
1472 if (!SDL_BlitSurfaceUnchecked(src, &curr_src, dst, &curr_dst)) {
1473 return false;
1474 }
1475 }
1476 }
1477 return true;
1478}
1479
1480bool SDL_BlitSurfaceTiledWithScale(SDL_Surface *src, const SDL_Rect *srcrect, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect)
1481{
1482 SDL_Rect r_src, r_dst;
1483
1484 // Make sure the surfaces aren't locked
1485 if (!SDL_SurfaceValid(src)) {
1486 return SDL_InvalidParamError("src");
1487 } else if (!SDL_SurfaceValid(dst)) {
1488 return SDL_InvalidParamError("dst");
1489 } else if ((src->flags & SDL_SURFACE_LOCKED) || (dst->flags & SDL_SURFACE_LOCKED)) {
1490 return SDL_SetError("Surfaces must not be locked during blit");
1491 }
1492
1493 if (scale <= 0.0f) {
1494 return SDL_InvalidParamError("scale");
1495 }
1496
1497 // Full src surface
1498 r_src.x = 0;
1499 r_src.y = 0;
1500 r_src.w = src->w;
1501 r_src.h = src->h;
1502
1503 if (dstrect) {
1504 r_dst.x = dstrect->x;
1505 r_dst.y = dstrect->y;
1506 r_dst.w = dstrect->w;
1507 r_dst.h = dstrect->h;
1508 } else {
1509 r_dst.x = 0;
1510 r_dst.y = 0;
1511 r_dst.w = dst->w;
1512 r_dst.h = dst->h;
1513 }
1514
1515 // clip the source rectangle to the source surface
1516 if (srcrect) {
1517 if (SDL_GetRectIntersection(srcrect, &r_src, &r_src) == false) {
1518 return true;
1519 }
1520
1521 // For tiling we don't adjust the destination rectangle
1522 }
1523
1524 // clip the destination rectangle against the clip rectangle
1525 {
1526 if (SDL_GetRectIntersection(&r_dst, &dst->clip_rect, &r_dst) == false) {
1527 return true;
1528 }
1529
1530 // For tiling we don't adjust the source rectangle
1531 }
1532
1533 // Switch back to a fast blit if we were previously stretching
1534 if (src->map.info.flags & SDL_COPY_NEAREST) {
1535 src->map.info.flags &= ~SDL_COPY_NEAREST;
1536 SDL_InvalidateMap(&src->map);
1537 }
1538
1539 int tile_width = (int)(r_src.w * scale);
1540 int tile_height = (int)(r_src.h * scale);
1541 int rows = r_dst.h / tile_height;
1542 int cols = r_dst.w / tile_width;
1543 int remaining_dst_w = (r_dst.w - cols * tile_width);
1544 int remaining_dst_h = (r_dst.h - rows * tile_height);
1545 int remaining_src_w = (int)(remaining_dst_w / scale);
1546 int remaining_src_h = (int)(remaining_dst_h / scale);
1547 SDL_Rect curr_src, curr_dst;
1548
1549 SDL_copyp(&curr_src, &r_src);
1550 curr_dst.y = r_dst.y;
1551 curr_dst.w = tile_width;
1552 curr_dst.h = tile_height;
1553 for (int y = 0; y < rows; ++y) {
1554 curr_dst.x = r_dst.x;
1555 for (int x = 0; x < cols; ++x) {
1556 if (!SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1557 return false;
1558 }
1559 curr_dst.x += curr_dst.w;
1560 }
1561 if (remaining_dst_w > 0) {
1562 curr_src.w = remaining_src_w;
1563 curr_dst.w = remaining_dst_w;
1564 if (!SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1565 return false;
1566 }
1567 curr_src.w = r_src.w;
1568 curr_dst.w = tile_width;
1569 }
1570 curr_dst.y += curr_dst.h;
1571 }
1572 if (remaining_dst_h > 0) {
1573 curr_src.h = remaining_src_h;
1574 curr_dst.h = remaining_dst_h;
1575 curr_dst.x = r_dst.x;
1576 for (int x = 0; x < cols; ++x) {
1577 if (!SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1578 return false;
1579 }
1580 curr_dst.x += curr_dst.w;
1581 }
1582 if (remaining_dst_w > 0) {
1583 curr_src.w = remaining_src_w;
1584 curr_dst.w = remaining_dst_w;
1585 if (!SDL_BlitSurfaceUncheckedScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1586 return false;
1587 }
1588 }
1589 }
1590 return true;
1591}
1592
1593bool SDL_BlitSurface9Grid(SDL_Surface *src, const SDL_Rect *srcrect, int left_width, int right_width, int top_height, int bottom_height, float scale, SDL_ScaleMode scaleMode, SDL_Surface *dst, const SDL_Rect *dstrect)
1594{
1595 SDL_Rect full_src, full_dst;
1596 SDL_Rect curr_src, curr_dst;
1597 int dst_left_width;
1598 int dst_right_width;
1599 int dst_top_height;
1600 int dst_bottom_height;
1601
1602 // Make sure the surfaces aren't locked
1603 if (!SDL_SurfaceValid(src)) {
1604 return SDL_InvalidParamError("src");
1605 } else if (!SDL_SurfaceValid(dst)) {
1606 return SDL_InvalidParamError("dst");
1607 }
1608
1609 if (!srcrect) {
1610 full_src.x = 0;
1611 full_src.y = 0;
1612 full_src.w = src->w;
1613 full_src.h = src->h;
1614 srcrect = &full_src;
1615 }
1616
1617 if (!dstrect) {
1618 full_dst.x = 0;
1619 full_dst.y = 0;
1620 full_dst.w = dst->w;
1621 full_dst.h = dst->h;
1622 dstrect = &full_dst;
1623 }
1624
1625 if (scale <= 0.0f || scale == 1.0f) {
1626 dst_left_width = left_width;
1627 dst_right_width = right_width;
1628 dst_top_height = top_height;
1629 dst_bottom_height = bottom_height;
1630 } else {
1631 dst_left_width = (int)SDL_roundf(left_width * scale);
1632 dst_right_width = (int)SDL_roundf(right_width * scale);
1633 dst_top_height = (int)SDL_roundf(top_height * scale);
1634 dst_bottom_height = (int)SDL_roundf(bottom_height * scale);
1635 }
1636
1637 // Upper-left corner
1638 curr_src.x = srcrect->x;
1639 curr_src.y = srcrect->y;
1640 curr_src.w = left_width;
1641 curr_src.h = top_height;
1642 curr_dst.x = dstrect->x;
1643 curr_dst.y = dstrect->y;
1644 curr_dst.w = dst_left_width;
1645 curr_dst.h = dst_top_height;
1646 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1647 return false;
1648 }
1649
1650 // Upper-right corner
1651 curr_src.x = srcrect->x + srcrect->w - right_width;
1652 curr_src.w = right_width;
1653 curr_dst.x = dstrect->x + dstrect->w - dst_right_width;
1654 curr_dst.w = dst_right_width;
1655 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1656 return false;
1657 }
1658
1659 // Lower-right corner
1660 curr_src.y = srcrect->y + srcrect->h - bottom_height;
1661 curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height;
1662 curr_dst.h = dst_bottom_height;
1663 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1664 return false;
1665 }
1666
1667 // Lower-left corner
1668 curr_src.x = srcrect->x;
1669 curr_src.w = left_width;
1670 curr_dst.x = dstrect->x;
1671 curr_dst.w = dst_left_width;
1672 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1673 return false;
1674 }
1675
1676 // Left
1677 curr_src.y = srcrect->y + top_height;
1678 curr_src.h = srcrect->h - top_height - bottom_height;
1679 curr_dst.y = dstrect->y + dst_top_height;
1680 curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height;
1681 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1682 return false;
1683 }
1684
1685 // Right
1686 curr_src.x = srcrect->x + srcrect->w - right_width;
1687 curr_src.w = right_width;
1688 curr_dst.x = dstrect->x + dstrect->w - dst_right_width;
1689 curr_dst.w = dst_right_width;
1690 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1691 return false;
1692 }
1693
1694 // Top
1695 curr_src.x = srcrect->x + left_width;
1696 curr_src.y = srcrect->y;
1697 curr_src.w = srcrect->w - left_width - right_width;
1698 curr_src.h = top_height;
1699 curr_dst.x = dstrect->x + dst_left_width;
1700 curr_dst.y = dstrect->y;
1701 curr_dst.w = dstrect->w - dst_left_width - dst_right_width;
1702 curr_dst.h = dst_top_height;
1703 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1704 return false;
1705 }
1706
1707 // Bottom
1708 curr_src.y = srcrect->y + srcrect->h - bottom_height;
1709 curr_dst.y = dstrect->y + dstrect->h - dst_bottom_height;
1710 curr_dst.h = dst_bottom_height;
1711 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1712 return false;
1713 }
1714
1715 // Center
1716 curr_src.x = srcrect->x + left_width;
1717 curr_src.y = srcrect->y + top_height;
1718 curr_src.w = srcrect->w - left_width - right_width;
1719 curr_src.h = srcrect->h - top_height - bottom_height;
1720 curr_dst.x = dstrect->x + dst_left_width;
1721 curr_dst.y = dstrect->y + dst_top_height;
1722 curr_dst.w = dstrect->w - dst_left_width - dst_right_width;
1723 curr_dst.h = dstrect->h - dst_top_height - dst_bottom_height;
1724 if (!SDL_BlitSurfaceScaled(src, &curr_src, dst, &curr_dst, scaleMode)) {
1725 return false;
1726 }
1727
1728 return true;
1729}
1730
1731/*
1732 * Lock a surface to directly access the pixels
1733 */
1734bool SDL_LockSurface(SDL_Surface *surface)
1735{
1736 if (!SDL_SurfaceValid(surface)) {
1737 return SDL_InvalidParamError("surface");
1738 }
1739
1740 if (!surface->locked) {
1741#ifdef SDL_HAVE_RLE
1742 // Perform the lock
1743 if (surface->internal_flags & SDL_INTERNAL_SURFACE_RLEACCEL) {
1744 SDL_UnRLESurface(surface, true);
1745 surface->internal_flags |= SDL_INTERNAL_SURFACE_RLEACCEL; // save accel'd state
1746 }
1747#endif
1748 }
1749
1750 // Increment the surface lock count, for recursive locks
1751 ++surface->locked;
1752 surface->flags |= SDL_SURFACE_LOCKED;
1753
1754 // Ready to go..
1755 return true;
1756}
1757
1758/*
1759 * Unlock a previously locked surface
1760 */
1761void SDL_UnlockSurface(SDL_Surface *surface)
1762{
1763 if (!SDL_SurfaceValid(surface)) {
1764 return;
1765 }
1766
1767 // Only perform an unlock if we are locked
1768 if (!surface->locked || (--surface->locked > 0)) {
1769 return;
1770 }
1771
1772#ifdef SDL_HAVE_RLE
1773 // Update RLE encoded surface with new data
1774 if (surface->internal_flags & SDL_INTERNAL_SURFACE_RLEACCEL) {
1775 surface->internal_flags &= ~SDL_INTERNAL_SURFACE_RLEACCEL; // stop lying
1776 SDL_RLESurface(surface);
1777 }
1778#endif
1779
1780 surface->flags &= ~SDL_SURFACE_LOCKED;
1781}
1782
1783static bool SDL_FlipSurfaceHorizontal(SDL_Surface *surface)
1784{
1785 bool isstack;
1786 Uint8 *row, *a, *b, *tmp;
1787 int i, j, bpp;
1788
1789 if (SDL_BITSPERPIXEL(surface->format) < 8) {
1790 // We could implement this if needed, but we'd have to flip sets of bits within a byte
1791 return SDL_Unsupported();
1792 }
1793
1794 if (surface->h <= 0) {
1795 return true;
1796 }
1797
1798 if (surface->w <= 1) {
1799 return true;
1800 }
1801
1802 bpp = SDL_BYTESPERPIXEL(surface->format);
1803 row = (Uint8 *)surface->pixels;
1804 tmp = SDL_small_alloc(Uint8, surface->pitch, &isstack);
1805 if (!tmp) {
1806 return false;
1807 }
1808 for (i = surface->h; i--; ) {
1809 a = row;
1810 b = a + (surface->w - 1) * bpp;
1811 for (j = surface->w / 2; j--; ) {
1812 SDL_memcpy(tmp, a, bpp);
1813 SDL_memcpy(a, b, bpp);
1814 SDL_memcpy(b, tmp, bpp);
1815 a += bpp;
1816 b -= bpp;
1817 }
1818 row += surface->pitch;
1819 }
1820 SDL_small_free(tmp, isstack);
1821 return true;
1822}
1823
1824static bool SDL_FlipSurfaceVertical(SDL_Surface *surface)
1825{
1826 bool isstack;
1827 Uint8 *a, *b, *tmp;
1828 int i;
1829
1830 if (surface->h <= 1) {
1831 return true;
1832 }
1833
1834 a = (Uint8 *)surface->pixels;
1835 b = a + (surface->h - 1) * surface->pitch;
1836 tmp = SDL_small_alloc(Uint8, surface->pitch, &isstack);
1837 if (!tmp) {
1838 return false;
1839 }
1840 for (i = surface->h / 2; i--; ) {
1841 SDL_memcpy(tmp, a, surface->pitch);
1842 SDL_memcpy(a, b, surface->pitch);
1843 SDL_memcpy(b, tmp, surface->pitch);
1844 a += surface->pitch;
1845 b -= surface->pitch;
1846 }
1847 SDL_small_free(tmp, isstack);
1848 return true;
1849}
1850
1851bool SDL_FlipSurface(SDL_Surface *surface, SDL_FlipMode flip)
1852{
1853 if (!SDL_SurfaceValid(surface)) {
1854 return SDL_InvalidParamError("surface");
1855 }
1856 if (!surface->pixels) {
1857 return true;
1858 }
1859
1860 switch (flip) {
1861 case SDL_FLIP_HORIZONTAL:
1862 return SDL_FlipSurfaceHorizontal(surface);
1863 case SDL_FLIP_VERTICAL:
1864 return SDL_FlipSurfaceVertical(surface);
1865 default:
1866 return SDL_InvalidParamError("flip");
1867 }
1868}
1869
1870SDL_Surface *SDL_ConvertSurfaceAndColorspace(SDL_Surface *surface, SDL_PixelFormat format, SDL_Palette *palette, SDL_Colorspace colorspace, SDL_PropertiesID props)
1871{
1872 SDL_Palette *temp_palette = NULL;
1873 SDL_Surface *convert = NULL;
1874 SDL_Colorspace src_colorspace;
1875 SDL_PropertiesID src_properties;
1876 Uint32 copy_flags;
1877 SDL_Color copy_color;
1878 SDL_Rect bounds;
1879 bool result;
1880 bool palette_ck_transform = false;
1881 Uint8 palette_ck_value = 0;
1882 Uint8 *palette_saved_alpha = NULL;
1883 int palette_saved_alpha_ncolors = 0;
1884
1885 if (!SDL_SurfaceValid(surface)) {
1886 SDL_InvalidParamError("surface");
1887 goto error;
1888 }
1889
1890 if (format == SDL_PIXELFORMAT_UNKNOWN) {
1891 SDL_InvalidParamError("format");
1892 goto error;
1893 }
1894
1895 // Check for empty destination palette! (results in empty image)
1896 if (palette) {
1897 int i;
1898 for (i = 0; i < palette->ncolors; ++i) {
1899 if ((palette->colors[i].r != 0xFF) || (palette->colors[i].g != 0xFF) || (palette->colors[i].b != 0xFF)) {
1900 break;
1901 }
1902 }
1903 if (i == palette->ncolors) {
1904 SDL_SetError("Empty destination palette");
1905 goto error;
1906 }
1907 } else if (SDL_ISPIXELFORMAT_INDEXED(format)) {
1908 // Create a dither palette for conversion
1909 temp_palette = SDL_CreatePalette(1 << SDL_BITSPERPIXEL(format));
1910 if (temp_palette) {
1911 SDL_DitherPalette(temp_palette);
1912 palette = temp_palette;
1913 }
1914 }
1915
1916 src_colorspace = surface->colorspace;
1917 src_properties = surface->props;
1918
1919 // Create a new surface with the desired format
1920 convert = SDL_CreateSurface(surface->w, surface->h, format);
1921 if (!convert) {
1922 goto error;
1923 }
1924 if (SDL_ISPIXELFORMAT_INDEXED(format)) {
1925 SDL_SetSurfacePalette(convert, palette);
1926 }
1927
1928 if (colorspace == SDL_COLORSPACE_UNKNOWN) {
1929 colorspace = src_colorspace;
1930 }
1931 SDL_SetSurfaceColorspace(convert, colorspace);
1932
1933 if (SDL_ISPIXELFORMAT_FOURCC(format) || SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
1934 if (surface->format == SDL_PIXELFORMAT_MJPG && format == SDL_PIXELFORMAT_MJPG) {
1935 // Just do a straight pixel copy of the JPEG image
1936 size_t size = (size_t)surface->pitch;
1937 convert->pixels = SDL_malloc(size);
1938 if (!convert->pixels) {
1939 goto error;
1940 }
1941 convert->pitch = surface->pitch;
1942 SDL_memcpy(convert->pixels, surface->pixels, size);
1943
1944 } else if (!SDL_ConvertPixelsAndColorspace(surface->w, surface->h, surface->format, src_colorspace, src_properties, surface->pixels, surface->pitch, convert->format, colorspace, props, convert->pixels, convert->pitch)) {
1945 goto error;
1946 }
1947
1948 // Save the original copy flags
1949 copy_flags = surface->map.info.flags;
1950
1951 goto end;
1952 }
1953
1954 // Save the original copy flags
1955 copy_flags = surface->map.info.flags;
1956 copy_color.r = surface->map.info.r;
1957 copy_color.g = surface->map.info.g;
1958 copy_color.b = surface->map.info.b;
1959 copy_color.a = surface->map.info.a;
1960 surface->map.info.r = 0xFF;
1961 surface->map.info.g = 0xFF;
1962 surface->map.info.b = 0xFF;
1963 surface->map.info.a = 0xFF;
1964 surface->map.info.flags = (copy_flags & (SDL_COPY_RLE_COLORKEY | SDL_COPY_RLE_ALPHAKEY));
1965 SDL_InvalidateMap(&surface->map);
1966
1967 // Copy over the image data
1968 bounds.x = 0;
1969 bounds.y = 0;
1970 bounds.w = surface->w;
1971 bounds.h = surface->h;
1972
1973 /* Source surface has a palette with no real alpha (0 or OPAQUE).
1974 * Destination format has alpha.
1975 * -> set alpha channel to be opaque */
1976 if (surface->palette && SDL_ISPIXELFORMAT_ALPHA(format)) {
1977 bool set_opaque = false;
1978
1979 bool is_opaque, has_alpha_channel;
1980 SDL_DetectPalette(surface->palette, &is_opaque, &has_alpha_channel);
1981
1982 if (is_opaque) {
1983 if (!has_alpha_channel) {
1984 set_opaque = true;
1985 }
1986 }
1987
1988 // Set opaque and backup palette alpha values
1989 if (set_opaque) {
1990 int i;
1991 palette_saved_alpha_ncolors = surface->palette->ncolors;
1992 if (palette_saved_alpha_ncolors > 0) {
1993 palette_saved_alpha = SDL_stack_alloc(Uint8, palette_saved_alpha_ncolors);
1994 for (i = 0; i < palette_saved_alpha_ncolors; i++) {
1995 palette_saved_alpha[i] = surface->palette->colors[i].a;
1996 surface->palette->colors[i].a = SDL_ALPHA_OPAQUE;
1997 }
1998 }
1999 }
2000 }
2001
2002 // Transform colorkey to alpha. for cases where source palette has duplicate values, and colorkey is one of them
2003 if (copy_flags & SDL_COPY_COLORKEY) {
2004 if (surface->palette && !palette) {
2005 palette_ck_transform = true;
2006 palette_ck_value = surface->palette->colors[surface->map.info.colorkey].a;
2007 surface->palette->colors[surface->map.info.colorkey].a = SDL_ALPHA_TRANSPARENT;
2008 }
2009 }
2010
2011 result = SDL_BlitSurfaceUnchecked(surface, &bounds, convert, &bounds);
2012
2013 // Restore colorkey alpha value
2014 if (palette_ck_transform) {
2015 surface->palette->colors[surface->map.info.colorkey].a = palette_ck_value;
2016 }
2017
2018 // Restore palette alpha values
2019 if (palette_saved_alpha) {
2020 int i;
2021 for (i = 0; i < palette_saved_alpha_ncolors; i++) {
2022 surface->palette->colors[i].a = palette_saved_alpha[i];
2023 }
2024 SDL_stack_free(palette_saved_alpha);
2025 }
2026
2027 // Clean up the original surface, and update converted surface
2028 convert->map.info.r = copy_color.r;
2029 convert->map.info.g = copy_color.g;
2030 convert->map.info.b = copy_color.b;
2031 convert->map.info.a = copy_color.a;
2032 convert->map.info.flags =
2033 (copy_flags &
2034 ~(SDL_COPY_COLORKEY | SDL_COPY_BLEND | SDL_COPY_RLE_DESIRED | SDL_COPY_RLE_COLORKEY |
2035 SDL_COPY_RLE_ALPHAKEY));
2036 surface->map.info.r = copy_color.r;
2037 surface->map.info.g = copy_color.g;
2038 surface->map.info.b = copy_color.b;
2039 surface->map.info.a = copy_color.a;
2040 surface->map.info.flags = copy_flags;
2041 SDL_InvalidateMap(&surface->map);
2042
2043 // SDL_BlitSurfaceUnchecked failed, and so the conversion
2044 if (!result) {
2045 goto error;
2046 }
2047
2048 if (copy_flags & SDL_COPY_COLORKEY) {
2049 bool set_colorkey_by_color = false;
2050 bool convert_colorkey = true;
2051
2052 if (surface->palette) {
2053 if (palette &&
2054 surface->palette->ncolors <= palette->ncolors &&
2055 (SDL_memcmp(surface->palette->colors, palette->colors,
2056 surface->palette->ncolors * sizeof(SDL_Color)) == 0)) {
2057 // The palette is identical, just set the same colorkey
2058 SDL_SetSurfaceColorKey(convert, true, surface->map.info.colorkey);
2059 } else if (!palette) {
2060 if (SDL_ISPIXELFORMAT_ALPHA(format)) {
2061 // No need to add the colorkey, transparency is in the alpha channel
2062 } else {
2063 // Only set the colorkey information
2064 set_colorkey_by_color = true;
2065 convert_colorkey = false;
2066 }
2067 } else {
2068 set_colorkey_by_color = true;
2069 }
2070 } else {
2071 set_colorkey_by_color = true;
2072 }
2073
2074 if (set_colorkey_by_color) {
2075 SDL_Surface *tmp;
2076 SDL_Surface *tmp2;
2077 int converted_colorkey = 0;
2078
2079 // Create a dummy surface to get the colorkey converted
2080 tmp = SDL_CreateSurface(1, 1, surface->format);
2081 if (!tmp) {
2082 goto error;
2083 }
2084
2085 // Share the palette, if any
2086 if (surface->palette) {
2087 SDL_SetSurfacePalette(tmp, surface->palette);
2088 }
2089
2090 SDL_FillSurfaceRect(tmp, NULL, surface->map.info.colorkey);
2091
2092 tmp->map.info.flags &= ~SDL_COPY_COLORKEY;
2093
2094 // Conversion of the colorkey
2095 tmp2 = SDL_ConvertSurfaceAndColorspace(tmp, format, palette, colorspace, props);
2096 if (!tmp2) {
2097 SDL_DestroySurface(tmp);
2098 goto error;
2099 }
2100
2101 // Get the converted colorkey
2102 SDL_memcpy(&converted_colorkey, tmp2->pixels, tmp2->fmt->bytes_per_pixel);
2103
2104 SDL_DestroySurface(tmp);
2105 SDL_DestroySurface(tmp2);
2106
2107 // Set the converted colorkey on the new surface
2108 SDL_SetSurfaceColorKey(convert, true, converted_colorkey);
2109
2110 // This is needed when converting for 3D texture upload
2111 if (convert_colorkey) {
2112 SDL_ConvertColorkeyToAlpha(convert, true);
2113 }
2114 }
2115 }
2116
2117end:
2118 if (temp_palette) {
2119 SDL_DestroyPalette(temp_palette);
2120 }
2121
2122 SDL_SetSurfaceClipRect(convert, &surface->clip_rect);
2123
2124 /* Enable alpha blending by default if the new surface has an
2125 * alpha channel or alpha modulation */
2126 if (SDL_ISPIXELFORMAT_ALPHA(format) ||
2127 (copy_flags & SDL_COPY_MODULATE_ALPHA)) {
2128 SDL_SetSurfaceBlendMode(convert, SDL_BLENDMODE_BLEND);
2129 }
2130 if (copy_flags & SDL_COPY_RLE_DESIRED) {
2131 SDL_SetSurfaceRLE(convert, true);
2132 }
2133
2134 // Copy alternate images
2135 for (int i = 0; i < surface->num_images; ++i) {
2136 if (!SDL_AddSurfaceAlternateImage(convert, surface->images[i])) {
2137 goto error;
2138 }
2139 }
2140
2141 // We're ready to go!
2142 return convert;
2143
2144error:
2145 if (temp_palette) {
2146 SDL_DestroyPalette(temp_palette);
2147 }
2148 if (convert) {
2149 SDL_DestroySurface(convert);
2150 }
2151 return NULL;
2152}
2153
2154SDL_Surface *SDL_DuplicateSurface(SDL_Surface *surface)
2155{
2156 if (!SDL_SurfaceValid(surface)) {
2157 SDL_InvalidParamError("surface");
2158 return NULL;
2159 }
2160
2161 return SDL_ConvertSurfaceAndColorspace(surface, surface->format, surface->palette, surface->colorspace, surface->props);
2162}
2163
2164SDL_Surface *SDL_ScaleSurface(SDL_Surface *surface, int width, int height, SDL_ScaleMode scaleMode)
2165{
2166 SDL_Surface *convert = NULL;
2167 Uint32 copy_flags;
2168 SDL_Color copy_color;
2169 bool rc;
2170
2171 if (!SDL_SurfaceValid(surface)) {
2172 SDL_InvalidParamError("surface");
2173 goto error;
2174 }
2175
2176 if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2177 // We can't directly scale a YUV surface (yet!)
2178 SDL_Surface *tmp = SDL_CreateSurface(surface->w, surface->h, SDL_PIXELFORMAT_ARGB8888);
2179 if (!tmp) {
2180 return NULL;
2181 }
2182
2183 SDL_Surface *scaled = SDL_ScaleSurface(tmp, width, height, scaleMode);
2184 SDL_DestroySurface(tmp);
2185 if (!scaled) {
2186 return NULL;
2187 }
2188 tmp = scaled;
2189
2190 SDL_Surface *result = SDL_ConvertSurfaceAndColorspace(tmp, surface->format, NULL, surface->colorspace, surface->props);
2191 SDL_DestroySurface(tmp);
2192 return result;
2193 }
2194
2195 // Create a new surface with the desired size
2196 convert = SDL_CreateSurface(width, height, surface->format);
2197 if (!convert) {
2198 goto error;
2199 }
2200 SDL_SetSurfacePalette(convert, surface->palette);
2201 SDL_SetSurfaceColorspace(convert, surface->colorspace);
2202
2203 // Save the original copy flags
2204 copy_flags = surface->map.info.flags;
2205 copy_color.r = surface->map.info.r;
2206 copy_color.g = surface->map.info.g;
2207 copy_color.b = surface->map.info.b;
2208 copy_color.a = surface->map.info.a;
2209 surface->map.info.r = 0xFF;
2210 surface->map.info.g = 0xFF;
2211 surface->map.info.b = 0xFF;
2212 surface->map.info.a = 0xFF;
2213 surface->map.info.flags = (copy_flags & (SDL_COPY_RLE_COLORKEY | SDL_COPY_RLE_ALPHAKEY));
2214 SDL_InvalidateMap(&surface->map);
2215
2216 rc = SDL_BlitSurfaceScaled(surface, NULL, convert, NULL, scaleMode);
2217
2218 // Clean up the original surface, and update converted surface
2219 convert->map.info.r = copy_color.r;
2220 convert->map.info.g = copy_color.g;
2221 convert->map.info.b = copy_color.b;
2222 convert->map.info.a = copy_color.a;
2223 convert->map.info.flags = (copy_flags & ~(SDL_COPY_RLE_COLORKEY | SDL_COPY_RLE_ALPHAKEY));
2224 surface->map.info.r = copy_color.r;
2225 surface->map.info.g = copy_color.g;
2226 surface->map.info.b = copy_color.b;
2227 surface->map.info.a = copy_color.a;
2228 surface->map.info.flags = copy_flags;
2229 SDL_InvalidateMap(&surface->map);
2230
2231 // SDL_BlitSurfaceScaled failed, and so the conversion
2232 if (!rc) {
2233 goto error;
2234 }
2235
2236 // We're ready to go!
2237 return convert;
2238
2239error:
2240 if (convert) {
2241 SDL_DestroySurface(convert);
2242 }
2243 return NULL;
2244}
2245
2246SDL_Surface *SDL_ConvertSurface(SDL_Surface *surface, SDL_PixelFormat format)
2247{
2248 if (!SDL_SurfaceValid(surface)) {
2249 SDL_InvalidParamError("surface");
2250 return NULL;
2251 }
2252
2253 return SDL_ConvertSurfaceAndColorspace(surface, format, NULL, SDL_GetDefaultColorspaceForFormat(format), surface->props);
2254}
2255
2256SDL_Surface *SDL_DuplicatePixels(int width, int height, SDL_PixelFormat format, SDL_Colorspace colorspace, void *pixels, int pitch)
2257{
2258 SDL_Surface *surface = SDL_CreateSurface(width, height, format);
2259 if (surface) {
2260 int length = width * SDL_BYTESPERPIXEL(format);
2261 Uint8 *src = (Uint8 *)pixels;
2262 Uint8 *dst = (Uint8 *)surface->pixels;
2263 int rows = height;
2264 while (rows--) {
2265 SDL_memcpy(dst, src, length);
2266 dst += surface->pitch;
2267 src += pitch;
2268 }
2269
2270 SDL_SetSurfaceColorspace(surface, colorspace);
2271 }
2272 return surface;
2273}
2274
2275bool SDL_ConvertPixelsAndColorspace(int width, int height,
2276 SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch,
2277 SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch)
2278{
2279 SDL_Surface src_surface;
2280 SDL_Surface dst_surface;
2281 SDL_Rect rect;
2282 void *nonconst_src = (void *)src;
2283 bool result;
2284
2285 if (!src) {
2286 return SDL_InvalidParamError("src");
2287 }
2288 if (!src_pitch) {
2289 return SDL_InvalidParamError("src_pitch");
2290 }
2291 if (!dst) {
2292 return SDL_InvalidParamError("dst");
2293 }
2294 if (!dst_pitch) {
2295 return SDL_InvalidParamError("dst_pitch");
2296 }
2297
2298 if (src_colorspace == SDL_COLORSPACE_UNKNOWN) {
2299 src_colorspace = SDL_GetDefaultColorspaceForFormat(src_format);
2300 }
2301 if (dst_colorspace == SDL_COLORSPACE_UNKNOWN) {
2302 dst_colorspace = SDL_GetDefaultColorspaceForFormat(dst_format);
2303 }
2304
2305 if (src_format == SDL_PIXELFORMAT_MJPG) {
2306 return SDL_ConvertPixels_STB(width, height, src_format, src_colorspace, src_properties, src, src_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch);
2307 }
2308
2309#ifdef SDL_HAVE_YUV
2310 if (SDL_ISPIXELFORMAT_FOURCC(src_format) && SDL_ISPIXELFORMAT_FOURCC(dst_format)) {
2311 return SDL_ConvertPixels_YUV_to_YUV(width, height, src_format, src_colorspace, src_properties, src, src_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch);
2312 } else if (SDL_ISPIXELFORMAT_FOURCC(src_format)) {
2313 return SDL_ConvertPixels_YUV_to_RGB(width, height, src_format, src_colorspace, src_properties, src, src_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch);
2314 } else if (SDL_ISPIXELFORMAT_FOURCC(dst_format)) {
2315 return SDL_ConvertPixels_RGB_to_YUV(width, height, src_format, src_colorspace, src_properties, src, src_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch);
2316 }
2317#else
2318 if (SDL_ISPIXELFORMAT_FOURCC(src_format) || SDL_ISPIXELFORMAT_FOURCC(dst_format)) {
2319 return SDL_SetError("SDL not built with YUV support");
2320 }
2321#endif
2322
2323 // Fast path for same format copy
2324 if (src_format == dst_format && src_colorspace == dst_colorspace) {
2325 if (src_pitch == dst_pitch) {
2326 SDL_memcpy(dst, src, height * src_pitch);
2327 } else {
2328 int i;
2329 const int bpp = SDL_BYTESPERPIXEL(src_format);
2330 width *= bpp;
2331 for (i = height; i--;) {
2332 SDL_memcpy(dst, src, width);
2333 src = (const Uint8 *)src + src_pitch;
2334 dst = (Uint8 *)dst + dst_pitch;
2335 }
2336 }
2337 return true;
2338 }
2339
2340 if (!SDL_InitializeSurface(&src_surface, width, height, src_format, src_colorspace, src_properties, nonconst_src, src_pitch, true)) {
2341 return false;
2342 }
2343 SDL_SetSurfaceBlendMode(&src_surface, SDL_BLENDMODE_NONE);
2344
2345 if (!SDL_InitializeSurface(&dst_surface, width, height, dst_format, dst_colorspace, dst_properties, dst, dst_pitch, true)) {
2346 return false;
2347 }
2348
2349 // Set up the rect and go!
2350 rect.x = 0;
2351 rect.y = 0;
2352 rect.w = width;
2353 rect.h = height;
2354 result = SDL_BlitSurfaceUnchecked(&src_surface, &rect, &dst_surface, &rect);
2355
2356 SDL_DestroySurface(&src_surface);
2357 SDL_DestroySurface(&dst_surface);
2358
2359 return result;
2360}
2361
2362bool SDL_ConvertPixels(int width, int height, SDL_PixelFormat src_format, const void *src, int src_pitch, SDL_PixelFormat dst_format, void *dst, int dst_pitch)
2363{
2364 return SDL_ConvertPixelsAndColorspace(width, height,
2365 src_format, SDL_COLORSPACE_UNKNOWN, 0, src, src_pitch,
2366 dst_format, SDL_COLORSPACE_UNKNOWN, 0, dst, dst_pitch);
2367}
2368
2369/*
2370 * Premultiply the alpha on a block of pixels
2371 *
2372 * Here are some ideas for optimization:
2373 * https://github.com/Wizermil/premultiply_alpha/tree/master/premultiply_alpha
2374 * https://developer.arm.com/documentation/101964/0201/Pre-multiplied-alpha-channel-data
2375 */
2376
2377static void SDL_PremultiplyAlpha_AXYZ8888(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch)
2378{
2379 int c;
2380 Uint32 srcpixel;
2381 Uint32 srcR, srcG, srcB, srcA;
2382 Uint32 dstpixel;
2383 Uint32 dstR, dstG, dstB, dstA;
2384
2385 while (height--) {
2386 const Uint32 *src_px = (const Uint32 *)src;
2387 Uint32 *dst_px = (Uint32 *)dst;
2388 for (c = width; c; --c) {
2389 // Component bytes extraction.
2390 srcpixel = *src_px++;
2391 RGBA_FROM_ARGB8888(srcpixel, srcR, srcG, srcB, srcA);
2392
2393 // Alpha pre-multiplication of each component.
2394 dstA = srcA;
2395 dstR = (srcA * srcR) / 255;
2396 dstG = (srcA * srcG) / 255;
2397 dstB = (srcA * srcB) / 255;
2398
2399 // ARGB8888 pixel recomposition.
2400 ARGB8888_FROM_RGBA(dstpixel, dstR, dstG, dstB, dstA);
2401 *dst_px++ = dstpixel;
2402 }
2403 src = (const Uint8 *)src + src_pitch;
2404 dst = (Uint8 *)dst + dst_pitch;
2405 }
2406}
2407
2408static void SDL_PremultiplyAlpha_XYZA8888(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch)
2409{
2410 int c;
2411 Uint32 srcpixel;
2412 Uint32 srcR, srcG, srcB, srcA;
2413 Uint32 dstpixel;
2414 Uint32 dstR, dstG, dstB, dstA;
2415
2416 while (height--) {
2417 const Uint32 *src_px = (const Uint32 *)src;
2418 Uint32 *dst_px = (Uint32 *)dst;
2419 for (c = width; c; --c) {
2420 // Component bytes extraction.
2421 srcpixel = *src_px++;
2422 RGBA_FROM_RGBA8888(srcpixel, srcR, srcG, srcB, srcA);
2423
2424 // Alpha pre-multiplication of each component.
2425 dstA = srcA;
2426 dstR = (srcA * srcR) / 255;
2427 dstG = (srcA * srcG) / 255;
2428 dstB = (srcA * srcB) / 255;
2429
2430 // RGBA8888 pixel recomposition.
2431 RGBA8888_FROM_RGBA(dstpixel, dstR, dstG, dstB, dstA);
2432 *dst_px++ = dstpixel;
2433 }
2434 src = (const Uint8 *)src + src_pitch;
2435 dst = (Uint8 *)dst + dst_pitch;
2436 }
2437}
2438
2439static void SDL_PremultiplyAlpha_AXYZ128(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch)
2440{
2441 int c;
2442 float flR, flG, flB, flA;
2443
2444 while (height--) {
2445 const float *src_px = (const float *)src;
2446 float *dst_px = (float *)dst;
2447 for (c = width; c; --c) {
2448 flA = *src_px++;
2449 flR = *src_px++;
2450 flG = *src_px++;
2451 flB = *src_px++;
2452
2453 // Alpha pre-multiplication of each component.
2454 flR *= flA;
2455 flG *= flA;
2456 flB *= flA;
2457
2458 *dst_px++ = flA;
2459 *dst_px++ = flR;
2460 *dst_px++ = flG;
2461 *dst_px++ = flB;
2462 }
2463 src = (const Uint8 *)src + src_pitch;
2464 dst = (Uint8 *)dst + dst_pitch;
2465 }
2466}
2467
2468static bool SDL_PremultiplyAlphaPixelsAndColorspace(int width, int height, SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch, SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch, bool linear)
2469{
2470 SDL_Surface *convert = NULL;
2471 void *final_dst = dst;
2472 int final_dst_pitch = dst_pitch;
2473 SDL_PixelFormat format;
2474 SDL_Colorspace colorspace;
2475 bool result = false;
2476
2477 if (!src) {
2478 return SDL_InvalidParamError("src");
2479 }
2480 if (!src_pitch) {
2481 return SDL_InvalidParamError("src_pitch");
2482 }
2483 if (!dst) {
2484 return SDL_InvalidParamError("dst");
2485 }
2486 if (!dst_pitch) {
2487 return SDL_InvalidParamError("dst_pitch");
2488 }
2489
2490 // Use a high precision format if we're converting to linear colorspace or using high precision pixel formats
2491 if (linear ||
2492 SDL_ISPIXELFORMAT_10BIT(src_format) || SDL_BITSPERPIXEL(src_format) > 32 ||
2493 SDL_ISPIXELFORMAT_10BIT(dst_format) || SDL_BITSPERPIXEL(dst_format) > 32) {
2494 if (src_format == SDL_PIXELFORMAT_ARGB128_FLOAT ||
2495 src_format == SDL_PIXELFORMAT_ABGR128_FLOAT) {
2496 format = src_format;
2497 } else {
2498 format = SDL_PIXELFORMAT_ARGB128_FLOAT;
2499 }
2500 } else {
2501 if (src_format == SDL_PIXELFORMAT_ARGB8888 ||
2502 src_format == SDL_PIXELFORMAT_ABGR8888 ||
2503 src_format == SDL_PIXELFORMAT_RGBA8888 ||
2504 src_format == SDL_PIXELFORMAT_BGRA8888) {
2505 format = src_format;
2506 } else {
2507 format = SDL_PIXELFORMAT_ARGB8888;
2508 }
2509 }
2510 if (linear) {
2511 colorspace = SDL_COLORSPACE_SRGB_LINEAR;
2512 } else {
2513 colorspace = SDL_COLORSPACE_SRGB;
2514 }
2515
2516 if (src_format != format || src_colorspace != colorspace) {
2517 convert = SDL_CreateSurface(width, height, format);
2518 if (!convert) {
2519 goto done;
2520 }
2521 if (!SDL_ConvertPixelsAndColorspace(width, height, src_format, src_colorspace, src_properties, src, src_pitch, format, colorspace, 0, convert->pixels, convert->pitch)) {
2522 goto done;
2523 }
2524
2525 src = convert->pixels;
2526 src_pitch = convert->pitch;
2527 dst = convert->pixels;
2528 dst_pitch = convert->pitch;
2529
2530 } else if (dst_format != format || dst_colorspace != colorspace) {
2531 convert = SDL_CreateSurface(width, height, format);
2532 if (!convert) {
2533 goto done;
2534 }
2535 dst = convert->pixels;
2536 dst_pitch = convert->pitch;
2537 }
2538
2539 switch (format) {
2540 case SDL_PIXELFORMAT_ARGB8888:
2541 case SDL_PIXELFORMAT_ABGR8888:
2542 SDL_PremultiplyAlpha_AXYZ8888(width, height, src, src_pitch, dst, dst_pitch);
2543 break;
2544 case SDL_PIXELFORMAT_RGBA8888:
2545 case SDL_PIXELFORMAT_BGRA8888:
2546 SDL_PremultiplyAlpha_XYZA8888(width, height, src, src_pitch, dst, dst_pitch);
2547 break;
2548 case SDL_PIXELFORMAT_ARGB128_FLOAT:
2549 case SDL_PIXELFORMAT_ABGR128_FLOAT:
2550 SDL_PremultiplyAlpha_AXYZ128(width, height, src, src_pitch, dst, dst_pitch);
2551 break;
2552 default:
2553 SDL_SetError("Unexpected internal pixel format");
2554 goto done;
2555 }
2556
2557 if (dst != final_dst) {
2558 if (!SDL_ConvertPixelsAndColorspace(width, height, format, colorspace, 0, convert->pixels, convert->pitch, dst_format, dst_colorspace, dst_properties, final_dst, final_dst_pitch)) {
2559 goto done;
2560 }
2561 }
2562 result = true;
2563
2564done:
2565 if (convert) {
2566 SDL_DestroySurface(convert);
2567 }
2568 return result;
2569}
2570
2571bool SDL_PremultiplyAlpha(int width, int height,
2572 SDL_PixelFormat src_format, const void *src, int src_pitch,
2573 SDL_PixelFormat dst_format, void *dst, int dst_pitch, bool linear)
2574{
2575 SDL_Colorspace src_colorspace = SDL_GetDefaultColorspaceForFormat(src_format);
2576 SDL_Colorspace dst_colorspace = SDL_GetDefaultColorspaceForFormat(dst_format);
2577
2578 return SDL_PremultiplyAlphaPixelsAndColorspace(width, height, src_format, src_colorspace, 0, src, src_pitch, dst_format, dst_colorspace, 0, dst, dst_pitch, linear);
2579}
2580
2581bool SDL_PremultiplySurfaceAlpha(SDL_Surface *surface, bool linear)
2582{
2583 SDL_Colorspace colorspace;
2584
2585 if (!SDL_SurfaceValid(surface)) {
2586 return SDL_InvalidParamError("surface");
2587 }
2588
2589 colorspace = surface->colorspace;
2590
2591 return SDL_PremultiplyAlphaPixelsAndColorspace(surface->w, surface->h, surface->format, colorspace, surface->props, surface->pixels, surface->pitch, surface->format, colorspace, surface->props, surface->pixels, surface->pitch, linear);
2592}
2593
2594bool SDL_ClearSurface(SDL_Surface *surface, float r, float g, float b, float a)
2595{
2596 SDL_Rect clip_rect;
2597 bool result = false;
2598
2599 if (!SDL_SurfaceValid(surface)) {
2600 return SDL_InvalidParamError("surface");
2601 }
2602
2603 SDL_GetSurfaceClipRect(surface, &clip_rect);
2604 SDL_SetSurfaceClipRect(surface, NULL);
2605
2606 if (!SDL_ISPIXELFORMAT_FOURCC(surface->format) &&
2607 SDL_BYTESPERPIXEL(surface->format) <= sizeof(Uint32)) {
2608 Uint32 color;
2609
2610 color = SDL_MapSurfaceRGBA(surface,
2611 (Uint8)SDL_roundf(SDL_clamp(r, 0.0f, 1.0f) * 255.0f),
2612 (Uint8)SDL_roundf(SDL_clamp(g, 0.0f, 1.0f) * 255.0f),
2613 (Uint8)SDL_roundf(SDL_clamp(b, 0.0f, 1.0f) * 255.0f),
2614 (Uint8)SDL_roundf(SDL_clamp(a, 0.0f, 1.0f) * 255.0f));
2615 result = SDL_FillSurfaceRect(surface, NULL, color);
2616 } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2617 // We can't directly set an RGB value on a YUV surface
2618 SDL_Surface *tmp = SDL_CreateSurface(surface->w, surface->h, SDL_PIXELFORMAT_ARGB8888);
2619 if (!tmp) {
2620 goto done;
2621 }
2622
2623 if (SDL_ClearSurface(tmp, r, g, b, a)) {
2624 result = SDL_ConvertPixelsAndColorspace(surface->w, surface->h, tmp->format, tmp->colorspace, tmp->props, tmp->pixels, tmp->pitch, surface->format, surface->colorspace, surface->props, surface->pixels, surface->pitch);
2625 }
2626 SDL_DestroySurface(tmp);
2627 } else {
2628 // Take advantage of blit color conversion
2629 SDL_Surface *tmp = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA128_FLOAT);
2630 if (!tmp) {
2631 goto done;
2632 }
2633 SDL_SetSurfaceColorspace(tmp, surface->colorspace);
2634 SDL_SetSurfaceBlendMode(tmp, SDL_BLENDMODE_NONE);
2635
2636 float *pixels = (float *)tmp->pixels;
2637 pixels[0] = r;
2638 pixels[1] = g;
2639 pixels[2] = b;
2640 pixels[3] = a;
2641
2642 result = SDL_BlitSurfaceScaled(tmp, NULL, surface, NULL, SDL_SCALEMODE_NEAREST);
2643 SDL_DestroySurface(tmp);
2644 }
2645
2646done:
2647 SDL_SetSurfaceClipRect(surface, &clip_rect);
2648
2649 return result;
2650}
2651
2652Uint32 SDL_MapSurfaceRGB(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b)
2653{
2654 return SDL_MapSurfaceRGBA(surface, r, g, b, SDL_ALPHA_OPAQUE);
2655}
2656
2657Uint32 SDL_MapSurfaceRGBA(SDL_Surface *surface, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
2658{
2659 if (!SDL_SurfaceValid(surface)) {
2660 SDL_InvalidParamError("surface");
2661 return true;
2662 }
2663 return SDL_MapRGBA(surface->fmt, surface->palette, r, g, b, a);
2664}
2665
2666// This function Copyright 2023 Collabora Ltd., contributed to SDL under the ZLib license
2667bool SDL_ReadSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a)
2668{
2669 Uint32 pixel = 0;
2670 size_t bytes_per_pixel;
2671 Uint8 unused;
2672 Uint8 *p;
2673 bool result = false;
2674
2675 if (r) {
2676 *r = 0;
2677 } else {
2678 r = &unused;
2679 }
2680
2681 if (g) {
2682 *g = 0;
2683 } else {
2684 g = &unused;
2685 }
2686
2687 if (b) {
2688 *b = 0;
2689 } else {
2690 b = &unused;
2691 }
2692
2693 if (a) {
2694 *a = 0;
2695 } else {
2696 a = &unused;
2697 }
2698
2699 if (!SDL_SurfaceValid(surface) || !surface->format || !surface->pixels) {
2700 return SDL_InvalidParamError("surface");
2701 }
2702
2703 if (x < 0 || x >= surface->w) {
2704 return SDL_InvalidParamError("x");
2705 }
2706
2707 if (y < 0 || y >= surface->h) {
2708 return SDL_InvalidParamError("y");
2709 }
2710
2711 bytes_per_pixel = SDL_BYTESPERPIXEL(surface->format);
2712
2713 if (SDL_MUSTLOCK(surface)) {
2714 if (!SDL_LockSurface(surface)) {
2715 return false;
2716 }
2717 }
2718
2719 p = (Uint8 *)surface->pixels + y * surface->pitch + x * bytes_per_pixel;
2720
2721 if (bytes_per_pixel <= sizeof(pixel) && !SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2722 /* Fill the appropriate number of least-significant bytes of pixel,
2723 * leaving the most-significant bytes set to zero */
2724#if SDL_BYTEORDER == SDL_BIG_ENDIAN
2725 SDL_memcpy(((Uint8 *)&pixel) + (sizeof(pixel) - bytes_per_pixel), p, bytes_per_pixel);
2726#else
2727 SDL_memcpy(&pixel, p, bytes_per_pixel);
2728#endif
2729 SDL_GetRGBA(pixel, surface->fmt, surface->palette, r, g, b, a);
2730 result = true;
2731 } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2732 // FIXME: We need code to extract a single macroblock from a YUV surface
2733 SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
2734 if (converted) {
2735 result = SDL_ReadSurfacePixel(converted, x, y, r, g, b, a);
2736 SDL_DestroySurface(converted);
2737 }
2738 } else {
2739 // This is really slow, but it gets the job done
2740 Uint8 rgba[4];
2741
2742 if (SDL_ConvertPixelsAndColorspace(1, 1, surface->format, surface->colorspace, surface->props, p, surface->pitch, SDL_PIXELFORMAT_RGBA32, SDL_COLORSPACE_SRGB, 0, rgba, sizeof(rgba))) {
2743 *r = rgba[0];
2744 *g = rgba[1];
2745 *b = rgba[2];
2746 *a = rgba[3];
2747 result = true;
2748 }
2749 }
2750
2751 if (SDL_MUSTLOCK(surface)) {
2752 SDL_UnlockSurface(surface);
2753 }
2754 return result;
2755}
2756
2757bool SDL_ReadSurfacePixelFloat(SDL_Surface *surface, int x, int y, float *r, float *g, float *b, float *a)
2758{
2759 float unused;
2760 bool result = false;
2761
2762 if (r) {
2763 *r = 0.0f;
2764 } else {
2765 r = &unused;
2766 }
2767
2768 if (g) {
2769 *g = 0.0f;
2770 } else {
2771 g = &unused;
2772 }
2773
2774 if (b) {
2775 *b = 0.0f;
2776 } else {
2777 b = &unused;
2778 }
2779
2780 if (a) {
2781 *a = 0.0f;
2782 } else {
2783 a = &unused;
2784 }
2785
2786 if (!SDL_SurfaceValid(surface) || !surface->format || !surface->pixels) {
2787 return SDL_InvalidParamError("surface");
2788 }
2789
2790 if (x < 0 || x >= surface->w) {
2791 return SDL_InvalidParamError("x");
2792 }
2793
2794 if (y < 0 || y >= surface->h) {
2795 return SDL_InvalidParamError("y");
2796 }
2797
2798 if (SDL_BYTESPERPIXEL(surface->format) <= sizeof(Uint32) && !SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2799 Uint8 r8, g8, b8, a8;
2800
2801 if (SDL_ReadSurfacePixel(surface, x, y, &r8, &g8, &b8, &a8)) {
2802 *r = (float)r8 / 255.0f;
2803 *g = (float)g8 / 255.0f;
2804 *b = (float)b8 / 255.0f;
2805 *a = (float)a8 / 255.0f;
2806 result = true;
2807 }
2808 } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2809 // FIXME: We need code to extract a single macroblock from a YUV surface
2810 SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888);
2811 if (converted) {
2812 result = SDL_ReadSurfacePixelFloat(converted, x, y, r, g, b, a);
2813 SDL_DestroySurface(converted);
2814 }
2815 } else {
2816 // This is really slow, but it gets the job done
2817 float rgba[4];
2818 Uint8 *p;
2819
2820 if (SDL_MUSTLOCK(surface)) {
2821 if (!SDL_LockSurface(surface)) {
2822 return false;
2823 }
2824 }
2825
2826 p = (Uint8 *)surface->pixels + y * surface->pitch + x * SDL_BYTESPERPIXEL(surface->format);
2827
2828 if (surface->format == SDL_PIXELFORMAT_RGBA128_FLOAT) {
2829 SDL_memcpy(rgba, p, sizeof(rgba));
2830 result = true;
2831 } else {
2832 SDL_Colorspace src_colorspace = surface->colorspace;
2833 SDL_Colorspace dst_colorspace = (src_colorspace == SDL_COLORSPACE_SRGB_LINEAR ? SDL_COLORSPACE_SRGB_LINEAR : SDL_COLORSPACE_SRGB);
2834
2835 if (SDL_ConvertPixelsAndColorspace(1, 1, surface->format, src_colorspace, surface->props, p, surface->pitch, SDL_PIXELFORMAT_RGBA128_FLOAT, dst_colorspace, 0, rgba, sizeof(rgba))) {
2836 result = true;
2837 }
2838 }
2839
2840 if (result) {
2841 *r = rgba[0];
2842 *g = rgba[1];
2843 *b = rgba[2];
2844 *a = rgba[3];
2845 }
2846
2847 if (SDL_MUSTLOCK(surface)) {
2848 SDL_UnlockSurface(surface);
2849 }
2850 }
2851 return result;
2852}
2853
2854bool SDL_WriteSurfacePixel(SDL_Surface *surface, int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
2855{
2856 Uint32 pixel = 0;
2857 size_t bytes_per_pixel;
2858 Uint8 *p;
2859 bool result = false;
2860
2861 if (!SDL_SurfaceValid(surface) || !surface->format || !surface->pixels) {
2862 return SDL_InvalidParamError("surface");
2863 }
2864
2865 if (x < 0 || x >= surface->w) {
2866 return SDL_InvalidParamError("x");
2867 }
2868
2869 if (y < 0 || y >= surface->h) {
2870 return SDL_InvalidParamError("y");
2871 }
2872
2873 bytes_per_pixel = SDL_BYTESPERPIXEL(surface->format);
2874
2875 if (SDL_MUSTLOCK(surface)) {
2876 if (!SDL_LockSurface(surface)) {
2877 return false;
2878 }
2879 }
2880
2881 p = (Uint8 *)surface->pixels + y * surface->pitch + x * bytes_per_pixel;
2882
2883 if (bytes_per_pixel <= sizeof(pixel) && !SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2884 pixel = SDL_MapRGBA(surface->fmt, surface->palette, r, g, b, a);
2885#if SDL_BYTEORDER == SDL_BIG_ENDIAN
2886 SDL_memcpy(p, ((Uint8 *)&pixel) + (sizeof(pixel) - bytes_per_pixel), bytes_per_pixel);
2887#else
2888 SDL_memcpy(p, &pixel, bytes_per_pixel);
2889#endif
2890 result = true;
2891 } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2892 result = SDL_Unsupported();
2893 } else {
2894 // This is really slow, but it gets the job done
2895 Uint8 rgba[4];
2896
2897 rgba[0] = r;
2898 rgba[1] = g;
2899 rgba[2] = b;
2900 rgba[3] = a;
2901 result = SDL_ConvertPixelsAndColorspace(1, 1, SDL_PIXELFORMAT_RGBA32, SDL_COLORSPACE_SRGB, 0, rgba, sizeof(rgba), surface->format, surface->colorspace, surface->props, p, surface->pitch);
2902 }
2903
2904 if (SDL_MUSTLOCK(surface)) {
2905 SDL_UnlockSurface(surface);
2906 }
2907 return result;
2908}
2909
2910bool SDL_WriteSurfacePixelFloat(SDL_Surface *surface, int x, int y, float r, float g, float b, float a)
2911{
2912 bool result = false;
2913
2914 if (!SDL_SurfaceValid(surface) || !surface->format || !surface->pixels) {
2915 return SDL_InvalidParamError("surface");
2916 }
2917
2918 if (x < 0 || x >= surface->w) {
2919 return SDL_InvalidParamError("x");
2920 }
2921
2922 if (y < 0 || y >= surface->h) {
2923 return SDL_InvalidParamError("y");
2924 }
2925
2926 if (SDL_BYTESPERPIXEL(surface->format) <= sizeof(Uint32) && !SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2927 Uint8 r8, g8, b8, a8;
2928
2929 r8 = (Uint8)SDL_round(SDL_clamp(r, 0.0f, 1.0f) * 255.0f);
2930 g8 = (Uint8)SDL_round(SDL_clamp(g, 0.0f, 1.0f) * 255.0f);
2931 b8 = (Uint8)SDL_round(SDL_clamp(b, 0.0f, 1.0f) * 255.0f);
2932 a8 = (Uint8)SDL_round(SDL_clamp(a, 0.0f, 1.0f) * 255.0f);
2933 if (SDL_WriteSurfacePixel(surface, x, y, r8, g8, b8, a8)) {
2934 result = true;
2935 }
2936 } else if (SDL_ISPIXELFORMAT_FOURCC(surface->format)) {
2937 result = SDL_Unsupported();
2938 } else {
2939 // This is really slow, but it gets the job done
2940 float rgba[4];
2941 Uint8 *p;
2942
2943 if (SDL_MUSTLOCK(surface)) {
2944 if (!SDL_LockSurface(surface)) {
2945 return false;
2946 }
2947 }
2948
2949 p = (Uint8 *)surface->pixels + y * surface->pitch + x * SDL_BYTESPERPIXEL(surface->format);
2950
2951 rgba[0] = r;
2952 rgba[1] = g;
2953 rgba[2] = b;
2954 rgba[3] = a;
2955
2956 if (surface->format == SDL_PIXELFORMAT_RGBA128_FLOAT) {
2957 SDL_memcpy(p, rgba, sizeof(rgba));
2958 result = true;
2959 } else {
2960 SDL_Colorspace dst_colorspace = surface->colorspace;
2961 SDL_Colorspace src_colorspace = (dst_colorspace == SDL_COLORSPACE_SRGB_LINEAR ? SDL_COLORSPACE_SRGB_LINEAR : SDL_COLORSPACE_SRGB);
2962
2963 result = SDL_ConvertPixelsAndColorspace(1, 1, SDL_PIXELFORMAT_RGBA128_FLOAT, src_colorspace, 0, rgba, sizeof(rgba), surface->format, dst_colorspace, surface->props, p, surface->pitch);
2964 }
2965
2966 if (SDL_MUSTLOCK(surface)) {
2967 SDL_UnlockSurface(surface);
2968 }
2969 }
2970 return result;
2971}
2972
2973/*
2974 * Free a surface created by the above function.
2975 */
2976void SDL_DestroySurface(SDL_Surface *surface)
2977{
2978 if (!SDL_SurfaceValid(surface)) {
2979 return;
2980 }
2981 if (surface->internal_flags & SDL_INTERNAL_SURFACE_DONTFREE) {
2982 return;
2983 }
2984 if (--surface->refcount > 0) {
2985 return;
2986 }
2987
2988 SDL_RemoveSurfaceAlternateImages(surface);
2989
2990 SDL_DestroyProperties(surface->props);
2991
2992 SDL_InvalidateMap(&surface->map);
2993
2994 while (surface->locked > 0) {
2995 SDL_UnlockSurface(surface);
2996 }
2997#ifdef SDL_HAVE_RLE
2998 if (surface->internal_flags & SDL_INTERNAL_SURFACE_RLEACCEL) {
2999 SDL_UnRLESurface(surface, false);
3000 }
3001#endif
3002 SDL_SetSurfacePalette(surface, NULL);
3003
3004 if (surface->flags & SDL_SURFACE_PREALLOCATED) {
3005 // Don't free
3006 } else if (surface->flags & SDL_SURFACE_SIMD_ALIGNED) {
3007 // Free aligned
3008 SDL_aligned_free(surface->pixels);
3009 } else {
3010 // Normal
3011 SDL_free(surface->pixels);
3012 }
3013
3014 surface->reserved = NULL;
3015
3016 if (!(surface->internal_flags & SDL_INTERNAL_SURFACE_STACK)) {
3017 SDL_free(surface);
3018 }
3019}
3020