Hey Claude, make it HD etc.
# Setup temporary environment and install dependencies
using Pkg
Pkg.activate(temp=true)
Pkg.add(["Sixel", "ColorTypes", "FixedPointNumbers", "Random"])
using Sixel, ColorTypes, FixedPointNumbers, Random
"""
HD Realistic Snow Animation using Sixel Graphics
Renders a realistic winter scene with:
- Night sky gradient with twinkling stars
- Realistic pine trees with ornaments and snow
- Physically-simulated falling snowflakes with depth blur
- Accumulating snow on the ground
- Vignette and atmospheric effects
"""
mutable struct Snowflake
x::Float64
y::Float64
size::Float64
speed::Float64
drift::Float64
depth::Float64
end
mutable struct Deer
x::Float64
y::Float64
scale::Float64
speed::Float64
leg_phase::Float64
facing_right::Bool
end
mutable struct Skier
x::Float64
speed::Float64
pole_phase::Float64
facing_right::Bool
end
function snow_hd(; width=1024, height=768, fps=24)
# Initialize snowflakes
n_flakes = 400
flakes = [Snowflake(
rand() * width,
rand() * height,
0.5 + rand() * 2.5,
0.8 + rand() * 2.5,
randn() * 0.3,
rand()
) for _ in 1:n_flakes]
# Ground snow accumulation
ground_snow = fill(Float64(height - 25), width)
# Tree definitions: (center_x, base_y, height, width)
# Smaller trees positioned higher for proper perspective
trees = [
(x=width÷2, y=height-30, h=220, w=140), # Main center tree (big, closest)
(x=width÷4, y=height-55, h=170, w=110), # Left tree (mid-distance)
(x=3width÷4, y=height-55, h=170, w=110), # Right tree (mid-distance)
(x=width÷8, y=height-85, h=120, w=75), # Far left (more distant)
(x=7width÷8, y=height-85, h=120, w=75), # Far right (more distant)
]
# Light colors for tree decorations
light_colors = [
RGB{Float64}(1.0, 0.95, 0.4), # Warm yellow
RGB{Float64}(1.0, 0.25, 0.2), # Red
RGB{Float64}(0.3, 0.5, 1.0), # Blue
RGB{Float64}(1.0, 1.0, 1.0), # White
RGB{Float64}(0.2, 1.0, 0.4), # Green
]
# Pre-generate star positions (fixed, no flicker)
n_stars = round(Int, width * height * 0.6 * 0.0004) # Same density as before
star_y_max = round(Int, height * 0.6)
stars = [(rand(1:width), rand(1:star_y_max), rand()) for _ in 1:n_stars]
# Pre-generate distant tree positions on hills (x, height_scale, y_offset up the slope)
# Distant trees (nearer foothills, medium) - scattered up to 35px down the slope
distant_trees = [(rand(1:width), 0.6 + rand() * 0.4, rand() * 35) for _ in 1:round(Int, width * 0.06)]
# Initialize pair of deer walking on the snowy foothills
deer = [
Deer(width * 0.3, 0.0, 0.8, 0.3, 0.0, true), # First deer
Deer(width * 0.22, 0.0, 0.75, 0.3, π, true), # Second deer, slightly behind
]
# Initialize skier on the snowy hill
skier = Skier(width * 0.9, 1.2, 0.0, false) # Skiing left across the hill
# Pre-sort trees by y position (back to front)
sorted_tree_indices = sort(collect(enumerate(trees)), by=t->t[2].y)
# Pre-compute hill heights for each x coordinate
hill2_heights = Vector{Float64}(undef, width)
hill3_heights = Vector{Float64}(undef, width)
for x in 1:width
cx = (x - width/2) / (width/2)
valley = abs(cx)^1.5 * sign(cx)^2
peak2_left = exp(-((x - width*0.25)^2) / (width*60)) * height * 0.10
peak2_right = exp(-((x - width*0.75)^2) / (width*60)) * height * 0.10
peak2_mid = exp(-((x - width*0.5)^2) / (width*120)) * height * 0.05
ridge2 = height * 0.48 - valley * height * 0.15 - peak2_left - peak2_right + peak2_mid
hill2_heights[x] = ridge2 + 20 * sin(x * 0.012 + 2) + 12 * sin(x * 0.028) + 6 * sin(x * 0.055 + 1)
ridge3 = height * 0.60 - valley * height * 0.08
hill3_heights[x] = ridge3 + 15 * sin(x * 0.015 + 0.5) + 8 * sin(x * 0.04 + 3) + 4 * sin(x * 0.08)
end
# Pre-render static background (sky gradient, moon, hills)
bg_img = zeros(RGB{Float64}, height, width)
sky_color_top = RGB{Float64}(0.02, 0.03, 0.10)
# Sky gradient
for y in 1:height
t = y / height
t_smooth = t * t * (3 - 2 * t)
r, g, b = 0.02 + 0.09 * t_smooth, 0.03 + 0.12 * t_smooth, 0.10 + 0.20 * t_smooth
@inbounds for x in 1:width
bg_img[y, x] = RGB{Float64}(r, g, b)
end
end
# Moon (static)
moon_x, moon_y = width * 0.82, height * 0.12
moon_radius = 35.0
moon_y_min = max(1, round(Int, moon_y - moon_radius - 60))
moon_y_max = min(height, round(Int, moon_y + moon_radius + 60))
moon_x_min = max(1, round(Int, moon_x - moon_radius - 60))
moon_x_max = min(width, round(Int, moon_x + moon_radius + 60))
for y in moon_y_min:moon_y_max, x in moon_x_min:moon_x_max
dx, dy = x - moon_x, y - moon_y
dist = sqrt(dx^2 + dy^2)
if dist < moon_radius
shade = clamp(0.85 + 0.15 * (-dx - dy) / (moon_radius * 1.4), 0.6, 1.0)
crater1 = exp(-((dx + 8)^2 + (dy + 5)^2) / 80) * 0.15
crater2 = exp(-((dx - 12)^2 + (dy - 3)^2) / 60) * 0.12
crater3 = exp(-((dx + 3)^2 + (dy - 10)^2) / 40) * 0.10
edge = clamp(1 - (dist - moon_radius + 2) / 2, 0, 1)
brightness = shade - crater1 - crater2 - crater3
c = bg_img[y, x]
bg_img[y, x] = RGB{Float64}(
brightness * 0.98 * edge + red(c) * (1-edge),
brightness * 0.96 * edge + green(c) * (1-edge),
brightness * 0.88 * edge + blue(c) * (1-edge)
)
elseif dist < moon_radius + 60
glow = max(0, 1 - (dist - moon_radius) / 60)^2.5 * 0.4
c = bg_img[y, x]
bg_img[y, x] = RGB{Float64}(
min(1, red(c) + glow * 0.95),
min(1, green(c) + glow * 0.92),
min(1, blue(c) + glow * 0.85)
)
end
end
# Hills (static)
for x in 1:width
hill2_y = hill2_heights[x]
for y in max(1, round(Int, hill2_y)):height
@inbounds if bg_img[y, x] == bg_img[1, 1]
fade = clamp((y - hill2_y) / 45, 0, 1)
bg_img[y, x] = RGB{Float64}(0.10 + 0.07 * fade, 0.13 + 0.09 * fade, 0.22 + 0.08 * fade)
end
end
hill3_y = hill3_heights[x]
for y in max(1, round(Int, hill3_y)):height
@inbounds c = bg_img[y, x]
if blue(c) < 0.45
fade = clamp((y - hill3_y) / 35, 0, 1)
bg_img[y, x] = RGB{Float64}(0.45 + 0.35 * fade, 0.50 + 0.32 * fade, 0.58 + 0.26 * fade)
end
end
end
# Distant trees on background (static)
for (tx, scale, y_off) in distant_trees
cx_val = (tx - width/2) / (width/2)
valley = abs(cx_val)^1.5
ridge3 = height * 0.60 - valley * height * 0.08
base_y = ridge3 + 15 * sin(tx * 0.015 + 0.5) + 8 * sin(tx * 0.04 + 3) + 4 * sin(tx * 0.08) + y_off
tree_h = (18 + 14 * scale) * (1 - y_off * 0.005)
tree_color = RGB{Float64}(0.12 + y_off * 0.002, 0.18 + y_off * 0.002, 0.14 + y_off * 0.002)
tree_w = tree_h * 0.4
for dy in 0:round(Int, tree_h)
y = round(Int, base_y - dy)
1 <= y <= height || continue
progress = dy / tree_h
half_w = round(Int, tree_w * (1 - progress * 0.85))
for ddx in -half_w:half_w
xx = tx + ddx
if 1 <= xx <= width
shade = 0.85 + 0.15 * rand()
bg_img[y, xx] = RGB{Float64}(red(tree_color) * shade, green(tree_color) * shade, blue(tree_color) * shade)
end
end
end
end
# Pre-generate tree light positions for each tree
tree_lights = Dict{Int, Vector{Tuple{Int,Int,RGB{Float64},Float64}}}()
tree_snow = Dict{Int, Vector{Tuple{Int,Int}}}()
for (i, tree) in enumerate(trees)
lights = Tuple{Int,Int,RGB{Float64},Float64}[]
snow_positions = Tuple{Int,Int}[]
cx, base_y, tree_h, tree_w = tree.x, tree.y, tree.h, tree.w
n_lights = round(Int, tree_h * tree_w * 0.003) # Light density
for _ in 1:n_lights
# Random position within tree bounds
rel_y = rand() # 0 = bottom, 1 = top
layer_width = tree_w * (1 - rel_y * 0.7)
lx = cx + round(Int, (rand() - 0.5) * layer_width)
ly = round(Int, base_y - rel_y * tree_h * 0.9)
push!(lights, (lx, ly, rand(light_colors), rand() * 2π))
end
tree_lights[i] = lights
# Pre-generate snow cap positions for each layer
# Snow accumulates on the TOP EDGE of each layer (where branches spread out)
n_layers = 8
for layer in 1:n_layers
# layer_base is where this layer starts (the widest point / shelf)
layer_base = base_y - (layer - 1) * tree_h ÷ (n_layers + 2)
layer_width = tree_w * (n_layers - layer + 3) / (n_layers + 2)
# Snow sits on the shelf at the base of each layer
snow_y = layer_base
# Only add snow near the edges where branches stick out
half_w = round(Int, layer_width / 1.3)
for dx in -half_w:half_w
# More likely to have snow near edges of branches
edge_prob = 0.3 + 0.5 * (abs(dx) / (half_w + 1))
if rand() < edge_prob
push!(snow_positions, (cx + dx, snow_y))
end
end
end
tree_snow[i] = snow_positions
end
function draw_tree!(img, tree, tree_idx, frame)
cx, base_y, tree_h, tree_w = tree.x, tree.y, tree.h, tree.w
h, w = size(img)
# Trunk with bark texture
trunk_w = max(4, tree_w ÷ 7)
trunk_h = tree_h ÷ 4
for dy in 0:trunk_h, dx in -trunk_w÷2:trunk_w÷2
ty, tx = base_y + dy, cx + dx
if 1 <= ty <= h && 1 <= tx <= w
bark = 0.28 + 0.08 * rand()
img[ty, tx] = RGB{Float64}(bark + 0.08, bark - 0.05, bark - 0.12)
end
end
# Layered foliage - more layers for denser trees
n_layers = 8
for layer in 1:n_layers
layer_base = base_y - (layer - 1) * tree_h ÷ (n_layers + 2)
layer_top = base_y - layer * tree_h ÷ n_layers
layer_width = tree_w * (n_layers - layer + 3) / (n_layers + 2)
for y in max(1, layer_top):min(h, layer_base)
progress = (layer_base - y) / max(1, layer_base - layer_top)
# Sharper taper toward top (use power function for pointed top)
taper = (1 - progress * 0.55) ^ (1 + layer * 0.15)
half_w = max(0, Int(round(layer_width * taper)))
for dx in -half_w:half_w
tx = cx + dx
if 1 <= tx <= w
# Dense pine needle texture with more variation
edge_fade = 1 - 0.7 * (abs(dx) / (half_w + 1))^0.8
shade = 0.06 + 0.14 * rand() * edge_fade
green_val = 0.30 + 0.28 * rand() * edge_fade
img[y, tx] = RGB{Float64}(shade, green_val, shade * 0.35)
end
end
end
end
# Draw pre-generated snow caps (fixed positions, no flicker)
for (sx, sy) in tree_snow[tree_idx]
if 1 <= sy <= h && 1 <= sx <= w
for sdy in -1:1
py = sy + sdy
if 1 <= py <= h
brightness = 0.92 + 0.04 * sdy # Slightly vary by row
img[py, sx] = RGB{Float64}(brightness, brightness, brightness + 0.02)
end
end
end
end
# Draw pre-generated lights (bigger, fixed position)
for (lx, ly, color, phase) in tree_lights[tree_idx]
if 1 <= ly <= h && 1 <= lx <= w
twinkle = 0.5 + 0.5 * sin(frame * 0.25 + phase)
# Draw bigger lights (3x3 with glow)
for dy in -2:2, dx in -2:2
px, py = lx + dx, ly + dy
if 1 <= py <= h && 1 <= px <= w
dist = sqrt(dx^2 + dy^2)
if dist <= 2.5
glow = (1 - dist / 3)^0.7 * twinkle
old = img[py, px]
img[py, px] = RGB{Float64}(
min(1.0, red(old) + red(color) * glow),
min(1.0, green(old) + green(color) * glow),
min(1.0, blue(old) + blue(color) * glow)
)
end
end
end
end
end
end
function draw_flake!(img, flake, h, w)
x, y = round(Int, flake.x), round(Int, flake.y)
r = flake.size * (0.4 + 0.6 * flake.depth)
alpha = 0.25 + 0.75 * flake.depth
ri = ceil(Int, r)
r_inv = 1 / r
@inbounds for dy in -ri:ri, dx in -ri:ri
dist_sq = dx*dx + dy*dy
if dist_sq <= r*r
px, py = x + dx, y + dy
if 1 <= py <= h && 1 <= px <= w
dist = sqrt(dist_sq)
brightness = alpha * max(0, 1 - dist * r_inv)^0.6
old = img[py, px]
img[py, px] = RGB{Float64}(
min(1.0, red(old) + brightness * 0.97),
min(1.0, green(old) + brightness * 0.98),
min(1.0, blue(old) + brightness)
)
end
end
end
end
function draw_deer!(img, d::Deer, h, w, hill3_heights)
sc = d.scale
dx = d.facing_right ? 1 : -1
x_int = clamp(round(Int, d.x), 1, w)
base_y = hill3_heights[x_int] + 20
body_color = RGB{Float64}(0.45, 0.32, 0.22)
belly_color = RGB{Float64}(0.55, 0.45, 0.38)
leg_swing = sin(d.leg_phase) * 3 * sc
body_len = round(Int, 28 * sc)
body_h = round(Int, 14 * sc)
leg_len = round(Int, 16 * sc)
neck_len = round(Int, 12 * sc)
head_size = round(Int, 8 * sc)
bx, by = round(Int, d.x), round(Int, base_y)
# Legs
for (lx, swing) in [(bx + round(Int, 8*sc*dx), leg_swing), (bx + round(Int, 5*sc*dx), -leg_swing),
(bx - round(Int, 8*sc*dx), -leg_swing), (bx - round(Int, 11*sc*dx), leg_swing)]
for ly in 0:leg_len
px = lx + round(Int, swing * ly / leg_len)
py = by + ly
if 1 <= py <= h && 1 <= px <= w
@inbounds img[py, px] = body_color
px+1 <= w && (@inbounds img[py, px+1] = body_color)
end
end
end
# Body
for bdy in -body_h÷2:body_h÷2
row_width = round(Int, body_len * sqrt(max(0, 1 - (2*bdy/body_h)^2)) / 2)
col = bdy > 0 ? belly_color : body_color
for bdx in -row_width:row_width
px, py = bx + bdx * dx, by - body_h÷2 + bdy
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = col)
end
end
# Neck
neck_base_x = bx + round(Int, 10*sc*dx)
for ny in 0:neck_len
nx, py = neck_base_x + round(Int, ny * 0.3 * dx), by - body_h÷2 - ny
if 1 <= py <= h
for nw in -1:1
1 <= nx+nw <= w && (@inbounds img[py, nx+nw] = body_color)
end
end
end
# Head
head_x = neck_base_x + round(Int, (neck_len * 0.3 + head_size÷2) * dx)
head_y = by - body_h÷2 - neck_len - head_size÷2
hs2 = head_size÷2
for hdy in -hs2:hs2, hdx in -hs2:hs2
if hdx*hdx + hdy*hdy <= hs2*hs2
px, py = head_x + hdx, head_y + hdy
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = body_color)
end
end
# Ears
for ey in 0:round(Int, 5*sc), eoff in [-3, 3]
px, py = head_x + round(Int, eoff * sc), head_y - hs2 - ey
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = body_color)
end
# Tail
tail_x, tail_y = bx - round(Int, 12*sc*dx), by - body_h÷3
for ty in 0:round(Int, 4*sc)
px, py = tail_x + round(Int, ty * 0.5 * dx), tail_y - ty
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = belly_color)
end
end
function draw_skier!(img, s::Skier, h, w, hill3_heights)
dx = s.facing_right ? 1 : -1
x_int = clamp(round(Int, s.x), 1, w)
base_y = hill3_heights[x_int] + 25
# Colors
jacket_color = RGB{Float64}(0.8, 0.15, 0.1) # Red jacket
pants_color = RGB{Float64}(0.1, 0.1, 0.15) # Dark pants
skin_color = RGB{Float64}(0.9, 0.75, 0.65) # Skin
ski_color = RGB{Float64}(0.2, 0.4, 0.8) # Blue skis
pole_color = RGB{Float64}(0.3, 0.3, 0.35) # Gray poles
bx, by = round(Int, s.x), round(Int, base_y)
# Skis (long and thin)
ski_len = 25
for ski_off in [-3, 3]
for sx in -ski_len÷2:ski_len÷2
px = bx + sx * dx + ski_off
py = by + 2
if 1 <= py <= h && 1 <= px <= w
@inbounds img[py, px] = ski_color
py+1 <= h && (@inbounds img[py+1, px] = ski_color)
end
end
# Ski tips curved up
tip_x = bx + (ski_len÷2 + 2) * dx + ski_off
for tip_y in 0:3
px = tip_x + tip_y * dx
py = by + 1 - tip_y
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = ski_color)
end
end
# Legs (bent skiing position)
leg_h = 12
for leg_off in [-2, 2]
for ly in 0:leg_h
px = bx + leg_off + round(Int, ly * 0.15 * dx)
py = by - ly
if 1 <= py <= h && 1 <= px <= w
@inbounds img[py, px] = pants_color
px+1 <= w && (@inbounds img[py, px+1] = pants_color)
end
end
end
# Body/torso (leaning forward)
torso_h = 14
for ty in 0:torso_h
torso_width = ty < 4 ? 4 : (ty < 10 ? 5 : 4)
lean = round(Int, ty * 0.25 * dx)
for tw in -torso_width÷2:torso_width÷2
px = bx + tw + lean
py = by - leg_h - ty
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = jacket_color)
end
end
# Arms with poles
arm_y = by - leg_h - 10
pole_swing = sin(s.pole_phase) * 4
for (arm_dir, swing) in [(-1, pole_swing), (1, -pole_swing)]
arm_x = bx + arm_dir * 5
# Upper arm
for ay in 0:6
px = arm_x + round(Int, (ay * 0.4 + swing * 0.3) * dx)
py = arm_y + ay
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = jacket_color)
end
# Pole
pole_top_x = arm_x + round(Int, (2 + swing * 0.3) * dx)
pole_top_y = arm_y + 6
for py_off in 0:20
px = pole_top_x + round(Int, py_off * 0.15 * dx)
py = pole_top_y + py_off
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = pole_color)
end
end
# Head
head_y = by - leg_h - torso_h - 5
head_x = bx + round(Int, torso_h * 0.25 * dx)
for hdy in -4:4, hdx in -3:3
if hdx*hdx + hdy*hdy <= 16
px, py = head_x + hdx, head_y + hdy
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = skin_color)
end
end
# Helmet/hat
for hdy in -5:-2, hdx in -4:4
if hdx*hdx + (hdy+3)*(hdy+3) <= 20
px, py = head_x + hdx, head_y + hdy
1 <= py <= h && 1 <= px <= w && (@inbounds img[py, px] = jacket_color)
end
end
end
# Check sixel support
sixel_ok = Sixel.is_sixel_supported()
if !sixel_ok
@warn "Terminal doesn't support Sixel graphics. Try iTerm2, WezTerm, or mlterm."
end
frame = 0
print("\e[2J\e[H") # Clear screen
print("\e[?25l") # Hide cursor
# Pre-allocate frame buffer
img = zeros(RGB{Float64}, height, width)
img_out = zeros(RGB{N0f8}, height, width)
timer = Timer(1/fps)
try
while true
frame += 1
# Copy pre-rendered background
copyto!(img, bg_img)
# Draw twinkling stars (only need to update star pixels)
@inbounds for (sx, sy, phase) in stars
twinkle = 0.4 + 0.6 * abs(sin(frame * 0.15 + phase * 10))
img[sy, sx] = RGB{Float64}(twinkle, twinkle * 0.98, twinkle * 0.9)
end
# Update and draw deer
for d in deer
d.x += d.speed * (d.facing_right ? 1 : -1)
d.leg_phase += 0.08
if d.x > width + 50
d.x = -40
elseif d.x < -50
d.x = width + 40
end
draw_deer!(img, d, height, width, hill3_heights)
end
# Update and draw skier
skier.x += skier.speed * (skier.facing_right ? 1 : -1)
skier.pole_phase += 0.15
if skier.x > width + 50
skier.x = -40
skier.facing_right = false
elseif skier.x < -50
skier.x = width + 40
skier.facing_right = true
end
draw_skier!(img, skier, height, width, hill3_heights)
# Ground snow
@inbounds for x in 1:width
ground_base = height - 25 + 8 * sin(x * 0.025) + 4 * sin(x * 0.06 + 1)
snow_top = clamp(round(Int, min(ground_snow[x], ground_base)), 1, height)
inv_depth = 1 / max(1, height - snow_top)
for y in snow_top:height
depth = (y - snow_top) * inv_depth
noise = 0.02 * sin(x * 0.15 + y * 0.1)
img[y, x] = RGB{Float64}(0.88 - 0.10 * depth + noise, 0.90 - 0.08 * depth + noise, 0.96 - 0.04 * depth + noise * 0.5)
end
end
# Trees
for (i, tree) in sorted_tree_indices
draw_tree!(img, tree, i, frame)
end
# Update snowflakes in-place
@inbounds for i in eachindex(flakes)
flake = flakes[i]
wind = 0.4 * sin(frame * 0.04 + flake.y * 0.015)
new_x = mod(flake.x + flake.drift + wind - 1, width) + 1
new_y = flake.y + flake.speed * (0.7 + 0.3 * flake.depth)
gx = clamp(round(Int, new_x), 1, width)
if new_y >= ground_snow[gx] - 1
if rand() < 0.05
ground_snow[gx] = max(height * 0.55, ground_snow[gx] - 0.15)
for ddx in -3:3
nx = clamp(gx + ddx, 1, width)
ground_snow[nx] = 0.7 * ground_snow[nx] + 0.3 * ground_snow[gx]
end
end
flakes[i] = Snowflake(rand()*width, -rand()*30, 0.5+rand()*2.5, 0.8+rand()*2.5, randn()*0.3, rand())
else
flakes[i] = Snowflake(new_x, new_y, flake.size, flake.speed, flake.drift, flake.depth)
end
end
# Sort and draw snowflakes
sort!(flakes, by=f->f.depth)
@inbounds for flake in flakes
1 <= flake.y <= height && draw_flake!(img, flake, height, width)
end
# Vignette
@inbounds for y in 1:height, x in 1:width
dx_v, dy_v = (x - width/2) / (width/2), (y - height/2) / (height/2)
dist_sq = dx_v*dx_v + dy_v*dy_v
v = clamp(1 - 0.30 * dist_sq * (1 + 0.3 * dist_sq) + ((x & 3) + 4 * (y & 3)) / 16.0 * 0.015 - 0.0075, 0.0, 1.0)
c = img[y, x]
img[y, x] = RGB{Float64}(red(c)*v, green(c)*v, blue(c)*v)
end
# Convert to output format
@inbounds for I in CartesianIndices(img)
y, x = Tuple(I)
c = img[I]
d = ((x & 3) + 4 * (y & 3)) / 16.0 / 255 - 0.5/255
img_out[I] = RGB{N0f8}(clamp(red(c)+d,0,1), clamp(green(c)+d,0,1), clamp(blue(c)+d,0,1))
end
print("\e[H")
sixel_encode(img_out; transpose=false)
println("\n Frame $frame | Ctrl+C to exit")
try wait(timer) catch; end
end
catch e
isa(e, InterruptException) || rethrow(e)
finally
close(timer)
print("\e[?25h") # Show cursor
end
end
# Run it!
snow_hd()