Skip to content

Instantly share code, notes, and snippets.

@noudadrichem
Created September 10, 2020 07:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save noudadrichem/2e058505d5e181fcbf37a8b79d636b56 to your computer and use it in GitHub Desktop.
Save noudadrichem/2e058505d5e181fcbf37a8b79d636b56 to your computer and use it in GitHub Desktop.
import Logger from "../lib/logger";
import axios from "axios";
import { createCanvas, loadImage, registerFont } from "canvas";
import path from "path";
import ColorThief from "colorthief";
import { uploadImage } from "../resolvers/imgResolver";
import { KeycapsetModel } from "../models";
registerFont(__dirname + "../../../static/Rubik-SemiBold.ttf", {
family: "Rubik Semi",
});
// registerFont("/static/Rubik-Regular.ttf", { family: "Rubik" });
class MetaService {
log: any;
colors: any;
constructor() {
this.log = Logger;
this.colors = {
blue: "#539BFB",
blueLight: "#D4E4FA",
whiteLight: "#f8f9fb",
blackLight: "#1B1B1B",
black: "#000000",
white: "#ffffff",
};
}
public drawImageProp(
ctx: any,
img: any,
x: number,
y: number,
w: number,
h: number,
offsetX: number = 0.5,
offsetY: number = 0.5
) {
if (arguments.length === 2) {
x = y = 0;
w = ctx.canvas.width;
h = ctx.canvas.height;
}
// keep bounds [0.0, 1.0]
if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0;
if (offsetX > 1) offsetX = 1;
if (offsetY > 1) offsetY = 1;
var iw = img.width,
ih = img.height,
r = Math.min(w / iw, h / ih),
nw = iw * r, // new prop. width
nh = ih * r, // new prop. height
cx,
cy,
cw,
ch,
ar = 1;
// decide which gap to fill
if (nw < w) ar = w / nw;
if (Math.abs(ar - 1) < 1e-14 && nh < h) ar = h / nh; // updated
nw *= ar;
nh *= ar;
// calc source rectangle
cw = iw / (nw / w);
ch = ih / (nh / h);
cx = (iw - cw) * offsetX;
cy = (ih - ch) * offsetY;
// make sure source rectangle is valid
if (cx < 0) cx = 0;
if (cy < 0) cy = 0;
if (cw > iw) cw = iw;
if (ch > ih) ch = ih;
// fill image in dest. rectangle
ctx.drawImage(img, cx, cy, cw, ch, x, y, w, h);
}
public isColourDarkOrLight(rgb: number[]): "light" | "dark" {
// RGB to HSP equation: http://alienryderflex.com/hsp.html
const [r, g, b] = rgb;
const hsp = Math.sqrt(
0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)
);
return hsp > 117.5 ? "light" : "dark";
}
public async getColoursFromImage(url: string) {
console.log("MetaService.getColoursFromImage()");
const pallete = await ColorThief.getPalette(url);
const dominant = await ColorThief.getColor(url);
const type = this.isColourDarkOrLight(dominant);
return { dominant, pallete, type };
}
public async generateMeta(keycapset: any) {
this.log.info(keycapset, "Generate meta image...");
const width = 1200;
const height = 630;
const logoPath = path.join("./static/logo-dark.png");
try {
const canvas = createCanvas(width, height);
const context = canvas.getContext("2d");
this.log.info("Loading logo img...");
const logo = await loadImage(logoPath);
this.log.info("Loading cover img...");
const coverImg = await loadImage(keycapset.coverImageUrl);
const { dominant, pallete, type } = await this.getColoursFromImage(
keycapset.coverImageUrl
);
context.fillStyle = "rgba(255, 255, 255, 1)";
context.fillRect(0, 0, width, height);
this.drawImageProp(context, coverImg, 0, 0, 880, height);
const gradient = context.createLinearGradient(185, 315, 721, 380);
gradient.addColorStop(0, `rgba(${dominant.join(",")}, 0)`);
gradient.addColorStop(0.8, `rgba(${dominant.join(",")}, 1)`);
context.fillStyle = gradient;
context.fillRect(0, 0, width, height);
context.drawImage(logo, 820, 162, 180, 71);
context.font = "bold 35pt Rubik Semi";
context.textAlign = "center";
context.textBaseline = "top";
context.fillStyle =
type === "light"
? this.colors.blackLight
: this.colors.whiteLight;
const title = `${
keycapset.brand === "gmk"
? "GMK"
: keycapset.brand === "epbt"
? "EPBT"
: keycapset.type.toUpperCase()
} ${keycapset.name}`;
context.fillText(title, 920, 262);
context.fillStyle =
type === "light" ? this.colors.blue : this.colors.blueLight;
context.fillRect(757, 360, 326, 64);
context.font = "bold 14pt Rubik Semi";
context.textAlign = "center";
context.textBaseline = "top";
context.fillStyle =
type === "light" ? this.colors.white : this.colors.blue;
context.fillText("See on keycapsets.com", 920, 380);
const buffer = canvas.toBuffer("image/png");
// fs.writeFileSync(
// `./tmp/meta-${keycapset._id}-${keycapset.name}.png`,
// buffer
// );
this.log.info("DONE");
return buffer;
} catch (err) {
this.log.error(err, "Error while generating meta...");
}
}
async setMetaURLonSet(keycapset) {
const metaImgBuffer = await this.generateMeta(keycapset);
console.log(metaImgBuffer);
// const metaGenURL = `https://api-testing.keycapsets.com/meta/${keycapset._id}`;
if (metaImgBuffer) {
const newImgUrl = await uploadImage(
metaImgBuffer.toString("base64")
);
const newKeycapset: any = await KeycapsetModel.findOneAndUpdate(
{ _id: keycapset._id },
{ metaUrl: newImgUrl.url },
{ new: true }
);
return newKeycapset;
}
return keycapset;
}
}
export default new MetaService();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment