diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4315289..14e8d36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,12 +4,12 @@ jobs: test: strategy: matrix: - go-version: [1.6.x, 1.7.x, 1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x] + go-version: [1.6.x, 1.7.x, 1.8.x, 1.9.x, 1.10.x, 1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x] platform: [ubuntu-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} - name: Set GOPATH diff --git a/.github/workflows/ci_gomodule.yml b/.github/workflows/ci_gomodule.yml index 1bf31c5..8ff0aeb 100644 --- a/.github/workflows/ci_gomodule.yml +++ b/.github/workflows/ci_gomodule.yml @@ -4,12 +4,12 @@ jobs: test: strategy: matrix: - go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x] + go-version: [1.11.x, 1.12.x, 1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x, 1.19.x, 1.20.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go - uses: actions/setup-go@v1 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} - name: Checkout code diff --git a/.gitignore b/.gitignore index a1338d6..198fc00 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +*.html # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ diff --git a/go.mod b/go.mod index 681fdc2..94bd86e 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/corona10/goimagehash +go 1.11 + require github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 diff --git a/hashcompute.go b/hashcompute.go index e1ac427..f964285 100644 --- a/hashcompute.go +++ b/hashcompute.go @@ -14,7 +14,7 @@ import ( "github.com/nfnt/resize" ) -// AverageHash fuction returns a hash computation of average hash. +// AverageHash function returns a hash computation of average hash. // Implementation follows // http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html func AverageHash(img image.Image) (*ImageHash, error) { diff --git a/hashcompute_test.go b/hashcompute_test.go index 83711a9..6d77834 100644 --- a/hashcompute_test.go +++ b/hashcompute_test.go @@ -124,6 +124,34 @@ func TestNilHashCompute(t *testing.T) { } } +func TestExtendHashCompute(t *testing.T) { + file, err := os.Open("_examples/sample1.jpg") + if err != nil { + t.Errorf("%s", err) + } + defer file.Close() + img, err := jpeg.Decode(file) + if err != nil { + t.Errorf("%s", err) + } + + hash, err := ExtPerceptionHash(img, 0, 8) + if err == nil { + t.Errorf("Error should be got.") + } + if hash != nil { + t.Errorf("Nil hash should be got. but got %v", hash) + } + + hash, err = ExtPerceptionHash(img, 16, 2) + if err != nil { + t.Errorf("%s", err) + } + if hash == nil { + t.Errorf("Hash should be got.") + } +} + func TestNilExtendHashCompute(t *testing.T) { hash, err := ExtAverageHash(nil, 8, 8) if err == nil { @@ -348,7 +376,7 @@ func BenchmarkAverageHash(b *testing.B) { } } -func BenchmarkDiffrenceHash(b *testing.B) { +func BenchmarkDifferenceHash(b *testing.B) { file1, err := os.Open("_examples/sample3.jpg") if err != nil { b.Errorf("%s", err) diff --git a/imagehash_test.go b/imagehash_test.go index da9dd5b..93ebf21 100644 --- a/imagehash_test.go +++ b/imagehash_test.go @@ -66,6 +66,9 @@ func TestSerialization(t *testing.T) { methods := []func(img image.Image) (*ImageHash, error){ AverageHash, PerceptionHash, DifferenceHash, } + extMethods := []func(img image.Image, width int, height int) (*ExtImageHash, error){ + ExtAverageHash, ExtPerceptionHash, ExtDifferenceHash, + } examples := []string{ "_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg", } @@ -103,28 +106,50 @@ func TestSerialization(t *testing.T) { } // test for ExtIExtImageHash - sizeList := []int{8, 16} - for _, size := range sizeList { - hash, err := ExtPerceptionHash(img, size, size) - checkErr(err) + for _, extMethod := range extMethods { + extMethodStr := runtime.FuncForPC(reflect.ValueOf(extMethod).Pointer()).Name() + sizeList := []int{8, 16} + for _, size := range sizeList { + hash, err := extMethod(img, size, size) + checkErr(err) - hex := hash.ToString() - // len(kind) == 1, len(":") == 1 - if len(hex) != size*size/4+2 { - t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, "ExtPerceptionHash", ex) - } + hex := hash.ToString() + // len(kind) == 1, len(":") == 1 + if len(hex) != size*size/4+2 { + t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, extMethodStr, ex) + } - reHash, err := ExtImageHashFromString(hex) - checkErr(err) + reHash, err := ExtImageHashFromString(hex) + checkErr(err) - distance, err := hash.Distance(reHash) - checkErr(err) + distance, err := hash.Distance(reHash) + checkErr(err) - if distance != 0 { - t.Errorf("Original and unserialized objects should be identical, got distance=%v; %v of '%v'", distance, "ExtPerceptionHash", ex) + if distance != 0 { + t.Errorf("Original and unserialized objects should be identical, got distance=%v; %v of '%v'", distance, "ExtPerceptionHash", ex) + } } } } + + // test for hashing empty string + imageHash, err := ImageHashFromString("") + if imageHash != nil { + t.Errorf("Expected reHash to be nil, got %v", imageHash) + } + if err == nil { + t.Errorf("Should got error for empty string") + } + extImageHash, err := ExtImageHashFromString("") + if extImageHash != nil { + t.Errorf("Expected reHash to be nil, got %v", extImageHash) + } + if err == nil { + t.Errorf("Should got error for empty string") + } + + // test for hashing invalid (non-hexadecimal) string + extImageHash, err = ExtImageHashFromString("k:g") } func TestDifferentBitSizeHash(t *testing.T) { @@ -231,4 +256,16 @@ func TestDumpAndLoad(t *testing.T) { } } } + + // test for loading empty bytes buffer + var b bytes.Buffer + bar := bufio.NewReader(&b) + _, err := LoadImageHash(bar) + if err == nil { + t.Errorf("Should got error for empty bytes buffer") + } + _, err = LoadExtImageHash(bar) + if err == nil { + t.Errorf("Should got error for empty bytes buffer") + } } diff --git a/transforms/dct.go b/transforms/dct.go index 1cc9645..9916467 100644 --- a/transforms/dct.go +++ b/transforms/dct.go @@ -37,7 +37,7 @@ func forwardTransform(input, temp []float64, Len int) { input[Len-2], input[Len-1] = temp[halfLen-1], temp[Len-1] } -// DCT2D function returns a result of DCT2D by using the seperable property. +// DCT2D function returns a result of DCT2D by using the separable property. func DCT2D(input [][]float64, w int, h int) [][]float64 { output := make([][]float64, h) for i := range output { @@ -74,7 +74,7 @@ func DCT2D(input [][]float64, w int, h int) [][]float64 { return output } -// DCT2DFast64 function returns a result of DCT2D by using the seperable property. +// DCT2DFast64 function returns a result of DCT2D by using the separable property. // Fast uses static DCT tables for improved performance. Returns flattened pixels. func DCT2DFast64(input *[]float64) (flattens [64]float64) { if len(*input) != 64*64 { @@ -98,7 +98,7 @@ func DCT2DFast64(input *[]float64) (flattens [64]float64) { return } -// DCT2DFast256 function returns a result of DCT2D by using the seperable property. +// DCT2DFast256 function returns a result of DCT2D by using the separable property. // DCT type II, unscaled. Algorithm by Byeong Gi Lee, 1984. // Fast uses static DCT tables for improved performance. Returns flattened pixels. func DCT2DFast256(input *[]float64) (flattens [256]float64) { diff --git a/transforms/pixels.go b/transforms/pixels.go index be82b86..82bdb0d 100644 --- a/transforms/pixels.go +++ b/transforms/pixels.go @@ -58,7 +58,7 @@ func rgb2GrayDefault(colorImg image.Image, pixels []float64, s int) { } } -// rgb2GrayYCbCR uses *image.YCbCr which is signifiantly faster than the image.Image interface. +// rgb2GrayYCbCR uses *image.YCbCr which is significantly faster than the image.Image interface. func rgb2GrayYCbCR(colorImg *image.YCbCr, pixels []float64, s int) { for i := 0; i < s; i++ { for j := 0; j < s; j++ { @@ -67,7 +67,7 @@ func rgb2GrayYCbCR(colorImg *image.YCbCr, pixels []float64, s int) { } } -// rgb2GrayYCbCR uses *image.RGBA which is signifiantly faster than the image.Image interface. +// rgb2GrayRGBA uses *image.RGBA which is significantly faster than the image.Image interface. func rgb2GrayRGBA(colorImg *image.RGBA, pixels []float64, s int) { for i := 0; i < s; i++ { for j := 0; j < s; j++ {