[FFmpeg-devel] [PATCH/WIP] lavfi: hqx filters.
Clément Bœsch
u at pkh.me
Sun Jun 8 23:15:17 CEST 2014
---
Only hq2x for now.
Original code for hq2x is about 2800 lines of unrolled code. Current filter is
about 300 lines of code. The code in hq2x_interp() is generated from
https://github.com/ubitux/hqx ("make code" - see also "make show"), which is
done by parsing, analyzing and cutting down hq2x.c and friends.
The same need to be done for hq3x and hq4x. I don't know how long it will take.
Help welcome for those interested.
See also Ticket #3404.
---
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vf_hqx.c | 306 ++++++++++++++++++++++++++++++++++++++++++++
tests/fate/filter-video.mak | 7 +
tests/ref/fate/filter-hq2x | 6 +
tests/ref/fate/filter-hq3x | 6 +
tests/ref/fate/filter-hq4x | 6 +
7 files changed, 333 insertions(+)
create mode 100644 libavfilter/vf_hqx.c
create mode 100644 tests/ref/fate/filter-hq2x
create mode 100644 tests/ref/fate/filter-hq3x
create mode 100644 tests/ref/fate/filter-hq4x
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 8ba0312..ea9815d 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -130,6 +130,7 @@ OBJS-$(CONFIG_HFLIP_FILTER) += vf_hflip.o
OBJS-$(CONFIG_HISTEQ_FILTER) += vf_histeq.o
OBJS-$(CONFIG_HISTOGRAM_FILTER) += vf_histogram.o
OBJS-$(CONFIG_HQDN3D_FILTER) += vf_hqdn3d.o
+OBJS-$(CONFIG_HQX_FILTER) += vf_hqx.o
OBJS-$(CONFIG_HUE_FILTER) += vf_hue.o
OBJS-$(CONFIG_IDET_FILTER) += vf_idet.o
OBJS-$(CONFIG_IL_FILTER) += vf_il.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 55d505a..7e1fd1d 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -148,6 +148,7 @@ void avfilter_register_all(void)
REGISTER_FILTER(HISTEQ, histeq, vf);
REGISTER_FILTER(HISTOGRAM, histogram, vf);
REGISTER_FILTER(HQDN3D, hqdn3d, vf);
+ REGISTER_FILTER(HQX, hqx, vf);
REGISTER_FILTER(HUE, hue, vf);
REGISTER_FILTER(IDET, idet, vf);
REGISTER_FILTER(IL, il, vf);
diff --git a/libavfilter/vf_hqx.c b/libavfilter/vf_hqx.c
new file mode 100644
index 0000000..4b83b07
--- /dev/null
+++ b/libavfilter/vf_hqx.c
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 2014 Clément Bœsch
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/opt.h"
+#include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
+#include "internal.h"
+
+typedef void (*hqxfunc_t)(const uint32_t *r2y,
+ uint8_t *dst, int dst_linesize,
+ const uint8_t *src, int src_linesize,
+ int w, int h);
+
+typedef struct {
+ const AVClass *class;
+ int n;
+ hqxfunc_t func;
+ uint32_t rgbtoyuv[1<<24];
+} HQXContext;
+
+#define OFFSET(x) offsetof(HQXContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+static const AVOption hqx_options[] = {
+ { "n", "set scale factor", OFFSET(n), AV_OPT_TYPE_INT, {.i64 = 4}, 2, 4, .flags = FLAGS },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(hqx);
+
+static av_always_inline uint32_t rgb2yuv(const uint32_t *r2y, uint32_t c)
+{
+ return r2y[c & 0xffffff];
+}
+
+static av_always_inline int yuv_diff(uint32_t yuv1, uint32_t yuv2)
+{
+#define YMASK 0xff0000
+#define UMASK 0x00ff00
+#define VMASK 0x0000ff
+#define TRESH_Y 0x300000
+#define TRESH_U 0x000700
+#define TRESH_V 0x000006
+ return abs((yuv1 & YMASK) - (yuv2 & YMASK)) > TRESH_Y ||
+ abs((yuv1 & UMASK) - (yuv2 & UMASK)) > TRESH_U ||
+ abs((yuv1 & VMASK) - (yuv2 & VMASK)) > TRESH_V;
+}
+
+static av_always_inline uint32_t interp_2px(uint32_t c1, int w1, uint32_t c2, int w2, int s)
+{
+ return (((((c1 & 0xff000000) >> 24) * w1 + ((c2 & 0xff000000) >> 24) * w2) << (24 - s)) & 0xff000000) |
+ (((((c1 & 0x00ff0000) >> 16) * w1 + ((c2 & 0x00ff0000) >> 16) * w2) << (16 - s)) & 0x00ff0000) |
+ (((((c1 & 0x0000ff00) >> 8) * w1 + ((c2 & 0x0000ff00) >> 8) * w2) << ( 8 - s)) & 0x0000ff00) |
+ (((((c1 & 0x000000ff) ) * w1 + ((c2 & 0x000000ff) ) * w2) >> s ) & 0x000000ff);
+}
+
+static av_always_inline uint32_t interp_3px(uint32_t c1, int w1, uint32_t c2, int w2, uint32_t c3, int w3, int s)
+{
+ return (((((c1 & 0xff000000) >> 24) * w1 + ((c2 & 0xff000000) >> 24) * w2 + ((c3 & 0xff000000) >> 24) * w3) << (24 - s)) & 0xff000000) |
+ (((((c1 & 0x00ff0000) >> 16) * w1 + ((c2 & 0x00ff0000) >> 16) * w2 + ((c3 & 0x00ff0000) >> 16) * w3) << (16 - s)) & 0x00ff0000) |
+ (((((c1 & 0x0000ff00) >> 8) * w1 + ((c2 & 0x0000ff00) >> 8) * w2 + ((c3 & 0x0000ff00) >> 8) * w3) << ( 8 - s)) & 0x0000ff00) |
+ (((((c1 & 0x000000ff) ) * w1 + ((c2 & 0x000000ff) ) * w2 + ((c3 & 0x000000ff) ) * w3) >> s ) & 0x000000ff);
+}
+
+static av_always_inline uint32_t hq2x_interp(const uint32_t *r2y, int k,
+ const uint32_t *w,
+ int p0, int p1, int p2,
+ int p3, int p4, int p5,
+ int p6, int p7, int p8)
+{
+/* m is the mask of diff with the center pixel that matters in the pattern, and
+ * r is the expected result (bit set to 1 if there is difference with the
+ * center, 0 otherwise */
+#define P(m, r) ((k_transposed & (m)) == (r))
+/* adjust 012345678 to 01235678: the mask doesn't contain the (null) diff
+ * between the center/current pixel and itself */
+#define DROP4(z) ((z) > 4 ? (z)-1 : (z))
+/* transpose the input mask */
+#define TRP(x, v, n) (((x) >> (7-(DROP4(v))) & 1) << (n))
+
+#define WDIFF(c1, c2) yuv_diff(rgb2yuv(r2y, c1), rgb2yuv(r2y, c2))
+
+ const int k_transposed = TRP(k,p0,7) | TRP(k,p1,6) | TRP(k,p2,5)
+ | TRP(k,p3,4) | 0 | TRP(k,p5,3)
+ | TRP(k,p6,2) | TRP(k,p7,1) | TRP(k,p8,0);
+
+ const uint32_t w0 = w[p0], w1 = w[p1],
+ w3 = w[p3], w4 = w[p4], w5 = w[p5],
+ w7 = w[p7];
+
+ if ((P(0xbf,0x37) || P(0xdb,0x13)) && WDIFF(w1, w5))
+ return interp_2px(w4, 3, w3, 1, 2);
+ if ((P(0xef,0x6d) || P(0xdb,0x49)) && WDIFF(w7, w3))
+ return interp_2px(w4, 3, w1, 1, 2);
+ if ((P(0x0b,0x0b) || P(0xfe,0x1a) || P(0xfe,0x4a)) && WDIFF(w3, w1))
+ return w4;
+ if ((P(0xeb,0x8a) || P(0xaf,0x8a) || P(0x7f,0x5a) || P(0xcf,0x8a) || P(0x6f,0x2a) || P(0xbb,0x8a) || P(0x3f,0x0e) || P(0x5b,0x0a) || P(0x9f,0x8a) || P(0xfb,0x5a) || P(0xef,0x4e) || P(0xdf,0x5a) || P(0xbf,0x3a)) && WDIFF(w3, w1))
+ return interp_2px(w4, 3, w0, 1, 2);
+ if (P(0x0b,0x08))
+ return interp_3px(w4, 2, w0, 1, w1, 1, 2);
+ if (P(0x0b,0x02))
+ return interp_3px(w4, 2, w0, 1, w3, 1, 2);
+ if (P(0x2f,0x2f))
+ return interp_3px(w4, 14, w3, 1, w1, 1, 4);
+ if (P(0xbf,0x37) || P(0xdb,0x13))
+ return interp_3px(w4, 5, w1, 2, w3, 1, 3);
+ if (P(0xef,0x6d) || P(0xdb,0x49))
+ return interp_3px(w4, 5, w3, 2, w1, 1, 3);
+ if (P(0x8b,0x83) || P(0x6b,0x43) || P(0x4f,0x43) || P(0x1b,0x03))
+ return interp_2px(w4, 3, w3, 1, 2);
+ if (P(0x8b,0x89) || P(0x3b,0x19) || P(0x4b,0x09) || P(0x1f,0x19))
+ return interp_2px(w4, 3, w1, 1, 2);
+ if (P(0xef,0xab) || P(0xbf,0x8f) || P(0x7e,0x0e) || P(0x7e,0x2a))
+ return interp_3px(w4, 2, w3, 3, w1, 3, 3);
+ if (P(0xdf,0xde) || P(0xfb,0xfa) || P(0x3f,0x3e) || P(0x6f,0x6e) || P(0xdf,0x1e) || P(0xfb,0x6a))
+ return interp_2px(w4, 3, w0, 1, 2);
+ if (P(0x0a,0x00) || P(0x3b,0x1b) || P(0x4f,0x4b) || P(0x2f,0x0b) || P(0x7e,0x0a) || P(0xeb,0x4b) || P(0x9f,0x1b) || P(0xee,0x0a) || P(0xbe,0x0a))
+ return interp_3px(w4, 2, w3, 1, w1, 1, 2);
+ return interp_3px(w4, 6, w3, 1, w1, 1, 3);
+}
+
+static av_always_inline void hqx_filter(const uint32_t *r2y,
+ uint8_t *dst, uint32_t dst_linesize,
+ const uint8_t *src, uint32_t src_linesize,
+ int width, int height, int n)
+{
+ int i, j, k;
+ const int dst32_linesize = dst_linesize >> 2;
+ const int src32_linesize = src_linesize >> 2;
+
+ for (j = 0; j < height; j++) {
+ const uint32_t *src32 = (const uint32_t *)src;
+ uint32_t *dst32 = (uint32_t *)dst;
+ const int prevline = j > 0 ? -src32_linesize : 0;
+ const int nextline = j < height - 1 ? src32_linesize : 0;
+
+ for (i = 0; i < width; i++) {
+ uint32_t yuv1, yuv2;
+ const int prevcol = i > 0 ? -1 : 0;
+ const int nextcol = i < width -1 ? 1 : 0;
+ int pattern = 0, flag = 1;
+ const uint32_t w[3*3] = {
+ src32[prevcol + prevline], src32[prevline], src32[prevline + nextcol],
+ src32[prevcol ], src32[ 0], src32[ nextcol],
+ src32[prevcol + nextline], src32[nextline], src32[nextline + nextcol]
+ };
+
+ yuv1 = rgb2yuv(r2y, w[4]);
+
+ for (k = 0; k < FF_ARRAY_ELEMS(w); k++) {
+ if (k == 4)
+ continue;
+ if (w[k] != w[4]) {
+ yuv2 = rgb2yuv(r2y, w[k]);
+ if (yuv_diff(yuv1, yuv2))
+ pattern |= flag;
+ }
+ flag <<= 1;
+ }
+
+ if (n == 2) {
+ dst32[ 0] = hq2x_interp(r2y, pattern, w, 0,1,2,3,4,5,6,7,8); // 00
+ dst32[ 1] = hq2x_interp(r2y, pattern, w, 2,5,8,1,4,7,0,3,6); // 01
+ dst32[dst32_linesize+0] = hq2x_interp(r2y, pattern, w, 6,3,0,7,4,1,8,5,2); // 10
+ dst32[dst32_linesize+1] = hq2x_interp(r2y, pattern, w, 8,7,6,5,4,3,2,1,0); // 11
+ } else if (n == 3) {
+ // TODO
+ av_assert0(0);
+ } else if (n == 4) {
+ // TODO
+ av_assert0(0);
+ } else {
+ av_assert0(0);
+ }
+
+ src32 += 1;
+ dst32 += n;
+ }
+
+ src += src_linesize;
+ dst += dst_linesize * n;
+
+ }
+}
+
+#define HQX_FUNC(size) \
+static void hq##size##x(const uint32_t *r2y, \
+ uint8_t *dst, int dst_linesize, \
+ const uint8_t *src, int src_linesize, \
+ int w, int h) \
+{ \
+ hqx_filter(r2y, dst, dst_linesize, src, src_linesize, w, h, size); \
+}
+
+HQX_FUNC(2)
+HQX_FUNC(3)
+HQX_FUNC(4)
+
+static int query_formats(AVFilterContext *ctx)
+{
+ static const enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_BGRA, AV_PIX_FMT_NONE};
+ ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
+ return 0;
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+ AVFilterContext *ctx = outlink->src;
+ HQXContext *hqx = ctx->priv;
+ AVFilterLink *inlink = ctx->inputs[0];
+
+ outlink->w = inlink->w * hqx->n;
+ outlink->h = inlink->h * hqx->n;
+ av_log(inlink->dst, AV_LOG_VERBOSE, "fmt:%s size:%dx%d -> size:%dx%d\n",
+ av_get_pix_fmt_name(inlink->format),
+ inlink->w, inlink->h, outlink->w, outlink->h);
+ return 0;
+}
+
+static int filter_frame(AVFilterLink *inlink, AVFrame *in)
+{
+ AVFilterContext *ctx = inlink->dst;
+ AVFilterLink *outlink = ctx->outputs[0];
+ HQXContext *hqx = ctx->priv;
+ AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+ if (!out) {
+ av_frame_free(&in);
+ return AVERROR(ENOMEM);
+ }
+ av_frame_copy_props(out, in);
+ out->width = outlink->w;
+ out->height = outlink->h;
+
+ hqx->func(hqx->rgbtoyuv,
+ out->data[0], out->linesize[0],
+ in ->data[0], in ->linesize[0],
+ inlink->w, inlink->h);
+
+ av_frame_free(&in);
+ return ff_filter_frame(outlink, out);
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+ HQXContext *hqx = ctx->priv;
+ static const hqxfunc_t hqxfuncs[] = {hq2x, hq3x, hq4x};
+
+ uint32_t c, r, g, b, y, u, v;
+ for (c = 0; c <= FF_ARRAY_ELEMS(hqx->rgbtoyuv); c++) {
+ r = c >> 16 & 0xff;
+ g = c >> 8 & 0xff;
+ b = c & 0xff;
+ y = (uint32_t)( 0.299*r + 0.587*g + 0.114*b);
+ u = (uint32_t)(-0.169*r - 0.331*g + 0.5*b) + 128;
+ v = (uint32_t)( 0.5*r - 0.419*g - 0.081*b) + 128;
+ hqx->rgbtoyuv[c] = (y << 16) + (u << 8) + v;
+ }
+
+ hqx->func = hqxfuncs[hqx->n - 2];
+ return 0;
+}
+
+static const AVFilterPad hqx_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = filter_frame,
+ },
+ { NULL }
+};
+
+static const AVFilterPad hqx_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .config_props = config_output,
+ },
+ { NULL }
+};
+
+AVFilter ff_vf_hqx = {
+ .name = "hqx",
+ .description = NULL_IF_CONFIG_SMALL("Scale the input by 2, 3 or 4 using the hq*x magnification algorithm."),
+ .priv_size = sizeof(HQXContext),
+ .init = init,
+ .query_formats = query_formats,
+ .inputs = hqx_inputs,
+ .outputs = hqx_outputs,
+ .priv_class = &hqx_class,
+};
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index 08349ce..0624444 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -140,6 +140,13 @@ FATE_FILTER-$(call ALLYES, SMJPEG_DEMUXER MJPEG_DECODER PERMS_FILTER HQDN3D_FILT
fate-filter-hqdn3d-sample: tests/data/filtergraphs/hqdn3d
fate-filter-hqdn3d-sample: CMD = framecrc -idct simple -i $(TARGET_SAMPLES)/smjpeg/scenwin.mjpg -filter_complex_script $(TARGET_PATH)/tests/data/filtergraphs/hqdn3d -an
+FATE_FILTER_HQX-$(call ALLYES, IMAGE2_DEMUXER PNG_DECODER HQX_FILTER) = fate-filter-hq4x fate-filter-hq3x fate-filter-hq2x
+FATE_FILTER-yes += $(FATE_FILTER_HQX-yes)
+fate-filter-hq4x: CMD = framemd5 -i $(TARGET_SAMPLES)/filter/hqx.png -vf hqx=2
+fate-filter-hq3x: CMD = framemd5 -i $(TARGET_SAMPLES)/filter/hqx.png -vf hqx=2
+fate-filter-hq2x: CMD = framemd5 -i $(TARGET_SAMPLES)/filter/hqx.png -vf hqx=2
+fate-filter-hqx: $(FATE_FILTER_HQX-yes)
+
FATE_FILTER-$(call ALLYES, UTVIDEO_DECODER AVI_DEMUXER PERMS_FILTER CURVES_FILTER) += fate-filter-curves
fate-filter-curves: CMD = framecrc -i $(TARGET_SAMPLES)/utvideo/utvideo_rgb_median.avi -vf perms=random,curves=vintage
diff --git a/tests/ref/fate/filter-hq2x b/tests/ref/fate/filter-hq2x
new file mode 100644
index 0000000..92391b6
--- /dev/null
+++ b/tests/ref/fate/filter-hq2x
@@ -0,0 +1,6 @@
+#format: frame checksums
+#version: 1
+#hash: MD5
+#tb 0: 1/25
+#stream#, dts, pts, duration, size, hash
+0, 0, 0, 1, 131072, 66f8f4e12c37534c2c6dbf715203290d
diff --git a/tests/ref/fate/filter-hq3x b/tests/ref/fate/filter-hq3x
new file mode 100644
index 0000000..92391b6
--- /dev/null
+++ b/tests/ref/fate/filter-hq3x
@@ -0,0 +1,6 @@
+#format: frame checksums
+#version: 1
+#hash: MD5
+#tb 0: 1/25
+#stream#, dts, pts, duration, size, hash
+0, 0, 0, 1, 131072, 66f8f4e12c37534c2c6dbf715203290d
diff --git a/tests/ref/fate/filter-hq4x b/tests/ref/fate/filter-hq4x
new file mode 100644
index 0000000..92391b6
--- /dev/null
+++ b/tests/ref/fate/filter-hq4x
@@ -0,0 +1,6 @@
+#format: frame checksums
+#version: 1
+#hash: MD5
+#tb 0: 1/25
+#stream#, dts, pts, duration, size, hash
+0, 0, 0, 1, 131072, 66f8f4e12c37534c2c6dbf715203290d
--
2.0.0
More information about the ffmpeg-devel
mailing list