Skip to content

Instantly share code, notes, and snippets.

@tompng
Last active February 15, 2023 06:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tompng/cd9cae2abcfbe9d9684725792872262d to your computer and use it in GitHub Desktop.
Save tompng/cd9cae2abcfbe9d9684725792872262d to your computer and use it in GitHub Desktop.
require 'chunky_png' # gem install chunky_png
file = ARGV[0]
level = ARGV[1]&.to_i || 5
NPIXEL = 16
size = NPIXEL * 2 ** level
p size
def scale(img, size)
w = img.width
h = img.height
at = -> ix, iy {
ix = w - 1 if ix > w-1
iy = h - 1 if ix > h-1
col = img[iy.floor, ix.floor]
(((col >> 8) & 0xff) + ((col >> 16) & 0xff) + ((col >> 24) & 0xff)) / 3.0 / 0xff
}
size.times.map do |iy|
size.times.map do |ix|
x = (w - 1) * ix.fdiv(size - 1)
y = (h - 1) * iy.fdiv(size - 1)
x1 = x.floor
y1 = y.floor
x2 = [x + 1, w - 1].min
y2 = [y + 1, h - 1].min
x -= x1
y -= y1
(at[x1, y1] * (1 - x) + x * at[x2, y1]) * (1 - y) + y * (at[x1, y2] * (1 - x) + x * at[x2, y2]) < 0.5
# (ix-size/2)**2+(iy-size/2)**2<(size/2-NPIXEL)**2
end.reverse
end
end
pixels = scale(ChunkyPNG::Image.from_file(file), size)
def range_stat(img, x, y, size)
all = true
none = true
size.times do |ix|
size.times do |iy|
if img[y + iy][x + ix]
none = false
else
all = false
end
end
end
all ? :all : none ? :none : :both
end
def compress(img, x, y)
pixels = (NPIXEL+1).times.map do |dy|
(NPIXEL+1).times.map do |dx|
ix = x + dx
iy = y + dy
line = img[iy == img.size ? iy - 1 : iy]
line[ix == line.size ? ix - 1 : ix]
end
end
f = ->(x,y,dx,dy){
a = pixels[y][x]
b = pixels[y+NPIXEL*dy][x+NPIXEL*dx]
next nil if a==b
(1...NPIXEL).max_by do |l|
(0..NPIXEL).count{|i|pixels[y+dy*i][x+dx*i] == (i<l ? a : b)}
end
}
aa = pixels[0][0]
ab = pixels[0][NPIXEL]
ba = pixels[NPIXEL][0]
bb = pixels[NPIXEL][NPIXEL]
if aa==bb&&ba==ab&&aa!=ab
aa = ab = ba = bb = pixels.flatten.count(true) > (NPIXEL+1)**2/2.0
end
if aa==bb&&ab==ba&&aa==ab
aa ? NPIXEL * 4 - 1 : 1
else
a=f[0,0,1,0]
b=f[NPIXEL,0,0,1]
c=f[0,NPIXEL,1,0]
d=f[0,0,0,1]
p, q = [a, b && b + NPIXEL, c && (NPIXEL-c) + 2 * NPIXEL, d && (NPIXEL-d) + 3 * NPIXEL].compact
p, q = q, p if aa
p * NPIXEL * 4 + q
end
end
NONE = compress([[false]*NPIXEL]*NPIXEL, 0, 0)
ALL = compress([[true]*NPIXEL]*NPIXEL, 0, 0)
def recursive_build(img, x, y, size)
children = []
return compress(img, x, y) if size == NPIXEL
sub_size = size / 2
all = true
none = true
4.times do |i|
dx, dy = i.divmod 2
res = recursive_build img, x + dx * sub_size, y + dy * sub_size, sub_size
children << res
all = false unless res == :all || res == ALL
none = false unless res == :none || res == NONE
end
if none
:none
elsif all
:all
else
children
end
end
def encode(tree, output, index)
children = tree
case children
when Integer
output[index] = -children
return
when :all
output[index] = -ALL
when :none
output[index] = -NONE
else
child_index = output.size
children.each { output << nil }
output[index] = child_index
children.each_with_index do |sub_tree, i|
encode(sub_tree, output, child_index + i)
end
end
end
tree = recursive_build pixels, 0, 0, size
output = [0]
encode(tree, output, 0)
p output.size
puts "A=["+output.join(',')+"]"
__END__
https://www.desmos.com/calculator/ewhtdr8fqs
https://www.desmos.com/calculator/saeocfcmfd
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment