#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { static SpriteAtlasObserver nullObserver; struct SpriteAtlas::Loader { std::shared_ptr image; std::shared_ptr json; std::unique_ptr jsonRequest; std::unique_ptr spriteRequest; }; SpriteAtlasElement::SpriteAtlasElement(Rect rect_, std::shared_ptr spriteImage, Size size_, float pixelRatio) : pos(std::move(rect_)), sdf(spriteImage->sdf), relativePixelRatio(spriteImage->pixelRatio / pixelRatio), width(spriteImage->getWidth()), height(spriteImage->getHeight()) { const float padding = 1; const float w = spriteImage->getWidth() * relativePixelRatio; const float h = spriteImage->getHeight() * relativePixelRatio; size = {{ float(spriteImage->getWidth()), spriteImage->getHeight() }}; tl = {{ float(pos.x + padding) / size_.width, float(pos.y + padding) / size_.height }}; br = {{ float(pos.x + padding + w) / size_.width, float(pos.y + padding + h) / size_.height }}; } SpriteAtlas::SpriteAtlas(Size size_, float pixelRatio_) : size(std::move(size_)), pixelRatio(pixelRatio_), observer(&nullObserver), bin(size.width, size.height), dirty(true) { } SpriteAtlas::~SpriteAtlas() = default; void SpriteAtlas::load(const std::string& url, FileSource& fileSource) { if (url.empty()) { // Treat a non-existent sprite as a successfully loaded empty sprite. loaded = true; return; } loader = std::make_unique(); loader->jsonRequest = fileSource.request(Resource::spriteJSON(url, pixelRatio), [this](Response res) { if (res.error) { observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); } else if (res.notModified) { return; } else if (res.noContent) { loader->json = std::make_shared(); emitSpriteLoadedIfComplete(); } else { // Only trigger a sprite loaded event we got new data. loader->json = res.data; emitSpriteLoadedIfComplete(); } }); loader->spriteRequest = fileSource.request(Resource::spriteImage(url, pixelRatio), [this](Response res) { if (res.error) { observer->onSpriteError(std::make_exception_ptr(std::runtime_error(res.error->message))); } else if (res.notModified) { return; } else if (res.noContent) { loader->image = std::make_shared(); emitSpriteLoadedIfComplete(); } else { loader->image = res.data; emitSpriteLoadedIfComplete(); } }); } void SpriteAtlas::emitSpriteLoadedIfComplete() { assert(loader); if (!loader->image || !loader->json) { return; } auto result = parseSprite(*loader->image, *loader->json); if (result.is()) { loaded = true; setSprites(result.get()); observer->onSpriteLoaded(); for (auto requestor : requestors) { requestor->onIconsAvailable(this, buildIconMap()); } requestors.clear(); } else { observer->onSpriteError(result.get()); } } void SpriteAtlas::setObserver(SpriteAtlasObserver* observer_) { observer = observer_; } void SpriteAtlas::dumpDebugLogs() const { Log::Info(Event::General, "SpriteAtlas::loaded: %d", loaded); } void SpriteAtlas::setSprites(const Sprites& newSprites) { for (const auto& pair : newSprites) { _setSprite(pair.first, pair.second); } } void SpriteAtlas::setSprite(const std::string& name, std::shared_ptr sprite) { _setSprite(name, sprite); } void SpriteAtlas::removeSprite(const std::string& name) { icons.clear(); auto it = entries.find(name); if (it == entries.end()) { return; } Entry& entry = it->second; if (entry.iconRect) { bin.release(*entry.iconRect); } if (entry.patternRect) { bin.release(*entry.patternRect); } entries.erase(it); } void SpriteAtlas::_setSprite(const std::string& name, const std::shared_ptr& sprite) { icons.clear(); if (!sprite->image.valid()) { Log::Warning(Event::Sprite, "invalid sprite image '%s'", name.c_str()); return; } auto it = entries.find(name); if (it == entries.end()) { entries.emplace(name, Entry { sprite, {}, {} }); return; } Entry& entry = it->second; // There is already a sprite with that name in our store. if (entry.spriteImage->image.size != sprite->image.size) { Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", name.c_str()); return; } entry.spriteImage = sprite; if (entry.iconRect) { copy(entry, &Entry::iconRect); } if (entry.patternRect) { copy(entry, &Entry::patternRect); } } std::shared_ptr SpriteAtlas::getSprite(const std::string& name) { const auto it = entries.find(name); if (it != entries.end()) { return it->second.spriteImage; } else { if (!entries.empty()) { Log::Info(Event::Sprite, "Can't find sprite named '%s'", name.c_str()); } return nullptr; } } void SpriteAtlas::getIcons(IconRequestor& requestor) { if (isLoaded()) { requestor.onIconsAvailable(this, buildIconMap()); } else { requestors.insert(&requestor); } } void SpriteAtlas::removeRequestor(IconRequestor& requestor) { requestors.erase(&requestor); } optional SpriteAtlas::getIcon(const std::string& name) { return getImage(name, &Entry::iconRect); } optional SpriteAtlas::getPattern(const std::string& name) { return getImage(name, &Entry::patternRect); } optional SpriteAtlas::getImage(const std::string& name, optional> Entry::*entryRect) { auto it = entries.find(name); if (it == entries.end()) { if (!entries.empty()) { Log::Info(Event::Sprite, "Can't find sprite named '%s'", name.c_str()); } return {}; } Entry& entry = it->second; if (entry.*entryRect) { assert(entry.spriteImage.get()); return SpriteAtlasElement { *(entry.*entryRect), entry.spriteImage, size, pixelRatio }; } const uint16_t pixelWidth = std::ceil(entry.spriteImage->image.size.width / pixelRatio); const uint16_t pixelHeight = std::ceil(entry.spriteImage->image.size.height / pixelRatio); // Increase to next number divisible by 4, but at least 1. // This is so we can scale down the texture coordinates and pack them // into 2 bytes rather than 4 bytes. const uint16_t packWidth = (pixelWidth + 1) + (4 - (pixelWidth + 1) % 4); const uint16_t packHeight = (pixelHeight + 1) + (4 - (pixelHeight + 1) % 4); // We have to allocate a new area in the bin, and store an empty image in it. Rect rect = bin.allocate(packWidth, packHeight); if (rect.w == 0) { if (debug::spriteWarnings) { Log::Warning(Event::Sprite, "sprite atlas bitmap overflow"); } return {}; } entry.*entryRect = rect; copy(entry, entryRect); return SpriteAtlasElement { rect, entry.spriteImage, size, pixelRatio }; } void SpriteAtlas::copy(const Entry& entry, optional> Entry::*entryRect) { if (!image.valid()) { image = PremultipliedImage({ static_cast(std::ceil(size.width * pixelRatio)), static_cast(std::ceil(size.height * pixelRatio)) }); image.fill(0); } const PremultipliedImage& src = entry.spriteImage->image; const Rect& rect = *(entry.*entryRect); const uint32_t padding = 1; const uint32_t x = (rect.x + padding) * pixelRatio; const uint32_t y = (rect.y + padding) * pixelRatio; const uint32_t w = src.size.width; const uint32_t h = src.size.height; PremultipliedImage::copy(src, image, { 0, 0 }, { x, y }, { w, h }); if (entryRect == &Entry::patternRect) { // Add 1 pixel wrapped padding on each side of the image. PremultipliedImage::copy(src, image, { 0, h - 1 }, { x, y - 1 }, { w, 1 }); // T PremultipliedImage::copy(src, image, { 0, 0 }, { x, y + h }, { w, 1 }); // B PremultipliedImage::copy(src, image, { w - 1, 0 }, { x - 1, y }, { 1, h }); // L PremultipliedImage::copy(src, image, { 0, 0 }, { x + w, y }, { 1, h }); // R } dirty = true; } IconMap SpriteAtlas::buildIconMap() { if (icons.empty()) { for (auto entry : entries) { icons.emplace(std::piecewise_construct, std::forward_as_tuple(entry.first), std::forward_as_tuple(*getIcon(entry.first))); } } return icons; } void SpriteAtlas::upload(gl::Context& context, gl::TextureUnit unit) { if (!texture) { texture = context.createTexture(image, unit); } else if (dirty) { context.updateTexture(*texture, image, unit); } #if not MBGL_USE_GLES2 // if (dirty) { // platform::showColorDebugImage("Sprite Atlas", // reinterpret_cast(image.data.get()), size.width, // size.height, image.size.width, image.size.height); // } #endif // MBGL_USE_GLES2 dirty = false; } void SpriteAtlas::bind(bool linear, gl::Context& context, gl::TextureUnit unit) { upload(context, unit); context.bindTexture(*texture, unit, linear ? gl::TextureFilter::Linear : gl::TextureFilter::Nearest); } } // namespace mbgl