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