1 /*
2 * filter_shape.c -- Arbitrary alpha channel shaping
3 * Copyright (C) 2005 Visual Media Fx Inc.
4 * Copyright (C) 2021 Meltytech, LLC
5 * Author: Charles Yates <charles.yates@gmail.com>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include <framework/mlt.h>
23 #include <string.h>
24 #include <framework/mlt_factory.h>
25 #include <framework/mlt_frame.h>
26 #include <framework/mlt_producer.h>
27
smoothstep(const double e1,const double e2,const double a)28 static inline double smoothstep( const double e1, const double e2, const double a )
29 {
30 if ( a < e1 ) return 0.0;
31 if ( a >= e2 ) return 1.0;
32 double v = ( a - e1 ) / ( e2 - e1 );
33 return ( v * v * ( 3 - 2 * v ) );
34 }
35
36 /** Get the images and apply the luminance of the mask to the alpha of the frame.
37 */
38
filter_get_image(mlt_frame frame,uint8_t ** image,mlt_image_format * format,int * width,int * height,int writable)39 static int filter_get_image( mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable )
40 {
41 // Fetch the data from the stack (mix, mask, filter)
42 double mix = mlt_deque_pop_back_double( MLT_FRAME_IMAGE_STACK( frame ) );
43 mlt_frame mask = mlt_frame_pop_service( frame );
44 mlt_filter filter = mlt_frame_pop_service( frame );
45
46 // Obtain the constants
47 double softness = mlt_properties_get_double( MLT_FILTER_PROPERTIES( filter ), "softness" );
48 int use_luminance = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "use_luminance" );
49 int use_mix = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "use_mix" );
50 int invert = mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "invert" ) * 255;
51
52 if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "reverse")) {
53 mix = 1.0 - mix;
54 invert = !mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "invert" ) * 255;
55 }
56
57 // Render the frame
58 *format = mlt_image_yuv422;
59 *width -= *width % 2;
60 if ( mlt_frame_get_image( frame, image, format, width, height, writable ) == 0 &&
61 ( !use_luminance || !use_mix || (int) mix != 1 || invert == 255 ) )
62 {
63 // Obtain a scaled/distorted mask to match
64 uint8_t *mask_img = NULL;
65 mlt_image_format mask_fmt = mlt_image_yuv422;
66 mlt_properties_set_int( MLT_FRAME_PROPERTIES( mask ), "distort", 1 );
67 mlt_properties_pass_list( MLT_FRAME_PROPERTIES( mask ), MLT_FRAME_PROPERTIES( frame ), "consumer_deinterlace, deinterlace_method, rescale.interp, consumer_tff, consumer_color_trc" );
68
69 if ( mlt_frame_get_image( mask, &mask_img, &mask_fmt, width, height, 0 ) == 0 )
70 {
71 int size = *width * *height;
72 double a = 0;
73 double b = 0;
74 uint8_t* p = mlt_frame_get_alpha( frame );
75 if ( !p )
76 {
77 int alphasize = *width * *height;
78 p = mlt_pool_alloc( alphasize );
79 memset( p, 255, alphasize );
80 mlt_frame_set_alpha( frame, p, alphasize, mlt_pool_release );
81 }
82
83 if ( !use_luminance )
84 {
85 uint8_t* q = mlt_frame_get_alpha( mask );
86 if ( !q )
87 {
88 int alphasize = *width * *height;
89 q = mlt_pool_alloc( alphasize );
90 memset( q, 255, alphasize );
91 mlt_frame_set_alpha( mask, q, alphasize, mlt_pool_release );
92 }
93 if ( use_mix )
94 {
95 while( size -- )
96 {
97 a = ( double )*q ++ / 255.0;
98 b = 1.0 - smoothstep( a, a + softness, mix );
99 *p = ( uint8_t )( *p * b ) ^ invert;
100 p ++;
101 }
102 }
103 else
104 {
105 while( size -- )
106 *p++ = *q++;
107 }
108 }
109 else if ( !use_mix )
110 {
111 // Do not apply threshold filter.
112 uint8_t *q = mask_img;
113 while( size -- )
114 {
115 *p = *q;
116 p++;
117 q += 2;
118 }
119 }
120 else if ( (int) mix != 1 || invert == 255 )
121 {
122 int full_range = mlt_properties_get_int( MLT_FRAME_PROPERTIES( frame ), "full_luma" );
123 double offset = full_range ? 0.0 : 16.0;
124 double divisor = full_range ? 255.0 : 235.0;
125 uint8_t *q = mask_img;
126 // Ensure softness tends to zero as mix tends to 1
127 softness *= ( 1.0 - mix );
128 while( size -- )
129 {
130 a = ( ( double ) *q - offset ) / divisor;
131 b = smoothstep( a, a + softness, mix );
132 *p = ( uint8_t )( *p * b ) ^ invert;
133 p ++;
134 q += 2;
135 }
136 }
137 }
138 }
139
140 return 0;
141 }
142
143 /** Filter processing.
144 */
145
filter_process(mlt_filter filter,mlt_frame frame)146 static mlt_frame filter_process( mlt_filter filter, mlt_frame frame )
147 {
148 // Obtain the shape instance
149 char *resource = mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "resource" );
150 if ( !resource ) return frame;
151 char *last_resource = mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "_resource" );
152 mlt_producer producer = mlt_properties_get_data( MLT_FILTER_PROPERTIES( filter ), "instance", NULL );
153
154 // Calculate the position and length
155 int position = mlt_filter_get_position( filter, frame );
156 mlt_position length = mlt_filter_get_length2( filter, frame );
157
158 // If we haven't created the instance or it's changed
159 if ( producer == NULL || !last_resource || strcmp( resource, last_resource ) )
160 {
161 mlt_profile profile = mlt_service_profile( MLT_FILTER_SERVICE( filter ) );
162 char temp[ 512 ];
163
164 // Store the last resource now
165 mlt_properties_set( MLT_FILTER_PROPERTIES( filter ), "_resource", resource );
166
167 // This is a hack - the idea is that we can indirectly reference the
168 // luma modules pgm or png images by a short cut like %luma01.pgm - we then replace
169 // the % with the full path to the image and use it if it exists, if not, check for
170 // the file ending in a .png, and failing that, default to a fade in
171 if ( strchr( resource, '%' ) )
172 {
173 FILE *test;
174 sprintf( temp, "%s/lumas/%s/%s", mlt_environment( "MLT_DATA" ), mlt_profile_lumas_dir(profile), strchr( resource, '%' ) + 1 );
175 test = mlt_fopen( temp, "r" );
176
177 if ( test == NULL )
178 {
179 strcat( temp, ".png" );
180 test = mlt_fopen( temp, "r" );
181 }
182
183 if ( test ) {
184 fclose( test );
185 resource = temp;
186 }
187 }
188
189 producer = mlt_factory_producer( profile, NULL, resource );
190 if ( producer != NULL )
191 mlt_properties_set( MLT_PRODUCER_PROPERTIES( producer ), "eof", "loop" );
192 mlt_properties_set_data( MLT_FILTER_PROPERTIES( filter ), "instance", producer, 0, ( mlt_destructor )mlt_producer_close, NULL );
193 }
194
195 // We may still not have a producer in which case, we do nothing
196 if ( producer != NULL )
197 {
198 mlt_frame mask = NULL;
199 double alpha_mix = mlt_properties_anim_get_double( MLT_FILTER_PROPERTIES(filter), "mix", position, length );
200 mlt_properties_pass( MLT_PRODUCER_PROPERTIES( producer ), MLT_FILTER_PROPERTIES( filter ), "producer." );
201 mlt_producer_seek( producer, position );
202 if ( mlt_service_get_frame( MLT_PRODUCER_SERVICE( producer ), &mask, 0 ) == 0 )
203 {
204 char name[64];
205 snprintf( name, sizeof(name), "shape %s",
206 mlt_properties_get( MLT_FILTER_PROPERTIES( filter ), "_unique_id" ) );
207 mlt_properties_set_data( MLT_FRAME_PROPERTIES( frame ), name, mask, 0, ( mlt_destructor )mlt_frame_close, NULL );
208 mlt_frame_push_service( frame, filter );
209 mlt_frame_push_service( frame, mask );
210 mlt_deque_push_back_double( MLT_FRAME_IMAGE_STACK( frame ), alpha_mix / 100.0 );
211 mlt_frame_push_get_image( frame, filter_get_image );
212 if ( mlt_properties_get_int( MLT_FILTER_PROPERTIES( filter ), "audio_match" ) )
213 {
214 mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "meta.mixdown", 1 );
215 mlt_properties_set_double( MLT_FRAME_PROPERTIES( frame ), "meta.volume", alpha_mix / 100.0 );
216 }
217 mlt_properties_set_int( MLT_FRAME_PROPERTIES( frame ), "always_scale", 1 );
218 }
219 }
220
221 return frame;
222 }
223
224 /** Constructor for the filter.
225 */
226
filter_shape_init(mlt_profile profile,mlt_service_type type,const char * id,char * arg)227 mlt_filter filter_shape_init( mlt_profile profile, mlt_service_type type, const char *id, char *arg )
228 {
229 mlt_filter filter = mlt_filter_new( );
230 if ( filter != NULL )
231 {
232 mlt_properties_set( MLT_FILTER_PROPERTIES( filter ), "resource", arg );
233 mlt_properties_set( MLT_FILTER_PROPERTIES( filter ), "mix", "100" );
234 mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "use_mix", 1 );
235 mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "audio_match", 1 );
236 mlt_properties_set_int( MLT_FILTER_PROPERTIES( filter ), "invert", 0 );
237 mlt_properties_set_double( MLT_FILTER_PROPERTIES( filter ), "softness", 0.1 );
238 filter->process = filter_process;
239 }
240 return filter;
241 }
242
243