parent
e27092843e
commit
578b58794b
@ -0,0 +1,161 @@ |
||||
// 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); |
||||
} |
Loading…
Reference in new issue