mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
PyAPI: add ImBuf.file_type read/write access
Support different file types in the imbuf module, previously it wasn't possible to use imbuf to save an image as a different file format. - Add `ImBuf.file_type` read/write property to get/set the image file format (e.g. 'PNG', 'JPEG', 'TIFF', ... etc). - Add `imbuf.file_types` read-only dict mapping type identifiers to `ImBufFileType` objects which expose `id` and `file_extensions`. - Add `imbuf.file_type_from_buffer()` to detect the file type from image data in memory. Ref !155740
This commit is contained in:
parent
d2fe1d4302
commit
3abf54cf3f
5 changed files with 380 additions and 1 deletions
|
|
@ -19,6 +19,16 @@ from imbuf.types import ImBuf
|
|||
|
||||
DEFAULT_SIZE = 32, 32
|
||||
|
||||
|
||||
def rgba_bytes_from_packed_int(value):
|
||||
return (
|
||||
(value >> 24) & 0xFF,
|
||||
(value >> 16) & 0xFF,
|
||||
(value >> 8) & 0xFF,
|
||||
value & 0xFF,
|
||||
)
|
||||
|
||||
|
||||
# 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)
|
||||
|
|
@ -115,6 +125,15 @@ class TestImBufProperties(unittest.TestCase):
|
|||
with self.assertRaises(ValueError):
|
||||
self.ibuf.ppm = (-1.0, 100.0)
|
||||
|
||||
def test_file_type(self):
|
||||
self.assertEqual(self.ibuf.file_type, 'PNG')
|
||||
self.ibuf.file_type = 'PNG'
|
||||
self.assertEqual(self.ibuf.file_type, 'PNG')
|
||||
with self.assertRaises(ValueError):
|
||||
self.ibuf.file_type = 'INVALID'
|
||||
with self.assertRaises(ValueError):
|
||||
self.ibuf.file_type = ""
|
||||
|
||||
def test_quality(self):
|
||||
self.assertEqual(self.ibuf.quality, 90)
|
||||
self.ibuf.quality = 50
|
||||
|
|
@ -414,6 +433,40 @@ class TestImBufPlanes(unittest.TestCase):
|
|||
with self.assertRaises(ValueError):
|
||||
imbuf.new((2, 2), planes=p)
|
||||
|
||||
def test_planes_roundtrip(self):
|
||||
file_type = 'IRIS'
|
||||
file_ext = imbuf.file_types[file_type].file_extensions[0]
|
||||
# Per-plane pixel data (2x2 = 4 pixels, packed as 0xRRGGBBAA).
|
||||
# 8: gray-scale (R=G=B, A ignored on save, becomes 0xFF on load).
|
||||
# 16: gray-scale + alpha (R=G=B, A preserved).
|
||||
# 24: RGB (A ignored on save, becomes 0xFF on load).
|
||||
# 32: RGBA (all channels preserved).
|
||||
pixel_data = {
|
||||
8: (0x404040FF, 0x808080FF, 0xC0C0C0FF, 0xFFFFFFFF),
|
||||
16: (0x404040C0, 0x808080FF, 0xC0C0C080, 0xFFFFFF40),
|
||||
24: (0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0xAABBCCFF),
|
||||
32: (0xFF000080, 0x00FF00C0, 0x0000FF40, 0xAABBCCDD),
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
for pixel_planes, pixels in pixel_data.items():
|
||||
with self.subTest(planes=pixel_planes):
|
||||
ibuf = imbuf.new((2, 2), planes=pixel_planes)
|
||||
ibuf.file_type = file_type
|
||||
with ibuf.with_buffer('BYTE', write=True) as buf:
|
||||
for i, pixel in enumerate(pixels):
|
||||
buf[i * 4:(i + 1) * 4] = bytes(rgba_bytes_from_packed_int(pixel))
|
||||
filepath = os.path.join(tempdir, "test_{:d}{:s}".format(pixel_planes, file_ext))
|
||||
imbuf.write(ibuf, filepath=filepath)
|
||||
ibuf.free()
|
||||
|
||||
ibuf_loaded = imbuf.load(filepath)
|
||||
self.assertEqual(ibuf_loaded.planes, pixel_planes)
|
||||
with ibuf_loaded.with_buffer('BYTE') as buf:
|
||||
for i, expected in enumerate(pixels):
|
||||
actual = tuple(buf[i * 4:(i + 1) * 4])
|
||||
self.assertEqual(actual, rgba_bytes_from_packed_int(expected))
|
||||
ibuf_loaded.free()
|
||||
|
||||
|
||||
class TestImBufPixelsBytes(unittest.TestCase):
|
||||
|
||||
|
|
@ -699,6 +752,47 @@ class TestImBufExitSync(unittest.TestCase):
|
|||
ibuf.free()
|
||||
|
||||
|
||||
class TestImBufFileTypes(unittest.TestCase):
|
||||
|
||||
def test_file_types_have_id_and_extensions(self):
|
||||
for type_id, file_type in imbuf.file_types.items():
|
||||
with self.subTest(type_id=type_id):
|
||||
self.assertEqual(file_type.id, type_id)
|
||||
self.assertIsInstance(file_type.file_extensions, tuple)
|
||||
self.assertGreater(len(file_type.file_extensions), 0)
|
||||
|
||||
def test_write_and_detect_all_types(self):
|
||||
# TODO: make this meta-data available.
|
||||
read_only_types = {'PSD', 'DDS'}
|
||||
size = (32, 32)
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
for type_id, file_type in imbuf.file_types.items():
|
||||
if type_id in read_only_types:
|
||||
continue
|
||||
ext = file_type.file_extensions[0]
|
||||
with self.subTest(type_id=type_id):
|
||||
ibuf = imbuf.new(size)
|
||||
ibuf.file_type = type_id
|
||||
|
||||
filepath = os.path.join(tempdir, "test" + ext)
|
||||
imbuf.write(ibuf, filepath=filepath)
|
||||
ibuf.free()
|
||||
|
||||
with open(filepath, "rb") as fh:
|
||||
data = fh.read()
|
||||
|
||||
# Detect type from buffer.
|
||||
detected = imbuf.file_type_from_buffer(data)
|
||||
self.assertIsNotNone(detected, msg=type_id)
|
||||
self.assertEqual(detected.id, type_id)
|
||||
|
||||
# Load from buffer and verify size and type.
|
||||
ibuf_loaded = imbuf.load_from_buffer(data)
|
||||
self.assertEqual(ibuf_loaded.size, size)
|
||||
self.assertEqual(ibuf_loaded.file_type, type_id)
|
||||
ibuf_loaded.free()
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue