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