/* This file is part of GEGL * * GEGL 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 3 of the License, or (at your option) any later version. * * GEGL 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 GEGL; if not, see . * * Copyright 2012 Nicolas Robidoux based on earlier code * 2012 Massimo Valentini * 2018 Øyvind Kolås */ #include "config.h" #include #include #include "gegl-buffer.h" #include "gegl-buffer-formats.h" #include "gegl-sampler-cubic.h" enum { PROP_0, PROP_B, PROP_C, PROP_TYPE, PROP_LAST }; static void gegl_sampler_cubic_finalize ( GObject *gobject); static inline void gegl_sampler_cubic_interpolate ( GeglSampler* restrict self, const gdouble absolute_x, const gdouble absolute_y, gfloat* restrict output, GeglAbyssPolicy repeat_mode); static void gegl_sampler_cubic_get ( GeglSampler* restrict self, const gdouble absolute_x, const gdouble absolute_y, GeglBufferMatrix2* scale, void* restrict output, GeglAbyssPolicy repeat_mode); static void get_property ( GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec); static void set_property ( GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec); static inline gfloat cubicKernel (const gfloat x, const gfloat b, const gfloat c); G_DEFINE_TYPE (GeglSamplerCubic, gegl_sampler_cubic, GEGL_TYPE_SAMPLER) static void gegl_sampler_cubic_class_init (GeglSamplerCubicClass *klass) { GeglSamplerClass *sampler_class = GEGL_SAMPLER_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->set_property = set_property; object_class->get_property = get_property; object_class->finalize = gegl_sampler_cubic_finalize; sampler_class->get = gegl_sampler_cubic_get; sampler_class->interpolate = gegl_sampler_cubic_interpolate; g_object_class_install_property ( object_class, PROP_B, g_param_spec_double ("b", "B", "B-spline parameter", 0.0, 1.0, 1.0, G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_C, g_param_spec_double ("c", "C", "C-spline parameter", 0.0, 1.0, 0.0, G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_TYPE, g_param_spec_string ("type", "type", "B-spline type (cubic | catmullrom | formula) 2c+b=1", "cubic", G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); } static void gegl_sampler_cubic_finalize (GObject *object) { g_free (GEGL_SAMPLER_CUBIC (object)->type); G_OBJECT_CLASS (gegl_sampler_cubic_parent_class)->finalize (object); } static void gegl_sampler_cubic_init (GeglSamplerCubic *self) { /* * In principle, x=y=-1 and width=height=4 are enough. The following * values are chosen so as to make the context_rect symmetrical * w.r.t. the anchor point. This is so that enough elbow room is * added with transformations that reflect the context rect. If the * context_rect is not symmetrical, the transformation may turn * right into left, and if the context_rect does not stretch far * enough on the left, pixel lookups will fail. */ GEGL_SAMPLER (self)->level[0].context_rect.x = -2; GEGL_SAMPLER (self)->level[0].context_rect.y = -2; GEGL_SAMPLER (self)->level[0].context_rect.width = 5; GEGL_SAMPLER (self)->level[0].context_rect.height = 5; self->b=0.5; /* 0.0 = sharp, but with anomaly of issue #167 1.0 = fuzzy cubic, without anomaly 0.5 is a compromise against issue #145 */ /* * This ensures that the spline is a Keys spline. The c of * BC-splines is the alpha of Keys. */ self->c = 0.5 * (1.0 - self->b); } static inline void gegl_sampler_cubic_interpolate ( GeglSampler *self, const gdouble absolute_x, const gdouble absolute_y, gfloat *output, GeglAbyssPolicy repeat_mode) { GeglSamplerCubic *cubic = (GeglSamplerCubic*)(self); gint components = self->interpolate_components; gfloat cubic_b = cubic->b; gfloat cubic_c = cubic->c; gfloat *sampler_bptr; gfloat factor_i[4]; gint c; gint i; gint j; /* * The "-1/2"s are there because we want the index of the pixel * center to the left and top of the location, and with GIMP's * convention the top left of the top left pixel is located at * (0,0), and its center is at (1/2,1/2), so that anything less than * 1/2 needs to go negative. Another way to look at this is that we * are converting from a coordinate system in which the origin is at * the top left corner of the pixel with index (0,0), to a * coordinate system in which the origin is at the center of the * same pixel. */ const double iabsolute_x = (double) absolute_x - 0.5; const double iabsolute_y = (double) absolute_y - 0.5; const gint ix = int_floorf (iabsolute_x); const gint iy = int_floorf (iabsolute_y); /* * x is the x-coordinate of the sampling point relative to the * position of the center of the top left pixel. Similarly for * y. Range of values: [0,1]. */ const gfloat x = iabsolute_x - ix; const gfloat y = iabsolute_y - iy; sampler_bptr = gegl_sampler_get_ptr (self, ix, iy, repeat_mode) - (GEGL_SAMPLER_MAXIMUM_WIDTH + 1) * components; for (c = 0; c < components; c++) output[c] = 0.0f; for (i = 0; i < 4; i++) factor_i[i] = cubicKernel (x - (i - 1), cubic_b, cubic_c); for (j = 0; j < 4; j++) { gfloat factor_j = cubicKernel (y - (j - 1), cubic_b, cubic_c); for (i = 0; i < 4; i++) { const gfloat factor = factor_j * factor_i[i]; for (c = 0; c < components; c++) output[c] += factor * sampler_bptr[c]; sampler_bptr += components; } sampler_bptr += (GEGL_SAMPLER_MAXIMUM_WIDTH - 4) * components; } } static void gegl_sampler_cubic_get ( GeglSampler *self, const gdouble absolute_x, const gdouble absolute_y, GeglBufferMatrix2 *scale, void *output, GeglAbyssPolicy repeat_mode) { if (! _gegl_sampler_box_get (self, absolute_x, absolute_y, scale, output, repeat_mode, 5)) { gfloat result[5]; gegl_sampler_cubic_interpolate (self, absolute_x, absolute_y, result, repeat_mode); #if (BABL_MINOR_VERSION >=1) && (BABL_MICRO_VERSION >= 90) self->fish_process #else babl_process #endif (self->fish, (void*)result, (void*)output, 1); } } static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GeglSamplerCubic *self = GEGL_SAMPLER_CUBIC (object); switch (prop_id) { case PROP_B: g_value_set_double (value, self->b); break; case PROP_TYPE: g_value_set_string (value, self->type); break; default: break; } } static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GeglSamplerCubic *self = GEGL_SAMPLER_CUBIC (object); switch (prop_id) { case PROP_B: self->b = g_value_get_double (value); gegl_sampler_cubic_init (self); break; case PROP_TYPE: if (self->type) g_free (self->type); self->type = g_value_dup_string (value); gegl_sampler_cubic_init (self); break; default: break; } } static inline gfloat int_fabsf (const gfloat x) { union {gfloat f; guint32 i;} u = {x}; u.i &= 0x7fffffff; return u.f; } static inline gfloat cubicKernel (const gfloat x, const gfloat b, const gfloat c) { const gfloat x2 = x*x; const gfloat ax = int_fabsf (x); if (x2 <= (gfloat) 1.f) return ( (gfloat) ((12-9*b-6*c)/6) * ax + (gfloat) ((-18+12*b+6*c)/6) ) * x2 + (gfloat) ((6-2*b)/6); if (x2 < (gfloat) 4.f) return ( (gfloat) ((-b-6*c)/6) * ax + (gfloat) ((6*b+30*c)/6) ) * x2 + (gfloat) ((-12*b-48*c)/6) * ax + (gfloat) ((8*b+24*c)/6); return (gfloat) 0.f; }