OLD | NEW |
| (Empty) |
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 an
d 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 */ | |
30 const static char kJsonKey_ActualResults[] = "actual-results"; | |
31 const static char kJsonKey_Descriptions[] = "descriptions"; | |
32 const static char kJsonKey_ExpectedResults[] = "expected-results"; | |
33 const static char kJsonKey_ImageBaseGSUrl[] = "image-base-gs-url"; | |
34 const static char kJsonKey_Header[] = "header"; | |
35 const static char kJsonKey_Header_Type[] = "type"; | |
36 const static char kJsonKey_Header_Revision[] = "revision"; | |
37 const static char kJsonKey_Image_ChecksumAlgorithm[] = "checksumAlgorithm"; | |
38 const static char kJsonKey_Image_ChecksumValue[] = "checksumValue"; | |
39 const static char kJsonKey_Image_ComparisonResult[] = "comparisonResult"; | |
40 const static char kJsonKey_Image_Filepath[] = "filepath"; | |
41 const static char kJsonKey_Image_IgnoreFailure[] = "ignoreFailure"; | |
42 const static char kJsonKey_Source_TiledImages[] = "tiled-images"; | |
43 const static char kJsonKey_Source_WholeImage[] = "whole-image"; | |
44 // Values (not keys) that are written out by this JSON generator | |
45 const static char kJsonValue_Header_Type[] = "ChecksummedImages"; | |
46 const static int kJsonValue_Header_Revision = 1; | |
47 const static char kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5[] = "bitmap-
64bitMD5"; | |
48 const static char kJsonValue_Image_ComparisonResult_Failed[] = "failed"; | |
49 const static char kJsonValue_Image_ComparisonResult_FailureIgnored[] = "failure-
ignored"; | |
50 const static char kJsonValue_Image_ComparisonResult_NoComparison[] = "no-compari
son"; | |
51 const static char kJsonValue_Image_ComparisonResult_Succeeded[] = "succeeded"; | |
52 | |
53 namespace 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_Bi
tmap64bitMD5 . | |
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(kDummyImageD
igest) {} | |
107 | |
108 Expectation::Expectation(const SkString &hashType, uint64_t hashValue, bool
ignoreFailure) : | |
109 fIsEmpty(false), fIgnoreFailure(ignoreFailure), fImageDigest(hashType, h
ashValue) {} | |
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 (nullptr == jsonPath) { | |
126 SkDebugf("JSON expectations filename not specified\n"); | |
127 return false; | |
128 } | |
129 SkFILE* filePtr = sk_fopen(jsonPath, kRead_SkFILE_Flag); | |
130 if (nullptr == 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', fou
nd '%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, f
ound %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 *fi
leName, | |
165 ImageDigest &digest, const int *tileNu
mber) { | |
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 (nullptr == 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 *imageBaseGSU
rl) { | |
203 fImageBaseGSUrl = imageBaseGSUrl; | |
204 } | |
205 | |
206 Expectation ImageResultsAndExpectations::getExpectation(const char *sourceNa
me, | |
207 const int *tileNumbe
r) { | |
208 if (fExpectedResults.isNull()) { | |
209 return Expectation(); | |
210 } | |
211 | |
212 Json::Value expectedImage; | |
213 if (nullptr == tileNumber) { | |
214 expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeIm
age]; | |
215 } else { | |
216 expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledIm
ages][*tileNumber]; | |
217 } | |
218 if (expectedImage.isNull()) { | |
219 return Expectation(); | |
220 } | |
221 | |
222 bool ignoreFailure = (expectedImage[kJsonKey_Image_IgnoreFailure] == tru
e); | |
223 return Expectation(SkString(expectedImage[kJsonKey_Image_ChecksumAlgorit
hm].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 (nullptr == 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 | |
OLD | NEW |