Compare commits

...

7 Commits

Author SHA1 Message Date
e91c39c79e Upgrade dependencies 2025-01-29 16:41:39 -08:00
72c18ebad3 Use limited pool 2025-01-12 17:26:32 -08:00
b4754030d1 Fix tests 2025-01-12 17:25:23 -08:00
33ff96e45f Add json marshaling/unmarshaling 2024-08-11 19:57:15 -07:00
003cba6e1b Fix data race 2024-08-10 16:08:38 -07:00
88dd4778eb Fix bufPool 2024-08-05 13:56:02 -07:00
5cd3cb8dcf staticcheck fixes 2024-08-04 18:24:04 -07:00
12 changed files with 493 additions and 356 deletions

17
go.mod
View File

@ -1,19 +1,18 @@
module gitea.narnian.us/lordwelch/goimagehash
go 1.21
go 1.22.0
toolchain go1.22.0
toolchain go1.22.5
require (
github.com/disintegration/imaging v1.6.2
github.com/gen2brain/avif v0.3.1
github.com/spakin/netpbm v1.3.0
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
github.com/gen2brain/avif v0.4.2
github.com/spakin/netpbm v1.3.2
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8
golang.org/x/image v0.23.0
)
require (
github.com/ebitengine/purego v0.7.1 // indirect
github.com/tetratelabs/wazero v1.7.1 // indirect
golang.org/x/sys v0.19.0 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
)

25
go.sum
View File

@ -1,17 +1,16 @@
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/ebitengine/purego v0.7.1 h1:6/55d26lG3o9VCZX8lping+bZcmShseiqlh2bnUDiPA=
github.com/ebitengine/purego v0.7.1/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/gen2brain/avif v0.3.1 h1:womS2LKvhS/dSR3zIKUxtJW+riGlY48akGWqc+YgHtE=
github.com/gen2brain/avif v0.3.1/go.mod h1:s9sI2zo2cF6EdyRVCtnIfwL/Qb3k0TkOIEsz6ovK1ms=
github.com/spakin/netpbm v1.3.0 h1:eDX7VvrkN5sHXW0luZXRA4AKDlLmu0E5sNxJ7VSTwxc=
github.com/spakin/netpbm v1.3.0/go.mod h1:Q+ep6vNv1G44qSWp0wt3Y9o1m/QXjmaXZIFC0PMVpq0=
github.com/tetratelabs/wazero v1.7.1 h1:QtSfd6KLc41DIMpDYlJdoMc6k7QTN246DM2+n2Y/Dx8=
github.com/tetratelabs/wazero v1.7.1/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gen2brain/avif v0.4.2 h1:rOZklPjZg3qTvKw/oR4xbdAe2JxvJGdFsGltnYmn2Mo=
github.com/gen2brain/avif v0.4.2/go.mod h1:oePci7KPleKZ8X/2rjZ3FlVm2JFYjPwXiQpNgq9wrzs=
github.com/spakin/netpbm v1.3.2 h1:ZAb16Sw/+b4QeO9NokEvejVvbrYdF6DdcYJe0dKONL0=
github.com/spakin/netpbm v1.3.2/go.mod h1:cVep9uXARFgAu2UU+0c+OE1J+eKmDN2hW0EN+tazkTA=
github.com/tetratelabs/wazero v1.8.2 h1:yIgLR/b2bN31bjxwXHD8a3d+BogigR952csSDdLYEv4=
github.com/tetratelabs/wazero v1.8.2/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -8,30 +8,22 @@ import (
"errors"
"image"
"image/draw"
"sync"
"gitea.narnian.us/lordwelch/goimagehash/etcs"
"gitea.narnian.us/lordwelch/goimagehash/pool"
"gitea.narnian.us/lordwelch/goimagehash/transforms"
"github.com/disintegration/imaging"
)
var bufPool = sync.Pool{
New: func() any {
// The Pool's New function should generally only return pointer
// types, since a pointer can be put into the return interface
// value without an allocation:
return make([]uint8, 1024)
},
}
var bufPool = &pool.Pool{}
func getBuf(size, capacity int) []uint8 {
if capacity < size {
capacity = size
}
buf := *bufPool.Get().(*[]uint8)
buf := *bufPool.Get()
if cap(buf) < capacity {
bufPool.Put(&buf)
buf = make([]uint8, capacity)
}
@ -40,6 +32,7 @@ func getBuf(size, capacity int) []uint8 {
}
return buf
}
func ToGray(img image.Image, pix []uint8) *image.Gray {
c := img.Bounds().Dx() * img.Bounds().Dy()
if cap(pix) < c {

View File

@ -19,37 +19,36 @@ func TestHashCompute(t *testing.T) {
name string
distance int
}{
{"_examples/sample1.jpg", "_examples/sample1.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample4.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample1.jpg", "_examples/sample2.jpg", AverageHash, "AverageHash", 40},
{"_examples/sample1.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 0},
{"_examples/sample1.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 34},
{"_examples/sample2.jpg", "_examples/sample3.jpg", AverageHash, "AverageHash", 40},
{"_examples/sample2.jpg", "_examples/sample4.jpg", AverageHash, "AverageHash", 6},
{"_examples/sample1.jpg", "_examples/sample1.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample4.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/sample1.jpg", "_examples/sample2.jpg", DifferenceHash, "DifferenceHash", 40},
{"_examples/sample1.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 2},
{"_examples/sample1.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 38},
{"_examples/sample2.jpg", "_examples/sample3.jpg", DifferenceHash, "DifferenceHash", 42},
{"_examples/sample2.jpg", "_examples/sample4.jpg", DifferenceHash, "DifferenceHash", 20},
{"_examples/sample1.jpg", "_examples/sample1.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample2.jpg", "_examples/sample2.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample3.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample4.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample1.jpg", "_examples/sample2.jpg", PerceptionHash, "PerceptionHash", 34},
{"_examples/sample1.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/sample1.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 30},
{"_examples/sample2.jpg", "_examples/sample3.jpg", PerceptionHash, "PerceptionHash", 34},
{"_examples/sample2.jpg", "_examples/sample4.jpg", PerceptionHash, "PerceptionHash", 20},
{"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", AverageHash, "AverageHash", 0},
{"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", AverageHash, "AverageHash", 0},
{"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", AverageHash, "AverageHash", 0},
{"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", AverageHash, "AverageHash", 0},
{"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", AverageHash, "AverageHash", 40},
{"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", AverageHash, "AverageHash", 0},
{"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", AverageHash, "AverageHash", 34},
{"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", AverageHash, "AverageHash", 40},
{"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", AverageHash, "AverageHash", 6},
{"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", DifferenceHash, "DifferenceHash", 0},
{"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", DifferenceHash, "DifferenceHash", 40},
{"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", DifferenceHash, "DifferenceHash", 2},
{"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", DifferenceHash, "DifferenceHash", 38},
{"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", DifferenceHash, "DifferenceHash", 42},
{"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", DifferenceHash, "DifferenceHash", 20},
{"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", PerceptionHash, "PerceptionHash", 34},
{"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", PerceptionHash, "PerceptionHash", 0},
{"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", PerceptionHash, "PerceptionHash", 30},
{"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", PerceptionHash, "PerceptionHash", 34},
{"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", PerceptionHash, "PerceptionHash", 20},
} {
file1, err := os.Open(tt.img1)
if err != nil {
}
defer file1.Close()
@ -125,7 +124,7 @@ func TestNilHashCompute(t *testing.T) {
}
func TestExtendHashCompute(t *testing.T) {
file, err := os.Open("_examples/sample1.jpg")
file, err := os.Open("_examples/images/sample1.jpg")
if err != nil {
t.Errorf("%s", err)
}
@ -214,68 +213,68 @@ func BenchmarkDistanceDifferent(b *testing.B) {
// name string
// distance int
// }{
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample1.jpg", "_examples/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
// {"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 34},
// {"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
// {"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 6},
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample1.jpg", "_examples/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 143},
// {"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 3},
// {"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 148},
// {"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 146},
// {"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 31},
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample1.jpg", "_examples/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 32},
// {"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 2},
// {"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 30},
// {"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 34},
// {"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 20},
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/sample1.jpg", "_examples/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 124},
// {"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 14},
// {"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 122},
// {"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 120},
// {"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 102},
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample1.jpg", "_examples/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 40},
// {"_examples/sample1.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 2},
// {"_examples/sample1.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 38},
// {"_examples/sample2.jpg", "_examples/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 42},
// {"_examples/sample2.jpg", "_examples/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 20},
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample1.jpg", "_examples/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 137},
// {"_examples/sample1.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 13},
// {"_examples/sample1.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 124},
// {"_examples/sample2.jpg", "_examples/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 140},
// {"_examples/sample2.jpg", "_examples/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 109},
// {"_examples/sample1.jpg", "_examples/sample1.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample2.jpg", "_examples/sample2.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample3.jpg", "_examples/sample3.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/sample4.jpg", "_examples/sample4.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 34},
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 40},
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 8, 8, ExtAverageHash, "ExtAverageHash", 6},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 143},
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 3},
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 148},
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 146},
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 16, 16, ExtAverageHash, "ExtAverageHash", 31},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 17, 17, ExtAverageHash, "ExtAverageHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 32},
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 2},
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 30},
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 34},
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 8, 4, ExtPerceptionHash, "ExtPerceptionHash", 20},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 124},
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 14},
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 122},
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 120},
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 16, 4, ExtPerceptionHash, "ExtPerceptionHash", 102},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 40},
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 2},
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 38},
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 42},
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 8, 8, ExtDifferenceHash, "ExtDifferenceHash", 20},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 137},
// {"_examples/images/sample1.jpg", "_examples/images/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 13},
// {"_examples/images/sample1.jpg", "_examples/images/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 124},
// {"_examples/images/sample2.jpg", "_examples/images/sample3.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 140},
// {"_examples/images/sample2.jpg", "_examples/images/sample4.jpg", 16, 16, ExtDifferenceHash, "ExtDifferenceHash", 109},
// {"_examples/images/sample1.jpg", "_examples/images/sample1.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample2.jpg", "_examples/images/sample2.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample3.jpg", "_examples/images/sample3.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// {"_examples/images/sample4.jpg", "_examples/images/sample4.jpg", 17, 17, ExtDifferenceHash, "ExtDifferenceHash", 0},
// } {
// file1, err := os.Open(tt.img1)
// if err != nil {
@ -341,7 +340,7 @@ func BenchmarkExtImageHashDistanceDifferent(b *testing.B) {
}
func BenchmarkPerceptionHash(b *testing.B) {
file1, err := os.Open("_examples/sample3.jpg")
file1, err := os.Open("_examples/images/sample3.jpg")
if err != nil {
b.Errorf("%s", err)
}
@ -351,45 +350,84 @@ func BenchmarkPerceptionHash(b *testing.B) {
b.Errorf("%s", err)
}
for i := 0; i < b.N; i++ {
_, err := ExtPerceptionHash(img1, 8, 8)
_, err := PerceptionHash(img1)
if err != nil {
b.Errorf("%s", err)
}
}
}
// func BenchmarkAverageHash(b *testing.B) {
// file1, err := os.Open("_examples/sample3.jpg")
// if err != nil {
// b.Errorf("%s", err)
// }
// defer file1.Close()
// img1, err := jpeg.Decode(file1)
// if err != nil {
// b.Errorf("%s", err)
// }
// for i := 0; i < b.N; i++ {
// _, err := ExtAverageHash(img1, 8, 8)
// if err != nil {
// b.Errorf("%s", err)
// }
// }
// }
func BenchmarkAverageHash(b *testing.B) {
file1, err := os.Open("_examples/images/sample3.jpg")
if err != nil {
b.Errorf("%s", err)
}
defer file1.Close()
img1, err := jpeg.Decode(file1)
if err != nil {
b.Errorf("%s", err)
}
for i := 0; i < b.N; i++ {
_, err := AverageHash(img1)
if err != nil {
b.Errorf("%s", err)
}
}
}
// func BenchmarkDifferenceHash(b *testing.B) {
// file1, err := os.Open("_examples/sample3.jpg")
// if err != nil {
// b.Errorf("%s", err)
// }
// defer file1.Close()
// img1, err := jpeg.Decode(file1)
// if err != nil {
// b.Errorf("%s", err)
// }
// for i := 0; i < b.N; i++ {
// _, err := ExtDifferenceHash(img1, 8, 8)
// if err != nil {
// b.Errorf("%s", err)
// }
// }
// }
func BenchmarkDifferenceHash(b *testing.B) {
file1, err := os.Open("_examples/images/sample3.jpg")
if err != nil {
b.Errorf("%s", err)
}
defer file1.Close()
img1, err := jpeg.Decode(file1)
if err != nil {
b.Errorf("%s", err)
}
for i := 0; i < b.N; i++ {
_, err := DifferenceHash(img1)
if err != nil {
b.Errorf("%s", err)
}
}
}
func BenchmarkAllHash(b *testing.B) {
imagePaths := []string{
"_examples/images/example.png",
"_examples/images/sample1.jpg",
"_examples/images/sample2.jpg",
"_examples/images/sample3.jpg",
"_examples/images/sample4.jpg",
}
images := []image.Image{}
for _, path := range imagePaths {
file1, err := os.Open(path)
if err != nil {
b.Errorf("%s", err)
}
defer file1.Close()
img, _, err := image.Decode(file1)
if err != nil {
b.Errorf("%s", err)
}
images = append(images, img)
}
for i := 0; i < b.N; i++ {
for _, image := range images {
_, err := AverageHash(image)
if err != nil {
b.Errorf("%s", err)
}
_, err = DifferenceHash(image)
if err != nil {
b.Errorf("%s", err)
}
_, err = PerceptionHash(image)
if err != nil {
b.Errorf("%s", err)
}
}
}
}

View File

@ -8,6 +8,7 @@ import (
"encoding/binary"
"encoding/gob"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
@ -36,19 +37,55 @@ const (
Unknown Kind = iota
// AHash is a enum value of the average hash.
AHash
//PHash is a enum value of the perceptual hash.
PHash
// DHash is a enum value of the difference hash.
DHash
// PHash is a enum value of the perceptual hash.
PHash
// WHash is a enum value of the wavelet hash.
WHash
)
var kindNames = map[Kind]string{
Unknown: "Unknown",
AHash: "ahash",
DHash: "dhash",
PHash: "phash",
WHash: "whash",
}
var nameKinds = map[string]Kind{
"Unknown": Unknown,
"ahash": AHash,
"dhash": DHash,
"phash": PHash,
"whash": WHash,
}
// NewImageHash function creates a new image hash.
func NewImageHash(hash uint64, kind Kind) *ImageHash {
return &ImageHash{hash: hash, kind: kind}
}
func (k Kind) String() string {
if name, ok := kindNames[k]; ok {
return name
}
return "Unknown"
}
func (k *Kind) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*k = nameKinds[s]
return nil
}
func (k *Kind) MarshalJSON() ([]byte, error) {
return json.Marshal(k.String())
}
// Bits method returns an actual hash bit size
func (h *ImageHash) Bits() int {
return 64
@ -60,7 +97,7 @@ func (h *ImageHash) Distance(other *ImageHash) (int, error) {
return -1, errNoOther
}
if h.GetKind() != other.GetKind() {
return -1, errors.New("Image hashes's kind should be identical")
return -1, errors.New("image hashes's kind should be identical")
}
lhash := h.GetHash()
@ -84,8 +121,10 @@ func (h *ImageHash) leftShiftSet(idx int) {
h.hash |= 1 << uint(idx)
}
const strFmtHex = "%1s:%016x"
const strFmtBin = "%1s:%064b"
const (
strFmtHex = "%1s:%016x"
strFmtBin = "%1s:%064b"
)
// Dump method writes a binary serialization into w io.Writer.
func (h *ImageHash) Dump(w io.Writer) error {
@ -164,18 +203,18 @@ func (h *ExtImageHash) Bits() int {
// Distance method returns a distance between two big hashes
func (h *ExtImageHash) Distance(other *ExtImageHash) (int, error) {
if h.GetKind() != other.GetKind() {
return -1, errors.New("Extended Image hashes's kind should be identical")
return -1, errors.New("extended Image hashes's kind should be identical")
}
if h.Bits() != other.Bits() {
msg := fmt.Sprintf("Extended image hash should has an identical bit size but got %v vs %v", h.Bits(), other.Bits())
msg := fmt.Sprintf("extended image hash should has an identical bit size but got %v vs %v", h.Bits(), other.Bits())
return -1, errors.New(msg)
}
lHash := h.GetHash()
rHash := other.GetHash()
if len(lHash) != len(rHash) {
return -1, errors.New("Extended Image hashes's size should be identical")
return -1, errors.New("extended Image hashes's size should be identical")
}
distance := 0

View File

@ -11,8 +11,6 @@ import (
"image"
_ "image/jpeg"
"os"
"reflect"
"runtime"
"testing"
)
@ -27,7 +25,7 @@ func TestNewImageHash(t *testing.T) {
{[][]uint8{{1, 0, 1, 1}, {0, 0, 0, 0}}, Unknown, Unknown, 3, nil},
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 0}}, Unknown, Unknown, 0, nil},
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, Unknown, 1, nil},
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, AHash, -1, errors.New("Image hashes's kind should be identical")},
{[][]uint8{{0, 0, 0, 0}, {0, 0, 0, 1}}, Unknown, AHash, -1, errors.New("image hashes's kind should be identical")},
} {
data1 := tt.datas[0]
data2 := tt.datas[1]
@ -51,7 +49,7 @@ func TestNewImageHash(t *testing.T) {
t.Errorf("Distance between %v and %v expected as %d but got %d", data1, data2, tt.distance, dis)
}
if err != nil && err.Error() != tt.err.Error() {
t.Errorf("Expected err %s, actual %s", tt.err, err)
t.Errorf("Expected err %#v, actual %#v", tt.err, err)
}
}
}
@ -67,109 +65,13 @@ func TestNil(t *testing.T) {
}
}
func TestSerialization(t *testing.T) {
checkErr := func(err error) {
if err != nil {
t.Errorf("%v", err)
}
}
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",
}
for _, ex := range examples {
file, err := os.Open(ex)
checkErr(err)
defer file.Close()
img, _, err := image.Decode(file)
checkErr(err)
for _, method := range methods {
methodStr := runtime.FuncForPC(reflect.ValueOf(method).Pointer()).Name()
hash, err := method(img)
checkErr(err)
hex := hash.String()
// len(kind) == 1, len(":") == 1, len(hash) == 16
if len(hex) != 18 {
t.Errorf("Got invalid hex string '%v'; %v of '%v'", hex, methodStr, ex)
}
reHash, err := ImageHashFromString(hex)
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, methodStr, ex)
}
}
// test for ExtIExtImageHash
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.String()
// 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)
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)
}
}
}
}
// 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) {
// checkErr := func(err error) {
// if err != nil {
// t.Errorf("%v", err)
// }
// }
// file, err := os.Open("_examples/sample1.jpg")
// file, err := os.Open("_examples/images/sample1.jpg")
// checkErr(err)
// defer file.Close()
@ -199,7 +101,7 @@ func TestDumpAndLoad(t *testing.T) {
AverageHash, PerceptionHash, DifferenceHash,
}
examples := []string{
"_examples/sample1.jpg", "_examples/sample2.jpg", "_examples/sample3.jpg", "_examples/sample4.jpg",
"_examples/images/sample1.jpg", "_examples/images/sample2.jpg", "_examples/images/sample3.jpg", "_examples/images/sample4.jpg",
}
for _, ex := range examples {

148
pool/pool.go Normal file
View File

@ -0,0 +1,148 @@
package pool
import (
"sort"
"sync"
"sync/atomic"
)
const (
minBitSize = 6 // 2**6=64 is a CPU cache line size
steps = 20
minSize = 1 << minBitSize
maxSize = 1 << (minBitSize + steps - 1)
calibrateCallsThreshold = 42000
maxPercentile = 0.95
)
// Pool represents byte buffer pool.
//
// Distinct pools may be used for distinct types of byte buffers.
// Properly determined byte buffer types with their own pools may help reducing
// memory waste.
type Pool struct {
calls [steps]uint64
calibrating uint64
defaultSize uint64
maxSize uint64
pool sync.Pool
}
var defaultPool Pool
// Get returns an empty byte buffer from the pool.
//
// Got byte buffer may be returned to the pool via Put call.
// This reduces the number of memory allocations required for byte buffer
// management.
func Get() *[]uint8 { return defaultPool.Get() }
// Get returns new byte buffer with zero length.
//
// The byte buffer may be returned to the pool via Put after the use
// in order to minimize GC overhead.
func (p *Pool) Get() *[]uint8 {
v := p.pool.Get()
if v != nil {
return v.(*[]uint8)
}
return &[]uint8{}
}
// Put returns byte buffer to the pool.
//
// []uint8.B mustn't be touched after returning it to the pool.
// Otherwise data races will occur.
func Put(b *[]uint8) { defaultPool.Put(b) }
// Put releases byte buffer obtained via Get to the pool.
//
// The buffer mustn't be accessed after returning to the pool.
func (p *Pool) Put(b *[]uint8) {
idx := index(len(*b))
if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
p.calibrate()
}
maxSize := int(atomic.LoadUint64(&p.maxSize))
if maxSize == 0 || cap(*b) <= maxSize {
p.pool.Put(b)
}
}
func (p *Pool) calibrate() {
if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
return
}
a := make(callSizes, 0, steps)
var callsSum uint64
for i := uint64(0); i < steps; i++ {
calls := atomic.SwapUint64(&p.calls[i], 0)
callsSum += calls
a = append(a, callSize{
calls: calls,
size: minSize << i,
})
}
sort.Sort(a)
defaultSize := a[0].size
maxSize := defaultSize
maxSum := uint64(float64(callsSum) * maxPercentile)
callsSum = 0
for i := 0; i < steps; i++ {
if callsSum > maxSum {
break
}
callsSum += a[i].calls
size := a[i].size
if size > maxSize {
maxSize = size
}
}
atomic.StoreUint64(&p.defaultSize, defaultSize)
atomic.StoreUint64(&p.maxSize, maxSize)
atomic.StoreUint64(&p.calibrating, 0)
}
type callSize struct {
calls uint64
size uint64
}
type callSizes []callSize
func (ci callSizes) Len() int {
return len(ci)
}
func (ci callSizes) Less(i, j int) bool {
return ci[i].calls > ci[j].calls
}
func (ci callSizes) Swap(i, j int) {
ci[i], ci[j] = ci[j], ci[i]
}
func index(n int) int {
n--
n >>= minBitSize
idx := 0
for n > 0 {
n >>= 1
idx++
}
if idx >= steps {
idx = steps - 1
}
return idx
}

View File

@ -97,6 +97,7 @@ func DCT2DFast64(input *[]float64) (flattens [64]float64) {
}
return
}
func DCT2DFast32(input *[]float64) (flattens [64]float64) {
if len(*input) != 32*32 {
panic("incorrect input size, wanted 32x32.")

View File

@ -43,18 +43,23 @@ func TestDCT2D(t *testing.T) {
for _, tt := range []struct {
input [][]float64
output [][]float64
w int
h int
w, h int
}{
{[][]float64{{1.0, 2.0, 3.0, 4.0},
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0},
{13.0, 14.0, 15.0, 16.0}},
[][]float64{{136.0, -12.6172881195958, 0.0, -0.8966830583359305},
{
[][]float64{
{1.0, 2.0, 3.0, 4.0},
{5.0, 6.0, 7.0, 8.0},
{9.0, 10.0, 11.0, 12.0},
{13.0, 14.0, 15.0, 16.0},
},
[][]float64{
{136.0, -12.6172881195958, 0.0, -0.8966830583359305},
{-50.4691524783832, 0.0, 0.0, 0.0},
{0.0, 0.0, 0.0, 0.0},
{-3.586732233343722, 0.0, 0.0, 0.0}},
4, 4},
{-3.586732233343722, 0.0, 0.0, 0.0},
},
4, 4,
},
} {
out := DCT2D(tt.input, tt.w, tt.h)
pass := true

View File

@ -86,7 +86,7 @@ func forwardDCT16(input []float64) {
}
func forwardDCT8(input []float64) {
var a, b = [4]float64{}, [4]float64{}
a, b := [4]float64{}, [4]float64{}
x0, y0 := input[0], input[7]
x1, y1 := input[1], input[6]
@ -159,7 +159,8 @@ var (
0.7540148204328366, 0.7312259956095479, 0.708327050840981, 0.6853214346239888, 0.6622126115197529, 0.6390040616320315, 0.61569928008307, 0.5923017764872479,
0.5688150744225436, 0.545242710899898, 0.5215882358305511, 0.4978552114914405, 0.4740472119887347, 0.45016782271958555, 0.4262206398321827, 0.40220926968418386,
0.37813732829961255, 0.3540084408242977, 0.3298262409799401, 0.3055943705168868, 0.2813164786656985, 0.25699622158758645, 0.23263726182380973, 0.20824326774410945,
0.1838179129942654, 0.15936487594286025, 0.1348878391273282, 0.11039048869938006, 0.08587651386988192, 0.06134960635327317, 0.03681345981160964, 0.012271769298309032}
0.1838179129942654, 0.15936487594286025, 0.1348878391273282, 0.11039048869938006, 0.08587651386988192, 0.06134960635327317, 0.03681345981160964, 0.012271769298309032,
}
dct128 = [64]float64{
1.9998494036782892, 1.9986447691766989, 1.9962362258002984, 1.992625224365556, 1.9878139400047121, 1.98180527085556, 1.9746028363157169, 1.9662109748624328,
1.9566347414392553, 1.9458799044111204, 1.9339529420897041, 1.9208610388311316, 1.9066120807083875, 1.8912146507610426, 1.87467802382515, 1.8570121609464312,
@ -168,7 +169,8 @@ var (
1.3967524988179458, 1.3612019955909063, 1.3248315551803436, 1.287663085779583, 1.249718976284773, 1.211022082808651, 1.1715957149128777, 1.1314636215672265,
1.0906499768440931, 1.049179365356938, 1.0070767674514352, 0.9643675441582458, 0.92107742191648, 0.8772324770770554, 0.8328591201952746, 0.7879840801220962,
0.7426343879036752, 0.696837360498869, 0.650620584324526, 0.6040118986384564, 0.5570393787701061, 0.5097313192090293, 0.4621162165613425, 0.4142227523844371,
0.36607977591028207, 0.3177162866677228, 0.26916141701425245, 0.22044441458776637, 0.17159462468887976, 0.12264147260441731, 0.07361444588271798, 0.02454307657143989}
0.36607977591028207, 0.3177162866677228, 0.26916141701425245, 0.22044441458776637, 0.17159462468887976, 0.12264147260441731, 0.07361444588271798, 0.02454307657143989,
}
dct64 = [32]float64{
1.9993976373924083, 1.9945809133573804, 1.9849590691974202, 1.9705552847778824, 1.9514042600770571, 1.9275521315908797, 1.8990563611860733, 1.8659855976694777,
1.8284195114070614, 1.7864486023910306, 1.7401739822174227, 1.6897071304994142, 1.6351696263031674, 1.5766928552532127, 1.5144176930129691, 1.448494165902934,

View File

@ -16,6 +16,7 @@ const (
func MultHi(v int32, coeff int32) int32 {
return (v * coeff) >> 8
}
func VP8Clip8(v int32) uint8 {
if (v &^ YUV_MASK2) == 0 {
return uint8(v >> YUV_FIX2)
@ -25,15 +26,19 @@ func VP8Clip8(v int32) uint8 {
}
return 0xff
}
func VP8YUVToR(y uint8, v uint8) uint8 {
return VP8Clip8(MultHi(int32(y), 19077) + MultHi(int32(v), 26149) - 14234)
}
func VP8YUVToG(y uint8, u uint8, v uint8) uint8 {
return VP8Clip8(MultHi(int32(y), 19077) - MultHi(int32(u), 6419) - MultHi(int32(v), 13320) + 8708)
}
func VP8YUVToB(y uint8, u uint8) uint8 {
return VP8Clip8(MultHi(int32(y), 19077) + MultHi(int32(u), 33050) - 17685)
}
func VP8YuvToRgb(y uint8, u uint8, v uint8) (rgb [3]uint8) {
rgb[0] = uint8(VP8YUVToR(y, v))
rgb[1] = uint8(VP8YUVToG(y, u, v))
@ -41,6 +46,7 @@ func VP8YuvToRgb(y uint8, u uint8, v uint8) (rgb [3]uint8) {
return rgb
}
func VP8YuvToBgr(y uint8, u uint8, v uint8) (bgr [3]uint8) {
bgr[0] = uint8(VP8YUVToB(y, u))
bgr[1] = uint8(VP8YUVToG(y, u, v))
@ -48,6 +54,7 @@ func VP8YuvToBgr(y uint8, u uint8, v uint8) (bgr [3]uint8) {
return bgr
}
func VP8YuvToRgb565(y uint8, u uint8, v uint8) (rgb [2]uint8) {
var (
r = VP8YUVToR(y, v)
@ -55,13 +62,14 @@ func VP8YuvToRgb565(y uint8, u uint8, v uint8) (rgb [2]uint8) {
b = VP8YUVToB(y, u)
)
var rg = (r & 0xf8) | (g >> 5)
var gb = ((g << 3) & 0xe0) | (b >> 3)
rg := (r & 0xf8) | (g >> 5)
gb := ((g << 3) & 0xe0) | (b >> 3)
rgb[0] = uint8(rg)
rgb[1] = uint8(gb)
return rgb
}
func VP8YuvToRgba4444(y uint8, u uint8, v uint8) (argb [2]uint8) {
var (
r = VP8YUVToR(y, v)
@ -69,13 +77,14 @@ func VP8YuvToRgba4444(y uint8, u uint8, v uint8) (argb [2]uint8) {
b = VP8YUVToB(y, u)
)
var rg = (r & 0xf0) | (g >> 4)
var ba = (b & 0xf0) | 0x0f // overwrite the lower 4 bits
rg := (r & 0xf0) | (g >> 4)
ba := (b & 0xf0) | 0x0f // overwrite the lower 4 bits
argb[0] = uint8(rg)
argb[1] = uint8(ba)
return argb
}
func VP8YuvToArgb(y uint8, u uint8, v uint8) (argb [4]uint8) {
argb[0] = 0xff
rgb := VP8YuvToRgb(y, u, v)
@ -83,6 +92,7 @@ func VP8YuvToArgb(y uint8, u uint8, v uint8) (argb [4]uint8) {
return argb
}
func VP8YuvToBgra(y uint8, u uint8, v uint8) (bgra [4]uint8) {
bgr := VP8YuvToBgr(y, u, v)
copy(bgra[:], bgr[:])
@ -90,6 +100,7 @@ func VP8YuvToBgra(y uint8, u uint8, v uint8) (bgra [4]uint8) {
return bgra
}
func VP8YuvToRgba(y uint8, u uint8, v uint8) (rgba [4]uint8) {
rgb := VP8YuvToRgb(uint8(y), uint8(u), uint8(v))
copy(rgba[:], rgb[:])

View File

@ -4,132 +4,132 @@ import (
"image"
)
type WebPUpsampleLinePairFunc func(top_y []uint8, bottom_y []uint8,
top_u []uint8, top_v []uint8,
bottom_u []uint8, bottom_v []uint8,
top_dst []uint8, bottom_dst []uint8, len int)
type WebPUpsampleLinePairFunc func(topY []uint8, bottomY []uint8,
topU []uint8, topV []uint8,
bottomU []uint8, bottomV []uint8,
topDst []uint8, bottomDst []uint8, len int)
func FancyUpscale(yuv *image.YCbCr) *image.RGBA {
rgb := image.NewRGBA(image.Rect(0, 0, yuv.Rect.Dx(), yuv.Rect.Dy()))
var (
upsample WebPUpsampleLinePairFunc = UpsampleRgbaLinePair
last_row int = yuv.Rect.Dy()
mb_w int = yuv.Rect.Dx()
lastRow = yuv.Rect.Dy()
mbW = yuv.Rect.Dx()
)
// First line is special cased. We mirror the u/v samples at boundary.
upsample(yuv.Y[:yuv.YStride], nil,
yuv.Cb[:yuv.CStride], yuv.Cr[:yuv.CStride],
yuv.Cb[:yuv.CStride], yuv.Cr[:yuv.CStride],
rgb.Pix[:rgb.Stride], nil,
mb_w,
mbW,
)
var (
top_c int
bottom_c int
top_y int
bottom_y int
top_dst int
bottom_dst int
topC int
bottomC int
topY int
bottomY int
topDst int
bottomDst int
)
// Loop over each output pairs of row.
for row := 1; row+1 < last_row; row += 2 {
top_c = yuv.COffset(0, row)
bottom_c = top_c + yuv.CStride
for row := 1; row+1 < lastRow; row += 2 {
topC = yuv.COffset(0, row)
bottomC = topC + yuv.CStride
top_y = yuv.YOffset(0, row)
bottom_y = top_y + yuv.YStride
topY = yuv.YOffset(0, row)
bottomY = topY + yuv.YStride
top_dst = rgb.PixOffset(0, row)
bottom_dst = top_dst + rgb.Stride
topDst = rgb.PixOffset(0, row)
bottomDst = topDst + rgb.Stride
upsample(yuv.Y[top_y:bottom_y], yuv.Y[bottom_y:bottom_y+yuv.YStride],
yuv.Cb[top_c:bottom_c], yuv.Cr[top_c:bottom_c],
yuv.Cb[bottom_c:bottom_c+yuv.CStride], yuv.Cr[bottom_c:bottom_c+yuv.CStride],
rgb.Pix[top_dst:bottom_dst], rgb.Pix[bottom_dst:bottom_dst+rgb.Stride],
mb_w,
upsample(yuv.Y[topY:bottomY], yuv.Y[bottomY:bottomY+yuv.YStride],
yuv.Cb[topC:bottomC], yuv.Cr[topC:bottomC],
yuv.Cb[bottomC:bottomC+yuv.CStride], yuv.Cr[bottomC:bottomC+yuv.CStride],
rgb.Pix[topDst:bottomDst], rgb.Pix[bottomDst:bottomDst+rgb.Stride],
mbW,
)
}
// Process the very last row of even-sized picture
if last_row%2 == 0 {
if lastRow%2 == 0 {
upsample(yuv.Y[len(yuv.Y)-yuv.YStride:], nil,
yuv.Cb[len(yuv.Cb)-yuv.CStride:], yuv.Cr[len(yuv.Cr)-yuv.CStride:],
yuv.Cb[len(yuv.Cb)-yuv.CStride:], yuv.Cr[len(yuv.Cr)-yuv.CStride:],
rgb.Pix[len(rgb.Pix)-rgb.Stride:], nil,
mb_w,
mbW,
)
}
return rgb
}
func UpsampleRgbaLinePair(top_y []uint8, bottom_y []uint8,
top_u []uint8, top_v []uint8,
bottom_u []uint8, bottom_v []uint8,
top_dst []uint8, bottom_dst []uint8, rowLength int,
func UpsampleRgbaLinePair(topY []uint8, bottomY []uint8,
topU []uint8, topV []uint8,
bottomU []uint8, bottomV []uint8,
topDst []uint8, bottomDst []uint8, rowLength int,
) {
var (
x int
last_pixel_pair int = (rowLength - 1) >> 1
tl_uv uint32 = (uint32(top_u[0]) | (uint32(top_v[0]) << 16)) /* top-left sample */
l_uv uint32 = (uint32(bottom_u[0]) | (uint32(bottom_v[0]) << 16)) /* left-sample */
x int
lastPixelPair = (rowLength - 1) >> 1
tlUV = (uint32(topU[0]) | (uint32(topV[0]) << 16)) /* top-left sample */
lUV = (uint32(bottomU[0]) | (uint32(bottomV[0]) << 16)) /* left-sample */
)
{
var uv0 uint32 = (3*tl_uv + l_uv + 0x00020002) >> 2
dst := VP8YuvToRgba(top_y[0], uint8(uv0&0xff), uint8((uv0 >> 16)))
copy(top_dst[0:], dst[:])
uv0 := (3*tlUV + lUV + 0x00020002) >> 2
dst := VP8YuvToRgba(topY[0], uint8(uv0&0xff), uint8((uv0 >> 16)))
copy(topDst[0:], dst[:])
}
if bottom_y != nil {
var uv0 uint32 = (3*l_uv + tl_uv + 0x00020002) >> 2
dst := VP8YuvToRgba(bottom_y[0], uint8(uv0&0xff), uint8(uv0>>16))
copy(bottom_dst[0:], dst[:])
if bottomY != nil {
uv0 := (3*lUV + tlUV + 0x00020002) >> 2
dst := VP8YuvToRgba(bottomY[0], uint8(uv0&0xff), uint8(uv0>>16))
copy(bottomDst[0:], dst[:])
}
for x = 1; x <= last_pixel_pair; x++ {
for x = 1; x <= lastPixelPair; x++ {
var (
t_uv uint32 = (uint32(top_u[x]) | (uint32(top_v[x]) << 16)) /* top sample */
uv uint32 = (uint32(bottom_u[x]) | (uint32(bottom_v[x]) << 16)) /* sample */
tUV = (uint32(topU[x]) | (uint32(topV[x]) << 16)) /* top sample */
uv = (uint32(bottomU[x]) | (uint32(bottomV[x]) << 16)) /* sample */
/* precompute invariant values associated with first and second diagonals*/
avg uint32 = tl_uv + t_uv + l_uv + uv + 0x00080008
diag_12 uint32 = (avg + 2*(t_uv+l_uv)) >> 3
diag_03 uint32 = (avg + 2*(tl_uv+uv)) >> 3
avg = tlUV + tUV + lUV + uv + 0x00080008
diag12 = (avg + 2*(tUV+lUV)) >> 3
diag03 = (avg + 2*(tlUV+uv)) >> 3
)
{
var (
uv0 uint32 = (diag_12 + tl_uv) >> 1
uv1 uint32 = (diag_03 + t_uv) >> 1
uv0 = (diag12 + tlUV) >> 1
uv1 = (diag03 + tUV) >> 1
)
dst := VP8YuvToRgba(top_y[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(top_dst[(2*x-1)*4:], dst[:])
dst = VP8YuvToRgba(top_y[2*x-0], uint8(uv1&0xff), uint8(uv1>>16))
copy(top_dst[(2*x-0)*4:], dst[:])
dst := VP8YuvToRgba(topY[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(topDst[(2*x-1)*4:], dst[:])
dst = VP8YuvToRgba(topY[2*x-0], uint8(uv1&0xff), uint8(uv1>>16))
copy(topDst[(2*x-0)*4:], dst[:])
}
if bottom_y != nil {
if bottomY != nil {
var (
uv0 uint32 = (diag_03 + l_uv) >> 1
uv1 uint32 = (diag_12 + uv) >> 1
uv0 = (diag03 + lUV) >> 1
uv1 = (diag12 + uv) >> 1
)
dst := VP8YuvToRgba(bottom_y[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(bottom_dst[(2*x-1)*4:], dst[:])
dst = VP8YuvToRgba(bottom_y[2*x+0], uint8(uv1&0xff), uint8(uv1>>16))
copy(bottom_dst[(2*x+0)*4:], dst[:])
dst := VP8YuvToRgba(bottomY[2*x-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(bottomDst[(2*x-1)*4:], dst[:])
dst = VP8YuvToRgba(bottomY[2*x+0], uint8(uv1&0xff), uint8(uv1>>16))
copy(bottomDst[(2*x+0)*4:], dst[:])
}
tl_uv = t_uv
l_uv = uv
tlUV = tUV
lUV = uv
}
if rowLength%2 == 0 {
{
var uv0 uint32 = (3*tl_uv + l_uv + 0x00020002) >> 2
dst := VP8YuvToRgba(top_y[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(top_dst[(rowLength-1)*4:], dst[:])
uv0 := (3*tlUV + lUV + 0x00020002) >> 2
dst := VP8YuvToRgba(topY[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(topDst[(rowLength-1)*4:], dst[:])
}
if bottom_y != nil {
var uv0 uint32 = (3*l_uv + tl_uv + 0x00020002) >> 2
dst := VP8YuvToRgba(bottom_y[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(bottom_dst[(rowLength-1)*4:], dst[:])
if bottomY != nil {
uv0 := (3*lUV + tlUV + 0x00020002) >> 2
dst := VP8YuvToRgba(bottomY[rowLength-1], uint8(uv0&0xff), uint8(uv0>>16))
copy(bottomDst[(rowLength-1)*4:], dst[:])
}
}
}