Add a text effect
Writes a line or several over its input in a chosen family, weight, size and colour, optionally on a box filled with a colour or with a picture from disk. Like the blur and the overlay it needs no sources of its own, so it can lead a chain; given sources it fills the canvas from a randomly chosen one first. Two param types rather than effect-specific branches in the front-ends: Text, free-form text the user types, and Color, a colour written "#rrggbb". Both travel as strings, so the interface gets an entry and a colour button, the command line gets a flag (a colour that does not parse is refused there rather than drawn as black), and a project keeps them across a round trip. The glyphs come from Pango, which draws through Cairo without a display, so the core stays headless — but they are the one thing here that is not byte-identical across machines: a machine without the chosen family renders the substitute its fontconfig picks. Everything the effect is handed, the rng included, is used exactly as every other effect uses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,28 @@ ParamSpec ParamSpec::makePath(std::string key, std::string label, std::string he
|
||||
return spec;
|
||||
}
|
||||
|
||||
ParamSpec ParamSpec::makeText(std::string key, std::string label, std::string defaultText,
|
||||
std::string help) {
|
||||
ParamSpec spec;
|
||||
spec.type = Type::Text;
|
||||
spec.key = std::move(key);
|
||||
spec.label = std::move(label);
|
||||
spec.help = std::move(help);
|
||||
spec.defaultChoice = std::move(defaultText);
|
||||
return spec;
|
||||
}
|
||||
|
||||
ParamSpec ParamSpec::makeColor(std::string key, std::string label, std::string defaultColor,
|
||||
std::string help) {
|
||||
ParamSpec spec;
|
||||
spec.type = Type::Color;
|
||||
spec.key = std::move(key);
|
||||
spec.label = std::move(label);
|
||||
spec.help = std::move(help);
|
||||
spec.defaultChoice = std::move(defaultColor);
|
||||
return spec;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const ParamValue* find(const ParamValues& values, const std::string& key) {
|
||||
@@ -119,4 +141,50 @@ std::string paramText(const ParamValues& values, const std::string& key,
|
||||
return fallback;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int hexDigit(char c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool parseColor(const std::string& text, std::uint8_t rgb[3]) {
|
||||
std::string digits = text;
|
||||
if (!digits.empty() && digits.front() == '#') digits.erase(digits.begin());
|
||||
if (digits.size() != 3 && digits.size() != 6) return false;
|
||||
|
||||
int parsed[6];
|
||||
for (std::size_t i = 0; i < digits.size(); ++i) {
|
||||
parsed[i] = hexDigit(digits[i]);
|
||||
if (parsed[i] < 0) return false;
|
||||
}
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
// "#abc" is the usual shorthand for "#aabbcc".
|
||||
const int value = digits.size() == 3 ? parsed[c] * 17 : parsed[c * 2] * 16 + parsed[c * 2 + 1];
|
||||
rgb[c] = static_cast<std::uint8_t>(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string formatColor(const std::uint8_t rgb[3]) {
|
||||
static const char* const kDigits = "0123456789abcdef";
|
||||
std::string text = "#";
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
text += kDigits[rgb[c] >> 4];
|
||||
text += kDigits[rgb[c] & 0x0F];
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void paramColor(const ParamValues& values, const std::string& key, const std::string& fallback,
|
||||
std::uint8_t rgb[3]) {
|
||||
if (parseColor(paramText(values, key, fallback), rgb)) return;
|
||||
if (parseColor(fallback, rgb)) return;
|
||||
rgb[0] = rgb[1] = rgb[2] = 0;
|
||||
}
|
||||
|
||||
} // namespace effector
|
||||
|
||||
Reference in New Issue
Block a user