diff --git a/assets/whisper.otf b/assets/whisper.otf new file mode 100644 index 0000000..49ed5e7 Binary files /dev/null and b/assets/whisper.otf differ diff --git a/commands/image-editing/whisper.js b/commands/image-editing/whisper.js new file mode 100644 index 0000000..e0376cd --- /dev/null +++ b/commands/image-editing/whisper.js @@ -0,0 +1,21 @@ +const ImageCommand = require("../../classes/imageCommand.js"); + +class WhisperCommand extends ImageCommand { + params(args, url) { + const newArgs = args.filter(item => !item.includes(url)); + return { + caption: newArgs.join(" ").replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%") + }; + } + + static description = "Adds a Whisper style caption to an image"; + static aliases = ["caption4"]; + static arguments = ["[text]"]; + + static requiresText = true; + static noText = "You need to provide some text to add a caption!"; + static noImage = "You need to provide an image to add a caption!"; + static command = "whisper"; +} + +module.exports = WhisperCommand; \ No newline at end of file diff --git a/natives/image.cc b/natives/image.cc index ca43fce..bccbd94 100644 --- a/natives/image.cc +++ b/natives/image.cc @@ -35,8 +35,9 @@ #include "trump.h" #include "uncaption.h" #include "wall.h" -#include "wdt.h" #include "watermark.h" +#include "wdt.h" +#include "whisper.h" Napi::Object Init(Napi::Env env, Napi::Object exports) { @@ -75,8 +76,9 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) 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, "wdt"), Napi::Function::New(env, Wdt)); exports.Set(Napi::String::New(env, "watermark"), Napi::Function::New(env, Watermark)); + exports.Set(Napi::String::New(env, "wdt"), Napi::Function::New(env, Wdt)); + exports.Set(Napi::String::New(env, "whisper"), Napi::Function::New(env, Whisper)); return exports; } diff --git a/natives/whisper.cc b/natives/whisper.cc new file mode 100644 index 0000000..626413a --- /dev/null +++ b/natives/whisper.cc @@ -0,0 +1,89 @@ +#include +#include + +#include + +using namespace std; +using namespace Magick; + +Napi::Value Whisper(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + + try { + Napi::Object obj = info[0].As(); + Napi::Buffer data = obj.Get("data").As>(); + string caption = obj.Get("caption").As().Utf8Value(); + 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 captioned; + Blob caption_blob; + readImages(&frames, Blob(data.Data(), data.Length())); + + size_t width = frames.front().baseColumns(); + size_t height = frames.front().baseRows(); + + int dividedWidth = width / 150; + + 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 / 6); + 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); + + 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); + } + + optimizeTransparency(captioned.begin(), captioned.end()); + + if (type == "gif") { + for (Image &image : captioned) { + image.quantizeDither(false); + image.quantize(); + } + } + + writeImages(captioned.begin(), captioned.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/whisper.h b/natives/whisper.h new file mode 100644 index 0000000..1b97c70 --- /dev/null +++ b/natives/whisper.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +Napi::Value Whisper(const Napi::CallbackInfo& info);