mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Provide buffer access to pixel data, as well as methods to ensure/clear float/byte buffers as previously it wasn't possible to create new float images. - ImBuf.with_buffer(): context manager yielding a memoryview of pixel data (byte or float, with optional region and write support). - ImBuf.ensure_buffer(), has_buffer(), clear_buffer(): manage byte/float pixel buffers. - Export blocking: resize/crop/free raise BufferError while pixel buffers are active. - PyC_ParseRectI, PyC_ParseOptionalRectI: reusable O& converters for rcti argument parsing. - Additional tests. Ref !155710
704 lines
23 KiB
Python
704 lines
23 KiB
Python
# SPDX-FileCopyrightText: 2025 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# ./blender.bin --background --python tests/python/bl_imbuf_py_api.py -- --verbose
|
|
|
|
__all__ = (
|
|
"main",
|
|
)
|
|
|
|
import copy
|
|
import io
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
|
|
import imbuf
|
|
from imbuf.types import ImBuf
|
|
|
|
DEFAULT_SIZE = 32, 32
|
|
|
|
# Test color in linear float, sRGB byte, and the expected conversions between them.
|
|
COLOR_FLOAT = (1.0, 0.5, 0.25, 1.0)
|
|
COLOR_BYTE = (255, 128, 196, 255)
|
|
# sRGB byte -> linear float.
|
|
COLOR_BYTE_AS_FLOAT = (1.0, 0.2159, 0.5520, 1.0)
|
|
# Linear float -> sRGB byte.
|
|
COLOR_FLOAT_AS_BYTE = (255, 188, 137, 255)
|
|
|
|
# NOTE: an invalid file format & missing files is aborting on the build-bot.
|
|
# Disable as it makes tests fail, although we could consider demoting this to a warning
|
|
# since it prevents valid code-paths from being tested.
|
|
USE_TESTS_THAT_ABORT = False
|
|
|
|
|
|
class TestImBufNew(unittest.TestCase):
|
|
|
|
def test_new_basic(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
self.assertIsInstance(ibuf, ImBuf)
|
|
self.assertEqual(ibuf.size, DEFAULT_SIZE)
|
|
ibuf.free()
|
|
|
|
def test_new_single_pixel(self):
|
|
size = (1, 1)
|
|
ibuf = imbuf.new(size)
|
|
self.assertEqual(ibuf.size, size)
|
|
ibuf.free()
|
|
|
|
def test_new_default_properties(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
self.assertEqual(ibuf.planes, 32)
|
|
self.assertEqual(ibuf.channels, 4)
|
|
self.assertEqual(ibuf.filepath, "")
|
|
ibuf.free()
|
|
|
|
def test_new_large(self):
|
|
size = (4096, 4096)
|
|
ibuf = imbuf.new(size)
|
|
self.assertEqual(ibuf.size, size)
|
|
ibuf.free()
|
|
|
|
def test_new_invalid_size(self):
|
|
with self.assertRaises(ValueError):
|
|
imbuf.new((0, 10))
|
|
with self.assertRaises(ValueError):
|
|
imbuf.new((-1, 10))
|
|
|
|
|
|
class TestImBufProperties(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.ibuf = imbuf.new(DEFAULT_SIZE)
|
|
|
|
def tearDown(self):
|
|
self.ibuf.free()
|
|
|
|
def test_readonly_properties(self):
|
|
readonly_attrs = (
|
|
("size", DEFAULT_SIZE, (10, 10)),
|
|
("planes", 32, 24),
|
|
("channels", 4, 3),
|
|
)
|
|
for attr, expected, new_value in readonly_attrs:
|
|
self.assertTrue(hasattr(self.ibuf, attr))
|
|
self.assertEqual(getattr(self.ibuf, attr), expected)
|
|
with self.assertRaises(AttributeError):
|
|
setattr(self.ibuf, attr, new_value)
|
|
|
|
def test_filepath_read_write(self):
|
|
filepath = "/tmp/test.png"
|
|
self.ibuf.filepath = filepath
|
|
self.assertEqual(self.ibuf.filepath, filepath)
|
|
|
|
def test_filepath_read_write_bytes(self):
|
|
filepath = b"/tmp/test.png"
|
|
self.ibuf.filepath = filepath
|
|
self.assertEqual(self.ibuf.filepath, filepath.decode('ascii'))
|
|
|
|
def test_filepath_empty_default(self):
|
|
self.assertEqual(self.ibuf.filepath, "")
|
|
|
|
def test_filepath_type_error(self):
|
|
with self.assertRaises(TypeError):
|
|
self.ibuf.filepath = 123
|
|
|
|
def test_ppm_read_write(self):
|
|
ppm = (3780.0, 3780.0)
|
|
self.ibuf.ppm = ppm
|
|
self.assertEqual(self.ibuf.ppm, ppm)
|
|
|
|
def test_ppm_invalid(self):
|
|
with self.assertRaises(ValueError):
|
|
self.ibuf.ppm = (0.0, 0.0)
|
|
with self.assertRaises(ValueError):
|
|
self.ibuf.ppm = (-1.0, 100.0)
|
|
|
|
def test_quality(self):
|
|
self.assertEqual(self.ibuf.quality, 90)
|
|
self.ibuf.quality = 50
|
|
self.assertEqual(self.ibuf.quality, 50)
|
|
self.ibuf.quality = -1
|
|
self.assertEqual(self.ibuf.quality, 0)
|
|
self.ibuf.quality = 101
|
|
self.assertEqual(self.ibuf.quality, 100)
|
|
|
|
def test_compress(self):
|
|
self.assertEqual(self.ibuf.compress, 15)
|
|
self.ibuf.compress = 50
|
|
self.assertEqual(self.ibuf.compress, 50)
|
|
self.ibuf.compress = -1
|
|
self.assertEqual(self.ibuf.compress, 0)
|
|
self.ibuf.compress = 101
|
|
self.assertEqual(self.ibuf.compress, 100)
|
|
|
|
|
|
class TestImBufResize(unittest.TestCase):
|
|
|
|
def test_resize_basic(self):
|
|
ibuf = imbuf.new((64, 64))
|
|
ibuf.resize(DEFAULT_SIZE)
|
|
self.assertEqual(ibuf.size, DEFAULT_SIZE)
|
|
ibuf.free()
|
|
|
|
def test_resize_upscale(self):
|
|
size = (128, 128)
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf.resize(size)
|
|
self.assertEqual(ibuf.size, size)
|
|
ibuf.free()
|
|
|
|
def test_resize_non_square(self):
|
|
size = (100, 50)
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf.resize(size)
|
|
self.assertEqual(ibuf.size, size)
|
|
ibuf.free()
|
|
|
|
def test_resize_method_bilinear(self):
|
|
ibuf = imbuf.new((64, 64))
|
|
ibuf.resize(DEFAULT_SIZE, method='BILINEAR')
|
|
self.assertEqual(ibuf.size, DEFAULT_SIZE)
|
|
ibuf.free()
|
|
|
|
def test_resize_invalid(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with self.assertRaises(ValueError):
|
|
ibuf.resize((0, 64))
|
|
with self.assertRaises(ValueError):
|
|
ibuf.resize((-1, 64))
|
|
with self.assertRaises(ValueError):
|
|
ibuf.resize(DEFAULT_SIZE, method='INVALID')
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufCrop(unittest.TestCase):
|
|
|
|
def test_crop_basic(self):
|
|
size = (64, 64)
|
|
ibuf = imbuf.new(size)
|
|
crop_max = (DEFAULT_SIZE[0] - 1, DEFAULT_SIZE[1] - 1)
|
|
ibuf.crop((0, 0), crop_max)
|
|
self.assertEqual(ibuf.size, DEFAULT_SIZE)
|
|
ibuf.free()
|
|
|
|
def test_crop_partial(self):
|
|
crop_min = (10, 20)
|
|
crop_max = (49, 79)
|
|
expected_size = (crop_max[0] - crop_min[0] + 1, crop_max[1] - crop_min[1] + 1)
|
|
ibuf = imbuf.new((100, 100))
|
|
ibuf.crop(crop_min, crop_max)
|
|
self.assertEqual(ibuf.size, expected_size)
|
|
ibuf.free()
|
|
|
|
def test_crop_single_pixel(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf.crop((0, 0), (0, 0))
|
|
self.assertEqual(ibuf.size, (1, 1))
|
|
ibuf.free()
|
|
|
|
def test_crop_full_image(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
crop_max = (DEFAULT_SIZE[0] - 1, DEFAULT_SIZE[1] - 1)
|
|
ibuf.crop((0, 0), crop_max)
|
|
self.assertEqual(ibuf.size, DEFAULT_SIZE)
|
|
ibuf.free()
|
|
|
|
def test_crop_invalid(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with self.assertRaises(ValueError):
|
|
ibuf.crop((0, 0), DEFAULT_SIZE)
|
|
with self.assertRaises(ValueError):
|
|
ibuf.crop((20, 20), (10, 10))
|
|
with self.assertRaises(ValueError):
|
|
ibuf.crop((-1, 0), (10, 10))
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufCopy(unittest.TestCase):
|
|
|
|
def test_copy(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf_copy = ibuf.copy()
|
|
self.assertIsInstance(ibuf_copy, ImBuf)
|
|
self.assertEqual(ibuf_copy.size, DEFAULT_SIZE)
|
|
# Copy should be independent.
|
|
ibuf.free()
|
|
self.assertEqual(ibuf_copy.size, DEFAULT_SIZE)
|
|
ibuf_copy.free()
|
|
|
|
def test_copy_and_deepcopy(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf_copy = copy.copy(ibuf)
|
|
ibuf_deepcopy = copy.deepcopy(ibuf)
|
|
self.assertEqual(ibuf_copy.size, ibuf.size)
|
|
self.assertEqual(ibuf_deepcopy.size, ibuf.size)
|
|
ibuf.free()
|
|
ibuf_copy.free()
|
|
ibuf_deepcopy.free()
|
|
|
|
|
|
class TestImBufFree(unittest.TestCase):
|
|
|
|
def test_free_then_access(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf.free()
|
|
with self.assertRaises(ReferenceError):
|
|
_ = ibuf.size
|
|
with self.assertRaises(ReferenceError):
|
|
ibuf.resize(DEFAULT_SIZE)
|
|
with self.assertRaises(ReferenceError):
|
|
ibuf.crop((0, 0), (10, 10))
|
|
with self.assertRaises(ReferenceError):
|
|
ibuf.copy()
|
|
with self.assertRaises(ReferenceError):
|
|
imbuf.write_to_buffer(ibuf, io.BytesIO())
|
|
with self.assertRaises(ReferenceError):
|
|
ibuf.filepath = "/tmp/test.png"
|
|
|
|
def test_double_free(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf.free()
|
|
# Double free should not crash.
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufPythonProtocols(unittest.TestCase):
|
|
|
|
def test_repr(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
r = repr(ibuf)
|
|
self.assertIn("imbuf", r)
|
|
self.assertIn("size=({:d}, {:d})".format(*DEFAULT_SIZE), r)
|
|
ibuf.free()
|
|
|
|
def test_repr_after_free(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
ibuf.free()
|
|
r = repr(ibuf)
|
|
self.assertIn("address=0x0", r)
|
|
|
|
def test_hash(self):
|
|
ibuf1 = imbuf.new(DEFAULT_SIZE)
|
|
ibuf2 = imbuf.new(DEFAULT_SIZE)
|
|
self.assertIsInstance(hash(ibuf1), int)
|
|
# Different image buffers should (almost certainly) have different hashes.
|
|
self.assertNotEqual(hash(ibuf1), hash(ibuf2))
|
|
ibuf1.free()
|
|
ibuf2.free()
|
|
|
|
def test_isinstance(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
self.assertIsInstance(ibuf, ImBuf)
|
|
self.assertEqual(type(ibuf).__name__, "ImBuf")
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufIO(unittest.TestCase):
|
|
|
|
def test_write_and_load(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
filepath = os.path.join(tempdir, "test.png")
|
|
imbuf.write(ibuf, filepath=filepath)
|
|
ibuf.free()
|
|
|
|
ibuf_loaded = imbuf.load(filepath)
|
|
self.assertIsInstance(ibuf_loaded, ImBuf)
|
|
self.assertEqual(ibuf_loaded.size, DEFAULT_SIZE)
|
|
ibuf_loaded.free()
|
|
|
|
def test_write_uses_image_filepath(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
filepath = os.path.join(tempdir, "test.png")
|
|
ibuf.filepath = filepath
|
|
imbuf.write(ibuf)
|
|
ibuf.free()
|
|
|
|
ibuf_loaded = imbuf.load(filepath)
|
|
self.assertEqual(ibuf_loaded.size, DEFAULT_SIZE)
|
|
ibuf_loaded.free()
|
|
|
|
def test_load_nonexistent(self):
|
|
import bpy
|
|
# The binary path is a file, not a directory, so this sub-path will not exist in practice.
|
|
filepath = os.path.join(bpy.app.binary_path, "image.png")
|
|
with self.assertRaises(IOError):
|
|
imbuf.load(filepath)
|
|
|
|
def test_load_from_buffer(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
filepath = os.path.join(tempdir, "test.png")
|
|
imbuf.write(ibuf, filepath=filepath)
|
|
ibuf.free()
|
|
|
|
with open(filepath, "rb") as f:
|
|
data = f.read()
|
|
ibuf_loaded = imbuf.load_from_buffer(data)
|
|
self.assertIsInstance(ibuf_loaded, ImBuf)
|
|
self.assertEqual(ibuf_loaded.size, DEFAULT_SIZE)
|
|
ibuf_loaded.free()
|
|
|
|
def test_load_from_buffer_bytearray(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
filepath = os.path.join(tempdir, "test.png")
|
|
imbuf.write(ibuf, filepath=filepath)
|
|
ibuf.free()
|
|
|
|
with open(filepath, "rb") as f:
|
|
data = bytearray(f.read())
|
|
ibuf_loaded = imbuf.load_from_buffer(data)
|
|
self.assertEqual(ibuf_loaded.size, DEFAULT_SIZE)
|
|
ibuf_loaded.free()
|
|
|
|
def test_load_from_buffer_invalid(self):
|
|
if USE_TESTS_THAT_ABORT:
|
|
with self.assertRaises(ValueError):
|
|
imbuf.load_from_buffer(b"not an image")
|
|
with self.assertRaises(TypeError):
|
|
imbuf.load_from_buffer(12345)
|
|
|
|
def test_write_to_invalid_path(self):
|
|
import bpy
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
# The binary path is a file, not a directory, so this sub-path will not exist in practice.
|
|
filepath = os.path.join(bpy.app.binary_path, "test.png")
|
|
if USE_TESTS_THAT_ABORT:
|
|
with self.assertRaises(IOError):
|
|
imbuf.write(ibuf, filepath=filepath)
|
|
ibuf.free()
|
|
|
|
def test_write_to_buffer(self):
|
|
size = (16, 24)
|
|
ibuf = imbuf.new(size)
|
|
buf = io.BytesIO()
|
|
imbuf.write_to_buffer(ibuf, buf)
|
|
ibuf.free()
|
|
# Round-trip: the written data should be loadable.
|
|
ibuf_loaded = imbuf.load_from_buffer(buf.getvalue())
|
|
self.assertEqual(ibuf_loaded.size, size)
|
|
ibuf_loaded.free()
|
|
|
|
def test_write_to_buffer_not_writable(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with self.assertRaises(AttributeError):
|
|
imbuf.write_to_buffer(ibuf, object())
|
|
ibuf.free()
|
|
|
|
def test_load_sets_filepath(self):
|
|
ibuf = imbuf.new(DEFAULT_SIZE)
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
filepath = os.path.join(tempdir, "test.png")
|
|
imbuf.write(ibuf, filepath=filepath)
|
|
ibuf.free()
|
|
|
|
ibuf_loaded = imbuf.load(filepath)
|
|
self.assertEqual(ibuf_loaded.filepath, filepath)
|
|
ibuf_loaded.free()
|
|
|
|
|
|
class TestImBufPlanes(unittest.TestCase):
|
|
|
|
def test_new_planes_values(self):
|
|
for p in (8, 16, 24, 32):
|
|
ibuf = imbuf.new((2, 2), planes=p)
|
|
self.assertEqual(ibuf.planes, p)
|
|
ibuf.free()
|
|
|
|
def test_new_planes_invalid(self):
|
|
for p in (0, 4, 12, 64):
|
|
with self.assertRaises(ValueError):
|
|
imbuf.new((2, 2), planes=p)
|
|
|
|
|
|
class TestImBufPixelsBytes(unittest.TestCase):
|
|
|
|
def test_read_only_by_default(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
self.assertEqual(buf.readonly, True)
|
|
with self.assertRaises(TypeError):
|
|
buf[0] = 0xFF
|
|
ibuf.free()
|
|
|
|
def test_write_mode(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
with ibuf.with_buffer('BYTE', write=True) as buf:
|
|
self.assertEqual(buf.readonly, False)
|
|
buf[0] = 42
|
|
self.assertEqual(buf[0], 42)
|
|
ibuf.free()
|
|
|
|
def test_buffer_length(self):
|
|
w, h = 8, 6
|
|
ibuf = imbuf.new((w, h))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
self.assertEqual(len(buf), w * h * 4)
|
|
ibuf.free()
|
|
|
|
def test_freed_image(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.free()
|
|
with self.assertRaises(ReferenceError):
|
|
ibuf.with_buffer('BYTE')
|
|
|
|
def test_exports_block_resize(self):
|
|
ibuf = imbuf.new((4, 4))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
with self.assertRaises(BufferError):
|
|
ibuf.resize((2, 2))
|
|
# After context exit, resize works.
|
|
ibuf.resize((2, 2))
|
|
self.assertEqual(ibuf.size, (2, 2))
|
|
ibuf.free()
|
|
|
|
def test_exports_block_crop(self):
|
|
ibuf = imbuf.new((4, 4))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
with self.assertRaises(BufferError):
|
|
ibuf.crop((0, 0), (1, 1))
|
|
ibuf.free()
|
|
|
|
def test_exports_block_free(self):
|
|
ibuf = imbuf.new((4, 4))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
with self.assertRaises(BufferError):
|
|
ibuf.free()
|
|
ibuf.free()
|
|
|
|
def test_double_enter_blocked(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ctx = ibuf.with_buffer('BYTE')
|
|
ctx.__enter__()
|
|
with self.assertRaises(RuntimeError):
|
|
ctx.__enter__()
|
|
ctx.__exit__(None, None, None)
|
|
ibuf.free()
|
|
|
|
def test_reentry_after_exit(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ctx = ibuf.with_buffer('BYTE', write=True)
|
|
buf1 = ctx.__enter__()
|
|
buf1[0] = 10
|
|
ctx.__exit__(None, None, None)
|
|
buf2 = ctx.__enter__()
|
|
self.assertEqual(buf2[0], 10)
|
|
ctx.__exit__(None, None, None)
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufPixelsRegion(unittest.TestCase):
|
|
|
|
def setUp(self):
|
|
self.ibuf = imbuf.new((8, 6))
|
|
with self.ibuf.with_buffer('BYTE', write=True) as buf:
|
|
for i in range(8 * 6):
|
|
buf[i * 4] = i % 256
|
|
|
|
def tearDown(self):
|
|
self.ibuf.free()
|
|
|
|
def test_full_image_1d(self):
|
|
with self.ibuf.with_buffer('BYTE') as buf:
|
|
self.assertEqual(buf.ndim, 1)
|
|
self.assertEqual(len(buf), 8 * 6 * 4)
|
|
|
|
def test_region_none_is_1d(self):
|
|
with self.ibuf.with_buffer('BYTE', region=None) as buf:
|
|
self.assertEqual(buf.ndim, 1)
|
|
|
|
def test_sub_region_2d(self):
|
|
with self.ibuf.with_buffer('BYTE', region=((2, 1), (6, 4))) as buf:
|
|
self.assertEqual(buf.ndim, 2)
|
|
self.assertEqual(buf.shape, (3, 16))
|
|
|
|
def test_region_pixel_values(self):
|
|
with self.ibuf.with_buffer('BYTE', region=((2, 1), (6, 4))) as buf:
|
|
# Pixel (2, 1) has index 1*8+2 = 10.
|
|
self.assertEqual(buf[0, 0], 10)
|
|
# Pixel (2, 2) has index 2*8+2 = 18.
|
|
self.assertEqual(buf[1, 0], 18)
|
|
|
|
def test_full_image_region_collapses_to_1d(self):
|
|
with self.ibuf.with_buffer('BYTE', region=((0, 0), (8, 6))) as buf:
|
|
self.assertEqual(buf.ndim, 1)
|
|
|
|
def test_oversized_region_collapses_to_1d(self):
|
|
with self.ibuf.with_buffer('BYTE', region=((-5, -5), (100, 100))) as buf:
|
|
self.assertEqual(buf.ndim, 1)
|
|
|
|
def test_zero_area_region(self):
|
|
with self.ibuf.with_buffer('BYTE', region=((100, 100), (200, 200))) as buf:
|
|
self.assertEqual(buf.ndim, 2)
|
|
self.assertEqual(buf.shape[0] * buf.shape[1], 0)
|
|
|
|
with self.ibuf.with_buffer('BYTE', region=((5, 5), (2, 2))) as buf:
|
|
self.assertEqual(buf.shape[0] * buf.shape[1], 0)
|
|
|
|
with self.ibuf.with_buffer('BYTE', region=((3, 3), (3, 3))) as buf:
|
|
self.assertEqual(buf.shape[0] * buf.shape[1], 0)
|
|
|
|
def test_region_write_does_not_corrupt_neighbors(self):
|
|
with self.ibuf.with_buffer('BYTE', write=True, region=((1, 1), (3, 3))) as buf:
|
|
for r in range(buf.shape[0]):
|
|
for c in range(buf.shape[1]):
|
|
buf[r, c] = 0xFF
|
|
with self.ibuf.with_buffer('BYTE') as buf:
|
|
self.assertEqual(buf[0], 0)
|
|
inside = (1 * 8 + 1) * 4
|
|
self.assertEqual(buf[inside], 0xFF)
|
|
|
|
|
|
class TestImBufPixelsFloat(unittest.TestCase):
|
|
|
|
def test_no_float_buffer_error(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
with self.assertRaises(RuntimeError):
|
|
ibuf.with_buffer('FLOAT')
|
|
ibuf.free()
|
|
|
|
def test_ensure_and_access(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('FLOAT')
|
|
with ibuf.with_buffer('FLOAT') as buf:
|
|
self.assertEqual(buf.format, "f")
|
|
ibuf.free()
|
|
|
|
def test_float_write(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('FLOAT')
|
|
with ibuf.with_buffer('FLOAT', write=True) as buf:
|
|
buf[0] = 0.5
|
|
self.assertAlmostEqual(buf[0], 0.5, places=5)
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufEnsureBuffers(unittest.TestCase):
|
|
|
|
def test_ensure_float_from_byte(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
with ibuf.with_buffer('BYTE', write=True) as buf:
|
|
buf[0:4] = bytes(COLOR_BYTE)
|
|
ibuf.ensure_buffer('FLOAT')
|
|
with ibuf.with_buffer('FLOAT') as buf:
|
|
for i, expected in enumerate(COLOR_BYTE_AS_FLOAT):
|
|
self.assertAlmostEqual(buf[i], expected, places=3)
|
|
ibuf.free()
|
|
|
|
def test_ensure_byte_from_float(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('FLOAT')
|
|
with ibuf.with_buffer('FLOAT', write=True) as buf:
|
|
for i, v in enumerate(COLOR_FLOAT):
|
|
buf[i] = v
|
|
ibuf.clear_buffer('BYTE')
|
|
self.assertFalse(ibuf.has_buffer('BYTE'))
|
|
ibuf.ensure_buffer('BYTE')
|
|
self.assertTrue(ibuf.has_buffer('BYTE'))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
for i, expected in enumerate(COLOR_FLOAT_AS_BYTE):
|
|
self.assertEqual(buf[i], expected)
|
|
ibuf.free()
|
|
|
|
def test_ensure_noop_when_present(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('BYTE')
|
|
ibuf.ensure_buffer('FLOAT')
|
|
ibuf.ensure_buffer('FLOAT')
|
|
ibuf.free()
|
|
|
|
def test_ensure_blocked_while_exported(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
with self.assertRaises(BufferError):
|
|
ibuf.ensure_buffer('FLOAT')
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufHasAndClearBuffer(unittest.TestCase):
|
|
|
|
def test_has_buffer_byte(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
self.assertTrue(ibuf.has_buffer('BYTE'))
|
|
self.assertFalse(ibuf.has_buffer('FLOAT'))
|
|
ibuf.free()
|
|
|
|
def test_clear_buffer_byte(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
self.assertTrue(ibuf.has_buffer('BYTE'))
|
|
ibuf.clear_buffer('BYTE')
|
|
self.assertFalse(ibuf.has_buffer('BYTE'))
|
|
ibuf.free()
|
|
|
|
def test_clear_buffer_float(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('FLOAT')
|
|
self.assertTrue(ibuf.has_buffer('FLOAT'))
|
|
ibuf.clear_buffer('FLOAT')
|
|
self.assertFalse(ibuf.has_buffer('FLOAT'))
|
|
ibuf.free()
|
|
|
|
def test_clear_buffer_blocked_while_exported(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
with self.assertRaises(BufferError):
|
|
ibuf.clear_buffer('BYTE')
|
|
ibuf.free()
|
|
|
|
def test_clear_already_absent(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.clear_buffer('FLOAT')
|
|
ibuf.free()
|
|
|
|
|
|
class TestImBufExitSync(unittest.TestCase):
|
|
|
|
def test_byte_write_syncs_to_float(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('FLOAT')
|
|
with ibuf.with_buffer('BYTE', write=True) as buf:
|
|
for i, v in enumerate(COLOR_BYTE):
|
|
buf[i] = v
|
|
with ibuf.with_buffer('FLOAT') as buf:
|
|
for i, expected in enumerate(COLOR_BYTE_AS_FLOAT):
|
|
self.assertAlmostEqual(buf[i], expected, places=3)
|
|
ibuf.free()
|
|
|
|
def test_float_write_syncs_to_byte(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('FLOAT')
|
|
with ibuf.with_buffer('FLOAT', write=True) as buf:
|
|
for i, v in enumerate(COLOR_FLOAT):
|
|
buf[i] = v
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
for i, expected in enumerate(COLOR_FLOAT_AS_BYTE):
|
|
self.assertEqual(buf[i], expected)
|
|
ibuf.free()
|
|
|
|
def test_read_only_does_not_sync(self):
|
|
ibuf = imbuf.new((2, 2))
|
|
ibuf.ensure_buffer('FLOAT')
|
|
with ibuf.with_buffer('FLOAT', write=True) as buf:
|
|
for i, v in enumerate(COLOR_FLOAT):
|
|
buf[i] = v
|
|
# Read-only byte access should NOT overwrite the float buffer.
|
|
with ibuf.with_buffer('BYTE') as buf:
|
|
_ = buf[0]
|
|
with ibuf.with_buffer('FLOAT') as buf:
|
|
for i, expected in enumerate(COLOR_FLOAT):
|
|
self.assertAlmostEqual(buf[i], expected, places=3)
|
|
ibuf.free()
|
|
|
|
|
|
def main():
|
|
import sys
|
|
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
|
|
unittest.main()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|