Load fonts directly from assets directory, fix image buffer issues

This commit is contained in:
Essem 2022-09-22 23:44:54 -05:00
parent 273e5b94d7
commit 4f66519aa7
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
22 changed files with 188 additions and 111 deletions

View file

@ -12,15 +12,6 @@ RUN apk add --no-cache git cmake msttcorefonts-installer python3 alpine-sdk ffmp
RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store \ RUN --mount=type=cache,id=pnpm-store,target=/root/.pnpm-store \
npm install -g pnpm@6.27.1 npm install -g pnpm@6.27.1
# gets latest version of twemoji
RUN mkdir /tmp/twemoji \
&& cd /tmp/twemoji \
&& package=$(wget --quiet -O - https://fedora.mirror.liteserver.nl/linux/development/rawhide/Everything/aarch64/os/Packages/t/ | grep -Po '(?<=href=")twitter-twemoji-fonts-[^"]*' | tail -1) \
&& wget https://fedora.mirror.liteserver.nl/linux/development/rawhide/Everything/aarch64/os/Packages/t/$package \
&& rpm2cpio $package | cpio -ivd \
&& cp ./usr/share/fonts/twemoji/Twemoji.ttf /usr/share/fonts/Twemoji.ttf \
&& rm -r /tmp/twemoji
# liblqr needs to be built manually for magick to work # liblqr needs to be built manually for magick to work
# and because alpine doesn't have it in their repos # and because alpine doesn't have it in their repos
RUN git clone https://github.com/carlobaldassi/liblqr \ RUN git clone https://github.com/carlobaldassi/liblqr \
@ -53,17 +44,12 @@ RUN git clone https://github.com/ImageMagick/ImageMagick.git ImageMagick \
RUN update-ms-fonts && fc-cache -f RUN update-ms-fonts && fc-cache -f
RUN adduser esmBot -s /bin/sh -D RUN adduser esmBot -s /bin/sh -D
USER esmBot
WORKDIR /home/esmBot/.internal WORKDIR /home/esmBot/.internal
COPY ./assets/caption.otf /usr/share/fonts/caption.otf COPY --chown=esmBot:esmBot ./package.json package.json
COPY ./assets/caption2.ttf /usr/share/fonts/caption2.ttf COPY --chown=esmBot:esmBot ./pnpm-lock.yaml pnpm-lock.yaml
COPY ./assets/hbc.ttf /usr/share/fonts/hbc.ttf
COPY ./assets/reddit.ttf /usr/share/fonts/reddit.ttf
COPY ./assets/whisper.otf /usr/share/fonts/whisper.otf
RUN fc-cache -fv
COPY --chown=node:node ./package.json package.json
COPY --chown=node:node ./pnpm-lock.yaml pnpm-lock.yaml
RUN pnpm install RUN pnpm install
COPY . . COPY . .
RUN rm .env RUN rm .env
@ -81,6 +67,4 @@ RUN mkdir /home/esmBot/.internal/logs \
&& chown esmBot:esmBot /home/esmBot/.internal/logs \ && chown esmBot:esmBot /home/esmBot/.internal/logs \
&& chmod 777 /home/esmBot/.internal/logs && chmod 777 /home/esmBot/.internal/logs
USER esmBot
ENTRYPOINT ["node", "app.js"] ENTRYPOINT ["node", "app.js"]

BIN
assets/fonts/twemoji.otf Normal file

Binary file not shown.

View file

@ -77,13 +77,13 @@ class ImageCommand extends Command {
} }
try { try {
const { arrayBuffer, type } = await runImageJob(imageParams); const { buffer, type } = await runImageJob(imageParams);
if (type === "nogif" && this.constructor.requiresGIF) { if (type === "nogif" && this.constructor.requiresGIF) {
return "That isn't a GIF!"; return "That isn't a GIF!";
} }
this.success = true; this.success = true;
return { return {
file: Buffer.from(arrayBuffer), file: buffer,
name: `${this.constructor.command}.${type}` name: `${this.constructor.command}.${type}`
}; };
} catch (e) { } catch (e) {

View file

@ -17,7 +17,7 @@ class UncannyCommand extends ImageCommand {
return { return {
caption: text1?.trim() ? textEncode(text1) : random(prompts), caption: text1?.trim() ? textEncode(text1) : random(prompts),
caption2: textEncode(text2), caption2: textEncode(text2),
path: `./assets/images/uncanny/${typeof this.options.phase === "string" && names.includes(this.options.phase.toLowerCase()) ? this.options.phase.toLowerCase() : random(names.filter((val) => val !== "goated"))}.png`, path: `assets/images/uncanny/${typeof this.options.phase === "string" && names.includes(this.options.phase.toLowerCase()) ? this.options.phase.toLowerCase() : random(names.filter((val) => val !== "goated"))}.png`,
font: typeof this.options.font === "string" && this.constructor.allowedFonts.includes(this.options.font.toLowerCase()) ? this.options.font.toLowerCase() : "helvetica" font: typeof this.options.font === "string" && this.constructor.allowedFonts.includes(this.options.font.toLowerCase()) ? this.options.font.toLowerCase() : "helvetica"
}; };
} }

View file

@ -43,7 +43,7 @@ Choose the distro you're using below for insallation instructions.
### 2. Install libvips. ### 2. Install libvips.
[libvips](https://github.com/libvips/libvips) is the core of esmBot's image processing commands. The latest version (8.13.0) is recommended because it contains fixes to GIF handling and support for the freeze command; however, this version isn't packaged for most distros yet. To fix this, you'll need to build libvips from source. [libvips](https://github.com/libvips/libvips) is the core of esmBot's image processing commands. Version 8.13.0 or higher is recommended because it contains fixes to GIF handling and support for the freeze command; however, this version isn't packaged for most distros yet. To fix this, you'll need to build libvips from source.
!!! note !!! note
Alpine, Arch, and RHEL **(not Fedora!)** users can skip this step, since these distros now have 8.13.0 packaged. Alpine, Arch, and RHEL **(not Fedora!)** users can skip this step, since these distros now have 8.13.0 packaged.
@ -120,11 +120,6 @@ pnpm i -g node-gyp
pnpm install pnpm install
pnpm build pnpm build
``` ```
You'll also need to copy over some fonts for the image commands:
```sh
sudo cp assets/*.ttf assets/*.otf /usr/local/share/fonts
fc-cache -fv
```
*** ***
@ -225,13 +220,6 @@ pm2 start app.js
??? faq "Gifs from Tenor result in a "no decode delegate for this image format" or "improper image header" error" ??? faq "Gifs from Tenor result in a "no decode delegate for this image format" or "improper image header" error"
Tenor GIFs are actually stored as MP4s, which libvips can't decode most of the time. You'll need to get a Tenor API key from [here](https://developers.google.com/tenor/guides/quickstart) and put it in the `TENOR` variable in .env. Tenor GIFs are actually stored as MP4s, which libvips can't decode most of the time. You'll need to get a Tenor API key from [here](https://developers.google.com/tenor/guides/quickstart) and put it in the `TENOR` variable in .env.
??? faq "Emojis are missing in some commands"
Your system doesn't have an emoji font installed. You can install Google's emoji set with `sudo apt-get install fonts-noto-color-emoji` on Debian/Ubuntu systems, `doas apk add font-noto-emoji` on Alpine, and `sudo pacman -S noto-fonts-emoji` on Arch/Manjaro.
If you want to use the same set that Discord and the main bot uses (Twemoji) on Fedora, then you can run `sudo dnf remove google-noto-emoji-color-fonts && sudo dnf install twitter-twemoji-fonts`.
If you want to install Twemoji on another distro then it's slightly more difficult. Go [here](https://koji.fedoraproject.org/koji/packageinfo?packageID=26306) and choose the latest build, then download the `noarch` RPM file. You'll then have to extract this file; most graphical tools (e.g. 7-Zip, Ark, The Unarchiver) should be able to extract this just fine, but on the command line you'll have to use the `rpm2cpio` tool. The font file should be inside the archive at `usr/share/fonts/Twemoji/Twemoji.ttf`; copy this to `/usr/share/fonts/Twemoji.ttf` (note the / at the beginning). After this, run `fc-cache -fv` and you should be good to go!
??? faq "Sound/music commands do nothing" ??? faq "Sound/music commands do nothing"
Make sure Lavalink is running and started up completely. The bot skips loading sound commands if Lavalink is not present, so make sure it's running when the bot starts as well. Make sure Lavalink is running and started up completely. The bot skips loading sound commands if Lavalink is not present, so make sure it's running when the bot starts as well.

View file

@ -1,3 +1,4 @@
#include "common.h"
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
@ -15,6 +16,7 @@ Napi::Value Caption(const Napi::CallbackInfo &info) {
string caption = obj.Get("caption").As<Napi::String>().Utf8Value(); string caption = obj.Get("caption").As<Napi::String>().Utf8Value();
string font = obj.Get("font").As<Napi::String>().Utf8Value(); string font = obj.Get("font").As<Napi::String>().Utf8Value();
string type = obj.Get("type").As<Napi::String>().Utf8Value(); string type = obj.Get("type").As<Napi::String>().Utf8Value();
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
VOption *options = VImage::option()->set("access", "sequential"); VOption *options = VImage::option()->set("access", "sequential");
@ -31,17 +33,27 @@ Napi::Value Caption(const Napi::CallbackInfo &info) {
int nPages = vips_image_get_n_pages(in.get_image()); int nPages = vips_image_get_n_pages(in.get_image());
int textWidth = width - ((width / 25) * 2); int textWidth = width - ((width / 25) * 2);
string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " + string font_string = "Twemoji Color Emoji, " +
(font == "roboto" ? "Roboto Condensed" : font) + " " +
(font != "impact" ? "bold" : "normal") + " " + (font != "impact" ? "bold" : "normal") + " " +
to_string(size); to_string(size);
string captionText = "<span background=\"white\">" + caption + "</span>"; string captionText = "<span background=\"white\">" + caption + "</span>";
VImage text = VImage text;
VImage::text(captionText.c_str(), VImage::option() auto findResult = fontPaths.find(font);
if (findResult != fontPaths.end()) {
text = VImage::text(
".", VImage::option()->set("fontfile",
(basePath + findResult->second).c_str()));
}
text = VImage::text(
captionText.c_str(),
VImage::option()
->set("rgba", true) ->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE) ->set("align", VIPS_ALIGN_CENTRE)
->set("font", font_string.c_str()) ->set("font", font_string.c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", textWidth)); ->set("width", textWidth));
VImage captionImage = VImage captionImage =
((text == (vector<double>){0, 0, 0, 0}).bandand()) ((text == (vector<double>){0, 0, 0, 0}).bandand())

View file

@ -1,3 +1,4 @@
#include "common.h"
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
@ -16,6 +17,7 @@ Napi::Value CaptionTwo(const Napi::CallbackInfo &info) {
bool top = obj.Get("top").As<Napi::Boolean>().Value(); bool top = obj.Get("top").As<Napi::Boolean>().Value();
string font = obj.Get("font").As<Napi::String>().Utf8Value(); string font = obj.Get("font").As<Napi::String>().Utf8Value();
string type = obj.Get("type").As<Napi::String>().Utf8Value(); string type = obj.Get("type").As<Napi::String>().Utf8Value();
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
VOption *options = VImage::option()->set("access", "sequential"); VOption *options = VImage::option()->set("access", "sequential");
@ -32,17 +34,27 @@ Napi::Value CaptionTwo(const Napi::CallbackInfo &info) {
int nPages = vips_image_get_n_pages(in.get_image()); int nPages = vips_image_get_n_pages(in.get_image());
int textWidth = width - ((width / 25) * 2); int textWidth = width - ((width / 25) * 2);
string font_string = string font_string = "Twemoji Color Emoji, " +
(font == "roboto" ? "Roboto Condensed" : font) + " " + to_string(size); (font == "roboto" ? "Roboto Condensed" : font) + " " +
to_string(size);
string captionText = "<span background=\"white\">" + caption + "</span>"; string captionText = "<span background=\"white\">" + caption + "</span>";
VImage text = VImage text;
VImage::text(captionText.c_str(), VImage::option() auto findResult = fontPaths.find(font);
->set("rgba", true) if (findResult != fontPaths.end()) {
->set("font", font_string.c_str()) text = VImage::text(
->set("align", VIPS_ALIGN_LOW) ".", VImage::option()->set("fontfile",
->set("width", textWidth)); (basePath + findResult->second).c_str()));
}
text = VImage::text(
captionText.c_str(),
VImage::option()
->set("rgba", true)
->set("font", font_string.c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("align", VIPS_ALIGN_LOW)
->set("width", textWidth));
VImage captionImage = VImage captionImage =
((text == (vector<double>){0, 0, 0, 0}).bandand()) ((text == (vector<double>){0, 0, 0, 0}).bandand())
.ifthenelse(255, text) .ifthenelse(255, text)

10
natives/common.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include <string>
#include <unordered_map>
const std::unordered_map<std::string, std::string> fontPaths {
{"futura", "assets/fonts/caption.otf"},
{"helvetica", "assets/fonts/caption2.ttf"},
{"roboto", "assets/fonts/reddit.ttf"}
};

View file

@ -17,14 +17,18 @@ Napi::Value Homebrew(const Napi::CallbackInfo &info) {
string assetPath = basePath + "assets/images/hbc.png"; string assetPath = basePath + "assets/images/hbc.png";
VImage bg = VImage::new_from_file(assetPath.c_str()); VImage bg = VImage::new_from_file(assetPath.c_str());
VImage text = VImage text = VImage::text(
VImage::text(("<span letter_spacing=\"-5120\" color=\"white\">" + ".", VImage::option()->set(
caption + "</span>") "fontfile", (basePath + "assets/fonts/hbc.ttf").c_str()));
.c_str(), text = VImage::text(
VImage::option() ("<span letter_spacing=\"-5120\" color=\"white\">" + caption +
->set("rgba", true) "</span>")
->set("align", VIPS_ALIGN_CENTRE) .c_str(),
->set("font", "PF Square Sans Pro 96")); VImage::option()
->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE)
->set("font", "Twemoji Color Font, PF Square Sans Pro 96")
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str()));
VImage out = bg.composite2(text, VIPS_BLEND_MODE_OVER, VImage out = bg.composite2(text, VIPS_BLEND_MODE_OVER,
VImage::option() VImage::option()

View file

@ -1,3 +1,4 @@
#include "common.h"
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
@ -16,6 +17,7 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
string bottom = obj.Get("bottom").As<Napi::String>().Utf8Value(); string bottom = obj.Get("bottom").As<Napi::String>().Utf8Value();
string font = obj.Get("font").As<Napi::String>().Utf8Value(); string font = obj.Get("font").As<Napi::String>().Utf8Value();
string type = obj.Get("type").As<Napi::String>().Utf8Value(); string type = obj.Get("type").As<Napi::String>().Utf8Value();
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
VOption *options = VImage::option()->set("access", "sequential"); VOption *options = VImage::option()->set("access", "sequential");
@ -23,7 +25,8 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
VImage::new_from_buffer(data.Data(), data.Length(), "", VImage::new_from_buffer(data.Data(), data.Length(), "",
type == "gif" ? options->set("n", -1) : options) type == "gif" ? options->set("n", -1) : options)
.colourspace(VIPS_INTERPRETATION_sRGB); .colourspace(VIPS_INTERPRETATION_sRGB);
if (!in.has_alpha()) in = in.bandjoin(255); if (!in.has_alpha())
in = in.bandjoin(255);
int width = in.width(); int width = in.width();
int pageHeight = vips_image_get_page_height(in.get_image()); int pageHeight = vips_image_get_page_height(in.get_image());
@ -33,7 +36,8 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
int rad = 1; int rad = 1;
vector<double> zeroVec = {0, 0, 0, 0}; vector<double> zeroVec = {0, 0, 0, 0};
string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " + string font_string = "Twemoji Color Font, " +
(font == "roboto" ? "Roboto Condensed" : font) + " " +
(font != "impact" ? "bold" : "normal") + " " + (font != "impact" ? "bold" : "normal") + " " +
to_string(size); to_string(size);
@ -48,6 +52,13 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
VImage::option()->set("fill", true)); VImage::option()->set("fill", true));
} }
auto findResult = fontPaths.find(font);
if (findResult != fontPaths.end()) {
VImage::text(
".", VImage::option()->set("fontfile",
(basePath + findResult->second).c_str()));
}
VImage topText; VImage topText;
if (top != "") { if (top != "") {
VImage topIn = VImage::text( VImage topIn = VImage::text(
@ -56,6 +67,7 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
->set("rgba", true) ->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE) ->set("align", VIPS_ALIGN_CENTRE)
->set("font", font_string.c_str()) ->set("font", font_string.c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", width)); ->set("width", width));
topIn = topIn.embed(rad + 10, rad + 10, (topIn.width() + 2 * rad) + 20, topIn = topIn.embed(rad + 10, rad + 10, (topIn.width() + 2 * rad) + 20,
@ -70,10 +82,11 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
} }
topOutline = (topOutline == zeroVec); topOutline = (topOutline == zeroVec);
VImage topInvert = topOutline.extract_band(3).invert(); VImage topInvert = topOutline.extract_band(3).invert();
topOutline = topOutline topOutline =
.extract_band(0, VImage::option()->set( topOutline
"n", topOutline.bands() - 1)) .extract_band(0,
.bandjoin(topInvert); VImage::option()->set("n", topOutline.bands() - 1))
.bandjoin(topInvert);
topText = topOutline.composite2(topIn, VIPS_BLEND_MODE_OVER); topText = topOutline.composite2(topIn, VIPS_BLEND_MODE_OVER);
} }
@ -85,6 +98,7 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
->set("rgba", true) ->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE) ->set("align", VIPS_ALIGN_CENTRE)
->set("font", font_string.c_str()) ->set("font", font_string.c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", width)); ->set("width", width));
bottomIn = bottomIn =
bottomIn.embed(rad + 10, rad + 10, (bottomIn.width() + 2 * rad) + 20, bottomIn.embed(rad + 10, rad + 10, (bottomIn.width() + 2 * rad) + 20,
@ -130,7 +144,8 @@ Napi::Value Meme(const Napi::CallbackInfo &info) {
size_t length; size_t length;
final.write_to_buffer( final.write_to_buffer(
("." + type).c_str(), &buf, &length, ("." + type).c_str(), &buf, &length,
type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1) : 0); type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1)
: 0);
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length)); result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
result.Set("type", type); result.Set("type", type);

View file

@ -1,3 +1,4 @@
#include "common.h"
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
@ -16,6 +17,7 @@ Napi::Value Motivate(const Napi::CallbackInfo &info) {
string bottom_text = obj.Get("bottom").As<Napi::String>().Utf8Value(); string bottom_text = obj.Get("bottom").As<Napi::String>().Utf8Value();
string font = obj.Get("font").As<Napi::String>().Utf8Value(); string font = obj.Get("font").As<Napi::String>().Utf8Value();
string type = obj.Get("type").As<Napi::String>().Utf8Value(); string type = obj.Get("type").As<Napi::String>().Utf8Value();
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
VOption *options = VImage::option()->set("access", "sequential"); VOption *options = VImage::option()->set("access", "sequential");
@ -32,7 +34,15 @@ Napi::Value Motivate(const Napi::CallbackInfo &info) {
int nPages = vips_image_get_n_pages(in.get_image()); int nPages = vips_image_get_n_pages(in.get_image());
int textWidth = width - ((width / 25) * 2); int textWidth = width - ((width / 25) * 2);
string font_string = font == "roboto" ? "Roboto Condensed" : font; string font_string =
"Twemoji Color Font, " + (font == "roboto" ? "Roboto Condensed" : font);
auto findResult = fontPaths.find(font);
if (findResult != fontPaths.end()) {
VImage::text(
".", VImage::option()->set("fontfile",
(basePath + findResult->second).c_str()));
}
VImage topImage; VImage topImage;
if (top_text != "") { if (top_text != "") {
@ -45,6 +55,7 @@ Napi::Value Motivate(const Napi::CallbackInfo &info) {
->set("rgba", true) ->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE) ->set("align", VIPS_ALIGN_CENTRE)
->set("font", (font_string + " " + to_string(size)).c_str()) ->set("font", (font_string + " " + to_string(size)).c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", textWidth)); ->set("width", textWidth));
} }
@ -59,6 +70,7 @@ Napi::Value Motivate(const Napi::CallbackInfo &info) {
->set("rgba", true) ->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE) ->set("align", VIPS_ALIGN_CENTRE)
->set("font", (font_string + " " + to_string(size * 0.4)).c_str()) ->set("font", (font_string + " " + to_string(size * 0.4)).c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", textWidth)); ->set("width", textWidth));
} }
@ -96,7 +108,8 @@ Napi::Value Motivate(const Napi::CallbackInfo &info) {
VImage::option()->set("background", 0x000000)->set("expand", true)); VImage::option()->set("background", 0x000000)->set("expand", true));
} }
if (bottom_text != "") { if (bottom_text != "") {
if (top_text == "") frame = bordered3; if (top_text == "")
frame = bordered3;
frame = frame.join( frame = frame.join(
bottomImage.gravity(VIPS_COMPASS_DIRECTION_NORTH, bordered3.width(), bottomImage.gravity(VIPS_COMPASS_DIRECTION_NORTH, bordered3.width(),
bottomImage.height() + (size / 4), bottomImage.height() + (size / 4),

View file

@ -22,7 +22,8 @@ Napi::Value Reddit(const Napi::CallbackInfo &info) {
VImage::new_from_buffer(data.Data(), data.Length(), "", VImage::new_from_buffer(data.Data(), data.Length(), "",
type == "gif" ? options->set("n", -1) : options) type == "gif" ? options->set("n", -1) : options)
.colourspace(VIPS_INTERPRETATION_sRGB); .colourspace(VIPS_INTERPRETATION_sRGB);
if (!in.has_alpha()) in = in.bandjoin(255); if (!in.has_alpha())
in = in.bandjoin(255);
string assetPath = basePath + "assets/images/reddit.png"; string assetPath = basePath + "assets/images/reddit.png";
VImage tmpl = VImage::new_from_file(assetPath.c_str()); VImage tmpl = VImage::new_from_file(assetPath.c_str());
@ -33,11 +34,16 @@ Napi::Value Reddit(const Napi::CallbackInfo &info) {
string captionText = "<span foreground=\"white\">" + text + "</span>"; string captionText = "<span foreground=\"white\">" + text + "</span>";
VImage textImage = VImage textImage = VImage::text(
VImage::text(captionText.c_str(), VImage::option() ".", VImage::option()->set(
->set("rgba", true) "fontfile", (basePath + "assets/fonts/reddit.ttf").c_str()));
->set("font", "Roboto 62") textImage = VImage::text(
->set("align", VIPS_ALIGN_LOW)); captionText.c_str(),
VImage::option()
->set("rgba", true)
->set("font", "Twemoji Color Font, Roboto 62")
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("align", VIPS_ALIGN_LOW));
VImage composited = VImage composited =
tmpl.composite2(textImage, VIPS_BLEND_MODE_OVER, tmpl.composite2(textImage, VIPS_BLEND_MODE_OVER,
@ -61,7 +67,8 @@ Napi::Value Reddit(const Napi::CallbackInfo &info) {
size_t length; size_t length;
final.write_to_buffer( final.write_to_buffer(
("." + type).c_str(), &buf, &length, ("." + type).c_str(), &buf, &length,
type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1) : 0); type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1)
: 0);
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length)); result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
result.Set("type", type); result.Set("type", type);

View file

@ -16,6 +16,7 @@ Napi::Value Snapchat(const Napi::CallbackInfo &info) {
float pos = float pos =
obj.Has("pos") ? obj.Get("pos").As<Napi::Number>().FloatValue() : 0.5; obj.Has("pos") ? obj.Get("pos").As<Napi::Number>().FloatValue() : 0.5;
string type = obj.Get("type").As<Napi::String>().Utf8Value(); string type = obj.Get("type").As<Napi::String>().Utf8Value();
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
VOption *options = VImage::option()->set("access", "sequential"); VOption *options = VImage::option()->set("access", "sequential");
@ -23,7 +24,8 @@ Napi::Value Snapchat(const Napi::CallbackInfo &info) {
VImage::new_from_buffer(data.Data(), data.Length(), "", VImage::new_from_buffer(data.Data(), data.Length(), "",
type == "gif" ? options->set("n", -1) : options) type == "gif" ? options->set("n", -1) : options)
.colourspace(VIPS_INTERPRETATION_sRGB); .colourspace(VIPS_INTERPRETATION_sRGB);
if (!in.has_alpha()) in = in.bandjoin(255); if (!in.has_alpha())
in = in.bandjoin(255);
int width = in.width(); int width = in.width();
int pageHeight = vips_image_get_page_height(in.get_image()); int pageHeight = vips_image_get_page_height(in.get_image());
@ -31,17 +33,22 @@ Napi::Value Snapchat(const Napi::CallbackInfo &info) {
int size = width / 20; int size = width / 20;
int textWidth = width - ((width / 25) * 2); int textWidth = width - ((width / 25) * 2);
string font_string = "Helvetica Neue " + to_string(size); string font_string =
"Twemoji Color Font, Helvetica Neue " + to_string(size);
VImage textIn = VImage textIn = VImage::text(
VImage::text(("<span foreground=\"white\" background=\"#000000B2\">" + ".", VImage::option()->set(
caption + "</span>") "fontfile", (basePath + "assets/fonts/caption2.ttf").c_str()));
.c_str(), textIn = VImage::text(
VImage::option() ("<span foreground=\"white\" background=\"#000000B2\">" + caption +
->set("rgba", true) "</span>")
->set("align", VIPS_ALIGN_CENTRE) .c_str(),
->set("font", font_string.c_str()) VImage::option()
->set("width", textWidth)); ->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE)
->set("font", font_string.c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", textWidth));
int bgHeight = textIn.height() + (width / 25); int bgHeight = textIn.height() + (width / 25);
textIn = textIn =
((textIn == (vector<double>){0, 0, 0, 0}).bandand()) ((textIn == (vector<double>){0, 0, 0, 0}).bandand())
@ -68,7 +75,8 @@ Napi::Value Snapchat(const Napi::CallbackInfo &info) {
size_t length; size_t length;
final.write_to_buffer( final.write_to_buffer(
("." + type).c_str(), &buf, &length, ("." + type).c_str(), &buf, &length,
type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1) : 0); type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1)
: 0);
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length)); result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
result.Set("type", type); result.Set("type", type);

View file

@ -18,13 +18,16 @@ Napi::Value Sonic(const Napi::CallbackInfo &info) {
VImage bg = VImage::new_from_file(assetPath.c_str()); VImage bg = VImage::new_from_file(assetPath.c_str());
VImage textImage = VImage textImage =
VImage::text(("<span foreground=\"white\">" + text + "</span>").c_str(), VImage::text(
VImage::option() ("<span foreground=\"white\">" + text + "</span>").c_str(),
->set("rgba", true) VImage::option()
->set("align", VIPS_ALIGN_CENTRE) ->set("rgba", true)
->set("font", "Bitstream Vera Sans") ->set("align", VIPS_ALIGN_CENTRE)
->set("width", 542) ->set("font", "Twemoji Color Font, Bitstream Vera Sans")
->set("height", 390)) ->set("fontfile",
(basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", 542)
->set("height", 390))
.gravity(VIPS_COMPASS_DIRECTION_CENTRE, 542, 390); .gravity(VIPS_COMPASS_DIRECTION_CENTRE, 542, 390);
VImage out = bg.composite2(textImage, VIPS_BLEND_MODE_OVER, VImage out = bg.composite2(textImage, VIPS_BLEND_MODE_OVER,

View file

@ -1,3 +1,4 @@
#include "common.h"
#include <napi.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
@ -17,6 +18,7 @@ Napi::Value Uncanny(const Napi::CallbackInfo &info) {
string font = obj.Get("font").As<Napi::String>().Utf8Value(); string font = obj.Get("font").As<Napi::String>().Utf8Value();
string type = obj.Get("type").As<Napi::String>().Utf8Value(); string type = obj.Get("type").As<Napi::String>().Utf8Value();
string path = obj.Get("path").As<Napi::String>().Utf8Value(); string path = obj.Get("path").As<Napi::String>().Utf8Value();
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
VOption *options = VImage::option()->set("access", "sequential"); VOption *options = VImage::option()->set("access", "sequential");
@ -28,7 +30,8 @@ Napi::Value Uncanny(const Napi::CallbackInfo &info) {
VImage base = VImage::black(1280, 720, VImage::option()->set("bands", 3)); VImage base = VImage::black(1280, 720, VImage::option()->set("bands", 3));
string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " + string font_string = "Twemoji Color Font, " +
(font == "roboto" ? "Roboto Condensed" : font) + " " +
(font != "impact" ? "bold" : "normal") + " 72"; (font != "impact" ? "bold" : "normal") + " 72";
string captionText = "<span background=\"black\" foreground=\"white\">" + string captionText = "<span background=\"black\" foreground=\"white\">" +
@ -36,25 +39,36 @@ Napi::Value Uncanny(const Napi::CallbackInfo &info) {
string caption2Text = string caption2Text =
"<span background=\"black\" foreground=\"red\">" + caption2 + "</span>"; "<span background=\"black\" foreground=\"red\">" + caption2 + "</span>";
VImage text = auto findResult = fontPaths.find(font);
VImage::text(captionText.c_str(), VImage::option() if (findResult != fontPaths.end()) {
->set("rgba", true) VImage::text(
->set("align", VIPS_ALIGN_CENTRE) ".", VImage::option()->set("fontfile",
->set("font", font_string.c_str()) (basePath + findResult->second).c_str()));
->set("width", 588) }
->set("height", 90));
VImage text = VImage::text(
captionText.c_str(),
VImage::option()
->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE)
->set("font", font_string.c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", 588)
->set("height", 90));
VImage captionImage = VImage captionImage =
text.extract_band(0, VImage::option()->set("n", 3)) text.extract_band(0, VImage::option()->set("n", 3))
.gravity(VIPS_COMPASS_DIRECTION_CENTRE, 640, text.height() + 40, .gravity(VIPS_COMPASS_DIRECTION_CENTRE, 640, text.height() + 40,
VImage::option()->set("extend", "black")); VImage::option()->set("extend", "black"));
VImage text2 = VImage::text(caption2Text.c_str(), VImage text2 = VImage::text(
VImage::option() caption2Text.c_str(),
->set("rgba", true) VImage::option()
->set("align", VIPS_ALIGN_CENTRE) ->set("rgba", true)
->set("font", font_string.c_str()) ->set("align", VIPS_ALIGN_CENTRE)
->set("width", 588) ->set("font", font_string.c_str())
->set("height", 90)); ->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", 588)
->set("height", 90));
VImage caption2Image = VImage caption2Image =
text2.extract_band(0, VImage::option()->set("n", 3)) text2.extract_band(0, VImage::option()->set("n", 3))
.gravity(VIPS_COMPASS_DIRECTION_CENTRE, 640, text.height() + 40, .gravity(VIPS_COMPASS_DIRECTION_CENTRE, 640, text.height() + 40,
@ -66,7 +80,7 @@ Napi::Value Uncanny(const Napi::CallbackInfo &info) {
int pageHeight = vips_image_get_page_height(in.get_image()); int pageHeight = vips_image_get_page_height(in.get_image());
int nPages = vips_image_get_n_pages(in.get_image()); int nPages = vips_image_get_n_pages(in.get_image());
VImage uncanny = VImage::new_from_file(path.c_str()); VImage uncanny = VImage::new_from_file((basePath + path).c_str());
base = base.insert(uncanny, 0, 130); base = base.insert(uncanny, 0, 130);

View file

@ -14,6 +14,7 @@ Napi::Value Whisper(const Napi::CallbackInfo &info) {
Napi::Buffer<char> data = obj.Get("data").As<Napi::Buffer<char>>(); Napi::Buffer<char> data = obj.Get("data").As<Napi::Buffer<char>>();
string caption = obj.Get("caption").As<Napi::String>().Utf8Value(); string caption = obj.Get("caption").As<Napi::String>().Utf8Value();
string type = obj.Get("type").As<Napi::String>().Utf8Value(); string type = obj.Get("type").As<Napi::String>().Utf8Value();
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
VOption *options = VImage::option()->set("access", "sequential"); VOption *options = VImage::option()->set("access", "sequential");
@ -21,7 +22,8 @@ Napi::Value Whisper(const Napi::CallbackInfo &info) {
VImage::new_from_buffer(data.Data(), data.Length(), "", VImage::new_from_buffer(data.Data(), data.Length(), "",
type == "gif" ? options->set("n", -1) : options) type == "gif" ? options->set("n", -1) : options)
.colourspace(VIPS_INTERPRETATION_sRGB); .colourspace(VIPS_INTERPRETATION_sRGB);
if (!in.has_alpha()) in = in.bandjoin(255); if (!in.has_alpha())
in = in.bandjoin(255);
int width = in.width(); int width = in.width();
int pageHeight = vips_image_get_page_height(in.get_image()); int pageHeight = vips_image_get_page_height(in.get_image());
@ -30,7 +32,7 @@ Napi::Value Whisper(const Napi::CallbackInfo &info) {
int dividedWidth = width / 175; int dividedWidth = width / 175;
int rad = 1; int rad = 1;
string font_string = "Upright " + to_string(size); string font_string = "Twemoji Color Font, Upright " + to_string(size);
VImage mask; VImage mask;
if (dividedWidth >= 1) { if (dividedWidth >= 1) {
@ -44,11 +46,15 @@ Napi::Value Whisper(const Napi::CallbackInfo &info) {
} }
VImage textIn = VImage::text( VImage textIn = VImage::text(
".", VImage::option()->set(
"fontfile", (basePath + "assets/fonts/whisper.otf").c_str()));
textIn = VImage::text(
("<span foreground=\"white\">" + caption + "</span>").c_str(), ("<span foreground=\"white\">" + caption + "</span>").c_str(),
VImage::option() VImage::option()
->set("rgba", true) ->set("rgba", true)
->set("align", VIPS_ALIGN_CENTRE) ->set("align", VIPS_ALIGN_CENTRE)
->set("font", font_string.c_str()) ->set("font", font_string.c_str())
->set("fontfile", (basePath + "assets/fonts/twemoji.otf").c_str())
->set("width", width)); ->set("width", width));
textIn = textIn.embed(rad + 10, rad + 10, (textIn.width() + 2 * rad) + 20, textIn = textIn.embed(rad + 10, rad + 10, (textIn.width() + 2 * rad) + 20,
@ -82,7 +88,8 @@ Napi::Value Whisper(const Napi::CallbackInfo &info) {
size_t length; size_t length;
final.write_to_buffer( final.write_to_buffer(
("." + type).c_str(), &buf, &length, ("." + type).c_str(), &buf, &length,
type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1) : 0); type == "gif" ? VImage::option()->set("dither", 0)->set("reoptimise", 1)
: 0);
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length)); result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
result.Set("type", type); result.Set("type", type);

View file

@ -140,7 +140,7 @@ class ImageConnection {
type = contentType; type = contentType;
break; break;
} }
return { arrayBuffer: await req.body.arrayBuffer(), type }; return { buffer: Buffer.from(await req.body.arrayBuffer()), type };
} }
async getCount() { async getCount() {