/* * Copyright 2019 Google LLC * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "modules/skresources/include/SkResources.h" #include "include/codec/SkCodec.h" #include "include/core/SkBitmap.h" #include "include/core/SkData.h" #include "include/core/SkImage.h" #include "include/private/SkTPin.h" #include "include/utils/SkAnimCodecPlayer.h" #include "include/utils/SkBase64.h" #include "src/core/SkOSFile.h" #include "src/utils/SkOSPath.h" #if defined(HAVE_VIDEO_DECODER) #include "experimental/ffmpeg/SkVideoDecoder.h" #endif namespace skresources { namespace { #if defined(HAVE_VIDEO_DECODER) class VideoAsset final : public ImageAsset { public: static sk_sp Make(sk_sp data) { auto decoder = std::make_unique(); if (!decoder->loadStream(SkMemoryStream::Make(std::move(data))) || decoder->duration() <= 0) { return nullptr; } return sk_sp(new VideoAsset(std::move(decoder))); } private: explicit VideoAsset(std::unique_ptr decoder) : fDecoder(std::move(decoder)) { } bool isMultiFrame() override { return true; } // Each frame has a presentation timestamp // => the timespan for frame N is [stamp_N .. stamp_N+1) // => we use a two-frame sliding window to track the current interval. void advance() { fWindow[0] = std::move(fWindow[1]); fWindow[1].frame = fDecoder->nextImage(&fWindow[1].stamp); fEof = !fWindow[1].frame; } sk_sp getFrame(float t_float) override { const auto t = SkTPin(static_cast(t_float), 0.0, fDecoder->duration()); if (t < fWindow[0].stamp) { // seeking back requires a full rewind fDecoder->rewind(); fWindow[0].stamp = fWindow[1].stamp = 0; fEof = 0; } while (!fEof && t >= fWindow[1].stamp) { this->advance(); } SkASSERT(fWindow[0].stamp <= t && (fEof || t < fWindow[1].stamp)); return fWindow[0].frame; } const std::unique_ptr fDecoder; struct FrameRec { sk_sp frame; double stamp = 0; }; FrameRec fWindow[2]; bool fEof = false; }; #endif // defined(HAVE_VIDEO_DECODER) } // namespace sk_sp ImageAsset::getFrame(float t) { return nullptr; } ImageAsset::FrameData ImageAsset::getFrameData(float t) { // legacy behavior return { this->getFrame(t), SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest), SkMatrix::I(), }; } sk_sp MultiFrameImageAsset::Make(sk_sp data, bool predecode) { if (auto codec = SkCodec::MakeFromData(std::move(data))) { return sk_sp( new MultiFrameImageAsset(std::make_unique(std::move(codec)), predecode)); } return nullptr; } MultiFrameImageAsset::MultiFrameImageAsset(std::unique_ptr player, bool predecode) : fPlayer(std::move(player)) , fPreDecode(predecode) { SkASSERT(fPlayer); } bool MultiFrameImageAsset::isMultiFrame() { return fPlayer->duration() > 0; } sk_sp MultiFrameImageAsset::generateFrame(float t) { auto decode = [](sk_sp image) { SkASSERT(image->isLazyGenerated()); static constexpr size_t kMaxArea = 2048 * 2048; const auto image_area = SkToSizeT(image->width() * image->height()); if (image_area > kMaxArea) { // When the image is too large, decode and scale down to a reasonable size. const auto scale = std::sqrt(static_cast(kMaxArea) / image_area); const auto info = SkImageInfo::MakeN32Premul(scale * image->width(), scale * image->height()); SkBitmap bm; if (bm.tryAllocPixels(info, info.minRowBytes()) && image->scalePixels(bm.pixmap(), SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNearest), SkImage::kDisallow_CachingHint)) { image = bm.asImage(); } } else { // When the image size is OK, just force-decode. image = image->makeRasterImage(); } return image; }; fPlayer->seek(static_cast(t * 1000)); auto frame = fPlayer->getFrame(); if (fPreDecode && frame && frame->isLazyGenerated()) { // The multi-frame decoder should never return lazy images. SkASSERT(!this->isMultiFrame()); frame = decode(std::move(frame)); } return frame; } sk_sp MultiFrameImageAsset::getFrame(float t) { // For static images we can reuse the cached frame // (which includes the optional pre-decode step). if (!fCachedFrame || this->isMultiFrame()) { fCachedFrame = this->generateFrame(t); } return fCachedFrame; } sk_sp FileResourceProvider::Make(SkString base_dir, bool predecode) { return sk_isdir(base_dir.c_str()) ? sk_sp(new FileResourceProvider(std::move(base_dir), predecode)) : nullptr; } FileResourceProvider::FileResourceProvider(SkString base_dir, bool predecode) : fDir(std::move(base_dir)) , fPredecode(predecode) {} sk_sp FileResourceProvider::load(const char resource_path[], const char resource_name[]) const { const auto full_dir = SkOSPath::Join(fDir.c_str() , resource_path), full_path = SkOSPath::Join(full_dir.c_str(), resource_name); return SkData::MakeFromFileName(full_path.c_str()); } sk_sp FileResourceProvider::loadImageAsset(const char resource_path[], const char resource_name[], const char[]) const { auto data = this->load(resource_path, resource_name); if (auto image = MultiFrameImageAsset::Make(data, fPredecode)) { return std::move(image); } #if defined(HAVE_VIDEO_DECODER) if (auto video = VideoAsset::Make(data)) { return std::move(video); } #endif return nullptr; } ResourceProviderProxyBase::ResourceProviderProxyBase(sk_sp rp) : fProxy(std::move(rp)) {} sk_sp ResourceProviderProxyBase::load(const char resource_path[], const char resource_name[]) const { return fProxy ? fProxy->load(resource_path, resource_name) : nullptr; } sk_sp ResourceProviderProxyBase::loadImageAsset(const char rpath[], const char rname[], const char rid[]) const { return fProxy ? fProxy->loadImageAsset(rpath, rname, rid) : nullptr; } sk_sp ResourceProviderProxyBase::loadTypeface(const char name[], const char url[]) const { return fProxy ? fProxy->loadTypeface(name, url) : nullptr; } sk_sp ResourceProviderProxyBase::loadFont(const char name[], const char url[]) const { return fProxy ? fProxy->loadFont(name, url) : nullptr; } sk_sp ResourceProviderProxyBase::loadAudioAsset(const char path[], const char name[], const char id[]) { return fProxy ? fProxy->loadAudioAsset(path, name, id) : nullptr; } CachingResourceProvider::CachingResourceProvider(sk_sp rp) : INHERITED(std::move(rp)) {} sk_sp CachingResourceProvider::loadImageAsset(const char resource_path[], const char resource_name[], const char resource_id[]) const { SkAutoMutexExclusive amx(fMutex); const SkString key(resource_id); if (const auto* asset = fImageCache.find(key)) { return *asset; } auto asset = this->INHERITED::loadImageAsset(resource_path, resource_name, resource_id); fImageCache.set(key, asset); return asset; } sk_sp DataURIResourceProviderProxy::Make(sk_sp rp, bool predecode) { return sk_sp( new DataURIResourceProviderProxy(std::move(rp), predecode)); } DataURIResourceProviderProxy::DataURIResourceProviderProxy(sk_sp rp, bool predecode) : INHERITED(std::move(rp)) , fPredecode(predecode) {} static sk_sp decode_datauri(const char prefix[], const char uri[]) { // We only handle B64 encoded image dataURIs: data:image/;base64, // (https://en.wikipedia.org/wiki/Data_URI_scheme) static constexpr char kDataURIEncodingStr[] = ";base64,"; const size_t prefixLen = strlen(prefix); if (strncmp(uri, prefix, prefixLen) != 0) { return nullptr; } const char* encoding = strstr(uri + prefixLen, kDataURIEncodingStr); if (!encoding) { return nullptr; } const char* b64Data = encoding + SK_ARRAY_COUNT(kDataURIEncodingStr) - 1; size_t b64DataLen = strlen(b64Data); size_t dataLen; if (SkBase64::Decode(b64Data, b64DataLen, nullptr, &dataLen) != SkBase64::kNoError) { return nullptr; } sk_sp data = SkData::MakeUninitialized(dataLen); void* rawData = data->writable_data(); if (SkBase64::Decode(b64Data, b64DataLen, rawData, &dataLen) != SkBase64::kNoError) { return nullptr; } return data; } sk_sp DataURIResourceProviderProxy::loadImageAsset(const char rpath[], const char rname[], const char rid[]) const { if (auto data = decode_datauri("data:image/", rname)) { return MultiFrameImageAsset::Make(std::move(data), fPredecode); } return this->INHERITED::loadImageAsset(rpath, rname, rid); } sk_sp DataURIResourceProviderProxy::loadTypeface(const char name[], const char url[]) const { if (auto data = decode_datauri("data:font/", url)) { return SkTypeface::MakeFromData(std::move(data)); } return this->INHERITED::loadTypeface(name, url); } } // namespace skresources