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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +5 -6
  3. data/CHANGELOG.md +41 -6
  4. data/README.md +229 -81
  5. data/Rakefile +5 -0
  6. data/docs/cli.md +5 -0
  7. data/docs/samples/background.png +0 -0
  8. data/docs/samples/bitmap_text.png +0 -0
  9. data/docs/samples/colors.png +0 -0
  10. data/docs/samples/constructor.png +0 -0
  11. data/docs/samples/dither.png +0 -0
  12. data/docs/samples/draw.png +0 -0
  13. data/docs/samples/iterator.png +0 -0
  14. data/docs/samples/paste.png +0 -0
  15. data/docs/samples/pdither.png +0 -0
  16. data/docs/samples/pipe.png +0 -0
  17. data/docs/samples/range.png +0 -0
  18. data/docs/samples/redimension.png +0 -0
  19. data/docs/samples/resize.png +0 -0
  20. data/docs/samples/sixel.png +0 -0
  21. data/docs/samples/transform.png +0 -0
  22. data/exe/image_util +7 -0
  23. data/lib/image_util/benchmarking.rb +25 -0
  24. data/lib/image_util/bitmap_font/fonts/smfont/charset.txt +1 -0
  25. data/lib/image_util/bitmap_font/fonts/smfont/font.png +0 -0
  26. data/lib/image_util/bitmap_font.rb +72 -0
  27. data/lib/image_util/cli.rb +54 -0
  28. data/lib/image_util/codec/chunky_png.rb +67 -0
  29. data/lib/image_util/codec/image_magick.rb +82 -15
  30. data/lib/image_util/codec/kitty.rb +81 -0
  31. data/lib/image_util/codec/libpng.rb +2 -10
  32. data/lib/image_util/codec/libsixel.rb +14 -14
  33. data/lib/image_util/codec/libturbojpeg.rb +1 -11
  34. data/lib/image_util/codec/pam.rb +24 -22
  35. data/lib/image_util/codec/ruby_sixel.rb +12 -13
  36. data/lib/image_util/codec.rb +5 -1
  37. data/lib/image_util/color/css_colors.rb +158 -0
  38. data/lib/image_util/color.rb +67 -14
  39. data/lib/image_util/extension.rb +24 -0
  40. data/lib/image_util/filter/_mixin.rb +9 -0
  41. data/lib/image_util/filter/background.rb +4 -4
  42. data/lib/image_util/filter/bitmap_text.rb +17 -0
  43. data/lib/image_util/filter/colors.rb +21 -0
  44. data/lib/image_util/filter/draw.rb +22 -9
  45. data/lib/image_util/filter/palette.rb +197 -0
  46. data/lib/image_util/filter/paste.rb +1 -1
  47. data/lib/image_util/filter/redimension.rb +83 -0
  48. data/lib/image_util/filter/resize.rb +1 -1
  49. data/lib/image_util/filter/transform.rb +48 -0
  50. data/lib/image_util/filter.rb +5 -1
  51. data/lib/image_util/generator/bitmap_text.rb +38 -0
  52. data/lib/image_util/generator/example/rose.png +0 -0
  53. data/lib/image_util/generator/example.rb +9 -0
  54. data/lib/image_util/generator.rb +8 -0
  55. data/lib/image_util/image/buffer.rb +11 -11
  56. data/lib/image_util/image.rb +54 -26
  57. data/lib/image_util/magic.rb +8 -6
  58. data/lib/image_util/statistic/{color.rb → colors.rb} +2 -2
  59. data/lib/image_util/statistic.rb +1 -1
  60. data/lib/image_util/terminal.rb +61 -0
  61. data/lib/image_util/version.rb +1 -1
  62. data/lib/image_util/view/interpolated.rb +1 -1
  63. data/lib/image_util.rb +6 -0
  64. metadata +82 -4
  65. 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, color_length: color_length) do |loc|
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
@@ -2,11 +2,15 @@
2
2
 
3
3
  module ImageUtil
4
4
  module Filter
5
- autoload :Dither, "image_util/filter/dither"
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ImageUtil
4
+ module Generator
5
+ module Example
6
+ def example_rose = Image.from_file("#{__dir__}/example/rose.png", :png)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ImageUtil
4
+ module Generator
5
+ autoload :BitmapText, "image_util/generator/bitmap_text"
6
+ autoload :Example, "image_util/generator/example"
7
+ end
8
+ end
@@ -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, color_length, buffer = nil)
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
- @color_length = color_length
24
+ @channels = channels
25
25
 
26
26
  @buffer_size = @dimensions.reduce(&:*)
27
- @buffer_size *= @color_length
27
+ @buffer_size *= @channels
28
28
  @buffer_size *= @color_bytes
29
29
 
30
- @io_buffer_types = ([@color_type]*@color_length).freeze
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, :color_length
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.from(value).to_buffer(@color_bits, @color_length)
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
- @color_length,
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
@@ -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, color_length: 4, &block)
13
- @buf = Buffer.new(dimensions, color_bits, color_length)
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 color_length = @buf.color_length
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
- color_length: color_length)
111
+ channels: channels)
113
112
 
114
113
  locations.each_with_index do |i, idx|
115
- new_image.buffer.set_index(idx * @buf.pixel_bytes, @buf.get(i))
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
- _, locations = location_expand(location)
140
- locations.each do |loc|
141
- self[*loc] = value
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::Dither
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 Statistic::Color
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(fill_to: nil)
180
- Codec.encode(:pam, self, fill_to: fill_to)
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
- p.flush
206
- p.output << to_sixel
207
- p.text("", 0)
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
- each_pixel_location(locations) do |location|
228
- value = yield location
229
- self[*location] = value if value
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
 
@@ -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
@@ -1,8 +1,8 @@
1
1
  module ImageUtil
2
2
  module Statistic
3
- module Color
3
+ module Colors
4
4
  def histogram = each_pixel.to_a.tally
5
- def unique_colors = histogram.keys
5
+ def unique_colors = each_pixel.to_a.uniq
6
6
  def unique_color_count = unique_colors.length
7
7
  end
8
8
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ImageUtil
4
4
  module Statistic
5
- autoload :Color, "image_util/statistic/color"
5
+ autoload :Colors, "image_util/statistic/colors"
6
6
  end
7
7
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ImageUtil
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -24,7 +24,7 @@ module ImageUtil
24
24
  end
25
25
 
26
26
  def [](*location)
27
- accum = Array.new(image.color_length, 0.0)
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