Merge pull request #946 from synesthesiam/master

Added alpha support to novice
This commit is contained in:
Johannes Schönberger
2014-03-26 09:31:15 -04:00
2 changed files with 121 additions and 20 deletions
+76 -13
View File
@@ -1,6 +1,7 @@
import os
import imghdr
from collections import namedtuple
from io import BytesIO
import numpy as np
from skimage import io
@@ -10,6 +11,9 @@ from skimage.color import color_dict
from skimage.io.util import file_or_url_context, is_url
import six
from six.moves.urllib_parse import urlparse
from six.moves.urllib import request
urlopen = request.urlopen
# Convert colors from `skimage.color` to uint8 and allow access through
# dict or a named tuple.
@@ -72,15 +76,18 @@ class Pixel(object):
Vertical coordinate of this pixel (bottom = 0).
rgb : tuple
RGB tuple with red, green, and blue components (0-255)
alpha : int
Transparency component (0-255), 255 (opaque) by default
"""
def __init__(self, pic, array, x, y, rgb):
def __init__(self, pic, array, x, y, rgb, alpha=255):
self._picture = pic
self._x = x
self._y = y
self._red = self._validate(rgb[0])
self._green = self._validate(rgb[1])
self._blue = self._validate(rgb[2])
self._alpha = self._validate(alpha)
@property
def x(self):
@@ -122,6 +129,16 @@ class Pixel(object):
self._blue = self._validate(value)
self._setpixel()
@property
def alpha(self):
"""The transparency component of the pixel (0-255)."""
return self._alpha
@alpha.setter
def alpha(self, value):
self._alpha = self._validate(value)
self._setpixel()
@property
def rgb(self):
"""The RGB color components of the pixel (3 values 0-255)."""
@@ -129,7 +146,25 @@ class Pixel(object):
@rgb.setter
def rgb(self, value):
self._red, self._green, self._blue = (self._validate(v) for v in value)
if len(value) == 4:
self.rgba = value
else:
self._red, self._green, self._blue \
= (self._validate(v) for v in value)
self._alpha = 255
self._setpixel()
@property
def rgba(self):
"""The RGB color and transparency components of the pixel
(4 values 0-255).
"""
return (self.red, self.green, self.blue, self.alpha)
@rgba.setter
def rgba(self, value):
self._red, self._green, self._blue, self._alpha \
= (self._validate(v) for v in value)
self._setpixel()
def _validate(self, value):
@@ -145,16 +180,17 @@ class Pixel(object):
return value
def _setpixel(self):
self._picture.xy_array[self._x, self._y] = self.rgb
# RGB + alpha
self._picture.xy_array[self._x, self._y] = self.rgba
self._picture._array_modified()
def __eq__(self, other):
if isinstance(other, Pixel):
return self.rgb == other.rgb
return self.rgba == other.rgba
def __repr__(self):
args = self.red, self.green, self.blue
return "Pixel(red={0}, green={1}, blue={2})".format(*args)
args = self.red, self.green, self.blue, self.alpha
return "Pixel(red={0}, green={1}, blue={2}, alpha={3})".format(*args)
class Picture(object):
@@ -165,9 +201,9 @@ class Picture(object):
path : str
Path to an image file to load / URL of an image
array : array
Raw RGB image data [0-255], with origin at top-left.
Raw RGB or RGBA image data [0-255], with origin at top-left.
xy_array : array
Raw RGB image data [0-255], with origin at bottom-left.
Raw RGB or RGBA image data [0-255], with origin at bottom-left.
Examples
--------
@@ -218,13 +254,17 @@ class Picture(object):
path = os.path.abspath(path)
self._path = path
with file_or_url_context(path) as context:
self.array = io.imread(context)
self.array = img_as_ubyte(io.imread(context))
self._format = imghdr.what(context)
elif array is not None:
self.array = array
elif xy_array is not None:
self.xy_array = xy_array
# Force RGBA internally (use max alpha)
if self.array.shape[-1] == 3:
self.array = np.insert(self.array, 3, values=255, axis=2)
@staticmethod
def from_size(size, color='black'):
"""Return a Picture of the specified size and a uniform color.
@@ -234,13 +274,18 @@ class Picture(object):
size : tuple
Width and height of the picture in pixels.
color : tuple or str
RGB tuple with the fill color for the picture [0-255] or a valid
key in `color_dict`.
RGB or RGBA tuple with the fill color for the picture [0-255] or
a valid key in `color_dict`.
"""
if isinstance(color, six.string_types):
color = color_dict[color]
rgb_size = tuple(size) + (3,)
rgb_size = tuple(size) + (len(color),)
array = np.ones(rgb_size, dtype=np.uint8) * color
# Force RGBA internally (use max alpha)
if array.shape[-1] == 3:
array = np.insert(array, 3, values=255, axis=2)
return Picture(array=array)
@property
@@ -384,13 +429,31 @@ class Picture(object):
def blue(self, value):
self._set_channel(2, value)
@property
def alpha(self):
"""The transparency component of the pixel (0-255)."""
return self._get_channel(3).ravel()
@alpha.setter
def alpha(self, value):
self._set_channel(3, value)
@property
def rgb(self):
"""The RGB color components of the pixel (3 values 0-255)."""
return self.xy_array
return self.xy_array[:, :, :3]
@rgb.setter
def rgb(self, value):
self.xy_array[:, :, :3] = value
@property
def rgba(self):
"""The RGBA color components of the pixel (4 values 0-255)."""
return self.xy_array
@rgba.setter
def rgba(self, value):
self.xy_array[:] = value
def __iter__(self):
+45 -7
View File
@@ -13,8 +13,12 @@ IMAGE_PATH = os.path.join(data_dir, "chelsea.png")
SMALL_IMAGE_PATH = os.path.join(data_dir, "block.png")
def _array_2d_to_RGB(array):
return np.tile(array[:, :, np.newaxis], (1, 1, 3))
def _array_2d_to_RGBA(array):
return np.tile(array[:, :, np.newaxis], (1, 1, 4))
def _array_2d_to_RGBA(array):
return np.tile(array[:, :, np.newaxis], (1, 1, 4))
def test_xy_to_array_origin():
@@ -82,6 +86,28 @@ def test_pixel_rgb():
for i, channel in enumerate((pixel.red, pixel.green, pixel.blue)):
assert_equal(channel, i + 3)
pixel.rgb = np.arange(4)
assert_equal(pixel.rgb, np.arange(3))
def test_pixel_rgba():
pic = novice.Picture.from_size((3, 3), color=(10, 10, 10))
pixel = pic[0, 0]
pixel.rgba = np.arange(4)
assert_equal(pixel.rgba, np.arange(4))
for i, channel in enumerate((pixel.red, pixel.green, pixel.blue, pixel.alpha)):
assert_equal(channel, i)
pixel.red = 3
pixel.green = 4
pixel.blue = 5
pixel.alpha = 6
assert_equal(pixel.rgba, np.arange(4) + 3)
for i, channel in enumerate((pixel.red, pixel.green, pixel.blue, pixel.alpha)):
assert_equal(channel, i + 3)
def test_pixel_rgb_float():
pixel = novice.Picture.from_size((1, 1))[0, 0]
@@ -89,6 +115,12 @@ def test_pixel_rgb_float():
assert_equal(pixel.rgb, (1, 1, 1))
def test_pixel_rgba_float():
pixel = novice.Picture.from_size((1, 1))[0, 0]
pixel.rgba = (1.1, 1.1, 1.1, 1.1)
assert_equal(pixel.rgba, (1, 1, 1, 1))
def test_modified_on_set():
pic = novice.Picture(SMALL_IMAGE_PATH)
pic[0, 0] = (1, 1, 1)
@@ -161,7 +193,7 @@ def test_indexing():
def test_picture_slice():
array = _array_2d_to_RGB(np.arange(0, 10)[np.newaxis, :])
array = _array_2d_to_RGBA(np.arange(0, 10)[np.newaxis, :])
pic = novice.Picture(array=array)
x_slice = slice(3, 8)
@@ -171,7 +203,7 @@ def test_picture_slice():
def test_move_slice():
h, w = 3, 12
array = _array_2d_to_RGB(np.linspace(0, 255, h * w).reshape(h, w))
array = _array_2d_to_RGBA(np.linspace(0, 255, h * w).reshape(h, w))
array = array.astype(np.uint8)
pic = novice.Picture(array=array)
@@ -191,7 +223,7 @@ def test_move_slice():
def test_negative_index():
n = 10
array = _array_2d_to_RGB(np.arange(0, n)[np.newaxis, :])
array = _array_2d_to_RGBA(np.arange(0, n)[np.newaxis, :])
# Test both x and y indices.
pic = novice.Picture(array=array)
assert pic[-1, 0] == pic[n - 1, 0]
@@ -201,7 +233,7 @@ def test_negative_index():
def test_negative_slice():
n = 10
array = _array_2d_to_RGB(np.arange(0, n)[np.newaxis, :])
array = _array_2d_to_RGBA(np.arange(0, n)[np.newaxis, :])
# Test both x and y slices.
pic = novice.Picture(array=array)
assert pic[-3:, 0] == pic[n - 3:, 0]
@@ -211,7 +243,7 @@ def test_negative_slice():
def test_getitem_with_step():
h, w = 5, 5
array = _array_2d_to_RGB(np.linspace(0, 255, h * w).reshape(h, w))
array = _array_2d_to_RGBA(np.linspace(0, 255, h * w).reshape(h, w))
pic = novice.Picture(array=array)
sliced_pic = pic[::2, ::2]
assert sliced_pic == novice.Picture(array=array[::2, ::2])
@@ -271,5 +303,11 @@ def test_pixel_blue_raises():
pixel.blue = 256
@raises(ValueError)
def test_pixel_alpha_raises():
pixel = novice.Picture.from_size((1, 1))[0, 0]
pixel.alpha = 256
if __name__ == '__main__':
np.testing.run_module_suite()