/* -*- mode: c++; c-basic-offset: 4 -*- */ #ifndef MPL_RESAMPLE_H #define MPL_RESAMPLE_H #include "agg_image_accessors.h" #include "agg_path_storage.h" #include "agg_pixfmt_gray.h" #include "agg_pixfmt_rgb.h" #include "agg_pixfmt_rgba.h" #include "agg_renderer_base.h" #include "agg_renderer_scanline.h" #include "agg_rasterizer_scanline_aa.h" #include "agg_scanline_u.h" #include "agg_span_allocator.h" #include "agg_span_converter.h" #include "agg_span_image_filter_gray.h" #include "agg_span_image_filter_rgba.h" #include "agg_span_interpolator_adaptor.h" #include "agg_span_interpolator_linear.h" #include "agg_workaround.h" #include <type_traits> // Based on: //---------------------------------------------------------------------------- // Anti-Grain Geometry - Version 2.4 // Copyright (C) 2002-2005 Maxim Shemanarev (http://antigrain.com/) // // Permission to copy, use, modify, sell and distribute this software // is granted provided this copyright notice appears in all copies. // This software is provided "as is" without express or implied // warranty, and with no claim as to its suitability for any purpose. // //---------------------------------------------------------------------------- // Contact: mcseem@antigrain.com // mcseemagg@yahoo.com // http://antigrain.com/ //---------------------------------------------------------------------------- // // Adaptation for high precision colors has been sponsored by // Liberty Technology Systems, Inc., visit http://lib-sys.com // // Liberty Technology Systems, Inc. is the provider of // PostScript and PDF technology for software developers. // //===================================================================gray64 namespace agg { struct gray64 { typedef double value_type; typedef double calc_type; typedef double long_type; typedef gray64 self_type; value_type v; value_type a; //-------------------------------------------------------------------- gray64() {} //-------------------------------------------------------------------- explicit gray64(value_type v_, value_type a_ = 1) : v(v_), a(a_) {} //-------------------------------------------------------------------- gray64(const self_type& c, value_type a_) : v(c.v), a(a_) {} //-------------------------------------------------------------------- gray64(const gray64& c) : v(c.v), a(c.a) {} //-------------------------------------------------------------------- static AGG_INLINE double to_double(value_type a) { return a; } //-------------------------------------------------------------------- static AGG_INLINE value_type from_double(double a) { return value_type(a); } //-------------------------------------------------------------------- static AGG_INLINE value_type empty_value() { return 0; } //-------------------------------------------------------------------- static AGG_INLINE value_type full_value() { return 1; } //-------------------------------------------------------------------- AGG_INLINE bool is_transparent() const { return a <= 0; } //-------------------------------------------------------------------- AGG_INLINE bool is_opaque() const { return a >= 1; } //-------------------------------------------------------------------- static AGG_INLINE value_type invert(value_type x) { return 1 - x; } //-------------------------------------------------------------------- static AGG_INLINE value_type multiply(value_type a, value_type b) { return value_type(a * b); } //-------------------------------------------------------------------- static AGG_INLINE value_type demultiply(value_type a, value_type b) { return (b == 0) ? 0 : value_type(a / b); } //-------------------------------------------------------------------- template<typename T> static AGG_INLINE T downscale(T a) { return a; } //-------------------------------------------------------------------- template<typename T> static AGG_INLINE T downshift(T a, unsigned n) { return n > 0 ? a / (1 << n) : a; } //-------------------------------------------------------------------- static AGG_INLINE value_type mult_cover(value_type a, cover_type b) { return value_type(a * b / cover_mask); } //-------------------------------------------------------------------- static AGG_INLINE cover_type scale_cover(cover_type a, value_type b) { return cover_type(uround(a * b)); } //-------------------------------------------------------------------- // Interpolate p to q by a, assuming q is premultiplied by a. static AGG_INLINE value_type prelerp(value_type p, value_type q, value_type a) { return (1 - a) * p + q; // more accurate than "p + q - p * a" } //-------------------------------------------------------------------- // Interpolate p to q by a. static AGG_INLINE value_type lerp(value_type p, value_type q, value_type a) { // The form "p + a * (q - p)" avoids a multiplication, but may produce an // inaccurate result. For example, "p + (q - p)" may not be exactly equal // to q. Therefore, stick to the basic expression, which at least produces // the correct result at either extreme. return (1 - a) * p + a * q; } //-------------------------------------------------------------------- self_type& clear() { v = a = 0; return *this; } //-------------------------------------------------------------------- self_type& transparent() { a = 0; return *this; } //-------------------------------------------------------------------- self_type& opacity(double a_) { if (a_ < 0) a = 0; else if (a_ > 1) a = 1; else a = value_type(a_); return *this; } //-------------------------------------------------------------------- double opacity() const { return a; } //-------------------------------------------------------------------- self_type& premultiply() { if (a < 0) v = 0; else if(a < 1) v *= a; return *this; } //-------------------------------------------------------------------- self_type& demultiply() { if (a < 0) v = 0; else if (a < 1) v /= a; return *this; } //-------------------------------------------------------------------- self_type gradient(self_type c, double k) const { return self_type( value_type(v + (c.v - v) * k), value_type(a + (c.a - a) * k)); } //-------------------------------------------------------------------- static self_type no_color() { return self_type(0,0); } }; //====================================================================rgba32 struct rgba64 { typedef double value_type; typedef double calc_type; typedef double long_type; typedef rgba64 self_type; value_type r; value_type g; value_type b; value_type a; //-------------------------------------------------------------------- rgba64() {} //-------------------------------------------------------------------- rgba64(value_type r_, value_type g_, value_type b_, value_type a_= 1) : r(r_), g(g_), b(b_), a(a_) {} //-------------------------------------------------------------------- rgba64(const self_type& c, float a_) : r(c.r), g(c.g), b(c.b), a(a_) {} //-------------------------------------------------------------------- rgba64(const rgba& c) : r(value_type(c.r)), g(value_type(c.g)), b(value_type(c.b)), a(value_type(c.a)) {} //-------------------------------------------------------------------- operator rgba() const { return rgba(r, g, b, a); } //-------------------------------------------------------------------- static AGG_INLINE double to_double(value_type a) { return a; } //-------------------------------------------------------------------- static AGG_INLINE value_type from_double(double a) { return value_type(a); } //-------------------------------------------------------------------- static AGG_INLINE value_type empty_value() { return 0; } //-------------------------------------------------------------------- static AGG_INLINE value_type full_value() { return 1; } //-------------------------------------------------------------------- AGG_INLINE bool is_transparent() const { return a <= 0; } //-------------------------------------------------------------------- AGG_INLINE bool is_opaque() const { return a >= 1; } //-------------------------------------------------------------------- static AGG_INLINE value_type invert(value_type x) { return 1 - x; } //-------------------------------------------------------------------- static AGG_INLINE value_type multiply(value_type a, value_type b) { return value_type(a * b); } //-------------------------------------------------------------------- static AGG_INLINE value_type demultiply(value_type a, value_type b) { return (b == 0) ? 0 : value_type(a / b); } //-------------------------------------------------------------------- template<typename T> static AGG_INLINE T downscale(T a) { return a; } //-------------------------------------------------------------------- template<typename T> static AGG_INLINE T downshift(T a, unsigned n) { return n > 0 ? a / (1 << n) : a; } //-------------------------------------------------------------------- static AGG_INLINE value_type mult_cover(value_type a, cover_type b) { return value_type(a * b / cover_mask); } //-------------------------------------------------------------------- static AGG_INLINE cover_type scale_cover(cover_type a, value_type b) { return cover_type(uround(a * b)); } //-------------------------------------------------------------------- // Interpolate p to q by a, assuming q is premultiplied by a. static AGG_INLINE value_type prelerp(value_type p, value_type q, value_type a) { return (1 - a) * p + q; // more accurate than "p + q - p * a" } //-------------------------------------------------------------------- // Interpolate p to q by a. static AGG_INLINE value_type lerp(value_type p, value_type q, value_type a) { // The form "p + a * (q - p)" avoids a multiplication, but may produce an // inaccurate result. For example, "p + (q - p)" may not be exactly equal // to q. Therefore, stick to the basic expression, which at least produces // the correct result at either extreme. return (1 - a) * p + a * q; } //-------------------------------------------------------------------- self_type& clear() { r = g = b = a = 0; return *this; } //-------------------------------------------------------------------- self_type& transparent() { a = 0; return *this; } //-------------------------------------------------------------------- AGG_INLINE self_type& opacity(double a_) { if (a_ < 0) a = 0; else if (a_ > 1) a = 1; else a = value_type(a_); return *this; } //-------------------------------------------------------------------- double opacity() const { return a; } //-------------------------------------------------------------------- AGG_INLINE self_type& premultiply() { if (a < 1) { if (a <= 0) { r = g = b = 0; } else { r *= a; g *= a; b *= a; } } return *this; } //-------------------------------------------------------------------- AGG_INLINE self_type& demultiply() { if (a < 1) { if (a <= 0) { r = g = b = 0; } else { r /= a; g /= a; b /= a; } } return *this; } //-------------------------------------------------------------------- AGG_INLINE self_type gradient(const self_type& c, double k) const { self_type ret; ret.r = value_type(r + (c.r - r) * k); ret.g = value_type(g + (c.g - g) * k); ret.b = value_type(b + (c.b - b) * k); ret.a = value_type(a + (c.a - a) * k); return ret; } //-------------------------------------------------------------------- AGG_INLINE void add(const self_type& c, unsigned cover) { if (cover == cover_mask) { if (c.is_opaque()) { *this = c; return; } else { r += c.r; g += c.g; b += c.b; a += c.a; } } else { r += mult_cover(c.r, cover); g += mult_cover(c.g, cover); b += mult_cover(c.b, cover); a += mult_cover(c.a, cover); } if (a > 1) a = 1; if (r > a) r = a; if (g > a) g = a; if (b > a) b = a; } //-------------------------------------------------------------------- static self_type no_color() { return self_type(0,0,0,0); } }; } typedef enum { NEAREST, BILINEAR, BICUBIC, SPLINE16, SPLINE36, HANNING, HAMMING, HERMITE, KAISER, QUADRIC, CATROM, GAUSSIAN, BESSEL, MITCHELL, SINC, LANCZOS, BLACKMAN, _n_interpolation } interpolation_e; // T is rgba if and only if it has an T::r field. template<typename T, typename = void> struct is_grayscale : std::true_type {}; template<typename T> struct is_grayscale<T, decltype(T::r, void())> : std::false_type {}; template<typename color_type> struct type_mapping { using blender_type = typename std::conditional< is_grayscale<color_type>::value, agg::blender_gray<color_type>, typename std::conditional< std::is_same<color_type, agg::rgba8>::value, fixed_blender_rgba_plain<color_type, agg::order_rgba>, agg::blender_rgba_plain<color_type, agg::order_rgba> >::type >::type; using pixfmt_type = typename std::conditional< is_grayscale<color_type>::value, agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer>, agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer> >::type; using pixfmt_pre_type = typename std::conditional< is_grayscale<color_type>::value, pixfmt_type, agg::pixfmt_alpha_blend_rgba< typename std::conditional< std::is_same<color_type, agg::rgba8>::value, fixed_blender_rgba_pre<color_type, agg::order_rgba>, agg::blender_rgba_pre<color_type, agg::order_rgba> >::type, agg::rendering_buffer> >::type; template<typename A> using span_gen_affine_type = typename std::conditional< is_grayscale<color_type>::value, agg::span_image_resample_gray_affine<A>, agg::span_image_resample_rgba_affine<A> >::type; template<typename A, typename B> using span_gen_filter_type = typename std::conditional< is_grayscale<color_type>::value, agg::span_image_filter_gray<A, B>, agg::span_image_filter_rgba<A, B> >::type; template<typename A, typename B> using span_gen_nn_type = typename std::conditional< is_grayscale<color_type>::value, agg::span_image_filter_gray_nn<A, B>, agg::span_image_filter_rgba_nn<A, B> >::type; }; template<typename color_type> class span_conv_alpha { public: span_conv_alpha(const double alpha) : m_alpha(alpha) { } void prepare() {} void generate(color_type* span, int x, int y, unsigned len) const { if (m_alpha != 1.0) { do { span->a *= m_alpha; ++span; } while (--len); } } private: const double m_alpha; }; /* A class to use a lookup table for a transformation */ class lookup_distortion { public: lookup_distortion(const double *mesh, int in_width, int in_height, int out_width, int out_height) : m_mesh(mesh), m_in_width(in_width), m_in_height(in_height), m_out_width(out_width), m_out_height(out_height) {} void calculate(int* x, int* y) { if (m_mesh) { double dx = double(*x) / agg::image_subpixel_scale; double dy = double(*y) / agg::image_subpixel_scale; if (dx >= 0 && dx < m_out_width && dy >= 0 && dy < m_out_height) { const double *coord = m_mesh + (int(dy) * m_out_width + int(dx)) * 2; *x = int(coord[0] * agg::image_subpixel_scale); *y = int(coord[1] * agg::image_subpixel_scale); } } } protected: const double *m_mesh; int m_in_width; int m_in_height; int m_out_width; int m_out_height; }; struct resample_params_t { interpolation_e interpolation; bool is_affine; agg::trans_affine affine; const double *transform_mesh; bool resample; bool norm; double radius; double alpha; }; static void get_filter(const resample_params_t ¶ms, agg::image_filter_lut &filter) { switch (params.interpolation) { case NEAREST: case _n_interpolation: // Never should get here. Here to silence compiler warnings. break; case HANNING: filter.calculate(agg::image_filter_hanning(), params.norm); break; case HAMMING: filter.calculate(agg::image_filter_hamming(), params.norm); break; case HERMITE: filter.calculate(agg::image_filter_hermite(), params.norm); break; case BILINEAR: filter.calculate(agg::image_filter_bilinear(), params.norm); break; case BICUBIC: filter.calculate(agg::image_filter_bicubic(), params.norm); break; case SPLINE16: filter.calculate(agg::image_filter_spline16(), params.norm); break; case SPLINE36: filter.calculate(agg::image_filter_spline36(), params.norm); break; case KAISER: filter.calculate(agg::image_filter_kaiser(), params.norm); break; case QUADRIC: filter.calculate(agg::image_filter_quadric(), params.norm); break; case CATROM: filter.calculate(agg::image_filter_catrom(), params.norm); break; case GAUSSIAN: filter.calculate(agg::image_filter_gaussian(), params.norm); break; case BESSEL: filter.calculate(agg::image_filter_bessel(), params.norm); break; case MITCHELL: filter.calculate(agg::image_filter_mitchell(), params.norm); break; case SINC: filter.calculate(agg::image_filter_sinc(params.radius), params.norm); break; case LANCZOS: filter.calculate(agg::image_filter_lanczos(params.radius), params.norm); break; case BLACKMAN: filter.calculate(agg::image_filter_blackman(params.radius), params.norm); break; } } template<typename color_type> void resample( const void *input, int in_width, int in_height, void *output, int out_width, int out_height, resample_params_t ¶ms) { using type_mapping_t = type_mapping<color_type>; using input_pixfmt_t = typename type_mapping_t::pixfmt_type; using output_pixfmt_t = typename type_mapping_t::pixfmt_type; using renderer_t = agg::renderer_base<output_pixfmt_t>; using rasterizer_t = agg::rasterizer_scanline_aa<agg::rasterizer_sl_clip_dbl>; using reflect_t = agg::wrap_mode_reflect; using image_accessor_t = agg::image_accessor_wrap<input_pixfmt_t, reflect_t, reflect_t>; using span_alloc_t = agg::span_allocator<color_type>; using span_conv_alpha_t = span_conv_alpha<color_type>; using affine_interpolator_t = agg::span_interpolator_linear<>; using arbitrary_interpolator_t = agg::span_interpolator_adaptor<agg::span_interpolator_linear<>, lookup_distortion>; size_t itemsize = sizeof(color_type); if (is_grayscale<color_type>::value) { itemsize /= 2; // agg::grayXX includes an alpha channel which we don't have. } if (params.interpolation != NEAREST && params.is_affine && fabs(params.affine.sx) == 1.0 && fabs(params.affine.sy) == 1.0 && params.affine.shx == 0.0 && params.affine.shy == 0.0) { params.interpolation = NEAREST; } span_alloc_t span_alloc; rasterizer_t rasterizer; agg::scanline_u8 scanline; span_conv_alpha_t conv_alpha(params.alpha); agg::rendering_buffer input_buffer; input_buffer.attach( (unsigned char *)input, in_width, in_height, in_width * itemsize); input_pixfmt_t input_pixfmt(input_buffer); image_accessor_t input_accessor(input_pixfmt); agg::rendering_buffer output_buffer; output_buffer.attach( (unsigned char *)output, out_width, out_height, out_width * itemsize); output_pixfmt_t output_pixfmt(output_buffer); renderer_t renderer(output_pixfmt); agg::trans_affine inverted = params.affine; inverted.invert(); rasterizer.clip_box(0, 0, out_width, out_height); agg::path_storage path; if (params.is_affine) { path.move_to(0, 0); path.line_to(in_width, 0); path.line_to(in_width, in_height); path.line_to(0, in_height); path.close_polygon(); agg::conv_transform<agg::path_storage> rectangle(path, params.affine); rasterizer.add_path(rectangle); } else { path.move_to(0, 0); path.line_to(out_width, 0); path.line_to(out_width, out_height); path.line_to(0, out_height); path.close_polygon(); rasterizer.add_path(path); } if (params.interpolation == NEAREST) { if (params.is_affine) { using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, affine_interpolator_t>; using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; affine_interpolator_t interpolator(inverted); span_gen_t span_gen(input_accessor, interpolator); span_conv_t span_conv(span_gen, conv_alpha); nn_renderer_t nn_renderer(renderer, span_alloc, span_conv); agg::render_scanlines(rasterizer, scanline, nn_renderer); } else { using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, arbitrary_interpolator_t>; using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; lookup_distortion dist( params.transform_mesh, in_width, in_height, out_width, out_height); arbitrary_interpolator_t interpolator(inverted, dist); span_gen_t span_gen(input_accessor, interpolator); span_conv_t span_conv(span_gen, conv_alpha); nn_renderer_t nn_renderer(renderer, span_alloc, span_conv); agg::render_scanlines(rasterizer, scanline, nn_renderer); } } else { agg::image_filter_lut filter; get_filter(params, filter); if (params.is_affine && params.resample) { using span_gen_t = typename type_mapping_t::template span_gen_affine_type<image_accessor_t>; using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; using int_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; affine_interpolator_t interpolator(inverted); span_gen_t span_gen(input_accessor, interpolator, filter); span_conv_t span_conv(span_gen, conv_alpha); int_renderer_t int_renderer(renderer, span_alloc, span_conv); agg::render_scanlines(rasterizer, scanline, int_renderer); } else { using span_gen_t = typename type_mapping_t::template span_gen_filter_type<image_accessor_t, arbitrary_interpolator_t>; using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>; using int_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>; lookup_distortion dist( params.transform_mesh, in_width, in_height, out_width, out_height); arbitrary_interpolator_t interpolator(inverted, dist); span_gen_t span_gen(input_accessor, interpolator, filter); span_conv_t span_conv(span_gen, conv_alpha); int_renderer_t int_renderer(renderer, span_alloc, span_conv); agg::render_scanlines(rasterizer, scanline, int_renderer); } } } #endif /* MPL_RESAMPLE_H */