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 */ 7package main 8 9import ( 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 27const ( 28 min_png = "min.png" 29 max_png = "max.png" 30) 31 32type ExportTestRecordArray []search.ExportTestRecord 33 34func (a ExportTestRecordArray) Len() int { return len(a) } 35func (a ExportTestRecordArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 36func (a ExportTestRecordArray) Less(i, j int) bool { return a[i].TestName < a[j].TestName } 37 38func 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 47func 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 56func processTest(testName string, imgUrls []string, output string) error { 57 if strings.ContainsRune(testName, '/') { 58 return 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 err 67 } 68 img, err := png.Decode(resp.Body) 69 resp.Body.Close() 70 if err != nil { 71 return 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 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 nil 95 } 96 97 if err := os.Mkdir(output_directory, os.ModePerm); err != nil && !os.IsExist(err) { 98 return err 99 } 100 if err := writePngToFile(path.Join(output_directory, min_png), &img_min); err != nil { 101 return err 102 } 103 if err := writePngToFile(path.Join(output_directory, max_png), &img_max); err != nil { 104 return err 105 } 106 return nil 107 108} 109 110func readMetaJsonFile(filename string) ([]search.ExportTestRecord, error) { 111 file, err := os.Open(filename) 112 if err != nil { 113 return nil, err 114 } 115 dec := json.NewDecoder(file) 116 var records []search.ExportTestRecord 117 err = dec.Decode(&records) 118 return records, err 119} 120 121func writePngToFile(path string, img image.Image) error { 122 file, err := os.Create(path) 123 if err != nil { 124 return err 125 } 126 defer file.Close() 127 return png.Encode(file, img) 128} 129 130// to_nrgb() may return a shallow copy of img if it's already NRGBA. 131func toNrgba(img image.Image) image.NRGBA { 132 switch v := img.(type) { 133 case *image.NRGBA: 134 return *v 135 } 136 nimg := *image.NewNRGBA(img.Bounds()) 137 draw.Draw(&nimg, img.Bounds(), img, image.Point{0, 0}, draw.Src) 138 return nimg 139} 140 141func copyNrgba(src image.NRGBA) image.NRGBA { 142 dst := image.NRGBA{make([]uint8, len(src.Pix)), src.Stride, src.Rect} 143 copy(dst.Pix, src.Pix) 144 return dst 145} 146 147func main() { 148 if len(os.Args) != 3 { 149 log.Printf("Usage:\n %s INPUT.json OUTPUT_DIRECTORY\n\n", os.Args[0]) 150 os.Exit(1) 151 } 152 input := os.Args[1] 153 output := os.Args[2] 154 // output is removed and replaced with a clean directory. 155 if err := os.RemoveAll(output); err != nil && !os.IsNotExist(err) { 156 log.Fatal(err) 157 } 158 if err := os.MkdirAll(output, os.ModePerm); err != nil && !os.IsExist(err) { 159 log.Fatal(err) 160 } 161 162 records, err := readMetaJsonFile(input) 163 if err != nil { 164 log.Fatal(err) 165 } 166 sort.Sort(ExportTestRecordArray(records)) 167 168 var wg sync.WaitGroup 169 for _, record := range records { 170 var goodUrls []string 171 for _, digest := range record.Digests { 172 if (in("vk", digest.ParamSet["config"]) || 173 in("gles", digest.ParamSet["config"])) && 174 digest.Status == "positive" { 175 goodUrls = append(goodUrls, digest.URL) 176 } 177 } 178 wg.Add(1) 179 go func(testName string, imgUrls []string, output string) { 180 defer wg.Done() 181 if err := processTest(testName, imgUrls, output); err != nil { 182 log.Fatal(err) 183 } 184 fmt.Printf("\r%-60s", testName) 185 }(record.TestName, goodUrls, output) 186 } 187 wg.Wait() 188 fmt.Printf("\r%60s\n", "") 189} 190