-
Notifications
You must be signed in to change notification settings - Fork 0
/
image.go
160 lines (133 loc) · 5.16 KB
/
image.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package hash
import (
"image"
"github.com/disintegration/imaging"
)
// Ahash calculates the average hash of an image. The image is first grayscaled,
// then scaled down to "hashLen" for the width and height. Then, the average value
// of the pixels is computed, and if a pixel is above the average, a 1 is appended
// to the byte array; a 0 otherwise.
func Ahash(img image.Image, hashLen int) ([]byte, error) {
var sum uint32 // Sum of the pixels
numbits := hashLen * hashLen // Perform the hashLen^2 operation once
bitArray, err := NewBitArray(numbits) // Resultant byte array init
if err != nil {
return nil, err
}
// As the average is being computed, create & populate an array
// of pixels to optimize runtime
var pixelArray []uint32
// Grayscale and resize
res := imaging.Grayscale(img)
res = imaging.Resize(res, hashLen, hashLen, imaging.Lanczos)
// Iterate over every pixel to generate the sum.
// Additionally, store every pixel into an array for faster re-computation
for x := 0; x < hashLen; x++ {
for y := 0; y < hashLen; y++ {
r, _, _, _ := res.At(x, y).RGBA() // r = g = b since the image is grayscaled
sum += r // increment the sum
pixelArray = append(pixelArray, r) // append the pixel
}
}
// Compute the average
avg := sum / uint32(numbits)
// For every pixel, check if it's below or above the average
for _, pix := range pixelArray {
if pix > avg {
bitArray.AppendBit(1) // If above, append 1
} else {
bitArray.AppendBit(0) // else append 0
}
}
return bitArray.GetArray(), nil
}
func Dhash(img image.Image, hashLen int) ([]byte, error) {
imgGray := imaging.Grayscale(img) // Grayscale image first for performance
// Calculate both horizontal and vertical gradients
horiz, err1 := horizontalGradient(imgGray, hashLen)
vert, err2 := verticalGradient(imgGray, hashLen)
if err1 != nil {
return nil, err1
}
if err2 != nil {
return nil, err2
}
// Return the concatenated horizontal and vertical hash
return append(horiz, vert...), nil
}
// DhashHorizontal returns the result of a horizontal gradient hash.
// 'img' is an Image object returned by opening an image file using OpenImg().
// 'hashLen' is the size that the image will be shrunk to. It must be a non-zero multiple of 8.
func DhashHorizontal(img image.Image, hashLen int) ([]byte, error) {
imgGray := imaging.Grayscale(img) // Grayscale image first
return horizontalGradient(imgGray, hashLen) // horizontal diff gradient
}
// DhashVertical returns the result of a vertical gradient hash.
// 'img' is an Image object returned by opening an image file using OpenImg().
// 'hashLen' is the size that the image will be shrunk to. It must be a non-zero multiple of 8.
func DhashVertical(img image.Image, hashLen int) ([]byte, error) {
imgGray := imaging.Grayscale(img) // Grayscale image first
return verticalGradient(imgGray, hashLen) // vertical diff gradient
}
// horizontalGradient performs a horizontal gradient diff on a grayscaled image
func horizontalGradient(img image.Image, hashLen int) ([]byte, error) {
// Width and height of the scaled-down image
width, height := hashLen+1, hashLen
// Downscale the image by 'hashLen' amount for a horizonal diff.
res := imaging.Resize(img, width, height, imaging.Lanczos)
// Create a new bitArray
bitArray, err := NewBitArray(hashLen * hashLen)
if err != nil {
return nil, err
}
var prev uint32 // Variable to store the previous pixel value
// Calculate the horizonal gradient difference
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// Since the image is grayscaled, r = g = b
r, _, _, _ := res.At(x, y).RGBA() // Get the pixel at (x,y)
// If this is not the first value of the current row, then
// compare the gradient difference from the previous one
if x > 0 {
if prev < r {
bitArray.AppendBit(1) // if it's smaller, append '1'
} else {
bitArray.AppendBit(0) // else append '0'
}
}
prev = r // Set this current pixel value as the previous one
}
}
return bitArray.GetArray(), nil
}
// verticalGradient performs a vertical gradient diff on a grayscaled image
func verticalGradient(img image.Image, hashLen int) ([]byte, error) {
// Width and height of the scaled-down image
width, height := hashLen, hashLen+1
// Downscale the image by 'hashLen' amount for a vertical diff.
res := imaging.Resize(img, width, height, imaging.Lanczos)
// Create a new bitArray
bitArray, err := NewBitArray(hashLen * hashLen)
if err != nil {
return nil, err
}
var prev uint32 // Variable to store the previous pixel value
// Calculate the vertical gradient difference
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
// Since the image is grayscaled, r = g = b
r, _, _, _ := res.At(x, y).RGBA() // Get the pixel at (x,y)
// If this is not the first value of the current column, then
// compare the gradient difference from the previous one
if y > 0 {
if prev < r {
bitArray.AppendBit(1) // if it's smaller, append '1'
} else {
bitArray.AppendBit(0) // else append '0'
}
}
prev = r // Set this current pixel value as the previous one
}
}
return bitArray.GetArray(), nil
}