Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bin/banner script for random banner generation #165

Merged
merged 12 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Dependencies
/node_modules
node_modules/

# Production
/build
Expand Down
34 changes: 32 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ image: Cover or thumbnail image for your blog display.
Consider the following "Cool Project" example. Its file will begin with the following metadata, set off
with three dashes, followed by the actual blog text.

```
```yaml
---
title: Cool Project V1 Released!
description: The release of Cool Project version 1
Expand All @@ -270,7 +270,7 @@ The project contains the following cool things:
See [the Docusaurus blog frontmatter documentation](docusaurus-blog-docs) for more information
on your blog creation options.

#### Storing and using images
### Storing and using images

If your blog post has images, please put them in the following folder: `static/img/blog/<blog-file-name>/`
where `<blog-file-name>` is the same name as the initial blog post (e.g. `static/img/blog/2023-04-22-cool-project-v1-release/`).
Expand All @@ -286,6 +286,36 @@ import bannerImage from "@site/static/img/blog/2023-04-22-cool-project-v1-releas
<Image img={bannerImage} />;
```

### Adding banners and credits

Blog posts often include a banner image (and optionally an image credit to properly attribute the image).

Since this is a common pattern, we have dual-purposed the frontmatter `image` to also add a banner image:

```yaml
---
image: img/blog/<post>/banner.jpg
image_credit: <a href="<url>">Someone</a>
---
```

This will automatically add a banner image to the top of the blog post and optionally add a credit below it.

#### Auto-generated banners

Sometimes coming up with an engaging and relevant banner image is hard (if not impossible).

As an alternative we offer the ability to generate randomized banners overlaid with the conda C logo:

```bash
$ npm run -- banner --output static/img/blog/<post>/banner.png
```

This will produce banners like the following:

<img src="static/img/blog/2023-10-12-september-releases/banner.jpg" width=300 />
<img src="static/img/blog/2023-07-28-july-releases/banner.jpg" width=300 />

## Technical contributors

_Coming soon! We are still figuring out exactly how this will look_
117 changes: 117 additions & 0 deletions bin/banner/banner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Usage: npm run banner --input path/to/icon.png --output path/to/output.png
// Warning: canvas isn't compiled for macOS-arm64, follow compiling instructions:
// https://www.npmjs.com/package/canvas#compiling
const { existsSync, mkdirSync, createWriteStream } = require("fs");
const trianglify = require("trianglify");
const { program } = require("commander");
const { loadImage } = require("canvas");
const { join, dirname } = require("path");

program
.version("0.0.0")
.description(
"A small tool to generate geometric banner layered with an icon.",
)
.option(
"-i, --input <filename>",
"Specify the icon/input file",
"static/img/conda_logo_c.png",
)
.requiredOption("-o, --output <filename>", "Specify the output file")
.parse();
const options = program.opts();

const input = join("..", "..", options.input);
const output = join("..", "..", options.output);

// Randomize color function
const colorFunctions = Object.values(trianglify.colorFunctions);
const colorIndex = Math.floor(Math.random() * colorFunctions.length);

// Generate a canvas using trianglify
const canvas = trianglify({
width: 1200,
height: 630,
cellSize: Math.random() * 100 + 20, // 20-120
variance: Math.random() * 0.7 + 0.3, // 0.3-1
xColors: "random",
colorFunction: colorFunctions[colorIndex](Math.random()),
}).toCanvas();

// Load the icon/image
loadImage(input)
.then((image) => {
// Calculate height/width
const radius = 20;
const padding = 20;
const height = canvas.height / 3;
const hIcon = height - padding * 2;
const wIcon = (image.width / image.height) * hIcon;
const width = wIcon + padding * 2;

// Get 2D context to draw icon
const ctx = canvas.getContext("2d");
ctx.shadowColor = "black";
ctx.shadowBlur = 10;

// Draw the rounded rectangle
// I ╭ H ──── G ╮ F
// J E
// │ │
// K D
// L ╰ A ──── B ╯ C
const [x0, y0] = [(canvas.width - width) / 2, (canvas.height - height) / 2];

const [xA, yA] = [x0 + radius, y0];
const [xB, yB] = [x0 + width - radius, y0];
const [xC, yC] = [x0 + width, y0];

const [xD, yD] = [x0 + width, y0 + radius];
const [xE, yE] = [x0 + width, y0 + height - radius];
const [xF, yF] = [x0 + width, y0 + height];

const [xG, yG] = [x0 + width - radius, y0 + height];
const [xH, yH] = [x0 + radius, y0 + height];
const [xI, yI] = [x0, y0 + height];

const [xJ, yJ] = [x0, y0 + height - radius];
const [xK, yK] = [x0, y0 + radius];
const [xL, yL] = [x0, y0];

ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(xA, yA); // A
ctx.lineTo(xB, yB); // A → B: ─
ctx.arcTo(xC, yC, xD, yD, radius); // B → C → D: ╯
ctx.lineTo(xE, yE); // D → E: │
ctx.arcTo(xF, yF, xG, yG, radius); // E → F → G: ╮
ctx.lineTo(xH, yH); // G → H: ─
ctx.arcTo(xI, yI, xJ, yJ, radius); // H → I → J: ╭
ctx.lineTo(xK, yK); // J → K: │
ctx.arcTo(xL, yL, xA, yA, radius); // K → L → A: ╰
ctx.closePath();
ctx.fill();

// Clear the shadow properties
ctx.shadowColor = "white";
ctx.shadowBlur = 0;

// Draw the image in the center of the canvas
const xCenter = canvas.width / 2 - wIcon / 2;
const yCenter = canvas.height / 2 - hIcon / 2;
ctx.drawImage(image, xCenter, yCenter, wIcon, hIcon);

// Create output directory if missing
const parent = dirname(output);
if (!existsSync(parent)) mkdirSync(parent, { recursive: true });

// Save the canvas to a file
const out = createWriteStream(output);
canvas.createPNGStream().pipe(out);
out.on("finish", () => {
console.log(`Banner saved to ${output}`);
});
})
.catch((error) => {
console.error(`Error: ${error.message}`);
});
Loading
Loading