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  }