1 /*
2     Copyright (C) 2003 Daniel Moreno <comac@comac.darktech.org>
3     Converted for use in transcode by Tilmann Bitterberg <transcode@tibit.org>
4     Converted hqdn3d -> denoise3d and also heavily optimised for transcode
5         by Erik Slagter <erik@oldconomy.org> (GPL) 2003
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 */
21 
22 #define MOD_NAME    "filter_denoise3d.so"
23 #define MOD_VERSION "v1.0.6 (2003-12-20)"
24 #define MOD_CAP     "High speed 3D Denoiser"
25 #define MOD_AUTHOR  "Daniel Moreno, A'rpi"
26 
27 #include "transcode.h"
28 #include "filter.h"
29 #include "libtc/libtc.h"
30 #include "libtc/optstr.h"
31 
32 #include <math.h>
33 
34 /*
35 	set tabstop=4 for best layout
36 
37 	Changelog
38 
39 	1.0.3	EMS	first public version
40 	1.0.4	EMS	added YUV422 support
41 	1.0.5	EMS	added RGB support
42 				large cleanup
43 				added arbitrary layout support
44 				denoising U&V (colour) planes now actually works
45 	1.0.6	EMS	fixed annoying typo
46 */
47 
48 #define MAX_PLANES 3
49 
50 #define DEFAULT_LUMA_SPATIAL 4.0
51 #define DEFAULT_CHROMA_SPATIAL 3.0
52 #define DEFAULT_LUMA_TEMPORAL 6.0
53 #define DEFAULT_CHROMA_TEMPORAL 4.0
54 
55 typedef enum { dn3d_yuv420p, dn3d_yuv422, dn3d_rgb } dn3d_fmt_t;
56 typedef enum { dn3d_planar, dn3d_packed } dn3d_basic_layout_t;
57 typedef enum { dn3d_luma, dn3d_chroma, dn3d_disabled } dn3d_plane_type_t;
58 
59 typedef enum
60 {
61 	dn3d_off_y420,	dn3d_off_u420,	dn3d_off_v420,
62 	dn3d_off_y422,	dn3d_off_u422,	dn3d_off_v422,
63 	dn3d_off_r,		dn3d_off_g,		dn3d_off_b
64 } dn3d_offset_t;
65 
66 typedef struct
67 {
68 	dn3d_plane_type_t	plane_type;
69 	dn3d_offset_t		offset;
70 	int					skip;
71 	int					scale_x;
72 	int					scale_y;
73 } dn3d_single_layout_t;
74 
75 typedef struct
76 {
77 	int						tc_fmt;
78 	dn3d_fmt_t				fmt;
79 	dn3d_basic_layout_t		layout_type;
80 	dn3d_single_layout_t	layout[MAX_PLANES];
81 } dn3d_layout_t;
82 
83 typedef struct
84 {
85 	vob_t *			vob;
86 	dn3d_layout_t	layout_data;
87 
88 	struct
89 	{
90 		double luma_spatial;
91 		double chroma_spatial;
92 		double luma_temporal;
93 		double chroma_temporal;
94 	} parameter;
95 
96 	int				coefficients[4][512];
97 	unsigned char *	lineant;
98 	unsigned char * previous;
99 	int				prefilter;
100 	int				enable_luma;
101 	int				enable_chroma;
102 
103 } dn3d_private_data_t;
104 
105 /* FIXME: this uses the filter ID as an index--the ID can grow
106  * arbitrarily large, so this needs to be fixed */
107 static dn3d_private_data_t dn3d_private_data[100];
108 
109 static const dn3d_layout_t dn3d_layout[] =
110 {
111 	{ CODEC_YUV,    dn3d_yuv420p, dn3d_planar, {{ dn3d_luma, dn3d_off_y420,  1, 1, 1 }, { dn3d_chroma, dn3d_off_u420,  1, 2, 2 }, { dn3d_chroma, dn3d_off_v420,  1, 2, 2 }}},
112 	{ CODEC_YUV422, dn3d_yuv422,  dn3d_planar, {{ dn3d_luma, dn3d_off_y422,  1, 1, 1 }, { dn3d_chroma, dn3d_off_u422,  1, 2, 1 }, { dn3d_chroma, dn3d_off_v422,  1, 2, 1 }}},
113 	{ CODEC_RGB,    dn3d_rgb,     dn3d_packed, {{ dn3d_luma, dn3d_off_r,     3, 1, 1 }, { dn3d_luma,   dn3d_off_g,     3, 1, 1 }, { dn3d_luma,   dn3d_off_b,     3, 1, 1 }}}
114 };
115 
116 #define LowPass(prev, curr, coef) (curr + coef[prev - curr])
117 
ABS(int i)118 static inline int ABS(int i)
119 {
120     return(((i) >= 0) ? i : (0 - i));
121 }
122 
deNoise(unsigned char * frame,unsigned char * frameprev,unsigned char * lineant,int w,int h,int * horizontal,int * vertical,int * temporal,int offset,int skip)123 static void deNoise(unsigned char * frame, unsigned char * frameprev, unsigned char * lineant,
124                     int w, int h,
125                     int * horizontal, int * vertical, int * temporal,
126 					int offset, int skip)
127 {
128     int x, y;
129     unsigned char pixelant;
130     unsigned char * lineantptr = lineant;
131 
132     horizontal += 256;
133     vertical   += 256;
134     temporal   += 256;
135 
136 	frame		+= offset;
137 	frameprev	+= offset;
138 
139     // First pixel has no left nor top neighbour, only previous frame
140 
141     *lineantptr = pixelant = *frame;
142     *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
143 	frame += skip;
144 	frameprev += skip;
145     lineantptr++;
146 
147     // First line has no top neighbour, only left one for each pixel and last frame
148 
149     for(x = 1; x < w; x++)
150     {
151         pixelant = LowPass(pixelant, *frame,  horizontal);
152         *lineantptr = pixelant;
153         *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
154 		frame += skip;
155 		frameprev += skip;
156 		lineantptr++;
157     }
158 
159     for (y = 1; y < h; y++)
160     {
161 		lineantptr = lineant;
162 
163         // First pixel on each line doesn't have previous pixel
164 
165         pixelant = *frame;
166         *lineantptr = LowPass(*lineantptr, pixelant, vertical);
167         *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
168 		frame += skip;
169 		frameprev += skip;
170 		lineantptr++;
171 
172         for (x = 1; x < w; x++)
173         {
174             // The rest is normal
175 
176             pixelant = LowPass(pixelant, *frame,  horizontal);
177             *lineantptr = LowPass(*lineantptr, pixelant, vertical);
178             *frame = *frameprev = LowPass(*(frameprev), *lineantptr, temporal);
179 			// *frame ^= 255; // debug
180 			frame += skip;
181 			frameprev += skip;
182 	    	lineantptr++;
183         }
184     }
185 }
186 
PrecalcCoefs(int * ct,double dist25)187 static void PrecalcCoefs(int * ct, double dist25)
188 {
189 	int i;
190 	double gamma, simil, c;
191 
192 	gamma = log(0.25) / log(1.0 - dist25 / 255.0);
193 
194 	for(i = -256; i <= 255; i++)
195 	{
196 		simil = 1.0 - (double)ABS(i) / 255.0;
197 		c = pow(simil, gamma) * (double)i;
198 		ct[256 + i] = (int)((c < 0) ? (c - 0.5) : (c + 0.5));
199 	}
200 }
201 
help_optstr(void)202 static void help_optstr(void)
203 {
204     tc_log_info(MOD_NAME, "(%s) help\n"
205 "* Overview\n"
206 "  This filter aims to reduce image noise producing\n"
207 "  smooth images and making still images really still\n"
208 "  (This should enhance compressibility).\n"
209 "* Options\n"
210 "   luma:            spatial luma strength (%f)\n"
211 "   chroma:          spatial chroma strength (%f)\n"
212 "   luma_strength:   temporal luma strength (%f)\n"
213 "   chroma_strength: temporal chroma strength (%f)\n"
214 "   pre:             run as a pre filter (0)\n"
215 		, MOD_CAP,
216 		DEFAULT_LUMA_SPATIAL,
217 		DEFAULT_CHROMA_SPATIAL,
218 		DEFAULT_LUMA_TEMPORAL,
219 		DEFAULT_CHROMA_TEMPORAL);
220 }
221 
tc_filter(frame_list_t * vframe_,char * options)222 int tc_filter(frame_list_t *vframe_, char * options)
223 {
224 	vframe_list_t *vframe = (vframe_list_t *)vframe_;
225 	int instance;
226 	int tag = vframe->tag;
227 	dn3d_private_data_t * pd;
228 
229 	if(tag & TC_AUDIO)
230 		return(0);
231 
232 	instance	= vframe->filter_id;
233 	pd			= &dn3d_private_data[instance];
234 
235 	if(tag & TC_FILTER_GET_CONFIG)
236 	{
237 		char buf[128];
238 		optstr_filter_desc(options, MOD_NAME, MOD_CAP, MOD_VERSION, MOD_AUTHOR, "VYMOE", "2");
239 
240 		tc_snprintf(buf, 128, "%f", DEFAULT_LUMA_SPATIAL);
241 		optstr_param(options, "luma", "spatial luma strength", "%f", buf, "0.0", "100.0" );
242 
243 		tc_snprintf(buf, 128, "%f", DEFAULT_CHROMA_SPATIAL);
244 		optstr_param(options, "chroma", "spatial chroma strength", "%f", buf, "0.0", "100.0" );
245 
246 		tc_snprintf(buf, 128, "%f", DEFAULT_LUMA_TEMPORAL);
247 		optstr_param(options, "luma_strength", "temporal luma strength", "%f", buf, "0.0", "100.0" );
248 
249 		tc_snprintf(buf, 128, "%f", DEFAULT_CHROMA_TEMPORAL);
250 		optstr_param(options, "chroma_strength", "temporal chroma strength", "%f", buf, "0.0", "100.0" );
251 
252 		tc_snprintf(buf, 128, "%d", dn3d_private_data[instance].prefilter);
253 		optstr_param(options, "pre", "run as a pre filter", "%d", buf, "0", "1" );
254 	}
255 
256 	if(tag & TC_FILTER_INIT)
257 	{
258 		int format_index, plane_index, found;
259 		const dn3d_layout_t * lp;
260 		size_t size;
261 
262 		if(!(pd->vob = tc_get_vob()))
263 			return(TC_IMPORT_ERROR);
264 
265 		pd->parameter.luma_spatial		= 0;
266 		pd->parameter.luma_temporal		= 0;
267 		pd->parameter.chroma_spatial	= 0;
268 		pd->parameter.chroma_temporal	= 0;
269 
270 		if(!options)
271 		{
272 			tc_log_error(MOD_NAME, "options not set!");
273 			return(TC_IMPORT_ERROR);
274 		}
275 
276 		if(optstr_lookup(options, "help"))
277 		{
278 			help_optstr();
279 			return(TC_IMPORT_ERROR);
280 	  	}
281 
282 		optstr_get(options, "luma",				"%lf",	&pd->parameter.luma_spatial);
283 		optstr_get(options, "luma_strength",	"%lf",	&pd->parameter.luma_temporal);
284 		optstr_get(options, "chroma",			"%lf",	&pd->parameter.chroma_spatial);
285 		optstr_get(options, "chroma_strength",	"%lf",	&pd->parameter.chroma_temporal);
286 		optstr_get(options, "pre",				"%d",	&dn3d_private_data[instance].prefilter);
287 
288 		if((pd->parameter.luma_spatial < 0) || (pd->parameter.luma_temporal < 0))
289 			pd->enable_luma = 0;
290 		else
291 		{
292 			pd->enable_luma = 1;
293 
294 			if(pd->parameter.luma_spatial == 0)
295 			{
296 				if(pd->parameter.luma_temporal == 0)
297 				{
298 					pd->parameter.luma_spatial	= DEFAULT_LUMA_SPATIAL;
299 					pd->parameter.luma_temporal	= DEFAULT_LUMA_TEMPORAL;
300 				}
301 				else
302 				{
303 					pd->parameter.luma_spatial = pd->parameter.luma_temporal * 3 / 2;
304 				}
305 			}
306 			else
307 			{
308 				if(pd->parameter.luma_temporal == 0)
309 				{
310 					pd->parameter.luma_temporal = pd->parameter.luma_spatial * 2 / 3;
311 				}
312 			}
313 		}
314 
315 		if((pd->parameter.chroma_spatial < 0) || (pd->parameter.chroma_temporal < 0))
316 			pd->enable_chroma = 0;
317 		else
318 		{
319 			pd->enable_chroma = 1;
320 
321 			if(pd->parameter.chroma_spatial == 0)
322 			{
323 				if(pd->parameter.chroma_temporal == 0)
324 				{
325 					pd->parameter.chroma_spatial	= DEFAULT_CHROMA_SPATIAL;
326 					pd->parameter.chroma_temporal	= DEFAULT_CHROMA_TEMPORAL;
327 				}
328 				else
329 				{
330 					pd->parameter.chroma_spatial = pd->parameter.chroma_temporal * 3 / 2;
331 				}
332 			}
333 			else
334 			{
335 				if(pd->parameter.chroma_temporal == 0)
336 				{
337 					pd->parameter.chroma_temporal = pd->parameter.chroma_spatial * 2 / 3;
338 				}
339 			}
340 		}
341 
342 		for(format_index = 0, found = 0; format_index < (sizeof(dn3d_layout) / sizeof(*dn3d_layout)); format_index++)
343 		{
344 			if(pd->vob->im_v_codec == dn3d_layout[format_index].tc_fmt)
345 			{
346 				found = 1;
347 				break;
348 			}
349 		}
350 
351 		if(!found)
352 		{
353 			tc_log_error(MOD_NAME, "This filter is only capable of YUV, YUV422 and RGB mode");
354 	  		return(TC_IMPORT_ERROR);
355 		}
356 
357 		lp = &dn3d_layout[format_index];
358 		pd->layout_data = *lp;
359 
360 		for(plane_index = 0; plane_index < MAX_PLANES; plane_index++)
361 		{
362 			if((pd->layout_data.layout[plane_index].plane_type == dn3d_luma) && !pd->enable_luma)
363 				pd->layout_data.layout[plane_index].plane_type = dn3d_disabled;
364 
365 			if((pd->layout_data.layout[plane_index].plane_type == dn3d_chroma) && !pd->enable_chroma)
366 				pd->layout_data.layout[plane_index].plane_type = dn3d_disabled;
367 		}
368 
369 		size = pd->vob->im_v_width * MAX_PLANES * sizeof(char) * 2;
370 
371 		pd->lineant = tc_zalloc(size);
372 		if(pd->lineant == NULL)
373 			tc_log_error(MOD_NAME, "Malloc failed");
374 
375 		size *= pd->vob->im_v_height * 2;
376 
377 		pd->previous = tc_zalloc(size);
378 		if(pd->previous == NULL)
379 			tc_log_error(MOD_NAME, "Malloc failed");
380 
381 		PrecalcCoefs(pd->coefficients[0], pd->parameter.luma_spatial);
382 		PrecalcCoefs(pd->coefficients[1], pd->parameter.luma_temporal);
383 		PrecalcCoefs(pd->coefficients[2], pd->parameter.chroma_spatial);
384 		PrecalcCoefs(pd->coefficients[3], pd->parameter.chroma_temporal);
385 
386 		if(verbose)
387 		{
388 			tc_log_info(MOD_NAME, "%s %s #%d", MOD_VERSION, MOD_CAP, instance);
389 			tc_log_info(MOD_NAME, "Settings luma (spatial): %.2f "
390                                   "luma_strength (temporal): %.2f "
391                                   "chroma (spatial): %.2f "
392                                   "chroma_strength (temporal): %.2f",
393 				        pd->parameter.luma_spatial,
394         				pd->parameter.luma_temporal,
395 		        		pd->parameter.chroma_spatial,
396 				        pd->parameter.chroma_temporal);
397 			tc_log_info(MOD_NAME, "luma enabled: %s, chroma enabled: %s",
398 		                pd->enable_luma ? "yes" : "no",
399                         pd->enable_chroma ? "yes" : "no");
400 		}
401 	}
402 
403 	if(((tag & TC_PRE_M_PROCESS  && pd->prefilter) || (tag & TC_POST_M_PROCESS && !pd->prefilter)) &&
404 		!(vframe->attributes & TC_FRAME_IS_SKIPPED))
405 	{
406 		int plane_index, coef[2];
407 		int offset = 0;
408 		const dn3d_single_layout_t * lp;
409 
410 		for(plane_index = 0; plane_index < MAX_PLANES; plane_index++)
411 		{
412 			lp = &pd->layout_data.layout[plane_index];
413 
414 			if(lp->plane_type != dn3d_disabled)
415 			{
416 				// if(plane_index != 2) // debug
417 				//	continue;
418 
419 				coef[0] = (lp->plane_type == dn3d_luma) ? 0 : 2;
420 				coef[1] = coef[0] + 1;
421 
422 				switch(lp->offset)
423 				{
424 					case(dn3d_off_r):		offset = 0; break;
425 					case(dn3d_off_g):		offset = 1; break;
426 					case(dn3d_off_b):		offset = 2; break;
427 
428 					case(dn3d_off_y420):	offset = vframe->v_width * vframe->v_height * 0 / 4; break;
429 					case(dn3d_off_u420):	offset = vframe->v_width * vframe->v_height * 4 / 4; break;
430 					case(dn3d_off_v420):	offset = vframe->v_width * vframe->v_height * 5 / 4; break;
431 
432 					case(dn3d_off_y422):	offset = vframe->v_width * vframe->v_height * 0 / 2; break;
433 					case(dn3d_off_u422):	offset = vframe->v_width * vframe->v_height * 2 / 2; break;
434 					case(dn3d_off_v422):	offset = vframe->v_width * vframe->v_height * 3 / 2; break;
435 
436 				}
437 
438 				deNoise(vframe->video_buf,			// frame
439 					pd->previous,					// previous (saved) frame
440 					pd->lineant,					// line buffer
441 					vframe->v_width / lp->scale_x,	// width (pixels)
442 					vframe->v_height / lp->scale_y,	// height (pixels) // debug
443 					pd->coefficients[coef[0]],		// horizontal (spatial) strength
444 					pd->coefficients[coef[0]],		// vertical (spatial) strength
445 					pd->coefficients[coef[1]],		// temporal strength
446 					offset,							// offset in bytes of first relevant pixel in frame
447 					lp->skip						// skip this amount of bytes between two pixels
448 				);
449 			}
450 		}
451 	}
452 
453 	if(tag & TC_FILTER_CLOSE)
454 	{
455 		if(pd->previous)
456 		{
457 			free(pd->previous);
458 			pd->previous = 0;
459 		}
460 
461 		if(pd->lineant)
462 		{
463 			free(pd->lineant);
464 			pd->lineant = 0;
465 		}
466 	}
467 
468 	return(0);
469 }
470