1 /* 2 * Copyright 2017 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 package main 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 "image" 14 "image/draw" 15 "image/png" 16 "log" 17 "net/http" 18 "os" 19 "path" 20 "sort" 21 "strings" 22 "sync" 23 24 "go.skia.org/infra/golden/go/search" 25 ) 26 27 const ( 28 min_png = "min.png" 29 max_png = "max.png" 30 ) 31 32 type ExportTestRecordArray []search.ExportTestRecord 33 34 func (a ExportTestRecordArray) Len() int { return len(a) } 35 func (a ExportTestRecordArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 36 func (a ExportTestRecordArray) Less(i, j int) bool { return a[i].TestName < a[j].TestName } 37 38 func in(v string, a []string) bool { 39 for _, u := range a { 40 if u == v { 41 return true 42 } 43 } 44 return false 45 } 46 47 func clampU8(v int) uint8 { 48 if v < 0 { 49 return 0 50 } else if v > 255 { 51 return 255 52 } 53 return uint8(v) 54 } 55 56 func processTest(testName string, imgUrls []string, output string) (bool, error) { 57 if strings.ContainsRune(testName, '/') { 58 return false, nil 59 } 60 output_directory := path.Join(output, testName) 61 var img_max image.NRGBA 62 var img_min image.NRGBA 63 for _, url := range imgUrls { 64 resp, err := http.Get(url) 65 if err != nil { 66 return false, err 67 } 68 img, err := png.Decode(resp.Body) 69 resp.Body.Close() 70 if err != nil { 71 return false, err 72 } 73 if img_max.Rect.Max.X == 0 { 74 // N.B. img_max.Pix may alias img.Pix (if they're already NRGBA). 75 img_max = toNrgba(img) 76 img_min = copyNrgba(img_max) 77 continue 78 } 79 w := img.Bounds().Max.X - img.Bounds().Min.X 80 h := img.Bounds().Max.Y - img.Bounds().Min.Y 81 if img_max.Rect.Max.X != w || img_max.Rect.Max.Y != h { 82 return false, errors.New("size mismatch") 83 } 84 img_nrgba := toNrgba(img) 85 for i, value := range img_nrgba.Pix { 86 if value > img_max.Pix[i] { 87 img_max.Pix[i] = value 88 } else if value < img_min.Pix[i] { 89 img_min.Pix[i] = value 90 } 91 } 92 } 93 if img_max.Rect.Max.X == 0 { 94 return false, nil 95 } 96 97 if err := os.Mkdir(output_directory, os.ModePerm); err != nil && !os.IsExist(err) { 98 return false, err 99 } 100 if err := writePngToFile(path.Join(output_directory, min_png), &img_min); err != nil { 101 return false, err 102 } 103 if err := writePngToFile(path.Join(output_directory, max_png), &img_max); err != nil { 104 return false, err 105 } 106 return true, nil 107 } 108 109 type LockedStringList struct { 110 List []string 111 mux sync.Mutex 112 } 113 114 func (l *LockedStringList) add(v string) { 115 l.mux.Lock() 116 defer l.mux.Unlock() 117 l.List = append(l.List, v) 118 } 119 120 121 func readMetaJsonFile(filename string) ([]search.ExportTestRecord, error) { 122 file, err := os.Open(filename) 123 if err != nil { 124 return nil, err 125 } 126 dec := json.NewDecoder(file) 127 var records []search.ExportTestRecord 128 err = dec.Decode(&records) 129 return records, err 130 } 131 132 func writePngToFile(path string, img image.Image) error { 133 file, err := os.Create(path) 134 if err != nil { 135 return err 136 } 137 defer file.Close() 138 return png.Encode(file, img) 139 } 140 141 // to_nrgb() may return a shallow copy of img if it's already NRGBA. 142 func toNrgba(img image.Image) image.NRGBA { 143 switch v := img.(type) { 144 case *image.NRGBA: 145 return *v 146 } 147 nimg := *image.NewNRGBA(img.Bounds()) 148 draw.Draw(&nimg, img.Bounds(), img, image.Point{0, 0}, draw.Src) 149 return nimg 150 } 151 152 func copyNrgba(src image.NRGBA) image.NRGBA { 153 dst := image.NRGBA{make([]uint8, len(src.Pix)), src.Stride, src.Rect} 154 copy(dst.Pix, src.Pix) 155 return dst 156 } 157 158 func main() { 159 if len(os.Args) != 3 { 160 log.Printf("Usage:\n %s INPUT.json OUTPUT_DIRECTORY\n\n", os.Args[0]) 161 os.Exit(1) 162 } 163 input := os.Args[1] 164 output := os.Args[2] 165 // output is removed and replaced with a clean directory. 166 if err := os.RemoveAll(output); err != nil && !os.IsNotExist(err) { 167 log.Fatal(err) 168 } 169 if err := os.MkdirAll(output, os.ModePerm); err != nil && !os.IsExist(err) { 170 log.Fatal(err) 171 } 172 173 records, err := readMetaJsonFile(input) 174 if err != nil { 175 log.Fatal(err) 176 } 177 sort.Sort(ExportTestRecordArray(records)) 178 179 var results LockedStringList 180 var wg sync.WaitGroup 181 for _, record := range records { 182 var goodUrls []string 183 for _, digest := range record.Digests { 184 if (in("vk", digest.ParamSet["config"]) || 185 in("gles", digest.ParamSet["config"])) && 186 digest.Status == "positive" { 187 goodUrls = append(goodUrls, digest.URL) 188 } 189 } 190 wg.Add(1) 191 go func(testName string, imgUrls []string, output string, results* LockedStringList) { 192 defer wg.Done() 193 success, err := processTest(testName, imgUrls, output) 194 if err != nil { 195 log.Fatal(err) 196 } 197 if success { 198 results.add(testName) 199 } 200 fmt.Printf("\r%-60s", testName) 201 }(record.TestName, goodUrls, output, &results) 202 } 203 wg.Wait() 204 fmt.Printf("\r%60s\n", "") 205 sort.Strings(results.List) 206 modelFile, err := os.Create(path.Join(output, "models.txt")) 207 if err != nil { 208 log.Fatal(err) 209 } 210 for _, v := range results.List { 211 fmt.Fprintln(modelFile, v) 212 } 213 } 214