Sculpt: Add global automasking propagation steps #117316

Merged
Hans Goudey merged 8 commits from Sean-Kim/blender:102377-auto-masking into main 2024-01-29 15:39:50 +01:00
68 changed files with 1516 additions and 1207 deletions
Showing only changes of commit 801428aa84 - Show all commits

View File

@ -12,7 +12,7 @@
# This can also be an environment variable.
# OPENIMAGEIO_FOUND, If false, do not try to use OpenImageIO.
# OPENIMAGEIO_PUGIXML_FOUND, Indicates whether OIIO has biltin PuguXML parser.
# OPENIMAGEIO_IDIFF, full path to idiff application if found.
# OPENIMAGEIO_TOOL, full path to oiiotool application if found.
#
# also defined, but not for general use are
# OPENIMAGEIO_LIBRARY, where to find the OpenImageIO library.
@ -51,9 +51,9 @@ find_library(OPENIMAGEIO_LIBRARY
set(_openimageio_LIBRARIES ${OPENIMAGEIO_LIBRARY})
find_file(OPENIMAGEIO_IDIFF
find_file(OPENIMAGEIO_TOOL
NAMES
idiff
oiiotool
HINTS
${_openimageio_SEARCH_DIRS}
PATH_SUFFIXES
@ -115,7 +115,7 @@ mark_as_advanced(
OPENIMAGEIO_INCLUDE_DIR
OPENIMAGEIO_LIBRARY
OPENIMAGEIO_UTIL_LIBRARY
OPENIMAGEIO_IDIFF
OPENIMAGEIO_TOOL
)
unset(_openimageio_SEARCH_DIRS)

View File

@ -684,7 +684,7 @@ if(NOT OpenImageIO_FOUND)
set(OIIO_OPTIMIZED optimized ${OPENIMAGEIO_LIBPATH}/OpenImageIO.lib optimized ${OPENIMAGEIO_LIBPATH}/OpenImageIO_Util.lib)
set(OIIO_DEBUG debug ${OPENIMAGEIO_LIBPATH}/OpenImageIO_d.lib debug ${OPENIMAGEIO_LIBPATH}/OpenImageIO_Util_d.lib)
set(OPENIMAGEIO_LIBRARIES ${OIIO_OPTIMIZED} ${OIIO_DEBUG})
set(OPENIMAGEIO_IDIFF "${OPENIMAGEIO}/bin/idiff.exe")
set(OPENIMAGEIO_TOOL "${OPENIMAGEIO}/bin/oiiotool.exe")
endif()
if(WITH_LLVM)

View File

@ -1718,10 +1718,12 @@ class CyclesPreferences(bpy.types.AddonPreferences):
col.prop(self, "metalrt")
if compute_device_type == 'HIP':
has_cuda, has_optix, has_hip, has_metal, has_oneapi, has_hiprt = _cycles.get_device_types()
row = layout.row()
row.enabled = has_hiprt
row.prop(self, "use_hiprt")
import platform
if platform.system() == "Windows": # HIP-RT is currently only supported on Windows
has_cuda, has_optix, has_hip, has_metal, has_oneapi, has_hiprt = _cycles.get_device_types()
row = layout.row()
row.enabled = has_hiprt
row.prop(self, "use_hiprt")
elif compute_device_type == 'ONEAPI' and _cycles.with_embree_gpu:
row = layout.row()

View File

@ -650,6 +650,9 @@ static bool bake_setup_pass(Scene *scene, const string &bake_type, const int bak
integrator->set_use_direct_light(use_direct_light);
integrator->set_use_indirect_light(use_indirect_light);
const PassInfo pass_info = Pass::get_info(type);
integrator->set_use_denoise(pass_info.support_denoise);
return true;
}

View File

@ -6,6 +6,7 @@ __all__ = (
"paths",
"modules",
"check",
"check_extension",
"enable",
"disable",
"disable_all",
@ -82,6 +83,10 @@ def _fake_module(mod_name, mod_path, speedy=True, force_support=None):
if _bpy.app.debug_python:
print("fake_module", mod_path, mod_name)
if mod_name.startswith(_ext_base_pkg_idname_with_dot):
return _fake_module_from_extension(mod_name, mod_path, force_support=force_support)
import ast
ModuleType = type(ast)
try:
@ -268,6 +273,14 @@ def check(module_name):
return loaded_default, loaded_state
def check_extension(module_name):
"""
Return true if the module is an extension.
"""
return module_name.startswith(_ext_base_pkg_idname_with_dot)
# utility functions
@ -553,21 +566,25 @@ def _blender_manual_url_prefix():
return "https://docs.blender.org/manual/%s/%d.%d" % (_bpy.utils.manual_language_code(), *_bpy.app.version[:2])
def _bl_info_basis():
return {
"name": "",
"author": "",
"version": (),
"blender": (),
"location": "",
"description": "",
"doc_url": "",
"support": 'COMMUNITY',
"category": "",
"warning": "",
"show_expanded": False,
}
def module_bl_info(mod, *, info_basis=None):
if info_basis is None:
info_basis = {
"name": "",
"author": "",
"version": (),
"blender": (),
"location": "",
"description": "",
"doc_url": "",
"support": 'COMMUNITY',
"category": "",
"warning": "",
"show_expanded": False,
}
info_basis = _bl_info_basis()
addon_info = getattr(mod, "bl_info", {})
@ -597,6 +614,55 @@ def module_bl_info(mod, *, info_basis=None):
return addon_info
# -----------------------------------------------------------------------------
# Extension Utilities
def _fake_module_from_extension(mod_name, mod_path, force_support=None):
# Extract the `bl_info` from an extensions manifest.
# This is returned as a module which has a `bl_info` variable.
# When support for non-extension add-ons is dropped (Blender v5.0 perhaps)
# this can be updated not to use a fake module.
import os
import tomllib
bl_info = _bl_info_basis()
filepath = os.path.join(os.path.dirname(mod_path), _ext_manifest_filename_toml)
try:
with open(filepath, "rb") as fh:
data = tomllib.load(fh)
bl_info["name"] = data["name"]
bl_info["version"] = data["version"]
bl_info["author"] = data["author"]
bl_info["category"] = "Development" # Dummy, will be removed.
except BaseException as ex:
print("Error:", str(ex), "in", filepath)
return None
# Full validation must be done on install.
try:
assert type(bl_info["name"]) is str
assert type(bl_info["version"]) is str
assert type(bl_info["author"]) is str
assert type(bl_info["category"]) is str
except BaseException as ex:
print("Error:", str(ex), "in", filepath)
return None
if force_support is not None:
bl_info["support"] = force_support
ModuleType = type(os)
mod = ModuleType(mod_name)
mod.bl_info = bl_info
# TODO: implement a way to update based on the time of the TOML file.
mod.__file__ = mod_path
mod.__time__ = os.path.getmtime(mod_path)
return mod
# -----------------------------------------------------------------------------
# Extensions
@ -625,6 +691,7 @@ class _ext_global:
# The name (in `sys.modules`) keep this short because it's stored as part of add-on modules name.
_ext_base_pkg_idname = "bl_ext"
_ext_base_pkg_idname_with_dot = _ext_base_pkg_idname + "."
_ext_manifest_filename_toml = "bl_manifest.toml"
def _extension_preferences_idmap():

View File

@ -4711,9 +4711,9 @@ def km_object_mode(params):
("object.join", {"type": 'J', "value": 'PRESS', "ctrl": True}, None),
("wm.context_toggle", {"type": 'PERIOD', "value": 'PRESS', "ctrl": True},
{"properties": [("data_path", 'tool_settings.use_transform_data_origin')]}),
("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
("anim.keyframe_insert_menu", {"type": 'K', "value": 'PRESS'}, {"properties": [("always_prompt", True)]}),
("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None),
("anim.keying_set_active_set", {"type": 'I', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
("anim.keying_set_active_set", {"type": 'K', "value": 'PRESS', "shift": True}, None),
("collection.create", {"type": 'G', "value": 'PRESS', "ctrl": True}, None),
("collection.objects_remove", {"type": 'G', "value": 'PRESS', "ctrl": True, "alt": True}, None),
("collection.objects_remove_all",
@ -4730,6 +4730,16 @@ def km_object_mode(params):
*_template_items_context_menu("VIEW3D_MT_object_context_menu", params.context_menu_event),
])
if params.use_pie_click_drag:
items.extend([
("anim.keyframe_insert", {"type": 'I', "value": 'CLICK'}, None),
op_menu_pie("ANIM_MT_keyframe_insert_pie", {"type": 'I', "value": 'CLICK_DRAG'}),
])
else:
items.extend([
("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
])
if params.legacy:
items.extend([
("object.select_mirror", {"type": 'M', "value": 'PRESS', "shift": True, "ctrl": True}, None),
@ -4850,9 +4860,9 @@ def km_pose(params):
("armature.assign_to_collection", {"type": 'M', "value": 'PRESS', "shift": True}, None),
("armature.move_to_collection", {"type": 'M', "value": 'PRESS'}, None),
("transform.bbone_resize", {"type": 'S', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
("anim.keyframe_insert_menu", {"type": 'K', "value": 'PRESS'}, {"properties": [("always_prompt", True)]}),
("anim.keyframe_delete_v3d", {"type": 'I', "value": 'PRESS', "alt": True}, None),
("anim.keying_set_active_set", {"type": 'I', "value": 'PRESS', "shift": True, "ctrl": True, "alt": True}, None),
("anim.keying_set_active_set", {"type": 'K', "value": 'PRESS', "shift": True}, None),
("pose.push", {"type": 'E', "value": 'PRESS', "ctrl": True}, None),
("pose.relax", {"type": 'E', "value": 'PRESS', "alt": True}, None),
("pose.breakdown", {"type": 'E', "value": 'PRESS', "shift": True}, None),
@ -4861,6 +4871,16 @@ def km_pose(params):
*_template_items_context_menu("VIEW3D_MT_pose_context_menu", params.context_menu_event),
])
if params.use_pie_click_drag:
items.extend([
("anim.keyframe_insert", {"type": 'I', "value": 'CLICK'}, None),
op_menu_pie("ANIM_MT_keyframe_insert_pie", {"type": 'I', "value": 'CLICK_DRAG'}),
])
else:
items.extend([
("anim.keyframe_insert", {"type": 'I', "value": 'PRESS'}, None),
])
return keymap

View File

@ -11,6 +11,7 @@ if "bpy" in locals():
del reload
_modules = [
"anim",
"asset_shelf",
"node_add_menu",
"node_add_menu_compositor",

View File

@ -0,0 +1,31 @@
from bpy.types import Menu
class ANIM_MT_keyframe_insert_pie(Menu):
bl_label = "Keyframe Insert Pie"
def draw(self, context):
layout = self.layout
pie = layout.menu_pie()
prop = pie.operator("anim.keyframe_insert_by_name", text="Location")
prop.type = "Location"
prop = pie.operator("anim.keyframe_insert_by_name", text="Scale")
prop.type = "Scaling"
prop = pie.operator("anim.keyframe_insert_by_name", text="Available")
prop.type = "Available"
prop = pie.operator("anim.keyframe_insert_by_name", text="Rotation")
prop.type = "Rotation"
classes = (
ANIM_MT_keyframe_insert_pie,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)

View File

@ -2253,132 +2253,141 @@ class USERPREF_PT_addons(AddOnPanel, Panel):
if show_enabled_only:
is_visible = is_visible and is_enabled
if is_visible:
if search and not (
(search in info["name"].lower() or
search in iface_(info["name"]).lower()) or
(info["author"] and (search in info["author"].lower())) or
((filter == "All") and (search in info["category"].lower() or
search in iface_(info["category"]).lower()))
):
continue
if not is_visible:
continue
# Addon UI Code
col_box = col.column()
box = col_box.box()
colsub = box.column()
row = colsub.row(align=True)
if search and not (
(search in info["name"].lower() or
search in iface_(info["name"]).lower()) or
(info["author"] and (search in info["author"].lower())) or
((filter == "All") and (search in info["category"].lower() or
search in iface_(info["category"]).lower()))
):
continue
# Addon UI Code
col_box = col.column()
box = col_box.box()
colsub = box.column()
row = colsub.row(align=True)
is_extension = addon_utils.check_extension(module_name)
row.operator(
"preferences.addon_expand",
icon='DISCLOSURE_TRI_DOWN' if info["show_expanded"] else 'DISCLOSURE_TRI_RIGHT',
emboss=False,
).module = module_name
if not use_extension_repos:
row.operator(
"preferences.addon_expand",
icon='DISCLOSURE_TRI_DOWN' if info["show_expanded"] else 'DISCLOSURE_TRI_RIGHT',
"preferences.addon_disable" if is_enabled else "preferences.addon_enable",
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT', text="",
emboss=False,
).module = module_name
if not use_extension_repos:
row.operator(
"preferences.addon_disable" if is_enabled else "preferences.addon_enable",
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT', text="",
emboss=False,
).module = module_name
sub = row.row()
sub.active = is_enabled
if use_extension_repos:
sub.label(text=iface_(info["name"]))
else:
sub.label(text="%s: %s" % (iface_(info["category"]), iface_(info["name"])))
sub = row.row()
sub.active = is_enabled
if use_extension_repos:
sub.label(text=iface_(info["name"]))
else:
sub.label(text="%s: %s" % (iface_(info["category"]), iface_(info["name"])))
if info["warning"]:
sub.label(icon='ERROR')
if info["warning"]:
sub.label(icon='ERROR')
# icon showing support level.
if not use_extension_repos:
sub.label(icon=self._support_icon_mapping.get(info["support"], 'QUESTION'))
# icon showing support level.
if not use_extension_repos:
sub.label(icon=self._support_icon_mapping.get(info["support"], 'QUESTION'))
# Expanded UI (only if additional info is available)
if info["show_expanded"]:
if value := info["description"]:
split = colsub.row().split(factor=0.15)
split.label(text="Description:")
split.label(text=iface_(value))
if value := info["location"]:
split = colsub.row().split(factor=0.15)
split.label(text="Location:")
split.label(text=iface_(value))
if mod:
split = colsub.row().split(factor=0.15)
split.label(text="File:")
split.label(text=mod.__file__, translate=False)
if value := info["author"]:
split = colsub.row().split(factor=0.15)
split.label(text="Author:")
split.label(text=value, translate=False)
if value := info["version"]:
split = colsub.row().split(factor=0.15)
split.label(text="Version:")
# Extensions use SEMVER.
if is_extension:
split.label(text=value, translate=False)
else:
split.label(text=".".join(str(x) for x in value), translate=False)
if value := info["warning"]:
split = colsub.row().split(factor=0.15)
split.label(text="Warning:")
split.label(text=" " + iface_(value), icon='ERROR')
del value
# Expanded UI (only if additional info is available)
if info["show_expanded"]:
if info["description"]:
split = colsub.row().split(factor=0.15)
split.label(text="Description:")
split.label(text=iface_(info["description"]))
if info["location"]:
split = colsub.row().split(factor=0.15)
split.label(text="Location:")
split.label(text=iface_(info["location"]))
if mod:
split = colsub.row().split(factor=0.15)
split.label(text="File:")
split.label(text=mod.__file__, translate=False)
if info["author"]:
split = colsub.row().split(factor=0.15)
split.label(text="Author:")
split.label(text=info["author"], translate=False)
if info["version"]:
split = colsub.row().split(factor=0.15)
split.label(text="Version:")
split.label(text=".".join(str(x) for x in info["version"]), translate=False)
if info["warning"]:
split = colsub.row().split(factor=0.15)
split.label(text="Warning:")
split.label(text=" " + iface_(info["warning"]), icon='ERROR')
user_addon = USERPREF_PT_addons.is_user_addon(mod, user_addon_paths)
if info["doc_url"] or info.get("tracker_url"):
split = colsub.row().split(factor=0.15)
split.label(text="Internet:")
sub = split.row()
if info["doc_url"]:
sub.operator(
"wm.url_open", text="Documentation", icon='HELP',
).url = info["doc_url"]
# Only add "Report a Bug" button if tracker_url is set
# or the add-on is bundled (use official tracker then).
if info.get("tracker_url"):
sub.operator(
"wm.url_open", text="Report a Bug", icon='URL',
).url = info["tracker_url"]
elif not user_addon:
addon_info = (
"Name: %s %s\n"
"Author: %s\n"
) % (info["name"], str(info["version"]), info["author"])
props = sub.operator(
"wm.url_open_preset", text="Report a Bug", icon='URL',
)
props.type = 'BUG_ADDON'
props.id = addon_info
user_addon = USERPREF_PT_addons.is_user_addon(mod, user_addon_paths)
if info["doc_url"] or info.get("tracker_url"):
split = colsub.row().split(factor=0.15)
split.label(text="Internet:")
sub = split.row()
if info["doc_url"]:
sub.operator(
"wm.url_open", text="Documentation", icon='HELP',
).url = info["doc_url"]
# Only add "Report a Bug" button if tracker_url is set
# or the add-on is bundled (use official tracker then).
if info.get("tracker_url"):
sub.operator(
"wm.url_open", text="Report a Bug", icon='URL',
).url = info["tracker_url"]
elif not user_addon:
addon_info = (
"Name: %s %s\n"
"Author: %s\n"
) % (info["name"], str(info["version"]), info["author"])
props = sub.operator(
"wm.url_open_preset", text="Report a Bug", icon='URL',
)
props.type = 'BUG_ADDON'
props.id = addon_info
if user_addon:
split = colsub.row().split(factor=0.15)
split.label(text="User:")
split.operator(
"preferences.addon_remove", text="Remove", icon='CANCEL',
).module = mod.__name__
if user_addon:
split = colsub.row().split(factor=0.15)
split.label(text="User:")
split.operator(
"preferences.addon_remove", text="Remove", icon='CANCEL',
).module = mod.__name__
# Show addon user preferences
if is_enabled:
addon_preferences = prefs.addons[module_name].preferences
if addon_preferences is not None:
draw = getattr(addon_preferences, "draw", None)
if draw is not None:
addon_preferences_class = type(addon_preferences)
box_prefs = col_box.box()
box_prefs.label(text="Preferences:")
addon_preferences_class.layout = box_prefs
try:
draw(context)
except BaseException:
import traceback
traceback.print_exc()
box_prefs.label(text="Error (see console)", icon='ERROR')
del addon_preferences_class.layout
if use_extension_repos:
row.operator(
"preferences.addon_disable" if is_enabled else "preferences.addon_enable",
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT', text="",
emboss=False,
).module = module_name
# Show addon user preferences
if is_enabled:
addon_preferences = prefs.addons[module_name].preferences
if addon_preferences is not None:
draw = getattr(addon_preferences, "draw", None)
if draw is not None:
addon_preferences_class = type(addon_preferences)
box_prefs = col_box.box()
box_prefs.label(text="Preferences:")
addon_preferences_class.layout = box_prefs
try:
draw(context)
except BaseException:
import traceback
traceback.print_exc()
box_prefs.label(text="Error (see console)", icon='ERROR')
del addon_preferences_class.layout
if use_extension_repos:
row.operator(
"preferences.addon_disable" if is_enabled else "preferences.addon_enable",
icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT', text="",
emboss=False,
).module = module_name
# Append missing scripts
# First collect scripts that are used but have no script file.

View File

@ -2772,7 +2772,7 @@ class VIEW3D_MT_object_animation(Menu):
layout = self.layout
layout.operator("anim.keyframe_insert", text="Insert Keyframe")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set").always_prompt = True
layout.operator("anim.keyframe_delete_v3d", text="Delete Keyframes...")
layout.operator("anim.keyframe_clear_v3d", text="Clear Keyframes...")
layout.operator("anim.keying_set_active_set", text="Change Keying Set...")
@ -3037,7 +3037,7 @@ class VIEW3D_MT_object_context_menu(Menu):
layout.separator()
layout.operator("anim.keyframe_insert", text="Insert Keyframe")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set").always_prompt = True
layout.separator()
@ -4155,7 +4155,7 @@ class VIEW3D_MT_pose_context_menu(Menu):
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("anim.keyframe_insert", text="Insert Keyframe")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe with Keying Set").always_prompt = True
layout.separator()

View File

@ -29,7 +29,7 @@ extern "C" {
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 15
#define BLENDER_FILE_SUBVERSION 16
/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to

View File

@ -23,8 +23,7 @@
#include "BLT_translation.h"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
/* Allow using deprecated functionality for .blend file I/O. */
#define DNA_DEPRECATED_ALLOW

View File

@ -31,6 +31,7 @@ static constexpr StringRef IDP_KEY_SUBTYPE("subtype");
static constexpr StringRef IDP_KEY_VALUE("value");
static constexpr StringRef IDP_PROPERTY_TYPENAME_STRING("IDP_STRING");
static constexpr StringRef IDP_PROPERTY_TYPENAME_BOOL("IDP_BOOL");
static constexpr StringRef IDP_PROPERTY_TYPENAME_INT("IDP_INT");
static constexpr StringRef IDP_PROPERTY_TYPENAME_FLOAT("IDP_FLOAT");
static constexpr StringRef IDP_PROPERTY_TYPENAME_DOUBLE("IDP_DOUBLE");
@ -125,6 +126,11 @@ struct DictionaryEntryParser {
return get_string(IDP_KEY_VALUE);
}
std::optional<bool> get_bool_value() const
{
return get_bool(IDP_KEY_VALUE);
}
std::optional<int32_t> get_int_value() const
{
return get_int(IDP_KEY_VALUE);
@ -196,6 +202,21 @@ struct DictionaryEntryParser {
return value->as_array_value();
}
std::optional<bool> get_bool(StringRef key) const
{
const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key);
if (value_ptr == nullptr) {
return std::nullopt;
}
const DictionaryValue::LookupValue &value = *value_ptr;
if (value->type() != eValueType::Boolean) {
return std::nullopt;
}
return value->as_boolean_value()->value();
}
std::optional<int32_t> get_int(StringRef key) const
{
const DictionaryValue::LookupValue *value_ptr = lookup.lookup_ptr(key);
@ -321,6 +342,46 @@ class IDPStringSerializer : public IDPropertySerializer {
}
};
/** \brief IDPSerializer for IDP_INT. */
class IDPBoolSerializer : public IDPropertySerializer {
public:
constexpr IDPBoolSerializer() = default;
std::string type_name() const override
{
return IDP_PROPERTY_TYPENAME_BOOL;
}
std::optional<eIDPropertyType> property_type() const override
{
return IDP_BOOLEAN;
}
std::shared_ptr<DictionaryValue> idprop_to_dictionary(
const IDProperty *id_property) const override
{
std::shared_ptr<DictionaryValue> result = create_dictionary(id_property);
DictionaryValue::Items &attributes = result->elements();
attributes.append_as(std::pair(IDP_KEY_VALUE, new BooleanValue(IDP_Bool(id_property) != 0)));
return result;
}
std::unique_ptr<IDProperty, IDPropertyDeleter> entry_to_idprop(
DictionaryEntryParser &entry_reader) const override
{
BLI_assert(*(entry_reader.get_type()) == IDP_BOOLEAN);
std::optional<std::string> name = entry_reader.get_name();
if (!name.has_value()) {
return nullptr;
}
std::optional<bool> extracted_value = entry_reader.get_bool_value();
if (!extracted_value.has_value()) {
return nullptr;
}
return create_bool(name->c_str(), *extracted_value);
}
};
/** \brief IDPSerializer for IDP_INT. */
class IDPIntSerializer : public IDPropertySerializer {
public:
@ -704,6 +765,7 @@ class IDPUnknownSerializer : public IDPropertySerializer {
/* Serializers are constructed statically to remove construction/destruction. */
static constexpr IDPStringSerializer IDP_SERIALIZER_STRING;
static constexpr IDPBoolSerializer IDP_SERIALIZER_BOOL;
static constexpr IDPIntSerializer IDP_SERIALIZER_INT;
static constexpr IDPFloatSerializer IDP_SERIALIZER_FLOAT;
static constexpr IDPDoubleSerializer IDP_SERIALIZER_DOUBLE;
@ -718,6 +780,9 @@ static const IDPropertySerializer &serializer_for(eIDPropertyType property_type)
case IDP_STRING:
return IDP_SERIALIZER_STRING;
case IDP_BOOLEAN:
return IDP_SERIALIZER_BOOL;
case IDP_INT:
return IDP_SERIALIZER_INT;
@ -745,6 +810,9 @@ static const IDPropertySerializer &serializer_for(StringRef idprop_typename)
if (idprop_typename == IDP_PROPERTY_TYPENAME_STRING) {
return IDP_SERIALIZER_STRING;
}
if (idprop_typename == IDP_PROPERTY_TYPENAME_BOOL) {
return IDP_SERIALIZER_BOOL;
}
if (idprop_typename == IDP_PROPERTY_TYPENAME_INT) {
return IDP_SERIALIZER_INT;
}

View File

@ -26,7 +26,7 @@
#include "DNA_listBase.h"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "IMB_openexr.hh"
#include "GPU_texture.h"
@ -35,6 +35,8 @@
#include <cstring>
using blender::float4;
/* Statics */
static ListBase studiolights;
static int last_studiolight_id = 0;
@ -487,11 +489,11 @@ static void studiolight_create_matcap_specular_gputexture(StudioLight *sl)
sl->flag |= STUDIOLIGHT_MATCAP_SPECULAR_GPUTEXTURE;
}
static void studiolight_calculate_radiance(ImBuf *ibuf, float color[4], const float direction[3])
static float4 studiolight_calculate_radiance(ImBuf *ibuf, const float direction[3])
{
float uv[2];
direction_to_equirect(uv, direction);
nearest_interpolation_color_wrap(ibuf, nullptr, color, uv[0] * ibuf->x, uv[1] * ibuf->y);
return blender::imbuf::interpolate_nearest_fl(ibuf, uv[0] * ibuf->x, uv[1] * ibuf->y);
}
/*
@ -692,7 +694,7 @@ static void studiolight_radiance_preview(uint *icon_buffer, StudioLight *sl)
uint alphamask = alpha_circle_mask(dx, dy, 0.5f - texel_size[0], 0.5f);
if (alphamask != 0) {
float normal[3], direction[3], color[4];
float normal[3], direction[3];
const float incoming[3] = {0.0f, 0.0f, -1.0f};
sphere_normal_from_uv(normal, dx, dy);
reflect_v3_v3v3(direction, incoming, normal);
@ -700,7 +702,7 @@ static void studiolight_radiance_preview(uint *icon_buffer, StudioLight *sl)
SWAP(float, direction[1], direction[2]);
direction[1] = -direction[1];
studiolight_calculate_radiance(sl->equirect_radiance_buffer, color, direction);
float4 color = studiolight_calculate_radiance(sl->equirect_radiance_buffer, direction);
*pixel = rgb_to_cpack(linearrgb_to_srgb(color[0]),
linearrgb_to_srgb(color[1]),
@ -716,6 +718,8 @@ static void studiolight_radiance_preview(uint *icon_buffer, StudioLight *sl)
static void studiolight_matcap_preview(uint *icon_buffer, StudioLight *sl, bool flipped)
{
using namespace blender;
BKE_studiolight_ensure_flag(sl, STUDIOLIGHT_EXTERNAL_IMAGE_LOADED);
ImBuf *diffuse_buffer = sl->matcap_diffuse.ibuf;
@ -728,14 +732,12 @@ static void studiolight_matcap_preview(uint *icon_buffer, StudioLight *sl, bool
dx = 1.0f - dx;
}
float color[4];
float u = dx * diffuse_buffer->x - 1.0f;
float v = dy * diffuse_buffer->y - 1.0f;
nearest_interpolation_color(diffuse_buffer, nullptr, color, u, v);
float4 color = imbuf::interpolate_nearest_fl(diffuse_buffer, u, v);
if (specular_buffer) {
float specular[4];
nearest_interpolation_color(specular_buffer, nullptr, specular, u, v);
float4 specular = imbuf::interpolate_nearest_fl(specular_buffer, u, v);
add_v3_v3(color, specular);
}

View File

@ -32,6 +32,7 @@
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "MEM_guardedalloc.h"
/* == Parameterization constants == */
@ -1286,34 +1287,79 @@ void BKE_tracking_stabilization_data_get(MovieClip *clip,
discard_stabilization_working_context(ctx);
}
using interpolation_func = void (*)(const ImBuf *, ImBuf *, float, float, int, int);
struct TrackingStabilizeFrameInterpolationData {
ImBuf *ibuf;
ImBuf *tmpibuf;
float (*mat)[4];
interpolation_func interpolation;
int tracking_filter;
};
static void tracking_stabilize_frame_interpolation_cb(void *__restrict userdata,
const int j,
const int y,
const TaskParallelTLS *__restrict /*tls*/)
{
using namespace blender;
TrackingStabilizeFrameInterpolationData *data =
static_cast<TrackingStabilizeFrameInterpolationData *>(userdata);
ImBuf *ibuf = data->ibuf;
ImBuf *tmpibuf = data->tmpibuf;
float(*mat)[4] = data->mat;
interpolation_func interpolation = data->interpolation;
float vec[3] = {0.0f, float(y), 0.0f};
float rvec[3];
for (int i = 0; i < tmpibuf->x; i++) {
float vec[3] = {float(i), float(j), 0.0f};
mul_v3_m4v3(vec, mat, vec);
interpolation(ibuf, tmpibuf, vec[0], vec[1], i, j);
if (ibuf->float_buffer.data) {
/* Float image. */
float4 *dst = reinterpret_cast<float4 *>(tmpibuf->float_buffer.data) + y * tmpibuf->x;
if (data->tracking_filter == TRACKING_FILTER_BILINEAR) {
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_bilinear_fl(ibuf, rvec[0], rvec[1]);
}
}
else if (data->tracking_filter == TRACKING_FILTER_BICUBIC) {
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_cubic_bspline_fl(ibuf, rvec[0], rvec[1]);
}
}
else {
/* Nearest or fallback to nearest. */
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_nearest_fl(ibuf, rvec[0], rvec[1]);
}
}
}
else if (ibuf->byte_buffer.data) {
/* Byte image. */
uchar4 *dst = reinterpret_cast<uchar4 *>(tmpibuf->byte_buffer.data) + y * tmpibuf->x;
if (data->tracking_filter == TRACKING_FILTER_BILINEAR) {
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_bilinear_byte(ibuf, rvec[0], rvec[1]);
}
}
else if (data->tracking_filter == TRACKING_FILTER_BICUBIC) {
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_cubic_bspline_byte(ibuf, rvec[0], rvec[1]);
}
}
else {
/* Nearest or fallback to nearest. */
for (int x = 0; x < tmpibuf->x; x++, dst++) {
vec[0] = float(x);
mul_v3_m4v3(rvec, mat, vec);
*dst = imbuf::interpolate_nearest_byte(ibuf, rvec[0], rvec[1]);
}
}
}
}
@ -1327,8 +1373,6 @@ ImBuf *BKE_tracking_stabilize_frame(
int width = ibuf->x, height = ibuf->y;
float pixel_aspect = tracking->camera.pixel_aspect;
float mat[4][4];
int filter = tracking->stabilization.filter;
interpolation_func interpolation = nullptr;
int ibuf_flags;
if (translation) {
@ -1378,25 +1422,11 @@ ImBuf *BKE_tracking_stabilize_frame(
* thus we need the inverse of the transformation to apply. */
invert_m4(mat);
if (filter == TRACKING_FILTER_NEAREST) {
interpolation = nearest_interpolation;
}
else if (filter == TRACKING_FILTER_BILINEAR) {
interpolation = bilinear_interpolation;
}
else if (filter == TRACKING_FILTER_BICUBIC) {
interpolation = bicubic_interpolation;
}
else {
/* fallback to default interpolation method */
interpolation = nearest_interpolation;
}
TrackingStabilizeFrameInterpolationData data = {};
data.ibuf = ibuf;
data.tmpibuf = tmpibuf;
data.mat = mat;
data.interpolation = interpolation;
data.tracking_filter = tracking->stabilization.filter;
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);

View File

@ -289,18 +289,24 @@ MINLINE uint ceil_to_multiple_u(uint a, uint b);
MINLINE uint64_t ceil_to_multiple_ul(uint64_t a, uint64_t b);
/**
* modulo that handles negative numbers, works the same as Python's.
* Floored modulo that is useful for wrapping numbers over \a n,
* including when \a i is negative.
*
* This is the same as Python % or GLSL mod(): `mod_i(-5, 3) = 1`.
*
* \return an integer in the interval [0, n), same sign as n.
*/
MINLINE int mod_i(int i, int n);
/**
* Modulo that returns a positive result, regardless of the sign of \a f.
* Floored modulo that is useful for wrapping numbers over \a n,
* including when \a f is negative.
*
* For example, mod_f_positive(-0.1, 1.0) => 0.9.
* This is the same as Python % or GLSL mod(): `floored_fmod(-0.2, 1.0) = 0.8`.
*
* \returns a float in the interval [0, n).
* \return a float in the interval [0, n), same sign as n.
*/
MINLINE float mod_f_positive(float f, float n);
MINLINE float floored_fmod(float f, float n);
/**
* Round to closest even number, halfway cases are rounded away from zero.

View File

@ -6,33 +6,204 @@
/** \file
* \ingroup bli
*
* 2D image sampling with filtering functions.
*
* All functions take (u, v) texture coordinate, non-normalized (i.e. ranging
* from (0,0) to (width,height) over the image).
*
* Any filtering done on texel values just blends them without color space or
* gamma conversions.
*
* Sampling completely outside the image returns transparent black.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "BLI_math_base.h"
#include "BLI_math_vector_types.hh"
void BLI_bicubic_interpolation_fl(
namespace blender::math {
/**
* Nearest (point) sampling.
*
* Returns texel at floor(u,v) integer index -- note that it is not "nearest
* to u,v coordinate", but rather with fractional part truncated (it would be
* "nearest" if subtracting 0.5 from input u,v).
*/
inline void interpolate_nearest_byte(
const uchar *buffer, uchar *output, int width, int height, float u, float v)
{
BLI_assert(buffer);
int x = int(u);
int y = int(v);
/* Outside image? */
if (x < 0 || x >= width || y < 0 || y >= height) {
output[0] = output[1] = output[2] = output[3] = 0;
return;
}
const uchar *data = buffer + (int64_t(width) * y + x) * 4;
output[0] = data[0];
output[1] = data[1];
output[2] = data[2];
output[3] = data[3];
}
[[nodiscard]] inline uchar4 interpolate_nearest_byte(
const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
interpolate_nearest_byte(buffer, res, width, height, u, v);
return res;
}
inline void interpolate_nearest_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
BLI_assert(buffer);
int x = int(u);
int y = int(v);
/* Outside image? */
if (x < 0 || x >= width || y < 0 || y >= height) {
for (int i = 0; i < components; i++) {
output[i] = 0.0f;
}
return;
}
const float *data = buffer + (int64_t(width) * y + x) * components;
for (int i = 0; i < components; i++) {
output[i] = data[i];
}
}
[[nodiscard]] inline float4 interpolate_nearest_fl(
const float *buffer, int width, int height, float u, float v)
{
float4 res;
interpolate_nearest_fl(buffer, res, width, height, 4, u, v);
return res;
}
/**
* Wrapped nearest sampling. (u,v) is repeated to be inside the image size.
*/
inline void interpolate_nearest_wrap_byte(
const uchar *buffer, uchar *output, int width, int height, float u, float v)
{
BLI_assert(buffer);
u = floored_fmod(u, float(width));
v = floored_fmod(v, float(height));
int x = int(u);
int y = int(v);
BLI_assert(x >= 0 && y >= 0 && x < width && y < height);
const uchar *data = buffer + (int64_t(width) * y + x) * 4;
output[0] = data[0];
output[1] = data[1];
output[2] = data[2];
output[3] = data[3];
}
[[nodiscard]] inline uchar4 interpolate_nearest_wrap_byte(
const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
interpolate_nearest_wrap_byte(buffer, res, width, height, u, v);
return res;
}
inline void interpolate_nearest_wrap_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
BLI_assert(buffer);
u = floored_fmod(u, float(width));
v = floored_fmod(v, float(height));
int x = int(u);
int y = int(v);
BLI_assert(x >= 0 && y >= 0 && x < width && y < height);
const float *data = buffer + (int64_t(width) * y + x) * components;
for (int i = 0; i < components; i++) {
output[i] = data[i];
}
}
[[nodiscard]] inline float4 interpolate_nearest_wrap_fl(
const float *buffer, int width, int height, float u, float v)
{
float4 res;
interpolate_nearest_wrap_fl(buffer, res, width, height, 4, u, v);
return res;
}
/**
* Bilinear sampling.
*
* Takes four image samples at floor(u,v) and floor(u,v)+1, and blends them
* based on fractional parts of u,v. Samples outside the image are turned
* into transparent black.
*
* Note that you probably want to subtract 0.5 from u,v before this function,
* to get proper filtering.
*/
[[nodiscard]] uchar4 interpolate_bilinear_byte(
const uchar *buffer, int width, int height, float u, float v);
[[nodiscard]] float4 interpolate_bilinear_fl(
const float *buffer, int width, int height, float u, float v);
void interpolate_bilinear_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v);
void BLI_bicubic_interpolation_char(
const unsigned char *buffer, unsigned char *output, int width, int height, float u, float v);
/**
* Wrapped bilinear sampling. (u,v) is repeated to be inside the image size,
* including properly wrapping samples that are right on the edges.
*/
void BLI_bilinear_interpolation_fl(
[[nodiscard]] uchar4 interpolate_bilinear_wrap_byte(
const uchar *buffer, int width, int height, float u, float v);
[[nodiscard]] float4 interpolate_bilinear_wrap_fl(
const float *buffer, int width, int height, float u, float v);
void interpolate_bilinear_wrap_fl(const float *buffer,
float *output,
int width,
int height,
int components,
float u,
float v,
bool wrap_x,
bool wrap_y);
/**
* Cubic B-Spline sampling.
*
* Takes 4x4 image samples at floor(u,v)-1 .. floor(u,v)+2, and blends them
* based on fractional parts of u,v. Uses B-Spline variant Mitchell-Netravali
* filter (B=1, C=0), which has no ringing but introduces quite a lot of blur.
* Samples outside the image are clamped to texels at image edge.
*
* Note that you probably want to subtract 0.5 from u,v before this function,
* to get proper filtering.
*/
[[nodiscard]] uchar4 interpolate_cubic_bspline_byte(
const uchar *buffer, int width, int height, float u, float v);
[[nodiscard]] float4 interpolate_cubic_bspline_fl(
const float *buffer, int width, int height, float u, float v);
void interpolate_cubic_bspline_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v);
void BLI_bilinear_interpolation_char(
const unsigned char *buffer, unsigned char *output, int width, int height, float u, float v);
void BLI_bilinear_interpolation_wrap_fl(const float *buffer,
float *output,
int width,
int height,
int components,
float u,
float v,
bool wrap_x,
bool wrap_y);
} // namespace blender::math
#define EWA_MAXIDX 255
extern const float EWA_WTS[EWA_MAXIDX + 1];
@ -56,7 +227,3 @@ void BLI_ewa_filter(int width,
ewa_filter_read_pixel_cb read_pixel_cb,
void *userdata,
float result[4]);
#ifdef __cplusplus
}
#endif

View File

@ -310,14 +310,9 @@ MINLINE int mod_i(int i, int n)
return (i % n + n) % n;
}
MINLINE float mod_f_positive(const float f, const float n)
MINLINE float floored_fmod(const float f, const float n)
{
const float modulo = fmodf(f, n);
if (modulo < 0) {
/* fmodf returns a value in the interval (-n, n). */
return modulo + n;
}
return modulo;
return f - n * floorf(f / n);
}
MINLINE float fractf(float a)

View File

@ -117,6 +117,8 @@ static void bicubic_interpolation(
{
using namespace blender;
BLI_assert(src_buffer && output);
#if BLI_HAVE_SSE2
if constexpr (std::is_same_v<T, uchar>) {
if (components == 4) {
@ -203,33 +205,28 @@ static void bicubic_interpolation(
}
}
void BLI_bicubic_interpolation_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
bicubic_interpolation<float>(buffer, output, width, height, components, u, v);
}
void BLI_bicubic_interpolation_char(
const uchar *buffer, uchar *output, int width, int height, float u, float v)
{
bicubic_interpolation<uchar>(buffer, output, width, height, 4, u, v);
}
/* BILINEAR INTERPOLATION */
BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer,
float *float_output,
int width,
int height,
int components,
float u,
float v,
bool wrap_x,
bool wrap_y)
BLI_INLINE void bilinear_fl_impl(const float *buffer,
float *output,
int width,
int height,
int components,
float u,
float v,
bool wrap_x = false,
bool wrap_y = false)
{
BLI_assert(buffer && output);
float a, b;
float a_b, ma_b, a_mb, ma_mb;
int y1, y2, x1, x2;
if (wrap_x) {
u = floored_fmod(u, float(width));
}
if (wrap_y) {
v = floored_fmod(v, float(height));
}
float uf = floorf(u);
float vf = floorf(v);
@ -241,60 +238,54 @@ BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer,
const float *row1, *row2, *row3, *row4;
const float empty[4] = {0.0f, 0.0f, 0.0f, 0.0f};
/* pixel value must be already wrapped, however values at boundaries may flip */
/* Check if +1 samples need wrapping, or we we don't do wrapping then if
* we are sampling completely outside the image. */
if (wrap_x) {
if (x1 < 0) {
x1 = width - 1;
}
if (x2 >= width) {
x2 = 0;
}
}
else if (x2 < 0 || x1 >= width) {
copy_vn_fl(float_output, components, 0.0f);
copy_vn_fl(output, components, 0.0f);
return;
}
if (wrap_y) {
if (y1 < 0) {
y1 = height - 1;
}
if (y2 >= height) {
y2 = 0;
}
}
else if (y2 < 0 || y1 >= height) {
copy_vn_fl(float_output, components, 0.0f);
copy_vn_fl(output, components, 0.0f);
return;
}
/* sample including outside of edges of image */
/* Sample including outside of edges of image. */
if (x1 < 0 || y1 < 0) {
row1 = empty;
}
else {
row1 = float_buffer + width * y1 * components + components * x1;
row1 = buffer + width * y1 * components + components * x1;
}
if (x1 < 0 || y2 > height - 1) {
row2 = empty;
}
else {
row2 = float_buffer + width * y2 * components + components * x1;
row2 = buffer + width * y2 * components + components * x1;
}
if (x2 > width - 1 || y1 < 0) {
row3 = empty;
}
else {
row3 = float_buffer + width * y1 * components + components * x2;
row3 = buffer + width * y1 * components + components * x2;
}
if (x2 > width - 1 || y2 > height - 1) {
row4 = empty;
}
else {
row4 = float_buffer + width * y2 * components + components * x2;
row4 = buffer + width * y2 * components + components * x2;
}
a = u - uf;
@ -305,12 +296,12 @@ BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer,
ma_mb = (1.0f - a) * (1.0f - b);
if (components == 1) {
float_output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0];
output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0];
}
else if (components == 3) {
float_output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0];
float_output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1];
float_output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2];
output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0];
output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1];
output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2];
}
else {
#if BLI_HAVE_SSE2
@ -325,19 +316,23 @@ BLI_INLINE void bilinear_interpolation_fl(const float *float_buffer,
__m128 rgba13 = _mm_add_ps(rgba1, rgba3);
__m128 rgba24 = _mm_add_ps(rgba2, rgba4);
__m128 rgba = _mm_add_ps(rgba13, rgba24);
_mm_storeu_ps(float_output, rgba);
_mm_storeu_ps(output, rgba);
#else
float_output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0];
float_output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1];
float_output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2];
float_output[3] = ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3];
output[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0];
output[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1];
output[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2];
output[3] = ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3];
#endif
}
}
void BLI_bilinear_interpolation_char(
const uchar *buffer, uchar *output, int width, int height, float u, float v)
namespace blender::math {
uchar4 interpolate_bilinear_byte(const uchar *buffer, int width, int height, float u, float v)
{
BLI_assert(buffer);
uchar4 res;
#if BLI_HAVE_SSE2
/* Bilinear interpolation needs to read and blend four image pixels, while
* also handling conditions of sample coordinate being outside of the
@ -375,7 +370,7 @@ void BLI_bilinear_interpolation_char(
int ycoord[4];
_mm_storeu_ps((float *)xcoord, _mm_castsi128_ps(x1234));
_mm_storeu_ps((float *)ycoord, _mm_castsi128_ps(y1234));
int sample1 = ((const int *)buffer)[ycoord[0] * (int64_t)width + xcoord[0]];
int sample1 = ((const int *)buffer)[ycoord[0] * int64_t(width) + xcoord[0]];
int sample2 = ((const int *)buffer)[ycoord[1] * int64_t(width) + xcoord[1]];
int sample3 = ((const int *)buffer)[ycoord[2] * int64_t(width) + xcoord[2]];
int sample4 = ((const int *)buffer)[ycoord[3] * int64_t(width) + xcoord[3]];
@ -414,37 +409,26 @@ void BLI_bilinear_interpolation_char(
__m128i rgba32 = _mm_cvttps_epi32(rgba);
__m128i rgba16 = _mm_packs_epi32(rgba32, _mm_setzero_si128());
__m128i rgba8 = _mm_packus_epi16(rgba16, _mm_setzero_si128());
_mm_store_ss((float *)output, _mm_castsi128_ps(rgba8));
_mm_store_ss((float *)&res, _mm_castsi128_ps(rgba8));
#else
float a, b;
float a_b, ma_b, a_mb, ma_mb;
int y1, y2, x1, x2;
float uf = floorf(u);
float vf = floorf(v);
x1 = (int)uf;
x2 = x1 + 1;
y1 = (int)vf;
y2 = y1 + 1;
int x1 = (int)uf;
int x2 = x1 + 1;
int y1 = (int)vf;
int y2 = y1 + 1;
/* Completely outside of the image? */
if (x2 < 0 || x1 >= width || y2 < 0 || y1 >= height) {
return uchar4(0);
}
/* Sample including outside of edges of image. */
const uchar *row1, *row2, *row3, *row4;
uchar empty[4] = {0, 0, 0, 0};
/* completely outside of the image? */
if (x2 < 0 || x1 >= width) {
copy_vn_uchar(output, 4, 0);
return;
}
if (y2 < 0 || y1 >= height) {
copy_vn_uchar(output, 4, 0);
return;
}
/* sample including outside of edges of image */
if (x1 < 0 || y1 < 0) {
row1 = empty;
}
@ -473,39 +457,119 @@ void BLI_bilinear_interpolation_char(
row4 = buffer + width * y2 * 4 + 4 * x2;
}
a = u - uf;
b = v - vf;
a_b = a * b;
ma_b = (1.0f - a) * b;
a_mb = a * (1.0f - b);
ma_mb = (1.0f - a) * (1.0f - b);
float a = u - uf;
float b = v - vf;
float a_b = a * b;
float ma_b = (1.0f - a) * b;
float a_mb = a * (1.0f - b);
float ma_mb = (1.0f - a) * (1.0f - b);
output[0] = (uchar)(ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0] + 0.5f);
output[1] = (uchar)(ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1] + 0.5f);
output[2] = (uchar)(ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2] + 0.5f);
output[3] = (uchar)(ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3] + 0.5f);
res.x = (uchar)(ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0] + 0.5f);
res.y = (uchar)(ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1] + 0.5f);
res.z = (uchar)(ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2] + 0.5f);
res.w = (uchar)(ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3] + 0.5f);
#endif
return res;
}
void BLI_bilinear_interpolation_fl(
float4 interpolate_bilinear_fl(const float *buffer, int width, int height, float u, float v)
{
float4 res;
bilinear_fl_impl(buffer, res, width, height, 4, u, v);
return res;
}
void interpolate_bilinear_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
bilinear_interpolation_fl(buffer, output, width, height, components, u, v, false, false);
bilinear_fl_impl(buffer, output, width, height, components, u, v);
}
void BLI_bilinear_interpolation_wrap_fl(const float *buffer,
float *output,
int width,
int height,
int components,
float u,
float v,
bool wrap_x,
bool wrap_y)
void interpolate_bilinear_wrap_fl(const float *buffer,
float *output,
int width,
int height,
int components,
float u,
float v,
bool wrap_x,
bool wrap_y)
{
bilinear_interpolation_fl(buffer, output, width, height, components, u, v, wrap_x, wrap_y);
bilinear_fl_impl(buffer, output, width, height, components, u, v, wrap_x, wrap_y);
}
uchar4 interpolate_bilinear_wrap_byte(const uchar *buffer, int width, int height, float u, float v)
{
u = floored_fmod(u, float(width));
v = floored_fmod(v, float(height));
float uf = floorf(u);
float vf = floorf(v);
int x1 = (int)uf;
int x2 = x1 + 1;
int y1 = (int)vf;
int y2 = y1 + 1;
/* Wrap interpolation pixels if needed. */
BLI_assert(x1 >= 0 && x1 < width && y1 >= 0 && y1 < height);
if (x2 >= width) {
x2 = 0;
}
if (y2 >= height) {
y2 = 0;
}
float a = u - uf;
float b = v - vf;
float a_b = a * b;
float ma_b = (1.0f - a) * b;
float a_mb = a * (1.0f - b);
float ma_mb = (1.0f - a) * (1.0f - b);
/* Blend samples. */
const uchar *row1 = buffer + (int64_t(width) * y1 + x1) * 4;
const uchar *row2 = buffer + (int64_t(width) * y2 + x1) * 4;
const uchar *row3 = buffer + (int64_t(width) * y1 + x2) * 4;
const uchar *row4 = buffer + (int64_t(width) * y2 + x2) * 4;
uchar4 res;
res.x = uchar(ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0] + 0.5f);
res.y = uchar(ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1] + 0.5f);
res.z = uchar(ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2] + 0.5f);
res.w = uchar(ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3] + 0.5f);
return res;
}
float4 interpolate_bilinear_wrap_fl(const float *buffer, int width, int height, float u, float v)
{
float4 res;
bilinear_fl_impl(buffer, res, width, height, 4, u, v, true, true);
return res;
}
uchar4 interpolate_cubic_bspline_byte(const uchar *buffer, int width, int height, float u, float v)
{
uchar4 res;
bicubic_interpolation<uchar>(buffer, res, width, height, 4, u, v);
return res;
}
float4 interpolate_cubic_bspline_fl(const float *buffer, int width, int height, float u, float v)
{
float4 res;
bicubic_interpolation<float>(buffer, res, width, height, 4, u, v);
return res;
}
void interpolate_cubic_bspline_fl(
const float *buffer, float *output, int width, int height, int components, float u, float v)
{
bicubic_interpolation<float>(buffer, output, width, height, components, u, v);
}
} // namespace blender::math
/**************************************************************************
* Filtering method based on
* "Creating raster omnimax images from multiple perspective views

View File

@ -183,11 +183,17 @@ TEST(math_base, InterpolateInt)
EXPECT_EQ(math::interpolate(100, 200, 0.4f), 140);
}
TEST(math_base, ModFPositive)
TEST(math_base, FlooredFMod)
{
EXPECT_FLOAT_EQ(mod_f_positive(3.27f, 1.57f), 0.12999988f);
EXPECT_FLOAT_EQ(mod_f_positive(327.f, 47.f), 45.f);
EXPECT_FLOAT_EQ(mod_f_positive(-0.1f, 1.0f), 0.9f);
EXPECT_FLOAT_EQ(floored_fmod(3.27f, 1.57f), 0.12999988f);
EXPECT_FLOAT_EQ(floored_fmod(327.f, 47.f), 45.f);
EXPECT_FLOAT_EQ(floored_fmod(-0.1f, 1.0f), 0.9f);
EXPECT_FLOAT_EQ(floored_fmod(-0.9f, 1.0f), 0.1f);
EXPECT_FLOAT_EQ(floored_fmod(-100.1f, 1.0f), 0.90000153f);
EXPECT_FLOAT_EQ(floored_fmod(-0.1f, 12345.0f), 12344.9f);
EXPECT_FLOAT_EQ(floored_fmod(12345.1f, 12345.0f), 0.099609375f);
EXPECT_FLOAT_EQ(floored_fmod(12344.999f, 12345.0f), 12344.999f);
EXPECT_FLOAT_EQ(floored_fmod(12345.0f, 12345.0f), 0.0f);
}
} // namespace blender::tests

View File

@ -8,7 +8,9 @@
#include "BLI_math_interp.hh"
using namespace blender;
using namespace blender::math;
static constexpr float float_tolerance = 0.00005f;
static constexpr int image_width = 3;
static constexpr int image_height = 3;
static constexpr unsigned char image_char[image_height][image_width][4] = {
@ -16,147 +18,241 @@ static constexpr unsigned char image_char[image_height][image_width][4] = {
{{0, 1, 2, 3}, {62, 72, 82, 92}, {126, 127, 128, 129}},
{{1, 2, 3, 4}, {73, 108, 153, 251}, {128, 129, 130, 131}},
};
static constexpr float image_fl[image_height][image_width][4] = {
{{255, 254, 217, 216}, {230, 230, 230, 230}, {240, 160, 90, 20}},
{{0, 1, 2, 3}, {62, 72, 82, 92}, {126, 127, 128, 129}},
{{1, 2, 3, 4}, {73, 108, 153, 251}, {128, 129, 130, 131}},
};
TEST(math_interp, BilinearCharExactSamples)
{
unsigned char res[4];
unsigned char exp1[4] = {73, 108, 153, 251};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.0f, 2.0f);
EXPECT_EQ_ARRAY(exp1, res, 4);
unsigned char exp2[4] = {240, 160, 90, 20};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 2.0f, 0.0f);
EXPECT_EQ_ARRAY(exp2, res, 4);
uchar4 res;
uchar4 exp1 = {73, 108, 153, 251};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 1.0f, 2.0f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {240, 160, 90, 20};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, BilinearCharHalfwayUSamples)
{
unsigned char res[4];
unsigned char exp1[4] = {31, 37, 42, 48};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0.5f, 1.0f);
EXPECT_EQ_ARRAY(exp1, res, 4);
unsigned char exp2[4] = {243, 242, 224, 223};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0.5f, 0.0f);
EXPECT_EQ_ARRAY(exp2, res, 4);
uchar4 res;
uchar4 exp1 = {31, 37, 42, 48};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0.5f, 1.0f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {243, 242, 224, 223};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0.5f, 0.0f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, BilinearCharHalfwayVSamples)
{
unsigned char res[4];
unsigned char exp1[4] = {1, 2, 3, 4};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0.0f, 1.5f);
EXPECT_EQ_ARRAY(exp1, res, 4);
unsigned char exp2[4] = {127, 128, 129, 130};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 2.0f, 1.5f);
EXPECT_EQ_ARRAY(exp2, res, 4);
uchar4 res;
uchar4 exp1 = {1, 2, 3, 4};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0.0f, 1.5f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {127, 128, 129, 130};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 2.0f, 1.5f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, BilinearCharSamples)
{
unsigned char res[4];
unsigned char exp1[4] = {136, 133, 132, 130};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 0.625f);
EXPECT_EQ_ARRAY(exp1, res, 4);
unsigned char exp2[4] = {219, 191, 167, 142};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.4f, 0.1f);
EXPECT_EQ_ARRAY(exp2, res, 4);
uchar4 res;
uchar4 exp1 = {136, 133, 132, 130};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {219, 191, 167, 142};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, BilinearFloatSamples)
{
float4 res;
float4 exp1 = {135.9375f, 133.28125f, 131.5625f, 129.84375f};
res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {219.36f, 191.2f, 166.64f, 142.08f};
res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
}
TEST(math_interp, BilinearCharPartiallyOutsideImage)
{
unsigned char res[4];
unsigned char exp1[4] = {1, 1, 2, 2};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, -0.5f, 2.0f);
EXPECT_EQ_ARRAY(exp1, res, 4);
unsigned char exp2[4] = {9, 11, 15, 22};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 2.9f);
EXPECT_EQ_ARRAY(exp2, res, 4);
unsigned char exp3[4] = {173, 115, 65, 14};
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 2.2f, -0.1f);
EXPECT_EQ_ARRAY(exp3, res, 4);
uchar4 res;
uchar4 exp1 = {1, 1, 2, 2};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {9, 11, 15, 22};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_EQ(exp2, res);
uchar4 exp3 = {173, 115, 65, 14};
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_EQ(exp3, res);
}
TEST(math_interp, BilinearCharPartiallyOutsideImageWrap)
{
uchar4 res;
uchar4 exp1 = {65, 66, 67, 68};
res = interpolate_bilinear_wrap_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_EQ(exp1, res);
uchar4 exp2 = {218, 203, 190, 182};
res = interpolate_bilinear_wrap_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_EQ(exp2, res);
uchar4 exp3 = {229, 171, 114, 64};
res = interpolate_bilinear_wrap_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_EQ(exp3, res);
}
TEST(math_interp, BilinearFloatPartiallyOutsideImage)
{
float4 res;
float4 exp1 = {0.5f, 1, 1.5f, 2};
res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {8.675f, 11.325f, 14.725f, 22.1f};
res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
float4 exp3 = {172.8f, 115.2f, 64.8f, 14.4f};
res = interpolate_bilinear_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_V4_NEAR(exp3, res, float_tolerance);
}
TEST(math_interp, BilinearFloatPartiallyOutsideImageWrap)
{
float4 res;
float4 exp1 = {64.5f, 65.5f, 66.5f, 67.5f};
interpolate_bilinear_wrap_fl(
image_fl[0][0], res, image_width, image_height, 4, -0.5f, 2.0f, true, true);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
res = interpolate_bilinear_wrap_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {217.92502f, 202.57501f, 190.22501f, 181.85f};
interpolate_bilinear_wrap_fl(
image_fl[0][0], res, image_width, image_height, 4, 1.25f, 2.9f, true, true);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
res = interpolate_bilinear_wrap_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
float4 exp3 = {228.96f, 171.27998f, 114.32f, 63.84f};
interpolate_bilinear_wrap_fl(
image_fl[0][0], res, image_width, image_height, 4, 2.2f, -0.1f, true, true);
EXPECT_V4_NEAR(exp3, res, float_tolerance);
res = interpolate_bilinear_wrap_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_V4_NEAR(exp3, res, float_tolerance);
}
TEST(math_interp, BilinearCharFullyOutsideImage)
{
unsigned char res[4];
unsigned char exp[4] = {0, 0, 0, 0};
uchar4 res;
uchar4 exp = {0, 0, 0, 0};
/* Out of range on U */
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, -1.5f, 0);
EXPECT_EQ_ARRAY(exp, res, 4);
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, -1.1f, 0);
EXPECT_EQ_ARRAY(exp, res, 4);
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 3, 0);
EXPECT_EQ_ARRAY(exp, res, 4);
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 5, 0);
EXPECT_EQ_ARRAY(exp, res, 4);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, -1.5f, 0);
EXPECT_EQ(exp, res);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, -1.1f, 0);
EXPECT_EQ(exp, res);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 3, 0);
EXPECT_EQ(exp, res);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 5, 0);
EXPECT_EQ(exp, res);
/* Out of range on V */
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -3.2f);
EXPECT_EQ_ARRAY(exp, res, 4);
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -1.5f);
EXPECT_EQ_ARRAY(exp, res, 4);
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 3.1f);
EXPECT_EQ_ARRAY(exp, res, 4);
BLI_bilinear_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 500.0f);
EXPECT_EQ_ARRAY(exp, res, 4);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, -3.2f);
EXPECT_EQ(exp, res);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, -1.5f);
EXPECT_EQ(exp, res);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, 3.1f);
EXPECT_EQ(exp, res);
res = interpolate_bilinear_byte(image_char[0][0], image_width, image_height, 0, 500.0f);
EXPECT_EQ(exp, res);
}
TEST(math_interp, BicubicCharExactSamples)
TEST(math_interp, CubicBSplineCharExactSamples)
{
ColorTheme4b res;
ColorTheme4b exp1 = {69, 90, 116, 172};
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.0f, 2.0f);
uchar4 res;
uchar4 exp1 = {69, 90, 116, 172};
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.0f, 2.0f);
EXPECT_EQ(exp1, res);
ColorTheme4b exp2 = {218, 163, 115, 66};
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 2.0f, 0.0f);
uchar4 exp2 = {218, 163, 115, 66};
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 2.0f, 0.0f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, BicubicCharSamples)
TEST(math_interp, CubicBSplineCharSamples)
{
ColorTheme4b res;
ColorTheme4b exp1 = {142, 136, 131, 128};
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 0.625f);
uchar4 res;
uchar4 exp1 = {142, 136, 131, 128};
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_EQ(exp1, res);
ColorTheme4b exp2 = {202, 177, 154, 132};
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.4f, 0.1f);
uchar4 exp2 = {202, 177, 154, 132};
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_EQ(exp2, res);
}
TEST(math_interp, BicubicCharPartiallyOutsideImage)
TEST(math_interp, CubicBSplineFloatSamples)
{
ColorTheme4b res;
ColorTheme4b exp1 = {2, 4, 6, 8};
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, -0.5f, 2.0f);
float4 res;
float4 exp1 = {142.14418f, 136.255798f, 130.87924f, 127.85243f};
res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 1.25f, 0.625f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {202.36082f, 177.13397f, 154.21078f, 132.30153f};
res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 1.4f, 0.1f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
}
TEST(math_interp, CubicBSplineCharPartiallyOutsideImage)
{
uchar4 res;
uchar4 exp1 = {2, 4, 6, 8};
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_EQ(exp1, res);
ColorTheme4b exp2 = {85, 107, 135, 195};
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 1.25f, 2.9f);
uchar4 exp2 = {85, 107, 135, 195};
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_EQ(exp2, res);
ColorTheme4b exp3 = {225, 161, 105, 49};
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 2.2f, -0.1f);
uchar4 exp3 = {225, 161, 105, 49};
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_EQ(exp3, res);
}
TEST(math_interp, BicubicCharFullyOutsideImage)
TEST(math_interp, CubicBSplineFloatPartiallyOutsideImage)
{
ColorTheme4b res;
ColorTheme4b exp = {0, 0, 0, 0};
float4 res;
float4 exp1 = {2.29861f, 3.92014f, 5.71528f, 8.430554f};
res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, -0.5f, 2.0f);
EXPECT_V4_NEAR(exp1, res, float_tolerance);
float4 exp2 = {85.41022f, 107.21497f, 135.13849f, 195.49146f};
res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 1.25f, 2.9f);
EXPECT_V4_NEAR(exp2, res, float_tolerance);
float4 exp3 = {224.73579f, 160.66783f, 104.63521f, 48.60260f};
res = interpolate_cubic_bspline_fl(image_fl[0][0], image_width, image_height, 2.2f, -0.1f);
EXPECT_V4_NEAR(exp3, res, float_tolerance);
}
TEST(math_interp, CubicBSplineCharFullyOutsideImage)
{
uchar4 res;
uchar4 exp = {0, 0, 0, 0};
/* Out of range on U */
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, -1.5f, 0);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -1.5f, 0);
EXPECT_EQ(exp, res);
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, -1.1f, 0);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, -1.1f, 0);
EXPECT_EQ(exp, res);
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 3, 0);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 3, 0);
EXPECT_EQ(exp, res);
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 5, 0);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 5, 0);
EXPECT_EQ(exp, res);
/* Out of range on V */
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -3.2f);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, -3.2f);
EXPECT_EQ(exp, res);
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, -1.5f);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, -1.5f);
EXPECT_EQ(exp, res);
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 3.1f);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 3.1f);
EXPECT_EQ(exp, res);
BLI_bicubic_interpolation_char(image_char[0][0], res, image_width, image_height, 0, 500.0f);
res = interpolate_cubic_bspline_byte(image_char[0][0], image_width, image_height, 0, 500.0f);
EXPECT_EQ(exp, res);
}

View File

@ -2715,6 +2715,20 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 15)) {
FOREACH_NODETREE_BEGIN (bmain, ntree, id) {
if (ntree->type == NTREE_COMPOSIT) {
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type == CMP_NODE_KEYING) {
NodeKeyingData &keying_data = *static_cast<NodeKeyingData *>(node->storage);
keying_data.edge_kernel_radius = max_ii(keying_data.edge_kernel_radius - 1, 0);
}
}
}
}
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 401, 16)) {
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
Sculpt *sculpt = scene->toolsettings->sculpt;
if (sculpt != nullptr) {

View File

@ -249,11 +249,11 @@ class MemoryBuffer {
single_y = rel_y - last_y;
}
BLI_bilinear_interpolation_fl(buffer_, out, 1, 1, num_channels_, single_x, single_y);
math::interpolate_bilinear_fl(buffer_, out, 1, 1, num_channels_, single_x, single_y);
return;
}
BLI_bilinear_interpolation_fl(buffer_,
math::interpolate_bilinear_fl(buffer_,
out,
get_width(),
get_height(),
@ -451,10 +451,7 @@ class MemoryBuffer {
}
break;
case MemoryBufferExtend::Repeat:
x = fmodf(x, w);
if (x < 0.0f) {
x += w;
}
x = floored_fmod(x, w);
break;
}
@ -470,10 +467,7 @@ class MemoryBuffer {
}
break;
case MemoryBufferExtend::Repeat:
y = fmodf(y, h);
if (y < 0.0f) {
y += h;
}
y = floored_fmod(y, h);
break;
}
@ -547,7 +541,7 @@ class MemoryBuffer {
memcpy(result, buffer_, sizeof(float) * num_channels_);
}
else {
BLI_bilinear_interpolation_wrap_fl(buffer_,
math::interpolate_bilinear_wrap_fl(buffer_,
result,
get_width(),
get_height(),

View File

@ -7,8 +7,7 @@
#include "BKE_scene.h"
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
namespace blender::compositor {
@ -102,27 +101,27 @@ static void sample_image_at_location(ImBuf *ibuf,
if (ibuf->float_buffer.data) {
switch (sampler) {
case PixelSampler::Nearest:
nearest_interpolation_color(ibuf, nullptr, color, x, y);
imbuf::interpolate_nearest_fl(ibuf, color, x, y);
break;
case PixelSampler::Bilinear:
bilinear_interpolation_color(ibuf, nullptr, color, x, y);
imbuf::interpolate_bilinear_fl(ibuf, color, x, y);
break;
case PixelSampler::Bicubic:
bicubic_interpolation_color(ibuf, nullptr, color, x, y);
imbuf::interpolate_cubic_bspline_fl(ibuf, color, x, y);
break;
}
}
else {
uchar byte_color[4];
uchar4 byte_color;
switch (sampler) {
case PixelSampler::Nearest:
nearest_interpolation_color(ibuf, byte_color, nullptr, x, y);
byte_color = imbuf::interpolate_nearest_byte(ibuf, x, y);
break;
case PixelSampler::Bilinear:
bilinear_interpolation_color(ibuf, byte_color, nullptr, x, y);
byte_color = imbuf::interpolate_bilinear_byte(ibuf, x, y);
break;
case PixelSampler::Bicubic:
bicubic_interpolation_color(ibuf, byte_color, nullptr, x, y);
byte_color = imbuf::interpolate_cubic_bspline_byte(ibuf, x, y);
break;
}
rgba_uchar_to_float(color, byte_color);

View File

@ -44,11 +44,13 @@ void KeyingClipOperation::execute_pixel(float output[4], int x, int y, void *dat
float value = buffer[(y * buffer_width + x)];
bool ok = false;
int start_x = max_ff(0, x - delta + 1), start_y = max_ff(0, y - delta + 1),
end_x = min_ff(x + delta - 1, buffer_width - 1),
end_y = min_ff(y + delta - 1, buffer_height - 1);
int count = 0, total_count = (end_x - start_x + 1) * (end_y - start_y + 1) - 1;
const int start_x = max_ff(0, x - delta);
const int start_y = max_ff(0, y - delta);
const int end_x = min_ff(x + delta, buffer_width - 1);
const int end_y = min_ff(y + delta, buffer_height - 1);
int count = 0, total_count = (end_x - start_x + 1) * (end_y - start_y + 1);
int threshold_count = ceil(float(total_count) * 0.9f);
if (delta == 0) {
@ -57,10 +59,6 @@ void KeyingClipOperation::execute_pixel(float output[4], int x, int y, void *dat
for (int cx = start_x; ok == false && cx <= end_x; cx++) {
for (int cy = start_y; ok == false && cy <= end_y; cy++) {
if (UNLIKELY(cx == x && cy == y)) {
continue;
}
int buffer_index = (cy * buffer_width + cx);
float current_value = buffer[buffer_index];
@ -141,14 +139,14 @@ void KeyingClipOperation::update_memory_buffer_partial(MemoryBuffer *output,
const int x = it.x;
const int y = it.y;
const int start_x = std::max(0, x - delta + 1);
const int start_y = std::max(0, y - delta + 1);
const int end_x = std::min(x + delta, width);
const int end_y = std::min(y + delta, height);
const int x_len = end_x - start_x;
const int y_len = end_y - start_y;
const int start_x = std::max(0, x - delta);
const int start_y = std::max(0, y - delta);
const int end_x = std::min(x + delta, width - 1);
const int end_y = std::min(y + delta, height - 1);
const int x_len = end_x - start_x + 1;
const int y_len = end_y - start_y + 1;
const int total_count = x_len * y_len - 1;
const int total_count = x_len * y_len;
const int threshold_count = ceil(float(total_count) * 0.9f);
bool ok = false;
if (delta == 0) {
@ -163,10 +161,6 @@ void KeyingClipOperation::update_memory_buffer_partial(MemoryBuffer *output,
for (; ok == false && row < end_row; row += row_stride) {
const float *end_elem = row + x_len * elem_stride;
for (const float *elem = row; ok == false && elem < end_elem; elem += elem_stride) {
if (UNLIKELY(elem == main_elem)) {
continue;
}
const float current_value = *elem;
if (fabsf(current_value - value) < tolerance) {
count++;

View File

@ -8,6 +8,7 @@
#include "BKE_movieclip.h"
#include "IMB_imbuf.hh"
#include "IMB_interp.hh"
namespace blender::compositor {
@ -81,13 +82,13 @@ void MovieClipBaseOperation::execute_pixel_sampled(float output[4],
else {
switch (sampler) {
case PixelSampler::Nearest:
nearest_interpolation_color(ibuf, nullptr, output, x, y);
imbuf::interpolate_nearest_fl(ibuf, output, x, y);
break;
case PixelSampler::Bilinear:
bilinear_interpolation_color(ibuf, nullptr, output, x, y);
imbuf::interpolate_bilinear_fl(ibuf, output, x, y);
break;
case PixelSampler::Bicubic:
bicubic_interpolation_color(ibuf, nullptr, output, x, y);
imbuf::interpolate_cubic_bspline_fl(ibuf, output, x, y);
break;
}
}

View File

@ -6,7 +6,7 @@
#include "BLI_string.h"
#include "IMB_imbuf.hh"
#include "IMB_interp.hh"
namespace blender::compositor {
@ -88,13 +88,13 @@ void MultilayerColorOperation::execute_pixel_sampled(float output[4],
if (number_of_channels_ == 4) {
switch (sampler) {
case PixelSampler::Nearest:
nearest_interpolation_color(buffer_, nullptr, output, x, y);
imbuf::interpolate_nearest_fl(buffer_, output, x, y);
break;
case PixelSampler::Bilinear:
bilinear_interpolation_color(buffer_, nullptr, output, x, y);
imbuf::interpolate_bilinear_fl(buffer_, output, x, y);
break;
case PixelSampler::Bicubic:
bicubic_interpolation_color(buffer_, nullptr, output, x, y);
imbuf::interpolate_cubic_bspline_fl(buffer_, output, x, y);
break;
}
}

View File

@ -4,6 +4,7 @@
#include "COM_RenderLayersProg.h"
#include "BLI_math_interp.hh"
#include "BLI_string.h"
#include "BKE_image.h"
@ -55,7 +56,6 @@ void RenderLayersProg::init_execution()
void RenderLayersProg::do_interpolation(float output[4], float x, float y, PixelSampler sampler)
{
uint offset;
int width = this->get_width(), height = this->get_height();
int ix = x, iy = y;
@ -73,27 +73,14 @@ void RenderLayersProg::do_interpolation(float output[4], float x, float y, Pixel
}
switch (sampler) {
case PixelSampler::Nearest: {
offset = (iy * width + ix) * elementsize_;
if (elementsize_ == 1) {
output[0] = input_buffer_[offset];
}
else if (elementsize_ == 3) {
copy_v3_v3(output, &input_buffer_[offset]);
}
else {
copy_v4_v4(output, &input_buffer_[offset]);
}
case PixelSampler::Nearest:
math::interpolate_nearest_fl(input_buffer_, output, width, height, elementsize_, x, y);
break;
}
case PixelSampler::Bilinear:
BLI_bilinear_interpolation_fl(input_buffer_, output, width, height, elementsize_, x, y);
math::interpolate_bilinear_fl(input_buffer_, output, width, height, elementsize_, x, y);
break;
case PixelSampler::Bicubic:
BLI_bicubic_interpolation_fl(input_buffer_, output, width, height, elementsize_, x, y);
math::interpolate_cubic_bspline_fl(input_buffer_, output, width, height, elementsize_, x, y);
break;
}
}

View File

@ -11,6 +11,7 @@
#include "BKE_image_partial_update.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
@ -503,11 +504,10 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD
float xf = x / (float)texture_width;
float u = info.clipping_uv_bounds.xmax * xf +
info.clipping_uv_bounds.xmin * (1.0 - xf) - tile_offset_x;
nearest_interpolation_color(tile_buffer,
nullptr,
&extracted_buffer.float_buffer.data[offset * 4],
u * tile_buffer->x,
v * tile_buffer->y);
imbuf::interpolate_nearest_fl(tile_buffer,
&extracted_buffer.float_buffer.data[offset * 4],
u * tile_buffer->x,
v * tile_buffer->y);
offset++;
}
}

View File

@ -38,11 +38,7 @@ namespace blender::draw {
BLI_INLINE eParticleRefineShaderType drw_curves_shader_type_get()
{
/* NOTE: Curve refine is faster using transform feedback via vertex processing pipeline with
* Metal and Apple Silicon GPUs. This is also because vertex work can more easily be executed in
* parallel with fragment work, whereas compute inserts an explicit dependency,
* due to switching of command encoder types. */
if (GPU_compute_shader_support() && (GPU_backend_get_type() != GPU_BACKEND_METAL)) {
if (GPU_compute_shader_support()) {
return PART_REFINE_SHADER_COMPUTE;
}
if (GPU_transform_feedback_support()) {

View File

@ -37,11 +37,7 @@
BLI_INLINE eParticleRefineShaderType drw_hair_shader_type_get()
{
/* NOTE: Hair refine is faster using transform feedback via vertex processing pipeline with Metal
* and Apple Silicon GPUs. This is also because vertex work can more easily be executed in
* parallel with fragment work, whereas compute inserts an explicit dependency,
* due to switching of command encoder types. */
if (GPU_compute_shader_support() && (GPU_backend_get_type() != GPU_BACKEND_METAL)) {
if (GPU_compute_shader_support()) {
return PART_REFINE_SHADER_COMPUTE;
}
if (GPU_transform_feedback_support()) {

View File

@ -936,7 +936,7 @@ void time_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float
/* This simulates the fcu curve moving in time. */
const float time = fcu->bezt[segment->start_index + i].vec[1][0] + frame_offset;
/* Need to normalize time to first_key to specify that as the wrapping point. */
const float wrapped_time = mod_f_positive(time - first_key_x, fcu_x_range) + first_key_x;
const float wrapped_time = floored_fmod(time - first_key_x, fcu_x_range) + first_key_x;
const float delta_y = fcu_y_range * floorf((time - first_key_x) / fcu_x_range);
const float key_y_value = evaluate_fcurve(fcu, wrapped_time) + delta_y;

View File

@ -1381,7 +1381,13 @@ static KeyingSet *keyingset_get_from_op_with_error(wmOperator *op, PropertyRNA *
else if (prop_type == PROP_STRING) {
char type_id[MAX_ID_NAME - 2];
RNA_property_string_get(op->ptr, prop, type_id);
ks = ANIM_keyingset_get_from_idname(scene, type_id);
if (strcmp(type_id, "__ACTIVE__") == 0) {
ks = ANIM_keyingset_get_from_enum_type(scene, scene->active_keyingset);
}
else {
ks = ANIM_keyingset_get_from_idname(scene, type_id);
}
if (ks == nullptr) {
BKE_reportf(op->reports, RPT_ERROR, "Keying set '%s' not found", type_id);

View File

@ -27,6 +27,7 @@
#include "BLI_math_bits.h"
#include "BLI_math_color_blend.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.hh"
#include "BLI_memarena.h"
#include "BLI_task.h"
#include "BLI_threads.h"
@ -37,7 +38,7 @@
#include "BLT_translation.h"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "DNA_brush_types.h"
#include "DNA_customdata_types.h"
@ -715,35 +716,17 @@ static int project_paint_PickFace(const ProjPaintState *ps, const float pt[2], f
return best_tri_index;
}
/* Converts a uv coord into a pixel location wrapping if the uv is outside 0-1 range */
static void uvco_to_wrapped_pxco(const float uv[2], int ibuf_x, int ibuf_y, float *x, float *y)
{
/* use */
*x = fmodf(uv[0], 1.0f);
*y = fmodf(uv[1], 1.0f);
if (*x < 0.0f) {
*x += 1.0f;
}
if (*y < 0.0f) {
*y += 1.0f;
}
*x = *x * ibuf_x - 0.5f;
*y = *y * ibuf_y - 0.5f;
}
/* Set the top-most face color that the screen space coord 'pt' touches
* (or return 0 if none touch) */
static bool project_paint_PickColor(
const ProjPaintState *ps, const float pt[2], float *rgba_fp, uchar *rgba, const bool interp)
{
using namespace blender;
const float *tri_uv[3];
float w[3], uv[2];
int tri_index;
Image *ima;
ImBuf *ibuf;
int xi, yi;
tri_index = project_paint_PickFace(ps, pt, w);
@ -770,58 +753,32 @@ static bool project_paint_PickColor(
return false;
}
float x = uv[0] * ibuf->x;
float y = uv[1] * ibuf->y;
if (interp) {
float x, y;
uvco_to_wrapped_pxco(uv, ibuf->x, ibuf->y, &x, &y);
x -= 0.5f;
y -= 0.5f;
}
if (ibuf->float_buffer.data) {
if (rgba_fp) {
bilinear_interpolation_color_wrap(ibuf, nullptr, rgba_fp, x, y);
}
else {
float rgba_tmp_f[4];
bilinear_interpolation_color_wrap(ibuf, nullptr, rgba_tmp_f, x, y);
premul_float_to_straight_uchar(rgba, rgba_tmp_f);
}
if (ibuf->float_buffer.data) {
float4 col = interp ? imbuf::interpolate_bilinear_wrap_fl(ibuf, x, y) :
imbuf::interpolate_nearest_wrap_fl(ibuf, x, y);
col = math::clamp(col, 0.0f, 1.0f);
if (rgba_fp) {
memcpy(rgba_fp, &col, sizeof(col));
}
else {
if (rgba) {
bilinear_interpolation_color_wrap(ibuf, rgba, nullptr, x, y);
}
else {
uchar rgba_tmp[4];
bilinear_interpolation_color_wrap(ibuf, rgba_tmp, nullptr, x, y);
straight_uchar_to_premul_float(rgba_fp, rgba_tmp);
}
premul_float_to_straight_uchar(rgba, col);
}
}
else {
// xi = int((uv[0]*ibuf->x) + 0.5f);
// yi = int((uv[1]*ibuf->y) + 0.5f);
// if (xi < 0 || xi >= ibuf->x || yi < 0 || yi >= ibuf->y) return false;
/* wrap */
xi = mod_i(int(uv[0] * ibuf->x), ibuf->x);
yi = mod_i(int(uv[1] * ibuf->y), ibuf->y);
uchar4 col = interp ? imbuf::interpolate_bilinear_wrap_byte(ibuf, x, y) :
imbuf::interpolate_nearest_wrap_byte(ibuf, x, y);
if (rgba) {
if (ibuf->float_buffer.data) {
const float *rgba_tmp_fp = ibuf->float_buffer.data + (xi + yi * ibuf->x * 4);
premul_float_to_straight_uchar(rgba, rgba_tmp_fp);
}
else {
*((uint *)rgba) = *(uint *)(((char *)ibuf->byte_buffer.data) + ((xi + yi * ibuf->x) * 4));
}
memcpy(rgba, &col, sizeof(col));
}
if (rgba_fp) {
if (ibuf->float_buffer.data) {
copy_v4_v4(rgba_fp, (ibuf->float_buffer.data + ((xi + yi * ibuf->x) * 4)));
}
else {
uchar *tmp_ch = ibuf->byte_buffer.data + ((xi + yi * ibuf->x) * 4);
straight_uchar_to_premul_float(rgba_fp, tmp_ch);
}
else {
straight_uchar_to_premul_float(rgba_fp, col);
}
}
BKE_image_release_ibuf(ima, ibuf, nullptr);
@ -1657,18 +1614,22 @@ static float screen_px_line_point_factor_v2_persp(const ProjPaintState *ps,
static void project_face_pixel(
const float *tri_uv[3], ImBuf *ibuf_other, const float w[3], uchar rgba_ub[4], float rgba_f[4])
{
float uv_other[2], x, y;
using namespace blender;
float uv_other[2];
interp_v2_v2v2v2(uv_other, UNPACK3(tri_uv), w);
/* use */
uvco_to_wrapped_pxco(uv_other, ibuf_other->x, ibuf_other->y, &x, &y);
float x = uv_other[0] * ibuf_other->x - 0.5f;
float y = uv_other[1] * ibuf_other->y - 0.5f;
if (ibuf_other->float_buffer.data) { /* from float to float */
bilinear_interpolation_color_wrap(ibuf_other, nullptr, rgba_f, x, y);
if (ibuf_other->float_buffer.data) {
float4 col = imbuf::interpolate_bilinear_wrap_fl(ibuf_other, x, y);
col = math::clamp(col, 0.0f, 1.0f);
memcpy(rgba_f, &col, sizeof(col));
}
else { /* from char to float */
bilinear_interpolation_color_wrap(ibuf_other, rgba_ub, nullptr, x, y);
else {
uchar4 col = imbuf::interpolate_bilinear_wrap_byte(ibuf_other, x, y);
memcpy(rgba_ub, &col, sizeof(col));
}
}
@ -5388,11 +5349,10 @@ static void do_projectpaint_thread(TaskPool *__restrict /*pool*/, void *ph_v)
if (is_floatbuf) {
BLI_assert(ps->reproject_ibuf->float_buffer.data != nullptr);
bicubic_interpolation_color(ps->reproject_ibuf,
nullptr,
projPixel->newColor.f,
projPixel->projCoSS[0],
projPixel->projCoSS[1]);
blender::imbuf::interpolate_cubic_bspline_fl(ps->reproject_ibuf,
projPixel->newColor.f,
projPixel->projCoSS[0],
projPixel->projCoSS[1]);
if (projPixel->newColor.f[3]) {
float mask = float(projPixel->mask) * (1.0f / 65535.0f);
@ -5404,12 +5364,10 @@ static void do_projectpaint_thread(TaskPool *__restrict /*pool*/, void *ph_v)
}
else {
BLI_assert(ps->reproject_ibuf->byte_buffer.data != nullptr);
bicubic_interpolation_color(ps->reproject_ibuf,
projPixel->newColor.ch,
nullptr,
projPixel->projCoSS[0],
projPixel->projCoSS[1]);
blender::imbuf::interpolate_cubic_bspline_byte(ps->reproject_ibuf,
projPixel->newColor.ch,
projPixel->projCoSS[0],
projPixel->projCoSS[1]);
if (projPixel->newColor.ch[3]) {
float mask = float(projPixel->mask) * (1.0f / 65535.0f);
projPixel->newColor.ch[3] *= mask;

View File

@ -19,8 +19,7 @@
#include "BLT_translation.h"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "DNA_brush_types.h"
#include "DNA_customdata_types.h"

View File

@ -19,6 +19,7 @@
#include "BLI_listbase.h"
#include "BLI_math_color.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.hh"
#include "BLI_rect.h"
#include "BLI_utildefines.h"
@ -51,6 +52,7 @@
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "RE_texture.h"
@ -372,6 +374,7 @@ static int imapaint_pick_face(ViewContext *vc, const int mval[2], uint *r_index,
void paint_sample_color(
bContext *C, ARegion *region, int x, int y, bool texpaint_proj, bool use_palette)
{
using namespace blender;
Scene *scene = CTX_data_scene(C);
Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C);
Paint *paint = BKE_paint_get_active_from_context(C);
@ -445,46 +448,31 @@ void paint_sample_color(
}
if (image) {
float uv[2];
float u, v;
/* XXX get appropriate ImageUser instead */
ImageUser iuser;
BKE_imageuser_default(&iuser);
iuser.framenr = image->lastframe;
float uv[2];
imapaint_pick_uv(me_eval, scene, ob_eval, faceindex, mval, uv);
if (image->source == IMA_SRC_TILED) {
float new_uv[2];
iuser.tile = BKE_image_get_tile_from_pos(image, uv, new_uv, nullptr);
u = new_uv[0];
v = new_uv[1];
}
else {
u = fmodf(uv[0], 1.0f);
v = fmodf(uv[1], 1.0f);
if (u < 0.0f) {
u += 1.0f;
}
if (v < 0.0f) {
v += 1.0f;
}
uv[0] = new_uv[0];
uv[1] = new_uv[1];
}
ImBuf *ibuf = BKE_image_acquire_ibuf(image, &iuser, nullptr);
if (ibuf && (ibuf->byte_buffer.data || ibuf->float_buffer.data)) {
u = u * ibuf->x;
v = v * ibuf->y;
float u = uv[0] * ibuf->x;
float v = uv[1] * ibuf->y;
if (ibuf->float_buffer.data) {
float rgba_f[4];
if (interp == SHD_INTERP_CLOSEST) {
nearest_interpolation_color_wrap(ibuf, nullptr, rgba_f, u, v);
}
else {
bilinear_interpolation_color_wrap(ibuf, nullptr, rgba_f, u, v);
}
float4 rgba_f = interp == SHD_INTERP_CLOSEST ?
imbuf::interpolate_nearest_wrap_fl(ibuf, u, v) :
imbuf::interpolate_bilinear_wrap_fl(ibuf, u, v);
rgba_f = math::clamp(rgba_f, 0.0f, 1.0f);
straight_to_premul_v4(rgba_f);
if (use_palette) {
linearrgb_to_srgb_v3_v3(color->rgb, rgba_f);
@ -495,13 +483,9 @@ void paint_sample_color(
}
}
else {
uchar rgba[4];
if (interp == SHD_INTERP_CLOSEST) {
nearest_interpolation_color_wrap(ibuf, rgba, nullptr, u, v);
}
else {
bilinear_interpolation_color_wrap(ibuf, rgba, nullptr, u, v);
}
uchar4 rgba = interp == SHD_INTERP_CLOSEST ?
imbuf::interpolate_nearest_wrap_byte(ibuf, u, v) :
imbuf::interpolate_bilinear_wrap_byte(ibuf, u, v);
if (use_palette) {
rgb_uchar_to_float(color->rgb, rgba);
}

View File

@ -237,6 +237,7 @@ static void lineart_gpencil_bake_startjob(void *customdata, wmJobWorkerStatus *w
BKE_scene_frame_set(bj->scene, frame);
BKE_scene_graph_update_for_newframe(bj->dg);
DEG_graph_build_from_view_layer(bj->dg);
for (LinkNode *l = bj->objects; l; l = l->next) {
Object *ob = static_cast<Object *>(l->link);

View File

@ -36,6 +36,7 @@ set(SRC
intern/format_tiff.cc
intern/imageprocess.cc
intern/indexer.cc
intern/interp.cc
intern/iris.cc
intern/jpeg.cc
intern/metadata.cc
@ -58,6 +59,7 @@ set(SRC
IMB_imbuf.hh
IMB_imbuf_enums.h
IMB_imbuf_types.hh
IMB_interp.hh
IMB_metadata.hh
IMB_moviecache.hh
IMB_openexr.hh

View File

@ -512,47 +512,9 @@ void IMB_buffer_byte_from_byte(unsigned char *rect_to,
*/
void IMB_convert_rgba_to_abgr(ImBuf *ibuf);
void bicubic_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout);
void nearest_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout);
void bilinear_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout);
typedef void (*InterpolationColorFunction)(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
void bicubic_interpolation_color(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
/* Functions assumes out to be zeroed, only does RGBA. */
void nearest_interpolation_color_char(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
void nearest_interpolation_color_fl(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
void nearest_interpolation_color(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
void nearest_interpolation_color_wrap(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
void bilinear_interpolation_color(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
void bilinear_interpolation_color_char(const ImBuf *in, unsigned char outI[4], float u, float v);
void bilinear_interpolation_color_fl(const ImBuf *in, float outF[4], float u, float v);
/**
* Note about wrapping, the u/v still needs to be within the image bounds,
* just the interpolation is wrapped.
* This the same as bilinear_interpolation_color except it wraps
* rather than using empty and emptyI.
*/
void bilinear_interpolation_color_wrap(
const ImBuf *in, unsigned char outI[4], float outF[4], float u, float v);
void IMB_alpha_under_color_float(float *rect_float, int x, int y, float backcol[3]);
void IMB_alpha_under_color_byte(unsigned char *rect, int x, int y, const float backcol[3]);
/**
* Sample pixel of image using NEAREST method.
*/
void IMB_sampleImageAtLocation(
ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4]);
ImBuf *IMB_loadifffile(int file, int flags, char colorspace[IM_MAX_SPACE], const char *descr);
ImBuf *IMB_half_x(ImBuf *ibuf1);

View File

@ -0,0 +1,100 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*
* Image buffer pixel sampling functions.
* Mostly convenience wrappers around lower level `BLI_math_interp.hh`.
*/
#pragma once
#include "BLI_math_interp.hh"
#include "IMB_imbuf_types.hh"
#include <cstring>
namespace blender::imbuf {
[[nodiscard]] inline uchar4 interpolate_nearest_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_nearest_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline float4 interpolate_nearest_fl(const ImBuf *in, float u, float v)
{
return math::interpolate_nearest_fl(in->float_buffer.data, in->x, in->y, u, v);
}
inline void interpolate_nearest_byte(const ImBuf *in, uchar output[4], float u, float v)
{
math::interpolate_nearest_byte(in->byte_buffer.data, output, in->x, in->y, u, v);
}
inline void interpolate_nearest_fl(const ImBuf *in, float output[4], float u, float v)
{
math::interpolate_nearest_fl(in->float_buffer.data, output, in->x, in->y, 4, u, v);
}
[[nodiscard]] inline uchar4 interpolate_nearest_wrap_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_nearest_wrap_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline float4 interpolate_nearest_wrap_fl(const ImBuf *in, float u, float v)
{
return math::interpolate_nearest_wrap_fl(in->float_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline uchar4 interpolate_bilinear_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_bilinear_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline float4 interpolate_bilinear_fl(const ImBuf *in, float u, float v)
{
return math::interpolate_bilinear_fl(in->float_buffer.data, in->x, in->y, u, v);
}
inline void interpolate_bilinear_byte(const ImBuf *in, uchar output[4], float u, float v)
{
uchar4 col = math::interpolate_bilinear_byte(in->byte_buffer.data, in->x, in->y, u, v);
memcpy(output, &col, sizeof(col));
}
inline void interpolate_bilinear_fl(const ImBuf *in, float output[4], float u, float v)
{
float4 col = math::interpolate_bilinear_fl(in->float_buffer.data, in->x, in->y, u, v);
memcpy(output, &col, sizeof(col));
}
[[nodiscard]] inline uchar4 interpolate_bilinear_wrap_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_bilinear_wrap_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline float4 interpolate_bilinear_wrap_fl(const ImBuf *in, float u, float v)
{
return math::interpolate_bilinear_wrap_fl(in->float_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline uchar4 interpolate_cubic_bspline_byte(const ImBuf *in, float u, float v)
{
return math::interpolate_cubic_bspline_byte(in->byte_buffer.data, in->x, in->y, u, v);
}
[[nodiscard]] inline float4 interpolate_cubic_bspline_fl(const ImBuf *in, float u, float v)
{
return math::interpolate_cubic_bspline_fl(in->float_buffer.data, in->x, in->y, u, v);
}
inline void interpolate_cubic_bspline_byte(const ImBuf *in, uchar output[4], float u, float v)
{
uchar4 col = math::interpolate_cubic_bspline_byte(in->byte_buffer.data, in->x, in->y, u, v);
memcpy(output, &col, sizeof(col));
}
inline void interpolate_cubic_bspline_fl(const ImBuf *in, float output[4], float u, float v)
{
float4 col = math::interpolate_cubic_bspline_fl(in->float_buffer.data, in->x, in->y, u, v);
memcpy(output, &col, sizeof(col));
}
} // namespace blender::imbuf
/**
* Sample pixel of image using NEAREST method.
*/
void IMB_sampleImageAtLocation(
ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4]);

View File

@ -1,14 +1,10 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*
* This file was moved here from the `src/` directory.
* It is meant to deal with endianness. It resided in a general blending lib.
* The other functions were only used during rendering. This single function remained.
* It should probably move to `imbuf/intern/util.cc`, but we'll keep it here for the time being.
*/
#include <cmath>
@ -16,7 +12,6 @@
#include "MEM_guardedalloc.h"
#include "BLI_math_interp.hh"
#include "BLI_task.h"
#include "BLI_utildefines.h"
@ -59,286 +54,6 @@ void IMB_convert_rgba_to_abgr(ImBuf *ibuf)
}
}
static void pixel_from_buffer(const ImBuf *ibuf, uchar **outI, float **outF, int x, int y)
{
size_t offset = size_t(ibuf->x) * y * 4 + 4 * x;
if (ibuf->byte_buffer.data) {
*outI = ibuf->byte_buffer.data + offset;
}
if (ibuf->float_buffer.data) {
*outF = ibuf->float_buffer.data + offset;
}
}
/* -------------------------------------------------------------------- */
/** \name Bi-Cubic Interpolation
* \{ */
void bicubic_interpolation_color(const ImBuf *in, uchar outI[4], float outF[4], float u, float v)
{
if (outF) {
BLI_bicubic_interpolation_fl(in->float_buffer.data, outF, in->x, in->y, 4, u, v);
}
else {
BLI_bicubic_interpolation_char(in->byte_buffer.data, outI, in->x, in->y, u, v);
}
}
void bicubic_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout)
{
uchar *outI = nullptr;
float *outF = nullptr;
if (in == nullptr || (in->byte_buffer.data == nullptr && in->float_buffer.data == nullptr)) {
return;
}
/* GCC warns these could be uninitialized, but its ok. */
pixel_from_buffer(out, &outI, &outF, xout, yout);
bicubic_interpolation_color(in, outI, outF, u, v);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Bi-Linear Interpolation
* \{ */
void bilinear_interpolation_color_fl(const ImBuf *in, float outF[4], float u, float v)
{
BLI_assert(outF);
BLI_assert(in->float_buffer.data);
BLI_bilinear_interpolation_fl(in->float_buffer.data, outF, in->x, in->y, 4, u, v);
}
void bilinear_interpolation_color_char(const ImBuf *in, uchar outI[4], float u, float v)
{
BLI_assert(outI);
BLI_assert(in->byte_buffer.data);
BLI_bilinear_interpolation_char(in->byte_buffer.data, outI, in->x, in->y, u, v);
}
void bilinear_interpolation_color(const ImBuf *in, uchar outI[4], float outF[4], float u, float v)
{
if (outF) {
BLI_bilinear_interpolation_fl(in->float_buffer.data, outF, in->x, in->y, 4, u, v);
}
else {
BLI_bilinear_interpolation_char(in->byte_buffer.data, outI, in->x, in->y, u, v);
}
}
/* Function assumes out to be zeroed, only does RGBA. */
/* BILINEAR INTERPOLATION */
void bilinear_interpolation_color_wrap(
const ImBuf *in, uchar outI[4], float outF[4], float u, float v)
{
float *row1, *row2, *row3, *row4, a, b;
uchar *row1I, *row2I, *row3I, *row4I;
float a_b, ma_b, a_mb, ma_mb;
int y1, y2, x1, x2;
/* ImBuf in must have a valid rect or rect_float, assume this is already checked */
x1 = int(floor(u));
x2 = int(ceil(u));
y1 = int(floor(v));
y2 = int(ceil(v));
/* sample area entirely outside image? */
if (x2 < 0 || x1 > in->x - 1 || y2 < 0 || y1 > in->y - 1) {
return;
}
/* Wrap interpolation pixels - main difference from #bilinear_interpolation_color. */
if (x1 < 0) {
x1 = in->x + x1;
}
if (y1 < 0) {
y1 = in->y + y1;
}
if (x2 >= in->x) {
x2 = x2 - in->x;
}
if (y2 >= in->y) {
y2 = y2 - in->y;
}
a = u - floorf(u);
b = v - floorf(v);
a_b = a * b;
ma_b = (1.0f - a) * b;
a_mb = a * (1.0f - b);
ma_mb = (1.0f - a) * (1.0f - b);
if (outF) {
float *in_rect_float = in->float_buffer.data;
/* sample including outside of edges of image */
row1 = in_rect_float + size_t(in->x) * y1 * 4 + 4 * x1;
row2 = in_rect_float + size_t(in->x) * y2 * 4 + 4 * x1;
row3 = in_rect_float + size_t(in->x) * y1 * 4 + 4 * x2;
row4 = in_rect_float + size_t(in->x) * y2 * 4 + 4 * x2;
outF[0] = ma_mb * row1[0] + a_mb * row3[0] + ma_b * row2[0] + a_b * row4[0];
outF[1] = ma_mb * row1[1] + a_mb * row3[1] + ma_b * row2[1] + a_b * row4[1];
outF[2] = ma_mb * row1[2] + a_mb * row3[2] + ma_b * row2[2] + a_b * row4[2];
outF[3] = ma_mb * row1[3] + a_mb * row3[3] + ma_b * row2[3] + a_b * row4[3];
/* clamp here or else we can easily get off-range */
clamp_v4(outF, 0.0f, 1.0f);
}
if (outI) {
uchar *in_rect = in->byte_buffer.data;
/* sample including outside of edges of image */
row1I = in_rect + size_t(in->x) * y1 * 4 + 4 * x1;
row2I = in_rect + size_t(in->x) * y2 * 4 + 4 * x1;
row3I = in_rect + size_t(in->x) * y1 * 4 + 4 * x2;
row4I = in_rect + size_t(in->x) * y2 * 4 + 4 * x2;
/* Tested with white images and this should not wrap back to zero. */
outI[0] = roundf(ma_mb * row1I[0] + a_mb * row3I[0] + ma_b * row2I[0] + a_b * row4I[0]);
outI[1] = roundf(ma_mb * row1I[1] + a_mb * row3I[1] + ma_b * row2I[1] + a_b * row4I[1]);
outI[2] = roundf(ma_mb * row1I[2] + a_mb * row3I[2] + ma_b * row2I[2] + a_b * row4I[2]);
outI[3] = roundf(ma_mb * row1I[3] + a_mb * row3I[3] + ma_b * row2I[3] + a_b * row4I[3]);
}
}
void bilinear_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout)
{
uchar *outI = nullptr;
float *outF = nullptr;
if (in == nullptr || (in->byte_buffer.data == nullptr && in->float_buffer.data == nullptr)) {
return;
}
/* GCC warns these could be uninitialized, but its ok. */
pixel_from_buffer(out, &outI, &outF, xout, yout);
bilinear_interpolation_color(in, outI, outF, u, v);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Nearest Interpolation
* \{ */
void nearest_interpolation_color_char(
const ImBuf *in, uchar outI[4], float /*outF*/[4], float u, float v)
{
BLI_assert(outI);
BLI_assert(in->byte_buffer.data);
/* ImBuf in must have a valid rect or rect_float, assume this is already checked */
int x1 = int(u);
int y1 = int(v);
/* sample area entirely outside image? */
if (x1 < 0 || x1 >= in->x || y1 < 0 || y1 >= in->y) {
outI[0] = outI[1] = outI[2] = outI[3] = 0;
return;
}
const size_t offset = (size_t(in->x) * y1 + x1) * 4;
const uchar *dataI = in->byte_buffer.data + offset;
outI[0] = dataI[0];
outI[1] = dataI[1];
outI[2] = dataI[2];
outI[3] = dataI[3];
}
void nearest_interpolation_color_fl(
const ImBuf *in, uchar /*outI*/[4], float outF[4], float u, float v)
{
BLI_assert(outF);
BLI_assert(in->float_buffer.data);
/* ImBuf in must have a valid rect or rect_float, assume this is already checked */
int x1 = int(u);
int y1 = int(v);
/* sample area entirely outside image? */
if (x1 < 0 || x1 >= in->x || y1 < 0 || y1 >= in->y) {
zero_v4(outF);
return;
}
const size_t offset = (size_t(in->x) * y1 + x1) * 4;
const float *dataF = in->float_buffer.data + offset;
copy_v4_v4(outF, dataF);
}
void nearest_interpolation_color(const ImBuf *in, uchar outI[4], float outF[4], float u, float v)
{
if (outF) {
nearest_interpolation_color_fl(in, outI, outF, u, v);
}
else {
nearest_interpolation_color_char(in, outI, outF, u, v);
}
}
void nearest_interpolation_color_wrap(
const ImBuf *in, uchar outI[4], float outF[4], float u, float v)
{
const float *dataF;
uchar *dataI;
int y, x;
/* ImBuf in must have a valid rect or rect_float, assume this is already checked */
x = int(floor(u));
y = int(floor(v));
x = x % in->x;
y = y % in->y;
/* Wrap interpolation pixels - main difference from #nearest_interpolation_color. */
if (x < 0) {
x += in->x;
}
if (y < 0) {
y += in->y;
}
dataI = in->byte_buffer.data + size_t(in->x) * y * 4 + 4 * x;
if (outI) {
outI[0] = dataI[0];
outI[1] = dataI[1];
outI[2] = dataI[2];
outI[3] = dataI[3];
}
dataF = in->float_buffer.data + size_t(in->x) * y * 4 + 4 * x;
if (outF) {
outF[0] = dataF[0];
outF[1] = dataF[1];
outF[2] = dataF[2];
outF[3] = dataF[3];
}
}
void nearest_interpolation(const ImBuf *in, ImBuf *out, float u, float v, int xout, int yout)
{
uchar *outI = nullptr;
float *outF = nullptr;
if (in == nullptr || (in->byte_buffer.data == nullptr && in->float_buffer.data == nullptr)) {
return;
}
/* gcc warns these could be uninitialized, but its ok. */
pixel_from_buffer(out, &outI, &outF, xout, yout);
nearest_interpolation_color(in, outI, outF, u, v);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Threaded Image Processing
* \{ */
@ -472,25 +187,3 @@ void IMB_alpha_under_color_byte(uchar *rect, int x, int y, const float backcol[3
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sample Pixel
* \{ */
void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4])
{
if (ibuf->float_buffer.data) {
nearest_interpolation_color(ibuf, nullptr, color, x, y);
}
else {
uchar byte_color[4];
nearest_interpolation_color(ibuf, byte_color, nullptr, x, y);
rgba_uchar_to_float(color, byte_color);
if (make_linear_rgb) {
IMB_colormanagement_colorspace_to_scene_linear_v4(
color, false, ibuf->byte_buffer.colorspace);
}
}
}
/** \} */

View File

@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup imbuf
*/
#include "BLI_math_vector.h"
#include "IMB_colormanagement.hh"
#include "IMB_interp.hh"
void IMB_sampleImageAtLocation(ImBuf *ibuf, float x, float y, bool make_linear_rgb, float color[4])
{
using namespace blender;
if (ibuf->float_buffer.data) {
imbuf::interpolate_nearest_fl(ibuf, color, x, y);
}
else {
uchar4 byte_color = imbuf::interpolate_nearest_byte(ibuf, x, y);
rgba_uchar_to_float(color, byte_color);
if (make_linear_rgb) {
IMB_colormanagement_colorspace_to_scene_linear_v4(
color, false, ibuf->byte_buffer.colorspace);
}
}
}

View File

@ -9,16 +9,15 @@
#include <cmath>
#include "BLI_math_color.h"
#include "BLI_math_interp.hh"
#include "BLI_utildefines.h"
#include "MEM_guardedalloc.h"
#include "IMB_filter.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "imbuf.hh"
#include "IMB_filter.hh"
#include "BLI_sys_types.h" /* for intptr_t support */
static void imb_half_x_no_alloc(ImBuf *ibuf2, ImBuf *ibuf1)
@ -1745,6 +1744,7 @@ static void scale_thread_init(void *data_v, int start_line, int tot_line, void *
static void *do_scale_thread(void *data_v)
{
using namespace blender::imbuf;
ScaleThreadData *data = (ScaleThreadData *)data_v;
ImBuf *ibuf = data->ibuf;
int i;
@ -1761,13 +1761,12 @@ static void *do_scale_thread(void *data_v)
int offset = y * data->newx + x;
if (data->byte_buffer) {
uchar *pixel = data->byte_buffer + 4 * offset;
BLI_bilinear_interpolation_char(ibuf->byte_buffer.data, pixel, ibuf->x, ibuf->y, u, v);
interpolate_bilinear_byte(ibuf, data->byte_buffer + 4 * offset, u, v);
}
if (data->float_buffer) {
float *pixel = data->float_buffer + ibuf->channels * offset;
BLI_bilinear_interpolation_fl(
blender::math::interpolate_bilinear_fl(
ibuf->float_buffer.data, pixel, ibuf->x, ibuf->y, ibuf->channels, u, v);
}
}

View File

@ -1,4 +1,5 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
@ -17,7 +18,10 @@
#include "BLI_vector.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
using blender::float4;
using blender::uchar4;
namespace blender::imbuf::transform {
@ -134,27 +138,6 @@ static float wrap_uv(float value, int size)
return x;
}
template<int NumChannels>
static void sample_nearest_float(const ImBuf *source, float u, float v, float *r_sample)
{
int x1 = int(u);
int y1 = int(v);
/* Break when sample outside image is requested. */
if (x1 < 0 || x1 >= source->x || y1 < 0 || y1 >= source->y) {
for (int i = 0; i < NumChannels; i++) {
r_sample[i] = 0.0f;
}
return;
}
size_t offset = (size_t(source->x) * y1 + x1) * NumChannels;
const float *dataF = source->float_buffer.data + offset;
for (int i = 0; i < NumChannels; i++) {
r_sample[i] = dataF[i];
}
}
/* Read a pixel from an image buffer, with filtering/wrapping parameters. */
template<eIMBInterpolationFilterMode Filter, typename T, int NumChannels, bool WrapUV>
static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
@ -171,19 +154,19 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
v -= 0.5f;
}
if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float> && NumChannels == 4) {
bilinear_interpolation_color_fl(source, r_sample, u, v);
interpolate_bilinear_fl(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, uchar> && NumChannels == 4)
{
nearest_interpolation_color_char(source, r_sample, nullptr, u, v);
interpolate_nearest_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, uchar> && NumChannels == 4)
{
bilinear_interpolation_color_char(source, r_sample, u, v);
interpolate_bilinear_byte(source, r_sample, u, v);
}
else if constexpr (Filter == IMB_FILTER_BILINEAR && std::is_same_v<T, float>) {
if constexpr (WrapUV) {
BLI_bilinear_interpolation_wrap_fl(source->float_buffer.data,
math::interpolate_bilinear_wrap_fl(source->float_buffer.data,
r_sample,
source->x,
source->y,
@ -194,20 +177,21 @@ static void sample_image(const ImBuf *source, float u, float v, T *r_sample)
true);
}
else {
BLI_bilinear_interpolation_fl(
math::interpolate_bilinear_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
}
else if constexpr (Filter == IMB_FILTER_NEAREST && std::is_same_v<T, float>) {
sample_nearest_float<NumChannels>(source, u, v, r_sample);
math::interpolate_nearest_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, float>) {
BLI_bicubic_interpolation_fl(
math::interpolate_cubic_bspline_fl(
source->float_buffer.data, r_sample, source->x, source->y, NumChannels, u, v);
}
else if constexpr (Filter == IMB_FILTER_BICUBIC && std::is_same_v<T, uchar> && NumChannels == 4)
{
BLI_bicubic_interpolation_char(source->byte_buffer.data, r_sample, source->x, source->y, u, v);
interpolate_cubic_bspline_byte(source, r_sample, u, v);
}
else {
/* Unsupported sampler. */

View File

@ -16,6 +16,7 @@
#include "BLI_color.hh"
#include "BLI_enumerable_thread_specific.hh"
#include "BLI_math_matrix.hh"
#include "BLI_path_util.h"
#include "BLI_task.hh"
@ -234,8 +235,8 @@ void obj_parallel_chunked_output(FormatHandler &fh, int tot_count, const Functio
return;
}
/* Give each chunk its own temporary output buffer, and process them in parallel. */
std::vector<FormatHandler> buffers(chunk_count);
blender::threading::parallel_for(IndexRange(chunk_count), 1, [&](IndexRange range) {
Array<FormatHandler> buffers(chunk_count);
threading::parallel_for(IndexRange(chunk_count), 1, [&](IndexRange range) {
for (const int r : range) {
int i_start = r * chunk_size;
int i_end = std::min(i_start + chunk_size, tot_count);
@ -259,6 +260,10 @@ void OBJWriter::write_vertex_coords(FormatHandler &fh,
const Mesh *mesh = obj_mesh_data.get_mesh();
const StringRef name = mesh->active_color_attribute;
const float4x4 transform = obj_mesh_data.get_world_axes_transform();
const Span<float3> positions = obj_mesh_data.get_mesh()->vert_positions();
if (write_colors && !name.is_empty()) {
const bke::AttributeAccessor attributes = mesh->attributes();
const VArray<ColorGeometry4f> attribute = *attributes.lookup_or_default<ColorGeometry4f>(
@ -266,7 +271,7 @@ void OBJWriter::write_vertex_coords(FormatHandler &fh,
BLI_assert(tot_count == attribute.size());
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.global_scale);
const float3 vertex = math::transform_point(transform, positions[i]);
ColorGeometry4f linear = attribute.get(i);
float srgb[3];
linearrgb_to_srgb_v3_v3(srgb, linear);
@ -275,7 +280,7 @@ void OBJWriter::write_vertex_coords(FormatHandler &fh,
}
else {
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.global_scale);
const float3 vertex = math::transform_point(transform, positions[i]);
buf.write_obj_vertex(vertex[0], vertex[1], vertex[2]);
});
}
@ -283,9 +288,8 @@ void OBJWriter::write_vertex_coords(FormatHandler &fh,
void OBJWriter::write_uv_coords(FormatHandler &fh, OBJMesh &r_obj_mesh_data) const
{
const Vector<float2> &uv_coords = r_obj_mesh_data.get_uv_coords();
const int tot_count = uv_coords.size();
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
const Span<float2> uv_coords = r_obj_mesh_data.get_uv_coords();
obj_parallel_chunked_output(fh, uv_coords.size(), [&](FormatHandler &buf, int i) {
const float2 &uv_vertex = uv_coords[i];
buf.write_obj_uv(uv_vertex[0], uv_vertex[1]);
});
@ -294,9 +298,8 @@ void OBJWriter::write_uv_coords(FormatHandler &fh, OBJMesh &r_obj_mesh_data) con
void OBJWriter::write_poly_normals(FormatHandler &fh, OBJMesh &obj_mesh_data)
{
/* Poly normals should be calculated earlier via store_normal_coords_and_indices. */
const Vector<float3> &normal_coords = obj_mesh_data.get_normal_coords();
const int tot_count = normal_coords.size();
obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler &buf, int i) {
const Span<float3> normal_coords = obj_mesh_data.get_normal_coords();
obj_parallel_chunked_output(fh, normal_coords.size(), [&](FormatHandler &buf, int i) {
const float3 &normal = normal_coords[i];
buf.write_obj_normal(normal[0], normal[1], normal[2]);
});

View File

@ -14,12 +14,12 @@
#include "BKE_mesh.hh"
#include "BKE_mesh_mapping.hh"
#include "BKE_object.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_rotation.h"
#include "BLI_sort.hh"
#include "DEG_depsgraph_query.hh"
@ -70,7 +70,8 @@ OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Obj
this->materials[i] = BKE_object_material_get_eval(obj_eval, i + 1);
}
set_world_axes_transform(*obj_eval, export_params.forward_axis, export_params.up_axis);
set_world_axes_transform(
*obj_eval, export_params.forward_axis, export_params.up_axis, export_params.global_scale);
}
/**
@ -103,11 +104,11 @@ void OBJMesh::clear()
owned_export_mesh_ = nullptr;
}
export_mesh_ = nullptr;
loop_to_uv_index_.clear_and_shrink();
loop_to_uv_index_ = {};
uv_coords_.clear_and_shrink();
loop_to_normal_index_.clear_and_shrink();
loop_to_normal_index_ = {};
normal_coords_.clear_and_shrink();
poly_order_.clear_and_shrink();
poly_order_ = {};
if (poly_smooth_groups_) {
MEM_freeN(poly_smooth_groups_);
poly_smooth_groups_ = nullptr;
@ -146,23 +147,27 @@ void OBJMesh::triangulate_mesh_eval()
void OBJMesh::set_world_axes_transform(const Object &obj_eval,
const eIOAxis forward,
const eIOAxis up)
const eIOAxis up,
const float global_scale)
{
float axes_transform[3][3];
unit_m3(axes_transform);
float3x3 axes_transform;
/* +Y-forward and +Z-up are the default Blender axis settings. */
mat3_from_axis_conversion(forward, up, IO_AXIS_Y, IO_AXIS_Z, axes_transform);
mul_m4_m3m4(world_and_axes_transform_, axes_transform, obj_eval.object_to_world);
/* mul_m4_m3m4 does not transform last row of obmat, i.e. location data. */
mul_v3_m3v3(world_and_axes_transform_[3], axes_transform, obj_eval.object_to_world[3]);
world_and_axes_transform_[3][3] = obj_eval.object_to_world[3][3];
mat3_from_axis_conversion(forward, up, IO_AXIS_Y, IO_AXIS_Z, axes_transform.ptr());
const float4x4 object_to_world(obj_eval.object_to_world);
const float3x3 transform = axes_transform * float3x3(object_to_world);
world_and_axes_transform_ = float4x4(transform);
world_and_axes_transform_.location() = axes_transform * object_to_world.location();
world_and_axes_transform_[3][3] = object_to_world[3][3];
world_and_axes_transform_ = math::from_scale<float4x4>(float3(global_scale)) *
world_and_axes_transform_;
/* Normals need inverse transpose of the regular matrix to handle non-uniform scale. */
float normal_matrix[3][3];
copy_m3_m4(normal_matrix, world_and_axes_transform_);
invert_m3_m3(world_and_axes_normal_transform_, normal_matrix);
transpose_m3(world_and_axes_normal_transform_);
mirrored_transform_ = is_negative_m3(world_and_axes_normal_transform_);
world_and_axes_normal_transform_ = math::transpose(math::invert(transform));
mirrored_transform_ = math::is_negative(world_and_axes_normal_transform_);
}
int OBJMesh::tot_vertices() const
@ -227,7 +232,7 @@ void OBJMesh::calc_poly_order()
}
const VArraySpan<int> material_indices_span(material_indices);
poly_order_.resize(material_indices_span.size());
poly_order_.reinitialize(material_indices_span.size());
for (const int i : material_indices_span.index_range()) {
poly_order_[i] = i;
}
@ -258,14 +263,6 @@ const char *OBJMesh::get_object_mesh_name() const
return export_mesh_->id.name + 2;
}
float3 OBJMesh::calc_vertex_coords(const int vert_index, const float global_scale) const
{
float3 r_coords = mesh_positions_[vert_index];
mul_m4_v3(world_and_axes_transform_, r_coords);
mul_v3_fl(r_coords, global_scale);
return r_coords;
}
Span<int> OBJMesh::calc_poly_vertex_indices(const int face_index) const
{
return mesh_corner_verts_.slice(mesh_faces_[face_index]);
@ -292,7 +289,7 @@ void OBJMesh::store_uv_coords_and_indices()
uv_to_index.reserve(export_mesh_->verts_num);
uv_coords_.reserve(export_mesh_->verts_num);
loop_to_uv_index_.resize(uv_map.size());
loop_to_uv_index_.reinitialize(uv_map.size());
for (int index = 0; index < int(uv_map.size()); index++) {
float2 uv = uv_map[index];
@ -317,11 +314,9 @@ Span<int> OBJMesh::calc_poly_uv_indices(const int face_index) const
float3 OBJMesh::calc_poly_normal(const int face_index) const
{
float3 r_poly_normal = bke::mesh::face_normal_calc(
mesh_positions_, mesh_corner_verts_.slice(mesh_faces_[face_index]));
mul_m3_v3(world_and_axes_normal_transform_, r_poly_normal);
normalize_v3(r_poly_normal);
return r_poly_normal;
const Span<int> face_verts = mesh_corner_verts_.slice(mesh_faces_[face_index]);
const float3 normal = bke::mesh::face_normal_calc(mesh_positions_, face_verts);
return math::normalize(world_and_axes_normal_transform_ * normal);
}
/** Round \a f to \a round_digits decimal digits. */
@ -352,7 +347,7 @@ void OBJMesh::store_normal_coords_and_indices()
Map<float3, int> normal_to_index;
/* We don't know how many unique normals there will be, but this is a guess. */
normal_to_index.reserve(export_mesh_->faces_num);
loop_to_normal_index_.resize(export_mesh_->corners_num);
loop_to_normal_index_.reinitialize(export_mesh_->corners_num);
loop_to_normal_index_.fill(-1);
Span<float3> corner_normals;
@ -368,17 +363,15 @@ void OBJMesh::store_normal_coords_and_indices()
bool need_per_loop_normals = !corner_normals.is_empty() || !(sharp_faces_[face_index]);
if (need_per_loop_normals) {
for (const int corner : face) {
float3 loop_normal;
BLI_assert(corner < export_mesh_->corners_num);
copy_v3_v3(loop_normal, corner_normals[corner]);
mul_m3_v3(world_and_axes_normal_transform_, loop_normal);
normalize_v3(loop_normal);
float3 rounded_loop_normal = round_float3_to_n_digits(loop_normal, round_digits);
int loop_norm_index = normal_to_index.lookup_default(rounded_loop_normal, -1);
const float3 normal = math::normalize(world_and_axes_normal_transform_ *
corner_normals[corner]);
const float3 rounded = round_float3_to_n_digits(normal, round_digits);
int loop_norm_index = normal_to_index.lookup_default(rounded, -1);
if (loop_norm_index == -1) {
loop_norm_index = cur_normal_index++;
normal_to_index.add(rounded_loop_normal, loop_norm_index);
normal_coords_.append(rounded_loop_normal);
normal_to_index.add(rounded, loop_norm_index);
normal_coords_.append(rounded);
}
loop_to_normal_index_[corner] = loop_norm_index;
}

View File

@ -10,6 +10,7 @@
#include <optional>
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_offset_indices.hh"
#include "BLI_utility_mixins.hh"
@ -44,14 +45,14 @@ class OBJMesh : NonCopyable {
* Final transform of an object obtained from export settings (up_axis, forward_axis) and the
* object's world transform matrix.
*/
float world_and_axes_transform_[4][4];
float world_and_axes_normal_transform_[3][3];
float4x4 world_and_axes_transform_;
float3x3 world_and_axes_normal_transform_;
bool mirrored_transform_;
/**
* Per-loop UV index.
*/
Vector<int> loop_to_uv_index_;
Array<int> loop_to_uv_index_;
/*
* UV vertices.
*/
@ -60,7 +61,7 @@ class OBJMesh : NonCopyable {
/**
* Per-loop normal index.
*/
Vector<int> loop_to_normal_index_;
Array<int> loop_to_normal_index_;
/*
* Normal coords.
*/
@ -81,7 +82,7 @@ class OBJMesh : NonCopyable {
/**
* Order in which the polygons should be written into the file (sorted by material index).
*/
Vector<int> poly_order_;
Array<int> poly_order_;
public:
Array<const Material *> materials;
@ -132,10 +133,11 @@ class OBJMesh : NonCopyable {
*/
const char *get_object_mesh_name() const;
/**
* Calculate coordinates of the vertex at the given index.
*/
float3 calc_vertex_coords(int vert_index, float global_scale) const;
const float4x4 &get_world_axes_transform() const
{
return world_and_axes_transform_;
}
/**
* Calculate vertex indices of all vertices of the polygon at the given index.
*/
@ -221,6 +223,9 @@ class OBJMesh : NonCopyable {
/**
* Set the final transform after applying axes settings and an Object's world transform.
*/
void set_world_axes_transform(const Object &obj_eval, eIOAxis forward, eIOAxis up);
void set_world_axes_transform(const Object &obj_eval,
eIOAxis forward,
eIOAxis up,
float global_scale);
};
} // namespace blender::io::obj

View File

@ -138,7 +138,7 @@ filter_supported_objects(Depsgraph *depsgraph, const OBJExportParams &export_par
return {std::move(r_exportable_meshes), std::move(r_exportable_nurbs)};
}
static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_mesh,
static void write_mesh_objects(const Span<std::unique_ptr<OBJMesh>> exportable_as_mesh,
OBJWriter &obj_writer,
MTLWriter *mtl_writer,
const OBJExportParams &export_params)
@ -147,7 +147,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
* we have to have the output text buffer for each object,
* and write them all into the file at the end. */
size_t count = exportable_as_mesh.size();
std::vector<FormatHandler> buffers(count);
Array<FormatHandler> buffers(count);
/* Serial: gather material indices, ensure normals & edges. */
Vector<Vector<int>> mtlindices;
@ -163,7 +163,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
}
/* Parallel over meshes: store normal coords & indices, uv coords and indices. */
blender::threading::parallel_for(IndexRange(count), 1, [&](IndexRange range) {
threading::parallel_for(IndexRange(count), 1, [&](IndexRange range) {
for (const int i : range) {
OBJMesh &obj = *exportable_as_mesh[i];
if (export_params.export_normals) {
@ -189,7 +189,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
}
/* Parallel over meshes: main result writing. */
blender::threading::parallel_for(IndexRange(count), 1, [&](IndexRange range) {
threading::parallel_for(IndexRange(count), 1, [&](IndexRange range) {
for (const int i : range) {
OBJMesh &obj = *exportable_as_mesh[i];
auto &fh = buffers[i];
@ -239,7 +239,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
/**
* Export NURBS Curves in parameter form, not as vertices and edges.
*/
static void write_nurbs_curve_objects(const Vector<std::unique_ptr<OBJCurve>> &exportable_as_nurbs,
static void write_nurbs_curve_objects(const Span<std::unique_ptr<OBJCurve>> exportable_as_nurbs,
const OBJWriter &obj_writer)
{
FormatHandler fh;
@ -280,8 +280,7 @@ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, co
auto [exportable_as_mesh, exportable_as_nurbs] = filter_supported_objects(depsgraph,
export_params);
write_mesh_objects(
std::move(exportable_as_mesh), *frame_writer, mtl_writer.get(), export_params);
write_mesh_objects(exportable_as_mesh, *frame_writer, mtl_writer.get(), export_params);
if (mtl_writer) {
mtl_writer->write_header(export_params.blen_filepath);
char dest_dir[PATH_MAX];
@ -298,7 +297,7 @@ void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, co
dest_dir,
export_params.export_pbr_extensions);
}
write_nurbs_curve_objects(std::move(exportable_as_nurbs), *frame_writer);
write_nurbs_curve_objects(exportable_as_nurbs, *frame_writer);
}
bool append_frame_to_filename(const char *filepath, const int frame, char *r_filepath_with_frames)

View File

@ -19,7 +19,7 @@
#include "BKE_mesh_mapping.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "MEM_guardedalloc.h"
@ -221,9 +221,12 @@ class TextureMarginMap {
*/
void lookup_pixels(ImBuf *ibuf, char *mask, int maxPolygonSteps)
{
float4 *ibuf_ptr_fl = reinterpret_cast<float4 *>(ibuf->float_buffer.data);
uchar4 *ibuf_ptr_ch = reinterpret_cast<uchar4 *>(ibuf->byte_buffer.data);
size_t pixel_index = 0;
for (int y = 0; y < h_; y++) {
for (int x = 0; x < w_; x++) {
uint32_t dp = get_pixel(x, y);
uint32_t dp = pixel_data_[pixel_index];
if (IsDijkstraPixel(dp) && !DijkstraPixelIsUnset(dp)) {
int dist = DijkstraPixelGetDistance(dp);
int direction = DijkstraPixelGetDirection(dp);
@ -270,9 +273,14 @@ class TextureMarginMap {
}
if (found_pixel_in_polygon) {
bilinear_interpolation(ibuf, ibuf, destX, destY, x, y);
if (ibuf_ptr_fl) {
ibuf_ptr_fl[pixel_index] = imbuf::interpolate_bilinear_fl(ibuf, destX, destY);
}
if (ibuf_ptr_ch) {
ibuf_ptr_ch[pixel_index] = imbuf::interpolate_bilinear_byte(ibuf, destX, destY);
}
/* Add our new pixels to the assigned pixel map. */
mask[y * w_ + x] = 1;
mask[pixel_index] = 1;
}
}
}
@ -280,8 +288,9 @@ class TextureMarginMap {
/* These are not margin pixels, make sure the extend filter which is run after this step
* leaves them alone.
*/
mask[y * w_ + x] = 1;
mask[pixel_index] = 1;
}
pixel_index++;
}
}
}

View File

@ -1,5 +1,5 @@
/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
* SPDX-FileCopyrightText: 2003-2009 Blender Authors
* SPDX-FileCopyrightText: 2003-2024 Blender Authors
* SPDX-FileCopyrightText: 2005-2006 Peter Schlaile <peter [at] schlaile [dot] de>
*
* SPDX-License-Identifier: GPL-2.0-or-later */
@ -40,6 +40,7 @@
#include "IMB_colormanagement.hh"
#include "IMB_imbuf.hh"
#include "IMB_imbuf_types.hh"
#include "IMB_interp.hh"
#include "IMB_metadata.hh"
#include "BLI_math_color_blend.h"
@ -64,7 +65,7 @@
#include "strip_time.hh"
#include "utils.hh"
using blender::float4;
using namespace blender;
static SeqEffectHandle get_sequence_effect_impl(int seq_type);
@ -1536,7 +1537,7 @@ static void transform_image(int x,
int y,
int start_line,
int total_lines,
ImBuf *ibuf1,
ImBuf *ibuf,
ImBuf *out,
float scale_x,
float scale_y,
@ -1549,6 +1550,10 @@ static void transform_image(int x,
float s = sinf(rotate);
float c = cosf(rotate);
float4 *dst_fl = reinterpret_cast<float4 *>(out->float_buffer.data);
uchar4 *dst_ch = reinterpret_cast<uchar4 *>(out->byte_buffer.data);
size_t offset = size_t(x) * start_line;
for (int yi = start_line; yi < start_line + total_lines; yi++) {
for (int xi = 0; xi < x; xi++) {
/* Translate point. */
@ -1570,15 +1575,31 @@ static void transform_image(int x,
/* interpolate */
switch (interpolation) {
case 0:
nearest_interpolation(ibuf1, out, xt, yt, xi, yi);
if (dst_fl) {
dst_fl[offset] = imbuf::interpolate_nearest_fl(ibuf, xt, yt);
}
else {
dst_ch[offset] = imbuf::interpolate_nearest_byte(ibuf, xt, yt);
}
break;
case 1:
bilinear_interpolation(ibuf1, out, xt, yt, xi, yi);
if (dst_fl) {
dst_fl[offset] = imbuf::interpolate_bilinear_fl(ibuf, xt, yt);
}
else {
dst_ch[offset] = imbuf::interpolate_bilinear_byte(ibuf, xt, yt);
}
break;
case 2:
bicubic_interpolation(ibuf1, out, xt, yt, xi, yi);
if (dst_fl) {
dst_fl[offset] = imbuf::interpolate_cubic_bspline_fl(ibuf, xt, yt);
}
else {
dst_ch[offset] = imbuf::interpolate_cubic_bspline_byte(ibuf, xt, yt);
}
break;
}
offset++;
}
}
}

View File

@ -930,18 +930,15 @@ enum wmConfirmPosition {
};
struct wmConfirmDetails {
char title[1024];
char message[1024];
char message2[1024];
char confirm_button[256];
char cancel_button[256];
std::string title;
std::string message;
std::string message2;
std::string confirm_text;
int icon;
wmConfirmSize size;
wmConfirmPosition position;
bool confirm_default;
bool cancel_default;
bool mouse_move_quit;
bool red_alert;
};
/**

View File

@ -3584,8 +3584,8 @@ static void wm_clear_recent_files_confirm(bContext * /*C*/,
wmOperator * /*op*/,
wmConfirmDetails *confirm)
{
STRNCPY(confirm->message, IFACE_("Remove all items from the recent files list"));
STRNCPY(confirm->confirm_button, IFACE_("Remove All"));
confirm->message = IFACE_("Remove all items from the recent files list");
confirm->confirm_text = IFACE_("Remove All");
confirm->position = WM_WARNING_POSITION_CENTER;
confirm->size = WM_WARNING_SIZE_LARGE;
confirm->cancel_default = true;

View File

@ -1217,16 +1217,13 @@ static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_
wmConfirmDetails confirm = {{0}};
STRNCPY(confirm.title, WM_operatortype_description(C, op->type, op->ptr).c_str());
STRNCPY(confirm.confirm_button, WM_operatortype_name(op->type, op->ptr).c_str());
STRNCPY(confirm.cancel_button, IFACE_("Cancel"));
confirm.title = WM_operatortype_description(C, op->type, op->ptr);
confirm.confirm_text = WM_operatortype_name(op->type, op->ptr);
confirm.icon = ALERT_ICON_WARNING;
confirm.size = WM_WARNING_SIZE_SMALL;
confirm.position = WM_WARNING_POSITION_MOUSE;
confirm.confirm_default = true;
confirm.cancel_default = false;
confirm.mouse_move_quit = false;
confirm.red_alert = false;
/* uiBlock.flag */
int block_flags = UI_BLOCK_KEEP_OPEN | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT;
@ -1248,21 +1245,23 @@ static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_
const uiStyle *style = UI_style_get_dpi();
int text_width = std::max(
120 * UI_SCALE_FAC,
BLF_width(style->widget.uifont_id, confirm.title, ARRAY_SIZE(confirm.title)));
if (confirm.message[0]) {
text_width = std::max(
text_width,
int(BLF_width(style->widget.uifont_id, confirm.message, ARRAY_SIZE(confirm.message))));
BLF_width(style->widget.uifont_id, confirm.title.c_str(), confirm.title.length()));
if (!confirm.message.empty()) {
text_width = std::max(text_width,
int(BLF_width(style->widget.uifont_id,
confirm.message.c_str(),
confirm.message.length())));
}
if (confirm.message2[0]) {
text_width = std::max(
text_width,
int(BLF_width(style->widget.uifont_id, confirm.message2, ARRAY_SIZE(confirm.message2))));
if (!confirm.message2.empty()) {
text_width = std::max(text_width,
int(BLF_width(style->widget.uifont_id,
confirm.message2.c_str(),
confirm.message2.length())));
}
const bool small = confirm.size == WM_WARNING_SIZE_SMALL;
const int padding = (small ? 7 : 14) * UI_SCALE_FAC;
const short icon_size = (small ? (confirm.message[0] ? 48 : 32) : 64) * UI_SCALE_FAC;
const short icon_size = (small ? (confirm.message.empty() ? 32 : 48) : 64) * UI_SCALE_FAC;
const int dialog_width = icon_size + text_width + (style->columnspace * 2.5);
const float split_factor = (float)icon_size / (float)(dialog_width - style->columnspace);
@ -1281,17 +1280,19 @@ static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_
/* The rest of the content on the right. */
layout = uiLayoutColumn(split_block, true);
if (confirm.title[0]) {
if (!confirm.message[0]) {
if (!confirm.title.empty()) {
if (confirm.message.empty()) {
uiItemS(layout);
}
uiItemL_ex(layout, confirm.title, ICON_NONE, true, false);
uiItemL_ex(layout, confirm.title.c_str(), ICON_NONE, true, false);
}
if (confirm.message[0]) {
uiItemL(layout, confirm.message, ICON_NONE);
if (!confirm.message.empty()) {
uiItemL(layout, confirm.message.c_str(), ICON_NONE);
}
if (confirm.message2[0]) {
uiItemL(layout, confirm.message2, ICON_NONE);
if (!confirm.message2.empty()) {
uiItemL(layout, confirm.message2.c_str(), ICON_NONE);
}
uiItemS_ex(layout, small ? 0.5f : 4.0f);
@ -1315,7 +1316,7 @@ static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_
UI_BTYPE_BUT,
0,
0,
confirm.confirm_button,
confirm.confirm_text.c_str(),
0,
0,
0,
@ -1333,7 +1334,7 @@ static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_
UI_BTYPE_BUT,
0,
0,
confirm.cancel_button,
IFACE_("Cancel"),
0,
0,
0,
@ -1351,7 +1352,7 @@ static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_
UI_BTYPE_BUT,
0,
0,
confirm.confirm_button,
confirm.confirm_text.c_str(),
0,
0,
0,
@ -1369,18 +1370,7 @@ static uiBlock *wm_block_confirm_create(bContext *C, ARegion *region, void *arg_
UI_but_func_set(cancel_but, wm_operator_block_cancel, op, block);
UI_but_drawflag_disable(confirm_but, UI_BUT_TEXT_LEFT);
UI_but_drawflag_disable(cancel_but, UI_BUT_TEXT_LEFT);
if (confirm.red_alert) {
UI_but_flag_enable(confirm_but, UI_BUT_REDALERT);
}
else {
if (confirm.cancel_default) {
UI_but_flag_enable(cancel_but, UI_BUT_ACTIVE_DEFAULT);
}
else if (confirm.confirm_default) {
UI_but_flag_enable(confirm_but, UI_BUT_ACTIVE_DEFAULT);
}
}
UI_but_flag_enable(confirm.cancel_default ? cancel_but : confirm_but, UI_BUT_ACTIVE_DEFAULT);
if (confirm.position == WM_WARNING_POSITION_MOUSE) {
int bounds_offset[2];

View File

@ -109,7 +109,7 @@ endfunction()
# Run Python render test.
function(add_render_test testname testscript)
set(_args ${ARGN} -blender "${TEST_BLENDER_EXE}" -idiff "${OPENIMAGEIO_IDIFF}")
set(_args ${ARGN} -blender "${TEST_BLENDER_EXE}" -oiiotool "${OPENIMAGEIO_TOOL}")
if(WITH_TESTS_BATCHED)
list(APPEND _args --batch)
endif()
@ -584,8 +584,8 @@ endif()
# SVG Import
if(TRUE)
if(NOT OPENIMAGEIO_IDIFF)
message(WARNING "Disabling SVG tests because OIIO idiff does not exist")
if(NOT OPENIMAGEIO_TOOL)
message(WARNING "Disabling SVG tests because OIIO oiiotool does not exist")
else()
set(_svg_render_tests complex path)
@ -603,8 +603,8 @@ if(TRUE)
endif()
if(WITH_CYCLES OR WITH_GPU_RENDER_TESTS)
if(NOT OPENIMAGEIO_IDIFF)
message(WARNING "Disabling render tests because OIIO idiff does not exist")
if(NOT OPENIMAGEIO_TOOL)
message(WARNING "Disabling render tests because OIIO oiiotool does not exist")
elseif(NOT EXISTS "${TEST_SRC_DIR}/render/shader")
message(WARNING "Disabling render tests because tests folder does not exist at ${TEST_SRC_DIR}")
elseif(NOT WITH_COMPOSITOR_CPU)
@ -751,8 +751,8 @@ if(WITH_CYCLES OR WITH_GPU_RENDER_TESTS)
endif()
if(WITH_COMPOSITOR_CPU)
if(NOT OPENIMAGEIO_IDIFF)
message(WARNING "Disabling Compositor CPU tests because OIIO idiff does not exist")
if(NOT OPENIMAGEIO_TOOL)
message(WARNING "Disabling Compositor CPU tests because OIIO oiiotool does not exist")
else()
set(compositor_tests
color
@ -783,8 +783,8 @@ endif()
# NOTE: WITH_COMPOSITOR_CPU is needed for rendering.
if(WITH_COMPOSITOR_REALTIME_TESTS AND WITH_COMPOSITOR_CPU)
if(NOT OPENIMAGEIO_IDIFF)
message(WARNING "Disabling realtime compositor tests because OIIO idiff does not exist")
if(NOT OPENIMAGEIO_TOOL)
message(WARNING "Disabling realtime compositor tests because OIIO oiiotool does not exist")
else()
set(compositor_tests
color
@ -876,8 +876,8 @@ endif()
if(WITH_GPU_DRAW_TESTS)
if(NOT OPENIMAGEIO_IDIFF)
message(STATUS "Disabling OpenGL draw tests because OIIO idiff does not exist")
if(NOT OPENIMAGEIO_TOOL)
message(STATUS "Disabling OpenGL draw tests because OIIO oiiotool does not exist")
elseif(NOT EXISTS "${TEST_SRC_DIR}/opengl")
message(STATUS "Disabling OpenGL draw tests because tests folder does not exist at ${TEST_SRC_DIR}")
else()
@ -950,8 +950,8 @@ if(WITH_CODEC_FFMPEG)
)
endif()
if(NOT OPENIMAGEIO_IDIFF)
message(STATUS "Disabling ImBuf image format tests because OIIO idiff does not exist")
if(NOT OPENIMAGEIO_TOOL)
message(STATUS "Disabling ImBuf image format tests because OIIO oiiotool does not exist")
else()
set(OPTIONAL_FORMATS "")
if(WITH_IMAGE_CINEON)
@ -973,7 +973,7 @@ else()
--
-test_dir "${TEST_SRC_DIR}/imbuf_io"
-output_dir "${TEST_OUT_DIR}/imbuf_io/save"
-idiff "${OPENIMAGEIO_IDIFF}"
-oiiotool "${OPENIMAGEIO_TOOL}"
-optional_formats "${OPTIONAL_FORMATS}"
)
@ -983,7 +983,7 @@ else()
--
-test_dir "${TEST_SRC_DIR}/imbuf_io"
-output_dir "${TEST_OUT_DIR}/imbuf_io/load"
-idiff "${OPENIMAGEIO_IDIFF}"
-oiiotool "${OPENIMAGEIO_TOOL}"
-optional_formats "${OPTIONAL_FORMATS}"
)
endif()
@ -991,8 +991,8 @@ endif()
# ------------------------------------------------------------------------------
# SEQUENCER RENDER TESTS
if(NOT OPENIMAGEIO_IDIFF)
message(STATUS "Disabling sequencer render tests because OIIO idiff does not exist")
if(NOT OPENIMAGEIO_TOOL)
message(STATUS "Disabling sequencer render tests because OIIO oiiotool does not exist")
else()
set(render_tests
transform

View File

@ -196,7 +196,7 @@ def main():
parser = argparse.ArgumentParser()
parser.add_argument('-test_dir', required=True, type=pathlib.Path)
parser.add_argument('-output_dir', required=True, type=pathlib.Path)
parser.add_argument('-idiff', required=True, type=pathlib.Path)
parser.add_argument('-oiiotool', required=True, type=pathlib.Path)
parser.add_argument('-optional_formats', required=True)
args, remaining = parser.parse_known_args(argv)

View File

@ -263,7 +263,7 @@ def main():
parser = argparse.ArgumentParser()
parser.add_argument('-test_dir', required=True, type=pathlib.Path)
parser.add_argument('-output_dir', required=True, type=pathlib.Path)
parser.add_argument('-idiff', required=True, type=pathlib.Path)
parser.add_argument('-oiiotool', required=True, type=pathlib.Path)
parser.add_argument('-optional_formats', required=True)
args, remaining = parser.parse_known_args(argv)

View File

@ -36,7 +36,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -47,11 +47,11 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
from modules import render_report
report = render_report.Report('IO Curve SVG', output_dir, idiff)
report = render_report.Report('IO Curve SVG', output_dir, oiiotool)
report.set_pixelated(True)
test_dir_name = Path(test_dir).name

View File

@ -37,7 +37,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -48,11 +48,11 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
from modules import render_report
report = render_report.Report("Compositor CPU", output_dir, idiff)
report = render_report.Report("Compositor CPU", output_dir, oiiotool)
report.set_pixelated(True)
report.set_reference_dir("compositor_cpu_renders")

View File

@ -42,7 +42,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -53,11 +53,11 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
from modules import render_report
report = render_report.Report("Compositor Realtime", output_dir, idiff)
report = render_report.Report("Compositor Realtime", output_dir, oiiotool)
report.set_reference_dir("compositor_realtime_renders")
ok = report.run(test_dir, blender, get_arguments, batch=args.batch)

View File

@ -104,7 +104,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument("-device", nargs=1)
parser.add_argument("-blacklist", nargs="*")
parser.add_argument('--batch', default=False, action='store_true')
@ -117,7 +117,7 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
device = args.device[0]
@ -132,7 +132,7 @@ def main():
blacklist += BLACKLIST_METAL
from modules import render_report
report = render_report.Report('Cycles', output_dir, idiff, device, blacklist)
report = render_report.Report('Cycles', output_dir, oiiotool, device, blacklist)
report.set_pixelated(True)
report.set_reference_dir("cycles_renders")
if device == 'CPU':

View File

@ -114,7 +114,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -125,7 +125,7 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
gpu_device_type = get_gpu_device_type(blender)
@ -134,8 +134,9 @@ def main():
reference_override_dir = "eevee_next_renders/amd"
from modules import render_report
report = render_report.Report("Eevee Next", output_dir, idiff)
report = render_report.Report("Eevee Next", output_dir, oiiotool)
report.set_pixelated(True)
report.set_engine_name('eevee_next')
report.set_reference_dir("eevee_next_renders")
report.set_reference_override_dir(reference_override_dir)
report.set_compare_engine('cycles', 'CPU')

View File

@ -142,7 +142,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -153,7 +153,7 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
gpu_device_type = get_gpu_device_type(blender)
@ -162,7 +162,7 @@ def main():
reference_override_dir = "eevee_renders/amd"
from modules import render_report
report = render_report.Report("Eevee", output_dir, idiff)
report = render_report.Report("Eevee", output_dir, oiiotool)
report.set_pixelated(True)
report.set_reference_dir("eevee_renders")
report.set_reference_override_dir(reference_override_dir)

View File

@ -19,7 +19,7 @@ class AbstractImBufTest(unittest.TestCase):
cls.reference_load_dir = pathlib.Path(args.test_dir).joinpath("reference_load")
cls.output_dir = pathlib.Path(args.output_dir)
cls.diff_dir = pathlib.Path(args.output_dir).joinpath("diff")
cls.idiff = pathlib.Path(args.idiff)
cls.oiiotool = pathlib.Path(args.oiiotool)
cls.optional_formats = args.optional_formats
os.makedirs(cls.diff_dir, exist_ok=True)
@ -51,11 +51,12 @@ class AbstractImBufTest(unittest.TestCase):
if os.path.exists(ref_filepath):
# Diff images test with threshold.
command = (
str(self.idiff),
"-fail", str(self.fail_threshold),
"-failpercent", str(self.fail_percent),
str(self.oiiotool),
ref_filepath,
out_filepath,
"--fail", str(self.fail_threshold),
"--failpercent", str(self.fail_percent),
"--diff",
)
try:
subprocess.check_output(command)
@ -63,7 +64,7 @@ class AbstractImBufTest(unittest.TestCase):
except subprocess.CalledProcessError as e:
if self.verbose:
print_message(e.output.decode("utf-8", 'ignore'))
failed = e.returncode != 1
failed = e.returncode != 0
else:
if not self.update:
return False
@ -78,13 +79,13 @@ class AbstractImBufTest(unittest.TestCase):
# Generate diff image (set fail thresholds high to reduce output spam).
diff_img = str(self.diff_dir.joinpath(out_name + ".diff.png"))
command = (
str(self.idiff),
"-fail", "1",
"-failpercent", "100",
"-abs", "-scale", "16",
"-o", diff_img,
str(self.oiiotool),
ref_filepath,
out_filepath
out_filepath,
"--sub",
"--abs",
"--mulc", "16",
"-o", diff_img,
)
try:

View File

@ -67,19 +67,21 @@ def test_get_images(output_dir, filepath, reference_dir, reference_override_dir)
diff_dirpath = os.path.join(output_dir, os.path.basename(dirpath), "diff")
os.makedirs(diff_dirpath, exist_ok=True)
diff_img = os.path.join(diff_dirpath, testname + ".diff.png")
diff_color_img = os.path.join(diff_dirpath, testname + ".diff_color.png")
diff_alpha_img = os.path.join(diff_dirpath, testname + ".diff_alpha.png")
return old_img, ref_img, new_img, diff_img
return old_img, ref_img, new_img, diff_color_img, diff_alpha_img
class Report:
__slots__ = (
'title',
'engine_name',
'output_dir',
'global_dir',
'reference_dir',
'reference_override_dir',
'idiff',
'oiiotool',
'pixelated',
'fail_threshold',
'fail_percent',
@ -93,16 +95,17 @@ class Report:
'blacklist',
)
def __init__(self, title, output_dir, idiff, device=None, blacklist=[]):
def __init__(self, title, output_dir, oiiotool, device=None, blacklist=[]):
self.title = title
self.output_dir = output_dir
self.global_dir = os.path.dirname(output_dir)
self.reference_dir = 'reference_renders'
self.reference_override_dir = None
self.idiff = idiff
self.oiiotool = oiiotool
self.compare_engine = None
self.fail_threshold = 0.016
self.fail_percent = 1
self.engine_name = self.title.lower().replace(" ", "_")
self.device = device
self.blacklist = blacklist
@ -141,6 +144,9 @@ class Report:
def set_compare_engine(self, other_engine, other_device=None):
self.compare_engine = (other_engine, other_device)
def set_engine_name(self, engine_name):
self.engine_name = engine_name
def run(self, dirpath, blender, arguments_cb, batch=False):
# Run tests and output report.
dirname = os.path.basename(dirpath)
@ -231,7 +237,7 @@ class Report:
if failed:
message = """<div class="alert alert-danger" role="alert">"""
message += """<p>Run this command to regenerate reference (ground truth) images:</p>"""
message += """<p><tt>BLENDER_TEST_UPDATE=1 ctest -R %s</tt></p>""" % self.title.lower()
message += """<p><tt>BLENDER_TEST_UPDATE=1 ctest -R %s</tt></p>""" % self.engine_name
message += """<p>This then happens for new and failing tests; reference images of """ \
"""passing test cases will not be updated. Be sure to commit the new reference """ \
"""images to the SVN repository afterwards.</p>"""
@ -246,13 +252,22 @@ class Report:
columns_html = "<tr><th>Name</th><th>%s</th><th>%s</th>" % (engine_self, engine_other)
else:
title = self.title + " Test Report"
columns_html = "<tr><th>Name</th><th>New</th><th>Reference</th><th>Diff</th>"
columns_html = "<tr><th>Name</th><th>New</th><th>Reference</th><th>Diff Color</th><th>Diff Alpha</th>"
html = """
html = f"""
<html>
<head>
<title>{title}</title>
<style>
div.page_container {{
text-align: center;
}}
div.page_container div {{
text-align: left;
}}
div.page_content {{
display: inline-block;
}}
img {{ image-rendering: {image_rendering}; width: 256px; background-color: #000; }}
img.render {{
background-color: #fff;
@ -279,7 +294,7 @@ class Report:
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="page_container"><div class="page_content">
<br/>
<h1>{title}</h1>
{menu}
@ -291,15 +306,10 @@ class Report:
{tests_html}
</table>
<br/>
</div>
</div></div>
</body>
</html>
""" . format(title=title,
menu=menu,
message=message,
image_rendering=image_rendering,
tests_html=tests_html,
columns_html=columns_html)
"""
filename = "report.html" if not comparison else "compare.html"
filepath = os.path.join(self.output_dir, filename)
@ -320,7 +330,7 @@ class Report:
name = test_get_name(filepath)
name = name.replace('_', ' ')
old_img, ref_img, new_img, diff_img = test_get_images(
old_img, ref_img, new_img, diff_color_img, diff_alpha_img = test_get_images(
self.output_dir, filepath, self.reference_dir, self.reference_override_dir)
status = error if error else ""
@ -328,21 +338,17 @@ class Report:
new_url = self._relative_url(new_img)
ref_url = self._relative_url(ref_img)
diff_url = self._relative_url(diff_img)
diff_color_url = self._relative_url(diff_color_img)
diff_alpha_url = self._relative_url(diff_alpha_img)
test_html = """
test_html = f"""
<tr{tr_style}>
<td><b>{name}</b><br/>{testname}<br/>{status}</td>
<td><img src="{new_url}" onmouseover="this.src='{ref_url}';" onmouseout="this.src='{new_url}';" class="render"></td>
<td><img src="{ref_url}" onmouseover="this.src='{new_url}';" onmouseout="this.src='{ref_url}';" class="render"></td>
<td><img src="{diff_url}"></td>
</tr>""" . format(tr_style=tr_style,
name=name,
testname=testname,
status=status,
new_url=new_url,
ref_url=ref_url,
diff_url=diff_url)
<td><img src="{diff_color_url}"></td>
<td><img src="{diff_alpha_url}"></td>
</tr>"""
if error:
self.failed_tests += test_html
@ -368,7 +374,7 @@ class Report:
self.compare_tests += test_html
def _diff_output(self, filepath, tmp_filepath):
old_img, ref_img, new_img, diff_img = test_get_images(
old_img, ref_img, new_img, diff_color_img, diff_alpha_img = test_get_images(
self.output_dir, filepath, self.reference_dir, self.reference_override_dir)
# Create reference render directory.
@ -384,11 +390,12 @@ class Report:
if os.path.exists(ref_img):
# Diff images test with threshold.
command = (
self.idiff,
"-fail", str(self.fail_threshold),
"-failpercent", str(self.fail_percent),
self.oiiotool,
ref_img,
tmp_filepath,
"--fail", str(self.fail_threshold),
"--failpercent", str(self.fail_percent),
"--diff",
)
try:
subprocess.check_output(command)
@ -396,7 +403,7 @@ class Report:
except subprocess.CalledProcessError as e:
if self.verbose:
print_message(e.output.decode("utf-8", 'ignore'))
failed = e.returncode != 1
failed = e.returncode != 0
else:
if not self.update:
return False
@ -409,15 +416,36 @@ class Report:
shutil.copy(new_img, old_img)
failed = False
# Generate diff image.
# Generate color diff image.
command = (
self.idiff,
"-o", diff_img,
"-abs", "-scale", "16",
self.oiiotool,
ref_img,
tmp_filepath
"--ch", "R,G,B",
tmp_filepath,
"--ch", "R,G,B",
"--sub",
"--abs",
"--mulc", "16",
"-o", diff_color_img,
)
try:
subprocess.check_output(command)
except subprocess.CalledProcessError as e:
if self.verbose:
print_message(e.output.decode("utf-8", 'ignore'))
# Generate alpha diff image.
command = (
self.oiiotool,
ref_img,
"--ch", "A",
tmp_filepath,
"--ch", "A",
"--sub",
"--abs",
"--mulc", "16",
"-o", diff_alpha_img,
)
try:
subprocess.check_output(command)
except subprocess.CalledProcessError as e:

View File

@ -54,7 +54,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
return parser
@ -64,11 +64,11 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
from modules import render_report
report = render_report.Report("OpenGL Draw", output_dir, idiff)
report = render_report.Report("OpenGL Draw", output_dir, oiiotool)
ok = report.run(test_dir, blender, get_arguments)
sys.exit(not ok)

View File

@ -33,7 +33,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -44,11 +44,11 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
from modules import render_report
report = render_report.Report("Sequencer", output_dir, idiff)
report = render_report.Report("Sequencer", output_dir, oiiotool)
report.set_pixelated(True)
report.set_reference_dir("reference")

View File

@ -56,7 +56,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument("-export_method", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -68,18 +68,18 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
export_method = args.export_method[0]
from modules import render_report
if export_method == 'HYDRA':
report = render_report.Report("Storm Hydra", output_dir, idiff)
report = render_report.Report("Storm Hydra", output_dir, oiiotool)
report.set_reference_dir("storm_hydra_renders")
report.set_compare_engine('cycles', 'CPU')
else:
report = render_report.Report("Storm USD", output_dir, idiff)
report = render_report.Report("Storm USD", output_dir, oiiotool)
report.set_reference_dir("storm_usd_renders")
report.set_compare_engine('storm_hydra')

View File

@ -56,7 +56,7 @@ def create_argparse():
parser.add_argument("-blender", nargs="+")
parser.add_argument("-testdir", nargs=1)
parser.add_argument("-outdir", nargs=1)
parser.add_argument("-idiff", nargs=1)
parser.add_argument("-oiiotool", nargs=1)
parser.add_argument('--batch', default=False, action='store_true')
return parser
@ -67,11 +67,11 @@ def main():
blender = args.blender[0]
test_dir = args.testdir[0]
idiff = args.idiff[0]
oiiotool = args.oiiotool[0]
output_dir = args.outdir[0]
from modules import render_report
report = render_report.Report("Workbench", output_dir, idiff)
report = render_report.Report("Workbench", output_dir, oiiotool)
report.set_pixelated(True)
report.set_reference_dir("workbench_renders")
report.set_compare_engine('eevee')