/* GStreamer * Copyright (C) <2014> Wim Taymans * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "video-resampler.h" #ifndef GST_DISABLE_GST_DEBUG #define GST_CAT_DEFAULT ensure_debug_category() static GstDebugCategory * ensure_debug_category (void) { static gsize cat_gonce = 0; if (g_once_init_enter (&cat_gonce)) { gsize cat_done; cat_done = (gsize) _gst_debug_category_new ("video-resampler", 0, "video-resampler object"); g_once_init_leave (&cat_gonce, cat_done); } return (GstDebugCategory *) cat_gonce; } #else #define ensure_debug_category() /* NOOP */ #endif /* GST_DISABLE_GST_DEBUG */ /** * SECTION:gstvideoresampler * @title: GstVideoResampler * @short_description: Utility structure for resampler information * * #GstVideoResampler is a structure which holds the information * required to perform various kinds of resampling filtering. * */ #define DEFAULT_OPT_CUBIC_B (1.0 / 3.0) #define DEFAULT_OPT_CUBIC_C (1.0 / 3.0) #define DEFAULT_OPT_ENVELOPE 2.0 #define DEFAULT_OPT_SHARPNESS 1.0 #define DEFAULT_OPT_SHARPEN 0.0 #define DEFAULT_OPT_MAX_TAPS 128 typedef struct _ResamplerParams ResamplerParams; struct _ResamplerParams { GstVideoResamplerMethod method; GstVideoResamplerFlags flags; gdouble shift; gdouble (*get_tap) (ResamplerParams * params, gint l, gint xi, gdouble x); /* for cubic */ gdouble b, c; /* used by lanczos */ gdouble ex, fx, dx; /* extra params */ gdouble envelope; gdouble sharpness; gdouble sharpen; GstVideoResampler *resampler; }; static gdouble get_opt_double (GstStructure * options, const gchar * name, gdouble def) { gdouble res; if (!options || !gst_structure_get_double (options, name, &res)) res = def; return res; } static gint get_opt_int (GstStructure * options, const gchar * name, gint def) { gint res; if (!options || !gst_structure_get_int (options, name, &res)) res = def; return res; } #define GET_OPT_CUBIC_B(options) get_opt_double(options, \ GST_VIDEO_RESAMPLER_OPT_CUBIC_B, DEFAULT_OPT_CUBIC_B) #define GET_OPT_CUBIC_C(options) get_opt_double(options, \ GST_VIDEO_RESAMPLER_OPT_CUBIC_C, DEFAULT_OPT_CUBIC_C) #define GET_OPT_ENVELOPE(options) get_opt_double(options, \ GST_VIDEO_RESAMPLER_OPT_ENVELOPE, DEFAULT_OPT_ENVELOPE) #define GET_OPT_SHARPNESS(options) get_opt_double(options, \ GST_VIDEO_RESAMPLER_OPT_SHARPNESS, DEFAULT_OPT_SHARPNESS) #define GET_OPT_SHARPEN(options) get_opt_double(options, \ GST_VIDEO_RESAMPLER_OPT_SHARPEN, DEFAULT_OPT_SHARPEN) #define GET_OPT_MAX_TAPS(options) get_opt_int(options, \ GST_VIDEO_RESAMPLER_OPT_MAX_TAPS, DEFAULT_OPT_MAX_TAPS) static double sinc (double x) { if (x == 0) return 1; return sin (G_PI * x) / (G_PI * x); } static double envelope (double x) { if (x <= -1 || x >= 1) return 0; return sinc (x); } static gdouble get_nearest_tap (ResamplerParams * params, gint l, gint xi, gdouble x) { return 1.0; } static gdouble get_linear_tap (ResamplerParams * params, gint l, gint xi, gdouble x) { gdouble res, a; gint xl = xi + l; a = fabs (x - xl) * params->fx; if (a < 1.0) res = 1.0 - a; else res = 0.0; return res; } static gdouble get_cubic_tap (ResamplerParams * params, gint l, gint xi, gdouble x) { gdouble a, a2, a3, b, c; gint xl = xi + l; a = fabs (x - xl) * params->fx; a2 = a * a; a3 = a2 * a; b = params->b; c = params->c; if (a <= 1.0) return ((12.0 - 9.0 * b - 6.0 * c) * a3 + (-18.0 + 12.0 * b + 6.0 * c) * a2 + (6.0 - 2.0 * b)) / 6.0; else if (a <= 2.0) return ((-b - 6.0 * c) * a3 + (6.0 * b + 30.0 * c) * a2 + (-12.0 * b - 48.0 * c) * a + (8.0 * b + 24.0 * c)) / 6.0; else return 0.0; } static gdouble get_sinc_tap (ResamplerParams * params, gint l, gint xi, gdouble x) { gint xl = xi + l; return sinc ((x - xl) * params->fx); } static gdouble get_lanczos_tap (ResamplerParams * params, gint l, gint xi, gdouble x) { gint xl = xi + l; gdouble env = envelope ((x - xl) * params->ex); return (sinc ((x - xl) * params->fx) - params->sharpen) * env; } static void resampler_calculate_taps (ResamplerParams * params) { GstVideoResampler *resampler = params->resampler; gint j; guint32 *offset, *n_taps, *phase; gint tap_offs; gint max_taps; gint in_size, out_size; gdouble shift; gdouble corr; in_size = resampler->in_size; out_size = resampler->out_size; max_taps = resampler->max_taps; tap_offs = (max_taps - 1) / 2; corr = (max_taps == 1 ? 0.0 : 0.5); shift = params->shift; resampler->taps = g_malloc (sizeof (gdouble) * max_taps * out_size); n_taps = resampler->n_taps = g_malloc (sizeof (guint32) * out_size); offset = resampler->offset = g_malloc (sizeof (guint32) * out_size); phase = resampler->phase = g_malloc (sizeof (guint32) * out_size); for (j = 0; j < out_size; j++) { gdouble ox, x; gint xi; gint l; gdouble weight; gdouble *taps; /* center of the output pixel */ ox = (0.5 + (gdouble) j - shift) / out_size; /* x is the source pixel to use, can be fractional */ x = ox * (gdouble) in_size - corr; x = CLAMP (x, 0, in_size - 1); /* this is the first source pixel to use */ xi = floor (x - tap_offs); offset[j] = xi; phase[j] = j; n_taps[j] = max_taps; weight = 0; taps = resampler->taps + j * max_taps; for (l = 0; l < max_taps; l++) { taps[l] = params->get_tap (params, l, xi, x); weight += taps[l]; } for (l = 0; l < max_taps; l++) taps[l] /= weight; if (xi < 0) { gint sh = -xi; for (l = 0; l < sh; l++) { taps[sh] += taps[l]; } for (l = 0; l < max_taps - sh; l++) { taps[l] = taps[sh + l]; } for (; l < max_taps; l++) { taps[l] = 0; } offset[j] += sh; } if (xi > in_size - max_taps) { gint sh = xi - (in_size - max_taps); for (l = 0; l < sh; l++) { taps[max_taps - sh - 1] += taps[max_taps - sh + l]; } for (l = 0; l < max_taps - sh; l++) { taps[max_taps - 1 - l] = taps[max_taps - 1 - sh - l]; } for (l = 0; l < sh; l++) { taps[l] = 0; } offset[j] -= sh; } } } static void resampler_dump (GstVideoResampler * resampler) { #if 0 gint i, max_taps, out_size; out_size = resampler->out_size; max_taps = resampler->max_taps; for (i = 0; i < out_size; i++) { gint j, o, phase, n_taps; gdouble sum; o = resampler->offset[i]; n_taps = resampler->n_taps[i]; phase = resampler->phase[i]; printf ("%u: \t%d ", i, o); sum = 0; for (j = 0; j < n_taps; j++) { gdouble tap; tap = resampler->taps[phase * max_taps + j]; printf ("\t%f ", tap); sum += tap; } printf ("\t: sum %f\n", sum); } #endif } /** * gst_video_resampler_new: * @resampler: a #GstVideoResampler * @method: a #GstVideoResamplerMethod * @flags: #GstVideoResamplerFlags * @n_phases: number of phases to use * @n_taps: number of taps to use * @in_size: number of source elements * @out_size: number of destination elements * @options: extra options * * Make a new resampler. @in_size source elements will * be resampled to @out_size destination elements. * * @n_taps specifies the amount of elements to use from the source for one output * element. If n_taps is 0, this function chooses a good value automatically based * on the @method and @in_size/@out_size. * * Returns: %TRUE on success * * Since: 1.6 */ gboolean gst_video_resampler_init (GstVideoResampler * resampler, GstVideoResamplerMethod method, GstVideoResamplerFlags flags, guint n_phases, guint n_taps, gdouble shift, guint in_size, guint out_size, GstStructure * options) { ResamplerParams params; gint max_taps; gdouble scale_factor; g_return_val_if_fail (in_size != 0, FALSE); g_return_val_if_fail (out_size != 0, FALSE); g_return_val_if_fail (n_phases == out_size, FALSE); resampler->in_size = in_size; resampler->out_size = out_size; resampler->n_phases = n_phases; params.method = method; params.flags = flags; params.shift = shift; params.resampler = resampler; GST_DEBUG ("%d %u %u->%u", method, n_taps, in_size, out_size); params.sharpness = GET_OPT_SHARPNESS (options); params.sharpen = GET_OPT_SHARPEN (options); scale_factor = in_size / (gdouble) out_size; if (scale_factor > 1.0) { params.fx = (1.0 / scale_factor) * params.sharpness; } else { params.fx = (1.0) * params.sharpness; } max_taps = GET_OPT_MAX_TAPS (options); n_taps = MIN (n_taps, max_taps); switch (method) { case GST_VIDEO_RESAMPLER_METHOD_NEAREST: params.envelope = GET_OPT_ENVELOPE (options); params.get_tap = get_nearest_tap; if (n_taps == 0) n_taps = 1; break; case GST_VIDEO_RESAMPLER_METHOD_LINEAR: params.get_tap = get_linear_tap; params.envelope = 1.0; break; case GST_VIDEO_RESAMPLER_METHOD_CUBIC: params.b = GET_OPT_CUBIC_B (options); params.c = GET_OPT_CUBIC_C (options); params.envelope = 2.0; params.get_tap = get_cubic_tap; break; case GST_VIDEO_RESAMPLER_METHOD_SINC: params.envelope = GET_OPT_ENVELOPE (options); params.get_tap = get_sinc_tap; break; case GST_VIDEO_RESAMPLER_METHOD_LANCZOS: params.envelope = GET_OPT_ENVELOPE (options); params.get_tap = get_lanczos_tap; break; default: break; } if (n_taps == 0) { params.dx = ceil (2.0 * params.envelope / params.fx); n_taps = CLAMP (params.dx, 0, max_taps); } if (flags & GST_VIDEO_RESAMPLER_FLAG_HALF_TAPS && n_taps > 3) n_taps /= 2; params.fx = 2.0 * params.envelope / n_taps; params.ex = 2.0 / n_taps; if (n_taps > in_size) n_taps = in_size; resampler->max_taps = n_taps; resampler_calculate_taps (¶ms); resampler_dump (resampler); return TRUE; } /** * gst_video_resampler_clear: * @resampler: a #GstVideoResampler * * Clear a previously initialized #GstVideoResampler @resampler. * * Since: 1.6 */ void gst_video_resampler_clear (GstVideoResampler * resampler) { g_return_if_fail (resampler != NULL); g_free (resampler->phase); g_free (resampler->offset); g_free (resampler->n_taps); g_free (resampler->taps); }