add 'add_stroke_effect' script function

This commit is contained in:
GenevensiS
2026-01-14 11:48:17 +01:00
parent d10a71e925
commit 8afd412021
10 changed files with 128 additions and 111 deletions
+1
View File
@@ -91,6 +91,7 @@ These functions are built into the program, other [[type:function]]s can be defi
| [[fun:recolor_image]] Change the colors of an image to match the font color.
| [[fun:resize_image]] Stretch or squeeze an image to a given height and width.
| [[fun:enlarge]] Enlarge an image by putting a border around it.
| [[fun:add_stroke_effect]] Add a stroke effect around an image.
| [[fun:add_bleed_edge]] Add a crude print bleed edge around an image.
| [[fun:crop]] Crop an image, giving only a small subset of it.
| [[fun:flip_horizontal]] Flip an image horizontally.
+15
View File
@@ -0,0 +1,15 @@
Function: add_stroke_effect
--Usage--
> add_stroke_effect(input: image, radius: number, blur: number, color: color)
Add a stroke effect around an image.
--Parameters--
! Parameter Type Description
| @input@ [[type:image]] Image that needs a stroke effect
| @radius@ [[type:int]] Size of the stroke, in pixels
| @blur@ [[type:int]] Size of the blur of the stroke, in pixels. Optional, defaults to 0
| @color@ [[type:color]] Color of the stroke
| @include_image@ [[type:boolean]] Draw the original image in the middle of the stroke effect. Optional, defaults to true
+5 -81
View File
@@ -301,96 +301,20 @@ void SymbolFont::draw(RotatedDC& dc, RealRect rect, double scale, const SymbolFo
RealPoint bmp_pos = align_in_rect(font.alignment(), bmp_size, sym_rect);
// 2. draw potential stroke or shadow
if (font.hasStroke()) {
// add margin
Image img = bmp.ConvertToImage();
if (!img.HasAlpha()) set_alpha(img, 0);
int blur_radius = lround(font.stroke_blur() * s_scale);
int stroke_radius = lround(font.stroke_radius() * s_scale);
int margin = blur_radius + stroke_radius;
int s_width = img.GetWidth() + 2 * margin, s_height = img.GetHeight() + 2 * margin;
int x_end = s_width - margin;
int y_end = s_height - margin;
wxImage s_img(s_width, s_height, false);
s_img.InitAlpha();
// convert to stroke color
Byte* s_data = s_img.GetData();
Byte* s_alpha = s_img.GetAlpha(), *alpha = img.GetAlpha();
Color color = font.stroke_color();
unsigned char r = color.Red();
unsigned char g = color.Green();
unsigned char b = color.Blue();
unsigned char a = color.Alpha();
for (int y = 0 ; y < s_height ; ++y) {
for (int x = 0 ; x < s_width ; ++x) {
s_data[0] = r;
s_data[1] = g;
s_data[2] = b;
s_data += 3;
if (margin <= x && x < x_end && margin <= y && y < y_end) {
s_alpha[0] = alpha[0] * a / 255;
alpha += 1;
}
else {
s_alpha[0] = 0;
}
s_alpha += 1;
}
}
// add stroke effect
for (int i = 0 ; i < stroke_radius ; ++i) {
thicken_image_alpha(s_img, 1);
}
// add blur
for (int i = 0 ; i < blur_radius ; ++i) {
blur_image_alpha(s_img, 3);
}
// draw
Image s_img = make_stroke_image(bmp.ConvertToImage(), font.stroke_color(), stroke_radius, blur_radius);
RealSize s_size = dc.trInvS(RealSize(s_img));
RealPoint s_pos(bmp_pos.x - (s_size.width - bmp_size.width)/2, bmp_pos.y - (s_size.height - bmp_size.height)/2);
dc.DrawImage(s_img, s_pos);
}
else if (font.hasShadow()) {
// add margin
Image img = bmp.ConvertToImage();
if (!img.HasAlpha()) set_alpha(img, 0);
int margin = lround(font.shadow_blur() * s_scale);
int s_width = img.GetWidth() + 2 * margin, s_height = img.GetHeight() + 2 * margin;
int x_end = s_width - margin;
int y_end = s_height - margin;
wxImage s_img(s_width, s_height, false);
s_img.InitAlpha();
// convert to shadow color
Byte* s_data = s_img.GetData();
Byte* s_alpha = s_img.GetAlpha(), *alpha = img.GetAlpha();
Color color = font.shadow_color();
unsigned char r = color.Red();
unsigned char g = color.Green();
unsigned char b = color.Blue();
unsigned char a = color.Alpha();
for (int y = 0 ; y < s_height ; ++y) {
for (int x = 0 ; x < s_width ; ++x) {
s_data[0] = r;
s_data[1] = g;
s_data[2] = b;
s_data += 3;
if (margin <= x && x < x_end && margin <= y && y < y_end) {
s_alpha[0] = alpha[0] * a / 255;
alpha += 1;
}
else {
s_alpha[0] = 0;
}
s_alpha += 1;
}
}
// add blur
for (int i = 0 ; i < margin ; ++i) {
blur_image_alpha(s_img, 3);
}
// draw
int blur_radius = lround(font.shadow_blur() * s_scale);
Image s_img = make_stroke_image(bmp.ConvertToImage(), font.shadow_color(), 0, blur_radius);
RealSize s_size = dc.trInvS(RealSize(s_img));
RealPoint s_pos(bmp_pos.x - (s_size.width - bmp_size.width)/2, bmp_pos.y - (s_size.height - bmp_size.height)/2);
dc.DrawImage(s_img, s_pos + RealPoint(font.shadow_displacement_x(), font.shadow_displacement_y()) * scale);
RealSize s_displacement = dc.trInvS(RealSize(font.shadow_displacement_x, font.shadow_displacement_y) * s_scale);
dc.DrawImage(s_img, s_pos + s_displacement);
}
bmps.push_back(std::move(bmp));
bmp_sizes.push_back(bmp_size);
+20
View File
@@ -320,6 +320,26 @@ bool ResizeImage::operator == (const GeneratedImage& that) const {
&& height == that2->height;
}
// ----------------------------------------------------------------------------- : StrokeImage
Image StrokeImage::generate(const Options& opt) {
// create enlarged image
Image base_img = base_image->generate(opt);
Image s_img = make_stroke_image(base_img, color, radius, blur);
if (include_image) {
int offset = radius + blur;
s_img.Paste(base_img, offset, offset, wxIMAGE_ALPHA_BLEND_COMPOSE);
}
return s_img;
}
bool StrokeImage::operator == (const GeneratedImage& that) const {
const StrokeImage* that2 = dynamic_cast<const StrokeImage*>(&that);
return that2 && *base_image == *that2->base_image
&& radius == that2->radius
&& blur == that2->blur
&& color == that2->color;
}
// ----------------------------------------------------------------------------- : BleedEdgedImage
Image BleedEdgedImage::generate(const Options& opt) {
+17
View File
@@ -318,6 +318,23 @@ private:
double offset_x, offset_y;
};
// ----------------------------------------------------------------------------- : StrokeImage
/// Create a stroke effect that goes around an image
class StrokeImage : public GeneratedImage {
public:
inline StrokeImage(const GeneratedImageP& base_image, int radius, int blur, Color color, bool include_image)
: base_image(base_image), radius(radius), blur(blur), color(color), include_image(include_image)
{}
Image generate(const Options& opt) override;
bool operator == (const GeneratedImage& that) const override;
private:
GeneratedImageP base_image;
int radius, blur;
Color color;
bool include_image;
};
// ----------------------------------------------------------------------------- : BleedEdgedImage
/// Add a crude bleed edge to an image
+3
View File
@@ -108,6 +108,9 @@ Byte thicken_pixel_alpha_7x7(std::vector<Byte> in, int i, int x, int y, int widt
// Thicken the alpha channel of an image
void thicken_image_alpha(Image& img, int radius);
// Create a stroke effect that goes around an image
Image make_stroke_image(Image& img, Color stroke_color, int stroke_radius, int blur_radius = 0);
// ----------------------------------------------------------------------------- : Combining
/// Ways in which images can be combined, similair to what Photoshop supports
+44
View File
@@ -362,6 +362,50 @@ void downsample_to_alpha(Bitmap& bmp_in, Image& img_out) {
delete[] temp;
}
Image make_stroke_image(Image& img, Color stroke_color, int stroke_radius, int blur_radius) {
stroke_radius = max(0,min(100,stroke_radius));
blur_radius = max(0,min(100,blur_radius));
if (!img.HasAlpha()) set_alpha(img, 255);
int margin = blur_radius + stroke_radius;
int s_width = img.GetWidth() + 2 * margin, s_height = img.GetHeight() + 2 * margin;
int x_end = s_width - margin;
int y_end = s_height - margin;
wxImage s_img(s_width, s_height, false);
s_img.InitAlpha();
// convert to stroke color
Byte* s_data = s_img.GetData();
Byte* s_alpha = s_img.GetAlpha(), *alpha = img.GetAlpha();
unsigned char r = stroke_color.Red();
unsigned char g = stroke_color.Green();
unsigned char b = stroke_color.Blue();
unsigned char a = stroke_color.Alpha();
for (int y = 0 ; y < s_height ; ++y) {
for (int x = 0 ; x < s_width ; ++x) {
s_data[0] = r;
s_data[1] = g;
s_data[2] = b;
s_data += 3;
if (margin <= x && x < x_end && margin <= y && y < y_end) {
s_alpha[0] = alpha[0] * a / 255;
alpha += 1;
}
else {
s_alpha[0] = 0;
}
s_alpha += 1;
}
}
// add stroke effect
for (int i = 0 ; i < stroke_radius ; ++i) {
thicken_image_alpha(s_img, 1);
}
// add blur
for (int i = 0 ; i < blur_radius ; ++i) {
blur_image_alpha(s_img, 3);
}
return s_img;
}
// ----------------------------------------------------------------------------- : Coloring symbol images
RGB recolor(RGB x, RGB cr, RGB cg, RGB cb, RGB cw) {
+7 -25
View File
@@ -47,40 +47,22 @@ void draw_resampled_text(DC& dc, const String& text, const RealPoint& pos, const
downsample_to_alpha(buffer, img);
// if there is no stroke effect, just add blur and draw
if (stroke_radius == 0) {
if (blur_radius == 0) {
if (color.Alpha() != 255) {
set_alpha(img, color.Alpha() / 255.);
}
if (blur_radius > 0) {
Image s_img(w + 2*blur_radius, h + 2*blur_radius, false);
set_alpha(s_img, 0);
s_img.Paste(img, blur_radius, blur_radius, wxIMAGE_ALPHA_BLEND_COMPOSE);
for (int i = 0 ; i < blur_radius ; ++i) {
blur_image_alpha(s_img, 3);
}
fill_image(s_img, color);
dc.DrawBitmap(s_img, xi-blur_radius, yi-blur_radius);
}
else {
fill_image(img, color);
dc.DrawBitmap(img, xi, yi);
}
else {
Image s_img = make_stroke_image(img, color, 0, blur_radius);
dc.DrawBitmap(s_img, xi-blur_radius, yi-blur_radius);
}
// otherwise add stroke effect
}
// otherwise add stroke effect, add copy of text on top, draw
else {
int radius = blur_radius + stroke_radius;
Image s_img(w + 2*radius, h + 2*radius, false);
set_alpha(s_img, 0);
s_img.Paste(img, radius, radius, wxIMAGE_ALPHA_BLEND_COMPOSE);
for (int i = 0 ; i < stroke_radius ; ++i) {
thicken_image_alpha(s_img, 1);
}
for (int i = 0 ; i < blur_radius ; ++i) {
blur_image_alpha(s_img, 3);
}
if (stroke_color.Alpha() != 255) {
set_alpha(s_img, stroke_color.Alpha() / 255.);
}
fill_image(s_img, stroke_color);
Image s_img = make_stroke_image(img, stroke_color, stroke_radius, blur_radius);
if (stroke_color != color) {
fill_image(img, color);
if (color.Alpha() != 255) {
+10
View File
@@ -172,6 +172,15 @@ SCRIPT_FUNCTION(enlarge) {
return make_intrusive<EnlargeImage>(input, border_size);
}
SCRIPT_FUNCTION(add_stroke_effect) {
SCRIPT_PARAM_C(GeneratedImageP, input);
SCRIPT_PARAM(Color, color);
SCRIPT_PARAM(int, radius);
SCRIPT_PARAM_DEFAULT(int, blur, 0);
SCRIPT_PARAM_DEFAULT(bool, include_image, true);
return make_intrusive<StrokeImage>(input, radius, blur, color, include_image);
}
SCRIPT_FUNCTION(add_bleed_edge) {
SCRIPT_PARAM_C(GeneratedImageP, input);
SCRIPT_PARAM_DEFAULT(double, horizontal_size, -1.0);
@@ -308,6 +317,7 @@ void init_script_image_functions(Context& ctx) {
ctx.setVariable(_("invert_image"), script_invert_image);
ctx.setVariable(_("recolor_image"), script_recolor_image);
ctx.setVariable(_("enlarge"), script_enlarge);
ctx.setVariable(_("add_stroke_effect"),script_add_stroke_effect);
ctx.setVariable(_("add_bleed_edge"), script_add_bleed_edge);
ctx.setVariable(_("resize_image"), script_resize_image);
ctx.setVariable(_("crop"), script_crop);
+2 -1
View File
@@ -221,7 +221,8 @@ void RotatedDC::DrawText(const String& text, const RealPoint& pos, Color color,
void RotatedDC::DrawTextWithShadowOrStroke(const String& text, const Font& font, const RealPoint& pos, double scale, double stretch) {
double s_scale = scale * dc.GetFont().GetPointSize() / text_scaling / 15.;
if (font.hasShadow() && !font.hasStroke()) {
DrawText(text, pos + RealSize(font.shadow_displacement_x, font.shadow_displacement_y) * scale, font.shadow_color, lround(font.shadow_blur * s_scale), Color(0,0,0), 0, stretch);
RealSize shadow_displacement = trInvS(RealSize(font.shadow_displacement_x, font.shadow_displacement_y) * s_scale);
DrawText(text, pos + shadow_displacement, font.shadow_color, lround(font.shadow_blur * s_scale), Color(0,0,0), 0, stretch);
}
DrawText(text, pos, font.color, lround(font.stroke_blur * s_scale), font.stroke_color, lround(font.stroke_radius * s_scale), stretch);
}