diff --git a/doc/function/index.txt b/doc/function/index.txt
index 700fdf7d7..a05e1a41f 100644
--- a/doc/function/index.txt
+++ b/doc/function/index.txt
@@ -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.
diff --git a/doc/function/overlay.txt b/doc/function/overlay.txt
new file mode 100644
index 000000000..d8a00de50
--- /dev/null
+++ b/doc/function/overlay.txt
@@ -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:
, image2:
, combine: "add") ==
+
+--See also--
+| [[fun:crop]] Crop an image, giving only a small subset of it.
diff --git a/doc/function/resize_image.txt b/doc/function/resize_image.txt
new file mode 100644
index 000000000..289e74d98
--- /dev/null
+++ b/doc/function/resize_image.txt
@@ -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
diff --git a/doc/type/resize_mode.txt b/doc/type/resize_mode.txt
new file mode 100644
index 000000000..97cc01503
--- /dev/null
+++ b/doc/type/resize_mode.txt
@@ -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")
+
diff --git a/src/gfx/generated_image.cpp b/src/gfx/generated_image.cpp
index 5465c16f0..42a578afe 100644
--- a/src/gfx/generated_image.cpp
+++ b/src/gfx/generated_image.cpp
@@ -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(&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 {
@@ -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(&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
diff --git a/src/gfx/generated_image.hpp b/src/gfx/generated_image.hpp
index fd14ccd12..2f04e6dc0 100644
--- a/src/gfx/generated_image.hpp
+++ b/src/gfx/generated_image.hpp
@@ -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
@@ -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
diff --git a/src/script/functions/image.cpp b/src/script/functions/image.cpp
index 3e9caf65d..0dc3cf3b2 100644
--- a/src/script/functions/image.cpp
+++ b/src/script/functions/image.cpp
@@ -20,6 +20,7 @@
#include
void parse_enum(const String&, ImageCombine& out);
+void parse_enum(const String&, wxImageResizeQuality& out);
// ----------------------------------------------------------------------------- : Utility
@@ -54,6 +55,14 @@ SCRIPT_FUNCTION(combine_blend) {
return make_intrusive(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(image1, image2, offset_x, offset_y);
+}
+
SCRIPT_FUNCTION(set_mask) {
SCRIPT_PARAM(GeneratedImageP, image);
SCRIPT_PARAM(GeneratedImageP, mask);
@@ -113,6 +122,19 @@ SCRIPT_FUNCTION(crop) {
return make_intrusive(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(input, width, height, resize_quality);
+}
+
SCRIPT_FUNCTION(flip_horizontal) {
SCRIPT_PARAM_C(GeneratedImageP, input);
return make_intrusive(input);
@@ -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);
@@ -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);
diff --git a/tools/website/drupal/mse-drupal-modules/highlight.inc b/tools/website/drupal/mse-drupal-modules/highlight.inc
index c34c41c41..90a69df58 100644
--- a/tools/website/drupal/mse-drupal-modules/highlight.inc
+++ b/tools/website/drupal/mse-drupal-modules/highlight.inc
@@ -74,6 +74,7 @@ $built_in_functions = array(
'linear_blend' =>'',
'masked_blend' =>'',
'combine_blend' =>'',
+ 'overlay' =>'',
'set_mask' =>'',
'set_alpha' =>'',
'set_combine' =>'',
@@ -82,6 +83,7 @@ $built_in_functions = array(
'recolor_image' =>'',
'enlarge' =>'',
'crop' =>'',
+ 'resize_image' =>'',
'flip_horizontal' =>'',
'flip_vertical' =>'',
'rotate' =>'',