From 6020cf459fc97bb7246a99b9757b8da4402cfd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 26 Feb 2020 23:49:35 -0800 Subject: [PATCH 1/4] [ios, macos] Nine-part resizable images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MGLSymbolStyleLayer.iconTextFit property now respects the cap insets of any nine-part stretchable image passed into the -[MGLStyle setImage:forName:] method. The “Manipulate Style” command in macosapp demonstrates the stretchable image feature by adding a single Ohio state route shield image, which has cap insets defined in the asset catalog, and labeling each Ohio state route with that image, stretched to fit the route number without widening the icon’s stroke. --- .../scripts/style-spec-overrides-v8.json | 3 ++ platform/darwin/src/MGLSymbolStyleLayer.h | 8 +++-- platform/ios/CHANGELOG.md | 1 + platform/ios/src/UIImage+MGLAdditions.mm | 26 +++++++++++++++- platform/macos/CHANGELOG.md | 3 +- .../ohio.imageset/Contents.json | 26 ++++++++++++++++ .../Assets.xcassets/ohio.imageset/ohio.pdf | Bin 0 -> 1840 bytes platform/macos/app/MapDocument.m | 23 ++++++++++++-- platform/macos/src/NSImage+MGLAdditions.mm | 29 ++++++++++++++++-- 9 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 platform/macos/app/Assets.xcassets/ohio.imageset/Contents.json create mode 100644 platform/macos/app/Assets.xcassets/ohio.imageset/ohio.pdf diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json index e478ccaf00..d3ecdae549 100644 --- a/platform/darwin/scripts/style-spec-overrides-v8.json +++ b/platform/darwin/scripts/style-spec-overrides-v8.json @@ -57,6 +57,9 @@ } } }, + "icon-text-fit": { + "doc": "The directions in which the icon stretches to fit around the text. If the icon image is a resizable image, the resizable areas may be stretched, while the cap insets are always drawn at the original scale." + }, "icon-text-fit-padding": { "doc": "Size of the additional area added to dimensions determined by `icon-text-fit`." }, diff --git a/platform/darwin/src/MGLSymbolStyleLayer.h b/platform/darwin/src/MGLSymbolStyleLayer.h index 9ca6628871..712ed55b84 100644 --- a/platform/darwin/src/MGLSymbolStyleLayer.h +++ b/platform/darwin/src/MGLSymbolStyleLayer.h @@ -102,7 +102,9 @@ typedef NS_ENUM(NSUInteger, MGLIconRotationAlignment) { }; /** - Scales the icon to fit around the associated text. + The directions in which the icon stretches to fit around the text. If the icon + image is a resizable image, the resizable areas may be stretched, while the cap + insets are always drawn at the original scale. Values of this type are used in the `MGLSymbolStyleLayer.iconTextFit` property. @@ -776,7 +778,9 @@ MGL_EXPORT @property (nonatomic, null_resettable) NSExpression *iconSize __attribute__((unavailable("Use iconScale instead."))); /** - Scales the icon to fit around the associated text. + The directions in which the icon stretches to fit around the text. If the icon + image is a resizable image, the resizable areas may be stretched, while the cap + insets are always drawn at the original scale. The default value of this property is an expression that evaluates to `none`. Set this property to `nil` to reset it to the default value. diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 516a682199..88b42e2ade 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -9,6 +9,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * Added the `in` expression function for testing whether a value is included in an array expression or whether a string is a substring of another string. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16162](https://github.com/mapbox/mapbox-gl-native/pull/16162)) * Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157)) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) +* The `MGLSymbolStyleLayer.iconTextFit` property now respects the cap insets of any [nine-part stretchable image](https://developer.apple.com/documentation/uikit/uiimage#1658362) passed into the `-[MGLStyle setImage:forName:]` method. You can define the stretchable area in Xcode’s asset catalog or by calling the `-[UIImage resizableImageWithCapInsets:]` method. ([#182](https://github.com/mapbox/mapbox-gl-native-ios/pull/182)) * The `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` methods can now localize text into Traditional Chinese and Vietnamese. ([#173](https://github.com/mapbox/mapbox-gl-native-ios/pull/173)) * Fixed an issue where an `MGLSymbolStyleLayer.lineDashPattern` value of `{1, 0}` resulted in hairline gaps. ([#16202](https://github.com/mapbox/mapbox-gl-native/pull/16202)) * Improved the performance of loading a style that has many style images. ([#16187](https://github.com/mapbox/mapbox-gl-native/pull/16187)) diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index e2f8ea09dc..d5ce19cfc7 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -5,6 +5,10 @@ const MGLExceptionName MGLResourceNotFoundException = @"MGLResourceNotFoundException"; +BOOL MGLEdgeInsetsIsZero(UIEdgeInsets edgeInsets) { + return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && edgeInsets.bottom == 0; +} + @implementation UIImage (MGLAdditions) - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)styleImage @@ -39,10 +43,30 @@ - (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::Premultiplie } - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { + mbgl::style::ImageStretches stretchX = {{ + self.capInsets.left / self.scale, (self.size.width - self.capInsets.right) / self.scale, + }}; + mbgl::style::ImageStretches stretchY = {{ + self.capInsets.top / self.scale, (self.size.height - self.capInsets.bottom) / self.scale, + }}; + + mbgl::optional imageContent; + if (!MGLEdgeInsetsIsZero(self.capInsets)) { + imageContent = (mbgl::style::ImageContent){ + .left = static_cast(self.capInsets.left * self.scale), + .top = static_cast(self.capInsets.top * self.scale), + .right = static_cast((self.size.width - self.capInsets.right) * self.scale), + .bottom = static_cast((self.size.height - self.capInsets.bottom) * self.scale), + }; + } + BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate; return std::make_unique([identifier UTF8String], self.mgl_premultipliedImage, - float(self.scale), isTemplate); + static_cast(self.scale), + isTemplate, + stretchX, stretchY, + imageContent); } - (mbgl::PremultipliedImage)mgl_premultipliedImage { diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index ee41770aa1..74be7d930c 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -10,7 +10,8 @@ * Added the `within` expression function for testing whether the evaluated feature lies within the given GeoJSON object. Use this function in expressions in style JSON or with the `MGL_FUNCTION()` syntax in an `NSExpression` format string. ([#16157](https://github.com/mapbox/mapbox-gl-native/pull/16157), [#16194](https://github.com/mapbox/mapbox-gl-native/pull/16194), [#16220](https://github.com/mapbox/mapbox-gl-native/pull/16220)) * Added the `MGLSymbolStyleLayer.textWritingModes` layout property. This property can be set to `MGLTextWritingModeHorizontal` or `MGLTextWritingModeVertical`. ([#14932](https://github.com/mapbox/mapbox-gl-native/pull/14932)) * Added the `MGLLineStyleLayer.lineSortKey` and `MGLFillStyleLayer.fillSortKey` properties. ([#179](https://github.com/mapbox/mapbox-gl-native-ios/pull/179)) -* The `MGLIdeographicFontFamilyName` Info.plist key now also accepts an array of font family names, to customize font fallback behavior. It can also be set to a Boolean value of `NO` to force the SDK to typeset CJK characters in a remote font specified by `MGLSymbolStyleLayer.textFontNames`. ([#14862](https://github.com/mapbox/mapbox-gl-native/pull/14862)) +* The `MGLIdeographicFontFamilyName` Info.plist key now also accepts an array of font family names, to customize font fallback behavior. It can also be set to a Boolean value of `NO` to force the SDK to typeset CJK characters in a remote font specified by `MGLSymbolStyleLayer.textFontNames`. ([#14862](https://github.com/mapbox/mapbox-gl-native/pull/14862)) +* The `MGLSymbolStyleLayer.iconTextFit` property now respects the cap insets of any nine-part stretchable image passed into the `-[MGLStyle setImage:forName:]` method. You can define the stretchable area in Xcode’s asset catalog or by setting the `NSImage.capInsets` property. ([#182](https://github.com/mapbox/mapbox-gl-native-ios/pull/182)) * The `-[MGLStyle localizeLabelsIntoLocale:]` and `-[NSExpression mgl_expressionLocalizedIntoLocale:]` methods can now localize text into Traditional Chinese and Vietnamese. ([#173](https://github.com/mapbox/mapbox-gl-native-ios/pull/173)) * Fixed crashes triggered when `MGLSource` and `MGLStyleLayer` objects are accessed after having been invalidated after a style change. ([#15539](https://github.com/mapbox/mapbox-gl-native/pull/15539)) * Fixed an issue where fill extrusion layers would be incorrectly rendered above other layers. ([#15065](https://github.com/mapbox/mapbox-gl-native/pull/15065)) diff --git a/platform/macos/app/Assets.xcassets/ohio.imageset/Contents.json b/platform/macos/app/Assets.xcassets/ohio.imageset/Contents.json new file mode 100644 index 0000000000..aa28df8d12 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/ohio.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "ohio.pdf", + "resizing" : { + "mode" : "9-part", + "center" : { + "mode" : "stretch", + "width" : 15, + "height" : 18 + }, + "cap-insets" : { + "bottom" : 3, + "top" : 3, + "right" : 4, + "left" : 5 + } + } + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/ohio.imageset/ohio.pdf b/platform/macos/app/Assets.xcassets/ohio.imageset/ohio.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a09b2e0b468846aa6b2aa46921c74179d03ed887 GIT binary patch literal 1840 zcmah~dr%Wc80RG;Q9*PRX~iyB9w8<7NOHN*fIt!+3Y7*d1;HA_0TIZ-X zYDL?rR6tR|2hy>M#n)ifS}Fo!3!^xWjt(e-6=%RXQ5*eA_gJ;h;7Q$yRi zVSTY+X`b!14{p~?F3XII{Mmx)hds;-HVk5}`>sB1*A7kl^GSX0EUct?(|}(#RvBn} zz~(X-Y9}9Udi$`;nnt%7UwrB{spFd%M;GVTlB3rjH13&mXmQCZ`xupfW=(R1VDibf zud7akkl(Olc3l6Vh%ZYHhAw zWNSQ2TZj(SYm0oQ&*v%5v(HV-wXK`sFwZ8lM^@Vk`ee#H({^2G#n(0Vdf(1n2mXkC z&_?Wz%Nrbyy2t(#+Q0Ih4Oe=rJdepMT`sz?CHs+r+VZDIls)}-BP%Nhci2~Dd>qWy z6$WmO8(@;=H~Cr$8~J4!`FZL{JgEL!5bM6AZCQEB;`@U>tynbwv4cPy6W}H&tBT+D zR!`Tnp2UW{?xJJPHO@Eve6vIDbtoScrKQ$c<{}mK{f%F`t_WF|nOS}0MpbLEsQcHB zgtPfxH3|LCNsqKWIeV`#L58h~sl_`LxgN>MhU1lIqMLwdy}ilix$83dnJt5MPVJY1 znZEi*acBu)=Qy2RXu7@;vpZk6vQ9B#ConR*O3B%`4W}R1yA^C!HC@?0cO<#kv>dc2 z2K~_K?tZp(`0f(Y=^|*sN>3-u;on|Jy7^{Lv+K3=oLG~%Z|$4}$FQ=wQRf}(|L&Ha zC7Nz}Hq4LEnSOIa)~pkRUayt=)5$SGMK4U9tM|L~?tW%Jyp0IrQTo&|*26chU$`dW zR@f#|v6S2AB+<)mvi6V4%tpkquy=r5&H->B8WaiuQG@~vAX*K4!j6I)@CkoocJP;W z01}^{LjfdKlTHC|fLZOvY8Xn-!lUDj8bhV(G-0$EEQJsXaIp}D1D63@B$UY{s8j+{ ztS1$;o`pvZfb09wCXZlfW6p#CNBvpR@tEUa6pTi1$l?5I^T**Rz%oW^Tv9+SC6%Ys zQtF5>4jj!3QBwxm$Y?18;HsmZkR=!Y_}%j=O9OWnS)@xfz-+ies9(lz)0#`_{CrW zk&*yh9Dt9)3V6k_Gy~+}s_<|W70XbRP{A37qEZoDxrxDdSY|>3OAS{MlyRs7w@?BC z94-})>WQGt>EI-k;pA}ZgcxT}6bkpCA8r!==ZlL2>Pa~tFaTU;B@ki_Sx4)Y9O3+w zfjH+8#|ez!Tm+z`FN*RfR8KmgdZXo6Xjn}aoo=lQM>xY6#a48I8<<24`7nN#Wi+}h xin0E$#pwzt7&1pIHBGaCOMI03QTj9*a4fRY(!gpM)|w$Ek&!%aZ`I;(-hU~tXRZJM literal 0 HcmV?d00001 diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 1ab8b690b9..3431f4e258 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -1012,9 +1012,9 @@ - (IBAction)manipulateStyle:(id)sender { MGLSource *streetsSource = [self.mapView.style sourceWithIdentifier:@"composite"]; if (streetsSource) { - NSImage *image = [NSImage imageNamed:NSImageNameIChatTheaterTemplate]; - [self.mapView.style setImage:image forName:NSImageNameIChatTheaterTemplate]; - + NSImage *image = [NSImage imageNamed:NSImageNameIChatTheaterTemplate]; + [self.mapView.style setImage:image forName:NSImageNameIChatTheaterTemplate]; + MGLSymbolStyleLayer *theaterLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"theaters" source:streetsSource]; theaterLayer.sourceLayerIdentifier = @"poi_label"; theaterLayer.predicate = [NSPredicate predicateWithFormat:@"maki == 'theatre'"]; @@ -1026,6 +1026,23 @@ - (IBAction)manipulateStyle:(id)sender { @20.0: [NSColor blackColor], }]; [self.mapView.style addLayer:theaterLayer]; + + NSImage *ohio = [NSImage imageNamed:@"ohio"]; + [self.mapView.style setImage:ohio forName:@"ohio"]; + + MGLSymbolStyleLayer *ohioLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"ohio" source:streetsSource]; + ohioLayer.sourceLayerIdentifier = @"road"; + ohioLayer.predicate = [NSPredicate predicateWithFormat:@"shield = 'circle-white' and iso_3166_2 = 'US-OH'"]; + ohioLayer.symbolPlacement = [NSExpression expressionForConstantValue:@"line"]; + ohioLayer.text = [NSExpression expressionForKeyPath:@"ref"]; + ohioLayer.textFontNames = [NSExpression expressionWithFormat:@"{'DIN Offc Pro Bold', 'Arial Unicode MS Bold'}"]; + ohioLayer.textFontSize = [NSExpression expressionForConstantValue:@10]; + ohioLayer.textRotationAlignment = [NSExpression expressionForConstantValue:@"viewport"]; + ohioLayer.iconImageName = [NSExpression expressionForConstantValue:@"ohio"]; + ohioLayer.iconTextFit = [NSExpression expressionForConstantValue:@"both"]; + ohioLayer.iconTextFitPadding = [NSExpression expressionForConstantValue:[NSValue valueWithEdgeInsets:NSEdgeInsetsMake(1, 2, 1, 3)]]; + ohioLayer.iconRotationAlignment = [NSExpression expressionForConstantValue:@"viewport"]; + [self.mapView.style addLayer:ohioLayer]; } NSURL *imageURL = [NSURL URLWithString:@"https://www.mapbox.com/mapbox-gl-js/assets/radar.gif"]; diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm index a1671a276d..34f0325d6f 100644 --- a/platform/macos/src/NSImage+MGLAdditions.mm +++ b/platform/macos/src/NSImage+MGLAdditions.mm @@ -2,6 +2,10 @@ #include +BOOL MGLEdgeInsetsIsZero(NSEdgeInsets edgeInsets) { + return edgeInsets.left == 0 && edgeInsets.top == 0 && edgeInsets.right == 0 && edgeInsets.bottom == 0; +} + @implementation NSImage (MGLAdditions) - (nullable instancetype)initWithMGLPremultipliedImage:(mbgl::PremultipliedImage&&)src { @@ -35,10 +39,31 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)style - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { mbgl::PremultipliedImage cPremultipliedImage = self.mgl_premultipliedImage; auto imageWidth = cPremultipliedImage.size.width; + + float scale = static_cast(imageWidth) / self.size.width; + mbgl::style::ImageStretches stretchX = {{ + self.capInsets.left * scale, (self.size.width - self.capInsets.right) * scale, + }}; + mbgl::style::ImageStretches stretchY = {{ + self.capInsets.top * scale, (self.size.height - self.capInsets.bottom) * scale, + }}; + + mbgl::optional imageContent; + if (!MGLEdgeInsetsIsZero(self.capInsets)) { + imageContent = (mbgl::style::ImageContent){ + .left = static_cast(self.capInsets.left * scale), + .top = static_cast(self.capInsets.top * scale), + .right = static_cast((self.size.width - self.capInsets.right) * scale), + .bottom = static_cast((self.size.height - self.capInsets.bottom) * scale), + }; + } + return std::make_unique([identifier UTF8String], std::move(cPremultipliedImage), - (float)(imageWidth / self.size.width), - [self isTemplate]); + scale, + [self isTemplate], + stretchX, stretchY, + imageContent); } - (mbgl::PremultipliedImage)mgl_premultipliedImage { From cbb21abbfa126a16bebea150c64b126116d94388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 5 Mar 2020 13:59:22 -0800 Subject: [PATCH 2/4] [ios, macos] Added image stretching tests --- platform/darwin/test/MGLImageTests.mm | 81 +++++++++++++++++++ platform/ios/ios.xcodeproj/project.pbxproj | 4 + .../macos/macos.xcodeproj/project.pbxproj | 4 + 3 files changed, 89 insertions(+) create mode 100644 platform/darwin/test/MGLImageTests.mm diff --git a/platform/darwin/test/MGLImageTests.mm b/platform/darwin/test/MGLImageTests.mm new file mode 100644 index 0000000000..f2334059fa --- /dev/null +++ b/platform/darwin/test/MGLImageTests.mm @@ -0,0 +1,81 @@ +#import +#import + +#if TARGET_OS_IPHONE + #import "UIImage+MGLAdditions.h" +#else + #import "NSImage+MGLAdditions.h" +#endif + +@interface MGLImageTests : XCTestCase + +@end + +@implementation MGLImageTests + +- (void)testStretching { +#if TARGET_OS_IPHONE + CGRect rect = CGRectMake(0, 0, 24, 24); + UIGraphicsBeginImageContextWithOptions(rect.size, NO, UIScreen.mainScreen.scale); + CGContextRef context = UIGraphicsGetCurrentContext(); + CGContextSetStrokeColorWithColor(context, UIColor.blackColor.CGColor); + CGContextStrokeRectWithWidth(context, rect, 2); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); +#else + NSImage *image = [NSImage imageWithSize:NSMakeSize(24, 24) flipped:NO drawingHandler:^BOOL(NSRect dstRect) { + // A little more fanciful than the iOS version, but we aren’t testing the actual contents of the image anyways. + NSRectEdge allSides[] = {NSMinYEdge, NSMaxXEdge, NSMaxYEdge, NSMinXEdge, NSMinYEdge, NSMaxXEdge}; + CGFloat grays[] = {NSBlack, NSBlack, NSWhite, NSWhite, NSDarkGray, NSDarkGray}; + dstRect = NSDrawTiledRects(dstRect, dstRect, allSides, grays, sizeof(grays) / sizeof(grays[0])); + [NSColor.grayColor set]; + NSRectFill(dstRect); + return YES; + }]; +#endif + + { + auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; + XCTAssert(styleImage); + if (styleImage) { + XCTAssert(!styleImage->getContent()); + XCTAssertFalse(styleImage->isSdf()); + } + } + +#if TARGET_OS_IPHONE + image = [image resizableImageWithCapInsets:UIEdgeInsetsZero]; +#else + image.capInsets = NSEdgeInsetsZero; +#endif + { + auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; + XCTAssert(styleImage); + if (styleImage) { + XCTAssert(!styleImage->getContent()); + } + } + +#if TARGET_OS_IPHONE + image = [image resizableImageWithCapInsets:UIEdgeInsetsMake(1, 2, 3, 4)]; +#else + image.capInsets = NSEdgeInsetsMake(1, 2, 3, 4); +#endif + { + auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; + XCTAssert(styleImage); + if (styleImage) { + auto scale = styleImage->getPixelRatio(); + auto content = styleImage->getContent(); + XCTAssert(content); + if (content) { + XCTAssertEqual(content->top, 1 * scale); + XCTAssertEqual(content->left, 2 * scale); + XCTAssertEqual(content->bottom, 21 * scale); + XCTAssertEqual(content->right, 20 * scale); + } + } + } +} + +@end diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 50a1441aa2..7c7381fb7b 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -439,6 +439,7 @@ DA0BDD202407C12600DAA576 /* libmbgl-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F78399D235AA1E600D4D606 /* libmbgl-core.a */; }; DA0BDD212407C13000DAA576 /* libmbgl-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F78399D235AA1E600D4D606 /* libmbgl-core.a */; }; DA0CD5901CF56F6A00A5F5A5 /* MGLFeatureTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */; }; + DA0E9F3F2411AC9B007C75D4 /* MGLImageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0E9F3E2411AC9B007C75D4 /* MGLImageTests.mm */; }; DA17BE301CC4BAC300402C41 /* MGLMapView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */; }; DA17BE311CC4BDAA00402C41 /* MGLMapView_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */; }; DA1DC96A1CB6C6B7006E619F /* MBXCustomCalloutView.m in Sources */ = {isa = PBXBuildFile; fileRef = DA1DC9671CB6C6B7006E619F /* MBXCustomCalloutView.m */; }; @@ -1075,6 +1076,7 @@ DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = ""; }; DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = ""; }; DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = ""; }; + DA0E9F3E2411AC9B007C75D4 /* MGLImageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLImageTests.mm; path = ../../darwin/test/MGLImageTests.mm; sourceTree = ""; }; DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapView_Private.h; sourceTree = ""; }; DA1AC01B1E5B8774006DF1D6 /* lt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; }; DA1AC0201E5B8917006DF1D6 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Foundation.stringsdict; sourceTree = ""; }; @@ -1858,6 +1860,7 @@ DD58A4C51D822BD000E1F038 /* MGLExpressionTests.mm */, DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */, DA2E885C1CC0382C00F24E7B /* MGLGeometryTests.mm */, + DA0E9F3E2411AC9B007C75D4 /* MGLImageTests.mm */, DA5DB1291FABF1EE001C2326 /* MGLMapAccessibilityElementTests.m */, DA695425215B1E75002041A4 /* MGLMapCameraTests.m */, 96E6145522CC135200109F14 /* MGLMapViewCompassViewTests.mm */, @@ -3014,6 +3017,7 @@ 357579801D501E09000B822E /* MGLFillStyleLayerTests.mm in Sources */, 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.mm in Sources */, DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */, + DA0E9F3F2411AC9B007C75D4 /* MGLImageTests.mm in Sources */, 076171C32139C70900668A35 /* MGLMapViewTests.m in Sources */, 3598544D1E1D38AA00B29F84 /* MGLDistanceFormatterTests.m in Sources */, 071BBB071EE77631001FB02A /* MGLImageSourceTests.m in Sources */, diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index dd3cafecbb..03e18743ea 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -134,6 +134,7 @@ DA00FC8A1D5EEAC3009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8B1D5EEAC3009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC891D5EEAC3009AABC8 /* MGLAttributionInfo.mm */; }; DA0CD58E1CF56F5800A5F5A5 /* MGLFeatureTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */; }; + DA0E9F3B24119F6B007C75D4 /* MGLImageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA0E9F3A24119F6B007C75D4 /* MGLImageTests.mm */; }; DA2784FE1DF03060001D5B8D /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA2784FD1DF03060001D5B8D /* Media.xcassets */; }; DA29875A1E1A4290002299F5 /* MGLDocumentationExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2987591E1A4290002299F5 /* MGLDocumentationExampleTests.swift */; }; DA35A2A41CC9EB1A00E826B2 /* MGLCoordinateFormatter.h in Headers */ = {isa = PBXBuildFile; fileRef = DA35A2A31CC9EB1A00E826B2 /* MGLCoordinateFormatter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -480,6 +481,7 @@ DA00FC881D5EEAC3009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = ""; }; DA00FC891D5EEAC3009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = ""; }; DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = ""; }; + DA0E9F3A24119F6B007C75D4 /* MGLImageTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLImageTests.mm; path = ../../darwin/test/MGLImageTests.mm; sourceTree = ""; }; DA1AC01E1E5B8826006DF1D6 /* lt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Localizable.strings; sourceTree = ""; }; DA1AC01F1E5B8904006DF1D6 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Foundation.stringsdict; sourceTree = ""; }; DA2207BA1DC076930002F84D /* test-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "test-Bridging-Header.h"; sourceTree = ""; }; @@ -1206,6 +1208,7 @@ DD58A4C71D822C6200E1F038 /* MGLExpressionTests.mm */, DA0CD58D1CF56F5800A5F5A5 /* MGLFeatureTests.mm */, DAE6C3C81CC34BD800DB3429 /* MGLGeometryTests.mm */, + DA0E9F3A24119F6B007C75D4 /* MGLImageTests.mm */, DA695423215B1E6C002041A4 /* MGLMapCameraTests.m */, 076171C4213A0DC200668A35 /* MGLMapViewTests.m */, 1F95931A1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm */, @@ -1768,6 +1771,7 @@ DA695424215B1E6C002041A4 /* MGLMapCameraTests.m in Sources */, 920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */, DA35A2B61CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */, + DA0E9F3B24119F6B007C75D4 /* MGLImageTests.mm in Sources */, 35C6DF871E214C1800ACA483 /* MGLDistanceFormatterTests.m in Sources */, CAD9D0AC22A88A32001B25EE /* MGLResourceTests.mm in Sources */, DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */, From 4e0a77e53e245b0c365f9d969b34e1213c4658d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Thu, 5 Mar 2020 14:17:15 -0800 Subject: [PATCH 3/4] [ios, macos] Roundtrip resizable image cap insets --- platform/darwin/test/MGLImageTests.mm | 18 ++++++++++++++++++ platform/ios/src/UIImage+MGLAdditions.mm | 12 +++++++++++- platform/macos/src/NSImage+MGLAdditions.mm | 13 ++++++++++--- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/platform/darwin/test/MGLImageTests.mm b/platform/darwin/test/MGLImageTests.mm index f2334059fa..cb3e0394dc 100644 --- a/platform/darwin/test/MGLImageTests.mm +++ b/platform/darwin/test/MGLImageTests.mm @@ -40,6 +40,12 @@ - (void)testStretching { if (styleImage) { XCTAssert(!styleImage->getContent()); XCTAssertFalse(styleImage->isSdf()); + + MGLImage *imageAfter = [[MGLImage alloc] initWithMGLStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.capInsets.top, 0); + XCTAssertEqual(imageAfter.capInsets.left, 0); + XCTAssertEqual(imageAfter.capInsets.bottom, 0); + XCTAssertEqual(imageAfter.capInsets.right, 0); } } @@ -53,6 +59,12 @@ - (void)testStretching { XCTAssert(styleImage); if (styleImage) { XCTAssert(!styleImage->getContent()); + + MGLImage *imageAfter = [[MGLImage alloc] initWithMGLStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.capInsets.top, 0); + XCTAssertEqual(imageAfter.capInsets.left, 0); + XCTAssertEqual(imageAfter.capInsets.bottom, 0); + XCTAssertEqual(imageAfter.capInsets.right, 0); } } @@ -74,6 +86,12 @@ - (void)testStretching { XCTAssertEqual(content->bottom, 21 * scale); XCTAssertEqual(content->right, 20 * scale); } + + MGLImage *imageAfter = [[MGLImage alloc] initWithMGLStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.capInsets.top, 1); + XCTAssertEqual(imageAfter.capInsets.left, 2); + XCTAssertEqual(imageAfter.capInsets.bottom, 3); + XCTAssertEqual(imageAfter.capInsets.right, 4); } } } diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index d5ce19cfc7..1f468aa3f2 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -18,12 +18,22 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)style return nil; } - if (self = [self initWithCGImage:image scale:styleImage.getPixelRatio() orientation:UIImageOrientationUp]) + CGFloat scale = styleImage.getPixelRatio(); + if (self = [self initWithCGImage:image scale:scale orientation:UIImageOrientationUp]) { if (styleImage.isSdf()) { self = [self imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; } + + if (auto content = styleImage.getContent()) + { + UIEdgeInsets capInsets = UIEdgeInsetsMake(content->top / scale, + content->left / scale, + self.size.height - content->bottom / scale, + self.size.width - content->right / scale); + self = [self resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; + } } CGImageRelease(image); return self; diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm index 34f0325d6f..2bb4fae602 100644 --- a/platform/macos/src/NSImage+MGLAdditions.mm +++ b/platform/macos/src/NSImage+MGLAdditions.mm @@ -27,11 +27,18 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)style NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:image]; CGImageRelease(image); - CGFloat w = styleImage.getImage().size.width / styleImage.getPixelRatio(); - CGFloat h = styleImage.getImage().size.height / styleImage.getPixelRatio(); - if (self = [self initWithSize:NSMakeSize(w, h)]) { + CGFloat scale = styleImage.getPixelRatio(); + NSSize size = NSMakeSize(styleImage.getImage().size.width / scale, + styleImage.getImage().size.height / scale); + if (self = [self initWithSize:size]) { [self addRepresentation:rep]; [self setTemplate:styleImage.isSdf()]; + if (auto content = styleImage.getContent()) { + self.capInsets = NSEdgeInsetsMake(content->top / scale, + content->left / scale, + size.height - content->bottom / scale, + size.width - content->right / scale); + } } return self; } From c06937e613b8a9582411dc3aa5c341fe49e657ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 11 Mar 2020 16:03:02 -0700 Subject: [PATCH 4/4] [ios, macos] Only set stretchable regions for stretch resizing mode --- platform/darwin/test/MGLImageTests.mm | 36 +++++++++++++++++++++- platform/ios/src/UIImage+MGLAdditions.mm | 30 +++++++++++++----- platform/macos/src/NSImage+MGLAdditions.mm | 20 ++++++++---- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/platform/darwin/test/MGLImageTests.mm b/platform/darwin/test/MGLImageTests.mm index cb3e0394dc..6f3e1d1ebd 100644 --- a/platform/darwin/test/MGLImageTests.mm +++ b/platform/darwin/test/MGLImageTests.mm @@ -3,8 +3,12 @@ #if TARGET_OS_IPHONE #import "UIImage+MGLAdditions.h" + #define MGLImageResizingModeTile UIImageResizingModeTile + #define MGLImageResizingModeStretch UIImageResizingModeStretch #else #import "NSImage+MGLAdditions.h" + #define MGLImageResizingModeTile NSImageResizingModeTile + #define MGLImageResizingModeStretch NSImageResizingModeStretch #endif @interface MGLImageTests : XCTestCase @@ -32,6 +36,7 @@ - (void)testStretching { NSRectFill(dstRect); return YES; }]; + image.resizingMode = NSImageResizingModeTile; #endif { @@ -40,8 +45,11 @@ - (void)testStretching { if (styleImage) { XCTAssert(!styleImage->getContent()); XCTAssertFalse(styleImage->isSdf()); + XCTAssertTrue(styleImage->getStretchX().empty()); + XCTAssertTrue(styleImage->getStretchY().empty()); MGLImage *imageAfter = [[MGLImage alloc] initWithMGLStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.resizingMode, MGLImageResizingModeTile); XCTAssertEqual(imageAfter.capInsets.top, 0); XCTAssertEqual(imageAfter.capInsets.left, 0); XCTAssertEqual(imageAfter.capInsets.bottom, 0); @@ -50,17 +58,31 @@ - (void)testStretching { } #if TARGET_OS_IPHONE - image = [image resizableImageWithCapInsets:UIEdgeInsetsZero]; + image = [image resizableImageWithCapInsets:UIEdgeInsetsZero resizingMode:UIImageResizingModeStretch]; #else + image.resizingMode = NSImageResizingModeStretch; image.capInsets = NSEdgeInsetsZero; #endif { auto styleImage = [image mgl_styleImageWithIdentifier:@"box"]; XCTAssert(styleImage); if (styleImage) { + auto scale = styleImage->getPixelRatio(); XCTAssert(!styleImage->getContent()); + auto stretchX = styleImage->getStretchX(); + XCTAssertEqual(stretchX.size(), 1UL); + if (!stretchX.empty()) { + XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(0, 24 * scale)); + } + auto stretchY = styleImage->getStretchY(); + XCTAssertEqual(stretchY.size(), 1UL); + if (!stretchY.empty()) { + XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(0, 24 * scale)); + } + MGLImage *imageAfter = [[MGLImage alloc] initWithMGLStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.resizingMode, MGLImageResizingModeStretch); XCTAssertEqual(imageAfter.capInsets.top, 0); XCTAssertEqual(imageAfter.capInsets.left, 0); XCTAssertEqual(imageAfter.capInsets.bottom, 0); @@ -87,7 +109,19 @@ - (void)testStretching { XCTAssertEqual(content->right, 20 * scale); } + auto stretchX = styleImage->getStretchX(); + XCTAssertEqual(stretchX.size(), 1UL); + if (!stretchX.empty()) { + XCTAssertEqual(stretchX.front(), mbgl::style::ImageStretch(2 * scale, 20 * scale)); + } + auto stretchY = styleImage->getStretchY(); + XCTAssertEqual(stretchY.size(), 1UL); + if (!stretchY.empty()) { + XCTAssertEqual(stretchY.front(), mbgl::style::ImageStretch(1 * scale, 21 * scale)); + } + MGLImage *imageAfter = [[MGLImage alloc] initWithMGLStyleImage:*styleImage]; + XCTAssertEqual(imageAfter.resizingMode, MGLImageResizingModeStretch); XCTAssertEqual(imageAfter.capInsets.top, 1); XCTAssertEqual(imageAfter.capInsets.left, 2); XCTAssertEqual(imageAfter.capInsets.bottom, 3); diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index 1f468aa3f2..19c000c6a5 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -32,7 +32,16 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)style content->left / scale, self.size.height - content->bottom / scale, self.size.width - content->right / scale); - self = [self resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; + UIImageResizingMode resizingMode = UIImageResizingModeTile; + if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) + { + resizingMode = UIImageResizingModeStretch; + } + self = [self resizableImageWithCapInsets:capInsets resizingMode:resizingMode]; + } + if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) + { + self = [self resizableImageWithCapInsets:self.capInsets resizingMode:UIImageResizingModeStretch]; } } CGImageRelease(image); @@ -53,15 +62,20 @@ - (nullable instancetype)initWithMGLPremultipliedImage:(const mbgl::Premultiplie } - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { - mbgl::style::ImageStretches stretchX = {{ - self.capInsets.left / self.scale, (self.size.width - self.capInsets.right) / self.scale, - }}; - mbgl::style::ImageStretches stretchY = {{ - self.capInsets.top / self.scale, (self.size.height - self.capInsets.bottom) / self.scale, - }}; + mbgl::style::ImageStretches stretchX, stretchY; + if (self.resizingMode == UIImageResizingModeStretch) + { + stretchX.push_back({ + self.capInsets.left * self.scale, (self.size.width - self.capInsets.right) * self.scale, + }); + stretchY.push_back({ + self.capInsets.top * self.scale, (self.size.height - self.capInsets.bottom) * self.scale, + }); + } mbgl::optional imageContent; - if (!MGLEdgeInsetsIsZero(self.capInsets)) { + if (!MGLEdgeInsetsIsZero(self.capInsets)) + { imageContent = (mbgl::style::ImageContent){ .left = static_cast(self.capInsets.left * self.scale), .top = static_cast(self.capInsets.top * self.scale), diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm index 2bb4fae602..178cd17c74 100644 --- a/platform/macos/src/NSImage+MGLAdditions.mm +++ b/platform/macos/src/NSImage+MGLAdditions.mm @@ -33,6 +33,11 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)style if (self = [self initWithSize:size]) { [self addRepresentation:rep]; [self setTemplate:styleImage.isSdf()]; + if (!styleImage.getStretchX().empty() || !styleImage.getStretchY().empty()) { + self.resizingMode = NSImageResizingModeStretch; + } else { + self.resizingMode = NSImageResizingModeTile; + } if (auto content = styleImage.getContent()) { self.capInsets = NSEdgeInsetsMake(content->top / scale, content->left / scale, @@ -48,12 +53,15 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image &)style auto imageWidth = cPremultipliedImage.size.width; float scale = static_cast(imageWidth) / self.size.width; - mbgl::style::ImageStretches stretchX = {{ - self.capInsets.left * scale, (self.size.width - self.capInsets.right) * scale, - }}; - mbgl::style::ImageStretches stretchY = {{ - self.capInsets.top * scale, (self.size.height - self.capInsets.bottom) * scale, - }}; + mbgl::style::ImageStretches stretchX, stretchY; + if (self.resizingMode == NSImageResizingModeStretch) { + stretchX.push_back({ + self.capInsets.left * scale, (self.size.width - self.capInsets.right) * scale, + }); + stretchY.push_back({ + self.capInsets.top * scale, (self.size.height - self.capInsets.bottom) * scale, + }); + } mbgl::optional imageContent; if (!MGLEdgeInsetsIsZero(self.capInsets)) {