blockhash 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7b43e3ea031b810952658ae5a1d250071bcfb6cc0ad30563b5ef37f678b097de
4
+ data.tar.gz: f541fe84b1646f6997c2d874daad48c7d6f8014490218e62e64e67141e9fb2f6
5
+ SHA512:
6
+ metadata.gz: c853a8df57ff3ec69fad34d4d08a08b666dc7c41a0dc006c9a9db725b9b8aac2f24a78d4778a5ec3f6c7ed02b5a4ad30857aed255cc593ec722518ce24e4ee31
7
+ data.tar.gz: 6a0ba1da3475a44ebe9fb14666377c602286d20a1e693271d32c6032805d41b1353dcf492b6fa210eee2898e25e5fcee201ded9fff82fbe651b638f4688138f5
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Damien Kingsley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Blockhash
2
+
3
+ This is Ruby Gem that can be used to produce and compare perceptual image hashes.
4
+
5
+ This Gem is a translation of the algorithm at [http://blockhash.io](http://blockhash.io)
6
+
7
+ ## Installation
8
+
9
+ ### Prerequisites
10
+
11
+ On Debian/Ubuntu/Mint you can run:
12
+ ```sh
13
+ sudo apt get install libmagickwand-dev
14
+ ```
15
+
16
+ On Arch you can run:
17
+ ```sh
18
+ pacman -Syy pkg-config imagemagick
19
+ ```
20
+
21
+ On macOS, you can run:
22
+ ```sh
23
+ brew install pkg-config imagemagick
24
+ ```
25
+
26
+ ### Gem Install
27
+
28
+ Install via Bundler
29
+ ```sh
30
+ bundle add blockhash
31
+ ```
32
+
33
+ Or install via RubyGems:
34
+ ```sh
35
+ gem install blockhash
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```ruby
41
+ require "blockhash"
42
+ require "rmagick"
43
+
44
+ image1 = Magick::Image.read("image1.png").first
45
+ image2 = Magick::Image.read("image2.png").first
46
+
47
+ # Generate full image hashes
48
+ hash1 = blockhash.calculate(image1, bits = 16)
49
+ hash2 = blockhash.calculate(image2, bits = 16)
50
+
51
+ # Generate fast image hashes
52
+ hash1 = blockhash.calculate_fast(image1, bits = 16)
53
+ hash2 = blockhash.calculate_fast(image2, bits = 16)
54
+
55
+ # Generate image hash from file path
56
+ hash1 = blockhash.calculate_from_path("image1.png", bits = 16)
57
+
58
+ # Determine if hashes are similar
59
+ similar = blockhash.similar?(hash1, hash2, threshold = 10)
60
+
61
+ # Calculate distance between two hashes
62
+ distance = blockhash.distance(hash1, hash2)
63
+ ```
64
+
65
+ ## Contributing
66
+
67
+ Bug reports and pull requests are welcome on GitHub at https://github.com/actuallydamo/blockhash-ruby.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Blockhash
4
+ VERSION = "0.1.0"
5
+ end
data/lib/blockhash.rb ADDED
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "blockhash/version"
4
+ require "rmagick"
5
+ # Blockhash module
6
+ module Blockhash
7
+ DEFAULT_BITS = 16
8
+
9
+ def self.distance(hash1, hash2)
10
+ (hash1.to_i(16) ^ hash2.to_i(16)).to_s(2).count("1")
11
+ end
12
+
13
+ def self.similar?(hash1, hash2, thresh = 10)
14
+ distance(hash1, hash2) < thresh
15
+ end
16
+
17
+ def self.calculate_fast(img, bits = DEFAULT_BITS)
18
+ if img.alpha?
19
+ total_value = method(:total_value_rgba)
20
+ img_data = img.export_pixels(0, 0, img.columns, img.rows, "RGBA")
21
+ else
22
+ total_value = method(:total_value_rgb)
23
+ img_data = img.export_pixels(0, 0, img.columns, img.rows, "RGB")
24
+ end
25
+ blocksize_y = img.rows / bits
26
+ blocksize_x = img.columns / bits
27
+ # Loop through each block
28
+ result = (0...bits).flat_map do |y|
29
+ (0...bits).map do |x|
30
+ # Sum all pixels in block
31
+ (0...blocksize_y).sum do |iy|
32
+ cy = (y * blocksize_y) + iy
33
+ (0...blocksize_x).sum do |ix|
34
+ cx = (x * blocksize_x) + ix
35
+ total_value.call(img_data, img.columns, cx, cy)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ bits_to_hexhash(blocks_to_bits(result, blocksize_y * blocksize_x))
41
+ end
42
+
43
+ def self.calculate(img, bits = DEFAULT_BITS)
44
+ even_x = (img.columns % bits).zero?
45
+ even_y = (img.rows % bits).zero?
46
+ return calculate_fast(img, bits) if even_x && even_y
47
+
48
+ if img.alpha?
49
+ total_value = method(:total_value_rgba)
50
+ img_data = img.export_pixels(0, 0, img.columns, img.rows, "RGBA")
51
+ else
52
+ total_value = method(:total_value_rgb)
53
+ img_data = img.export_pixels(0, 0, img.columns, img.rows, "RGB")
54
+ end
55
+
56
+ blocks = Array.new(bits**2, 0)
57
+ block_width = img.columns / bits.to_f
58
+ block_height = img.rows / bits.to_f
59
+
60
+ (0...img.rows).each do |y|
61
+ b_top, b_bottom, w_top, w_bottom = block_overlaps(y, img.rows, block_height, even_y)
62
+ top_index = b_top * bits
63
+ bottom_index = b_bottom * bits
64
+ (0...img.columns).each do |x|
65
+ value = total_value.call(img_data, img.columns, x, y)
66
+ b_left, b_right, w_left, w_right = block_overlaps(x, img.columns, block_width, even_x)
67
+ blocks[top_index + b_left] += value * w_top * w_left
68
+ blocks[top_index + b_right] += value * w_top * w_right
69
+ blocks[bottom_index + b_left] += value * w_bottom * w_left
70
+ blocks[bottom_index + b_right] += value * w_bottom * w_right
71
+ end
72
+ end
73
+ bits_to_hexhash(blocks_to_bits(blocks, block_width * block_height))
74
+ end
75
+
76
+ def self.calculate_from_path(path, bits = DEFAULT_BITS)
77
+ calculate(Magick::Image.read(path).first, bits)
78
+ end
79
+
80
+ def self.block_overlaps(index, size, block_size, even)
81
+ block = index / block_size
82
+ block_prev = block.to_i
83
+ if even
84
+ weight_prev = 1
85
+ weight_next = 0
86
+ block_next = block_prev
87
+ else
88
+ index_int, index_frac = ((index + 1) % block_size).divmod 1
89
+ weight_prev = 1 - index_frac
90
+ weight_next = index_frac
91
+ block_next = index_int.positive? || (index + 1) == size ? block_prev : block.ceil.to_i
92
+ end
93
+ [block_prev, block_next, weight_prev, weight_next]
94
+ end
95
+ private_class_method :block_overlaps
96
+
97
+ def self.chunk(string, size)
98
+ string.unpack("a#{size}" * (string.size / size))
99
+ end
100
+ private_class_method :chunk
101
+
102
+ def self.median(array)
103
+ sorted = array.sort
104
+ length = array.length
105
+ middle = length / 2
106
+ return sorted[middle] if length.odd?
107
+
108
+ (sorted[middle - 1] + sorted[middle]) / 2.0
109
+ end
110
+ private_class_method :median
111
+
112
+ def self.total_value_rgba(img, cols, px_x, px_y)
113
+ i = ((px_y * cols * 4) + (px_x * 4))
114
+ # Set to max value if pixel is transparent
115
+ return Magick::QuantumRange * 3 if img[i + 3].zero?
116
+
117
+ # Sum RGB values
118
+ img[i] + img[i + 1] + img[i + 2]
119
+ end
120
+ private_class_method :total_value_rgba
121
+
122
+ def self.total_value_rgb(img, cols, px_x, px_y)
123
+ i = ((px_y * cols * 3) + (px_x * 3))
124
+ # Sum RGB values
125
+ img[i] + img[i + 1] + img[i + 2]
126
+ end
127
+ private_class_method :total_value_rgb
128
+
129
+ def self.bits_to_hexhash(bits)
130
+ chunk(bits, 4).sum("") do |chunk|
131
+ chunk.to_i(2).to_s(16)
132
+ end
133
+ end
134
+ private_class_method :bits_to_hexhash
135
+
136
+ def self.blocks_to_bits(blocks, pixels_per_block)
137
+ # Half of the max total colour value for the block
138
+ half_block = pixels_per_block * Magick::QuantumRange * 3 / 2.0
139
+ # Split image into 4 horizontal bands
140
+ bandsize = blocks.length / 4
141
+ # Loop over each band
142
+ blocks.each_slice(bandsize).sum("") do |band|
143
+ med = median(band)
144
+ band.sum("") do |block|
145
+ block > med || ((block - med).abs < 1 && med > half_block) ? "1" : "0"
146
+ end
147
+ end
148
+ end
149
+ private_class_method :blocks_to_bits
150
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: blockhash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Damien Kingsley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-07-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rmagick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.5
27
+ description: This is a perceptual image hash calculation and comparison tool based
28
+ on the blockhash method. This can be used to match compressed or resized images
29
+ to each other.
30
+ email:
31
+ - actuallydamo@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - LICENSE
37
+ - README.md
38
+ - lib/blockhash.rb
39
+ - lib/blockhash/version.rb
40
+ homepage: https://github.com/actuallydamo/blockhash-ruby
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ homepage_uri: https://github.com/actuallydamo/blockhash-ruby
45
+ source_code_uri: https://github.com/actuallydamo/blockhash-ruby
46
+ rubygems_mfa_required: 'true'
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 2.7.0
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements:
62
+ - ImageMagick
63
+ rubygems_version: 3.3.7
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: Generate and compare perceptual image hashes using the blockhash method
67
+ test_files: []