Skip to content

Instantly share code, notes, and snippets.

@katspaugh
Created August 25, 2023 15:59
Show Gist options
  • Save katspaugh/3037b44717c61ca5010299c70ae0ec1b to your computer and use it in GitHub Desktop.
Save katspaugh/3037b44717c61ca5010299c70ae0ec1b to your computer and use it in GitHub Desktop.
// Generate unique avatars based on Ethereum addresses.
// It uses celluar automata to generate the avatars which are unique for each address.
// Define the color pallets.
// Each pallet is an array of 3 colors: background, foreground, and highlight.
// The colors are in the RGB format, and we use soft pastel colors.
const palettes = [
// Palette 1
[
[255, 182, 193], // Background: Light Pink
[219, 112, 147], // Foreground: Pale Violet Red
[255, 130, 171], // Highlight: Lighter Pale Violet Red
],
// Palette 2
[
[173, 216, 230], // Background: Light Blue
[70, 130, 180], // Foreground: Steel Blue
[100, 149, 237], // Highlight: Lighter Steel Blue
],
// Palette 3
[
[240, 230, 140], // Background: Khaki
[189, 183, 107], // Foreground: Dark Khaki
[238, 232, 170], // Highlight: Lighter Dark Khaki
],
// Palette 4
[
[152, 251, 152], // Background: Pale Green
[60, 179, 113], // Foreground: Medium Sea Green
[84, 255, 159], // Highlight: Lighter Medium Sea Green
],
// Palette 5
[
[255, 228, 225], // Background: Misty Rose
[255, 105, 180], // Foreground: Hot Pink
[255, 182, 193], // Highlight: Lighter Hot Pink
],
// Palette 6
[
[240, 248, 255], // Background: Alice Blue
[100, 149, 237], // Foreground: Cornflower Blue
[135, 206, 250], // Highlight: Lighter Cornflower Blue
],
// Palette 7
[
[255, 240, 245], // Background: Lavender Blush
[219, 112, 147], // Foreground: Pale Violet Red
[255, 182, 193], // Highlight: Lighter Pale Violet Red
],
// Palette 8
[
[250, 235, 215], // Background: Antique White
[222, 184, 135], // Foreground: Burlywood
[255, 211, 155], // Highlight: Lighter Burlywood
],
// Palette 9
[
[255, 222, 173], // Background: Navajo White
[210, 105, 30], // Foreground: Chocolate
[255, 140, 64], // Highlight: Lighter Chocolate
],
// Palette 10
[
[245, 245, 220], // Background: Beige
[188, 143, 143], // Foreground: Rosy Brown
[255, 193, 193], // Highlight: Lighter Rosy Brown
],
// Palette 11
[
[255, 239, 213], // Background: Papaya Whip
[255, 165, 0], // Foreground: Orange
[255, 193, 37], // Highlight: Lighter Orange
],
// Palette 12
[
[245, 255, 250], // Background: Mint Cream
[32, 178, 170], // Foreground: Light Sea Green
[64, 224, 208], // Highlight: Lighter Light Sea Green
],
// Palette 13
[
[250, 250, 210], // Background: Light Goldenrod Yellow
[238, 232, 170], // Foreground: Pale Goldenrod
[255, 250, 205], // Highlight: Lighter Pale Goldenrod
],
// Palette 14
[
[255, 248, 220], // Background: Cornsilk
[218, 165, 32], // Foreground: Goldenrod
[255, 215, 64], // Highlight: Lighter Goldenrod
],
// Palette 15
[
[255, 228, 196], // Background: Bisque
[205, 133, 63], // Foreground: Peru
[255, 160, 122], // Highlight: Lighter Peru
],
// Palette 16
[
[255, 218, 185], // Background: Peach Puff
[233, 150, 122], // Foreground: Dark Salmon
[255, 160, 122], // Highlight: Lighter Dark Salmon
],
// Palette 17
[
[255, 245, 238], // Background: Seashell
[210, 105, 30], // Foreground: Chocolate
[255, 140, 64], // Highlight: Lighter Chocolate
],
// Palette 18
[
[245, 245, 245], // Background: White Smoke
[192, 192, 192], // Foreground: Silver
[211, 211, 211], // Highlight: Lighter Silver
],
// Palette 19
[
[253, 245, 230], // Background: Old Lace
[205, 92, 92], // Foreground: Indian Red
[255, 106, 106], // Highlight: Lighter Indian Red
],
// Palette 20
[
[250, 240, 230], // Background: Linen
[139, 69, 19], // Foreground: Saddle Brown
[160, 82, 45], // Highlight: Lighter Saddle Brown
],
]
// We will generate a pallet based on the address.
// Next, define the number of iterations.
// The more iterations, the more detailed the image will be.
// However, the more iterations, the longer it will take to generate the image.
// The number of iterations must be a power of 2.
const iterations = 256
// Next, define the number of cells.
// The more cells, the more detailed the image will be.
// However, the more cells, the longer it will take to generate the image.
// The number of cells must be a power of 2.
const cellNumber = 196
// Next, define the celluar automata rules.
// The rules are defined as a 2D array.
// The first dimension is the number of neighbors.
// The second dimension is the number of states.
// The value is the new state.
// For example, if the cell has 2 neighbors, and the current state is 1, then the new state is 0.
// The rules must be a power of 2.
const rules = [
[0, 1],
[1, 0],
[1, 1],
[1, 0],
]
// Now, define the flood fill threshold.
// The flood fill algorithm will fill in holes in the image.
// The threshold is the maximum size of a hole that will be filled.
// The threshold must be a power of 2.
const threshold = 16
// Now, define the celluar automata function.
// This function will generate an array of cells.
// Each cell is a number between 0 and 1.
// The function takes a seed as an argument.
// The seed is a string.
// The function returns an array of cells.
const celluarAutomata = (ethereumAddress) => {
const binaryAddress = Number(ethereumAddress, 16).toString(2)
const cells = new Array(cellNumber)
// Next, create an array of cells according to the rules defined above.
for (let i = 0; i < cellNumber; i++) {
cells[i] = Number(binaryAddress[i % binaryAddress.length])
}
// Next, apply the rules to the cells.
for (let i = 0; i < iterations; i++) {
const prev = cells.slice()
for (let j = 0; j < cellNumber; j++) {
const neighbors = prev[(j - 1 + cellNumber) % cellNumber] + prev[j] + prev[(j + 1) % cellNumber]
cells[j] = rules[neighbors][prev[j]]
}
}
// Next, return the cells.
return cells
}
function generatePalette(ethereumAddress) {
const seed = Number('0x' + ethereumAddress.slice(12, 19), 16)
return palettes[seed % palettes.length]
}
function draw(cells, palette) {
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
const size = Math.round(Math.sqrt(cellNumber))
canvas.width = canvas.height = size
canvas.style.width = canvas.style.height = `${300}px`
canvas.style.imageRendering = 'pixelated'
canvas.style.border = '1px solid #ddd'
const imageData = context.createImageData(size, size)
const data = imageData.data
// Draw the first half of the cells.
for (let i = 0; i < cellNumber; i++) {
const x = i % size
const y = Math.floor(i / size)
const neighbors = cells[(i - 1 + cellNumber) % cellNumber] + cells[i] + cells[(i + 1) % cellNumber]
const color = cells[i] ? neighbors > 2 ? 2 : 1 : 0
data[i * 4 + 0] = palette[color][0]
data[i * 4 + 1] = palette[color][1]
data[i * 4 + 2] = palette[color][2]
data[i * 4 + 3] = 255
}
// Now take the left side of the imageData, mirror it, and draw it on the right side.
data.forEach((value, index) => {
if (index % 4 === 0) {
const x = index / 4 % size
const y = Math.floor(index / 4 / size)
const mirrorX = size - x - 1
const mirrorIndex = (mirrorX + y * size) * 4
data[index] = data[mirrorIndex]
data[index + 1] = data[mirrorIndex + 1]
data[index + 2] = data[mirrorIndex + 2]
data[index + 3] = data[mirrorIndex + 3]
}
})
context.putImageData(imageData, 0, 0)
document.body.appendChild(canvas)
}
function main(address) {
draw(celluarAutomata(address), generatePalette(address))
}
main('0xA858DDc0445d8131daC4d1DE01f834ffcbA52Ef1')
main('0x9d94ef33e7f8087117f85b3ff7b1d8f27e4053d5')
main('0x474e5Ded6b5D078163BFB8F6dBa355C3aA5478C8')
main('0x85C9f5aA0F82A531087a356a55623Cf05E7Bb895')
main('0xA77DE01e157f9f57C7c4A326eeE9C4874D0598b6')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment