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