okmain 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 +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +29 -0
- data/lib/okmain/config.rb +12 -2
- data/lib/okmain/distance_mask.rb +13 -12
- data/lib/okmain/kmeans.rb +7 -6
- data/lib/okmain/oklab.rb +0 -10
- data/lib/okmain/sampler.rb +23 -13
- data/lib/okmain/scorer.rb +3 -2
- data/lib/okmain/version.rb +1 -1
- data/lib/okmain/xoshiro.rb +66 -0
- data/lib/okmain.rb +2 -1
- data/okmain.gemspec +5 -1
- metadata +33 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c425619a6eb56aa2ab047e664de0d648f269decefd2c98e0f761e59b6a0b386
|
|
4
|
+
data.tar.gz: 6d9a8f85c673daf3b64da8c8758887b86f581b6110feb32ca8533fab152ef7b4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8194c83b670eef20447272468737948914a0a845576f8e4a7b288b2c7389d5ed4c6a8b38eb6abd9b01d20e19fdd520bf2fd1155693e0eb4fe8374b46d5fc9762
|
|
7
|
+
data.tar.gz: a3fb6d6f38a734d12911f94b0c6c63a91ef385dfb76eaaa70a94b1c260138004d20bb357831df70c2ced7178d36a11406758e02567d5673baa49d929360bb1f7
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project might adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) if I ever get around to releasing a 1.0, but I wouldn't count on that happening any time soon.
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
- May try to make KMeans faster: now that I have harvested the low-hanging fruit in sampler performance, kmeans is almost 90% of runtime on my test images.
|
|
11
|
+
- Adding a suite of test images and automating benchmarking new versions against old versions for speed and correctness would be cool.
|
|
12
|
+
- Even starting this project was AI psychosis. Would have been smarter to see if I like the output of the algorithm for the one specific thing I need this for first, no?
|
|
13
|
+
|
|
14
|
+
## [0.2.0] - 2026-04-25
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- RNG replaced with a Xoshiro256++ implementation matching the upstream Rust
|
|
18
|
+
`okmain` crate, so results closely track the reference implementation.
|
|
19
|
+
Output for a given image will differ from 0.1.0.
|
|
20
|
+
- Sampler, distance mask, scorer, and Oklab conversion paths reworked for
|
|
21
|
+
speed and closer parity with the Rust implementation.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `Okmain::Xoshiro` PRNG (`lib/okmain/xoshiro.rb`).
|
|
25
|
+
- `bench.rb` benchmark script and a `rust_compare/` companion crate for
|
|
26
|
+
apples-to-apples timing and output comparisons against the Rust crate.
|
|
27
|
+
- `Rakefile` exposing the standard `bundler/gem_tasks` (`build`, `install`,
|
|
28
|
+
`release`).
|
|
29
|
+
- Development dependencies: `benchmark ~> 0.4`, `rake ~> 13.0`.
|
|
30
|
+
|
|
31
|
+
## [0.1.0] - 2026-04-24
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- Initial port of the Rust `okmain` crate to Ruby: extracts up to 4 dominant
|
|
35
|
+
colors from an image via adaptive K-means clustering in Oklab space.
|
|
36
|
+
- `ruby-vips`-based image loading with block-average downsampling.
|
|
37
|
+
- Dual MIT / Apache-2.0 licensing matching the upstream crate.
|
|
38
|
+
- README with install and usage examples.
|
|
39
|
+
|
|
40
|
+
[Unreleased]: https://github.com/midnightmonster/okmain-ruby/compare/v0.2.0...HEAD
|
|
41
|
+
[0.2.0]: https://github.com/midnightmonster/okmain-ruby/compare/v0.1.0...v0.2.0
|
|
42
|
+
[0.1.0]: https://github.com/midnightmonster/okmain-ruby/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -62,6 +62,35 @@ Colors are returned as `[r, g, b]` arrays (0-255), sorted by score descending. T
|
|
|
62
62
|
3. **Score** — Centroids ranked by a weighted combination of pixel count (with center-priority mask) and chroma
|
|
63
63
|
4. **Return** — Up to 4 `[r, g, b]` arrays sorted by score
|
|
64
64
|
|
|
65
|
+
## Comparing with the Rust implementation
|
|
66
|
+
|
|
67
|
+
A small Rust binary is included in `rust_compare/` for verifying results against the original [okmain](https://github.com/si14/okmain) crate. Requires [Rust](https://rustup.rs/) 1.93+.
|
|
68
|
+
|
|
69
|
+
Build it once:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
cd rust_compare
|
|
73
|
+
cargo build --release
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then compare outputs on any image:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Rust
|
|
80
|
+
rust_compare/target/release/rust_compare path/to/photo.jpg
|
|
81
|
+
|
|
82
|
+
# Ruby
|
|
83
|
+
bundle exec ruby -e "require 'okmain'; pp Okmain.colors('path/to/photo.jpg')"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Both print `[r, g, b]` arrays in the same order, so results can be diffed directly.
|
|
87
|
+
|
|
88
|
+
For benchmarking (against rust, but mostly to catch progress or regression in the gem), use:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bundle exec ruby bench.rb path/to/image.png
|
|
92
|
+
```
|
|
93
|
+
|
|
65
94
|
## Credits
|
|
66
95
|
|
|
67
96
|
Based on the Rust [okmain](https://github.com/si14/okmain) crate (v0.2.0) by Dan Groshev and okmain contributors.
|
data/lib/okmain/config.rb
CHANGED
|
@@ -2,14 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Okmain
|
|
4
4
|
class Config
|
|
5
|
-
attr_reader :chroma_weight, :mask_weighted_counts_weight
|
|
5
|
+
attr_reader :chroma_weight, :mask_weighted_counts_weight,
|
|
6
|
+
:mask_weight, :mask_saturated_threshold
|
|
6
7
|
|
|
7
|
-
def initialize(
|
|
8
|
+
def initialize(
|
|
9
|
+
chroma_weight: 0.7,
|
|
10
|
+
mask_weighted_counts_weight: 0.3,
|
|
11
|
+
mask_weight: 1.0,
|
|
12
|
+
mask_saturated_threshold: 0.3
|
|
13
|
+
)
|
|
8
14
|
raise ArgumentError, "chroma_weight must be between 0 and 1" unless (0.0..1.0).cover?(chroma_weight)
|
|
9
15
|
raise ArgumentError, "mask_weighted_counts_weight must be between 0 and 1" unless (0.0..1.0).cover?(mask_weighted_counts_weight)
|
|
16
|
+
raise ArgumentError, "mask_weight must be between 0 and 1" unless (0.0..1.0).cover?(mask_weight)
|
|
17
|
+
raise ArgumentError, "mask_saturated_threshold must be between 0 and 0.5 (exclusive)" unless mask_saturated_threshold >= 0.0 && mask_saturated_threshold < 0.5
|
|
10
18
|
|
|
11
19
|
@chroma_weight = chroma_weight.to_f
|
|
12
20
|
@mask_weighted_counts_weight = mask_weighted_counts_weight.to_f
|
|
21
|
+
@mask_weight = mask_weight.to_f
|
|
22
|
+
@mask_saturated_threshold = mask_saturated_threshold.to_f
|
|
13
23
|
end
|
|
14
24
|
end
|
|
15
25
|
end
|
data/lib/okmain/distance_mask.rb
CHANGED
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
module Okmain
|
|
4
4
|
module DistanceMask
|
|
5
|
-
SATURATED_THRESHOLD = 0.75
|
|
6
|
-
|
|
7
5
|
module_function
|
|
8
6
|
|
|
9
|
-
# Returns a flat Array of mask values (0.1..1.0) for each pixel,
|
|
7
|
+
# Returns a flat Array of mask values (0.1..1.0) for each pixel,
|
|
8
|
+
# matching the Rust rectangular mask exactly.
|
|
10
9
|
# Center pixels get 1.0, corner pixels get 0.1, with a linear ramp.
|
|
11
|
-
def compute(width, height)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
def compute(width, height, saturated_threshold)
|
|
11
|
+
w = width.to_f
|
|
12
|
+
h = height.to_f
|
|
13
|
+
middle_x = w / 2.0
|
|
14
|
+
middle_y = h / 2.0
|
|
15
|
+
x_threshold = w * saturated_threshold
|
|
16
|
+
y_threshold = h * saturated_threshold
|
|
16
17
|
|
|
17
18
|
mask = Array.new(width * height)
|
|
18
19
|
y = 0
|
|
19
20
|
while y < height
|
|
20
|
-
|
|
21
|
-
my =
|
|
21
|
+
yf = y.to_f
|
|
22
|
+
my = yf <= middle_y ? yf : h - yf
|
|
22
23
|
y_contrib = if y_threshold > 0.0
|
|
23
24
|
v = 0.1 + 0.9 * (my / y_threshold)
|
|
24
25
|
v < 1.0 ? v : 1.0
|
|
@@ -29,8 +30,8 @@ module Okmain
|
|
|
29
30
|
row_offset = y * width
|
|
30
31
|
x = 0
|
|
31
32
|
while x < width
|
|
32
|
-
|
|
33
|
-
mx =
|
|
33
|
+
xf = x.to_f
|
|
34
|
+
mx = xf <= middle_x ? xf : w - xf
|
|
34
35
|
x_contrib = if x_threshold > 0.0
|
|
35
36
|
v = 0.1 + 0.9 * (mx / x_threshold)
|
|
36
37
|
v < 1.0 ? v : 1.0
|
data/lib/okmain/kmeans.rb
CHANGED
|
@@ -7,13 +7,14 @@ module Okmain
|
|
|
7
7
|
LLOYDS_CONVERGENCE_TOL = 1e-3
|
|
8
8
|
SIMILAR_CLUSTER_DISTANCE_SQ = 0.005
|
|
9
9
|
KMEANSPP_CANDIDATES = 3
|
|
10
|
+
RANDOM_SEED = 314159
|
|
10
11
|
|
|
11
12
|
module_function
|
|
12
13
|
|
|
13
14
|
# Returns [centroids, assignments] where centroids is Array of [L, a, b]
|
|
14
15
|
# and assignments is Array of centroid indices per pixel.
|
|
15
16
|
def cluster(pixels, k: MAX_CENTROIDS)
|
|
16
|
-
rng =
|
|
17
|
+
rng = Xoshiro256PlusPlus.new(RANDOM_SEED)
|
|
17
18
|
n = pixels.size
|
|
18
19
|
return [pixels.dup, Array.new(n) { |i| i }] if n <= k
|
|
19
20
|
|
|
@@ -45,7 +46,7 @@ module Okmain
|
|
|
45
46
|
# K-means++ initialization with 3 candidates per step
|
|
46
47
|
def init_plusplus(pixels, k, rng)
|
|
47
48
|
n = pixels.size
|
|
48
|
-
centroids = [pixels[rng.
|
|
49
|
+
centroids = [pixels[rng.random_range(n)].dup]
|
|
49
50
|
|
|
50
51
|
dist_sq = Array.new(n, Float::INFINITY)
|
|
51
52
|
|
|
@@ -64,13 +65,13 @@ module Okmain
|
|
|
64
65
|
best_potential = Float::INFINITY
|
|
65
66
|
|
|
66
67
|
KMEANSPP_CANDIDATES.times do
|
|
67
|
-
# Weighted random selection
|
|
68
|
-
r = rng.
|
|
68
|
+
# Weighted random selection (matching Rust's sample_by_distance)
|
|
69
|
+
r = rng.random_f32 * total
|
|
69
70
|
cumulative = 0.0
|
|
70
71
|
idx = 0
|
|
71
72
|
while idx < n
|
|
72
73
|
cumulative += dist_sq[idx]
|
|
73
|
-
if cumulative
|
|
74
|
+
if cumulative > r
|
|
74
75
|
break
|
|
75
76
|
end
|
|
76
77
|
idx += 1
|
|
@@ -143,7 +144,7 @@ module Okmain
|
|
|
143
144
|
while c < k
|
|
144
145
|
if counts[c] == 0
|
|
145
146
|
# Reassign empty cluster to random data point
|
|
146
|
-
ri = rng.
|
|
147
|
+
ri = rng.random_range(n)
|
|
147
148
|
new_centroids[c] = pixels[ri].dup
|
|
148
149
|
else
|
|
149
150
|
inv = 1.0 / counts[c]
|
data/lib/okmain/oklab.rb
CHANGED
|
@@ -38,16 +38,6 @@ module Okmain
|
|
|
38
38
|
[r, g, b_]
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
# sRGB component (0..255) → linear (0..1)
|
|
42
|
-
def srgb_to_linear(c)
|
|
43
|
-
c = c / 255.0
|
|
44
|
-
if c <= 0.04045
|
|
45
|
-
c / 12.92
|
|
46
|
-
else
|
|
47
|
-
((c + 0.055) / 1.055)**2.4
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
41
|
# Linear (0..1) → sRGB component (0..255), clamped
|
|
52
42
|
def linear_to_srgb(c)
|
|
53
43
|
c = c.clamp(0.0, 1.0)
|
data/lib/okmain/sampler.rb
CHANGED
|
@@ -26,27 +26,37 @@ module Okmain
|
|
|
26
26
|
block_size = compute_block_size(total)
|
|
27
27
|
|
|
28
28
|
if block_size > 1
|
|
29
|
+
# Claude wanted to do this in Ruby for a more-exact rust match, because apparently image.shrink
|
|
30
|
+
# uses a slightly different algorithm, but an occasional 1 or 2 RGB value difference is a small
|
|
31
|
+
# price for the huge speedup in letting VIPS do it for us.
|
|
29
32
|
image = image.shrink(block_size, block_size)
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
width = image.width
|
|
33
36
|
height = image.height
|
|
34
37
|
|
|
38
|
+
# Claude's approach: Doing the colourspace translation ourselves
|
|
39
|
+
# ==============================================================
|
|
35
40
|
# Extract float pixel data [r, g, b] in linear space
|
|
36
|
-
floats = image.write_to_memory.unpack("f*")
|
|
37
|
-
bands = image.bands
|
|
38
|
-
pixel_count = width * height
|
|
41
|
+
# floats = image.write_to_memory.unpack("f*")
|
|
42
|
+
# bands = image.bands
|
|
43
|
+
# pixel_count = width * height
|
|
39
44
|
|
|
40
|
-
pixels = Array.new(pixel_count)
|
|
41
|
-
i = 0
|
|
42
|
-
while i < pixel_count
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
end
|
|
45
|
+
# pixels = Array.new(pixel_count)
|
|
46
|
+
# i = 0
|
|
47
|
+
# while i < pixel_count
|
|
48
|
+
# offset = i * bands
|
|
49
|
+
# r = floats[offset]
|
|
50
|
+
# g = floats[offset + 1]
|
|
51
|
+
# b = floats[offset + 2]
|
|
52
|
+
# pixels[i] = Oklab.linear_rgb_to_oklab(r, g, b)
|
|
53
|
+
# i += 1
|
|
54
|
+
# end
|
|
55
|
+
|
|
56
|
+
# My approach: more than twice as fast to let VIPS do it for us
|
|
57
|
+
# =============================================================
|
|
58
|
+
image = image.colourspace(:oklab)
|
|
59
|
+
pixels = image.write_to_memory.unpack("f*").each_slice(3).to_a
|
|
50
60
|
|
|
51
61
|
[pixels, width, height]
|
|
52
62
|
end
|
data/lib/okmain/scorer.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Okmain
|
|
|
10
10
|
def score(centroids, assignments, mask, config)
|
|
11
11
|
k = centroids.size
|
|
12
12
|
n = assignments.size
|
|
13
|
-
mask_w = config.
|
|
13
|
+
mask_w = config.mask_weight
|
|
14
|
+
counts_w = config.mask_weighted_counts_weight
|
|
14
15
|
chroma_w = config.chroma_weight
|
|
15
16
|
|
|
16
17
|
# Weighted counts per centroid
|
|
@@ -37,7 +38,7 @@ module Okmain
|
|
|
37
38
|
# Score each centroid
|
|
38
39
|
scored = centroids.each_with_index.map do |centroid, idx|
|
|
39
40
|
chroma = Math.sqrt(centroid[1] * centroid[1] + centroid[2] * centroid[2]) / MAX_SRGB_OKLAB_CHROMA
|
|
40
|
-
score =
|
|
41
|
+
score = counts_w * weighted_counts[idx] + chroma_w * chroma
|
|
41
42
|
[score, centroid]
|
|
42
43
|
end
|
|
43
44
|
|
data/lib/okmain/version.rb
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Okmain
|
|
4
|
+
# Xoshiro256++ PRNG, matching Rust's rand_xoshiro crate exactly.
|
|
5
|
+
# Seeding via SplitMix64 matches Rust's SeedableRng::seed_from_u64.
|
|
6
|
+
class Xoshiro256PlusPlus
|
|
7
|
+
MASK64 = (1 << 64) - 1
|
|
8
|
+
|
|
9
|
+
def initialize(seed)
|
|
10
|
+
# SplitMix64 seeding (matches Rust's SeedableRng::seed_from_u64)
|
|
11
|
+
sm = seed & MASK64
|
|
12
|
+
@s = Array.new(4) do
|
|
13
|
+
sm = (sm + 0x9e3779b97f4a7c15) & MASK64
|
|
14
|
+
z = sm
|
|
15
|
+
z = ((z ^ (z >> 30)) * 0xbf58476d1ce4e5b9) & MASK64
|
|
16
|
+
z = ((z ^ (z >> 27)) * 0x94d049bb133111eb) & MASK64
|
|
17
|
+
z ^ (z >> 31)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Generate next u64
|
|
22
|
+
def next_u64
|
|
23
|
+
s = @s
|
|
24
|
+
result = (rotl(s[0] + s[3], 23) + s[0]) & MASK64
|
|
25
|
+
t = (s[1] << 17) & MASK64
|
|
26
|
+
s[2] ^= s[0]
|
|
27
|
+
s[3] ^= s[1]
|
|
28
|
+
s[1] ^= s[2]
|
|
29
|
+
s[0] ^= s[3]
|
|
30
|
+
s[2] ^= t
|
|
31
|
+
s[3] = rotl(s[3], 45)
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Uniform integer in 0...range, using Lemire's method (matching Rust's rand crate)
|
|
36
|
+
def random_range(range)
|
|
37
|
+
full = next_u64 * range
|
|
38
|
+
hi = full >> 64
|
|
39
|
+
lo = full & MASK64
|
|
40
|
+
if lo < range
|
|
41
|
+
threshold = (MASK64 - range + 1) % range # (2^64 - range) % range
|
|
42
|
+
while lo < threshold
|
|
43
|
+
full = next_u64 * range
|
|
44
|
+
hi = full >> 64
|
|
45
|
+
lo = full & MASK64
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
hi
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Uniform f32 in [0, 1), matching Rust's rand StandardUniform for f32.
|
|
52
|
+
# Xoshiro256++.next_u32() returns next_u64() >> 32 (upper bits).
|
|
53
|
+
# Then StandardUniform takes top 24 bits of the u32.
|
|
54
|
+
def random_f32
|
|
55
|
+
u64 = next_u64
|
|
56
|
+
u32 = u64 >> 32 # Rust's next_u32() for Xoshiro256++
|
|
57
|
+
(u32 >> 8).to_f / 16777216.0 # top 24 bits / 2^24
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def rotl(x, k)
|
|
63
|
+
((x << k) | (x >> (64 - k))) & MASK64
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/okmain.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "okmain/config"
|
|
|
5
5
|
require_relative "okmain/oklab"
|
|
6
6
|
require_relative "okmain/sampler"
|
|
7
7
|
require_relative "okmain/distance_mask"
|
|
8
|
+
require_relative "okmain/xoshiro"
|
|
8
9
|
require_relative "okmain/kmeans"
|
|
9
10
|
require_relative "okmain/scorer"
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ module Okmain
|
|
|
18
19
|
# @return [Array<Array<Integer>>] up to 4 [r, g, b] arrays sorted by score
|
|
19
20
|
def colors(input, config: Config.new)
|
|
20
21
|
pixels, width, height = Sampler.sample(input)
|
|
21
|
-
mask = DistanceMask.compute(width, height)
|
|
22
|
+
mask = DistanceMask.compute(width, height, config.mask_saturated_threshold)
|
|
22
23
|
centroids, assignments = KMeans.cluster(pixels)
|
|
23
24
|
Scorer.score(centroids, assignments, mask, config)
|
|
24
25
|
end
|
data/okmain.gemspec
CHANGED
|
@@ -13,9 +13,13 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
|
|
14
14
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
15
15
|
spec.metadata["source_code_uri"] = spec.homepage
|
|
16
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
16
17
|
|
|
17
|
-
spec.files = Dir["lib/**/*.rb"] + ["okmain.gemspec", "README.md", "LICENSE-MIT", "LICENSE-APACHE"]
|
|
18
|
+
spec.files = Dir["lib/**/*.rb"] + ["okmain.gemspec", "README.md", "CHANGELOG.md", "LICENSE-MIT", "LICENSE-APACHE"]
|
|
18
19
|
spec.require_paths = ["lib"]
|
|
19
20
|
|
|
20
21
|
spec.add_dependency "ruby-vips", "~> 2.1"
|
|
22
|
+
|
|
23
|
+
spec.add_development_dependency "benchmark", "~> 0.4"
|
|
24
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
21
25
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: okmain
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Joshua Paine
|
|
@@ -23,10 +23,39 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: benchmark
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.4'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.4'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
26
54
|
executables: []
|
|
27
55
|
extensions: []
|
|
28
56
|
extra_rdoc_files: []
|
|
29
57
|
files:
|
|
58
|
+
- CHANGELOG.md
|
|
30
59
|
- LICENSE-APACHE
|
|
31
60
|
- LICENSE-MIT
|
|
32
61
|
- README.md
|
|
@@ -38,6 +67,7 @@ files:
|
|
|
38
67
|
- lib/okmain/sampler.rb
|
|
39
68
|
- lib/okmain/scorer.rb
|
|
40
69
|
- lib/okmain/version.rb
|
|
70
|
+
- lib/okmain/xoshiro.rb
|
|
41
71
|
- okmain.gemspec
|
|
42
72
|
homepage: https://github.com/midnightmonster/okmain-ruby
|
|
43
73
|
licenses:
|
|
@@ -46,6 +76,7 @@ licenses:
|
|
|
46
76
|
metadata:
|
|
47
77
|
homepage_uri: https://github.com/midnightmonster/okmain-ruby
|
|
48
78
|
source_code_uri: https://github.com/midnightmonster/okmain-ruby
|
|
79
|
+
changelog_uri: https://github.com/midnightmonster/okmain-ruby/blob/main/CHANGELOG.md
|
|
49
80
|
rdoc_options: []
|
|
50
81
|
require_paths:
|
|
51
82
|
- lib
|
|
@@ -60,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
60
91
|
- !ruby/object:Gem::Version
|
|
61
92
|
version: '0'
|
|
62
93
|
requirements: []
|
|
63
|
-
rubygems_version:
|
|
94
|
+
rubygems_version: 4.0.6
|
|
64
95
|
specification_version: 4
|
|
65
96
|
summary: Extract dominant colors from images using adaptive K-means in Oklab space
|
|
66
97
|
test_files: []
|