Perlin Noise Generator
Security Notice
Changelog
v1.0
Description
A image or text to prelin noise image generator
Needs scriptable, copy an paste this into a browser then copy text into a scriptable:
https://www.icloud.com/attachment/?u=https%3A%2F%2Fcvws.icloud-content.com%2FB%2FAdz9_P-lhgYJ5l5b5IeKtyDW0ieRAc2-NyDfKqpPUUfvwHWaXgFXLJDo%2F%24%7Bf%7D%3Fo%3DAtV50_11fKvwAUVyQ5Td200JtGKsFfX-g4PjieC-c1bC%26v%3D1%26x%3D3%26a%3DCAog-J2zfQyCPTxSUYfzxp-f0vbR1A-QgQe8wZLVV_cxc4wSbRCk1NmPtzMYpOTU48AzIgEAUgTW0ieRWgRXLJDoaibAGLNMMIBBZKfB8dGrwLB3kbCqa8xcddaQGwa7k28yRaOb76nxVHImf28xnW9kfZWHTCMC8J4t3YDuDWHva0ZryHlXfFMhvYWVsnNxglk%26e%3D1769735533%26fl%3D%26r%3D631B66AA-70E2-4926-8C03-E7E9AD980FAF-1%26k%3D%24%7Buk%7D%26ckc%3Dcom.apple.clouddocs%26ckz%3DiCloud.is.workflow.my.workflows%26p%3D171%26s%3DK3YNvbYDreOhcQXSKuKp54-Gnjo&uk=GDvO49hVW7Dx_8DboqTJgw&f=File-short-b1-103725.xz&sz=2816
Code:
// --- LOGGER SETUP ---
const startTime = Date.now();
function log(msg) {
const diff = Date.now() - startTime;
console.log(`[${diff}ms] ${msg}`);
}
log("Script started.");
// --- INPUT HANDLING ---
let inputImage = null;
let inputString = "default"; // Default seed
if (args.images.length > 0) {
log("Input detected: Image provided.");
inputImage = args.images[0];
} else if (args.shortcutParameter) {
if (typeof args.shortcutParameter === "string") {
log(`Input detected: Text seed "${args.shortcutParameter}"`);
inputString = args.shortcutParameter;
} else {
// Handle case where image is passed as parameter
log("Input detected: Image object in parameter.");
inputImage = args.shortcutParameter;
}
} else {
log("No input found. Using default seed.");
}
// --- CONFIGURATION ---
const BLOCK_SIZE = 4; // 4 = Retro/Fast, 2 = HD/Slow
const NOISE_SCALE = 0.02; // Lower = Zoomed in (bigger clouds), Higher = Zoomed out
const OCTAVES = 1; // Complexity of noise (Keep to 1 for speed in JS)
// If overlaying on an image, use white fog with transparency.
// If standalone, use opaque colorful plasma.
const IS_OVERLAY = inputImage !== null;
const NOISE_ALPHA = IS_OVERLAY ? 0.5 : 1.0;
// --- PERLIN NOISE IMPLEMENTATION ---
// We need a permutation table (0-255) to drive the noise.
// We will shuffle this table using the user's seed.
log("Initializing Random Seed...");
// 1. Simple Seeded RNG (Linear Congruential Generator)
let seed = 0;
for (let i = 0; i < inputString.length; i++) {
seed = (Math.imul(31, seed) + inputString.charCodeAt(i)) | 0;
}
function random() {
seed = (Math.imul(1664525, seed) + 1013904223) | 0;
return ((seed >>> 0) / 4294967296);
}
// 2. Initialize Permutation Table
const PERM_SIZE = 256;
const p = new Uint8Array(PERM_SIZE * 2);
// Fill with 0..255
let permutation = [];
for (let i = 0; i < PERM_SIZE; i++) {
permutation[i] = i;
}
// Shuffle using our seed
for (let i = PERM_SIZE - 1; i > 0; i--) {
let r = Math.floor(random() * (i + 1));
[permutation[i], permutation[r]] = [permutation[r], permutation[i]];
}
// Double the array to avoid overflow wrapping
for (let i = 0; i < PERM_SIZE * 2; i++) {
p[i] = permutation[i % PERM_SIZE];
}
log("Permutation table generated.");
// 3. Perlin Math Helpers
function fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
function lerp(t, a, b) {
return a + t * (b - a);
}
function grad(hash, x, y, z) {
const h = hash & 15;
const u = h < 8 ? x : y;
const v = h < 4 ? y : h === 12 || h === 14 ? x : z;
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}
// 4. The Noise Function (2D)
function perlin(x, y) {
// Find unit cube that contains point
let X = Math.floor(x) & 255;
let Y = Math.floor(y) & 255;
// Find relative x,y of point in cube
x -= Math.floor(x);
y -= Math.floor(y);
// Compute fade curves
let u = fade(x);
let v = fade(y);
// Hash coordinates of the 4 cube corners
let A = p[X] + Y;
let B = p[X + 1] + Y;
// Add blended results from 4 corners
return lerp(v,
lerp(u, grad(p[A], x, y, 0), grad(p[B], x - 1, y, 0)),
lerp(u, grad(p[A + 1], x, y - 1, 0), grad(p[B + 1], x - 1, y - 1, 0))
);
}
// --- RENDERING ---
let width, height;
if (inputImage) {
width = inputImage.size.width;
height = inputImage.size.height;
} else {
width = 800;
height = 800;
}
log(`Canvas dimensions set: ${width}x${height}`);
let ctx = new DrawContext();
ctx.size = new Size(width, height);
ctx.respectScreenScale = true;
ctx.shouldAntialias = false;
// Draw Background
if (inputImage) {
log("Drawing background image...");
ctx.drawImageInRect(inputImage, new Rect(0, 0, width, height));
} else {
// Dark background for colorful noise
ctx.setFillColor(new Color("#000000"));
ctx.fillRect(new Rect(0, 0, width, height));
}
// Helper: Map -1..1 to 0..255
function mapRange(val, inMin, inMax, outMin, outMax) {
return (val - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
}
// Helper: Float to Hex
function toHex(val) {
let intVal = Math.floor(Math.max(0, Math.min(255, val)));
let hex = intVal.toString(16);
return hex.length === 1 ? "0" + hex : hex;
}
log(`Starting render loop (Block Size: ${BLOCK_SIZE})...`);
let totalBlocks = (width * height) / (BLOCK_SIZE * BLOCK_SIZE);
let count = 0;
for (let px = 0; px < width; px += BLOCK_SIZE) {
for (let py = 0; py < height; py += BLOCK_SIZE) {
// Calculate Noise Value (-1.0 to 1.0)
// We add seed offset to x/y to ensure different text makes different clouds
let noiseVal = perlin(px * NOISE_SCALE, py * NOISE_SCALE);
let color;
if (IS_OVERLAY) {
// White Fog Effect
// Map noise -1..1 to Alpha 0..1
let alphaVal = mapRange(noiseVal, -1, 1, 0, NOISE_ALPHA);
// White color with variable alpha
color = new Color("FFFFFF", alphaVal);
} else {
// Colorful Plasma Effect
// Map noise to 0..255
let v = mapRange(noiseVal, -1, 1, 0, 255);
// Generate pseudo-palette based on noise value
// This creates bands of color
let r = v;
let g = (v + 85) % 255; // Offset green
let b = (v + 170) % 255; // Offset blue
let hex = toHex(r) + toHex(g) + toHex(b);
color = new Color(hex, 1.0);
}
ctx.setFillColor(color);
ctx.fillRect(new Rect(px, py, BLOCK_SIZE, BLOCK_SIZE));
count++;
}
}
log(`Render complete. Processed ${count} blocks.`);
// --- OUTPUT ---
let finalImage = ctx.getImage();
let fm = FileManager.local();
let tempPath = fm.joinPath(fm.temporaryDirectory(), "perlin_output.png");
fm.writeImage(tempPath, finalImage);
log(`Image saved to: ${tempPath}`);
Script.setShortcutOutput(tempPath);
if (config.runsInApp) {
QuickLook.present(finalImage);
}
log("Script finished.");
Script.complete();
Comments
Start the conversation
No comments yet. Be the first to comment!
Please sign in to post comments.