diff options
author | Roman Blum <rmnblm@gmail.com> | 2016-12-06 02:16:13 +0100 |
---|---|---|
committer | Minh Nguyễn <mxn@1ec5.org> | 2016-12-05 17:16:13 -0800 |
commit | 1045934d5e62eaf85bc280ce2d75e7558f5a5104 (patch) | |
tree | cf299675768bb6ba1996d3a312823b60737bc109 | |
parent | 5579e67804369777819babba5cf6e25eb91ee77e (diff) | |
download | qtlocation-mapboxgl-1045934d5e62eaf85bc280ce2d75e7558f5a5104.tar.gz |
[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.
19 files changed, 173 insertions, 6 deletions
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 10153c54e3..092cfbe291 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -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; diff --git a/platform/darwin/src/MGLStyle.h b/platform/darwin/src/MGLStyle.h index 54ca267bab..3fe8fb9492 100644 --- a/platform/darwin/src/MGLStyle.h +++ b/platform/darwin/src/MGLStyle.h @@ -372,6 +372,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. To use an image in a style layer, give it a unique name using this method, then diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 56dc4ad8f7..99d6ca89b6 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -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 = %@>", diff --git a/platform/darwin/test/MGLStyleTests.mm b/platform/darwin/test/MGLStyleTests.mm index 4c0c163b38..8f0d2502fb 100644 --- a/platform/darwin/test/MGLStyleTests.mm +++ b/platform/darwin/test/MGLStyleTests.mm @@ -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 diff --git a/platform/darwin/test/Media.xcassets/Contents.json b/platform/darwin/test/Media.xcassets/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/platform/darwin/test/Media.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +}
\ No newline at end of file diff --git a/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/Contents.json b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/Contents.json new file mode 100644 index 0000000000..08cd551fc7 --- /dev/null +++ b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/Contents.json @@ -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" + } +}
\ No newline at end of file diff --git a/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask.png b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask.png Binary files differnew file mode 100644 index 0000000000..bb7348c482 --- /dev/null +++ b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask.png diff --git a/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@2x.png b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@2x.png Binary files differnew file mode 100644 index 0000000000..35c5a293ec --- /dev/null +++ b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@2x.png diff --git a/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@3x.png b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@3x.png Binary files differnew file mode 100644 index 0000000000..af523975a5 --- /dev/null +++ b/platform/darwin/test/Media.xcassets/TrackingLocationMask.imageset/TrackingLocationMask@3x.png diff --git a/platform/ios/app/Assets.xcassets/AppIcon.appiconset/Contents.json b/platform/ios/app/Assets.xcassets/AppIcon.appiconset/Contents.json index c1b88669e7..e1bc22272f 100644 --- a/platform/ios/app/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/platform/ios/app/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,16 @@ { "images" : [ { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-29@2x.png", @@ -37,6 +47,16 @@ "scale" : "3x" }, { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-Small.png", diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index a5c042a08c..488872ad34 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -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; diff --git a/platform/ios/src/UIImage+MGLAdditions.h b/platform/ios/src/UIImage+MGLAdditions.h index 411220c503..f291a302c9 100644 --- a/platform/ios/src/UIImage+MGLAdditions.h +++ b/platform/ios/src/UIImage+MGLAdditions.h @@ -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 diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index 8ec8f9e15f..4507fb6c41 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -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; diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index fdfc1b9f04..07a912386b 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -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; diff --git a/platform/macos/src/NSImage+MGLAdditions.h b/platform/macos/src/NSImage+MGLAdditions.h index a2144e96a2..ee01a763a3 100644 --- a/platform/macos/src/NSImage+MGLAdditions.h +++ b/platform/macos/src/NSImage+MGLAdditions.h @@ -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 diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm index 7d02271bb3..dd132d0e87 100644 --- a/platform/macos/src/NSImage+MGLAdditions.mm +++ b/platform/macos/src/NSImage+MGLAdditions.mm @@ -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), diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 68c20a3132..f6ee6a5650 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -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 { diff --git a/test/fixtures/map/get_icon/expected.png b/test/fixtures/map/get_icon/expected.png Binary files differnew file mode 100644 index 0000000000..b112096c18 --- /dev/null +++ b/test/fixtures/map/get_icon/expected.png diff --git a/test/map/map.test.cpp b/test/map/map.test.cpp index 97a09e94a0..ac82bbacae 100644 --- a/test/map/map.test.cpp +++ b/test/map/map.test.cpp @@ -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); +} |