Initial commit! 🎉
This commit is contained in:
		
						commit
						bb1b543721
					
				| 
						 | 
				
			
			@ -0,0 +1,107 @@
 | 
			
		|||
# Logs
 | 
			
		||||
logs
 | 
			
		||||
*.log
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
yarn-debug.log*
 | 
			
		||||
yarn-error.log*
 | 
			
		||||
lerna-debug.log*
 | 
			
		||||
 | 
			
		||||
# 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/
 | 
			
		||||
 | 
			
		||||
# TypeScript v1 declaration files
 | 
			
		||||
typings/
 | 
			
		||||
 | 
			
		||||
# TypeScript cache
 | 
			
		||||
*.tsbuildinfo
 | 
			
		||||
 | 
			
		||||
# Optional npm cache directory
 | 
			
		||||
.npm
 | 
			
		||||
 | 
			
		||||
# Optional eslint cache
 | 
			
		||||
.eslintcache
 | 
			
		||||
 | 
			
		||||
# 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 variables file
 | 
			
		||||
.env
 | 
			
		||||
.env.test
 | 
			
		||||
 | 
			
		||||
# parcel-bundler cache (https://parceljs.org/)
 | 
			
		||||
.cache
 | 
			
		||||
 | 
			
		||||
# Next.js build output
 | 
			
		||||
.next
 | 
			
		||||
 | 
			
		||||
# Nuxt.js build / generate output
 | 
			
		||||
.nuxt
 | 
			
		||||
dist
 | 
			
		||||
 | 
			
		||||
# Gatsby files
 | 
			
		||||
.cache/
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
# Serverless directories
 | 
			
		||||
.serverless/
 | 
			
		||||
 | 
			
		||||
# FuseBox cache
 | 
			
		||||
.fusebox/
 | 
			
		||||
 | 
			
		||||
# DynamoDB Local files
 | 
			
		||||
.dynamodb/
 | 
			
		||||
 | 
			
		||||
# TernJS port file
 | 
			
		||||
.tern-port
 | 
			
		||||
 | 
			
		||||
# Image & data output directory
 | 
			
		||||
output/
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "driftingnebula",
 | 
			
		||||
  "description": "Generative art with GIMP, GEGL and ImageMagick.",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "start": "node --loader=ts-node/esm --no-warnings source/driftingnebula.ts",
 | 
			
		||||
    "test": "xo"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "execa": "^6.1.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/node": "^17.0.21",
 | 
			
		||||
    "ts-node": "^10.6.0",
 | 
			
		||||
    "typescript": "^4.6.2",
 | 
			
		||||
    "xo": "^0.48.0"
 | 
			
		||||
  },
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "xo": {
 | 
			
		||||
    "prettier": true,
 | 
			
		||||
    "rules": {
 | 
			
		||||
      "no-await-in-loop": "off"
 | 
			
		||||
    },
 | 
			
		||||
    "space": true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
import fsp from 'node:fs/promises';
 | 
			
		||||
import path from 'node:path';
 | 
			
		||||
import {performance} from 'node:perf_hooks';
 | 
			
		||||
 | 
			
		||||
import {execa} from 'execa';
 | 
			
		||||
 | 
			
		||||
import {Crop} from './gegl/exports.js';
 | 
			
		||||
 | 
			
		||||
async function main(): Promise<void> {
 | 
			
		||||
  const projects: Project[] = [];
 | 
			
		||||
 | 
			
		||||
  for (const {name, operations, resolution} of projects) {
 | 
			
		||||
    const dataStart = performance.now();
 | 
			
		||||
    const {width, height} = resolution;
 | 
			
		||||
 | 
			
		||||
    const baseDir = path.resolve(`./output/${name}`);
 | 
			
		||||
    await fsp.mkdir(baseDir, {recursive: true});
 | 
			
		||||
 | 
			
		||||
    console.log(`# ${name}`);
 | 
			
		||||
    console.log(`* ${width}x${height}`);
 | 
			
		||||
    console.log(`* ${operations.length} operations`);
 | 
			
		||||
 | 
			
		||||
    const graph = operations.flatMap((operation) => {
 | 
			
		||||
      const graph = operation.graph();
 | 
			
		||||
      if (operation.appendCrop) {
 | 
			
		||||
        graph.push(...new Crop({height, width}).graph());
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return graph;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const prettyGraph = graph.map((operation) =>
 | 
			
		||||
      operation.startsWith('gegl:') ? `\n${operation}\n` : `  ${operation}\n`,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const graphFile = `${name}.txt`;
 | 
			
		||||
    const outputFile = `${name}.png`;
 | 
			
		||||
 | 
			
		||||
    console.log(`* Writing ${graphFile}`);
 | 
			
		||||
    await fsp.writeFile(
 | 
			
		||||
      path.join(baseDir, graphFile),
 | 
			
		||||
      prettyGraph.join('').trimStart(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    console.log(`* Writing ${outputFile}`);
 | 
			
		||||
    await execa('gegl', ['-o', path.join(baseDir, outputFile), '--', ...graph]);
 | 
			
		||||
 | 
			
		||||
    const time = (performance.now() - dataStart).toFixed(2);
 | 
			
		||||
    console.log(`* Generated in ${time}ms`);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void main();
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
export abstract class BaseOperation<P> {
 | 
			
		||||
  public parameters: P;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Some GEGL operations will run infinitely unless you limit the buffer in
 | 
			
		||||
   * some way, so all operations must indicate whether or not they should be
 | 
			
		||||
   * followed by a crop operation.
 | 
			
		||||
   */
 | 
			
		||||
  public abstract appendCrop: boolean;
 | 
			
		||||
 | 
			
		||||
  /** The GEGL operation name, starting with `gegl:`. */
 | 
			
		||||
  public abstract name: string;
 | 
			
		||||
 | 
			
		||||
  constructor(parameters: P) {
 | 
			
		||||
    this.parameters = parameters;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public graph(): string[] {
 | 
			
		||||
    const graph: string[] = [this.name];
 | 
			
		||||
 | 
			
		||||
    for (const [key, value] of Object.entries(this.parameters)) {
 | 
			
		||||
      const kebabCasedKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
 | 
			
		||||
      graph.push(`${kebabCasedKey}=${value as string}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return graph;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
import {BaseOperation} from './base.js';
 | 
			
		||||
 | 
			
		||||
export interface CropParameters {
 | 
			
		||||
  height: number;
 | 
			
		||||
  resetOrigin: boolean;
 | 
			
		||||
  width: number;
 | 
			
		||||
  x: number;
 | 
			
		||||
  y: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Crop extends BaseOperation<CropParameters> {
 | 
			
		||||
  public static default: CropParameters = {
 | 
			
		||||
    height: 0,
 | 
			
		||||
    resetOrigin: false,
 | 
			
		||||
    width: 0,
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: 0,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  public appendCrop = false;
 | 
			
		||||
  public name = 'gegl:crop';
 | 
			
		||||
 | 
			
		||||
  constructor(parameters?: Partial<CropParameters>) {
 | 
			
		||||
    super({...Crop.default, ...parameters});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
export * from './base.js';
 | 
			
		||||
export * from './crop.js';
 | 
			
		||||
export * from './generic.js';
 | 
			
		||||
export * from './simplex-noise.js';
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
import {BaseOperation} from './base.js';
 | 
			
		||||
 | 
			
		||||
export type GenericParameters = Record<string, number | string>;
 | 
			
		||||
 | 
			
		||||
export class Generic extends BaseOperation<GenericParameters> {
 | 
			
		||||
  public appendCrop: boolean;
 | 
			
		||||
  public name: string;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    name: string,
 | 
			
		||||
    parameters?: GenericParameters,
 | 
			
		||||
    appendCrop?: boolean,
 | 
			
		||||
  ) {
 | 
			
		||||
    super(parameters ?? {});
 | 
			
		||||
    this.name = name;
 | 
			
		||||
    this.appendCrop = appendCrop ?? false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
import {BaseOperation} from './base.js';
 | 
			
		||||
 | 
			
		||||
export interface SimplexNoiseParameters {
 | 
			
		||||
  iterations: number;
 | 
			
		||||
  scale: number;
 | 
			
		||||
  seed: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class SimplexNoise extends BaseOperation<SimplexNoiseParameters> {
 | 
			
		||||
  public static default: SimplexNoiseParameters = {
 | 
			
		||||
    iterations: 1,
 | 
			
		||||
    scale: 1,
 | 
			
		||||
    seed: 1,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  public appendCrop = true;
 | 
			
		||||
  public name = 'gegl:simplex-noise';
 | 
			
		||||
 | 
			
		||||
  constructor(parameters?: Partial<SimplexNoiseParameters>) {
 | 
			
		||||
    super({...SimplexNoise.default, ...parameters});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
import {BaseOperation} from './gegl/exports.js';
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Project {
 | 
			
		||||
    name: string;
 | 
			
		||||
    operations: Array<InstanceType<typeof BaseOperation>>;
 | 
			
		||||
    resolution: {
 | 
			
		||||
      height: number;
 | 
			
		||||
      width: number;
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "module": "ESNext",
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "outDir": "build",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "target": "ESNext"
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "source/**/*.ts"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue