1/*
2 * Copyright 2014 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
8#include "SkBitmap.h"
9#include "SkBitmapHasher.h"
10#include "SkData.h"
11#include "SkJSONCPP.h"
12#include "SkOSFile.h"
13#include "SkStream.h"
14#include "SkTypes.h"
15
16#include "image_expectations.h"
17
18/*
19 * TODO(epoger): Make constant strings consistent instead of mixing hypenated and camel-caps.
20 *
21 * TODO(epoger): Similar constants are already maintained in 2 other places:
22 * gm/gm_json.py and gm/gm_expectations.cpp. We shouldn't add yet a third place.
23 * Figure out a way to share the definitions instead.
24 *
25 * Note that, as of https://codereview.chromium.org/226293002 , the JSON
26 * schema used here has started to differ from the one in gm_expectations.cpp .
27 * TODO(epoger): Consider getting GM and render_pictures to use the same JSON
28 * output module.
29 */
30const static char kJsonKey_ActualResults[] = "actual-results";
31const static char kJsonKey_Descriptions[] = "descriptions";
32const static char kJsonKey_ExpectedResults[] = "expected-results";
33const static char kJsonKey_ImageBaseGSUrl[] = "image-base-gs-url";
34const static char kJsonKey_Header[] = "header";
35const static char kJsonKey_Header_Type[] = "type";
36const static char kJsonKey_Header_Revision[] = "revision";
37const static char kJsonKey_Image_ChecksumAlgorithm[] = "checksumAlgorithm";
38const static char kJsonKey_Image_ChecksumValue[] = "checksumValue";
39const static char kJsonKey_Image_ComparisonResult[] = "comparisonResult";
40const static char kJsonKey_Image_Filepath[] = "filepath";
41const static char kJsonKey_Image_IgnoreFailure[] = "ignoreFailure";
42const static char kJsonKey_Source_TiledImages[] = "tiled-images";
43const static char kJsonKey_Source_WholeImage[] = "whole-image";
44// Values (not keys) that are written out by this JSON generator
45const static char kJsonValue_Header_Type[] = "ChecksummedImages";
46const static int kJsonValue_Header_Revision = 1;
47const static char kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5[] = "bitmap-64bitMD5";
48const static char kJsonValue_Image_ComparisonResult_Failed[] = "failed";
49const static char kJsonValue_Image_ComparisonResult_FailureIgnored[] = "failure-ignored";
50const static char kJsonValue_Image_ComparisonResult_NoComparison[] = "no-comparison";
51const static char kJsonValue_Image_ComparisonResult_Succeeded[] = "succeeded";
52
53namespace sk_tools {
54
55    // ImageDigest class...
56
57    ImageDigest::ImageDigest(const SkBitmap &bitmap) :
58        fBitmap(bitmap), fHashValue(0), fComputedHashValue(false) {}
59
60    ImageDigest::ImageDigest(const SkString &hashType, uint64_t hashValue) :
61        fBitmap(), fHashValue(hashValue), fComputedHashValue(true) {
62        if (!hashType.equals(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5)) {
63            SkDebugf("unsupported hashType '%s'\n", hashType.c_str());
64            SkFAIL("unsupported hashType (see above)");
65        }
66    }
67
68    bool ImageDigest::equals(ImageDigest &other) {
69        // TODO(epoger): The current implementation assumes that this
70        // and other always have hashType kJsonKey_Hashtype_Bitmap_64bitMD5
71        return (this->getHashValue() == other.getHashValue());
72    }
73
74    SkString ImageDigest::getHashType() {
75        // TODO(epoger): The current implementation assumes that the
76        // result digest is always of type kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5 .
77        return SkString(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5);
78    }
79
80    uint64_t ImageDigest::getHashValue() {
81        if (!this->fComputedHashValue) {
82            if (!SkBitmapHasher::ComputeDigest(this->fBitmap, &this->fHashValue)) {
83                SkFAIL("unable to compute image digest");
84            }
85            this->fComputedHashValue = true;
86        }
87        return this->fHashValue;
88    }
89
90    // BitmapAndDigest class...
91
92    BitmapAndDigest::BitmapAndDigest(const SkBitmap &bitmap) :
93        fBitmap(bitmap), fImageDigest(bitmap) {}
94
95    const SkBitmap *BitmapAndDigest::getBitmapPtr() const {return &fBitmap;}
96
97    ImageDigest *BitmapAndDigest::getImageDigestPtr() {return &fImageDigest;}
98
99    // Expectation class...
100
101    // For when we need a valid ImageDigest, but we don't care what it is.
102    static const ImageDigest kDummyImageDigest(
103        SkString(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5), 0);
104
105    Expectation::Expectation(bool ignoreFailure) :
106        fIsEmpty(true), fIgnoreFailure(ignoreFailure), fImageDigest(kDummyImageDigest) {}
107
108    Expectation::Expectation(const SkString &hashType, uint64_t hashValue, bool ignoreFailure) :
109        fIsEmpty(false), fIgnoreFailure(ignoreFailure), fImageDigest(hashType, hashValue) {}
110
111    Expectation::Expectation(const SkBitmap& bitmap, bool ignoreFailure) :
112        fIsEmpty(false), fIgnoreFailure(ignoreFailure), fImageDigest(bitmap) {}
113
114    bool Expectation::ignoreFailure() const { return this->fIgnoreFailure; }
115
116    bool Expectation::empty() const { return this->fIsEmpty; }
117
118    bool Expectation::matches(ImageDigest &imageDigest) {
119        return !(this->fIsEmpty) && (this->fImageDigest.equals(imageDigest));
120    }
121
122    // ImageResultsAndExpectations class...
123
124    bool ImageResultsAndExpectations::readExpectationsFile(const char *jsonPath) {
125        if (NULL == jsonPath) {
126            SkDebugf("JSON expectations filename not specified\n");
127            return false;
128        }
129        SkFILE* filePtr = sk_fopen(jsonPath, kRead_SkFILE_Flag);
130        if (NULL == filePtr) {
131            SkDebugf("JSON expectations file '%s' does not exist\n", jsonPath);
132            return false;
133        }
134        size_t size = sk_fgetsize(filePtr);
135        if (0 == size) {
136            SkDebugf("JSON expectations file '%s' is empty, so no expectations\n", jsonPath);
137            sk_fclose(filePtr);
138            fExpectedResults.clear();
139            return true;
140        }
141        bool parsedJson = Parse(filePtr, &fExpectedJsonRoot);
142        sk_fclose(filePtr);
143        if (!parsedJson) {
144            SkDebugf("Failed to parse JSON expectations file '%s'\n", jsonPath);
145            return false;
146        }
147        Json::Value header = fExpectedJsonRoot[kJsonKey_Header];
148        Json::Value headerType = header[kJsonKey_Header_Type];
149        Json::Value headerRevision = header[kJsonKey_Header_Revision];
150        if (strcmp(headerType.asCString(), kJsonValue_Header_Type)) {
151            SkDebugf("JSON expectations file '%s': expected headerType '%s', found '%s'\n",
152                     jsonPath, kJsonValue_Header_Type, headerType.asCString());
153            return false;
154        }
155        if (headerRevision.asInt() != kJsonValue_Header_Revision) {
156            SkDebugf("JSON expectations file '%s': expected headerRevision %d, found %d\n",
157                     jsonPath, kJsonValue_Header_Revision, headerRevision.asInt());
158            return false;
159        }
160        fExpectedResults = fExpectedJsonRoot[kJsonKey_ExpectedResults];
161        return true;
162    }
163
164    void ImageResultsAndExpectations::add(const char *sourceName, const char *fileName,
165                                          ImageDigest &digest, const int *tileNumber) {
166        // Get expectation, if any.
167        Expectation expectation = this->getExpectation(sourceName, tileNumber);
168
169        // Fill in info about the actual result.
170        Json::Value actualChecksumAlgorithm = digest.getHashType().c_str();
171        Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue());
172        Json::Value actualImage;
173        actualImage[kJsonKey_Image_ChecksumAlgorithm] = actualChecksumAlgorithm;
174        actualImage[kJsonKey_Image_ChecksumValue] = actualChecksumValue;
175        actualImage[kJsonKey_Image_Filepath] = fileName;
176
177        // Compare against expectedImage to fill in comparisonResult.
178        Json::Value comparisonResult;
179        if (expectation.empty()) {
180            comparisonResult = kJsonValue_Image_ComparisonResult_NoComparison;
181        } else if (expectation.matches(digest)) {
182            comparisonResult = kJsonValue_Image_ComparisonResult_Succeeded;
183        } else if (expectation.ignoreFailure()) {
184            comparisonResult = kJsonValue_Image_ComparisonResult_FailureIgnored;
185        } else {
186            comparisonResult = kJsonValue_Image_ComparisonResult_Failed;
187        }
188        actualImage[kJsonKey_Image_ComparisonResult] = comparisonResult;
189
190        // Add this actual result to our collection.
191        if (NULL == tileNumber) {
192            fActualResults[sourceName][kJsonKey_Source_WholeImage] = actualImage;
193        } else {
194            fActualResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber] = actualImage;
195        }
196    }
197
198    void ImageResultsAndExpectations::addDescription(const char *key, const char *value) {
199        fDescriptions[key] = value;
200    }
201
202    void ImageResultsAndExpectations::setImageBaseGSUrl(const char *imageBaseGSUrl) {
203        fImageBaseGSUrl = imageBaseGSUrl;
204    }
205
206    Expectation ImageResultsAndExpectations::getExpectation(const char *sourceName,
207                                                            const int *tileNumber) {
208        if (fExpectedResults.isNull()) {
209            return Expectation();
210        }
211
212        Json::Value expectedImage;
213        if (NULL == tileNumber) {
214            expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage];
215        } else {
216            expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber];
217        }
218        if (expectedImage.isNull()) {
219            return Expectation();
220        }
221
222        bool ignoreFailure = (expectedImage[kJsonKey_Image_IgnoreFailure] == true);
223        return Expectation(SkString(expectedImage[kJsonKey_Image_ChecksumAlgorithm].asCString()),
224                           expectedImage[kJsonKey_Image_ChecksumValue].asUInt64(),
225                           ignoreFailure);
226    }
227
228    void ImageResultsAndExpectations::writeToFile(const char *filename) const {
229        Json::Value header;
230        header[kJsonKey_Header_Type] = kJsonValue_Header_Type;
231        header[kJsonKey_Header_Revision] = kJsonValue_Header_Revision;
232        Json::Value root;
233        root[kJsonKey_ActualResults] = fActualResults;
234        root[kJsonKey_Descriptions] = fDescriptions;
235        root[kJsonKey_Header] = header;
236        root[kJsonKey_ImageBaseGSUrl] = fImageBaseGSUrl;
237        std::string jsonStdString = root.toStyledString();
238        SkFILEWStream stream(filename);
239        stream.write(jsonStdString.c_str(), jsonStdString.length());
240    }
241
242    /*static*/ bool ImageResultsAndExpectations::Parse(SkFILE *filePtr,
243                                                       Json::Value *jsonRoot) {
244        SkAutoDataUnref dataRef(SkData::NewFromFILE(filePtr));
245        if (NULL == dataRef.get()) {
246            return false;
247        }
248
249        const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data());
250        size_t size = dataRef.get()->size();
251        Json::Reader reader;
252        if (!reader.parse(bytes, bytes+size, *jsonRoot)) {
253            return false;
254        }
255
256        return true;
257    }
258
259} // namespace sk_tools
260