Add full chroma input flag to swscale
[x262.git] / filters / video / resize.c
1 /*****************************************************************************
2  * resize.c: resize video filter
3  *****************************************************************************
4  * Copyright (C) 2010 x264 project
5  *
6  * Authors: Steven Walters <kemuri9@gmail.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
21  *
22  * This program is also available under a commercial proprietary license.
23  * For more information, contact us at licensing@x264.com.
24  *****************************************************************************/
25
26 #include "video.h"
27 #define NAME "resize"
28 #define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, NAME, __VA_ARGS__ )
29
30 cli_vid_filter_t resize_filter;
31
32 static int full_check( video_info_t *info, x264_param_t *param )
33 {
34     int required = 0;
35     required |= info->csp    != param->i_csp;
36     required |= info->width  != param->i_width;
37     required |= info->height != param->i_height;
38     return required;
39 }
40
41 #if HAVE_SWSCALE
42 #undef DECLARE_ALIGNED
43 #include <libswscale/swscale.h>
44
45 /* this function is not a part of the swscale API but is defined in swscale_internal.h */
46 const char *sws_format_name( enum PixelFormat format );
47
48 typedef struct
49 {
50     int width;
51     int height;
52     int pix_fmt;
53 } frame_prop_t;
54
55 typedef struct
56 {
57     hnd_t prev_hnd;
58     cli_vid_filter_t prev_filter;
59
60     cli_pic_t buffer;
61     int buffer_allocated;
62     int dst_csp;
63     struct SwsContext *ctx;
64     int ctx_flags;
65     int swap_chroma;    /* state of swapping chroma planes */
66     frame_prop_t dst;   /* desired output properties */
67     frame_prop_t scale; /* properties of the SwsContext input */
68 } resizer_hnd_t;
69
70 static void help( int longhelp )
71 {
72     printf( "      "NAME":[width,height][,sar][,fittobox][,csp][,method]\n" );
73     if( !longhelp )
74         return;
75     printf( "            resizes frames based on the given criteria:\n"
76             "            - resolution only: resizes and adapts sar to avoid stretching\n"
77             "            - sar only: sets the sar and resizes to avoid stretching\n"
78             "            - resolution and sar: resizes to given resolution and sets the sar\n"
79             "            - fittobox: resizes the video based on the desired contraints\n"
80             "               - width, height, both\n"
81             "            - fittobox and sar: same as above except with specified sar\n"
82             "            simultaneously converting to the given colorspace\n"
83             "            using resizer method [\"bicubic\"]\n"
84             "             - fastbilinear, bilinear, bicubic, experimental, point,\n"
85             "             - area, bicublin, gauss, sinc, lanczos, spline\n" );
86 }
87
88 static uint32_t convert_cpu_to_flag( uint32_t cpu )
89 {
90     uint32_t swscale_cpu = 0;
91     if( cpu & X264_CPU_ALTIVEC )
92         swscale_cpu |= SWS_CPU_CAPS_ALTIVEC;
93     if( cpu & X264_CPU_MMXEXT )
94         swscale_cpu |= SWS_CPU_CAPS_MMX | SWS_CPU_CAPS_MMX2;
95     return swscale_cpu;
96 }
97
98 static uint32_t convert_method_to_flag( const char *name )
99 {
100     uint32_t flag = 0;
101     if( !strcasecmp( name, "fastbilinear" ) )
102         flag = SWS_FAST_BILINEAR;
103     else if( !strcasecmp( name, "bilinear" ) )
104         flag = SWS_BILINEAR;
105     else if( !strcasecmp( name, "bicubic" ) )
106         flag = SWS_BICUBIC;
107     else if( !strcasecmp( name, "experimental" ) )
108         flag = SWS_X;
109     else if( !strcasecmp( name, "point" ) )
110         flag = SWS_POINT;
111     else if( !strcasecmp( name, "area" ) )
112         flag = SWS_AREA;
113     else if( !strcasecmp( name, "bicublin" ) )
114         flag = SWS_BICUBLIN;
115     else if( !strcasecmp( name, "guass" ) )
116         flag = SWS_GAUSS;
117     else if( !strcasecmp( name, "sinc" ) )
118         flag = SWS_SINC;
119     else if( !strcasecmp( name, "lanczos" ) )
120         flag = SWS_LANCZOS;
121     else if( !strcasecmp( name, "spline" ) )
122         flag = SWS_SPLINE;
123     else // default
124         flag = SWS_BICUBIC;
125     return flag;
126 }
127
128 static int convert_csp_to_pix_fmt( int csp )
129 {
130     if( csp&X264_CSP_OTHER )
131         return csp&X264_CSP_MASK;
132     switch( csp&X264_CSP_MASK )
133     {
134         case X264_CSP_I420: return PIX_FMT_YUV420P;
135         case X264_CSP_I422: return PIX_FMT_YUV422P;
136         case X264_CSP_I444: return PIX_FMT_YUV444P;
137         case X264_CSP_NV12: return PIX_FMT_NV12;
138         case X264_CSP_YV12: return PIX_FMT_YUV420P; /* specially handled via swapping chroma */
139         case X264_CSP_BGR:  return PIX_FMT_BGR24;
140         case X264_CSP_BGRA: return PIX_FMT_BGRA;
141         default:            return PIX_FMT_NONE;
142     }
143 }
144
145 static int pick_closest_supported_csp( int csp )
146 {
147     int pix_fmt = convert_csp_to_pix_fmt( csp );
148     switch( pix_fmt )
149     {
150         case PIX_FMT_YUV422P:
151         case PIX_FMT_YUV422P16LE:
152         case PIX_FMT_YUV422P16BE:
153         case PIX_FMT_YUYV422:
154         case PIX_FMT_UYVY422:
155             return X264_CSP_I422;
156         case PIX_FMT_YUV444P:
157         case PIX_FMT_YUV444P16LE:
158         case PIX_FMT_YUV444P16BE:
159             return X264_CSP_I444;
160         case PIX_FMT_RGB24:    // convert rgb to bgr
161         case PIX_FMT_RGB48BE:
162         case PIX_FMT_RGB48LE:
163         case PIX_FMT_RGB565BE:
164         case PIX_FMT_RGB565LE:
165         case PIX_FMT_RGB555BE:
166         case PIX_FMT_RGB555LE:
167         case PIX_FMT_BGR24:
168         case PIX_FMT_BGR565BE:
169         case PIX_FMT_BGR565LE:
170         case PIX_FMT_BGR555BE:
171         case PIX_FMT_BGR555LE:
172             return X264_CSP_BGR;
173         case PIX_FMT_ARGB:
174         case PIX_FMT_RGBA:
175         case PIX_FMT_ABGR:
176         case PIX_FMT_BGRA:
177             return X264_CSP_BGRA;
178         case PIX_FMT_NV12:
179         case PIX_FMT_NV21:
180              return X264_CSP_NV12;
181         default:
182             return X264_CSP_I420;
183     }
184 }
185
186 static int round_dbl( double val, int precision, int b_truncate )
187 {
188     int ret = (int)(val / precision) * precision;
189     if( !b_truncate && (val - ret) >= (precision/2) ) // use the remainder if we're not truncating it
190         ret += precision;
191     return ret;
192 }
193
194 static int handle_opts( const char **optlist, char **opts, video_info_t *info, resizer_hnd_t *h )
195 {
196     uint32_t out_sar_w, out_sar_h;
197
198     char *str_width  = x264_get_option( optlist[0], opts );
199     char *str_height = x264_get_option( optlist[1], opts );
200     char *str_sar    = x264_get_option( optlist[2], opts );
201     char *fittobox   = x264_get_option( optlist[3], opts );
202     char *str_csp    = x264_get_option( optlist[4], opts );
203     int width        = x264_otoi( str_width, -1 );
204     int height       = x264_otoi( str_height, -1 );
205
206     int csp_only = 0;
207     uint32_t in_sar_w = info->sar_width;
208     uint32_t in_sar_h = info->sar_height;
209
210     if( str_csp )
211     {
212         /* output csp was specified, lookup against valid values */
213         int csp;
214         for( csp = X264_CSP_CLI_MAX-1; x264_cli_csps[csp].name && strcasecmp( x264_cli_csps[csp].name, str_csp ); )
215             csp--;
216         FAIL_IF_ERROR( csp == X264_CSP_NONE, "unsupported colorspace `%s'\n", str_csp );
217         h->dst_csp = csp;
218     }
219
220     /* if the input sar is currently invalid, set it to 1:1 so it can be used in math */
221     if( !in_sar_w || !in_sar_h )
222         in_sar_w = in_sar_h = 1;
223     if( str_sar )
224     {
225         FAIL_IF_ERROR( 2 != sscanf( str_sar, "%u:%u", &out_sar_w, &out_sar_h ) &&
226                        2 != sscanf( str_sar, "%u/%u", &out_sar_w, &out_sar_h ),
227                        "invalid sar `%s'\n", str_sar )
228     }
229     else
230         out_sar_w = out_sar_h = 1;
231     if( fittobox )
232     {
233         /* resize the video to fit the box as much as possible */
234         double box_width = width;
235         double box_height = height;
236         if( !strcasecmp( fittobox, "both" ) )
237         {
238             FAIL_IF_ERROR( box_width <= 0 || box_height <= 0, "invalid box resolution %sx%s\n",
239                            x264_otos( str_width, "unset" ), x264_otos( str_height, "unset" ) )
240         }
241         else if( !strcasecmp( fittobox, "width" ) )
242         {
243             FAIL_IF_ERROR( box_width <= 0, "invalid box width `%s'\n", x264_otos( str_width, "unset" ) )
244             box_height = INT_MAX;
245         }
246         else if( !strcasecmp( fittobox, "height" ) )
247         {
248             FAIL_IF_ERROR( box_height <= 0, "invalid box height `%s'\n", x264_otos( str_height, "unset" ) )
249             box_width = INT_MAX;
250         }
251         else FAIL_IF_ERROR( 1, "invalid fittobox mode `%s'\n", fittobox )
252
253         /* we now have the requested bounding box display dimensions, now adjust them for output sar */
254         if( out_sar_w > out_sar_h ) // SAR is wide, decrease width
255             box_width  *= (double)out_sar_h / out_sar_w;
256         else // SAR is thin, decrease height
257             box_height *= (double)out_sar_w  / out_sar_h;
258
259         /* get the display resolution of the clip as it is now */
260         double d_width  = info->width;
261         double d_height = info->height;
262         if( in_sar_w > in_sar_h )
263             d_width  *= (double)in_sar_w / in_sar_h;
264         else
265             d_height *= (double)in_sar_h / in_sar_w;
266         /* now convert it to the coded resolution in accordance with the output sar */
267         if( out_sar_w > out_sar_h )
268             d_width  *= (double)out_sar_h / out_sar_w;
269         else
270             d_height *= (double)out_sar_w / out_sar_h;
271
272         /* maximally fit the new coded resolution to the box */
273         double scale = X264_MIN( box_width / d_width, box_height / d_height );
274         const x264_cli_csp_t *csp = x264_cli_get_csp( h->dst_csp );
275         width  = round_dbl( scale * d_width,  csp->mod_width,  1 );
276         height = round_dbl( scale * d_height, csp->mod_height, 1 );
277     }
278     else
279     {
280         if( str_width || str_height )
281         {
282             FAIL_IF_ERROR( width <= 0 || height <= 0, "invalid resolution %sx%s\n",
283                            x264_otos( str_width, "unset" ), x264_otos( str_height, "unset" ) )
284             if( !str_sar ) /* res only -> adjust sar */
285             {
286                 /* new_sar = (new_h * old_w * old_sar_w) / (old_h * new_w * old_sar_h) */
287                 uint64_t num = (uint64_t)info->width  * height;
288                 uint64_t den = (uint64_t)info->height * width;
289                 x264_reduce_fraction64( &num, &den );
290                 out_sar_w = num * in_sar_w;
291                 out_sar_h = den * in_sar_h;
292                 x264_reduce_fraction( &out_sar_w, &out_sar_h );
293             }
294         }
295         else if( str_sar ) /* sar only -> adjust res */
296         {
297              const x264_cli_csp_t *csp = x264_cli_get_csp( h->dst_csp );
298              width  = info->width;
299              height = info->height;
300              if( (out_sar_w * in_sar_h) > (out_sar_h * in_sar_w) ) // SAR got wider, decrease width
301                  width = round_dbl( (double)info->width * in_sar_w * out_sar_h
302                                   / in_sar_h / out_sar_w, csp->mod_width, 0 );
303              else // SAR got thinner, decrease height
304                  height = round_dbl( (double)info->height * in_sar_h * out_sar_w
305                                    / in_sar_w / out_sar_h, csp->mod_height, 0 );
306         }
307         else /* csp only */
308         {
309             h->dst.width  = info->width;
310             h->dst.height = info->height;
311             csp_only = 1;
312         }
313     }
314     if( !csp_only )
315     {
316         info->sar_width  = out_sar_w;
317         info->sar_height = out_sar_h;
318         h->dst.width  = width;
319         h->dst.height = height;
320     }
321     return 0;
322 }
323
324 static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x264_param_t *param, char *opt_string )
325 {
326     /* if called for normalizing the csp to known formats and the format is not unknown, exit */
327     if( opt_string && !strcmp( opt_string, "normcsp" ) && !(info->csp&X264_CSP_OTHER) )
328         return 0;
329     /* if called by x264cli and nothing needs to be done, exit */
330     if( !opt_string && !full_check( info, param ) )
331         return 0;
332
333     static const char *optlist[] = { "width", "height", "sar", "fittobox", "csp", "method", NULL };
334     char **opts = x264_split_options( opt_string, optlist );
335     if( !opts && opt_string )
336         return -1;
337
338     resizer_hnd_t *h = calloc( 1, sizeof(resizer_hnd_t) );
339     if( !h )
340         return -1;
341     if( opts )
342     {
343         h->dst_csp    = info->csp;
344         h->dst.width  = info->width;
345         h->dst.height = info->height;
346         if( !strcmp( opt_string, "normcsp" ) )
347             h->dst_csp = pick_closest_supported_csp( info->csp );
348         else if( handle_opts( optlist, opts, info, h ) )
349             return -1;
350     }
351     else
352     {
353         h->dst_csp    = param->i_csp;
354         h->dst.width  = param->i_width;
355         h->dst.height = param->i_height;
356     }
357     uint32_t method = convert_method_to_flag( x264_otos( x264_get_option( optlist[5], opts ), "" ) );
358     x264_free_string_array( opts );
359
360     h->ctx_flags = convert_cpu_to_flag( param->cpu ) | method;
361     if( method != SWS_FAST_BILINEAR )
362         h->ctx_flags |= SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | SWS_ACCURATE_RND;
363     h->dst.pix_fmt = convert_csp_to_pix_fmt( h->dst_csp );
364     h->scale = h->dst;
365     /* swap chroma planes for yv12 to have it become i420 */
366     h->swap_chroma = (info->csp & X264_CSP_MASK) == X264_CSP_YV12;
367     int src_pix_fmt = convert_csp_to_pix_fmt( info->csp );
368
369     /* confirm swscale can support this conversion */
370     FAIL_IF_ERROR( !sws_isSupportedInput( src_pix_fmt ), "input colorspace %s is not supported\n", sws_format_name( src_pix_fmt ) )
371     FAIL_IF_ERROR( !sws_isSupportedOutput( h->dst.pix_fmt ), "output colorspace %s is not supported\n", sws_format_name( h->dst.pix_fmt ) )
372     FAIL_IF_ERROR( h->dst.height != info->height && info->interlaced,
373                    "swscale is not compatible with interlaced vertical resizing\n" )
374     /* confirm that the desired resolution meets the colorspace requirements */
375     const x264_cli_csp_t *csp = x264_cli_get_csp( h->dst_csp );
376     FAIL_IF_ERROR( h->dst.width % csp->mod_width || h->dst.height % csp->mod_height,
377                    "resolution %dx%d is not compliant with colorspace %s\n", h->dst.width, h->dst.height, csp->name )
378
379     if( h->dst.width != info->width || h->dst.height != info->height )
380         x264_cli_log( NAME, X264_LOG_INFO, "resizing to %dx%d\n", h->dst.width, h->dst.height );
381     if( h->dst.pix_fmt != src_pix_fmt )
382         x264_cli_log( NAME, X264_LOG_WARNING, "converting from %s to %s\n",
383                       sws_format_name( src_pix_fmt ), sws_format_name( h->dst.pix_fmt ) );
384     h->dst_csp |= info->csp & X264_CSP_VFLIP; // preserve vflip
385     /* finished initing, overwrite values */
386     info->csp    = h->dst_csp;
387     info->width  = h->dst.width;
388     info->height = h->dst.height;
389
390     h->prev_filter = *filter;
391     h->prev_hnd = *handle;
392     *handle = h;
393     *filter = resize_filter;
394
395     return 0;
396 }
397
398 static int check_resizer( resizer_hnd_t *h, cli_pic_t *in )
399 {
400     frame_prop_t input_prop = { in->img.width, in->img.height, convert_csp_to_pix_fmt( in->img.csp ) };
401     if( !memcmp( &input_prop, &h->scale, sizeof(frame_prop_t) ) )
402         return 0;
403     if( h->ctx )
404     {
405         sws_freeContext( h->ctx );
406         x264_cli_log( NAME, X264_LOG_WARNING, "stream properties changed at pts %"PRId64"\n", in->pts );
407     }
408     h->scale = input_prop;
409     if( !h->buffer_allocated )
410     {
411         if( x264_cli_pic_alloc( &h->buffer, h->dst_csp, h->dst.width, h->dst.height ) )
412             return -1;
413         h->buffer_allocated = 1;
414     }
415     h->ctx = sws_getContext( h->scale.width, h->scale.height, h->scale.pix_fmt, h->dst.width,
416                              h->dst.height, h->dst.pix_fmt, h->ctx_flags, NULL, NULL, NULL );
417     FAIL_IF_ERROR( !h->ctx, "swscale init failed\n" )
418     return 0;
419 }
420
421 static int get_frame( hnd_t handle, cli_pic_t *output, int frame )
422 {
423     resizer_hnd_t *h = handle;
424     if( h->prev_filter.get_frame( h->prev_hnd, output, frame ) )
425         return -1;
426     if( check_resizer( h, output ) )
427         return -1;
428     if( h->ctx )
429     {
430         sws_scale( h->ctx, (const uint8_t* const*)output->img.plane, output->img.stride,
431                    0, output->img.height, h->buffer.img.plane, h->buffer.img.stride );
432         output->img = h->buffer.img; /* copy img data */
433     }
434     else
435         output->img.csp = h->dst_csp;
436     if( h->swap_chroma )
437         XCHG( uint8_t*, output->img.plane[1], output->img.plane[2] );
438
439     return 0;
440 }
441
442 static int release_frame( hnd_t handle, cli_pic_t *pic, int frame )
443 {
444     resizer_hnd_t *h = handle;
445     return h->prev_filter.release_frame( h->prev_hnd, pic, frame );
446 }
447
448 static void free_filter( hnd_t handle )
449 {
450     resizer_hnd_t *h = handle;
451     h->prev_filter.free( h->prev_hnd );
452     if( h->ctx )
453         sws_freeContext( h->ctx );
454     if( h->buffer_allocated )
455         x264_cli_pic_clean( &h->buffer );
456     free( h );
457 }
458
459 #else /* no swscale */
460 static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x264_param_t *param, char *opt_string )
461 {
462     int ret = 0;
463
464     if( !opt_string )
465         ret = full_check( info, param );
466     else
467     {
468         if( !strcmp( opt_string, "normcsp" ) )
469             ret = info->csp & X264_CSP_OTHER;
470         else
471             ret = -1;
472     }
473
474     /* pass if nothing needs to be done, otherwise fail */
475     FAIL_IF_ERROR( ret, "not compiled with swscale support\n" )
476     return 0;
477 }
478
479 #define help NULL
480 #define get_frame NULL
481 #define release_frame NULL
482 #define free_filter NULL
483 #define convert_csp_to_pix_fmt(x) (x & X264_CSP_MASK)
484
485 #endif
486
487 cli_vid_filter_t resize_filter = { NAME, help, init, get_frame, release_frame, free_filter, NULL };