Compositor: Add Image Coordinates node

This patch adds a new Image Coordinates node for the compositor. The
Texture Coordinates and Pixel Coordinates outputs were removed from the
Image Info node and were added to the new node instead. Additionally, a
new Normalized Coordinates output was added.

The Pixel Coordinates output now no longer includes half pixel offsets.

Pull Request: https://projects.blender.org/blender/blender/pulls/138935
This commit is contained in:
Omar Emara
2025-05-26 08:25:06 +02:00
committed by Omar Emara
parent 8485d8a33c
commit a4502f82c1
25 changed files with 428 additions and 406 deletions

View File

@@ -27,6 +27,7 @@ class NODE_MT_category_compositor_input(Menu):
node_add_menu.add_node_type(layout, "CompositorNodeMovieClip")
node_add_menu.add_node_type(layout, "CompositorNodeTexture")
node_add_menu.add_node_type(layout, "CompositorNodeImageInfo")
node_add_menu.add_node_type(layout, "CompositorNodeImageCoordinates")
if is_group:
layout.separator()

View File

@@ -27,7 +27,7 @@
/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 77
#define BLENDER_FILE_SUBVERSION 78
/* 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

@@ -3976,6 +3976,80 @@ static void do_version_color_balance_node_options_to_inputs_animation(bNodeTree
});
}
/* The Coordinates outputs were moved into their own Texture Coordinate node. If used, add a
* Texture Coordinates node and use it instead. */
static void do_version_replace_image_info_node_coordinates(bNodeTree *node_tree)
{
LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) {
if (!STREQ(node->idname, "CompositorNodeImageInfo")) {
continue;
}
bNodeLink *input_link = nullptr;
bNodeLink *output_texture_link = nullptr;
bNodeLink *output_pixel_link = nullptr;
LISTBASE_FOREACH (bNodeLink *, link, &node_tree->links) {
if (link->tonode == node) {
input_link = link;
}
if (link->fromnode == node &&
blender::StringRef(link->fromsock->identifier) == "Texture Coordinates")
{
output_texture_link = link;
}
if (link->fromnode == node &&
blender::StringRef(link->fromsock->identifier) == "Pixel Coordinates")
{
output_pixel_link = link;
}
}
if (!output_texture_link && !output_pixel_link) {
continue;
}
bNode *image_coordinates_node = blender::bke::node_add_node(
nullptr, *node_tree, "CompositorNodeImageCoordinates");
image_coordinates_node->parent = node->parent;
image_coordinates_node->location[0] = node->location[0];
image_coordinates_node->location[1] = node->location[1] - node->height - 10.0f;
if (input_link) {
bNodeSocket *image_input = blender::bke::node_find_socket(
*image_coordinates_node, SOCK_IN, "Image");
version_node_add_link(*node_tree,
*input_link->fromnode,
*input_link->fromsock,
*image_coordinates_node,
*image_input);
}
if (output_texture_link) {
bNodeSocket *uniform_output = blender::bke::node_find_socket(
*image_coordinates_node, SOCK_OUT, "Uniform");
version_node_add_link(*node_tree,
*image_coordinates_node,
*uniform_output,
*output_texture_link->tonode,
*output_texture_link->tosock);
blender::bke::node_remove_link(node_tree, *output_texture_link);
}
if (output_pixel_link) {
bNodeSocket *pixel_output = blender::bke::node_find_socket(
*image_coordinates_node, SOCK_OUT, "Pixel");
version_node_add_link(*node_tree,
*image_coordinates_node,
*pixel_output,
*output_pixel_link->tonode,
*output_pixel_link->tosock);
blender::bke::node_remove_link(node_tree, *output_pixel_link);
}
}
}
void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain)
{
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 8)) {
@@ -5737,6 +5811,15 @@ void blo_do_versions_450(FileData * /*fd*/, Library * /*lib*/, Main *bmain)
FOREACH_NODETREE_END;
}
if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 78)) {
FOREACH_NODETREE_BEGIN (bmain, node_tree, id) {
if (node_tree->type == NTREE_COMPOSIT) {
do_version_replace_image_info_node_coordinates(node_tree);
}
}
FOREACH_NODETREE_END;
}
/* Always run this versioning (keep at the bottom of the function). Meshes are written with the
* legacy format which always needs to be converted to the new format on file load. To be moved
* to a subversion check in 5.0. */

View File

@@ -106,14 +106,13 @@ set(SRC
cached_resources/intern/deriche_gaussian_coefficients.cc
cached_resources/intern/distortion_grid.cc
cached_resources/intern/fog_glow_kernel.cc
cached_resources/intern/image_coordinates.cc
cached_resources/intern/keying_screen.cc
cached_resources/intern/morphological_distance_feather_weights.cc
cached_resources/intern/ocio_color_space_conversion_shader.cc
cached_resources/intern/pixel_coordinates.cc
cached_resources/intern/smaa_precomputed_textures.cc
cached_resources/intern/symmetric_blur_weights.cc
cached_resources/intern/symmetric_separable_blur_weights.cc
cached_resources/intern/texture_coordinates.cc
cached_resources/intern/van_vliet_gaussian_coefficients.cc
cached_resources/COM_bokeh_kernel.hh
@@ -125,14 +124,13 @@ set(SRC
cached_resources/COM_deriche_gaussian_coefficients.hh
cached_resources/COM_distortion_grid.hh
cached_resources/COM_fog_glow_kernel.hh
cached_resources/COM_image_coordinates.hh
cached_resources/COM_keying_screen.hh
cached_resources/COM_morphological_distance_feather_weights.hh
cached_resources/COM_ocio_color_space_conversion_shader.hh
cached_resources/COM_pixel_coordinates.hh
cached_resources/COM_smaa_precomputed_textures.hh
cached_resources/COM_symmetric_blur_weights.hh
cached_resources/COM_symmetric_separable_blur_weights.hh
cached_resources/COM_texture_coordinates.hh
cached_resources/COM_van_vliet_gaussian_coefficients.hh
derived_resources/intern/denoised_auxiliary_pass.cc
@@ -205,6 +203,9 @@ set(GLSL_SRC
shaders/compositor_glare_write_highlights_output.glsl
shaders/compositor_horizontal_lens_distortion.glsl
shaders/compositor_id_mask.glsl
shaders/compositor_image_coordinates_normalized.glsl
shaders/compositor_image_coordinates_pixel.glsl
shaders/compositor_image_coordinates_uniform.glsl
shaders/compositor_image_crop.glsl
shaders/compositor_inpaint_compute_boundary.glsl
shaders/compositor_inpaint_compute_region.glsl
@@ -231,7 +232,6 @@ set(GLSL_SRC
shaders/compositor_movie_distortion.glsl
shaders/compositor_normalize.glsl
shaders/compositor_parallel_reduction.glsl
shaders/compositor_pixel_coordinates.glsl
shaders/compositor_pixelate.glsl
shaders/compositor_plane_deform_anisotropic.glsl
shaders/compositor_plane_deform_mask.glsl
@@ -256,7 +256,6 @@ set(GLSL_SRC
shaders/compositor_symmetric_blur_variable_size.glsl
shaders/compositor_symmetric_separable_blur.glsl
shaders/compositor_symmetric_separable_blur_variable_size.glsl
shaders/compositor_texture_coordinates.glsl
shaders/compositor_tone_map_photoreceptor.glsl
shaders/compositor_tone_map_simple.glsl
shaders/compositor_translate_wrapped.glsl

View File

@@ -12,14 +12,13 @@
#include "COM_deriche_gaussian_coefficients.hh"
#include "COM_distortion_grid.hh"
#include "COM_fog_glow_kernel.hh"
#include "COM_image_coordinates.hh"
#include "COM_keying_screen.hh"
#include "COM_morphological_distance_feather_weights.hh"
#include "COM_ocio_color_space_conversion_shader.hh"
#include "COM_pixel_coordinates.hh"
#include "COM_smaa_precomputed_textures.hh"
#include "COM_symmetric_blur_weights.hh"
#include "COM_symmetric_separable_blur_weights.hh"
#include "COM_texture_coordinates.hh"
#include "COM_van_vliet_gaussian_coefficients.hh"
namespace blender::compositor {
@@ -65,8 +64,7 @@ class StaticCacheManager {
DericheGaussianCoefficientsContainer deriche_gaussian_coefficients;
VanVlietGaussianCoefficientsContainer van_vliet_gaussian_coefficients;
FogGlowKernelContainer fog_glow_kernels;
TextureCoordinatesContainer texture_coordinates;
PixelCoordinatesContainer pixel_coordinates;
ImageCoordinatesContainer image_coordinates;
private:
/* The cache manager should skip the next reset. See the skip_next_reset() method for more

View File

@@ -17,55 +17,61 @@ namespace blender::compositor {
class Context;
enum class CoordinatesType : uint8_t {
Uniform,
Normalized,
Pixel,
};
/* ------------------------------------------------------------------------------------------------
* Pixel Coordinates Key.
* Image Coordinates Key.
*/
class PixelCoordinatesKey {
class ImageCoordinatesKey {
public:
int2 size;
CoordinatesType type;
PixelCoordinatesKey(const int2 &size);
ImageCoordinatesKey(const int2 &size, const CoordinatesType type);
uint64_t hash() const;
};
bool operator==(const PixelCoordinatesKey &a, const PixelCoordinatesKey &b);
bool operator==(const ImageCoordinatesKey &a, const ImageCoordinatesKey &b);
/* -------------------------------------------------------------------------------------------------
* Pixel Coordinates.
* Image Coordinates.
*
* A cached resource that computes and caches a result containing the pixel coordinates of an image
* with the given size. The coordinates represent the center of pixels, so they include half pixel
* offsets. */
class PixelCoordinates : public CachedResource {
* A cached resource that computes and caches a result containing the coordinates of the pixels of
* an image with the given size. */
class ImageCoordinates : public CachedResource {
public:
Result result;
PixelCoordinates(Context &context, const int2 &size);
ImageCoordinates(Context &context, const int2 &size, const CoordinatesType type);
~PixelCoordinates();
~ImageCoordinates();
private:
void compute_gpu(Context &context);
void compute_gpu(Context &context, const CoordinatesType type);
void compute_cpu();
void compute_cpu(const CoordinatesType type);
};
/* ------------------------------------------------------------------------------------------------
* Pixel Coordinates Container.
* Image Coordinates Container.
*/
class PixelCoordinatesContainer : CachedResourceContainer {
class ImageCoordinatesContainer : CachedResourceContainer {
private:
Map<PixelCoordinatesKey, std::unique_ptr<PixelCoordinates>> map_;
Map<ImageCoordinatesKey, std::unique_ptr<ImageCoordinates>> map_;
public:
void reset() override;
/* Check if there is an available PixelCoordinates cached resource with the given parameters in
/* Check if there is an available ImageCoordinates cached resource with the given parameters in
* the container, if one exists, return it, otherwise, return a newly created one and add it to
* the container. In both cases, tag the cached resource as needed to keep it cached for the next
* evaluation. */
Result &get(Context &context, const int2 &size);
Result &get(Context &context, const int2 &size, const CoordinatesType type);
};
} // namespace blender::compositor

View File

@@ -1,72 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
#include <memory>
#include "BLI_map.hh"
#include "BLI_math_vector_types.hh"
#include "COM_cached_resource.hh"
#include "COM_result.hh"
namespace blender::compositor {
class Context;
/* ------------------------------------------------------------------------------------------------
* Texture Coordinates Key.
*/
class TextureCoordinatesKey {
public:
int2 size;
TextureCoordinatesKey(const int2 &size);
uint64_t hash() const;
};
bool operator==(const TextureCoordinatesKey &a, const TextureCoordinatesKey &b);
/* -------------------------------------------------------------------------------------------------
* Texture Coordinates.
*
* A cached resource that computes and caches a result containing the texture coordinates of an
* image with the given size. The texture coordinates are the zero centered pixel coordinates
* normalized along the greater dimension. Pixel coordinates represent the center of pixels, so
* they include half pixel offsets. */
class TextureCoordinates : public CachedResource {
public:
Result result;
TextureCoordinates(Context &context, const int2 &size);
~TextureCoordinates();
private:
void compute_gpu(Context &context);
void compute_cpu();
};
/* ------------------------------------------------------------------------------------------------
* Texture Coordinates Container.
*/
class TextureCoordinatesContainer : CachedResourceContainer {
private:
Map<TextureCoordinatesKey, std::unique_ptr<TextureCoordinates>> map_;
public:
void reset() override;
/* Check if there is an available TextureCoordinates cached resource with the given parameters in
* the container, if one exists, return it, otherwise, return a newly created one and add it to
* the container. In both cases, tag the cached resource as needed to keep it cached for the next
* evaluation. */
Result &get(Context &context, const int2 &size);
};
} // namespace blender::compositor

View File

@@ -0,0 +1,149 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include <memory>
#include "BLI_assert.h"
#include "BLI_hash.hh"
#include "BLI_math_vector_types.hh"
#include "GPU_shader.hh"
#include "COM_context.hh"
#include "COM_image_coordinates.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
namespace blender::compositor {
/* --------------------------------------------------------------------
* Image Coordinates Key.
*/
ImageCoordinatesKey::ImageCoordinatesKey(const int2 &size, const CoordinatesType type)
: size(size), type(type)
{
}
uint64_t ImageCoordinatesKey::hash() const
{
return get_default_hash(this->size, this->type);
}
bool operator==(const ImageCoordinatesKey &a, const ImageCoordinatesKey &b)
{
return a.size == b.size && a.type == b.type;
}
/* --------------------------------------------------------------------
* Image Coordinates.
*/
ImageCoordinates::ImageCoordinates(Context &context, const int2 &size, const CoordinatesType type)
: result(context.create_result(ResultType::Float3))
{
this->result.allocate_texture(Domain(size), false);
if (context.use_gpu()) {
this->compute_gpu(context, type);
}
else {
this->compute_cpu(type);
}
}
ImageCoordinates::~ImageCoordinates()
{
this->result.release();
}
static const char *get_shader_name(const CoordinatesType type)
{
switch (type) {
case CoordinatesType::Uniform:
return "compositor_image_coordinates_uniform";
case CoordinatesType::Normalized:
return "compositor_image_coordinates_normalized";
case CoordinatesType::Pixel:
return "compositor_image_coordinates_pixel";
}
BLI_assert_unreachable();
return "";
}
void ImageCoordinates::compute_gpu(Context &context, const CoordinatesType type)
{
GPUShader *shader = context.get_shader(get_shader_name(type));
GPU_shader_bind(shader);
this->result.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, this->result.domain().size);
this->result.unbind_as_image();
GPU_shader_unbind();
}
void ImageCoordinates::compute_cpu(const CoordinatesType type)
{
switch (type) {
case CoordinatesType::Uniform: {
const int2 size = this->result.domain().size;
const int max_size = math::max(size.x, size.y);
parallel_for(size, [&](const int2 texel) {
float2 centered_coordinates = (float2(texel) + 0.5f) - float2(size) / 2.0f;
float2 normalized_coordinates = (centered_coordinates / max_size) * 2.0f;
this->result.store_pixel(texel, float3(normalized_coordinates, 0.0f));
});
break;
}
case CoordinatesType::Normalized: {
const int2 size = this->result.domain().size;
parallel_for(size, [&](const int2 texel) {
float2 normalized_coordinates = (float2(texel) + 0.5f) / float2(size);
this->result.store_pixel(texel, float3(normalized_coordinates, 0.0f));
});
break;
}
case CoordinatesType::Pixel: {
parallel_for(this->result.domain().size, [&](const int2 texel) {
this->result.store_pixel(texel, float3(float2(texel), 0.0f));
});
break;
}
}
}
/* --------------------------------------------------------------------
* Image Coordinates Container.
*/
void ImageCoordinatesContainer::reset()
{
/* First, delete all resources that are no longer needed. */
map_.remove_if([](auto item) { return !item.value->needed; });
/* Second, reset the needed status of the remaining resources to false to ready them to track
* their needed status for the next evaluation. */
for (auto &value : map_.values()) {
value->needed = false;
}
}
Result &ImageCoordinatesContainer::get(Context &context,
const int2 &size,
const CoordinatesType type)
{
const ImageCoordinatesKey key(size, type);
auto &pixel_coordinates = *map_.lookup_or_add_cb(
key, [&]() { return std::make_unique<ImageCoordinates>(context, size, type); });
pixel_coordinates.needed = true;
return pixel_coordinates.result;
}
} // namespace blender::compositor

View File

@@ -1,105 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include <memory>
#include "BLI_hash.hh"
#include "BLI_math_vector_types.hh"
#include "GPU_shader.hh"
#include "COM_context.hh"
#include "COM_pixel_coordinates.hh"
#include "COM_result.hh"
#include "COM_utilities.hh"
namespace blender::compositor {
/* --------------------------------------------------------------------
* Pixel Coordinates Key.
*/
PixelCoordinatesKey::PixelCoordinatesKey(const int2 &size) : size(size) {}
uint64_t PixelCoordinatesKey::hash() const
{
return get_default_hash(size);
}
bool operator==(const PixelCoordinatesKey &a, const PixelCoordinatesKey &b)
{
return a.size == b.size;
}
/* --------------------------------------------------------------------
* Pixel Coordinates.
*/
PixelCoordinates::PixelCoordinates(Context &context, const int2 &size)
: result(context.create_result(ResultType::Float3))
{
this->result.allocate_texture(Domain(size), false);
if (context.use_gpu()) {
this->compute_gpu(context);
}
else {
this->compute_cpu();
}
}
PixelCoordinates::~PixelCoordinates()
{
this->result.release();
}
void PixelCoordinates::compute_gpu(Context &context)
{
GPUShader *shader = context.get_shader("compositor_pixel_coordinates");
GPU_shader_bind(shader);
this->result.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, this->result.domain().size);
this->result.unbind_as_image();
GPU_shader_unbind();
}
void PixelCoordinates::compute_cpu()
{
parallel_for(this->result.domain().size, [&](const int2 texel) {
this->result.store_pixel(texel, float3(float2(texel) + float2(0.5f), 0.0f));
});
}
/* --------------------------------------------------------------------
* Pixel Coordinates Container.
*/
void PixelCoordinatesContainer::reset()
{
/* First, delete all resources that are no longer needed. */
map_.remove_if([](auto item) { return !item.value->needed; });
/* Second, reset the needed status of the remaining resources to false to ready them to track
* their needed status for the next evaluation. */
for (auto &value : map_.values()) {
value->needed = false;
}
}
Result &PixelCoordinatesContainer::get(Context &context, const int2 &size)
{
const PixelCoordinatesKey key(size);
auto &pixel_coordinates = *map_.lookup_or_add_cb(
key, [&]() { return std::make_unique<PixelCoordinates>(context, size); });
pixel_coordinates.needed = true;
return pixel_coordinates.result;
}
} // namespace blender::compositor

View File

@@ -1,112 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <cstdint>
#include <memory>
#include "BLI_hash.hh"
#include "BLI_math_base.hh"
#include "BLI_math_vector_types.hh"
#include "GPU_shader.hh"
#include "COM_context.hh"
#include "COM_result.hh"
#include "COM_texture_coordinates.hh"
#include "COM_utilities.hh"
namespace blender::compositor {
/* --------------------------------------------------------------------
* Texture Coordinates Key.
*/
TextureCoordinatesKey::TextureCoordinatesKey(const int2 &size) : size(size) {}
uint64_t TextureCoordinatesKey::hash() const
{
return get_default_hash(size);
}
bool operator==(const TextureCoordinatesKey &a, const TextureCoordinatesKey &b)
{
return a.size == b.size;
}
/* --------------------------------------------------------------------
* Texture Coordinates.
*/
TextureCoordinates::TextureCoordinates(Context &context, const int2 &size)
: result(context.create_result(ResultType::Float3))
{
this->result.allocate_texture(Domain(size), false);
if (context.use_gpu()) {
this->compute_gpu(context);
}
else {
this->compute_cpu();
}
}
TextureCoordinates::~TextureCoordinates()
{
this->result.release();
}
void TextureCoordinates::compute_gpu(Context &context)
{
GPUShader *shader = context.get_shader("compositor_texture_coordinates");
GPU_shader_bind(shader);
this->result.bind_as_image(shader, "output_img");
compute_dispatch_threads_at_least(shader, this->result.domain().size);
this->result.unbind_as_image();
GPU_shader_unbind();
}
void TextureCoordinates::compute_cpu()
{
const int2 size = this->result.domain().size;
parallel_for(size, [&](const int2 texel) {
float2 centered_coordinates = (float2(texel) + 0.5f) - float2(size) / 2.0f;
int max_size = math::max(size.x, size.y);
float2 normalized_coordinates = (centered_coordinates / max_size) * 2.0f;
this->result.store_pixel(texel, float3(normalized_coordinates, 0.0f));
});
}
/* --------------------------------------------------------------------
* Texture Coordinates Container.
*/
void TextureCoordinatesContainer::reset()
{
/* First, delete all resources that are no longer needed. */
map_.remove_if([](auto item) { return !item.value->needed; });
/* Second, reset the needed status of the remaining resources to false to ready them to track
* their needed status for the next evaluation. */
for (auto &value : map_.values()) {
value->needed = false;
}
}
Result &TextureCoordinatesContainer::get(Context &context, const int2 &size)
{
const TextureCoordinatesKey key(size);
auto &texture_coordinates = *map_.lookup_or_add_cb(
key, [&]() { return std::make_unique<TextureCoordinates>(context, size); });
texture_coordinates.needed = true;
return texture_coordinates.result;
}
} // namespace blender::compositor

View File

@@ -44,8 +44,8 @@ void ImplicitInputOperation::execute()
break;
case ImplicitInput::TextureCoordinates:
const int2 size = this->context().get_compositing_region_size();
result.wrap_external(
this->context().cache_manager().texture_coordinates.get(this->context(), size));
result.wrap_external(this->context().cache_manager().image_coordinates.get(
this->context(), size, CoordinatesType::Uniform));
}
}

View File

@@ -28,8 +28,7 @@ void StaticCacheManager::reset()
deriche_gaussian_coefficients.reset();
van_vliet_gaussian_coefficients.reset();
fog_glow_kernels.reset();
texture_coordinates.reset();
pixel_coordinates.reset();
image_coordinates.reset();
}
void StaticCacheManager::skip_next_reset()

View File

@@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
const int2 texel = int2(gl_GlobalInvocationID.xy);
const int2 size = imageSize(output_img);
const float2 normalized_coordinates = (float2(texel) + 0.5f) / float2(size);
imageStore(output_img, texel, float4(normalized_coordinates, float2(0.0f)));
}

View File

@@ -0,0 +1,10 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
const int2 texel = int2(gl_GlobalInvocationID.xy);
imageStore(output_img, texel, float4(float2(texel), float2(0.0f)));
}

View File

@@ -0,0 +1,16 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
const int2 texel = int2(gl_GlobalInvocationID.xy);
const int2 size = imageSize(output_img);
const float2 centered_coordinates = (float2(texel) + 0.5f) - float2(size) / 2.0f;
const int max_size = max(size.x, size.y);
const float2 normalized_coordinates = (centered_coordinates / max_size) * 2.0f;
imageStore(output_img, texel, float4(normalized_coordinates, float2(0.0f)));
}

View File

@@ -1,10 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
int2 texel = int2(gl_GlobalInvocationID.xy);
imageStore(output_img, texel, float4(float2(texel) + float2(0.5f), float2(0.0f)));
}

View File

@@ -1,16 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
void main()
{
int2 texel = int2(gl_GlobalInvocationID.xy);
int2 size = imageSize(output_img);
float2 centered_coordinates = (float2(texel) + 0.5f) - float2(size) / 2.0f;
int max_size = max(size.x, size.y);
float2 normalized_coordinates = (centered_coordinates / max_size) * 2.0f;
imageStore(output_img, texel, float4(normalized_coordinates, float2(0.0f)));
}

View File

@@ -0,0 +1,26 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_image_coordinates_uniform)
LOCAL_GROUP_SIZE(16, 16)
IMAGE(0, GPU_RGBA16F, write, image2D, output_img)
COMPUTE_SOURCE("compositor_image_coordinates_uniform.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_image_coordinates_normalized)
LOCAL_GROUP_SIZE(16, 16)
IMAGE(0, GPU_RGBA16F, write, image2D, output_img)
COMPUTE_SOURCE("compositor_image_coordinates_normalized.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_image_coordinates_pixel)
LOCAL_GROUP_SIZE(16, 16)
IMAGE(0, GPU_RGBA16F, write, image2D, output_img)
COMPUTE_SOURCE("compositor_image_coordinates_pixel.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View File

@@ -1,12 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_pixel_coordinates)
LOCAL_GROUP_SIZE(16, 16)
IMAGE(0, GPU_RGBA16F, write, image2D, output_img)
COMPUTE_SOURCE("compositor_pixel_coordinates.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View File

@@ -1,12 +0,0 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_create_info.hh"
GPU_SHADER_CREATE_INFO(compositor_texture_coordinates)
LOCAL_GROUP_SIZE(16, 16)
IMAGE(0, GPU_RGBA16F, write, image2D, output_img)
COMPUTE_SOURCE("compositor_texture_coordinates.glsl")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View File

@@ -73,6 +73,7 @@
#include "compositor_gamma_correct_info.hh"
#include "compositor_glare_info.hh"
#include "compositor_id_mask_info.hh"
#include "compositor_image_coordinates_info.hh"
#include "compositor_image_crop_info.hh"
#include "compositor_inpaint_info.hh"
#include "compositor_jump_flooding_info.hh"
@@ -90,7 +91,6 @@
#include "compositor_movie_distortion_info.hh"
#include "compositor_normalize_info.hh"
#include "compositor_parallel_reduction_info.hh"
#include "compositor_pixel_coordinates_info.hh"
#include "compositor_pixelate_info.hh"
#include "compositor_plane_deform_info.hh"
#include "compositor_premultiply_alpha_info.hh"
@@ -105,7 +105,6 @@
#include "compositor_symmetric_blur_variable_size_info.hh"
#include "compositor_symmetric_separable_blur_info.hh"
#include "compositor_symmetric_separable_blur_variable_size_info.hh"
#include "compositor_texture_coordinates_info.hh"
#include "compositor_tone_map_photoreceptor_info.hh"
#include "compositor_tone_map_simple_info.hh"
#include "compositor_translate_wrapped_info.hh"

View File

@@ -13838,6 +13838,7 @@ static void rna_def_nodes(BlenderRNA *brna)
define("CompositorNode", "CompositorNodeHueSat");
define("CompositorNode", "CompositorNodeIDMask", def_cmp_id_mask);
define("CompositorNode", "CompositorNodeImage", def_cmp_image);
define("CompositorNode", "CompositorNodeImageCoordinates");
define("CompositorNode", "CompositorNodeImageInfo");
define("CompositorNode", "CompositorNodeInpaint", def_cmp_inpaint);
define("CompositorNode", "CompositorNodeInvert", def_cmp_invert);

View File

@@ -63,6 +63,7 @@ set(SRC
nodes/node_composite_huecorrect.cc
nodes/node_composite_id_mask.cc
nodes/node_composite_image.cc
nodes/node_composite_image_coordinates.cc
nodes/node_composite_image_info.cc
nodes/node_composite_inpaint.cc
nodes/node_composite_invert.cc

View File

@@ -0,0 +1,91 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "COM_node_operation.hh"
#include "node_composite_util.hh"
namespace blender::nodes::node_composite_image_coordinates_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Color>("Image").hide_value().compositor_realization_mode(
CompositorInputRealizationMode::None);
b.add_output<decl::Vector>("Uniform").description(
"Zero centered coordinates normalizes along the larger dimension for uniform scaling");
b.add_output<decl::Vector>("Normalized")
.description("Normalized coordinates with half pixel offsets");
b.add_output<decl::Vector>("Pixel").description("Integer pixel coordinates");
}
using namespace blender::compositor;
class ImageCoordinatesOperation : public NodeOperation {
public:
ImageCoordinatesOperation(Context &context, DNode node) : NodeOperation(context, node)
{
InputDescriptor &image_descriptor = this->get_input_descriptor("Image");
image_descriptor.skip_type_conversion = true;
}
void execute() override
{
const Result &input = this->get_input("Image");
Result &uniform_coordinates_result = this->get_result("Uniform");
Result &normalized_coordinates_result = this->get_result("Normalized");
Result &pixel_coordinates_result = this->get_result("Pixel");
if (input.is_single_value()) {
uniform_coordinates_result.allocate_invalid();
normalized_coordinates_result.allocate_invalid();
pixel_coordinates_result.allocate_invalid();
return;
}
const Domain domain = input.domain();
if (uniform_coordinates_result.should_compute()) {
const Result &uniform_coordinates = this->context().cache_manager().image_coordinates.get(
this->context(), domain.size, CoordinatesType::Uniform);
uniform_coordinates_result.wrap_external(uniform_coordinates);
uniform_coordinates_result.transform(domain.transformation);
}
if (normalized_coordinates_result.should_compute()) {
const Result &normalized_coordinates = this->context().cache_manager().image_coordinates.get(
this->context(), domain.size, CoordinatesType::Normalized);
normalized_coordinates_result.wrap_external(normalized_coordinates);
normalized_coordinates_result.transform(domain.transformation);
}
if (pixel_coordinates_result.should_compute()) {
const Result &pixel_coordinates = this->context().cache_manager().image_coordinates.get(
this->context(), domain.size, CoordinatesType::Pixel);
pixel_coordinates_result.wrap_external(pixel_coordinates);
pixel_coordinates_result.transform(domain.transformation);
}
}
};
static NodeOperation *get_compositor_operation(Context &context, DNode node)
{
return new ImageCoordinatesOperation(context, node);
}
static void register_node()
{
static blender::bke::bNodeType ntype;
cmp_node_type_base(&ntype, "CompositorNodeImageCoordinates");
ntype.ui_name = "Image Coordinates";
ntype.ui_description = "Returns the coordinates of the pixels of an image";
ntype.nclass = NODE_CLASS_INPUT;
ntype.declare = node_declare;
ntype.get_compositor_operation = get_compositor_operation;
blender::bke::node_register_type(ntype);
}
NOD_REGISTER_NODE(register_node)
} // namespace blender::nodes::node_composite_image_coordinates_cc

View File

@@ -6,9 +6,6 @@
#include "BLI_math_matrix.hh"
#include "BLI_math_vector_types.hh"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "COM_node_operation.hh"
#include "node_composite_util.hh"
@@ -17,11 +14,9 @@ namespace blender::nodes::node_composite_image_info_cc {
static void node_declare(NodeDeclarationBuilder &b)
{
b.add_input<decl::Color>("Image").compositor_domain_priority(0).compositor_realization_mode(
b.add_input<decl::Color>("Image").compositor_realization_mode(
CompositorInputRealizationMode::None);
b.add_output<decl::Vector>("Texture Coordinates");
b.add_output<decl::Vector>("Pixel Coordinates");
b.add_output<decl::Vector>("Resolution");
b.add_output<decl::Vector>("Location");
b.add_output<decl::Float>("Rotation");
@@ -48,22 +43,6 @@ class ImageInfoOperation : public NodeOperation {
const Domain domain = input.domain();
Result &texture_coordinates_result = this->get_result("Texture Coordinates");
if (texture_coordinates_result.should_compute()) {
const Result &texture_coordinates = this->context().cache_manager().texture_coordinates.get(
this->context(), domain.size);
texture_coordinates_result.wrap_external(texture_coordinates);
texture_coordinates_result.transform(domain.transformation);
}
Result &pixel_coordinates_result = this->get_result("Pixel Coordinates");
if (pixel_coordinates_result.should_compute()) {
const Result &pixel_coordinates = this->context().cache_manager().pixel_coordinates.get(
this->context(), domain.size);
pixel_coordinates_result.wrap_external(pixel_coordinates);
pixel_coordinates_result.transform(domain.transformation);
}
Result &resolution_result = this->get_result("Resolution");
if (resolution_result.should_compute()) {
resolution_result.allocate_single_value();
@@ -95,16 +74,6 @@ class ImageInfoOperation : public NodeOperation {
void execute_invalid()
{
Result &texture_coordinates_result = this->get_result("Texture Coordinates");
if (texture_coordinates_result.should_compute()) {
texture_coordinates_result.allocate_invalid();
}
Result &pixel_coordinates_result = this->get_result("Pixel Coordinates");
if (pixel_coordinates_result.should_compute()) {
pixel_coordinates_result.allocate_invalid();
}
Result &resolution_result = this->get_result("Resolution");
if (resolution_result.should_compute()) {
resolution_result.allocate_invalid();