image_util 0.2.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 +3 -0
- data/CHANGELOG.md +31 -0
- data/README.md +125 -24
- 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/iterator.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/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 +76 -18
- 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 +11 -12
- data/lib/image_util/codec.rb +5 -1
- data/lib/image_util/color/css_colors.rb +3 -1
- data/lib/image_util/color.rb +62 -9
- 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 +2 -11
- 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 +49 -23
- 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 +75 -4
- data/lib/image_util/filter/dither.rb +0 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31687d0660e040e340b6ed26f581f95de0746e33f992acf7dfcdb4fa4a88f4cd
|
4
|
+
data.tar.gz: 4fee3793295fb2207a54caa1ed430bdc43ddc8307f96ef597ba0ed777618b8e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c02064cb968adae5e5d5b5328922e77fd542d6ca4322a9259d27b513339ff44ca71e272f8cf01055d864f3dbc3fef702d3187a745e98af09a95f2dc9907470c4
|
7
|
+
data.tar.gz: 97c9852b31aab0d2b71f19579968d856fd87eed7339c3b2ec9e6bd1dcee3b674862821f6d4a033148db8d45b0a8228d890670d43c2f0ba921dc98d7d5ed774de
|
data/AGENTS.md
CHANGED
@@ -10,3 +10,6 @@
|
|
10
10
|
- Don't discuss codec internals or bug fixes in README. Only list supported formats. Document bug fixes in the CHANGELOG, not in README.
|
11
11
|
- Specs target at least 80% coverage as enforced by SimpleCov.
|
12
12
|
- The library aims to remain lightweight and portable.
|
13
|
+
- Remember to always ensure rake tests pass and Rubocop doesn't complain.
|
14
|
+
- If you are an OpenAI Codex, don't upload images! Tell me to use `rebuild-images-in-readme` script.
|
15
|
+
- When adding files into collection directories (like codec/), if something isn't part of a collection (ie. isn't a codec, but is a mixin), ensure to name the file like `_something.rb`. Consult existing directory structure.
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,34 @@
|
|
1
|
+
## [0.3.0] - 2025-07-25
|
2
|
+
- Rename `dither!` to `palette_reduce!`
|
3
|
+
- Rename `#set_each_pixel_by_location` to `#set_each_pixel_by_location!` since it's mutable
|
4
|
+
- Rename `color_length` to a more appropriate name `channels`
|
5
|
+
- Thor based CLI with a `support` command that lists codec support, default
|
6
|
+
format handlers and detected terminal features
|
7
|
+
- Replace `palette_reduce!` implementation with a much faster one
|
8
|
+
- Terminal detection for graphic protocols
|
9
|
+
- Support Kitty graphics protocol
|
10
|
+
- Transform filter with rotate and flip operations
|
11
|
+
- Fallback PNG codec via chunky_png
|
12
|
+
- ImageMagick codec can now read and write `gif` and `apng` including animations
|
13
|
+
- Redimension filter to change image dimensions
|
14
|
+
- Sixel and Kitty codecs accept 1D images
|
15
|
+
- `Pam.encode` no longer accepts `fill_to`; Sixel codecs pad images using the
|
16
|
+
redimension filter
|
17
|
+
- Add `BitmapFont` with a sample hand crafted font, add `bitmap_text` generator.
|
18
|
+
- `Color#*` can now accept another `Color` to multiply channels
|
19
|
+
- Add `Colors` filter with `color_multiply!` and alias `*`
|
20
|
+
- `bitmap_text` accepts multiline strings and supports colorization
|
21
|
+
- `bitmap_text` supports left, center and right alignment
|
22
|
+
- Add `BitmapText` filter for overlaying text onto images
|
23
|
+
- `bitmap_text` filter now always respects an alpha channel when pasting text
|
24
|
+
- Open ImageMagick codec pipes in binary mode for Windows compatibility
|
25
|
+
- Format inference from file extension in `Image#to_file`
|
26
|
+
- ImageMagick codec now reads PAM frames using the Pam codec
|
27
|
+
- Force 8-bit output when decoding through ImageMagick to avoid 1-bit images on Windows
|
28
|
+
- ImageMagick codec checks available formats before advertising APNG support
|
29
|
+
- Add `Example` generator: `Image.example_rose`
|
30
|
+
- Disable APNG on Windows until we find out something else than ImageMagick to support it
|
31
|
+
|
1
32
|
## [0.2.0] - 2025-07-21
|
2
33
|
- Ruby Sixel encoder now sets pixel aspect ratio metadata to display correctly in Windows Terminal
|
3
34
|
- Support for all CSS color names
|
data/README.md
CHANGED
@@ -14,34 +14,59 @@ img = ImageUtil::Image.new(40, 40)
|
|
14
14
|
An optional block receives pixel coordinates and should return something that can be converted to a color. Dimensions of more than two axes are supported.
|
15
15
|
|
16
16
|
```ruby
|
17
|
-
img = ImageUtil::Image.new(
|
17
|
+
img = ImageUtil::Image.new(128, 128) { |x, y| ImageUtil::Color[x, y, 40] }
|
18
18
|
```
|
19
19
|
|
20
|
+

|
21
|
+
|
20
22
|
## Loading and Saving
|
21
23
|
|
22
24
|
Instead of building an image from scratch you can load one with
|
23
25
|
`ImageUtil::Image.from_string` or `ImageUtil::Image.from_file`.
|
24
|
-
Both helpers understand the built in codecs for `png`, `jpeg
|
25
|
-
formats:
|
26
|
+
Both helpers understand the built in codecs for `png`, `jpeg`, `pam`, `gif`
|
27
|
+
and `apng` formats:
|
26
28
|
|
27
29
|
```ruby
|
28
30
|
img = ImageUtil::Image.from_file("logo.png")
|
29
31
|
data = ImageUtil::Image.from_string(File.binread("logo.jpeg"))
|
30
32
|
```
|
31
33
|
|
34
|
+
A `from_file` method also supports passing IO objects:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
img = ImageUtil::Image.from_file(IO.popen("magick rose: png:"))
|
38
|
+
img.draw_line([0,0], [69,45], :blue)
|
39
|
+
```
|
40
|
+
|
41
|
+

|
42
|
+
|
32
43
|
The same formats can be written back using `to_string` or `to_file`.
|
44
|
+
When saving to a file path, `to_file` can infer the format from the file extension.
|
33
45
|
|
34
46
|
```ruby
|
35
|
-
img.to_file("out.png"
|
47
|
+
img.to_file("out.png")
|
36
48
|
binary = img.to_string(:jpeg)
|
37
49
|
```
|
38
50
|
|
39
|
-
##
|
51
|
+
## Image Information
|
52
|
+
|
53
|
+
After loading or creating an image, you can access information about it, like
|
54
|
+
dimensions or color bits.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
img.dimensions # => [20,30]
|
58
|
+
img.width # => 20
|
59
|
+
img.height # => 30
|
60
|
+
img.color_bits # => 8 (means every channel has 8 bits of color)
|
61
|
+
img.channels # => 3 (RGB)
|
62
|
+
```
|
63
|
+
|
64
|
+
## Terminal Output
|
40
65
|
|
41
66
|
Images can be previewed in compatible terminals:
|
42
67
|
|
43
68
|
```ruby
|
44
|
-
puts img
|
69
|
+
puts ImageUtil::Terminal.output_image($stdin, $stdout, img)
|
45
70
|
```
|
46
71
|
|
47
72
|
In `irb` or `pry` the `inspect` method shows the image automatically, so you can
|
@@ -51,12 +76,15 @@ just evaluate the object:
|
|
51
76
|
img
|
52
77
|
```
|
53
78
|
|
54
|
-
|
79
|
+
The library checks if the Kitty graphics protocol is available and falls back to SIXEL otherwise. Kitty graphics protocol is supported by Kitty, Konsole
|
80
|
+
and a couple others. SIXEL, most notably, works in Windows Terminal, Konsole (KDE), iTerm2 (macOS), XTerm (launch with: `xterm -ti vt340`). Here's how SIXEL
|
81
|
+
looks in Konsole:
|
55
82
|
|
56
83
|

|
57
84
|
|
85
|
+
This library supports generating Sixel with either `libsixel`, `ImageMagick` or using a pure-Ruby Sixel generator. For best performance, try to install one of
|
86
|
+
the earlier system packages. Both Kitty and SIXEL outputs also accept one-dimensional images, treating them as height `1`.
|
58
87
|
|
59
|
-
This library supports generating Sixel with either `libsixel`, `ImageMagick` or using a pure-Ruby Sixel generator. For best performance, try to install one of the earlier system packages.
|
60
88
|
|
61
89
|
## Color Values
|
62
90
|
|
@@ -94,32 +122,45 @@ patch = img[0..1, 0..1]
|
|
94
122
|
For instance, you can extract a region, edit it and paste it back:
|
95
123
|
|
96
124
|
```ruby
|
97
|
-
img = ImageUtil::Image.new(
|
98
|
-
corner = img[0..
|
125
|
+
img = ImageUtil::Image.new(128, 128) { [0, 0, 0] }
|
126
|
+
corner = img[0..32, 0..32]
|
99
127
|
corner.all = :green
|
100
|
-
img[0..
|
128
|
+
img[0..32, 0..32] = corner
|
101
129
|
img[2, 2] = :yellow
|
102
|
-
img.to_file("pixel_patch.png", :png)
|
130
|
+
# img.to_file("pixel_patch.png", :png)
|
131
|
+
img
|
103
132
|
```
|
104
133
|
|
134
|
+

|
135
|
+
|
105
136
|
Assigning an image to a range automatically resizes it to fit before pasting.
|
106
137
|
|
138
|
+
On the other hand, if you assign a color to a range, it will fill all referenced
|
139
|
+
pixels with that color (draw a rectangle).
|
140
|
+
|
107
141
|
Iteration helpers operate on arbitrary ranges and share the same syntax used
|
108
142
|
when indexing images. `each_pixel` yields color objects, while
|
109
|
-
`each_pixel_location` yields coordinate arrays. `set_each_pixel_by_location
|
143
|
+
`each_pixel_location` yields coordinate arrays. `set_each_pixel_by_location!`
|
110
144
|
assigns the value returned by the block to every location (unless `nil` is returned).
|
111
145
|
|
112
146
|
```ruby
|
147
|
+
# create an all-black image
|
148
|
+
img = ImageUtil::Image.new(128, 128) { :black }
|
149
|
+
|
113
150
|
# fill a checkerboard pattern
|
114
|
-
img
|
115
|
-
|
116
|
-
:black if (x + y).odd?
|
151
|
+
img.set_each_pixel_by_location! do |x, y|
|
152
|
+
:red if (x + y).odd?
|
117
153
|
end
|
118
154
|
|
119
|
-
# count how many
|
120
|
-
black = img.each_pixel.count { |c| c == :
|
155
|
+
# count how many red pixels were set
|
156
|
+
black = img.each_pixel.count { |c| c == :red }
|
157
|
+
|
158
|
+
# display img in terminal
|
159
|
+
img
|
121
160
|
```
|
122
161
|
|
162
|
+

|
163
|
+
|
123
164
|
Note that instead of manually calling `set_each_pixel_by_location`, you can just pass a block to `ImageUtil::Image.new`.
|
124
165
|
|
125
166
|
## Filters
|
@@ -132,7 +173,10 @@ modifies the image in place while the non-bang version returns a copy.
|
|
132
173
|
Flatten an RGBA image on a solid color.
|
133
174
|
|
134
175
|
```ruby
|
176
|
+
# create a transparent image gradient containing shades of red only
|
135
177
|
img = ImageUtil::Image.new(128, 128) { |x, y| [255, 0, 0, x + y] }
|
178
|
+
|
179
|
+
# put it on a blue background
|
136
180
|
img.background([0, 0, 255])
|
137
181
|
```
|
138
182
|
|
@@ -169,23 +213,76 @@ img.draw_circle!([64, 64], 30, :blue)
|
|
169
213
|
Scale an image to new dimensions.
|
170
214
|
|
171
215
|
```ruby
|
172
|
-
img = ImageUtil::Image.new(
|
173
|
-
img[
|
216
|
+
img = ImageUtil::Image.new(128, 128) { |x, y| [x, y, 30] }
|
217
|
+
img[20, 20] = img.resize(64, 64)
|
174
218
|
img
|
175
219
|
```
|
176
220
|
|
177
221
|

|
178
222
|
|
179
|
-
###
|
223
|
+
### Palette
|
180
224
|
|
181
225
|
Reduce the image to a limited palette.
|
182
226
|
|
183
227
|
```ruby
|
184
|
-
img = ImageUtil::Image.new(
|
185
|
-
img.
|
228
|
+
img = ImageUtil::Image.new(128, 128) { |x, y| [x * 2, y * 2, 200] }
|
229
|
+
img.palette_reduce(64)
|
186
230
|
```
|
187
231
|
|
188
|
-

|
233
|
+
|
234
|
+
### Transform
|
235
|
+
|
236
|
+
Rotate or flip an image.
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
img = ImageUtil::Image.new(128, 128) { |x, y| [x, y, 0] }
|
240
|
+
img.flip!(:x)
|
241
|
+
img.rotate!(90)
|
242
|
+
# img.rotate!(90, axes: [:x, :z])
|
243
|
+
```
|
244
|
+
|
245
|
+

|
246
|
+
|
247
|
+
### Colors
|
248
|
+
|
249
|
+
Multiply all pixels by a color.
|
250
|
+
|
251
|
+
```ruby
|
252
|
+
img = ImageUtil::Image.new(128, 128) { [255, 255, 255, 128] }
|
253
|
+
img * :red
|
254
|
+
```
|
255
|
+
|
256
|
+

|
257
|
+
|
258
|
+
### Bitmap Text
|
259
|
+
|
260
|
+
Overlay text using the bundled bitmap font.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
img = ImageUtil::Image.new(128, 128) { [0, 0, 0] }
|
264
|
+
img.bitmap_text!("Lorem ipsum dolor sit\namet, consectetur adipiscing\nelit.", 2, 2, color: :blue)
|
265
|
+
```
|
266
|
+
|
267
|
+

|
268
|
+
|
269
|
+
### Redimension
|
270
|
+
|
271
|
+
Change how many dimensions an image has or adjust their size.
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
img = ImageUtil::Image.new(64, 64) { :white }
|
275
|
+
img.redimension!(128, 128) # can also add additional dimensions
|
276
|
+
img.background(:blue)
|
277
|
+
```
|
278
|
+
|
279
|
+

|
280
|
+
|
281
|
+
## Command Line
|
282
|
+
|
283
|
+
The gem includes a small `image_util` CLI. Run `image_util support` to list
|
284
|
+
available codecs, default format handlers and detected terminal features.
|
285
|
+
See [docs/cli.md](docs/cli.md) for details.
|
189
286
|
|
190
287
|
## Development
|
191
288
|
|
@@ -193,6 +290,10 @@ After checking out the repo, run `bin/setup` to install dependencies. Then run
|
|
193
290
|
`rake spec` to execute the tests. You can also run `bin/console` for an
|
194
291
|
interactive prompt for experimenting with the library.
|
195
292
|
|
293
|
+
### Benchmarking
|
294
|
+
|
295
|
+
Run `bin/benchmark` to execute a small benchmark.
|
296
|
+
|
196
297
|
## Contributing
|
197
298
|
|
198
299
|
Bug reports and pull requests are welcome on GitHub at
|
data/Rakefile
CHANGED
data/docs/cli.md
ADDED
data/docs/samples/background.png
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/docs/samples/dither.png
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/docs/samples/resize.png
CHANGED
Binary file
|
Binary file
|
data/exe/image_util
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "benchmark/ips"
|
4
|
+
|
5
|
+
module ImageUtil
|
6
|
+
module Benchmarking
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Benchmarks creating a 64×64×64 black image for the given time in seconds.
|
10
|
+
def image_creation(seconds = 5)
|
11
|
+
::Benchmark.ips do |x|
|
12
|
+
x.warmup = 0
|
13
|
+
x.time = seconds
|
14
|
+
x.report("from Symbol") { Image.new(64, 64, 64) { :black } }
|
15
|
+
x.report("from Array (4->4 channels)") { Image.new(64, 64, 64) { |x,y,z| [x,y,z,255] } }
|
16
|
+
x.report("from Array (3->4 channels)") { Image.new(64, 64, 64) { |x,y,z| [x,y,z] } }
|
17
|
+
x.report("from Array (3->3 channels)") { Image.new(64, 64, 64, channels: 3) { |x,y,z| [x,y,z] } }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(seconds = 5)
|
22
|
+
image_creation(seconds)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`~!@#$%^&*()-_+=:;'"|\/?.,[]{}<>€
|
Binary file
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ImageUtil
|
4
|
+
class BitmapFont
|
5
|
+
def initialize(name)
|
6
|
+
font = Image.from_file("#{__dir__}/bitmap_font/fonts/#{name}/font.png")
|
7
|
+
charset = File.read("#{__dir__}/bitmap_font/fonts/#{name}/charset.txt").chomp.chars
|
8
|
+
|
9
|
+
parse_image(font, charset)
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_image(font, charset)
|
13
|
+
font.height.times do |n|
|
14
|
+
if font[0,n] == :red
|
15
|
+
@height = n
|
16
|
+
break
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
character_pos = []
|
21
|
+
|
22
|
+
n = 0
|
23
|
+
while n < font.width
|
24
|
+
if font[n, @height] == :blue
|
25
|
+
start = n
|
26
|
+
n += 1 while n < font.width && font[n, @height] == :blue
|
27
|
+
n -= 1
|
28
|
+
finish = n
|
29
|
+
character_pos << (start..finish)
|
30
|
+
end
|
31
|
+
n += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
font = font.dup
|
35
|
+
font.set_each_pixel_by_location! do |x,y|
|
36
|
+
[255, 255, 255, 255 - font[x,y].g]
|
37
|
+
end
|
38
|
+
|
39
|
+
@characters = character_pos.map.with_index do |range,idx|
|
40
|
+
[charset[idx], font[range, ...@height]]
|
41
|
+
end.to_h
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_line_of_text(text)
|
45
|
+
width = 1
|
46
|
+
text.chars.each do |char|
|
47
|
+
width += @characters[char].width + 1
|
48
|
+
end
|
49
|
+
width -= 1
|
50
|
+
|
51
|
+
img = Image.new(width, @height)
|
52
|
+
width = 0
|
53
|
+
text.chars.each do |char|
|
54
|
+
img[width,0] = @characters[char]
|
55
|
+
width += @characters[char].width + 1
|
56
|
+
end
|
57
|
+
|
58
|
+
img
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.fonts
|
62
|
+
Dir["#{__dir__}/bitmap_font/fonts/*"].map { |i| File.basename(i) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.default_font = "smfont"
|
66
|
+
|
67
|
+
def self.cached_load(font)
|
68
|
+
@fonts ||= {}
|
69
|
+
@fonts[font] ||= BitmapFont.new(font)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
module ImageUtil
|
6
|
+
class CLI < Thor
|
7
|
+
desc "support", "Display codec support, default codecs and terminal features"
|
8
|
+
def support
|
9
|
+
width = (codec_names + format_names).map(&:length).max
|
10
|
+
use_color = Terminal.detect_support.include?(:tty)
|
11
|
+
|
12
|
+
puts "Codecs:"
|
13
|
+
codec_names.each do |name|
|
14
|
+
mod = Codec.const_get(name)
|
15
|
+
supported = mod.supported?
|
16
|
+
status = supported ? color("supported", 32, use_color) : color("not supported", 31, use_color)
|
17
|
+
puts format(" %-#{width}s %s", name, status)
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "\nFormats:"
|
21
|
+
format_names.each do |fmt|
|
22
|
+
codec = default_codec(fmt)
|
23
|
+
codec_name = codec ? color(codec.to_s, 32, use_color) : color("none", 31, use_color)
|
24
|
+
puts format(" %-#{width}s %s", fmt, codec_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "\nTerminal features:"
|
28
|
+
Terminal.detect_support.each do |feat|
|
29
|
+
puts " #{color(feat, 34, use_color)}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
no_commands do
|
34
|
+
def codec_names = Codec.constants.select { |name| Codec.const_get(name).respond_to?(:supported?) }
|
35
|
+
def format_names = (Codec.encoders + Codec.decoders).flat_map { |r| r[:formats] }.uniq.sort
|
36
|
+
|
37
|
+
def default_codec(fmt)
|
38
|
+
Codec.encoders.each do |r|
|
39
|
+
next unless r[:formats].include?(fmt.to_s)
|
40
|
+
|
41
|
+
codec_mod = Codec.const_get(r[:codec])
|
42
|
+
next if codec_mod.respond_to?(:supported?) && !codec_mod.supported?(fmt.to_sym)
|
43
|
+
|
44
|
+
return r[:codec]
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def color(text, code, enable)
|
50
|
+
enable ? "\e[#{code}m#{text}\e[0m" : text
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ImageUtil
|
4
|
+
module Codec
|
5
|
+
module ChunkyPng
|
6
|
+
SUPPORTED_FORMATS = [:png].freeze
|
7
|
+
|
8
|
+
extend Guard
|
9
|
+
|
10
|
+
begin
|
11
|
+
require "chunky_png"
|
12
|
+
AVAILABLE = true
|
13
|
+
rescue LoadError
|
14
|
+
AVAILABLE = false
|
15
|
+
end
|
16
|
+
|
17
|
+
module_function
|
18
|
+
|
19
|
+
def supported?(format = nil)
|
20
|
+
return false unless AVAILABLE
|
21
|
+
|
22
|
+
return true if format.nil?
|
23
|
+
|
24
|
+
SUPPORTED_FORMATS.include?(format.to_s.downcase.to_sym)
|
25
|
+
end
|
26
|
+
|
27
|
+
def encode(format, image)
|
28
|
+
guard_supported_format!(format, SUPPORTED_FORMATS)
|
29
|
+
raise UnsupportedFormatError, "chunky_png not available" unless AVAILABLE
|
30
|
+
|
31
|
+
guard_2d_image!(image)
|
32
|
+
guard_8bit_colors!(image)
|
33
|
+
|
34
|
+
raw = if image.channels == 4
|
35
|
+
image.buffer.get_string
|
36
|
+
else
|
37
|
+
data = String.new(capacity: image.width * image.height * 4)
|
38
|
+
buf = image.buffer
|
39
|
+
idx = 0
|
40
|
+
step = buf.pixel_bytes
|
41
|
+
image.height.times do
|
42
|
+
image.width.times do
|
43
|
+
color = buf.get_index(idx)
|
44
|
+
data << color[0].chr << color[1].chr << color[2].chr << 255.chr
|
45
|
+
idx += step
|
46
|
+
end
|
47
|
+
end
|
48
|
+
data
|
49
|
+
end
|
50
|
+
|
51
|
+
png = ::ChunkyPNG::Image.from_rgba_stream(image.width, image.height, raw)
|
52
|
+
png.to_blob
|
53
|
+
end
|
54
|
+
|
55
|
+
def decode(format, data)
|
56
|
+
guard_supported_format!(format, SUPPORTED_FORMATS)
|
57
|
+
raise UnsupportedFormatError, "chunky_png not available" unless AVAILABLE
|
58
|
+
|
59
|
+
png = ::ChunkyPNG::Image.from_blob(data)
|
60
|
+
raw = png.to_rgba_stream
|
61
|
+
io_buf = IO::Buffer.for(raw)
|
62
|
+
buf = Image::Buffer.new([png.width, png.height], 8, 4, io_buf)
|
63
|
+
Image.from_buffer(buf)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|