dcthash 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: b73e184bb46162ad4bc3de29888ba7b427c5a28ffd8ab3b5a7028f601868c7fd
4
+ data.tar.gz: a89de0b14f3498f3ca4c6ca94123b44798d998c8a6163130c57ca340d80198d5
5
+ SHA512:
6
+ metadata.gz: b33b7d04d8441c142d8f65853d29e59fbca08b9cf8cbea499081f1eba228e4b09dae225ebbac3c81f821a1c81c46320dba748d30c514b2521bbe015c33b338d7
7
+ data.tar.gz: 3ec6099076b8bb4c418b38cae43aab403e5bc8ce7675198314d95d5e050abd6a92a54ffb03fabe65017e59e4b5a09a59c174104ef802925b60ed108a2a8111f1
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # DCTHash
2
+
3
+ This is Ruby Gem that can be used to produce and compare perceptual image hashes.
4
+
5
+ ## Installation
6
+
7
+ ### Prerequisites
8
+
9
+ On Debian/Ubuntu/Mint, you can run:
10
+ ```sh
11
+ sudo apt get install libmagickwand-dev
12
+ ```
13
+
14
+ On Arch you can run:
15
+ ```sh
16
+ pacman -Sy pkg-config imagemagick
17
+ ```
18
+
19
+ On macOS, you can run:
20
+ ```sh
21
+ brew install pkg-config imagemagick
22
+ ```
23
+
24
+ ### Gem Install
25
+
26
+ Install via Bundler
27
+ ```sh
28
+ bundle add dcthash
29
+ ```
30
+
31
+ Or install via RubyGems:
32
+ ```sh
33
+ gem install dcthash
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```ruby
39
+ require "dcthash"
40
+ require "rmagick"
41
+
42
+ image1 = Magick::Image.read("image1.png").first
43
+ image2 = Magick::Image.read("image2.png").first
44
+
45
+ # Generate image hashes
46
+ hash1 = Dcthash.calculate(image1)
47
+ hash2 = Dcthash.calculate(image2)
48
+
49
+ # Determine if hashes are similar
50
+ similar = Dcthash.similar?(hash1, hash2, threshold = 13)
51
+
52
+ # Calculate difference between two hashes
53
+ distance = Dcthash.distance(hash1, hash2)
54
+ ```
55
+
56
+ ## Contributing
57
+
58
+ Bug reports and pull requests are welcome on GitHub at https://github.com/actuallydamo/dcthash.
59
+
60
+
61
+ ## License
62
+
63
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dcthash
4
+ VERSION = "0.1.0"
5
+ end
data/lib/dcthash.rb ADDED
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dcthash/version"
4
+
5
+ # DCT Hash Module
6
+ module Dcthash
7
+ EDGE_SIZE = 32 # Resize each edge of image to this
8
+ SUBSAMPLE_START = 1 # Grab subsample from this point
9
+ SUBSAMPLE_END = 8 # Grab subsample to this point
10
+
11
+ # Calculate the distance between two hashes
12
+ # @param hash1 [String] The first hash
13
+ # @param hash2 [String] The second hash
14
+ # @return [Integer] hamming distance between hashes
15
+ def self.distance(hash1, hash2)
16
+ (hash1.to_i(16) ^ hash2.to_i(16)).to_s(2).count("1")
17
+ end
18
+
19
+ # Determine if two hashes are similar
20
+ # @param hash1 [String] The first hash
21
+ # @param hash2 [String] The second hash
22
+ # @param threshold [Integer] (optional) The threshold for similarity
23
+ # @return [Boolean] true if hashes are similar
24
+ def self.similar?(hash1, hash2, threshold = 13)
25
+ distance(hash1, hash2) < threshold
26
+ end
27
+
28
+ # Calculate the hash of an image
29
+ # @param image [Magick::Image] The image to hash
30
+ # @return [String] the hash of the image
31
+ def self.calculate(image)
32
+ image.resize!(EDGE_SIZE, EDGE_SIZE, Magick::PointFilter)
33
+ image = image.quantize(256, Magick::GRAYColorspace, Magick::NoDitherMethod)
34
+
35
+ intensity_matrix = image.export_pixels(0, 0, EDGE_SIZE, EDGE_SIZE, "I").each_slice(EDGE_SIZE).to_a
36
+
37
+ dct_result = dct_2d(intensity_matrix)
38
+ # @type var sub_matrix: Array[Float]
39
+ sub_matrix = ((dct_result[SUBSAMPLE_START..SUBSAMPLE_END] || [])
40
+ .transpose[SUBSAMPLE_START..SUBSAMPLE_END] || []).flatten
41
+
42
+ median = median(sub_matrix)
43
+ result = sub_matrix.map { |px| px < median ? 1 : 0 }
44
+ result.join.to_i(2).to_s(16)
45
+ end
46
+
47
+ # Find the median from an even length array of numbers
48
+ # @param array [Array<Float>] The array of numbers
49
+ # @return [Float] the value of the median
50
+ def self.median(array)
51
+ sorted = array.sort
52
+ length = array.length
53
+ middle = length / 2
54
+ # We only use even length arrays so the following line is not needed
55
+ # return sorted[middle] if length.odd?
56
+
57
+ (sorted[middle - 1] + sorted[middle]).fdiv(2)
58
+ end
59
+ private_class_method :median
60
+
61
+ # Calculate the DCT of a vector
62
+ # Adapted from https://www.nayuki.io/page/fast-discrete-cosine-transform-algorithms
63
+ # Copyright (c) 2020 Project Nayuki. (MIT License)
64
+ # @param vector [Array<Numeric>] the vector to transform
65
+ # @return [Array<Float>] the discrete cosine transform of the vector
66
+ def self.dct(vector)
67
+ vector_size = vector.size
68
+ return [Float(vector[0])] if vector_size == 1
69
+
70
+ half = vector_size / 2
71
+ alpha = (0...half).map { |i| vector[i] + (vector[-(i + 1)]) }
72
+ beta = (0...half).map do |i|
73
+ (vector[i] - (vector[-(i + 1)])).fdiv(Math.cos(((i + 0.5) * Math::PI).fdiv(vector_size)) * 2)
74
+ end
75
+ alpha = dct(alpha)
76
+ beta = dct(beta)
77
+ result = (0...half - 1).flat_map do |i|
78
+ [alpha[i], beta[i] + beta[i + 1]]
79
+ end
80
+ result.push(alpha[-1])
81
+ result.push(beta[-1])
82
+ end
83
+ private_class_method :dct
84
+
85
+ # Calculate the 2D DCT of a matrix
86
+ # @param input [Array<Array<Numeric>>] the matrix to calculate the 2D DCT of
87
+ # @return [Array<Array<Float>>] the DCT of the matrix
88
+ def self.dct_2d(input)
89
+ input.map { |row| dct(row) }
90
+ .transpose
91
+ .map { |col| dct(col) }
92
+ end
93
+ private_class_method :dct_2d
94
+ end
data/sig/dcthash.rbs ADDED
@@ -0,0 +1,18 @@
1
+ module Dcthash
2
+ VERSION: String
3
+ EDGE_SIZE: Integer
4
+ SUBSAMPLE_START: Integer
5
+ SUBSAMPLE_END: Integer
6
+
7
+ def self.distance: (String, String) -> Integer
8
+
9
+ def self.similar?: (String, String, ?Integer) -> bool
10
+
11
+ def self.calculate: (Magick::Image) -> String
12
+
13
+ def self.median: (Array[Float]) -> Float
14
+
15
+ def self.dct: (Array[Numeric]) -> Array[Float]
16
+
17
+ def self.dct_2d: (Array[Array[Numeric]]) -> Array[Array[Float]]
18
+ end
data/sig/rmagick.rbs ADDED
@@ -0,0 +1,18 @@
1
+ module Magick
2
+ class Enum
3
+ end
4
+ class FilterType < Enum
5
+ end
6
+ PointFilter: FilterType
7
+ class ColorspaceType < Enum
8
+ end
9
+ GRAYColorspace: ColorspaceType
10
+ class DitherMethod < Enum
11
+ end
12
+ NoDitherMethod: DitherMethod
13
+ class Image
14
+ def quantize: (?Numeric, ?Magick::ColorspaceType, ?Magick::DitherMethod, ?Integer, ?bool) -> Magick::Image
15
+ def resize!: ((Float | Integer), ?(Float | Integer), ?Magick::FilterType, ?Float) -> Magick::Image
16
+ def export_pixels: (?Numeric, ?Numeric, ?Numeric, ?Numeric, ?String) -> Array[Numeric]
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dcthash
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-17 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.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ description: This is a perceptual image hash calculation and comparison tool. This
28
+ can be used to match compressed or resized images to each other.
29
+ email:
30
+ - actuallydamo@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - README.md
36
+ - lib/dcthash.rb
37
+ - lib/dcthash/version.rb
38
+ - sig/dcthash.rbs
39
+ - sig/rmagick.rbs
40
+ homepage: https://github.com/actuallydamo/dcthash
41
+ licenses:
42
+ - MIT
43
+ metadata:
44
+ homepage_uri: https://github.com/actuallydamo/dcthash
45
+ source_code_uri: https://github.com/actuallydamo/dcthash
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
+ rubygems_version: 3.3.7
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Generate and compare perceptual image hashes
66
+ test_files: []