Version 0.3
[colorize.git] / src / yuv.c
1 /* input and output is an array of double
2  * the array has thee dimensions of the followin size: width, height, 3
3  *
4  * note: source and destination may point ot the same array
5  */
6 #include "yuv.h"
7 double black_level = 0.00;
8 double white_level = 1.00;
9 int scale_levels = 0; /* scale between black and white level */
10 double fade_level = 0.20;
11 int yuv_mod = 0; /* UV vectors are not affected by lightness of the pixle */
12
13 /* Modified YUV:
14  *
15  * Instead of having U and V vector length affected by the lightnes of the
16  * pixle, we only apply the saturation to U and V. This prevents change of
17  * saturation when convertig YUV /with different Y) back to RGB.
18  */
19
20 inline double max_rgb(double r, double g, double b)
21 {
22         if (r > g) {
23                 if (r > b)
24                         return r;
25                 else if (b > g)
26                         return b;
27                 return g;
28         }
29         if (b > g)
30                 return b;
31         return g;
32 }
33
34 /* modified YUV space */
35 inline void rgb2yuv_pixle_mod(double r, double g, double b, double *y, double *u, double *v)
36 {
37         double max = 0;
38         double r_, g_, b_, y_;
39
40         /* 1. scale RGB to maximum lightness */
41         /* 1.1 find maximum value */
42         max = max_rgb(r, g, b);
43         /* 1.2 if value is too low, assume grey pixle */
44         if (max < 0.001) {
45                 *y = *u = *v = 0;
46                 return;
47         }
48         /* 1.3 use max to scale RGB to max lightness */
49         r_ = r / max;
50         g_ = g / max;
51         b_ = b / max;
52
53         /* 2. convert to modified YUV */
54         /* 2.1 use Y from original RGB */
55         *y = 0.299*r + 0.587*g + 0.114*b;
56         /* 2.2 use UV from scaled RGB */
57         y_ = 0.299*r_ + 0.587*g_ + 0.114*b_;
58         *u = 0.492*(b_ - y_);
59         *v = 0.877*(r_ - y_);
60 }
61
62 void rgb2yuv_pixle(double r, double g, double b, double *y, double *u, double *v)
63 {
64         if (yuv_mod) {
65                 rgb2yuv_pixle_mod(r, g, b, y, u, v);
66                 return;
67         }
68         *y = 0.299*r + 0.587*g + 0.114*b;
69         *u = 0.492*(b - *y);
70         *v = 0.877*(r - *y);
71 }
72
73 void rgb2yuv(double *src, double *dst, int width, int height)
74 {
75         int i, ii;
76         double r, g, b, y, u, v;
77
78         for (i = 0, ii = width * height; i < ii; i++) {
79                 /* fetch */
80                 r = src[i];
81                 g = src[i + ii];
82                 b = src[i + ii + ii];
83                 /* convert */
84                 rgb2yuv_pixle(r, g, b, &y, &u, &v);
85                 /* store */
86                 dst[i] = y;
87                 dst[i + ii] = u;
88                 dst[i + ii + ii] = v;
89         }
90 }
91
92 inline int _scale_levels(double *y, double *r, double *g, double *b)
93 {
94         double ny;
95
96         /* prevent from crashing/artefacts, if black level is at full level */
97         if (black_level > 0.999) {
98                 *r = *g = *b = 1.0;
99                 return -1;
100         }
101         if (white_level < 0.001) {
102                 *r = *g = *b = 0.0;
103                 return -1 ;
104         }
105         if (white_level - black_level < 0.001) {
106                 *r = *b = 1.0 ; *g = 0.0;
107                 return -1;
108         }
109         /* scale down, black level becomes 0 */
110         if (black_level > 0.0 || white_level < 1.0) {
111                 ny = (*y - black_level) / (white_level - black_level);
112                 if (ny < 0.0) {
113                         if (scale_levels)
114                                 *r = *g = *b = 0.0;
115                         else
116                                 *r = *g = *b = *y;
117                         return -1;
118                 }
119                 if (ny > 1.0) {
120                         if (scale_levels)
121                                 *r = *g = *b = 1.0;
122                         else
123                                 *r = *g = *b = *y;
124                         return -1;
125                 }
126                 *y = ny;
127         }
128
129         return 0;
130 }
131
132 inline void _unscale_levels(double *r, double *g, double *b)
133 {
134         if (scale_levels)
135                 return;
136         /* scale up, so 0 becomes black level */
137         if (black_level > 0.0 || white_level < 1.0) {
138                 *r = black_level + *r * (white_level - black_level);
139                 *g = black_level + *g * (white_level - black_level);
140                 *b = black_level + *b * (white_level - black_level);
141         }
142 }
143
144 /* modified YUV space */
145 void yuv2rgb_pixle_mod(double y, double u, double v, double *r, double *g, double *b)
146 {
147         double r_, g_, b_, y_;
148         double max, scale;
149         double is_dist, must_dist;
150
151         if (_scale_levels(&y, r, g, b))
152                 return;
153
154         /* 1. get RGB from U and V with maximum lightness */
155         /* 1.1 U and V conversion without Y */
156         r_ = v/0.877;
157         g_ = -0.395*u - 0.581*v;
158         b_ = u/0.492;
159         /* 1.2 find maximum value */
160         max = max_rgb(r_, g_, b_);
161         /* 1.3 maximize RGB */
162         r_ = 1.0-max+r_;
163         g_ = 1.0-max+g_;
164         b_ = 1.0-max+b_;
165
166         /* 2. calculate scale factor to scale RGB (with max lightness) to target Y */
167         /* 2.1 calculate Y of scaled RGB */
168         y_ = 0.299*r_ + 0.587*g_ + 0.114*b_;
169         /* 2.2 calculate scale factor */
170         scale = y/y_;
171
172         /* 3. scale RGB (with max lightness) to target Y and clip (by reducing saturation), if needed */
173         /* 3.1 scale RGB with factor */
174         r_ *= scale;
175         g_ *= scale;
176         b_ *= scale;
177         /* 3.2 finx max value to clip if needed */
178         max = max_rgb(r_, g_, b_);
179         /* 3.3 clip by reducing saturation */
180         if (max > 1.0) {
181                 is_dist = max-y;
182                 must_dist = 1-y;
183                 *r = (r_-y)/is_dist*must_dist + y;
184                 *g = (g_-y)/is_dist*must_dist + y;
185                 *b = (b_-y)/is_dist*must_dist + y;
186         } else {
187                 *r = r_;
188                 *g = g_;
189                 *b = b_;
190         }
191
192         _unscale_levels(r, g, b);
193 }
194
195 //#define SCALE_LEVEL
196 void yuv2rgb_pixle(double y, double u, double v, double *r, double *g, double *b)
197 {
198         double nu, nv, uv, nuv;
199         double fade;
200
201         if (yuv_mod) {
202                 yuv2rgb_pixle_mod(y, u, v, r, g, b);
203                 return;
204         }
205
206         if (_scale_levels(&y, r, g, b))
207                 return;
208
209         /* scale UV vector lengh between black level and fade level */
210         if (y < fade_level && fade_level > 0.001) {
211                 fade = y / fade_level;
212                 u *= fade;
213                 v *= fade;
214         }
215
216         /* convert YUV to RGB */
217         *r = y + v/0.877;
218         *g = y - 0.395*u - 0.581*v;
219         *b = y + u/0.492;
220
221         /* clip UV vector length, if green exceed range */
222         if (*g > 1.0) {
223                 uv = 0.395*u + 0.581*v;
224                 nuv = y - 1.0;
225                 u = u * nuv / uv;
226                 v = v * nuv / uv;
227                 *r = y + v/0.877;
228                 *g = y - 0.395*u - 0.581*v;
229                 *b = y + u/0.492;
230         } else
231         if (*g < 0.0) {
232                 uv = 0.395*u + 0.581*v;
233                 nuv = y;
234                 u = u * nuv / uv;
235                 v = v * nuv / uv;
236                 *r = y + v/0.877;
237                 *g = y - 0.395*u - 0.581*v;
238                 *b = y + u/0.492;
239         }
240         /* clip UV vector length, if red exceed range */
241         if (*r > 1.0F) {
242                 nv = (1-y)*0.877;
243                 u = u * nv / v;
244                 v = nv;
245                 *r = y + v/0.877;
246                 *g = y - 0.395*u - 0.581*v;
247                 *b = y + u/0.492;
248         } else
249         if (*r < 0.0F) {
250                 nv = (0-y)*0.877;
251                 u = u * nv / v;
252                 v = nv;
253                 *r = y + v/0.877;
254                 *g = y - 0.395*u - 0.581*v;
255                 *b = y + u/0.492;
256         }
257         /* clip UV vector length, if blue exceed range */
258         if (*b > 1.0F) {
259                 nu = (1-y)*0.492;
260                 v = v * nu / u;
261                 u = nu;
262                 *r = y + v/0.877;
263                 *g = y - 0.395*u - 0.581*v;
264                 *b = y + u/0.492;
265         } else
266         if (*b < 0.0F) {
267                 nu = (0-y)*0.492;
268                 v = v * nu / u;
269                 u = nu;
270                 *r = y + v/0.877;
271                 *g = y - 0.395*u - 0.581*v;
272                 *b = y + u/0.492;
273         }
274
275         _unscale_levels(r, g, b);
276 }
277
278 void yuv2rgb(double *src, double *dst, int width, int height)
279 {
280         int i, ii;
281         double r, g, b, y, u, v;
282
283         for (i = 0, ii = width * height; i < ii; i++) {
284                 /* fetch */
285                 y = src[i];
286                 u = src[i + ii];
287                 v = src[i + ii + ii];
288                 /* convert */
289                 yuv2rgb_pixle(y, u, v, &r, &g, &b);
290                 /* store */
291                 dst[i] = r;
292                 dst[i + ii] = g;
293                 dst[i + ii + ii] = b;
294         }
295 }
296