dhash-vips 0.2.3.0 → 0.2.4.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 (10) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +0 -0
  3. data/bin/idhash +57 -0
  4. data/dhash-vips.gemspec +7 -5
  5. data/extconf.rb +13 -19
  6. data/idhash.c +30 -24
  7. metadata +15 -16
  8. data/Gemfile +0 -12
  9. data/common.rb +0 -11
  10. data/test.rb +0 -126
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce70f825a89117f682dad1e5ae6400ec3cd430d08d8bb34b36719dff4905894e
4
- data.tar.gz: 17ff221fad7650632cefc04f7421efc79b4f0fe0f2f884d6f1019fc82d360c94
3
+ metadata.gz: 6b9bb7ce3d33457aa4a9562c223fc86404304e6a15d92db631462e7f3d3ba1cb
4
+ data.tar.gz: '09c3df795276527873062983d954ebbdeb07125eccca88551a2fd56f95e13be2'
5
5
  SHA512:
6
- metadata.gz: '019fa1a1b89a5e734332eb1993ea4ccd9dd18b6add3677f065c41cbf0b624986220a094ee5e055cf4f965b56292abe9275ee96fdcbdae53481ea557b0317ad46'
7
- data.tar.gz: 4fa0705056bc27ef272b9a4ca93657bada9646486e0a9532b21d4f53f8d23847188e0b156d66c105054d9a0f2745be4be89b3c9b0244e8e52bc593abd8757a96
6
+ metadata.gz: 48e976f177d8fe6dcd010bc0501ec862d2c2bfdad45bfd17e1f6c2f03abd7481e4930683018c7ea879b9c7b339e8676460bea135f943314d25d5dc1b70f76e04
7
+ data.tar.gz: 23a9ea2f316a226872940e7f0e1dfb3547a01db4ab0c24d981ba4edc68934a02a54be4007aeb7015977c72f7fbf90ead4d092113b45a77c4076feb07ebb421c0
data/LICENSE.txt CHANGED
File without changes
data/bin/idhash ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ Signal.trap(:INT){ abort "\n(interrupted by SIGINT)" }
3
+
4
+ unless 2 == ARGV.size
5
+ puts "this command is to compare two images"
6
+ puts "usage: #{__FILE__} <path to image1> <path to image2>"
7
+ exit
8
+ end
9
+
10
+ require_relative "../lib/dhash-vips"
11
+ ha, hb = ARGV.map{ |filename| DHashVips::IDHash.fingerprint filename }
12
+ puts "distance: #{d1 = DHashVips::IDHash.distance ha, hb}"
13
+ size = 2 ** 3
14
+ shift = 2 * size * size
15
+ ai = ha >> shift
16
+ ad = ha - (ai << shift)
17
+ bi = hb >> shift
18
+ bd = hb - (bi << shift)
19
+
20
+ _127 = shift - 1
21
+ _63 = size * size - 1
22
+ # width = 800
23
+ # height = 800
24
+
25
+ d2 = 0
26
+ a, b = [[ad, ai, ARGV[0]], [bd, bi, ARGV[1]]].map do |xd, xi, path|
27
+ puts File.basename path
28
+ hor = Array.new(size){Array.new(size){" "}}
29
+ ver = Array.new(size){Array.new(size){" "}}
30
+ _127.downto(0).each_with_index do |i, ii|
31
+ if i > _63
32
+ y, x = (_127 - i).divmod size
33
+ else
34
+ x, y = (_63 - i).divmod size
35
+ end
36
+ if xi[i] > 0
37
+ target, c = if i > _63
38
+ [ver, %w{ v ^ }[xd[i]]]
39
+ else
40
+ [hor, %w{ > < }[xd[i]]]
41
+ end
42
+ target[y][x] = c
43
+ end
44
+ if ai[i] + bi[i] > 0 && ad[i] != bd[i]
45
+ d2 += 1
46
+ target = if i > _63
47
+ ver
48
+ else
49
+ hor
50
+ end
51
+ target[y][x] = "\e[7m#{target[y][x]}\e[27m"
52
+ end
53
+ end
54
+ hor.zip(ver).each{ |_| puts _.join " " }
55
+ end
56
+ abort "something went wrong" unless d1 * 2 == d2
57
+ puts "OK"
data/dhash-vips.gemspec CHANGED
@@ -1,17 +1,19 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "dhash-vips"
3
- spec.version = "0.2.3.0"
3
+ spec.version = "0.2.4.0"
4
4
  spec.summary = "dHash and IDHash perceptual image hashing/fingerprinting"
5
- # spec.homepage = "https://github.com/nakilon/dhash-vips"
6
5
 
7
- spec.author = "Victor Maslov"
6
+ spec.author = "Victor Maslov aka Nakilon"
8
7
  spec.email = "nakilon@gmail.com"
9
8
  spec.license = "MIT"
9
+ spec.metadata = {"source_code_uri" => "https://github.com/nakilon/dhash-vips"}
10
10
 
11
11
  spec.add_dependency "ruby-vips", "~> 2.0", "!= 2.1.0", "!= 2.1.1"
12
12
 
13
13
  spec.require_path = "lib"
14
- spec.test_files = %w{ test.rb }
15
14
  spec.extensions = %w{ extconf.rb }
16
- spec.files = %w{ extconf.rb Gemfile LICENSE.txt common.rb dhash-vips.gemspec idhash.c lib/dhash-vips-post-install-test.rb lib/dhash-vips.rb }
15
+ spec.files = %w{ LICENSE.txt dhash-vips.gemspec lib/dhash-vips.rb idhash.c lib/dhash-vips-post-install-test.rb } + spec.extensions +
16
+ %w{ bin/idhash }
17
+ spec.executables = %w{ idhash }
18
+ spec.bindir = "bin"
17
19
  end
data/extconf.rb CHANGED
@@ -3,32 +3,26 @@ require "mkmf"
3
3
  File.write "Makefile", dummy_makefile(?.).join
4
4
 
5
5
  unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.3.8")
6
- if ruby_source_dir = if File.directory? "/ruby"
7
- "-I/ruby" # for Github Actions: docker (currently disabled) and benchmark
8
- elsif ENV["RBENV_ROOT"] && ENV["RBENV_VERSION"] && File.exist?(t = "#{ENV["RBENV_ROOT"]}/sources/#{ENV["RBENV_VERSION"]}/ruby-#{ENV["RBENV_VERSION"]}/bignum.c") # https://github.com/rbenv/rbenv/issues/1199
9
- "-I#{File.dirname t}"
10
- end
11
- append_cppflags ruby_source_dir
12
- append_cppflags "-DRUBY_EXPORT" unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4")
13
- create_makefile "idhash"
14
- # Why this hack?
15
- # 1. Because I want to use Ruby and ./idhash.bundle for tests, not C.
16
- # 2. Because I don't want to bother users with two gems instead of one.
17
- File.write "Makefile", <<~HEREDOC + File.read("Makefile")
18
- .PHONY: test
19
- test: all
20
- \t$(RUBY) -r./lib/dhash-vips.rb ./lib/dhash-vips-post-install-test.rb
21
- HEREDOC
22
- end
6
+ append_cppflags "-DRUBY_EXPORT" unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4")
7
+ create_makefile "idhash"
8
+ # Why this hack?
9
+ # 1. Because I want to use Ruby and ./idhash.bundle for tests, not C.
10
+ # 2. Because I don't want to bother users with two gems instead of one.
11
+ File.write "Makefile", <<~HEREDOC + File.read("Makefile")
12
+ .PHONY: test
13
+ test: all
14
+ \t$(RUBY) -r./lib/dhash-vips.rb ./lib/dhash-vips-post-install-test.rb
15
+ HEREDOC
23
16
  end
24
17
 
25
18
  __END__
26
19
 
27
20
  # this unlike using rake is building to current directory
28
- # that is vital to be able to require the native extention for benchmarking, etc.
21
+ # that is vital to be able to require the native extension for benchmarking, etc.
29
22
  $ ruby extconf.rb && make clean && make
30
23
 
31
- # to test: $ rake clean && rake install
24
+ # to test the installation:
25
+ $ rake clean && rake install
32
26
 
33
27
  $ ruby -e "require 'dhash-vips'; p DHashVips::IDHash.method(:distance3).source_location" # using -r makes bundler mad
34
28
  # [".../dhash-vips.rb", 32] # if LoadError
data/idhash.c CHANGED
@@ -1,29 +1,35 @@
1
- #include <bignum.c>
1
+ #include <ruby.h>
2
2
 
3
- static VALUE idhash_distance(VALUE self, VALUE a, VALUE b){
4
- BDIGIT* tempd;
5
- long i, an = BIGNUM_LEN(a), bn = BIGNUM_LEN(b), templ, acc = 0;
6
- BDIGIT* as = BDIGITS(a);
7
- BDIGIT* bs = BDIGITS(b);
8
- while (0 < an && as[an-1] == 0) an--; // for (i = an; --i;) printf("%u\n", as[i]);
9
- while (0 < bn && bs[bn-1] == 0) bn--; // for (i = bn; --i;) printf("%u\n", bs[i]);
10
- // printf("%lu %lu\n", an, bn);
11
- if (an < bn) {
12
- tempd = as; as = bs; bs = tempd;
13
- templ = an; an = bn; bn = templ;
14
- }
15
- for (i = an; i-- > 4;) {
16
- // printf("%ld : (%u | %u) & (%u ^ %u)\n", i, as[i], (i >= bn ? 0 : bs[i]), as[i-4], bs[i-4]);
17
- acc += __builtin_popcountl((as[i] | (i >= bn ? 0 : bs[i])) & (as[i-4] ^ bs[i-4]));
18
- // printf("%ld : %ld\n", i, acc);
19
- }
20
- RB_GC_GUARD(a);
21
- RB_GC_GUARD(b);
22
- return INT2FIX(acc);
3
+ static VALUE idhash_distance(VALUE self, VALUE a, VALUE b) {
4
+ const size_t max_words = 256 / sizeof(uint64_t);
5
+
6
+ const size_t word_numbits = sizeof(uint64_t) * CHAR_BIT;
7
+ size_t n;
8
+ n = rb_absint_numwords(a, word_numbits, NULL);
9
+ if (n > max_words || n == (size_t)-1)
10
+ rb_raise(rb_eRangeError, "fingerprint #1 exceeds 256 bits");
11
+ n = rb_absint_numwords(b, word_numbits, NULL);
12
+ if (n > max_words || n == (size_t)-1)
13
+ rb_raise(rb_eRangeError, "fingerprint #2 exceeds 256 bits");
14
+
15
+ uint64_t as[max_words], bs[max_words];
16
+ rb_integer_pack(
17
+ a, as, max_words, sizeof(uint64_t), 0,
18
+ INTEGER_PACK_LSWORD_FIRST | INTEGER_PACK_NATIVE_BYTE_ORDER | INTEGER_PACK_2COMP
19
+ );
20
+ rb_integer_pack(
21
+ b, bs, max_words, sizeof(uint64_t), 0,
22
+ INTEGER_PACK_LSWORD_FIRST | INTEGER_PACK_NATIVE_BYTE_ORDER | INTEGER_PACK_2COMP
23
+ );
24
+
25
+ return INT2FIX(
26
+ __builtin_popcountll((as[3] | bs[3]) & (as[1] ^ bs[1])) +
27
+ __builtin_popcountll((as[2] | bs[2]) & (as[0] ^ bs[0]))
28
+ );
23
29
  }
24
30
 
25
31
  void Init_idhash() {
26
- VALUE m = rb_define_module("DHashVips");
27
- VALUE mm = rb_define_module_under(m, "IDHash");
28
- rb_define_module_function(mm, "distance3_c", idhash_distance, 2);
32
+ VALUE m = rb_define_module("DHashVips");
33
+ VALUE mm = rb_define_module_under(m, "IDHash");
34
+ rb_define_module_function(mm, "distance3_c", idhash_distance, 2);
29
35
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dhash-vips
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3.0
4
+ version: 0.2.4.0
5
5
  platform: ruby
6
6
  authors:
7
- - Victor Maslov
8
- autorequire:
7
+ - Victor Maslov aka Nakilon
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-23 00:00:00.000000000 Z
11
+ date: 2025-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-vips
@@ -36,27 +36,27 @@ dependencies:
36
36
  - - "!="
37
37
  - !ruby/object:Gem::Version
38
38
  version: 2.1.1
39
- description:
39
+ description:
40
40
  email: nakilon@gmail.com
41
- executables: []
41
+ executables:
42
+ - idhash
42
43
  extensions:
43
44
  - extconf.rb
44
45
  extra_rdoc_files: []
45
46
  files:
46
- - Gemfile
47
47
  - LICENSE.txt
48
- - common.rb
48
+ - bin/idhash
49
49
  - dhash-vips.gemspec
50
50
  - extconf.rb
51
51
  - idhash.c
52
52
  - lib/dhash-vips-post-install-test.rb
53
53
  - lib/dhash-vips.rb
54
- - test.rb
55
- homepage:
54
+ homepage:
56
55
  licenses:
57
56
  - MIT
58
- metadata: {}
59
- post_install_message:
57
+ metadata:
58
+ source_code_uri: https://github.com/nakilon/dhash-vips
59
+ post_install_message:
60
60
  rdoc_options: []
61
61
  require_paths:
62
62
  - lib
@@ -71,9 +71,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
71
71
  - !ruby/object:Gem::Version
72
72
  version: '0'
73
73
  requirements: []
74
- rubygems_version: 3.3.25
75
- signing_key:
74
+ rubygems_version: 3.1.6
75
+ signing_key:
76
76
  specification_version: 4
77
77
  summary: dHash and IDHash perceptual image hashing/fingerprinting
78
- test_files:
79
- - test.rb
78
+ test_files: []
data/Gemfile DELETED
@@ -1,12 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "rake"
4
- gem "rmagick"
5
- gem "dhash", github: "nakilon/dhash"
6
- gem "phamilie"
7
- gem "get_process_mem"
8
- gem "mll"
9
- gem "minitest"
10
- gem "byebug", "<11.1.0" # for Ruby 2.3
11
-
12
- gemspec
data/common.rb DELETED
@@ -1,11 +0,0 @@
1
- def download_if_needed path
2
- require "open-uri"
3
- require "digest"
4
- FileUtils.mkdir_p File.dirname path
5
- URI("http://gems.nakilon.pro.storage.yandexcloud.net/dhash-vips/#{File.basename path}".tap do |url|
6
- puts "downloading #{path} from #{url}"
7
- end).open do |link|
8
- File.open(path, "wb"){ |file| IO.copy_stream link, file }
9
- end unless File.exist?(path) && Digest::MD5.file(path) == File.basename(path, File.extname(path))
10
- path
11
- end
data/test.rb DELETED
@@ -1,126 +0,0 @@
1
- require "minitest/autorun"
2
-
3
- require_relative "lib/dhash-vips"
4
-
5
- # TODO tests about `fingerprint(4)`
6
-
7
- [
8
- [DHashVips::DHash, :hamming, :calculate, 2, 23, 16, 50, 7, 0x7919395919191919],
9
-
10
- # v0.2.3.0
11
- # [[0, 18, 26, 28, 28, 24, 35, 31, 42, 42, 32, 34, 33, 29, 35, 40],
12
- # [18, 0, 30, 24, 38, 34, 33, 33, 44, 42, 38, 42, 41, 37, 37, 50],
13
- # [26, 30, 0, 16, 34, 38, 29, 35, 42, 40, 36, 38, 31, 31, 31, 36],
14
- # [28, 24, 16, 0, 36, 36, 31, 35, 40, 38, 34, 34, 41, 37, 27, 34],
15
- # [28, 38, 34, 36, 0, 14, 35, 33, 40, 42, 32, 26, 27, 33, 35, 28],
16
- # [24, 34, 38, 36, 14, 0, 43, 39, 38, 40, 24, 28, 25, 31, 29, 28],
17
- # [35, 33, 29, 31, 35, 43, 0, 8, 27, 25, 35, 33, 32, 32, 28, 31],
18
- # [31, 33, 35, 35, 33, 39, 8, 0, 27, 27, 35, 33, 34, 34, 28, 33],
19
- # [42, 44, 42, 40, 40, 38, 27, 27, 0, 2, 34, 32, 31, 33, 31, 28],
20
- # [42, 42, 40, 38, 42, 40, 25, 27, 2, 0, 34, 34, 33, 35, 31, 28],
21
- # [32, 38, 36, 34, 32, 24, 35, 35, 34, 34, 0, 10, 23, 31, 25, 18],
22
- # [34, 42, 38, 34, 26, 28, 33, 33, 32, 34, 10, 0, 23, 29, 25, 16],
23
- # [33, 41, 31, 41, 27, 25, 32, 34, 31, 33, 23, 23, 0, 20, 24, 19],
24
- # [29, 37, 31, 37, 33, 31, 32, 34, 33, 35, 31, 29, 20, 0, 22, 27],
25
- # [35, 37, 31, 27, 35, 29, 28, 28, 31, 31, 25, 25, 24, 22, 0, 23],
26
- # [40, 50, 36, 34, 28, 28, 31, 33, 28, 28, 18, 16, 19, 27, 23, 0]]
27
-
28
- [DHashVips::IDHash, :distance, :fingerprint, 8, 22, 25, 70, 0, 0x1d5cdc0d1d0c1d9f5720a6fff2fe02013f00df9f005e1dc0ff670000ffff0080],
29
-
30
- # v0.2.3.0
31
- # [[0, 19, 34, 38, 57, 47, 50, 49, 45, 42, 55, 45, 60, 49, 51, 53],
32
- # [19, 0, 34, 33, 55, 47, 52, 49, 46, 49, 59, 46, 62, 54, 50, 57],
33
- # [34, 34, 0, 8, 48, 55, 46, 42, 55, 57, 45, 35, 51, 45, 46, 47],
34
- # [38, 33, 8, 0, 52, 61, 43, 38, 49, 50, 50, 37, 50, 39, 43, 51],
35
- # [57, 55, 48, 52, 0, 22, 46, 42, 70, 63, 51, 50, 35, 41, 46, 46],
36
- # [47, 47, 55, 61, 22, 0, 54, 54, 57, 53, 39, 44, 43, 41, 45, 38],
37
- # [50, 52, 46, 43, 46, 54, 0, 8, 35, 38, 51, 45, 50, 42, 43, 43],
38
- # [49, 49, 42, 38, 42, 54, 8, 0, 34, 36, 54, 49, 49, 42, 43, 44],
39
- # [45, 46, 55, 49, 70, 57, 35, 34, 0, 10, 53, 55, 56, 50, 49, 49],
40
- # [42, 49, 57, 50, 63, 53, 38, 36, 10, 0, 50, 52, 55, 48, 46, 45],
41
- # [55, 59, 45, 50, 51, 39, 51, 54, 53, 50, 0, 10, 32, 35, 40, 25],
42
- # [45, 46, 35, 37, 50, 44, 45, 49, 55, 52, 10, 0, 30, 27, 37, 26],
43
- # [60, 62, 51, 50, 35, 43, 50, 49, 56, 55, 32, 30, 0, 22, 26, 28],
44
- # [49, 54, 45, 39, 41, 41, 42, 42, 50, 48, 35, 27, 22, 0, 37, 35],
45
- # [51, 50, 46, 43, 46, 45, 43, 43, 49, 46, 40, 37, 26, 37, 0, 22],
46
- # [53, 57, 47, 51, 46, 38, 43, 44, 49, 45, 25, 26, 28, 35, 22, 0]]
47
-
48
- ].each do |lib, dm, calc, min_similar, max_similar, min_not_similar, max_not_similar, bw_exceptional, hash|
49
-
50
- describe lib do
51
- require "fileutils"
52
- require "digest"
53
- require "mll"
54
-
55
- require_relative "common"
56
-
57
- # these are false positive by idhash
58
- # 1b1d4bde376084011d027bba1c047a4b.jpg
59
- # 6d97739b4a08f965dc9239dd24382e96.jpg
60
- [
61
- [:similar, %w{
62
- 1d468d064d2e26b5b5de9a0241ef2d4b.jpg 92d90b8977f813af803c78107e7f698e.jpg
63
- 309666c7b45ecbf8f13e85a0bd6b0a4c.jpg 3f9f3db06db20d1d9f8188cd753f6ef4.jpg
64
- 679634ff89a31279a39f03e278bc9a01.jpg df0a3b93e9412536ee8a11255f974141.jpg
65
- 54192a3f65bd03163b04849e1577a40b.jpg 6d32f57459e5b79b5deca2a361eb8c6e.jpg
66
- 4b62e0eef58bfbc8d0d2fbf2b9d05483.jpg b8eb0ca91855b657f12fb3d627d45c53.jpg
67
- 21cd9a6986d98976b6b4655e1de7baf4.jpg 9b158c0d4953d47171a22ed84917f812.jpg
68
- 9c2c240ec02356472fb532f404d28dde.jpg fc762fa286489d8afc80adc8cdcb125e.jpg
69
- 7a833d873f8d49f12882e86af1cc6b79.jpg ac033cf01a3941dd1baa876082938bc9.jpg
70
- }, min_similar, max_similar, min_not_similar, max_not_similar], # slightly silimar images
71
- [:bw_exceptional, %w{
72
- 71662d4d4029a3b41d47d5baf681ab9a.jpg ad8a37f872956666c3077a3e9e737984.jpg
73
- }, bw_exceptional, bw_exceptional], # these are the same photo but of different size and colorspace
74
- ].each do |test_name, _images, min, max, min_not, max_not|
75
-
76
- images = _images.map{ |_| download_if_needed "test_images/#{_}" }
77
-
78
- hashes = images.map &lib.method(calc)
79
- table = MLL::table[lib.method(dm), [hashes], [hashes]]
80
-
81
- # STDERR.puts ""
82
- # require "pp"
83
- # PP.pp table, STDERR
84
- # STDERR.puts ""
85
-
86
- it test_name do
87
- similar = []
88
- not_similar = []
89
- hashes.size.times.to_a.repeated_combination(2) do |i, j|
90
- case
91
- when i == j
92
- assert_predicate table[i][j], :zero?
93
- when (j - i).abs == 1 && (i + j - 1) % 4 == 0
94
- # STDERR.puts [table[i][j], min, max].inspect
95
- similar.push table[i][j]
96
- else
97
- # STDERR.puts [table[i][j], min_not_similar, max_not_similar].inspect
98
- not_similar.push table[i][j]
99
- end
100
- end
101
- assert_equal [min, max], similar.minmax
102
- assert_equal [min_not, max_not], not_similar.minmax if min_not
103
- end
104
-
105
- end
106
-
107
- it "accepts Vips::Image" do
108
- # https://github.com/libvips/ruby-vips/issues/349
109
- lib.public_send calc, Vips::Image.new_from_buffer("GIF89a\x01\x00\x01\x00\x80\x01\x00\xFF\xFF\xFF\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;", "")
110
- end
111
-
112
- it "correct calculation" do
113
- download_if_needed "alpha_only.png"
114
- t = lib.public_send calc, "alpha_only.png"
115
- assert_equal hash, t, ->{ "0x#{hash.to_s 16} != 0x#{t.to_s 16}" }
116
- end
117
-
118
- end
119
-
120
- end
121
-
122
- describe DHashVips::IDHash do
123
- it "does not call distance3_ruby" do
124
- assert_equal :distance3, DHashVips::IDHash.method(:distance3).original_name
125
- end
126
- end