dcthash 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []