[core] Split style image collection from SpriteAtlas

This commit is contained in:
Ivo van Dongen
2017-05-12 23:19:00 +03:00
committed by John Firebaugh
parent c80f3e9d29
commit cc9f040a2d
33 changed files with 677 additions and 413 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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];
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View 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

View 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

View File

@@ -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>

View File

@@ -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) {}

View File

@@ -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.
}

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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();
}

View 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'",
}));
}

View 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();
}

View File

@@ -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());
}
}

View File

@@ -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;

View File

@@ -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());
}