[core, ios, macos] Add image accessor to MGLStyle (#7096)

* [core] Add interface to get image from sprite atlas

* [tests] Add tests for Map::getImage

* [ios, macos] WIP: get MGLImage for name from style

* [ios, macos] Fixed -imageForName:

Convert from sprite images to platform images using the existing encodePNG() function, which is also used for printing. Allow -imageForName: to return nil without an assertion failure. Added a basic test.
This commit is contained in:
Roman Blum
2016-12-06 02:16:13 +01:00
committed by Minh Nguyễn
parent 5579e67804
commit 1045934d5e
19 changed files with 173 additions and 6 deletions

View File

@@ -171,6 +171,7 @@ public:
// Add image, bound to the style
void addImage(const std::string&, std::unique_ptr<const SpriteImage>);
void removeImage(const std::string&);
const SpriteImage* getImage(const std::string&);
// Defaults
std::string getStyleName() const;

View File

@@ -371,6 +371,23 @@ static const NSInteger MGLStyleDefaultVersion = 9;
#pragma mark Managing a Style’s Images
/**
Returns the image associated with the given name in the style.
@note Names and their associated images are not guaranteed to exist across
styles or different versions of the same style. Applications that use this
API must first set the style URL to an explicitly versioned style using a
convenience method like `+[MGLStyle outdoorsStyleURLWithVersion:]`,
`MGLMapView`'s “Style URL” inspectable in Interface Builder, or a manually
constructed `NSURL`. This approach also avoids image name changes that will
occur in the default style over time.
@param name The name associated with the image you want to obtain.
@return The image associated with the given name, or `nil` if no image is
associated with that name.
*/
- (nullable MGLImage *)imageForName:(NSString *)name;
/**
Adds or overrides an image used by the style’s layers.

View File

@@ -482,19 +482,39 @@ static NSURL *MGLStyleURL_emerald;
- (void)setImage:(MGLImage *)image forName:(NSString *)name
{
NSAssert(image, @"image is null");
NSAssert(name, @"name is null");
if (!image) {
[NSException raise:NSInvalidArgumentException
format:@"Cannot assign nil image to “%@”.", name];
}
if (!name) {
[NSException raise:NSInvalidArgumentException
format:@"Cannot assign image %@ to a nil name.", image];
}
self.mapView.mbglMap->addImage([name UTF8String], image.mgl_spriteImage);
}
- (void)removeImageForName:(NSString *)name
{
NSAssert(name, @"name is null");
if (!name) {
[NSException raise:NSInvalidArgumentException
format:@"Cannot remove image with nil name."];
}
self.mapView.mbglMap->removeImage([name UTF8String]);
}
- (MGLImage *)imageForName:(NSString *)name
{
if (!name) {
[NSException raise:NSInvalidArgumentException
format:@"Cannot get image with nil name."];
}
auto spriteImage = self.mapView.mbglMap->getImage([name UTF8String]);
return spriteImage ? [[MGLImage alloc] initWithMGLSpriteImage:spriteImage] : nil;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; name = %@, URL = %@>",

View File

@@ -17,6 +17,11 @@
#import <mbgl/util/default_styles.hpp>
#import <XCTest/XCTest.h>
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#endif
#import <objc/runtime.h>
@interface MGLStyleTests : XCTestCase
@@ -175,4 +180,23 @@
return styleHeader;
}
- (void)testImages {
NSString *imageName = @"TrackingLocationMask";
#if TARGET_OS_IPHONE
MGLImage *image = [MGLImage imageNamed:imageName
inBundle:[NSBundle bundleForClass:[self class]]
compatibleWithTraitCollection:nil];
#else
MGLImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName];
#endif
XCTAssertNotNil(image);
[self.mapView.style setImage:image forName:imageName];
MGLImage *styleImage = [self.mapView.style imageForName:imageName];
XCTAssertNotNil(styleImage);
XCTAssertEqual(image.size.width, styleImage.size.width);
XCTAssertEqual(image.size.height, styleImage.size.height);
}
@end

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,26 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "TrackingLocationMask.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "TrackingLocationMask@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "TrackingLocationMask@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

View File

@@ -1,5 +1,15 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
@@ -36,6 +46,16 @@
"filename" : "Icon-60@3x.png",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",

View File

@@ -188,6 +188,7 @@
DA25D5C01CCD9F8400607828 /* Root.plist in Resources */ = {isa = PBXBuildFile; fileRef = DA25D5BF1CCD9F8400607828 /* Root.plist */; };
DA25D5C61CCDA06800607828 /* Root.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA25D5C41CCDA06800607828 /* Root.strings */; };
DA25D5CD1CCDA11500607828 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DA25D5B91CCD9EDE00607828 /* Settings.bundle */; };
DA2784FC1DF02FF4001D5B8D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA2784FB1DF02FF4001D5B8D /* Media.xcassets */; };
DA27C24E1CBB3811000B0ECD /* GLKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA27C24D1CBB3811000B0ECD /* GLKit.framework */; };
DA27C24F1CBB4C11000B0ECD /* MGLAccountManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8847FF1CBAFA6200AB86E3 /* MGLAccountManager_Private.h */; };
DA2DBBCE1D51E80400D38FF9 /* MGLStyleLayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */; };
@@ -618,6 +619,7 @@
DA25D5B91CCD9EDE00607828 /* Settings.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Settings.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
DA25D5BF1CCD9F8400607828 /* Root.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Root.plist; sourceTree = "<group>"; };
DA25D5C51CCDA06800607828 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Root.strings; sourceTree = "<group>"; };
DA2784FB1DF02FF4001D5B8D /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = ../../darwin/test/Media.xcassets; sourceTree = "<group>"; };
DA27C24D1CBB3811000B0ECD /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; };
DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MGLStyleLayerTests.h; path = ../../darwin/test/MGLStyleLayerTests.h; sourceTree = "<group>"; };
DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLStyleLayerTests.m; path = ../../darwin/test/MGLStyleLayerTests.m; sourceTree = "<group>"; };
@@ -1065,6 +1067,7 @@
DA2E88601CC0382C00F24E7B /* MGLStyleTests.mm */,
DD58A4C51D822BD000E1F038 /* MGLExpressionTests.mm */,
DA2E88551CC036F400F24E7B /* Info.plist */,
DA2784FB1DF02FF4001D5B8D /* Media.xcassets */,
);
name = "SDK Tests";
path = test;
@@ -1805,6 +1808,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
DA2784FC1DF02FF4001D5B8D /* Media.xcassets in Resources */,
353BAEF71D646370009A8DA9 /* amsterdam.geojson in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -2,8 +2,14 @@
#include <mbgl/sprite/sprite_image.hpp>
NS_ASSUME_NONNULL_BEGIN
@interface UIImage (MGLAdditions)
- (nullable instancetype)initWithMGLSpriteImage:(const mbgl::SpriteImage *)spriteImage;
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage;
@end
NS_ASSUME_NONNULL_END

View File

@@ -2,6 +2,13 @@
@implementation UIImage (MGLAdditions)
- (nullable instancetype)initWithMGLSpriteImage:(const mbgl::SpriteImage *)spriteImage
{
std::string png = encodePNG(spriteImage->image);
NSData *data = [[NSData alloc] initWithBytes:png.data() length:png.size()];
return [self initWithData:data scale:spriteImage->pixelRatio];
}
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage
{
CGImageRef cgImage = self.CGImage;

View File

@@ -61,6 +61,7 @@
55D9B4B11D005D3900C1CCE2 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D9B4B01D005D3900C1CCE2 /* libz.tbd */; };
DA0CD58E1CF56F5800A5F5A5 /* MGLFeatureTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */; };
DA2207BC1DC076940002F84D /* MGLStyleValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2207BB1DC076940002F84D /* MGLStyleValueTests.swift */; };
DA2784FE1DF03060001D5B8D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA2784FD1DF03060001D5B8D /* Media.xcassets */; };
DA2DBBCB1D51E30A00D38FF9 /* MGLStyleLayerTests.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA2DBBCA1D51E30A00D38FF9 /* MGLStyleLayerTests.xib */; };
DA35A2A41CC9EB1A00E826B2 /* MGLCoordinateFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2A31CC9EB1A00E826B2 /* MGLCoordinateFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA35A2A61CC9EB2700E826B2 /* MGLCoordinateFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = DA35A2A51CC9EB2700E826B2 /* MGLCoordinateFormatter.m */; };
@@ -296,6 +297,7 @@
DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; };
DA2207BA1DC076930002F84D /* test-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "test-Bridging-Header.h"; sourceTree = "<group>"; };
DA2207BB1DC076940002F84D /* MGLStyleValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MGLStyleValueTests.swift; sourceTree = "<group>"; };
DA2784FD1DF03060001D5B8D /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Media.xcassets; path = ../../darwin/test/Media.xcassets; sourceTree = "<group>"; };
DA2DBBCA1D51E30A00D38FF9 /* MGLStyleLayerTests.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MGLStyleLayerTests.xib; sourceTree = "<group>"; };
DA35A2A31CC9EB1A00E826B2 /* MGLCoordinateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCoordinateFormatter.h; sourceTree = "<group>"; };
DA35A2A51CC9EB2700E826B2 /* MGLCoordinateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLCoordinateFormatter.m; sourceTree = "<group>"; };
@@ -842,6 +844,7 @@
DAE6C3CC1CC34BD800DB3429 /* MGLStyleTests.mm */,
DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */,
DAE6C33A1CC30DB200DB3429 /* Info.plist */,
DA2784FD1DF03060001D5B8D /* Media.xcassets */,
);
name = "SDK Tests";
path = test;
@@ -1150,6 +1153,7 @@
buildActionMask = 2147483647;
files = (
35724FC41D630502002A4AB4 /* amsterdam.geojson in Resources */,
DA2784FE1DF03060001D5B8D /* Media.xcassets in Resources */,
DA2DBBCB1D51E30A00D38FF9 /* MGLStyleLayerTests.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@@ -2,8 +2,14 @@
#include <mbgl/sprite/sprite_image.hpp>
NS_ASSUME_NONNULL_BEGIN
@interface NSImage (MGLAdditions)
- (nullable instancetype)initWithMGLSpriteImage:(const mbgl::SpriteImage *)spriteImage;
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage;
@end
NS_ASSUME_NONNULL_END

View File

@@ -2,6 +2,16 @@
@implementation NSImage (MGLAdditions)
- (nullable instancetype)initWithMGLSpriteImage:(const mbgl::SpriteImage *)spriteImage {
std::string png = encodePNG(spriteImage->image);
NSData *data = [[NSData alloc] initWithBytes:png.data() length:png.size()];
NSBitmapImageRep *rep = [NSBitmapImageRep imageRepWithData:data];
if ([self initWithSize:NSMakeSize(spriteImage->getWidth(), spriteImage->getHeight())]) {
[self addRepresentation:rep];
}
return self;
}
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage {
// Create a bitmap image representation from the image, respecting backing
// scale factor and any resizing done on the image at runtime.
@@ -10,9 +20,6 @@
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, self.size }];
[self unlockFocus];
// Get the image’s raw pixel data as an RGBA buffer.
std::string pixelString((const char *)rep.bitmapData, rep.pixelsWide * rep.pixelsHigh * 4 /* RGBA */);
mbgl::PremultipliedImage cPremultipliedImage(rep.pixelsWide, rep.pixelsHigh);
std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.size(), cPremultipliedImage.data.get());
return std::make_unique<mbgl::SpriteImage>(std::move(cPremultipliedImage),

View File

@@ -896,6 +896,13 @@ void Map::removeImage(const std::string& name) {
impl->onUpdate(Update::Repaint);
}
const SpriteImage* Map::getImage(const std::string& name) {
if (impl->style) {
return impl->style->spriteAtlas->getSprite(name).get();
}
return nullptr;
}
#pragma mark - Defaults
std::string Map::getStyleName() const {

BIN
test/fixtures/map/get_icon/expected.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

View File

@@ -394,3 +394,15 @@ TEST(Map, RemoveImage) {
map.removeImage("test-icon");
test::checkImage("test/fixtures/map/remove_icon", test::render(map, test.view));
}
TEST(Map, GetImage) {
MapTest test;
Map map(test.backend, test.view.size, 1, test.fileSource, test.threadPool, MapMode::Still);
auto decoded = decodeImage(util::read_file("test/fixtures/sprites/default_marker.png"));
auto image = std::make_unique<SpriteImage>(std::move(decoded), 1.0);
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);
}