Repository: celiagg/celiagg (language: python)

Issue:
Improve image rendering
This means support for:
- [ ] Drawing with alpha/Blending modes
- [ ] Arbitrary transformations; _with filtering_



Discussion / hints:

Fix patch:
diff --git a/celiagg/__init__.py b/celiagg/__init__.py
index ed435de..84ed247 100644
--- a/celiagg/__init__.py
+++ b/celiagg/__init__.py
@@ -29,9 +29,9 @@
 from ._celiagg import (
     AggError, BSpline, BlendMode, DrawingMode, FontCache, FontWeight,
     FreeTypeFont, GradientSpread, GradientUnits, GraphicsState, Image,
-    InnerJoin, LineCap, LineJoin, LinearGradientPaint, Path, PatternPaint,
-    PatternStyle, PixelFormat, RadialGradientPaint, Rect, ShapeAtPoints,
-    SolidPaint, TextDrawingMode, Transform, Win32Font,
+    InterpolationMode, InnerJoin, LineCap, LineJoin, LinearGradientPaint,
+    Path, PatternPaint, PatternStyle, PixelFormat, RadialGradientPaint, Rect,
+    ShapeAtPoints, SolidPaint, TextDrawingMode, Transform, Win32Font,
 )
 
 # Query the library
@@ -61,10 +61,10 @@ def example_font():
 
     'AggError', 'BlendMode', 'BSpline', 'DrawingMode', 'Font', 'FontCache',
     'FontWeight', 'FreeTypeFont', 'GradientSpread', 'GradientUnits',
-    'GraphicsState', 'Image', 'InnerJoin', 'LinearGradientPaint', 'LineCap',
-    'LineJoin', 'RadialGradientPaint', 'Path', 'PatternPaint', 'PatternStyle',
-    'PixelFormat', 'Rect', 'ShapeAtPoints', 'SolidPaint', 'TextDrawingMode',
-    'Transform', 'Win32Font',
+    'GraphicsState', 'Image', 'InterpolationMode', 'InnerJoin',
+    'LinearGradientPaint', 'LineCap', 'LineJoin', 'RadialGradientPaint',
+    'Path', 'PatternPaint', 'PatternStyle', 'PixelFormat', 'Rect',
+    'ShapeAtPoints', 'SolidPaint', 'TextDrawingMode', 'Transform', 'Win32Font',
 
     'CanvasG8', 'CanvasGA16', 'CanvasRGB24', 'CanvasRGBA32', 'CanvasBGRA32',
     'CanvasRGBA128',
diff --git a/celiagg/_enums.pxd b/celiagg/_enums.pxd
index 795d173..43b93b3 100644
--- a/celiagg/_enums.pxd
+++ b/celiagg/_enums.pxd
@@ -130,6 +130,19 @@ cdef extern from "graphics_state.h" namespace "GraphicsState":
         BlendDifference
         BlendExclusion
 
+    cdef enum InterpolationMode:
+        InterpolationNearest
+        InterpolationBilinear
+        InterpolationBicubic
+        InterpolationSpline16
+        InterpolationSpline36
+        InterpolationSinc64
+        InterpolationSinc144
+        InterpolationSinc256
+        InterpolationBlackman64
+        InterpolationBlackman100
+        InterpolationBlackman256
+
 
 cdef extern from "paint.h" namespace "Paint":
     cdef enum PaintType:
diff --git a/celiagg/_graphics_state.pxd b/celiagg/_graphics_state.pxd
index 848be16..f7786a6 100644
--- a/celiagg/_graphics_state.pxd
+++ b/celiagg/_graphics_state.pxd
@@ -66,6 +66,9 @@ cdef extern from "graphics_state.h":
         void image_blend_mode(_enums.BlendMode m)
         _enums.BlendMode image_blend_mode() const
 
+        void image_interpolation_mode(_enums.InterpolationMode m)
+        _enums.InterpolationMode image_interpolation_mode() const
+
         void master_alpha(double a)
         double master_alpha() const
 
diff --git a/celiagg/enums.pxi b/celiagg/enums.pxi
index 5be22f6..4d989e3 100644
--- a/celiagg/enums.pxi
+++ b/celiagg/enums.pxi
@@ -107,6 +107,19 @@ cpdef enum BlendMode:
     BlendDifference = _enums.BlendDifference
     BlendExclusion = _enums.BlendExclusion
 
+cpdef enum InterpolationMode:
+    Nearest = _enums.InterpolationNearest
+    Bilinear = _enums.InterpolationBilinear
+    Bicubic = _enums.InterpolationBicubic
+    Spline16 = _enums.InterpolationSpline16
+    Spline36 = _enums.InterpolationSpline36
+    Sinc64 = _enums.InterpolationSinc64
+    Sinc144 = _enums.InterpolationSinc144
+    Sinc256 = _enums.InterpolationSinc256
+    Blackman64 = _enums.InterpolationBlackman64
+    Blackman100 = _enums.InterpolationBlackman100
+    Blackman256 = _enums.InterpolationBlackman256
+
 cpdef enum FontWeight:
     Any = _enums.k_FontWeightAny
     Thin = _enums.k_FontWeightThin
diff --git a/celiagg/graphics_state.h b/celiagg/graphics_state.h
index d880fde..45c8450 100644
--- a/celiagg/graphics_state.h
+++ b/celiagg/graphics_state.h
@@ -115,6 +115,21 @@ class GraphicsState
         BlendExclusion  = agg::comp_op_exclusion,
     };
 
+    enum InterpolationMode
+    {
+        InterpolationNearest = 0,
+        InterpolationBilinear,
+        InterpolationBicubic,
+        InterpolationSpline16,
+        InterpolationSpline36,
+        InterpolationSinc64,
+        InterpolationSinc144,
+        InterpolationSinc256,
+        InterpolationBlackman64,
+        InterpolationBlackman100,
+        InterpolationBlackman256,
+    };
+
     GraphicsState() :
         m_clip_box(0.0, 0.0, -1.0, -1.0),  // Invalid by default!
         m_stencil(NULL),
@@ -122,6 +137,7 @@ class GraphicsState
         m_text_drawing_mode(TextDrawRaster),
         m_blend_mode(BlendAlpha),
         m_image_blend_mode(BlendDst),
+        m_image_interp_mode(InterpolationNearest),
         m_master_alpha(1.0),
         m_line_dash_phase(0.0),
         m_miter_limit(1.0),
@@ -152,6 +168,9 @@ class GraphicsState
     void image_blend_mode(BlendMode m) { m_image_blend_mode = m; }
     BlendMode image_blend_mode() const { return m_image_blend_mode; }
 
+    void image_interpolation_mode(InterpolationMode m) { m_image_interp_mode = m; }
+    InterpolationMode image_interpolation_mode() const { return m_image_interp_mode; }
+
     void master_alpha(double a) { m_master_alpha = a; }
     double master_alpha() const { return m_master_alpha; }
 
@@ -192,22 +211,23 @@ class GraphicsState
     const Image* stencil() const { return m_stencil; }
 
 private:
-    Rect            m_clip_box;
-    DashPattern     m_dashes;
-    const Image*    m_stencil;
-    DrawingMode     m_drawing_mode;
-    TextDrawingMode m_text_drawing_mode;
-    BlendMode       m_blend_mode;
-    BlendMode       m_image_blend_mode;
-    double          m_master_alpha;
-    double          m_line_dash_phase;
-    double          m_miter_limit;
-    double          m_inner_miter_limit;
-    double          m_line_width;
-    LineCap         m_line_cap;
-    LineJoin        m_line_join;
-    InnerJoin       m_inner_join;
-    bool            m_anti_aliased;
+    Rect                m_clip_box;
+    DashPattern         m_dashes;
+    const Image*        m_stencil;
+    DrawingMode         m_drawing_mode;
+    TextDrawingMode     m_text_drawing_mode;
+    BlendMode           m_blend_mode;
+    BlendMode           m_image_blend_mode;
+    InterpolationMode   m_image_interp_mode;
+    double              m_master_alpha;
+    double              m_line_dash_phase;
+    double              m_miter_limit;
+    double              m_inner_miter_limit;
+    double              m_line_width;
+    LineCap             m_line_cap;
+    LineJoin            m_line_join;
+    InnerJoin           m_inner_join;
+    bool                m_anti_aliased;
 };
 
 #endif // CELIAGG_GRAPHICS_STATE_H
diff --git a/celiagg/graphics_state.pxi b/celiagg/graphics_state.pxi
index f5ac837..06d17da 100644
--- a/celiagg/graphics_state.pxi
+++ b/celiagg/graphics_state.pxi
@@ -101,6 +101,7 @@ cdef class GraphicsState:
                          mode.
     * blend_mode: A ``BlendMode`` for non-image drawing. (ignored)
     * image_blend_mode: A ``BlendMode`` for image drawing. (ignored)
+    * image_interpolation_moded: An ``InterpolationMode`` for image drawing.
     * line_cap: A ``LineCap`` value denoting the style of line ends.
     * line_join: A ``LineJoin`` value denoting the style of joins.
     * inner_join: An ``InnerJoin`` value denoting the style of inner joins.
@@ -199,6 +200,13 @@ cdef class GraphicsState:
         def __set__(self, BlendMode m):
             self._this.image_blend_mode(m)
 
+    property image_interpolation_mode:
+        def __get__(self):
+            return InterpolationMode(self._this.image_interpolation_mode())
+
+        def __set__(self, InterpolationMode m):
+            self._this.image_interpolation_mode(m)
+
     property master_alpha:
         def __get__(self):
             return self._this.master_alpha()
diff --git a/celiagg/ndarray_canvas.h b/celiagg/ndarray_canvas.h
index b9a627f..42e5449 100644
--- a/celiagg/ndarray_canvas.h
+++ b/celiagg/ndarray_canvas.h
@@ -145,11 +145,14 @@ class ndarray_canvas : public ndarray_canvas_base
 
 private:
 
-    template<typename base_renderer_t, typename span_gen_t>
+    template<typename base_renderer_t>
     void _draw_image_internal(Image& img,
                               const agg::trans_affine& transform,
                               const GraphicsState& gs,
                               base_renderer_t& renderer);
+    template<typename base_renderer_t, typename span_gen_t>
+    void _draw_image_final(base_renderer_t& renderer,
+                           span_gen_t& span_generator);
     template<typename base_renderer_t>
     void _draw_shape_internal(VertexSource& shape,
                               const agg::trans_affine& transform,
diff --git a/celiagg/ndarray_canvas.hxx b/celiagg/ndarray_canvas.hxx
index b2b4242..889568f 100644
--- a/celiagg/ndarray_canvas.hxx
+++ b/celiagg/ndarray_canvas.hxx
@@ -81,19 +81,17 @@ template<typename pixfmt_t>
 void ndarray_canvas<pixfmt_t>::draw_image(Image& img,
     const agg::trans_affine& transform, const GraphicsState& gs)
 {
-    typedef typename image_filters<pixfmt_t>::nearest_t span_gen_t;
-
     _set_clipping(gs.clip_box());
     // XXX: Apply master alpha here somehow!
 
     if (gs.stencil() == NULL)
     {
-        _draw_image_internal<renderer_t, span_gen_t>(img, transform, gs, m_renderer);
+        _draw_image_internal<renderer_t>(img, transform, gs, m_renderer);
     }
     else
     {
         _WITH_MASKED_RENDERER(gs, renderer)
-        _draw_image_internal<masked_renderer_t, span_gen_t>(img, transform, gs, renderer);
+        _draw_image_internal<masked_renderer_t>(img, transform, gs, renderer);
     }
 }
 
@@ -175,33 +173,97 @@ void ndarray_canvas<pixfmt_t>::draw_text(const char* text,
 }
 
 template<typename pixfmt_t>
-template<typename base_renderer_t, typename span_gen_t>
+template<typename base_renderer_t>
 void ndarray_canvas<pixfmt_t>::_draw_image_internal(Image& img,
     const agg::trans_affine& transform, const GraphicsState& gs,
     base_renderer_t& renderer)
 {
     typedef typename image_filters<pixfmt_t>::source_t source_t;
-    typedef typename agg::span_allocator<typename pixfmt_t::color_type> span_alloc_t;
-    typedef agg::renderer_scanline_aa<base_renderer_t, span_alloc_t, span_gen_t> img_renderer_t;
     typedef agg::conv_transform<agg::path_storage> trans_curve_t;
 
-    agg::path_storage img_outline = img.image_outline();
     pixfmt_t src_pix(img.get_buffer());
+    typename pixfmt_t::color_type back_color(agg::rgba(0.5, 0.5, 0.5, 1.0));
+    source_t source(src_pix, back_color);
 
     agg::trans_affine src_mtx = transform;
-    agg::trans_affine inv_img_mtx = transform;
-    inv_img_mtx.invert();
+    agg::trans_affine inv_img_mtx = ~src_mtx;
     interpolator_t interpolator(inv_img_mtx);
 
-    typename pixfmt_t::color_type back_color(agg::rgba(0.5, 0.5, 0.5, 1.0));
-    source_t source(src_pix, back_color);
-    span_gen_t span_generator(source, interpolator);
-    span_alloc_t span_allocator;
-    img_renderer_t img_renderer(renderer, span_allocator, span_generator);
-
     m_rasterizer.reset();
+    agg::path_storage img_outline = img.image_outline();
     trans_curve_t trans_outline(img_outline, src_mtx);
     m_rasterizer.add_path(trans_outline);
+
+    switch (gs.image_interpolation_mode())
+    {
+    case GraphicsState::InterpolationNearest:
+        {
+            typedef typename image_filters<pixfmt_t>::nearest_t span_gen_t;
+
+            span_gen_t span_generator(source, interpolator);
+            _draw_image_final<base_renderer_t>(renderer, span_generator);
+            break;
+        }
+    case GraphicsState::InterpolationBilinear:
+        {
+            typedef typename image_filters<pixfmt_t>::bilinear_t span_gen_t;
+
+            span_gen_t span_generator(source, interpolator);
+            _draw_image_final<base_renderer_t>(renderer, span_generator);
+            break;
+        }
+    case GraphicsState::InterpolationBicubic:
+    case GraphicsState::InterpolationSpline16:
+    case GraphicsState::InterpolationSpline36:
+    case GraphicsState::InterpolationSinc64:
+    case GraphicsState::InterpolationSinc144:
+    case GraphicsState::InterpolationSinc256:
+    case GraphicsState::InterpolationBlackman64:
+    case GraphicsState::InterpolationBlackman100:
+    case GraphicsState::InterpolationBlackman256:
+        {
+            typedef typename image_filters<pixfmt_t>::general_t span_gen_t;
+            agg::image_filter_lut filter;
+            switch (gs.image_interpolation_mode())
+            {
+                case GraphicsState::InterpolationBicubic:
+                    filter.calculate(agg::image_filter_bicubic()); break;
+                case GraphicsState::InterpolationSpline16:
+                    filter.calculate(agg::image_filter_spline16()); break;
+                case GraphicsState::InterpolationSpline36:
+                    filter.calculate(agg::image_filter_spline36()); break;
+                case GraphicsState::InterpolationSinc64:
+                    filter.calculate(agg::image_filter_sinc64()); break;
+                case GraphicsState::InterpolationSinc144:
+                    filter.calculate(agg::image_filter_sinc144()); break;
+                case GraphicsState::InterpolationSinc256:
+                    filter.calculate(agg::image_filter_sinc256()); break;
+                case GraphicsState::InterpolationBlackman64:
+                    filter.calculate(agg::image_filter_blackman64()); break;
+                case GraphicsState::InterpolationBlackman100:
+                    filter.calculate(agg::image_filter_blackman100()); break;
+                case GraphicsState::InterpolationBlackman256:
+                    filter.calculate(agg::image_filter_blackman256()); break;
+                default: break;
+            }
+
+            span_gen_t span_generator(source, interpolator, filter);
+            _draw_image_final<base_renderer_t>(renderer, span_generator);
+            break;
+        }
+    }
+}
+
+template<typename pixfmt_t>
+template<typename base_renderer_t, typename span_gen_t>
+void ndarray_canvas<pixfmt_t>::_draw_image_final(
+    base_renderer_t& renderer, span_gen_t& span_generator)
+{
+    typedef typename agg::span_allocator<typename pixfmt_t::color_type> span_alloc_t;
+    typedef agg::renderer_scanline_aa<base_renderer_t, span_alloc_t, span_gen_t> img_renderer_t;
+
+    span_alloc_t span_allocator;
+    img_renderer_t img_renderer(renderer, span_allocator, span_generator);
     agg::render_scanlines(m_rasterizer, m_scanline, img_renderer);
 }
 


Test patch:
diff --git a/celiagg/tests/test_state.py b/celiagg/tests/test_state.py
index d360eb8..87173fb 100644
--- a/celiagg/tests/test_state.py
+++ b/celiagg/tests/test_state.py
@@ -25,8 +25,10 @@
 
 import numpy as np
 
-from celiagg import (GraphicsState, BlendMode, DrawingMode, Image, InnerJoin,
-                     LineCap, LineJoin, PixelFormat, Rect, TextDrawingMode)
+from celiagg import (
+    BlendMode, DrawingMode, GraphicsState, Image, InterpolationMode, InnerJoin,
+    LineCap, LineJoin, PixelFormat, Rect, TextDrawingMode
+)
 
 
 def array_bases_equal(arr0, arr1):
@@ -89,6 +91,12 @@ def test_state_properties(self):
         gs.image_blend_mode = BlendMode.BlendLighten
         self.assertEqual(gs.image_blend_mode, BlendMode.BlendLighten)
 
+        gs.image_interpolation_mode = InterpolationMode.Sinc64
+        self.assertEqual(gs.image_interpolation_mode, InterpolationMode.Sinc64)
+        gs.image_interpolation_mode = InterpolationMode.Blackman100
+        self.assertEqual(gs.image_interpolation_mode,
+                         InterpolationMode.Blackman100)
+
         gs.line_cap = LineCap.CapSquare
         self.assertEqual(gs.line_cap, LineCap.CapSquare)
         gs.line_cap = LineCap.CapRound
@@ -195,6 +203,7 @@ def test_copy(self):
             text_drawing_mode=TextDrawingMode.TextDrawFillStroke,
             blend_mode=BlendMode.BlendXor,
             image_blend_mode=BlendMode.BlendXor,
+            image_interpolation_mode=InterpolationMode.Sinc64,
             line_cap=LineCap.CapSquare,
             line_join=LineJoin.JoinRound,
             inner_join=InnerJoin.InnerRound,
@@ -215,6 +224,8 @@ def test_copy(self):
                          TextDrawingMode.TextDrawFillStroke)
         self.assertEqual(cpy.blend_mode, BlendMode.BlendXor)
         self.assertEqual(cpy.image_blend_mode, BlendMode.BlendXor)
+        self.assertEqual(cpy.image_interpolation_mode,
+                         InterpolationMode.Sinc64)
         self.assertEqual(cpy.line_cap, LineCap.CapSquare)
         self.assertEqual(cpy.line_join, LineJoin.JoinRound)
         self.assertEqual(cpy.inner_join, InnerJoin.InnerRound)