Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--release/scripts/startup/nodeitems_builtins.py1
-rw-r--r--source/blender/blenkernel/BKE_node.h1
-rw-r--r--source/blender/blenkernel/intern/node.cc1
-rw-r--r--source/blender/editors/space_node/drawnode.cc34
-rw-r--r--source/blender/editors/space_node/node_add.cc14
-rw-r--r--source/blender/makesdna/DNA_node_types.h5
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c51
-rw-r--r--source/blender/nodes/CMakeLists.txt1
-rw-r--r--source/blender/nodes/NOD_geometry.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_image_texture.cc428
11 files changed, 530 insertions, 8 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 0a34f541e5c..13adde5c439 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -735,6 +735,7 @@ geometry_node_categories = [
NodeItem("ShaderNodeTexVoronoi"),
NodeItem("ShaderNodeTexWave"),
NodeItem("ShaderNodeTexWhiteNoise"),
+ NodeItem("GeometryNodeImageTexture"),
]),
GeometryNodeCategory("GEO_UTILITIES", "Utilities", items=[
NodeItem("ShaderNodeMapRange"),
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index ef1e7249658..c868eb414f7 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -1547,6 +1547,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_RAYCAST 1128
#define GEO_NODE_CURVE_TO_POINTS 1130
#define GEO_NODE_INSTANCES_TO_POINTS 1131
+#define GEO_NODE_IMAGE_TEXTURE 1132
/** \} */
diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc
index eda57d9e984..0452481849b 100644
--- a/source/blender/blenkernel/intern/node.cc
+++ b/source/blender/blenkernel/intern/node.cc
@@ -5800,6 +5800,7 @@ static void registerGeometryNodes()
register_node_type_geo_delete_geometry();
register_node_type_geo_distribute_points_on_faces();
register_node_type_geo_edge_split();
+ register_node_type_geo_image_texture();
register_node_type_geo_input_curve_handles();
register_node_type_geo_input_curve_tilt();
register_node_type_geo_input_index();
diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc
index 8a63a1f3505..dfbb2132f1b 100644
--- a/source/blender/editors/space_node/drawnode.cc
+++ b/source/blender/editors/space_node/drawnode.cc
@@ -3619,7 +3619,39 @@ static void std_node_socket_draw(
break;
}
case SOCK_IMAGE: {
- uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0);
+ const bNodeTree *node_tree = (const bNodeTree *)node_ptr->owner_id;
+ if (node_tree->type == NTREE_GEOMETRY) {
+ if (text[0] == '\0') {
+ uiTemplateID(layout,
+ C,
+ ptr,
+ "default_value",
+ "image.new",
+ "image.open",
+ nullptr,
+ 0,
+ ICON_NONE,
+ nullptr);
+ }
+ else {
+ /* 0.3 split ratio is inconsistent, but use it here because the "New" button is large. */
+ uiLayout *row = uiLayoutSplit(layout, 0.3f, false);
+ uiItemL(row, text, 0);
+ uiTemplateID(row,
+ C,
+ ptr,
+ "default_value",
+ "image.new",
+ "image.open",
+ nullptr,
+ 0,
+ ICON_NONE,
+ nullptr);
+ }
+ }
+ else {
+ uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0);
+ }
break;
}
case SOCK_COLLECTION: {
diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc
index 7b6ca5e6e61..cb66d0dbd2b 100644
--- a/source/blender/editors/space_node/node_add.cc
+++ b/source/blender/editors/space_node/node_add.cc
@@ -758,7 +758,7 @@ static bool node_add_file_poll(bContext *C)
{
const SpaceNode *snode = CTX_wm_space_node(C);
return ED_operator_node_editable(C) &&
- ELEM(snode->nodetree->type, NTREE_SHADER, NTREE_TEXTURE, NTREE_COMPOSIT);
+ ELEM(snode->nodetree->type, NTREE_SHADER, NTREE_TEXTURE, NTREE_COMPOSIT, NTREE_GEOMETRY);
}
static int node_add_file_exec(bContext *C, wmOperator *op)
@@ -784,6 +784,9 @@ static int node_add_file_exec(bContext *C, wmOperator *op)
case NTREE_COMPOSIT:
type = CMP_NODE_IMAGE;
break;
+ case NTREE_GEOMETRY:
+ type = GEO_NODE_IMAGE_TEXTURE;
+ break;
default:
return OPERATOR_CANCELLED;
}
@@ -797,7 +800,14 @@ static int node_add_file_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
- node->id = (ID *)ima;
+ if (type == GEO_NODE_IMAGE_TEXTURE) {
+ bNodeSocket *image_socket = (bNodeSocket *)node->inputs.first;
+ bNodeSocketValueImage *socket_value = (bNodeSocketValueImage *)image_socket->default_value;
+ socket_value->value = ima;
+ }
+ else {
+ node->id = (ID *)ima;
+ }
/* When adding new image file via drag-drop we need to load imbuf in order
* to get proper image source.
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index b585cbd6306..c2eb67b7dd8 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -1581,6 +1581,11 @@ typedef struct NodeGeometrySeparateGeometry {
int8_t domain;
} NodeGeometrySeparateGeometry;
+typedef struct NodeGeometryImageTexture {
+ int interpolation;
+ int extension;
+} NodeGeometryImageTexture;
+
/* script node mode */
#define NODE_SCRIPT_INTERNAL 0
#define NODE_SCRIPT_EXTERNAL 1
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index f0508ee5317..8daf311fbfe 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -5022,10 +5022,8 @@ static void def_fn_input_bool(StructRNA *srna)
prop = RNA_def_property(srna, "boolean", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "boolean", 1);
- RNA_def_property_ui_text(
- prop, "Boolean", "Input value used for unconnected socket");
+ RNA_def_property_ui_text(prop, "Boolean", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
-
}
static void def_fn_input_int(StructRNA *srna)
@@ -5037,8 +5035,7 @@ static void def_fn_input_int(StructRNA *srna)
prop = RNA_def_property(srna, "integer", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, NULL, "integer");
RNA_def_property_int_default(prop, 1);
- RNA_def_property_ui_text(
- prop, "Integer", "Input value used for unconnected socket");
+ RNA_def_property_ui_text(prop, "Integer", "Input value used for unconnected socket");
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
}
@@ -5416,6 +5413,50 @@ static void def_sh_tex_image(StructRNA *srna)
RNA_def_property_update(prop, 0, "rna_Node_update");
}
+static void def_geo_image_texture(StructRNA *srna)
+{
+ static const EnumPropertyItem fn_tex_prop_interpolation_items[] = {
+ {SHD_INTERP_LINEAR, "Linear", 0, "Linear", "Linear interpolation"},
+ {SHD_INTERP_CLOSEST, "Closest", 0, "Closest", "No interpolation (sample closest texel)"},
+ {SHD_INTERP_CUBIC, "Cubic", 0, "Cubic", "Cubic interpolation"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ static const EnumPropertyItem prop_image_extension[] = {
+ {SHD_IMAGE_EXTENSION_REPEAT,
+ "REPEAT",
+ 0,
+ "Repeat",
+ "Cause the image to repeat horizontally and vertically"},
+ {SHD_IMAGE_EXTENSION_EXTEND,
+ "EXTEND",
+ 0,
+ "Extend",
+ "Extend by repeating edge pixels of the image"},
+ {SHD_IMAGE_EXTENSION_CLIP,
+ "CLIP",
+ 0,
+ "Clip",
+ "Clip to image size and set exterior pixels as transparent"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ PropertyRNA *prop;
+
+ RNA_def_struct_sdna_from(srna, "NodeGeometryImageTexture", "storage");
+
+ prop = RNA_def_property(srna, "interpolation", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, fn_tex_prop_interpolation_items);
+ RNA_def_property_ui_text(prop, "Interpolation", "Method for smoothing values between pixels");
+ RNA_def_property_update(prop, 0, "rna_Node_update");
+
+ prop = RNA_def_property(srna, "extension", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, prop_image_extension);
+ RNA_def_property_ui_text(
+ prop, "Extension", "How the image is extrapolated past its original bounds");
+ RNA_def_property_update(prop, 0, "rna_Node_update");
+}
+
static void def_sh_tex_gradient(StructRNA *srna)
{
static const EnumPropertyItem prop_gradient_type[] = {
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 7a728b9041b..657d0d11441 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -226,6 +226,7 @@ set(SRC
geometry/nodes/node_geo_delete_geometry.cc
geometry/nodes/node_geo_distribute_points_on_faces.cc
geometry/nodes/node_geo_edge_split.cc
+ geometry/nodes/node_geo_image_texture.cc
geometry/nodes/node_geo_input_curve_handles.cc
geometry/nodes/node_geo_input_curve_tilt.cc
geometry/nodes/node_geo_input_index.cc
diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h
index a37dcf28757..6702443e20e 100644
--- a/source/blender/nodes/NOD_geometry.h
+++ b/source/blender/nodes/NOD_geometry.h
@@ -94,6 +94,7 @@ void register_node_type_geo_curve_trim(void);
void register_node_type_geo_delete_geometry(void);
void register_node_type_geo_distribute_points_on_faces(void);
void register_node_type_geo_edge_split(void);
+void register_node_type_geo_image_texture(void);
void register_node_type_geo_input_curve_handles(void);
void register_node_type_geo_input_curve_tilt(void);
void register_node_type_geo_input_index(void);
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index af14538e6cc..29cbe0d13c8 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -355,6 +355,7 @@ DefNode(GeometryNode, GEO_NODE_DELETE_GEOMETRY, def_geo_delete_geometry, "DELETE
DefNode(GeometryNode, GEO_NODE_DISTRIBUTE_POINTS_ON_FACES, def_geo_distribute_points_on_faces, "DISTRIBUTE_POINTS_ON_FACES", DistributePointsOnFaces, "Distribute Points on Faces", "")
DefNode(GeometryNode, GEO_NODE_SPLIT_EDGES, 0, "SPLIT_EDGES", SplitEdges, "Split Edges", "")
DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_HANDLES, 0, "INPUT_CURVE_HANDLES", InputCurveHandlePositions, "Curve Handle Positions", "")
+DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "")
DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_TILT, 0, "INPUT_CURVE_TILT", InputCurveTilt, "Curve Tilt", "")
DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "")
DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "")
diff --git a/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc b/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc
new file mode 100644
index 00000000000..02f99b6cf94
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_image_texture.cc
@@ -0,0 +1,428 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2005 Blender Foundation.
+ * All rights reserved.
+ */
+
+#include "node_geometry_util.hh"
+
+#include "BKE_image.h"
+
+#include "BLI_float4.hh"
+#include "BLI_threads.h"
+#include "BLI_timeit.hh"
+
+#include "IMB_colormanagement.h"
+#include "IMB_imbuf.h"
+#include "IMB_imbuf_types.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+namespace blender::nodes {
+
+static void geo_node_image_texture_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Image>("Image").hide_label();
+ b.add_input<decl::Vector>("Vector").implicit_field().description(
+ "Texture coordinates from 0 to 1");
+ b.add_input<decl::Int>("Frame").min(0).max(MAXFRAMEF);
+ b.add_output<decl::Color>("Color").no_muted_links().dependent_field();
+ b.add_output<decl::Float>("Alpha").no_muted_links().dependent_field();
+}
+
+static void geo_node_image_texture_layout(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiItemR(layout, ptr, "interpolation", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
+ uiItemR(layout, ptr, "extension", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
+}
+
+static void geo_node_image_texture_init(bNodeTree *UNUSED(ntree), bNode *node)
+{
+ NodeGeometryImageTexture *tex = (NodeGeometryImageTexture *)MEM_callocN(
+ sizeof(NodeGeometryImageTexture), __func__);
+ node->storage = tex;
+}
+
+class ImageFieldsFunction : public fn::MultiFunction {
+ private:
+ const int interpolation_;
+ const int extension_;
+ Image &image_;
+ ImageUser image_user_;
+ void *image_lock_;
+ ImBuf *image_buffer_;
+
+ public:
+ ImageFieldsFunction(const int interpolation,
+ const int extension,
+ Image &image,
+ ImageUser image_user)
+ : interpolation_(interpolation),
+ extension_(extension),
+ image_(image),
+ image_user_(image_user)
+ {
+ static fn::MFSignature signature = create_signature();
+ this->set_signature(&signature);
+
+ image_buffer_ = BKE_image_acquire_ibuf(&image_, &image_user_, &image_lock_);
+ if (image_buffer_ == nullptr) {
+ throw std::runtime_error("cannot aquire image buffer");
+ }
+
+ if (image_buffer_->rect_float == nullptr) {
+ BLI_thread_lock(LOCK_IMAGE);
+ if (!image_buffer_->rect_float) {
+ IMB_float_from_rect(image_buffer_);
+ }
+ BLI_thread_unlock(LOCK_IMAGE);
+ }
+
+ if (image_buffer_->rect_float == nullptr) {
+ BKE_image_release_ibuf(&image_, image_buffer_, image_lock_);
+ throw std::runtime_error("cannot get float buffer");
+ }
+ }
+
+ ~ImageFieldsFunction() override
+ {
+ BKE_image_release_ibuf(&image_, image_buffer_, image_lock_);
+ }
+
+ static fn::MFSignature create_signature()
+ {
+ fn::MFSignatureBuilder signature{"ImageFunction"};
+ signature.single_input<float3>("Vector");
+ signature.single_output<ColorGeometry4f>("Color");
+ signature.single_output<float>("Alpha");
+ return signature.build();
+ }
+
+ static int wrap_periodic(int x, const int width)
+ {
+ x %= width;
+ if (x < 0) {
+ x += width;
+ }
+ return x;
+ }
+
+ static int wrap_clamp(const int x, const int width)
+ {
+ return std::clamp(x, 0, width - 1);
+ }
+
+ static float4 image_pixel_lookup(const ImBuf *ibuf, const int px, const int py)
+ {
+ if (px < 0 || py < 0 || px >= ibuf->x || py >= ibuf->y) {
+ return float4(0.0f, 0.0f, 0.0f, 0.0f);
+ }
+ return ((const float4 *)ibuf->rect_float)[px + py * ibuf->x];
+ }
+
+ static float frac(const float x, int *ix)
+ {
+ const int i = (int)x - ((x < 0.0f) ? 1 : 0);
+ *ix = i;
+ return x - (float)i;
+ }
+
+ static float4 image_cubic_texture_lookup(const ImBuf *ibuf,
+ const float px,
+ const float py,
+ const int extension)
+ {
+ const int width = ibuf->x;
+ const int height = ibuf->y;
+ int pix, piy, nix, niy;
+ const float tx = frac(px * (float)width - 0.5f, &pix);
+ const float ty = frac(py * (float)height - 0.5f, &piy);
+ int ppix, ppiy, nnix, nniy;
+
+ switch (extension) {
+ case SHD_IMAGE_EXTENSION_REPEAT: {
+ pix = wrap_periodic(pix, width);
+ piy = wrap_periodic(piy, height);
+ ppix = wrap_periodic(pix - 1, width);
+ ppiy = wrap_periodic(piy - 1, height);
+ nix = wrap_periodic(pix + 1, width);
+ niy = wrap_periodic(piy + 1, height);
+ nnix = wrap_periodic(pix + 2, width);
+ nniy = wrap_periodic(piy + 2, height);
+ break;
+ }
+ case SHD_IMAGE_EXTENSION_CLIP: {
+ ppix = pix - 1;
+ ppiy = piy - 1;
+ nix = pix + 1;
+ niy = piy + 1;
+ nnix = pix + 2;
+ nniy = piy + 2;
+ break;
+ }
+ case SHD_IMAGE_EXTENSION_EXTEND: {
+ ppix = wrap_clamp(pix - 1, width);
+ ppiy = wrap_clamp(piy - 1, height);
+ nix = wrap_clamp(pix + 1, width);
+ niy = wrap_clamp(piy + 1, height);
+ nnix = wrap_clamp(pix + 2, width);
+ nniy = wrap_clamp(piy + 2, height);
+ pix = wrap_clamp(pix, width);
+ piy = wrap_clamp(piy, height);
+ break;
+ }
+ default:
+ return float4(0.0f, 0.0f, 0.0f, 0.0f);
+ }
+
+ const int xc[4] = {ppix, pix, nix, nnix};
+ const int yc[4] = {ppiy, piy, niy, nniy};
+ float u[4], v[4];
+
+ u[0] = (((-1.0f / 6.0f) * tx + 0.5f) * tx - 0.5f) * tx + (1.0f / 6.0f);
+ u[1] = ((0.5f * tx - 1.0f) * tx) * tx + (2.0f / 3.0f);
+ u[2] = ((-0.5f * tx + 0.5f) * tx + 0.5f) * tx + (1.0f / 6.0f);
+ u[3] = (1.0f / 6.0f) * tx * tx * tx;
+
+ v[0] = (((-1.0f / 6.0f) * ty + 0.5f) * ty - 0.5f) * ty + (1.0f / 6.0f);
+ v[1] = ((0.5f * ty - 1.0f) * ty) * ty + (2.0f / 3.0f);
+ v[2] = ((-0.5f * ty + 0.5f) * ty + 0.5f) * ty + (1.0f / 6.0f);
+ v[3] = (1.0f / 6.0f) * ty * ty * ty;
+
+ return (v[0] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[0])) +
+ u[1] * (image_pixel_lookup(ibuf, xc[1], yc[0])) +
+ u[2] * (image_pixel_lookup(ibuf, xc[2], yc[0])) +
+ u[3] * (image_pixel_lookup(ibuf, xc[3], yc[0])))) +
+ (v[1] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[1])) +
+ u[1] * (image_pixel_lookup(ibuf, xc[1], yc[1])) +
+ u[2] * (image_pixel_lookup(ibuf, xc[2], yc[1])) +
+ u[3] * (image_pixel_lookup(ibuf, xc[3], yc[1])))) +
+ (v[2] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[2])) +
+ u[1] * (image_pixel_lookup(ibuf, xc[1], yc[2])) +
+ u[2] * (image_pixel_lookup(ibuf, xc[2], yc[2])) +
+ u[3] * (image_pixel_lookup(ibuf, xc[3], yc[2])))) +
+ (v[3] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[3])) +
+ u[1] * (image_pixel_lookup(ibuf, xc[1], yc[3])) +
+ u[2] * (image_pixel_lookup(ibuf, xc[2], yc[3])) +
+ u[3] * (image_pixel_lookup(ibuf, xc[3], yc[3]))));
+ }
+
+ static float4 image_linear_texture_lookup(const ImBuf *ibuf,
+ const float px,
+ const float py,
+ const int extension)
+ {
+ const int width = ibuf->x;
+ const int height = ibuf->y;
+ int pix, piy, nix, niy;
+ const float nfx = frac(px * (float)width - 0.5f, &pix);
+ const float nfy = frac(py * (float)height - 0.5f, &piy);
+
+ switch (extension) {
+ case SHD_IMAGE_EXTENSION_CLIP: {
+ nix = pix + 1;
+ niy = piy + 1;
+ break;
+ }
+ case SHD_IMAGE_EXTENSION_EXTEND: {
+ nix = wrap_clamp(pix + 1, width);
+ niy = wrap_clamp(piy + 1, height);
+ pix = wrap_clamp(pix, width);
+ piy = wrap_clamp(piy, height);
+ break;
+ }
+ default:
+ case SHD_IMAGE_EXTENSION_REPEAT:
+ pix = wrap_periodic(pix, width);
+ piy = wrap_periodic(piy, height);
+ nix = wrap_periodic(pix + 1, width);
+ niy = wrap_periodic(piy + 1, height);
+ break;
+ }
+
+ const float ptx = 1.0f - nfx;
+ const float pty = 1.0f - nfy;
+
+ return image_pixel_lookup(ibuf, pix, piy) * ptx * pty +
+ image_pixel_lookup(ibuf, nix, piy) * nfx * pty +
+ image_pixel_lookup(ibuf, pix, niy) * ptx * nfy +
+ image_pixel_lookup(ibuf, nix, niy) * nfx * nfy;
+ }
+
+ static float4 image_closest_texture_lookup(const ImBuf *ibuf,
+ const float px,
+ const float py,
+ const int extension)
+ {
+ const int width = ibuf->x;
+ const int height = ibuf->y;
+ int ix, iy;
+ const float tx = frac(px * (float)width - 0.5f, &ix);
+ const float ty = frac(py * (float)height - 0.5f, &iy);
+
+ switch (extension) {
+ case SHD_IMAGE_EXTENSION_REPEAT: {
+ ix = wrap_periodic(ix, width);
+ iy = wrap_periodic(iy, height);
+ return image_pixel_lookup(ibuf, ix, iy);
+ }
+ case SHD_IMAGE_EXTENSION_CLIP: {
+ if (tx < 0.0f || ty < 0.0f || tx > 1.0f || ty > 1.0f) {
+ return float4(0.0f, 0.0f, 0.0f, 0.0f);
+ }
+ if (ix < 0 || iy < 0 || ix > width || iy > height) {
+ return float4(0.0f, 0.0f, 0.0f, 0.0f);
+ }
+ ATTR_FALLTHROUGH;
+ }
+ case SHD_IMAGE_EXTENSION_EXTEND: {
+ ix = wrap_clamp(ix, width);
+ iy = wrap_clamp(iy, height);
+ return image_pixel_lookup(ibuf, ix, iy);
+ }
+ default:
+ return float4(0.0f, 0.0f, 0.0f, 0.0f);
+ }
+ }
+
+ void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override
+ {
+ const VArray<float3> &vectors = params.readonly_single_input<float3>(0, "Vector");
+ MutableSpan<ColorGeometry4f> r_color = params.uninitialized_single_output<ColorGeometry4f>(
+ 1, "Color");
+ MutableSpan<float> r_alpha = params.uninitialized_single_output_if_required<float>(2, "Alpha");
+
+ MutableSpan<float4> color_data{(float4 *)r_color.data(), r_color.size()};
+
+ /* Sample image texture. */
+ switch (interpolation_) {
+ case SHD_INTERP_LINEAR:
+ for (const int64_t i : mask) {
+ const float3 p = vectors[i];
+ color_data[i] = image_linear_texture_lookup(image_buffer_, p.x, p.y, extension_);
+ }
+ break;
+ case SHD_INTERP_CLOSEST:
+ for (const int64_t i : mask) {
+ const float3 p = vectors[i];
+ color_data[i] = image_closest_texture_lookup(image_buffer_, p.x, p.y, extension_);
+ }
+ break;
+ case SHD_INTERP_CUBIC:
+ case SHD_INTERP_SMART:
+ for (const int64_t i : mask) {
+ const float3 p = vectors[i];
+ color_data[i] = image_cubic_texture_lookup(image_buffer_, p.x, p.y, extension_);
+ }
+ break;
+ }
+
+ int alpha_mode = image_.alpha_mode;
+ if (IMB_colormanagement_space_name_is_data(image_.colorspace_settings.name)) {
+ alpha_mode = IMA_ALPHA_CHANNEL_PACKED;
+ }
+
+ switch (alpha_mode) {
+ case IMA_ALPHA_STRAIGHT: {
+ /* #ColorGeometry expects premultiplied alpha, so convert from straight to that. */
+ for (int64_t i : mask) {
+ straight_to_premul_v4(color_data[i]);
+ }
+ break;
+ }
+ case IMA_ALPHA_PREMUL: {
+ /* Alpha is premultiplied already, nothing to do. */
+ break;
+ }
+ case IMA_ALPHA_CHANNEL_PACKED: {
+ /* Color and alpha channels shouldn't interact with each other, nothing to do. */
+ break;
+ }
+ case IMA_ALPHA_IGNORE: {
+ /* The image should be treated as being opaque. */
+ for (int64_t i : mask) {
+ color_data[i].w = 1.0f;
+ }
+ break;
+ }
+ }
+
+ if (!r_alpha.is_empty()) {
+ for (int64_t i : mask) {
+ r_alpha[i] = r_color[i].a;
+ }
+ }
+ }
+};
+
+static void geo_node_image_texture_exec(GeoNodeExecParams params)
+{
+ auto return_default = [&]() {
+ params.set_output("Color", ColorGeometry4f(0.0f, 0.0f, 0.0f, 1.0f));
+ params.set_output("Alpha", 1.0f);
+ };
+
+ Image *image = params.get_input<Image *>("Image");
+ if (image == nullptr) {
+ return return_default();
+ }
+
+ const bNode &node = params.node();
+ NodeGeometryImageTexture *data = (NodeGeometryImageTexture *)node.storage;
+
+ ImageUser image_user;
+ BKE_imageuser_default(&image_user);
+ image_user.cycl = false;
+ image_user.frames = INT_MAX;
+ image_user.sfra = 1;
+ image_user.framenr = BKE_image_is_animated(image) ? params.get_input<int>("Frame") : 0;
+
+ std::unique_ptr<ImageFieldsFunction> image_fn;
+ try {
+ image_fn = std::make_unique<ImageFieldsFunction>(
+ data->interpolation, data->extension, *image, image_user);
+ }
+ catch (const std::runtime_error &) {
+ return return_default();
+ }
+
+ Field<float3> vector_field = params.extract_input<Field<float3>>("Vector");
+
+ auto image_op = std::make_shared<FieldOperation>(
+ FieldOperation(std::move(image_fn), {std::move(vector_field)}));
+
+ params.set_output("Color", Field<ColorGeometry4f>(image_op, 0));
+ params.set_output("Alpha", Field<float>(image_op, 1));
+}
+
+} // namespace blender::nodes
+
+void register_node_type_geo_image_texture(void)
+{
+ static bNodeType ntype;
+
+ geo_node_type_base(&ntype, GEO_NODE_IMAGE_TEXTURE, "Image Texture", NODE_CLASS_TEXTURE, 0);
+ ntype.declare = blender::nodes::geo_node_image_texture_declare;
+ ntype.draw_buttons = blender::nodes::geo_node_image_texture_layout;
+ node_type_init(&ntype, blender::nodes::geo_node_image_texture_init);
+ node_type_storage(
+ &ntype, "NodeGeometryImageTexture", node_free_standard_storage, node_copy_standard_storage);
+ node_type_size_preset(&ntype, NODE_SIZE_LARGE);
+ ntype.geometry_node_execute = blender::nodes::geo_node_image_texture_exec;
+
+ nodeRegisterType(&ntype);
+}