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