Skip to content

Instantly share code, notes, and snippets.

@kibolho
Created June 22, 2023 17:45
Show Gist options
  • Select an option

  • Save kibolho/f4ab69b76c559acae7013f569a3aa10b to your computer and use it in GitHub Desktop.

Select an option

Save kibolho/f4ab69b76c559acae7013f569a3aa10b to your computer and use it in GitHub Desktop.
Generate Image from HTML
import chrome from 'chrome-aws-lambda';
const chromeExecPaths = {
win32: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
linux: '/usr/bin/google-chrome',
darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
};
const exePath = chromeExecPaths[process.platform];
interface Options {
args: string[];
executablePath: string;
headless: boolean;
}
export async function getOptions(isDev: boolean): Promise<Options> {
let options: Options;
if (isDev) {
options = {
args: [],
executablePath: exePath,
headless: true,
};
} else {
options = {
args: chrome.args,
executablePath: await chrome.executablePath,
headless: chrome.headless,
};
}
return options;
}
import puppeteer, { Page } from 'puppeteer-core';
import chrome from 'chrome-aws-lambda';
let _page: Page | null;
const chromeExecPaths = {
win32: 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
linux: '/usr/bin/google-chrome',
darwin: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
};
const exePath = chromeExecPaths[process.platform];
interface Options {
args: string[];
executablePath: string;
headless: boolean;
}
export async function getOptions(isDev: boolean): Promise<Options> {
let options: Options;
if (isDev) {
options = {
args: [],
executablePath: exePath,
headless: true,
};
} else {
options = {
args: chrome.args,
executablePath: await chrome.executablePath,
headless: chrome.headless,
};
}
return options;
}
async function getPage(isDev: boolean): Promise<Page> {
if (_page) {
return _page;
}
const options = await getOptions(isDev);
const browser = await puppeteer.launch(options);
_page = await browser.newPage();
return _page;
}
export async function getScreenshot(
html: string,
isDev: boolean
): Promise<Buffer | string> {
const page = await getPage(isDev);
await page.setViewport({ width: 1200, height: 630 });
await page.setContent(html);
const file = await page.screenshot({ type: 'png' });
return file;
}
import { NextApiRequest, NextApiResponse } from 'next';
import { getHtml } from './thumbnailTemplate';
import { getScreenshot } from './chromium';
const isDev = !process.env.AWS_REGION;
const isHtmlDebug = process.env.OG_HTML_DEBUG === '1';
const async = async (
req: NextApiRequest,
res: NextApiResponse
): Promise<any> => {
try {
const { query } = req;
const title = String(query.title);
const thumbnail_bg = String(query.thumbnail_bg);
const image_avatar_url = String(query.image_avatar_url);
const image_post_url = String(query.image_post_url);
const avatar_name = String(query.avatar_name);
const html = getHtml({
title,
thumbnailBG: thumbnail_bg,
avatarImg: image_avatar_url,
postImg: image_post_url,
avatarName: avatar_name,
});
if (isHtmlDebug) {
res.setHeader('Content-Type', 'text/html');
res.end(html);
return;
}
const file = await getScreenshot(html, isDev);
res.statusCode = 200;
res.setHeader('Content-Type', `image/png`);
res.setHeader(
'Cache-Control',
'public, immutable, no-transform, s-maxage=31536000, max-age=31536000'
);
res.end(file);
} catch (e) {
res.statusCode = 500;
res.setHeader('Content-Type', 'text/html');
res.end(
`<h1>Internal Error</h1><p>Sorry, there was a problem: ${e.message}</p>`
);
}
};
export default async;
interface GetHtmlProps {
title: string;
thumbnailBG?: string;
avatarImg?: string;
postImg?: string;
avatarName?: string;
}
export function getHtml({
title,
thumbnailBG = '#8257e5',
avatarImg = '',
postImg = null,
avatarName = 'Abílio Azevedo',
}: GetHtmlProps) {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charSet="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Thumbnail</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap" rel="stylesheet">
<style>
body {
margin: 0;
font-family: Roboto, sans-serif;
color: #FFF;
background: ${thumbnailBG};
background-image:
radial-gradient(circle at 25px 25px, rgba(255, 255, 255, 0.2) 2%, transparent 0%),
radial-gradient(circle at 75px 75px, rgba(255, 255, 255, 0.2) 2%, transparent 0%);
background-size: 100px 100px;
height: 100vh;
}
#wrapper {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
#wrapper-post-img {
position: relative;
max-height: 220px;
width: 300px;
margin-bottom: 30px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden
}
#wrapper-post-img img {
flex-shrink: 0;
max-width: 100%;
max-height: 100%
}
#wrapper-avatar-img {
display: flex;
flex-direction: row;
}
#wrapper-avatar-img img {
border-radius: 50%;
background-color: #e2e8f0;
margin-right: 20px;
width: 100px;
height: 100px;
}
svg {
height: 40px;
margin-top: 80px;
}
h1 {
font-size: 300%;
line-height: 80px;
max-width: 80%;
}
h2 {
font-size: 32px;
line-height: 40px;
max-width: 80%;
}
</style>
</head>
<body>
<div id="wrapper">
<h1>${title}</h1>
<div id="wrapper-post-img">
<img
id="image"
src=${postImg}
alt="post"
/>
</div>
<div id="wrapper-avatar-img">
<img
id="img"
src=${avatarImg}
alt="me"
/>
<h2>${avatarName}</h2>
</div>
<script>
var image_x = document.getElementById("image");
if(${postImg}===undefined){
image_x.parentNode.removeChild(image_x);
}
</script>
</body>
</html>`;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment