1 /*  -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
2  * levels.c
3  * Copyright (C) 2009 Maksim Golovkin (m4ks1k@gmail.com)
4  * This file is a Frei0r plugin.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19  */
20 
21 #include <stdlib.h>
22 #include <assert.h>
23 #include <math.h>
24 #include <stdio.h>
25 
26 #include "frei0r.h"
27 #include "frei0r_math.h"
28 
29 enum ChannelChoice
30 {
31   CHANNEL_RED,
32   CHANNEL_GREEN,
33   CHANNEL_BLUE,
34   CHANNEL_LUMA,
35 };
36 
37 enum HistogramPosChoice
38 {
39   POS_TOP_LEFT,
40   POS_TOP_RIGHT,
41   POS_BOTTOM_LEFT,
42   POS_BOTTOM_RIGHT,
43 };
44 
45 enum ParamIndex
46 {
47   PARAM_CHANNEL,
48   PARAM_INPUT_MIN,
49   PARAM_INPUT_MAX,
50   PARAM_GAMMA,
51   PARAM_OUTPUT_MIN,
52   PARAM_OUTPUT_MAX,
53   PARAM_SHOW_HISTOGRAM,
54   PARAM_HISTOGRAM_POS,
55 
56   PARAMETER_COUNT  // last one.
57 };
58 
59 typedef struct levels_instance
60 {
61   unsigned int width;
62   unsigned int height;
63   double inputMin; // 0 - 1
64   double inputMax; // 0 - 1
65   double outputMin; // 0 - 1
66   double outputMax; // 0 - 1
67   double gamma;
68   enum ChannelChoice channel;
69   char showHistogram;
70   enum HistogramPosChoice histogramPosition;
71 } levels_instance_t;
72 
f0r_init()73 int f0r_init()
74 {
75   return 1;
76 }
77 
f0r_deinit()78 void f0r_deinit()
79 { /* no initialization required */ }
80 
f0r_get_plugin_info(f0r_plugin_info_t * levels_instance_t)81 void f0r_get_plugin_info(f0r_plugin_info_t* levels_instance_t)
82 {
83   levels_instance_t->name = "Levels";
84   levels_instance_t->author = "Maksim Golovkin";
85   levels_instance_t->plugin_type = F0R_PLUGIN_TYPE_FILTER;
86   levels_instance_t->color_model = F0R_COLOR_MODEL_RGBA8888;
87   levels_instance_t->frei0r_version = FREI0R_MAJOR_VERSION;
88   levels_instance_t->major_version = 0;
89   levels_instance_t->minor_version = 4;
90   levels_instance_t->num_params = PARAMETER_COUNT;
91   levels_instance_t->explanation = "Adjust luminance or color channel intensity";
92 }
93 
f0r_get_param_info(f0r_param_info_t * info,int param_index)94 void f0r_get_param_info(f0r_param_info_t* info, int param_index)
95 {
96   switch(param_index)
97   {
98   case PARAM_CHANNEL:
99     info->name = "Channel";
100     info->type = F0R_PARAM_DOUBLE;
101     info->explanation = "Channel to adjust levels. "
102       "0%=R, 10%=G, 20%=B, 30%=Luma";
103     break;
104   case PARAM_INPUT_MIN:
105     info->name = "Input black level";
106     info->type = F0R_PARAM_DOUBLE;
107     info->explanation = "Input black level";
108     break;
109   case PARAM_INPUT_MAX:
110     info->name = "Input white level";
111     info->type = F0R_PARAM_DOUBLE;
112     info->explanation = "Input white level";
113     break;
114   case PARAM_GAMMA:
115     info->name = "Gamma";
116     info->type = F0R_PARAM_DOUBLE;
117     info->explanation = "Gamma";
118     break;
119   case PARAM_OUTPUT_MIN:
120     info->name = "Black output";
121     info->type = F0R_PARAM_DOUBLE;
122     info->explanation = "Black output";
123     break;
124   case PARAM_OUTPUT_MAX:
125     info->name = "White output";
126     info->type = F0R_PARAM_DOUBLE;
127     info->explanation = "White output";
128     break;
129   case PARAM_SHOW_HISTOGRAM:
130     info->name = "Show histogram";
131     info->type = F0R_PARAM_BOOL;
132     info->explanation = "Show histogram";
133     break;
134   case PARAM_HISTOGRAM_POS:
135     info->name = "Histogram position";
136     info->type = F0R_PARAM_DOUBLE;
137     info->explanation = "Histogram position. 0%=TL, 10%=TR, 20%=BL, 30%=BR";
138     break;
139   }
140 }
141 
f0r_construct(unsigned int width,unsigned int height)142 f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
143 {
144   levels_instance_t* inst = (levels_instance_t*)calloc(1, sizeof(*inst));
145   inst->width = width; inst->height = height;
146   inst->inputMin = 0;
147   inst->inputMax = 1;
148   inst->outputMin = 0;
149   inst->outputMax = 1;
150   inst->gamma = 1;
151   inst->channel = CHANNEL_LUMA;
152   inst->showHistogram = 1;
153   inst->histogramPosition = POS_BOTTOM_RIGHT;
154   return (f0r_instance_t)inst;
155 }
156 
f0r_destruct(f0r_instance_t instance)157 void f0r_destruct(f0r_instance_t instance)
158 {
159   free(instance);
160 }
161 
f0r_set_param_value(f0r_instance_t instance,f0r_param_t param,int param_index)162 void f0r_set_param_value(f0r_instance_t instance,
163                          f0r_param_t param, int param_index)
164 {
165   assert(instance);
166   levels_instance_t* inst = (levels_instance_t*)instance;
167 
168   switch(param_index)
169   {
170   case PARAM_CHANNEL:
171     inst->channel = (enum ChannelChoice)
172       CLAMP(floor(*((f0r_param_double *)param) * 10),
173             CHANNEL_RED, CHANNEL_LUMA);
174     break;
175   case PARAM_INPUT_MIN:
176     inst->inputMin =  *((f0r_param_double *)param);
177     break;
178   case PARAM_INPUT_MAX:
179     inst->inputMax =  *((f0r_param_double *)param);
180     break;
181   case PARAM_GAMMA:
182     inst->gamma = *((f0r_param_double *)param) * 4;
183     break;
184   case PARAM_OUTPUT_MIN:
185     inst->outputMin = *((f0r_param_double *)param);
186     break;
187   case PARAM_OUTPUT_MAX:
188     inst->outputMax = *((f0r_param_double *)param);
189     break;
190   case PARAM_SHOW_HISTOGRAM:
191     inst->showHistogram = *((f0r_param_bool *)param);
192     break;
193   case PARAM_HISTOGRAM_POS:
194     inst->histogramPosition = (enum HistogramPosChoice)
195       CLAMP(floor(*((f0r_param_double *)param) * 10),
196             POS_TOP_LEFT, POS_BOTTOM_RIGHT);
197     break;
198   }
199 }
200 
f0r_get_param_value(f0r_instance_t instance,f0r_param_t param,int param_index)201 void f0r_get_param_value(f0r_instance_t instance,
202                          f0r_param_t param, int param_index)
203 {
204   assert(instance);
205   levels_instance_t* inst = (levels_instance_t*)instance;
206 
207   switch(param_index)
208   {
209   case PARAM_CHANNEL:
210     *((f0r_param_double *)param) = inst->channel / 10.;
211     break;
212   case PARAM_INPUT_MIN:
213     *((f0r_param_double *)param) = inst->inputMin;
214     break;
215   case PARAM_INPUT_MAX:
216     *((f0r_param_double *)param) = inst->inputMax;
217     break;
218   case PARAM_GAMMA:
219     *((f0r_param_double *)param) = inst->gamma / 4;
220     break;
221   case PARAM_OUTPUT_MIN:
222     *((f0r_param_double *)param) = inst->outputMin;
223     break;
224   case PARAM_OUTPUT_MAX:
225     *((f0r_param_double *)param) = inst->outputMax;
226     break;
227   case PARAM_SHOW_HISTOGRAM:
228     *((f0r_param_bool *)param) = inst->showHistogram;
229     break;
230   case PARAM_HISTOGRAM_POS:
231     *((f0r_param_double *)param) = inst->histogramPosition / 10.;
232     break;
233   }
234 }
235 
f0r_update(f0r_instance_t instance,double time,const uint32_t * inframe,uint32_t * outframe)236 void f0r_update(f0r_instance_t instance, double time,
237                 const uint32_t* inframe, uint32_t* outframe)
238 {
239   assert(instance);
240   levels_instance_t* inst = (levels_instance_t*)instance;
241   unsigned int len = inst->width * inst->height;
242   unsigned int maxHisto = 0;
243 
244   unsigned char* dst = (unsigned char*)outframe;
245   const unsigned char* src = (unsigned char*)inframe;
246   int r, g, b;
247 
248   double levels[256];
249   unsigned int map[256];
250 
251   double inScale = inst->inputMax != inst->inputMin?inst->inputMax - inst->inputMin:1;
252   double exp = inst->gamma == 0?1:1/inst->gamma;
253   double outScale = inst->outputMax - inst->outputMin;
254 
255   for(int i = 0; i < 256; i++) {
256 	double v = i / 255. - inst->inputMin;
257 	if (v < 0.0) {
258 		v = 0.0;
259 	}
260 	double w = pow(v / inScale, exp) * outScale + inst->outputMin;
261 	map[i] = CLAMP0255(lrintf(w * 255.0));
262   }
263 
264   if (inst->showHistogram)
265 	for(int i = 0; i < 256; i++)
266 	  levels[i] = 0;
267 
268   while (len--)
269   {
270 	r = *src++;
271 	g = *src++;
272 	b = *src++;
273 
274 	if (inst->showHistogram) {
275 	  int intensity =
276 	    inst->channel == CHANNEL_RED?r:
277 	    inst->channel == CHANNEL_GREEN?g:
278 	    inst->channel == CHANNEL_BLUE?b:
279 	        CLAMP0255(b * .114 + g * .587 + r * .299);
280 	  int index = CLAMP0255(intensity);
281 	  levels[index]++;
282 	  if (levels[index] > maxHisto)
283 		maxHisto = levels[index];
284 	}
285 
286 	switch (inst->channel) {
287 	case CHANNEL_RED:
288 	  *dst++ = map[r];
289 	  *dst++ = g;
290 	  *dst++ = b;
291 	  break;
292 	case CHANNEL_GREEN:
293 	  *dst++ = r;
294 	  *dst++ = map[g];
295 	  *dst++ = b;
296 	  break;
297 	case CHANNEL_BLUE:
298 	  *dst++ = r;
299 	  *dst++ = g;
300 	  *dst++ = map[b];
301 	  break;
302 	case CHANNEL_LUMA:
303 	  *dst++ = map[r];
304 	  *dst++ = map[g];
305 	  *dst++ = map[b];
306 	  break;
307 	}
308 
309 	*dst++ = *src++;  // copy alpha
310   }
311   if (inst->showHistogram) {
312 	dst = (unsigned char *)outframe;
313 	src = (unsigned char *)inframe;
314 	int thirdY = inst->height / 3;
315 	int thirdX = inst->width / 3;
316 	int barHeight = inst->height / 27;
317 	int histoHeight = thirdY - barHeight * 3;
318 	int xOffset = 0;
319 	int yOffset = 0;
320 	if (inst->histogramPosition == POS_BOTTOM_RIGHT || inst->histogramPosition == POS_BOTTOM_LEFT)
321 	  yOffset += 2 * thirdY;
322 	if (inst->histogramPosition == POS_BOTTOM_RIGHT || inst->histogramPosition == POS_TOP_RIGHT)
323 	  xOffset += 2 * thirdX;
324 	for(int y = 0; y < histoHeight; y++) {
325 	  double pointValue = (double)(histoHeight - y) / histoHeight;
326 	  for(int x = 0; x < thirdX; x++) {
327 		int offset = ((y + yOffset) * inst->width + x + xOffset) * 4;
328 		int drawPoint = pointValue < (double)levels[CLAMP0255(x * 255 / thirdX)] / maxHisto;
329 		dst[offset] = drawPoint?(CHANNEL_RED != inst->channel || CHANNEL_LUMA == inst->channel?0:255):127 + src[offset]/2;
330 		dst[offset + 1] = drawPoint?(CHANNEL_GREEN != inst->channel || CHANNEL_LUMA == inst->channel?0:255):127 + src[offset + 1]/2;
331 		dst[offset + 2] = drawPoint?(CHANNEL_BLUE != inst->channel || CHANNEL_LUMA == inst->channel?0:255):127 + src[offset + 2]/2;
332 		dst[offset + 3] = src[offset + 3];
333 	  }
334 	}
335 	int posInMin = inst->inputMin * thirdX;
336 	int posInMax = inst->inputMax * thirdX;
337 	int posOutMin = inst->outputMin * thirdX;
338 	int posOutMax = inst->outputMax * thirdX;
339 	int posGamma = posInMin + (posInMax - posInMin) * pow(inst->gamma, .5) *.5;
340 	int color[3];
341 	color[0] = CHANNEL_RED == inst->channel || CHANNEL_LUMA == inst->channel?255:0;
342 	color[1] = CHANNEL_GREEN == inst->channel || CHANNEL_LUMA == inst->channel?255:0;
343 	color[2] = CHANNEL_BLUE == inst->channel || CHANNEL_LUMA == inst->channel?255:0;
344 	int midColor[3];
345 	midColor[0] = color[0]>>1;
346 	midColor[1] = color[1]>>1;
347 	midColor[2] = color[2]>>1;
348 	for(int y = histoHeight; y < thirdY - barHeight * 2; y++) {
349 	  int offsettedY = (y + yOffset) * inst->width;
350 	  int offsettedYlower = (y + yOffset + barHeight * 2) * inst->width;
351 	  for(int x = 0; x < thirdX; x++) {
352 		int offset = (offsettedY + x + xOffset) * 4;
353 		dst[offset] = 127 + dst[offset]/2;
354 		dst[offset + 1] = 127 + dst[offset + 1]/2;
355 		dst[offset + 2] = 127 + dst[offset + 2]/2;
356 		offset = (offsettedYlower + x + xOffset) * 4;
357 		dst[offset] = 127 + dst[offset]/2;
358 		dst[offset + 1] = 127 + dst[offset + 1]/2;
359 		dst[offset + 2] = 127 + dst[offset + 2]/2;
360 	  }
361 	  int delta = (y - histoHeight)/2;
362 
363 	  for(int x = -delta; x < delta; x++) {
364 		int xInMin = x + posInMin;
365 		int xInMax = x + posInMax;
366 		int xOutMin = x + posOutMin;
367 		int xOutMax = x + posOutMax;
368 		int xGamma = x + posGamma;
369 		if (xInMin >= 0 && xInMin < thirdX) {
370 		  int offset = (offsettedY + xInMin + xOffset) * 4;
371 		  dst[offset] = 0;
372 		  dst[offset + 1] = 0;
373 		  dst[offset + 2] = 0;
374 		}
375 		if (xInMax >= 0 && xInMax < thirdX) {
376 		  int offset = (offsettedY + xInMax + xOffset) * 4;
377 		  dst[offset] = color[0];
378 		  dst[offset + 1] = color[1];
379 		  dst[offset + 2] = color[2];
380 		}
381 		if (xGamma >= 0 && xGamma < thirdX) {
382 		  int offset = (offsettedY + xGamma + xOffset) * 4;
383 		  dst[offset] = midColor[0];
384 		  dst[offset + 1] = midColor[1];
385 		  dst[offset + 2] = midColor[2];
386 		}
387 		if (xOutMin >= 0 && xOutMin < thirdX) {
388 		  int offset = (offsettedYlower + xOutMin + xOffset) * 4;
389 		  dst[offset] = 0;
390 		  dst[offset + 1] = 0;
391 		  dst[offset + 2] = 0;
392 		}
393 		if (xOutMax >= 0 && xOutMax < thirdX) {
394 		  int offset = (offsettedYlower + xOutMax + xOffset) * 4;
395 		  dst[offset] = color[0];
396 		  dst[offset + 1] = color[1];
397 		  dst[offset + 2] = color[2];
398 		}
399 	  }
400 	}
401 	for(int y = thirdY - barHeight * 2; y < thirdY - barHeight; y++) {
402 	  for(int x = 0; x < thirdX; x++) {
403 		int offset = ((y + yOffset) * inst->width + x + xOffset) * 4;
404 		int pointValue = CLAMP0255(x * 255 / thirdX);
405 		dst[offset] = inst->channel == CHANNEL_RED || inst->channel == CHANNEL_LUMA?pointValue:0;
406 		dst[offset + 1] = inst->channel == CHANNEL_GREEN || inst->channel == CHANNEL_LUMA?pointValue:0;
407 		dst[offset + 2] = inst->channel == CHANNEL_BLUE || inst->channel == CHANNEL_LUMA?pointValue:0;
408 	  }
409 	}
410   }
411 }
412