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  }