```
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Requires Blender SVN Revision 36007 (2.57rc2) or later
class Pixel:
def __init__(self, r=0.0, g=0.0, b=0.0, a=None, colour=None):
self.r = r
self.g = g
self.b = b
self.a = a
if colour:
self.r = colour[0]
self.g = colour[1]
self.b = colour[2]
if len(colour) > 3:
self.a = colour[3]
if self.a is None:
self.a = 1.0
def __sub__(self, other):
return Pixel(
self.r - other.r,
self.g - other.g,
self.b - other.b,
self.a - other.a)
def __add__(self, other):
return Pixel(
self.r + other.r,
self.g + other.g,
self.b + other.b,
self.a + other.a)
def __mul__(self, scalar):
return Pixel(
self.r * scalar,
self.g * scalar,
self.b * scalar,
self.a * scalar)
def __neg__(self):
return self * -1
def __repr__(self):
return "Pixel(%s, %s, %s, %s)" % (self.r, self.g, self.b, self.a)
def as_tuple(self):
return (self.r, self.g, self.b, self.a)
class ImageBuffer:
def __init__(self, image, clear=False):
self.image = image
self.x, self.y = self.image.size
if clear:
self.clear()
else:
self.buffer = list(self.image.pixels)
def update(self):
self.image.pixels = self.buffer
def _index(self, x, y):
if x < 0 or y < 0 or x >= self.x or y >= self.y:
return None
return (x + y * self.x) * 4
def clear(self):
self.buffer = [0.0 for i in range(self.x * self.y * 4)]
def set_pixel(self, x, y, colour):
index = self._index(x, y)
if index is not None:
self.buffer[index:index + 4] = colour.as_tuple()
def get_pixel(self, x, y):
index = self._index(x, y)
if index is not None:
return Pixel(colour=self.buffer[index:index + 4])
else:
return None
def uv_to_xy(self, u, v):
x = int(u * self.x)
y = int(v * self.y)
return x, y
def draw_line(self, u1, v1, c1, u2, v2, c2, ends=True):
'''Draws a gradient line'''
if type(u1) == int:
x1, y1, x2, y2 = u1, v1, u2, v2
else:
x1, y1 = self.uv_to_xy(u1, v1)
x2, y2 = self.uv_to_xy(u2, v2)
steep = abs(y2 - y1) > abs(x2 - x1)
if steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
c1, c2 = c2, c1
deltax = x2 - x1
deltay = abs(y2 - y1)
error = deltax / 2
mix = c2 - c1
y = y1
if y1 < y2:
ystep = 1
else:
ystep = -1
for x in range(x1, x2 + 1):
draw = ends or (x != x1 and x != x2)
if not draw:
#make sure we can skip this pixel
if steep:
s, t = y, x
else:
s, t = x, y
c = self.get_pixel(s, t)
if c is not None:
if sum(c.as_tuple(4)) == 0:
draw = True
if draw:
d = x - x1
if d:
if d == deltax:
colour = c2 * 1.0 # makes sure we are using copy
else:
colour = c1 + mix * (d / deltax)
else:
colour = c1 * 1.0 # makes sure we are using copy
if steep:
self.set_pixel(y, x, colour)
else:
self.set_pixel(x, y, colour)
error = error - deltay
if error < 0:
y = y + ystep
error = error + deltax
```

EDIT: New code above no longer has the speed issues. If you got the early version of this script it will no longer work on current builds of Blender, please update to the one above.

============== Original outdated post continues below ===================

Warning it’s VERY slow due to the size of the buffer to hold the pixels in Python. Currently this is the recommended way to do it though. http://cia.vc/stats/project/Blender/.message/3ca215b

If you know you are only going to write to the image, specify clear=True when you create the buffer as this will slightly improve the performance.