1/* 2 * Copyright 2012 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#include "skdiff.h" 8#include "skdiff_utils.h" 9#include "sk_tool_utils.h" 10#include "SkBitmap.h" 11#include "SkCodec.h" 12#include "SkData.h" 13#include "SkImageEncoder.h" 14#include "SkStream.h" 15#include "SkTypes.h" 16 17#include <memory> 18 19bool are_buffers_equal(SkData* skdata1, SkData* skdata2) { 20 if ((nullptr == skdata1) || (nullptr == skdata2)) { 21 return false; 22 } 23 if (skdata1->size() != skdata2->size()) { 24 return false; 25 } 26 return (0 == memcmp(skdata1->data(), skdata2->data(), skdata1->size())); 27} 28 29sk_sp<SkData> read_file(const char* file_path) { 30 sk_sp<SkData> data(SkData::MakeFromFileName(file_path)); 31 if (!data) { 32 SkDebugf("WARNING: could not open file <%s> for reading\n", file_path); 33 } 34 return data; 35} 36 37bool get_bitmap(sk_sp<SkData> fileBits, DiffResource& resource, bool sizeOnly) { 38 std::unique_ptr<SkCodec> codec(SkCodec::NewFromData(fileBits)); 39 if (!codec) { 40 SkDebugf("ERROR: could not create codec for <%s>\n", resource.fFullPath.c_str()); 41 resource.fStatus = DiffResource::kCouldNotDecode_Status; 42 return false; 43 } 44 45 if (!resource.fBitmap.setInfo(codec->getInfo().makeColorType(kN32_SkColorType))) { 46 SkDebugf("ERROR: could not set bitmap info for <%s>\n", resource.fFullPath.c_str()); 47 resource.fStatus = DiffResource::kCouldNotDecode_Status; 48 return false; 49 } 50 51 if (sizeOnly) { 52 return true; 53 } 54 55 if (!resource.fBitmap.tryAllocPixels()) { 56 SkDebugf("ERROR: could not allocate pixels for <%s>\n", resource.fFullPath.c_str()); 57 resource.fStatus = DiffResource::kCouldNotDecode_Status; 58 return false; 59 } 60 61 if (SkCodec::kSuccess != codec->getPixels(resource.fBitmap.info(), 62 resource.fBitmap.getPixels(), resource.fBitmap.rowBytes())) { 63 SkDebugf("ERROR: codec failed for basePath <%s>\n", resource.fFullPath.c_str()); 64 resource.fStatus = DiffResource::kCouldNotDecode_Status; 65 return false; 66 } 67 68 resource.fStatus = DiffResource::kDecoded_Status; 69 return true; 70} 71 72/** Thanks to PNG, we need to force all pixels 100% opaque. */ 73static void force_all_opaque(const SkBitmap& bitmap) { 74 for (int y = 0; y < bitmap.height(); y++) { 75 for (int x = 0; x < bitmap.width(); x++) { 76 *bitmap.getAddr32(x, y) |= (SK_A32_MASK << SK_A32_SHIFT); 77 } 78 } 79} 80 81bool write_bitmap(const SkString& path, const SkBitmap& bitmap) { 82 SkBitmap copy; 83 sk_tool_utils::copy_to(©, kN32_SkColorType, bitmap); 84 force_all_opaque(copy); 85 return sk_tool_utils::EncodeImageToFile(path.c_str(), copy, 86 SkEncodedImageFormat::kPNG, 100); 87} 88 89/// Return a copy of the "input" string, within which we have replaced all instances 90/// of oldSubstring with newSubstring. 91/// 92/// TODO: If we like this, we should move it into the core SkString implementation, 93/// adding more checks and ample test cases, and paying more attention to efficiency. 94static SkString replace_all(const SkString &input, 95 const char oldSubstring[], const char newSubstring[]) { 96 SkString output; 97 const char *input_cstr = input.c_str(); 98 const char *first_char = input_cstr; 99 const char *match_char; 100 size_t oldSubstringLen = strlen(oldSubstring); 101 while ((match_char = strstr(first_char, oldSubstring))) { 102 output.append(first_char, (match_char - first_char)); 103 output.append(newSubstring); 104 first_char = match_char + oldSubstringLen; 105 } 106 output.append(first_char); 107 return output; 108} 109 110static SkString filename_to_derived_filename(const SkString& filename, const char *suffix) { 111 SkString diffName (filename); 112 const char* cstring = diffName.c_str(); 113 size_t dotOffset = strrchr(cstring, '.') - cstring; 114 diffName.remove(dotOffset, diffName.size() - dotOffset); 115 diffName.append(suffix); 116 117 // In case we recursed into subdirectories, replace slashes with something else 118 // so the diffs will all be written into a single flat directory. 119 diffName = replace_all(diffName, PATH_DIV_STR, "_"); 120 return diffName; 121} 122 123SkString filename_to_diff_filename(const SkString& filename) { 124 return filename_to_derived_filename(filename, "-diff.png"); 125} 126 127SkString filename_to_white_filename(const SkString& filename) { 128 return filename_to_derived_filename(filename, "-white.png"); 129} 130 131void create_and_write_diff_image(DiffRecord* drp, 132 DiffMetricProc dmp, 133 const int colorThreshold, 134 const SkString& outputDir, 135 const SkString& filename) { 136 const int w = drp->fBase.fBitmap.width(); 137 const int h = drp->fBase.fBitmap.height(); 138 139 if (w != drp->fComparison.fBitmap.width() || h != drp->fComparison.fBitmap.height()) { 140 drp->fResult = DiffRecord::kDifferentSizes_Result; 141 } else { 142 drp->fDifference.fBitmap.allocN32Pixels(w, h); 143 144 drp->fWhite.fBitmap.allocN32Pixels(w, h); 145 146 SkASSERT(DiffRecord::kUnknown_Result == drp->fResult); 147 compute_diff(drp, dmp, colorThreshold); 148 SkASSERT(DiffRecord::kUnknown_Result != drp->fResult); 149 } 150 151 if (outputDir.isEmpty()) { 152 drp->fDifference.fStatus = DiffResource::kUnspecified_Status; 153 drp->fWhite.fStatus = DiffResource::kUnspecified_Status; 154 155 } else { 156 drp->fDifference.fFilename = filename_to_diff_filename(filename); 157 drp->fDifference.fFullPath = outputDir; 158 drp->fDifference.fFullPath.append(drp->fDifference.fFilename); 159 drp->fDifference.fStatus = DiffResource::kSpecified_Status; 160 161 drp->fWhite.fFilename = filename_to_white_filename(filename); 162 drp->fWhite.fFullPath = outputDir; 163 drp->fWhite.fFullPath.append(drp->fWhite.fFilename); 164 drp->fWhite.fStatus = DiffResource::kSpecified_Status; 165 166 if (DiffRecord::kDifferentPixels_Result == drp->fResult) { 167 if (write_bitmap(drp->fDifference.fFullPath, drp->fDifference.fBitmap)) { 168 drp->fDifference.fStatus = DiffResource::kExists_Status; 169 } else { 170 drp->fDifference.fStatus = DiffResource::kDoesNotExist_Status; 171 } 172 if (write_bitmap(drp->fWhite.fFullPath, drp->fWhite.fBitmap)) { 173 drp->fWhite.fStatus = DiffResource::kExists_Status; 174 } else { 175 drp->fWhite.fStatus = DiffResource::kDoesNotExist_Status; 176 } 177 } 178 } 179} 180