Hash .pngs instead of SkBitmaps.
This has the nice property of being able to double-check hashes after the fact. mtklein@mtklein ~/skia (hash-png)> md5sum bad/8888/3x3bitmaprect.png deede70ab2f34067d461fb4a93332d4c bad/8888/3x3bitmaprect.png mtklein@mtklein ~/skia (hash-png)> grep 3x3bitmaprect_8888 bad/dm.json "3x3bitmaprect_8888" : "deede70ab2f34067d461fb4a93332d4c", I have checked that no two premultiplied colors map to the same unpremultiplied color (math nerds: unpremultiplication is injective), so a change in premultiplied SkBitmap will always imply a change in the encoded unpremultiplied .png. This means, it's safe to hash .pngs; we won't miss subtle changes. BUG=skia: R=jcgregorio@google.com, stephana@google.com, mtklein@google.com Author: mtklein@chromium.org Review URL: https://codereview.chromium.org/549203003
This commit is contained in:
parent
655ad128d0
commit
e2d4eb7072
@ -67,59 +67,37 @@ void WriteTask::makeDirOrFail(SkString dir) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool save_bitmap_to_file(SkBitmap bitmap, const char* path) {
|
||||
SkFILEWStream stream(path);
|
||||
if (!stream.isValid() ||
|
||||
!SkImageEncoder::EncodeStream(&stream, bitmap, SkImageEncoder::kPNG_Type, 100)) {
|
||||
SkDebugf("Can't write a PNG to %s.\n", path);
|
||||
return false;
|
||||
static SkStreamAsset* encode_to_png(const SkBitmap& bitmap) {
|
||||
SkDynamicMemoryWStream png;
|
||||
if (!SkImageEncoder::EncodeStream(&png, bitmap, SkImageEncoder::kPNG_Type, 100)) {
|
||||
return NULL;
|
||||
}
|
||||
return true;
|
||||
png.copyToData()->unref(); // Forces detachAsStream() to be contiguous.
|
||||
return png.detachAsStream();
|
||||
}
|
||||
|
||||
// Does not take ownership of data.
|
||||
static bool save_data_to_file(SkStreamAsset* data, const char* path) {
|
||||
data->rewind();
|
||||
SkFILEWStream stream(path);
|
||||
if (!stream.isValid() || !stream.writeStream(data, data->getLength())) {
|
||||
SkDebugf("Can't write data to %s.\n", path);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static SkString finish_hash(SkMD5* hasher) {
|
||||
SkMD5::Digest digest;
|
||||
hasher->finish(digest);
|
||||
|
||||
SkString out;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
out.appendf("%02x", digest.data[i]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static SkString hash(const SkBitmap& src) {
|
||||
SkMD5 hasher;
|
||||
{
|
||||
SkAutoLockPixels lock(src);
|
||||
hasher.write(src.getPixels(), src.getSize());
|
||||
}
|
||||
return finish_hash(&hasher);
|
||||
}
|
||||
|
||||
static SkString hash(SkStreamAsset* src) {
|
||||
static SkString get_md5(SkStreamAsset* src) {
|
||||
SkMD5 hasher;
|
||||
hasher.write(src->getMemoryBase(), src->getLength());
|
||||
return finish_hash(&hasher);
|
||||
SkMD5::Digest digest;
|
||||
hasher.finish(digest);
|
||||
|
||||
SkString md5;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
md5.appendf("%02x", digest.data[i]);
|
||||
}
|
||||
return md5;
|
||||
}
|
||||
|
||||
void WriteTask::draw() {
|
||||
JsonData entry = {
|
||||
fFullName,
|
||||
fData ? hash(fData) : hash(fBitmap),
|
||||
};
|
||||
if (!fData.get()) {
|
||||
fData.reset(encode_to_png(fBitmap));
|
||||
if (!fData.get()) {
|
||||
this->fail("Can't encode to PNG.");
|
||||
}
|
||||
}
|
||||
|
||||
JsonData entry = { fFullName, get_md5(fData) };
|
||||
{
|
||||
SkAutoMutexAcquire lock(&gJsonDataLock);
|
||||
gJsonData.push_back(entry);
|
||||
@ -156,10 +134,14 @@ void WriteTask::draw() {
|
||||
// If already present we overwrite here, since the content may have changed.
|
||||
}
|
||||
|
||||
const bool ok = fData.get() ? save_data_to_file(fData.get(), path.c_str())
|
||||
: save_bitmap_to_file(fBitmap, path.c_str());
|
||||
if (!ok) {
|
||||
this->fail();
|
||||
SkFILEWStream file(path.c_str());
|
||||
if (!file.isValid()) {
|
||||
return this->fail("Can't open file.");
|
||||
}
|
||||
|
||||
fData->rewind();
|
||||
if (!file.writeStream(fData, fData->getLength())) {
|
||||
return this->fail("Can't write to file.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +195,8 @@ bool WriteTask::Expectations::check(const Task& task, SkBitmap bitmap) const {
|
||||
}
|
||||
|
||||
const char* expected = fJson[name.c_str()].asCString();
|
||||
SkString actual = hash(bitmap);
|
||||
SkAutoTDelete<SkStreamAsset> png(encode_to_png(bitmap));
|
||||
SkString actual = get_md5(png);
|
||||
return actual.equals(expected);
|
||||
}
|
||||
|
||||
|
@ -205,6 +205,7 @@
|
||||
'../tests/TracingTest.cpp',
|
||||
'../tests/TypefaceTest.cpp',
|
||||
'../tests/UnicodeTest.cpp',
|
||||
'../tests/UnpremultiplyTest.cpp',
|
||||
'../tests/UtilsTest.cpp',
|
||||
'../tests/WArrayTest.cpp',
|
||||
'../tests/WritePixelsTest.cpp',
|
||||
|
38
tests/UnpremultiplyTest.cpp
Normal file
38
tests/UnpremultiplyTest.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2014 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "SkColorPriv.h"
|
||||
#include "SkUnPreMultiply.h"
|
||||
#include "Test.h"
|
||||
|
||||
DEF_TEST(Unpremultiply, reporter) {
|
||||
// Here we test that unpremultiplication is injective:
|
||||
// no two distinct premul colors map to the same unpremul color.
|
||||
|
||||
// DM exploits this fact to safely hash .pngs instead of the original bitmaps.
|
||||
|
||||
// It is sufficient to test red. Green and blue follow the same rules.
|
||||
// This means we have at most 256*256 possible colors to deal with.
|
||||
int hits[256*256];
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(hits); i++) {
|
||||
hits[i] = 0;
|
||||
}
|
||||
|
||||
for (int a = 0; a < 256; a++) {
|
||||
for (int r = 0; r <= a; r++) {
|
||||
SkPMColor pm = SkPackARGB32(a, r, 0, 0);
|
||||
SkColor upm = SkUnPreMultiply::PMColorToColor(pm);
|
||||
|
||||
// ARGB -> AR
|
||||
hits[upm >> 16]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(hits); i++) {
|
||||
REPORTER_ASSERT(reporter, hits[i] < 2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user