You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.9 KiB
161 lines
4.9 KiB
// you may not need all of these but they come up a lot
|
|
import fs from "fs";
|
|
import assert from "assert";
|
|
import logging from '../lib/logging.js';
|
|
import glob from "fast-glob";
|
|
import path from "path";
|
|
import { fabric } from "fabric";
|
|
import { defer } from "../lib/api.js";
|
|
import { createClient } from 'pexels';
|
|
import { exec_i } from "../lib/builderator.js";
|
|
import { pexels } from "../lib/config.js";
|
|
import fetch from "node-fetch";
|
|
import { playstart, playstop } from "../lib/testing.js";
|
|
|
|
const log = logging.create(import.meta.url);
|
|
|
|
export const description = "Gets a random image from Pexels and places the text on it."
|
|
|
|
// your command uses the npm package commander's options format
|
|
export const options = [
|
|
["--background <filename>", "The file name to save the background image.", "background.jpg"],
|
|
["--force", "Force overwriting the background.jpg temp file."],
|
|
["--query <text>", "Query to give pexels for the image", "keyboard"],
|
|
["--color <color>", "General color of the image"],
|
|
["--text-color <color>", "The border around text.", "#fff"],
|
|
["--font-family <name>", "Font to use", "Andale Mono"],
|
|
["--font-size <pts>", "Font size to use", 140],
|
|
["--blend-mode <mode>", "One of: none, multiply, screen, add, diff, subtract, darken, lighten, overlay, exclusion, tint", "screen"],
|
|
["--pixelate <blocksize>", "Pixelate the image with blocksize (try 8)"],
|
|
]
|
|
|
|
// example of a positional argument, it's the 1st argument to main
|
|
export const argument = ["text", "The message to put on the image."];
|
|
|
|
// put required options in the required variable
|
|
export const required = [
|
|
["--output <string>", "Image file to save."],
|
|
]
|
|
|
|
// handy function for checking things are good and aborting
|
|
const check = (test, fail_message) => {
|
|
if(!test) {
|
|
log.error(fail_message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
export const main = async (title, opts) => {
|
|
const per_page = 50;
|
|
|
|
const ext_check = path.parse(opts.output).ext;
|
|
check(ext_check === "", `Do not add an extension to the output. You have ${ext_check}.`);
|
|
|
|
title = title.replaceAll("\\n", "\n");
|
|
|
|
if(opts.force || !fs.existsSync(opts.background)) {
|
|
const client = createClient(pexels.key);
|
|
|
|
const result = await client.photos.search({
|
|
query: opts.query,
|
|
per_page,
|
|
orientation: opts.orientation,
|
|
color: opts.color });
|
|
|
|
const randi = Math.floor(Math.random() * per_page);
|
|
const image = await client.photos.show({id: result.photos[randi].id});
|
|
const img_fetch = await fetch(image.src.large);
|
|
const img_data = await img_fetch.arrayBuffer();
|
|
await fs.writeFileSync(opts.background, new Uint8Array(img_data));
|
|
}
|
|
|
|
const background = path.join(process.cwd(), opts.background);
|
|
|
|
let canvas = new fabric.Canvas('c', { width: 1600, height: 900 })
|
|
|
|
let gradient = new fabric.Gradient({
|
|
type: 'linear',
|
|
gradientUnits: 'pixels', // or 'percentage'
|
|
coords: { x1: 0, y1: 0, x2: canvas.height, y2: canvas.width },
|
|
colorStops:[
|
|
{ offset: 0, color: '#000' },
|
|
{ offset: 1, color: '#fff'}
|
|
]
|
|
})
|
|
|
|
let image_def = defer();
|
|
|
|
fabric.Image.fromURL(`file:///${background}`, img => {
|
|
let blur = new fabric.Image.filters.Blur({ blur: 0.05 });
|
|
|
|
img.set('scaleY', canvas.height / img.height);
|
|
img.set('scaleX', canvas.width / img.width);
|
|
|
|
if(opts.pixelate) {
|
|
let pixelate = new fabric.Image.filters.Pixelate({
|
|
blocksize: parseInt(opts.pixelate, 10)
|
|
});
|
|
|
|
img.filters.push(pixelate);
|
|
}
|
|
|
|
let blend = new fabric.Image.filters.BlendColor({
|
|
color: opts.color,
|
|
mode: opts.blendMode,
|
|
});
|
|
|
|
img.filters.push(blend);
|
|
img.filters.push(blur);
|
|
img.applyFilters();
|
|
image_def.resolve(img);
|
|
});
|
|
|
|
let bg_img = await image_def;
|
|
|
|
let rect = new fabric.Rect({
|
|
left: 0,
|
|
top: 0,
|
|
fill: opts.color,
|
|
width: canvas.width,
|
|
height: canvas.height,
|
|
opacity: 0.3
|
|
});
|
|
|
|
let text = new fabric.Text(title, {
|
|
fontFamily: opts.fontFamily,
|
|
fontSize: opts.fontSize,
|
|
strokeWidth: 5,
|
|
originX: "center",
|
|
originY: "center",
|
|
left: canvas.width / 2,
|
|
top: canvas.height / 2,
|
|
stroke: opts.textColor,
|
|
shadow: 'rgba(0, 0, 0, 1) 10px 10px 10px',
|
|
});
|
|
|
|
canvas.add(bg_img);
|
|
// canvas.add(rect);
|
|
canvas.add(text);
|
|
|
|
const output_svg = `${opts.output}.svg`;
|
|
const output_jpg = `${opts.output}.jpg`;
|
|
const output_png = `${opts.output}.png`;
|
|
|
|
fs.writeFileSync(output_svg, canvas.toSVG());
|
|
|
|
const {browser, context, p} = await playstart(`file:///${path.resolve(output_svg)}`);
|
|
|
|
await p.setViewportSize({
|
|
width: canvas.width,
|
|
height: canvas.height });
|
|
|
|
await p.screenshot({ path: output_png,
|
|
type: "png", omitBackground: true});
|
|
|
|
await playstop(browser, p);
|
|
|
|
exec_i(`convert ${output_png} -sampling-factor 4:2:0 -strip -quality 85 -interlace JPEG -colorspace RGB ${output_jpg}`);
|
|
|
|
// due to how async/await works it's just easier to manually exit with exit codes
|
|
process.exit(0);
|
|
}
|
|
|