Repository: celiagg/celiagg (language: python)

Issue:
Add exception handling for rasterizer cell limit overflow
Beginning with 894ae3b91ab252691114f5478f77be18825a7a06, it's possible for polygon rasterization to throw an exception if the rasterizer runs out of cells:
https://github.com/celiagg/celiagg/blob/894ae3b91ab252691114f5478f77be18825a7a06/agg-svn/agg-2.4/include/agg_rasterizer_cells_aa.h#L188-L190

As long as this exception is not caught, it will cause the entire process to exit for something which is a recoverable error.


Discussion / hints:

Fix patch:
diff --git a/agg-svn/agg-2.4/include/agg_rasterizer_cells_aa.h b/agg-svn/agg-2.4/include/agg_rasterizer_cells_aa.h
index 3809bdb..1b31bf0 100644
--- a/agg-svn/agg-2.4/include/agg_rasterizer_cells_aa.h
+++ b/agg-svn/agg-2.4/include/agg_rasterizer_cells_aa.h
@@ -185,9 +185,6 @@ namespace agg
         {
             if((m_num_cells & cell_block_mask) == 0)
             {
-                if(m_num_blocks >= m_cell_block_limit) {
-                    throw std::overflow_error("Exceeded cell block limit");
-                }
                 allocate_block();
             }
             *m_curr_cell_ptr++ = m_curr_cell;
@@ -474,6 +471,10 @@ namespace agg
         {
             if(m_num_blocks >= m_max_blocks)
             {
+                if(m_num_blocks >= m_cell_block_limit) {
+                    throw std::overflow_error("Exceeded cell block limit");
+                }
+
                 cell_type** new_cells =
                     pod_allocator<cell_type*>::allocate(m_max_blocks +
                                                         cell_block_pool);
diff --git a/celiagg/_ndarray_canvas.pxd b/celiagg/_ndarray_canvas.pxd
index 65749cd..620fb92 100644
--- a/celiagg/_ndarray_canvas.pxd
+++ b/celiagg/_ndarray_canvas.pxd
@@ -67,13 +67,13 @@ cdef extern from "ndarray_canvas.h":
         void draw_shape(_vertex_source.VertexSource& shape,
                         const _transform.trans_affine& transform,
                         _paint.Paint& linePaint, _paint.Paint& fillPaint,
-                        const _graphics_state.GraphicsState& gs)
+                        const _graphics_state.GraphicsState& gs) except +
         void draw_shape_at_points(_vertex_source.VertexSource& shape,
                                   const double* points,
                                   const size_t point_count,
                                   const _transform.trans_affine& transform,
                                   _paint.Paint& linePaint, _paint.Paint& fillPaint,
-                                  const _graphics_state.GraphicsState& gs)
+                                  const _graphics_state.GraphicsState& gs) except +
         void draw_text(const char* text, _font.Font& font,
                        const _transform.trans_affine& transform,
                        _paint.Paint& linePaint, _paint.Paint& fillPaint,


Test patch:
diff --git a/celiagg/tests/test_canvas.py b/celiagg/tests/test_canvas.py
index fac055b..e128623 100644
--- a/celiagg/tests/test_canvas.py
+++ b/celiagg/tests/test_canvas.py
@@ -93,6 +93,35 @@ def test_stencil_size_mismatch(self):
         with self.assertRaises(agg.AggError):
             canvas.draw_shape(path, transform, gs)
 
+    def test_rasterizer_cell_overflow(self):
+        canvas = agg.CanvasRGB24(np.zeros((100, 100, 3), dtype=np.uint8))
+        gs = agg.GraphicsState()
+        transform = agg.Transform()
+        path = agg.Path()
+
+        def genpoints(num):
+            arr = np.empty((num, 2))
+            arr[::2, 0] = np.linspace(0, 99, num=arr[::2, 0].shape[0])
+            arr[::2, 1] = 0.0
+            arr[1::2, 0] = np.linspace(1, 100, num=arr[1::2, 0].shape[0])
+            arr[1::2, 1] = 100.0
+            return arr
+
+        # This many points definitely generates more than 2^22 cells
+        count = 2**22 // 100 // 2
+        points = genpoints(count)
+        path.lines(points)
+        with self.assertRaises(OverflowError):
+            canvas.draw_shape(path, transform, gs)
+
+        path.reset()
+
+        # This many points is OK
+        count = 2**22 // 103 // 2
+        points = genpoints(count)
+        path.lines(points)
+        canvas.draw_shape(path, transform, gs)
+
     def test_clear(self):
         expected = np.zeros((4, 4, 3), dtype=np.uint8)
         buffer = np.zeros((4, 4, 3), dtype=np.uint8)