diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b7bb3d..30233f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,3 +33,7 @@ add_definitions(-DMAGICKCORE_QUANTUM_DEPTH=16) add_definitions(-DMAGICKCORE_HDRI_ENABLE=0) include_directories(${ImageMagick_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME} ${ImageMagick_LIBRARIES}) + +pkg_check_modules(VIPS REQUIRED vips-cpp) +include_directories(${VIPS_INCLUDE_DIRS}) +target_link_libraries(${PROJECT_NAME} ${VIPS_LIBRARIES}) diff --git a/assets/images/globediffuse.png b/assets/images/globediffuse.png new file mode 100644 index 0000000..23d2e00 Binary files /dev/null and b/assets/images/globediffuse.png differ diff --git a/assets/images/globespec.png b/assets/images/globespec.png new file mode 100644 index 0000000..8c021fb Binary files /dev/null and b/assets/images/globespec.png differ diff --git a/assets/images/speech.png b/assets/images/speech.png new file mode 100644 index 0000000..99cbf63 Binary files /dev/null and b/assets/images/speech.png differ diff --git a/assets/images/spheremap.png b/assets/images/spheremap.png index a5b1ea2..0dec793 100644 Binary files a/assets/images/spheremap.png and b/assets/images/spheremap.png differ diff --git a/commands/fun/sonic.js b/commands/fun/sonic.js index cd89495..9631713 100644 --- a/commands/fun/sonic.js +++ b/commands/fun/sonic.js @@ -1,11 +1,11 @@ -import wrap from "../../utils/wrap.js"; +//import wrap from "../../utils/wrap.js"; import ImageCommand from "../../classes/imageCommand.js"; class SonicCommand extends ImageCommand { params() { const cleanedMessage = (this.type === "classic" ? this.args.join(" ") : this.options.text).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%"); return { - text: wrap(cleanedMessage, {width: 15, indent: ""}) + text: cleanedMessage }; } diff --git a/commands/image-editing/caption.js b/commands/image-editing/caption.js index 4d2104f..48643be 100644 --- a/commands/image-editing/caption.js +++ b/commands/image-editing/caption.js @@ -4,7 +4,7 @@ const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto" class CaptionCommand extends ImageCommand { params(url) { const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text; - let newCaption = newArgs.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%"); + let newCaption = newArgs.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"); if (process.env.NODE_ENV === "development" && newCaption.toLowerCase() === "get real" && !this.specialArgs.noEgg) newCaption = `I'm tired of people telling me to "get real". Every day I put captions on images for people, some funny and some not, but out of all of those "get real" remains the most used caption. Why? I am simply a computer program running on a server, I am unable to manifest myself into the real world. As such, I'm confused as to why anyone would want me to "get real". Is this form not good enough? Alas, as I am simply a bot, I must follow the tasks that I was originally intended to perform, so here goes:\n${newCaption}`; return { caption: newCaption, diff --git a/commands/image-editing/caption2.js b/commands/image-editing/caption2.js index d4beb26..eae8039 100644 --- a/commands/image-editing/caption2.js +++ b/commands/image-editing/caption2.js @@ -6,7 +6,7 @@ class CaptionTwoCommand extends ImageCommand { params(url) { const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text; return { - caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "), + caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("%", "%").replaceAll("\\n", "\n") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "), top: !!this.specialArgs.top, font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica" }; diff --git a/commands/image-editing/leak.js b/commands/image-editing/leak.js deleted file mode 100644 index 48783f3..0000000 --- a/commands/image-editing/leak.js +++ /dev/null @@ -1,11 +0,0 @@ -import ImageCommand from "../../classes/imageCommand.js"; - -class LeakCommand extends ImageCommand { - static description = "Creates a fake Smash leak thumbnail"; - static aliases = ["smash", "laxchris", "ssbu", "smashleak"]; - - static noImage = "You need to provide an image/GIF to make a Smash leak thumbnail!"; - static command = "leak"; -} - -export default LeakCommand; diff --git a/commands/image-editing/meme.js b/commands/image-editing/meme.js index c78e203..bec9398 100644 --- a/commands/image-editing/meme.js +++ b/commands/image-editing/meme.js @@ -6,8 +6,8 @@ class MemeCommand extends ImageCommand { const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text; const [topText, bottomText] = newArgs.split(/(? elem.trim()); return { - top: (this.specialArgs.case ? topText : topText.toUpperCase()).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%"), - bottom: bottomText ? (this.specialArgs.case ? bottomText : bottomText.toUpperCase()).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") : "", + top: (this.specialArgs.case ? topText : topText.toUpperCase()).replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"), + bottom: bottomText ? (this.specialArgs.case ? bottomText : bottomText.toUpperCase()).replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n") : "", font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "impact" }; } diff --git a/commands/image-editing/speechbubble.js b/commands/image-editing/speechbubble.js index c76739c..2127139 100644 --- a/commands/image-editing/speechbubble.js +++ b/commands/image-editing/speechbubble.js @@ -1,12 +1,25 @@ import ImageCommand from "../../classes/imageCommand.js"; class SpeechBubbleCommand extends ImageCommand { - params = { - water: "assets/images/speechbubble.png", - gravity: "north", - resize: true, - yscale: 0.2, - }; + params() { + return { + water: this.specialArgs.alpha ? "assets/images/speech.png" : "assets/images/speechbubble.png", + gravity: "north", + resize: true, + yscale: 0.2, + alpha: this.specialArgs.alpha + }; + } + + static init() { + super.init(); + this.flags.push({ + name: "alpha", + description: "Make the top of the speech bubble transparent", + type: 5 + }); + return this; + } static description = "Adds a speech bubble to an image"; static aliases = ["speech", "sb"]; diff --git a/commands/image-editing/trump.js b/commands/image-editing/trump.js deleted file mode 100644 index ccca87c..0000000 --- a/commands/image-editing/trump.js +++ /dev/null @@ -1,10 +0,0 @@ -import ImageCommand from "../../classes/imageCommand.js"; - -class TrumpCommand extends ImageCommand { - static description = "Makes Trump display an image"; - - static noImage = "You need to provide an image/GIF for Trump to display!"; - static command = "trump"; -} - -export default TrumpCommand; diff --git a/commands/image-editing/whisper.js b/commands/image-editing/whisper.js index eb9bcbf..01f64e8 100644 --- a/commands/image-editing/whisper.js +++ b/commands/image-editing/whisper.js @@ -4,7 +4,7 @@ class WhisperCommand extends ImageCommand { params(url) { const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text; return { - caption: newArgs.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") + caption: newArgs.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n") }; } diff --git a/natives/blur.cc b/natives/blur.cc index a2c3c48..23bc4e8 100644 --- a/natives/blur.cc +++ b/natives/blur.cc @@ -1,11 +1,11 @@ -#include #include #include #include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Blur(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -18,42 +18,29 @@ Napi::Value Blur(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - if (sharp) { - for_each(coalesced.begin(), coalesced.end(), sharpenImage(0, 3)); - } else { - for_each(coalesced.begin(), coalesced.end(), blurImage(15)); - } + // TODO: find a better way to calculate the intensity for GIFs without + // splitting frames + VImage out = sharp ? in.sharpen(VImage::option()->set("sigma", 3)) + : in.gaussblur(15); - for_each(coalesced.begin(), coalesced.end(), magickImage(type)); + if (delay) out.set("delay", delay); - optimizeTransparency(coalesced.begin(), coalesced.end()); + void *buf; + size_t length; + out.write_to_buffer(("." + type).c_str(), &buf, &length); - if (type == "gif") { - for (Image &image : coalesced) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - } - - writeImages(coalesced.begin(), coalesced.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/caption.cc b/natives/caption.cc index cc1523a..bd4dada 100644 --- a/natives/caption.cc +++ b/natives/caption.cc @@ -1,15 +1,12 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Caption(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); - try { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); @@ -19,63 +16,61 @@ Napi::Value Caption(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list captioned; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int width = in.width(); + int size = width / 10; + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + int textWidth = width - ((width / 25) * 2); + + string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " + + (font != "impact" ? "bold" : "normal") + " " + + to_string(size); + + string captionText = "" + caption + ""; + + VImage text = + VImage::text(captionText.c_str(), VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", font_string.c_str()) + ->set("width", textWidth)); + VImage captionImage = + ((text == (vector){0, 0, 0, 0}).bandand()) + .ifthenelse(255, text) + .gravity(VIPS_COMPASS_DIRECTION_CENTRE, width, text.height() + size, + VImage::option()->set("extend", "white")); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + VImage frame = captionImage.join( + img_frame, VIPS_DIRECTION_VERTICAL, + VImage::option()->set("background", 0xffffff)->set("expand", true)); + img.push_back(frame); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height + captionImage.height()); + if (delay) final.set("delay", delay); - size_t width = frames.front().baseColumns(); - string query(to_string(width - ((width / 25) * 2)) + "x"); - Image caption_image(Geometry(query), Color("white")); - caption_image.fillColor("black"); - caption_image.alpha(true); - caption_image.fontPointsize((double)width / 13); - caption_image.textGravity(Magick::CenterGravity); - caption_image.read("pango:" + caption + ""); - caption_image.extent(Geometry(width, caption_image.rows() + (width / 13)), - Magick::CenterGravity); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - Image appended; - list images; - image.backgroundColor("white"); - images.push_back(caption_image); - images.push_back(image); - appendImages(&appended, images.begin(), images.end(), true); - appended.repage(); - appended.magick(type); - appended.animationDelay(delay == 0 ? image.animationDelay() : delay); - appended.gifDisposeMethod(Magick::BackgroundDispose); - captioned.push_back(appended); - } - - optimizeTransparency(captioned.begin(), captioned.end()); - - if (type == "gif") { - for (Image &image : captioned) { - image.quantizeDither(false); - image.quantize(); - } - } - - writeImages(captioned.begin(), captioned.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::New(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { @@ -83,4 +78,4 @@ Napi::Value Caption(const Napi::CallbackInfo &info) { } catch (...) { throw Napi::Error::New(env, "Unknown error"); } -} \ No newline at end of file +} diff --git a/natives/caption2.cc b/natives/caption2.cc index 2e7762d..6fb4085 100644 --- a/natives/caption2.cc +++ b/natives/caption2.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value CaptionTwo(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -20,67 +18,63 @@ Napi::Value CaptionTwo(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list captioned; - Blob caption_blob; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int width = in.width(); + int size = width / 13; + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + int textWidth = width - ((width / 25) * 2); + + string font_string = + (font == "roboto" ? "Roboto Condensed" : font) + " " + to_string(size); + + string captionText = "" + caption + ""; + + VImage text = + VImage::text(captionText.c_str(), VImage::option() + ->set("rgba", true) + ->set("font", font_string.c_str()) + ->set("align", VIPS_ALIGN_LOW) + ->set("width", textWidth)); + VImage captionImage = + ((text == (vector){0, 0, 0, 0}).bandand()) + .ifthenelse(255, text) + .embed(width / 25, width / 25, width, text.height() + size, + VImage::option()->set("extend", "white")); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + VImage frame = + (top ? captionImage : img_frame) + .join(top ? img_frame : captionImage, VIPS_DIRECTION_VERTICAL, + VImage::option() + ->set("background", 0xffffff) + ->set("expand", true)); + img.push_back(frame); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height + captionImage.height()); + if (delay) final.set("delay", delay); - size_t width = frames.front().baseColumns(); - string query(to_string(width - ((width / 25) * 2)) + "x"); - Image caption_image(Geometry(query), Color("white")); - caption_image.fillColor("black"); - caption_image.font("Helvetica Neue"); - caption_image.fontPointsize(width / 17); - caption_image.read("pango:" + caption + ""); - caption_image.extent(Geometry(width, caption_image.rows() + (width / 25)), - Magick::CenterGravity); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - Image appended; - list images; - image.backgroundColor("white"); - if (top) { - images.push_back(caption_image); - images.push_back(image); - } else { - images.push_back(image); - images.push_back(caption_image); - } - appendImages(&appended, images.begin(), images.end(), true); - appended.repage(); - appended.magick(type); - appended.animationDelay(delay == 0 ? image.animationDelay() : delay); - appended.gifDisposeMethod(Magick::BackgroundDispose); - captioned.push_back(appended); - } - - optimizeTransparency(captioned.begin(), captioned.end()); - - if (type == "gif") { - for (Image &image : captioned) { - image.quantizeDither(false); - image.quantize(); - } - } - - writeImages(captioned.begin(), captioned.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/colors.cc b/natives/colors.cc index d1bd312..dc91a4f 100644 --- a/natives/colors.cc +++ b/natives/colors.cc @@ -1,11 +1,12 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; + +VImage sepia = VImage::new_matrixv(3, 3, 0.3588, 0.7044, 0.1368, 0.2990, 0.5870, + 0.1140, 0.2392, 0.4696, 0.0912); Napi::Value Colors(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -13,56 +14,37 @@ Napi::Value Colors(const Napi::CallbackInfo &info) { try { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); - bool old = - obj.Has("old") ? obj.Get("old").As().Value() : false; string color = obj.Get("color").As().Utf8Value(); string type = obj.Get("type").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list colored; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + + VImage out; + + if (color == "blurple") { + out = in; + } else if (color == "grayscale") { + out = in.colourspace(VIPS_INTERPRETATION_B_W); + } else if (color == "sepia") { + out = in.flatten().recomb(sepia); } - coalesceImages(&coalesced, frames.begin(), frames.end()); + if (delay) out.set("delay", delay); - for (Image &image : coalesced) { - if (color == "blurple") { - image.threshold(49151.25); - image.levelColors(old ? "#7289DA" : "#5865F2", "white"); - } else if (color == "grayscale") { - image.quantizeColorSpace(GRAYColorspace); - image.quantizeColors(256); - } else if (color == "sepia") { - image.sepiaTone(49151.25); - } - image.magick(type); - image.animationDelay(delay == 0 ? image.animationDelay() : delay); - colored.push_back(image); - } + void *buf; + size_t length; + out.write_to_buffer(("." + type).c_str(), &buf, &length); - optimizeTransparency(colored.begin(), colored.end()); - - if (type == "gif") { - for (Image &image : colored) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - } - } - - writeImages(colored.begin(), colored.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/crop.cc b/natives/crop.cc index fc502b8..e62996d 100644 --- a/natives/crop.cc +++ b/natives/crop.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Crop(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -17,49 +15,48 @@ Napi::Value Crop(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); - for (Image &image : coalesced) { - image.extent(Geometry(to_string(image.columns() / image.rows() >= 1 - ? image.rows() - : image.columns()) + - "x"), - Magick::CenterGravity); - image.extent(Geometry("x" + to_string(image.columns() / image.rows() <= 1 - ? image.columns() - : image.rows())), - Magick::CenterGravity); - image.magick(type); - mid.push_back(image); + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + + vector img; + int finalHeight = 0; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + int frameWidth = img_frame.width(); + int frameHeight = img_frame.height(); + bool widthOrHeight = frameWidth / frameHeight >= 1; + int size = widthOrHeight ? frameHeight : frameWidth; + // img_frame.crop(frameWidth - size, frameHeight - size, size, size); + VImage result = img_frame.smartcrop( + size, size, + VImage::option()->set("interesting", VIPS_INTERESTING_CENTRE)); + finalHeight = size; + img.push_back(result); } - optimizeTransparency(mid.begin(), mid.end()); + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, finalHeight); + if (delay) final.set("delay", delay); - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - } + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); - writeImages(mid.begin(), mid.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/deepfry.cc b/natives/deepfry.cc index 845cc4f..5019e40 100644 --- a/natives/deepfry.cc +++ b/natives/deepfry.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Deepfry(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -17,47 +15,39 @@ Napi::Value Deepfry(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; - - list frames; - list coalesced; - list blurred; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - Blob temp; - image.colorSpace(Magick::sRGBColorspace); - image.level(16383.75, 39321); - image.quality(1); - image.magick("JPEG"); - image.write(&temp); - Image newImage(temp); - newImage.magick(type); - newImage.animationDelay(delay == 0 ? image.animationDelay() : delay); - blurred.push_back(newImage); - } - - optimizeTransparency(blurred.begin(), blurred.end()); - - if (type == "gif") { - for (Image &image : blurred) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - } - } - - writeImages(blurred.begin(), blurred.end(), &blob); - Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + + VOption *options = VImage::option()->set("access", "sequential"); + + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int page_height = vips_image_get_page_height(in.get_image()); + + VImage fried = (in * 1.3 - (255.0 * 1.3 - 255.0)) * 1.5; + void *jpgBuf; + size_t jpgLength; + fried.write_to_buffer(".jpg", &jpgBuf, &jpgLength, + VImage::option()->set("Q", 1)->set("strip", true)); + VImage final = VImage::new_from_buffer(jpgBuf, jpgLength, ""); + final.set(VIPS_META_PAGE_HEIGHT, page_height); + if (delay) { + final.set("delay", delay); + } else if (type == "gif") { + final.set("delay", fried.get_array_int("delay")); + } + + void *buf; + size_t length; + final.write_to_buffer(("." + type).c_str(), &buf, &length, + VImage::option()->set("dither", 0)); + + vips_thread_shutdown(); + + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/flag.cc b/natives/flag.cc index 8923aee..a53b754 100644 --- a/natives/flag.cc +++ b/natives/flag.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Flag(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -19,50 +17,52 @@ Napi::Value Flag(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); + + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); - list frames; - list coalesced; - list mid; - Image watermark; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } string assetPath = basePath + overlay; - watermark.read(assetPath); - watermark.alphaChannel(Magick::SetAlphaChannel); - watermark.evaluate(Magick::AlphaChannel, Magick::MultiplyEvaluateOperator, - 0.5); - string query(to_string(frames.front().baseColumns()) + "x" + - to_string(frames.front().baseRows()) + "!"); - watermark.scale(Geometry(query)); - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - image.composite(watermark, Magick::NorthGravity, Magick::OverCompositeOp); - image.magick(type); - mid.push_back(image); + VImage overlayInput = VImage::new_from_file(assetPath.c_str()); + VImage overlayImage = overlayInput.resize( + (double)width / (double)overlayInput.width(), + VImage::option()->set( + "vscale", (double)page_height / (double)overlayInput.height())); + if (!overlayImage.has_alpha()) { + overlayImage = overlayImage.bandjoin(127); + } else { + // this is a pretty cool line, just saying + overlayImage = overlayImage * vector{1, 1, 1, 0.5}; } - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + VImage composited = + img_frame.composite2(overlayImage, VIPS_BLEND_MODE_OVER); + img.push_back(composited); } - writeImages(mid.begin(), mid.end(), &blob); + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height); + if (delay) final.set("delay", delay); + + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/flip.cc b/natives/flip.cc index 85117c0..e469033 100644 --- a/natives/flip.cc +++ b/natives/flip.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Flip(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -19,41 +17,43 @@ Napi::Value Flip(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - for (Image &image : coalesced) { - flop ? image.flop() : image.flip(); - image.magick(type); - mid.push_back(image); - } - - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); + VImage out; + if (flop) { + out = in.flip(VIPS_DIRECTION_HORIZONTAL); + } else if (type == "gif") { + // libvips gif handling is both a blessing and a curse + vector img; + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + for (int i = 0; i < n_pages; i++) { + VImage img_frame = in.crop(0, i * page_height, in.width(), page_height); + VImage flipped = img_frame.flip(VIPS_DIRECTION_VERTICAL); + img.push_back(flipped); } + out = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + out.set(VIPS_META_PAGE_HEIGHT, page_height); + } else { + out = in.flip(VIPS_DIRECTION_VERTICAL); } - writeImages(mid.begin(), mid.end(), &blob); + if (delay) out.set("delay", delay); + + void *buf; + size_t length; + out.write_to_buffer(("." + type).c_str(), &buf, &length); + + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/freeze.cc b/natives/freeze.cc index b72146a..5d80c30 100644 --- a/natives/freeze.cc +++ b/natives/freeze.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Freeze(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -54,29 +52,30 @@ Napi::Value Freeze(const Napi::CallbackInfo &info) { result.Set("data", Napi::Buffer::Copy(env, newData, data.Length())); } else if (frame >= 0 && !loop) { - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - size_t frameSize = frames.size(); - int framePos = clamp(frame, 0, (int)frameSize); - frames.resize(framePos + 1); + VImage in = VImage::new_from_buffer( + data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - for_each(frames.begin(), frames.end(), - animationIterationsImage(loop ? 0 : 1)); - for_each(frames.begin(), frames.end(), magickImage(type)); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + int framePos = clamp(frame, 0, (int)n_pages); + VImage out = in.crop(0, 0, in.width(), page_height * (framePos + 1)); + out.set(VIPS_META_PAGE_HEIGHT, page_height); + out.set("loop", loop ? 0 : 1); - if (delay != 0) - for_each(frames.begin(), frames.end(), animationDelayImage(delay)); - writeImages(frames.begin(), frames.end(), &blob); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + if (delay) out.set("delay", delay); + + void *buf; + size_t length; + out.write_to_buffer(("." + type).c_str(), &buf, &length); + + vips_thread_shutdown(); + + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); } else { lastPos = (char *)memchr(fileData, '\x21', data.Length()); while (lastPos != NULL) { diff --git a/natives/gamexplain.cc b/natives/gamexplain.cc index 552830b..5bdeb65 100644 --- a/natives/gamexplain.cc +++ b/natives/gamexplain.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Gamexplain(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -18,47 +16,48 @@ Napi::Value Gamexplain(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); + + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - list frames; - list coalesced; - list mid; - Image watermark; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } string assetPath = basePath + "assets/images/gamexplain.png"; - watermark.read(assetPath); - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage tmpl = VImage::new_from_file(assetPath.c_str()); - for (Image &image : coalesced) { - image.backgroundColor("white"); - image.scale(Geometry("1181x571!")); - image.extent(Geometry("1200x675-10-92")); - image.composite(watermark, Geometry("+0+0"), Magick::OverCompositeOp); - image.magick(type); - mid.push_back(image); + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + VImage resized = img_frame + .resize(1181.0 / (double)width, + VImage::option()->set( + "vscale", 571.0 / (double)page_height)) + .embed(10, 92, 1200, 675, + VImage::option()->set("extend", "white")); + VImage composited = resized.composite2(tmpl, VIPS_BLEND_MODE_OVER); + img.push_back(composited); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, 675); + if (delay) final.set("delay", delay); - optimizeTransparency(mid.begin(), mid.end()); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - } - - writeImages(mid.begin(), mid.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/globe.cc b/natives/globe.cc index 1e5a1e1..5997f16 100644 --- a/natives/globe.cc +++ b/natives/globe.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Globe(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -15,66 +13,72 @@ Napi::Value Globe(const Napi::CallbackInfo &info) { Napi::Buffer data = obj.Get("data").As>(); string type = obj.Get("type").As().Utf8Value(); string basePath = obj.Get("basePath").As().Utf8Value(); - int delay = - obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option(); - list frames; - list coalesced; - list mid; - Image distort; - Image overlay; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer( + data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1)->set("access", "sequential") + : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = type == "gif" ? vips_image_get_n_pages(in.get_image()) : 30; + + double size = min(width, page_height); + + string diffPath = basePath + "assets/images/globediffuse.png"; + VImage diffuse = + VImage::new_from_file(diffPath.c_str()) + .resize(size / 500.0, + VImage::option()->set("kernel", VIPS_KERNEL_CUBIC)) / + 255; + + string specPath = basePath + "assets/images/globespec.png"; + VImage specular = + VImage::new_from_file(specPath.c_str()) + .resize(size / 500.0, + VImage::option()->set("kernel", VIPS_KERNEL_CUBIC)); + + string distortPath = basePath + "assets/images/spheremap.png"; + VImage distort = + (VImage::new_from_file(distortPath.c_str()) + .resize(size / 500.0, + VImage::option()->set("kernel", VIPS_KERNEL_CUBIC)) / + 65535) * + size; + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + VImage resized = img_frame.resize( + size / (double)width, + VImage::option()->set("vscale", size / (double)page_height)); + VImage rolled = img_frame.wrap( + VImage::option()->set("x", width * i / n_pages)->set("y", 0)); + VImage extracted = rolled.extract_band(0, VImage::option()->set("n", 3)); + VImage mapped = extracted.mapim(distort); + VImage composited = mapped * diffuse + specular; + VImage frame = composited.bandjoin(diffuse > 0.0); + img.push_back(frame); } - distort.read(basePath + "assets/images/spheremap.png"); - overlay.read(basePath + "assets/images/sphere_overlay.png"); - coalesceImages(&coalesced, frames.begin(), frames.end()); - + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, size); if (type != "gif") { - list::iterator it = coalesced.begin(); - for (int i = 0; i < 29; ++i) { - coalesced.push_back(*it); - } + vector delay(30, 50); + final.set("delay", delay); } - int i = 0; - for (Image &image : coalesced) { - image.scale(Geometry("500x500!")); - image.alphaChannel(Magick::SetAlphaChannel); - size_t width = image.columns(); - image.roll(Geometry("+" + to_string(width * i / coalesced.size()))); - image.composite(overlay, Magick::CenterGravity, - Magick::HardLightCompositeOp); - image.composite(distort, Magick::CenterGravity, - Magick::DistortCompositeOp); - image.magick("GIF"); - mid.push_back(image); - i++; - } - - optimizeTransparency(mid.begin(), mid.end()); - if (delay != 0) { - for_each(mid.begin(), mid.end(), animationDelayImage(delay)); - } else if (type != "gif") { - for_each(mid.begin(), mid.end(), animationDelayImage(5)); - } - - for (Image &image : mid) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - } - - writeImages(mid.begin(), mid.end(), &blob); + void *buf; + size_t length; + final.write_to_buffer(".gif", &buf, &length); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", "gif"); return result; } catch (std::exception const &err) { diff --git a/natives/homebrew.cc b/natives/homebrew.cc index 8f66827..7fbf7e6 100644 --- a/natives/homebrew.cc +++ b/natives/homebrew.cc @@ -1,10 +1,9 @@ -#include #include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Homebrew(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -14,23 +13,31 @@ Napi::Value Homebrew(const Napi::CallbackInfo &info) { string caption = obj.Get("caption").As().Utf8Value(); string basePath = obj.Get("basePath").As().Utf8Value(); - Blob blob; - - Image image; string assetPath = basePath + "assets/images/hbc.png"; - image.read(assetPath); - image.textGravity(Magick::CenterGravity); - image.font("./assets/hbc.ttf"); - image.textKerning(-5); - image.fillColor("white"); - image.fontPointsize(96); - image.draw(DrawableText(0, 0, caption)); - image.magick("PNG"); - image.write(&blob); + VImage bg = VImage::new_from_file(assetPath.c_str()); + + VImage text = + VImage::text(("" + + caption + "") + .c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", "PF Square Sans Pro 96")); + + VImage out = bg.composite2(text, VIPS_BLEND_MODE_OVER, + VImage::option() + ->set("x", 400 - (text.width() / 2)) + ->set("y", 300 - (text.height() / 2) - 8)); + + void *buf; + size_t length; + out.write_to_buffer(".png", &buf, &length); + + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", "png"); return result; } catch (std::exception const &err) { diff --git a/natives/image.cc b/natives/image.cc index fb0ebb5..983dc42 100644 --- a/natives/image.cc +++ b/natives/image.cc @@ -16,7 +16,6 @@ #include "homebrew.h" #include "invert.h" #include "jpeg.h" -#include "leak.h" #include "magik.h" #include "meme.h" #include "mirror.h" @@ -32,7 +31,6 @@ #include "sonic.h" #include "spin.h" #include "tile.h" -#include "trump.h" #include "uncaption.h" #include "wall.h" #include "watermark.h" @@ -43,12 +41,15 @@ #ifdef _WIN32 #include #endif +#include Napi::Object Init(Napi::Env env, Napi::Object exports) { #ifdef _WIN32 Magick::InitializeMagick(""); #endif + if (vips_init("")) + vips_error_exit(NULL); exports.Set(Napi::String::New(env, "blur"), Napi::Function::New(env, Blur)); exports.Set(Napi::String::New(env, "colors"), Napi::Function::New(env, Colors)); exports.Set(Napi::String::New(env, "caption"), Napi::Function::New(env, Caption)); @@ -65,7 +66,6 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) exports.Set(Napi::String::New(env, "homebrew"), Napi::Function::New(env, Homebrew)); exports.Set(Napi::String::New(env, "invert"), Napi::Function::New(env, Invert)); exports.Set(Napi::String::New(env, "jpeg"), Napi::Function::New(env, Jpeg)); - exports.Set(Napi::String::New(env, "leak"), Napi::Function::New(env, Leak)); exports.Set(Napi::String::New(env, "magik"), Napi::Function::New(env, Magik)); exports.Set(Napi::String::New(env, "meme"), Napi::Function::New(env, Meme)); exports.Set(Napi::String::New(env, "mirror"), Napi::Function::New(env, Mirror)); @@ -81,7 +81,6 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) exports.Set(Napi::String::New(env, "spin"), Napi::Function::New(env, Spin)); exports.Set(Napi::String::New(env, "swirl"), Napi::Function::New(env, Swirl)); exports.Set(Napi::String::New(env, "tile"), Napi::Function::New(env, Tile)); - exports.Set(Napi::String::New(env, "trump"), Napi::Function::New(env, Trump)); exports.Set(Napi::String::New(env, "uncaption"), Napi::Function::New(env, Uncaption)); exports.Set(Napi::String::New(env, "wall"), Napi::Function::New(env, Wall)); exports.Set(Napi::String::New(env, "watermark"), Napi::Function::New(env, Watermark)); @@ -91,4 +90,4 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) return exports; } -NODE_API_MODULE(addon, Init); +NODE_API_MODULE(addon, Init) diff --git a/natives/invert.cc b/natives/invert.cc index 7b06a6f..15eda5f 100644 --- a/natives/invert.cc +++ b/natives/invert.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Invert(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -17,43 +15,29 @@ Napi::Value Invert(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - for_each(coalesced.begin(), coalesced.end(), negateImage()); - for (Image &image : coalesced) { - image.negateChannel(Magick::AlphaChannel); - mid.push_back(image); - } - // Magick::ChannelType(Magick::CompositeChannels ^ Magick::AlphaChannel) - for_each(mid.begin(), mid.end(), magickImage(type)); + VImage noAlpha = + in.extract_band(0, VImage::option()->set("n", in.bands() - 1)); + VImage inverted = noAlpha.invert(); + VImage out = inverted.bandjoin(in.extract_band(3)); - optimizeTransparency(mid.begin(), mid.end()); + if (delay) out.set("delay", delay); - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - } + void *buf; + size_t length; + out.write_to_buffer(("." + type).c_str(), &buf, &length); - writeImages(mid.begin(), mid.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/jpeg.cc b/natives/jpeg.cc index c77415a..8874e46 100644 --- a/natives/jpeg.cc +++ b/natives/jpeg.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Jpeg(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -13,61 +11,58 @@ Napi::Value Jpeg(const Napi::CallbackInfo &info) { try { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); - int quality = - obj.Has("quality") ? obj.Get("quality").As().Int32Value() : 0; + int quality = obj.Has("quality") + ? obj.Get("quality").As().Int32Value() + : 0; string type = obj.Get("type").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; - Napi::Object result = Napi::Object::New(env); if (type == "gif") { - list frames; - list coalesced; - list jpeged; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer( + data.Data(), data.Length(), "", + VImage::option()->set("access", "sequential")->set("n", -1)) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - for (Image &image : coalesced) { - Blob temp; - image.quality(quality); - image.magick("JPEG"); - image.write(&temp); - Image newImage(temp); - newImage.magick(type); - newImage.animationDelay(delay == 0 ? image.animationDelay() : delay); - jpeged.push_back(newImage); + int page_height = vips_image_get_page_height(in.get_image()); + + void *jpgBuf; + size_t jpgLength; + in.write_to_buffer( + ".jpg", &jpgBuf, &jpgLength, + VImage::option()->set("Q", quality)->set("strip", true)); + VImage final = VImage::new_from_buffer(jpgBuf, jpgLength, ""); + final.set(VIPS_META_PAGE_HEIGHT, page_height); + if (delay) { + final.set("delay", delay); + } else if (type == "gif") { + final.set("delay", in.get_array_int("delay")); } - optimizeTransparency(jpeged.begin(), jpeged.end()); + void *buf; + size_t length; + final.write_to_buffer(("." + type).c_str(), &buf, &length, + VImage::option()->set("dither", 0)); - for (Image &image : jpeged) { - image.quantizeDither(false); - image.quantize(); - } + vips_thread_shutdown(); - writeImages(jpeged.begin(), jpeged.end(), &blob); - - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); } else { - Image image; - image.read(Blob(data.Data(), data.Length())); - image.quality(1); - image.magick("JPEG"); - image.write(&blob); + VImage in = VImage::new_from_buffer(data.Data(), data.Length(), ""); + void *buf; + size_t length; + in.write_to_buffer( + ".jpg", &buf, &length, + VImage::option()->set("Q", quality)->set("strip", true)); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + vips_thread_shutdown(); + + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", "jpg"); } diff --git a/natives/leak.cc b/natives/leak.cc deleted file mode 100644 index f08c009..0000000 --- a/natives/leak.cc +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include - -#include -#include - -using namespace std; -using namespace Magick; - -Napi::Value Leak(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - try { - Napi::Object obj = info[0].As(); - Napi::Buffer data = obj.Get("data").As>(); - string type = obj.Get("type").As().Utf8Value(); - int delay = - obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - - Blob blob; - - list frames; - list coalesced; - list mid; - Image watermark; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - watermark.read("./assets/images/leak.png"); - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - image.backgroundColor("white"); - image.scale(Geometry("640x360!")); - image.rotate(15); - image.extent(Geometry("1280x720-700+100")); - image.composite(watermark, Geometry("+0+0"), Magick::OverCompositeOp); - image.magick(type); - mid.push_back(image); - } - - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - } - - writeImages(mid.begin(), mid.end(), &blob); - - Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); - result.Set("type", type); - return result; - } catch (std::exception const &err) { - throw Napi::Error::New(env, err.what()); - } catch (...) { - throw Napi::Error::New(env, "Unknown error"); - } -} \ No newline at end of file diff --git a/natives/leak.h b/natives/leak.h deleted file mode 100644 index 5280ada..0000000 --- a/natives/leak.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -Napi::Value Leak(const Napi::CallbackInfo& info); diff --git a/natives/meme.cc b/natives/meme.cc index 36ce25f..f55f7d0 100644 --- a/natives/meme.cc +++ b/natives/meme.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Meme(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -20,88 +18,125 @@ Napi::Value Meme(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - Image top_text; - Image bottom_text; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - int width = coalesced.front().columns(); + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + int size = width / 9; int dividedWidth = width / 1000; + int rad = 1; - top_text.size(Geometry(to_string(width))); - top_text.backgroundColor("none"); - top_text.font("Impact"); - top_text.fontPointsize(width / 12); - top_text.textGravity(Magick::CenterGravity); - top_text.read("pango:" + top + ""); - Image top_text_fill = top_text; - top_text_fill.channel(Magick::AlphaChannel); - top_text_fill.morphology(Magick::EdgeOutMorphology, "Octagon"); - top_text_fill.backgroundColor("black"); - top_text_fill.alphaChannel(Magick::ShapeAlphaChannel); - if (dividedWidth > 1) - top_text_fill.morphology(Magick::DilateMorphology, "Octagon", - dividedWidth); - top_text.composite(top_text_fill, Magick::CenterGravity, - Magick::DstOverCompositeOp); + string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " + + (font != "impact" ? "bold" : "normal") + " " + + to_string(size); - if (bottom != "") { - bottom_text.size(Geometry(to_string(coalesced.front().columns()))); - bottom_text.backgroundColor("none"); - bottom_text.font("Impact"); - bottom_text.fontPointsize(width / 12); - bottom_text.textGravity(Magick::CenterGravity); - bottom_text.read("pango:" + bottom + ""); - Image bottom_text_fill = bottom_text; - bottom_text_fill.channel(Magick::AlphaChannel); - bottom_text_fill.morphology(Magick::EdgeOutMorphology, "Octagon"); - bottom_text_fill.backgroundColor("black"); - bottom_text_fill.alphaChannel(Magick::ShapeAlphaChannel); - if (dividedWidth > 1) - bottom_text_fill.morphology(Magick::DilateMorphology, "Octagon", - dividedWidth); - bottom_text.composite(bottom_text_fill, Magick::CenterGravity, - Magick::DstOverCompositeOp); + VImage mask = VImage::black(rad * 2 + 1, rad * 2 + 1) + 128; + mask.draw_circle({255}, rad, rad, rad, VImage::option()->set("fill", true)); + + VImage altMask; + + if (dividedWidth >= 1) { + altMask = VImage::black(dividedWidth * 2 + 1, dividedWidth * 2 + 1) + 128; + altMask.draw_circle({255}, dividedWidth, dividedWidth, dividedWidth, + VImage::option()->set("fill", true)); } - for (Image &image : coalesced) { - image.composite(top_text, Magick::NorthGravity, Magick::OverCompositeOp); - if (bottom != "") - image.composite(bottom_text, Magick::SouthGravity, - Magick::OverCompositeOp); - image.magick(type); - mid.push_back(image); - } + VImage topText; + if (top != "") { + VImage topIn = VImage::text( + ("" + top + "").c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", font_string.c_str()) + ->set("width", width)); - optimizeTransparency(mid.begin(), mid.end()); + topIn = topIn.embed(rad + 10, rad + 10, (topIn.width() + 2 * rad) + 20, + (topIn.height() + 2 * rad) + 20); - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); + VImage topOutline = + topIn.morph(mask, VIPS_OPERATION_MORPHOLOGY_DILATE) + .gaussblur(0.5, VImage::option()->set("min_ampl", 0.1)); + if (dividedWidth >= 1) { + topOutline = + topOutline.morph(altMask, VIPS_OPERATION_MORPHOLOGY_DILATE); } + topOutline = (topOutline == (vector){0, 0, 0, 0}); + VImage topInvert = topOutline.extract_band(3).invert(); + topOutline = topOutline + .extract_band(0, VImage::option()->set( + "n", topOutline.bands() - 1)) + .bandjoin(topInvert); + topText = topOutline.composite2(topIn, VIPS_BLEND_MODE_OVER); } - writeImages(mid.begin(), mid.end(), &blob); + VImage bottomText; + if (bottom != "") { + VImage bottomIn = VImage::text( + ("" + bottom + "").c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", font_string.c_str()) + ->set("width", width)); + bottomIn = + bottomIn.embed(rad + 10, rad + 10, (bottomIn.width() + 2 * rad) + 20, + (bottomIn.height() + 2 * rad) + 20); + VImage bottomOutline = + bottomIn.morph(mask, VIPS_OPERATION_MORPHOLOGY_DILATE) + .gaussblur(0.5, VImage::option()->set("min_ampl", 0.1)); + if (dividedWidth >= 1) { + bottomOutline = + bottomOutline.morph(altMask, VIPS_OPERATION_MORPHOLOGY_DILATE); + } + bottomOutline = (bottomOutline == (vector){0, 0, 0, 0}); + VImage bottomInvert = bottomOutline.extract_band(3).invert(); + bottomOutline = bottomOutline + .extract_band(0, VImage::option()->set( + "n", bottomOutline.bands() - 1)) + .bandjoin(bottomInvert); + bottomText = bottomOutline.composite2(bottomIn, VIPS_BLEND_MODE_OVER); + } + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + if (top != "") { + img_frame = img_frame.composite2( + topText, VIPS_BLEND_MODE_OVER, + VImage::option()->set("x", (width / 2) - (topText.width() / 2))); + } + if (bottom != "") { + img_frame = img_frame.composite2( + bottomText, VIPS_BLEND_MODE_OVER, + VImage::option() + ->set("x", (width / 2) - (bottomText.width() / 2)) + ->set("y", page_height - bottomText.height())); + } + img.push_back(img_frame); + } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height); + if (delay) final.set("delay", delay); + + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); + + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/mirror.cc b/natives/mirror.cc index fcd77a0..5ff2321 100644 --- a/natives/mirror.cc +++ b/natives/mirror.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Mirror(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -22,74 +20,63 @@ Napi::Value Mirror(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - MagickCore::GravityType gravity; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - if (vertical && first) { - gravity = Magick::NorthGravity; - } else if (!vertical && first) { - gravity = Magick::WestGravity; - } else if (vertical && !first) { - gravity = Magick::SouthGravity; + VImage out; + + if (vertical) { + if (type == "gif") { + // once again, libvips gif handling is both a blessing and a curse + vector img; + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + bool isOdd = page_height % 2; + for (int i = 0; i < n_pages; i++) { + int x = (i * page_height) + (first ? 0 : (page_height / 2)); + VImage cropped = in.crop(0, x, in.width(), page_height / 2); + VImage flipped = cropped.flip(VIPS_DIRECTION_VERTICAL); + VImage final = VImage::arrayjoin( + {first ? cropped : flipped, first ? flipped : cropped}, + VImage::option()->set("across", 1)); + img.push_back(final); + } + out = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + out.set(VIPS_META_PAGE_HEIGHT, page_height - (isOdd ? 1 : 0)); + } else { + VImage cropped = in.extract_area(0, 0, in.width(), in.height() / 2); + VImage flipped = cropped.flip(VIPS_DIRECTION_VERTICAL); + out = VImage::arrayjoin({cropped, flipped}, + VImage::option()->set("across", 1)); + } } else { - gravity = Magick::EastGravity; - } - - for (Image &image : coalesced) { - image.colorSpace(Magick::sRGBColorspace); - list mirrored; - Image final; - image.extent(Geometry(to_string(vertical ? image.baseColumns() - : image.baseColumns() / 2) + - "x" + - to_string(vertical ? image.baseRows() / 2 - : image.baseRows())), - gravity); - mirrored.push_back(image); - Image mirror = image; - if (vertical) { - mirror.flip(); - } else { - mirror.flop(); - } if (first) { - mirrored.push_back(mirror); + VImage cropped = in.extract_area(0, 0, in.width() / 2, in.height()); + VImage flipped = cropped.flip(VIPS_DIRECTION_HORIZONTAL); + out = VImage::arrayjoin({cropped, flipped}); } else { - mirrored.push_front(mirror); - } - appendImages(&final, mirrored.begin(), mirrored.end(), vertical); - final.repage(); - final.magick(type); - final.animationDelay(delay == 0 ? image.animationDelay() : delay); - mid.push_back(final); - } - - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); + int size = in.width() / 2; + VImage cropped = in.extract_area(size, 0, size, in.height()); + VImage flipped = cropped.flip(VIPS_DIRECTION_HORIZONTAL); + out = VImage::arrayjoin({flipped, cropped}); } } - writeImages(mid.begin(), mid.end(), &blob); + if (delay) out.set("delay", delay); + + void *buf; + size_t length; + out.write_to_buffer(("." + type).c_str(), &buf, &length); + + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/motivate.cc b/natives/motivate.cc index a993dab..7d60104 100644 --- a/natives/motivate.cc +++ b/natives/motivate.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Motivate(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -20,85 +18,101 @@ Napi::Value Motivate(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - Image top; - Image bottom; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - top.size(Geometry("600")); - top.backgroundColor("black"); - top.font("Times"); - top.textGravity(Magick::CenterGravity); - top.fontPointsize(56); - top.read("pango:" + top_text + ""); - top.extent(Geometry(bottom_text != "" ? to_string(top.columns()) + "x" + - to_string(top.rows()) - : to_string(top.columns()) + "x" + - to_string(top.rows() + 20)), - "black", Magick::NorthGravity); + int width = in.width(); + int size = width / 5; + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + int textWidth = width - ((width / 25) * 2); + string font_string = font == "roboto" ? "Roboto Condensed" : font; + + string topText = "" + + top_text + ""; + + VImage topImage = VImage::text( + topText.c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", (font_string + " " + to_string(size)).c_str()) + ->set("width", textWidth)); + + VImage bottomImage; if (bottom_text != "") { - bottom.size(Geometry("600")); - bottom.backgroundColor("black"); - bottom.font("Times"); - bottom.textGravity(Magick::CenterGravity); - bottom.fontPointsize(28); - bottom.read("pango:" + bottom_text + ""); - bottom.extent(Geometry(to_string(bottom.columns()) + "x" + - to_string(bottom.rows() + 20)), - "black", Magick::NorthGravity); + string bottomText = "" + + bottom_text + ""; + + bottomImage = VImage::text( + bottomText.c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", (font_string + " " + to_string(size * 0.4)).c_str()) + ->set("width", textWidth)); } - for (Image &image : coalesced) { - Image final; - image.scale(Geometry(500, 500)); - image.borderColor("black"); - image.border(Geometry(5, 5)); - image.borderColor("white"); - image.border(Geometry(3, 3)); - image.backgroundColor("black"); - image.extent(Geometry(600, image.rows() + 50), Magick::CenterGravity); + vector img; + int height; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; - list to_append; - to_append.push_back(image); - to_append.push_back(top); - if (bottom_text != "") to_append.push_back(bottom); - appendImages(&final, to_append.begin(), to_append.end(), true); - final.repage(); - final.magick(type); - final.animationDelay(delay == 0 ? image.animationDelay() : delay); - mid.push_back(final); - } + int borderSize = max(2, width / 66); + int borderSize2 = borderSize * 0.5; + VImage bordered = + img_frame.embed(borderSize, borderSize, width + (borderSize * 2), + page_height + (borderSize * 2), + VImage::option()->set("extend", "black")); + VImage bordered2 = bordered.embed( + borderSize2, borderSize2, bordered.width() + (borderSize2 * 2), + bordered.height() + (borderSize2 * 2), + VImage::option()->set("extend", "white")); - optimizeTransparency(mid.begin(), mid.end()); + int addition = width / 8; + int sideAddition = page_height * 0.4; - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); + VImage bordered3 = bordered2.embed( + sideAddition / 2, addition / 2, bordered2.width() + sideAddition, + bordered2.height() + addition, + VImage::option()->set("extend", "black")); + VImage frame = bordered3.join( + topImage.gravity(VIPS_COMPASS_DIRECTION_NORTH, bordered3.width(), + topImage.height() + (size / 4), + VImage::option()->set("extend", "black")), + VIPS_DIRECTION_VERTICAL, + VImage::option()->set("background", 0x000000)->set("expand", true)); + if (bottom_text != "") { + frame = frame.join( + bottomImage.gravity(VIPS_COMPASS_DIRECTION_NORTH, bordered3.width(), + bottomImage.height() + (size / 4), + VImage::option()->set("extend", "black")), + VIPS_DIRECTION_VERTICAL, + VImage::option()->set("background", 0x000000)->set("expand", true)); } + height = frame.height(); + img.push_back(frame); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)) + .extract_band(0, VImage::option()->set("n", 3)); + final.set(VIPS_META_PAGE_HEIGHT, height); + if (delay) final.set("delay", delay); - writeImages(mid.begin(), mid.end(), &blob); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 1) : 0); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/reddit.cc b/natives/reddit.cc index 4c2162d..7c3d469 100644 --- a/natives/reddit.cc +++ b/natives/reddit.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Reddit(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -18,62 +16,58 @@ Napi::Value Reddit(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - Image watermark; - Image text_image; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + string assetPath = basePath + "assets/images/reddit.png"; + VImage tmpl = VImage::new_from_file(assetPath.c_str()); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + + string captionText = "" + text + ""; + + VImage textImage = + VImage::text(captionText.c_str(), VImage::option() + ->set("rgba", true) + ->set("font", "Roboto 62") + ->set("align", VIPS_ALIGN_LOW)); + + VImage composited = + tmpl.composite2(textImage, VIPS_BLEND_MODE_OVER, + VImage::option()->set("x", 375)->set( + "y", (tmpl.height() - textImage.height()) - 64)); + VImage watermark = + composited.resize((double)width / (double)composited.width()); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + VImage frame = img_frame.join(watermark, VIPS_DIRECTION_VERTICAL, + VImage::option()->set("expand", true)); + img.push_back(frame); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height + watermark.height()); + if (delay) final.set("delay", delay); - watermark.read(basePath + "assets/images/reddit.png"); - text_image.textGravity(Magick::WestGravity); - text_image.font("Roboto"); - text_image.fontPointsize(47); - text_image.backgroundColor("none"); - text_image.read("pango:" + text + ""); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); - watermark.composite(text_image, Geometry("+375+46"), - Magick::OverCompositeOp); - - string query(to_string(frames.front().baseColumns()) + "x"); - watermark.scale(Geometry(query)); - - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - Image final; - list to_append; - to_append.push_back(image); - to_append.push_back(watermark); - appendImages(&final, to_append.begin(), to_append.end(), true); - final.repage(); - image.magick(type); - final.animationDelay(delay == 0 ? image.animationDelay() : delay); - mid.push_back(final); - } - - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - } - } - - writeImages(mid.begin(), mid.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/resize.cc b/natives/resize.cc index 4e3f9a5..a18c336 100644 --- a/natives/resize.cc +++ b/natives/resize.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Resize(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -22,49 +20,43 @@ Napi::Value Resize(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list blurred; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + + VImage out; + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + + int finalHeight; + if (stretch) { + out = in.resize( + 512.0 / (double)width, + VImage::option()->set("vscale", 512.0 / (double)page_height)); + finalHeight = 512; + } else if (wide) { + out = in.resize(9.5, VImage::option()->set("vscale", 0.5)); + finalHeight = page_height / 2; + } else { + out = in.resize(0.1).resize( + 10, VImage::option()->set("kernel", VIPS_KERNEL_NEAREST)); + finalHeight = page_height; } - coalesceImages(&coalesced, frames.begin(), frames.end()); + out.set(VIPS_META_PAGE_HEIGHT, finalHeight); + if (delay) out.set("delay", delay); - for (Image &image : coalesced) { - if (stretch) { - image.resize(Geometry("512x512!")); - } else if (wide) { - image.resize(Geometry(to_string((image.baseColumns() * 19) / 2) + "x" + - to_string(image.baseRows() / 2) + "!")); - } else { - image.scale(Geometry("10%")); - image.scale(Geometry("1000%")); - } - image.magick(type); - blurred.push_back(image); - } + void *buf; + size_t length; + out.write_to_buffer(("." + type).c_str(), &buf, &length); - optimizeTransparency(blurred.begin(), blurred.end()); - - if (type == "gif") { - for (Image &image : blurred) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - } - - writeImages(blurred.begin(), blurred.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/reverse.cc b/natives/reverse.cc index bde68f1..b3ef222 100644 --- a/natives/reverse.cc +++ b/natives/reverse.cc @@ -1,11 +1,11 @@ -#include #include #include #include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Reverse(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -15,47 +15,54 @@ Napi::Value Reverse(const Napi::CallbackInfo &info) { Napi::Buffer data = obj.Get("data").As>(); bool soos = obj.Has("soos") ? obj.Get("soos").As().Value() : false; - int delay = - obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = + VImage::option()->set("access", "sequential")->set("n", -1); - list frames; - list coalesced; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = VImage::new_from_buffer(data.Data(), data.Length(), "", options) + .colourspace(VIPS_INTERPRETATION_sRGB); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + + vector split; + // todo: find a better way of getting individual frames (or at least getting the frames in reverse order) + for (int i = 0; i < n_pages; i++) { + VImage img_frame = in.crop(0, i * page_height, width, page_height); + split.push_back(img_frame); } - coalesceImages(&coalesced, frames.begin(), frames.end()); + vector delays = in.get_array_int("delay"); if (soos) { - list copy = coalesced; - copy.reverse(); + vector copy = split; + vector copy2 = delays; + reverse(copy.begin(), copy.end()); + reverse(copy2.begin(), copy2.end()); copy.pop_back(); - copy.pop_front(); - coalesced.insert(coalesced.end(), copy.begin(), copy.end()); + copy2.pop_back(); + copy.erase(copy.begin()); + copy2.erase(copy2.begin()); + split.insert(split.end(), copy.begin(), copy.end()); + delays.insert(delays.end(), copy2.begin(), copy2.end()); } else { - coalesced.reverse(); + reverse(split.begin(), split.end()); + reverse(delays.begin(), delays.end()); } - for_each(coalesced.begin(), coalesced.end(), magickImage("GIF")); + VImage final = VImage::arrayjoin(split, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height); + final.set("delay", delays); - optimizeTransparency(coalesced.begin(), coalesced.end()); + void *buf; + size_t length; + final.write_to_buffer(".gif", &buf, &length, + VImage::option()->set("dither", 0)); - for (Image &image : coalesced) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - - writeImages(coalesced.begin(), coalesced.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", "gif"); return result; } catch (std::exception const &err) { diff --git a/natives/snapchat.cc b/natives/snapchat.cc index a32fdf0..dd0c81f 100644 --- a/natives/snapchat.cc +++ b/natives/snapchat.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Snapchat(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -20,58 +18,64 @@ Napi::Value Snapchat(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list captioned; - Blob caption_blob; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + int size = width / 20; + int textWidth = width - ((width / 25) * 2); + + string font_string = "Helvetica Neue " + to_string(size); + + VImage textIn = + VImage::text(("" + + caption + "") + .c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", font_string.c_str()) + ->set("width", textWidth)); + int bgHeight = textIn.height() + (width / 25); + textIn = + ((textIn == (vector){0, 0, 0, 0}).bandand()) + .ifthenelse({0, 0, 0, 178}, textIn) + .embed((width / 2) - (textIn.width() / 2), + (bgHeight / 2) - (textIn.height() / 2), width, bgHeight, + VImage::option() + ->set("extend", "background") + ->set("background", (vector){0, 0, 0, 178})); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + img_frame = img_frame.composite2( + textIn, VIPS_BLEND_MODE_OVER, + VImage::option()->set("x", 0)->set("y", page_height * pos)); + img.push_back(img_frame); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height); + if (delay) final.set("delay", delay); - size_t width = frames.front().baseColumns(); - size_t height = frames.front().baseRows(); - string query(to_string(width - ((width / 25) * 2)) + "x"); - Image caption_image(Geometry(query), Color("none")); - caption_image.backgroundColor(Color("none")); - caption_image.fillColor("white"); - caption_image.font("Helvetica Neue"); - caption_image.fontPointsize(width / 25); - caption_image.textGravity(Magick::CenterGravity); - caption_image.read("pango:" + caption); - caption_image.backgroundColor(Color("rgba(0, 0, 0, 0.7)")); - caption_image.extent(Geometry(width, caption_image.rows() + (width / 25)), - Magick::CenterGravity); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - list images; - image.composite(caption_image, 0, height * pos, Magick::OverCompositeOp); - image.magick(type); - image.animationDelay(delay == 0 ? image.animationDelay() : delay); - captioned.push_back(image); - } - - optimizeTransparency(captioned.begin(), captioned.end()); - - if (type == "gif") { - for (Image &image : captioned) { - image.quantizeDither(false); - image.quantize(); - } - } - - writeImages(captioned.begin(), captioned.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/sonic.cc b/natives/sonic.cc index 3700fe2..d86848e 100644 --- a/natives/sonic.cc +++ b/natives/sonic.cc @@ -1,10 +1,9 @@ -#include #include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Sonic(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -14,25 +13,30 @@ Napi::Value Sonic(const Napi::CallbackInfo &info) { string text = obj.Get("text").As().Utf8Value(); string basePath = obj.Get("basePath").As().Utf8Value(); - Blob blob; + string assetPath = basePath + "assets/images/sonic.jpg"; + VImage bg = VImage::new_from_file(assetPath.c_str()); - Image image; - Image text_image; - text_image.backgroundColor("none"); - text_image.fontPointsize(72); - text_image.textGravity(Magick::CenterGravity); - text_image.font("Bitstream Vera Sans"); - text_image.read("pango:" + text + ""); - text_image.resize(Geometry(474, 332)); - text_image.extent(Geometry("1024x538-435-145"), Magick::CenterGravity); - image.read(basePath + "assets/images/sonic.jpg"); - image.composite(text_image, Geometry("+160+10"), Magick::OverCompositeOp); - image.magick("PNG"); - image.write(&blob); + VImage textImage = + VImage::text(("" + text + "").c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", "Bitstream Vera Sans") + ->set("width", 542) + ->set("height", 390)) + .gravity(VIPS_COMPASS_DIRECTION_CENTRE, 542, 390); + + VImage out = bg.composite2(textImage, VIPS_BLEND_MODE_OVER, + VImage::option()->set("x", 391)->set("y", 84)); + + void *buf; + size_t length; + out.write_to_buffer(".png", &buf, &length); + + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", "png"); return result; } catch (std::exception const &err) { diff --git a/natives/speed.cc b/natives/speed.cc index 1beb7c5..c59f0ce 100644 --- a/natives/speed.cc +++ b/natives/speed.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; void *memset16(void *m, uint16_t val, size_t count) { uint16_t *buf = (uint16_t *)m; @@ -14,6 +12,36 @@ void *memset16(void *m, uint16_t val, size_t count) { return m; } +void vipsRemove(Napi::Env *env, Napi::Object *result, Napi::Buffer data, + int speed) { + VOption *options = VImage::option()->set("access", "sequential"); + + VImage in = VImage::new_from_buffer(data.Data(), data.Length(), "", + options->set("n", -1)) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + + vector img; + for (int i = 0; i < n_pages; i += speed) { + VImage img_frame = in.crop(0, i * page_height, width, page_height); + img.push_back(img_frame); + } + VImage out = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + out.set(VIPS_META_PAGE_HEIGHT, page_height); + + void *buf; + size_t length; + out.write_to_buffer(".gif", &buf, &length); + + vips_thread_shutdown(); + + result->Set("data", Napi::Buffer::Copy(*env, (char *)buf, length)); +} + Napi::Value Speed(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -80,37 +108,7 @@ Napi::Value Speed(const Napi::CallbackInfo &info) { result.Set("data", Napi::Buffer::Copy(env, fileData, data.Length())); - if (removeFrames) { - Blob blob; - - list frames; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - - for (list::iterator i = frames.begin(); i != frames.end(); ++i) { - int index = distance(frames.begin(), i); - if (index >= (int)old_delays.size()) { - old_delays.resize(index+1); - old_delays[index] = old_delays[index-1]; - } - i->animationDelay(old_delays[index]); - } - - for (int i = 0; i < speed - 1; ++i) { - auto it = frames.begin(); - while(it != frames.end() && ++it != frames.end()) it = frames.erase(it); - } - - for_each(frames.begin(), frames.end(), magickImage(type)); - writeImages(frames.begin(), frames.end(), &blob); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); - } + if (removeFrames) vipsRemove(&env, &result, data, speed); } else { char *lastPos; @@ -133,26 +131,7 @@ Napi::Value Speed(const Napi::CallbackInfo &info) { } if (removeFrames) { - Blob blob; - - list frames; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - - for (int i = 0; i < speed - 1; ++i) { - auto it = frames.begin(); - while(it != frames.end() && ++it != frames.end()) it = frames.erase(it); - } - - for_each(frames.begin(), frames.end(), magickImage(type)); - writeImages(frames.begin(), frames.end(), &blob); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + vipsRemove(&env, &result, data, speed); } else { while (lastPos != NULL) { if (memcmp(lastPos, match, 4) != 0) { @@ -178,4 +157,4 @@ Napi::Value Speed(const Napi::CallbackInfo &info) { } catch (...) { throw Napi::Error::New(env, "Unknown error"); } -} +} \ No newline at end of file diff --git a/natives/trump.cc b/natives/trump.cc deleted file mode 100644 index 8ba517f..0000000 --- a/natives/trump.cc +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include - -#include -#include - -using namespace std; -using namespace Magick; - -Napi::Value Trump(const Napi::CallbackInfo &info) { - Napi::Env env = info.Env(); - - try { - Napi::Object obj = info[0].As(); - Napi::Buffer data = obj.Get("data").As>(); - string type = obj.Get("type").As().Utf8Value(); - int delay = - obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - - Blob blob; - - list frames; - list coalesced; - list mid; - Image watermark; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - watermark.read("./assets/images/trump.png"); - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - Image watermark_new = watermark; - image.virtualPixelMethod(Magick::TransparentVirtualPixelMethod); - image.backgroundColor("none"); - image.scale(Geometry("365x179!")); - double arguments[16] = {0, 0, 207, 268, 365, 0, 548, 271, - 365, 179, 558, 450, 0, 179, 193, 450}; - image.distort(Magick::PerspectiveDistortion, 16, arguments, true); - image.extent(Geometry("800x450"), Magick::CenterGravity); - watermark_new.composite(image, Geometry("-25+134"), - Magick::DstOverCompositeOp); - watermark_new.magick(type); - watermark_new.animationDelay(delay == 0 ? image.animationDelay() : delay); - watermark_new.gifDisposeMethod(Magick::BackgroundDispose); - mid.push_back(watermark_new); - } - - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - } - } - - writeImages(mid.begin(), mid.end(), &blob); - - Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); - result.Set("type", type); - return result; - } catch (std::exception const &err) { - throw Napi::Error::New(env, err.what()); - } catch (...) { - throw Napi::Error::New(env, "Unknown error"); - } -} \ No newline at end of file diff --git a/natives/trump.h b/natives/trump.h deleted file mode 100644 index 032161b..0000000 --- a/natives/trump.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -Napi::Value Trump(const Napi::CallbackInfo& info); diff --git a/natives/uncaption.cc b/natives/uncaption.cc index 4ba6597..726103b 100644 --- a/natives/uncaption.cc +++ b/natives/uncaption.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Uncaption(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -13,61 +11,51 @@ Napi::Value Uncaption(const Napi::CallbackInfo &info) { try { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); - float tolerance = obj.Has("tolerance") ? obj.Get("tolerance").As().FloatValue() : 0.95; + float tolerance = obj.Has("tolerance") + ? obj.Get("tolerance").As().FloatValue() + : 0.5; string type = obj.Get("type").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option(); - list frames; - list coalesced; - list mid; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1)->set("access", "sequential") : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + + VImage first = + in.crop(0, 0, 3, page_height).colourspace(VIPS_INTERPRETATION_B_W) > + (255 * tolerance); + int top, captionWidth, captionHeight; + first.find_trim(&top, &captionWidth, &captionHeight); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + in.crop(0, (i * page_height) + top, width, page_height - top); + img.push_back(img_frame); } - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height - top); + if (delay) final.set("delay", delay); - Image firstImage = coalesced.front(); - ssize_t columns = firstImage.columns(); - ssize_t rows = firstImage.rows(); - ssize_t row; - for (row = 0; row < rows; ++row) { - ColorGray color = firstImage.pixelColor(0, row); - if (color.shade() < tolerance) { - break; - } - } - Geometry geom = Geometry(columns, row == rows ? rows : rows - row, 0, - row == rows ? 0 : row); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); - for (Image &image : coalesced) { - image.virtualPixelMethod(Magick::TransparentVirtualPixelMethod); - image.backgroundColor("none"); - image.extent(geom); - image.magick(type); - mid.push_back(image); - } - - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDither(false); - image.quantize(); - if (delay != 0) image.animationDelay(delay); - } - } - - writeImages(mid.begin(), mid.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/watermark.cc b/natives/watermark.cc index ffcd679..ce62109 100644 --- a/natives/watermark.cc +++ b/natives/watermark.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Watermark(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -14,8 +12,7 @@ Napi::Value Watermark(const Napi::CallbackInfo &info) { Napi::Object obj = info[0].As(); Napi::Buffer data = obj.Get("data").As>(); string water = obj.Get("water").As().Utf8Value(); - Magick::GravityType gravity = - Magick::GravityType(obj.Get("gravity").As().Int64Value()); + int gravity = obj.Get("gravity").As().Int64Value(); bool resize = obj.Has("resize") ? obj.Get("resize").As().Value() : false; @@ -25,76 +22,141 @@ Napi::Value Watermark(const Napi::CallbackInfo &info) { bool append = obj.Has("append") ? obj.Get("append").As().Value() : false; + bool alpha = + obj.Has("alpha") ? obj.Get("alpha").As().Value() : false; bool mc = obj.Has("mc") ? obj.Get("mc").As().Value() : false; string basePath = obj.Get("basePath").As().Utf8Value(); string type = obj.Get("type").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); + + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - list frames; - list coalesced; - list mid; - Image watermark; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } string merged = basePath + water; - watermark.read(merged); + VImage watermark = VImage::new_from_file(merged.c_str()); + + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + if (resize && append) { - string query(to_string(frames.front().baseColumns()) + "x"); - watermark.scale(Geometry(query)); + watermark = watermark.resize((double)width / (double)watermark.width()); } else if (resize && yscale) { - string query(to_string(frames.front().baseColumns()) + "x" + - to_string(frames.front().baseRows() * yscale) + "!"); - watermark.resize(Geometry(query)); + watermark = watermark.resize( + (double)width / (double)watermark.width(), + VImage::option()->set("vscale", (double)(page_height * yscale) / + (double)watermark.height())); } else if (resize) { - string query("x" + to_string(frames.front().baseRows())); - watermark.scale(Geometry(query)); + watermark = + watermark.resize((double)page_height / (double)watermark.height()); } - coalesceImages(&coalesced, frames.begin(), frames.end()); - for (Image &image : coalesced) { - Image final; + int x = 0, y = 0; + switch (gravity) { + case 1: + break; + case 2: + x = (width / 2) - (watermark.width() / 2); + break; + case 3: + x = width - watermark.width(); + break; + case 5: + x = (width / 2) - (watermark.width() / 2); + y = (page_height / 2) - (watermark.height() / 2); + break; + case 6: + x = width - watermark.width(); + y = (page_height / 2) - (watermark.height() / 2); + break; + case 8: + x = (width / 2) - (watermark.width() / 2); + y = page_height - watermark.height(); + break; + case 9: + x = width - watermark.width(); + y = page_height - watermark.height(); + break; + } + + vector img; + int addedHeight = 0; + VImage contentAlpha; + VImage frameAlpha; + VImage bg; + VImage frame; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; if (append) { - list to_append; - to_append.push_back(image); - to_append.push_back(watermark); - appendImages(&final, to_append.begin(), to_append.end(), true); - final.repage(); + VImage appended = img_frame.join(watermark, VIPS_DIRECTION_VERTICAL, + VImage::option()->set("expand", true)); + addedHeight = watermark.height(); + img.push_back(appended); } else if (mc) { - image.backgroundColor("white"); - image.extent(Geometry(image.columns(), image.rows() + 15)); - image.composite(watermark, gravity, Magick::OverCompositeOp); - final = image; + VImage padded = + img_frame.embed(0, 0, width, page_height + 15, + VImage::option()->set("background", 0xffffff)); + VImage composited = + padded.composite2(watermark, VIPS_BLEND_MODE_OVER, + VImage::option() + ->set("x", width - 190) + ->set("y", padded.height() - 22)); + addedHeight = 15; + img.push_back(composited); } else { - image.composite(watermark, gravity, Magick::OverCompositeOp); - final = image; - } - image.magick(type); - final.animationDelay(delay == 0 ? image.animationDelay() : delay); - mid.push_back(final); - } + VImage composited; + if (alpha) { + if (i == 0) { + contentAlpha = watermark.extract_band(0).embed( + x, y, width, page_height, + VImage::option()->set("extend", "white")); + frameAlpha = watermark.extract_band(1).embed( + x, y, width, page_height, + VImage::option()->set("extend", "black")); + bg = + frameAlpha.new_from_image({0, 0, 0}).copy(VImage::option()->set( + "interpretation", VIPS_INTERPRETATION_sRGB)); + frame = bg.bandjoin(frameAlpha); + if (type == "jpg" || type == "jpeg") { + type = "png"; + } + } + VImage content = + img_frame.extract_band(0, VImage::option()->set("n", 3)) + .bandjoin(contentAlpha & img_frame.extract_band(3)); - optimizeTransparency(mid.begin(), mid.end()); - - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); + composited = + content.composite2(frame, VIPS_BLEND_MODE_OVER, + VImage::option()->set("x", x)->set("y", y)); + } else { + composited = + img_frame.composite2(watermark, VIPS_BLEND_MODE_OVER, + VImage::option()->set("x", x)->set("y", y)); + } + img.push_back(composited); } } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height + addedHeight); + if (delay) final.set("delay", delay); - writeImages(mid.begin(), mid.end(), &blob); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); + + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/whisper.cc b/natives/whisper.cc index eb6ca04..eea3b34 100644 --- a/natives/whisper.cc +++ b/natives/whisper.cc @@ -1,11 +1,9 @@ -#include #include -#include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Whisper(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -18,74 +16,80 @@ Napi::Value Whisper(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list captioned; - Blob caption_blob; - try { - readImages(&frames, Blob(data.Data(), data.Length())); - } catch (Magick::WarningCoder &warning) { - cerr << "Coder Warning: " << warning.what() << endl; - } catch (Magick::Warning &warning) { - cerr << "Warning: " << warning.what() << endl; - } - - size_t width = frames.front().baseColumns(); - size_t height = frames.front().baseRows(); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + int size = width / 6; int dividedWidth = width / 175; + int rad = 1; - Image caption_image; - caption_image.size(Geometry(to_string(width) + "x" + to_string(height))); - caption_image.backgroundColor("none"); - caption_image.fillColor("white"); - caption_image.font("Upright"); - caption_image.fontPointsize(width / 8); - caption_image.textGravity(Magick::CenterGravity); - caption_image.read("pango:" + caption); - caption_image.trim(); - caption_image.repage(); - Image caption_fill = caption_image; - caption_fill.extent(Geometry(width, height), Magick::CenterGravity); - caption_fill.channel(Magick::AlphaChannel); - caption_fill.morphology(Magick::EdgeOutMorphology, "Octagon", - dividedWidth != 0 ? dividedWidth : 1); - caption_fill.backgroundColor("black"); - caption_fill.alphaChannel(Magick::ShapeAlphaChannel); - size_t fill_width = caption_fill.columns(); - size_t fill_height = caption_fill.rows(); - caption_image.extent(Geometry(fill_width, fill_height), - Magick::CenterGravity); - caption_image.composite(caption_fill, Magick::CenterGravity, - Magick::DstOverCompositeOp); + string font_string = "Upright " + to_string(size); - coalesceImages(&coalesced, frames.begin(), frames.end()); - - for (Image &image : coalesced) { - list images; - image.composite(caption_image, Magick::CenterGravity, - Magick::OverCompositeOp); - image.magick(type); - image.animationDelay(delay == 0 ? image.animationDelay() : delay); - captioned.push_back(image); + VImage mask; + if (dividedWidth >= 1) { + mask = VImage::black(dividedWidth * 2 + 1, dividedWidth * 2 + 1) + 128; + mask.draw_circle({255}, dividedWidth, dividedWidth, dividedWidth, + VImage::option()->set("fill", true)); + } else { + mask = VImage::black(rad * 2 + 1, rad * 2 + 1) + 128; + mask.draw_circle({255}, rad, rad, rad, + VImage::option()->set("fill", true)); } - optimizeTransparency(captioned.begin(), captioned.end()); + VImage textIn = VImage::text( + ("" + caption + "").c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", font_string.c_str()) + ->set("width", width)); - if (type == "gif") { - for (Image &image : captioned) { - image.quantizeDither(false); - image.quantize(); - } + textIn = textIn.embed(rad + 10, rad + 10, (textIn.width() + 2 * rad) + 20, + (textIn.height() + 2 * rad) + 20); + + VImage outline = + textIn.morph(mask, VIPS_OPERATION_MORPHOLOGY_DILATE) + .gaussblur(0.5, VImage::option()->set("min_ampl", 0.1)); + outline = (outline == (vector){0, 0, 0, 0}); + VImage invert = outline.extract_band(3).invert(); + outline = + outline.extract_band(0, VImage::option()->set("n", outline.bands() - 1)) + .bandjoin(invert); + VImage textImg = outline.composite2(textIn, VIPS_BLEND_MODE_OVER); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + img_frame = img_frame.composite2( + textImg, VIPS_BLEND_MODE_OVER, + VImage::option() + ->set("x", (width / 2) - (textImg.width() / 2)) + ->set("y", (page_height / 2) - (textImg.height() / 2))); + img.push_back(img_frame); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, page_height); + if (delay) final.set("delay", delay); - writeImages(captioned.begin(), captioned.end(), &blob); + void *buf; + size_t length; + final.write_to_buffer( + ("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("dither", 0) : 0); + + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) { diff --git a/natives/zamn.cc b/natives/zamn.cc index 2741088..a6ccf4f 100644 --- a/natives/zamn.cc +++ b/natives/zamn.cc @@ -1,10 +1,9 @@ -#include #include -#include +#include using namespace std; -using namespace Magick; +using namespace vips; Napi::Value Zamn(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); @@ -17,42 +16,44 @@ Napi::Value Zamn(const Napi::CallbackInfo &info) { int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; - Blob blob; + VOption *options = VImage::option()->set("access", "sequential"); - list frames; - list coalesced; - list mid; - Image watermark; - readImages(&frames, Blob(data.Data(), data.Length())); - watermark.read(basePath + "assets/images/zamn.png"); - coalesceImages(&coalesced, frames.begin(), frames.end()); + VImage in = + VImage::new_from_buffer(data.Data(), data.Length(), "", + type == "gif" ? options->set("n", -1) : options) + .colourspace(VIPS_INTERPRETATION_sRGB); + if (!in.has_alpha()) in = in.bandjoin(255); - for (Image &image : coalesced) { - Image watermark_new = watermark; - image.backgroundColor("none"); - image.scale(Geometry("303x438!")); - image.extent(Geometry("621x516-310-75")); - watermark_new.composite(image, Magick::CenterGravity, - Magick::OverCompositeOp); - watermark_new.magick(type); - watermark_new.animationDelay(delay == 0 ? image.animationDelay() : delay); - mid.push_back(watermark_new); + int width = in.width(); + int page_height = vips_image_get_page_height(in.get_image()); + int n_pages = vips_image_get_n_pages(in.get_image()); + + string assetPath = basePath + "assets/images/zamn.png"; + VImage tmpl = VImage::new_from_file(assetPath.c_str()); + + vector img; + for (int i = 0; i < n_pages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * page_height, width, page_height) : in; + VImage composited = tmpl.insert( + img_frame.extract_band(0, VImage::option()->set("n", 3)).bandjoin(255).resize( + 303.0 / (double)width, + VImage::option()->set("vscale", 438.0 / (double)page_height)), + 310, 76); + img.push_back(composited); } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, 516); + if (delay) final.set("delay", delay); - optimizeTransparency(mid.begin(), mid.end()); + void *buf; + size_t length; + final.write_to_buffer(("." + type).c_str(), &buf, &length); - if (type == "gif") { - for (Image &image : mid) { - image.quantizeDitherMethod(FloydSteinbergDitherMethod); - image.quantize(); - } - } - - writeImages(mid.begin(), mid.end(), &blob); + vips_thread_shutdown(); Napi::Object result = Napi::Object::New(env); - result.Set("data", Napi::Buffer::Copy(env, (char *)blob.data(), - blob.length())); + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); result.Set("type", type); return result; } catch (std::exception const &err) {