git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/imaging/convolution_test.go (about) 1 package imaging 2 3 import ( 4 "image" 5 "testing" 6 ) 7 8 func TestConvolve3x3(t *testing.T) { 9 testCases := []struct { 10 name string 11 src image.Image 12 kernel [9]float64 13 options *ConvolveOptions 14 want *image.NRGBA 15 }{ 16 { 17 "Convolve3x3 0x0", 18 &image.NRGBA{ 19 Rect: image.Rect(0, 0, 0, 0), 20 Stride: 0, 21 Pix: []uint8{}, 22 }, 23 [9]float64{ 24 0, 0, 0, 25 0, 1, 0, 26 0, 0, 0, 27 }, 28 nil, 29 &image.NRGBA{Rect: image.Rect(0, 0, 0, 0)}, 30 }, 31 { 32 "Convolve3x3 4x4 identity", 33 &image.NRGBA{ 34 Rect: image.Rect(-1, -1, 3, 3), 35 Stride: 4 * 4, 36 Pix: []uint8{ 37 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 38 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 39 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 40 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 41 }, 42 }, 43 [9]float64{ 44 0, 0, 0, 45 0, 1, 0, 46 0, 0, 0, 47 }, 48 nil, 49 &image.NRGBA{ 50 Rect: image.Rect(0, 0, 4, 4), 51 Stride: 4 * 4, 52 Pix: []uint8{ 53 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 54 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 55 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 56 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 57 }, 58 }, 59 }, 60 { 61 "Convolve3x3 4x4 abs", 62 &image.NRGBA{ 63 Rect: image.Rect(-1, -1, 3, 3), 64 Stride: 4 * 4, 65 Pix: []uint8{ 66 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 67 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 68 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 69 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 70 }, 71 }, 72 [9]float64{ 73 0, 0, 0, 74 0, -1, 0, 75 0, 0, 0, 76 }, 77 &ConvolveOptions{Abs: true}, 78 &image.NRGBA{ 79 Rect: image.Rect(0, 0, 4, 4), 80 Stride: 4 * 4, 81 Pix: []uint8{ 82 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 83 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 84 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 85 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 86 }, 87 }, 88 }, 89 { 90 "Convolve3x3 4x4 bias", 91 &image.NRGBA{ 92 Rect: image.Rect(-1, -1, 3, 3), 93 Stride: 4 * 4, 94 Pix: []uint8{ 95 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 96 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 97 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 98 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 99 }, 100 }, 101 [9]float64{ 102 0, 0, 0, 103 0, 1, 0, 104 0, 0, 0, 105 }, 106 &ConvolveOptions{Bias: 0x10}, 107 &image.NRGBA{ 108 Rect: image.Rect(0, 0, 4, 4), 109 Stride: 4 * 4, 110 Pix: []uint8{ 111 0x10, 0x11, 0x12, 0x03, 0x14, 0x15, 0x16, 0x07, 0x18, 0x19, 0x1a, 0x0b, 0x1c, 0x1d, 0x1e, 0x0f, 112 0x20, 0x21, 0x22, 0x13, 0x24, 0x25, 0x26, 0x17, 0x28, 0x29, 0x2a, 0x1b, 0x2c, 0x2d, 0x2e, 0x1f, 113 0x30, 0x31, 0x32, 0x23, 0x34, 0x35, 0x36, 0x27, 0x38, 0x39, 0x3a, 0x2b, 0x3c, 0x3d, 0x3e, 0x2f, 114 0x40, 0x41, 0x42, 0x33, 0x44, 0x45, 0x46, 0x37, 0x48, 0x49, 0x4a, 0x3b, 0x4c, 0x4d, 0x4e, 0x3f, 115 }, 116 }, 117 }, 118 { 119 "Convolve3x3 4x4 norm", 120 &image.NRGBA{ 121 Rect: image.Rect(-1, -1, 3, 3), 122 Stride: 4 * 4, 123 Pix: []uint8{ 124 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 125 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 126 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 127 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 128 }, 129 }, 130 [9]float64{ 131 1, 1, 1, 132 1, 1, 1, 133 1, 1, 1, 134 }, 135 &ConvolveOptions{Normalize: true}, 136 &image.NRGBA{ 137 Rect: image.Rect(0, 0, 4, 4), 138 Stride: 4 * 4, 139 Pix: []uint8{ 140 0x07, 0x08, 0x09, 0x03, 0x09, 0x0a, 0x0b, 0x07, 0x0d, 0x0e, 0x0f, 0x0b, 0x10, 0x11, 0x12, 0x0f, 141 0x11, 0x12, 0x13, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1b, 0x1c, 0x1d, 0x1f, 142 0x21, 0x22, 0x23, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2b, 0x2c, 0x2d, 0x2f, 143 0x2c, 0x2d, 0x2e, 0x33, 0x2f, 0x30, 0x31, 0x37, 0x33, 0x34, 0x35, 0x3b, 0x35, 0x36, 0x37, 0x3f, 144 }, 145 }, 146 }, 147 { 148 "Convolve3x3 3x3 laplacian", 149 &image.NRGBA{ 150 Rect: image.Rect(-1, -1, 2, 2), 151 Stride: 3 * 4, 152 Pix: []uint8{ 153 0x00, 0x01, 0x01, 0xff, 0x00, 0x01, 0x02, 0xff, 0x00, 0x01, 0x03, 0xff, 154 0x00, 0x01, 0x04, 0xff, 0x10, 0x10, 0x10, 0xff, 0x00, 0x01, 0x05, 0xff, 155 0x00, 0x01, 0x06, 0xff, 0x00, 0x01, 0x07, 0xff, 0x00, 0x01, 0x08, 0xff, 156 }, 157 }, 158 [9]float64{ 159 -1, -1, -1, 160 -1, 8, -1, 161 -1, -1, -1, 162 }, 163 nil, 164 &image.NRGBA{ 165 Rect: image.Rect(0, 0, 3, 3), 166 Stride: 3 * 4, 167 Pix: []uint8{ 168 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 169 0x00, 0x00, 0x00, 0xff, 0x80, 0x78, 0x5c, 0xff, 0x00, 0x00, 0x00, 0xff, 170 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 171 }, 172 }, 173 }, 174 } 175 176 for _, tc := range testCases { 177 t.Run(tc.name, func(t *testing.T) { 178 got := Convolve3x3(tc.src, tc.kernel, tc.options) 179 if !compareNRGBA(got, tc.want, 0) { 180 t.Fatalf("got result %#v want %#v", got, tc.want) 181 } 182 }) 183 } 184 } 185 186 func TestConvolve5x5(t *testing.T) { 187 testCases := []struct { 188 name string 189 src image.Image 190 kernel [25]float64 191 options *ConvolveOptions 192 want *image.NRGBA 193 }{ 194 { 195 "Convolve5x5 4x4 translate", 196 &image.NRGBA{ 197 Rect: image.Rect(-1, -1, 3, 3), 198 Stride: 4 * 4, 199 Pix: []uint8{ 200 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 201 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 202 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 203 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 204 }, 205 }, 206 [25]float64{ 207 0, 0, 0, 0, 0, 208 0, 0, 0, 0, 0, 209 0, 0, 0, 0, 0, 210 0, 0, 0, 0, 0, 211 0, 0, 0, 0, 1, 212 }, 213 nil, 214 &image.NRGBA{ 215 Rect: image.Rect(0, 0, 4, 4), 216 Stride: 4 * 4, 217 Pix: []uint8{ 218 0x28, 0x29, 0x2a, 0x03, 0x2c, 0x2d, 0x2e, 0x07, 0x2c, 0x2d, 0x2e, 0x0b, 0x2c, 0x2d, 0x2e, 0x0f, 219 0x38, 0x39, 0x3a, 0x13, 0x3c, 0x3d, 0x3e, 0x17, 0x3c, 0x3d, 0x3e, 0x1b, 0x3c, 0x3d, 0x3e, 0x1f, 220 0x38, 0x39, 0x3a, 0x23, 0x3c, 0x3d, 0x3e, 0x27, 0x3c, 0x3d, 0x3e, 0x2b, 0x3c, 0x3d, 0x3e, 0x2f, 221 0x38, 0x39, 0x3a, 0x33, 0x3c, 0x3d, 0x3e, 0x37, 0x3c, 0x3d, 0x3e, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 222 }, 223 }, 224 }, 225 } 226 227 for _, tc := range testCases { 228 t.Run(tc.name, func(t *testing.T) { 229 got := Convolve5x5(tc.src, tc.kernel, tc.options) 230 if !compareNRGBA(got, tc.want, 0) { 231 t.Fatalf("got result %#v want %#v", got, tc.want) 232 } 233 }) 234 } 235 } 236 237 func TestNormalizeKernel(t *testing.T) { 238 testCases := []struct { 239 name string 240 kernel []float64 241 want []float64 242 }{ 243 { 244 name: "positive sum", 245 kernel: []float64{ 246 2, 0, 2, 247 0, 2, 0, 248 2, 0, 2, 249 }, 250 want: []float64{ 251 0.2, 0, 0.2, 252 0, 0.2, 0, 253 0.2, 0, 0.2, 254 }, 255 }, 256 { 257 name: "negative sum", 258 kernel: []float64{ 259 -2, 0, -2, 260 2, 2, 2, 261 -2, 0, -2, 262 }, 263 want: []float64{ 264 1, 0, 1, 265 -1, -1, -1, 266 1, 0, 1, 267 }, 268 }, 269 { 270 name: "zero sum", 271 kernel: []float64{ 272 0, 2, 0, 273 2, 0, -2, 274 0, -2, 0, 275 }, 276 want: []float64{ 277 0, 0.5, 0, 278 0.5, 0, -0.5, 279 0, -0.5, 0, 280 }, 281 }, 282 { 283 name: "all zero", 284 kernel: []float64{ 285 0, 0, 0, 286 0, 0, 0, 287 0, 0, 0, 288 }, 289 want: []float64{ 290 0, 0, 0, 291 0, 0, 0, 292 0, 0, 0, 293 }, 294 }, 295 } 296 for _, tc := range testCases { 297 t.Run(tc.name, func(t *testing.T) { 298 normalizeKernel(tc.kernel) 299 for i := range tc.kernel { 300 if tc.kernel[i] != tc.want[i] { 301 t.Fatalf("got kernel %v want %v", tc.kernel, tc.want) 302 } 303 } 304 }) 305 } 306 } 307 308 func BenchmarkConvolve3x3(b *testing.B) { 309 b.ReportAllocs() 310 for i := 0; i < b.N; i++ { 311 Convolve3x3( 312 testdataBranchesJPG, 313 [9]float64{ 314 -1, -1, 0, 315 -1, 0, 1, 316 0, 1, 1, 317 }, 318 nil, 319 ) 320 } 321 } 322 323 func BenchmarkConvolve5x5(b *testing.B) { 324 b.ReportAllocs() 325 for i := 0; i < b.N; i++ { 326 Convolve5x5( 327 testdataBranchesJPG, 328 [25]float64{ 329 -1, -1, -1, -1, 0, 330 -1, -1, -1, 0, 1, 331 -1, -1, 0, 1, 1, 332 -1, 0, 1, 1, 1, 333 0, 1, 1, 1, 1, 334 }, 335 nil, 336 ) 337 } 338 }