[core] Split style image collection from SpriteAtlas
This commit is contained in:
committed by
John Firebaugh
parent
c80f3e9d29
commit
cc9f040a2d
@@ -283,9 +283,13 @@ set(MBGL_CORE_FILES
|
||||
# sprite
|
||||
src/mbgl/sprite/sprite_atlas.cpp
|
||||
src/mbgl/sprite/sprite_atlas.hpp
|
||||
src/mbgl/sprite/sprite_atlas_observer.hpp
|
||||
src/mbgl/sprite/sprite_atlas_worker.cpp
|
||||
src/mbgl/sprite/sprite_atlas_worker.hpp
|
||||
src/mbgl/sprite/sprite_image_collection.cpp
|
||||
src/mbgl/sprite/sprite_image_collection.hpp
|
||||
src/mbgl/sprite/sprite_loader.cpp
|
||||
src/mbgl/sprite/sprite_loader.hpp
|
||||
src/mbgl/sprite/sprite_loader_observer.hpp
|
||||
src/mbgl/sprite/sprite_loader_worker.cpp
|
||||
src/mbgl/sprite/sprite_loader_worker.hpp
|
||||
src/mbgl/sprite/sprite_parser.cpp
|
||||
src/mbgl/sprite/sprite_parser.hpp
|
||||
|
||||
@@ -322,6 +326,8 @@ set(MBGL_CORE_FILES
|
||||
src/mbgl/style/class_dictionary.cpp
|
||||
src/mbgl/style/class_dictionary.hpp
|
||||
src/mbgl/style/image.cpp
|
||||
src/mbgl/style/image_impl.cpp
|
||||
src/mbgl/style/image_impl.hpp
|
||||
src/mbgl/style/layer.cpp
|
||||
src/mbgl/style/layer_impl.cpp
|
||||
src/mbgl/style/layer_impl.hpp
|
||||
@@ -363,6 +369,7 @@ set(MBGL_CORE_FILES
|
||||
include/mbgl/style/conversion/tileset.hpp
|
||||
include/mbgl/style/conversion/transition_options.hpp
|
||||
src/mbgl/style/conversion/geojson.cpp
|
||||
src/mbgl/style/conversion/json.hpp
|
||||
src/mbgl/style/conversion/stringify.hpp
|
||||
|
||||
# style/function
|
||||
|
||||
@@ -46,6 +46,8 @@ set(MBGL_TEST_FILES
|
||||
|
||||
# sprite
|
||||
test/sprite/sprite_atlas.test.cpp
|
||||
test/sprite/sprite_image_collection.test.cpp
|
||||
test/sprite/sprite_loader.test.cpp
|
||||
test/sprite/sprite_parser.test.cpp
|
||||
|
||||
# src/mbgl/test
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <mbgl/util/image.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
@@ -9,16 +11,21 @@ class Image {
|
||||
public:
|
||||
Image(PremultipliedImage&&, float pixelRatio, bool sdf = false);
|
||||
|
||||
PremultipliedImage image;
|
||||
PremultipliedImage& getImage() const;
|
||||
|
||||
// Pixel ratio of the sprite image.
|
||||
const float pixelRatio;
|
||||
float getPixelRatio() const;
|
||||
|
||||
// Whether this image should be interpreted as a signed distance field icon.
|
||||
const bool sdf;
|
||||
bool isSdf() const;
|
||||
|
||||
float getWidth() const { return image.size.width / pixelRatio; }
|
||||
float getHeight() const { return image.size.height / pixelRatio; }
|
||||
float getWidth() const;
|
||||
float getHeight() const;
|
||||
|
||||
class Impl;
|
||||
|
||||
private:
|
||||
const std::shared_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage
|
||||
{
|
||||
CGImageRef image = CGImageFromMGLPremultipliedImage(styleImage->image.clone());
|
||||
CGImageRef image = CGImageFromMGLPremultipliedImage(styleImage->getImage().clone());
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (self = [self initWithCGImage:image scale:styleImage->pixelRatio orientation:UIImageOrientationUp])
|
||||
if (self = [self initWithCGImage:image scale:styleImage->getPixelRatio() orientation:UIImageOrientationUp])
|
||||
{
|
||||
if (styleImage->sdf)
|
||||
if (styleImage->isSdf())
|
||||
{
|
||||
self = [self imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
- (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)styleImage {
|
||||
CGImageRef image = CGImageFromMGLPremultipliedImage(styleImage->image.clone());
|
||||
CGImageRef image = CGImageFromMGLPremultipliedImage(styleImage->getImage().clone());
|
||||
if (!image) {
|
||||
return nil;
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
CGImageRelease(image);
|
||||
if (self = [self initWithSize:NSMakeSize(styleImage->getWidth(), styleImage->getHeight())]) {
|
||||
[self addRepresentation:rep];
|
||||
[self setTemplate:styleImage->sdf];
|
||||
[self setTemplate:styleImage->isSdf()];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mbgl/annotation/symbol_annotation_impl.hpp>
|
||||
#include <mbgl/annotation/line_annotation_impl.hpp>
|
||||
#include <mbgl/annotation/fill_annotation_impl.hpp>
|
||||
#include <mbgl/sprite/sprite_image_collection.hpp>
|
||||
#include <mbgl/style/style.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer_impl.hpp>
|
||||
@@ -19,7 +20,7 @@ const std::string AnnotationManager::SourceID = "com.mapbox.annotations";
|
||||
const std::string AnnotationManager::PointLayerID = "com.mapbox.annotations.points";
|
||||
|
||||
AnnotationManager::AnnotationManager(float pixelRatio)
|
||||
: spriteAtlas({ 1024, 1024 }, pixelRatio) {
|
||||
: spriteAtlas({ 1024, 1024 }, pixelRatio){
|
||||
// This is a special atlas, holding only images added via addIcon, so we always treat it as
|
||||
// loaded.
|
||||
spriteAtlas.markAsLoaded();
|
||||
@@ -190,16 +191,20 @@ void AnnotationManager::removeTile(AnnotationTile& tile) {
|
||||
}
|
||||
|
||||
void AnnotationManager::addImage(const std::string& id, std::unique_ptr<style::Image> image) {
|
||||
spriteAtlas.addImage(id, std::move(image));
|
||||
addSpriteImage(spriteImages, id, std::move(image), [&](style::Image& added) {
|
||||
spriteAtlas.addImage(id, std::make_unique<style::Image>(added));
|
||||
});
|
||||
}
|
||||
|
||||
void AnnotationManager::removeImage(const std::string& id) {
|
||||
spriteAtlas.removeImage(id);
|
||||
removeSpriteImage(spriteImages, id, [&] () {
|
||||
spriteAtlas.removeImage(id);
|
||||
});
|
||||
}
|
||||
|
||||
double AnnotationManager::getTopOffsetPixelsForImage(const std::string& id) {
|
||||
const style::Image* image = spriteAtlas.getImage(id);
|
||||
return image ? -(image->image.size.height / image->pixelRatio) / 2 : 0;
|
||||
return image ? -(image->getImage().size.height / image->getPixelRatio()) / 2 : 0;
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -72,6 +73,8 @@ private:
|
||||
ShapeAnnotationMap shapeAnnotations;
|
||||
std::unordered_set<std::string> obsoleteShapeAnnotationLayers;
|
||||
std::unordered_set<AnnotationTile*> tiles;
|
||||
|
||||
std::unordered_map<std::string, std::unique_ptr<style::Image>> spriteImages;
|
||||
SpriteAtlas spriteAtlas;
|
||||
|
||||
friend class AnnotationTile;
|
||||
|
||||
@@ -942,8 +942,7 @@ void Map::addImage(const std::string& id, std::unique_ptr<style::Image> image) {
|
||||
}
|
||||
|
||||
impl->styleMutated = true;
|
||||
impl->style->spriteAtlas->addImage(id, std::move(image));
|
||||
impl->onUpdate(Update::Repaint);
|
||||
impl->style->addImage(id, std::move(image));
|
||||
}
|
||||
|
||||
void Map::removeImage(const std::string& id) {
|
||||
@@ -952,13 +951,12 @@ void Map::removeImage(const std::string& id) {
|
||||
}
|
||||
|
||||
impl->styleMutated = true;
|
||||
impl->style->spriteAtlas->removeImage(id);
|
||||
impl->onUpdate(Update::Repaint);
|
||||
impl->style->removeImage(id);
|
||||
}
|
||||
|
||||
const style::Image* Map::getImage(const std::string& id) {
|
||||
if (impl->style) {
|
||||
return impl->style->spriteAtlas->getImage(id);
|
||||
return impl->style->getImage(id);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas_worker.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
#include <mbgl/gl/context.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
#include <mbgl/util/platform.hpp>
|
||||
@@ -9,11 +6,6 @@
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/actor/actor.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
@@ -21,28 +13,12 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
static SpriteAtlasObserver nullObserver;
|
||||
|
||||
struct SpriteAtlas::Loader {
|
||||
Loader(Scheduler& scheduler, SpriteAtlas& spriteAtlas)
|
||||
: mailbox(std::make_shared<Mailbox>(*util::RunLoop::Get())),
|
||||
worker(scheduler, ActorRef<SpriteAtlas>(spriteAtlas, mailbox)) {
|
||||
}
|
||||
|
||||
std::shared_ptr<const std::string> image;
|
||||
std::shared_ptr<const std::string> json;
|
||||
std::unique_ptr<AsyncRequest> jsonRequest;
|
||||
std::unique_ptr<AsyncRequest> spriteRequest;
|
||||
std::shared_ptr<Mailbox> mailbox;
|
||||
Actor<SpriteAtlasWorker> worker;
|
||||
};
|
||||
|
||||
SpriteAtlasElement::SpriteAtlasElement(Rect<uint16_t> rect_,
|
||||
const style::Image& image,
|
||||
Size size_, float pixelRatio)
|
||||
: pos(std::move(rect_)),
|
||||
sdf(image.sdf),
|
||||
relativePixelRatio(image.pixelRatio / pixelRatio),
|
||||
sdf(image.isSdf()),
|
||||
relativePixelRatio(image.getPixelRatio() / pixelRatio),
|
||||
width(image.getWidth()),
|
||||
height(image.getHeight()) {
|
||||
|
||||
@@ -59,87 +35,25 @@ SpriteAtlasElement::SpriteAtlasElement(Rect<uint16_t> rect_,
|
||||
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, Scheduler& scheduler, FileSource& fileSource) {
|
||||
if (url.empty()) {
|
||||
// Treat a non-existent sprite as a successfully loaded empty sprite.
|
||||
markAsLoaded();
|
||||
return;
|
||||
}
|
||||
|
||||
loader = std::make_unique<Loader>(scheduler, *this);
|
||||
|
||||
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<const std::string>();
|
||||
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<const std::string>();
|
||||
emitSpriteLoadedIfComplete();
|
||||
} else {
|
||||
loader->image = res.data;
|
||||
emitSpriteLoadedIfComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SpriteAtlas::emitSpriteLoadedIfComplete() {
|
||||
assert(loader);
|
||||
|
||||
if (!loader->image || !loader->json) {
|
||||
return;
|
||||
}
|
||||
|
||||
loader->worker.invoke(&SpriteAtlasWorker::parse, loader->image, loader->json);
|
||||
// TODO: delete the loader?
|
||||
}
|
||||
|
||||
void SpriteAtlas::onParsed(Images&& result) {
|
||||
void SpriteAtlas::onSpriteLoaded(Images&& result) {
|
||||
markAsLoaded();
|
||||
|
||||
for (auto& pair : result) {
|
||||
addImage(pair.first, std::move(pair.second));
|
||||
}
|
||||
observer->onSpriteLoaded();
|
||||
|
||||
for (auto requestor : requestors) {
|
||||
requestor->onIconsAvailable(buildIconMap());
|
||||
}
|
||||
requestors.clear();
|
||||
}
|
||||
|
||||
void SpriteAtlas::onError(std::exception_ptr err) {
|
||||
observer->onSpriteError(err);
|
||||
}
|
||||
|
||||
void SpriteAtlas::setObserver(SpriteAtlasObserver* observer_) {
|
||||
observer = observer_;
|
||||
}
|
||||
|
||||
void SpriteAtlas::dumpDebugLogs() const {
|
||||
Log::Info(Event::General, "SpriteAtlas::loaded: %d", loaded);
|
||||
}
|
||||
|
||||
void SpriteAtlas::addImage(const std::string& id, std::unique_ptr<style::Image> image_) {
|
||||
icons.clear();
|
||||
|
||||
@@ -152,10 +66,7 @@ void SpriteAtlas::addImage(const std::string& id, std::unique_ptr<style::Image>
|
||||
Entry& entry = it->second;
|
||||
|
||||
// There is already a sprite with that name in our store.
|
||||
if (entry.image->image.size != image_->image.size) {
|
||||
Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", id.c_str());
|
||||
return;
|
||||
}
|
||||
assert(entry.image->getImage().size == image_->getImage().size);
|
||||
|
||||
entry.image = std::move(image_);
|
||||
|
||||
@@ -172,9 +83,7 @@ void SpriteAtlas::removeImage(const std::string& id) {
|
||||
icons.clear();
|
||||
|
||||
auto it = entries.find(id);
|
||||
if (it == entries.end()) {
|
||||
return;
|
||||
}
|
||||
assert(it != entries.end());
|
||||
|
||||
Entry& entry = it->second;
|
||||
|
||||
@@ -243,8 +152,8 @@ optional<SpriteAtlasElement> SpriteAtlas::getImage(const std::string& id,
|
||||
};
|
||||
}
|
||||
|
||||
const uint16_t pixelWidth = std::ceil(entry.image->image.size.width / pixelRatio);
|
||||
const uint16_t pixelHeight = std::ceil(entry.image->image.size.height / pixelRatio);
|
||||
const uint16_t pixelWidth = std::ceil(entry.image->getImage().size.width / pixelRatio);
|
||||
const uint16_t pixelHeight = std::ceil(entry.image->getImage().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
|
||||
@@ -279,7 +188,7 @@ void SpriteAtlas::copy(const Entry& entry, optional<Rect<uint16_t>> Entry::*entr
|
||||
image.fill(0);
|
||||
}
|
||||
|
||||
const PremultipliedImage& src = entry.image->image;
|
||||
const PremultipliedImage& src = entry.image->getImage();
|
||||
const Rect<uint16_t>& rect = *(entry.*entryRect);
|
||||
|
||||
const uint32_t padding = 1;
|
||||
@@ -337,4 +246,8 @@ void SpriteAtlas::bind(bool linear, gl::Context& context, gl::TextureUnit unit)
|
||||
linear ? gl::TextureFilter::Linear : gl::TextureFilter::Nearest);
|
||||
}
|
||||
|
||||
void SpriteAtlas::dumpDebugLogs() const {
|
||||
Log::Info(Event::General, "SpriteAtlas::loaded: %d", loaded);
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <mbgl/style/image.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
@@ -15,10 +14,6 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class Scheduler;
|
||||
class FileSource;
|
||||
class SpriteAtlasObserver;
|
||||
|
||||
namespace gl {
|
||||
class Context;
|
||||
} // namespace gl
|
||||
@@ -38,7 +33,7 @@ public:
|
||||
float height;
|
||||
};
|
||||
|
||||
using IconMap = std::map<std::string, SpriteAtlasElement>;
|
||||
using IconMap = std::unordered_map<std::string, SpriteAtlasElement>;
|
||||
using IconDependencies = std::set<std::string>;
|
||||
|
||||
class IconRequestor {
|
||||
@@ -48,12 +43,12 @@ public:
|
||||
|
||||
class SpriteAtlas : public util::noncopyable {
|
||||
public:
|
||||
using Images = std::map<std::string, std::unique_ptr<style::Image>>;
|
||||
using Images = std::unordered_map<std::string, std::unique_ptr<style::Image>>;
|
||||
|
||||
SpriteAtlas(Size, float pixelRatio);
|
||||
~SpriteAtlas();
|
||||
|
||||
void load(const std::string& url, Scheduler&, FileSource&);
|
||||
void onSpriteLoaded(Images&&);
|
||||
|
||||
void markAsLoaded() {
|
||||
loaded = true;
|
||||
@@ -65,8 +60,6 @@ public:
|
||||
|
||||
void dumpDebugLogs() const;
|
||||
|
||||
void setObserver(SpriteAtlasObserver*);
|
||||
|
||||
const style::Image* getImage(const std::string&) const;
|
||||
void addImage(const std::string&, std::unique_ptr<style::Image>);
|
||||
void removeImage(const std::string&);
|
||||
@@ -93,25 +86,12 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void emitSpriteLoadedIfComplete();
|
||||
|
||||
// Invoked by SpriteAtlasWorker
|
||||
friend class SpriteAtlasWorker;
|
||||
void onParsed(Images&& result);
|
||||
void onError(std::exception_ptr);
|
||||
|
||||
const Size size;
|
||||
const float pixelRatio;
|
||||
|
||||
struct Loader;
|
||||
std::unique_ptr<Loader> loader;
|
||||
|
||||
bool loaded = false;
|
||||
|
||||
SpriteAtlasObserver* observer = nullptr;
|
||||
|
||||
struct Entry {
|
||||
std::unique_ptr<style::Image> image;
|
||||
std::unique_ptr<const style::Image> image;
|
||||
|
||||
// One sprite image might be used as both an icon image and a pattern image. If so,
|
||||
// it must have two distinct entries in the texture. The one for the icon image has
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class SpriteAtlasObserver {
|
||||
public:
|
||||
virtual ~SpriteAtlasObserver() = default;
|
||||
|
||||
virtual void onSpriteLoaded() {}
|
||||
virtual void onSpriteError(std::exception_ptr) {}
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
40
src/mbgl/sprite/sprite_image_collection.cpp
Normal file
40
src/mbgl/sprite/sprite_image_collection.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include <mbgl/sprite/sprite_image_collection.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
void addSpriteImage(Images& images, const std::string& id,
|
||||
std::unique_ptr<style::Image> image_,
|
||||
std::function<void (style::Image&)> onAdded) {
|
||||
|
||||
auto it = images.find(id);
|
||||
if (it == images.end()) {
|
||||
// Add new
|
||||
it = images.emplace(id, std::move(image_)).first;
|
||||
onAdded(*it->second.get());
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<style::Image>& image = it->second;
|
||||
|
||||
// There is already a sprite with that name in our store.
|
||||
if (image->getImage().size != image_->getImage().size) {
|
||||
Log::Warning(Event::Sprite, "Can't change sprite dimensions for '%s'", id.c_str());
|
||||
}
|
||||
|
||||
// Update existing
|
||||
image = std::move(image_);
|
||||
onAdded(*it->second.get());
|
||||
}
|
||||
|
||||
void removeSpriteImage(Images& images,
|
||||
const std::string& id,
|
||||
std::function<void ()> onRemoved) {
|
||||
if (images.erase(id) > 0) {
|
||||
onRemoved();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace mbgl
|
||||
25
src/mbgl/sprite/sprite_image_collection.hpp
Normal file
25
src/mbgl/sprite/sprite_image_collection.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/image.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
using Images = std::unordered_map<std::string, std::unique_ptr<style::Image>>;
|
||||
|
||||
void addSpriteImage(Images&,
|
||||
const std::string&,
|
||||
std::unique_ptr<style::Image>,
|
||||
std::function<void (style::Image&)> onAdded = [] (style::Image&){});
|
||||
|
||||
void removeSpriteImage(Images&,
|
||||
const std::string&,
|
||||
std::function<void ()> onRemoved = [] (){});
|
||||
|
||||
|
||||
|
||||
} // namespace mbgl
|
||||
107
src/mbgl/sprite/sprite_loader.cpp
Normal file
107
src/mbgl/sprite/sprite_loader.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include <mbgl/sprite/sprite_loader.hpp>
|
||||
#include <mbgl/sprite/sprite_loader_worker.hpp>
|
||||
#include <mbgl/sprite/sprite_loader_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
#include <mbgl/util/logging.hpp>
|
||||
#include <mbgl/util/platform.hpp>
|
||||
#include <mbgl/util/std.hpp>
|
||||
#include <mbgl/util/constants.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
#include <mbgl/storage/file_source.hpp>
|
||||
#include <mbgl/storage/resource.hpp>
|
||||
#include <mbgl/storage/response.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/actor/actor.hpp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
static SpriteLoaderObserver nullObserver;
|
||||
|
||||
struct SpriteLoader::Loader {
|
||||
Loader(Scheduler& scheduler, SpriteLoader& spriteAtlas)
|
||||
: mailbox(std::make_shared<Mailbox>(*util::RunLoop::Get())),
|
||||
worker(scheduler, ActorRef<SpriteLoader>(spriteAtlas, mailbox)) {
|
||||
}
|
||||
|
||||
std::shared_ptr<const std::string> image;
|
||||
std::shared_ptr<const std::string> json;
|
||||
std::unique_ptr<AsyncRequest> jsonRequest;
|
||||
std::unique_ptr<AsyncRequest> spriteRequest;
|
||||
std::shared_ptr<Mailbox> mailbox;
|
||||
Actor<SpriteLoaderWorker> worker;
|
||||
};
|
||||
|
||||
SpriteLoader::SpriteLoader(float pixelRatio_)
|
||||
: pixelRatio(pixelRatio_)
|
||||
, observer(&nullObserver) {
|
||||
}
|
||||
|
||||
SpriteLoader::~SpriteLoader() = default;
|
||||
|
||||
void SpriteLoader::load(const std::string& url, Scheduler& scheduler, FileSource& fileSource) {
|
||||
if (url.empty()) {
|
||||
// Treat a non-existent sprite as a successfully loaded empty sprite.
|
||||
observer->onSpriteLoaded({});
|
||||
return;
|
||||
}
|
||||
|
||||
loader = std::make_unique<Loader>(scheduler, *this);
|
||||
|
||||
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<const std::string>();
|
||||
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<const std::string>();
|
||||
emitSpriteLoadedIfComplete();
|
||||
} else {
|
||||
loader->image = res.data;
|
||||
emitSpriteLoadedIfComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SpriteLoader::emitSpriteLoadedIfComplete() {
|
||||
assert(loader);
|
||||
|
||||
if (!loader->image || !loader->json) {
|
||||
return;
|
||||
}
|
||||
|
||||
loader->worker.invoke(&SpriteLoaderWorker::parse, loader->image, loader->json);
|
||||
}
|
||||
|
||||
void SpriteLoader::onParsed(Images&& result) {
|
||||
|
||||
|
||||
|
||||
observer->onSpriteLoaded(std::move(result));
|
||||
}
|
||||
|
||||
void SpriteLoader::onError(std::exception_ptr err) {
|
||||
observer->onSpriteError(err);
|
||||
}
|
||||
|
||||
void SpriteLoader::setObserver(SpriteLoaderObserver* observer_) {
|
||||
observer = observer_;
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
46
src/mbgl/sprite/sprite_loader.hpp
Normal file
46
src/mbgl/sprite/sprite_loader.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/util/noncopyable.hpp>
|
||||
#include <mbgl/style/image.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class Scheduler;
|
||||
class FileSource;
|
||||
class SpriteLoaderObserver;
|
||||
|
||||
class SpriteLoader : public util::noncopyable {
|
||||
public:
|
||||
using Images = std::unordered_map<std::string, std::unique_ptr<style::Image>>;
|
||||
|
||||
SpriteLoader(float pixelRatio);
|
||||
~SpriteLoader();
|
||||
|
||||
void load(const std::string& url, Scheduler&, FileSource&);
|
||||
|
||||
void setObserver(SpriteLoaderObserver*);
|
||||
|
||||
private:
|
||||
void emitSpriteLoadedIfComplete();
|
||||
|
||||
// Invoked by SpriteAtlasWorker
|
||||
friend class SpriteLoaderWorker;
|
||||
void onParsed(Images&& result);
|
||||
void onError(std::exception_ptr);
|
||||
|
||||
const float pixelRatio;
|
||||
|
||||
struct Loader;
|
||||
std::unique_ptr<Loader> loader;
|
||||
|
||||
SpriteLoaderObserver* observer = nullptr;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
24
src/mbgl/sprite/sprite_loader_observer.hpp
Normal file
24
src/mbgl/sprite/sprite_loader_observer.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
namespace style {
|
||||
class Image;
|
||||
} // namespace style
|
||||
|
||||
class SpriteLoaderObserver {
|
||||
public:
|
||||
using Images = std::unordered_map<std::string, std::unique_ptr<style::Image>>;
|
||||
|
||||
virtual ~SpriteLoaderObserver() = default;
|
||||
|
||||
virtual void onSpriteLoaded(Images&&) {}
|
||||
virtual void onSpriteError(std::exception_ptr) {}
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -1,14 +1,14 @@
|
||||
#include <mbgl/sprite/sprite_atlas_worker.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_loader_worker.hpp>
|
||||
#include <mbgl/sprite/sprite_loader.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
SpriteAtlasWorker::SpriteAtlasWorker(ActorRef<SpriteAtlasWorker>, ActorRef<SpriteAtlas> parent_)
|
||||
SpriteLoaderWorker::SpriteLoaderWorker(ActorRef<SpriteLoaderWorker>, ActorRef<SpriteLoader> parent_)
|
||||
: parent(std::move(parent_)) {
|
||||
}
|
||||
|
||||
void SpriteAtlasWorker::parse(std::shared_ptr<const std::string> image,
|
||||
void SpriteLoaderWorker::parse(std::shared_ptr<const std::string> image,
|
||||
std::shared_ptr<const std::string> json) {
|
||||
try {
|
||||
if (!image) {
|
||||
@@ -20,9 +20,9 @@ void SpriteAtlasWorker::parse(std::shared_ptr<const std::string> image,
|
||||
throw std::runtime_error("missing sprite metadata");
|
||||
}
|
||||
|
||||
parent.invoke(&SpriteAtlas::onParsed, parseSprite(*image, *json));
|
||||
parent.invoke(&SpriteLoader::onParsed, parseSprite(*image, *json));
|
||||
} catch (...) {
|
||||
parent.invoke(&SpriteAtlas::onError, std::current_exception());
|
||||
parent.invoke(&SpriteLoader::onError, std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
class SpriteAtlas;
|
||||
class SpriteLoader;
|
||||
|
||||
class SpriteAtlasWorker {
|
||||
class SpriteLoaderWorker {
|
||||
public:
|
||||
SpriteAtlasWorker(ActorRef<SpriteAtlasWorker>, ActorRef<SpriteAtlas>);
|
||||
SpriteLoaderWorker(ActorRef<SpriteLoaderWorker>, ActorRef<SpriteLoader>);
|
||||
|
||||
void parse(std::shared_ptr<const std::string> image, std::shared_ptr<const std::string> json);
|
||||
|
||||
private:
|
||||
ActorRef<SpriteAtlas> parent;
|
||||
ActorRef<SpriteLoader> parent;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
@@ -24,7 +24,7 @@ std::unique_ptr<style::Image> createStyleImage(const PremultipliedImage&,
|
||||
double ratio,
|
||||
bool sdf);
|
||||
|
||||
using Images = std::map<std::string, std::unique_ptr<style::Image>>;
|
||||
using Images = std::unordered_map<std::string, std::unique_ptr<style::Image>>;
|
||||
|
||||
// Parses an image and an associated JSON file and returns the sprite objects.
|
||||
Images parseSprite(const std::string& image, const std::string& json);
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
#include <mbgl/style/image.hpp>
|
||||
#include <mbgl/style/image_impl.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
Image::Image(PremultipliedImage&& image_,
|
||||
const float pixelRatio_,
|
||||
bool sdf_)
|
||||
: image(std::move(image_)),
|
||||
pixelRatio(pixelRatio_),
|
||||
sdf(sdf_) {
|
||||
Image::Image(PremultipliedImage &&image,
|
||||
const float pixelRatio,
|
||||
bool sdf)
|
||||
: impl(std::make_shared<Impl>(std::move(image), pixelRatio, sdf)) {
|
||||
}
|
||||
|
||||
if (!image.valid()) {
|
||||
throw util::SpriteImageException("Sprite image dimensions may not be zero");
|
||||
} else if (pixelRatio <= 0) {
|
||||
throw util::SpriteImageException("Sprite pixelRatio may not be <= 0");
|
||||
}
|
||||
PremultipliedImage& Image::getImage() const {
|
||||
assert(impl);
|
||||
return impl->image;
|
||||
}
|
||||
|
||||
bool Image::isSdf() const {
|
||||
return impl->sdf;
|
||||
}
|
||||
|
||||
float Image::getPixelRatio() const {
|
||||
return impl->pixelRatio;
|
||||
}
|
||||
|
||||
float Image::getWidth() const {
|
||||
return impl->getWidth();
|
||||
}
|
||||
|
||||
float Image::getHeight() const {
|
||||
return impl->getHeight();
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
|
||||
22
src/mbgl/style/image_impl.cpp
Normal file
22
src/mbgl/style/image_impl.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <mbgl/style/image_impl.hpp>
|
||||
#include <mbgl/util/exception.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
Image::Impl::Impl(PremultipliedImage&& image_,
|
||||
const float pixelRatio_,
|
||||
bool sdf_)
|
||||
: image(std::move(image_)),
|
||||
pixelRatio(pixelRatio_),
|
||||
sdf(sdf_) {
|
||||
|
||||
if (!image.valid()) {
|
||||
throw util::SpriteImageException("Sprite image dimensions may not be zero");
|
||||
} else if (pixelRatio <= 0) {
|
||||
throw util::SpriteImageException("Sprite pixelRatio may not be <= 0");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
25
src/mbgl/style/image_impl.hpp
Normal file
25
src/mbgl/style/image_impl.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/image.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
class Image::Impl {
|
||||
public:
|
||||
Impl(PremultipliedImage&&, float pixelRatio, bool sdf = false);
|
||||
|
||||
PremultipliedImage image;
|
||||
|
||||
// Pixel ratio of the sprite image.
|
||||
const float pixelRatio;
|
||||
|
||||
// Whether this image should be interpreted as a signed distance field icon.
|
||||
const bool sdf;
|
||||
|
||||
float getWidth() const { return image.size.width / pixelRatio; }
|
||||
float getHeight() const { return image.size.height / pixelRatio; }
|
||||
};
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/style/layer_impl.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer.hpp>
|
||||
#include <mbgl/style/layers/symbol_layer_properties.hpp>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/text/glyph_atlas_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_loader_observer.hpp>
|
||||
#include <mbgl/style/source_observer.hpp>
|
||||
#include <mbgl/map/update.hpp>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
class Observer : public GlyphAtlasObserver,
|
||||
public SpriteAtlasObserver,
|
||||
public SpriteLoaderObserver,
|
||||
public SourceObserver {
|
||||
public:
|
||||
virtual void onUpdate(Update) {}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include <mbgl/style/transition_options.hpp>
|
||||
#include <mbgl/style/class_dictionary.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_image_collection.hpp>
|
||||
#include <mbgl/sprite/sprite_loader.hpp>
|
||||
#include <mbgl/text/glyph_atlas.hpp>
|
||||
#include <mbgl/geometry/line_atlas.hpp>
|
||||
#include <mbgl/renderer/update_parameters.hpp>
|
||||
@@ -72,13 +74,14 @@ Style::Style(Scheduler& scheduler_, FileSource& fileSource_, float pixelRatio)
|
||||
: scheduler(scheduler_),
|
||||
fileSource(fileSource_),
|
||||
glyphAtlas(std::make_unique<GlyphAtlas>(Size{ 2048, 2048 }, fileSource)),
|
||||
spriteLoader(std::make_unique<SpriteLoader>(pixelRatio)),
|
||||
spriteAtlas(std::make_unique<SpriteAtlas>(Size{ 1024, 1024 }, pixelRatio)),
|
||||
lineAtlas(std::make_unique<LineAtlas>(Size{ 256, 512 })),
|
||||
light(std::make_unique<Light>()),
|
||||
renderLight(light->impl),
|
||||
observer(&nullObserver) {
|
||||
glyphAtlas->setObserver(this);
|
||||
spriteAtlas->setObserver(this);
|
||||
spriteLoader->setObserver(this);
|
||||
light->setObserver(this);
|
||||
}
|
||||
|
||||
@@ -160,7 +163,7 @@ void Style::setJSON(const std::string& json) {
|
||||
setLight(std::make_unique<Light>(parser.light));
|
||||
|
||||
glyphAtlas->setURL(parser.glyphURL);
|
||||
spriteAtlas->load(parser.spriteURL, scheduler, fileSource);
|
||||
spriteLoader->load(parser.spriteURL, scheduler, fileSource);
|
||||
|
||||
loaded = true;
|
||||
|
||||
@@ -539,6 +542,24 @@ bool Style::isLoaded() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Style::addImage(const std::string& id, std::unique_ptr<style::Image> image) {
|
||||
addSpriteImage(spriteImages, id, std::move(image), [&](style::Image& added) {
|
||||
spriteAtlas->addImage(id, std::make_unique<style::Image>(added));
|
||||
observer->onUpdate(Update::Repaint);
|
||||
});
|
||||
}
|
||||
|
||||
void Style::removeImage(const std::string& id) {
|
||||
removeSpriteImage(spriteImages, id, [&] () {
|
||||
spriteAtlas->removeImage(id);
|
||||
observer->onUpdate(Update::Repaint);
|
||||
});
|
||||
}
|
||||
|
||||
const style::Image* Style::getImage(const std::string& id) const {
|
||||
return spriteAtlas->getImage(id);
|
||||
}
|
||||
|
||||
RenderData Style::getRenderData(MapDebugOptions debugOptions, float angle) const {
|
||||
RenderData result;
|
||||
|
||||
@@ -755,8 +776,20 @@ void Style::onTileError(RenderSource& source, const OverscaledTileID& tileID, st
|
||||
observer->onResourceError(error);
|
||||
}
|
||||
|
||||
void Style::onSpriteLoaded() {
|
||||
observer->onSpriteLoaded();
|
||||
void Style::onSpriteLoaded(SpriteLoader::Images&& images) {
|
||||
// Add images to collection
|
||||
Images addedImages;
|
||||
for (auto& entry : images) {
|
||||
addSpriteImage(spriteImages, entry.first, std::move(entry.second), [&] (style::Image& added) {
|
||||
addedImages.emplace(entry.first, std::make_unique<Image>(added));
|
||||
});
|
||||
}
|
||||
|
||||
// Update render sprite atlas
|
||||
spriteAtlas->onSpriteLoaded(std::move(addedImages));
|
||||
|
||||
// Update observer
|
||||
observer->onSpriteLoaded(std::move(images));
|
||||
observer->onUpdate(Update::Repaint); // For *-pattern properties.
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <mbgl/renderer/render_layer.hpp>
|
||||
#include <mbgl/renderer/render_light.hpp>
|
||||
#include <mbgl/text/glyph_atlas_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas_observer.hpp>
|
||||
#include <mbgl/sprite/sprite_loader_observer.hpp>
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/map/zoom_history.hpp>
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace mbgl {
|
||||
class FileSource;
|
||||
class GlyphAtlas;
|
||||
class SpriteAtlas;
|
||||
class SpriteLoader;
|
||||
class LineAtlas;
|
||||
class RenderData;
|
||||
class TransformState;
|
||||
@@ -44,7 +45,7 @@ class Layer;
|
||||
class QueryParameters;
|
||||
|
||||
class Style : public GlyphAtlasObserver,
|
||||
public SpriteAtlasObserver,
|
||||
public SpriteLoaderObserver,
|
||||
public SourceObserver,
|
||||
public RenderSourceObserver,
|
||||
public LayerObserver,
|
||||
@@ -106,6 +107,10 @@ public:
|
||||
Light* getLight() const;
|
||||
const RenderLight& getRenderLight() const;
|
||||
|
||||
const style::Image* getImage(const std::string&) const;
|
||||
void addImage(const std::string&, std::unique_ptr<style::Image>);
|
||||
void removeImage(const std::string&);
|
||||
|
||||
RenderData getRenderData(MapDebugOptions, float angle) const;
|
||||
|
||||
std::vector<Feature> queryRenderedFeatures(const ScreenLineString& geometry,
|
||||
@@ -120,6 +125,7 @@ public:
|
||||
Scheduler& scheduler;
|
||||
FileSource& fileSource;
|
||||
std::unique_ptr<GlyphAtlas> glyphAtlas;
|
||||
std::unique_ptr<SpriteLoader> spriteLoader;
|
||||
std::unique_ptr<SpriteAtlas> spriteAtlas;
|
||||
std::unique_ptr<LineAtlas> lineAtlas;
|
||||
|
||||
@@ -152,8 +158,9 @@ private:
|
||||
void onGlyphsLoaded(const FontStack&, const GlyphRange&) override;
|
||||
void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr) override;
|
||||
|
||||
// SpriteStoreObserver implementation.
|
||||
void onSpriteLoaded() override;
|
||||
// SpriteLoaderObserver implementation.
|
||||
std::unordered_map<std::string, std::unique_ptr<style::Image>> spriteImages;
|
||||
void onSpriteLoaded(SpriteLoaderObserver::Images&&) override;
|
||||
void onSpriteError(std::exception_ptr) override;
|
||||
|
||||
// SourceObserver implementation.
|
||||
|
||||
@@ -512,7 +512,7 @@ TEST(Map, GetImage) {
|
||||
|
||||
map.setStyleJSON(util::read_file("test/fixtures/api/icon_style.json"));
|
||||
map.addImage("test-icon", std::move(image));
|
||||
test::checkImage("test/fixtures/map/get_icon", map.getImage("test-icon")->image);
|
||||
test::checkImage("test/fixtures/map/get_icon", map.getImage("test-icon")->getImage());
|
||||
}
|
||||
|
||||
TEST(Map, DontLoadUnneededTiles) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <mbgl/test/stub_file_source.hpp>
|
||||
#include <mbgl/test/stub_style_observer.hpp>
|
||||
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_atlas.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
@@ -185,189 +184,3 @@ TEST(SpriteAtlas, RemoveReleasesBinPackRect) {
|
||||
EXPECT_TRUE(atlas.getIcon("big"));
|
||||
EXPECT_TRUE(log.empty());
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, OtherPixelRatio) {
|
||||
FixtureLog log;
|
||||
SpriteAtlas atlas({ 32, 32 }, 1);
|
||||
|
||||
// Adding mismatched sprite image
|
||||
atlas.addImage("one", std::make_unique<style::Image>(PremultipliedImage({ 8, 8 }), 2));
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, Replace) {
|
||||
FixtureLog log;
|
||||
SpriteAtlas atlas({ 32, 32 }, 1);
|
||||
|
||||
atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2));
|
||||
auto image = atlas.getImage("sprite");
|
||||
atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2));
|
||||
EXPECT_NE(image, atlas.getImage("sprite"));
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, ReplaceWithDifferentDimensions) {
|
||||
FixtureLog log;
|
||||
SpriteAtlas atlas({ 32, 32 }, 1);
|
||||
|
||||
atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2));
|
||||
atlas.addImage("sprite", std::make_unique<style::Image>(PremultipliedImage({ 18, 18 }), 2));
|
||||
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Warning,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't change sprite dimensions for 'sprite'",
|
||||
}));
|
||||
}
|
||||
|
||||
class SpriteAtlasTest {
|
||||
public:
|
||||
SpriteAtlasTest() = default;
|
||||
|
||||
util::RunLoop loop;
|
||||
StubFileSource fileSource;
|
||||
StubStyleObserver observer;
|
||||
ThreadPool threadPool { 1 };
|
||||
SpriteAtlas spriteAtlas{ { 32, 32 }, 1 };
|
||||
|
||||
void run() {
|
||||
// Squelch logging.
|
||||
Log::setObserver(std::make_unique<Log::NullObserver>());
|
||||
|
||||
spriteAtlas.setObserver(&observer);
|
||||
spriteAtlas.load("test/fixtures/resources/sprite", threadPool, fileSource);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
|
||||
void end() {
|
||||
loop.stop();
|
||||
}
|
||||
};
|
||||
|
||||
Response successfulSpriteImageResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.png", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_unique<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response successfulSpriteJSONResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.json", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_unique<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response failedSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.error = std::make_unique<Response::Error>(
|
||||
Response::Error::Reason::Other,
|
||||
"Failed by the test case");
|
||||
return response;
|
||||
}
|
||||
|
||||
Response corruptSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.data = std::make_unique<std::string>("CORRUPT");
|
||||
return response;
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, LoadingSuccess) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
FAIL() << util::toString(error);
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] () {
|
||||
EXPECT_EQ(1.0, test.spriteAtlas.getPixelRatio());
|
||||
EXPECT_TRUE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, JSONLoadingFail) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = failedSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, ImageLoadingFail) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = failedSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, JSONLoadingCorrupted) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = corruptSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed to parse JSON: Invalid value. at offset 0", util::toString(error));
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, ImageLoadingCorrupted) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = corruptSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
// Not asserting on platform-specific error text.
|
||||
EXPECT_FALSE(test.spriteAtlas.isLoaded());
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteAtlas, LoadingCancel) {
|
||||
SpriteAtlasTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse =
|
||||
test.fileSource.spriteJSONResponse = [&] (const Resource&) {
|
||||
test.end();
|
||||
return optional<Response>();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] () {
|
||||
FAIL() << "Should never be called";
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
45
test/sprite/sprite_image_collection.test.cpp
Normal file
45
test/sprite/sprite_image_collection.test.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
#include <mbgl/test/fixture_log_observer.hpp>
|
||||
|
||||
#include <mbgl/sprite/sprite_image_collection.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
TEST(SpriteImageCollection, OtherPixelRatio) {
|
||||
FixtureLog log;
|
||||
Images images;
|
||||
|
||||
// Adding mismatched sprite image
|
||||
addSpriteImage(images, "one", std::make_unique<style::Image>(PremultipliedImage({ 8, 8 }), 2));
|
||||
}
|
||||
|
||||
TEST(SpriteImageCollection, Replace) {
|
||||
FixtureLog log;
|
||||
Images images;
|
||||
|
||||
addSpriteImage(images, "sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2));
|
||||
auto image = images.find("sprite")->second.get();
|
||||
addSpriteImage(images, "sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2));
|
||||
EXPECT_NE(image, images.find("sprite")->second.get());
|
||||
}
|
||||
|
||||
TEST(SpriteImageCollection, ReplaceWithDifferentDimensions) {
|
||||
FixtureLog log;
|
||||
Images images;
|
||||
|
||||
addSpriteImage(images, "sprite", std::make_unique<style::Image>(PremultipliedImage({ 16, 16 }), 2));
|
||||
addSpriteImage(images, "sprite", std::make_unique<style::Image>(PremultipliedImage({ 18, 18 }), 2));
|
||||
|
||||
EXPECT_EQ(1u, log.count({
|
||||
EventSeverity::Warning,
|
||||
Event::Sprite,
|
||||
int64_t(-1),
|
||||
"Can't change sprite dimensions for 'sprite'",
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
164
test/sprite/sprite_loader.test.cpp
Normal file
164
test/sprite/sprite_loader.test.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <mbgl/test/util.hpp>
|
||||
#include <mbgl/test/fixture_log_observer.hpp>
|
||||
#include <mbgl/test/stub_file_source.hpp>
|
||||
#include <mbgl/test/stub_style_observer.hpp>
|
||||
|
||||
#include <mbgl/sprite/sprite_loader.hpp>
|
||||
#include <mbgl/sprite/sprite_parser.hpp>
|
||||
#include <mbgl/util/io.hpp>
|
||||
#include <mbgl/util/image.hpp>
|
||||
#include <mbgl/util/run_loop.hpp>
|
||||
#include <mbgl/util/default_thread_pool.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
#include <utility>
|
||||
|
||||
using namespace mbgl;
|
||||
|
||||
class SpriteLoaderTest {
|
||||
public:
|
||||
SpriteLoaderTest() = default;
|
||||
|
||||
util::RunLoop loop;
|
||||
StubFileSource fileSource;
|
||||
StubStyleObserver observer;
|
||||
ThreadPool threadPool { 1 };
|
||||
SpriteLoader spriteLoader{ 1 };
|
||||
|
||||
void run() {
|
||||
// Squelch logging.
|
||||
Log::setObserver(std::make_unique<Log::NullObserver>());
|
||||
|
||||
spriteLoader.setObserver(&observer);
|
||||
spriteLoader.load("test/fixtures/resources/sprite", threadPool, fileSource);
|
||||
|
||||
loop.run();
|
||||
}
|
||||
|
||||
void end() {
|
||||
loop.stop();
|
||||
}
|
||||
};
|
||||
|
||||
Response successfulSpriteImageResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.png", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_unique<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response successfulSpriteJSONResponse(const Resource& resource) {
|
||||
EXPECT_EQ("test/fixtures/resources/sprite.json", resource.url);
|
||||
Response response;
|
||||
response.data = std::make_unique<std::string>(util::read_file(resource.url));
|
||||
return response;
|
||||
}
|
||||
|
||||
Response failedSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.error = std::make_unique<Response::Error>(
|
||||
Response::Error::Reason::Other,
|
||||
"Failed by the test case");
|
||||
return response;
|
||||
}
|
||||
|
||||
Response corruptSpriteResponse(const Resource&) {
|
||||
Response response;
|
||||
response.data = std::make_unique<std::string>("CORRUPT");
|
||||
return response;
|
||||
}
|
||||
|
||||
TEST(SpriteLoader, LoadingSuccess) {
|
||||
SpriteLoaderTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
FAIL() << util::toString(error);
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] (SpriteLoaderObserver::Images&& images) {
|
||||
EXPECT_EQ(images.size(), Images::size_type(367));
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteLoader, JSONLoadingFail) {
|
||||
SpriteLoaderTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = failedSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteLoader, ImageLoadingFail) {
|
||||
SpriteLoaderTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = failedSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed by the test case", util::toString(error));
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteLoader, JSONLoadingCorrupted) {
|
||||
SpriteLoaderTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = successfulSpriteImageResponse;
|
||||
test.fileSource.spriteJSONResponse = corruptSpriteResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
EXPECT_EQ("Failed to parse JSON: Invalid value. at offset 0", util::toString(error));
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteLoader, ImageLoadingCorrupted) {
|
||||
SpriteLoaderTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse = corruptSpriteResponse;
|
||||
test.fileSource.spriteJSONResponse = successfulSpriteJSONResponse;
|
||||
|
||||
test.observer.spriteError = [&] (std::exception_ptr error) {
|
||||
EXPECT_TRUE(error != nullptr);
|
||||
// Not asserting on platform-specific error text.
|
||||
test.end();
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
|
||||
TEST(SpriteLoader, LoadingCancel) {
|
||||
SpriteLoaderTest test;
|
||||
|
||||
test.fileSource.spriteImageResponse =
|
||||
test.fileSource.spriteJSONResponse = [&] (const Resource&) {
|
||||
test.end();
|
||||
return optional<Response>();
|
||||
};
|
||||
|
||||
test.observer.spriteLoaded = [&] (const SpriteLoaderObserver::Images&) {
|
||||
FAIL() << "Should never be called";
|
||||
};
|
||||
|
||||
test.run();
|
||||
}
|
||||
@@ -145,11 +145,11 @@ TEST(Sprite, SpriteImageCreation1x) {
|
||||
ASSERT_TRUE(sprite.get());
|
||||
EXPECT_EQ(18, sprite->getWidth());
|
||||
EXPECT_EQ(18, sprite->getHeight());
|
||||
EXPECT_EQ(18u, sprite->image.size.width);
|
||||
EXPECT_EQ(18u, sprite->image.size.height);
|
||||
EXPECT_EQ(1, sprite->pixelRatio);
|
||||
EXPECT_EQ(18u, sprite->getImage().size.width);
|
||||
EXPECT_EQ(18u, sprite->getImage().size.height);
|
||||
EXPECT_EQ(1, sprite->getPixelRatio());
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1x-museum.png"),
|
||||
sprite->image);
|
||||
sprite->getImage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,11 +161,11 @@ TEST(Sprite, SpriteImageCreation2x) {
|
||||
ASSERT_TRUE(sprite.get());
|
||||
EXPECT_EQ(18, sprite->getWidth());
|
||||
EXPECT_EQ(18, sprite->getHeight());
|
||||
EXPECT_EQ(36u, sprite->image.size.width);
|
||||
EXPECT_EQ(36u, sprite->image.size.height);
|
||||
EXPECT_EQ(2, sprite->pixelRatio);
|
||||
EXPECT_EQ(36u, sprite->getImage().size.width);
|
||||
EXPECT_EQ(36u, sprite->getImage().size.height);
|
||||
EXPECT_EQ(2, sprite->getPixelRatio());
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation2x.png"),
|
||||
sprite->image);
|
||||
sprite->getImage());
|
||||
}
|
||||
|
||||
TEST(Sprite, SpriteImageCreation1_5x) {
|
||||
@@ -176,22 +176,22 @@ TEST(Sprite, SpriteImageCreation1_5x) {
|
||||
ASSERT_TRUE(sprite.get());
|
||||
EXPECT_EQ(24, sprite->getWidth());
|
||||
EXPECT_EQ(24, sprite->getHeight());
|
||||
EXPECT_EQ(36u, sprite->image.size.width);
|
||||
EXPECT_EQ(36u, sprite->image.size.height);
|
||||
EXPECT_EQ(1.5, sprite->pixelRatio);
|
||||
EXPECT_EQ(36u, sprite->getImage().size.width);
|
||||
EXPECT_EQ(36u, sprite->getImage().size.height);
|
||||
EXPECT_EQ(1.5, sprite->getPixelRatio());
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1_5x-museum.png"),
|
||||
sprite->image);
|
||||
sprite->getImage());
|
||||
|
||||
// "hospital_icon":{"x":314,"y":518,"width":36,"height":36,"pixelRatio":2,"sdf":false}
|
||||
const auto sprite2 = createStyleImage(image_2x, 314, 518, 35, 35, 1.5, false);
|
||||
ASSERT_TRUE(sprite2.get());
|
||||
EXPECT_EQ(float(35 / 1.5), sprite2->getWidth());
|
||||
EXPECT_EQ(float(35 / 1.5), sprite2->getHeight());
|
||||
EXPECT_EQ(35u, sprite2->image.size.width);
|
||||
EXPECT_EQ(35u, sprite2->image.size.height);
|
||||
EXPECT_EQ(1.5, sprite2->pixelRatio);
|
||||
EXPECT_EQ(35u, sprite2->getImage().size.width);
|
||||
EXPECT_EQ(35u, sprite2->getImage().size.height);
|
||||
EXPECT_EQ(1.5, sprite2->getPixelRatio());
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1_5x-hospital.png"),
|
||||
sprite2->image);
|
||||
sprite2->getImage());
|
||||
}
|
||||
|
||||
TEST(Sprite, SpriteParsing) {
|
||||
@@ -283,10 +283,10 @@ TEST(Sprite, SpriteParsing) {
|
||||
auto& sprite = images.find("generic-metro")->second;
|
||||
EXPECT_EQ(18, sprite->getWidth());
|
||||
EXPECT_EQ(18, sprite->getHeight());
|
||||
EXPECT_EQ(18u, sprite->image.size.width);
|
||||
EXPECT_EQ(18u, sprite->image.size.height);
|
||||
EXPECT_EQ(1, sprite->pixelRatio);
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteparsing.png"), sprite->image);
|
||||
EXPECT_EQ(18u, sprite->getImage().size.width);
|
||||
EXPECT_EQ(18u, sprite->getImage().size.height);
|
||||
EXPECT_EQ(1, sprite->getPixelRatio());
|
||||
EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteparsing.png"), sprite->getImage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ public:
|
||||
if (glyphsError) glyphsError(fontStack, glyphRange, error);
|
||||
}
|
||||
|
||||
void onSpriteLoaded() override {
|
||||
if (spriteLoaded) spriteLoaded();
|
||||
void onSpriteLoaded(Images&& images) override {
|
||||
if (spriteLoaded) spriteLoaded(std::move(images));
|
||||
}
|
||||
|
||||
void onSpriteError(std::exception_ptr error) override {
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
std::function<void (const FontStack&, const GlyphRange&)> glyphsLoaded;
|
||||
std::function<void (const FontStack&, const GlyphRange&, std::exception_ptr)> glyphsError;
|
||||
std::function<void ()> spriteLoaded;
|
||||
std::function<void (Images&&)> spriteLoaded;
|
||||
std::function<void (std::exception_ptr)> spriteError;
|
||||
std::function<void (Source&)> sourceLoaded;
|
||||
std::function<void (Source&)> sourceChanged;
|
||||
|
||||
@@ -36,17 +36,17 @@ TEST(StyleImage, ZeroRatio) {
|
||||
TEST(StyleImage, Retina) {
|
||||
style::Image image(PremultipliedImage({ 32, 24 }), 2.0);
|
||||
EXPECT_EQ(16, image.getWidth());
|
||||
EXPECT_EQ(32u, image.image.size.width);
|
||||
EXPECT_EQ(32u, image.getImage().size.width);
|
||||
EXPECT_EQ(12, image.getHeight());
|
||||
EXPECT_EQ(24u, image.image.size.height);
|
||||
EXPECT_EQ(2, image.pixelRatio);
|
||||
EXPECT_EQ(24u, image.getImage().size.height);
|
||||
EXPECT_EQ(2, image.getPixelRatio());
|
||||
}
|
||||
|
||||
TEST(StyleImage, FractionalRatio) {
|
||||
style::Image image(PremultipliedImage({ 20, 12 }), 1.5);
|
||||
EXPECT_EQ(float(20.0 / 1.5), image.getWidth());
|
||||
EXPECT_EQ(20u, image.image.size.width);
|
||||
EXPECT_EQ(20u, image.getImage().size.width);
|
||||
EXPECT_EQ(float(12.0 / 1.5), image.getHeight());
|
||||
EXPECT_EQ(12u, image.image.size.height);
|
||||
EXPECT_EQ(1.5, image.pixelRatio);
|
||||
EXPECT_EQ(12u, image.getImage().size.height);
|
||||
EXPECT_EQ(1.5, image.getPixelRatio());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user