github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/images/resize/resize_test.go (about) 1 /* 2 Copyright 2013 The Camlistore Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package resize 18 19 import ( 20 "flag" 21 "fmt" 22 "image" 23 "image/color" 24 "image/draw" 25 "image/png" 26 "io" 27 "math" 28 "os" 29 "path/filepath" 30 "strings" 31 "testing" 32 ) 33 34 const ( 35 // psnrThreshold is the threshold over which images must match to consider 36 // HalveInplace equivalent to Resize. It is in terms of dB and 60-80 is 37 // good for RGB. 38 psnrThreshold = 50.0 39 40 maxPixelDiffPercentage = 10 41 ) 42 43 var ( 44 output = flag.String("output", "", "If non-empty, the directory to save comparison images.") 45 46 orig = image.Rect(0, 0, 1024, 1024) 47 thumb = image.Rect(0, 0, 64, 64) 48 ) 49 50 var somePalette = []color.Color{ 51 color.RGBA{0x00, 0x00, 0x00, 0xff}, 52 color.RGBA{0x00, 0x00, 0x44, 0xff}, 53 color.RGBA{0x00, 0x00, 0x88, 0xff}, 54 color.RGBA{0x00, 0x00, 0xcc, 0xff}, 55 color.RGBA{0x00, 0x44, 0x00, 0xff}, 56 color.RGBA{0x00, 0x44, 0x44, 0xff}, 57 color.RGBA{0x00, 0x44, 0x88, 0xff}, 58 color.RGBA{0x00, 0x44, 0xcc, 0xff}, 59 } 60 61 func makeImages(r image.Rectangle) []image.Image { 62 return []image.Image{ 63 image.NewGray(r), 64 image.NewGray16(r), 65 image.NewNRGBA(r), 66 image.NewNRGBA64(r), 67 image.NewPaletted(r, somePalette), 68 image.NewRGBA(r), 69 image.NewRGBA64(r), 70 image.NewYCbCr(r, image.YCbCrSubsampleRatio444), 71 image.NewYCbCr(r, image.YCbCrSubsampleRatio422), 72 image.NewYCbCr(r, image.YCbCrSubsampleRatio420), 73 image.NewYCbCr(r, image.YCbCrSubsampleRatio440), 74 } 75 } 76 77 func TestResize(t *testing.T) { 78 for i, im := range makeImages(orig) { 79 m := Resize(im, orig, thumb.Dx(), thumb.Dy()) 80 got, want := m.Bounds(), thumb 81 if !got.Eq(want) { 82 t.Error(i, "Want bounds", want, "got", got) 83 } 84 } 85 } 86 87 func TestResampleInplace(t *testing.T) { 88 for i, im := range makeImages(orig) { 89 m := ResampleInplace(im, orig, thumb.Dx(), thumb.Dy()) 90 got, want := m.Bounds(), thumb 91 if !got.Eq(want) { 92 t.Error(i, "Want bounds", want, "got", got) 93 } 94 } 95 } 96 97 func TestResample(t *testing.T) { 98 for i, im := range makeImages(orig) { 99 m := Resample(im, orig, thumb.Dx(), thumb.Dy()) 100 got, want := m.Bounds(), thumb 101 if !got.Eq(want) { 102 t.Error(i, "Want bounds", want, "got", got) 103 } 104 } 105 106 for _, d := range []struct { 107 wantFn string 108 r image.Rectangle 109 w, h int 110 }{ 111 { 112 // Generated with imagemagick: 113 // $ convert -crop 128x128+320+160 -resize 64x64 -filter point \ 114 // testdata/test.png testdata/test-resample-128x128-64x64.png 115 wantFn: "test-resample-128x128-64x64.png", 116 r: image.Rect(320, 160, 320+128, 160+128), 117 w: 64, 118 h: 64, 119 }, 120 { 121 // Generated with imagemagick: 122 // $ convert -resize 128x128 -filter point testdata/test.png \ 123 // testdata/test-resample-768x576-128x96.png 124 wantFn: "test-resample-768x576-128x96.png", 125 r: image.Rect(0, 0, 768, 576), 126 w: 128, 127 h: 96, 128 }, 129 } { 130 m := image.NewRGBA(testIm.Bounds()) 131 fillTestImage(m) 132 r, err := os.Open(filepath.Join("testdata", d.wantFn)) 133 if err != nil { 134 t.Fatal(err) 135 } 136 defer r.Close() 137 want, err := png.Decode(r) 138 if err != nil { 139 t.Fatal(err) 140 } 141 got := Resample(m, d.r, d.w, d.h) 142 res := compareImages(got, want) 143 t.Logf("PSNR %.4f", res.psnr) 144 s := got.Bounds().Size() 145 tot := s.X * s.Y 146 per := float32(100*res.diffCnt) / float32(tot) 147 t.Logf("Resample not the same %d pixels different %.2f%%", res.diffCnt, per) 148 if *output != "" { 149 err = savePng(t, want, fmt.Sprintf("Resample.%s->%dx%d.want.png", 150 d.r, d.w, d.h)) 151 if err != nil { 152 t.Fatal(err) 153 } 154 err = savePng(t, got, fmt.Sprintf("Resample.%s->%dx%d.got.png", 155 d.r, d.w, d.h)) 156 if err != nil { 157 t.Fatal(err) 158 } 159 err = savePng(t, res.diffIm, 160 fmt.Sprintf("Resample.%s->%dx%d.diff.png", d.r, d.w, d.h)) 161 if err != nil { 162 t.Fatal(err) 163 } 164 } 165 } 166 } 167 168 func TestHalveInplace(t *testing.T) { 169 for i, im := range makeImages(orig) { 170 m := HalveInplace(im) 171 b := im.Bounds() 172 got, want := m.Bounds(), image.Rectangle{ 173 Min: b.Min, 174 Max: b.Min.Add(b.Max.Div(2)), 175 } 176 if !got.Eq(want) { 177 t.Error(i, "Want bounds", want, "got", got) 178 } 179 } 180 } 181 182 type results struct { 183 diffCnt int 184 psnr float64 185 diffIm *image.Gray 186 } 187 188 func compareImages(m1, m2 image.Image) results { 189 b := m1.Bounds() 190 s := b.Size() 191 res := results{} 192 mse := uint32(0) 193 for y := b.Min.Y; y < b.Max.Y; y++ { 194 for x := b.Min.X; x < b.Max.X; x++ { 195 r1, g1, b1, a1 := m1.At(x, y).RGBA() 196 r2, g2, b2, a2 := m2.At(x, y).RGBA() 197 198 mse += ((r1-r2)*(r1-r2) + (g1-g2)*(g1-g2) + (b1-b2)*(b1-b2)) / 3 199 if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 { 200 if res.diffIm == nil { 201 res.diffIm = image.NewGray(m1.Bounds()) 202 } 203 res.diffCnt++ 204 res.diffIm.Set(x, y, color.White) 205 } 206 } 207 } 208 mse = mse / uint32(s.X*s.Y) 209 res.psnr = 20*math.Log10(1<<16) - 10*math.Log10(float64(mse)) 210 return res 211 } 212 213 var testIm image.Image 214 215 func init() { 216 r, err := os.Open(filepath.Join("testdata", "test.png")) 217 if err != nil { 218 panic(err) 219 } 220 defer r.Close() 221 testIm, err = png.Decode(r) 222 } 223 224 func fillTestImage(im image.Image) { 225 b := im.Bounds() 226 if !b.Eq(testIm.Bounds()) { 227 panic("Requested target image dimensions not equal reference image.") 228 } 229 src := testIm 230 if dst, ok := im.(*image.YCbCr); ok { 231 b := testIm.Bounds() 232 for y := b.Min.Y; y < b.Max.Y; y++ { 233 for x := b.Min.X; x < b.Max.X; x++ { 234 r, g, b, _ := src.At(x, y).RGBA() 235 yp, cb, cr := color.RGBToYCbCr(uint8(r), uint8(g), uint8(b)) 236 237 dst.Y[dst.YOffset(x, y)] = yp 238 off := dst.COffset(x, y) 239 dst.Cb[off] = cb 240 dst.Cr[off] = cr 241 } 242 } 243 return 244 } 245 draw.Draw(im.(draw.Image), b, testIm, b.Min, draw.Src) 246 } 247 248 func savePng(t *testing.T, m image.Image, fn string) error { 249 fn = filepath.Join(*output, fn) 250 t.Log("Saving", fn) 251 f, err := os.Create(fn) 252 if err != nil { 253 return err 254 } 255 defer f.Close() 256 257 return png.Encode(f, m) 258 } 259 260 func getFilename(im image.Image, method string) string { 261 imgType := fmt.Sprintf("%T", im) 262 imgType = imgType[strings.Index(imgType, ".")+1:] 263 if m, ok := im.(*image.YCbCr); ok { 264 imgType += "." + m.SubsampleRatio.String() 265 } 266 return fmt.Sprintf("%s.%s.png", imgType, method) 267 } 268 269 func TestCompareResizeToHavleInplace(t *testing.T) { 270 if testing.Short() { 271 t.Skip("Skipping TestCompareResizeToHavleInplace in short mode.") 272 } 273 images1, images2 := []image.Image{}, []image.Image{} 274 for _, im := range makeImages(testIm.Bounds()) { 275 fillTestImage(im) 276 images1 = append(images1, HalveInplace(im)) 277 } 278 for _, im := range makeImages(testIm.Bounds()) { 279 fillTestImage(im) 280 s := im.Bounds().Size() 281 images2 = append(images2, Resize(im, im.Bounds(), s.X/2, s.Y/2)) 282 } 283 284 var ( 285 f io.WriteCloser 286 err error 287 ) 288 if *output != "" { 289 os.Mkdir(*output, os.FileMode(0777)) 290 f, err = os.Create(filepath.Join(*output, "index.html")) 291 if err != nil { 292 t.Fatal(err) 293 } 294 defer f.Close() 295 fmt.Fprintf(f, `<!DOCTYPE html> 296 <html lang="en"> 297 <head> 298 <meta charset="utf-8"> 299 <title>Image comparison for TestCompareResizeToHavleInplace</title> 300 </head> 301 <body style="background-color: grey"> 302 <table> 303 `) 304 } 305 for i, im1 := range images1 { 306 im2 := images2[i] 307 res := compareImages(im1, im2) 308 if *output != "" { 309 fmt.Fprintf(f, "<tr>") 310 fn := getFilename(im1, "halve") 311 err := savePng(t, im1, fn) 312 if err != nil { 313 t.Fatal(err) 314 } 315 fmt.Fprintf(f, `<td><img src="%s"><br>%s`, fn, fn) 316 317 fn = getFilename(im1, "resize") 318 err = savePng(t, im2, fn) 319 if err != nil { 320 t.Fatal(err) 321 } 322 fmt.Fprintf(f, `<td><img src="%s"><br>%s`, fn, fn) 323 324 if res.diffIm != nil { 325 fn = getFilename(im1, "diff") 326 err = savePng(t, res.diffIm, fn) 327 if err != nil { 328 t.Fatal(err) 329 } 330 fmt.Fprintf(f, `<td><img src="%s"><br>%s`, fn, fn) 331 } 332 fmt.Fprintln(f) 333 } 334 335 if res.psnr < psnrThreshold { 336 t.Errorf("%T PSNR too low %.4f", im1, res.psnr) 337 } else { 338 t.Logf("%T PSNR %.4f", im1, res.psnr) 339 } 340 s := im1.Bounds().Size() 341 tot := s.X * s.Y 342 if per := float32(100*res.diffCnt) / float32(tot); per > maxPixelDiffPercentage { 343 t.Errorf("%T not the same %d pixels different %.2f%%", im1, res.diffCnt, per) 344 } 345 } 346 347 }