| 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 |