1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24/* Gamma correction support */
25
26#ifdef HAVE_MATH_H
27#include <math.h>	/* Used for calculating gamma ramps */
28#else
29/* Math routines from uClibc: http://www.uclibc.org */
30#include "math_private.h"
31#include "e_sqrt.h"
32#include "e_pow.h"
33#include "e_log.h"
34#define pow(x, y)	__ieee754_pow(x, y)
35#define log(x)		__ieee754_log(x)
36#endif
37
38#include "SDL_sysvideo.h"
39
40
41static void CalculateGammaRamp(float gamma, Uint16 *ramp)
42{
43	int i;
44
45	/* 0.0 gamma is all black */
46	if ( gamma <= 0.0f ) {
47		for ( i=0; i<256; ++i ) {
48			ramp[i] = 0;
49		}
50		return;
51	} else
52	/* 1.0 gamma is identity */
53	if ( gamma == 1.0f ) {
54		for ( i=0; i<256; ++i ) {
55			ramp[i] = (i << 8) | i;
56		}
57		return;
58	} else
59	/* Calculate a real gamma ramp */
60	{ int value;
61		gamma = 1.0f / gamma;
62		for ( i=0; i<256; ++i ) {
63			value = (int)(pow((double)i/256.0, gamma)*65535.0+0.5);
64			if ( value > 65535 ) {
65				value = 65535;
66			}
67			ramp[i] = (Uint16)value;
68		}
69	}
70}
71static void CalculateGammaFromRamp(float *gamma, Uint16 *ramp)
72{
73	/* The following is adapted from a post by Garrett Bass on OpenGL
74	   Gamedev list, March 4, 2000.
75	 */
76	float sum = 0.0f;
77	int i, count = 0;
78
79	*gamma = 1.0;
80	for ( i = 1; i < 256; ++i ) {
81	    if ( (ramp[i] != 0) && (ramp[i] != 65535) ) {
82	        double B = (double)i / 256.0;
83	        double A = ramp[i] / 65535.0;
84	        sum += (float) ( log(A) / log(B) );
85	        count++;
86	    }
87	}
88	if ( count && sum > 0.0f ) {
89		*gamma = 1.0f / (sum / count);
90	}
91}
92
93int SDL_SetGamma(float red, float green, float blue)
94{
95	int succeeded;
96	SDL_VideoDevice *video = current_video;
97	SDL_VideoDevice *this  = current_video;
98
99	succeeded = -1;
100	/* Prefer using SetGammaRamp(), as it's more flexible */
101	{
102		Uint16 ramp[3][256];
103
104		CalculateGammaRamp(red, ramp[0]);
105		CalculateGammaRamp(green, ramp[1]);
106		CalculateGammaRamp(blue, ramp[2]);
107		succeeded = SDL_SetGammaRamp(ramp[0], ramp[1], ramp[2]);
108	}
109	if ( (succeeded < 0) && video->SetGamma ) {
110		SDL_ClearError();
111		succeeded = video->SetGamma(this, red, green, blue);
112	}
113	return succeeded;
114}
115
116/* Calculating the gamma by integrating the gamma ramps isn't exact,
117   so this function isn't officially supported.
118*/
119int SDL_GetGamma(float *red, float *green, float *blue)
120{
121	int succeeded;
122	SDL_VideoDevice *video = current_video;
123	SDL_VideoDevice *this  = current_video;
124
125	succeeded = -1;
126	/* Prefer using GetGammaRamp(), as it's more flexible */
127	{
128		Uint16 ramp[3][256];
129
130		succeeded = SDL_GetGammaRamp(ramp[0], ramp[1], ramp[2]);
131		if ( succeeded >= 0 ) {
132			CalculateGammaFromRamp(red, ramp[0]);
133			CalculateGammaFromRamp(green, ramp[1]);
134			CalculateGammaFromRamp(blue, ramp[2]);
135		}
136	}
137	if ( (succeeded < 0) && video->GetGamma ) {
138		SDL_ClearError();
139		succeeded = video->GetGamma(this, red, green, blue);
140	}
141	return succeeded;
142}
143
144int SDL_SetGammaRamp(const Uint16 *red, const Uint16 *green, const Uint16 *blue)
145{
146	int succeeded;
147	SDL_VideoDevice *video = current_video;
148	SDL_VideoDevice *this  = current_video;
149	SDL_Surface *screen = SDL_PublicSurface;
150
151	/* Verify the screen parameter */
152	if ( !screen ) {
153		SDL_SetError("No video mode has been set");
154		return -1;
155	}
156
157	/* Lazily allocate the gamma tables */
158	if ( ! video->gamma ) {
159		SDL_GetGammaRamp(0, 0, 0);
160	}
161
162	/* Fill the gamma table with the new values */
163	if ( red ) {
164		SDL_memcpy(&video->gamma[0*256], red, 256*sizeof(*video->gamma));
165	}
166	if ( green ) {
167		SDL_memcpy(&video->gamma[1*256], green, 256*sizeof(*video->gamma));
168	}
169	if ( blue ) {
170		SDL_memcpy(&video->gamma[2*256], blue, 256*sizeof(*video->gamma));
171	}
172
173	/* Gamma correction always possible on split palettes */
174	if ( (screen->flags & SDL_HWPALETTE) == SDL_HWPALETTE ) {
175		SDL_Palette *pal = screen->format->palette;
176
177		/* If physical palette has been set independently, use it */
178		if(video->physpal)
179		        pal = video->physpal;
180
181		SDL_SetPalette(screen, SDL_PHYSPAL,
182			       pal->colors, 0, pal->ncolors);
183		return 0;
184	}
185
186	/* Try to set the gamma ramp in the driver */
187	succeeded = -1;
188	if ( video->SetGammaRamp ) {
189		succeeded = video->SetGammaRamp(this, video->gamma);
190	} else {
191		SDL_SetError("Gamma ramp manipulation not supported");
192	}
193	return succeeded;
194}
195
196int SDL_GetGammaRamp(Uint16 *red, Uint16 *green, Uint16 *blue)
197{
198	SDL_VideoDevice *video = current_video;
199	SDL_VideoDevice *this  = current_video;
200
201	/* Lazily allocate the gamma table */
202	if ( ! video->gamma ) {
203		video->gamma = SDL_malloc(3*256*sizeof(*video->gamma));
204		if ( ! video->gamma ) {
205			SDL_OutOfMemory();
206			return -1;
207		}
208		if ( video->GetGammaRamp ) {
209			/* Get the real hardware gamma */
210			video->GetGammaRamp(this, video->gamma);
211		} else {
212			/* Assume an identity gamma */
213			int i;
214			for ( i=0; i<256; ++i ) {
215				video->gamma[0*256+i] = (i << 8) | i;
216				video->gamma[1*256+i] = (i << 8) | i;
217				video->gamma[2*256+i] = (i << 8) | i;
218			}
219		}
220	}
221
222	/* Just copy from our internal table */
223	if ( red ) {
224		SDL_memcpy(red, &video->gamma[0*256], 256*sizeof(*red));
225	}
226	if ( green ) {
227		SDL_memcpy(green, &video->gamma[1*256], 256*sizeof(*green));
228	}
229	if ( blue ) {
230		SDL_memcpy(blue, &video->gamma[2*256], 256*sizeof(*blue));
231	}
232	return 0;
233}
234