Merge branch 'vips'
This commit is contained in:
commit
d3b3e67104
45 changed files with 1243 additions and 1396 deletions
|
@ -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})
|
||||||
|
|
BIN
assets/images/globediffuse.png
Normal file
BIN
assets/images/globediffuse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
assets/images/globespec.png
Normal file
BIN
assets/images/globespec.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
assets/images/speech.png
Normal file
BIN
assets/images/speech.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 234 KiB |
|
@ -1,11 +1,11 @@
|
||||||
import wrap from "../../utils/wrap.js";
|
//import wrap from "../../utils/wrap.js";
|
||||||
import ImageCommand from "../../classes/imageCommand.js";
|
import ImageCommand from "../../classes/imageCommand.js";
|
||||||
|
|
||||||
class SonicCommand extends ImageCommand {
|
class SonicCommand extends ImageCommand {
|
||||||
params() {
|
params() {
|
||||||
const cleanedMessage = (this.type === "classic" ? this.args.join(" ") : this.options.text).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%");
|
const cleanedMessage = (this.type === "classic" ? this.args.join(" ") : this.options.text).replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%");
|
||||||
return {
|
return {
|
||||||
text: wrap(cleanedMessage, {width: 15, indent: ""})
|
text: cleanedMessage
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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("&", "\\&").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}`;
|
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,
|
||||||
|
|
|
@ -6,7 +6,7 @@ class CaptionTwoCommand 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;
|
||||||
return {
|
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,
|
top: !!this.specialArgs.top,
|
||||||
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica"
|
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica"
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
|
|
@ -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 newArgs = this.type === "classic" ? this.args.filter(item => !item.includes(url)).join(" ") : this.options.text;
|
||||||
const [topText, bottomText] = newArgs.split(/(?<!\\),/).map(elem => elem.trim());
|
const [topText, bottomText] = newArgs.split(/(?<!\\),/).map(elem => elem.trim());
|
||||||
return {
|
return {
|
||||||
top: (this.specialArgs.case ? topText : topText.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("%", "\\%") : "",
|
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"
|
font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "impact"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,25 @@
|
||||||
import ImageCommand from "../../classes/imageCommand.js";
|
import ImageCommand from "../../classes/imageCommand.js";
|
||||||
|
|
||||||
class SpeechBubbleCommand extends ImageCommand {
|
class SpeechBubbleCommand extends ImageCommand {
|
||||||
params = {
|
params() {
|
||||||
water: "assets/images/speechbubble.png",
|
return {
|
||||||
gravity: "north",
|
water: this.specialArgs.alpha ? "assets/images/speech.png" : "assets/images/speechbubble.png",
|
||||||
resize: true,
|
gravity: "north",
|
||||||
yscale: 0.2,
|
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 description = "Adds a speech bubble to an image";
|
||||||
static aliases = ["speech", "sb"];
|
static aliases = ["speech", "sb"];
|
||||||
|
|
|
@ -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;
|
|
|
@ -4,7 +4,7 @@ class WhisperCommand 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;
|
||||||
return {
|
return {
|
||||||
caption: newArgs.replaceAll("&", "\\&").replaceAll(">", "\\>").replaceAll("<", "\\<").replaceAll("\"", "\\"").replaceAll("'", "\\'").replaceAll("%", "\\%")
|
caption: newArgs.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Blur(const Napi::CallbackInfo &info) {
|
Napi::Value Blur(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -18,42 +18,29 @@ Napi::Value Blur(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
try {
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
} catch (Magick::WarningCoder &warning) {
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
|
||||||
} catch (Magick::Warning &warning) {
|
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
|
||||||
}
|
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
|
||||||
|
|
||||||
if (sharp) {
|
// TODO: find a better way to calculate the intensity for GIFs without
|
||||||
for_each(coalesced.begin(), coalesced.end(), sharpenImage(0, 3));
|
// splitting frames
|
||||||
} else {
|
VImage out = sharp ? in.sharpen(VImage::option()->set("sigma", 3))
|
||||||
for_each(coalesced.begin(), coalesced.end(), blurImage(15));
|
: 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") {
|
vips_thread_shutdown();
|
||||||
for (Image &image : coalesced) {
|
|
||||||
image.quantizeDitherMethod(FloydSteinbergDitherMethod);
|
|
||||||
image.quantize();
|
|
||||||
if (delay != 0) image.animationDelay(delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(coalesced.begin(), coalesced.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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
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,63 +16,61 @@ 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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> captioned;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
} catch (Magick::WarningCoder &warning) {
|
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
int width = in.width();
|
||||||
} catch (Magick::Warning &warning) {
|
int size = width / 10;
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
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 = "<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 =
|
||||||
|
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();
|
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(
|
||||||
caption_image.fillColor("black");
|
("." + type).c_str(), &buf, &length,
|
||||||
caption_image.alpha(true);
|
type == "gif" ? VImage::option()->set("dither", 0) : 0);
|
||||||
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_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, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value CaptionTwo(const Napi::CallbackInfo &info) {
|
Napi::Value CaptionTwo(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -20,67 +18,63 @@ Napi::Value CaptionTwo(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> captioned;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
Blob caption_blob;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
try {
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
|
||||||
} catch (Magick::WarningCoder &warning) {
|
int width = in.width();
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
int size = width / 13;
|
||||||
} catch (Magick::Warning &warning) {
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
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 = "<span background=\"white\">" + caption + "</span>";
|
||||||
|
|
||||||
|
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<double>){0, 0, 0, 0}).bandand())
|
||||||
|
.ifthenelse(255, text)
|
||||||
|
.embed(width / 25, width / 25, width, text.height() + size,
|
||||||
|
VImage::option()->set("extend", "white"));
|
||||||
|
|
||||||
|
vector<VImage> 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();
|
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(
|
||||||
caption_image.fillColor("black");
|
("." + type).c_str(), &buf, &length,
|
||||||
caption_image.font("Helvetica Neue");
|
type == "gif" ? VImage::option()->set("dither", 0) : 0);
|
||||||
caption_image.fontPointsize(width / 17);
|
|
||||||
caption_image.read("pango:<span font_family=\"" +
|
|
||||||
(font == "roboto" ? "Roboto Condensed" : font) +
|
|
||||||
"\">" + caption + "</span>");
|
|
||||||
caption_image.extent(Geometry(width, caption_image.rows() + (width / 25)),
|
|
||||||
Magick::CenterGravity);
|
|
||||||
|
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
vips_thread_shutdown();
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
|
||||||
Image appended;
|
|
||||||
list<Image> 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);
|
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
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::Value Colors(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -13,56 +14,37 @@ Napi::Value Colors(const Napi::CallbackInfo &info) {
|
||||||
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>>();
|
||||||
bool old =
|
|
||||||
obj.Has("old") ? obj.Get("old").As<Napi::Boolean>().Value() : false;
|
|
||||||
string color = obj.Get("color").As<Napi::String>().Utf8Value();
|
string color = obj.Get("color").As<Napi::String>().Utf8Value();
|
||||||
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
||||||
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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> colored;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
|
||||||
} catch (Magick::WarningCoder &warning) {
|
VImage out;
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
|
||||||
} catch (Magick::Warning &warning) {
|
if (color == "blurple") {
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
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) {
|
void *buf;
|
||||||
if (color == "blurple") {
|
size_t length;
|
||||||
image.threshold(49151.25);
|
out.write_to_buffer(("." + type).c_str(), &buf, &length);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
optimizeTransparency(colored.begin(), colored.end());
|
vips_thread_shutdown();
|
||||||
|
|
||||||
if (type == "gif") {
|
|
||||||
for (Image &image : colored) {
|
|
||||||
image.quantizeDitherMethod(FloydSteinbergDitherMethod);
|
|
||||||
image.quantize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(colored.begin(), colored.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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Crop(const Napi::CallbackInfo &info) {
|
Napi::Value Crop(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -17,49 +15,48 @@ Napi::Value Crop(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
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) {
|
int width = in.width();
|
||||||
image.extent(Geometry(to_string(image.columns() / image.rows() >= 1
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
? image.rows()
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
: image.columns()) +
|
|
||||||
"x"),
|
vector<VImage> img;
|
||||||
Magick::CenterGravity);
|
int finalHeight = 0;
|
||||||
image.extent(Geometry("x" + to_string(image.columns() / image.rows() <= 1
|
for (int i = 0; i < n_pages; i++) {
|
||||||
? image.columns()
|
VImage img_frame =
|
||||||
: image.rows())),
|
type == "gif" ? in.crop(0, i * page_height, width, page_height) : in;
|
||||||
Magick::CenterGravity);
|
int frameWidth = img_frame.width();
|
||||||
image.magick(type);
|
int frameHeight = img_frame.height();
|
||||||
mid.push_back(image);
|
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") {
|
void *buf;
|
||||||
for (Image &image : mid) {
|
size_t length;
|
||||||
image.quantizeDither(false);
|
final.write_to_buffer(
|
||||||
image.quantize();
|
("." + type).c_str(), &buf, &length,
|
||||||
if (delay != 0) image.animationDelay(delay);
|
type == "gif" ? VImage::option()->set("dither", 0) : 0);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(mid.begin(), mid.end(), &blob);
|
vips_thread_shutdown();
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Deepfry(const Napi::CallbackInfo &info) {
|
Napi::Value Deepfry(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -17,47 +15,39 @@ Napi::Value Deepfry(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;
|
|
||||||
|
|
||||||
list<Image> frames;
|
|
||||||
list<Image> coalesced;
|
|
||||||
list<Image> 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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::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<char>::Copy(env, (char *)buf, length));
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Flag(const Napi::CallbackInfo &info) {
|
Napi::Value Flag(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -19,50 +17,52 @@ Napi::Value Flag(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;
|
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<Image> frames;
|
|
||||||
list<Image> coalesced;
|
|
||||||
list<Image> 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;
|
string assetPath = basePath + overlay;
|
||||||
watermark.read(assetPath);
|
VImage overlayInput = VImage::new_from_file(assetPath.c_str());
|
||||||
watermark.alphaChannel(Magick::SetAlphaChannel);
|
VImage overlayImage = overlayInput.resize(
|
||||||
watermark.evaluate(Magick::AlphaChannel, Magick::MultiplyEvaluateOperator,
|
(double)width / (double)overlayInput.width(),
|
||||||
0.5);
|
VImage::option()->set(
|
||||||
string query(to_string(frames.front().baseColumns()) + "x" +
|
"vscale", (double)page_height / (double)overlayInput.height()));
|
||||||
to_string(frames.front().baseRows()) + "!");
|
if (!overlayImage.has_alpha()) {
|
||||||
watermark.scale(Geometry(query));
|
overlayImage = overlayImage.bandjoin(127);
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
} else {
|
||||||
|
// this is a pretty cool line, just saying
|
||||||
for (Image &image : coalesced) {
|
overlayImage = overlayImage * vector<double>{1, 1, 1, 0.5};
|
||||||
image.composite(watermark, Magick::NorthGravity, Magick::OverCompositeOp);
|
|
||||||
image.magick(type);
|
|
||||||
mid.push_back(image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizeTransparency(mid.begin(), mid.end());
|
vector<VImage> img;
|
||||||
|
for (int i = 0; i < n_pages; i++) {
|
||||||
if (type == "gif") {
|
VImage img_frame =
|
||||||
for (Image &image : mid) {
|
type == "gif" ? in.crop(0, i * page_height, width, page_height) : in;
|
||||||
image.quantizeDither(false);
|
VImage composited =
|
||||||
image.quantize();
|
img_frame.composite2(overlayImage, VIPS_BLEND_MODE_OVER);
|
||||||
if (delay != 0) image.animationDelay(delay);
|
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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Flip(const Napi::CallbackInfo &info) {
|
Napi::Value Flip(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -19,41 +17,43 @@ Napi::Value Flip(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
} 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) {
|
VImage out;
|
||||||
flop ? image.flop() : image.flip();
|
if (flop) {
|
||||||
image.magick(type);
|
out = in.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||||
mid.push_back(image);
|
} else if (type == "gif") {
|
||||||
}
|
// libvips gif handling is both a blessing and a curse
|
||||||
|
vector<VImage> img;
|
||||||
optimizeTransparency(mid.begin(), mid.end());
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
if (type == "gif") {
|
for (int i = 0; i < n_pages; i++) {
|
||||||
for (Image &image : mid) {
|
VImage img_frame = in.crop(0, i * page_height, in.width(), page_height);
|
||||||
image.quantizeDither(false);
|
VImage flipped = img_frame.flip(VIPS_DIRECTION_VERTICAL);
|
||||||
image.quantize();
|
img.push_back(flipped);
|
||||||
if (delay != 0) image.animationDelay(delay);
|
|
||||||
}
|
}
|
||||||
|
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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Freeze(const Napi::CallbackInfo &info) {
|
Napi::Value Freeze(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -54,29 +52,30 @@ Napi::Value Freeze(const Napi::CallbackInfo &info) {
|
||||||
result.Set("data",
|
result.Set("data",
|
||||||
Napi::Buffer<char>::Copy(env, newData, data.Length()));
|
Napi::Buffer<char>::Copy(env, newData, data.Length()));
|
||||||
} else if (frame >= 0 && !loop) {
|
} else if (frame >= 0 && !loop) {
|
||||||
Blob blob;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in = VImage::new_from_buffer(
|
||||||
try {
|
data.Data(), data.Length(), "",
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
} catch (Magick::WarningCoder &warning) {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
} 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);
|
|
||||||
|
|
||||||
for_each(frames.begin(), frames.end(),
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
animationIterationsImage(loop ? 0 : 1));
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
for_each(frames.begin(), frames.end(), magickImage(type));
|
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)
|
if (delay) out.set("delay", delay);
|
||||||
for_each(frames.begin(), frames.end(), animationDelayImage(delay));
|
|
||||||
writeImages(frames.begin(), frames.end(), &blob);
|
void *buf;
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
size_t length;
|
||||||
blob.length()));
|
out.write_to_buffer(("." + type).c_str(), &buf, &length);
|
||||||
|
|
||||||
|
vips_thread_shutdown();
|
||||||
|
|
||||||
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
} else {
|
} else {
|
||||||
lastPos = (char *)memchr(fileData, '\x21', data.Length());
|
lastPos = (char *)memchr(fileData, '\x21', data.Length());
|
||||||
while (lastPos != NULL) {
|
while (lastPos != NULL) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Gamexplain(const Napi::CallbackInfo &info) {
|
Napi::Value Gamexplain(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -18,47 +16,48 @@ Napi::Value Gamexplain(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;
|
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<Image> frames;
|
|
||||||
list<Image> coalesced;
|
|
||||||
list<Image> 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";
|
string assetPath = basePath + "assets/images/gamexplain.png";
|
||||||
watermark.read(assetPath);
|
VImage tmpl = VImage::new_from_file(assetPath.c_str());
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
int width = in.width();
|
||||||
image.backgroundColor("white");
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
image.scale(Geometry("1181x571!"));
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
image.extent(Geometry("1200x675-10-92"));
|
|
||||||
image.composite(watermark, Geometry("+0+0"), Magick::OverCompositeOp);
|
vector<VImage> img;
|
||||||
image.magick(type);
|
for (int i = 0; i < n_pages; i++) {
|
||||||
mid.push_back(image);
|
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") {
|
vips_thread_shutdown();
|
||||||
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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
116
natives/globe.cc
116
natives/globe.cc
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Globe(const Napi::CallbackInfo &info) {
|
Napi::Value Globe(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -15,66 +13,72 @@ Napi::Value Globe(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 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();
|
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
|
||||||
int delay =
|
|
||||||
obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0;
|
|
||||||
|
|
||||||
Blob blob;
|
VOption *options = VImage::option();
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(
|
||||||
list<Image> mid;
|
data.Data(), data.Length(), "",
|
||||||
Image distort;
|
type == "gif" ? options->set("n", -1)->set("access", "sequential")
|
||||||
Image overlay;
|
: options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
} catch (Magick::WarningCoder &warning) {
|
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
int width = in.width();
|
||||||
} catch (Magick::Warning &warning) {
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
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<VImage> 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");
|
VImage final = VImage::arrayjoin(img, VImage::option()->set("across", 1));
|
||||||
overlay.read(basePath + "assets/images/sphere_overlay.png");
|
final.set(VIPS_META_PAGE_HEIGHT, size);
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
|
||||||
|
|
||||||
if (type != "gif") {
|
if (type != "gif") {
|
||||||
list<Image>::iterator it = coalesced.begin();
|
vector<int> delay(30, 50);
|
||||||
for (int i = 0; i < 29; ++i) {
|
final.set("delay", delay);
|
||||||
coalesced.push_back(*it);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int i = 0;
|
void *buf;
|
||||||
for (Image &image : coalesced) {
|
size_t length;
|
||||||
image.scale(Geometry("500x500!"));
|
final.write_to_buffer(".gif", &buf, &length);
|
||||||
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);
|
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", "gif");
|
result.Set("type", "gif");
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <list>
|
#include <vips/vips8>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Homebrew(const Napi::CallbackInfo &info) {
|
Napi::Value Homebrew(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -14,23 +13,31 @@ Napi::Value Homebrew(const Napi::CallbackInfo &info) {
|
||||||
string caption = obj.Get("caption").As<Napi::String>().Utf8Value();
|
string caption = obj.Get("caption").As<Napi::String>().Utf8Value();
|
||||||
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
|
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
|
||||||
|
|
||||||
Blob blob;
|
|
||||||
|
|
||||||
Image image;
|
|
||||||
string assetPath = basePath + "assets/images/hbc.png";
|
string assetPath = basePath + "assets/images/hbc.png";
|
||||||
image.read(assetPath);
|
VImage bg = VImage::new_from_file(assetPath.c_str());
|
||||||
image.textGravity(Magick::CenterGravity);
|
|
||||||
image.font("./assets/hbc.ttf");
|
VImage text =
|
||||||
image.textKerning(-5);
|
VImage::text(("<span letter_spacing=\"-5120\" color=\"white\">" +
|
||||||
image.fillColor("white");
|
caption + "</span>")
|
||||||
image.fontPointsize(96);
|
.c_str(),
|
||||||
image.draw(DrawableText(0, 0, caption));
|
VImage::option()
|
||||||
image.magick("PNG");
|
->set("rgba", true)
|
||||||
image.write(&blob);
|
->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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", "png");
|
result.Set("type", "png");
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
#include "homebrew.h"
|
#include "homebrew.h"
|
||||||
#include "invert.h"
|
#include "invert.h"
|
||||||
#include "jpeg.h"
|
#include "jpeg.h"
|
||||||
#include "leak.h"
|
|
||||||
#include "magik.h"
|
#include "magik.h"
|
||||||
#include "meme.h"
|
#include "meme.h"
|
||||||
#include "mirror.h"
|
#include "mirror.h"
|
||||||
|
@ -32,7 +31,6 @@
|
||||||
#include "sonic.h"
|
#include "sonic.h"
|
||||||
#include "spin.h"
|
#include "spin.h"
|
||||||
#include "tile.h"
|
#include "tile.h"
|
||||||
#include "trump.h"
|
|
||||||
#include "uncaption.h"
|
#include "uncaption.h"
|
||||||
#include "wall.h"
|
#include "wall.h"
|
||||||
#include "watermark.h"
|
#include "watermark.h"
|
||||||
|
@ -43,12 +41,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));
|
||||||
|
@ -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, "homebrew"), Napi::Function::New(env, Homebrew));
|
||||||
exports.Set(Napi::String::New(env, "invert"), Napi::Function::New(env, Invert));
|
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, "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, "magik"), Napi::Function::New(env, Magik));
|
||||||
exports.Set(Napi::String::New(env, "meme"), Napi::Function::New(env, Meme));
|
exports.Set(Napi::String::New(env, "meme"), Napi::Function::New(env, Meme));
|
||||||
exports.Set(Napi::String::New(env, "mirror"), Napi::Function::New(env, Mirror));
|
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, "spin"), Napi::Function::New(env, Spin));
|
||||||
exports.Set(Napi::String::New(env, "swirl"), Napi::Function::New(env, Swirl));
|
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, "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, "uncaption"), Napi::Function::New(env, Uncaption));
|
||||||
exports.Set(Napi::String::New(env, "wall"), Napi::Function::New(env, Wall));
|
exports.Set(Napi::String::New(env, "wall"), Napi::Function::New(env, Wall));
|
||||||
exports.Set(Napi::String::New(env, "watermark"), Napi::Function::New(env, Watermark));
|
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;
|
return exports;
|
||||||
}
|
}
|
||||||
|
|
||||||
NODE_API_MODULE(addon, Init);
|
NODE_API_MODULE(addon, Init)
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Invert(const Napi::CallbackInfo &info) {
|
Napi::Value Invert(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -17,43 +15,29 @@ Napi::Value Invert(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
} 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_each(coalesced.begin(), coalesced.end(), negateImage());
|
VImage noAlpha =
|
||||||
for (Image &image : coalesced) {
|
in.extract_band(0, VImage::option()->set("n", in.bands() - 1));
|
||||||
image.negateChannel(Magick::AlphaChannel);
|
VImage inverted = noAlpha.invert();
|
||||||
mid.push_back(image);
|
VImage out = inverted.bandjoin(in.extract_band(3));
|
||||||
}
|
|
||||||
// Magick::ChannelType(Magick::CompositeChannels ^ Magick::AlphaChannel)
|
|
||||||
for_each(mid.begin(), mid.end(), magickImage(type));
|
|
||||||
|
|
||||||
optimizeTransparency(mid.begin(), mid.end());
|
if (delay) out.set("delay", delay);
|
||||||
|
|
||||||
if (type == "gif") {
|
void *buf;
|
||||||
for (Image &image : mid) {
|
size_t length;
|
||||||
image.quantizeDitherMethod(FloydSteinbergDitherMethod);
|
out.write_to_buffer(("." + type).c_str(), &buf, &length);
|
||||||
image.quantize();
|
|
||||||
if (delay != 0) image.animationDelay(delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(mid.begin(), mid.end(), &blob);
|
vips_thread_shutdown();
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Jpeg(const Napi::CallbackInfo &info) {
|
Napi::Value Jpeg(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -13,61 +11,58 @@ Napi::Value Jpeg(const Napi::CallbackInfo &info) {
|
||||||
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>>();
|
||||||
int quality =
|
int quality = obj.Has("quality")
|
||||||
obj.Has("quality") ? obj.Get("quality").As<Napi::Number>().Int32Value() : 0;
|
? obj.Get("quality").As<Napi::Number>().Int32Value()
|
||||||
|
: 0;
|
||||||
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
||||||
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;
|
|
||||||
|
|
||||||
Napi::Object result = Napi::Object::New(env);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
|
|
||||||
if (type == "gif") {
|
if (type == "gif") {
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(
|
||||||
list<Image> jpeged;
|
data.Data(), data.Length(), "",
|
||||||
try {
|
VImage::option()->set("access", "sequential")->set("n", -1))
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
} catch (Magick::WarningCoder &warning) {
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
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) {
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
Blob temp;
|
|
||||||
image.quality(quality);
|
void *jpgBuf;
|
||||||
image.magick("JPEG");
|
size_t jpgLength;
|
||||||
image.write(&temp);
|
in.write_to_buffer(
|
||||||
Image newImage(temp);
|
".jpg", &jpgBuf, &jpgLength,
|
||||||
newImage.magick(type);
|
VImage::option()->set("Q", quality)->set("strip", true));
|
||||||
newImage.animationDelay(delay == 0 ? image.animationDelay() : delay);
|
VImage final = VImage::new_from_buffer(jpgBuf, jpgLength, "");
|
||||||
jpeged.push_back(newImage);
|
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) {
|
vips_thread_shutdown();
|
||||||
image.quantizeDither(false);
|
|
||||||
image.quantize();
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(jpeged.begin(), jpeged.end(), &blob);
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
|
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
} else {
|
} else {
|
||||||
Image image;
|
VImage in = VImage::new_from_buffer(data.Data(), data.Length(), "");
|
||||||
image.read(Blob(data.Data(), data.Length()));
|
void *buf;
|
||||||
image.quality(1);
|
size_t length;
|
||||||
image.magick("JPEG");
|
in.write_to_buffer(
|
||||||
image.write(&blob);
|
".jpg", &buf, &length,
|
||||||
|
VImage::option()->set("Q", quality)->set("strip", true));
|
||||||
|
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
vips_thread_shutdown();
|
||||||
blob.length()));
|
|
||||||
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
result.Set("type", "jpg");
|
result.Set("type", "jpg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
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::Object>();
|
|
||||||
Napi::Buffer<char> data = obj.Get("data").As<Napi::Buffer<char>>();
|
|
||||||
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
|
||||||
int delay =
|
|
||||||
obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0;
|
|
||||||
|
|
||||||
Blob blob;
|
|
||||||
|
|
||||||
list<Image> frames;
|
|
||||||
list<Image> coalesced;
|
|
||||||
list<Image> 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<char>::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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <napi.h>
|
|
||||||
|
|
||||||
Napi::Value Leak(const Napi::CallbackInfo& info);
|
|
179
natives/meme.cc
179
natives/meme.cc
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Meme(const Napi::CallbackInfo &info) {
|
Napi::Value Meme(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -20,88 +18,125 @@ Napi::Value Meme(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
Image top_text;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
Image bottom_text;
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
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());
|
|
||||||
|
|
||||||
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 dividedWidth = width / 1000;
|
||||||
|
int rad = 1;
|
||||||
|
|
||||||
top_text.size(Geometry(to_string(width)));
|
string font_string = (font == "roboto" ? "Roboto Condensed" : font) + " " +
|
||||||
top_text.backgroundColor("none");
|
(font != "impact" ? "bold" : "normal") + " " +
|
||||||
top_text.font("Impact");
|
to_string(size);
|
||||||
top_text.fontPointsize(width / 12);
|
|
||||||
top_text.textGravity(Magick::CenterGravity);
|
|
||||||
top_text.read("pango:<span font_family=\"" +
|
|
||||||
(font == "roboto" ? "Roboto Condensed" : font) +
|
|
||||||
"\" weight=\"" + (font != "impact" ? "bold" : "normal") +
|
|
||||||
"\" foreground='white'>" + top + "</span>");
|
|
||||||
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);
|
|
||||||
|
|
||||||
if (bottom != "") {
|
VImage mask = VImage::black(rad * 2 + 1, rad * 2 + 1) + 128;
|
||||||
bottom_text.size(Geometry(to_string(coalesced.front().columns())));
|
mask.draw_circle({255}, rad, rad, rad, VImage::option()->set("fill", true));
|
||||||
bottom_text.backgroundColor("none");
|
|
||||||
bottom_text.font("Impact");
|
VImage altMask;
|
||||||
bottom_text.fontPointsize(width / 12);
|
|
||||||
bottom_text.textGravity(Magick::CenterGravity);
|
if (dividedWidth >= 1) {
|
||||||
bottom_text.read("pango:<span foreground='white'>" + bottom + "</span>");
|
altMask = VImage::black(dividedWidth * 2 + 1, dividedWidth * 2 + 1) + 128;
|
||||||
Image bottom_text_fill = bottom_text;
|
altMask.draw_circle({255}, dividedWidth, dividedWidth, dividedWidth,
|
||||||
bottom_text_fill.channel(Magick::AlphaChannel);
|
VImage::option()->set("fill", true));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
VImage topText;
|
||||||
image.composite(top_text, Magick::NorthGravity, Magick::OverCompositeOp);
|
if (top != "") {
|
||||||
if (bottom != "")
|
VImage topIn = VImage::text(
|
||||||
image.composite(bottom_text, Magick::SouthGravity,
|
("<span foreground=\"white\">" + top + "</span>").c_str(),
|
||||||
Magick::OverCompositeOp);
|
VImage::option()
|
||||||
image.magick(type);
|
->set("rgba", true)
|
||||||
mid.push_back(image);
|
->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") {
|
VImage topOutline =
|
||||||
for (Image &image : mid) {
|
topIn.morph(mask, VIPS_OPERATION_MORPHOLOGY_DILATE)
|
||||||
image.quantizeDither(false);
|
.gaussblur(0.5, VImage::option()->set("min_ampl", 0.1));
|
||||||
image.quantize();
|
if (dividedWidth >= 1) {
|
||||||
if (delay != 0) image.animationDelay(delay);
|
topOutline =
|
||||||
|
topOutline.morph(altMask, VIPS_OPERATION_MORPHOLOGY_DILATE);
|
||||||
}
|
}
|
||||||
|
topOutline = (topOutline == (vector<double>){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(
|
||||||
|
("<span foreground=\"white\">" + bottom + "</span>").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<double>){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<VImage> 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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Mirror(const Napi::CallbackInfo &info) {
|
Napi::Value Mirror(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -22,74 +20,63 @@ Napi::Value Mirror(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
MagickCore::GravityType gravity;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
try {
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
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());
|
|
||||||
|
|
||||||
if (vertical && first) {
|
VImage out;
|
||||||
gravity = Magick::NorthGravity;
|
|
||||||
} else if (!vertical && first) {
|
if (vertical) {
|
||||||
gravity = Magick::WestGravity;
|
if (type == "gif") {
|
||||||
} else if (vertical && !first) {
|
// once again, libvips gif handling is both a blessing and a curse
|
||||||
gravity = Magick::SouthGravity;
|
vector<VImage> 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 {
|
} else {
|
||||||
gravity = Magick::EastGravity;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
|
||||||
image.colorSpace(Magick::sRGBColorspace);
|
|
||||||
list<Image> 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) {
|
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 {
|
} else {
|
||||||
mirrored.push_front(mirror);
|
int size = in.width() / 2;
|
||||||
}
|
VImage cropped = in.extract_area(size, 0, size, in.height());
|
||||||
appendImages(&final, mirrored.begin(), mirrored.end(), vertical);
|
VImage flipped = cropped.flip(VIPS_DIRECTION_HORIZONTAL);
|
||||||
final.repage();
|
out = VImage::arrayjoin({flipped, cropped});
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Motivate(const Napi::CallbackInfo &info) {
|
Napi::Value Motivate(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -20,85 +18,101 @@ Napi::Value Motivate(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
Image top;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
Image bottom;
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
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());
|
|
||||||
|
|
||||||
top.size(Geometry("600"));
|
int width = in.width();
|
||||||
top.backgroundColor("black");
|
int size = width / 5;
|
||||||
top.font("Times");
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
top.textGravity(Magick::CenterGravity);
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
top.fontPointsize(56);
|
int textWidth = width - ((width / 25) * 2);
|
||||||
top.read("pango:<span font_family=\"" +
|
|
||||||
(font == "roboto" ? "Roboto Condensed" : font) +
|
|
||||||
"\" foreground='white'>" + top_text + "</span>");
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
string font_string = font == "roboto" ? "Roboto Condensed" : font;
|
||||||
|
|
||||||
|
string topText = "<span foreground=\"white\" background=\"black\">" +
|
||||||
|
top_text + "</span>";
|
||||||
|
|
||||||
|
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 != "") {
|
if (bottom_text != "") {
|
||||||
bottom.size(Geometry("600"));
|
string bottomText = "<span foreground=\"white\" background=\"black\">" +
|
||||||
bottom.backgroundColor("black");
|
bottom_text + "</span>";
|
||||||
bottom.font("Times");
|
|
||||||
bottom.textGravity(Magick::CenterGravity);
|
bottomImage = VImage::text(
|
||||||
bottom.fontPointsize(28);
|
bottomText.c_str(),
|
||||||
bottom.read("pango:<span font_family=\"" +
|
VImage::option()
|
||||||
(font == "roboto" ? "Roboto Condensed" : font) +
|
->set("rgba", true)
|
||||||
"\" foreground='white'>" + bottom_text + "</span>");
|
->set("align", VIPS_ALIGN_CENTRE)
|
||||||
bottom.extent(Geometry(to_string(bottom.columns()) + "x" +
|
->set("font", (font_string + " " + to_string(size * 0.4)).c_str())
|
||||||
to_string(bottom.rows() + 20)),
|
->set("width", textWidth));
|
||||||
"black", Magick::NorthGravity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
vector<VImage> img;
|
||||||
Image final;
|
int height;
|
||||||
image.scale(Geometry(500, 500));
|
for (int i = 0; i < n_pages; i++) {
|
||||||
image.borderColor("black");
|
VImage img_frame =
|
||||||
image.border(Geometry(5, 5));
|
type == "gif" ? in.crop(0, i * page_height, width, page_height) : in;
|
||||||
image.borderColor("white");
|
|
||||||
image.border(Geometry(3, 3));
|
|
||||||
image.backgroundColor("black");
|
|
||||||
image.extent(Geometry(600, image.rows() + 50), Magick::CenterGravity);
|
|
||||||
|
|
||||||
list<Image> to_append;
|
int borderSize = max(2, width / 66);
|
||||||
to_append.push_back(image);
|
int borderSize2 = borderSize * 0.5;
|
||||||
to_append.push_back(top);
|
VImage bordered =
|
||||||
if (bottom_text != "") to_append.push_back(bottom);
|
img_frame.embed(borderSize, borderSize, width + (borderSize * 2),
|
||||||
appendImages(&final, to_append.begin(), to_append.end(), true);
|
page_height + (borderSize * 2),
|
||||||
final.repage();
|
VImage::option()->set("extend", "black"));
|
||||||
final.magick(type);
|
VImage bordered2 = bordered.embed(
|
||||||
final.animationDelay(delay == 0 ? image.animationDelay() : delay);
|
borderSize2, borderSize2, bordered.width() + (borderSize2 * 2),
|
||||||
mid.push_back(final);
|
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") {
|
VImage bordered3 = bordered2.embed(
|
||||||
for (Image &image : mid) {
|
sideAddition / 2, addition / 2, bordered2.width() + sideAddition,
|
||||||
image.quantizeDither(false);
|
bordered2.height() + addition,
|
||||||
image.quantize();
|
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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Reddit(const Napi::CallbackInfo &info) {
|
Napi::Value Reddit(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -18,62 +16,58 @@ Napi::Value Reddit(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
Image watermark;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
Image text_image;
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
try {
|
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
string assetPath = basePath + "assets/images/reddit.png";
|
||||||
} catch (Magick::WarningCoder &warning) {
|
VImage tmpl = VImage::new_from_file(assetPath.c_str());
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
|
||||||
} catch (Magick::Warning &warning) {
|
int width = in.width();
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
|
|
||||||
|
string captionText = "<span foreground=\"white\">" + text + "</span>";
|
||||||
|
|
||||||
|
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<VImage> 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");
|
void *buf;
|
||||||
text_image.textGravity(Magick::WestGravity);
|
size_t length;
|
||||||
text_image.font("Roboto");
|
final.write_to_buffer(
|
||||||
text_image.fontPointsize(47);
|
("." + type).c_str(), &buf, &length,
|
||||||
text_image.backgroundColor("none");
|
type == "gif" ? VImage::option()->set("dither", 0) : 0);
|
||||||
text_image.read("pango:<span foreground='white'>" + text + "</span>");
|
|
||||||
|
|
||||||
watermark.composite(text_image, Geometry("+375+46"),
|
vips_thread_shutdown();
|
||||||
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<Image> 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);
|
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Resize(const Napi::CallbackInfo &info) {
|
Napi::Value Resize(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -22,49 +20,43 @@ Napi::Value Resize(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> blurred;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
|
||||||
} catch (Magick::WarningCoder &warning) {
|
VImage out;
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
|
||||||
} catch (Magick::Warning &warning) {
|
int width = in.width();
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
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) {
|
void *buf;
|
||||||
if (stretch) {
|
size_t length;
|
||||||
image.resize(Geometry("512x512!"));
|
out.write_to_buffer(("." + type).c_str(), &buf, &length);
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
optimizeTransparency(blurred.begin(), blurred.end());
|
vips_thread_shutdown();
|
||||||
|
|
||||||
if (type == "gif") {
|
|
||||||
for (Image &image : blurred) {
|
|
||||||
image.quantizeDitherMethod(FloydSteinbergDitherMethod);
|
|
||||||
image.quantize();
|
|
||||||
if (delay != 0) image.animationDelay(delay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(blurred.begin(), blurred.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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Reverse(const Napi::CallbackInfo &info) {
|
Napi::Value Reverse(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -15,47 +15,54 @@ Napi::Value Reverse(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>>();
|
||||||
bool soos =
|
bool soos =
|
||||||
obj.Has("soos") ? obj.Get("soos").As<Napi::Boolean>().Value() : false;
|
obj.Has("soos") ? obj.Get("soos").As<Napi::Boolean>().Value() : false;
|
||||||
int delay =
|
|
||||||
obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0;
|
|
||||||
|
|
||||||
Blob blob;
|
VOption *options =
|
||||||
|
VImage::option()->set("access", "sequential")->set("n", -1);
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in = VImage::new_from_buffer(data.Data(), data.Length(), "", options)
|
||||||
list<Image> coalesced;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
try {
|
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
int width = in.width();
|
||||||
} catch (Magick::WarningCoder &warning) {
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
} catch (Magick::Warning &warning) {
|
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
vector<VImage> 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<int> delays = in.get_array_int("delay");
|
||||||
if (soos) {
|
if (soos) {
|
||||||
list<Image> copy = coalesced;
|
vector<VImage> copy = split;
|
||||||
copy.reverse();
|
vector<int> copy2 = delays;
|
||||||
|
reverse(copy.begin(), copy.end());
|
||||||
|
reverse(copy2.begin(), copy2.end());
|
||||||
copy.pop_back();
|
copy.pop_back();
|
||||||
copy.pop_front();
|
copy2.pop_back();
|
||||||
coalesced.insert(coalesced.end(), copy.begin(), copy.end());
|
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 {
|
} 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) {
|
vips_thread_shutdown();
|
||||||
image.quantizeDither(false);
|
|
||||||
image.quantize();
|
|
||||||
if (delay != 0) image.animationDelay(delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(coalesced.begin(), coalesced.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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", "gif");
|
result.Set("type", "gif");
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Snapchat(const Napi::CallbackInfo &info) {
|
Napi::Value Snapchat(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -20,58 +18,64 @@ Napi::Value Snapchat(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> captioned;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
Blob caption_blob;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
try {
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
|
||||||
} catch (Magick::WarningCoder &warning) {
|
int width = in.width();
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
} catch (Magick::Warning &warning) {
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
int size = width / 20;
|
||||||
|
int textWidth = width - ((width / 25) * 2);
|
||||||
|
|
||||||
|
string font_string = "Helvetica Neue " + to_string(size);
|
||||||
|
|
||||||
|
VImage textIn =
|
||||||
|
VImage::text(("<span foreground=\"white\" background=\"#000000B2\">" +
|
||||||
|
caption + "</span>")
|
||||||
|
.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<double>){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<double>){0, 0, 0, 178}));
|
||||||
|
|
||||||
|
vector<VImage> 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();
|
void *buf;
|
||||||
size_t height = frames.front().baseRows();
|
size_t length;
|
||||||
string query(to_string(width - ((width / 25) * 2)) + "x");
|
final.write_to_buffer(
|
||||||
Image caption_image(Geometry(query), Color("none"));
|
("." + type).c_str(), &buf, &length,
|
||||||
caption_image.backgroundColor(Color("none"));
|
type == "gif" ? VImage::option()->set("dither", 0) : 0);
|
||||||
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);
|
|
||||||
|
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
vips_thread_shutdown();
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
|
||||||
list<Image> 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);
|
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <list>
|
#include <vips/vips8>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Sonic(const Napi::CallbackInfo &info) {
|
Napi::Value Sonic(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -14,25 +13,30 @@ Napi::Value Sonic(const Napi::CallbackInfo &info) {
|
||||||
string text = obj.Get("text").As<Napi::String>().Utf8Value();
|
string text = obj.Get("text").As<Napi::String>().Utf8Value();
|
||||||
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
|
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
|
||||||
|
|
||||||
Blob blob;
|
string assetPath = basePath + "assets/images/sonic.jpg";
|
||||||
|
VImage bg = VImage::new_from_file(assetPath.c_str());
|
||||||
|
|
||||||
Image image;
|
VImage textImage =
|
||||||
Image text_image;
|
VImage::text(("<span foreground=\"white\">" + text + "</span>").c_str(),
|
||||||
text_image.backgroundColor("none");
|
VImage::option()
|
||||||
text_image.fontPointsize(72);
|
->set("rgba", true)
|
||||||
text_image.textGravity(Magick::CenterGravity);
|
->set("align", VIPS_ALIGN_CENTRE)
|
||||||
text_image.font("Bitstream Vera Sans");
|
->set("font", "Bitstream Vera Sans")
|
||||||
text_image.read("pango:<span foreground='white'>" + text + "</span>");
|
->set("width", 542)
|
||||||
text_image.resize(Geometry(474, 332));
|
->set("height", 390))
|
||||||
text_image.extent(Geometry("1024x538-435-145"), Magick::CenterGravity);
|
.gravity(VIPS_COMPASS_DIRECTION_CENTRE, 542, 390);
|
||||||
image.read(basePath + "assets/images/sonic.jpg");
|
|
||||||
image.composite(text_image, Geometry("+160+10"), Magick::OverCompositeOp);
|
VImage out = bg.composite2(textImage, VIPS_BLEND_MODE_OVER,
|
||||||
image.magick("PNG");
|
VImage::option()->set("x", 391)->set("y", 84));
|
||||||
image.write(&blob);
|
|
||||||
|
void *buf;
|
||||||
|
size_t length;
|
||||||
|
out.write_to_buffer(".png", &buf, &length);
|
||||||
|
|
||||||
|
vips_thread_shutdown();
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", "png");
|
result.Set("type", "png");
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
void *memset16(void *m, uint16_t val, size_t count) {
|
void *memset16(void *m, uint16_t val, size_t count) {
|
||||||
uint16_t *buf = (uint16_t *)m;
|
uint16_t *buf = (uint16_t *)m;
|
||||||
|
@ -14,6 +12,36 @@ void *memset16(void *m, uint16_t val, size_t count) {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void vipsRemove(Napi::Env *env, Napi::Object *result, Napi::Buffer<char> 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<VImage> 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<char>::Copy(*env, (char *)buf, length));
|
||||||
|
}
|
||||||
|
|
||||||
Napi::Value Speed(const Napi::CallbackInfo &info) {
|
Napi::Value Speed(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
|
||||||
|
@ -80,37 +108,7 @@ Napi::Value Speed(const Napi::CallbackInfo &info) {
|
||||||
result.Set("data",
|
result.Set("data",
|
||||||
Napi::Buffer<char>::Copy(env, fileData, data.Length()));
|
Napi::Buffer<char>::Copy(env, fileData, data.Length()));
|
||||||
|
|
||||||
if (removeFrames) {
|
if (removeFrames) vipsRemove(&env, &result, data, speed);
|
||||||
Blob blob;
|
|
||||||
|
|
||||||
list<Image> 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<Image>::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<char>::Copy(env, (char *)blob.data(),
|
|
||||||
blob.length()));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
char *lastPos;
|
char *lastPos;
|
||||||
|
|
||||||
|
@ -133,26 +131,7 @@ Napi::Value Speed(const Napi::CallbackInfo &info) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeFrames) {
|
if (removeFrames) {
|
||||||
Blob blob;
|
vipsRemove(&env, &result, data, speed);
|
||||||
|
|
||||||
list<Image> 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<char>::Copy(env, (char *)blob.data(),
|
|
||||||
blob.length()));
|
|
||||||
} else {
|
} else {
|
||||||
while (lastPos != NULL) {
|
while (lastPos != NULL) {
|
||||||
if (memcmp(lastPos, match, 4) != 0) {
|
if (memcmp(lastPos, match, 4) != 0) {
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <list>
|
|
||||||
|
|
||||||
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::Object>();
|
|
||||||
Napi::Buffer<char> data = obj.Get("data").As<Napi::Buffer<char>>();
|
|
||||||
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
|
||||||
int delay =
|
|
||||||
obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0;
|
|
||||||
|
|
||||||
Blob blob;
|
|
||||||
|
|
||||||
list<Image> frames;
|
|
||||||
list<Image> coalesced;
|
|
||||||
list<Image> 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<char>::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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <napi.h>
|
|
||||||
|
|
||||||
Napi::Value Trump(const Napi::CallbackInfo& info);
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Uncaption(const Napi::CallbackInfo &info) {
|
Napi::Value Uncaption(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -13,61 +11,51 @@ Napi::Value Uncaption(const Napi::CallbackInfo &info) {
|
||||||
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>>();
|
||||||
float tolerance = obj.Has("tolerance") ? obj.Get("tolerance").As<Napi::Number>().FloatValue() : 0.95;
|
float tolerance = obj.Has("tolerance")
|
||||||
|
? obj.Get("tolerance").As<Napi::Number>().FloatValue()
|
||||||
|
: 0.5;
|
||||||
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
||||||
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;
|
VOption *options = VImage::option();
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1)->set("access", "sequential") : options)
|
||||||
try {
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
} catch (Magick::WarningCoder &warning) {
|
|
||||||
cerr << "Coder Warning: " << warning.what() << endl;
|
int width = in.width();
|
||||||
} catch (Magick::Warning &warning) {
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
cerr << "Warning: " << warning.what() << endl;
|
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<VImage> 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();
|
void *buf;
|
||||||
ssize_t columns = firstImage.columns();
|
size_t length;
|
||||||
ssize_t rows = firstImage.rows();
|
final.write_to_buffer(
|
||||||
ssize_t row;
|
("." + type).c_str(), &buf, &length,
|
||||||
for (row = 0; row < rows; ++row) {
|
type == "gif" ? VImage::option()->set("dither", 0) : 0);
|
||||||
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);
|
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
vips_thread_shutdown();
|
||||||
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);
|
|
||||||
|
|
||||||
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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Watermark(const Napi::CallbackInfo &info) {
|
Napi::Value Watermark(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -14,8 +12,7 @@ Napi::Value Watermark(const Napi::CallbackInfo &info) {
|
||||||
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>>();
|
||||||
string water = obj.Get("water").As<Napi::String>().Utf8Value();
|
string water = obj.Get("water").As<Napi::String>().Utf8Value();
|
||||||
Magick::GravityType gravity =
|
int gravity = obj.Get("gravity").As<Napi::Number>().Int64Value();
|
||||||
Magick::GravityType(obj.Get("gravity").As<Napi::Number>().Int64Value());
|
|
||||||
bool resize = obj.Has("resize")
|
bool resize = obj.Has("resize")
|
||||||
? obj.Get("resize").As<Napi::Boolean>().Value()
|
? obj.Get("resize").As<Napi::Boolean>().Value()
|
||||||
: false;
|
: false;
|
||||||
|
@ -25,76 +22,141 @@ Napi::Value Watermark(const Napi::CallbackInfo &info) {
|
||||||
bool append = obj.Has("append")
|
bool append = obj.Has("append")
|
||||||
? obj.Get("append").As<Napi::Boolean>().Value()
|
? obj.Get("append").As<Napi::Boolean>().Value()
|
||||||
: false;
|
: false;
|
||||||
|
bool alpha =
|
||||||
|
obj.Has("alpha") ? obj.Get("alpha").As<Napi::Boolean>().Value() : false;
|
||||||
bool mc = obj.Has("mc") ? obj.Get("mc").As<Napi::Boolean>().Value() : false;
|
bool mc = obj.Has("mc") ? obj.Get("mc").As<Napi::Boolean>().Value() : false;
|
||||||
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
|
string basePath = obj.Get("basePath").As<Napi::String>().Utf8Value();
|
||||||
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
string type = obj.Get("type").As<Napi::String>().Utf8Value();
|
||||||
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;
|
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<Image> frames;
|
|
||||||
list<Image> coalesced;
|
|
||||||
list<Image> 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;
|
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) {
|
if (resize && append) {
|
||||||
string query(to_string(frames.front().baseColumns()) + "x");
|
watermark = watermark.resize((double)width / (double)watermark.width());
|
||||||
watermark.scale(Geometry(query));
|
|
||||||
} else if (resize && yscale) {
|
} else if (resize && yscale) {
|
||||||
string query(to_string(frames.front().baseColumns()) + "x" +
|
watermark = watermark.resize(
|
||||||
to_string(frames.front().baseRows() * yscale) + "!");
|
(double)width / (double)watermark.width(),
|
||||||
watermark.resize(Geometry(query));
|
VImage::option()->set("vscale", (double)(page_height * yscale) /
|
||||||
|
(double)watermark.height()));
|
||||||
} else if (resize) {
|
} else if (resize) {
|
||||||
string query("x" + to_string(frames.front().baseRows()));
|
watermark =
|
||||||
watermark.scale(Geometry(query));
|
watermark.resize((double)page_height / (double)watermark.height());
|
||||||
}
|
}
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
int x = 0, y = 0;
|
||||||
Image final;
|
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<VImage> 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) {
|
if (append) {
|
||||||
list<Image> to_append;
|
VImage appended = img_frame.join(watermark, VIPS_DIRECTION_VERTICAL,
|
||||||
to_append.push_back(image);
|
VImage::option()->set("expand", true));
|
||||||
to_append.push_back(watermark);
|
addedHeight = watermark.height();
|
||||||
appendImages(&final, to_append.begin(), to_append.end(), true);
|
img.push_back(appended);
|
||||||
final.repage();
|
|
||||||
} else if (mc) {
|
} else if (mc) {
|
||||||
image.backgroundColor("white");
|
VImage padded =
|
||||||
image.extent(Geometry(image.columns(), image.rows() + 15));
|
img_frame.embed(0, 0, width, page_height + 15,
|
||||||
image.composite(watermark, gravity, Magick::OverCompositeOp);
|
VImage::option()->set("background", 0xffffff));
|
||||||
final = image;
|
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 {
|
} else {
|
||||||
image.composite(watermark, gravity, Magick::OverCompositeOp);
|
VImage composited;
|
||||||
final = image;
|
if (alpha) {
|
||||||
}
|
if (i == 0) {
|
||||||
image.magick(type);
|
contentAlpha = watermark.extract_band(0).embed(
|
||||||
final.animationDelay(delay == 0 ? image.animationDelay() : delay);
|
x, y, width, page_height,
|
||||||
mid.push_back(final);
|
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());
|
composited =
|
||||||
|
content.composite2(frame, VIPS_BLEND_MODE_OVER,
|
||||||
if (type == "gif") {
|
VImage::option()->set("x", x)->set("y", y));
|
||||||
for (Image &image : mid) {
|
} else {
|
||||||
image.quantizeDitherMethod(FloydSteinbergDitherMethod);
|
composited =
|
||||||
image.quantize();
|
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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <vips/vips8>
|
||||||
#include <list>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Whisper(const Napi::CallbackInfo &info) {
|
Napi::Value Whisper(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -18,74 +16,80 @@ Napi::Value Whisper(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> captioned;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
Blob caption_blob;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
try {
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
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();
|
|
||||||
|
|
||||||
|
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 dividedWidth = width / 175;
|
||||||
|
int rad = 1;
|
||||||
|
|
||||||
Image caption_image;
|
string font_string = "Upright " + to_string(size);
|
||||||
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);
|
|
||||||
|
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
VImage mask;
|
||||||
|
if (dividedWidth >= 1) {
|
||||||
for (Image &image : coalesced) {
|
mask = VImage::black(dividedWidth * 2 + 1, dividedWidth * 2 + 1) + 128;
|
||||||
list<Image> images;
|
mask.draw_circle({255}, dividedWidth, dividedWidth, dividedWidth,
|
||||||
image.composite(caption_image, Magick::CenterGravity,
|
VImage::option()->set("fill", true));
|
||||||
Magick::OverCompositeOp);
|
} else {
|
||||||
image.magick(type);
|
mask = VImage::black(rad * 2 + 1, rad * 2 + 1) + 128;
|
||||||
image.animationDelay(delay == 0 ? image.animationDelay() : delay);
|
mask.draw_circle({255}, rad, rad, rad,
|
||||||
captioned.push_back(image);
|
VImage::option()->set("fill", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
optimizeTransparency(captioned.begin(), captioned.end());
|
VImage textIn = VImage::text(
|
||||||
|
("<span foreground=\"white\">" + caption + "</span>").c_str(),
|
||||||
|
VImage::option()
|
||||||
|
->set("rgba", true)
|
||||||
|
->set("align", VIPS_ALIGN_CENTRE)
|
||||||
|
->set("font", font_string.c_str())
|
||||||
|
->set("width", width));
|
||||||
|
|
||||||
if (type == "gif") {
|
textIn = textIn.embed(rad + 10, rad + 10, (textIn.width() + 2 * rad) + 20,
|
||||||
for (Image &image : captioned) {
|
(textIn.height() + 2 * rad) + 20);
|
||||||
image.quantizeDither(false);
|
|
||||||
image.quantize();
|
VImage outline =
|
||||||
}
|
textIn.morph(mask, VIPS_OPERATION_MORPHOLOGY_DILATE)
|
||||||
|
.gaussblur(0.5, VImage::option()->set("min_ampl", 0.1));
|
||||||
|
outline = (outline == (vector<double>){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<VImage> 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);
|
Napi::Object result = Napi::Object::New(env);
|
||||||
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)blob.data(),
|
result.Set("data", Napi::Buffer<char>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#include <Magick++.h>
|
|
||||||
#include <napi.h>
|
#include <napi.h>
|
||||||
|
|
||||||
#include <list>
|
#include <vips/vips8>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Magick;
|
using namespace vips;
|
||||||
|
|
||||||
Napi::Value Zamn(const Napi::CallbackInfo &info) {
|
Napi::Value Zamn(const Napi::CallbackInfo &info) {
|
||||||
Napi::Env env = info.Env();
|
Napi::Env env = info.Env();
|
||||||
|
@ -17,42 +16,44 @@ Napi::Value Zamn(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;
|
VOption *options = VImage::option()->set("access", "sequential");
|
||||||
|
|
||||||
list<Image> frames;
|
VImage in =
|
||||||
list<Image> coalesced;
|
VImage::new_from_buffer(data.Data(), data.Length(), "",
|
||||||
list<Image> mid;
|
type == "gif" ? options->set("n", -1) : options)
|
||||||
Image watermark;
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
readImages(&frames, Blob(data.Data(), data.Length()));
|
if (!in.has_alpha()) in = in.bandjoin(255);
|
||||||
watermark.read(basePath + "assets/images/zamn.png");
|
|
||||||
coalesceImages(&coalesced, frames.begin(), frames.end());
|
|
||||||
|
|
||||||
for (Image &image : coalesced) {
|
int width = in.width();
|
||||||
Image watermark_new = watermark;
|
int page_height = vips_image_get_page_height(in.get_image());
|
||||||
image.backgroundColor("none");
|
int n_pages = vips_image_get_n_pages(in.get_image());
|
||||||
image.scale(Geometry("303x438!"));
|
|
||||||
image.extent(Geometry("621x516-310-75"));
|
string assetPath = basePath + "assets/images/zamn.png";
|
||||||
watermark_new.composite(image, Magick::CenterGravity,
|
VImage tmpl = VImage::new_from_file(assetPath.c_str());
|
||||||
Magick::OverCompositeOp);
|
|
||||||
watermark_new.magick(type);
|
vector<VImage> img;
|
||||||
watermark_new.animationDelay(delay == 0 ? image.animationDelay() : delay);
|
for (int i = 0; i < n_pages; i++) {
|
||||||
mid.push_back(watermark_new);
|
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") {
|
vips_thread_shutdown();
|
||||||
for (Image &image : mid) {
|
|
||||||
image.quantizeDitherMethod(FloydSteinbergDitherMethod);
|
|
||||||
image.quantize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeImages(mid.begin(), mid.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>::Copy(env, (char *)buf, length));
|
||||||
blob.length()));
|
|
||||||
result.Set("type", type);
|
result.Set("type", type);
|
||||||
return result;
|
return result;
|
||||||
} catch (std::exception const &err) {
|
} catch (std::exception const &err) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue