diff --git a/README.md b/README.md index 13fcb4c..8768424 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,18 @@ Import package with import "github.com/nxshock/colorcrop" ``` -Crop white borders with 50% of thresold: +Crop **white** borders with **50%** of thresold: ```go croppedImage := colorcrop.Crop(sourceImage, color.RGBA{255, 255, 255, 255}, 0.5) ``` +You may use custom comparator of colors: + +```go +croppedImage := colorcrop.CropWithComparator(sourceImage, color.RGBA{255, 255, 255, 255}, 0.5, colorcrop.CmpCIE76) +``` + ## Examples See in "examples". diff --git a/colorconversion.go b/colorconversion.go new file mode 100644 index 0000000..e9d0510 --- /dev/null +++ b/colorconversion.go @@ -0,0 +1,74 @@ +package colorcrop + +import ( + "image/color" + "math" +) + +func rgbtoXYZ(r, g, b uint32) (x, y, z float64) { + varR := float64(r) / 255 + varG := float64(g) / 255 + varB := float64(b) / 255 + + if varR > 0.04045 { + varR = math.Pow((varR+0.055)/1.055, 2.4) + } else { + varR = varR / 12.92 + } + + if varG > 0.04045 { + varG = math.Pow((varG+0.055)/1.055, 2.4) + } else { + varG = varG / 12.92 + } + + if varB > 0.04045 { + varB = math.Pow((varB+0.055)/1.055, 2.4) + } else { + varB = varB / 12.92 + } + + varR = varR * 100 + varG = varG * 100 + varB = varB * 100 + + 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 +} + +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° + + varX := x / refX + varY := y / refY + varZ := z / refZ + + if varX > 0.008856 { + varX = math.Pow(varX, (1.0 / 3.0)) + } else { + varX = (7.787 * varX) + (16.0 / 116.0) + } + if varY > 0.008856 { + varY = math.Pow(varY, (1.0 / 3.0)) + } else { + varY = (7.787 * varY) + (16.0 / 116.0) + } + if varZ > 0.008856 { + varZ = math.Pow(varZ, (1.0 / 3.0)) + } else { + varZ = (7.787 * varZ) + (16.0 / 116.0) + } + + l = (116 * varY) - 16 + a = 500 * (varX - varY) + b = 200 * (varY - varZ) + + return l, a, b +} + +func colorToLAB(color color.Color) (l, a, b float64) { + cr, cg, cb, _ := color.RGBA() + return xyztoLAB(rgbtoXYZ(cr, cg, cb)) +} diff --git a/colorcrop.go b/colorcrop.go index a50d5fc..3195680 100644 --- a/colorcrop.go +++ b/colorcrop.go @@ -9,7 +9,7 @@ import ( // Crop returns cropped image with default comparator. func Crop(img image.Image, color color.Color, thresold float64) image.Image { - return CropWithComparator(img, color, thresold, CmpRGBComponentsDiff) + return CropWithComparator(img, color, thresold, CmpRGBComponents) } // CropWithComparator returns cropped image with specified comparator. diff --git a/comparators.go b/comparators.go index db71c8e..ba7f22f 100644 --- a/comparators.go +++ b/comparators.go @@ -6,27 +6,48 @@ import ( ) // comparator is a function that returns a difference between two colors in -// range 0.0..1.0. +// range 0.0..1.0 (0.0 - same colors, 1.0 - totally different colors). type comparator func(color.Color, color.Color) float64 -// CmpSquareRGBComponentsDiff returns difference of two colors -func CmpSquareRGBComponentsDiff(color1 color.Color, color2 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 r1, g1, b1, _ := color1.RGBA() r2, g2, b2, _ := color2.RGBA() - return math.Sqrt(math.Pow(float64(r2)-float64(r1), 2.0)+ - math.Pow(float64(g2)-float64(g1), 2.0)+ - math.Pow(float64(b2)-float64(b1), 2.0)) / maxDiff + + return math.Sqrt(distance(float64(r2), float64(r1))+ + distance(float64(g2), float64(g1))+ + distance(float64(b2), float64(b1))) / maxDiff } -// CmpRGBComponentsDiff returns difference of two colors. -func CmpRGBComponentsDiff(color1 color.Color, color2 color.Color) float64 { - const maxDiff = 765 // Difference between black and white colors +// CmpRGBComponents returns RGB components difference of two colors. +func CmpRGBComponents(color1 color.Color, color2 color.Color) float64 { + const maxDiff = 195075.0 // Difference between black and white colors r1, g1, b1, _ := color1.RGBA() r2, g2, b2, _ := color2.RGBA() - return math.Sqrt(math.Abs(float64(r2)-float64(r1))+ - math.Abs(float64(g2)-float64(g1))+ + + return (math.Abs(float64(r2)-float64(r1)) + + math.Abs(float64(g2)-float64(g1)) + math.Abs(float64(b2)-float64(b1))) / maxDiff } + +// 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 = 150.8463301377893 // Difference between blue and white colors + + r1, g1, b1, _ := color1.RGBA() + r2, g2, b2, _ := color2.RGBA() + + cl1, ca1, cb1 := xyztoLAB(rgbtoXYZ(r1/255, g1/255, b1/255)) + cl2, ca2, cb2 := xyztoLAB(rgbtoXYZ(r2/255, g2/255, b2/255)) + + return math.Sqrt(distance(cl2, cl1) + distance(ca2, ca1) + distance(cb2, cb1)) +} + +func distance(x, y float64) float64 { + return (x - y) * (x - y) +} diff --git a/comparators_test.go b/comparators_test.go index 36ed640..e8354c6 100644 --- a/comparators_test.go +++ b/comparators_test.go @@ -13,7 +13,7 @@ func TestColorComparators(t *testing.T) { color2 color.Color } - comparators := []comparator{CmpSquareRGBComponentsDiff, CmpRGBComponentsDiff} + comparators := []comparator{CmpEuclidean, CmpRGBComponents} tests := []struct { in In @@ -33,8 +33,8 @@ func TestColorComparators(t *testing.T) { for _, comparator := range comparators { for _, test := range tests { - if CmpSquareRGBComponentsDiff(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, CmpSquareRGBComponentsDiff(test.in.color2, test.in.color1)) + 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)) } } }