go-pattern is a collection of pre-created image.Image implementations. It provides a variety of ready-to-use patterns that implement the standard Go image interface.
This project is in the early stages, please let me know of use/dependencies.
These patterns are designed to be:
- Ready to use: Instantly available as standard
image.Imageobjects. - Composable: Easily combined (e.g., zooming, transposing) to form complex visual structures.
- Standard: Fully compatible with any Go library that accepts
image.Image.
v := NewVoronoi(
makePoints(30, 150, 150),
[]color.Color{
color.RGBA{200, 220, 255, 255},
color.RGBA{100, 150, 250, 255},
color.RGBA{50, 100, 200, 255},
color.RGBA{150, 200, 240, 255},
color.RGBA{20, 40, 100, 255},
},
)
shine := NewLinearGradient(
SetStartColor(color.RGBA{255, 255, 255, 50}),
SetEndColor(color.Transparent),
SetAngle(30),
)
return NewBlend(v, shine, BlendOverlay) // This function is for documentation reference
_ = GenerateAmbientOcclusion(image.Rect(0, 0, 200, 200)) return NewBrick(
SetBrickSize(50, 20),
SetMortarSize(4),
) // Create "Stone" textures
var stones []image.Image
for i := 0; i < 4; i++ {
noise := NewNoise(SetNoiseAlgorithm(&PerlinNoise{
Seed: int64(i*50 + 123),
Frequency: 0.2,
}))
// Grey/Blueish stone colors
colored := NewColorMap(noise,
ColorStop{0.0, color.RGBA{80, 80, 90, 255}},
ColorStop{0.6, color.RGBA{120, 120, 130, 255}},
ColorStop{1.0, color.RGBA{160, 160, 170, 255}},
)
stones = append(stones, colored)
}
mortar := NewUniform(color.RGBA{50, 50, 50, 255})
// Larger bricks/stones
return NewBrick(
SetBrickSize(40, 30),
SetMortarSize(6),
SetBrickImages(stones...),
SetMortarImage(mortar),
SetBrickOffset(0.3), // Non-standard offset
) // Bricks with variations
// Create 3 variations of brick textures using Noise
var bricks []image.Image
for i := 0; i < 3; i++ {
// Noise with different seeds to ensure different texture per variant
noise := NewNoise(SetNoiseAlgorithm(&PerlinNoise{
Seed: int64(i*100 + 1),
Frequency: 0.1,
}))
// Tint the noise red/brown
colored := NewColorMap(noise,
ColorStop{0.0, color.RGBA{100, 30, 30, 255}},
ColorStop{1.0, color.RGBA{180, 60, 50, 255}},
)
bricks = append(bricks, colored)
}
// Mortar texture: grey noise
mortar := NewColorMap(
NewNoise(SetNoiseAlgorithm(&PerlinNoise{
Seed: 999,
Frequency: 0.5,
})),
ColorStop{0.0, color.RGBA{180, 180, 180, 255}},
ColorStop{1.0, color.RGBA{220, 220, 220, 255}},
)
return NewBrick(
SetBrickSize(60, 25),
SetMortarSize(3),
SetBrickImages(bricks...),
SetMortarImage(mortar),
) img := GenerateCamouflage(image.Rect(0, 0, 150, 150))
f, err := os.Create(CamouflageOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err = png.Encode(f, img); err != nil {
panic(err)
} // 1. Define colors for our candy.
colors := []color.RGBA{
{255, 0, 0, 255}, // Red
{0, 255, 0, 255}, // Green
{0, 0, 255, 255}, // Blue
{255, 255, 0, 255}, // Yellow
{255, 165, 0, 255}, // Orange
{139, 69, 19, 255}, // Brown
}
// 2. Create the Scatter pattern.
candy := NewScatter(
SetScatterFrequency(0.04), // Controls size/spacing relative to pixels
SetScatterDensity(0.9), // High density
SetScatterGenerator(func(u, v float64, hash uint64) (color.Color, float64) {
// Radius of the candy
radius := 14.0
// Distance from center
distSq := u*u + v*v
if distSq > radius*radius {
return color.Transparent, 0
}
dist := math.Sqrt(distSq)
// Pick a random color based on hash
colIdx := hash % uint64(len(colors))
baseCol := colors[colIdx]
// Simple shading: slightly darker at edges, highlight at top-left
// Spherical shading approx
// Normal vector (nx, ny, nz)
// z = sqrt(1 - x^2 - y^2)
nx := u / radius
ny := v / radius
nz := math.Sqrt(math.Max(0, 1.0 - nx*nx - ny*ny))
// Light source direction (top-left)
lx, ly, lz := -0.5, -0.5, 0.7
lLen := math.Sqrt(lx*lx + ly*ly + lz*lz)
lx, ly, lz = lx/lLen, ly/lLen, lz/lLen
// Diffuse
dot := nx*lx + ny*ly + nz*lz
diffuse := math.Max(0, dot)
// Specular (Glossy plastic look)
// Reflected light vector
// R = 2(N.L)N - L
rx := 2*dot*nx - lx
ry := 2*dot*ny - ly
rz := 2*dot*nz - lz
// View vector (straight up)
vx, vy, vz := 0.0, 0.0, 1.0
specDot := rx*vx + ry*vy + rz*vz
specular := math.Pow(math.Max(0, specDot), 20) // Shininess
// Apply lighting
r := float64(baseCol.R) * (0.2 + 0.8*diffuse) + 255*specular*0.6
g := float64(baseCol.G) * (0.2 + 0.8*diffuse) + 255*specular*0.6
b := float64(baseCol.B) * (0.2 + 0.8*diffuse) + 255*specular*0.6
// Clamp
r = math.Min(255, math.Max(0, r))
g = math.Min(255, math.Max(0, g))
b = math.Min(255, math.Max(0, b))
// Anti-aliasing at edge
alpha := 1.0
if dist > radius - 1.0 {
alpha = radius - dist
}
// Use hash for random Z-ordering
z := float64(hash) / 18446744073709551615.0
return color.RGBA{
R: uint8(r),
G: uint8(g),
B: uint8(b),
A: uint8(alpha * 255),
}, z
}),
)
f, err := os.Create(CandyOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, candy); err != nil {
panic(err)
} bg := NewRect(SetFillColor(color.RGBA{80, 0, 0, 255})) // Dark Red Base
// Main Pattern: Large Diamonds
d1 := NewRotate(
NewChecker(
color.RGBA{160, 120, 40, 255}, // Gold
color.Transparent,
SetSpaceSize(30),
),
45,
)
// Secondary Pattern: Smaller overlay diamonds
d2 := NewRotate(
NewChecker(
color.Transparent,
color.RGBA{0, 0, 0, 60}, // Shadow
SetSpaceSize(10),
),
45,
)
// Border Elements?
// Striped background for texture
stripes := NewCrossHatch(SetLineSize(1), SetSpaceSize(3), SetAngle(0), SetLineColor(color.RGBA{0,0,0,30}))
l1 := NewBlend(bg, stripes, BlendNormal)
l2 := NewBlend(l1, d1, BlendNormal)
l3 := NewBlend(l2, d2, BlendNormal)
return l3 // F1 Euclidean gives distance to center of cell.
// We want irregular organic cells.
noise := NewWorleyNoise(
SetFrequency(0.02),
SetSeed(777),
SetWorleyOutput(OutputF1),
SetWorleyMetric(MetricEuclidean),
SetWorleyJitter(0.8), // High jitter for organic look
)
// ColorMap:
// 0.0 - 0.2: Nucleus (Dark Green)
// 0.2 - 0.25: Nucleus Membrane (Lighter)
// 0.25 - 0.7: Cytoplasm (Light Green, Translucent look)
// 0.7 - 0.9: Cell Wall Inner (Darker Green)
// 0.9 - 1.0: Cell Wall (Thick Dark Border)
cells := NewColorMap(noise,
ColorStop{Position: 0.0, Color: color.RGBA{20, 80, 20, 255}}, // Nucleus Center
ColorStop{Position: 0.18, Color: color.RGBA{40, 100, 40, 255}}, // Nucleus
ColorStop{Position: 0.20, Color: color.RGBA{100, 180, 100, 255}},// Membrane
ColorStop{Position: 0.25, Color: color.RGBA{150, 220, 150, 255}},// Cytoplasm Start
ColorStop{Position: 0.70, Color: color.RGBA{140, 210, 140, 255}},// Cytoplasm End
ColorStop{Position: 0.85, Color: color.RGBA{50, 120, 50, 255}}, // Wall Inner
ColorStop{Position: 0.95, Color: color.RGBA{10, 40, 10, 255}}, // Wall Outer
ColorStop{Position: 1.0, Color: color.RGBA{0, 20, 0, 255}}, // Gap
)
f, err := os.Create(CellsOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, cells); err != nil {
panic(err)
} check := NewChecker(
color.White,
color.Black,
SetSpaceSize(20),
)
return NewRect(
SetLineSize(20),
SetLineImageSource(check),
SetFillColor(color.Transparent),
) return GenerateChippedBrick(image.Rect(0, 0, 300, 300)) return GenerateCircuitImpl(image.Rect(0, 0, 150, 150)) return ExampleNewClouds_cumulus() // High frequency noise with high persistence to simulate wisps
wispyNoise := NewNoise(NoiseSeed(103), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.05,
Octaves: 6,
Persistence: 0.7,
}))
return NewColorMap(wispyNoise,
ColorStop{0.0, color.RGBA{20, 50, 150, 255}}, // Dark Blue Sky
ColorStop{0.6, color.RGBA{50, 100, 200, 255}}, // Blue
ColorStop{0.7, color.RGBA{150, 200, 255, 100}}, // Faint wisp
ColorStop{1.0, color.RGBA{255, 255, 255, 200}}, // Bright wisp
) // 1. Base shape: Low frequency noise to define the cloud blobs
noise := NewNoise(NoiseSeed(42), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.015,
Octaves: 4,
Persistence: 0.5,
Lacunarity: 2.0,
}))
// 2. Color Map: Sky Blue -> White
// We use a steep ramp around 0.5-0.6 to create distinct cloud shapes
// rather than a smooth fog.
return NewColorMap(noise,
ColorStop{0.0, color.RGBA{100, 180, 255, 255}}, // Blue Sky
ColorStop{0.4, color.RGBA{130, 200, 255, 255}}, // Light Sky
ColorStop{0.55, color.RGBA{245, 245, 255, 255}}, // Cloud Edge (White-ish)
ColorStop{0.7, color.RGBA{255, 255, 255, 255}}, // Cloud Body
ColorStop{1.0, color.RGBA{230, 230, 240, 255}}, // Cloud Shadow/Density
) // Layer 1: Large, brooding shapes
base := NewNoise(NoiseSeed(666), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.01,
Octaves: 3,
}))
// Layer 2: Detailed turbulence
detail := NewNoise(NoiseSeed(777), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.04,
Octaves: 5,
Persistence: 0.6,
}))
// Blend them: Overlay adds contrast
blended := NewBlend(base, detail, BlendOverlay)
// Map to stormy colors
return NewColorMap(blended,
ColorStop{0.0, color.RGBA{20, 20, 25, 255}}, // Darkest Grey
ColorStop{0.4, color.RGBA{50, 50, 60, 255}}, // Dark Grey
ColorStop{0.6, color.RGBA{80, 80, 90, 255}}, // Mid Grey
ColorStop{0.8, color.RGBA{120, 120, 130, 255}}, // Light Grey highlights
ColorStop{1.0, color.RGBA{160, 160, 170, 255}}, // Brightest peaks
) // 1. Sky Gradient (Orange to Purple)
sky := NewLinearGradient(
SetStartColor(color.RGBA{255, 100, 50, 255}), // Orange/Red Horizon
SetEndColor(color.RGBA{50, 20, 100, 255}), // Purple/Blue Zenith
GradientVertical(),
)
// 2. Cloud Shapes
clouds := NewNoise(NoiseSeed(888), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.012,
Octaves: 4,
}))
// Map cloud noise to alpha/color
// We want the clouds to be dark at the bottom (shadow) and pink/gold at the edges
cloudColor := NewColorMap(clouds,
ColorStop{0.0, color.Black}, // No clouds
ColorStop{0.4, color.Black}, // No clouds
ColorStop{0.5, color.RGBA{80, 40, 60, 255}}, // Dark cloud base
ColorStop{0.7, color.RGBA{200, 100, 80, 255}}, // Orange/Pink mid
ColorStop{1.0, color.RGBA{255, 200, 100, 255}}, // Gold highlights
)
// 3. Composite Clouds over Sky using Screen blend mode for a glowing effect
return NewBlend(sky, cloudColor, BlendScreen) // F2-F1 gives thick lines at cell boundaries (where distance to 1st and 2nd closest points are similar)
noise := NewWorleyNoise(
SetFrequency(0.02),
SetSeed(123),
SetWorleyOutput(OutputF2MinusF1),
SetWorleyMetric(MetricEuclidean),
)
// Map distance to mud colors.
// Low value (close to 0) means F1 ~= F2, i.e., boundary/crack.
// High value means center of cell.
mud := NewColorMap(noise,
ColorStop{Position: 0.0, Color: color.RGBA{30, 20, 10, 255}}, // Crack (Dark brown/black)
ColorStop{Position: 0.1, Color: color.RGBA{60, 40, 20, 255}}, // Crack edge
ColorStop{Position: 0.2, Color: color.RGBA{130, 100, 70, 255}}, // Mud surface
ColorStop{Position: 1.0, Color: color.RGBA{160, 120, 80, 255}}, // Center of mud chunk
)
f, err := os.Create(CrackedMudOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, mud); err != nil {
panic(err)
} // This function body is empty because the bootstrap tool uses the function signature
// and the following variable to generate the documentation and image. // This function is for documentation reference
_ = GenerateCurvature(image.Rect(0, 0, 200, 200)) img := GenerateDamascus(image.Rect(0, 0, 150, 150))
f, err := os.Create(DamascusOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err = png.Encode(f, img); err != nil {
panic(err)
} // 1. Base Dirt: Brown, grainy noise
base := NewNoise(
NoiseSeed(101),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 101,
Frequency: 0.1,
Octaves: 4,
Persistence: 0.6,
}),
)
dirtColor := NewColorMap(base,
ColorStop{Position: 0.0, Color: color.RGBA{40, 30, 20, 255}}, // Dark Brown
ColorStop{Position: 0.5, Color: color.RGBA{80, 60, 40, 255}}, // Brown
ColorStop{Position: 0.8, Color: color.RGBA{100, 80, 60, 255}}, // Light Brown
ColorStop{Position: 1.0, Color: color.RGBA{120, 100, 80, 255}}, // Pebbles
)
// 2. Grain: High freq noise overlay
grain := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.5}))
detailed := NewBlend(dirtColor, grain, BlendOverlay)
return detailed // Base dirt
dirt := ExampleNewDirt()
// 3. Wetness Mask: Puddles
// Low frequency noise thresholded
puddleNoise := NewNoise(
NoiseSeed(202),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 202,
Frequency: 0.02,
}),
)
// Mask: White where puddles are, Black where dirt is
// Threshold at 0.6
mask := NewColorMap(puddleNoise,
ColorStop{Position: 0.0, Color: color.Black},
ColorStop{Position: 0.55, Color: color.Black},
ColorStop{Position: 0.6, Color: color.White},
ColorStop{Position: 1.0, Color: color.White},
)
// Puddles: Darker, smoother, reflective (mocked by color)
// Or use NormalMap to make them flat vs rough dirt.
// Let's make puddles dark brown/black and subtract detail.
puddleColor := NewRect(SetFillColor(color.RGBA{20, 15, 10, 255}))
// Blend puddle color based on mask?
// We don't have a "BlendMask" pattern yet that takes a mask image.
// But we can use boolean ops or just Blend?
// Or we can use the mask as alpha for the puddle layer and overlay it.
// But our patterns usually return opaque images unless alpha is handled.
// Let's assume we want to composite Puddle over Dirt using Mask.
// This usually requires a MaskedComposite pattern.
// I don't see one.
// Workaround:
// 1. Create Puddle Layer (Dark)
// 2. Create Dirt Layer
// 3. Blend them? No, we want distinct areas.
// If I use `NewBlend` with a mode? No standard mode does masking.
// I can use `NewBoolean` (BitwiseAnd) if mask is binary?
// Dirt AND (NOT Mask) + Puddle AND Mask.
// Invert mask for dirt
invMask := NewBitwiseNot(mask)
dirtPart := NewBitwiseAnd([]image.Image{dirt, invMask})
puddlePart := NewBitwiseAnd([]image.Image{puddleColor, mask})
return NewBitwiseOr([]image.Image{dirtPart, puddlePart}) stoneTex := NewWorleyNoise(SetFrequency(0.2), NoiseSeed(1))
stoneCol := NewColorMap(stoneTex,
ColorStop{0.0, color.RGBA{60, 60, 65, 255}},
ColorStop{1.0, color.RGBA{40, 40, 45, 255}},
)
tiles := NewBrick(
SetBrickSize(50, 50),
SetMortarSize(4),
SetBrickOffset(0),
SetBrickImages(stoneCol),
SetMortarImage(NewRect(SetFillColor(color.RGBA{10, 10, 10, 255}))),
)
cracks := NewWorleyNoise(
SetFrequency(0.1),
SetWorleyOutput(OutputF2MinusF1),
NoiseSeed(2),
)
crackMask := NewColorMap(cracks,
ColorStop{0.0, color.Black},
ColorStop{0.05, color.Black},
ColorStop{0.1, color.Transparent},
)
mossNoise := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.05}), NoiseSeed(3))
mossDetail := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.5}), NoiseSeed(4))
mossMask := NewBlend(mossNoise, mossDetail, BlendMultiply)
mossCol := NewColorMap(mossMask,
ColorStop{0.4, color.Transparent},
ColorStop{0.6, color.RGBA{50, 100, 50, 150}},
)
withCracks := NewBlend(tiles, crackMask, BlendNormal)
withMoss := NewBlend(withCracks, mossCol, BlendNormal)
return withMoss return GenerateFantasyFrame(image.Rect(0, 0, 150, 150)) wires := NewCrossHatch(
SetLineSize(2),
SetSpaceSize(18),
SetAngles(45, 135),
SetLineColor(color.RGBA{180, 180, 180, 255}),
)
grass := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.1}), NoiseSeed(30))
bg := NewColorMap(grass,
ColorStop{0.0, color.RGBA{20, 100, 20, 255}},
ColorStop{1.0, color.RGBA{30, 150, 30, 255}},
)
return NewBlend(bg, wires, BlendNormal) img := NewFineGrid(
SetBounds(image.Rect(0, 0, 640, 640)),
SetFineGridCellSize(12),
SetFineGridGlowRadius(3.5),
SetFineGridHue(205),
SetFineGridAberration(1),
SetFineGridGlowStrength(0.9),
SetFineGridLineStrength(1.4),
SetFineGridBackgroundFade(0.0),
)
f, err := os.Create(FineGridOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if cerr := f.Close(); cerr != nil {
panic(cerr)
}
}()
if err := png.Encode(f, img); err != nil {
panic(err)
} // Tiled floor using Tile pattern?
// We have `NewTile` which tiles an image.
// We have `NewBrick` or `NewGrid`?
// `brick.go` makes bricks.
// `checker.go` makes checks.
// Let's use `NewBrick` for a tile floor.
// Large square tiles.
// Create a marble texture for tiles
marble := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.1}))
marbleColor := NewColorMap(marble,
ColorStop{0.0, color.RGBA{220, 220, 220, 255}},
ColorStop{1.0, color.White},
)
// Create a slightly different marble for variation
marble2 := NewRotate(marbleColor, 90)
mortarColor := NewRect(SetFillColor(color.RGBA{50, 50, 50, 255}))
return NewBrick(
SetBrickSize(60, 60),
SetMortarSize(3),
SetBrickOffset(0),
SetBrickImages(marbleColor, marble2),
SetMortarImage(mortarColor),
) ExampleNewGlobe_Projected() g := GenerateGlobe_Grid(image.Rect(0, 0, 300, 300))
saveImage(Globe_GridOutputFilename, g) g := GenerateGlobe_Projected(image.Rect(0, 0, 300, 300))
saveImage(Globe_ProjectedOutputFilename, g) g := GenerateGlobe_Simple(image.Rect(0, 0, 300, 300))
saveImage(Globe_SimpleOutputFilename, g) i := NewGlyphRing()
f, err := os.Create(GlyphRingOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // 1. Create a base noise layer for general color variation.
baseNoise := NewNoise(
SetNoiseAlgorithm(&PerlinNoise{
Seed: 500,
Frequency: 0.02,
Octaves: 4,
Persistence: 0.5,
Lacunarity: 2.0,
}),
)
// 2. Create a high-frequency noise layer for "blades" or detail.
detailNoise := NewNoise(
SetNoiseAlgorithm(&PerlinNoise{
Seed: 600,
Frequency: 0.2, // High frequency for grass blades
Octaves: 2,
Persistence: 0.5,
Lacunarity: 2.0,
}),
)
// 3. Blend them. We want the detail to be prominent but influenced by the base.
// Multiply might darken too much, let's use Overlay or just simple addition/average.
// Actually, let's just use the detail noise warped by base noise for a wind-blown look?
// Or simply blend them.
// Let's try blending: Base * 0.5 + Detail * 0.5
// Using BlendAverage is simple.
blended := NewBlend(baseNoise, detailNoise, BlendAverage)
// 4. Map to Grass Colors.
grass := NewColorMap(blended,
ColorStop{Position: 0.0, Color: color.RGBA{10, 40, 10, 255}}, // Deep shadow/dirt
ColorStop{Position: 0.3, Color: color.RGBA{30, 80, 30, 255}}, // Dark Grass
ColorStop{Position: 0.6, Color: color.RGBA{60, 140, 40, 255}}, // Mid Grass
ColorStop{Position: 0.8, Color: color.RGBA{100, 180, 60, 255}}, // Light Grass
ColorStop{Position: 1.0, Color: color.RGBA{140, 220, 100, 255}}, // Tips/Highlights
)
f, err := os.Create(GrassOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, grass); err != nil {
panic(err)
} // 1. Background: Dirt
dirt := NewColorMap(
NewNoise(SetFrequency(0.05), NoiseSeed(1)),
ColorStop{0.0, color.RGBA{40, 30, 20, 255}},
ColorStop{1.0, color.RGBA{80, 60, 40, 255}},
)
// 2. Wind map (Perlin noise)
wind := NewNoise(
SetFrequency(0.01),
NoiseSeed(2),
SetNoiseAlgorithm(&PerlinNoise{Seed: 2, Octaves: 2, Persistence: 0.5}),
)
// 3. Density map (Worley noise for clumping)
density := NewWorleyNoise(
SetFrequency(0.02),
SetSeed(3),
)
// 4. Grass Layer
grass := NewGrassClose(
SetBladeHeight(35),
SetBladeWidth(5),
SetFillColor(color.RGBA{20, 160, 30, 255}),
SetWindSource(wind),
SetDensitySource(density),
// Background source
func(p any) {
if g, ok := p.(*GrassClose); ok {
g.Source = dirt
}
},
)
f, err := os.Create(GrassCloseOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, grass); err != nil {
panic(err)
} img := GenerateHexGrid(image.Rect(0, 0, 255, 255))
f, err := os.Create(HexGridOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err := png.Encode(f, img); err != nil {
panic(err)
} base := NewColorMap(
NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.02}), NoiseSeed(10)),
ColorStop{0.0, color.RGBA{220, 230, 255, 255}},
ColorStop{1.0, color.RGBA{240, 250, 255, 255}},
)
cracks := NewWorleyNoise(
SetFrequency(0.08),
SetWorleyOutput(OutputF2MinusF1),
NoiseSeed(11),
)
crackLines := NewColorMap(cracks,
ColorStop{0.0, color.RGBA{255, 255, 255, 180}},
ColorStop{0.05, color.Transparent},
)
deepCracks := NewWorleyNoise(
SetFrequency(0.04),
SetWorleyOutput(OutputF2MinusF1),
NoiseSeed(12),
)
deepCrackLines := NewColorMap(deepCracks,
ColorStop{0.0, color.RGBA{180, 200, 220, 200}},
ColorStop{0.02, color.Transparent},
)
layer1 := NewBlend(base, crackLines, BlendNormal)
layer2 := NewBlend(layer1, deepCrackLines, BlendNormal)
return layer2 // Layer 1: Base Shape (Worley F1 Euclidean) - Large distinct landmasses
baseShape := NewWorleyNoise(
SetFrequency(0.01),
SetSeed(555),
SetWorleyOutput(OutputF1),
SetWorleyMetric(MetricEuclidean),
)
// Layer 2: Detail (Perlin Noise) - Adds coastline complexity and terrain roughness
detail := NewNoise(
SetNoiseAlgorithm(&PerlinNoise{
Seed: 123,
Frequency: 0.05,
Octaves: 4,
Persistence: 0.5,
Lacunarity: 2.0,
}),
)
// Blend: Subtract detail from base shape? Or Overlay?
// Worley F1 is 0 at center (Peak), 1 at edge (Deep Water).
// We want Peaks to be high (1.0). So let's Invert Worley first?
// Or just use ColorMap on the result.
// If we Add detail to Worley, the values increase.
// Let's use BlendOverlay to mix the gradients.
mixed := NewBlend(baseShape, detail, BlendOverlay)
// ColorMap:
// Worley: 0 (Peak) -> 1 (Edge)
// Overlay tends to push contrast.
// Let's define:
// 0.0 - 0.2: Snow (Peak)
// 0.2 - 0.4: Mountain/Rock
// 0.4 - 0.5: Forest
// 0.5 - 0.6: Sand
// 0.6 - 1.0: Water
islands := NewColorMap(mixed,
ColorStop{Position: 0.0, Color: color.RGBA{250, 250, 250, 255}}, // Snow
ColorStop{Position: 0.15, Color: color.RGBA{120, 120, 120, 255}}, // Rock
ColorStop{Position: 0.30, Color: color.RGBA{34, 139, 34, 255}}, // Forest
ColorStop{Position: 0.50, Color: color.RGBA{210, 180, 140, 255}}, // Sand
ColorStop{Position: 0.55, Color: color.RGBA{64, 164, 223, 255}}, // Water
ColorStop{Position: 1.0, Color: color.RGBA{0, 0, 128, 255}}, // Deep Water
)
f, err := os.Create(IslandsOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, islands); err != nil {
panic(err)
} img := NewGopher()
return NewKnollDither(img, Windows16, 8) // This function is for the testable example and documentation.
// It creates the file directly.
img := GenerateLava(image.Rect(0, 0, 150, 150))
f, err := os.Create(LavaOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err = png.Encode(f, img); err != nil {
panic(err)
} base := NewColorMap(
NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.05}), NoiseSeed(70)),
ColorStop{0.0, color.RGBA{20, 0, 0, 255}},
ColorStop{0.6, color.RGBA{60, 10, 0, 255}},
ColorStop{1.0, color.RGBA{100, 20, 0, 255}},
)
rivers := NewColorMap(
NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.03}), NoiseSeed(71)),
ColorStop{0.0, color.RGBA{255, 200, 0, 255}}, // Bright Yellow
ColorStop{0.2, color.RGBA{255, 50, 0, 255}}, // Red
ColorStop{0.4, color.Transparent}, // Cooled rock
)
flow := NewWarp(rivers,
WarpDistortion(NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.02}), NoiseSeed(72))),
WarpScale(20.0),
)
return NewBlend(base, flow, BlendNormal) // Brushed Metal
// 1. High frequency noise
noise := NewNoise(
NoiseSeed(333),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 333,
Frequency: 0.1, // Lower frequency to avoid aliasing when scaled
Octaves: 3,
Persistence: 0.5,
}),
)
// Map to grey gradients before scaling
metalBase := NewColorMap(noise,
ColorStop{Position: 0.0, Color: color.RGBA{50, 50, 50, 255}},
ColorStop{Position: 0.5, Color: color.RGBA{150, 150, 150, 255}},
ColorStop{Position: 1.0, Color: color.RGBA{200, 200, 200, 255}},
)
// 2. Anisotropy: Scale heavily
// Scale X large, Y small? Or X 1, Y large?
// Vertical streaks: Scale Y > 1.
// But `NewScale` interpolates.
// Try Scale X=1, Y=10.
brushed := NewScale(metalBase, ScaleX(1.0), ScaleY(10.0))
return brushed // Base: Brushed Metal
// Use highly directional noise
noise := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.1, Octaves: 3}))
// Scale X to 1.0, Scale Y to 0.05 to stretch vertically? Or horizontally?
// If we want horizontal brush, we stretch X.
// Scale(1, 0.05) -> Stretches Y (low freq Y).
// We want lines. Lines are high freq in one direction, low in other.
// High freq X (1.0), Low freq Y (0.01).
brushed := NewScale(noise, ScaleX(1.0), ScaleY(0.02))
metalBase := NewColorMap(brushed,
ColorStop{0.0, color.RGBA{120, 120, 125, 255}},
ColorStop{1.0, color.RGBA{180, 180, 190, 255}},
)
// Scratches
scratchNoise := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.5}))
// Rotate scratches
scratches := NewRotate(NewScale(scratchNoise, ScaleX(1.0), ScaleY(0.01)), 15)
scratchLayer := NewColorMap(scratches,
ColorStop{0.0, color.RGBA{255, 255, 255, 40}},
ColorStop{0.3, color.Transparent},
)
// Rivets
rivets := NewGeneric(func(x, y int) color.Color {
s := 50
nx := (x + s/2) / s * s
ny := (y + s/2) / s * s
dx := x - nx
dy := y - ny
dist := math.Sqrt(float64(dx*dx + dy*dy))
if dist < 6 {
// Rivet shading (simple gradient)
v := uint8(255 - dist*20)
return color.RGBA{v, v, v, 255}
}
return color.Transparent
})
l1 := NewBlend(metalBase, scratchLayer, BlendOverlay)
l2 := NewBlend(l1, rivets, BlendNormal)
return l2 // Base brushed metal
base := ExampleNewMetal()
// Scratches using CrossHatch
hatchMultiply := NewCrossHatch(
SetLineColor(color.Gray{100}), // Dark scratches
SetSpaceColor(color.White), // No change
SetLineSize(1),
SetSpaceSize(40),
SetAngles(10, 80, 170),
)
// Distort scratches slightly
distort := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.1}))
hatchWarped := NewWarp(hatchMultiply, WarpDistortion(distort), WarpScale(2.0))
return NewBlend(base, hatchWarped, BlendMultiply) // Base Worley Noise (F1) provides the cellular structure
noise := NewWorleyNoise(
SetFrequency(0.02),
SetSeed(42),
SetWorleyOutput(OutputF1),
SetWorleyMetric(MetricEuclidean),
)
// ColorMap:
// Center (distance 0) -> Light
// Edge (distance ~0.5) -> Dark
// Gaps -> Black
molecules := NewColorMap(noise,
ColorStop{Position: 0.0, Color: color.RGBA{180, 180, 190, 255}}, // Center
ColorStop{Position: 0.4, Color: color.RGBA{100, 100, 110, 255}}, // Edge
ColorStop{Position: 0.45, Color: color.RGBA{50, 50, 55, 255}}, // Darker edge
ColorStop{Position: 0.5, Color: color.RGBA{10, 10, 10, 255}}, // Gap
ColorStop{Position: 1.0, Color: color.RGBA{0, 0, 0, 255}}, // Deep gap
)
f, err := os.Create(MoleculesOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, molecules); err != nil {
panic(err)
} img := buildMudTracks(nil)
f, err := os.Create(MudTracksOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, img); err != nil {
panic(err)
} // Create a height map using Perlin noise
noise := NewNoise(
NoiseSeed(123),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 123,
Octaves: 4,
Persistence: 0.5,
Lacunarity: 2.0,
Frequency: 0.05,
}),
)
// Convert to normal map with strength 5.0
return NewNormalMap(noise, NormalMapStrength(5.0)) // A simple sphere gradient to show curvature normals
grad := NewRadialGradient(
GradientCenter(0.5, 0.5),
SetStartColor(color.White),
SetEndColor(color.Black),
)
// Increase strength significantly to visualize the curve on a smooth gradient
return NewNormalMap(grad, NormalMapStrength(30.0)) i := NewNull()
f, err := os.Create(NullOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} return GeneratePCBTraces(image.Rect(0, 0, 192, 192)) return NewPaintedPlanks(
SetPlankBaseWidth(72),
SetPlankWidthVariance(0.32),
SetGrainIntensity(0.75),
SetPaintWear(0.42),
SetPaintColor(color.RGBA{177, 202, 214, 255}),
) // Re-implement Pebbles using Scatter for true overlapping geometry.
pebbles := NewScatter(
SetScatterFrequency(0.04), // Size control
SetScatterDensity(1.0), // Packed tight
SetScatterMaxOverlap(1),
SetScatterGenerator(func(u, v float64, hash uint64) (color.Color, float64) {
// Randomize size slightly
rSize := float64(hash&0xFF)/255.0
radius := 12.0 + rSize*6.0 // 12 to 18 pixels radius
// Perturb the shape using simple noise (simulated by sin/cos of hash+angle)
// to make it "chipped" or irregular.
angle := math.Atan2(v, u)
dist := math.Sqrt(u*u + v*v)
// Simple radial noise
noise := math.Sin(angle*5 + float64(hash%10)) * 0.1
noise += math.Cos(angle*13 + float64(hash%7)) * 0.05
effectiveRadius := radius * (1.0 + noise)
if dist > effectiveRadius {
return color.Transparent, 0
}
// Stone Color: Grey/Brown variations
grey := 100 + int(hash%100)
col := color.RGBA{uint8(grey), uint8(grey - 5), uint8(grey - 10), 255}
// Shading (diffuse)
// Normal estimation for a flattened spheroid
nx := u / effectiveRadius
ny := v / effectiveRadius
nz := math.Sqrt(math.Max(0, 1.0 - nx*nx - ny*ny))
// Light dir
lx, ly, lz := -0.5, -0.5, 0.7
lLen := math.Sqrt(lx*lx + ly*ly + lz*lz)
lx, ly, lz = lx/lLen, ly/lLen, lz/lLen
diffuse := math.Max(0, nx*lx + ny*ly + nz*lz)
// Apply shading
r := float64(col.R) * (0.1 + 0.9*diffuse)
g := float64(col.G) * (0.1 + 0.9*diffuse)
b := float64(col.B) * (0.1 + 0.9*diffuse)
// Soft edge anti-aliasing
alpha := 1.0
edgeDist := effectiveRadius - dist
if edgeDist < 1.0 {
alpha = edgeDist
}
// Use hash for random Z-ordering
z := float64(hash) / 18446744073709551615.0
return color.RGBA{
R: uint8(math.Min(255, r)),
G: uint8(math.Min(255, g)),
B: uint8(math.Min(255, b)),
A: uint8(alpha * 255),
}, z
}),
)
f, err := os.Create(PebblesOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, pebbles); err != nil {
panic(err)
} return GeneratePersianRugImpl(image.Rect(0, 0, 150, 150)) pn := &PerlinNoise{Frequency: 0.04, Seed: 60}
return NewGeneric(func(x, y int) color.Color {
s := 10
qx := (x / s) * s
qy := (y / s) * s
c := pn.At(qx, qy)
g := c.(color.Gray).Y
v := float64(g) / 255.0
if v < 0.3 {
return color.RGBA{30, 25, 20, 255}
} else if v < 0.5 {
return color.RGBA{60, 80, 40, 255}
} else if v < 0.7 {
return color.RGBA{140, 130, 100, 255}
}
return color.RGBA{10, 10, 10, 255}
}) i := NewPolka(
SetRadius(10),
SetSpacing(40),
SetFillColor(color.Black),
SetSpaceColor(color.White),
)
f, err := os.Create(PolkaOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // Asphalt: Aggregate noise (grey with black/white speckles)
base := NewNoise(
NoiseSeed(707),
SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.8}),
)
asphalt := NewColorMap(base,
ColorStop{Position: 0.0, Color: color.RGBA{40, 40, 40, 255}},
ColorStop{Position: 0.2, Color: color.RGBA{60, 60, 60, 255}},
ColorStop{Position: 0.5, Color: color.RGBA{50, 50, 50, 255}},
ColorStop{Position: 0.8, Color: color.RGBA{70, 70, 70, 255}},
ColorStop{Position: 1.0, Color: color.RGBA{90, 90, 90, 255}},
)
// Cracks: Voronoi edges
v2 := NewVoronoi(
[]image.Point{
{10, 10}, {50, 200}, {200, 50}, {220, 220},
{100, 100}, {150, 150}, {80, 20}, {20, 80},
},
[]color.Color{color.Black, color.White},
)
edges := NewEdgeDetect(v2)
// Edges are white on black.
// Invert to get Black cracks on White background
cracks := NewBitwiseNot(edges)
// Multiply cracks onto asphalt
return NewBlend(asphalt, cracks, BlendMultiply) road := ExampleNewRoad()
// Painted lines
// Yellow center line (dashed?)
// Let's do a solid double yellow or single yellow.
// VerticalLine pattern repeats.
// Image width is usually 255.
// We want one line in the center.
// LineSize 10. SpaceSize big enough to push next line off screen.
lines := NewVerticalLine(
SetLineSize(8),
SetSpaceSize(300),
SetLineColor(color.RGBA{255, 200, 0, 255}), // Paint
SetSpaceColor(color.Transparent),
SetPhase(123), // Center: ~127 minus half line width (4) = 123.
)
// Composite lines over road using Normal blend (Paint on top)
return NewBlend(road, lines, BlendNormal) // Winding road on grass
// 1. Terrain (Grass)
grass := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.1}))
grassColor := NewColorMap(grass,
ColorStop{0.0, color.RGBA{30, 100, 30, 255}},
ColorStop{1.0, color.RGBA{50, 150, 50, 255}},
)
// 2. Road Mask (Winding curve)
// We can use a low freq noise thresholded to a thin band?
// Or use `ModuloStripe` or `Sine` warped.
// Let's use a warped VerticalLine.
roadPath := NewVerticalLine(
SetLineSize(40), // Road width
SetSpaceSize(300),
SetLineColor(color.White), // Mask: White = Road
SetSpaceColor(color.Black), // Mask: Black = Grass
SetPhase(105),
)
// Warp the road path to make it winding
warpNoise := NewNoise(NoiseSeed(999), SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.02}))
windingRoadMask := NewWarp(roadPath, WarpDistortionX(warpNoise), WarpScale(50.0))
// 3. Road Texture
// Use asphalt from ExampleNewRoad, but we need to map it to the winding path?
// A simple tiled asphalt is fine.
roadTex := ExampleNewRoad()
// 4. Composite
// We have Grass (Bg), RoadTex (Fg), Mask (windingRoadMask).
// We don't have a MaskedBlend.
// Workaround:
// GrassPart = Grass * (NOT Mask)
// RoadPart = RoadTex * Mask
// Result = GrassPart + RoadPart
// Invert mask
invMask := NewBitwiseNot(windingRoadMask)
// Masking requires BitwiseAnd?
// But BitwiseAnd operates on colors bits.
// If Mask is pure Black/White, it works like a stencil for RGB.
grassPart := NewBitwiseAnd([]image.Image{grassColor, invMask})
roadPart := NewBitwiseAnd([]image.Image{roadTex, windingRoadMask})
return NewBitwiseOr([]image.Image{grassPart, roadPart}) // 1. Fine grain noise
grain := NewNoise(
NoiseSeed(303),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 303,
Frequency: 0.5,
Octaves: 2,
}),
)
sandColor := NewColorMap(grain,
ColorStop{Position: 0.0, Color: color.RGBA{194, 178, 128, 255}}, // Sand
ColorStop{Position: 1.0, Color: color.RGBA{225, 205, 150, 255}}, // Light Sand
)
return sandColor // Base sand
sand := ExampleNewSand()
// 2. Ripples
ripples := NewNoise(
NoiseSeed(404),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 404,
Frequency: 0.05,
}),
)
// Stretch to make lines
ripplesStretched := NewScale(ripples, ScaleX(1.0), ScaleY(10.0))
// Darken troughs using Multiply
shadows := NewColorMap(ripplesStretched,
ColorStop{Position: 0.0, Color: color.RGBA{180, 180, 180, 255}}, // Darker (Grey for Multiply)
ColorStop{Position: 0.5, Color: color.White}, // No change
ColorStop{Position: 1.0, Color: color.White}, // No change
)
// Rotate ripples
rotatedShadows := NewRotate(shadows, 90)
return NewBlend(sand, rotatedShadows, BlendMultiply) // Zoomed in sand to show grains
// Use Scatter or just low freq noise mapped to dots?
// Let's use noise with thresholding to make "grains".
noise := NewNoise(
NoiseSeed(304),
SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.2}),
)
// Map to distinct grains
grains := NewColorMap(noise,
ColorStop{Position: 0.0, Color: color.RGBA{160, 140, 100, 255}}, // Dark grain
ColorStop{Position: 0.4, Color: color.RGBA{210, 190, 150, 255}}, // Main sand
ColorStop{Position: 0.7, Color: color.RGBA{230, 210, 170, 255}}, // Light grain
ColorStop{Position: 0.9, Color: color.RGBA{255, 255, 255, 255}}, // Quartz sparkle
)
return grains // Use the explicit Scales pattern for proper overlapping geometry.
// Radius 40, SpacingX 40 (touching horizontally), SpacingY 20 (half-overlap vertically).
pattern := NewScales(
SetScaleRadius(40),
SetScaleXSpacing(40),
SetScaleYSpacing(25),
)
// The Scales pattern returns a heightmap (0 edge, 1 center).
// We want to map this to look like a tough fish scale.
// Center: Shiny/Metallic
// Gradient towards edge.
// Edge: Dark border.
scales := NewColorMap(pattern,
ColorStop{Position: 0.0, Color: color.RGBA{10, 10, 10, 255}}, // Deep edge (overlap shadow)
ColorStop{Position: 0.2, Color: color.RGBA{40, 40, 30, 255}}, // Rim
ColorStop{Position: 0.5, Color: color.RGBA{100, 100, 80, 255}}, // Body
ColorStop{Position: 0.8, Color: color.RGBA{160, 150, 120, 255}}, // Highlight start
ColorStop{Position: 1.0, Color: color.RGBA{200, 190, 160, 255}}, // Peak Highlight
)
f, err := os.Create(ScalesOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, scales); err != nil {
panic(err)
} i := NewScreenTone(
SetRadius(3),
SetSpacing(10),
SetAngle(45),
SetFillColor(color.Black),
SetSpaceColor(color.White),
)
f, err := os.Create(ScreenToneOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} i := NewShojo()
f, err := os.Create(ShojoOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} i := NewShojo(
SetSpaceColor(color.RGBA{0, 0, 40, 255}), // Dark blue bg
SetFillColor(color.RGBA{200, 220, 255, 255}), // Blueish sparkles
)
f, err := os.Create(Shojo_blueOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} i := NewShojo(
SetSpaceColor(color.RGBA{20, 0, 10, 255}), // Dark red/brown bg
SetFillColor(color.RGBA{255, 200, 220, 255}), // Pink sparkles
)
f, err := os.Create(Shojo_pinkOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // Slate: Layered noise, laminar structure.
// Dark grey, slight blue/green tint.
base := NewNoise(
NoiseSeed(808),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 808,
Frequency: 0.05,
Octaves: 5,
}),
)
// Map to slate colors
slateColor := NewColorMap(base,
ColorStop{Position: 0.0, Color: color.RGBA{40, 45, 50, 255}},
ColorStop{Position: 0.5, Color: color.RGBA{60, 65, 70, 255}},
ColorStop{Position: 1.0, Color: color.RGBA{80, 85, 90, 255}},
)
// Laminar effect: Scale Y slightly to stretch horizontally? Or vertically?
// Slate cleaves. Usually fine layers.
laminar := NewScale(slateColor, ScaleX(2.0), ScaleY(1.0))
// Surface bumpiness
bump := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.2}))
return NewBlend(laminar, bump, BlendOverlay) // 1. Soft base noise (drifts) - Bright white/grey
drifts := NewNoise(
NoiseSeed(505),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 505,
Frequency: 0.01,
}),
)
snowColor := NewColorMap(drifts,
ColorStop{Position: 0.0, Color: color.RGBA{240, 240, 250, 255}}, // Slight blue-grey shadow
ColorStop{Position: 1.0, Color: color.White},
)
// 2. Sparkle: Use white/blue dots.
// We can use Scatter pattern to place small bright dots.
// But let's fix the noise approach.
// High frequency noise, thresholded.
sparkleNoise := NewNoise(
NoiseSeed(606),
SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.8}),
)
// We want sparkles to be White/Blue on Transparent background.
// Then Overlay or Screen them.
// If background is Transparent, Screen (1-(1-A)*(1-B)) of Snow(A) and Transparent(B=0) -> A.
// So sparkles need to be Additive.
// Or we can just use Mix/Over.
sparkles := NewColorMap(sparkleNoise,
ColorStop{Position: 0.0, Color: color.Transparent},
ColorStop{Position: 0.9, Color: color.Transparent},
ColorStop{Position: 0.92, Color: color.RGBA{200, 220, 255, 255}}, // Blue tint
ColorStop{Position: 1.0, Color: color.White},
)
// Use BlendNormal (Over) for sparkles
return NewBlend(snowColor, sparkles, BlendNormal) snow := ExampleNewSnow()
// 3. Compression Tracks: Blueish/Grey depression.
tracks := NewCrossHatch(
SetLineColor(color.RGBA{200, 210, 230, 255}), // Icy blue/grey
SetSpaceColor(color.White), // Neutral for Multiply
SetLineSize(15),
SetSpaceSize(80),
SetAngles(25, 35), // Overlapping tracks
)
// Distort tracks
distort := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.05}))
organicTracks := NewWarp(tracks, WarpDistortion(distort), WarpScale(8.0))
// Multiply tracks onto snow
// LineColor (Blueish) * Snow (White) -> Blueish.
// SpaceColor (White) * Snow (White) -> White.
return NewBlend(snow, organicTracks, BlendMultiply) img := GenerateStarfield(image.Rect(0, 0, 150, 150))
f, err := os.Create(StarfieldOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err = png.Encode(f, img); err != nil {
panic(err)
} // Voronoi base for cells (cobblestones)
// We want cells to be somewhat irregular.
voronoi := NewVoronoi(
// Points
[]image.Point{
{50, 50}, {150, 40}, {230, 60},
{40, 140}, {130, 130}, {240, 150},
{60, 230}, {160, 240}, {220, 220},
{100, 100}, {200, 200}, {30, 30},
{180, 80}, {80, 180},
},
// Colors: Using Greyscale for heightmap initially, or color for texture
// Let's make a texture.
[]color.Color{
color.RGBA{100, 100, 100, 255},
color.RGBA{120, 115, 110, 255},
color.RGBA{90, 90, 95, 255},
color.RGBA{110, 110, 110, 255},
color.RGBA{130, 125, 120, 255},
},
)
// 1. Edge Wear: Distort the Voronoi
distort := NewNoise(
NoiseSeed(77),
SetNoiseAlgorithm(&PerlinNoise{Seed: 77, Frequency: 0.1}),
)
worn := NewWarp(voronoi, WarpDistortion(distort), WarpScale(5.0))
// 2. Surface Detail: Grain
grain := NewNoise(
NoiseSeed(88),
SetNoiseAlgorithm(&PerlinNoise{Seed: 88, Frequency: 0.5}),
)
// Blend grain onto stones (Overlay)
textured := NewBlend(worn, grain, BlendOverlay)
// We return the Albedo texture, not the normal map.
// Normal map can be a separate pass or derived.
return textured // Cellular noise (Worley) for cobblestones heightmap
worley := NewWorleyNoise(
SetWorleyMetric(MetricEuclidean),
SetWorleyOutput(OutputF1), // Distance to closest point
NoiseSeed(123),
SetFrequency(0.06),
)
// Map Worley (0-1 distance) to Stone Colors
// Worley: 0 is center, 1 is edge.
// Cobbles: Center is high/bright, Edge is low/dark (mortar).
// We want to map the distance to a color gradient.
cobbleColor := NewColorMap(worley,
ColorStop{Position: 0.0, Color: color.RGBA{180, 175, 170, 255}}, // Center (Light Stone)
ColorStop{Position: 0.4, Color: color.RGBA{140, 135, 130, 255}}, // Mid Stone
ColorStop{Position: 0.7, Color: color.RGBA{100, 95, 90, 255}}, // Dark Stone edge
ColorStop{Position: 0.85, Color: color.RGBA{60, 55, 50, 255}}, // Mortar start
ColorStop{Position: 1.0, Color: color.RGBA{40, 35, 30, 255}}, // Deep Mortar
)
// Add noise for texture
noise := NewNoise(SetNoiseAlgorithm(&PerlinNoise{Frequency: 0.2}))
textured := NewBlend(cobbleColor, noise, BlendOverlay)
return textured // F2-F1 gives distance to the border.
// Border is 0. Center is High.
noise := NewWorleyNoise(
SetFrequency(0.02),
SetSeed(100),
SetWorleyOutput(OutputF2MinusF1),
SetWorleyMetric(MetricEuclidean),
)
// Map:
// 0.0 - 0.1: Mortar (Dark)
// 0.1 - 0.3: Edge of stone (Darker Grey)
// 0.3 - 1.0: Stone Body (Grey/Blueish with gradient)
stones := NewColorMap(noise,
ColorStop{Position: 0.0, Color: color.RGBA{20, 15, 10, 255}}, // Mortar
ColorStop{Position: 0.15, Color: color.RGBA{40, 40, 45, 255}}, // Stone Edge
ColorStop{Position: 0.3, Color: color.RGBA{80, 80, 90, 255}}, // Stone Body
ColorStop{Position: 0.8, Color: color.RGBA{150, 150, 160, 255}}, // Highlight
)
f, err := os.Create(StonesOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, stones); err != nil {
panic(err)
} stripes := NewCrossHatch(
SetLineSize(20),
SetSpaceSize(20),
SetAngle(45),
SetLineColor(color.RGBA{255, 200, 0, 255}), // Yellow
SetSpaceColor(color.RGBA{20, 20, 20, 255}), // Black
)
return stripes // Example body intentionally empty. The bootstrap tool inspects the signature and
// metadata variables below to render documentation assets. gopher := NewScale(NewGopher(), ScaleToRatio(0.25))
// Tile the gopher in a 200x200 area
return NewTile(gopher, image.Rect(0, 0, 200, 200)) // Define some points and colors
points := []image.Point{
{50, 50}, {200, 50}, {125, 125}, {50, 200}, {200, 200},
}
colors := []color.Color{
color.RGBA{255, 100, 100, 255},
color.RGBA{100, 255, 100, 255},
color.RGBA{100, 100, 255, 255},
color.RGBA{255, 255, 100, 255},
color.RGBA{100, 255, 255, 255},
}
i := NewVoronoi(points, colors)
f, err := os.Create(VoronoiOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} img := NewVoronoiTiles(image.Rect(0, 0, 255, 255), defaultVoronoiTileCellSize, defaultVoronoiTileGapWidth, defaultVoronoiTileHeightImpact, 2024)
f, err := os.Create(VoronoiTilesOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, img); err != nil {
panic(err)
} // Standard demo: Grid warped by noise
// We want a visual that clearly shows the warping effect.
// A checkerboard is good.
checker := NewChecker(
color.RGBA{200, 200, 200, 255},
color.RGBA{50, 50, 50, 255},
)
// Distortion noise
noise := NewNoise(NoiseSeed(99), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.03,
Octaves: 2,
}))
// Apply Warp
warped := NewWarp(checker,
WarpDistortion(noise),
WarpScale(10.0),
)
fmt.Println(warped.At(10, 10))
// Output: {50 50 50 255} baseNoise := NewNoise(NoiseSeed(777), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.02,
Octaves: 4,
Persistence: 0.5,
}))
warpNoise := NewNoise(NoiseSeed(888), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.02,
Octaves: 2,
}))
warped := NewWarp(baseNoise,
WarpDistortion(warpNoise),
WarpScale(50.0),
)
stops := []ColorStop{
{0.0, color.RGBA{0, 100, 200, 255}},
{0.4, color.RGBA{100, 150, 255, 255}},
{0.6, color.RGBA{255, 255, 255, 255}},
{1.0, color.RGBA{255, 255, 255, 255}},
}
return NewColorMap(warped, stops...) colors := []color.Color{
color.RGBA{240, 240, 245, 255},
color.RGBA{240, 240, 245, 255},
color.RGBA{240, 240, 245, 255},
color.RGBA{200, 200, 210, 255},
color.RGBA{100, 100, 110, 255},
color.RGBA{200, 200, 210, 255},
}
stripes := NewModuloStripe(colors)
noise := NewNoise(NoiseSeed(456), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.04,
Octaves: 4,
Persistence: 0.6,
}))
return NewWarp(stripes,
WarpDistortion(noise),
WarpScale(30.0),
) fbm := func(seed int64) image.Image {
return NewNoise(NoiseSeed(seed), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.015,
Octaves: 6,
Persistence: 0.5,
Lacunarity: 2.0,
}))
}
base := fbm(101)
warp := NewNoise(NoiseSeed(202), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.01,
Octaves: 2,
}))
warped := NewWarp(base,
WarpDistortion(warp),
WarpScale(80.0),
)
stops := []ColorStop{
{0.0, color.RGBA{0, 0, 150, 255}},
{0.2, color.RGBA{0, 50, 200, 255}},
{0.22, color.RGBA{240, 230, 140, 255}},
{0.3, color.RGBA{34, 139, 34, 255}},
{0.6, color.RGBA{107, 142, 35, 255}},
{0.8, color.RGBA{139, 69, 19, 255}},
{0.9, color.RGBA{100, 100, 100, 255}},
{0.98, color.RGBA{255, 250, 250, 255}},
}
return NewColorMap(warped, stops...) woodLight := color.RGBA{222, 184, 135, 255}
woodDark := color.RGBA{139, 69, 19, 255}
colors := []color.Color{}
steps := 20
for i := 0; i < steps; i++ {
t := float64(i) / float64(steps-1)
r := uint8(float64(woodLight.R)*(1-t) + float64(woodDark.R)*t)
g := uint8(float64(woodLight.G)*(1-t) + float64(woodDark.G)*t)
b := uint8(float64(woodLight.B)*(1-t) + float64(woodDark.B)*t)
colors = append(colors, color.RGBA{r, g, b, 255})
}
for i := steps - 1; i >= 0; i-- {
colors = append(colors, colors[i])
}
rings := NewConcentricRings(colors)
noiseLow := NewNoise(NoiseSeed(123), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.02,
Octaves: 2,
}))
// Apply Warp
return NewWarp(rings,
WarpDistortion(noiseLow),
WarpScale(15.0),
) // 1. Base Noise: Simplex/Perlin noise (FBM)
baseNoise := NewNoise(
NoiseSeed(1),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 1,
Octaves: 6,
Persistence: 0.5,
Lacunarity: 2.0,
Frequency: 0.03,
}),
)
// 2. Flow Maps: We simulate flow by warping the noise.
// We'll use another lower frequency noise as the vector field (x/y displacement).
// Since Warp takes one image for X and Y, we can use the same noise or different ones.
// Let's create a "flow" map.
flowX := NewNoise(
NoiseSeed(2),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 2,
Frequency: 0.01,
}),
)
// Apply warp
warped := NewWarp(baseNoise,
WarpDistortionX(flowX),
WarpDistortionY(flowX), // Using same for simplicity, or could offset.
WarpScale(20.0),
)
// 3. Normal Map: Convert the heightmap to normals
normals := NewNormalMap(warped, NormalMapStrength(4.0))
// 4. Colorization: We can use the normal map directly (it looks cool/techy),
// or we can try to render it. But the prompt asked for "normals + flow maps".
// Usually water is rendered with reflection/refraction which needs a shader.
// Here we can output the normal map as the representation of the water surface.
// Or we can blend it with a blue tint to make it look like water.
waterBlue := color.RGBA{0, 0, 100, 255}
waterTint := NewRect(SetFillColor(waterBlue))
// Blend normals with blue using Overlay or SoftLight
// Overlay might be too harsh for normals.
// Let's just return the normal map as it is the "texture" of water surface.
// Or maybe "Multiply" the blue with the normal map to tint it.
blended := NewBlend(normals, waterTint, BlendAverage)
return blended // A variation showing just the normal map which is often what is used in game engines.
baseNoise := NewNoise(
NoiseSeed(42),
SetNoiseAlgorithm(&PerlinNoise{
Seed: 42,
Octaves: 5,
Persistence: 0.6,
Lacunarity: 2.0,
Frequency: 0.04,
}),
)
// Strong warp for "choppy" water
distortion := NewNoise(
NoiseSeed(100),
SetNoiseAlgorithm(&PerlinNoise{Seed: 100, Frequency: 0.02}),
)
warped := NewWarp(baseNoise, WarpDistortion(distortion), WarpScale(30.0))
return NewNormalMap(warped, NormalMapStrength(8.0)) split := NewGeneric(func(x, y int) color.Color {
if y > 75 {
return color.RGBA{50, 100, 150, 255}
}
return color.Transparent
})
sine := NewGeneric(func(x, y int) color.Color {
v := math.Sin(float64(x) * 0.1) * 10.0
val := 128.0 + v
return color.Gray{Y: uint8(val)}
})
warp := NewWarp(split,
WarpDistortion(sine),
WarpYScale(1.0),
WarpXScale(0.0),
)
return warp img := GenerateWindRidges(image.Rect(0, 0, 200, 200))
// Output:
f, err := os.Create(WindRidgesOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, img); err != nil {
panic(err)
} img := NewGopher()
// Spread 0 = auto calculate, or we can fine tune.
// Standard Windows dithering often just used the nearest color after thresholding.
// We use NewBayer8x8Dither for "Standard Ordered Dithering".
return NewBayer8x8Dither(img, Windows16) img := NewGopher()
return NewBayer4x4Dither(img, Windows16) img := NewGopher()
return NewHalftoneDither(img, 8, Windows16) // 1. Wood Palette
// Dark brown (Late wood / Rings) -> Light Tan (Early wood) -> Dark
woodPalette := []ColorStop{
{0.0, color.RGBA{101, 67, 33, 255}}, // Dark Brown (Ring Edge)
{0.15, color.RGBA{160, 120, 80, 255}}, // Transition
{0.5, color.RGBA{222, 184, 135, 255}}, // Light Tan (Center - Burlywood)
{0.85, color.RGBA{160, 120, 80, 255}}, // Transition
{1.0, color.RGBA{101, 67, 33, 255}}, // Back to Edge
}
// 2. Base "Heightmap" Generator
// We create a grayscale gradient for rings (0-255).
grayScale := make([]color.Color, 256)
for i := range grayScale {
grayScale[i] = color.Gray{Y: uint8(i)}
}
// Use ConcentricRings to generate the base distance field.
// We want ~10 rings across the 256px width.
// 256 colors in palette.
// To get 1 cycle every 25 pixels: Freq = 256/25 ≈ 10.
// To get elongated vertical rings, FreqY should be lower (slower change).
ringsBase := NewConcentricRings(grayScale,
SetCenter(128, -100), // Off-center top
SetFrequencyX(8.0), // ~30px width per ring
SetFrequencyY(0.8), // Stretched vertically (10x elongation)
)
// 3. Main Distortion (Growth Wobble)
// Low frequency noise to warp the rings.
// Noise values are 0..1 (from NewNoise/Perlin).
// Warp maps intensity to offset.
wobbleNoise := NewNoise(NoiseSeed(101), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.015,
Octaves: 2,
}))
// Apply warp.
// Scale 20.0 means max offset is +/- 20 pixels.
// Since rings are ~30px wide, this distorts them significantly but keeps structure.
warpedRings := NewWarp(ringsBase,
WarpDistortion(wobbleNoise),
WarpScale(20.0),
)
// 4. Fiber Grain (Fine Detail)
// Add "Turbulence" to the warp using higher frequency noise.
// This simulates the jagged edges of the grain.
fiberDistortion := NewNoise(NoiseSeed(303), SetNoiseAlgorithm(&PerlinNoise{
Frequency: 0.1, // Higher freq
Octaves: 3, // More detail
}))
// Chain Warps: WarpedRings -> Warp again with fiber distortion
doubleWarped := NewWarp(warpedRings,
WarpDistortion(fiberDistortion),
WarpScale(2.0), // Small jaggedness (2 pixels)
)
// 5. Color Mapping
// Map the grayscale intensity (warped distance) to the wood palette.
finalWood := NewColorMap(doubleWarped, woodPalette...)
return finalWood // Standard F1 Euclidean Worley Noise
i := NewWorleyNoise(
SetFrequency(0.05),
SetSeed(1),
)
f, err := os.Create(WorleyNoiseOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} baseTile := NewWorleyTiles(
SetBounds(image.Rect(0, 0, 160, 160)),
SetTileStoneSize(52),
SetTileGapWidth(0.1),
SetTilePaletteSpread(0.18),
SetTilePalette(
color.RGBA{128, 116, 106, 255},
color.RGBA{146, 132, 118, 255},
color.RGBA{112, 102, 96, 255},
),
WithSeed(2024),
)
// Tile the base stone field over a larger canvas so seams repeat cleanly.
return NewTile(baseTile, image.Rect(0, 0, 320, 320)) img := NewGopher()
return NewYliluoma1Dither(img, Windows16, 8) img := NewGopher()
return NewYliluoma2Dither(img, Windows16, 8) i := NewChecker(color.Black, color.White)
f, err := os.Create(CheckerOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // Example 1: Simple 2x2 grid with Gophers
// Shrink the Gopher so it fits better
gopher := NewScale(NewGopher(), ScaleToRatio(0.25))
args := []any{
Row(Cell(gopher), Cell(gopher)),
Row(Cell(gopher), Cell(gopher)),
}
for _, op := range ops {
args = append(args, op)
}
// Create a grid with explicit Rows
return NewGrid(args...) // 300x100 Grid
// Col 0: Bounded (100x100)
// Col 1: Unbounded (Should take remaining 200px)
// bounded := NewChecker(color.Black, color.White) // Checkers default to 255x255 but here we want fixed?
// Actually NewChecker returns default bounds.
// Let's use NewCrop or just standard bounds behavior.
// But `layout()` uses `image.Bounds()` if not `Bounded`.
// Let's create a bounded Mock that is 100x100.
hundred := 100
zero := 0
b := &boundedGopher{
Image: NewScale(NewGopher(), ScaleToSize(100, 100)),
bounds: Bounds{
Left: &Range{Low: &zero, High: &zero},
Right: &Range{Low: &hundred, High: &hundred},
Top: &Range{Low: &zero, High: &zero},
Bottom: &Range{Low: &hundred, High: &hundred},
},
}
// Unbounded pattern: e.g. a generic Tile or Checker that we want to fill space.
// NewChecker returns 255x255.
// Let's wrap it in an unbounded structure.
u := &unboundedPattern{
Image: NewChecker(color.RGBA{200, 0, 0, 255}, color.White),
}
args := []any{
FixedSize(300, 100),
Row(Cell(b), Cell(u)),
}
for _, op := range ops {
args = append(args, op)
}
return NewGrid(args...) gopher := NewScale(NewGopher(), ScaleToRatio(0.5))
// Padding with transparent background (nil)
return NewPadding(gopher, PaddingMargin(20)) i := NewHorizontalLine(
SetLineSize(5),
SetSpaceSize(5),
SetLineColor(color.RGBA{255, 0, 0, 255}),
SetSpaceColor(color.White),
)
f, err := os.Create(HorizontalLineOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} i := NewVerticalLine(
SetLineSize(5),
SetSpaceSize(5),
SetLineColor(color.RGBA{0, 0, 255, 255}),
SetSpaceColor(color.White),
)
f, err := os.Create(VerticalLineOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // Gopher AND Horizontal Stripes
g := NewGopher()
// Line: Black (Alpha 1). Space: White (Alpha 1).
h := NewHorizontalLine(SetLineSize(10), SetSpaceSize(10), SetLineColor(color.Black), SetSpaceColor(color.White))
// Default uses component-wise min if no TrueColor/FalseColor set.
i := NewAnd([]image.Image{g, h})
f, err := os.Create(BooleanAndOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} i := NewGopher()
f, err := os.Create(GopherOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // See GenerateMathsMandelbrot for implementation details // Create a noise pattern with a seeded algorithm (Hash) for stability
i := NewNoise(NoiseSeed(1))
f, err := os.Create(NoiseOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // A simple black rectangle (default)
i := NewRect()
// Output:
// Create the file for the example
f, err := os.Create(RectOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} g := NewGopher()
v := NewVerticalLine(SetLineSize(10), SetSpaceSize(10), SetLineColor(color.Black), SetSpaceColor(color.White))
// OR(Gopher, Stripes) -> Max(Gopher, Stripes)
i := NewOr([]image.Image{g, v})
f, err := os.Create(BooleanOrOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // See GenerateMathsJulia for implementation details g := NewGopher()
v := NewVerticalLine(SetLineSize(20), SetSpaceSize(20), SetLineColor(color.Black))
// XOR(Gopher, Stripes)
i := NewXor([]image.Image{g, v}, SetTrueColor(color.RGBA{255, 255, 0, 255}), SetFalseColor(color.Transparent))
f, err := os.Create(BooleanXorOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // See GenerateMathsSine for implementation details g := NewGopher()
// Not Gopher.
// Default component-wise: Invert colors.
i := NewNot(g)
f, err := os.Create(BooleanNotOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // See GenerateMathsWaves for implementation details // Create a simple circle
c := NewCircle(SetLineColor(color.Black), SetSpaceColor(color.White))
fmt.Printf("Circle bounds: %v\n", c.Bounds())
// Output:
// Circle bounds: (0,0)-(255,255)
f, err := os.Create(CircleOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, c); err != nil {
panic(err)
} // See GenerateHeatmap for implementation details // 1. Create a Noise source (Perlin Noise with FBM)
noise := NewNoise(
NoiseSeed(42), // Fixed seed for reproducible documentation
SetNoiseAlgorithm(&PerlinNoise{
Seed: 42,
Octaves: 4,
Persistence: 0.5,
Lacunarity: 2.0,
Frequency: 0.1,
}),
)
// 2. Map the noise to a "Grass" color ramp
grass := NewColorMap(noise,
ColorStop{Position: 0.0, Color: color.RGBA{0, 50, 0, 255}}, // Deep shadow green
ColorStop{Position: 0.4, Color: color.RGBA{10, 100, 10, 255}}, // Mid green
ColorStop{Position: 0.7, Color: color.RGBA{50, 150, 30, 255}}, // Light green
ColorStop{Position: 1.0, Color: color.RGBA{100, 140, 60, 255}}, // Dried tip
)
f, err := os.Create(ColorMapOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, grass); err != nil {
panic(err)
} // Create a simple Fibonacci spiral
c := NewFibonacci(SetLineColor(color.Black), SetSpaceColor(color.White))
fmt.Printf("Fibonacci bounds: %v\n", c.Bounds())
// Output:
// Fibonacci bounds: (0,0)-(255,255)
f, err := os.Create(FibonacciOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, c); err != nil {
panic(err)
} i := NewSpeedLines(
SetDensity(150),
SetMinRadius(30),
SetMaxRadius(80),
)
f, err := os.Create(SpeedLinesOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // Linear Gradient (Horizontal)
NewLinearGradient(
SetStartColor(color.RGBA{255, 0, 0, 255}),
SetEndColor(color.RGBA{0, 0, 255, 255}),
) i := NewQuantize(NewGopher(), 4)
f, err := os.Create(QuantizeOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} i := NewSimpleZoom(NewChecker(color.Black, color.White), 2)
f, err := os.Create(SimpleZoomOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} p := NewXorPattern()
f, err := os.Create(XorGridOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} // Black and White Palette
palette := []color.Color{color.Black, color.White}
i := NewBayer2x2Dither(NewGopher(), palette)
f, err := os.Create(Bayer2x2DitherOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} p := NewModuloStripe([]color.Color{
color.RGBA{255, 0, 0, 255},
color.RGBA{0, 255, 0, 255},
color.RGBA{0, 0, 255, 255},
})
f, err := os.Create(ModuloStripeOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} // Radial Gradient
NewRadialGradient(
SetStartColor(color.RGBA{255, 0, 0, 255}),
SetEndColor(color.RGBA{0, 0, 255, 255}),
) i := NewTransposed(NewDemoNull(), 10, 10)
f, err := os.Create(TransposedOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} p := NewConcentricRings([]color.Color{
color.Black,
color.White,
color.RGBA{255, 0, 0, 255},
})
f, err := os.Create(ConcentricRingsOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} // Conic Gradient
NewConicGradient(
SetStartColor(color.RGBA{255, 0, 255, 255}),
SetEndColor(color.RGBA{0, 255, 255, 255}),
) i := NewMirror(NewDemoMirrorInput(image.Rect(0, 0, 40, 40)), true, false)
f, err := os.Create(MirrorOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} p := NewPlasma()
f, err := os.Create(PlasmaOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} i := NewRotate(NewDemoRotateInput(image.Rect(0, 0, 40, 60)), 90)
f, err := os.Create(RotateOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} grad := NewLinearGradient(
SetStartColor(color.Black),
SetEndColor(color.White),
)
p := NewBayerDither(grad, 4)
f, err := os.Create(BayerDitherOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} p := NewBlueNoise()
f, err := os.Create(BlueNoiseOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} grad := NewLinearGradient(
SetStartColor(color.Black),
SetEndColor(color.White),
)
p := NewQuantize(grad, 4)
f, err := os.Create(GradientQuantizationOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} h := NewHorizontalLine(SetLineSize(50), SetSpaceSize(50), SetLineColor(color.RGBA{255, 0, 0, 255}))
v := NewVerticalLine(SetLineSize(50), SetSpaceSize(50), SetLineColor(color.RGBA{0, 255, 0, 255}))
p := NewBitwiseAnd([]image.Image{h, v})
f, err := os.Create(BitwiseAndOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
if err := png.Encode(f, p); err != nil {
panic(err)
} // See GenerateBooleanModes for actual implementation. // See GenerateSierpinskiTriangle for implementation details // See GenerateVHS for implementation details // See GenerateSierpinskiCarpet for implementation details i := NewSubpixelLines(
SetLineThickness(2),
SetOffsetStrength(0.65),
SetVignetteRadius(0.82),
)
f, err := os.Create(SubpixelLinesOutputFilename)
if err != nil {
panic(err)
}
defer f.Close()
err = png.Encode(f, i)
if err != nil {
panic(err)
} // 1. Create a source pattern
source := NewSolid(color.RGBA{255, 0, 0, 255})
// 2. Create a buffer
b := NewBuffer(source, SetExpiry(10*time.Second))
// 3. Refresh the buffer explicitly to populate the cache
b.Refresh()
// Output:
// Create the file for the example
f, err := os.Create(BufferOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, b); err != nil {
panic(err)
} i := NewDemoEdgeDetect()
f, err := os.Create(EdgeDetectOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // Standard example
i := NewDemoErrorDiffusion()
f, err := os.Create(ErrorDiffusionOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} i := NewDemoOrderedDither()
f, err := os.Create(OrderedDitherOutputFilename)
if err != nil {
panic(err)
}
defer func() {
if e := f.Close(); e != nil {
panic(e)
}
}()
if err = png.Encode(f, i); err != nil {
panic(err)
} // Default view: Bayer 8x8 on a gradient
return NewBayer8x8Dither(NewLinearGradient(), nil) return NewBayer8x8Dither(NewGopher(), PaletteCGA) return NewFog(
SetDensity(0.85),
SetFalloffCurve(1.8),
SetFillColor(color.RGBA{185, 205, 230, 255}),
) return NewConcentricWater(
ConcentricWaterRingSpacing(14.0),
ConcentricWaterAmplitude(1.1),
ConcentricWaterAmplitudeFalloff(0.018),
ConcentricWaterBaseTint(color.RGBA{24, 104, 168, 255}),
ConcentricWaterNormalStrength(4.0),
)This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.
















































































































































