Skip to content

Commit

Permalink
Increased speed of saturation check by resizing copy of datablock.
Browse files Browse the repository at this point in the history
Addresses bug report #178
  • Loading branch information
TheDuckCow committed Dec 24, 2021
1 parent df1e91c commit 8bc18ee
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 26 deletions.
77 changes: 52 additions & 25 deletions MCprep_addon/materials/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,50 +805,71 @@ def rgb_to_saturation(r, g, b):
if mx == 0:
return 0
mn = min(r, g, b)
df = mx-mn
return (df/mx)
df = mx - mn
return (df / mx)

if not image:
return None
conf.log("Checking image for grayscale "+image.name, vv_only=True)
if 'grayscale' in image: # cache
conf.log("Checking image for grayscale " + image.name, vv_only=True)
if 'grayscale' in image: # cache
return image['grayscale']
if not image.pixels:
conf.log("Not an image / no pixels", vv_only=True)
return None

# setup sampling to limit number of processed pixels
max_samples = 1000
pxl_count = len(image.pixels)/image.channels
interval = int(pxl_count/max_samples) if pxl_count>max_samples else 1
max_samples = 1024 # A 32 by 32 image. May be higher with rounding.
pxl_count = len(image.pixels) / image.channels
aspect = image.size[0] / image.size[1]
datablock_copied = False

if pxl_count > max_samples:
imgcp = image.copy() # Duplicate the datablock

# Find new pixel sizes keeping aspect ratio, equation of:
# 1024 = nwith * nheight
# 1024 = (nheight * aspect) * nheight
nheight = (1024 / aspect)**0.5
imgcp.scale(int(nheight * aspect), int(nheight))
pxl_count = imgcp.size[0] * imgcp.size[1]
datablock_copied = True
else:
imgcp = image

# Pixel by pixel saturation checks, with some wiggle room thresholds
thresh = 0.1 # treat saturated if any more than 10%
thresh = 0.1 # treat saturated if any more than 10%

# max pixels above thresh to return as saturated,
# 15% is chosen as ~double the % of "yellow" pixels in vanilla jungle leaves
max_thresh = 0.15*pxl_count
max_thresh = 0.15 * pxl_count

# running count to check against
pixels_saturated = 0

# check all pixels until
for ind in range(int(pxl_count))[::interval]:
ind = ind*image.channels # could be rgb or rgba
if image.channels > 3 and image.pixels[ind+3] == 0:
continue # skip alpha pixels during check
if rgb_to_saturation(image.pixels[ind],
image.pixels[ind+1],
image.pixels[ind+2]) > thresh:
is_grayscale = True # True until proven false.

# check all pixels until exceeded threshold.
for ind in range(int(pxl_count))[::]:
ind = ind * imgcp.channels # could be rgb or rgba
if imgcp.channels > 3 and imgcp.pixels[ind + 3] == 0:
continue # skip alpha pixels during check
this_saturated = rgb_to_saturation(
imgcp.pixels[ind],
imgcp.pixels[ind + 1],
imgcp.pixels[ind + 2])
if this_saturated > thresh:
pixels_saturated += 1
if pixels_saturated >= max_thresh:
image['grayscale'] = False
conf.log("Image not grayscale: "+image.name, vv_only=True)
return False
is_grayscale = False
conf.log("Image not grayscale: " + image.name, vv_only=True)
break

image['grayscale'] = True # set cache
conf.log("Image is grayscale: "+image.name, vv_only=True)
return True
if datablock_copied: # Cleanup if image was copied to scale down size.
bpy.data.images.remove(imgcp)

image['grayscale'] = is_grayscale # set cache
conf.log("Image is grayscale: " + image.name, vv_only=True)
return is_grayscale


def set_saturation_material(mat):
Expand Down Expand Up @@ -1316,8 +1337,14 @@ def matgen_cycles_principled(mat, passes, use_reflections, use_emission, only_so
links.new(nodeMixEmit.outputs["Shader"], nodeMixTrans.inputs[2])
links.new(nodeMixTrans.outputs["Shader"], nodeOut.inputs[0])

nodeInputs = [[principled.inputs["Base Color"], nodeEmit.inputs["Color"], nodeEmitCam.inputs["Color"]], [nodeMixTrans.inputs["Fac"]], [nodeMixEmit.inputs[0]], [
principled.inputs["Roughness"]], [principled.inputs["Metallic"]], [principled.inputs["Specular"]], [principled.inputs["Normal"]]]
nodeInputs = [
[principled.inputs["Base Color"], nodeEmit.inputs["Color"], nodeEmitCam.inputs["Color"]],
[nodeMixTrans.inputs["Fac"]],
[nodeMixEmit.inputs[0]],
[principled.inputs["Roughness"]],
[principled.inputs["Metallic"]],
[principled.inputs["Specular"]],
[principled.inputs["Normal"]]]

# generate texture format and connect

Expand Down
1 change: 0 additions & 1 deletion MCprep_addon/util_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import os
import addon_utils

from . import conf
from . import util
from . import tracking

Expand Down
4 changes: 4 additions & 0 deletions test_files/addon_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1099,10 +1099,14 @@ def detect_desaturated_images(self):

for tex in saturated:
img = bpy.data.images.load(os.path.join(base, tex))
if not img:
raise Exception('Failed to load img ' + str(tex))
if is_image_grayscale(img) is True:
raise Exception('Image {} detected as grayscale, should be saturated'.format(tex))
for tex in desaturated:
img = bpy.data.images.load(os.path.join(base, tex))
if not img:
raise Exception('Failed to load img ' + str(tex))
if is_image_grayscale(img) is False:
raise Exception('Image {} detected as saturated - should be grayscale'.format(tex))

Expand Down

0 comments on commit 8bc18ee

Please sign in to comment.