title: Numpy-compatible image drawing library
body: <p>I need to overplot general coordinate grids on images in python. I can compute the pixel coordinates of the grid lines, so I just need a module capable of drawing them as <em>dashed lines</em> on top of an image. The image comes in the form of a <code>numpy</code> array, so I need to be able to convert between those and the image format used by the drawing library (one direction is sufficient - I can either draw the grid and export it to <code>numpy</code>, or import the <code>numpy</code> array and draw on it). It also needs to be reasonably fast.</p>
  
  <p>Here's what I've tried:</p>
  
  <h1>wand</h1>
  
  <p>Recently got support for drawing dashed lines, and is numpy-compatible:</p>
  
  <pre><code>with Drawing() as draw:
      draw.stroke_antialias = False
      draw.stroke_dash_array = [1,3]
      draw.stroke_color = Color("gray")
      draw.fill_opacity = 0
      points = calc_grid_points()
      draw.polyline(points)
      with Image(width=width, height=height) as img:
          draw(img)
          return np.fromstring(img.make_blob("RGBA"),np.uint8).reshape(img.height, img.width, 4)
  </code></pre>
  
  <p>However, drawing a few hundred dashed lines over a 2000x1000 image with this library takes <em>30s</em>! And pretty much all that time is spent in <code>draw(img)</code>. So unless I'm doing something terribly wrong here, wand is just too slow.</p>
  
  <h1>PIL</h1>
  
  <p>Python Image Library in general works fine, but it does not seem to support dashed lines. I've not seen anybody state it directly, but a google search just yields 2-3 people asking about it and not getting any answers. Non-dashed coordinate grids don't look as nice, and cover up to much of the image being plotted. PIL is <em>much</em> faster than wand. This dashless but otherwise equivalent version to the wand version above takes only 0.06
  s to draw. That's 450 times faster than wand!</p>
  
  <pre><code>    img  = PIL.Image.new("RGBA", (width, height))
      draw = PIL.ImageDraw.Draw(img)
      segs = calc_grid_points()
      for seg in segs:
          draw.line([tuple(i) for i in seg], fill=(0,0,0,32))
      return np.array(img)
  </code></pre>
  
  <h1>GD</h1>
  
  <p><code>gd</code> supports dashed lines, but I didn't find an efficient way of converting its images to and from <code>numpy</code> arrays.</p>
  
  <h1>Matplotlib</h1>
  
  <p>Matplotlib has proper support for drawing coordinate grids and axes, but sadly it seems to be impossible to avoid repixelization. It insists on building its new set of pixels that never have a 1-to-1 mapping with the original pixels. That's fine for smooth images, but not for images with large changes from pixel to pixel.</p>
  
  <p>Here is an example of matplotlib repixelization:</p>
  
  <pre><code>import numpy as np
  import matplotlib
  matplotlib.use("Agg")
  import matplotlib.pyplot as plt
  n = 1001
  data = np.arange(n*n).reshape(n,n)%2
  plt.imshow(data)
  plt.savefig("test.png")
  </code></pre>
  
  <p>This image contains a pattern that switches between 0 and 1 for every pixel, in a chessboard pattern. But as far as I know there is no way to ensure that one pixel of data will correspond to exactly one pixel in the output. And if it doesn't, you get moiree patterns like what this code produces:</p>
  
  <p><img src="https://i.stack.imgur.com/bJti9.png" alt="Matplotlib moiree"></p>
  
  <p>Manually tweaking the <code>dpi</code> setting can reduce this problem, but not eliminate it. The correct output would have the data part of the plot take up exactly 1001 by 1001 pixels (so the total image would be larger than that), and show 500 repetitions of the pattern in each direction.</p>
  
  <p>Edit: Adding <code>interpolation='none'</code> does not help. That just causes matplotlib to use nearest neighbor. It still doesn't show all the data. Here is what the output looks like then:</p>
  
  <p><img src="https://i.stack.imgur.com/Zchtg.png" alt="Matplotlib nearest neighbor"></p>
  
  <p>And here is what the correct output would look like (I cropped it to 500x500, the full version would be 1001x1001):</p>
  
  <p><img src="https://i.stack.imgur.com/2VHNe.png" alt="Correct output"></p>
  
  <p>But matplotlib isn't really my issue - it's simply drawing dashed lines on an image in python. Matplotlib is simply one possible way of doing that.</p>
  
  <h1>Alternatives?</h1>
  
  <p>So I wonder, are there any other image libraries out there that would achieve this? Or have I overlooked features of the ones above that make them usable?</p>