image_util 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27dbed58dd9646ef50f6ae022f3d2599629670f0d430f61c76416cb28d49bbd6
4
- data.tar.gz: 55d78376ffbbf3f85b60db158ca65c46b96164c9e6c52b0d7324b70e1bd11dc4
3
+ metadata.gz: 92f9a0c088385d833ceee58b203a3ba52a4de3d2cbfdd16ed6dc8d0213e18753
4
+ data.tar.gz: 275a94eb28e30c8d1a59ba25723284e864e8ad9b8a1041826cba37d983ba01fd
5
5
  SHA512:
6
- metadata.gz: 2c14b302a030e83cc6f398f8560138c31f53e93d9059c7f2b18ce5b81aeb41e4922932a64e8221b5e2e52fc4e5c876ac4dfd74f0eb7630e907e5a74a50a52335
7
- data.tar.gz: 6f3834f813239cc80418d54a01224ae5afbd28870a8beff19f1a2c32fabfe7a800d2cc59a857d98faed713c49fff2cc0519332927004c900a72709b08162bcc3
6
+ metadata.gz: 523748193e1b51083729b1583cfec3ac19ec02a11e893cc3d858deb30388eebd013a01ade2770cd236c2f947bffc0cc2583783d3094552451d99b873d840311b
7
+ data.tar.gz: 228f82c3508d236822c4a6e27ad721381ffa42aaa31d1e3ab839eeaf9d60363ab2bcd7e6340fab00cdeac23320ad4681036187bd2eb5bb5680242ff9a8451464
data/AGENTS.md CHANGED
@@ -6,11 +6,7 @@
6
6
  - Prefer double-quoted strings except in specs and the gemspec.
7
7
  - Use RSpec's `should` syntax instead of `expect`.
8
8
  - For one-line methods, use the `def name = expression` style.
9
-
10
- Additional notes from the existing code:
11
- - Image data is stored in `Image::Buffer` backed by `IO::Buffer`.
12
- - Use `Filter::Mixin#define_immutable_version` to add non-bang versions of mutating filters.
13
- - Views such as `View::Interpolated` and `View::Rounded` are built with `Data.define`.
14
- - Pure Ruby algorithms are provided with optional FFI wrappers for libpng, libturbojpeg and libsixel.
9
+ - After adding new features or modifying existing ones, change documentation accordingly (especially README and especially CHANGELOG).
10
+ - Don't discuss codec internals or bug fixes in README. Only list supported formats. Document bug fixes in the CHANGELOG, not in README.
15
11
  - Specs target at least 80% coverage as enforced by SimpleCov.
16
12
  - The library aims to remain lightweight and portable.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
- ## [Unreleased]
2
- ### Added
1
+ ## [0.2.0] - 2025-07-21
2
+ - Ruby Sixel encoder now sets pixel aspect ratio metadata to display correctly in Windows Terminal
3
+ - Support for all CSS color names
4
+ - Range assignments with images now resize the image before pasting
5
+ - Circle drawing filter
6
+ - ImageMagick codec can encode and decode `png`, `jpeg` and `sixel`
7
+
8
+ ## [0.1.0] - 2025-07-21
9
+
10
+ - Initial release
3
11
  - Drop support for Ruby 3.1
4
12
  - Native SIXEL encoder
5
13
  - Background filter
@@ -12,7 +20,3 @@
12
20
  - Paste and Draw filters for compositing and drawing
13
21
  - Rounded and interpolated pixel views
14
22
  - `Image#view` helper for custom access
15
-
16
- ## [0.1.0] - 2025-07-19
17
-
18
- - Initial release
data/README.md CHANGED
@@ -1,144 +1,191 @@
1
1
  # ImageUtil
2
2
 
3
- ImageUtil provides a minimal in-memory image container and a set of utilities for handling raw pixel data in Ruby. It is aimed at small scripts and tools that need to manipulate images without relying on heavy external dependencies.
3
+ ImageUtil is a lightweight Ruby library focused on manipulating images directly in memory. Its primary goal is to help scripts visualize data right in the terminal by supporting SIXEL output alongside common image formats. The API is still evolving and should be considered unstable until version 1.0.
4
4
 
5
- Features include:
5
+ ## Creating an Image
6
6
 
7
- * Representation of images with arbitrary dimensions.
8
- * Support for 8, 16 or 32 bit components and RGB or RGBA color values.
9
- * A `Color` helper class capable of parsing numbers, arrays and HTML style strings.
10
- * Conversion of an image to PAM or SIXEL for quick previews in compatible terminals.
11
- * Built-in SIXEL encoder that works without ImageMagick.
12
- * Convenience methods for iterating over pixel locations and setting values.
13
- * Overlaying colors with the `+` operator which blends using the alpha channel.
14
- * Automatic format detection when reading images from strings or files.
15
- * Alternate pixel views for interpolated or rounded coordinates.
16
-
17
- ## Installation
18
-
19
- ImageUtil is available on RubyGems:
7
+ ```ruby
8
+ require 'image_util'
20
9
 
21
- ```bash
22
- gem install image_util
10
+ # 40×40 RGBA image
11
+ img = ImageUtil::Image.new(40, 40)
23
12
  ```
24
13
 
25
- Alternatively add it to your `Gemfile`:
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.
26
15
 
27
16
  ```ruby
28
- gem "image_util"
17
+ img = ImageUtil::Image.new(4, 4) { |x, y| ImageUtil::Color[x * 64, y * 64, 0] }
29
18
  ```
30
19
 
31
- Run `bundle install` afterwards.
20
+ ## Loading and Saving
32
21
 
33
- You can also build and install the gem manually:
22
+ Instead of building an image from scratch you can load one with
23
+ `ImageUtil::Image.from_string` or `ImageUtil::Image.from_file`.
24
+ Both helpers understand the built in codecs for `png`, `jpeg` and `pam`
25
+ formats:
34
26
 
35
- ```bash
36
- git clone https://github.com/rbutils/image_util.git
37
- cd image_util
38
- bundle exec rake install
27
+ ```ruby
28
+ img = ImageUtil::Image.from_file("logo.png")
29
+ data = ImageUtil::Image.from_string(File.binread("logo.jpeg"))
39
30
  ```
40
31
 
41
- ## Usage
32
+ The same formats can be written back using `to_string` or `to_file`.
42
33
 
43
34
  ```ruby
44
- require 'image_util'
35
+ img.to_file("out.png", :png)
36
+ binary = img.to_string(:jpeg)
37
+ ```
45
38
 
46
- # create a 4×4 image using 8‑bit RGBA colors
47
- i = ImageUtil::Image.new(4, 4)
39
+ ## SIXEL Output
48
40
 
49
- # set the top‑left pixel to red
50
- i[0, 0] = ImageUtil::Color[255, 0, 0]
41
+ Images can be previewed in compatible terminals:
51
42
 
52
- # display the image in a SIXEL-capable terminal
53
- puts i.to_sixel
43
+ ```ruby
44
+ puts img.to_sixel
54
45
  ```
55
46
 
56
- Images can also be iterated over or modified using ranges:
47
+ In `irb` or `pry` the `inspect` method shows the image automatically, so you can
48
+ just evaluate the object:
57
49
 
58
50
  ```ruby
59
- # fill an area with blue
60
- i[0..1, 0..1] = ImageUtil::Color['#0000ff']
51
+ img
52
+ ```
61
53
 
62
- # iterate over every pixel
63
- i.each_pixel do |pixel|
64
- # pixel is an ImageUtil::Color instance
65
- end
54
+ Most notably, SIXEL works in Windows Terminal, Konsole (KDE), iTerm2 (macOS), XTerm (launch with: `xterm -ti vt340`). Here's how it looks in Konsole:
66
55
 
67
- # paste one image into another
68
- target = ImageUtil::Image.new(8, 8) { ImageUtil::Color[0] }
69
- source = ImageUtil::Image.new(2, 2) { ImageUtil::Color[255, 0, 0, 128] }
70
- target.paste!(source, 3, 3, respect_alpha: true)
56
+ ![Sixel example](docs/samples/sixel.png)
71
57
 
72
- # draw a diagonal line
73
- i.draw_line!([0, 0], [3, 3], ImageUtil::Color['red'], view: ImageUtil::View::Rounded)
74
- ```
75
58
 
76
- `View::Interpolated` provides subpixel access while `View::Rounded` snaps
77
- coordinates to the nearest pixel. These views are useful for drawing
78
- operations like the example above.
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.
79
60
 
80
- ### Reading and Writing Images
61
+ ## Color Values
62
+
63
+ `ImageUtil::Color.from` (also known as `ImageUtil::Color.[]`) accepts several inputs:
64
+
65
+ - Another `Color` instance
66
+ - Arrays of numeric components (`[r, g, b]` or `[r, g, b, a]`)
67
+ - Numbers (used for all RGB channels)
68
+ - Symbols or strings containing CSS color names (`:rebeccapurple`, 'papayawhip')
69
+ - Hex strings like `'#abc'`, `'#aabbcc'` or `'#rrggbbaa'`
70
+
71
+ When numeric components are given, integers are first clamped to the `0..255`
72
+ range. Float values are treated as fractions of 255, so `0.5` becomes `127.5`
73
+ and `1.0` becomes `255`. After scaling, values are again clamped to this range.
74
+ If the alpha channel is omitted it defaults to `255`.
81
75
 
82
76
  ```ruby
83
- # detect format automatically when loading from a file
84
- img = ImageUtil::Image.from_file("photo.png")
77
+ ImageUtil::Color[0.5] # => #808080
78
+ ImageUtil::Color[:red] # => #ff0000
79
+ ImageUtil::Color["#fc0"] # => #ffcc00
80
+ ```
81
+
82
+ Note that whenever the library expects a color, it may be given in any form accepted by this function.
85
83
 
86
- # save using a specific codec
87
- img.to_file("out.jpg", :jpeg)
84
+ ## Pixel Access
88
85
 
89
- # convert directly to a string
90
- data = img.to_string(:png)
86
+ Pixels can be accessed with integer coordinates or ranges. When ranges are used
87
+ a new `Image` containing that region is returned and can be modified separately.
88
+
89
+ ```ruby
90
+ img[0, 0] = '#ff0000'
91
+ patch = img[0..1, 0..1]
91
92
  ```
92
93
 
93
- ### Filters
94
+ For instance, you can extract a region, edit it and paste it back:
94
95
 
95
96
  ```ruby
96
- # reduce palette to 32 colors
97
- dithered = img.dither(32)
97
+ img = ImageUtil::Image.new(4, 4) { [0, 0, 0] }
98
+ corner = img[0..1, 0..1]
99
+ corner.all = :green
100
+ img[0..1, 0..1] = corner
101
+ img[2, 2] = :yellow
102
+ img.to_file("pixel_patch.png", :png)
103
+ ```
98
104
 
99
- # composite two images without altering the originals
100
- result = base.paste(other, 10, 10)
105
+ Assigning an image to a range automatically resizes it to fit before pasting.
101
106
 
102
- # apply a background color to an RGBA image
103
- flattened = img.background(ImageUtil::Color[255, 255, 255])
107
+ Iteration helpers operate on arbitrary ranges and share the same syntax used
108
+ when indexing images. `each_pixel` yields color objects, while
109
+ `each_pixel_location` yields coordinate arrays. `set_each_pixel_by_location`
110
+ assigns the value returned by the block to every location (unless `nil` is returned).
111
+
112
+ ```ruby
113
+ # fill a checkerboard pattern
114
+ img = ImageUtil::Image.new(8, 8) { :white }
115
+ img.set_each_pixel_by_location do |x, y|
116
+ :black if (x + y).odd?
117
+ end
118
+
119
+ # count how many black pixels were set
120
+ black = img.each_pixel.count { |c| c == :black }
104
121
  ```
105
122
 
106
- ### Working with Views
123
+ Note that instead of manually calling `set_each_pixel_by_location`, you can just pass a block to `ImageUtil::Image.new`.
124
+
125
+ ## Filters
126
+
127
+ `ImageUtil::Image` ships with a few convenience filters. Each bang method
128
+ modifies the image in place while the non-bang version returns a copy.
129
+
130
+ ### Background
131
+
132
+ Flatten an RGBA image on a solid color.
107
133
 
108
134
  ```ruby
109
- # access using fractional coordinates
110
- interp = img.view(ImageUtil::View::Interpolated)
111
- interp[1.2, 2.8] = ImageUtil::Color[0, 0, 255]
135
+ img = ImageUtil::Image.new(128, 128) { |x, y| [255, 0, 0, x + y] }
136
+ img.background([0, 0, 255])
137
+ ```
138
+
139
+ ![Background example](docs/samples/background.png)
140
+
141
+ ### Paste
142
+
143
+ Place one image over another. When `respect_alpha` is true, the pasted pixels are
144
+ blended with the base image.
112
145
 
113
- # round coordinates instead
114
- rounded = img.view(ImageUtil::View::Rounded)
115
- color = rounded[1.6, 0.3]
146
+ ```ruby
147
+ base = ImageUtil::Image.new(128, 128) { |x, y| [x, y, 50] }
148
+ overlay = ImageUtil::Image.new(64, 64) { |x, y| [255, 0, 0, (x + y) * 2] }
149
+ base.paste!(overlay, 32, 32, respect_alpha: true)
116
150
  ```
117
151
 
118
- ### Codecs
152
+ ![Paste example](docs/samples/paste.png)
153
+
154
+ ### Draw
119
155
 
120
- ImageUtil includes a small registry of codecs for converting images to and from
121
- common formats such as PNG, JPEG and SIXEL. The library ships with pure Ruby
122
- encoders and FFI wrappers around `libpng`, `libturbojpeg` and `libsixel` when
123
- available.
156
+ Draw simple shapes directly on the image.
124
157
 
125
158
  ```ruby
126
- png = ImageUtil::Codec.encode(:png, i)
127
- back = ImageUtil::Codec.decode(:png, png)
159
+ img = ImageUtil::Image.new(128, 128) { [0, 0, 0] }
160
+ img.draw_line!([0, 0], [127, 127], :red)
161
+ img.draw_line!([0, 127], [127, 0], :lime)
162
+ img.draw_circle!([64, 64], 30, :blue)
163
+ ```
128
164
 
129
- File.open("img.pam", "wb") do |f|
130
- ImageUtil::Codec.encode_io(:pam, i, f)
131
- end
165
+ ![Draw example](docs/samples/draw.png)
166
+
167
+ ### Resize
168
+
169
+ Scale an image to new dimensions.
170
+
171
+ ```ruby
172
+ img = ImageUtil::Image.new(256, 256) { |x, y| [x, y, 30] }
173
+ img[70, 70] = img.resize(64, 64)
174
+ img
132
175
  ```
133
176
 
134
- You can read images from files without specifying the format:
177
+ ![Resize example](docs/samples/resize.png)
178
+
179
+ ### Dither
180
+
181
+ Reduce the image to a limited palette.
135
182
 
136
183
  ```ruby
137
- image = ImageUtil::Image.from_file("picture.jpg")
184
+ img = ImageUtil::Image.new(256, 64) { |x, y| [x, y * 4, 200] }
185
+ img.dither(8)
138
186
  ```
139
187
 
140
- Use `ImageUtil::Codec.supported?(format)` to check if a particular format is
141
- available. Unsupported formats raise `ImageUtil::Codec::UnsupportedFormatError`.
188
+ ![Dither example](docs/samples/dither.png)
142
189
 
143
190
  ## Development
144
191
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -3,7 +3,7 @@
3
3
  module ImageUtil
4
4
  module Codec
5
5
  module ImageMagick
6
- SUPPORTED_FORMATS = [:sixel].freeze
6
+ SUPPORTED_FORMATS = %i[sixel jpeg png].freeze
7
7
 
8
8
  extend Guard
9
9
 
@@ -26,10 +26,13 @@ module ImageUtil
26
26
  def encode(format, image)
27
27
  guard_supported_format!(format, SUPPORTED_FORMATS)
28
28
 
29
- IO.popen("magick pam:- sixel:-", "r+") do |io|
30
- io << Codec::Pam.encode(:pam, image, fill_to: 6)
31
- io.close_write
32
- io.read
29
+ fmt = format.to_s.downcase
30
+ pam = Codec::Pam.encode(:pam, image, fill_to: fmt == "sixel" ? 6 : nil)
31
+
32
+ IO.popen(["magick", "pam:-", "#{fmt}:-"], "r+") do |proc_io|
33
+ proc_io << pam
34
+ proc_io.close_write
35
+ proc_io.read
33
36
  end
34
37
  end
35
38
 
@@ -37,12 +40,18 @@ module ImageUtil
37
40
  io << encode(format, image)
38
41
  end
39
42
 
40
- def decode(*)
41
- raise UnsupportedFormatError, "decode not supported for sixel"
43
+ def decode(format, data)
44
+ guard_supported_format!(format, SUPPORTED_FORMATS)
45
+
46
+ IO.popen(["magick", "#{format}:-", "pam:-"], "r+") do |proc_io|
47
+ proc_io << data
48
+ proc_io.close_write
49
+ Pam.decode(:pam, proc_io.read)
50
+ end
42
51
  end
43
52
 
44
- def decode_io(*)
45
- raise UnsupportedFormatError, "decode not supported for sixel"
53
+ def decode_io(format, io)
54
+ decode(format, io.read)
46
55
  end
47
56
  end
48
57
  end
@@ -56,7 +56,7 @@ module ImageUtil
56
56
  end
57
57
  end
58
58
 
59
- out = "\ePq".dup
59
+ out = "\ePq\"1;1;#{width};#{height}".dup
60
60
  palette.each_with_index do |c, idx|
61
61
  out << format("#%d;2;%d;%d;%d", idx, c.r * 100 / 255, c.g * 100 / 255, c.b * 100 / 255)
62
62
  end
@@ -111,7 +111,7 @@ module ImageUtil
111
111
  register_codec :Libpng, :png
112
112
  register_codec :Libturbojpeg, :jpeg
113
113
  register_encoder :Libsixel, :sixel
114
- register_encoder :ImageMagick, :sixel
114
+ register_codec :ImageMagick, :png, :jpeg, :sixel
115
115
  register_encoder :RubySixel, :sixel
116
116
  end
117
117
  end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ImageUtil
4
+ class Color < Array
5
+ CSS_COLORS = {
6
+ "aliceblue" => [240, 248, 255],
7
+ "antiquewhite" => [250, 235, 215],
8
+ "aqua" => [0, 255, 255],
9
+ "aquamarine" => [127, 255, 212],
10
+ "azure" => [240, 255, 255],
11
+ "beige" => [245, 245, 220],
12
+ "bisque" => [255, 228, 196],
13
+ "black" => [0, 0, 0],
14
+ "blanchedalmond" => [255, 235, 205],
15
+ "blue" => [0, 0, 255],
16
+ "blueviolet" => [138, 43, 226],
17
+ "brown" => [165, 42, 42],
18
+ "burlywood" => [222, 184, 135],
19
+ "cadetblue" => [95, 158, 160],
20
+ "chartreuse" => [127, 255, 0],
21
+ "chocolate" => [210, 105, 30],
22
+ "coral" => [255, 127, 80],
23
+ "cornflowerblue" => [100, 149, 237],
24
+ "cornsilk" => [255, 248, 220],
25
+ "crimson" => [220, 20, 60],
26
+ "cyan" => [0, 255, 255],
27
+ "darkblue" => [0, 0, 139],
28
+ "darkcyan" => [0, 139, 139],
29
+ "darkgoldenrod" => [184, 134, 11],
30
+ "darkgray" => [169, 169, 169],
31
+ "darkgrey" => [169, 169, 169],
32
+ "darkgreen" => [0, 100, 0],
33
+ "darkkhaki" => [189, 183, 107],
34
+ "darkmagenta" => [139, 0, 139],
35
+ "darkolivegreen" => [85, 107, 47],
36
+ "darkorange" => [255, 140, 0],
37
+ "darkorchid" => [153, 50, 204],
38
+ "darkred" => [139, 0, 0],
39
+ "darksalmon" => [233, 150, 122],
40
+ "darkseagreen" => [143, 188, 143],
41
+ "darkslateblue" => [72, 61, 139],
42
+ "darkslategray" => [47, 79, 79],
43
+ "darkslategrey" => [47, 79, 79],
44
+ "darkturquoise" => [0, 206, 209],
45
+ "darkviolet" => [148, 0, 211],
46
+ "deeppink" => [255, 20, 147],
47
+ "deepskyblue" => [0, 191, 255],
48
+ "dimgray" => [105, 105, 105],
49
+ "dimgrey" => [105, 105, 105],
50
+ "dodgerblue" => [30, 144, 255],
51
+ "firebrick" => [178, 34, 34],
52
+ "floralwhite" => [255, 250, 240],
53
+ "forestgreen" => [34, 139, 34],
54
+ "fuchsia" => [255, 0, 255],
55
+ "gainsboro" => [220, 220, 220],
56
+ "ghostwhite" => [248, 248, 255],
57
+ "gold" => [255, 215, 0],
58
+ "goldenrod" => [218, 165, 32],
59
+ "gray" => [128, 128, 128],
60
+ "grey" => [128, 128, 128],
61
+ "green" => [0, 128, 0],
62
+ "greenyellow" => [173, 255, 47],
63
+ "honeydew" => [240, 255, 240],
64
+ "hotpink" => [255, 105, 180],
65
+ "indianred" => [205, 92, 92],
66
+ "indigo" => [75, 0, 130],
67
+ "ivory" => [255, 255, 240],
68
+ "khaki" => [240, 230, 140],
69
+ "lavender" => [230, 230, 250],
70
+ "lavenderblush" => [255, 240, 245],
71
+ "lawngreen" => [124, 252, 0],
72
+ "lemonchiffon" => [255, 250, 205],
73
+ "lightblue" => [173, 216, 230],
74
+ "lightcoral" => [240, 128, 128],
75
+ "lightcyan" => [224, 255, 255],
76
+ "lightgoldenrodyellow" => [250, 250, 210],
77
+ "lightgray" => [211, 211, 211],
78
+ "lightgrey" => [211, 211, 211],
79
+ "lightgreen" => [144, 238, 144],
80
+ "lightpink" => [255, 182, 193],
81
+ "lightsalmon" => [255, 160, 122],
82
+ "lightseagreen" => [32, 178, 170],
83
+ "lightskyblue" => [135, 206, 250],
84
+ "lightslategray" => [119, 136, 153],
85
+ "lightslategrey" => [119, 136, 153],
86
+ "lightsteelblue" => [176, 196, 222],
87
+ "lightyellow" => [255, 255, 224],
88
+ "lime" => [0, 255, 0],
89
+ "limegreen" => [50, 205, 50],
90
+ "linen" => [250, 240, 230],
91
+ "magenta" => [255, 0, 255],
92
+ "maroon" => [128, 0, 0],
93
+ "mediumaquamarine" => [102, 205, 170],
94
+ "mediumblue" => [0, 0, 205],
95
+ "mediumorchid" => [186, 85, 211],
96
+ "mediumpurple" => [147, 112, 219],
97
+ "mediumseagreen" => [60, 179, 113],
98
+ "mediumslateblue" => [123, 104, 238],
99
+ "mediumspringgreen" => [0, 250, 154],
100
+ "mediumturquoise" => [72, 209, 204],
101
+ "mediumvioletred" => [199, 21, 133],
102
+ "midnightblue" => [25, 25, 112],
103
+ "mintcream" => [245, 255, 250],
104
+ "mistyrose" => [255, 228, 225],
105
+ "moccasin" => [255, 228, 181],
106
+ "navajowhite" => [255, 222, 173],
107
+ "navy" => [0, 0, 128],
108
+ "oldlace" => [253, 245, 230],
109
+ "olive" => [128, 128, 0],
110
+ "olivedrab" => [107, 142, 35],
111
+ "orange" => [255, 165, 0],
112
+ "orangered" => [255, 69, 0],
113
+ "orchid" => [218, 112, 214],
114
+ "palegoldenrod" => [238, 232, 170],
115
+ "palegreen" => [152, 251, 152],
116
+ "paleturquoise" => [175, 238, 238],
117
+ "palevioletred" => [219, 112, 147],
118
+ "papayawhip" => [255, 239, 213],
119
+ "peachpuff" => [255, 218, 185],
120
+ "peru" => [205, 133, 63],
121
+ "pink" => [255, 192, 203],
122
+ "plum" => [221, 160, 221],
123
+ "powderblue" => [176, 224, 230],
124
+ "purple" => [128, 0, 128],
125
+ "red" => [255, 0, 0],
126
+ "rosybrown" => [188, 143, 143],
127
+ "royalblue" => [65, 105, 225],
128
+ "saddlebrown" => [139, 69, 19],
129
+ "salmon" => [250, 128, 114],
130
+ "sandybrown" => [244, 164, 96],
131
+ "seagreen" => [46, 139, 87],
132
+ "seashell" => [255, 245, 238],
133
+ "sienna" => [160, 82, 45],
134
+ "silver" => [192, 192, 192],
135
+ "skyblue" => [135, 206, 235],
136
+ "slateblue" => [106, 90, 205],
137
+ "slategray" => [112, 128, 144],
138
+ "slategrey" => [112, 128, 144],
139
+ "snow" => [255, 250, 250],
140
+ "springgreen" => [0, 255, 127],
141
+ "steelblue" => [70, 130, 180],
142
+ "tan" => [210, 180, 140],
143
+ "teal" => [0, 128, 128],
144
+ "thistle" => [216, 191, 216],
145
+ "tomato" => [255, 99, 71],
146
+ "turquoise" => [64, 224, 208],
147
+ "violet" => [238, 130, 238],
148
+ "wheat" => [245, 222, 179],
149
+ "white" => [255, 255, 255],
150
+ "whitesmoke" => [245, 245, 245],
151
+ "yellow" => [255, 255, 0],
152
+ "yellowgreen" => [154, 205, 50],
153
+ "rebeccapurple" => [102, 51, 153]
154
+ }.freeze
155
+ end
156
+ end
@@ -6,6 +6,7 @@ module ImageUtil
6
6
  super(args)
7
7
  end
8
8
 
9
+ autoload :CSS_COLORS, "image_util/color/css_colors"
9
10
  def r = self[0]
10
11
  def g = self[1]
11
12
  def b = self[2]
@@ -69,13 +70,12 @@ module ImageUtil
69
70
  new($1.to_i(16), $2.to_i(16), $3.to_i(16))
70
71
  when /\A#(\h{2})(\h{2})(\h{2})(\h{2})\z/
71
72
  new($1.to_i(16), $2.to_i(16), $3.to_i(16), $4.to_i(16))
72
- when "black" then new(0, 0, 0)
73
- when "white" then new(255, 255, 255)
74
- when "red" then new(255, 0, 0)
75
- when "lime" then new(0, 255, 0)
76
- when "blue" then new(0, 0, 255)
77
73
  else
78
- raise ArgumentError, "wrong String passed as color (passed: #{value.inspect})"
74
+ if (rgb = CSS_COLORS[value.downcase])
75
+ new(*rgb)
76
+ else
77
+ raise ArgumentError, "wrong String passed as color (passed: #{value.inspect})"
78
+ end
79
79
  end
80
80
  when Symbol
81
81
  from(value.to_s)
@@ -62,7 +62,29 @@ module ImageUtil
62
62
  end
63
63
  end
64
64
 
65
- define_immutable_version :draw_function, :draw_line
65
+ def draw_circle!(center, radius, color = Color[:black], view: View::Interpolated)
66
+ fp = self.view(view)
67
+ cx, cy = center
68
+ min_x = (cx - radius).ceil
69
+ max_x = (cx + radius).floor
70
+ min_x.upto(max_x) do |x|
71
+ dy = Math.sqrt(radius * radius - (x - cx)**2)
72
+ fp[x, cy + dy] = color
73
+ fp[x, cy - dy] = color
74
+ end
75
+
76
+ min_y = (cy - radius).ceil
77
+ max_y = (cy + radius).floor
78
+ min_y.upto(max_y) do |y|
79
+ dx = Math.sqrt(radius * radius - (y - cy)**2)
80
+ fp[cx + dx, y] = color
81
+ fp[cx - dx, y] = color
82
+ end
83
+
84
+ self
85
+ end
86
+
87
+ define_immutable_version :draw_function, :draw_line, :draw_circle
66
88
 
67
89
  private
68
90
 
@@ -136,9 +136,11 @@ module ImageUtil
136
136
  @buf.set(location, value)
137
137
  end
138
138
  else
139
- _, locations = location_expand(location)
140
- locations.each do |loc|
141
- self[*loc] = value
139
+ sizes, locations = location_expand(location)
140
+ if value.is_a?(Image)
141
+ paste!(value.resize(*sizes), *locations.first)
142
+ else
143
+ locations.each { |loc| self[*loc] = value }
142
144
  end
143
145
  end
144
146
  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.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: image_util
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - hmdne
@@ -38,6 +38,12 @@ files:
38
38
  - LICENSE.txt
39
39
  - README.md
40
40
  - Rakefile
41
+ - docs/samples/background.png
42
+ - docs/samples/dither.png
43
+ - docs/samples/draw.png
44
+ - docs/samples/paste.png
45
+ - docs/samples/resize.png
46
+ - docs/samples/sixel.png
41
47
  - lib/image_util.rb
42
48
  - lib/image_util/codec.rb
43
49
  - lib/image_util/codec/_guard.rb
@@ -48,6 +54,7 @@ files:
48
54
  - lib/image_util/codec/pam.rb
49
55
  - lib/image_util/codec/ruby_sixel.rb
50
56
  - lib/image_util/color.rb
57
+ - lib/image_util/color/css_colors.rb
51
58
  - lib/image_util/filter.rb
52
59
  - lib/image_util/filter/_mixin.rb
53
60
  - lib/image_util/filter/background.rb