aboutsummaryrefslogtreecommitdiffhomepage
path: root/dm/DMWriteTask.cpp
blob: aee43a80b87c5fbf403abb82feae4b42b43a752b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include "DMWriteTask.h"

#include "DMJsonWriter.h"
#include "DMUtil.h"
#include "SkColorPriv.h"
#include "SkCommonFlags.h"
#include "SkData.h"
#include "SkImageEncoder.h"
#include "SkMD5.h"
#include "SkMallocPixelRef.h"
#include "SkOSFile.h"
#include "SkStream.h"
#include "SkString.h"

DEFINE_bool(nameByHash, false, "If true, write .../hash.png instead of .../mode/config/name.png");

namespace DM {

// Splits off the last N suffixes of name (splitting on _) and appends them to out.
// Returns the total number of characters consumed.
static int split_suffixes(int N, const char* name, SkTArray<SkString>* out) {
    SkTArray<SkString> split;
    SkStrSplit(name, "_", &split);
    int consumed = 0;
    for (int i = 0; i < N; i++) {
        // We're splitting off suffixes from the back to front.
        out->push_back(split[split.count()-i-1]);
        consumed += SkToInt(out->back().size() + 1);  // Add one for the _.
    }
    return consumed;
}

inline static SkString find_base_name(const Task& parent, SkTArray<SkString>* suffixList) {
    const int suffixes = parent.depth() + 1;
    const SkString& name = parent.name();
    const int totalSuffixLength = split_suffixes(suffixes, name.c_str(), suffixList);
    return SkString(name.c_str(), name.size() - totalSuffixLength);
}

WriteTask::WriteTask(const Task& parent, const char* sourceType, SkBitmap bitmap)
    : CpuTask(parent)
    , fBaseName(find_base_name(parent, &fSuffixes))
    , fSourceType(sourceType)
    , fBitmap(bitmap)
    , fData(NULL)
    , fExtension(".png") {
}

WriteTask::WriteTask(const Task& parent,
                     const char* sourceType,
                     SkStreamAsset *data,
                     const char* ext)
    : CpuTask(parent)
    , fBaseName(find_base_name(parent, &fSuffixes))
    , fSourceType(sourceType)
    , fData(data)
    , fExtension(ext) {
    SkASSERT(fData.get());
    SkASSERT(fData->unique());
}

void WriteTask::makeDirOrFail(SkString dir) {
    // This can be a little racy, so if it fails check to see if someone else succeeded.
    if (!sk_mkdir(dir.c_str()) && !sk_isdir(dir.c_str())) {
        this->fail("Can't make directory.");
    }
}

static SkString get_md5_string(SkMD5* hasher) {
    SkMD5::Digest digest;
    hasher->finish(digest);

    SkString md5;
    for (int i = 0; i < 16; i++) {
        md5.appendf("%02x", digest.data[i]);
    }
    return md5;
}

static SkString get_md5(const void* ptr, size_t len) {
    SkMD5 hasher;
    hasher.write(ptr, len);
    return get_md5_string(&hasher);
}

static bool write_asset(SkStreamAsset* input, SkWStream* output) {
    return input->rewind() && output->writeStream(input, input->getLength());
}

static SkString get_md5(SkStreamAsset* stream) {
    SkMD5 hasher;
    write_asset(stream, &hasher);
    return get_md5_string(&hasher);
}

static bool encode_png(const SkBitmap& src, SkFILEWStream* file) {
    SkBitmap bm;
    // We can't encode A8 bitmaps as PNGs.  Convert them to 8888 first.
    if (src.info().colorType() == kAlpha_8_SkColorType) {
        if (!src.copyTo(&bm, kN32_SkColorType)) {
            return false;
        }
    } else {
        bm = src;
    }
    return SkImageEncoder::EncodeStream(file, bm, SkImageEncoder::kPNG_Type, 100);
}

void WriteTask::draw() {
    SkString md5;
    {
        SkAutoLockPixels lock(fBitmap);
        md5 = fData ? get_md5(fData)
                    : get_md5(fBitmap.getPixels(), fBitmap.getSize());
    }

    SkASSERT(fSuffixes.count() > 0);
    SkString config = fSuffixes.back();
    SkString mode("direct");
    if (fSuffixes.count() > 1) {
        mode = fSuffixes.fromBack(1);
    }

    {
        const JsonWriter::BitmapResult entry = { fBaseName,
                                                 config,
                                                 mode,
                                                 fSourceType,
                                                 md5 };
        JsonWriter::AddBitmapResult(entry);
    }

    SkString dir(FLAGS_writePath[0]);
#if defined(SK_BUILD_FOR_IOS)
    if (dir.equals("@")) {
        dir.set(FLAGS_resourcePath[0]);
    }
#endif
    this->makeDirOrFail(dir);

    SkString path;
    if (FLAGS_nameByHash) {
        // Flat directory of hash-named files.
        path = SkOSPath::Join(dir.c_str(), md5.c_str());
        path.append(fExtension);
        // We're content-addressed, so it's possible two threads race to write
        // this file.  We let the first one win.  This also means we won't
        // overwrite identical files from previous runs.
        if (sk_exists(path.c_str())) {
            return;
        }
    } else {
        // Nested by mode, config, etc.
        for (int i = 0; i < fSuffixes.count(); i++) {
            dir = SkOSPath::Join(dir.c_str(), fSuffixes[i].c_str());
            this->makeDirOrFail(dir);
        }
        path = SkOSPath::Join(dir.c_str(), fBaseName.c_str());
        path.append(fExtension);
        // The path is unique, so two threads can't both write to the same file.
        // If already present we overwrite here, since the content may have changed.
    }

    SkFILEWStream file(path.c_str());
    if (!file.isValid()) {
        return this->fail("Can't open file.");
    }

    bool ok = fData ? write_asset(fData, &file)
                    : encode_png(fBitmap, &file);
    if (!ok) {
        return this->fail("Can't write to file.");
    }
}

SkString WriteTask::name() const {
    SkString name("writing ");
    for (int i = 0; i < fSuffixes.count(); i++) {
        name.appendf("%s/", fSuffixes[i].c_str());
    }
    name.append(fBaseName.c_str());
    return name;
}

bool WriteTask::shouldSkip() const {
    return FLAGS_writePath.isEmpty();
}

}  // namespace DM