From 28d09be6b53db5e60c75928d48fe822c9a66a51c Mon Sep 17 00:00:00 2001 From: nxshock Date: Mon, 26 Jun 2017 23:12:48 +0500 Subject: [PATCH] Test rework, bugfixes and more tests --- colorconversion.go | 27 ++++++----- colorconverson_test.go | 28 +++++++++++ colorcrop_test.go | 72 ++++++++++++++++++++++++++++ comparators.go | 17 +++---- comparators_test.go | 106 ++++++++++++++++++++++++++++++++--------- example_test.go | 35 ++++++++++++-- testimages/01.png | Bin 0 -> 137 bytes testimages/02.png | Bin 0 -> 140 bytes testimages/03.png | Bin 0 -> 140 bytes testimages/04.png | Bin 0 -> 138 bytes testimages/05.png | Bin 0 -> 138 bytes testimages/06.png | Bin 0 -> 139 bytes testimages/07.png | Bin 0 -> 138 bytes testimages/08.png | Bin 0 -> 138 bytes testimages/09.png | Bin 0 -> 137 bytes testimages/10.png | Bin 0 -> 139 bytes testimages/11.png | Bin 0 -> 137 bytes testimages/12.png | Bin 0 -> 136 bytes testimages/13.png | Bin 0 -> 135 bytes testimages/14.png | Bin 0 -> 136 bytes testimages/15.png | Bin 0 -> 134 bytes testimages/16.png | Bin 0 -> 131 bytes 22 files changed, 240 insertions(+), 45 deletions(-) create mode 100644 colorconverson_test.go create mode 100644 colorcrop_test.go create mode 100644 testimages/01.png create mode 100644 testimages/02.png create mode 100644 testimages/03.png create mode 100644 testimages/04.png create mode 100644 testimages/05.png create mode 100644 testimages/06.png create mode 100644 testimages/07.png create mode 100644 testimages/08.png create mode 100644 testimages/09.png create mode 100644 testimages/10.png create mode 100644 testimages/11.png create mode 100644 testimages/12.png create mode 100644 testimages/13.png create mode 100644 testimages/14.png create mode 100644 testimages/15.png create mode 100644 testimages/16.png diff --git a/colorconversion.go b/colorconversion.go index e9d0510..5e68834 100644 --- a/colorconversion.go +++ b/colorconversion.go @@ -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)) } diff --git a/colorconverson_test.go b/colorconverson_test.go new file mode 100644 index 0000000..8ab4fd3 --- /dev/null +++ b/colorconverson_test.go @@ -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) + } + } +} diff --git a/colorcrop_test.go b/colorcrop_test.go new file mode 100644 index 0000000..c7e0454 --- /dev/null +++ b/colorcrop_test.go @@ -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 +} diff --git a/comparators.go b/comparators.go index 174bead..7ea9599 100644 --- a/comparators.go +++ b/comparators.go @@ -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 diff --git a/comparators_test.go b/comparators_test.go index e8354c6..b61f949 100644 --- a/comparators_test.go +++ b/comparators_test.go @@ -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) + } + } +} diff --git a/example_test.go b/example_test.go index 92ed267..cb12430 100644 --- a/example_test.go +++ b/example_test.go @@ -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") diff --git a/testimages/01.png b/testimages/01.png new file mode 100644 index 0000000000000000000000000000000000000000..6ce94cc8dbfec672ef5de574de1b82f404e2f661 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8q>Eak75uTiqknn?_#es`y#so`+ j11w7)GWa$$GBWgBV`p4)x}%bTfq}u()z4*}Q$iB}@yQ|Y literal 0 HcmV?d00001 diff --git a/testimages/02.png b/testimages/02.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e155998ccfbc6d47d023431da595551d412ce0 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`&sB>Eak75uW^qpQWd#hsU9dk!6Mh mOAj+s0%J;p+JOTM3|1ex7$;ruT+6_~z~JfX=d#Wzp$Py$-y!z^ literal 0 HcmV?d00001 diff --git a/testimages/03.png b/testimages/03.png new file mode 100644 index 0000000000000000000000000000000000000000..a54e10f7dc0d66567e6a9cdae97aec4d1c8ae8d1 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`&sB>Eak75uW^qpM}Taz=;zl7?O+{ mEfg3;-8l|0%wk|>W>EUe#rQw_TPXtr1B0ilpUXO@geCw_AR;CJ literal 0 HcmV?d00001 diff --git a/testimages/04.png b/testimages/04.png new file mode 100644 index 0000000000000000000000000000000000000000..066cb4156e98a4929ac032c52db41423bc01e542 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8y>Eak75uW^qpM@vjz=;zl4zQ#) kGkWm3GO%1=k&s|0oxsi_aqq1*0|Nttr>mdKI;Vst02ZSmasU7T literal 0 HcmV?d00001 diff --git a/testimages/05.png b/testimages/05.png new file mode 100644 index 0000000000000000000000000000000000000000..0e1bea9ff4cbe71aca558cd9f3bd47849f2f7a3f GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8y>Eak75uW^qpM}Sxp>yIy!BZ^^ kY9_2aJ^@l(5)uq)6WCea^JuEak75uW^qpM}Sxfz!pn$f(Iu kfkDh6j77|WfsKtJY63gUgv~9s3=9kmp00i_>zopr0Kaq|EC2ui literal 0 HcmV?d00001 diff --git a/testimages/07.png b/testimages/07.png new file mode 100644 index 0000000000000000000000000000000000000000..f812ae888a686b08cedb8f30aca0732bab6c6383 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8y>Eak75uW^qpM}Sxfz!*_z{sd6 ka|eTuVhjV5gam`%1a_81!uNF<7#J8lUHx3vIVCg!0KK0c#sB~S literal 0 HcmV?d00001 diff --git a/testimages/08.png b/testimages/08.png new file mode 100644 index 0000000000000000000000000000000000000000..2fbbd6f123a361b4d74c2ae5da504af9d22aa661 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8y>Eak75uW^qpM}Sxfzzv*QCV4e j6@!Oi7XvdhGc$wJ1a_8^?xYV43=9mOu6{1-oD!MEak75uW^qpM}Sxfz#_Sqp-5F j@)S-+pA}XT5)8%@*jczQ?EJ;Rz`)??>gTe~DWM4frZ*o> literal 0 HcmV?d00001 diff --git a/testimages/10.png b/testimages/10.png new file mode 100644 index 0000000000000000000000000000000000000000..da88603e2a7367ad240c41369e6b179591cb819c GIT binary patch literal 139 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`&s3>Eak75uW^qpM}Sxfz#_SV?@Km liGpVs88Eak75uW^qpM}Sxfz#_SV@$)u jiGn_dSrx1#B^ab9u(LEiXkN>}z`)??>gTe~DWM4f&6OZ; literal 0 HcmV?d00001 diff --git a/testimages/12.png b/testimages/12.png new file mode 100644 index 0000000000000000000000000000000000000000..73923d1a9b740755e6be93e0f39973c6e291d5e8 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8u>Eak75uW^qpM}Sxfz#_SV;6(6 ivhu0}|NsA&WZ<8`&cbD;Eak75uW^qpM}Sxfz#_S<1z+i hW#uWHOguacY+WqueFooa7#J8BJYD@<);T3K0RUf^9tr>e literal 0 HcmV?d00001 diff --git a/testimages/14.png b/testimages/14.png new file mode 100644 index 0000000000000000000000000000000000000000..e4dd9ffa09da50f4832aadb1f62684eceb5f62a9 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8u>Eak75uW^qpM}Sxfz#_SV;Q5H io0~ylqjr7(8A5T-G@yGywpoYako| literal 0 HcmV?d00001 diff --git a/testimages/15.png b/testimages/15.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd392af33031e093c32ded8470c7285ecc620db GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8w>Eak75uW^qpM}Sxfz#_SV;-ZM eo14PP0}KpoMJ(*c&z}M*WAJqKb6Mw<&;$TwEFWJ0 literal 0 HcmV?d00001 diff --git a/testimages/16.png b/testimages/16.png new file mode 100644 index 0000000000000000000000000000000000000000..88d678aa58fa864073bbed3b58edec5606104698 GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0y~yU|<1Z4mJh`hLs=Z)iE$IFct^7J29*~C-V{{lkVv2 z$iT3%pZiZDD+2=qXMsm#F#`kN5fEmas?8_Oz`!8k>Eak75uW^qpM}Sxfz#_SV;!TL cn;RPgTN(?ybJx~=3=9kmp00i_>zopr0P->&f&c&j literal 0 HcmV?d00001