49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
# File 'lib/tessel/quantize.rb', line 49
def quantize(image, colors: 256, palette: nil, dither: :none, serpentine: true)
raise TypeError, "image must be a Tessel::Image" unless image.is_a?(Image)
raise TypeError, "palette must be an array" if palette && !palette.is_a?(Array)
raise ArgumentError, "palette must not be empty" if palette && palette.empty?
raise ArgumentError, "unknown dither: #{dither}" unless i[none floyd_steinberg ordered].include?(dither)
palette ||= palette_for(image, colors: colors)
palette = palette.map do |color|
raise TypeError, "palette colors must be RGB triples" unless color.is_a?(Array) && color.length == 3 && color.all? { |v| v.is_a?(Integer) && v.between?(0, 255) }
color
end
raise ArgumentError, "palette must contain at most 256 colors" if palette.length > 256
nearest = {}
indices = "\0".b * (image.width * image.height)
errors = Array.new(image.width) { [0, 0, 0] }
next_errors = Array.new(image.width) { [0, 0, 0] }
bytes = image.bytes
image.height.times do |y|
reverse = dither == :floyd_steinberg && serpentine && y.odd?
scan = reverse ? (image.width - 1).downto(0) : (0...image.width)
scan.each do |x|
index = y * image.width + x
offset = index * 4
r, g, b, alpha = bytes.getbyte(offset), bytes.getbyte(offset + 1), bytes.getbyte(offset + 2), bytes.getbyte(offset + 3)
if dither == :floyd_steinberg && alpha.positive?
input = [r, g, b].each_with_index.map { |v, c| [[v + errors[x][c].div(16), 0].max, 255].min }
elsif dither == :ordered
threshold = ((MATRIX[y % 8][x % 8] * 2 - 63) * 4).div(16)
input = [r, g, b].map { |v| [[v + threshold, 0].max, 255].min }
else
input = [r, g, b]
end
key = input.pack("C3")
palette_index = nearest[key] ||= closest_index(input, palette)
indices.setbyte(index, palette_index)
if dither == :floyd_steinberg && alpha.positive?
color = palette[palette_index]
direction = reverse ? -1 : 1
distribute(errors, next_errors, x, direction, input, color)
end
if x == (reverse ? 0 : image.width - 1)
errors, next_errors = next_errors, Array.new(image.width) { [0, 0, 0] }
end
end
end
[indices, palette]
end
|