/* cropscale.c

   Copyright (c) 2003-2018 HandBrake Team
   This file is part of the HandBrake source code
   Homepage: <http://handbrake.fr/>.
   It may be used under the terms of the GNU General Public License v2.
   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
 */
   
#include "hb.h"
#include "hbffmpeg.h"
#include "common.h"

struct hb_filter_private_s
{
    hb_job_t            *job;
    int                 width_in;
    int                 height_in;
    int                 pix_fmt;
    int                 pix_fmt_out;
    int                 width_out;
    int                 height_out;
    int                 crop[4];
    
    struct SwsContext * context;
};

static int hb_crop_scale_init( hb_filter_object_t * filter,
                               hb_filter_init_t * init );

static int hb_crop_scale_work( hb_filter_object_t * filter,
                               hb_buffer_t ** buf_in,
                               hb_buffer_t ** buf_out );

static hb_filter_info_t * hb_crop_scale_info( hb_filter_object_t * filter );

static void hb_crop_scale_close( hb_filter_object_t * filter );

static const char crop_scale_template[] =
    "width=^"HB_INT_REG"$:height=^"HB_INT_REG"$:"
    "crop-top=^"HB_INT_REG"$:crop-bottom=^"HB_INT_REG"$:"
    "crop-left=^"HB_INT_REG"$:crop-right=^"HB_INT_REG"$";

hb_filter_object_t hb_filter_crop_scale =
{
    .id                = HB_FILTER_CROP_SCALE,
    .enforce_order     = 1,
    .name              = "Crop and Scale",
    .settings          = NULL,
    .init              = hb_crop_scale_init,
    .work              = hb_crop_scale_work,
    .close             = hb_crop_scale_close,
    .info              = hb_crop_scale_info,
    .settings_template = crop_scale_template,
};

static int hb_crop_scale_init( hb_filter_object_t * filter,
                               hb_filter_init_t * init )
{
    filter->private_data = calloc( 1, sizeof(struct hb_filter_private_s) );
    hb_filter_private_t * pv = filter->private_data;

    // TODO: add pix format option to settings
    pv->job = init->job;
    pv->pix_fmt_out = init->pix_fmt;
    pv->width_in = init->geometry.width;
    pv->height_in = init->geometry.height;
    pv->width_out = init->geometry.width - (init->crop[2] + init->crop[3]);
    pv->height_out = init->geometry.height - (init->crop[0] + init->crop[1]);

    memcpy( pv->crop, init->crop, sizeof( int[4] ) );
    hb_dict_extract_int(&pv->width_out, filter->settings, "width");
    hb_dict_extract_int(&pv->height_out, filter->settings, "height");
    hb_dict_extract_int(&pv->crop[0], filter->settings, "crop-top");
    hb_dict_extract_int(&pv->crop[1], filter->settings, "crop-bottom");
    hb_dict_extract_int(&pv->crop[2], filter->settings, "crop-left");
    hb_dict_extract_int(&pv->crop[3], filter->settings, "crop-right");

    // Set init values so the next stage in the pipline
    // knows what it will be getting
    init->pix_fmt = pv->pix_fmt;
    init->geometry.width = pv->width_out;
    init->geometry.height = pv->height_out;
    memcpy( init->crop, pv->crop, sizeof( int[4] ) );

    return 0;
}

static hb_filter_info_t * hb_crop_scale_info( hb_filter_object_t * filter )
{
    hb_filter_private_t * pv = filter->private_data;
    hb_filter_info_t    * info;

    if( !pv )
        return NULL;

    info = calloc(1, sizeof(hb_filter_info_t));
    info->human_readable_desc = malloc(128);
    info->human_readable_desc[0] = 0;

    info->out.pix_fmt = pv->pix_fmt;
    info->out.geometry.width = pv->width_out;
    info->out.geometry.height = pv->height_out;
    memcpy( info->out.crop, pv->crop, sizeof( int[4] ) );

    int cropped_width = pv->width_in - ( pv->crop[2] + pv->crop[3] );
    int cropped_height = pv->height_in - ( pv->crop[0] + pv->crop[1] );

    snprintf( info->human_readable_desc, 128,
        "source: %d * %d, crop (%d/%d/%d/%d): %d * %d, scale: %d * %d",
        pv->width_in, pv->height_in,
        pv->crop[0], pv->crop[1], pv->crop[2], pv->crop[3],
        cropped_width, cropped_height, pv->width_out, pv->height_out );

    return info;
}

static void hb_crop_scale_close( hb_filter_object_t * filter )
{
    hb_filter_private_t * pv = filter->private_data;

    if ( !pv )
    {
        return;
    }

    if( pv->context )
    {
        sws_freeContext( pv->context );
    }

    free( pv );
    filter->private_data = NULL;
}

static hb_buffer_t* crop_scale( hb_filter_private_t * pv, hb_buffer_t * in )
{
    hb_buffer_t * out;
    uint8_t     * crop_data[4], * out_data[4];
    int           crop_stride[4], out_stride[4];

    out = hb_video_buffer_init( pv->width_out, pv->height_out );
    hb_picture_fill(out_data, out_stride, out);

    // Crop; this alters the pointer to the data to point to the
    // correct place for cropped frame
    hb_picture_crop(crop_data, crop_stride, in, pv->crop[0], pv->crop[2]);

    if (pv->context   == NULL         ||
        pv->width_in  != in->f.width  ||
        pv->height_in != in->f.height ||
        pv->pix_fmt   != in->f.fmt)
    {
        // Something changed, need a new scaling context.
        if (pv->context != NULL)
        {
            sws_freeContext(pv->context);
        }
        
        pv->context = hb_sws_get_context(
                            in->f.width  - (pv->crop[2] + pv->crop[3]),
                            in->f.height - (pv->crop[0] + pv->crop[1]),
                            in->f.fmt, out->f.width, out->f.height,
                            out->f.fmt, SWS_LANCZOS|SWS_ACCURATE_RND,
                            hb_ff_get_colorspace(pv->job->title->color_matrix));
        pv->width_in  = in->f.width;
        pv->height_in = in->f.height;
        pv->pix_fmt   = in->f.fmt;
    }

    if (pv->context == NULL)
    {
        hb_buffer_close(&out);
        return NULL;
    }

    // Scale crop into out according to the context set up above
    sws_scale(pv->context,
              (const uint8_t* const*)crop_data, crop_stride,
              0, in->f.height - (pv->crop[0] + pv->crop[1]),
              out_data, out_stride);

    out->s = in->s;
    return out;
}

static int hb_crop_scale_work( hb_filter_object_t * filter,
                               hb_buffer_t ** buf_in,
                               hb_buffer_t ** buf_out )
{
    hb_filter_private_t * pv = filter->private_data;
    hb_buffer_t * in = *buf_in;

    if (in->s.flags & HB_BUF_FLAG_EOF)
    {
        *buf_out = in;
        *buf_in = NULL;
        return HB_FILTER_DONE;
    }

    if ( !pv )
    {
        *buf_out = in;
        *buf_in = NULL;
        return HB_FILTER_OK;
    }

    // If width or height were not set, set them now based on the
    // input width & height
    if ( pv->width_out <= 0 || pv->height_out <= 0 )
    {
        pv->width_out = in->f.width - (pv->crop[2] + pv->crop[3]);
        pv->height_out = in->f.height - (pv->crop[0] + pv->crop[1]);
    }

    if (!pv->crop[0] && !pv->crop[1] && !pv->crop[2] && !pv->crop[3] &&
        in->f.fmt == pv->pix_fmt_out && in->f.width == pv->width_out &&
        in->f.height == pv->height_out)
    {
        *buf_out = in;
        *buf_in  = NULL;
        return HB_FILTER_OK;
    }

    *buf_out = crop_scale(pv, in);

    return HB_FILTER_OK;
}
