Test rework, bugfixes and more tests

This commit is contained in:
nxshock 2017-06-26 23:12:48 +05:00
parent 1cf1415b29
commit 28d09be6b5
22 changed files with 240 additions and 45 deletions

View file

@ -5,10 +5,13 @@ import (
"math"
)
func rgbtoXYZ(r, g, b uint32) (x, y, z float64) {
varR := float64(r) / 255
varG := float64(g) / 255
varB := float64(b) / 255
// colorToXYZ returns CIE XYZ representation of color.
// https://en.wikipedia.org/wiki/Color_model#CIE_XYZ_color_space
func colorToXYZ(color color.Color) (x, y, z float64) {
r, g, b, _ := color.RGBA()
varR := float64(r>>8) / 255
varG := float64(g>>8) / 255
varB := float64(b>>8) / 255
if varR > 0.04045 {
varR = math.Pow((varR+0.055)/1.055, 2.4)
@ -28,16 +31,15 @@ func rgbtoXYZ(r, g, b uint32) (x, y, z float64) {
varB = varB / 12.92
}
varR = varR * 100
varG = varG * 100
varB = varB * 100
x = varR*41.24 + varG*35.76 + varB*18.05
y = varR*21.26 + varG*71.52 + varB*7.22
z = varR*1.93 + varG*11.92 + varB*95.05
x = varR*0.4124 + varG*0.3576 + varB*0.1805
y = varR*0.2126 + varG*0.7152 + varB*0.0722
z = varR*0.0193 + varG*0.1192 + varB*0.9505
return x, y, z
}
// xyztoLAB converts CIE XYZ color space to CIE LAB color space
// https://en.wikipedia.org/wiki/Lab_color_space#CIELAB-CIEXYZ_conversions
func xyztoLAB(x, y, z float64) (l, a, b float64) {
refX, refY, refZ := 95.047, 100.000, 108.883 // Daylight, sRGB, Adobe-RGB, Observer D65, 2°
@ -68,7 +70,8 @@ func xyztoLAB(x, y, z float64) (l, a, b float64) {
return l, a, b
}
// colorToLAB returns LAB representation of any color (without aplha)
// https://en.wikipedia.org/wiki/Lab_color_space
func colorToLAB(color color.Color) (l, a, b float64) {
cr, cg, cb, _ := color.RGBA()
return xyztoLAB(rgbtoXYZ(cr, cg, cb))
return xyztoLAB(colorToXYZ(color))
}

28
colorconverson_test.go Normal file
View file

@ -0,0 +1,28 @@
package colorcrop
import (
"image/color"
"math"
"testing"
)
func TestColorToLAB(t *testing.T) {
tests := []struct {
color color.Color
expectedL, expectedA, expectedB float64
gotL, gotA, gotB float64
}{
{color: color.RGBA{0, 0, 0, 255}, expectedL: 0.0, expectedA: 0.0, expectedB: 0.0},
{color: color.RGBA{0, 0, 255, 255}, expectedL: 32.30258667, expectedA: 79.19666179, expectedB: -107.86368104},
{color: color.RGBA{0, 255, 0, 255}, expectedL: 87.73703347, expectedA: -86.18463650, expectedB: 83.18116475},
{color: color.RGBA{255, 0, 0, 255}, expectedL: 53.23288179, expectedA: 80.10930953, expectedB: 67.22006831},
{color: color.RGBA{255, 255, 255, 255}, expectedL: 100.00000000, expectedA: 0.00526050, expectedB: -0.01040818},
}
for _, test := range tests {
test.gotL, test.gotA, test.gotB = colorToLAB(test.color)
if math.Abs(test.gotL-test.expectedL) > epsilon || math.Abs(test.gotA-test.expectedA) > epsilon || math.Abs(test.gotB-test.expectedB) > epsilon {
t.Errorf("%v: expected {%.8f, %.8f, %.8f}, got {%.8f, %.8f, %.8f}", test.color, test.expectedL, test.expectedA, test.expectedB, test.gotL, test.gotA, test.gotB)
}
}
}

72
colorcrop_test.go Normal file
View file

@ -0,0 +1,72 @@
package colorcrop
import (
"image"
"image/color"
"image/png"
"os"
"reflect"
"runtime"
"strings"
"testing"
)
// epsilon is a maximum permissible error
const epsilon = 0.00000001
func TestCropRectanle(t *testing.T) {
type test struct {
filename string
expected image.Rectangle
got image.Rectangle
}
comparators := []comparator{CmpRGBComponents, CmpEuclidean, CmpCIE76}
thresold := 0.5
tests := []test{
{filename: "01.png", expected: image.Rectangle{image.Point{0, 0}, image.Point{1, 1}}},
{filename: "02.png", expected: image.Rectangle{image.Point{1, 0}, image.Point{2, 1}}},
{filename: "03.png", expected: image.Rectangle{image.Point{2, 0}, image.Point{3, 1}}},
{filename: "04.png", expected: image.Rectangle{image.Point{3, 0}, image.Point{4, 1}}},
{filename: "05.png", expected: image.Rectangle{image.Point{0, 1}, image.Point{1, 2}}},
{filename: "06.png", expected: image.Rectangle{image.Point{1, 1}, image.Point{2, 2}}},
{filename: "07.png", expected: image.Rectangle{image.Point{2, 1}, image.Point{3, 2}}},
{filename: "08.png", expected: image.Rectangle{image.Point{3, 1}, image.Point{4, 2}}},
{filename: "09.png", expected: image.Rectangle{image.Point{0, 2}, image.Point{1, 3}}},
{filename: "10.png", expected: image.Rectangle{image.Point{1, 2}, image.Point{2, 3}}},
{filename: "11.png", expected: image.Rectangle{image.Point{2, 2}, image.Point{3, 3}}},
{filename: "12.png", expected: image.Rectangle{image.Point{3, 2}, image.Point{4, 3}}},
{filename: "13.png", expected: image.Rectangle{image.Point{0, 3}, image.Point{1, 4}}},
{filename: "14.png", expected: image.Rectangle{image.Point{1, 3}, image.Point{2, 4}}},
{filename: "15.png", expected: image.Rectangle{image.Point{2, 3}, image.Point{3, 4}}},
{filename: "16.png", expected: image.Rectangle{image.Point{3, 3}, image.Point{4, 4}}},
}
for _, comparator := range comparators {
for _, test := range tests {
file, err := os.Open("testimages/" + test.filename)
if err != nil {
t.Fatal(err)
}
defer file.Close()
image, err := png.Decode(file)
if err != nil {
t.Fatal(err)
}
test.got = cropRectanle(image, color.RGBA{255, 255, 255, 255}, thresold, comparator)
if !reflect.DeepEqual(test.expected, test.got) {
t.Errorf("expected %v, got %v for comparator: %s, file: %s", test.expected, test.got, getFuncName(comparator), test.filename)
}
}
}
}
func getFuncName(i interface{}) string {
s := runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
p := strings.LastIndex(s, ".")
if p > 0 {
return string([]rune(s)[p+1:])
}
return s
}

View file

@ -10,6 +10,7 @@ import (
type comparator func(color.Color, color.Color) float64
// CmpEuclidean returns Euclidean difference of two colors.
//
// https://en.wikipedia.org/wiki/Color_difference#Euclidean
func CmpEuclidean(color1 color.Color, color2 color.Color) float64 {
const maxDiff = 113509.94967402637 // Difference between black and white colors
@ -29,8 +30,8 @@ func CmpRGBComponents(color1 color.Color, color2 color.Color) float64 {
r1, g1, b1, _ := color1.RGBA()
r2, g2, b2, _ := color2.RGBA()
r1, g1, b1 = r1/256, g1/256, b1/256
r2, g2, b2 = r2/256, g2/256, b2/256
r1, g1, b1 = r1>>8, g1>>8, b1>>8
r2, g2, b2 = r2>>8, g2>>8, b2>>8
return float64((max(r1, r2)-min(r1, r2))+
(max(g1, g2)-min(g1, g2))+
@ -38,15 +39,13 @@ func CmpRGBComponents(color1 color.Color, color2 color.Color) float64 {
}
// CmpCIE76 returns difference of two colors defined in CIE76 standart.
//
// https://en.wikipedia.org/wiki/Color_difference#CIE76
func CmpCIE76(color1 color.Color, color2 color.Color) float64 {
const maxDiff = 149.95514755026548 // Difference between blue and white colors
const maxDiff = 149.95514755 // Difference between blue and white colors
r1, g1, b1, _ := color1.RGBA()
r2, g2, b2, _ := color2.RGBA()
cl1, ca1, cb1 := xyztoLAB(rgbtoXYZ(r1/256, g1/256, b1/256))
cl2, ca2, cb2 := xyztoLAB(rgbtoXYZ(r2/256, g2/256, b2/256))
cl1, ca1, cb1 := colorToLAB(color1)
cl2, ca2, cb2 := colorToLAB(color2)
return math.Sqrt(distance(cl2, cl1)+distance(ca2, ca1)+distance(cb2, cb1)) / maxDiff
}
@ -55,6 +54,7 @@ func distance(x, y float64) float64 {
return (x - y) * (x - y)
}
// min is minimum of two uint32
func min(a, b uint32) uint32 {
if a < b {
return a
@ -62,6 +62,7 @@ func min(a, b uint32) uint32 {
return b
}
// max is maximum of two uint32
func max(a, b uint32) uint32 {
if a > b {
return a

View file

@ -2,40 +2,102 @@ package colorcrop
import (
"image/color"
"reflect"
"runtime"
"math"
"testing"
)
func TestColorComparators(t *testing.T) {
type In struct {
color1 color.Color
color2 color.Color
}
func TestLinearComparators(t *testing.T) {
comparators := []comparator{CmpEuclidean, CmpRGBComponents}
tests := []struct {
in In
out float64
commentary string
color1 color.Color
color2 color.Color
expected float64
got float64
}{
{in: In{color.RGBA{0, 0, 0, 255}, color.RGBA{255, 255, 255, 255}},
out: 1.00,
commentary: "Difference between black and white colors"},
{in: In{color.RGBA{255, 255, 255, 255}, color.RGBA{255, 255, 255, 255}},
out: 0.00,
commentary: "Difference between same colors"},
{in: In{color.RGBA{255, 255, 255, 0}, color.RGBA{255, 255, 255, 255}},
out: 0.00,
commentary: "Difference between same colors with different transparency"},
{color1: color.RGBA{0, 0, 0, 255}, color2: color.RGBA{0, 0, 0, 255}, expected: 0.00}, // same black colors
{color1: color.RGBA{255, 255, 255, 255}, color2: color.RGBA{255, 255, 255, 255}, expected: 0.00}, // same white colors
{color1: color.RGBA{0, 0, 0, 255}, color2: color.RGBA{255, 255, 255, 255}, expected: 1.00}, // different (black and white) colors
{color1: color.RGBA{255, 255, 255, 255}, color2: color.RGBA{0, 0, 0, 255}, expected: 1.00}, // different (white and black) colors
{color1: color.RGBA{255, 255, 255, 0}, color2: color.RGBA{255, 255, 255, 255}, expected: 0.00}, // must ignore alpha channel
}
for _, comparator := range comparators {
for _, test := range tests {
if comparator(test.in.color2, test.in.color1) != test.out {
t.Errorf("%s: %s: expected %.2f, got %.2f", runtime.FuncForPC(reflect.ValueOf(comparator).Pointer()).Name(), test.commentary, test.out, comparator(test.in.color2, test.in.color1))
test.got = comparator(test.color1, test.color2)
if math.Abs(test.got-test.expected) > epsilon {
t.Errorf("%v %v: expected %.8f, got %.8f", test.color1, test.color2, test.expected, test.got)
}
}
}
}
func TestCmpCIE76(t *testing.T) {
type test struct {
color1 color.Color
color2 color.Color
expected float64
got float64
}
tests := []test{
{color1: color.RGBA{0, 0, 0, 255}, color2: color.RGBA{0, 0, 0, 255}, expected: 0.00000000 / 149.95514755}, // same black colors
{color1: color.RGBA{255, 255, 255, 255}, color2: color.RGBA{255, 255, 255, 255}, expected: 0.00000000 / 149.95514755}, // same white colors
{color1: color.RGBA{0, 0, 0, 255}, color2: color.RGBA{255, 255, 255, 255}, expected: 100.00000068 / 149.95514755}, // different (black and white) colors
{color1: color.RGBA{255, 255, 255, 255}, color2: color.RGBA{0, 0, 0, 255}, expected: 100.00000068 / 149.95514755}, // different (white and black) colors
{color1: color.RGBA{255, 0, 0, 255}, color2: color.RGBA{255, 255, 255, 255}, expected: 114.55897602 / 149.95514755}, // different (red and white) colors
{color1: color.RGBA{0, 255, 0, 255}, color2: color.RGBA{255, 255, 255, 255}, expected: 120.41559907 / 149.95514755}, // different (green and white) colors
{color1: color.RGBA{0, 0, 255, 255}, color2: color.RGBA{255, 255, 255, 255}, expected: 149.95514755 / 149.95514755}, // different (blue and white) colors
}
for _, test := range tests {
test.got = CmpCIE76(test.color1, test.color2)
if math.Abs(test.got-test.expected) > epsilon {
t.Errorf("%v %v: expected %.8f, got %.8f", test.color1, test.color2, test.expected, test.got)
}
}
}
func TestMin(t *testing.T) {
type test struct {
x uint32
y uint32
expected uint32
got uint32
}
tests := []test{
{x: 1, y: 2, expected: 1},
{x: 3, y: 2, expected: 2},
{x: 4, y: 4, expected: 4},
}
for _, test := range tests {
test.got = min(test.x, test.y)
if test.got != test.expected {
t.Errorf("min(%d, %d): expected %d, got %d", test.x, test.y, test.expected, test.got)
}
}
}
func TestMax(t *testing.T) {
type test struct {
x uint32
y uint32
expected uint32
got uint32
}
tests := []test{
{x: 1, y: 2, expected: 2},
{x: 3, y: 2, expected: 3},
{x: 4, y: 4, expected: 4},
}
for _, test := range tests {
test.got = max(test.x, test.y)
if test.got != test.expected {
t.Errorf("max(%d, %d): expected %d, got %d", test.x, test.y, test.expected, test.got)
}
}
}

View file

@ -1,3 +1,6 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package colorcrop_test
import (
@ -9,7 +12,7 @@ import (
"github.com/nxshock/colorcrop"
)
func Example() {
func ExampleBasicUsage() {
log.SetFlags(0)
// Read source image
@ -18,8 +21,34 @@ func Example() {
sourceImage, _ := png.Decode(sourceFile)
// Crop image white border with 50% thresold
croppedImage := colorcrop.Crop(sourceImage, color.RGBA{255, 255, 255, 255}, 0.5)
// Crop white border with 50% thresold
croppedImage := colorcrop.Crop(
sourceImage, // for source image
color.RGBA{255, 255, 255, 255}, // crop white border
0.5) // with 50% thresold
// Save cropped image
croppedFile, _ := os.Create("cropped.png")
defer croppedFile.Close()
png.Encode(croppedFile, croppedImage)
}
func ExampleCustomComparator() {
log.SetFlags(0)
// Read source image
sourceFile, _ := os.Open("img.png")
defer sourceFile.Close()
sourceImage, _ := png.Decode(sourceFile)
// Crop white border with 50% thresold
croppedImage := colorcrop.CropWithComparator(
sourceImage, // for source image
color.RGBA{255, 255, 255, 255}, // crop white border
0.5, // with 50% thresold
colorcrop.CmpCIE76) // using CIE76 standart for defining color difference
// Save cropped image
croppedFile, _ := os.Create("cropped.png")

BIN
testimages/01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

BIN
testimages/02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

BIN
testimages/03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

BIN
testimages/04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
testimages/05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
testimages/06.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

BIN
testimages/07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
testimages/08.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

BIN
testimages/09.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

BIN
testimages/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

BIN
testimages/11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

BIN
testimages/12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

BIN
testimages/13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 B

BIN
testimages/14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 B

BIN
testimages/15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

BIN
testimages/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B