Skip to content

Instantly share code, notes, and snippets.

@IkhwanAL
Last active January 5, 2026 09:34
Show Gist options
  • Select an option

  • Save IkhwanAL/97b264f2bda7078f29fdd6b34737392e to your computer and use it in GitHub Desktop.

Select an option

Save IkhwanAL/97b264f2bda7078f29fdd6b34737392e to your computer and use it in GitHub Desktop.
How I Improve Speed in Canvas
function mapGenerator(option) {
const { permutationTable, width, height } = editorState
let noises = []
let max = -Infinity
let min = Infinity
const sampleHeight = Math.floor(height / 2)
const sampleWidth = Math.floor(width / 2)
for (let y = 0; y < sampleHeight; y++) {
noises[y] = []
for (let x = 0; x < sampleWidth; x++) {
const noise = FractalNoise(x, y, permutationTable, option)
if (noise > max) {
max = noise
}
if (noise < min) {
min = noise
}
noises[y][x] = noise
}
}
canvasState.map = normalizeNoise(noises, min, max)
drawMap()
}
/**
* @description Convert the Map from -1 - 1 into 0 - 1
*/
function normalizeNoise(noises, min, max) {
const range = max - min
for (let y = 0; y < noises.length; y++) {
for (let x = 0; x < noises[y].length; x++) {
noises[y][x] = (noises[y][x] - min) / range
}
}
return noises
}
function drawMap() {
const { map } = canvasState
const width = Math.abs(editorState.x1 - editorState.x0)
const height = Math.abs(editorState.y1 - editorState.y0)
canvasState.dirty = true
const imageData = ctx.createImageData(width, height)
const scaledMap = bilinearInterpolation(map, width, height)
let index = 0
for (let y = 0; y < scaledMap.length; y++) {
for (let x = 0; x < scaledMap[y].length; x++) {
const grid = scaledMap[y][x]
const n = clamp(grid * 255 | 0, 0, 255)
imageData.data[index++] = n
imageData.data[index++] = n
imageData.data[index++] = n
imageData.data[index++] = 255
}
}
ctx.putImageData(imageData, 0, 0)
}
function clamp(value, min, max) {
if (value < min) {
return min
}
if (value > max) {
return max
}
return value
}
export function NewPermutationTable(rand) {
const perm = new Array(256)
for (let index = 0; index < 256; index++) {
perm[index] = index
}
for (let index = 255; index > 0; index--) {
const j = Math.floor(rand() * (index + 1))
const temp = perm[index]
perm[index] = perm[j]
perm[j] = temp
}
return perm.concat(perm)
}
const INV_SQRT2 = 1 / Math.sqrt(2)
const GRADIENTS = [
[INV_SQRT2, INV_SQRT2],
[-INV_SQRT2, INV_SQRT2],
[-INV_SQRT2, -INV_SQRT2],
[INV_SQRT2, -INV_SQRT2]
]
function getGradient(x) {
const v = x & 3
switch (v) {
case 0:
return GRADIENTS[0]
// return [1.0, 1.0]
case 1:
return GRADIENTS[1]
// return [-1.0, 1.0]
case 2:
return GRADIENTS[2]
// return [-1.0, -1.0]
default:
return GRADIENTS[3]
// return [1.0, -1.0]
}
}
export function perlinNoise(x, y, PERM) {
const floorX = Math.floor(x)
const floorY = Math.floor(y)
const X = floorX & 255
const Y = floorY & 255
const xf = x - floorX
const yf = y - floorY
// Change To Array Instead Of Object
const topRightVecToPoint = [xf - 1.0, yf - 1.0]
const topLeftVecToPoint = [xf, yf - 1.0]
const bottomRightVecToPoint = [xf - 1.0, yf]
const bottomLeftVecToPoint = [xf, yf]
let loc
loc = PERM[PERM[X + 1] + Y + 1]
let topRightCornerDirection = getGradient(loc)
loc = PERM[PERM[X] + Y + 1]
let topLeftCornerDirection = getGradient(loc)
loc = PERM[PERM[X] + Y]
const bottomLeftCornerDirection = getGradient(loc)
loc = PERM[PERM[X + 1] + Y]
let bottomRightCornerDirection = getGradient(loc)
const dotTopRight = dot(topRightVecToPoint, topRightCornerDirection)
const dotTopLeft = dot(topLeftVecToPoint, topLeftCornerDirection)
const dotBottomLeft = dot(bottomLeftVecToPoint, bottomLeftCornerDirection)
const dotBottomRight = dot(bottomRightVecToPoint, bottomRightCornerDirection)
const u = fade(xf)
const v = fade(yf)
const lerpTop = lerp(u, dotTopLeft, dotTopRight)
const lerpBottom = lerp(u, dotBottomLeft, dotBottomRight)
return lerp(v, lerpBottom, lerpTop)
}
export const defaultFractalOption = {
octaves: 8,
persistence: 0.1,
lacunarity: 1,
frequency: 1.0,
amplitude: 1.0
}
export function FractalNoise(x, y, PERM, option = defaultFractalOption) {
let total = 0.0;
let maxValue = 0.0
const { lacunarity, octaves, persistence, ...options } = option
let { frequency, amplitude } = options
for (let i = 0; i < octaves; i++) {
const n = amplitude * perlinNoise(x * frequency, y * frequency, PERM)
total += n
maxValue += amplitude
amplitude *= persistence
frequency *= lacunarity
}
return total / maxValue
}
function dot(vec1, vec2) {
return (vec1[0] * vec2[0]) + (vec1[1] * vec2[1])
}
function fade(t) {
return ((6 * t - 15) * t + 10) * t * t * t
}
/**
* @param {number} t - Blend Factor
* @param {number} leftValue - start of point value
* @param {number} rightValue - end of point value
*
*/
function lerp(t, leftValue, rightValue) {
return leftValue + t * (rightValue - leftValue)
}
export function bilinearInterpolation(map, dx, dy) {
const maxX = map[0].length
const maxY = map.length
const sx = (maxX - 1) / (dx - 1)
const sy = (maxY - 1) / (dy - 1)
let newMap = []
for (let y = 0; y < dy; y++) {
let tempArr = []
for (let x = 0; x < dx; x++) {
let originalX = x * sx
let originalY = y * sy
let x1 = Math.floor(originalX)
let y1 = Math.floor(originalY)
let x2 = Math.min(maxX - 1, x1 + 1)
let y2 = Math.min(maxY - 1, y1 + 1)
const horizontalDistance = x2 === x1 ? 0 : (originalX - x1) / (x2 - x1)
const verticalDistance = y2 == y1 ? 0 : (originalY - y1) / (y2 - y1)
const finalValue = calculateDistibution(horizontalDistance, verticalDistance, x1, x2, y1, y2, map)
tempArr.push(finalValue)
}
newMap.push(tempArr)
}
return newMap
}
function calculateDistibution(dx, dy, x1, x2, y1, y2, map) {
const topLeft = map[y1][x1] * ((1 - dx) * (1 - dy))
const topRight = map[y1][x2] * ((dx) * (1 - dy))
const bottomLeft = map[y2][x1] * ((1 - dx) * (dy))
const bottomRight = map[y2][x2] * (dx * dy)
return topLeft + topRight + bottomLeft + bottomRight
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment