[Bf-extensions-cvs] [dfadb306] master: glTF exporter: perf: use foreach_{get, set} for image encoding
Julien Duroure
noreply at git.blender.org
Sat Apr 11 15:42:36 CEST 2020
Commit: dfadb306b0e93b894c0639379720fd2414b6b0ec
Author: Julien Duroure
Date: Sat Apr 11 15:41:05 2020 +0200
Branches: master
https://developer.blender.org/rBAdfadb306b0e93b894c0639379720fd2414b6b0ec
glTF exporter: perf: use foreach_{get,set} for image encoding
We can now remove compositor code, no more needed
===================================================================
M io_scene_gltf2/__init__.py
M io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.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 5317f0f8..f05cd174 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -15,8 +15,8 @@
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, 2, 62),
- 'blender': (2, 82, 7),
+ "version": (1, 2, 63),
+ 'blender': (2, 83, 9),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
'warning': '',
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
index f841f0e2..22f0bc6d 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitives.py
@@ -60,8 +60,6 @@ def gather_primitives(
material = gltf2_blender_gather_materials.gather_material(blender_material,
double_sided,
export_settings)
- # NOTE: gather_material may invalidate blender_mesh (see #932),
- # so make sure not to access blender_mesh again after this point
except IndexError:
# no material at that index
pass
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index e9db7e66..34bb8e87 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -14,7 +14,7 @@
import bpy
import os
-from typing import Optional
+from typing import Optional, Tuple
import numpy as np
import tempfile
import enum
@@ -121,98 +121,8 @@ class ExportImage:
return self.__encode_from_image(self.blender_image())
def __encode_unhappy(self) -> bytes:
- result = self.__encode_unhappy_with_compositor()
- if result is not None:
- return result
- return self.__encode_unhappy_with_numpy()
-
- def __encode_unhappy_with_compositor(self) -> bytes:
- # Builds a Compositor graph that will build the correct image
- # from the description in self.fills.
- #
- # [ Image ]->[ Sep RGBA ] [ Comb RGBA ]
- # [ src_chan]--->[dst_chan ]--->[ Output ]
- #
- # This is hacky, but is about 4x faster than using
- # __encode_unhappy_with_numpy. There are some caveats though:
-
- # First, we can't handle pre-multiplied alpha.
- if Channel.A in self.fills:
- return None
-
- # Second, in order to get the same results as using image.pixels
- # (which ignores the colorspace), we need to use the 'Non-Color'
- # colorspace for all images and set the output device to 'None'. But
- # setting the colorspace on dirty images discards their changes.
- # So we can't handle dirty images that aren't already 'Non-Color'.
- for fill in self.fills:
- if isinstance(fill, FillImage):
- if fill.image.is_dirty:
- if fill.image.colorspace_settings.name != 'Non-Color':
- return None
-
- tmp_scene = None
- orig_colorspaces = {} # remembers original colorspaces
- try:
- tmp_scene = bpy.data.scenes.new('##gltf-export:tmp-scene##')
- tmp_scene.use_nodes = True
- node_tree = tmp_scene.node_tree
- for node in node_tree.nodes:
- node_tree.nodes.remove(node)
-
- out = node_tree.nodes.new('CompositorNodeComposite')
- comb_rgba = node_tree.nodes.new('CompositorNodeCombRGBA')
- for i in range(4):
- comb_rgba.inputs[i].default_value = 1.0
- node_tree.links.new(out.inputs['Image'], comb_rgba.outputs['Image'])
-
- img_size = None
- for dst_chan, fill in self.fills.items():
- if not isinstance(fill, FillImage):
- continue
-
- img = node_tree.nodes.new('CompositorNodeImage')
- img.image = fill.image
- sep_rgba = node_tree.nodes.new('CompositorNodeSepRGBA')
- node_tree.links.new(sep_rgba.inputs['Image'], img.outputs['Image'])
- node_tree.links.new(comb_rgba.inputs[dst_chan], sep_rgba.outputs[fill.src_chan])
-
- if fill.image.colorspace_settings.name != 'Non-Color':
- if fill.image.name not in orig_colorspaces:
- orig_colorspaces[fill.image.name] = \
- fill.image.colorspace_settings.name
- fill.image.colorspace_settings.name = 'Non-Color'
-
- if img_size is None:
- img_size = fill.image.size[:2]
- else:
- # All images should be the same size (should be
- # guaranteed by gather_texture_info)
- assert img_size == fill.image.size[:2]
-
- width, height = img_size or (1, 1)
- return _render_temp_scene(
- tmp_scene=tmp_scene,
- width=width,
- height=height,
- file_format=self.file_format,
- color_mode='RGB',
- colorspace='None',
- )
-
- finally:
- for img_name, colorspace in orig_colorspaces.items():
- bpy.data.images[img_name].colorspace_settings.name = colorspace
-
- if tmp_scene is not None:
- bpy.data.scenes.remove(tmp_scene, do_unlink=True)
-
-
- def __encode_unhappy_with_numpy(self):
- # Read the pixels of each image with image.pixels, put them into a
- # numpy, and assemble the desired image that way. This is the slowest
- # method, and the conversion to Python data eats a lot of memory, so
- # it's only used as a last resort.
+ # We need to assemble the image out of channels.
+ # Do it with numpy and image.pixels.
result = None
img_fills = {
@@ -227,42 +137,40 @@ class ExportImage:
image = bpy.data.images[image_name]
if result is None:
- result = np.ones((image.size[0], image.size[1], 4), np.float32)
+ dim = (image.size[0], image.size[1])
+ result = np.ones(dim[0] * dim[1] * 4, np.float32)
+ tmp_buf = np.empty(dim[0] * dim[1] * 4, np.float32)
# Images should all be the same size (should be guaranteed by
# gather_texture_info).
- assert (image.size[0], image.size[1]) == result.shape[:2]
+ assert (image.size[0], image.size[1]) == dim
- # Slow and eats all your memory.
- pixels = np.array(image.pixels[:])
-
- pixels = pixels.reshape((image.size[0], image.size[1], image.channels))
+ image.pixels.foreach_get(tmp_buf)
for dst_chan, img_fill in img_fills.items():
if img_fill.image == image:
- result[:, :, dst_chan] = pixels[:, :, img_fill.src_chan]
+ result[int(dst_chan)::4] = tmp_buf[int(img_fill.src_chan)::4]
- pixels = None # GC this please
+ tmp_buf = None # GC this
if result is None:
# No ImageFills; use a 1x1 white pixel
+ dim = (1, 1)
result = np.array([1.0, 1.0, 1.0, 1.0])
- result = result.reshape((1, 1, 4))
- return self.__encode_from_numpy_array(result)
+ return self.__encode_from_numpy_array(result, dim)
- def __encode_from_numpy_array(self, array: np.ndarray) -> bytes:
+ def __encode_from_numpy_array(self, pixels: np.ndarray, dim: Tuple[int, int]) -> bytes:
tmp_image = None
try:
tmp_image = bpy.data.images.new(
"##gltf-export:tmp-image##",
- width=array.shape[0],
- height=array.shape[1],
+ width=dim[0],
+ height=dim[1],
alpha=Channel.A in self.fills,
)
assert tmp_image.channels == 4 # 4 regardless of the alpha argument above.
- # Also slow and eats all your memory.
- tmp_image.pixels = array.flatten().tolist()
+ tmp_image.pixels.foreach_set(pixels)
return _encode_temp_image(tmp_image, self.file_format)
@@ -296,8 +204,13 @@ class ExportImage:
try:
tmp_image = image.copy()
tmp_image.update()
+
if image.is_dirty:
- tmp_image.pixels = image.pixels[:]
+ # Copy the pixels to get the changes
+ tmp_buf = np.empty(image.size[0] * image.size[1] * 4, np.float32)
+ image.pixels.foreach_get(tmp_buf)
+ tmp_image.pixels.foreach_set(tmp_buf)
+ tmp_buf = None # GC this
return _encode_temp_image(tmp_image, self.file_format)
finally:
@@ -316,36 +229,3 @@ def _encode_temp_image(tmp_image: bpy.types.Image, file_format: str) -> bytes:
with open(tmpfilename, "rb") as f:
return f.read()
-
-
-def _render_temp_scene(
- tmp_scene: bpy.types.Scene,
- width: int,
- height: int,
- file_format: str,
- color_mode: str,
- colorspace: str,
-) -> bytes:
- """Set render settings, render to a file, and read back."""
- tmp_scene.render.resolution_x = width
- tmp_scene.render.resolution_y = height
- tmp_scene.render.resolution_percentage = 100
- tmp_scene.display_settings.display_device = colorspace
- tmp_scene.render.image_settings.color_mode = color_mode
- tmp_scene.render.dither_intensity = 0.0
-
- # Turn off all metadata (stuff like use_stamp_date, etc.)
- for attr in dir(tmp_scene.render):
- if attr.startswith('use_stamp_'):
- setattr(tmp_scene.render, attr, False)
-
- with tempfile.TemporaryDirectory() as tmpdirname:
- tmpfilename = tmpdirname + "/
@@ Diff output truncated at 10240 characters. @@
More information about the Bf-extensions-cvs
mailing list