Skip to content
This repository has been archived by the owner on Jul 21, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1 from paulkre/output-path-selection
Browse files Browse the repository at this point in the history
Output path selection
  • Loading branch information
Paul Kretschel authored Oct 16, 2019
2 parents 377fd3f + 11bc4cc commit 2a0610f
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 41 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- It is now possible to specify an output file path when saving a frame
- Add LICENSE
- Add CHANGELOG
- Clarify usage documentation in README
Expand Down
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
# P5 Runner
# p5 runner

A tool for running and exporting P5 sketches in a modern ESNext environment.
A tool for running and exporting [p5.js](https://p5js.org) sketches in a modern ESNext environment.

As soon as you make changes to your sketch file, this package will automatically reload the sketch in the browser. The tool also provides a simple API for getting animation frames out of the browser and saving them as PNG files.

## Usage

1. `mkdir my-sketch && cd my-sketch`
2. `npm init -y`
3. `npm install -S p5-runner`
4. Create index.js
1. `npm init -y`
2. `npm install --save p5-runner`
3. Create index.js

```javascript
import { runSketch } from "p5-runner"

const size = 512
const fps = 50

const { sin, cos, PI, random } = Math
const { sin, cos, PI } = Math
const r = size / 3

runSketch((p, api) => {
Expand All @@ -40,7 +41,7 @@ runSketch((p, api) => {
}
p.endShape()

// if (p.frameCount <= 250) api.saveFrame()
// if (p.frameCount <= 250) api.saveFrame("out/out.#####.png")
}

const getPos = frameCount => {
Expand All @@ -52,9 +53,9 @@ runSketch((p, api) => {
})
```

5. `npx p5-runner` or `npx p5-runner index.js`
6. Go to [http://localhost:3000](http://localhost:3000)
4. `npx p5-runner` or `npx p5-runner index.js`
5. Go to [http://localhost:3000](http://localhost:3000)

## Saving frames as PNG sequence
## Exporting your sketch as PNG files

When you call the `api.saveFrame()` method in your sketch file, P5 Runner will render the current frame to a PNG file and store it in a directory called "out" in your current working directory.
Calling the `api.saveFrame()` method in your sketch file will save the current animation frame in your project's directory. The default output location is "out/out.#####.png" but you can also provide your own destination path like this: `api.saveFrame("renders/sketch01/#####.png")` or `api.saveFrame("still.png")`
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "p5-runner",
"version": "1.1.2",
"description": "A tool for running and exporting P5 sketches in a modern ESNext environment",
"description": "A tool for running and exporting p5.js sketches in a modern ESNext environment",
"main": "lib/index.js",
"scripts": {
"build": "tsc",
Expand Down Expand Up @@ -33,6 +33,7 @@
"express": "^4.17.1",
"html-webpack-plugin": "^3.2.0",
"is-valid-path": "^0.1.1",
"mkdirp": "^0.5.1",
"p5": "^0.9.0",
"webpack": "^4.41.1",
"webpack-cli": "^3.3.9",
Expand All @@ -43,6 +44,7 @@
"@types/express": "^4.17.1",
"@types/html-webpack-plugin": "^3.2.1",
"@types/is-valid-path": "^0.1.0",
"@types/mkdirp": "^0.5.2",
"@types/p5": "^0.9.0",
"@types/webpack": "^4.39.3",
"@types/webpack-dev-middleware": "^2.0.3",
Expand Down
19 changes: 3 additions & 16 deletions src/client/api/createApi.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,10 @@
import P5 from "p5"
import { withSaveFrame } from "./withSaveFrame"

export type Api = {
saveFrame(): Promise<void>
saveFrame(path?: string): Promise<void> | void
}

export const createApi: (p: P5) => Api = p => ({
saveFrame: async () => {
const canvas = document.querySelector<HTMLCanvasElement>("body > canvas")
if (!canvas) return

p.noLoop()
await fetch(`/api/saveFrame/${p.frameCount}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "image/png",
},
body: canvas.toDataURL().replace(/^data:image\/png;base64,/, ""),
})
p.loop()
},
saveFrame: withSaveFrame(p),
})
51 changes: 51 additions & 0 deletions src/client/api/withSaveFrame.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import P5 from "p5"
import isValidPath from "is-valid-path"

const usedPaths: string[] = []

const padZeros = (n: string, width: number) =>
n.length >= width ? n : new Array(width - n.length + 1).join("0") + n

const validateExtension = (path: string) => {
if (!path.match(/\.png$/)) throw Error("Output file format must be PNG")
}

const sanitizeOutPath = (path: string, frameCount: number) => {
if (usedPaths.find(el => el === path))
throw Error("Cannot use the same output file name twice")

validateExtension(path)

const matches = path.match(/(#+)/g)
if (matches) {
if (matches.length > 1)
throw Error("Too many number placeholders in output path")

const paddedNumber = padZeros(String(frameCount), matches[0].length)
path = path.replace(matches[0], paddedNumber)
} else usedPaths.push(path)

if (!isValidPath(path)) throw Error("Invalid output file path")

return path
}

export const withSaveFrame = (p: P5) => async (
path: string = "out/out.#####.png",
) => {
const canvas = document.querySelector<HTMLCanvasElement>("body > canvas")
if (!canvas) return

path = sanitizeOutPath(path, p.frameCount)

p.noLoop()
await fetch(`/api/saveFrame/${encodeURIComponent(path)}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "image/png",
},
body: canvas.toDataURL().replace(/^data:image\/png;base64,/, ""),
})
p.loop()
}
27 changes: 15 additions & 12 deletions src/server/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,35 @@ import express from "express"
import bodyParser from "body-parser"
import path from "path"
import fs from "fs"
import isValidPath from "is-valid-path"
import mkdirp from "mkdirp"

export const api = express.Router()

const outDir = path.join(process.cwd(), "out")

api.use(
bodyParser.raw({
type: "image/png",
limit: "10mb",
}),
)

api.post("/saveFrame/:fid", (req, res) => {
const { fid } = req.params
if (isNaN(Number(fid))) {
api.post("/saveFrame/:filePath", async (req, res) => {
const filePath = path.join(
process.cwd(),
decodeURIComponent(req.params.filePath),
)

if (!isValidPath(filePath)) {
req.statusCode = 400
return "failure"
res.json("failure")
return
}

if (!fs.existsSync(outDir)) fs.mkdirSync(outDir)
const outDir = path.dirname(filePath)
if (!fs.existsSync(outDir)) await mkdirp.sync(outDir)

const data: string = req.body.toString()
fs.writeFileSync(path.join(outDir, `out.${pad(fid, 5)}.png`), data, "base64")
fs.writeFileSync(filePath, data, "base64")

res.json("success")
})

function pad(n: string, width: number) {
return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n
}

0 comments on commit 2a0610f

Please sign in to comment.