diff --git a/assets/images/uncanny/goated.png b/assets/images/uncanny/goated.png new file mode 100644 index 0000000..232bd20 Binary files /dev/null and b/assets/images/uncanny/goated.png differ diff --git a/assets/images/uncanny/nerd.png b/assets/images/uncanny/nerd.png new file mode 100644 index 0000000..88a8abc Binary files /dev/null and b/assets/images/uncanny/nerd.png differ diff --git a/assets/images/uncanny/normal.png b/assets/images/uncanny/normal.png new file mode 100644 index 0000000..7e0681b Binary files /dev/null and b/assets/images/uncanny/normal.png differ diff --git a/assets/images/uncanny/uncanny.png b/assets/images/uncanny/uncanny.png new file mode 100644 index 0000000..0d69cd5 Binary files /dev/null and b/assets/images/uncanny/uncanny.png differ diff --git a/assets/images/uncanny/uncanny2.png b/assets/images/uncanny/uncanny2.png new file mode 100644 index 0000000..f07d6d5 Binary files /dev/null and b/assets/images/uncanny/uncanny2.png differ diff --git a/assets/images/uncanny/uncanny3.png b/assets/images/uncanny/uncanny3.png new file mode 100644 index 0000000..66a8589 Binary files /dev/null and b/assets/images/uncanny/uncanny3.png differ diff --git a/assets/images/uncanny/uncanny4.png b/assets/images/uncanny/uncanny4.png new file mode 100644 index 0000000..d9c9d2f Binary files /dev/null and b/assets/images/uncanny/uncanny4.png differ diff --git a/assets/images/uncanny/uncanny5.png b/assets/images/uncanny/uncanny5.png new file mode 100644 index 0000000..2e6c8c0 Binary files /dev/null and b/assets/images/uncanny/uncanny5.png differ diff --git a/assets/images/uncanny/uncanny6.png b/assets/images/uncanny/uncanny6.png new file mode 100644 index 0000000..6bca9e7 Binary files /dev/null and b/assets/images/uncanny/uncanny6.png differ diff --git a/assets/images/uncanny/uncanny7.png b/assets/images/uncanny/uncanny7.png new file mode 100644 index 0000000..79273be Binary files /dev/null and b/assets/images/uncanny/uncanny7.png differ diff --git a/assets/images/uncanny/uncanny8.png b/assets/images/uncanny/uncanny8.png new file mode 100644 index 0000000..17d596e Binary files /dev/null and b/assets/images/uncanny/uncanny8.png differ diff --git a/assets/images/uncanny/young.png b/assets/images/uncanny/young.png new file mode 100644 index 0000000..64386c1 Binary files /dev/null and b/assets/images/uncanny/young.png differ diff --git a/classes/imageCommand.js b/classes/imageCommand.js index ef34270..16b89d3 100644 --- a/classes/imageCommand.js +++ b/classes/imageCommand.js @@ -72,6 +72,7 @@ class ImageCommand extends Command { magickParams.path = image.path; magickParams.params.type = image.type; magickParams.url = image.url; // technically not required but can be useful for text filtering + magickParams.name = image.name; if (this.constructor.requiresGIF) magickParams.onlyGIF = true; } catch (e) { runningCommands.delete(this.author.id); @@ -89,7 +90,7 @@ class ImageCommand extends Command { switch (typeof this.params) { case "function": - Object.assign(magickParams.params, this.params(magickParams.url)); + Object.assign(magickParams.params, this.params(magickParams.url, magickParams.name)); break; case "object": Object.assign(magickParams.params, this.params); diff --git a/commands/image-editing/uncanny.js b/commands/image-editing/uncanny.js new file mode 100644 index 0000000..ab637f3 --- /dev/null +++ b/commands/image-editing/uncanny.js @@ -0,0 +1,61 @@ +import ImageCommand from "../../classes/imageCommand.js"; +import { random } from "../../utils/misc.js"; +import { readdirSync } from "fs"; +import { resolve, dirname } from "path"; +import { fileURLToPath } from "url"; +const prompts = ["you found:", "your dad is:", "you ate:", "your mom is:", "your sister is:", "you saw:", "you get lost in:", "you find:", "you grab:", "you pull out of your pocket:", "you fight:", "it's in your room:"]; +const names = readdirSync(resolve(dirname(fileURLToPath(import.meta.url)), "../../assets/images/uncanny/")).map((val) => { + return val.split(".")[0]; +}); + +class UncannyCommand extends ImageCommand { + params(url, name = "unknown") { + const newArgs = this.options.text ?? this.args.filter(item => !item.includes(url)).join(" "); + // eslint-disable-next-line prefer-const + let [text1, text2] = newArgs.split(/(? elem.trim()); + if (!text2?.trim()) text2 = name; + return { + caption: text1?.trim() ? text1.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n") : random(prompts), + caption2: text2.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"), + path: `./assets/images/uncanny/${typeof this.options.phase === "string" && names.includes(this.options.phase.toLowerCase()) ? this.options.phase.toLowerCase() : random(names)}.png`, + font: typeof this.options.font === "string" && this.constructor.allowedFonts.includes(this.options.font.toLowerCase()) ? this.options.font.toLowerCase() : "helvetica" + }; + } + + static init() { + super.init(); + this.flags.push({ + name: "font", + type: 3, + choices: (() => { + const array = []; + for (const font of this.allowedFonts) { + array.push({ name: font, value: font }); + } + return array; + })(), + description: "Specify the font you want to use (default: helvetica)" + }, { + name: "phase", + type: 3, + choices: (() => { + const array = []; + for (const name of names) { + array.push({ name, value: name }); + } + return array; + })(), + description: "Specify the uncanny image you want to use" + }); + return this; + } + + static description = "Makes a Mr. Incredible Becomes Uncanny image (separate left/right text with a comma)"; + static aliases = ["canny", "incredible", "pain"]; + static arguments = ["{left text}", "{right text}"]; + + static noImage = "You need to provide an image/GIF to create an uncanny image!"; + static command = "uncanny"; +} + +export default UncannyCommand; diff --git a/natives/image.cc b/natives/image.cc index b7a5b89..83c833a 100644 --- a/natives/image.cc +++ b/natives/image.cc @@ -30,6 +30,7 @@ #include "spin.h" #include "tile.h" #include "togif.h" +#include "uncanny.h" #include "uncaption.h" #include "wall.h" #include "watermark.h" @@ -79,6 +80,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) 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, "togif"), Napi::Function::New(env, ToGif)); + exports.Set(Napi::String::New(env, "uncanny"), Napi::Function::New(env, Uncanny)); 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)); diff --git a/natives/uncanny.cc b/natives/uncanny.cc new file mode 100644 index 0000000..9346d7c --- /dev/null +++ b/natives/uncanny.cc @@ -0,0 +1,109 @@ +#include +#include + +#include +#include + +using namespace std; +using namespace vips; + +Napi::Value Uncanny(const Napi::CallbackInfo &info) { + Napi::Env env = info.Env(); + Napi::Object result = Napi::Object::New(env); + + try { + Napi::Object obj = info[0].As(); + Napi::Buffer data = obj.Get("data").As>(); + string caption = obj.Get("caption").As().Utf8Value(); + string caption2 = obj.Get("caption2").As().Utf8Value(); + string font = obj.Get("font").As().Utf8Value(); + string type = obj.Get("type").As().Utf8Value(); + string path = obj.Get("path").As().Utf8Value(); + + 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) + .extract_band(0, VImage::option()->set("n", 3)); + + VImage base = VImage::black(1280, 720, VImage::option()->set("bands", 3)); + + string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " + + (font != "impact" ? "bold" : "normal") + " 72"; + + string captionText = "" + + caption + ""; + string caption2Text = + "" + caption2 + ""; + + 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", 588) + ->set("height", 90)); + VImage captionImage = + text.extract_band(0, VImage::option()->set("n", 3)) + .gravity(VIPS_COMPASS_DIRECTION_CENTRE, 640, text.height() + 40, + VImage::option()->set("extend", "black")); + + VImage text2 = VImage::text(caption2Text.c_str(), + VImage::option() + ->set("rgba", true) + ->set("align", VIPS_ALIGN_CENTRE) + ->set("font", font_string.c_str()) + ->set("width", 588) + ->set("height", 90)); + VImage caption2Image = + text2.extract_band(0, VImage::option()->set("n", 3)) + .gravity(VIPS_COMPASS_DIRECTION_CENTRE, 640, text.height() + 40, + VImage::option()->set("extend", "black")); + + base = base.insert(captionImage, 0, 0).insert(caption2Image, 640, 0); + + int width = in.width(); + int pageHeight = vips_image_get_page_height(in.get_image()); + int nPages = vips_image_get_n_pages(in.get_image()); + + VImage uncanny = VImage::new_from_file(path.c_str()); + + base = base.insert(uncanny, 0, 130); + + vector img; + for (int i = 0; i < nPages; i++) { + VImage img_frame = + type == "gif" ? in.crop(0, i * pageHeight, width, pageHeight) : in; + VImage resized = img_frame.resize(690.0 / (double)width); + if (resized.height() > 590) { + double vscale = 590.0 / (double)resized.height(); + resized = + resized.resize(vscale, VImage::option()->set("vscale", vscale)); + } + VImage composited = base.insert(resized, 935 - (resized.width() / 2), + 425 - (resized.height() / 2)); + img.push_back(composited); + } + VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1)); + final.set(VIPS_META_PAGE_HEIGHT, 720); + + void *buf; + size_t length; + final.write_to_buffer(("." + type).c_str(), &buf, &length, + type == "gif" ? VImage::option()->set("reoptimise", 1) + : 0); + + result.Set("data", Napi::Buffer::Copy(env, (char *)buf, length)); + result.Set("type", type); + } catch (std::exception const &err) { + Napi::Error::New(env, err.what()).ThrowAsJavaScriptException(); + } catch (...) { + Napi::Error::New(env, "Unknown error").ThrowAsJavaScriptException(); + } + + vips_error_clear(); + vips_thread_shutdown(); + return result; +} \ No newline at end of file diff --git a/natives/uncanny.h b/natives/uncanny.h new file mode 100644 index 0000000..933bd16 --- /dev/null +++ b/natives/uncanny.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +Napi::Value Uncanny(const Napi::CallbackInfo& info); diff --git a/utils/imagedetect.js b/utils/imagedetect.js index e999713..b9f9b97 100644 --- a/utils/imagedetect.js +++ b/utils/imagedetect.js @@ -38,9 +38,12 @@ const videoFormats = ["video/mp4", "video/webm", "video/mov"]; // gets the proper image paths const getImage = async (image, image2, video, extraReturnTypes, gifv = false, type = null, link = false) => { try { + const fileNameSplit = new URL(image).pathname.split("/"); + const fileName = fileNameSplit[fileNameSplit.length - 1].split(".")[0]; const payload = { url: image2, - path: image + path: image, + name: fileName }; const host = new URL(image2).host; if (gifv || (link && combined.includes(host))) {