-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #150 from enthought/feature/image-component
Add a very basic Image component
- Loading branch information
Showing
9 changed files
with
271 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
include kiva/agg/agg.i | ||
include chaco/tests/data/PngSuite/*.png | ||
include chaco/tests/data/PngSuite/LICENSE.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
""" Defines the Image component class. | ||
""" | ||
|
||
from __future__ import absolute_import | ||
|
||
# Enthought library imports | ||
from traits.api import Array, Bool, Enum, Instance, Property, cached_property | ||
|
||
# Local imports | ||
from enable.component import Component | ||
from kiva.image import GraphicsContext | ||
|
||
|
||
class Image(Component): | ||
""" Component that displays a static image | ||
This is extremely simple right now. By default it will draw the array into | ||
the entire region occupied by the component, stretching or shrinking as | ||
needed. By default the bounds are set to the width and height of the data | ||
array, and we provide the same information to constraints-based layout | ||
with the layout_size_hint trait. | ||
""" | ||
|
||
#: the image data as an array | ||
data = Array(shape=(None, None, (3,4)), dtype='uint8') | ||
|
||
#: the format of the image data (eg. RGB vs. RGBA) | ||
format = Property(Enum('rgb24', 'rgba32'), depends_on='data') | ||
|
||
#: the size-hint for constraints-based layout | ||
layout_size_hint = Property(data, depends_on='data') | ||
|
||
#: the image as an Image GC | ||
_image = Property(Instance(GraphicsContext), depends_on='data') | ||
|
||
@classmethod | ||
def from_file(cls, filename, **traits): | ||
from PIL import Image | ||
from numpy import asarray | ||
data = asarray(Image.open(filename)) | ||
return cls(data=data, **traits) | ||
|
||
def __init__(self, data, **traits): | ||
# the default bounds are the size of the image | ||
traits.setdefault('bounds', data.shape[1::-1]) | ||
super(Image, self).__init__(data=data, **traits) | ||
|
||
def _draw_mainlayer(self, gc, view_bounds=None, mode="normal"): | ||
""" Draws the image. """ | ||
with gc: | ||
gc.draw_image(self._image, (self.x, self.y, self.width, self.height)) | ||
|
||
@cached_property | ||
def _get_format(self): | ||
if self.data.shape[-1] == 3: | ||
return 'rgb24' | ||
elif self.data.shape[-1] == 4: | ||
return 'rgba32' | ||
else: | ||
raise ValueError('Data array not correct shape') | ||
|
||
@cached_property | ||
def _get_layout_size_hint(self): | ||
return self.data.shape[1::-1] | ||
|
||
@cached_property | ||
def _get__image(self): | ||
if not self.data.flags['C_CONTIGUOUS']: | ||
data = self.data.copy() | ||
else: | ||
data = self.data | ||
image_gc = GraphicsContext(data, pix_format=self.format) | ||
return image_gc |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
PngSuite | ||
-------- | ||
|
||
Permission to use, copy, modify and distribute these images for any | ||
purpose and without fee is hereby granted. | ||
|
||
|
||
(c) Willem van Schaik, 1996, 2011 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
""" Tests for the Image component """ | ||
|
||
import os | ||
import sys | ||
if sys.version_info[:2] == (2, 6): | ||
import unittest2 as unittest | ||
else: | ||
import unittest | ||
|
||
import numpy as np | ||
from numpy.testing import assert_array_equal | ||
from pkg_resources import resource_filename | ||
|
||
from kiva.image import GraphicsContext | ||
from traits.api import TraitError | ||
from traits.testing.unittest_tools import UnittestTools | ||
|
||
from enable.primitives.image import Image | ||
|
||
|
||
data_dir = resource_filename('enable.tests.primitives', 'data') | ||
|
||
|
||
class ImageTest(unittest.TestCase, UnittestTools): | ||
|
||
def setUp(self): | ||
self.data = np.empty(shape=(128, 256, 4), dtype='uint8') | ||
self.data[:, :, 0] = np.arange(256) | ||
self.data[:, :, 1] = np.arange(128)[:, np.newaxis] | ||
self.data[:, :, 2] = np.arange(256)[::-1] | ||
self.data[:, :, 3] = np.arange(128)[::-1, np.newaxis] | ||
|
||
self.image_24 = Image(self.data[..., :3]) | ||
self.image_32 = Image(self.data) | ||
|
||
def test_fromfile_png_rgb(self): | ||
# basic smoke test - assume that kiva.image does the right thing | ||
path = os.path.join(data_dir, 'PngSuite', 'basn2c08.png') | ||
image = Image.from_file(path) | ||
|
||
self.assertEqual(image.data.shape, (32, 32, 3)) | ||
self.assertEqual(image.format, 'rgb24') | ||
|
||
def test_fromfile_png_rgba(self): | ||
# basic smoke test - assume that kiva.image does the right thing | ||
path = os.path.join(data_dir, 'PngSuite', 'basi6a08.png') | ||
image = Image.from_file(path) | ||
|
||
self.assertEqual(image.data.shape, (32, 32, 4)) | ||
self.assertEqual(image.format, 'rgba32') | ||
|
||
def test_init_bad_shape(self): | ||
data = np.zeros(shape=(256, 256), dtype='uint8') | ||
with self.assertRaises(TraitError): | ||
Image(data=data) | ||
|
||
def test_init_bad_dtype(self): | ||
data = np.array(['red']*65536).reshape(128, 128, 4) | ||
with self.assertRaises(TraitError): | ||
Image(data=data) | ||
|
||
def test_set_bad_shape(self): | ||
data = np.zeros(shape=(256, 256), dtype='uint8') | ||
with self.assertRaises(TraitError): | ||
self.image_32.data = data | ||
|
||
def test_set_bad_dtype(self): | ||
data = np.array(['red']*65536).reshape(128, 128, 4) | ||
with self.assertRaises(TraitError): | ||
self.image_32.data = data | ||
|
||
def test_format(self): | ||
self.assertEqual(self.image_24.format, 'rgb24') | ||
self.assertEqual(self.image_32.format, 'rgba32') | ||
|
||
def test_format_change(self): | ||
image = self.image_24 | ||
with self.assertTraitChanges(image, 'format'): | ||
image.data = self.data | ||
|
||
self.assertEqual(self.image_24.format, 'rgba32') | ||
|
||
def test_bounds_default(self): | ||
self.assertEqual(self.image_24.bounds, [256, 128]) | ||
self.assertEqual(self.image_32.bounds, [256, 128]) | ||
|
||
def test_bounds_overrride(self): | ||
image = Image(self.data, bounds=[200, 100]) | ||
self.assertEqual(image.bounds, [200, 100]) | ||
|
||
def test_size_hint(self): | ||
self.assertEqual(self.image_24.layout_size_hint, (256, 128)) | ||
self.assertEqual(self.image_32.layout_size_hint, (256, 128)) | ||
|
||
def test_size_hint_change(self): | ||
data = np.zeros(shape=(256, 128, 3), dtype='uint8') | ||
image = self.image_24 | ||
with self.assertTraitChanges(image, 'layout_size_hint'): | ||
image.data = data | ||
|
||
self.assertEqual(self.image_24.layout_size_hint, (128, 256)) | ||
|
||
def test_image_gc_24(self): | ||
# this is non-contiguous, because data comes from slice | ||
image_gc = self.image_24._image | ||
assert_array_equal(image_gc.bmp_array, self.data[..., :3]) | ||
|
||
def test_image_gc_32(self): | ||
# this is contiguous | ||
image_gc = self.image_32._image | ||
assert_array_equal(image_gc.bmp_array, self.data) | ||
|
||
def test_draw_24(self): | ||
gc = GraphicsContext((256, 128), pix_format='rgba32') | ||
self.image_24.draw(gc) | ||
# if test is failing, uncomment this line to see what is drawn | ||
#gc.save('test_image_draw_24.png') | ||
|
||
# smoke test: image isn't all white | ||
assert_array_equal(gc.bmp_array[..., :3], self.data[..., :3]) | ||
|
||
def test_draw_32(self): | ||
gc = GraphicsContext((256, 128), pix_format='rgba32') | ||
self.image_32.draw(gc) | ||
# if test is failing, uncommetn this line to see what is drawn | ||
#gc.save('test_image_draw_32.png') | ||
|
||
# smoke test: image isn't all white | ||
# XXX actually compute what it should look like with alpha transfer | ||
white_image = np.ones(shape=(256, 128, 4), dtype='uint8')*255 | ||
self.assertFalse(np.array_equal(white_image, gc.bmp_array)) | ||
|
||
def test_draw_stretched(self): | ||
gc = GraphicsContext((256, 256), pix_format='rgba32') | ||
self.image_32.bounds = [128, 258] | ||
self.image_32.position = [128, 0] | ||
self.image_32.draw(gc) | ||
# if test is failing, uncommetn this line to see what is drawn | ||
#gc.save('test_image_draw_stretched.png') | ||
|
||
# smoke test: image isn't all white | ||
# XXX actually compute what it should look like with alpha transfer | ||
white_image = np.ones(shape=(256, 256, 4), dtype='uint8')*255 | ||
self.assertFalse(np.array_equal(white_image, gc.bmp_array)) | ||
|
||
# left half of the image *should* be white | ||
assert_array_equal(gc.bmp_array[:, :128, :], white_image[:, :128, :]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
""" | ||
This demonstrates the use of the simple Image component. | ||
""" | ||
import os | ||
|
||
from enable.api import ConstraintsContainer, Window | ||
from enable.example_support import DemoFrame, demo_main | ||
from enable.primitives.image import Image | ||
|
||
THIS_DIR = os.path.split(__file__)[0] | ||
|
||
|
||
class MyFrame(DemoFrame): | ||
|
||
def _create_window(self): | ||
path = os.path.join(THIS_DIR, 'deepfield.jpg') | ||
image = Image.from_file(path, resist_width='weak', | ||
resist_height='weak') | ||
|
||
container = ConstraintsContainer(bounds=[500, 500]) | ||
container.add(image) | ||
ratio = float(image.data.shape[1])/image.data.shape[0] | ||
container.layout_constraints = [ | ||
image.left == container.contents_left, | ||
image.right == container.contents_right, | ||
image.top == container.contents_top, | ||
image.bottom == container.contents_bottom, | ||
image.layout_width == ratio*image.layout_height, | ||
] | ||
return Window(self, -1, component=container) | ||
|
||
|
||
if __name__ == "__main__": | ||
# Save demo so that it doesn't get garbage collected when run within | ||
# existing event loop (i.e. from ipython). | ||
demo = demo_main(MyFrame) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters