mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Compositor: Add String To Image node
This patch adds a new String To Image node that generates a new image with a text paragraph. This also adds the Font socket and Input Font nodes. Pull Request: https://projects.blender.org/blender/blender/pulls/155864
This commit is contained in:
parent
107c81827d
commit
d5fa560cc8
12 changed files with 651 additions and 2 deletions
|
|
@ -25,6 +25,7 @@ class NODE_MT_compositor_node_input_base(node_add_menu.NodeMenu):
|
|||
self.node_operator(layout, "CompositorNodeMovieClip")
|
||||
if context.space_data.node_tree_sub_type == 'SEQUENCER':
|
||||
self.node_operator(layout, "CompositorNodeSequencerStripInfo")
|
||||
self.node_operator(layout, "CompositorNodeStringToImage")
|
||||
|
||||
layout.separator()
|
||||
self.draw_menu(layout, path="Input/Scene")
|
||||
|
|
@ -41,6 +42,7 @@ class NODE_MT_compositor_node_input_constant_base(node_add_menu.NodeMenu):
|
|||
layout = self.layout
|
||||
self.node_operator(layout, "FunctionNodeInputBool")
|
||||
self.node_operator(layout, "CompositorNodeRGB")
|
||||
self.node_operator(layout, "GeometryNodeInputFont")
|
||||
self.node_operator(layout, "FunctionNodeInputInt")
|
||||
self.node_operator(layout, "FunctionNodeInputIntVector")
|
||||
self.node_operator(layout, "FunctionNodeInputMenu")
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ set(SRC
|
|||
cached_resources/intern/morphological_distance_feather_weights.cc
|
||||
cached_resources/intern/ocio_color_space_conversion_shader.cc
|
||||
cached_resources/intern/smaa_precomputed_textures.cc
|
||||
cached_resources/intern/string_image.cc
|
||||
cached_resources/intern/symmetric_blur_weights.cc
|
||||
cached_resources/intern/symmetric_separable_blur_weights.cc
|
||||
cached_resources/intern/van_vliet_gaussian_coefficients.cc
|
||||
|
|
@ -139,6 +140,7 @@ set(SRC
|
|||
cached_resources/COM_morphological_distance_feather_weights.hh
|
||||
cached_resources/COM_ocio_color_space_conversion_shader.hh
|
||||
cached_resources/COM_smaa_precomputed_textures.hh
|
||||
cached_resources/COM_string_image.hh
|
||||
cached_resources/COM_symmetric_blur_weights.hh
|
||||
cached_resources/COM_symmetric_separable_blur_weights.hh
|
||||
cached_resources/COM_van_vliet_gaussian_coefficients.hh
|
||||
|
|
@ -154,6 +156,7 @@ set(SRC
|
|||
)
|
||||
|
||||
set(LIB
|
||||
PRIVATE bf::blenfont
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::functions
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "COM_morphological_distance_feather_weights.hh"
|
||||
#include "COM_ocio_color_space_conversion_shader.hh"
|
||||
#include "COM_smaa_precomputed_textures.hh"
|
||||
#include "COM_string_image.hh"
|
||||
#include "COM_symmetric_blur_weights.hh"
|
||||
#include "COM_symmetric_separable_blur_weights.hh"
|
||||
#include "COM_van_vliet_gaussian_coefficients.hh"
|
||||
|
|
@ -62,6 +63,7 @@ class StaticCacheManager {
|
|||
VanVlietGaussianCoefficientsContainer van_vliet_gaussian_coefficients;
|
||||
FogGlowKernelContainer fog_glow_kernels;
|
||||
ImageCoordinatesContainer image_coordinates;
|
||||
StringImageContainer string_images;
|
||||
|
||||
public:
|
||||
/* Reset the cache manager by deleting the cached resources that are no longer needed because
|
||||
|
|
|
|||
104
source/blender/compositor/cached_resources/COM_string_image.hh
Normal file
104
source/blender/compositor/cached_resources/COM_string_image.hh
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include "DNA_node_types.h"
|
||||
#include "DNA_vfont_types.h"
|
||||
|
||||
#include "COM_cached_resource.hh"
|
||||
#include "COM_result.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
class Context;
|
||||
|
||||
enum class HorizontalAlignment : uint8_t {
|
||||
Left = 0,
|
||||
Center = 1,
|
||||
Right = 2,
|
||||
};
|
||||
|
||||
enum class VerticalAlignment : uint8_t {
|
||||
Top = 0,
|
||||
TopBaseline = 1,
|
||||
Middle = 2,
|
||||
BottomBaseline = 3,
|
||||
Bottom = 4,
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* String Image Key.
|
||||
*/
|
||||
class StringImageKey {
|
||||
public:
|
||||
const std::string string;
|
||||
const VFont *font;
|
||||
const float size;
|
||||
const HorizontalAlignment horizontal_alignment;
|
||||
const VerticalAlignment vertical_alignment;
|
||||
const std::optional<int> wrap_width;
|
||||
|
||||
StringImageKey(const std::string string,
|
||||
const VFont *font,
|
||||
const float size,
|
||||
const HorizontalAlignment horizontal_alignment,
|
||||
const VerticalAlignment vertical_alignment,
|
||||
const std::optional<int> wrap_width);
|
||||
|
||||
uint64_t hash() const;
|
||||
friend bool operator==(const StringImageKey &a, const StringImageKey &b) = default;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------------------------------
|
||||
* String Image.
|
||||
*
|
||||
* A cached resource that computes and caches a result containing a string with the given
|
||||
* parameters. */
|
||||
class StringImage : public CachedResource {
|
||||
public:
|
||||
Result result;
|
||||
|
||||
StringImage(Context &context,
|
||||
const std::string string,
|
||||
const VFont *font,
|
||||
const float size,
|
||||
const HorizontalAlignment horizontal_alignment,
|
||||
const VerticalAlignment vertical_alignment,
|
||||
const std::optional<int> wrap_width);
|
||||
|
||||
~StringImage();
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------
|
||||
* String Image Container.
|
||||
*/
|
||||
class StringImageContainer : CachedResourceContainer {
|
||||
private:
|
||||
Map<StringImageKey, std::unique_ptr<StringImage>> map_;
|
||||
|
||||
public:
|
||||
void reset() override;
|
||||
|
||||
/* Check if there is an available StringImage 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 std::string string,
|
||||
const VFont *font,
|
||||
const float size,
|
||||
const HorizontalAlignment horizontal_alignment,
|
||||
const VerticalAlignment vertical_alignment,
|
||||
const std::optional<int> wrap_width);
|
||||
};
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_memory_utils.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_rect.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "DNA_packedFile_types.h"
|
||||
#include "DNA_vfont_types.h"
|
||||
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_vfont.hh"
|
||||
|
||||
#include "BLF_api.hh"
|
||||
|
||||
#include "COM_context.hh"
|
||||
#include "COM_result.hh"
|
||||
#include "COM_string_image.hh"
|
||||
#include "COM_utilities.hh"
|
||||
|
||||
namespace blender::compositor {
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* String Image Key.
|
||||
*/
|
||||
|
||||
StringImageKey::StringImageKey(const std::string string,
|
||||
const VFont *font,
|
||||
const float size,
|
||||
const HorizontalAlignment horizontal_alignment,
|
||||
const VerticalAlignment vertical_alignment,
|
||||
const std::optional<int> wrap_width)
|
||||
: string(string),
|
||||
font(font),
|
||||
size(size),
|
||||
horizontal_alignment(horizontal_alignment),
|
||||
vertical_alignment(vertical_alignment),
|
||||
wrap_width(wrap_width)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t StringImageKey::hash() const
|
||||
{
|
||||
return get_default_hash(
|
||||
get_default_hash(string, font, size, horizontal_alignment, vertical_alignment),
|
||||
wrap_width.value_or(-1));
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* String Image.
|
||||
*/
|
||||
|
||||
/* Loads a BLF font from the given font ID and return its identifier. Unloading the font is the
|
||||
* responsibility of the caller. */
|
||||
static int load_font(const VFont *font)
|
||||
{
|
||||
if (!font || BKE_vfont_is_builtin(font)) {
|
||||
return BLF_load_default(true);
|
||||
}
|
||||
|
||||
if (font->packedfile != nullptr) {
|
||||
char name[MAX_ID_FULL_NAME];
|
||||
BKE_id_full_name_get(name, &font->id, 0);
|
||||
return BLF_load_mem_unique(
|
||||
name, static_cast<const uchar *>(font->packedfile->data), font->packedfile->size);
|
||||
}
|
||||
|
||||
char file_path[FILE_MAX];
|
||||
STRNCPY(file_path, font->filepath);
|
||||
BLI_path_abs(file_path, ID_BLEND_PATH_FROM_GLOBAL(&font->id));
|
||||
return BLF_load_unique(file_path);
|
||||
}
|
||||
|
||||
/* Computes the horizontal position in pixels from which the line with the given width should be
|
||||
* drawn. The BLF module draws from the left most edge of the line. The start offset is an offset
|
||||
* that is intrinsic to the used font and thus should be canceled here to draw right from the edge
|
||||
* of the image. The total width is the total width of the image and is the maximum width of all
|
||||
* lines. */
|
||||
static float compute_draw_horizontal_position(const int start_offset,
|
||||
const int line_width,
|
||||
const int total_width,
|
||||
const HorizontalAlignment alignment)
|
||||
{
|
||||
switch (alignment) {
|
||||
case HorizontalAlignment::Left:
|
||||
return float(-start_offset);
|
||||
case HorizontalAlignment::Center:
|
||||
return -start_offset + (total_width - line_width) / 2.0f;
|
||||
case HorizontalAlignment::Right:
|
||||
return float(-start_offset + total_width - line_width);
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return float(-start_offset);
|
||||
}
|
||||
|
||||
/* Computes the vertical position in pixels from which the line with the given index should be
|
||||
* drawn. Assuming there are the given number of lines with each having the given height and
|
||||
* descender. The BLF module draws from the baseline of the line starting from the lower left
|
||||
* origin of the image, therefore, we need to subtract the negative descender such that the line is
|
||||
* fully drawn in the image. */
|
||||
static float compute_draw_vertical_position(const int lines_count,
|
||||
const int line_index,
|
||||
const int line_height,
|
||||
const int descender)
|
||||
{
|
||||
return (lines_count - 1 - line_index) * line_height - float(descender);
|
||||
}
|
||||
|
||||
/* Computes the horizontal offset in pixels depending on the alignment. We also restore the start
|
||||
* offset that was canceled during drawing, see compute_draw_horizontal_position. */
|
||||
static float compute_horizontal_offset(const int start_offset,
|
||||
const int total_width,
|
||||
const HorizontalAlignment alignment)
|
||||
{
|
||||
switch (alignment) {
|
||||
case HorizontalAlignment::Left:
|
||||
return start_offset + total_width / 2.0f;
|
||||
case HorizontalAlignment::Center:
|
||||
return float(start_offset);
|
||||
case HorizontalAlignment::Right:
|
||||
return start_offset - total_width / 2.0f;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return float(start_offset);
|
||||
}
|
||||
|
||||
/* Computes the vertical offset in pixels depending on the alignment. Assuming the image has the
|
||||
* given total height, the lines have the given height and negative descender. */
|
||||
static float compute_vertical_offset(const int total_height,
|
||||
const int line_height,
|
||||
const int descender,
|
||||
const VerticalAlignment alignment)
|
||||
{
|
||||
switch (alignment) {
|
||||
case VerticalAlignment::Top:
|
||||
return -total_height / 2.0f;
|
||||
case VerticalAlignment::TopBaseline:
|
||||
return -total_height / 2.0f + line_height + descender;
|
||||
case VerticalAlignment::Middle:
|
||||
return 0.0f;
|
||||
case VerticalAlignment::BottomBaseline:
|
||||
return total_height / 2.0f + descender;
|
||||
case VerticalAlignment::Bottom:
|
||||
return total_height / 2.0f;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return float(descender);
|
||||
}
|
||||
|
||||
StringImage::StringImage(Context &context,
|
||||
const std::string string,
|
||||
const VFont *font,
|
||||
const float size,
|
||||
const HorizontalAlignment horizontal_alignment,
|
||||
const VerticalAlignment vertical_alignment,
|
||||
const std::optional<int> wrap_width)
|
||||
: result(context.create_result(ResultType::Float))
|
||||
{
|
||||
if (string.empty() || !font || size <= 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int font_identifier = load_font(font);
|
||||
if (font_identifier == -1) {
|
||||
return;
|
||||
}
|
||||
BLI_SCOPED_DEFER([&]() { BLF_unload_id(font_identifier); });
|
||||
|
||||
BLF_size(font_identifier, size);
|
||||
|
||||
Vector<StringRef> lines = BLF_string_wrap(
|
||||
font_identifier, string, wrap_width.value_or(-1), BLFWrapMode::Typographical);
|
||||
|
||||
/* Compute the width of all lines as well as their starting offset. The starting offset will be
|
||||
* zero in most fonts, but some special fonts might start before or after the zero point. */
|
||||
int total_width = 0;
|
||||
Array<int> line_widths(lines.size());
|
||||
int start_offset = std::numeric_limits<int>::max();
|
||||
for (const int64_t i : lines.index_range()) {
|
||||
rcti line_bounding_box;
|
||||
BLF_boundbox(font_identifier, lines[i].data(), lines[i].size(), &line_bounding_box);
|
||||
line_widths[i] = BLI_rcti_size_x(&line_bounding_box);
|
||||
total_width = math::max(total_width, line_widths[i]);
|
||||
start_offset = math::min(start_offset, line_bounding_box.xmin);
|
||||
}
|
||||
|
||||
const int line_height = BLF_height_max(font_identifier);
|
||||
const int total_height = line_height * lines.size();
|
||||
|
||||
/* Fill the background with alpha since the draws function does not initialize the background. */
|
||||
this->result.allocate_texture(int2(total_width, total_height), false, ResultStorageType::CPU);
|
||||
parallel_for(this->result.domain().data_size,
|
||||
[&](const int2 texel) { this->result.store_pixel(texel, 0.0f); });
|
||||
|
||||
BLF_buffer_col(font_identifier, Color(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
BLF_buffer(font_identifier,
|
||||
static_cast<float *>(this->result.cpu_data().data()),
|
||||
nullptr,
|
||||
total_width,
|
||||
total_height,
|
||||
1,
|
||||
nullptr);
|
||||
|
||||
/* Draw each of lines in the appropriate position. */
|
||||
const int descender = BLF_descender(font_identifier);
|
||||
for (const int64_t i : lines.index_range()) {
|
||||
const float vertical_position = compute_draw_vertical_position(
|
||||
lines.size(), i, line_height, descender);
|
||||
const float horizontal_position = compute_draw_horizontal_position(
|
||||
start_offset, line_widths[i], total_width, horizontal_alignment);
|
||||
BLF_position(font_identifier, horizontal_position, vertical_position, 0.0f);
|
||||
BLF_draw_buffer(font_identifier, lines[i].data(), lines[i].size());
|
||||
}
|
||||
|
||||
BLF_buffer(font_identifier, nullptr, nullptr, 0, 0, 1, nullptr);
|
||||
|
||||
/* Move the image to account for the requested alignment. */
|
||||
const float horizontal_offset = compute_horizontal_offset(
|
||||
start_offset, total_width, horizontal_alignment);
|
||||
const float vertical_offset = compute_vertical_offset(
|
||||
total_height, line_height, descender, vertical_alignment);
|
||||
this->result.domain().transformation.location() = float2(horizontal_offset, vertical_offset);
|
||||
|
||||
if (context.use_gpu()) {
|
||||
const Result gpu_result = this->result.upload_to_gpu(false);
|
||||
this->result.release();
|
||||
this->result = gpu_result;
|
||||
}
|
||||
}
|
||||
|
||||
StringImage::~StringImage()
|
||||
{
|
||||
this->result.release();
|
||||
}
|
||||
|
||||
/* --------------------------------------------------------------------
|
||||
* String Image Container.
|
||||
*/
|
||||
|
||||
void StringImageContainer::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 &StringImageContainer::get(Context &context,
|
||||
const std::string string,
|
||||
const VFont *font,
|
||||
const float size,
|
||||
const HorizontalAlignment horizontal_alignment,
|
||||
const VerticalAlignment vertical_alignment,
|
||||
const std::optional<int> wrap_width)
|
||||
{
|
||||
const StringImageKey key(
|
||||
string, font, size, horizontal_alignment, vertical_alignment, wrap_width);
|
||||
|
||||
auto &string_image = *map_.lookup_or_add_cb(key, [&]() {
|
||||
return std::make_unique<StringImage>(
|
||||
context, string, font, size, horizontal_alignment, vertical_alignment, wrap_width);
|
||||
});
|
||||
|
||||
string_image.needed = true;
|
||||
return string_image.result;
|
||||
}
|
||||
|
||||
} // namespace blender::compositor
|
||||
|
|
@ -24,6 +24,7 @@ void StaticCacheManager::reset()
|
|||
van_vliet_gaussian_coefficients.reset();
|
||||
fog_glow_kernels.reset();
|
||||
image_coordinates.reset();
|
||||
string_images.reset();
|
||||
}
|
||||
|
||||
void StaticCacheManager::free()
|
||||
|
|
|
|||
|
|
@ -1108,6 +1108,20 @@ enum CMPNodeRelativeToPixelReferenceDimension {
|
|||
CMP_NODE_RELATIVE_TO_PIXEL_REFERENCE_DIMENSION_DIAGONAL = 5,
|
||||
};
|
||||
|
||||
enum CMPNodeStringToImageHorizontalAlignment {
|
||||
CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_LEFT = 0,
|
||||
CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_CENTER = 1,
|
||||
CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_RIGHT = 2,
|
||||
};
|
||||
|
||||
enum CMPNodeStringToImageVerticalAlignment {
|
||||
CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_TOP = 0,
|
||||
CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_TOP_BASELINE = 1,
|
||||
CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_MIDDLE = 2,
|
||||
CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_BOTTOM_BASELINE = 3,
|
||||
CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_BOTTOM = 4,
|
||||
};
|
||||
|
||||
/* Scattering phase functions */
|
||||
enum {
|
||||
SHD_PHASE_HENYEY_GREENSTEIN = 0,
|
||||
|
|
|
|||
|
|
@ -10257,6 +10257,7 @@ static void rna_def_nodes(BlenderRNA *brna)
|
|||
define("CompositorNode", "CompositorNodeSplit");
|
||||
define("CompositorNode", "CompositorNodeStabilize", def_cmp_stabilize2d);
|
||||
define("CompositorNode", "CompositorNodeSequencerStripInfo");
|
||||
define("CompositorNode", "CompositorNodeStringToImage");
|
||||
define("CompositorNode", "CompositorNodeSwitch");
|
||||
define("CompositorNode", "CompositorNodeSwitchView");
|
||||
define("CompositorNode", "CompositorNodeTime", def_time);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ set(SRC
|
|||
nodes/node_composite_set_alpha.cc
|
||||
nodes/node_composite_split.cc
|
||||
nodes/node_composite_stabilize_2d.cc
|
||||
nodes/node_composite_string_to_image.cc
|
||||
nodes/node_composite_strip_info.cc
|
||||
nodes/node_composite_switch.cc
|
||||
nodes/node_composite_switch_view.cc
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ static bool composite_node_tree_socket_type_valid(bke::bNodeTreeType * /*ntreety
|
|||
SOCK_MATRIX,
|
||||
SOCK_MENU,
|
||||
SOCK_STRING,
|
||||
SOCK_OBJECT);
|
||||
SOCK_OBJECT,
|
||||
SOCK_FONT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,213 @@
|
|||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "DNA_vfont_types.h"
|
||||
|
||||
#include "BKE_vfont.hh"
|
||||
|
||||
#include "UI_resources.hh"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
|
||||
#include "node_composite_util.hh"
|
||||
|
||||
namespace blender::nodes::node_composite_string_to_image_cc {
|
||||
|
||||
static const EnumPropertyItem rna_node_compositor_string_to_image_horizontal_alignment_items[] = {
|
||||
{CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_LEFT,
|
||||
"LEFT",
|
||||
ICON_ALIGN_LEFT,
|
||||
"Left",
|
||||
"Align text to the left"},
|
||||
{CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_CENTER,
|
||||
"CENTER",
|
||||
ICON_ALIGN_CENTER,
|
||||
"Center",
|
||||
"Align text to the center"},
|
||||
{CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_RIGHT,
|
||||
"RIGHT",
|
||||
ICON_ALIGN_RIGHT,
|
||||
"Right",
|
||||
"Align text to the right"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static const EnumPropertyItem rna_node_compositor_string_to_image_vertical_alignment_items[] = {
|
||||
{CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_TOP,
|
||||
"TOP",
|
||||
ICON_ALIGN_TOP,
|
||||
"Top",
|
||||
"Align text to the top"},
|
||||
{CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_TOP_BASELINE,
|
||||
"TOP_BASELINE",
|
||||
ICON_ALIGN_TOP,
|
||||
"Top Baseline",
|
||||
"Align text to the top line's baseline"},
|
||||
{CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_MIDDLE,
|
||||
"MIDDLE",
|
||||
ICON_ALIGN_MIDDLE,
|
||||
"Middle",
|
||||
"Align text to the middle"},
|
||||
{CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_BOTTOM_BASELINE,
|
||||
"BOTTOM_BASELINE",
|
||||
ICON_ALIGN_BOTTOM,
|
||||
"Bottom Baseline",
|
||||
"Align text to the bottom line's baseline"},
|
||||
{CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_BOTTOM,
|
||||
"BOTTOM",
|
||||
ICON_ALIGN_BOTTOM,
|
||||
"Bottom",
|
||||
"Align text to the bottom"},
|
||||
{0, nullptr, 0, nullptr, nullptr},
|
||||
};
|
||||
|
||||
static void node_declare(NodeDeclarationBuilder &b)
|
||||
{
|
||||
b.use_custom_socket_order();
|
||||
b.allow_any_socket_order();
|
||||
|
||||
b.add_output<decl::Float>("Image"_ustr)
|
||||
.structure_type(StructureType::Dynamic)
|
||||
.description("The image containing the paragraph of text");
|
||||
|
||||
b.add_input<decl::String>("String"_ustr).optional_label();
|
||||
b.add_input<decl::Font>("Font"_ustr)
|
||||
.default_value_fn(
|
||||
[](const bNode & /*node*/) { return id_cast<ID *>(BKE_vfont_builtin_ensure()); })
|
||||
.optional_label();
|
||||
b.add_input<decl::Float>("Size"_ustr)
|
||||
.default_value(128.0f)
|
||||
.subtype(PROP_PIXEL)
|
||||
.min(0.0f)
|
||||
.description("The height of each line in pixels");
|
||||
|
||||
{
|
||||
PanelDeclarationBuilder &panel = b.add_panel("Alignment"_ustr).default_closed(true);
|
||||
panel.add_input<decl::Menu>("Horizontal Alignment"_ustr)
|
||||
.static_items(rna_node_compositor_string_to_image_horizontal_alignment_items)
|
||||
.default_value(CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_CENTER)
|
||||
.optional_label();
|
||||
panel.add_input<decl::Menu>("Vertical Alignment"_ustr)
|
||||
.static_items(rna_node_compositor_string_to_image_vertical_alignment_items)
|
||||
.default_value(CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_MIDDLE)
|
||||
.optional_label();
|
||||
}
|
||||
|
||||
{
|
||||
PanelDeclarationBuilder &panel = b.add_panel("Wrap"_ustr).default_closed(true);
|
||||
panel.add_input<decl::Bool>("Wrap"_ustr)
|
||||
.default_value(true)
|
||||
.panel_toggle()
|
||||
.description("Wrap text into new lines if it exceeds the specified width");
|
||||
panel.add_input<decl::Int>("Width"_ustr, "Wrap Width"_ustr)
|
||||
.default_value(1920)
|
||||
.min(0)
|
||||
.subtype(PROP_PIXEL)
|
||||
.description(
|
||||
"The maximum width of each line in pixels. Lines with larger widths will be wrapped "
|
||||
"into new lines");
|
||||
}
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class StringToImageOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
const std::string string = this->get_input("String").get_single_value_default<std::string>();
|
||||
const VFont *font = this->get_input("Font").get_single_value_default<VFont *>();
|
||||
const float size = this->get_input("Size").get_single_value_default<float>();
|
||||
|
||||
const Result &string_image = this->context().cache_manager().string_images.get(
|
||||
this->context(),
|
||||
string,
|
||||
font,
|
||||
size,
|
||||
this->get_horizontal_alignment(),
|
||||
this->get_vertical_alignment(),
|
||||
this->get_wrap_width());
|
||||
if (!string_image.is_allocated()) {
|
||||
this->allocate_default_remaining_outputs();
|
||||
return;
|
||||
}
|
||||
|
||||
Result &output = this->get_result("Image");
|
||||
output.wrap_external(string_image);
|
||||
}
|
||||
|
||||
HorizontalAlignment get_horizontal_alignment()
|
||||
{
|
||||
const auto horizontal_alignment = CMPNodeStringToImageHorizontalAlignment(
|
||||
this->get_input("Horizontal Alignment").get_single_value_default<MenuValue>().value);
|
||||
switch (horizontal_alignment) {
|
||||
case CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_LEFT:
|
||||
return HorizontalAlignment::Left;
|
||||
case CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_CENTER:
|
||||
return HorizontalAlignment::Center;
|
||||
case CMP_NODE_STRING_TO_IMAGE_HORIZONTAL_ALIGNMENT_RIGHT:
|
||||
return HorizontalAlignment::Right;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return HorizontalAlignment::Left;
|
||||
}
|
||||
|
||||
VerticalAlignment get_vertical_alignment()
|
||||
{
|
||||
const auto vertical_alignment = CMPNodeStringToImageVerticalAlignment(
|
||||
this->get_input("Vertical Alignment").get_single_value_default<MenuValue>().value);
|
||||
switch (vertical_alignment) {
|
||||
case CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_TOP:
|
||||
return VerticalAlignment::Top;
|
||||
case CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_TOP_BASELINE:
|
||||
return VerticalAlignment::TopBaseline;
|
||||
case CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_MIDDLE:
|
||||
return VerticalAlignment::Middle;
|
||||
case CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_BOTTOM_BASELINE:
|
||||
return VerticalAlignment::BottomBaseline;
|
||||
case CMP_NODE_STRING_TO_IMAGE_VERTICAL_ALIGNMENT_BOTTOM:
|
||||
return VerticalAlignment::Bottom;
|
||||
}
|
||||
|
||||
BLI_assert_unreachable();
|
||||
return VerticalAlignment::Middle;
|
||||
}
|
||||
|
||||
std::optional<int> get_wrap_width()
|
||||
{
|
||||
const bool use_wrap = this->get_input("Wrap").get_single_value_default<bool>();
|
||||
if (!use_wrap) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return math::max(0, this->get_input("Wrap Width").get_single_value_default<int>());
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new StringToImageOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
cmp_node_type_base(&ntype, "CompositorNodeStringToImage"_ustr);
|
||||
ntype.ui_name = "String To Image";
|
||||
ntype.ui_description = "Generates an image containing the given paragraph of text";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
||||
} // namespace blender::nodes::node_composite_string_to_image_cc
|
||||
|
|
@ -9,6 +9,8 @@
|
|||
#include "UI_interface_c.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "COM_node_operation.hh"
|
||||
|
||||
#include "node_geometry_util.hh"
|
||||
|
||||
namespace blender::nodes::node_geo_input_font_cc {
|
||||
|
|
@ -38,17 +40,38 @@ static void node_geo_exec(GeoNodeExecParams params)
|
|||
params.set_output("Font"_ustr, font);
|
||||
}
|
||||
|
||||
using namespace blender::compositor;
|
||||
|
||||
class InputFontOperation : public NodeOperation {
|
||||
public:
|
||||
using NodeOperation::NodeOperation;
|
||||
|
||||
void execute() override
|
||||
{
|
||||
VFont *font = id_cast<VFont *>(this->node().id);
|
||||
Result &result = this->get_result("Font");
|
||||
result.allocate_single_value();
|
||||
result.set_single_value(font);
|
||||
}
|
||||
};
|
||||
|
||||
static NodeOperation *get_compositor_operation(Context &context, const bNode &node)
|
||||
{
|
||||
return new InputFontOperation(context, node);
|
||||
}
|
||||
|
||||
static void node_register()
|
||||
{
|
||||
static bke::bNodeType ntype;
|
||||
|
||||
geo_node_type_base(&ntype, "GeometryNodeInputFont"_ustr);
|
||||
geo_cmp_node_type_base(&ntype, "GeometryNodeInputFont"_ustr);
|
||||
ntype.ui_name = "Font";
|
||||
ntype.ui_description = "Output a font";
|
||||
ntype.nclass = NODE_CLASS_INPUT;
|
||||
ntype.declare = node_declare;
|
||||
ntype.initfunc = node_init;
|
||||
ntype.geometry_node_execute = node_geo_exec;
|
||||
ntype.get_compositor_operation = get_compositor_operation;
|
||||
bke::node_register_type(ntype);
|
||||
}
|
||||
NOD_REGISTER_NODE(node_register)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue