Skip to content

Commit

Permalink
extract lib, parallel download-decode step
Browse files Browse the repository at this point in the history
  • Loading branch information
AskAlexSharov committed Jan 27, 2019
1 parent 8b971c8 commit 8cae90a
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 99 deletions.
32 changes: 32 additions & 0 deletions avg/difference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package avg

import (
"github.com/Nr90/imgsim"
"image"
)

func Difference(img1, img2 image.Image) int {
// Hashing
ahash1 := imgsim.AverageHash(img1)
ahash2 := imgsim.AverageHash(img2)

// distance
avgDistance := imgsim.Distance(ahash1, ahash2)

return avgDistance % 64 // because 64 bit hash
}

//func Difference(img1, img2 image.Image) int {
// // Hashing
// ahash1 := imgsim.AverageHash(img1)
// ahash2 := imgsim.AverageHash(img2)
//
// dhash1 := imgsim.DifferenceHash(img1)
// dhash2 := imgsim.DifferenceHash(img2)
//
// // distance
// avgDistance := imgsim.Distance(ahash1, ahash2)
// diffDistance := imgsim.Distance(dhash1, dhash2)
//
// return (avgDistance + diffDistance) % 128
//}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ module github.com/AskAlexSharov/imgdiff

require (
github.com/Nr90/imgsim v0.0.0-20180202144352-5caa057144b0
github.com/aws/aws-lambda-go v1.8.1
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
github.com/Nr90/imgsim v0.0.0-20180202144352-5caa057144b0 h1:8cjsGKoi/1QBb0V2ps3iBra9c4o+qY/NaJ15NsdjmQ4=
github.com/Nr90/imgsim v0.0.0-20180202144352-5caa057144b0/go.mod h1:PSWPVD+KeWK3XVt0i/AahAMRw38OZ1k1vJpJLuvIY1w=
github.com/aws/aws-lambda-go v1.8.1 h1:nHBpP6XC30bwF6qWKrw/BrK2A8i4GKmSZzajTBIJS4A=
github.com/aws/aws-lambda-go v1.8.1/go.mod h1:zUsUQhAUjYzR8AuduJPCfhBuKWUaDbQiPOG+ouzmE1A=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
106 changes: 106 additions & 0 deletions loader/loader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package loader

import (
"context"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
)

type Result struct {
Img image.Image
Err error
}

func ImageAsync(ctx context.Context, filePathOrUrl string) chan Result {
res := make(chan Result)
go func() {
defer close(res)

select {
case res <- img(ctx, filePathOrUrl):
return
case <-ctx.Done():
res <- Result{Err: ctx.Err()}
return
}
}()

return res
}

func img(ctx context.Context, filePathOrUrl string) Result {
// Read
var imgReader io.Reader
var fileName string

parsedUrl, isUrl := parseUrl(filePathOrUrl)
if isUrl {
resp, err := getByUrl(ctx, filePathOrUrl)
if err != nil {
return Result{Err: err}
}
defer resp.Body.Close()
imgReader = resp.Body
fileName = parsedUrl.Path
} else {
var err error
if imgReader, err = os.Open(filePathOrUrl); err != nil {
return Result{Err: err}
}
fileName = filePathOrUrl
}

return decode(fileName, imgReader)
}

func parseUrl(rawurl string) (*url.URL, bool) {
parsedUrl, err := url.ParseRequestURI(rawurl)
if err != nil {
return parsedUrl, false
}
return parsedUrl, true
}

func decode(fileName string, f io.Reader) Result {
ext := filepath.Ext(fileName)

result := Result{}
if ext == ".jpeg" || ext == ".jpg" {
result.Img, result.Err = jpeg.Decode(f)
return result
}

if ext == ".png" {
result.Img, result.Err = png.Decode(f)
return result
}

result.Err = fmt.Errorf("not supported file extension: %s", ext)
return result
}

func getByUrl(ctx context.Context, url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}

resp, err := http.DefaultClient.Do(req.WithContext(ctx))
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, err
}

return resp, nil
}
108 changes: 15 additions & 93 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
package main

import (
"context"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
"log"
"net/http"
"net/url"
"github.com/AskAlexSharov/imgdiff/avg"
"github.com/AskAlexSharov/imgdiff/loader"
"os"
"path/filepath"

"github.com/Nr90/imgsim"
)

func main() {
Expand All @@ -21,95 +14,24 @@ func main() {
return
}

fmt.Printf("Diffeernce: %d%%\n", readAndGetDistance(os.Args[1], os.Args[2]))
}

func readAndGetDistance(fileName1, fileName2 string) int {
return distance(<-readAndDecode(fileName1), <-readAndDecode(fileName2))
}

func parseUrl(rawurl string) (*url.URL, bool) {
parsedUrl, err := url.ParseRequestURI(rawurl)
if err != nil {
return parsedUrl, false
}
return parsedUrl, true
}

func mustGet(rawurl string) io.ReadCloser {
resp, err := http.Get(rawurl)
dist, err := readAndGetDistance(context.Background(), os.Args[1], os.Args[2])
if err != nil {
panic(err)
}

if resp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("Unexpected response from server. Status code: %d", resp.StatusCode))
}
return resp.Body
}

func readAndDecode(filePathOrUrl string) chan image.Image {
res := make(chan image.Image)
go func() {
defer close(res)

// Read
parsedUrl, isUrl := parseUrl(filePathOrUrl)

if isUrl {
body := mustGet(filePathOrUrl)
defer body.Close()
res <- decode(parsedUrl.Path, body)
return

}

f, err := os.Open(filePathOrUrl)
if err != nil {
panic(err)
}
defer f.Close()
res <- decode(filePathOrUrl, f)
return
}()

return res
fmt.Printf("Diffeernce: %d%%\n", dist)
}

func decode(fileName string, f io.Reader) image.Image {
ext := filepath.Ext(fileName)

if ext == ".jpeg" || ext == ".jpg" {
img, err := jpeg.Decode(f)
if err != nil {
panic(err)
}
return img
func readAndGetDistance(ctx context.Context, fileName1, fileName2 string) (int, error) {
ch1 := loader.ImageAsync(ctx, fileName1)
ch2 := loader.ImageAsync(ctx, fileName2)
r1 := <-ch1
r2 := <-ch2
if r1.Err != nil {
return 0, r1.Err
}

if ext == ".png" {
img, err := png.Decode(f)
if err != nil {
panic(err)
}
return img
if r2.Err != nil {
return 0, r2.Err
}

log.Fatalf("Not supported file extension: %s", ext)
return nil
}

func distance(img1, img2 image.Image) int {
// Hashing
ahash1 := imgsim.AverageHash(img1)
ahash2 := imgsim.AverageHash(img2)

dhash1 := imgsim.DifferenceHash(img1)
dhash2 := imgsim.DifferenceHash(img2)

// distance
avgDistance := imgsim.Distance(ahash1, ahash2)
diffDistance := imgsim.Distance(dhash1, dhash2)

return (avgDistance + diffDistance) % 128
return avg.Difference(r1.Img, r2.Img), nil
}
19 changes: 13 additions & 6 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package main

import "testing"
import (
"context"
"testing"
)

func Test_distance(t *testing.T) {
tests := []struct {
Expand All @@ -16,21 +19,25 @@ func Test_distance(t *testing.T) {
wantDistance: 0,
},
{
name: "Original vs Damaged - 3%",
name: "Original vs Damaged - 1%",
file1: "./test-png-original.png",
file2: "https://raw.githubusercontent.com/AskAlexSharov/imgdiff/master/test-png-damaged.png",
wantDistance: 3,
wantDistance: 1,
},
{
name: "Scaled Down vs Damaged - 3%",
name: "Scaled Down vs Damaged - 1%",
file1: "https://raw.githubusercontent.com/AskAlexSharov/imgdiff/master/test-png-scaled-down.png",
file2: "./test-png-damaged.png",
wantDistance: 3,
wantDistance: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotDistance := readAndGetDistance(tt.file1, tt.file2)
gotDistance, err := readAndGetDistance(context.Background(), tt.file1, tt.file2)
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if gotDistance != tt.wantDistance {
t.Errorf("distance() gotDistance = %v, want %v", gotDistance, tt.wantDistance)
}
Expand Down

0 comments on commit 8cae90a

Please sign in to comment.