Port caption to libvips

This commit is contained in:
Essem 2022-02-12 15:09:59 -06:00
parent 93dbadcd0f
commit 2c60e404a4
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
4 changed files with 68 additions and 60 deletions

View file

@ -33,3 +33,7 @@ add_definitions(-DMAGICKCORE_QUANTUM_DEPTH=16)
add_definitions(-DMAGICKCORE_HDRI_ENABLE=0) add_definitions(-DMAGICKCORE_HDRI_ENABLE=0)
include_directories(${ImageMagick_INCLUDE_DIRS}) include_directories(${ImageMagick_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${ImageMagick_LIBRARIES}) 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})

View file

@ -4,7 +4,7 @@ const allowedFonts = ["futura", "impact", "helvetica", "arial", "roboto", "noto"
class CaptionCommand extends ImageCommand { class CaptionCommand extends ImageCommand {
params(url) { params(url) {
const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text; const newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
let newCaption = newArgs.replaceAll("&", "\\&amp;").replaceAll(">", "\\&gt;").replaceAll("<", "\\&lt;").replaceAll("\"", "\\&quot;").replaceAll("'", "\\&apos;").replaceAll("%", "\\%"); let newCaption = newArgs.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
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}`; 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 { return {
caption: newCaption, caption: newCaption,

View file

@ -1,15 +1,16 @@
#include <Magick++.h> #include <iostream>
#include <vips/vips8>
#include <napi.h> #include <napi.h>
#include <iostream>
#include <list>
using namespace std; using namespace std;
using namespace Magick; using namespace vips;
/*void finalizer(Napi::Env env, char* data) {
free(data);
}*/
Napi::Value Caption(const Napi::CallbackInfo &info) { Napi::Value Caption(const Napi::CallbackInfo &info) {
Napi::Env env = info.Env(); Napi::Env env = info.Env();
try { try {
Napi::Object obj = info[0].As<Napi::Object>(); Napi::Object obj = info[0].As<Napi::Object>();
Napi::Buffer<char> data = obj.Get("data").As<Napi::Buffer<char>>(); Napi::Buffer<char> data = obj.Get("data").As<Napi::Buffer<char>>();
@ -19,68 +20,68 @@ Napi::Value Caption(const Napi::CallbackInfo &info) {
int delay = int delay =
obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0; obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0;
Blob blob; VImage in = VImage::new_from_buffer(data.Data(), data.Length(), "", VImage::option()
->set("n", -1)
->set("access", "sequential"))
.colourspace(VIPS_INTERPRETATION_sRGB);
if (!in.has_alpha())
in = in.bandjoin(255);
list<Image> frames; int width = in.width();
list<Image> coalesced; int size = width / 10;
list<Image> captioned; int page_height = vips_image_get_page_height(in.get_image());
try { int n_pages = vips_image_get_n_pages(in.get_image());
readImages(&frames, Blob(data.Data(), data.Length())); int textWidth = width - ((width / 25) * 2);
} catch (Magick::WarningCoder &warning) {
cerr << "Coder Warning: " << warning.what() << endl; //char font_string[12 + sizeof(size)];
} catch (Magick::Warning &warning) { //sprintf(font_string, "futura bold %d", size);
cerr << "Warning: " << warning.what() << endl;
string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " + (font != "impact" ? "bold" : "normal") + " " + to_string(size);
string captionText =
"<span background=\"white\">" + caption + "</span>";
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<double>) {0, 0, 0, 0}).bandand())
.ifthenelse(255, text)
.gravity(VIPS_COMPASS_DIRECTION_CENTRE,
width, text.height() + size, VImage::option()
->set("extend", "white"));
vector<VImage> img;
for (int i = 0; i < n_pages; i++) {
VImage img_frame = in.crop(0, i * page_height, width, page_height);
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());
size_t width = frames.front().baseColumns(); void *buf;
string query(to_string(width - ((width / 25) * 2)) + "x"); size_t length;
Image caption_image(Geometry(query), Color("white")); final.write_to_buffer(("." + type).c_str(), &buf, &length, VImage::option()
caption_image.fillColor("black"); ->set("dither", 0));
caption_image.alpha(true);
caption_image.fontPointsize((double)width / 13);
caption_image.textGravity(Magick::CenterGravity);
caption_image.read("pango:<span font_family=\"" +
(font == "roboto" ? "Roboto Condensed" : font) +
"\" weight=\"" + (font != "impact" ? "bold" : "normal") +
"\">" + caption + "</span>");
caption_image.extent(Geometry(width, caption_image.rows() + (width / 13)),
Magick::CenterGravity);
coalesceImages(&coalesced, frames.begin(), frames.end()); //vips_shutdown();
vips_thread_shutdown();
for (Image &image : coalesced) {
Image appended;
list<Image> 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);
Napi::Object result = Napi::Object::New(env); Napi::Object result = Napi::Object::New(env);
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(), result.Set("data", Napi::Buffer<char>::New(env, (char *)buf,
blob.length())); length));
result.Set("type", type); result.Set("type", type);
return result; return result;
} catch (std::exception const &err) { } catch (std::exception const &err) {
cerr << "Error: " << err.what() << endl;
throw Napi::Error::New(env, err.what()); throw Napi::Error::New(env, err.what());
} catch (...) { } catch (...) {
throw Napi::Error::New(env, "Unknown error"); throw Napi::Error::New(env, "Unknown error");
} }
} }

View file

@ -43,12 +43,15 @@
#ifdef _WIN32 #ifdef _WIN32
#include <Magick++.h> #include <Magick++.h>
#endif #endif
#include <vips/vips8>
Napi::Object Init(Napi::Env env, Napi::Object exports) Napi::Object Init(Napi::Env env, Napi::Object exports)
{ {
#ifdef _WIN32 #ifdef _WIN32
Magick::InitializeMagick(""); Magick::InitializeMagick("");
#endif #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, "blur"), Napi::Function::New(env, Blur));
exports.Set(Napi::String::New(env, "colors"), Napi::Function::New(env, Colors)); exports.Set(Napi::String::New(env, "colors"), Napi::Function::New(env, Colors));
exports.Set(Napi::String::New(env, "caption"), Napi::Function::New(env, Caption)); exports.Set(Napi::String::New(env, "caption"), Napi::Function::New(env, Caption));
@ -91,4 +94,4 @@ Napi::Object Init(Napi::Env env, Napi::Object exports)
return exports; return exports;
} }
NODE_API_MODULE(addon, Init); NODE_API_MODULE(addon, Init)