From 1e350b7ea485117cadc413d4d41062cf3c3c43a1 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 5 Nov 2015 15:05:43 -0800 Subject: [core] Reorganize sprite related files --- test/sprite/custom_sprites.cpp | 61 ++++++++ test/sprite/sprite.cpp | 173 +++++++++++++++++++++++ test/sprite/sprite_atlas.cpp | 173 +++++++++++++++++++++++ test/sprite/sprite_image.cpp | 64 +++++++++ test/sprite/sprite_parser.cpp | 310 +++++++++++++++++++++++++++++++++++++++++ test/sprite/sprite_store.cpp | 143 +++++++++++++++++++ 6 files changed, 924 insertions(+) create mode 100644 test/sprite/custom_sprites.cpp create mode 100644 test/sprite/sprite.cpp create mode 100644 test/sprite/sprite_atlas.cpp create mode 100644 test/sprite/sprite_image.cpp create mode 100644 test/sprite/sprite_parser.cpp create mode 100644 test/sprite/sprite_store.cpp (limited to 'test/sprite') diff --git a/test/sprite/custom_sprites.cpp b/test/sprite/custom_sprites.cpp new file mode 100644 index 0000000000..98d173ea2d --- /dev/null +++ b/test/sprite/custom_sprites.cpp @@ -0,0 +1,61 @@ +#include "../fixtures/util.hpp" +#include "../fixtures/fixture_log_observer.hpp" + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +using namespace mbgl; + +TEST(Sprite, CustomSpriteImages) { + FixtureLog log; + + auto display = std::make_shared(); + HeadlessView view(display, 1); + view.resize(256, 256); + DefaultFileSource fileSource(nullptr); + + const auto style = util::read_file("test/fixtures/headless/pois.json"); + + Map map(view, fileSource, MapMode::Still); + + map.setLatLngZoom(LatLng{ 52.499167, 13.418056 }, 15); + + map.setStyleJSON(style, ""); + map.setSprite("cafe", + std::make_shared(12, 12, 1, std::string(12 * 12 * 4, '\xFF'))); + std::promise> promise; + map.renderStill([&promise](std::exception_ptr error, std::unique_ptr image) { + if (error) { + promise.set_exception(error); + } else { + promise.set_value(std::move(image)); + } + }); + auto result = promise.get_future().get(); + ASSERT_EQ(256, result->width); + ASSERT_EQ(256, result->height); + + EXPECT_EQ( + 21u, + log.count({ + EventSeverity::Info, Event::Sprite, int64_t(-1), "Can't find sprite named 'bakery'", + })); + + // const size_t bytes = result->width * result->height * 4; + // const auto hash = test::crc64(reinterpret_cast(result->pixels.get()), bytes); + // EXPECT_EQ(0xC40A4BCD76AEC564u, hash) << std::hex << hash; + + // const std::string png = util::compress_png(result->width, result->height, + // result->pixels.get()); + // util::write_file("test/fixtures/headless/1.actual.png", png); +} diff --git a/test/sprite/sprite.cpp b/test/sprite/sprite.cpp new file mode 100644 index 0000000000..7575cb3c9a --- /dev/null +++ b/test/sprite/sprite.cpp @@ -0,0 +1,173 @@ +#include "../fixtures/fixture_log_observer.hpp" +#include "../fixtures/mock_file_source.hpp" +#include "../fixtures/util.hpp" + +#include +#include +#include + +using namespace mbgl; + +using SpriteTestCallback = std::function; + +struct SpriteParams { + const std::string baseUrl; + const float pixelRatio; +}; + +class SpriteThread : public Sprite::Observer { +public: + SpriteThread(FileSource* fileSource, SpriteTestCallback callback) : callback_(callback) { + util::ThreadContext::setFileSource(fileSource); + } + + void loadSprite(const SpriteParams& params) { + sprite_.reset(new Sprite(params.baseUrl, params.pixelRatio)); + sprite_->setObserver(this); + } + + void unloadSprite() { + sprite_->setObserver(nullptr); + sprite_.reset(); + } + + void onSpriteLoaded(const Sprites& sprites) override { + callback_(sprite_.get(), sprites, nullptr); + } + + void onSpriteLoadingFailed(std::exception_ptr error) override { + callback_(sprite_.get(), Sprites(), error); + } + +private: + std::unique_ptr sprite_; + SpriteTestCallback callback_; +}; + +class SpriteTest : public testing::Test { +protected: + void runTest(const SpriteParams& params, FileSource* fileSource, SpriteTestCallback callback) { + util::RunLoop loop(uv_default_loop()); + + async_ = std::make_unique(loop.get(), [&] { loop.stop(); }); + async_->unref(); + + const util::ThreadContext context = {"Map", util::ThreadType::Map, util::ThreadPriority::Regular}; + + util::Thread tester(context, fileSource, callback); + tester.invoke(&SpriteThread::loadSprite, params); + + uv_run(loop.get(), UV_RUN_DEFAULT); + + tester.invoke(&SpriteThread::unloadSprite); + } + + void stopTest() { + async_->send(); + } + +private: + std::unique_ptr async_; +}; + +TEST_F(SpriteTest, LoadingSuccess) { + SpriteParams params = { + "test/fixtures/resources/sprite", + 1.0, + }; + + auto callback = [this, ¶ms](Sprite* sprite, const Sprites& sprites, std::exception_ptr error) { + ASSERT_TRUE(util::ThreadContext::currentlyOn(util::ThreadType::Map)); + + ASSERT_TRUE(error == nullptr); + + ASSERT_TRUE(!sprites.empty()); + + ASSERT_EQ(sprite->pixelRatio, params.pixelRatio); + ASSERT_NE(sprite->pixelRatio, 1.5); + ASSERT_NE(sprite->pixelRatio, 2.0); + + ASSERT_TRUE(sprite->isLoaded()); + + stopTest(); + }; + + MockFileSource fileSource(MockFileSource::Success, ""); + runTest(params, &fileSource, callback); +} + +TEST_F(SpriteTest, LoadingFail) { + SpriteParams params = { + "test/fixtures/resources/sprite", + 1.0, + }; + + auto callback = [this, ¶ms](Sprite* sprite, const Sprites&, std::exception_ptr error) { + ASSERT_TRUE(util::ThreadContext::currentlyOn(util::ThreadType::Map)); + + ASSERT_TRUE(error != nullptr); + + ASSERT_EQ(sprite->pixelRatio, params.pixelRatio); + ASSERT_NE(sprite->pixelRatio, 1.5); + ASSERT_NE(sprite->pixelRatio, 2.0); + + ASSERT_FALSE(sprite->isLoaded()); + + stopTest(); + }; + + MockFileSource fileSourceFailSpriteJSON(MockFileSource::RequestFail, "sprite.json"); + runTest(params, &fileSourceFailSpriteJSON, callback); + + MockFileSource fileSourceFailSpriteImage(MockFileSource::RequestFail, "sprite.png"); + runTest(params, &fileSourceFailSpriteImage, callback); + + MockFileSource fileSourceCorruptedSpriteJSON(MockFileSource::RequestWithCorruptedData, "sprite.json"); + runTest(params, &fileSourceCorruptedSpriteJSON, callback); + + MockFileSource fileSourceCorruptedSpriteImage(MockFileSource::RequestWithCorruptedData, "sprite.png"); + runTest(params, &fileSourceCorruptedSpriteImage, callback); +} + +TEST_F(SpriteTest, LoadingCancel) { + SpriteParams params = { + "test/fixtures/resources/sprite", + 1.0, + }; + + auto callback = [this](Sprite*, const Sprites&, std::exception_ptr) { + FAIL() << "Should never be called"; + }; + + MockFileSource fileSourceDelaySpriteJSON(MockFileSource::SuccessWithDelay, "sprite.json"); + fileSourceDelaySpriteJSON.setOnRequestDelayedCallback([this]{ + stopTest(); + }); + runTest(params, &fileSourceDelaySpriteJSON, callback); + + MockFileSource fileSourceDelaySpriteImage(MockFileSource::SuccessWithDelay, "sprite.png"); + fileSourceDelaySpriteImage.setOnRequestDelayedCallback([this]{ + stopTest(); + }); + runTest(params, &fileSourceDelaySpriteImage, callback); +} + +TEST_F(SpriteTest, InvalidURL) { + SpriteParams params = { + "foo bar", + 1.0, + }; + + auto callback = [this](Sprite* sprite, const Sprites&, std::exception_ptr error) { + ASSERT_TRUE(util::ThreadContext::currentlyOn(util::ThreadType::Map)); + + ASSERT_TRUE(error != nullptr); + + ASSERT_EQ(sprite->isLoaded(), false); + + stopTest(); + }; + + MockFileSource fileSource(MockFileSource::Success, ""); + runTest(params, &fileSource, callback); +} diff --git a/test/sprite/sprite_atlas.cpp b/test/sprite/sprite_atlas.cpp new file mode 100644 index 0000000000..554ef16da9 --- /dev/null +++ b/test/sprite/sprite_atlas.cpp @@ -0,0 +1,173 @@ +#include "../fixtures/util.hpp" +#include "../fixtures/fixture_log_observer.hpp" + +#include +#include +#include +#include +#include + +using namespace mbgl; + +TEST(Sprite, SpriteAtlas) { + FixtureLog log; + + auto spriteParseResult = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + util::read_file("test/fixtures/annotations/emerald.json")); + + SpriteStore store; + store.setSprites(spriteParseResult.get()); + + SpriteAtlas atlas(63, 112, 1, store); + + EXPECT_EQ(1.0f, atlas.getPixelRatio()); + EXPECT_EQ(63, atlas.getWidth()); + EXPECT_EQ(112, atlas.getHeight()); + EXPECT_EQ(63, atlas.getTextureWidth()); + EXPECT_EQ(112, atlas.getTextureHeight()); + + // Image hasn't been created yet. + EXPECT_TRUE(atlas.getData()); + + auto metro = atlas.getImage("metro", false); + EXPECT_EQ(0, metro.pos.x); + EXPECT_EQ(0, metro.pos.y); + EXPECT_EQ(20, metro.pos.w); + EXPECT_EQ(20, metro.pos.h); + EXPECT_EQ(18, metro.pos.originalW); + EXPECT_EQ(18, metro.pos.originalH); + EXPECT_EQ(18, metro.texture->width); + EXPECT_EQ(18, metro.texture->height); + EXPECT_EQ(18, metro.texture->pixelWidth); + EXPECT_EQ(18, metro.texture->pixelHeight); + EXPECT_EQ(1.0f, metro.texture->pixelRatio); + + auto pos = atlas.getPosition("metro", false); + EXPECT_DOUBLE_EQ(20, pos.size[0]); + EXPECT_DOUBLE_EQ(20, pos.size[1]); + EXPECT_DOUBLE_EQ(1.0f / 63, pos.tl[0]); + EXPECT_DOUBLE_EQ(1.0f / 112, pos.tl[1]); + EXPECT_DOUBLE_EQ(21.0f / 63, pos.br[0]); + EXPECT_DOUBLE_EQ(21.0f / 112, pos.br[1]); + + + auto missing = atlas.getImage("doesnotexist", false); + EXPECT_FALSE(missing.pos.hasArea()); + EXPECT_EQ(0, missing.pos.x); + EXPECT_EQ(0, missing.pos.y); + EXPECT_EQ(0, missing.pos.w); + EXPECT_EQ(0, missing.pos.h); + EXPECT_EQ(0, missing.pos.originalW); + EXPECT_EQ(0, missing.pos.originalH); + EXPECT_FALSE(missing.texture); + + EXPECT_EQ(1u, log.count({ + EventSeverity::Info, + Event::Sprite, + int64_t(-1), + "Can't find sprite named 'doesnotexist'", + })); + + // Different wrapping mode produces different image. + auto metro2 = atlas.getImage("metro", true); + EXPECT_EQ(20, metro2.pos.x); + EXPECT_EQ(0, metro2.pos.y); + EXPECT_EQ(20, metro2.pos.w); + EXPECT_EQ(20, metro2.pos.h); + EXPECT_EQ(18, metro2.pos.originalW); + EXPECT_EQ(18, metro2.pos.originalH); + + const size_t bytes = atlas.getTextureWidth() * atlas.getTextureHeight() * 4; + const auto hash = test::crc64(reinterpret_cast(atlas.getData()), bytes); + EXPECT_EQ(0x9875FC0595489A9Fu, hash) << std::hex << hash; + + // util::write_file( + // "test/fixtures/annotations/atlas1.png", + // util::compress_png(atlas.getTextureWidth(), atlas.getTextureHeight(), atlas.getData())); +} + +TEST(Sprite, SpriteAtlasSize) { + auto spriteParseResult = parseSprite(util::read_file("test/fixtures/annotations/emerald.png"), + util::read_file("test/fixtures/annotations/emerald.json")); + + SpriteStore store; + store.setSprites(spriteParseResult.get()); + + SpriteAtlas atlas(63, 112, 1.4, store); + + EXPECT_DOUBLE_EQ(1.4f, atlas.getPixelRatio()); + EXPECT_EQ(63, atlas.getWidth()); + EXPECT_EQ(112, atlas.getHeight()); + EXPECT_EQ(89, atlas.getTextureWidth()); + EXPECT_EQ(157, atlas.getTextureHeight()); + + auto metro = atlas.getImage("metro", false); + EXPECT_EQ(0, metro.pos.x); + EXPECT_EQ(0, metro.pos.y); + EXPECT_EQ(20, metro.pos.w); + EXPECT_EQ(20, metro.pos.h); + EXPECT_EQ(18, metro.pos.originalW); + EXPECT_EQ(18, metro.pos.originalH); + EXPECT_EQ(18, metro.texture->width); + EXPECT_EQ(18, metro.texture->height); + EXPECT_EQ(18, metro.texture->pixelWidth); + EXPECT_EQ(18, metro.texture->pixelHeight); + EXPECT_EQ(1.0f, metro.texture->pixelRatio); + + const size_t bytes = atlas.getTextureWidth() * atlas.getTextureHeight() * 4; + const auto hash = test::crc64(reinterpret_cast(atlas.getData()), bytes); + EXPECT_EQ(0x2CDDA7DBB04D116Du, hash) << std::hex << hash; + + // util::write_file( + // "test/fixtures/annotations/atlas2.png", + // util::compress_png(atlas.getTextureWidth(), atlas.getTextureHeight(), atlas.getData())); +} + +TEST(Sprite, SpriteAtlasUpdates) { + SpriteStore store; + + SpriteAtlas atlas(32, 32, 1, store); + + EXPECT_EQ(1.0f, atlas.getPixelRatio()); + EXPECT_EQ(32, atlas.getWidth()); + EXPECT_EQ(32, atlas.getHeight()); + EXPECT_EQ(32, atlas.getTextureWidth()); + EXPECT_EQ(32, atlas.getTextureHeight()); + + store.setSprite("one", std::make_shared(16, 12, 1, std::string(16 * 12 * 4, '\x00'))); + auto one = atlas.getImage("one", false); + EXPECT_EQ(0, one.pos.x); + EXPECT_EQ(0, one.pos.y); + EXPECT_EQ(20, one.pos.w); + EXPECT_EQ(16, one.pos.h); + EXPECT_EQ(16, one.pos.originalW); + EXPECT_EQ(12, one.pos.originalH); + EXPECT_EQ(16, one.texture->width); + EXPECT_EQ(12, one.texture->height); + EXPECT_EQ(16, one.texture->pixelWidth); + EXPECT_EQ(12, one.texture->pixelHeight); + EXPECT_EQ(1.0f, one.texture->pixelRatio); + + const size_t bytes = atlas.getTextureWidth() * atlas.getTextureHeight() * 4; + const auto hash = test::crc64(reinterpret_cast(atlas.getData()), bytes); + EXPECT_EQ(0x0000000000000000u, hash) << std::hex << hash; + + // Update sprite + auto newSprite = std::make_shared(16, 12, 1, std::string(16 * 12 * 4, '\xFF')); + store.setSprite("one", newSprite); + ASSERT_EQ(newSprite, store.getSprite("one")); + + // Atlas texture hasn't changed yet. + const auto hash2 = test::crc64(reinterpret_cast(atlas.getData()), bytes); + EXPECT_EQ(0x0000000000000000u, hash2) << std::hex << hash2; + + atlas.updateDirty(); + + // Now the atlas texture has changed. + const auto hash3 = test::crc64(reinterpret_cast(atlas.getData()), bytes); + EXPECT_EQ(0x4E6D4900CD2D9149u, hash3) << std::hex << hash3; + + // util::write_file( + // "test/fixtures/annotations/atlas3.png", + // util::compress_png(atlas.getTextureWidth(), atlas.getTextureHeight(), atlas.getData())); +} diff --git a/test/sprite/sprite_image.cpp b/test/sprite/sprite_image.cpp new file mode 100644 index 0000000000..8bc88fcc84 --- /dev/null +++ b/test/sprite/sprite_image.cpp @@ -0,0 +1,64 @@ +#include "../fixtures/util.hpp" + +#include +#include + +using namespace mbgl; + +TEST(Sprite, SpriteImageZeroWidth) { + try { + SpriteImage(0, 16, 2, ""); + FAIL() << "Expected exception"; + } catch (util::SpriteImageException& ex) { + EXPECT_STREQ("Sprite image dimensions may not be zero", ex.what()); + } +} + +TEST(Sprite, SpriteImageZeroHeight) { + try { + SpriteImage(16, 0, 2, ""); + FAIL() << "Expected exception"; + } catch (util::SpriteImageException& ex) { + EXPECT_STREQ("Sprite image dimensions may not be zero", ex.what()); + } +} + +TEST(Sprite, SpriteImageZeroRatio) { + try { + SpriteImage(16, 16, 0, ""); + FAIL() << "Expected exception"; + } catch (util::SpriteImageException& ex) { + EXPECT_STREQ("Sprite image dimensions may not be zero", ex.what()); + } +} + +TEST(Sprite, SpriteImageMismatchedData) { + try { + SpriteImage(16, 16, 2, ""); + FAIL() << "Expected exception"; + } catch (util::SpriteImageException& ex) { + EXPECT_STREQ("Sprite image pixel count mismatch", ex.what()); + } +} + +TEST(Sprite, SpriteImage) { + std::string pixels(32 * 24 * 4, '\0'); + SpriteImage sprite(16, 12, 2, std::move(pixels)); + EXPECT_EQ(16, sprite.width); + EXPECT_EQ(32, sprite.pixelWidth); + EXPECT_EQ(12, sprite.height); + EXPECT_EQ(24, sprite.pixelHeight); + EXPECT_EQ(2, sprite.pixelRatio); + EXPECT_EQ(32u * 24 * 4, sprite.data.size()); +} + +TEST(Sprite, SpriteImageFractionalRatio) { + std::string pixels(20 * 12 * 4, '\0'); + SpriteImage sprite(13, 8, 1.5, std::move(pixels)); + EXPECT_EQ(13, sprite.width); + EXPECT_EQ(20, sprite.pixelWidth); + EXPECT_EQ(8, sprite.height); + EXPECT_EQ(12, sprite.pixelHeight); + EXPECT_EQ(1.5, sprite.pixelRatio); + EXPECT_EQ(20u * 12 * 4, sprite.data.size()); +} diff --git a/test/sprite/sprite_parser.cpp b/test/sprite/sprite_parser.cpp new file mode 100644 index 0000000000..c2e4df3c58 --- /dev/null +++ b/test/sprite/sprite_parser.cpp @@ -0,0 +1,310 @@ +#include "../fixtures/util.hpp" +#include "../fixtures/fixture_log_observer.hpp" + +#include +#include +#include +#include + +#include + +using namespace mbgl; + +TEST(Sprite, SpriteImageCreationInvalid) { + FixtureLog log; + + const util::Image image_1x(util::read_file("test/fixtures/annotations/emerald.png")); + ASSERT_TRUE(image_1x); + ASSERT_EQ(200u, image_1x.getWidth()); + ASSERT_EQ(299u, image_1x.getHeight()); + + ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 0, 16, 1, false)); // width == 0 + ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 16, 0, 1, false)); // height == 0 + ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 1, 1, 0, false)); // ratio == 0 + ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 1, 1, 23, false)); // ratio too large + ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 2048, 16, 1, false)); // too wide + ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 16, 1025, 1, false)); // too tall + + EXPECT_EQ(6u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Can't create sprite with invalid metrics", + })); +} + +TEST(Sprite, SpriteImageCreation1x) { + const util::Image image_1x(util::read_file("test/fixtures/annotations/emerald.png")); + ASSERT_TRUE(image_1x); + ASSERT_EQ(200u, image_1x.getWidth()); + ASSERT_EQ(299u, image_1x.getHeight()); + + { // "museum_icon":{"x":177,"y":187,"width":18,"height":18,"pixelRatio":1,"sdf":false} + const auto sprite = createSpriteImage(image_1x, 177, 187, 18, 18, 1, false); + ASSERT_TRUE(sprite.get()); + EXPECT_EQ(18, sprite->width); + EXPECT_EQ(18, sprite->height); + EXPECT_EQ(18, sprite->pixelWidth); + EXPECT_EQ(18, sprite->pixelHeight); + EXPECT_EQ(1, sprite->pixelRatio); + EXPECT_EQ(0x7FCC5F263D1FFE16u, test::crc64(sprite->data)); + } + + { // outside image == blank + const auto sprite = createSpriteImage(image_1x, 200, 0, 16, 16, 1, false); + ASSERT_TRUE(sprite.get()); + EXPECT_EQ(16, sprite->width); + EXPECT_EQ(16, sprite->height); + EXPECT_EQ(16, sprite->pixelWidth); + EXPECT_EQ(16, sprite->pixelHeight); + EXPECT_EQ(1, sprite->pixelRatio); + EXPECT_EQ(0x0000000000000000u, test::crc64(sprite->data)) << std::hex << test::crc64(sprite->data); + } + + { // outside image == blank + const auto sprite = createSpriteImage(image_1x, 0, 300, 16, 16, 1, false); + ASSERT_TRUE(sprite.get()); + EXPECT_EQ(16, sprite->width); + EXPECT_EQ(16, sprite->height); + EXPECT_EQ(16, sprite->pixelWidth); + EXPECT_EQ(16, sprite->pixelHeight); + EXPECT_EQ(1, sprite->pixelRatio); + EXPECT_EQ(0x0000000000000000u, test::crc64(sprite->data)) << std::hex << test::crc64(sprite->data); + } +} + +TEST(Sprite, SpriteImageCreation2x) { + const util::Image image_2x(util::read_file("test/fixtures/annotations/emerald@2x.png")); + ASSERT_TRUE(image_2x); + + // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} + const auto sprite = createSpriteImage(image_2x, 354, 374, 36, 36, 2, false); + ASSERT_TRUE(sprite.get()); + EXPECT_EQ(18, sprite->width); + EXPECT_EQ(18, sprite->height); + EXPECT_EQ(36, sprite->pixelWidth); + EXPECT_EQ(36, sprite->pixelHeight); + EXPECT_EQ(2, sprite->pixelRatio); + EXPECT_EQ(0x85F345098DD4F9E3u, test::crc64(sprite->data)); +} + +TEST(Sprite, SpriteImageCreation1_5x) { + const util::Image image_2x(util::read_file("test/fixtures/annotations/emerald@2x.png")); + ASSERT_TRUE(image_2x); + + // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} + const auto sprite = createSpriteImage(image_2x, 354, 374, 36, 36, 1.5, false); + ASSERT_TRUE(sprite.get()); + EXPECT_EQ(24, sprite->width); + EXPECT_EQ(24, sprite->height); + EXPECT_EQ(36, sprite->pixelWidth); + EXPECT_EQ(36, sprite->pixelHeight); + EXPECT_EQ(1.5, sprite->pixelRatio); + EXPECT_EQ(0x85F345098DD4F9E3u, test::crc64(sprite->data)); + + // "hospital_icon":{"x":314,"y":518,"width":36,"height":36,"pixelRatio":2,"sdf":false} + const auto sprite2 = createSpriteImage(image_2x, 314, 518, 35, 35, 1.5, false); + ASSERT_TRUE(sprite2.get()); + EXPECT_EQ(24, sprite2->width); + EXPECT_EQ(24, sprite2->height); + EXPECT_EQ(36, sprite2->pixelWidth); + EXPECT_EQ(36, sprite2->pixelHeight); + EXPECT_EQ(1.5, sprite2->pixelRatio); + EXPECT_EQ(0x134A530C742DD141u, test::crc64(sprite2->data)); +} + +TEST(Sprite, SpriteParsing) { + const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); + const auto json_1x = util::read_file("test/fixtures/annotations/emerald.json"); + + const auto images = parseSprite(image_1x, json_1x).get(); + + std::set names; + std::transform(images.begin(), images.end(), std::inserter(names, names.begin()), + [](const auto& pair) { return pair.first; }); + + EXPECT_EQ(std::set({ "airfield_icon", + "airport_icon", + "background", + "cemetery_icon", + "college_icon", + "default_1", + "default_2", + "default_3", + "default_4", + "default_5", + "default_6", + "default_marker", + "dlr", + "dlr.london-overground.london-underground.national-rail", + "dlr.london-underground", + "dlr.london-underground.national-rail", + "dlr.national-rail", + "dot", + "embassy_icon", + "fire-station_icon", + "generic-metro", + "generic-rail", + "generic_icon", + "golf_icon", + "government_icon", + "grass_pattern", + "harbor_icon", + "hospital_icon", + "hospital_striped", + "interstate_1", + "interstate_2", + "interstate_3", + "library_icon", + "london-overground", + "london-overground.london-underground", + "london-overground.london-underground.national-rail", + "london-overground.national-rail", + "london-underground", + "london-underground.national-rail", + "marker_icon", + "metro", + "metro.rer", + "monument_icon", + "moscow-metro", + "museum_icon", + "national-rail", + "oneway_motorway", + "oneway_road", + "park_icon", + "police_icon", + "post_icon", + "prison_icon", + "religious-christian_icon", + "religious-jewish_icon", + "religious-muslim_icon", + "rer", + "rer.transilien", + "s-bahn", + "s-bahn.u-bahn", + "sand_noise", + "school_icon", + "school_striped", + "secondary_marker", + "u-bahn", + "us_highway_1", + "us_highway_2", + "us_highway_3", + "us_state_1", + "us_state_2", + "us_state_3", + "washington-metro", + "wiener-linien", + "zoo_icon" }), + names); + + { + auto sprite = images.find("generic-metro")->second; + EXPECT_EQ(18, sprite->width); + EXPECT_EQ(18, sprite->height); + EXPECT_EQ(18, sprite->pixelWidth); + EXPECT_EQ(18, sprite->pixelHeight); + EXPECT_EQ(1, sprite->pixelRatio); + EXPECT_EQ(0xFF56F5F48F707147u, test::crc64(sprite->data)); + } +} + +TEST(Sprite, SpriteParsingInvalidJSON) { + const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); + const auto json_1x = R"JSON({ "image": " })JSON"; + + const auto error = parseSprite(image_1x, json_1x).get(); + + EXPECT_EQ(error, + std::string("Failed to parse JSON: Missing a closing quotation mark in string. at offset 13")); +} + +TEST(Sprite, SpriteParsingEmptyImage) { + FixtureLog log; + + const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); + const auto json_1x = R"JSON({ "image": {} })JSON"; + + const auto images = parseSprite(image_1x, json_1x).get(); + EXPECT_EQ(0u, images.size()); + + EXPECT_EQ(1u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Can't create sprite with invalid metrics", + })); +} + +TEST(Sprite, SpriteParsingSimpleWidthHeight) { + FixtureLog log; + + const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); + const auto json_1x = R"JSON({ "image": { "width": 32, "height": 32 } })JSON"; + + const auto images = parseSprite(image_1x, json_1x).get(); + EXPECT_EQ(1u, images.size()); +} + +TEST(Sprite, SpriteParsingWidthTooBig) { + FixtureLog log; + + const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); + const auto json_1x = R"JSON({ "image": { "width": 65536, "height": 32 } })JSON"; + + const auto images = parseSprite(image_1x, json_1x).get(); + EXPECT_EQ(0u, images.size()); + + EXPECT_EQ(1u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Value of 'width' must be an integer between 0 and 65535", + })); + EXPECT_EQ(1u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Can't create sprite with invalid metrics", + })); +} + +TEST(Sprite, SpriteParsingNegativeWidth) { + FixtureLog log; + + const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); + const auto json_1x = R"JSON({ "image": { "width": -1, "height": 32 } })JSON"; + + const auto images = parseSprite(image_1x, json_1x).get(); + EXPECT_EQ(0u, images.size()); + + EXPECT_EQ(1u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Value of 'width' must be an integer between 0 and 65535", + })); + EXPECT_EQ(1u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Can't create sprite with invalid metrics", + })); +} + +TEST(Sprite, SpriteParsingNullRatio) { + FixtureLog log; + + const auto image_1x = util::read_file("test/fixtures/annotations/emerald.png"); + const auto json_1x = R"JSON({ "image": { "width": 32, "height": 32, "pixelRatio": 0 } })JSON"; + + const auto images = parseSprite(image_1x, json_1x).get(); + EXPECT_EQ(0u, images.size()); + + EXPECT_EQ(1u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Can't create sprite with invalid metrics", + })); +} diff --git a/test/sprite/sprite_store.cpp b/test/sprite/sprite_store.cpp new file mode 100644 index 0000000000..17fce62a2b --- /dev/null +++ b/test/sprite/sprite_store.cpp @@ -0,0 +1,143 @@ +#include "../fixtures/util.hpp" +#include "../fixtures/fixture_log_observer.hpp" + +#include + +using namespace mbgl; + +TEST(Sprite, SpriteStore) { + FixtureLog log; + + const auto sprite1 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + const auto sprite2 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + const auto sprite3 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + + using Sprites = std::map>; + SpriteStore store; + + // Adding single + store.setSprite("one", sprite1); + EXPECT_EQ(Sprites({ + { "one", sprite1 }, + }), + store.getDirty()); + EXPECT_EQ(Sprites(), store.getDirty()); + + // Adding multiple + store.setSprite("two", sprite2); + store.setSprite("three", sprite3); + EXPECT_EQ(Sprites({ + { "two", sprite2 }, { "three", sprite3 }, + }), + store.getDirty()); + EXPECT_EQ(Sprites(), store.getDirty()); + + // Removing + store.removeSprite("one"); + store.removeSprite("two"); + EXPECT_EQ(Sprites({ + { "one", nullptr }, { "two", nullptr }, + }), + store.getDirty()); + EXPECT_EQ(Sprites(), store.getDirty()); + + // Accessing + EXPECT_EQ(sprite3, store.getSprite("three")); + + EXPECT_TRUE(log.empty()); + + EXPECT_EQ(nullptr, store.getSprite("two")); + EXPECT_EQ(nullptr, store.getSprite("four")); + + EXPECT_EQ(1u, log.count({ + EventSeverity::Info, + Event::Sprite, + int64_t(-1), + "Can't find sprite named 'two'", + })); + EXPECT_EQ(1u, log.count({ + EventSeverity::Info, + Event::Sprite, + int64_t(-1), + "Can't find sprite named 'four'", + })); + + // Overwriting + store.setSprite("three", sprite1); + EXPECT_EQ(Sprites({ + { "three", sprite1 }, + }), + store.getDirty()); + EXPECT_EQ(Sprites(), store.getDirty()); +} + +TEST(Sprite, SpriteStoreOtherPixelRatio) { + FixtureLog log; + + const auto sprite1 = std::make_shared(8, 8, 1, std::string(8 * 8 * 4, '\0')); + + using Sprites = std::map>; + SpriteStore store; + + // Adding mismatched sprite image + store.setSprite("one", sprite1); + EXPECT_EQ(Sprites({ { "one", sprite1 } }), store.getDirty()); +} + +TEST(Sprite, SpriteStoreMultiple) { + const auto sprite1 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + const auto sprite2 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + + using Sprites = std::map>; + SpriteStore store; + + store.setSprites({ + { "one", sprite1 }, { "two", sprite2 }, + }); + EXPECT_EQ(Sprites({ + { "one", sprite1 }, { "two", sprite2 }, + }), + store.getDirty()); + EXPECT_EQ(Sprites(), store.getDirty()); +} + +TEST(Sprite, SpriteStoreReplace) { + FixtureLog log; + + const auto sprite1 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + const auto sprite2 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + + using Sprites = std::map>; + SpriteStore store; + + store.setSprite("sprite", sprite1); + EXPECT_EQ(sprite1, store.getSprite("sprite")); + store.setSprite("sprite", sprite2); + EXPECT_EQ(sprite2, store.getSprite("sprite")); + + EXPECT_EQ(Sprites({ { "sprite", sprite2 } }), store.getDirty()); +} + +TEST(Sprite, SpriteStoreReplaceWithDifferentDimensions) { + FixtureLog log; + + const auto sprite1 = std::make_shared(8, 8, 2, std::string(16 * 16 * 4, '\0')); + const auto sprite2 = std::make_shared(9, 9, 2, std::string(18 * 18 * 4, '\0')); + + using Sprites = std::map>; + SpriteStore store; + + store.setSprite("sprite", sprite1); + store.setSprite("sprite", sprite2); + + EXPECT_EQ(1u, log.count({ + EventSeverity::Warning, + Event::Sprite, + int64_t(-1), + "Can't change sprite dimensions for 'sprite'", + })); + + EXPECT_EQ(sprite1, store.getSprite("sprite")); + + EXPECT_EQ(Sprites({ { "sprite", sprite1 } }), store.getDirty()); +} -- cgit v1.2.1