[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:
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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 = %@>",
|
||||
|
||||
@@ -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
|
||||
|
||||
6
platform/darwin/test/Media.xcassets/Contents.json
Normal file
6
platform/darwin/test/Media.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
26
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/Contents.json
vendored
Normal file
26
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/Contents.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask.png
vendored
Normal file
BIN
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 407 B |
BIN
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@2x.png
vendored
Normal file
BIN
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@2x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 680 B |
BIN
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@3x.png
vendored
Normal file
BIN
platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@3x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 903 B |
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
BIN
test/fixtures/map/get_icon/expected.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1010 B |
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user