From 56767a0f06fd2e2ae5b4e683d878a16d0759ab83 Mon Sep 17 00:00:00 2001 From: 409 Date: Fri, 22 Mar 2024 01:33:37 +0100 Subject: [PATCH] init --- .gitignore | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 15 +++++ bun.lockb | Bin 0 -> 15573 bytes index.ts | 52 +++++++++++++++ layers.ts | 72 ++++++++++++++++++++ package.json | 14 ++++ tsconfig.json | 27 ++++++++ 7 files changed, 358 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 index.ts create mode 100644 layers.ts create mode 100644 package.json create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ca96d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,178 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + +output +assets diff --git a/README.md b/README.md new file mode 100644 index 0000000..8779290 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# generative-art + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.0.33. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..55806a4f3a4c2547c7b44f23a17ac87323104422 GIT binary patch literal 15573 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 layer.chance) { + continue; + } + + const choice = weightedChoice(layer.options); + + layers.push({ input: `${layer.path}${choice}` }); + } + + const image = sharp(BASE_SHAPE_PATH).composite(layers); + + await Bun.write(`./output/${fileName}`, await image.toBuffer()); +} + +for (let i = 0; i < 100; i++) { + await generateRandomImage(`characters/${i}.png`, LAYERS); +} + +// combine into a single grid image +const characters: OverlayOptions[] = []; +for (let i = 0; i < 100; i++) { + characters.push({ input: `./output/characters/${i}.png`, left: (i % 10) * 16, top: Math.floor(i / 10.0) * 16 }); +} + +const grid = sharp({ create: { width: 160, height: 160, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } } }).composite(characters).png(); + +await Bun.write('./output/grid.png', await grid.toBuffer()); + +console.log('Finished generation'); diff --git a/layers.ts b/layers.ts new file mode 100644 index 0000000..cd6dc81 --- /dev/null +++ b/layers.ts @@ -0,0 +1,72 @@ +import type { Layer, TraitOption } from "."; + +export const LAYERS: Layer[] = [ + { + trait: 'skin', + path: './assets/skin/', + options: [ + { fileName: 'sand.png' }, + { fileName: 'sienna.png' }, + { fileName: 'bole.png' }, + { fileName: 'chocolate.png' } + ] + }, + { + trait: 'nose', + path: './assets/nose/', + options: [ + { fileName: 'small.png' }, + { fileName: 'big.png' } + ] + }, + { + trait: 'eyes', + path: './assets/eyes/', + options: [ + { fileName: 'blue.png', weight: 27 }, + { fileName: 'green.png', weight: 9 }, + { fileName: 'brown.png', weight: 19 }, + { fileName: 'dark_brown.png', weight: 45 } + ] + }, + { + trait: 'mouth', + path: './assets/mouth/', + options: [ + { fileName: 'neutral.png', weight: 8 }, + { fileName: 'happy.png', weight: 4 }, + { fileName: 'smirk.png', weight: 2 }, + { fileName: 'shock.png', weight: 1 } + ] + }, + { + trait: 'detail', + chance: 0.1, + path: './assets/detail/', + options: [ + { fileName: 'halo.png', weight: 1 }, + { fileName: 'red_beanie.png', weight: 25 }, + { fileName: 'green_beanie.png', weight: 25 } + ] + } +]; + +export function weightedChoice(options: TraitOption[]): string { + let i; + + let weights: number[] = [options[0].weight ?? 1]; + + for (i = 1; i < options.length; i++) { + weights[i] = (options[i].weight ?? 1) + weights[i - 1]; + } + + const random = Math.random() * weights[weights.length - 1]; + + for (i = 0; i < weights.length; i++) { + if (weights[i] > random) { + break; + } + } + + return options[i].fileName; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..248cab0 --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "generative-art", + "module": "index.ts", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "type": "module", + "dependencies": { + "sharp": "^0.33.2" + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0fef23a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}