Skip to content

Commit a64dbd5

Browse files
committed
Inline dhash-vips
It has post-install errors on Ruby 4.0. Replace it with pure ruby-vips with no C extension.
1 parent 9489387 commit a64dbd5

2 files changed

Lines changed: 48 additions & 8 deletions

File tree

image_processing.gemspec

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,4 @@ Gem::Specification.new do |spec|
2323
spec.add_development_dependency "minitest", "~> 5.8"
2424
spec.add_development_dependency "minitest-hooks", ">= 1.4.2"
2525
spec.add_development_dependency "minispec-metadata"
26-
spec.add_development_dependency "dhash-vips" unless RUBY_ENGINE == "jruby"
2726
end

test/test_helper.rb

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,53 @@
1010

1111
ENV["VIPS_WARNING"] = "0" # disable libvips warnings
1212

13-
require "dhash-vips" unless RUBY_ENGINE == "jruby"
1413
require "vips"
1514
require "mini_magick"
1615

16+
# Faithful port of DHashVips::IDHash, inlined to avoid the upstream gem's
17+
# fragile install-time test (which breaks on Ruby 4.0) and to make
18+
# similarity assertions available on JRuby as well.
19+
module PerceptualHash
20+
module_function
21+
22+
SIZE = 8
23+
24+
def fingerprint(path)
25+
image = Vips::Image.thumbnail(path, SIZE, height: SIZE, size: :force)
26+
image = image.flatten(background: 255) if image.has_alpha?
27+
image = image.colourspace("b-w")[0]
28+
rows = image.to_a.map(&:flatten)
29+
d1, i1 = signs_and_intensities(rows)
30+
d2, i2 = signs_and_intensities(rows.transpose)
31+
(((((i1 << SIZE * SIZE) | i2) << SIZE * SIZE) | d1) << SIZE * SIZE) | d2
32+
end
33+
34+
def distance(a, b)
35+
((a ^ b) & ((a | b) >> 2 * SIZE * SIZE)).to_s(2).count("1")
36+
end
37+
38+
def signs_and_intensities(matrix)
39+
differences = matrix.zip(matrix.rotate(1)).flat_map do |r1, r2|
40+
r1.zip(r2).map { |a, b| a - b }
41+
end
42+
threshold = median(differences.map(&:abs).sort)
43+
signs = differences.inject(0) { |bits, d| (bits << 1) | (d < 0 ? 1 : 0) }
44+
intensities = differences.inject(0) { |bits, d| (bits << 1) | (d.abs >= threshold ? 1 : 0) }
45+
[signs, intensities]
46+
end
47+
48+
def median(sorted)
49+
h = sorted.size / 2
50+
return sorted[h] if sorted[h] != sorted[h - 1]
51+
right = sorted.dup
52+
left = right.shift(h)
53+
right.shift if right.size > left.size
54+
return right.first if left.last != right.first
55+
return right.uniq[1] if left.count(left.last) > right.count(right.first)
56+
left.last
57+
end
58+
end
59+
1760
class Minitest::Test
1861
def fixture_image(name)
1962
File.open("test/fixtures/#{name}", "rb")
@@ -27,12 +70,10 @@ def copy_to_tempfile(io, extension = nil)
2770
end
2871

2972
def assert_similar(image1, image2)
30-
skip "dhash-vips not available on JRuby" if RUBY_ENGINE == "jruby"
3173
assert_operator distance(image1, image2), :<=, 3
3274
end
3375

3476
def refute_similar(image1, image2)
35-
skip "dhash-vips not available on JRuby" if RUBY_ENGINE == "jruby"
3677
assert_operator distance(image1, image2), :>, 3
3778
end
3879

@@ -47,10 +88,10 @@ def assert_type(type, file)
4788
private
4889

4990
def distance(image1, image2)
50-
hash1 = DHashVips::IDHash.fingerprint(image1.path)
51-
hash2 = DHashVips::IDHash.fingerprint(image2.path)
52-
53-
DHashVips::IDHash.distance hash1, hash2
91+
PerceptualHash.distance(
92+
PerceptualHash.fingerprint(image1.path),
93+
PerceptualHash.fingerprint(image2.path),
94+
)
5495
end
5596
end
5697

0 commit comments

Comments
 (0)