image_util 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +5 -6
- data/CHANGELOG.md +41 -6
- data/README.md +229 -81
- data/Rakefile +5 -0
- data/docs/cli.md +5 -0
- data/docs/samples/background.png +0 -0
- data/docs/samples/bitmap_text.png +0 -0
- data/docs/samples/colors.png +0 -0
- data/docs/samples/constructor.png +0 -0
- data/docs/samples/dither.png +0 -0
- data/docs/samples/draw.png +0 -0
- data/docs/samples/iterator.png +0 -0
- data/docs/samples/paste.png +0 -0
- data/docs/samples/pdither.png +0 -0
- data/docs/samples/pipe.png +0 -0
- data/docs/samples/range.png +0 -0
- data/docs/samples/redimension.png +0 -0
- data/docs/samples/resize.png +0 -0
- data/docs/samples/sixel.png +0 -0
- data/docs/samples/transform.png +0 -0
- data/exe/image_util +7 -0
- data/lib/image_util/benchmarking.rb +25 -0
- data/lib/image_util/bitmap_font/fonts/smfont/charset.txt +1 -0
- data/lib/image_util/bitmap_font/fonts/smfont/font.png +0 -0
- data/lib/image_util/bitmap_font.rb +72 -0
- data/lib/image_util/cli.rb +54 -0
- data/lib/image_util/codec/chunky_png.rb +67 -0
- data/lib/image_util/codec/image_magick.rb +82 -15
- data/lib/image_util/codec/kitty.rb +81 -0
- data/lib/image_util/codec/libpng.rb +2 -10
- data/lib/image_util/codec/libsixel.rb +14 -14
- data/lib/image_util/codec/libturbojpeg.rb +1 -11
- data/lib/image_util/codec/pam.rb +24 -22
- data/lib/image_util/codec/ruby_sixel.rb +12 -13
- data/lib/image_util/codec.rb +5 -1
- data/lib/image_util/color/css_colors.rb +158 -0
- data/lib/image_util/color.rb +67 -14
- data/lib/image_util/extension.rb +24 -0
- data/lib/image_util/filter/_mixin.rb +9 -0
- data/lib/image_util/filter/background.rb +4 -4
- data/lib/image_util/filter/bitmap_text.rb +17 -0
- data/lib/image_util/filter/colors.rb +21 -0
- data/lib/image_util/filter/draw.rb +22 -9
- data/lib/image_util/filter/palette.rb +197 -0
- data/lib/image_util/filter/paste.rb +1 -1
- data/lib/image_util/filter/redimension.rb +83 -0
- data/lib/image_util/filter/resize.rb +1 -1
- data/lib/image_util/filter/transform.rb +48 -0
- data/lib/image_util/filter.rb +5 -1
- data/lib/image_util/generator/bitmap_text.rb +38 -0
- data/lib/image_util/generator/example/rose.png +0 -0
- data/lib/image_util/generator/example.rb +9 -0
- data/lib/image_util/generator.rb +8 -0
- data/lib/image_util/image/buffer.rb +11 -11
- data/lib/image_util/image.rb +54 -26
- data/lib/image_util/magic.rb +8 -6
- data/lib/image_util/statistic/{color.rb → colors.rb} +2 -2
- data/lib/image_util/statistic.rb +1 -1
- data/lib/image_util/terminal.rb +61 -0
- data/lib/image_util/version.rb +1 -1
- data/lib/image_util/view/interpolated.rb +1 -1
- data/lib/image_util.rb +6 -0
- metadata +82 -4
- data/lib/image_util/filter/dither.rb +0 -96
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ImageUtil
|
4
|
+
module Filter
|
5
|
+
module Redimension
|
6
|
+
extend ImageUtil::Filter::Mixin
|
7
|
+
|
8
|
+
def redimension!(*new_dimensions)
|
9
|
+
if fast_redimension?(new_dimensions)
|
10
|
+
begin
|
11
|
+
resize_buffer!(new_dimensions)
|
12
|
+
return self
|
13
|
+
rescue StandardError
|
14
|
+
# fall back to generic implementation
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
out = Image.new(*new_dimensions, color_bits: color_bits, channels: channels)
|
19
|
+
|
20
|
+
copy_counts = new_dimensions.map.with_index do |dim, idx|
|
21
|
+
[dim, dimensions[idx] || 1].min
|
22
|
+
end
|
23
|
+
|
24
|
+
ranges = copy_counts[1..] || []
|
25
|
+
each_coordinates(ranges) do |coords|
|
26
|
+
src_buf = row_buffer(coords)
|
27
|
+
out.buffer.copy_1d(src_buf, 0, *coords)
|
28
|
+
end
|
29
|
+
|
30
|
+
initialize_from_buffer(out.buffer)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
define_immutable_version :redimension
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def fast_redimension?(new_dimensions)
|
39
|
+
io = buffer.io_buffer
|
40
|
+
return false unless io.respond_to?(:resize)
|
41
|
+
return false if io.external? || io.locked?
|
42
|
+
|
43
|
+
dims = dimensions
|
44
|
+
|
45
|
+
min_len = [dims.length, new_dimensions.length].min
|
46
|
+
idx = 0
|
47
|
+
idx += 1 while idx < min_len && dims[idx] == new_dimensions[idx]
|
48
|
+
|
49
|
+
return false if idx == dims.length && idx == new_dimensions.length
|
50
|
+
return false unless (dims[(idx + 1)..] || []).all? { |d| d == 1 }
|
51
|
+
return false unless (dims[new_dimensions.length..] || []).all? { |d| d == 1 }
|
52
|
+
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def resize_buffer!(new_dimensions)
|
57
|
+
new_size = new_dimensions.reduce(1, :*)
|
58
|
+
new_size *= channels
|
59
|
+
new_size *= color_bits / 8
|
60
|
+
buffer.io_buffer.resize(new_size)
|
61
|
+
initialize_from_buffer(Image::Buffer.new(new_dimensions, color_bits, channels, buffer.io_buffer))
|
62
|
+
end
|
63
|
+
|
64
|
+
def each_coordinates(ranges, prefix = [], &block)
|
65
|
+
if ranges.empty?
|
66
|
+
yield prefix
|
67
|
+
else
|
68
|
+
ranges.first.times do |i|
|
69
|
+
each_coordinates(ranges[1..], prefix + [i], &block)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def row_buffer(coords)
|
75
|
+
buf = buffer
|
76
|
+
coords_src = coords[0, dimensions.length - 1]
|
77
|
+
coords_src += [0] * (dimensions.length - 1 - coords_src.length)
|
78
|
+
coords_src.reverse_each { |c| buf = buf.last_dimension(c) }
|
79
|
+
buf
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -10,7 +10,7 @@ module ImageUtil
|
|
10
10
|
new_dim == 1 ? 0.0 : (old_dim - 1).to_f / (new_dim - 1)
|
11
11
|
end
|
12
12
|
|
13
|
-
Image.new(*new_dimensions, color_bits: color_bits,
|
13
|
+
Image.new(*new_dimensions, color_bits: color_bits, channels: channels) do |loc|
|
14
14
|
src_loc = loc.zip(factors).map { |coord, factor| coord * factor }
|
15
15
|
src[*src_loc]
|
16
16
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ImageUtil
|
4
|
+
module Filter
|
5
|
+
module Transform
|
6
|
+
extend ImageUtil::Filter::Mixin
|
7
|
+
|
8
|
+
def flip!(axis = :x)
|
9
|
+
axis = Filter::Mixin.axis_to_number(axis)
|
10
|
+
dims = dimensions
|
11
|
+
out = Image.new(*dims, color_bits: color_bits, channels: channels)
|
12
|
+
each_pixel_location do |loc|
|
13
|
+
new_loc = loc.dup
|
14
|
+
new_loc[axis] = dims[axis] - 1 - loc[axis]
|
15
|
+
out[*new_loc] = self[*loc]
|
16
|
+
end
|
17
|
+
initialize_from_buffer(out.buffer)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def rotate!(angle, axes: %i[x y])
|
22
|
+
axes = axes.map { |a| Filter::Mixin.axis_to_number(a) }
|
23
|
+
turns = (angle.to_f / 90).round % 4
|
24
|
+
turns += 4 if turns.negative?
|
25
|
+
turns.times { rotate90_once!(*axes) }
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
define_immutable_version :flip, :rotate
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def rotate90_once!(axis1 = 0, axis2 = 1)
|
34
|
+
dims = dimensions
|
35
|
+
new_dims = dims.dup
|
36
|
+
new_dims[axis1], new_dims[axis2] = dims[axis2], dims[axis1] # rubocop:disable Style/ParallelAssignment
|
37
|
+
out = Image.new(*new_dims, color_bits: color_bits, channels: channels)
|
38
|
+
each_pixel_location do |loc|
|
39
|
+
new_loc = loc.dup
|
40
|
+
new_loc[axis1] = dims[axis2] - 1 - loc[axis2]
|
41
|
+
new_loc[axis2] = loc[axis1]
|
42
|
+
out[*new_loc] = self[*loc]
|
43
|
+
end
|
44
|
+
initialize_from_buffer(out.buffer)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/image_util/filter.rb
CHANGED
@@ -2,11 +2,15 @@
|
|
2
2
|
|
3
3
|
module ImageUtil
|
4
4
|
module Filter
|
5
|
-
autoload :
|
5
|
+
autoload :Palette, "image_util/filter/palette"
|
6
6
|
autoload :Background, "image_util/filter/background"
|
7
7
|
autoload :Paste, "image_util/filter/paste"
|
8
8
|
autoload :Draw, "image_util/filter/draw"
|
9
9
|
autoload :Resize, "image_util/filter/resize"
|
10
|
+
autoload :Transform, "image_util/filter/transform"
|
11
|
+
autoload :Redimension, "image_util/filter/redimension"
|
12
|
+
autoload :Colors, "image_util/filter/colors"
|
13
|
+
autoload :BitmapText, "image_util/filter/bitmap_text"
|
10
14
|
|
11
15
|
autoload :Mixin, "image_util/filter/_mixin"
|
12
16
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ImageUtil
|
4
|
+
module Generator
|
5
|
+
module BitmapText
|
6
|
+
def bitmap_text(text, font: BitmapFont.default_font, color: nil, align: :left)
|
7
|
+
fnt = BitmapFont.cached_load(font)
|
8
|
+
lines = text.split("\n")
|
9
|
+
|
10
|
+
rendered = lines.map { |line| fnt.render_line_of_text(line) }
|
11
|
+
|
12
|
+
width = rendered.map(&:width).max || 0
|
13
|
+
height = rendered.map(&:height).first.to_i * rendered.length
|
14
|
+
height += rendered.length - 1 if rendered.length > 1
|
15
|
+
|
16
|
+
out = Image.new(width, height)
|
17
|
+
y = 0
|
18
|
+
rendered.each do |img|
|
19
|
+
x = case align
|
20
|
+
when :left
|
21
|
+
0
|
22
|
+
when :center
|
23
|
+
(width - img.width) / 2
|
24
|
+
when :right
|
25
|
+
width - img.width
|
26
|
+
else
|
27
|
+
raise ArgumentError, "invalid alignment #{align.inspect}"
|
28
|
+
end
|
29
|
+
out.paste!(img, x, y)
|
30
|
+
y += img.height + 1
|
31
|
+
end
|
32
|
+
|
33
|
+
out *= color if color
|
34
|
+
out
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
Binary file
|
@@ -6,7 +6,7 @@ Warning[:experimental] = false
|
|
6
6
|
module ImageUtil
|
7
7
|
class Image
|
8
8
|
class Buffer
|
9
|
-
def initialize(dimensions, color_bits,
|
9
|
+
def initialize(dimensions, color_bits, channels, buffer = nil)
|
10
10
|
@color_type = case color_bits
|
11
11
|
when 8
|
12
12
|
:U8
|
@@ -21,13 +21,15 @@ module ImageUtil
|
|
21
21
|
@dimensions = dimensions.freeze
|
22
22
|
@color_bits = color_bits
|
23
23
|
@color_bytes = color_bits / 8
|
24
|
-
@
|
24
|
+
@channels = channels
|
25
25
|
|
26
26
|
@buffer_size = @dimensions.reduce(&:*)
|
27
|
-
@buffer_size *= @
|
27
|
+
@buffer_size *= @channels
|
28
28
|
@buffer_size *= @color_bytes
|
29
29
|
|
30
|
-
@
|
30
|
+
@pixel_bytes = @channels * @color_bytes
|
31
|
+
|
32
|
+
@io_buffer_types = ([@color_type]*@channels).freeze
|
31
33
|
|
32
34
|
@buffer = buffer || IO::Buffer.new(@buffer_size)
|
33
35
|
|
@@ -36,7 +38,7 @@ module ImageUtil
|
|
36
38
|
freeze
|
37
39
|
end
|
38
40
|
|
39
|
-
attr_reader :dimensions, :color_bits, :color_bytes, :
|
41
|
+
attr_reader :dimensions, :color_bits, :color_bytes, :channels, :pixel_bytes
|
40
42
|
|
41
43
|
def offset_of(*location)
|
42
44
|
location.length == @dimensions.length or raise ArgumentError, "wrong number of dimensions"
|
@@ -50,8 +52,6 @@ module ImageUtil
|
|
50
52
|
offset * pixel_bytes
|
51
53
|
end
|
52
54
|
|
53
|
-
def pixel_bytes = @color_length * @color_bytes
|
54
|
-
|
55
55
|
def initialize_copy(_other)
|
56
56
|
@buffer = @buffer.dup
|
57
57
|
end
|
@@ -62,7 +62,7 @@ module ImageUtil
|
|
62
62
|
|
63
63
|
def get_index(index)
|
64
64
|
value = @buffer.get_values(@io_buffer_types, index)
|
65
|
-
Color.from_buffer(value, @color_bits)
|
65
|
+
Color.from_buffer(value, @color_bits).freeze
|
66
66
|
end
|
67
67
|
|
68
68
|
def set(location, value)
|
@@ -70,7 +70,7 @@ module ImageUtil
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def set_index(index, value)
|
73
|
-
value = Color.
|
73
|
+
value = Color.from_any_to_buffer(value, @color_bits, @channels)
|
74
74
|
@buffer.set_values(@io_buffer_types, index, value)
|
75
75
|
end
|
76
76
|
|
@@ -82,7 +82,7 @@ module ImageUtil
|
|
82
82
|
Buffer.new(
|
83
83
|
dimensions_without_last,
|
84
84
|
@color_bits,
|
85
|
-
@
|
85
|
+
@channels,
|
86
86
|
@buffer.slice(o0, o1 - o0)
|
87
87
|
)
|
88
88
|
end
|
@@ -126,7 +126,7 @@ module ImageUtil
|
|
126
126
|
}.freeze
|
127
127
|
|
128
128
|
OPT_GET_INDEX = {
|
129
|
-
8 => ->(index) { Color.new(*@buffer.get_values(@io_buffer_types, index)) }
|
129
|
+
8 => ->(index) { Color.new(*@buffer.get_values(@io_buffer_types, index)).freeze }
|
130
130
|
}.freeze
|
131
131
|
end
|
132
132
|
end
|
data/lib/image_util/image.rb
CHANGED
@@ -3,16 +3,15 @@
|
|
3
3
|
module ImageUtil
|
4
4
|
class Image
|
5
5
|
autoload :Buffer, "image_util/image/buffer"
|
6
|
-
autoload :PixelView, "image_util/image/pixel_view"
|
7
6
|
|
8
7
|
Util.irb_fixup
|
9
8
|
|
10
9
|
ALL = nil..nil
|
11
10
|
|
12
|
-
def initialize(*dimensions, color_bits: 8,
|
13
|
-
@buf = Buffer.new(dimensions, color_bits,
|
11
|
+
def initialize(*dimensions, color_bits: 8, channels: 4, &block)
|
12
|
+
@buf = Buffer.new(dimensions, color_bits, channels)
|
14
13
|
|
15
|
-
set_each_pixel_by_location(&block) if block_given?
|
14
|
+
set_each_pixel_by_location!(&block) if block_given?
|
16
15
|
end
|
17
16
|
|
18
17
|
def initialize_from_buffer(buffer)
|
@@ -66,10 +65,10 @@ module ImageUtil
|
|
66
65
|
def height = dimensions[1]
|
67
66
|
def length = dimensions[2]
|
68
67
|
def color_bits = @buf.color_bits
|
69
|
-
def
|
68
|
+
def channels = @buf.channels
|
70
69
|
def pixel_bytes = @buf.pixel_bytes
|
71
70
|
|
72
|
-
def location_expand(location)
|
71
|
+
def location_expand(location = full_image_location)
|
73
72
|
counts = []
|
74
73
|
|
75
74
|
location = location.reverse.map.with_index do |i,idx|
|
@@ -109,10 +108,10 @@ module ImageUtil
|
|
109
108
|
new_dimensions, locations = location_expand(location)
|
110
109
|
new_image = Image.new(*new_dimensions,
|
111
110
|
color_bits: color_bits,
|
112
|
-
|
111
|
+
channels: channels)
|
113
112
|
|
114
113
|
locations.each_with_index do |i, idx|
|
115
|
-
new_image.buffer.set_index(idx *
|
114
|
+
new_image.buffer.set_index(idx * pixel_bytes, @buf.get(i))
|
116
115
|
end
|
117
116
|
|
118
117
|
new_image
|
@@ -136,9 +135,11 @@ module ImageUtil
|
|
136
135
|
@buf.set(location, value)
|
137
136
|
end
|
138
137
|
else
|
139
|
-
|
140
|
-
|
141
|
-
|
138
|
+
sizes, locations = location_expand(location)
|
139
|
+
if value.is_a?(Image)
|
140
|
+
paste!(value.resize(*sizes), *locations.first)
|
141
|
+
else
|
142
|
+
locations.each { |loc| self[*loc] = value }
|
142
143
|
end
|
143
144
|
end
|
144
145
|
end
|
@@ -167,28 +168,39 @@ module ImageUtil
|
|
167
168
|
end
|
168
169
|
|
169
170
|
include Enumerable
|
170
|
-
include Filter::
|
171
|
+
include Filter::Palette
|
171
172
|
include Filter::Background
|
172
173
|
include Filter::Paste
|
173
174
|
include Filter::Draw
|
174
175
|
include Filter::Resize
|
175
|
-
include
|
176
|
+
include Filter::Transform
|
177
|
+
include Filter::Redimension
|
178
|
+
include Filter::Colors
|
179
|
+
include Filter::BitmapText
|
180
|
+
include Statistic::Colors
|
181
|
+
extend Generator::BitmapText
|
182
|
+
extend Generator::Example
|
176
183
|
|
177
184
|
def length = dimensions.last
|
178
185
|
|
179
|
-
def to_pam
|
180
|
-
Codec.encode(:pam, self
|
186
|
+
def to_pam
|
187
|
+
Codec.encode(:pam, self)
|
181
188
|
end
|
182
189
|
|
183
190
|
def to_string(format, codec: nil, **kwargs)
|
184
191
|
Codec.encode(format, self, codec: codec, **kwargs)
|
185
192
|
end
|
186
193
|
|
187
|
-
def to_file(path_or_io, format, codec: nil, **kwargs)
|
194
|
+
def to_file(path_or_io, format = nil, codec: nil, **kwargs)
|
188
195
|
if path_or_io.respond_to?(:write)
|
196
|
+
raise ArgumentError, "format required" unless format
|
197
|
+
|
189
198
|
path_or_io.binmode if path_or_io.respond_to?(:binmode)
|
190
199
|
Codec.encode_io(format, self, path_or_io, codec: codec, **kwargs)
|
191
200
|
else
|
201
|
+
format ||= Extension.detect(path_or_io)
|
202
|
+
raise ArgumentError, "could not detect format" unless format
|
203
|
+
|
192
204
|
File.open(path_or_io, "wb") do |io|
|
193
205
|
Codec.encode_io(format, self, io, codec: codec, **kwargs)
|
194
206
|
end
|
@@ -199,12 +211,14 @@ module ImageUtil
|
|
199
211
|
Codec.encode(:sixel, self)
|
200
212
|
end
|
201
213
|
|
202
|
-
alias inspect to_sixel
|
203
|
-
|
204
214
|
def pretty_print(p)
|
205
|
-
|
206
|
-
|
207
|
-
|
215
|
+
if (image = Terminal.output_image($stdin, $stdout, self))
|
216
|
+
p.flush
|
217
|
+
p.output << image
|
218
|
+
p.text("", 0)
|
219
|
+
else
|
220
|
+
super
|
221
|
+
end
|
208
222
|
end
|
209
223
|
|
210
224
|
def pixel_count(locations) = location_expand(locations).first.reduce(:*)
|
@@ -221,12 +235,26 @@ module ImageUtil
|
|
221
235
|
end
|
222
236
|
end
|
223
237
|
|
224
|
-
def set_each_pixel_by_location(locations = full_image_location)
|
225
|
-
return enum_for(:set_each_pixel_by_location) { pixel_count(locations) } unless block_given?
|
238
|
+
def set_each_pixel_by_location!(locations = full_image_location)
|
239
|
+
return enum_for(:set_each_pixel_by_location!) { pixel_count(locations) } unless block_given?
|
226
240
|
|
227
|
-
|
228
|
-
|
229
|
-
|
241
|
+
if locations == full_image_location
|
242
|
+
# Optimized path
|
243
|
+
pixels, locations = location_expand
|
244
|
+
|
245
|
+
iter = 0
|
246
|
+
pixels = pixels.reduce(:*)
|
247
|
+
|
248
|
+
while iter < pixels
|
249
|
+
value = yield locations[iter]
|
250
|
+
buffer.set_index(iter * pixel_bytes, value) if value
|
251
|
+
iter += 1
|
252
|
+
end
|
253
|
+
else
|
254
|
+
each_pixel_location(locations) do |location|
|
255
|
+
value = yield location
|
256
|
+
self[*location] = value if value
|
257
|
+
end
|
230
258
|
end
|
231
259
|
end
|
232
260
|
|
data/lib/image_util/magic.rb
CHANGED
@@ -7,7 +7,8 @@ module ImageUtil
|
|
7
7
|
MAGIC_NUMBERS = {
|
8
8
|
pam: "P7\n".b,
|
9
9
|
png: "\x89PNG\r\n\x1a\n".b,
|
10
|
-
jpeg: "\xFF\xD8".b
|
10
|
+
jpeg: "\xFF\xD8".b,
|
11
|
+
gif: "GIF8".b
|
11
12
|
}.freeze
|
12
13
|
|
13
14
|
BYTES_NEEDED = MAGIC_NUMBERS.values.map(&:bytesize).max
|
@@ -19,16 +20,18 @@ module ImageUtil
|
|
19
20
|
def detect(data)
|
20
21
|
return nil unless data
|
21
22
|
|
23
|
+
if data.start_with?(MAGIC_NUMBERS[:png]) && data.byteslice(0, 256).include?("acTL")
|
24
|
+
return :apng
|
25
|
+
end
|
26
|
+
|
22
27
|
MAGIC_NUMBERS.each do |fmt, magic|
|
23
28
|
return fmt if data.start_with?(magic)
|
24
|
-
crlf_magic = magic.gsub("\n", "\r\n")
|
25
|
-
return fmt if crlf_magic != magic && data.start_with?(crlf_magic)
|
26
29
|
end
|
30
|
+
|
27
31
|
nil
|
28
32
|
end
|
29
33
|
|
30
|
-
def detect_io(io)
|
31
|
-
|
34
|
+
def detect_io(io)
|
32
35
|
pos = io.pos
|
33
36
|
data = io.read(BYTES_NEEDED)
|
34
37
|
io.seek(pos)
|
@@ -39,7 +42,6 @@ module ImageUtil
|
|
39
42
|
rest = io.read
|
40
43
|
new_io = StringIO.new((data || "") + (rest || ""))
|
41
44
|
[fmt, new_io]
|
42
|
-
|
43
45
|
end
|
44
46
|
end
|
45
47
|
end
|
data/lib/image_util/statistic.rb
CHANGED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "io/console"
|
4
|
+
|
5
|
+
module ImageUtil
|
6
|
+
module Terminal
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def detect_support(termin = $stdin, termout = $stdout)
|
10
|
+
return [] if !termin.tty? || !termout.tty?
|
11
|
+
|
12
|
+
supported = termout.instance_variable_get(:@imageutil_support_cache)
|
13
|
+
return supported if supported
|
14
|
+
|
15
|
+
supported = [:tty]
|
16
|
+
|
17
|
+
# Send kitty query
|
18
|
+
query_terminal(termin, termout, "\e_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\e\\\e[c".b) do |resp|
|
19
|
+
resp.start_with?("\e_G".b) && resp.include?("OK".b)
|
20
|
+
end and supported << :kitty
|
21
|
+
|
22
|
+
# Send sixel query
|
23
|
+
query_terminal(termin, termout, "\e[0c".b) do |resp|
|
24
|
+
resp.include?(";4".b)
|
25
|
+
end and supported << :sixel
|
26
|
+
|
27
|
+
termout.instance_variable_set(:@imageutil_support_cache, supported)
|
28
|
+
end
|
29
|
+
|
30
|
+
def query_terminal(termin, termout, query, timeout = 0.2)
|
31
|
+
resp = ""
|
32
|
+
termin.raw do
|
33
|
+
termout.write query
|
34
|
+
termout.flush
|
35
|
+
t0 = Time.now
|
36
|
+
loop do
|
37
|
+
begin
|
38
|
+
resp += termin.read_nonblock(512)
|
39
|
+
break if resp.start_with?("\e".b)
|
40
|
+
rescue IO::WaitReadable
|
41
|
+
IO.select([termin], nil, nil, timeout)
|
42
|
+
end
|
43
|
+
break if Time.now - t0 > timeout
|
44
|
+
end
|
45
|
+
end
|
46
|
+
yield resp
|
47
|
+
rescue EOFError, Errno::EBADF
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def output_image(termin, termout, image)
|
52
|
+
support = detect_support(termin, termout)
|
53
|
+
|
54
|
+
if support.include? :kitty
|
55
|
+
image.to_string(:kitty)
|
56
|
+
elsif support.include? :sixel
|
57
|
+
image.to_string(:sixel)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/image_util/version.rb
CHANGED
@@ -24,7 +24,7 @@ module ImageUtil
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def [](*location)
|
27
|
-
accum = Array.new(image.
|
27
|
+
accum = Array.new(image.channels, 0.0)
|
28
28
|
generate_subpixel_hash(location).each do |loc, weight|
|
29
29
|
image[*loc].each_with_index do |val, idx|
|
30
30
|
accum[idx] += val * weight
|
data/lib/image_util.rb
CHANGED
@@ -6,12 +6,18 @@ module ImageUtil
|
|
6
6
|
class Error < StandardError; end
|
7
7
|
# Your code goes here...
|
8
8
|
|
9
|
+
autoload :BitmapFont, "image_util/bitmap_font"
|
9
10
|
autoload :Color, "image_util/color"
|
10
11
|
autoload :Image, "image_util/image"
|
11
12
|
autoload :Util, "image_util/util"
|
12
13
|
autoload :Codec, "image_util/codec"
|
13
14
|
autoload :Magic, "image_util/magic"
|
15
|
+
autoload :Extension, "image_util/extension"
|
14
16
|
autoload :Filter, "image_util/filter"
|
17
|
+
autoload :Generator, "image_util/generator"
|
15
18
|
autoload :Statistic, "image_util/statistic"
|
19
|
+
autoload :Terminal, "image_util/terminal"
|
16
20
|
autoload :View, "image_util/view"
|
21
|
+
autoload :CLI, "image_util/cli"
|
22
|
+
autoload :Benchmarking, "image_util/benchmarking"
|
17
23
|
end
|