Skip to content

Commit

Permalink
Merge pull request #2242 from KhronosGroup/RGB2BW_socket
Browse files Browse the repository at this point in the history
Fix #2176 - convert RGB 2 BW for float socket when possible
  • Loading branch information
julienduroure committed May 30, 2024
2 parents 113121f + 67f73e6 commit f0df348
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Channel(enum.IntEnum):
G = 1
B = 2
A = 3
RGB2BW = 4

# These describe how an ExportImage's channels should be filled.

Expand All @@ -41,6 +42,17 @@ def __init__(self, image: bpy.types.Image, tile, src_chan: Channel):
self.tile = tile
self.src_chan = src_chan

class FillImageRGB2BWTile:
"""Fills a channel from a Blender UDIM image, RGB 2 BW"""
def __init__(self, image: bpy.types.Image, tile):
self.image = image
self.tile = tile

class FillImageRGB2BW:
"""Fills a channel from a Blender image, RGB 2 BW"""
def __init__(self, image: bpy.types.Image):
self.image = image

class FillWhite:
"""Fills a channel with all ones (1.0)."""
pass
Expand Down Expand Up @@ -124,6 +136,12 @@ def fill_image(self, image: bpy.types.Image, dst_chan: Channel, src_chan: Channe
def fill_image_tile(self, image: bpy.types.Image, tile, dst_chan: Channel, src_chan: Channel):
self.fills[dst_chan] = FillImageTile(image, tile, src_chan)

def fill_image_bw(self, image: bpy.types.Image, dst_chan: Channel):
self.fills[dst_chan] = FillImageRGB2BW(image)

def fill_image_bw_tile(self, image: bpy.types.Image, tile, dst_chan: Channel):
self.fills[dst_chan] = FillImageRGB2BWTile(image, tile)

def store_data(self, identifier, data, type='Image'):
if type == "Image": # This is an image
self.stored[identifier] = StoreImage(data)
Expand Down Expand Up @@ -213,7 +231,7 @@ def __encode_happy_tile(self, export_settings) -> bytes:
return self.__encode_from_image_tile(self.fills[list(self.fills.keys())[0]].image, export_settings['current_udim_info']['tile'], export_settings)

def __unhappy_is_udim(self):
return any(isinstance(fill, FillImageTile) for fill in self.fills.values())
return any(isinstance(fill, FillImageTile) or isinstance(fill, FillImageRGB2BWTile) for fill in self.fills.values())


def __encode_unhappy_udim(self, export_settings) -> bytes:
Expand All @@ -222,7 +240,7 @@ def __encode_unhappy_udim(self, export_settings) -> bytes:

images = []
for fill in self.fills.values():
if isinstance(fill, FillImageTile):
if isinstance(fill, FillImageTile) or isinstance(fill, FillImageRGB2BWTile):
if fill.image not in images:
images.append((fill.image, fill.tile))
export_settings['exported_images'][fill.image.name] = 2 # 2 = partially used
Expand Down Expand Up @@ -273,6 +291,8 @@ def __encode_unhappy_udim(self, export_settings) -> bytes:
out_buf[int(dst_chan)::4] = tmp_buf[int(fill.src_chan)::4]
elif isinstance(fill, FillWith):
out_buf[int(dst_chan)::4] = fill.value
elif isinstance(fill, FillImageRGB2BWTile) and fill.image == image:
out_buf[int(dst_chan)::4] = tmp_buf[0::4] * 0.2989 + tmp_buf[1::4] * 0.5870 + tmp_buf[2::4] * 0.1140

tmp_buf = None # GC this

Expand All @@ -288,7 +308,7 @@ def __encode_unhappy(self, export_settings) -> bytes:
# Find all Blender images used
images = []
for fill in self.fills.values():
if isinstance(fill, FillImage):
if isinstance(fill, FillImage) or isinstance(fill, FillImageRGB2BW):
if fill.image not in images:
images.append(fill.image)
export_settings['exported_images'][fill.image.name] = 2 # 2 = partially used
Expand Down Expand Up @@ -321,6 +341,8 @@ def __encode_unhappy(self, export_settings) -> bytes:
out_buf[int(dst_chan)::4] = tmp_buf[int(fill.src_chan)::4]
elif isinstance(fill, FillWith):
out_buf[int(dst_chan)::4] = fill.value
elif isinstance(fill, FillImageRGB2BW) and fill.image == image:
out_buf[int(dst_chan)::4] = tmp_buf[0::4] * 0.2989 + tmp_buf[1::4] * 0.5870 + tmp_buf[2::4] * 0.1140

tmp_buf = None # GC this

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ....io.com import gltf2_io_debug
from ....io.exp.gltf2_io_user_extensions import export_user_extensions
from ..gltf2_blender_gather_cache import cached
from .extensions.gltf2_blender_image import Channel, ExportImage, FillImage, FillImageTile
from .extensions.gltf2_blender_image import Channel, ExportImage, FillImage, FillImageTile, FillImageRGB2BW
from .gltf2_blender_search_node_tree import get_texture_node_from_socket, detect_anisotropy_nodes

@cached
Expand Down Expand Up @@ -173,7 +173,7 @@ def __gather_name(export_image, use_tile, export_settings):

imgs = []
for fill in export_image.fills.values():
if isinstance(fill, FillCheck):
if isinstance(fill, FillCheck) or isinstance(fill, FillImageRGB2BW):
img = fill.image
if img not in imgs:
imgs.append(img)
Expand Down Expand Up @@ -296,7 +296,12 @@ def __get_image_data_mapping(sockets, results, use_tile, export_settings) -> Exp
elif socket.socket.name == 'Occlusion':
src_chan = Channel.R
elif socket.socket.name == 'Alpha':
src_chan = Channel.A
# For alpha, we need to check if we have a texture plugged in a Color socket
# In that case, we will convert RGB to BW
if elem.from_socket.type == "RGBA":
src_chan = Channel.RGB2BW
else:
src_chan = Channel.A
elif socket.socket.name == 'Coat Weight':
src_chan = Channel.R
elif socket.socket.name == 'Coat Roughness':
Expand Down Expand Up @@ -338,9 +343,15 @@ def __get_image_data_mapping(sockets, results, use_tile, export_settings) -> Exp

if dst_chan is not None:
if use_tile is None:
composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
if src_chan == Channel.RGB2BW:
composed_image.fill_image_bw(result.shader_node.image, dst_chan)
else:
composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
else:
composed_image.fill_image_tile(result.shader_node.image, export_settings['current_udim_info']['tile'], dst_chan, src_chan)
if src_chan == Channel.RGB2BW:
composed_image.fill_image_bw_tile(result.shader_node.image, export_settings['current_udim_info']['tile'], dst_chan)
else:
composed_image.fill_image_tile(result.shader_node.image, export_settings['current_udim_info']['tile'], dst_chan, src_chan)

# Since metal/roughness are always used together, make sure
# the other channel is filled.
Expand All @@ -357,7 +368,7 @@ def __get_image_data_mapping(sockets, results, use_tile, export_settings) -> Exp

# Check that we don't have some empty channels (based on weird images without any size for example)
keys = list(composed_image.fills.keys()) # do not loop on dict, we may have to delete an element
for k in [k for k in keys if isinstance(composed_image.fills[k], FillImage)]:
for k in [k for k in keys if isinstance(composed_image.fills[k], FillImage) or isinstance(composed_image.fills[k], FillImageRGB2BW)]:
if composed_image.fills[k].image.size[0] == 0 or composed_image.fills[k].image.size[1] == 0:
export_settings['log'].warning("Image '{}' has no size and cannot be exported.".format(
composed_image.fills[k].image))
Expand Down

0 comments on commit f0df348

Please sign in to comment.