diff --git a/data/Text.otio b/data/Text.otio new file mode 100644 index 0000000..d231b9f --- /dev/null +++ b/data/Text.otio @@ -0,0 +1,106 @@ +{ + "OTIO_SCHEMA": "Timeline.1", + "metadata": {}, + "name": "SimpleOver", + "tracks": { + "OTIO_SCHEMA": "Stack.1", + "children": [ + { + "OTIO_SCHEMA": "Track.1", + "children": [ + { + "OTIO_SCHEMA": "Clip.1", + "media_reference": { + "OTIO_SCHEMA": "ExternalReference.1", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 24 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "target_url": "Color_Black.png" + }, + "effects": [ + { + "OTIO_SCHEMA": "TextImageEffect.1", + "effect_name": "", + "x": 100, + "y": 200, + "text": "toucan", + "font_size": 200, + "font_name": "", + "red": 0.8, + "green": 0.0, + "blue": 0.3, + "alpha": 1.0 + }, + { + "OTIO_SCHEMA": "TextImageEffect.1", + "effect_name": "", + "x": 200, + "y": 350, + "text": "toucan", + "font_size": 200, + "font_name": "", + "red": 0.3, + "green": 0.8, + "blue": 0.1, + "alpha": 1.0 + }, + { + "OTIO_SCHEMA": "TextImageEffect.1", + "effect_name": "", + "x": 300, + "y": 500, + "text": "toucan", + "font_size": 200, + "font_name": "", + "red": 0.9, + "green": 0.7, + "blue": 0.1, + "alpha": 1.0 + }, + { + "OTIO_SCHEMA": "TextImageEffect.1", + "effect_name": "", + "x": 400, + "y": 650, + "text": "toucan", + "font_size": 200, + "font_name": "", + "red": 0.9, + "green": 0.5, + "blue": 0.1, + "alpha": 1.0 + } + ], + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 24 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "name": "Text" + } + ], + "kind": "Video", + "name": "Video" + } + ], + "name": "Stack" + } +} diff --git a/lib/toucan/CMakeLists.txt b/lib/toucan/CMakeLists.txt index 8834295..af07a6a 100644 --- a/lib/toucan/CMakeLists.txt +++ b/lib/toucan/CMakeLists.txt @@ -6,6 +6,7 @@ set(HEADERS Init.h NoiseImageOp.h ReadImageOp.h + TextImageOp.h TimelineTraverse.h) set(SOURCE CheckersImageOp.cpp @@ -15,6 +16,7 @@ set(SOURCE Init.cpp NoiseImageOp.cpp ReadImageOp.cpp + TextImageOp.cpp TimelineTraverse.cpp) add_library(toucan ${HEADERS} ${SOURCE}) diff --git a/lib/toucan/Init.cpp b/lib/toucan/Init.cpp index f3ae0ae..51b21a0 100644 --- a/lib/toucan/Init.cpp +++ b/lib/toucan/Init.cpp @@ -7,6 +7,7 @@ #include "CheckersImageOp.h" #include "FillImageOp.h" #include "NoiseImageOp.h" +#include "TextImageOp.h" #include @@ -17,5 +18,6 @@ namespace toucan OTIO_NS::TypeRegistry::instance().register_type(); OTIO_NS::TypeRegistry::instance().register_type(); OTIO_NS::TypeRegistry::instance().register_type(); + OTIO_NS::TypeRegistry::instance().register_type(); } } diff --git a/lib/toucan/TextImageOp.cpp b/lib/toucan/TextImageOp.cpp new file mode 100644 index 0000000..3705c79 --- /dev/null +++ b/lib/toucan/TextImageOp.cpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2024 Darby Johnston +// All rights reserved. + +#include "TextImageOp.h" + +#include + +namespace toucan +{ + TextImageOp::TextImageOp(const TextData& data) : + _data(data) + {} + + TextImageOp::~TextImageOp() + {} + + const TextData& TextImageOp::getData() const + { + return _data; + } + + void TextImageOp::setData(const TextData& value) + { + _data = value; + } + + OIIO::ImageBuf TextImageOp::exec() + { + OIIO::ImageBuf buf; + if (!_inputs.empty()) + { + buf = _inputs[0]->exec(); + OIIO::ImageBufAlgo::render_text( + buf, + _data.pos.x, + _data.pos.y, + _data.text, + _data.fontSize, + _data.fontName, + { _data.color.x, _data.color.y, _data.color.z, _data.color.w }); + } + return buf; + } + + TextImageEffect::TextImageEffect( + std::string const& name, + std::string const& effect_name, + OTIO_NS::AnyDictionary const& metadata) : + IEffect(name, effect_name, metadata) + {} + + TextImageEffect::~TextImageEffect() + {} + + const TextData& TextImageEffect::getData() const + { + return _data; + } + + void TextImageEffect::setData(const TextData & value) + { + _data = value; + } + + std::shared_ptr TextImageEffect::createOp() + { + return std::make_shared(_data); + } + + bool TextImageEffect::read_from(Reader& reader) + { + int64_t x = 0; + int64_t y = 0; + int64_t fontSize = 0; + IMATH_NAMESPACE::V4d color; + bool out = + reader.read("x", &x) && + reader.read("y", &y) && + reader.read("text", &_data.text) && + reader.read("font_size", &fontSize) && + reader.read("font_name", &_data.fontName) && + reader.read("red", &color.x) && + reader.read("green", &color.y) && + reader.read("blue", &color.z) && + reader.read("alpha", &color.w) && + IEffect::read_from(reader); + if (out) + { + _data.pos.x = x; + _data.pos.y = y; + _data.fontSize = fontSize; + _data.color = color; + } + return out; + } + + void TextImageEffect::write_to(Writer& writer) const + { + IEffect::write_to(writer); + writer.write("x", static_cast(_data.pos.x)); + writer.write("y", static_cast(_data.pos.y)); + writer.write("text", _data.text); + writer.write("font_size", static_cast(_data.fontSize)); + writer.write("font_name", _data.fontName); + writer.write("red", static_cast(_data.color.x)); + writer.write("green", static_cast(_data.color.y)); + writer.write("blue", static_cast(_data.color.z)); + writer.write("alpha", static_cast(_data.color.w)); + } +} diff --git a/lib/toucan/TextImageOp.h b/lib/toucan/TextImageOp.h new file mode 100644 index 0000000..b373132 --- /dev/null +++ b/lib/toucan/TextImageOp.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2024 Darby Johnston +// All rights reserved. + +#pragma once + +#include + +#include + +namespace toucan +{ + //! Text data. + struct TextData + { + IMATH_NAMESPACE::V2i pos = IMATH_NAMESPACE::V2i(0, 0); + std::string text; + int fontSize = 16; + std::string fontName; + IMATH_NAMESPACE::V4f color = IMATH_NAMESPACE::V4f(1.F, 1.F, 1.F, 1.F); + }; + + //! Text drawing image operation. + class TextImageOp : public IImageOp + { + public: + TextImageOp(const TextData& = TextData()); + + virtual ~TextImageOp(); + + const TextData& getData() const; + void setData(const TextData&); + + OIIO::ImageBuf exec() override; + + private: + TextData _data; + }; + + //! Text drawing image OTIO effect. + class TextImageEffect : public IEffect + { + public: + struct Schema + { + static auto constexpr name = "TextImageEffect"; + static int constexpr version = 1; + }; + + TextImageEffect( + std::string const& name = std::string(), + std::string const& effect_name = std::string(), + OTIO_NS::AnyDictionary const& metadata = OTIO_NS::AnyDictionary()); + + const TextData& getData() const; + void setData(const TextData&); + + std::shared_ptr createOp() override; + + protected: + virtual ~TextImageEffect(); + + bool read_from(Reader&) override; + void write_to(Writer&) const override; + + private: + TextData _data; + }; +} diff --git a/tests/TimelineTraverseTest.cpp b/tests/TimelineTraverseTest.cpp index 102a9e7..487554d 100644 --- a/tests/TimelineTraverseTest.cpp +++ b/tests/TimelineTraverseTest.cpp @@ -18,7 +18,8 @@ namespace toucan { "CompOver", "Gap", - "Patterns" + "Patterns", + "Text" }; for (const auto& otioFile : otioFiles) {