1 /**
2  * SDL_Image support.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.graphics.sdl2image;
15 
16 import ae.utils.graphics.color;
17 import ae.utils.graphics.image;
18 
19 import derelict.sdl2.sdl;
20 import derelict.sdl2.image;
21 
22 import std.exception;
23 import std..string : toStringz, format;
24 static import std..string;
25 
26 static this()
27 {
28 	DerelictSDL2.load();
29 	DerelictSDL2Image.load();
30 
31 	IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
32 }
33 
34 /// Loads an `Image` using `SDL_Image`.
35 auto loadImage(string path, ref Image!RGBX target = *new Image!RGBX)
36 {
37 	auto surface = IMG_Load(toStringz(path));
38 	enforce(surface, "Failed to load image " ~ path);
39 	scope(exit) SDL_FreeSurface(surface);
40 	target.size(surface.w, surface.h);
41 
42 	if (surface.format.palette)
43 	{
44 		switch (surface.format.BitsPerPixel)
45 		{
46 			case 1: depalettize!1(cast(ubyte*)surface.pixels, cast(RGBX*)target.pixels.ptr, surface.format.palette, surface.w, surface.h, surface.pitch); break;
47 			case 2: depalettize!2(cast(ubyte*)surface.pixels, cast(RGBX*)target.pixels.ptr, surface.format.palette, surface.w, surface.h, surface.pitch); break;
48 			case 4: depalettize!4(cast(ubyte*)surface.pixels, cast(RGBX*)target.pixels.ptr, surface.format.palette, surface.w, surface.h, surface.pitch); break;
49 			case 8: depalettize!8(cast(ubyte*)surface.pixels, cast(RGBX*)target.pixels.ptr, surface.format.palette, surface.w, surface.h, surface.pitch); break;
50 			default:
51 				enforce(false, format("Don't know how to depalettize image with %d bits per pixel", surface.format.BitsPerPixel));
52 		}
53 	}
54 	else
55 		rgbTransform(cast(ubyte*)surface.pixels, cast(RGBX*)target.pixels.ptr, surface.format, surface.w, surface.h, surface.pitch);
56 
57 	return target;
58 }
59 
60 private:
61 
62 void depalettize(int BITS)(ubyte* src, RGBX* dst, SDL_Palette *palette, uint w, uint h, int pitch)
63 {
64 	static assert(BITS <= 8);
65 
66 	auto ncolors = palette.ncolors;
67 	foreach (y; 0..h)
68 	{
69 		auto p = src;
70 		foreach (x; 0..w)
71 		{
72 			ubyte c;
73 			static if (BITS == 8)
74 				c = *p++;
75 			else
76 				c = p[x / (8/BITS)] & (((1<<BITS)-1) << (x % (8/BITS)));
77 
78 			if (c >= ncolors)
79 				throw new Exception("Color index exceeds number of colors in palette");
80 			*dst++ = cast(RGBX)(palette.colors[c]);
81 		}
82 		src += pitch;
83 	}
84 }
85 
86 void rgbTransform(ubyte* src, RGBX* dst, SDL_PixelFormat *format, uint w, uint h, int pitch)
87 {
88 	auto bpp = format.BitsPerPixel;
89 	enforce(bpp%8 == 0 && bpp >= 8 && bpp <= 32, std..string.format("Don't know how to process unpalettized image with %d bits per pixel", bpp));
90 
91 	if (bpp == 32
92 	 && format.Rmask == 0x00_00_00_FF && format.Rshift== 0
93 	 && format.Gmask == 0x00_00_FF_00 && format.Rshift== 8
94 	 && format.Bmask == 0x00_FF_00_00 && format.Rshift==16)
95 	{
96 		// Everything is already in our desired format.
97 		foreach (y; 0..h)
98 		{
99 			auto p = cast(RGBX*)src;
100 			dst[0..w] = p[0..w];
101 			src += pitch;
102 			dst += w;
103 		}
104 	}
105 	else
106 	{
107 		// Use SDL_GetRGB for whatever weird formats.
108 		auto Bpp = format.BytesPerPixel;
109 		foreach (y; 0..h)
110 		{
111 			auto p = src;
112 			foreach (x; 0..w)
113 			{
114 				RGBX c;
115 				SDL_GetRGB(*cast(uint*)p, format, &c.r, &c.g, &c.b);
116 				*dst++ = c;
117 				p += Bpp;
118 			}
119 			src += pitch;
120 		}
121 	}
122 }