[Bf-extensions-cvs] [289fb2b8] master: glTF exporter: Performance improvment on image export
Julien Duroure
noreply at git.blender.org
Sat Jan 4 21:17:39 CET 2020
Commit: 289fb2b8b89e7ded42f4655342d8359e6f1ae91f
Author: Julien Duroure
Date: Sat Jan 4 21:15:04 2020 +0100
Branches: master
https://developer.blender.org/rBA289fb2b8b89e7ded42f4655342d8359e6f1ae91f
glTF exporter: Performance improvment on image export
===================================================================
M io_scene_gltf2/__init__.py
M io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
M io_scene_gltf2/blender/exp/gltf2_blender_image.py
===================================================================
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 542c21e9..c8067771 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
- "version": (1, 1, 30),
+ "version": (1, 1, 31),
'blender': (2, 81, 6),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
index 183da33e..81da3780 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -24,7 +24,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
from io_scene_gltf2.io.exp import gltf2_io_binary_data
from io_scene_gltf2.io.exp import gltf2_io_image_data
from io_scene_gltf2.io.com import gltf2_io_debug
-from io_scene_gltf2.blender.exp import gltf2_blender_image
+from io_scene_gltf2.blender.exp.gltf2_blender_image import Channel, ExportImage
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
@@ -38,8 +38,8 @@ def gather_image(
return None
image_data = __get_image_data(blender_shader_sockets_or_texture_slots, export_settings)
- if image_data is None:
- # The blender image has no data
+ if image_data.empty():
+ # The export image has no data
return None
mime_type = __gather_mime_type(blender_shader_sockets_or_texture_slots, export_settings)
@@ -144,13 +144,13 @@ def __is_slot(sockets_or_slots):
return isinstance(sockets_or_slots[0], bpy.types.MaterialTextureSlot)
-def __get_image_data(sockets_or_slots, export_settings) -> gltf2_blender_image.ExportImage:
+def __get_image_data(sockets_or_slots, export_settings) -> ExportImage:
# For shared resources, such as images, we just store the portion of data that is needed in the glTF property
# in a helper class. During generation of the glTF in the exporter these will then be combined to actual binary
# resources.
if __is_socket(sockets_or_slots):
results = [__get_tex_from_socket(socket, export_settings) for socket in sockets_or_slots]
- composed_image = None
+ composed_image = ExportImage()
for result, socket in zip(results, sockets_or_slots):
if result.shader_node.image.channels == 0:
gltf2_io_debug.print_console("WARNING",
@@ -159,44 +159,45 @@ def __get_image_data(sockets_or_slots, export_settings) -> gltf2_blender_image.E
continue
# rudimentarily try follow the node tree to find the correct image data.
- source_channel = 0
+ src_chan = Channel.R
for elem in result.path:
if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateRGB):
- source_channel = {
- 'R': 0,
- 'G': 1,
- 'B': 2
+ src_chan = {
+ 'R': Channel.R,
+ 'G': Channel.G,
+ 'B': Channel.B,
}[elem.from_socket.name]
- image = gltf2_blender_image.ExportImage.from_blender_image(result.shader_node.image)
-
- target_channel = None
+ dst_chan = None
# some sockets need channel rewriting (gltf pbr defines fixed channels for some attributes)
if socket.name == 'Metallic':
- target_channel = 2
+ dst_chan = Channel.B
elif socket.name == 'Roughness':
- target_channel = 1
+ dst_chan = Channel.G
elif socket.name == 'Occlusion' and len(sockets_or_slots) > 1 and sockets_or_slots[1] is not None:
- target_channel = 0
+ dst_chan = Channel.R
elif socket.name == 'Alpha' and len(sockets_or_slots) > 1 and sockets_or_slots[1] is not None:
- composed_image.set_alpha(True)
- target_channel = 3
+ dst_chan = Channel.A
- if target_channel is not None:
- if composed_image is None:
- composed_image = gltf2_blender_image.ExportImage.white_image(image.width, image.height)
+ if dst_chan is not None:
+ composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
- composed_image[target_channel] = image[source_channel]
+ # Since metal/roughness are always used together, make sure
+ # the other channel is filled.
+ if socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
+ composed_image.fill_white(Channel.G)
+ elif socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
+ composed_image.fill_white(Channel.B)
else:
# copy full image...eventually following sockets might overwrite things
- composed_image = image
+ composed_image = ExportImage.from_blender_image(result.shader_node.image)
return composed_image
elif __is_slot(sockets_or_slots):
texture = __get_tex_from_slot(sockets_or_slots[0])
- image = gltf2_blender_image.ExportImage.from_blender_image(texture.image)
+ image = ExportImage.from_blender_image(texture.image)
return image
else:
raise NotImplementedError()
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index 828b07fe..e0eecd3c 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -14,122 +14,206 @@
import bpy
import os
-import typing
+from typing import Optional
import numpy as np
import tempfile
+import enum
-class ExportImage:
- """Custom image class that allows manipulation and encoding of images"""
- # FUTURE_WORK: as a method to allow the node graph to be better supported, we could model some of
- # the node graph elements with numpy functions
-
- def __init__(self, img: typing.Union[np.ndarray, typing.List[np.ndarray]], max_channels: int = 4,\
- blender_image: bpy.types.Image = None, has_alpha: bool = False):
- if isinstance(img, list):
- np.stack(img, axis=2)
-
- if len(img.shape) == 2:
- # images must always have a channels dimension
- img = np.expand_dims(img, axis=2)
-
- if not len(img.shape) == 3 or img.shape[2] > 4:
- raise RuntimeError("Cannot construct an export image from an array of shape {}".format(img.shape))
-
- self._img = img
- self._max_channels = max_channels
- self._blender_image = blender_image
- self._has_alpha = has_alpha
-
- def set_alpha(self, alpha: bool):
- self._has_alpha = alpha
-
- @classmethod
- def from_blender_image(cls, blender_image: bpy.types.Image):
- img = np.array(blender_image.pixels[:])
- img = img.reshape((blender_image.size[0], blender_image.size[1], blender_image.channels))
- has_alpha = blender_image.depth == 32
- return ExportImage(img=img, blender_image=blender_image, has_alpha=has_alpha)
-
- @classmethod
- def white_image(cls, width, height, num_channels: int = 4):
- img = np.ones((width, height, num_channels))
- return ExportImage(img=img)
-
- def split_channels(self):
- """return a list of numpy arrays where each list element corresponds to one image channel (r,g?,b?,a?)"""
- return np.split(self._img, self._img.shape[2], axis=2)
-
- @property
- def img(self) -> np.ndarray:
- return self._img
-
- @property
- def shape(self):
- return self._img.shape
-
- @property
- def width(self):
- return self.shape[0]
-
- @property
- def height(self):
- return self.shape[1]
-
- @property
- def channels(self):
- return self.shape[2]
-
- def __getitem__(self, key):
- """returns a new ExportImage with only the selected channels"""
- return ExportImage(self._img[:, :, key])
-
- def __setitem__(self, key, value):
- """set the selected channels to a new value"""
- if isinstance(key, slice):
- self._img[:, :, key] = value.img
- else:
- self._img[:, :, key] = value.img[:, :, 0]
-
- def append(self, other):
- if self.channels + other.channels > self._max_channels:
- raise RuntimeError("Cannot append image data to this image "
- "because the maximum number of channels is exceeded.")
-
- self._img = np.concatenate([self.img, other.img], axis=2)
-
- def __add__(self, other):
- self.append(other)
-
- def encode(self, mime_type: typing.Optional[str]) -> bytes:
- file_format = {
- "image/jpeg": "JPEG",
- "image/png": "PNG"
- }.get(mime_type, "PNG")
+class Channel(enum.IntEnum):
+ R = 0
+ G = 1
+ B = 2
+ A = 3
- if self._blender_image is not None and file_format == self._blender_image.file_format:
- src_path = bpy.path.abspath(self._blender_image.filepath_raw)
- if os.path.isfile(src_path):
- with open(src_path, "rb") as f:
- encoded_image = f.read()
- return encoded_image
+# These describe how an ExportImage's channels should be filled.
- image = bpy.data.images.new("TmpImage", width=self.width, height=self.height, alpha=self._has_alpha)
- pixels = self._img.flatten().tolist()
- image.pixels = pixels
+class FillImage:
+ """Fills a channel with the channel src_chan from a Blender image."""
+ def __init__(self, image: bpy.types.Image, src_chan: Channel):
+ self.image = image
+
@@ Diff output truncated at 10240 characters. @@
More information about the Bf-extensions-cvs
mailing list