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 */ 30 const static char kJsonKey_ActualResults[] = "actual-results"; 31 const static char kJsonKey_ExpectedResults[] = "expected-results"; 32 const static char kJsonKey_Header[] = "header"; 33 const static char kJsonKey_Header_Type[] = "type"; 34 const static char kJsonKey_Header_Revision[] = "revision"; 35 const static char kJsonKey_Image_ChecksumAlgorithm[] = "checksumAlgorithm"; 36 const static char kJsonKey_Image_ChecksumValue[] = "checksumValue"; 37 const static char kJsonKey_Image_ComparisonResult[] = "comparisonResult"; 38 const static char kJsonKey_Image_Filepath[] = "filepath"; 39 const static char kJsonKey_Image_IgnoreFailure[] = "ignoreFailure"; 40 const static char kJsonKey_Source_TiledImages[] = "tiled-images"; 41 const static char kJsonKey_Source_WholeImage[] = "whole-image"; 42 // Values (not keys) that are written out by this JSON generator 43 const static char kJsonValue_Header_Type[] = "ChecksummedImages"; 44 const static int kJsonValue_Header_Revision = 1; 45 const static char kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5[] = "bitmap-64bitMD5"; 46 const static char kJsonValue_Image_ComparisonResult_Failed[] = "failed"; 47 const static char kJsonValue_Image_ComparisonResult_FailureIgnored[] = "failure-ignored"; 48 const static char kJsonValue_Image_ComparisonResult_NoComparison[] = "no-comparison"; 49 const static char kJsonValue_Image_ComparisonResult_Succeeded[] = "succeeded"; 50 51 namespace sk_tools { 52 53 // ImageDigest class... 54 ImageDigest(const SkBitmap & bitmap)55 ImageDigest::ImageDigest(const SkBitmap &bitmap) { 56 if (!SkBitmapHasher::ComputeDigest(bitmap, &fHashValue)) { 57 SkFAIL("unable to compute image digest"); 58 } 59 } 60 ImageDigest(const SkString & hashType,uint64_t hashValue)61 ImageDigest::ImageDigest(const SkString &hashType, uint64_t hashValue) { 62 if (!hashType.equals(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5)) { 63 SkFAIL((SkString("unsupported hashType ")+=hashType).c_str()); 64 } else { 65 fHashValue = hashValue; 66 } 67 } 68 getHashType() const69 SkString ImageDigest::getHashType() const { 70 // TODO(epoger): The current implementation assumes that the 71 // result digest is always of type kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5 . 72 return SkString(kJsonValue_Image_ChecksumAlgorithm_Bitmap64bitMD5); 73 } 74 getHashValue() const75 uint64_t ImageDigest::getHashValue() const { 76 return fHashValue; 77 } 78 79 // BitmapAndDigest class... 80 BitmapAndDigest(const SkBitmap & bitmap)81 BitmapAndDigest::BitmapAndDigest(const SkBitmap &bitmap) : fBitmap(bitmap) { 82 } 83 getImageDigestPtr()84 const ImageDigest *BitmapAndDigest::getImageDigestPtr() { 85 if (NULL == fImageDigestRef.get()) { 86 fImageDigestRef.reset(SkNEW_ARGS(ImageDigest, (fBitmap))); 87 } 88 return fImageDigestRef.get(); 89 } 90 getBitmapPtr() const91 const SkBitmap *BitmapAndDigest::getBitmapPtr() const { 92 return &fBitmap; 93 } 94 95 // ImageResultsAndExpectations class... 96 readExpectationsFile(const char * jsonPath)97 bool ImageResultsAndExpectations::readExpectationsFile(const char *jsonPath) { 98 if (NULL == jsonPath) { 99 SkDebugf("JSON expectations filename not specified\n"); 100 return false; 101 } 102 SkFILE* filePtr = sk_fopen(jsonPath, kRead_SkFILE_Flag); 103 if (NULL == filePtr) { 104 SkDebugf("JSON expectations file '%s' does not exist\n", jsonPath); 105 return false; 106 } 107 size_t size = sk_fgetsize(filePtr); 108 if (0 == size) { 109 SkDebugf("JSON expectations file '%s' is empty, so no expectations\n", jsonPath); 110 sk_fclose(filePtr); 111 fExpectedResults.clear(); 112 return true; 113 } 114 bool parsedJson = Parse(filePtr, &fExpectedJsonRoot); 115 sk_fclose(filePtr); 116 if (!parsedJson) { 117 SkDebugf("Failed to parse JSON expectations file '%s'\n", jsonPath); 118 return false; 119 } 120 Json::Value header = fExpectedJsonRoot[kJsonKey_Header]; 121 Json::Value headerType = header[kJsonKey_Header_Type]; 122 Json::Value headerRevision = header[kJsonKey_Header_Revision]; 123 if (strcmp(headerType.asCString(), kJsonValue_Header_Type)) { 124 SkDebugf("JSON expectations file '%s': expected headerType '%s', found '%s'\n", 125 jsonPath, kJsonValue_Header_Type, headerType.asCString()); 126 return false; 127 } 128 if (headerRevision.asInt() != kJsonValue_Header_Revision) { 129 SkDebugf("JSON expectations file '%s': expected headerRevision %d, found %d\n", 130 jsonPath, kJsonValue_Header_Revision, headerRevision.asInt()); 131 return false; 132 } 133 fExpectedResults = fExpectedJsonRoot[kJsonKey_ExpectedResults]; 134 return true; 135 } 136 add(const char * sourceName,const char * fileName,const ImageDigest & digest,const int * tileNumber)137 void ImageResultsAndExpectations::add(const char *sourceName, const char *fileName, 138 const ImageDigest &digest, const int *tileNumber) { 139 // Get expectation, if any. 140 Json::Value expectedImage; 141 if (!fExpectedResults.isNull()) { 142 if (NULL == tileNumber) { 143 expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage]; 144 } else { 145 expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages] 146 [*tileNumber]; 147 } 148 } 149 150 // Fill in info about the actual result itself. 151 Json::Value actualChecksumAlgorithm = digest.getHashType().c_str(); 152 Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue()); 153 Json::Value actualImage; 154 actualImage[kJsonKey_Image_ChecksumAlgorithm] = actualChecksumAlgorithm; 155 actualImage[kJsonKey_Image_ChecksumValue] = actualChecksumValue; 156 actualImage[kJsonKey_Image_Filepath] = fileName; 157 158 // Compare against expectedImage to fill in comparisonResult. 159 Json::Value comparisonResult = kJsonValue_Image_ComparisonResult_NoComparison; 160 if (!expectedImage.isNull()) { 161 if ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) && 162 (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue])) { 163 comparisonResult = kJsonValue_Image_ComparisonResult_Succeeded; 164 } else if (expectedImage[kJsonKey_Image_IgnoreFailure] == true) { 165 comparisonResult = kJsonValue_Image_ComparisonResult_FailureIgnored; 166 } else { 167 comparisonResult = kJsonValue_Image_ComparisonResult_Failed; 168 } 169 } 170 actualImage[kJsonKey_Image_ComparisonResult] = comparisonResult; 171 172 // Add this actual result to our collection. 173 if (NULL == tileNumber) { 174 fActualResults[sourceName][kJsonKey_Source_WholeImage] = actualImage; 175 } else { 176 fActualResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber] = actualImage; 177 } 178 } 179 matchesExpectation(const char * sourceName,const ImageDigest & digest,const int * tileNumber)180 bool ImageResultsAndExpectations::matchesExpectation(const char *sourceName, 181 const ImageDigest &digest, 182 const int *tileNumber) { 183 if (fExpectedResults.isNull()) { 184 return false; 185 } 186 187 Json::Value expectedImage; 188 if (NULL == tileNumber) { 189 expectedImage = fExpectedResults[sourceName][kJsonKey_Source_WholeImage]; 190 } else { 191 expectedImage = fExpectedResults[sourceName][kJsonKey_Source_TiledImages][*tileNumber]; 192 } 193 if (expectedImage.isNull()) { 194 return false; 195 } 196 197 Json::Value actualChecksumAlgorithm = digest.getHashType().c_str(); 198 Json::Value actualChecksumValue = Json::UInt64(digest.getHashValue()); 199 return ((actualChecksumAlgorithm == expectedImage[kJsonKey_Image_ChecksumAlgorithm]) && 200 (actualChecksumValue == expectedImage[kJsonKey_Image_ChecksumValue])); 201 } 202 writeToFile(const char * filename) const203 void ImageResultsAndExpectations::writeToFile(const char *filename) const { 204 Json::Value header; 205 header[kJsonKey_Header_Type] = kJsonValue_Header_Type; 206 header[kJsonKey_Header_Revision] = kJsonValue_Header_Revision; 207 Json::Value root; 208 root[kJsonKey_Header] = header; 209 root[kJsonKey_ActualResults] = fActualResults; 210 std::string jsonStdString = root.toStyledString(); 211 SkFILEWStream stream(filename); 212 stream.write(jsonStdString.c_str(), jsonStdString.length()); 213 } 214 Parse(SkFILE * filePtr,Json::Value * jsonRoot)215 /*static*/ bool ImageResultsAndExpectations::Parse(SkFILE *filePtr, 216 Json::Value *jsonRoot) { 217 SkAutoDataUnref dataRef(SkData::NewFromFILE(filePtr)); 218 if (NULL == dataRef.get()) { 219 return false; 220 } 221 222 const char *bytes = reinterpret_cast<const char *>(dataRef.get()->data()); 223 size_t size = dataRef.get()->size(); 224 Json::Reader reader; 225 if (!reader.parse(bytes, bytes+size, *jsonRoot)) { 226 return false; 227 } 228 229 return true; 230 } 231 232 } // namespace sk_tools 233