1/*
2 * Copyright 2019 Google LLC
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 "modules/skresources/include/SkResources.h"
9
10#include "include/codec/SkCodec.h"
11#include "include/core/SkData.h"
12#include "include/core/SkImage.h"
13#include "include/utils/SkAnimCodecPlayer.h"
14#include "include/utils/SkBase64.h"
15#include "src/core/SkOSFile.h"
16#include "src/utils/SkOSPath.h"
17
18#if defined(HAVE_VIDEO_DECODER)
19 #include "experimental/ffmpeg/SkVideoDecoder.h"
20#endif
21
22namespace skresources {
23namespace {
24
25#if defined(HAVE_VIDEO_DECODER)
26
27class VideoAsset final : public ImageAsset {
28public:
29 static sk_sp<VideoAsset> Make(sk_sp<SkData> data) {
30 auto decoder = std::make_unique<SkVideoDecoder>();
31
32 if (!decoder->loadStream(SkMemoryStream::Make(std::move(data))) ||
33 decoder->duration() <= 0) {
34 return nullptr;
35 }
36
37 return sk_sp<VideoAsset>(new VideoAsset(std::move(decoder)));
38 }
39
40private:
41 explicit VideoAsset(std::unique_ptr<SkVideoDecoder> decoder)
42 : fDecoder(std::move(decoder)) {
43 }
44
45 bool isMultiFrame() override { return true; }
46
47 // Each frame has a presentation timestamp
48 // => the timespan for frame N is [stamp_N .. stamp_N+1)
49 // => we use a two-frame sliding window to track the current interval.
50 void advance() {
51 fWindow[0] = std::move(fWindow[1]);
52 fWindow[1].frame = fDecoder->nextImage(&fWindow[1].stamp);
53 fEof = !fWindow[1].frame;
54 }
55
56 sk_sp<SkImage> getFrame(float t_float) override {
57 const auto t = SkTPin(static_cast<double>(t_float), 0.0, fDecoder->duration());
58
59 if (t < fWindow[0].stamp) {
60 // seeking back requires a full rewind
61 fDecoder->rewind();
62 fWindow[0].stamp = fWindow[1].stamp = 0;
63 fEof = 0;
64 }
65
66 while (!fEof && t >= fWindow[1].stamp) {
67 this->advance();
68 }
69
70 SkASSERT(fWindow[0].stamp <= t && (fEof || t < fWindow[1].stamp));
71
72 return fWindow[0].frame;
73 }
74
75 const std::unique_ptr<SkVideoDecoder> fDecoder;
76
77 struct FrameRec {
78 sk_sp<SkImage> frame;
79 double stamp = 0;
80 };
81
82 FrameRec fWindow[2];
83 bool fEof = false;
84};
85
86#endif // defined(HAVE_VIDEO_DECODER)
87
88} // namespace
89
90sk_sp<MultiFrameImageAsset> MultiFrameImageAsset::Make(sk_sp<SkData> data, bool predecode) {
91 if (auto codec = SkCodec::MakeFromData(std::move(data))) {
92 return sk_sp<MultiFrameImageAsset>(
93 new MultiFrameImageAsset(std::make_unique<SkAnimCodecPlayer>(std::move(codec)),
94 predecode));
95 }
96
97 return nullptr;
98}
99
100MultiFrameImageAsset::MultiFrameImageAsset(std::unique_ptr<SkAnimCodecPlayer> player,
101 bool predecode)
102 : fPlayer(std::move(player))
103 , fPreDecode(predecode) {
104 SkASSERT(fPlayer);
105}
106
107bool MultiFrameImageAsset::isMultiFrame() {
108 return fPlayer->duration() > 0;
109}
110
111sk_sp<SkImage> MultiFrameImageAsset::generateFrame(float t) {
112 auto decode = [](sk_sp<SkImage> image) {
113 SkASSERT(image->isLazyGenerated());
114
115 static constexpr size_t kMaxArea = 2048 * 2048;
116 const auto image_area = SkToSizeT(image->width() * image->height());
117
118 if (image_area > kMaxArea) {
119 // When the image is too large, decode and scale down to a reasonable size.
120 const auto scale = std::sqrt(static_cast<float>(kMaxArea) / image_area);
121 const auto info = SkImageInfo::MakeN32Premul(scale * image->width(),
122 scale * image->height());
123 SkBitmap bm;
124 if (bm.tryAllocPixels(info, info.minRowBytes()) &&
125 image->scalePixels(bm.pixmap(),
126 SkFilterQuality::kMedium_SkFilterQuality,
127 SkImage::kDisallow_CachingHint)) {
128 image = SkImage::MakeFromBitmap(bm);
129 }
130 } else {
131 // When the image size is OK, just force-decode.
132 image = image->makeRasterImage();
133 }
134
135 return image;
136 };
137
138 fPlayer->seek(static_cast<uint32_t>(t * 1000));
139 auto frame = fPlayer->getFrame();
140
141 if (fPreDecode && frame && frame->isLazyGenerated()) {
142 // The multi-frame decoder should never return lazy images.
143 SkASSERT(!this->isMultiFrame());
144 frame = decode(std::move(frame));
145 }
146
147 return frame;
148}
149
150sk_sp<SkImage> MultiFrameImageAsset::getFrame(float t) {
151 // For static images we can reuse the cached frame
152 // (which includes the optional pre-decode step).
153 if (!fCachedFrame || this->isMultiFrame()) {
154 fCachedFrame = this->generateFrame(t);
155 }
156
157 return fCachedFrame;
158}
159
160sk_sp<FileResourceProvider> FileResourceProvider::Make(SkString base_dir, bool predecode) {
161 return sk_isdir(base_dir.c_str())
162 ? sk_sp<FileResourceProvider>(new FileResourceProvider(std::move(base_dir), predecode))
163 : nullptr;
164}
165
166FileResourceProvider::FileResourceProvider(SkString base_dir, bool predecode)
167 : fDir(std::move(base_dir))
168 , fPredecode(predecode) {}
169
170sk_sp<SkData> FileResourceProvider::load(const char resource_path[],
171 const char resource_name[]) const {
172 const auto full_dir = SkOSPath::Join(fDir.c_str() , resource_path),
173 full_path = SkOSPath::Join(full_dir.c_str(), resource_name);
174 return SkData::MakeFromFileName(full_path.c_str());
175}
176
177sk_sp<ImageAsset> FileResourceProvider::loadImageAsset(const char resource_path[],
178 const char resource_name[],
179 const char[]) const {
180 auto data = this->load(resource_path, resource_name);
181
182 if (auto image = MultiFrameImageAsset::Make(data, fPredecode)) {
183 return std::move(image);
184 }
185
186#if defined(HAVE_VIDEO_DECODER)
187 if (auto video = VideoAsset::Make(data)) {
188 return std::move(video);
189 }
190#endif
191
192 return nullptr;
193}
194
195ResourceProviderProxyBase::ResourceProviderProxyBase(sk_sp<ResourceProvider> rp)
196 : fProxy(std::move(rp)) {}
197
198sk_sp<SkData> ResourceProviderProxyBase::load(const char resource_path[],
199 const char resource_name[]) const {
200 return fProxy ? fProxy->load(resource_path, resource_name)
201 : nullptr;
202}
203
204sk_sp<ImageAsset> ResourceProviderProxyBase::loadImageAsset(const char rpath[],
205 const char rname[],
206 const char rid[]) const {
207 return fProxy ? fProxy->loadImageAsset(rpath, rname, rid)
208 : nullptr;
209}
210
211sk_sp<SkTypeface> ResourceProviderProxyBase::loadTypeface(const char name[],
212 const char url[]) const {
213 return fProxy ? fProxy->loadTypeface(name, url)
214 : nullptr;
215}
216
217sk_sp<SkData> ResourceProviderProxyBase::loadFont(const char name[], const char url[]) const {
218 return fProxy ? fProxy->loadFont(name, url)
219 : nullptr;
220}
221
222CachingResourceProvider::CachingResourceProvider(sk_sp<ResourceProvider> rp)
223 : INHERITED(std::move(rp)) {}
224
225sk_sp<ImageAsset> CachingResourceProvider::loadImageAsset(const char resource_path[],
226 const char resource_name[],
227 const char resource_id[]) const {
228 SkAutoMutexExclusive amx(fMutex);
229
230 const SkString key(resource_id);
231 if (const auto* asset = fImageCache.find(key)) {
232 return *asset;
233 }
234
235 auto asset = this->INHERITED::loadImageAsset(resource_path, resource_name, resource_id);
236 fImageCache.set(key, asset);
237
238 return asset;
239}
240
241sk_sp<DataURIResourceProviderProxy> DataURIResourceProviderProxy::Make(sk_sp<ResourceProvider> rp,
242 bool predecode) {
243 return sk_sp<DataURIResourceProviderProxy>(
244 new DataURIResourceProviderProxy(std::move(rp), predecode));
245}
246
247DataURIResourceProviderProxy::DataURIResourceProviderProxy(sk_sp<ResourceProvider> rp,
248 bool predecode)
249 : INHERITED(std::move(rp))
250 , fPredecode(predecode) {}
251
252static sk_sp<SkData> decode_datauri(const char prefix[], const char data[]) {
253 // We only handle B64 encoded image dataURIs: data:image/<type>;base64,<data>
254 // (https://en.wikipedia.org/wiki/Data_URI_scheme)
255 static constexpr char kDataURIEncodingStr[] = ";base64,";
256
257 const auto prefix_len = strlen(prefix);
258 if (!strncmp(data, prefix, prefix_len)) {
259 if (const auto* encoding_start = strstr(data + prefix_len, kDataURIEncodingStr)) {
260 const char* data_start = encoding_start + SK_ARRAY_COUNT(kDataURIEncodingStr) - 1;
261
262 // TODO: SkBase64::decode ergonomics are... interesting.
263 SkBase64 b64;
264 if (SkBase64::kNoError == b64.decode(data_start, strlen(data_start))) {
265 return SkData::MakeWithProc(b64.getData(), b64.getDataSize(),
266 [](const void* ptr, void*) {
267 delete[] static_cast<const char*>(ptr);
268 }, /*ctx=*/nullptr);
269 }
270 }
271 }
272
273 return nullptr;
274}
275
276sk_sp<ImageAsset> DataURIResourceProviderProxy::loadImageAsset(const char rpath[],
277 const char rname[],
278 const char rid[]) const {
279 if (auto data = decode_datauri("data:image/", rname)) {
280 return MultiFrameImageAsset::Make(std::move(data), fPredecode);
281 }
282
283 return this->INHERITED::loadImageAsset(rpath, rname, rid);
284}
285
286sk_sp<SkTypeface> DataURIResourceProviderProxy::loadTypeface(const char name[],
287 const char url[]) const {
288 if (auto data = decode_datauri("data:font/", url)) {
289 return SkTypeface::MakeFromData(std::move(data));
290 }
291
292 return this->INHERITED::loadTypeface(name, url);
293}
294
295} // namespace skresources
296