github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/images/images_test.go (about) 1 /* 2 Copyright 2012 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 images 18 19 import ( 20 "image" 21 "image/jpeg" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 "testing" 27 "time" 28 29 "camlistore.org/third_party/github.com/camlistore/goexif/exif" 30 ) 31 32 const datadir = "testdata" 33 34 func equals(im1, im2 image.Image) bool { 35 if !im1.Bounds().Eq(im2.Bounds()) { 36 return false 37 } 38 for y := 0; y < im1.Bounds().Dy(); y++ { 39 for x := 0; x < im1.Bounds().Dx(); x++ { 40 r1, g1, b1, a1 := im1.At(x, y).RGBA() 41 r2, g2, b2, a2 := im2.At(x, y).RGBA() 42 if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 { 43 return false 44 } 45 } 46 } 47 return true 48 } 49 50 func straightFImage(t *testing.T) image.Image { 51 g, err := os.Open(filepath.Join(datadir, "f1.jpg")) 52 if err != nil { 53 t.Fatal(err) 54 } 55 defer g.Close() 56 straightF, err := jpeg.Decode(g) 57 if err != nil { 58 t.Fatal(err) 59 } 60 return straightF 61 } 62 63 func smallStraightFImage(t *testing.T) image.Image { 64 g, err := os.Open(filepath.Join(datadir, "f1-s.jpg")) 65 if err != nil { 66 t.Fatal(err) 67 } 68 defer g.Close() 69 straightF, err := jpeg.Decode(g) 70 if err != nil { 71 t.Fatal(err) 72 } 73 return straightF 74 } 75 76 func sampleNames(t *testing.T) []string { 77 dir, err := os.Open(datadir) 78 if err != nil { 79 t.Fatal(err) 80 } 81 defer dir.Close() 82 samples, err := dir.Readdirnames(-1) 83 if err != nil { 84 t.Fatal(err) 85 } 86 sort.Strings(samples) 87 return samples 88 } 89 90 // TestEXIFCorrection tests that the input files with EXIF metadata 91 // are correctly automatically rotated/flipped when decoded. 92 func TestEXIFCorrection(t *testing.T) { 93 samples := sampleNames(t) 94 straightF := straightFImage(t) 95 for _, v := range samples { 96 if !strings.Contains(v, "exif") || strings.HasSuffix(v, "-s.jpg") { 97 continue 98 } 99 name := filepath.Join(datadir, v) 100 t.Logf("correcting %s with EXIF Orientation", name) 101 f, err := os.Open(name) 102 if err != nil { 103 t.Fatal(err) 104 } 105 defer f.Close() 106 im, _, err := Decode(f, nil) 107 if err != nil { 108 t.Fatal(err) 109 } 110 if !equals(im, straightF) { 111 t.Fatalf("%v not properly corrected with exif", name) 112 } 113 } 114 } 115 116 // TestForcedCorrection tests that manually specifying the 117 // rotation/flipping to be applied when decoding works as 118 // expected. 119 func TestForcedCorrection(t *testing.T) { 120 samples := sampleNames(t) 121 straightF := straightFImage(t) 122 for _, v := range samples { 123 if strings.HasSuffix(v, "-s.jpg") { 124 continue 125 } 126 name := filepath.Join(datadir, v) 127 t.Logf("forced correction of %s", name) 128 f, err := os.Open(name) 129 if err != nil { 130 t.Fatal(err) 131 } 132 defer f.Close() 133 num := name[10] 134 angle, flipMode := 0, 0 135 switch num { 136 case '1': 137 // nothing to do 138 case '2': 139 flipMode = 2 140 case '3': 141 angle = 180 142 case '4': 143 angle = 180 144 flipMode = 2 145 case '5': 146 angle = -90 147 flipMode = 2 148 case '6': 149 angle = -90 150 case '7': 151 angle = 90 152 flipMode = 2 153 case '8': 154 angle = 90 155 } 156 im, _, err := Decode(f, &DecodeOpts{Rotate: angle, Flip: FlipDirection(flipMode)}) 157 if err != nil { 158 t.Fatal(err) 159 } 160 if !equals(im, straightF) { 161 t.Fatalf("%v not properly corrected", name) 162 } 163 } 164 } 165 166 // TestRescale verifies that rescaling an image, without 167 // any rotation/flipping, produces the expected image. 168 func TestRescale(t *testing.T) { 169 name := filepath.Join(datadir, "f1.jpg") 170 t.Logf("rescaling %s with half-width and half-height", name) 171 f, err := os.Open(name) 172 if err != nil { 173 t.Fatal(err) 174 } 175 defer f.Close() 176 rescaledIm, _, err := Decode(f, &DecodeOpts{ScaleWidth: 0.5, ScaleHeight: 0.5}) 177 if err != nil { 178 t.Fatal(err) 179 } 180 181 smallIm := smallStraightFImage(t) 182 183 gotB, wantB := rescaledIm.Bounds(), smallIm.Bounds() 184 if !gotB.Eq(wantB) { 185 t.Errorf("(scale) %v bounds not equal, got %v want %v", name, gotB, wantB) 186 } 187 if !equals(rescaledIm, smallIm) { 188 t.Errorf("(scale) %v pixels not equal", name) 189 } 190 191 _, err = f.Seek(0, os.SEEK_SET) 192 if err != nil { 193 t.Fatal(err) 194 } 195 196 rescaledIm, _, err = Decode(f, &DecodeOpts{MaxWidth: 2000, MaxHeight: 40}) 197 if err != nil { 198 t.Fatal(err) 199 } 200 gotB = rescaledIm.Bounds() 201 if !gotB.Eq(wantB) { 202 t.Errorf("(max) %v bounds not equal, got %v want %v", name, gotB, wantB) 203 } 204 if !equals(rescaledIm, smallIm) { 205 t.Errorf("(max) %v pixels not equal", name) 206 } 207 } 208 209 // TestRescaleEXIF verifies that rescaling an image, followed 210 // by the automatic EXIF correction (rotation/flipping), 211 // produces the expected image. All the possible correction 212 // modes are tested. 213 func TestRescaleEXIF(t *testing.T) { 214 smallStraightF := smallStraightFImage(t) 215 samples := sampleNames(t) 216 for _, v := range samples { 217 if !strings.Contains(v, "exif") { 218 continue 219 } 220 name := filepath.Join(datadir, v) 221 t.Logf("rescaling %s with half-width and half-height", name) 222 f, err := os.Open(name) 223 if err != nil { 224 t.Fatal(err) 225 } 226 defer f.Close() 227 rescaledIm, _, err := Decode(f, &DecodeOpts{ScaleWidth: 0.5, ScaleHeight: 0.5}) 228 if err != nil { 229 t.Fatal(err) 230 } 231 232 gotB, wantB := rescaledIm.Bounds(), smallStraightF.Bounds() 233 if !gotB.Eq(wantB) { 234 t.Errorf("(scale) %v bounds not equal, got %v want %v", name, gotB, wantB) 235 } 236 if !equals(rescaledIm, smallStraightF) { 237 t.Errorf("(scale) %v pixels not equal", name) 238 } 239 240 _, err = f.Seek(0, os.SEEK_SET) 241 if err != nil { 242 t.Fatal(err) 243 } 244 rescaledIm, _, err = Decode(f, &DecodeOpts{MaxWidth: 2000, MaxHeight: 40}) 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 gotB = rescaledIm.Bounds() 250 if !gotB.Eq(wantB) { 251 t.Errorf("(max) %v bounds not equal, got %v want %v", name, gotB, wantB) 252 } 253 if !equals(rescaledIm, smallStraightF) { 254 t.Errorf("(max) %v pixels not equal", name) 255 } 256 } 257 } 258 259 // TODO(mpl): move this test to the goexif lib if/when we contribute 260 // back the DateTime stuff to upstream. 261 func TestDateTime(t *testing.T) { 262 f, err := os.Open(filepath.Join(datadir, "f1-exif.jpg")) 263 if err != nil { 264 t.Fatal(err) 265 } 266 defer f.Close() 267 ex, err := exif.Decode(f) 268 if err != nil { 269 t.Fatal(err) 270 } 271 got, err := ex.DateTime() 272 if err != nil { 273 t.Fatal(err) 274 } 275 exifTimeLayout := "2006:01:02 15:04:05" 276 want, err := time.Parse(exifTimeLayout, "2012:11:04 05:42:02") 277 if err != nil { 278 t.Fatal(err) 279 } 280 if got != want { 281 t.Fatalf("Creation times differ; got %v, want: %v\n", got, want) 282 } 283 }