Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Script: Added resize_image and overlay function #164

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/function/index.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ 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:enlarge]] Enlarge an image by putting a border around it.
| [[fun:crop]] Crop an image, giving only a small subset of it.
| [[fun:overlay]] Overlay an image over another.
| [[fun:resize_image]] Resize an image.
| [[fun:flip_horizontal]] Flip an image horizontally.
| [[fun:flip_vertical]] Flip an image vertically.
| [[fun:rotate_image]] Rotate an image.
Expand Down
20 changes: 20 additions & 0 deletions doc/function/overlay.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Function: overlay

--Usage--
> overlay(image1: image, image2: image, offset_x: x_position_to_overlay_image, offset_y: y_position_to_overlay_image)

Overlay an image over another, taking the second image's alpha channel into account.

--Parameters--
! Parameter Type Description
| @image1@ [[type:image]] Base image
| @image2@ [[type:image]] Image to overlay over the base image
| @offset_x@ [[type:double]] Offset of the overlayed image, horizontally
| @offset_y@ [[type:double]] Offset of the overlayed image, vertically

--Examples--
> combine_blend(image1: "image1.png", image2: "image2.png", combine: "add") == [[Image]]
>>> combine_blend(image1: <img src="image1.png" alt='"image1.png"' style="border:1px solid black;vertical-align:middle;margin:1px;" />, image2: <img src="image2.png" alt='"image2.png"' style="border:1px solid black;vertical-align:middle;margin:1px;" />, combine: "add") == <img src="image_combine_blend.png" alt='"image_combine_blend.png"' style="border:1px solid black;vertical-align:middle;margin:1px;" />

--See also--
| [[fun:crop]] Crop an image, giving only a small subset of it.
15 changes: 15 additions & 0 deletions doc/function/resize_image.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Function: resize_image

DOC_MSE_VERSION: since 0.3.9

--Usage--
> resize_image(input: image, height: height_of_new_image, width: width_of_new_image)

Resize an image using bilinear filtering.

--Parameters--
! Parameter Type Description
| @input@ [[type:image]] Image to resize.
| @height@ [[type:double]] Height of the resulting image
| @width@ [[type:double]] Width of the resulting image
| @mode@ [[type:resize_mode]] Resize mode to use (optional), default is nearest
17 changes: 17 additions & 0 deletions doc/type/resize_mode.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Enumeration: resize mode type

This specifies how an image is to be resized.

--Script syntax--
In scripts, resize modes are stored as a string.

--Possible values--
! Value Description
| @nearest@ No resampling is done, use the nearest pixel value.
| @bilinear@ Pixels are linearly interpolated.
| @bicubic@ Use [[https://en.wikipedia.org/wiki/Bicubic_interpolation|bicubic interpolation]].
| @box average@ Use surrounding pixels to calculate an average that will be used for new pixels. Typically used when reducing the size of an image.

--Examples--
> resize_image(..., width: ..., height: ..., mode: "bilinear")

78 changes: 78 additions & 0 deletions src/gfx/generated_image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,64 @@ bool CombineBlendImage::operator == (const GeneratedImage& that) const {
&& image_combine == that2->image_combine;
}

// ----------------------------------------------------------------------------- : OverlayImage

Image OverlayImage::generate(const Options& opt) const {
Image img = image1->generate(opt);
Image img2 = image2->generate(opt);
if (img.GetWidth() < img2.GetWidth() + offset_x || img.GetHeight() < img2.GetHeight() + offset_y)
throw ScriptError(_("Overlayed image is out of bounds"));

int off_x = offset_x, off_y = offset_y;
int w1 = img.GetWidth(), h1 = img.GetHeight(); // original image size
int w2 = img2.GetWidth(), h2 = img2.GetHeight(); // overlay image size
Byte* data1 = img.GetData(), *data2 = img2.GetData();
Byte* alpha1 = img.GetAlpha(), *alpha2 = img2.GetAlpha();

// if image2 has alpha, take it into account
if (alpha2) {
for (int x = 0; x < w2; ++x) {
for (int y = 0; y < h2; ++y) {
Byte a = 255 - alpha2[x + y * w2];
for (int b = 0; b < 3; ++b) { // each pixel is 3 bytes
Byte& d1 = data1[((y + off_y) * w1 + x + off_x) * 3 + b];
Byte d2 = data2[(y * w2 + x) * 3 + b];
d1 = (d1 * a + d2 * (255 - a)) / 255; // copied from mask_blend()
}
}
}

if (alpha1) {
for (int x = 0; x < w2; ++x) {
for (int y = 0; y < h2; ++y) {
int idx1 = (y + off_y) * w1 + x + off_x;
alpha1[idx1] = max(alpha1[idx1], alpha2[x + y * w2]);
}
}
}
// otherwise, we can copy the data directly
} else {
for (int y = 0; y < h2; ++y) {
memcpy(data1 + 3 * ((y + off_y) * w1 + off_x), data2 + 3 * y * w2, 3 * w2); // copy a line
}
if (alpha1) {
for (int y = 0; y < h2; ++y) {
memset(alpha1 + (y + off_y) * w1 + off_x, w2, 255);
}
}
}

return img;
}
ImageCombine OverlayImage::combine() const {
return image1->combine();
}
bool OverlayImage::operator == (const GeneratedImage& that) const {
const OverlayImage* that2 = dynamic_cast<const OverlayImage*>(&that);
return that2 && image1 == that2->image1 && image2 == that2->image2
&& offset_x == that2->offset_x && offset_y == that2->offset_y;
}

// ----------------------------------------------------------------------------- : SetMaskImage

Image SetMaskImage::generate(const Options& opt) const {
Expand Down Expand Up @@ -316,6 +374,26 @@ bool CropImage::operator == (const GeneratedImage& that) const {
&& offset_x == that2->offset_x && offset_y == that2->offset_y;
}

// ----------------------------------------------------------------------------- : ResizeImage

IMPLEMENT_REFLECTION_ENUM(wxImageResizeQuality) {
VALUE_N("nearest", wxIMAGE_QUALITY_NEAREST);
VALUE_N("bilinear", wxIMAGE_QUALITY_BILINEAR);
VALUE_N("bicubic", wxIMAGE_QUALITY_BICUBIC);
VALUE_N("box average", wxIMAGE_QUALITY_BOX_AVERAGE);
}

Image ResizeImage::generate(const Options& opt) const {
return image->generate(opt).Rescale((int)width, (int)height, resize_quality);
}
bool ResizeImage::operator == (const GeneratedImage& that) const {
const ResizeImage* that2 = dynamic_cast<const ResizeImage*>(&that);
return that2 && *image == *that2->image
&& width == that2->width
&& height == that2->height
&& resize_quality == that2->resize_quality;
}

// ----------------------------------------------------------------------------- : DropShadowImage

/// Preform a gaussian blur, from the image in of w*h bytes to out
Expand Down
32 changes: 32 additions & 0 deletions src/gfx/generated_image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@ class CombineBlendImage : public GeneratedImage {
ImageCombine image_combine;
};

// ----------------------------------------------------------------------------- : OverlayImage

/// Overlay an image over another
class OverlayImage : public GeneratedImage {
public:
inline OverlayImage(const GeneratedImageP& image1, const GeneratedImageP& image2, double offset_x, double offset_y)
: image1(image1), image2(image2), offset_x(offset_x), offset_y(offset_y)
{}
Image generate(const Options& opt) const override;
ImageCombine combine() const override;
bool operator == (const GeneratedImage& that) const override;
bool local() const override { return image1->local() && image2->local(); }
private:
GeneratedImageP image1, image2;
double offset_x, offset_y;
};

// ----------------------------------------------------------------------------- : SetMaskImage

/// Change the alpha channel of an image
Expand Down Expand Up @@ -302,6 +319,21 @@ class CropImage : public SimpleFilterImage {
double offset_x, offset_y;
};

// ----------------------------------------------------------------------------- : ResizeImage

/// Resize an image
class ResizeImage : public SimpleFilterImage {
public:
inline ResizeImage(const GeneratedImageP& image, double width, double height, wxImageResizeQuality resize_quality)
: SimpleFilterImage(image), width(width), height(height), resize_quality(resize_quality)
{}
Image generate(const Options& opt) const override;
bool operator == (const GeneratedImage& that) const override;
private:
double width, height;
wxImageResizeQuality resize_quality;
};

// ----------------------------------------------------------------------------- : DropShadowImage

/// Add a drop shadow to an image
Expand Down
24 changes: 24 additions & 0 deletions src/script/functions/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <render/symbol/filter.hpp>

void parse_enum(const String&, ImageCombine& out);
void parse_enum(const String&, wxImageResizeQuality& out);

// ----------------------------------------------------------------------------- : Utility

Expand Down Expand Up @@ -54,6 +55,14 @@ SCRIPT_FUNCTION(combine_blend) {
return make_intrusive<CombineBlendImage>(image1, image2, image_combine);
}

SCRIPT_FUNCTION(overlay) {
SCRIPT_PARAM(GeneratedImageP, image1);
SCRIPT_PARAM(GeneratedImageP, image2);
SCRIPT_PARAM(int, offset_x);
SCRIPT_PARAM(int, offset_y);
return make_intrusive<OverlayImage>(image1, image2, offset_x, offset_y);
}

SCRIPT_FUNCTION(set_mask) {
SCRIPT_PARAM(GeneratedImageP, image);
SCRIPT_PARAM(GeneratedImageP, mask);
Expand Down Expand Up @@ -113,6 +122,19 @@ SCRIPT_FUNCTION(crop) {
return make_intrusive<CropImage>(input, width, height, offset_x, offset_y);
}

SCRIPT_FUNCTION(resize_image) {
SCRIPT_PARAM_C(GeneratedImageP, input);
SCRIPT_PARAM(int, width);
SCRIPT_PARAM(int, height);
wxImageResizeQuality resize_quality;
SCRIPT_OPTIONAL_PARAM(String, mode) {
parse_enum(mode, resize_quality);
} else {
resize_quality = wxIMAGE_QUALITY_NEAREST;
}
return make_intrusive<ResizeImage>(input, width, height, resize_quality);
}

SCRIPT_FUNCTION(flip_horizontal) {
SCRIPT_PARAM_C(GeneratedImageP, input);
return make_intrusive<FlipImageHorizontal>(input);
Expand Down Expand Up @@ -213,6 +235,7 @@ void init_script_image_functions(Context& ctx) {
ctx.setVariable(_("linear_blend"), script_linear_blend);
ctx.setVariable(_("masked_blend"), script_masked_blend);
ctx.setVariable(_("combine_blend"), script_combine_blend);
ctx.setVariable(_("overlay"), script_overlay);
ctx.setVariable(_("set_mask"), script_set_mask);
ctx.setVariable(_("set_alpha"), script_set_alpha);
ctx.setVariable(_("set_combine"), script_set_combine);
Expand All @@ -221,6 +244,7 @@ void init_script_image_functions(Context& ctx) {
ctx.setVariable(_("recolor_image"), script_recolor_image);
ctx.setVariable(_("enlarge"), script_enlarge);
ctx.setVariable(_("crop"), script_crop);
ctx.setVariable(_("resize_image"), script_resize_image);
ctx.setVariable(_("flip_horizontal"), script_flip_horizontal);
ctx.setVariable(_("flip_vertical"), script_flip_vertical);
ctx.setVariable(_("rotate"), script_rotate);
Expand Down
2 changes: 2 additions & 0 deletions tools/website/drupal/mse-drupal-modules/highlight.inc
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ $built_in_functions = array(
'linear_blend' =>'',
'masked_blend' =>'',
'combine_blend' =>'',
'overlay' =>'',
'set_mask' =>'',
'set_alpha' =>'',
'set_combine' =>'',
Expand All @@ -82,6 +83,7 @@ $built_in_functions = array(
'recolor_image' =>'',
'enlarge' =>'',
'crop' =>'',
'resize_image' =>'',
'flip_horizontal' =>'',
'flip_vertical' =>'',
'rotate' =>'',
Expand Down