diff --git a/libavfilter/vf_zoom.c b/libavfilter/vf_zoom.c index 4d5fe7c2b93..25311022308 100644 --- a/libavfilter/vf_zoom.c +++ b/libavfilter/vf_zoom.c @@ -131,14 +131,87 @@ static const AVOption zoom_options[] = { AVFILTER_DEFINE_CLASS(zoom); +#define SUBPIXEL_LUT_RESOLUTION 100 +int subpixel_LUT_inited = 0; +uint8_t subpixel_LUT[256][256][SUBPIXEL_LUT_RESOLUTION]; + static av_cold int init(AVFilterContext *ctx) { + if(!subpixel_LUT_inited){ + subpixel_LUT_inited = 1; + + for(int i = 0; i < 256; i++) { + for(int j = 0; j < 256; j++) { + for(int k = 0; k < SUBPIXEL_LUT_RESOLUTION; k++) { + subpixel_LUT[i][j][k] = i * ( k / (float)SUBPIXEL_LUT_RESOLUTION) + + j * (1 - (k / (float)SUBPIXEL_LUT_RESOLUTION)); + } + } + } + + } + return 0; } static int query_formats(AVFilterContext *ctx) { - return ff_set_common_formats(ctx, ff_draw_supported_pixel_formats(0)); + static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_YUV420P, + AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUV410P, + AV_PIX_FMT_YUV411P, + AV_PIX_FMT_GRAY8, + AV_PIX_FMT_YUVJ420P, + AV_PIX_FMT_YUVJ422P, + AV_PIX_FMT_YUVJ444P, + AV_PIX_FMT_GRAY16LE, + AV_PIX_FMT_YUV440P, + AV_PIX_FMT_YUVJ440P, + AV_PIX_FMT_YUVA420P, + AV_PIX_FMT_YUV420P16LE, + AV_PIX_FMT_YUV422P16LE, + AV_PIX_FMT_YUV444P16LE, + AV_PIX_FMT_YA8, + AV_PIX_FMT_GBRP, + AV_PIX_FMT_GBRP16LE, + AV_PIX_FMT_YUVA422P, + AV_PIX_FMT_YUVA444P, + AV_PIX_FMT_YUVA420P16LE, + AV_PIX_FMT_YUVA422P16LE, + AV_PIX_FMT_YUVA444P16LE, + AV_PIX_FMT_YA16LE, + AV_PIX_FMT_GBRAP, + AV_PIX_FMT_GBRAP16LE, + AV_PIX_FMT_YUVJ411P, + + // unsupported: planar Y but packed U & V + // AV_PIX_FMT_NV12, + // AV_PIX_FMT_NV21, + // AV_PIX_FMT_NV16, + // AV_PIX_FMT_NV24, + // AV_PIX_FMT_NV42 + + // unsupported: packed RGB & variants + // AV_PIX_FMT_RGB24, + // AV_PIX_FMT_BGR24, + // AV_PIX_FMT_ARGB, + // AV_PIX_FMT_RGBA, + // AV_PIX_FMT_ABGR, + // AV_PIX_FMT_BGRA, + // AV_PIX_FMT_0RGB, + // AV_PIX_FMT_RGB0, + // AV_PIX_FMT_0BGR, + // AV_PIX_FMT_BGR0, + + AV_PIX_FMT_NONE + }; + + AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts); + if (!fmts_list) + return AVERROR(ENOMEM); + return ff_set_common_formats(ctx, fmts_list); } static int config_props(AVFilterLink *inlink) { @@ -268,6 +341,7 @@ static int config_output(AVFilterLink *outlink) if(outlink->h <= 0){ outlink->h = 2; } + return 0; } @@ -289,188 +363,368 @@ static AVFrame* alloc_frame(enum AVPixelFormat pixfmt, int w, int h) return frame; } -static inline int normalize_xy(double d, int chroma_sub) +// this function takes x/y already scaled to the chroma sub +// supports only planar formats +static inline uint8_t sample8_bilinear_at(uint8_t *data, + int linesize, int pixelstep, + float x, float y, + int w, int h, + int8_t oob_value + ) { - if (isnan(d)) - return INT_MAX; - return (int)d & ~((1 << chroma_sub) - 1); + int ix = x; + int iy = y; + float fracx = x - ix; + float fracy = y - iy; + //float ifracx = 1.0f - fracx; + //float ifracy = 1.0f - fracy; + //float lin0, lin1; + + int _fractx = fracx * SUBPIXEL_LUT_RESOLUTION; + int _fracty = fracy * SUBPIXEL_LUT_RESOLUTION; + + // check if requested value is out of bounds + if(x < 0 || y < 0 || x > w - 1 || y > h - 1){ + return oob_value; + } + + uint8_t *row_y = data + iy * linesize; + + // top left + uint8_t *a11 = row_y + ix * pixelstep; + + // top right = top left + 1px + uint8_t *a12 = a11 + pixelstep; + + // bottom left = top left + 1row + uint8_t *a21 = a11 + linesize; + // bottom right = bottom left + 1px + uint8_t *a22 = a21 + pixelstep; + + uint8_t l0 = subpixel_LUT[(*a11)][(*a12)][_fractx]; + uint8_t l1 = subpixel_LUT[(*a21)][(*a22)][_fractx]; + + return subpixel_LUT[l0][l1][_fracty]; + + // top interp + //lin0 = ifracx * (*a11) + fracx * (*a12); + // bottom interp + //lin1 = ifracx * (*a21) + fracx * (*a22); + + // vertical interp + //return ifracy * lin0 + fracy * lin1; } -static int zoom_out(ZoomContext *zoom, AVFrame *in, AVFrame *out, AVFilterLink *outlink) -{ - av_log(zoom, AV_LOG_DEBUG, "zoom out\n"); - int ret = 0; - zoom->sws = sws_alloc_context(); - if (!zoom->sws) { - ret = AVERROR(ENOMEM); - goto error; +// this function takes x/y already scaled to the chroma sub +// supports only planar formats +static inline uint16_t sample16_bilinear_at(uint8_t *data, + int linesize, int pixelstep, + float x, float y, + int w, int h, + int16_t oob_value + ) +{ + int ix = x; + int iy = y; + float fracx = x - ix; + float fracy = y - iy; + float ifracx = 1.0f - fracx; + float ifracy = 1.0f - fracy; + float lin0, lin1; + + // check if requested value is out of bounds + if(x < 0 || y < 0 || x > w - 1 || y > h - 1){ + return oob_value; } - const double zoom_val = zoom->zoom; + uint8_t *row_y = data + iy * linesize; - const int in_w = in->width; - const int in_h = in->height; - const int in_f = in->format; + // top left + uint8_t *a11 = row_y + ix * pixelstep; + // top right = top left + 1px + uint8_t *a12 = a11 + pixelstep; - int out_w = in->width * zoom_val; - int out_h = in->height * zoom_val; - const int out_f = outlink->format; + // bottom left = top left + 1row + uint8_t *a21 = a11 + linesize; + // bottom right = bottom left + 1px + uint8_t *a22 = a21 + pixelstep; - const int fout_w = out->width; - const int fout_h = out->height; + // top interp + lin0 = ifracx * (*((uint16_t*)a11)) + fracx * (*((uint16_t*)a12)); + // bottom interp + lin1 = ifracx * (*((uint16_t*)a21)) + fracx * (*((uint16_t*)a22)); + // vertical interp + return ifracy * lin0 + fracy * lin1; +} - const double originalAspectRatio = 1.0 * in_w / in_h; - const double aspectRatio = zoom->outAspectRatio; - const double x = zoom->x; - const double y = zoom->y; +typedef struct float2 { + float x, y; +} float2; - if(out_h <= 0 || out_w <= 0) - goto bypass; +static inline float2 scale_coords_pxout_to_pxin(float2 pix_out, float2 dim_out, float ZOOM, float2 dim_in, float2 PAN) { - // todo there's surely a way to implement this without a temp frame - AVFrame* temp_frame = alloc_frame(out_f, out_w, out_h); - av_log(zoom, AV_LOG_DEBUG, "zoom: %.6f y: %.3f\n", zoom->zoom); - av_log(zoom, AV_LOG_DEBUG, "scaling: %dx%d -> %dx%d\n", in_w, in_h, out_w, out_h); + float2 pix_in; - av_opt_set_int(zoom->sws, "srcw", in_w, 0); - av_opt_set_int(zoom->sws, "srch", in_h, 0); - av_opt_set_int(zoom->sws, "src_format", in_f, 0); - av_opt_set_int(zoom->sws, "dstw", out_w, 0); - av_opt_set_int(zoom->sws, "dsth", out_h, 0); - av_opt_set_int(zoom->sws, "dst_format", out_f, 0); + if (ZOOM < 1) { + // canvas offset obj scaled center offset scaled px location + // px_out = dim_out * PAN - dim_in / 2 * ZOOM + px_in * ZOOM - if(zoom->interpolation) - av_opt_set_int(zoom->sws, "sws_flags", zoom->interpolation, 0); + // -dim_out * PAN + px_out = (-dim_in/2 + px_in) * ZOOM - if ((ret = sws_init_context(zoom->sws, NULL, NULL)) < 0) - goto error; + // (-dim_out * PAN + px_out) / ZOOM = -dim_in/2 + px_in - sws_scale(zoom->sws, (const uint8_t *const *)&in->data, in->linesize, 0, in_h, temp_frame->data, temp_frame->linesize); + // (-dim_out * PAN + px_out) / ZOOM + dim_in/2 = px_in - sws_freeContext(zoom->sws); - zoom->sws = NULL; + pix_in.x = (-dim_out.x * PAN.x + pix_out.x) / ZOOM + dim_in.x / 2; + pix_in.y = (-dim_out.y * PAN.y + pix_out.y) / ZOOM + dim_in.y / 2; + } + // zoom >= 1 + else { - av_log(zoom, AV_LOG_DEBUG, "x: %.3f y: %.3f\n", x, y); - const int dx = FFMIN(FFMAX(fout_w * x - out_w/2, 0), FFMAX(fout_w - out_w, 0)); - const int dy = FFMIN(FFMAX(fout_h * y - out_h/2, 0), FFMAX(fout_h - out_h, 0)); - av_log(zoom, AV_LOG_DEBUG, "dx: %d dy: %d\n", dx, dy); - av_log(zoom, AV_LOG_DEBUG, "in_w: %d in_h: %d\n", in_w, in_h); + pix_in.x = (pix_out.x - dim_out.x / 2.0f) / ZOOM + dim_in.x * PAN.x; + pix_in.y = (pix_out.y - dim_out.y / 2.0f) / ZOOM + dim_in.y * PAN.y; + } - ff_copy_rectangle2(&zoom->dc, - out->data, out->linesize, - temp_frame->data, temp_frame->linesize, - dx, dy, 0, 0, - FFMIN(out_w, fout_w - dx), - FFMIN(out_h, fout_h - dy)); + return pix_in; +} - av_frame_free(&temp_frame); -error: - return ret; -bypass: - return 0; -} +static inline float2 scale_coords_find_PAN(float2 pix_in, float2 pix_out, float2 dim_out, float ZOOM, float2 dim_in) { + float2 PAN; -static int zoom_in (ZoomContext *zoom, AVFrame *in, AVFrame *out, AVFilterLink *outlink) -{ - av_log(zoom, AV_LOG_DEBUG, "zoom in\n"); + if(ZOOM < 1){ + // taken from scale_coords_pxout_to_pxin + // pix_in = (-dim_out * PAN + pix_out) / ZOOM + dim_in / 2; + // pix_in - dim_in / 2 = (-dim_out * PAN + pix_out) / ZOOM + // (pix_in - dim_in / 2) * ZOOM = -dim_out * PAN + pix_out + // (pix_in - dim_in / 2) * ZOOM - pix_out = -dim_out * PAN + // ((pix_in - dim_in / 2) * ZOOM - pix_out) / (-dim_out) = PAN - int ret = 0; - zoom->sws = sws_alloc_context(); - if (!zoom->sws) { - ret = AVERROR(ENOMEM); - goto error; + PAN.x = ((pix_in.x - dim_in.x / 2.0f) * ZOOM - pix_out.x) / (-dim_out.x); + PAN.y = ((pix_in.y - dim_in.y / 2.0f) * ZOOM - pix_out.y) / (-dim_out.y); + } + // zoom >= 1 + else { + // taken from scale_coords_pxout_to_pxin + // pix_in = (pix_out - dim_out/2) / ZOOM + dim_in * PAN + // pix_in - (pix_out - dim_out/2) / ZOOM = dim_in * PAN + // (pix_in - (pix_out - dim_out/2) / ZOOM) / dim_in = PAN + + PAN.x = (pix_in.x - (pix_out.x - dim_out.x / 2.0f) / ZOOM) / dim_in.x; + PAN.y = (pix_in.y - (pix_out.y - dim_out.y / 2.0f) / ZOOM) / dim_in.y; } + return PAN; +} - const double zoom_val = zoom->zoom; +static inline float clampf(float val, float min, float max) { + if(val < min) return min; + if(val > max) return max; + return val; +} - int in_w = in->width / zoom_val; - int in_h = in->height / zoom_val; - const int in_f = in->format; +static inline float2 clamp_pan_inbounds(float2 PAN, float2 dim_out, float ZOOM, float2 dim_in) { + + float2 adjusted_dim_in = {dim_in.x * ZOOM, dim_in.y * ZOOM}; + + float2 top_left = scale_coords_find_PAN((float2){0.0f, 0.0f}, (float2){-1.0f, -1.0f}, dim_out, ZOOM, dim_in); + //float2 bottom_right = {1.0f - top_left.x, 1.0f - top_left.y}; + float2 bottom_right = scale_coords_find_PAN((float2){ dim_in.x + 0.0f, dim_in.y + 0.0f}, + (float2){dim_out.x + ZOOM, dim_out.y + ZOOM}, + dim_out, ZOOM, dim_in); + + float2 CLAMPED_PAN = {0.0f, 0.0f}; + + if(ZOOM < 1) { + // if it fits + if(adjusted_dim_in.x <= dim_out.x && adjusted_dim_in.y <= dim_out.y) { + CLAMPED_PAN.x = clampf(PAN.x, top_left.x, bottom_right.x); + CLAMPED_PAN.y = clampf(PAN.y, top_left.y, bottom_right.y); + } + // if it doesn't fix + else { + + CLAMPED_PAN.x = adjusted_dim_in.x > dim_out.x ? + // doesn't fit on W + FFMAX(FFMIN(1 - PAN.x, top_left.x), bottom_right.x): + // fits on W + FFMAX(FFMIN(PAN.y, bottom_right.x), top_left.x); + CLAMPED_PAN.y = adjusted_dim_in.y > dim_out.y ? + // doesn't fit on W + FFMAX(FFMIN(1 - PAN.y, top_left.y), bottom_right.y): + // fits on W + FFMAX(FFMIN(PAN.y, bottom_right.y), top_left.y); + + } + } else { + + CLAMPED_PAN.x = clampf(PAN.x, top_left.x, bottom_right.x); + CLAMPED_PAN.y = clampf(PAN.y, top_left.y, bottom_right.y); + } - const double originalAspectRatio = 1.0 * in_w / in_h; - const double aspectRatio = zoom->outAspectRatio; + return CLAMPED_PAN; +} - if(originalAspectRatio < aspectRatio){ - in_h = round(in_h * (originalAspectRatio / aspectRatio)); - }else{ - in_w = round(in_w * (aspectRatio / originalAspectRatio)); +typedef struct ZoomThreadData { + float2 PAN; + float2 dim_in; + float2 dim_out; + + uint8_t* in; + uint8_t* out; + + FFDrawColor *fillcolor; + + float ZOOM; + int plane; + int linesize_in; + int linesize_out; + int pix_step; + int pix_depth; + +} ZoomThreadData; + +static void apply_zoom_plane_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) { + ZoomThreadData *td = arg; + + float x, y; + int _x, _y; + + int h = td->dim_out.y; + int w = td->dim_out.x; + + const int slice_start = (h * jobnr) / nb_jobs; + const int slice_end = (h * (jobnr + 1)) / nb_jobs; + + // calculate origin, origin + (0, 1), origin + (1, 0) + float2 src_location_00 = scale_coords_pxout_to_pxin( + (float2){0.0f, 0.0f}, + td->dim_out, + td->ZOOM, + td->dim_in, + td->PAN + ); + float2 src_location_01 = scale_coords_pxout_to_pxin( + (float2){0.0f, 1.0f}, + td->dim_out, + td->ZOOM, + td->dim_in, + td->PAN + ); + float2 src_location_10 = scale_coords_pxout_to_pxin( + (float2){1.0f, 0.0f}, + td->dim_out, + td->ZOOM, + td->dim_in, + td->PAN + ); + + + + // calculate deltas on X and Y + // to use to loop over frame space + // we can do this since out transformation is a simple liniar translation + float source_x = src_location_00.x; + float delta_x = src_location_10.x - src_location_00.x; + + float source_y = src_location_00.y; + float delta_y = src_location_01.y - src_location_00.y; + + for(y = source_y + slice_start * delta_y, _y = slice_start; _y < slice_end; y += delta_y, _y++) { + for(x = source_x, _x = 0; _x < w; x+=delta_x, _x++) { + + if (td->pix_depth == 8) { + int8_t value = sample8_bilinear_at( + td->in, + td->linesize_in, + td->pix_step, + x, y, + td->dim_in.x, td->dim_in.y, + td->fillcolor->comp[td->plane].u8[0] // out of bounds value + ); + + int8_t *dst_pixel = td->out + + _y * td->linesize_out + + _x * td->pix_step; + + (*dst_pixel) = value; + } + else { + int16_t value = sample16_bilinear_at( + td->in, + td->linesize_in, + td->pix_step, + x, y, + td->dim_in.x, td->dim_in.y, + td->fillcolor->comp[td->plane].u16[0] // out of bounds value + ); + + int16_t *dst_pixel = td->out + + _y * td->linesize_out + + _x * td->pix_step; + + (*dst_pixel) = value; + } + } } - const int out_w = out->width; - const int out_h = out->height; - const int out_f = outlink->format; - - const double x = zoom->x; - const double y = zoom->y; - if(out_h <= 0 || out_w <= 0) - goto bypass; +} - av_log(zoom, AV_LOG_DEBUG, "original in_w: %d in_h: %d\n", in->width, in->height); - av_log(zoom, AV_LOG_DEBUG, "in_w: %d in_h: %d\n", in_w, in_h); - av_log(zoom, AV_LOG_DEBUG, "out_w: %d out_h: %d\n", out_w, out_h); - const double pix_x = in->width * x - in_w / 2.0; - const double pix_y = in->height * y - in_h / 2.0; +static int apply_zoom(ZoomContext *s, AVFilterLink *link, AVFrame *in, AVFrame *out, FFDrawColor *fillcolor){ + int x, y, plane; - const int dx = normalize_xy( - FFMIN(FFMAX(pix_x, 0), FFMAX(in->width - in_w, 0)), - zoom->hsub); - const int dy = normalize_xy( - FFMIN(FFMAX(pix_y, 0), FFMAX(in->height - in_h, 0)), - zoom->vsub); + int out_w = out->width; + int out_h = out->height; + int in_w = in->width; + int in_h = in->height; - av_log(zoom, AV_LOG_DEBUG, "x: %0.3f y: %0.3f\n", x, y); - av_log(zoom, AV_LOG_DEBUG, "pix_x: %.3f pix_y: %.3f\n", pix_x, pix_y); - av_log(zoom, AV_LOG_DEBUG, "dx: %d dy: %d\n", dx, dy); + int hsub = s->hsub; + int vsub = s->vsub; + const FFDrawContext *draw = &s->dc; + const struct AVPixFmtDescriptor *desc = draw->desc; - int px[4], py[4]; - uint8_t *input[4]; + float2 dim_in_full = {(float) in_w, (float) in_h}; + float2 dim_out_full = {(float)out_w, (float)out_h}; - uint8_t chroma_w = zoom->desc->log2_chroma_w; - uint8_t chroma_h = zoom->desc->log2_chroma_h; - av_log(zoom, AV_LOG_DEBUG, "chroma_w: %d chroma_h: %d\n", chroma_w, chroma_h); - av_log(zoom, AV_LOG_DEBUG, "l[0]: %d l[1]: %d l[2]: %d l[3]: %d \n", in->linesize[0], in->linesize[1],in->linesize[2],in->linesize[3]); - av_log(zoom, AV_LOG_DEBUG, "planes: %d\n", zoom->nb_planes); - av_log(zoom, AV_LOG_DEBUG, "components: %d\n", zoom->nb_components); + float2 dim_in_chroma = {(float)( in_w >> hsub), (float)( in_h >> vsub)}; + float2 dim_out_chroma = {(float)(out_w >> hsub), (float)(out_h >> vsub)}; - // cutoff top left - px[1] = px[2] = AV_CEIL_RSHIFT(dx, chroma_w); - // support for yuv*, rgb*, etc... (any components & planes) - px[0] = px[3] = dx * (1.0 * zoom->nb_components / zoom->nb_planes); + const float ZOOM = s->zoom; + const float2 UNCLAMPED_PAN = {s->x, s->y}; + float2 PAN = clamp_pan_inbounds(UNCLAMPED_PAN, dim_out_full, ZOOM, dim_in_full); - py[1] = py[2] = AV_CEIL_RSHIFT(dy, chroma_h); - py[0] = py[3] = dy; + av_log(s, AV_LOG_DEBUG, "ZOOM %.3f\n", ZOOM); + av_log(s, AV_LOG_DEBUG, "UNCLAMPED_PAN x %.3f y %.3f\n", UNCLAMPED_PAN.x, UNCLAMPED_PAN.y); + av_log(s, AV_LOG_DEBUG, "PAN x %.3f y %.3f\n", PAN.x, PAN.y); - for (int k = 0; in->data[k]; k++) - input[k] = in->data[k] + py[k] * in->linesize[k] + px[k]; + ZoomThreadData td; + td.PAN = PAN; + td.fillcolor = fillcolor; + td.ZOOM = ZOOM; - // stretching bottom right - av_opt_set_int(zoom->sws, "srcw", in_w, 0); - av_opt_set_int(zoom->sws, "srch", in_h, 0); - av_opt_set_int(zoom->sws, "src_format", in_f, 0); - av_opt_set_int(zoom->sws, "dstw", out_w, 0); - av_opt_set_int(zoom->sws, "dsth", out_h, 0); - av_opt_set_int(zoom->sws, "dst_format", out_f, 0); - if(zoom->interpolation) - av_opt_set_int(zoom->sws, "sws_flags", zoom->interpolation, 0); + for(plane = 0; plane < desc->nb_components; plane++){ + float2 dim_in = plane == 1 || plane == 2 ? dim_in_chroma : dim_in_full; + float2 dim_out = plane == 1 || plane == 2 ? dim_out_chroma : dim_out_full; - if ((ret = sws_init_context(zoom->sws, NULL, NULL)) < 0) - goto error; + td.dim_in = dim_in; + td.dim_out = dim_out; + td.in = in->data[plane]; + td.out = out->data[plane]; + td.plane = plane; + td.linesize_in = in->linesize[plane]; + td.linesize_out = out->linesize[plane]; + td.pix_step = desc->comp[plane].step; + td.pix_depth = desc->comp[plane].depth; - sws_scale(zoom->sws, (const uint8_t *const *)&input, in->linesize, 0, in_h, out->data, out->linesize); + link->dst->internal->execute(link->dst, apply_zoom_plane_slice, &td, NULL, FFMIN(out_h, ff_filter_get_nb_threads(link->dst))); - sws_freeContext(zoom->sws); - zoom->sws = NULL; + } - error: - return ret; - bypass: return 0; } @@ -536,41 +790,12 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) av_log(zoom, AV_LOG_WARNING, "x position %.2f is out of range of [0-1]\n", zoom->x); zoom->x = av_clipd_c(zoom->x, 0, 1); } - if(zoom->y < 0 || zoom->y > 1){ - av_log(zoom, AV_LOG_WARNING, "y position %.2f is out of range of [0-1]\n", zoom->y); - zoom->y = av_clipd_c(zoom->y, 0, 1); - } - // copy in the background - ff_fill_rectangle(&zoom->dc, &zoom->fillcolor, - out->data, out->linesize, - 0, 0, - out_w, out_h); - - // scale - if(zoom_val == 1) { - // it's 1, just copy - // quite an expensive noop :D - ff_copy_rectangle2(&zoom->dc, - out->data, out->linesize, - in->data, in->linesize, - 0, 0, - av_clip_c(in_w * zoom->x - out_w / 2.0, 0, in_w - out_w), - av_clip_c(in_h * zoom->y - out_h / 2.0, 0, in_h - out_h), - out_w, out_h); - } else if (zoom_val <= 0) { - // if it's 0 or lower do nothing - // noop - } else if (zoom_val < 1) { - // zoom in (0, 1) - ret = zoom_out(zoom, in, out, outlink); - if(ret) - goto error; - } else if (zoom_val > 1){ - // zoom in (1, +ing) - ret = zoom_in(zoom, in, out, outlink); - if(ret) - goto error; - } + if(zoom->y < 0 || zoom->y > 1){ + av_log(zoom, AV_LOG_WARNING, "y position %.2f is out of range of [0-1]\n", zoom->y); + zoom->y = av_clipd_c(zoom->y, 0, 1); + } + + apply_zoom(zoom, inlink, in, out, &zoom->fillcolor); av_frame_free(&in); return ff_filter_frame(outlink, out); @@ -621,5 +846,5 @@ AVFilter ff_vf_zoom = { .inputs = zoom_inputs, .outputs = zoom_outputs, .priv_class = &zoom_class, - .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, };