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