diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2019-12-09 15:09:42 +0100 |
---|---|---|
committer | Alexander Shalamov <alexander.shalamov@mapbox.com> | 2020-01-15 15:02:11 +0200 |
commit | 62b0f4cde289e5918c41d5b69b0a03baa6821862 (patch) | |
tree | 2042319da9abd756564dcbba7812d4868a177800 | |
parent | 4b171cccf1c4012f8962b022f86c4ac8d73f09df (diff) | |
download | qtlocation-mapboxgl-62b0f4cde289e5918c41d5b69b0a03baa6821862.tar.gz |
[core] Add stretches and content to style::Image
-rw-r--r-- | include/mbgl/style/image.hpp | 46 | ||||
-rw-r--r-- | include/mbgl/util/exception.hpp | 6 | ||||
-rw-r--r-- | platform/android/src/map/image.cpp | 2 | ||||
-rw-r--r-- | platform/android/src/native_map_view.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/sprite/sprite_parser.cpp | 2 | ||||
-rw-r--r-- | src/mbgl/style/image.cpp | 23 | ||||
-rw-r--r-- | src/mbgl/style/image_impl.cpp | 57 | ||||
-rw-r--r-- | src/mbgl/style/image_impl.hpp | 15 | ||||
-rw-r--r-- | test/sprite/sprite_parser.test.cpp | 34 | ||||
-rw-r--r-- | test/style/style_image.test.cpp | 85 |
10 files changed, 229 insertions, 43 deletions
diff --git a/include/mbgl/style/image.hpp b/include/mbgl/style/image.hpp index ff3bfedf46..bbea081c71 100644 --- a/include/mbgl/style/image.hpp +++ b/include/mbgl/style/image.hpp @@ -2,15 +2,52 @@ #include <mbgl/util/image.hpp> #include <mbgl/util/immutable.hpp> +#include <mbgl/util/optional.hpp> #include <string> +#include <utility> +#include <vector> namespace mbgl { namespace style { +using ImageStretch = std::pair<float, float>; +using ImageStretches = std::vector<ImageStretch>; + +class ImageContent { +public: + float left; + float top; + float right; + float bottom; + + bool operator==(const ImageContent& rhs) const { + return left == rhs.left && top == rhs.top && right == rhs.right && bottom == rhs.bottom; + } +}; + class Image { public: - Image(std::string id, PremultipliedImage&&, float pixelRatio, bool sdf = false); + Image(std::string id, + PremultipliedImage&&, + float pixelRatio, + bool sdf, + ImageStretches stretchX = {}, + ImageStretches stretchY = {}, + optional<ImageContent> content = nullopt); + Image(std::string id, + PremultipliedImage&& image, + float pixelRatio, + ImageStretches stretchX = {}, + ImageStretches stretchY = {}, + optional<ImageContent> content = nullopt) + : Image(std::move(id), + std::move(image), + pixelRatio, + false, + std::move(stretchX), + std::move(stretchY), + std::move(content)) {} Image(const Image&); std::string getID() const; @@ -23,6 +60,13 @@ public: // Whether this image should be interpreted as a signed distance field icon. bool isSdf() const; + // Stretch areas of this image. + const ImageStretches& getStretchX() const; + const ImageStretches& getStretchY() const; + + // The space where text can be fit into this image. + const ImageContent& getContent() const; + class Impl; Immutable<Impl> baseImpl; }; diff --git a/include/mbgl/util/exception.hpp b/include/mbgl/util/exception.hpp index a9804e96c5..f0d7c64f42 100644 --- a/include/mbgl/util/exception.hpp +++ b/include/mbgl/util/exception.hpp @@ -10,9 +10,9 @@ struct Exception : std::runtime_error { Exception(const std::string &msg) : std::runtime_error(msg) {} }; -struct SpriteImageException : Exception { - SpriteImageException(const char *msg) : Exception(msg) {} - SpriteImageException(const std::string &msg) : Exception(msg) {} +struct StyleImageException : Exception { + StyleImageException(const char *msg) : Exception(msg) {} + StyleImageException(const std::string &msg) : Exception(msg) {} }; struct MisuseException : Exception { diff --git a/platform/android/src/map/image.cpp b/platform/android/src/map/image.cpp index a91cc938ed..10c32d2577 100644 --- a/platform/android/src/map/image.cpp +++ b/platform/android/src/map/image.cpp @@ -26,7 +26,7 @@ mbgl::style::Image Image::getImage(jni::JNIEnv& env, const jni::Object<Image>& i mbgl::PremultipliedImage premultipliedImage({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) }); if (premultipliedImage.bytes() != uint32_t(size)) { - throw mbgl::util::SpriteImageException("Sprite image pixel count mismatch"); + throw mbgl::util::StyleImageException("Image pixel count mismatch"); } jni::GetArrayRegion(env, *pixels, 0, size, reinterpret_cast<jbyte*>(premultipliedImage.data.get())); diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index d7351c7677..920779c18f 100644 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -763,7 +763,7 @@ void NativeMapView::addAnnotationIcon(JNIEnv& env, const jni::String& symbol, ji mbgl::PremultipliedImage premultipliedImage({ static_cast<uint32_t>(w), static_cast<uint32_t>(h) }); if (premultipliedImage.bytes() != uint32_t(size)) { - throw mbgl::util::SpriteImageException("Sprite image pixel count mismatch"); + throw mbgl::util::StyleImageException("Annotation icon image pixel count mismatch"); } jni::GetArrayRegion(env, *jpixels, 0, size, reinterpret_cast<jbyte*>(premultipliedImage.data.get())); diff --git a/src/mbgl/sprite/sprite_parser.cpp b/src/mbgl/sprite/sprite_parser.cpp index 99e2b0c8ca..f141037bc7 100644 --- a/src/mbgl/sprite/sprite_parser.cpp +++ b/src/mbgl/sprite/sprite_parser.cpp @@ -25,7 +25,7 @@ std::unique_ptr<style::Image> createStyleImage(const std::string& id, ratio <= 0 || ratio > 10 || srcX >= image.size.width || srcY >= image.size.height || srcX + width > image.size.width || srcY + height > image.size.height) { - Log::Error(Event::Sprite, "Can't create sprite with invalid metrics: %ux%u@%u,%u in %ux%u@%sx sprite", + Log::Error(Event::Sprite, "Can't create image with invalid metrics: %ux%u@%u,%u in %ux%u@%sx sprite", width, height, srcX, srcY, image.size.width, image.size.height, util::toString(ratio).c_str()); diff --git a/src/mbgl/style/image.cpp b/src/mbgl/style/image.cpp index 1747de5fcc..269f5f9d1a 100644 --- a/src/mbgl/style/image.cpp +++ b/src/mbgl/style/image.cpp @@ -6,11 +6,14 @@ namespace mbgl { namespace style { Image::Image(std::string id, - PremultipliedImage &&image, + PremultipliedImage&& image, const float pixelRatio, - bool sdf) - : baseImpl(makeMutable<Impl>(std::move(id), std::move(image), pixelRatio, sdf)) { -} + bool sdf, + ImageStretches stretchX, + ImageStretches stretchY, + optional<ImageContent> content) + : baseImpl(makeMutable<Impl>( + std::move(id), std::move(image), pixelRatio, sdf, std::move(stretchX), std::move(stretchY), content)) {} std::string Image::getID() const { return baseImpl->id; @@ -30,5 +33,17 @@ float Image::getPixelRatio() const { return baseImpl->pixelRatio; } +const ImageStretches& Image::getStretchX() const { + return baseImpl->stretchX; +} + +const ImageStretches& Image::getStretchY() const { + return baseImpl->stretchY; +} + +const ImageContent& Image::getContent() const { + return baseImpl->content; +} + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/image_impl.cpp b/src/mbgl/style/image_impl.cpp index ce327262e8..c43bb552ee 100644 --- a/src/mbgl/style/image_impl.cpp +++ b/src/mbgl/style/image_impl.cpp @@ -4,19 +4,60 @@ namespace mbgl { namespace style { +namespace { + +bool validateStretch(const ImageStretches& stretches, const float size) { + if (stretches.empty()) { + return true; + } + float last = 0; + for (auto& part : stretches) { + if (part.first < last || part.second < part.first || size < part.second) { + return false; + } + last = part.second; + } + return true; +} + +bool validateContent(const ImageContent& content, const Size& size) { + if (content.left < 0 || size.width < content.left) return false; + if (content.top < 0 || size.height < content.top) return false; + if (content.right < 0 || size.width < content.right) return false; + if (content.bottom < 0 || size.height < content.bottom) return false; + if (content.right < content.left) return false; + if (content.bottom < content.top) return false; + return true; +} + +} // namespace + Image::Impl::Impl(std::string id_, PremultipliedImage&& image_, const float pixelRatio_, - bool sdf_) - : id(std::move(id_)), - image(std::move(image_)), - pixelRatio(pixelRatio_), - sdf(sdf_) { - + bool sdf_, + ImageStretches stretchX_, + ImageStretches stretchY_, + optional<ImageContent> content_) + : id(std::move(id_)), + image(std::move(image_)), + pixelRatio(pixelRatio_), + sdf(sdf_), + stretchX(std::move(stretchX_)), + stretchY(std::move(stretchY_)), + content(content_ + ? std::move(*content_) + : ImageContent{0, 0, static_cast<float>(image.size.width), static_cast<float>(image.size.height)}) { if (!image.valid()) { - throw util::SpriteImageException("Sprite image dimensions may not be zero"); + throw util::StyleImageException("dimensions may not be zero"); } else if (pixelRatio <= 0) { - throw util::SpriteImageException("Sprite pixelRatio may not be <= 0"); + throw util::StyleImageException("pixelRatio may not be <= 0"); + } else if (!validateStretch(stretchX, image.size.width)) { + throw util::StyleImageException("stretchX is out of bounds or overlapping"); + } else if (!validateStretch(stretchY, image.size.height)) { + throw util::StyleImageException("stretchY is out of bounds or overlapping"); + } else if (!validateContent(content, image.size)) { + throw util::StyleImageException("content area is invalid"); } } diff --git a/src/mbgl/style/image_impl.hpp b/src/mbgl/style/image_impl.hpp index b2decbf781..683cd3347b 100644 --- a/src/mbgl/style/image_impl.hpp +++ b/src/mbgl/style/image_impl.hpp @@ -11,7 +11,13 @@ namespace style { class Image::Impl { public: - Impl(std::string id, PremultipliedImage&&, float pixelRatio, bool sdf = false); + Impl(std::string id, + PremultipliedImage&&, + float pixelRatio, + bool sdf = false, + ImageStretches stretchX = {}, + ImageStretches stretchY = {}, + optional<ImageContent> content = nullopt); const std::string id; @@ -22,6 +28,13 @@ public: // Whether this image should be interpreted as a signed distance field icon. const bool sdf; + + // Stretch areas of this image. + const ImageStretches stretchX; + const ImageStretches stretchY; + + // The space where text can be fit into this image. + const ImageContent content; }; } // namespace style diff --git a/test/sprite/sprite_parser.test.cpp b/test/sprite/sprite_parser.test.cpp index 529e4c75e8..1d6e006980 100644 --- a/test/sprite/sprite_parser.test.cpp +++ b/test/sprite/sprite_parser.test.cpp @@ -45,91 +45,91 @@ TEST(Sprite, SpriteImageCreationInvalid) { EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 0x16@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 0x16@0,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 16x0@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 16x0@0,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 4294967295x16@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 4294967295x16@0,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 16x4294967295@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 16x4294967295@0,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 1x1@0,0 in 200x299@0x sprite", + "Can't create image with invalid metrics: 1x1@0,0 in 200x299@0x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 1x1@0,0 in 200x299@-1x sprite", + "Can't create image with invalid metrics: 1x1@0,0 in 200x299@-1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 1x1@0,0 in 200x299@23x sprite", + "Can't create image with invalid metrics: 1x1@0,0 in 200x299@23x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 2048x16@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 2048x16@0,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 16x1025@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 16x1025@0,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 16x16@4294967295,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 16x16@4294967295,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 16x16@0,4294967295 in 200x299@1x sprite", + "Can't create image with invalid metrics: 16x16@0,4294967295 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 201x16@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 201x16@0,0 in 200x299@1x sprite", })); EXPECT_EQ(1u, log.count({ EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 16x300@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 16x300@0,0 in 200x299@1x sprite", })); } @@ -307,7 +307,7 @@ TEST(Sprite, SpriteParsingEmptyImage) { EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 0x0@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 0x0@0,0 in 200x299@1x sprite", })); } @@ -340,7 +340,7 @@ TEST(Sprite, SpriteParsingWidthTooBig) { EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 0x32@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 0x32@0,0 in 200x299@1x sprite", })); } @@ -363,7 +363,7 @@ TEST(Sprite, SpriteParsingNegativeWidth) { EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 0x32@0,0 in 200x299@1x sprite", + "Can't create image with invalid metrics: 0x32@0,0 in 200x299@1x sprite", })); } @@ -380,6 +380,6 @@ TEST(Sprite, SpriteParsingNullRatio) { EventSeverity::Error, Event::Sprite, int64_t(-1), - "Can't create sprite with invalid metrics: 32x32@0,0 in 200x299@0x sprite", + "Can't create image with invalid metrics: 32x32@0,0 in 200x299@0x sprite", })); } diff --git a/test/style/style_image.test.cpp b/test/style/style_image.test.cpp index e49bf37582..75dafb9938 100644 --- a/test/style/style_image.test.cpp +++ b/test/style/style_image.test.cpp @@ -10,8 +10,8 @@ TEST(StyleImage, ZeroWidth) { try { style::Image("test", PremultipliedImage({ 0, 16 }), 2.0); FAIL() << "Expected exception"; - } catch (util::SpriteImageException& ex) { - EXPECT_STREQ("Sprite image dimensions may not be zero", ex.what()); + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("dimensions may not be zero", ex.what()); } } @@ -19,8 +19,8 @@ TEST(StyleImage, ZeroHeight) { try { style::Image("test", PremultipliedImage({ 16, 0 }), 2.0); FAIL() << "Expected exception"; - } catch (util::SpriteImageException& ex) { - EXPECT_STREQ("Sprite image dimensions may not be zero", ex.what()); + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("dimensions may not be zero", ex.what()); } } @@ -28,8 +28,8 @@ TEST(StyleImage, ZeroRatio) { try { style::Image("test", PremultipliedImage({ 16, 16 }), 0.0); FAIL() << "Expected exception"; - } catch (util::SpriteImageException& ex) { - EXPECT_STREQ("Sprite pixelRatio may not be <= 0", ex.what()); + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("pixelRatio may not be <= 0", ex.what()); } } @@ -46,3 +46,76 @@ TEST(StyleImage, FractionalRatio) { EXPECT_EQ(12u, image.getImage().size.height); EXPECT_EQ(1.5, image.getPixelRatio()); } + +TEST(StyleImage, InvalidStretchX) { + // out of left bound + try { + style::Image("test", PremultipliedImage({16, 16}), 1, {{-1, 3}}); + FAIL() << "Expected exception"; + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("stretchX is out of bounds or overlapping", ex.what()); + } + + // overlapping + try { + style::Image("test", PremultipliedImage({16, 16}), 1, {{0, 3}, {2., 4.}}); + FAIL() << "Expected exception"; + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("stretchX is out of bounds or overlapping", ex.what()); + } +} + +TEST(StyleImage, InvalidStretchY) { + // out of bottom bound + try { + style::Image("test", PremultipliedImage({16, 16}), 1, {}, {{14, 20}}); + FAIL() << "Expected exception"; + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("stretchX is out of bounds or overlapping", ex.what()); + } + + // must be sorted + try { + style::Image("test", PremultipliedImage({16, 16}), 1, {}, {{4, 8}, {2, 3}}); + FAIL() << "Expected exception"; + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("stretchX is out of bounds or overlapping", ex.what()); + } +} + +TEST(StyleImage, InvalidContent) { + // bottom right out of bounds + try { + style::Image("test", PremultipliedImage({16, 16}), 1, {}, {}, style::ImageContent{0, 0, 24, 28}); + FAIL() << "Expected exception"; + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("content area is invalid", ex.what()); + } + + // bottom right < top left + try { + style::Image("test", PremultipliedImage({16, 16}), 1, {}, {}, style::ImageContent{14, 14, 12, 10}); + FAIL() << "Expected exception"; + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("content area is invalid", ex.what()); + } + + // top left out of bounds + try { + style::Image("test", PremultipliedImage({16, 16}), 1, {}, {}, style::ImageContent{-2, -8, 12, 10}); + FAIL() << "Expected exception"; + } catch (util::StyleImageException& ex) { + EXPECT_STREQ("content area is invalid", ex.what()); + } +} + +TEST(StyleImage, StretchContent) { + style::Image image( + "test", PremultipliedImage({16, 16}), 1, {{2, 14}}, {{0, 4}, {12, 16}}, style::ImageContent{2, 2, 14, 14}); + EXPECT_EQ(16u, image.getImage().size.width); + EXPECT_EQ(16u, image.getImage().size.height); + EXPECT_EQ(1.0, image.getPixelRatio()); + EXPECT_EQ((style::ImageStretches{{2, 14}}), image.getStretchX()); + EXPECT_EQ((style::ImageStretches{{0, 4}, {12, 16}}), image.getStretchY()); + EXPECT_EQ((style::ImageContent{2, 2, 14, 14}), image.getContent()); +} |