1 /*****************************************************************************
2 * panoramix.c : Wall panoramic video with edge blending plugin for vlc
3 *****************************************************************************
4 * Copyright (C) 2000, 2001, 2002, 2003 VideoLAN
7 * Authors: Cedric Cocquebert <cedric.cocquebert@supelec.fr>
8 * based on Samuel Hocevar <sam@zoy.org>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
35 #include <vlc_common.h>
36 #include <vlc_plugin.h>
37 #include <vlc_video_splitter.h>
39 /* FIXME it is needed for VOUT_ALIGN_* only */
45 /* OS CODE DEPENDENT to get display dimensions */
50 # include <xcb/randr.h>
57 /*****************************************************************************
59 *****************************************************************************/
60 #define COLS_TEXT N_("Number of columns")
61 #define COLS_LONGTEXT N_("Select the number of horizontal video windows in " \
62 "which to split the video")
64 #define ROWS_TEXT N_("Number of rows")
65 #define ROWS_LONGTEXT N_("Select the number of vertical video windows in " \
66 "which to split the video")
68 #define ACTIVE_TEXT N_("Active windows")
69 #define ACTIVE_LONGTEXT N_("Comma-separated list of active windows, " \
72 #define CFG_PREFIX "panoramix-"
74 #define PANORAMIX_HELP N_("Split the video in multiple windows to " \
75 "display on a wall of screens")
77 static int Open ( vlc_object_t
* );
78 static void Close( vlc_object_t
* );
81 set_description( N_("Panoramix: wall with overlap video filter") )
82 set_shortname( N_("Panoramix" ))
83 set_help(PANORAMIX_HELP
)
84 set_capability( "video splitter", 0 )
85 set_category( CAT_VIDEO
)
86 set_subcategory( SUBCAT_VIDEO_SPLITTER
)
88 add_integer( CFG_PREFIX
"cols", -1, COLS_TEXT
, COLS_LONGTEXT
, true )
89 change_integer_range( -1, COL_MAX
)
90 add_integer( CFG_PREFIX
"rows", -1, ROWS_TEXT
, ROWS_LONGTEXT
, true )
91 change_integer_range( -1, ROW_MAX
)
94 #define LENGTH_TEXT N_("length of the overlapping area (in %)")
95 #define LENGTH_LONGTEXT N_("Select in percent the length of the blended zone")
96 add_integer_with_range( CFG_PREFIX
"bz-length", 100, 0, 100, LENGTH_TEXT
, LENGTH_LONGTEXT
, true )
98 #define HEIGHT_TEXT N_("height of the overlapping area (in %)")
99 #define HEIGHT_LONGTEXT N_("Select in percent the height of the blended zone (case of 2x2 wall)")
100 add_integer_with_range( CFG_PREFIX
"bz-height", 100, 0, 100, HEIGHT_TEXT
, HEIGHT_LONGTEXT
, true )
102 #define ATTENUATION_TEXT N_("Attenuation")
103 #define ATTENUATION_LONGTEXT N_("Check this option if you want attenuate blended zone by this plug-in (if option is unchecked, attenuate is made by opengl)")
104 add_bool( CFG_PREFIX
"attenuate", true, ATTENUATION_TEXT
, ATTENUATION_LONGTEXT
, false )
106 #define BEGIN_TEXT N_("Attenuation, begin (in %)")
107 #define BEGIN_LONGTEXT N_("Select in percent the Lagrange coefficient of the beginning blended zone")
108 add_integer_with_range( CFG_PREFIX
"bz-begin", 0, 0, 100, BEGIN_TEXT
, BEGIN_LONGTEXT
, true )
110 #define MIDDLE_TEXT N_("Attenuation, middle (in %)")
111 #define MIDDLE_LONGTEXT N_("Select in percent the Lagrange coefficient of the middle of blended zone")
112 add_integer_with_range( CFG_PREFIX
"bz-middle", 50, 0, 100, MIDDLE_TEXT
, MIDDLE_LONGTEXT
, false )
114 #define END_TEXT N_("Attenuation, end (in %)")
115 #define END_LONGTEXT N_("Select in percent the Lagrange coefficient of the end of blended zone")
116 add_integer_with_range( CFG_PREFIX
"bz-end", 100, 0, 100, END_TEXT
, END_LONGTEXT
, true )
118 #define MIDDLE_POS_TEXT N_("middle position (in %)")
119 #define MIDDLE_POS_LONGTEXT N_("Select in percent (50 is center) the position of the middle point (Lagrange) of blended zone")
120 add_integer_with_range( CFG_PREFIX
"bz-middle-pos", 50, 1, 99, MIDDLE_POS_TEXT
, MIDDLE_POS_LONGTEXT
, false )
121 #define RGAMMA_TEXT N_("Gamma (Red) correction")
122 #define RGAMMA_LONGTEXT N_("Select the gamma for the correction of blended zone (Red or Y component)")
123 add_float_with_range( CFG_PREFIX
"bz-gamma-red", 1, 0, 5, RGAMMA_TEXT
, RGAMMA_LONGTEXT
, true )
125 #define GGAMMA_TEXT N_("Gamma (Green) correction")
126 #define GGAMMA_LONGTEXT N_("Select the gamma for the correction of blended zone (Green or U component)")
127 add_float_with_range( CFG_PREFIX
"bz-gamma-green", 1, 0, 5, GGAMMA_TEXT
, GGAMMA_LONGTEXT
, true )
129 #define BGAMMA_TEXT N_("Gamma (Blue) correction")
130 #define BGAMMA_LONGTEXT N_("Select the gamma for the correction of blended zone (Blue or V component)")
131 add_float_with_range( CFG_PREFIX
"bz-gamma-blue", 1, 0, 5, BGAMMA_TEXT
, BGAMMA_LONGTEXT
, true )
133 #define RGAMMA_BC_TEXT N_("Black Crush for Red")
134 #define RGAMMA_BC_LONGTEXT N_("Select the Black Crush of blended zone (Red or Y component)")
135 #define GGAMMA_BC_TEXT N_("Black Crush for Green")
136 #define GGAMMA_BC_LONGTEXT N_("Select the Black Crush of blended zone (Green or U component)")
137 #define BGAMMA_BC_TEXT N_("Black Crush for Blue")
138 #define BGAMMA_BC_LONGTEXT N_("Select the Black Crush of blended zone (Blue or V component)")
140 #define RGAMMA_WC_TEXT N_("White Crush for Red")
141 #define RGAMMA_WC_LONGTEXT N_("Select the White Crush of blended zone (Red or Y component)")
142 #define GGAMMA_WC_TEXT N_("White Crush for Green")
143 #define GGAMMA_WC_LONGTEXT N_("Select the White Crush of blended zone (Green or U component)")
144 #define BGAMMA_WC_TEXT N_("White Crush for Blue")
145 #define BGAMMA_WC_LONGTEXT N_("Select the White Crush of blended zone (Blue or V component)")
147 #define RGAMMA_BL_TEXT N_("Black Level for Red")
148 #define RGAMMA_BL_LONGTEXT N_("Select the Black Level of blended zone (Red or Y component)")
149 #define GGAMMA_BL_TEXT N_("Black Level for Green")
150 #define GGAMMA_BL_LONGTEXT N_("Select the Black Level of blended zone (Green or U component)")
151 #define BGAMMA_BL_TEXT N_("Black Level for Blue")
152 #define BGAMMA_BL_LONGTEXT N_("Select the Black Level of blended zone (Blue or V component)")
154 #define RGAMMA_WL_TEXT N_("White Level for Red")
155 #define RGAMMA_WL_LONGTEXT N_("Select the White Level of blended zone (Red or Y component)")
156 #define GGAMMA_WL_TEXT N_("White Level for Green")
157 #define GGAMMA_WL_LONGTEXT N_("Select the White Level of blended zone (Green or U component)")
158 #define BGAMMA_WL_TEXT N_("White Level for Blue")
159 #define BGAMMA_WL_LONGTEXT N_("Select the White Level of blended zone (Blue or V component)")
160 add_integer_with_range( CFG_PREFIX
"bz-blackcrush-red", 140, 0, 255, RGAMMA_BC_TEXT
, RGAMMA_BC_LONGTEXT
, true )
161 add_integer_with_range( CFG_PREFIX
"bz-blackcrush-green", 140, 0, 255, GGAMMA_BC_TEXT
, GGAMMA_BC_LONGTEXT
, true )
162 add_integer_with_range( CFG_PREFIX
"bz-blackcrush-blue", 140, 0, 255, BGAMMA_BC_TEXT
, BGAMMA_BC_LONGTEXT
, true )
163 add_integer_with_range( CFG_PREFIX
"bz-whitecrush-red", 200, 0, 255, RGAMMA_WC_TEXT
, RGAMMA_WC_LONGTEXT
, true )
164 add_integer_with_range( CFG_PREFIX
"bz-whitecrush-green", 200, 0, 255, GGAMMA_WC_TEXT
, GGAMMA_WC_LONGTEXT
, true )
165 add_integer_with_range( CFG_PREFIX
"bz-whitecrush-blue", 200, 0, 255, BGAMMA_WC_TEXT
, BGAMMA_WC_LONGTEXT
, true )
166 add_integer_with_range( CFG_PREFIX
"bz-blacklevel-red", 150, 0, 255, RGAMMA_BL_TEXT
, RGAMMA_BL_LONGTEXT
, true )
167 add_integer_with_range( CFG_PREFIX
"bz-blacklevel-green", 150, 0, 255, GGAMMA_BL_TEXT
, GGAMMA_BL_LONGTEXT
, true )
168 add_integer_with_range( CFG_PREFIX
"bz-blacklevel-blue", 150, 0, 255, BGAMMA_BL_TEXT
, BGAMMA_BL_LONGTEXT
, true )
169 add_integer_with_range( CFG_PREFIX
"bz-whitelevel-red", 0, 0, 255, RGAMMA_WL_TEXT
, RGAMMA_WL_LONGTEXT
, true )
170 add_integer_with_range( CFG_PREFIX
"bz-whitelevel-green", 0, 0, 255, GGAMMA_WL_TEXT
, GGAMMA_WL_LONGTEXT
, true )
171 add_integer_with_range( CFG_PREFIX
"bz-whitelevel-blue", 0, 0, 255, BGAMMA_WL_TEXT
, BGAMMA_WL_LONGTEXT
, true )
173 add_obsolete_bool( CFG_PREFIX
"xinerama" );
175 add_obsolete_bool( CFG_PREFIX
"offset-x" )
178 add_string( CFG_PREFIX
"active", NULL
, ACTIVE_TEXT
, ACTIVE_LONGTEXT
, true )
180 add_shortcut( "panoramix" )
181 set_callbacks( Open
, Close
)
185 /*****************************************************************************
187 *****************************************************************************/
188 static const char *const ppsz_filter_options
[] = {
189 "cols", "rows", "bz-length", "bz-height", "attenuate",
190 "bz-begin", "bz-middle", "bz-end", "bz-middle-pos", "bz-gamma-red",
191 "bz-gamma-green", "bz-gamma-blue", "bz-blackcrush-red",
192 "bz-blackcrush-green", "bz-blackcrush-blue", "bz-whitecrush-red",
193 "bz-whitecrush-green", "bz-whitecrush-blue", "bz-blacklevel-red",
194 "bz-blacklevel-green", "bz-blacklevel-blue", "bz-whitelevel-red",
195 "bz-whitelevel-green", "bz-whitelevel-blue", "active",
199 #define ACCURACY 1000
202 static inline int clip_accuracy( int a
)
204 return (a
> ACCURACY
) ? ACCURACY
: (a
< 0) ? 0 : a
;
206 static inline float clip_unit( float f
)
208 return f
< 0.0 ? 0.0 : ( f
> 1.0 ? 1.0 : f
);
239 } panoramix_filter_t
;
246 /* Output position and size */
253 /* Source position and size */
259 /* Filter configuration to use to create the output */
260 panoramix_filter_t filter
;
262 } panoramix_output_t
;
266 vlc_fourcc_t i_chroma
;
268 const int pi_div_w
[VOUT_MAX_PLANES
];
269 const int pi_div_h
[VOUT_MAX_PLANES
];
271 const int pi_black
[VOUT_MAX_PLANES
];
275 } panoramix_chroma_t
;
277 struct video_splitter_sys_t
279 const panoramix_chroma_t
*p_chroma
;
283 unsigned int bz_length
, bz_height
;
284 unsigned int bz_begin
, bz_middle
, bz_end
;
285 unsigned int bz_middle_pos
;
286 unsigned int a_0
, a_1
, a_2
;
288 int lambdav
[VOUT_MAX_PLANES
][2][ACCURACY
/2]; /* [plane][position][?] */
289 int lambdah
[VOUT_MAX_PLANES
][2][ACCURACY
/2];
291 unsigned int i_overlap_w2
; /* Half overlap width */
292 unsigned int i_overlap_h2
; /* Half overlap height */
293 uint8_t p_lut
[VOUT_MAX_PLANES
][ACCURACY
+ 1][256];
298 panoramix_output_t pp_output
[COL_MAX
][ROW_MAX
]; /* [x][y] */
302 static int Filter( video_splitter_t
*, picture_t
*pp_dst
[], picture_t
* );
304 static int Mouse( video_splitter_t
*, vlc_mouse_t
*,
306 const vlc_mouse_t
*p_old
, const vlc_mouse_t
*p_new
);
310 static int Configuration( panoramix_output_t pp_output
[ROW_MAX
][COL_MAX
],
311 int i_col
, int i_row
,
312 int i_src_width
, int i_src_height
,
313 int i_half_w
, int i_half_h
,
315 const bool *pb_active
);
316 static double GammaFactor( const panoramix_gamma_t
*, float f_value
);
318 static void FilterPlanar( uint8_t *p_out
, int i_out_pitch
,
319 const uint8_t *p_in
, int i_in_pitch
,
323 const panoramix_filter_t
*,
324 uint8_t p_lut
[ACCURACY
+ 1][256],
325 int lambdav
[2][ACCURACY
/2],
326 int lambdah
[2][ACCURACY
/2] );
329 static const panoramix_chroma_t p_chroma_array
[] = {
331 { VLC_CODEC_I410
, { 1, 4, 4, }, { 1, 1, 1, }, { 0, 128, 128 }, true },
332 { VLC_CODEC_I411
, { 1, 4, 4, }, { 1, 4, 4, }, { 0, 128, 128 }, true },
333 { VLC_CODEC_YV12
, { 1, 2, 2, }, { 1, 2, 2, }, { 0, 128, 128 }, true },
334 { VLC_CODEC_I420
, { 1, 2, 2, }, { 1, 2, 2, }, { 0, 128, 128 }, true },
335 { VLC_CODEC_J420
, { 1, 2, 2, }, { 1, 2, 2, }, { 0, 128, 128 }, true },
336 { VLC_CODEC_I422
, { 1, 2, 2, }, { 1, 1, 1, }, { 0, 128, 128 }, true },
337 { VLC_CODEC_J422
, { 1, 2, 2, }, { 1, 1, 1, }, { 0, 128, 128 }, true },
338 { VLC_CODEC_I440
, { 1, 1, 1, }, { 1, 2, 2, }, { 0, 128, 128 }, true },
339 { VLC_CODEC_J440
, { 1, 1, 1, }, { 1, 2, 2, }, { 0, 128, 128 }, true },
340 { VLC_CODEC_I444
, { 1, 1, 1, }, { 1, 1, 1, }, { 0, 128, 128 }, true },
341 /* TODO packed chroma (yuv/rgb) ? */
343 { 0, {0, }, { 0, }, { 0, 0, 0 }, false }
347 /* Get the number of outputs */
348 static unsigned CountMonitors( vlc_object_t
*obj
)
350 char *psz_display
= var_InheritString( obj
, "x11-display" );
352 xcb_connection_t
*conn
= xcb_connect( psz_display
, &snum
);
354 if( xcb_connection_has_error( conn
) )
357 const xcb_setup_t
*setup
= xcb_get_setup( conn
);
358 xcb_screen_t
*scr
= NULL
;
359 for( xcb_screen_iterator_t i
= xcb_setup_roots_iterator( setup
);
360 i
.rem
> 0; xcb_screen_next( &i
) )
374 xcb_randr_query_version_reply_t
*v
=
375 xcb_randr_query_version_reply( conn
,
376 xcb_randr_query_version( conn
, 1, 2 ), NULL
);
379 msg_Dbg( obj
, "using X RandR extension v%"PRIu32
".%"PRIu32
,
380 v
->major_version
, v
->minor_version
);
383 xcb_randr_get_screen_resources_reply_t
*r
=
384 xcb_randr_get_screen_resources_reply( conn
,
385 xcb_randr_get_screen_resources( conn
, scr
->root
), NULL
);
389 const xcb_randr_output_t
*outputs
=
390 xcb_randr_get_screen_resources_outputs( r
);
391 for( unsigned i
= 0; i
< r
->num_outputs
; i
++ )
393 xcb_randr_get_output_info_reply_t
*output
=
394 xcb_randr_get_output_info_reply( conn
,
395 xcb_randr_get_output_info( conn
, outputs
[i
], 0 ), NULL
);
398 /* FIXME: do not count cloned outputs multiple times */
399 /* XXX: what to do with UNKNOWN state connections? */
400 n
+= output
->connection
== XCB_RANDR_CONNECTION_CONNECTED
;
404 msg_Dbg( obj
, "X randr has %u outputs", n
);
407 xcb_disconnect( conn
);
412 /*****************************************************************************
413 * Open: allocates Wall video thread output method
414 *****************************************************************************
415 * This function allocates and initializes a Wall vout method.
416 *****************************************************************************/
417 static int Open( vlc_object_t
*p_this
)
419 video_splitter_t
*p_splitter
= (video_splitter_t
*)p_this
;
420 video_splitter_sys_t
*p_sys
;
422 const panoramix_chroma_t
*p_chroma
;
423 for( int i
= 0; ; i
++ )
425 vlc_fourcc_t i_chroma
= p_chroma_array
[i
].i_chroma
;
428 msg_Err( p_splitter
, "colorspace not supported by plug-in !" );
431 if( i_chroma
== p_splitter
->fmt
.i_chroma
)
433 p_chroma
= &p_chroma_array
[i
];
438 /* Allocate structure */
439 p_splitter
->p_sys
= p_sys
= malloc( sizeof( *p_sys
) );
444 p_sys
->p_chroma
= p_chroma
;
447 config_ChainParse( p_splitter
, CFG_PREFIX
, ppsz_filter_options
,
451 p_sys
->i_col
= var_InheritInteger( p_splitter
, CFG_PREFIX
"cols" );
452 p_sys
->i_row
= var_InheritInteger( p_splitter
, CFG_PREFIX
"rows" );
454 /* Autodetect number of displays */
455 if( p_sys
->i_col
< 0 || p_sys
->i_row
< 0 )
458 const int i_monitor_count
= GetSystemMetrics(SM_CMONITORS
);
459 if( i_monitor_count
> 1 )
461 p_sys
->i_col
= GetSystemMetrics( SM_CXVIRTUALSCREEN
) / GetSystemMetrics( SM_CXSCREEN
);
462 p_sys
->i_row
= GetSystemMetrics( SM_CYVIRTUALSCREEN
) / GetSystemMetrics( SM_CYSCREEN
);
463 if( p_sys
->i_col
* p_sys
->i_row
!= i_monitor_count
)
465 p_sys
->i_col
= i_monitor_count
;
470 const unsigned i_monitors
= CountMonitors( p_this
);
471 if( i_monitors
> 1 ) /* Find closest to square */
472 for( unsigned w
= 1; (i_monitors
/ w
) >= w
; w
++ )
477 p_sys
->i_col
= i_monitors
/ w
;
480 /* By default do 2x1 */
481 if( p_sys
->i_row
< 0 )
483 if( p_sys
->i_col
< 0 )
485 var_SetInteger( p_splitter
, CFG_PREFIX
"cols", p_sys
->i_col
);
486 var_SetInteger( p_splitter
, CFG_PREFIX
"rows", p_sys
->i_row
);
490 p_sys
->b_attenuate
= var_InheritBool( p_splitter
, CFG_PREFIX
"attenuate");
491 p_sys
->bz_length
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-length" );
492 p_sys
->bz_height
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-height" );
493 p_sys
->bz_begin
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-begin" );
494 p_sys
->bz_middle
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-middle" );
495 p_sys
->bz_end
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-end" );
496 p_sys
->bz_middle_pos
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-middle-pos" );
497 double d_p
= 100.0 / p_sys
->bz_middle_pos
;
499 p_sys
->a_2
= d_p
* p_sys
->bz_begin
- (double)(d_p
* d_p
/ (d_p
- 1)) * p_sys
->bz_middle
+ (double)(d_p
/ (d_p
- 1)) * p_sys
->bz_end
;
500 p_sys
->a_1
= -(d_p
+ 1) * p_sys
->bz_begin
+ (double)(d_p
* d_p
/ (d_p
- 1)) * p_sys
->bz_middle
- (double)(1 / (d_p
- 1)) * p_sys
->bz_end
;
501 p_sys
->a_0
= p_sys
->bz_begin
;
504 p_sys
->i_col
= VLC_CLIP( p_sys
->i_col
, 1, COL_MAX
);
505 p_sys
->i_row
= VLC_CLIP( p_sys
->i_row
, 1, ROW_MAX
);
506 msg_Dbg( p_splitter
, "opening a %i x %i wall",
507 p_sys
->i_col
, p_sys
->i_row
);
509 if( p_sys
->bz_length
> 0 && ( p_sys
->i_row
> 1 || p_sys
->i_col
> 1 ) )
511 const int i_overlap_w2_max
= p_splitter
->fmt
.i_width
/ p_sys
->i_col
/ 2;
512 const int i_overlap_h2_max
= p_splitter
->fmt
.i_height
/ p_sys
->i_row
/ 2;
513 const int i_overlap2_max
= __MIN( i_overlap_w2_max
, i_overlap_h2_max
);
515 if( p_sys
->i_col
> 1 )
516 p_sys
->i_overlap_w2
= i_overlap2_max
* p_sys
->bz_length
/ 100;
518 p_sys
->i_overlap_w2
= 0;
520 if( p_sys
->i_row
> 1 )
521 p_sys
->i_overlap_h2
= i_overlap2_max
* p_sys
->bz_height
/ 100;
523 p_sys
->i_overlap_h2
= 0;
528 for( int i
= 0; i
< VOUT_MAX_PLANES
; i
++ )
530 i_div_max_w
= __MAX( i_div_max_w
, p_chroma
->pi_div_w
[i
] );
531 i_div_max_h
= __MAX( i_div_max_h
, p_chroma
->pi_div_h
[i
] );
533 p_sys
->i_overlap_w2
= i_div_max_w
* (p_sys
->i_overlap_w2
/ i_div_max_w
);
534 p_sys
->i_overlap_h2
= i_div_max_h
* (p_sys
->i_overlap_h2
/ i_div_max_h
);
538 p_sys
->i_overlap_w2
= 0;
539 p_sys
->i_overlap_h2
= 0;
542 /* Compute attenuate parameters */
543 if( p_sys
->b_attenuate
)
545 panoramix_gamma_t p_gamma
[VOUT_MAX_PLANES
];
547 p_gamma
[0].f_gamma
= var_InheritFloat( p_splitter
, CFG_PREFIX
"bz-gamma-red" );
548 p_gamma
[1].f_gamma
= var_InheritFloat( p_splitter
, CFG_PREFIX
"bz-gamma-green" );
549 p_gamma
[2].f_gamma
= var_InheritFloat( p_splitter
, CFG_PREFIX
"bz-gamma-blue" );
551 p_gamma
[0].f_black_crush
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-blackcrush-red" ) / 255.0;
552 p_gamma
[1].f_black_crush
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-blackcrush-green" ) / 255.0;
553 p_gamma
[2].f_black_crush
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-blackcrush-blue" ) / 255.0;
554 p_gamma
[0].f_white_crush
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-whitecrush-red" ) / 255.0;
555 p_gamma
[1].f_white_crush
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-whitecrush-green" ) / 255.0;
556 p_gamma
[2].f_white_crush
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-whitecrush-blue" ) / 255.0;
558 p_gamma
[0].f_black_level
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-blacklevel-red" ) / 255.0;
559 p_gamma
[1].f_black_level
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-blacklevel-green" ) / 255.0;
560 p_gamma
[2].f_black_level
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-blacklevel-blue" ) / 255.0;
561 p_gamma
[0].f_white_level
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-whitelevel-red" ) / 255.0;
562 p_gamma
[1].f_white_level
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-whitelevel-green" ) / 255.0;
563 p_gamma
[2].f_white_level
= var_InheritInteger( p_splitter
, CFG_PREFIX
"bz-whitelevel-blue" ) / 255.0;
565 for( int i
= 3; i
< VOUT_MAX_PLANES
; i
++ )
567 /* Initialize unsupported planes */
568 p_gamma
[i
].f_gamma
= 1.0;
569 p_gamma
[i
].f_black_crush
= 140.0/255.0;
570 p_gamma
[i
].f_white_crush
= 200.0/255.0;
571 p_gamma
[i
].f_black_level
= 150.0/255.0;
572 p_gamma
[i
].f_white_level
= 0.0/255.0;
575 if( p_chroma
->i_chroma
== VLC_CODEC_YV12
)
577 /* Exchange U and V */
578 panoramix_gamma_t t
= p_gamma
[1];
579 p_gamma
[1] = p_gamma
[2];
583 for( int i_index
= 0; i_index
< 256; i_index
++ )
585 for( int i_index2
= 0; i_index2
<= ACCURACY
; i_index2
++ )
587 for( int i_plane
= 0; i_plane
< VOUT_MAX_PLANES
; i_plane
++ )
589 double f_factor
= GammaFactor( &p_gamma
[i_plane
], (float)i_index
/ 255.0 );
591 float f_lut
= clip_unit( 1.0 - ((ACCURACY
- (float)i_index2
) * f_factor
/ (ACCURACY
- 1)) );
593 p_sys
->p_lut
[i_plane
][i_index2
][i_index
] = f_lut
* i_index
+ (int)( (1.0 - f_lut
) * (float)p_chroma
->pi_black
[i_plane
] );
598 for( int i_plane
= 0; i_plane
< VOUT_MAX_PLANES
; i_plane
++ )
600 if( !p_chroma
->pi_div_w
[i_plane
] || !p_chroma
->pi_div_h
[i_plane
] )
602 const int i_length_w
= (2 * p_sys
->i_overlap_w2
) / p_chroma
->pi_div_w
[i_plane
];
603 const int i_length_h
= (2 * p_sys
->i_overlap_h2
) / p_chroma
->pi_div_h
[i_plane
];
605 for( int i_dir
= 0; i_dir
< 2; i_dir
++ )
607 const int i_length
= i_dir
== 0 ? i_length_w
: i_length_h
;
608 const int i_den
= i_length
* i_length
;
609 const int a_2
= p_sys
->a_2
* (ACCURACY
/ 100);
610 const int a_1
= p_sys
->a_1
* i_length
* (ACCURACY
/ 100);
611 const int a_0
= p_sys
->a_0
* i_den
* (ACCURACY
/ 100);
613 for( int i_position
= 0; i_position
< 2; i_position
++ )
615 for( int i_index
= 0; i_index
< i_length
; i_index
++ )
617 const int v
= i_position
== 1 ? i_index
: (i_length
- i_index
);
618 const int i_lambda
= clip_accuracy( ACCURACY
- (a_2
* v
*v
+ a_1
* v
+ a_0
) / i_den
);
621 p_sys
->lambdav
[i_plane
][i_position
][i_index
] = i_lambda
;
623 p_sys
->lambdah
[i_plane
][i_position
][i_index
] = i_lambda
;
631 char *psz_state
= var_InheritString( p_splitter
, CFG_PREFIX
"active" );
634 bool pb_active
[COL_MAX
*ROW_MAX
];
636 for( int i
= 0; i
< COL_MAX
*ROW_MAX
; i
++ )
637 pb_active
[i
] = psz_state
== NULL
;
639 /* Parse active list if provided */
640 char *psz_tmp
= psz_state
;
641 while( psz_tmp
&& *psz_tmp
)
643 char *psz_next
= strchr( psz_tmp
, ',' );
647 const int i_index
= atoi( psz_tmp
);
648 if( i_index
>= 0 && i_index
< COL_MAX
*ROW_MAX
)
649 pb_active
[i_index
] = true;
656 p_splitter
->i_output
=
657 Configuration( p_sys
->pp_output
,
658 p_sys
->i_col
, p_sys
->i_row
,
659 p_splitter
->fmt
.i_width
, p_splitter
->fmt
.i_height
,
660 p_sys
->i_overlap_w2
, p_sys
->i_overlap_h2
,
663 p_splitter
->p_output
= calloc( p_splitter
->i_output
,
664 sizeof(*p_splitter
->p_output
) );
665 if( !p_splitter
->p_output
)
671 for( int y
= 0; y
< p_sys
->i_row
; y
++ )
673 for( int x
= 0; x
< p_sys
->i_col
; x
++ )
675 panoramix_output_t
*p_output
= &p_sys
->pp_output
[x
][y
];
676 if( !p_output
->b_active
)
679 video_splitter_output_t
*p_cfg
= &p_splitter
->p_output
[p_output
->i_output
];
682 video_format_Copy( &p_cfg
->fmt
, &p_splitter
->fmt
);
683 p_cfg
->fmt
.i_visible_width
=
684 p_cfg
->fmt
.i_width
= p_output
->i_width
;
685 p_cfg
->fmt
.i_visible_height
=
686 p_cfg
->fmt
.i_height
= p_output
->i_height
;
688 p_cfg
->window
.i_x
= p_output
->i_x
;
689 p_cfg
->window
.i_y
= p_output
->i_y
;
690 p_cfg
->window
.i_align
= p_output
->i_align
;
692 p_cfg
->psz_module
= NULL
;
698 p_splitter
->pf_filter
= Filter
;
699 p_splitter
->pf_mouse
= Mouse
;
705 * Terminate a splitter module
707 static void Close( vlc_object_t
*p_this
)
709 video_splitter_t
*p_splitter
= (video_splitter_t
*)p_this
;
710 video_splitter_sys_t
*p_sys
= p_splitter
->p_sys
;
712 free( p_splitter
->p_output
);
717 * It creates multiples pictures from the source one
719 static int Filter( video_splitter_t
*p_splitter
, picture_t
*pp_dst
[], picture_t
*p_src
)
721 video_splitter_sys_t
*p_sys
= p_splitter
->p_sys
;
723 if( video_splitter_NewPicture( p_splitter
, pp_dst
) )
725 picture_Release( p_src
);
729 for( int y
= 0; y
< p_sys
->i_row
; y
++ )
731 for( int x
= 0; x
< p_sys
->i_col
; x
++ )
733 const panoramix_output_t
*p_output
= &p_sys
->pp_output
[x
][y
];
734 if( !p_output
->b_active
)
738 picture_t
*p_dst
= pp_dst
[p_output
->i_output
];
741 picture_CopyProperties( p_dst
, p_src
);
744 for( int i_plane
= 0; i_plane
< p_src
->i_planes
; i_plane
++ )
746 const int i_div_w
= p_sys
->p_chroma
->pi_div_w
[i_plane
];
747 const int i_div_h
= p_sys
->p_chroma
->pi_div_h
[i_plane
];
749 if( !i_div_w
|| !i_div_h
)
752 const plane_t
*p_srcp
= &p_src
->p
[i_plane
];
753 const plane_t
*p_dstp
= &p_dst
->p
[i_plane
];
756 panoramix_filter_t filter
;
757 filter
.black
.i_right
= p_output
->filter
.black
.i_right
/ i_div_w
;
758 filter
.black
.i_left
= p_output
->filter
.black
.i_left
/ i_div_w
;
759 filter
.black
.i_top
= p_output
->filter
.black
.i_top
/ i_div_h
;
760 filter
.black
.i_bottom
= p_output
->filter
.black
.i_bottom
/ i_div_h
;
762 filter
.attenuate
.i_right
= p_output
->filter
.attenuate
.i_right
/ i_div_w
;
763 filter
.attenuate
.i_left
= p_output
->filter
.attenuate
.i_left
/ i_div_w
;
764 filter
.attenuate
.i_top
= p_output
->filter
.attenuate
.i_top
/ i_div_h
;
765 filter
.attenuate
.i_bottom
= p_output
->filter
.attenuate
.i_bottom
/ i_div_h
;
768 const int i_x
= p_output
->i_src_x
/i_div_w
;
769 const int i_y
= p_output
->i_src_y
/i_div_h
;
771 assert( p_sys
->p_chroma
->b_planar
);
772 FilterPlanar( p_dstp
->p_pixels
, p_dstp
->i_pitch
,
773 &p_srcp
->p_pixels
[i_y
* p_srcp
->i_pitch
+ i_x
* p_srcp
->i_pixel_pitch
], p_srcp
->i_pitch
,
774 p_output
->i_src_width
/i_div_w
, p_output
->i_src_height
/i_div_h
,
775 p_sys
->p_chroma
->pi_black
[i_plane
],
777 p_sys
->p_lut
[i_plane
],
778 p_sys
->lambdav
[i_plane
],
779 p_sys
->lambdah
[i_plane
] );
784 picture_Release( p_src
);
789 * It converts mouse events
791 static int Mouse( video_splitter_t
*p_splitter
, vlc_mouse_t
*p_mouse
,
793 const vlc_mouse_t
*p_old
, const vlc_mouse_t
*p_new
)
795 video_splitter_sys_t
*p_sys
= p_splitter
->p_sys
;
798 for( int y
= 0; y
< p_sys
->i_row
; y
++ )
800 for( int x
= 0; x
< p_sys
->i_col
; x
++ )
802 const panoramix_output_t
*p_output
= &p_sys
->pp_output
[x
][y
];
803 if( p_output
->b_active
&& p_output
->i_output
== i_index
)
805 const int i_x
= p_new
->i_x
- p_output
->filter
.black
.i_left
;
806 const int i_y
= p_new
->i_y
- p_output
->filter
.black
.i_top
;
807 if( i_x
>= 0 && i_x
< p_output
->i_width
- p_output
->filter
.black
.i_right
&&
808 i_y
>= 0 && i_y
< p_output
->i_height
- p_output
->filter
.black
.i_bottom
)
811 p_mouse
->i_x
= p_output
->i_src_x
+ i_x
;
812 p_mouse
->i_y
= p_output
->i_src_y
+ i_y
;
821 /* It return a coefficient between 0.0 and 1.0 to be applied to the given
824 static double GammaFactor( const panoramix_gamma_t
*g
, float f_value
)
826 if( f_value
<= g
->f_black_crush
)
828 float f_input
= f_value
* g
->f_black_level
/ g
->f_black_crush
+ (1.0 - g
->f_black_level
);
830 return pow( f_input
, 1.0 / g
->f_gamma
);
832 else if( f_value
>= g
->f_white_crush
)
834 float f_input
= (f_value
* (1.0 - (g
->f_white_level
+ 1.0)) + (g
->f_white_level
+ 1.0) * g
->f_white_crush
- 1.0) / (g
->f_white_crush
- 1.0);
835 return pow( f_input
, 1.0 / g
->f_gamma
);
841 * It creates the panoramix configuration
843 static int Configuration( panoramix_output_t pp_output
[ROW_MAX
][COL_MAX
],
844 int i_col
, int i_row
,
845 int i_src_width
, int i_src_height
,
846 int i_half_w
, int i_half_h
,
848 const bool *pb_active
)
851 const bool b_overlap
= true;
853 const bool b_overlap
= false;
858 for( int y
= 0, i_src_y
= 0, i_dst_y
= 0; y
< i_row
; y
++ )
860 const bool b_row_first
= y
== 0;
861 const bool b_row_last
= y
== i_row
- 1;
863 /* Compute source height */
864 int i_win_height
= (i_src_height
/ i_row
) & ~1;
866 i_win_height
= i_src_height
- y
* i_win_height
;
868 for( int x
= 0, i_src_x
= 0, i_dst_x
= 0; x
< i_col
; x
++ )
870 const bool b_col_first
= x
== 0;
871 const bool b_col_last
= x
== i_col
- 1;
873 /* Compute source width */
874 int i_win_width
= (i_src_width
/ i_col
) & ~1;
876 i_win_width
= i_src_width
- x
* i_win_width
;
878 /* Compute filter configuration */
879 panoramix_filter_t cfg
;
881 memset( &cfg
, 0, sizeof(cfg
) );
882 if( b_overlap
&& b_attenuate
)
887 cfg
.black
.i_left
= i_half_w
;
889 cfg
.black
.i_right
= i_half_w
;
894 cfg
.black
.i_top
= i_half_h
;
896 cfg
.black
.i_bottom
= i_half_h
;
899 cfg
.attenuate
.i_left
= 2 * i_half_w
;
901 cfg
.attenuate
.i_right
= 2 * i_half_w
;
903 cfg
.attenuate
.i_top
= 2 * i_half_h
;
905 cfg
.attenuate
.i_bottom
= 2 * i_half_h
;
908 /* Compute alignment */
913 i_align
|= VOUT_ALIGN_RIGHT
;
915 i_align
|= VOUT_ALIGN_LEFT
;
920 i_align
|= VOUT_ALIGN_BOTTOM
;
922 i_align
|= VOUT_ALIGN_TOP
;
926 panoramix_output_t
*p_output
= &pp_output
[x
][y
];
929 p_output
->i_src_x
= i_src_x
- cfg
.attenuate
.i_left
/2;
930 p_output
->i_src_y
= i_src_y
- cfg
.attenuate
.i_top
/2;
931 p_output
->i_src_width
= i_win_width
+ cfg
.attenuate
.i_left
/2 + cfg
.attenuate
.i_right
/2;
932 p_output
->i_src_height
= i_win_height
+ cfg
.attenuate
.i_top
/2 + cfg
.attenuate
.i_bottom
/2;
935 p_output
->filter
= cfg
;
938 p_output
->i_align
= i_align
;
939 p_output
->i_x
= i_dst_x
;
940 p_output
->i_y
= i_dst_y
;
942 p_output
->i_width
= cfg
.black
.i_left
+ p_output
->i_src_width
+ cfg
.black
.i_right
;
943 p_output
->i_height
= cfg
.black
.i_top
+ p_output
->i_src_height
+ cfg
.black
.i_bottom
;
946 p_output
->b_active
= pb_active
[y
* i_col
+ x
] &&
947 p_output
->i_width
> 0 &&
948 p_output
->i_height
> 0;
949 if( p_output
->b_active
)
950 p_output
->i_output
= i_output
++;
953 i_src_x
+= i_win_width
;
954 i_dst_x
+= p_output
->i_width
;
956 i_src_y
+= i_win_height
;
957 i_dst_y
+= pp_output
[0][y
].i_height
;
963 * It filters a video plane
965 static void FilterPlanar( uint8_t *p_out
, int i_out_pitch
,
966 const uint8_t *p_in
, int i_in_pitch
,
970 const panoramix_filter_t
*p_cfg
,
971 uint8_t p_lut
[ACCURACY
+ 1][256],
972 int lambdav
[2][ACCURACY
/2],
973 int lambdah
[2][ACCURACY
/2] )
976 assert( !p_cfg
->black
.i_left
|| !p_cfg
->attenuate
.i_left
);
977 assert( !p_cfg
->black
.i_right
|| !p_cfg
->attenuate
.i_right
);
978 assert( !p_cfg
->black
.i_top
|| !p_cfg
->attenuate
.i_top
);
979 assert( !p_cfg
->black
.i_bottom
|| !p_cfg
->attenuate
.i_bottom
);
981 const int i_out_width
= p_cfg
->black
.i_left
+ i_copy_pitch
+ p_cfg
->black
.i_right
;
983 /* Top black border */
984 for( int b
= 0; b
< p_cfg
->black
.i_top
; b
++ )
986 memset( p_out
, i_pixel_black
, i_out_width
);
987 p_out
+= i_out_pitch
;
990 for( int y
= 0; y
< i_copy_lines
; y
++ )
992 const uint8_t *p_src
= p_in
;
993 uint8_t *p_dst
= p_out
;
995 /* Black border on the left */
996 if( p_cfg
->black
.i_left
> 0 )
998 memset( p_dst
, i_pixel_black
, p_cfg
->black
.i_left
);
999 p_dst
+= p_cfg
->black
.i_left
;
1001 /* Attenuated video on the left */
1002 for( int i
= 0; i
< p_cfg
->attenuate
.i_left
; i
++ )
1003 *p_dst
++ = p_lut
[lambdav
[0][i
]][*p_src
++];
1005 /* Unmodified video */
1006 const int i_unmodified_width
= i_copy_pitch
- p_cfg
->attenuate
.i_left
- p_cfg
->attenuate
.i_right
;
1007 memcpy( p_dst
, p_src
, i_unmodified_width
);
1008 p_dst
+= i_unmodified_width
;
1009 p_src
+= i_unmodified_width
;
1011 /* Attenuated video on the right */
1012 for( int i
= 0; i
< p_cfg
->attenuate
.i_right
; i
++ )
1013 *p_dst
++ = p_lut
[lambdav
[1][i
]][*p_src
++];
1014 /* Black border on the right */
1015 if( p_cfg
->black
.i_right
> 0 )
1017 memset( p_dst
, i_pixel_black
, p_cfg
->black
.i_right
);
1018 p_dst
+= p_cfg
->black
.i_right
;
1021 /* Attenuate complete line at top/bottom */
1022 const bool b_attenuate_top
= y
< p_cfg
->attenuate
.i_top
;
1023 const bool b_attenuate_bottom
= y
>= i_copy_lines
- p_cfg
->attenuate
.i_bottom
;
1024 if( b_attenuate_top
|| b_attenuate_bottom
)
1026 const int i_index
= b_attenuate_top
? lambdah
[0][y
] : lambdah
[1][y
- (i_copy_lines
- p_cfg
->attenuate
.i_bottom
)];
1027 for( int i
= 0; i
< i_out_width
; i
++)
1028 p_out
[i
] = p_lut
[i_index
][p_out
[i
]];
1033 p_out
+= i_out_pitch
;
1035 /* Bottom black border */
1036 for( int b
= 0; b
< p_cfg
->black
.i_bottom
; b
++ )
1038 memset( p_out
, i_pixel_black
, i_out_width
);
1039 p_out
+= i_out_pitch
;
1046 static void RenderPackedRGB( vout_thread_t
*p_vout
, picture_t
*p_pic
)
1049 length
= 2 * p_sys
->i_overlap_w2
* p_pic
->p
->i_pixel_pitch
;
1051 if (p_sys
->b_has_changed
)
1055 Denom
= F2(length
/ p_pic
->p
->i_pixel_pitch
);
1056 a_2
= p_sys
->a_2
* (ACCURACY
/ 100);
1057 a_1
= p_sys
->a_1
* 2 * p_sys
->i_overlap_w2
* (ACCURACY
/ 100);
1058 a_0
= p_sys
->a_0
* Denom
* (ACCURACY
/ 100);
1059 for(i_col_mod
= 0; i_col_mod
< 2; i_col_mod
++)
1060 for (i_index
= 0; i_index
< length
/ p_pic
->p
->i_pixel_pitch
; i_index
++)
1061 for (i_plane_
= 0; i_plane_
< p_pic
->p
->i_pixel_pitch
; i_plane_
++)
1062 p_sys
->lambda
[i_col_mod
][i_plane_
][i_index
] = clip_accuracy(!i_col_mod
? ACCURACY
- (F4(a_2
, a_1
, i_index
) + a_0
) / Denom
: ACCURACY
- (F4(a_2
, a_1
,(length
/ p_pic
->p
->i_pixel_pitch
) - i_index
) + a_0
) / Denom
);
1065 length
= 2 * p_sys
->i_overlap_h2
;
1066 if (p_sys
->b_has_changed
)
1071 a_2
= p_sys
->a_2
* (ACCURACY
/ 100);
1072 a_1
= p_sys
->a_1
* length
* (ACCURACY
/ 100);
1073 a_0
= p_sys
->a_0
* Denom
* (ACCURACY
/ 100);
1074 for(i_row_mod
= 0; i_row_mod
< 2; i_row_mod
++)
1075 for (i_index
= 0; i_index
< length
; i_index
++)
1076 for (i_plane_
= 0; i_plane_
< p_pic
->p
->i_pixel_pitch
; i_plane_
++)
1077 p_sys
->lambda2
[i_row_mod
][i_plane_
][i_index
] = clip_accuracy(!i_row_mod
? ACCURACY
- (F4(a_2
, a_1
, i_index
) + a_0
) / Denom
: ACCURACY
- (F4(a_2
, a_1
,(length
) - i_index
) + a_0
) / Denom
);