diff options
Diffstat (limited to 'platform/macos/src')
-rw-r--r-- | platform/macos/src/MGLMapView.mm | 336 | ||||
-rw-r--r-- | platform/macos/src/MGLMapViewDelegate.h | 35 | ||||
-rw-r--r-- | platform/macos/src/Mapbox.h | 11 | ||||
-rw-r--r-- | platform/macos/src/NSColor+MGLAdditions.h | 6 | ||||
-rw-r--r-- | platform/macos/src/NSColor+MGLAdditions.mm | 8 | ||||
-rw-r--r-- | platform/macos/src/NSImage+MGLAdditions.h | 9 | ||||
-rw-r--r-- | platform/macos/src/NSImage+MGLAdditions.mm | 22 |
7 files changed, 240 insertions, 187 deletions
diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index 8fca7b86ff..a8f91a24e7 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -30,6 +30,7 @@ #import <mbgl/math/wrap.hpp> #import <mbgl/util/constants.hpp> #import <mbgl/util/chrono.hpp> +#import <mbgl/util/run_loop.hpp> #import <unordered_map> #import <unordered_set> @@ -41,6 +42,7 @@ #import "NSString+MGLAdditions.h" #import "NSURL+MGLAdditions.h" #import "NSColor+MGLAdditions.h" +#import "NSImage+MGLAdditions.h" #import <QuartzCore/QuartzCore.h> @@ -112,6 +114,11 @@ NSImage *MGLDefaultMarkerImage() { return [[NSImage alloc] initWithContentsOfFile:path]; } +/// Initializes the run loop shim that lives on the main thread. +void MGLinitializeRunLoop() { + static mbgl::util::RunLoop mainRunLoop; +} + /// Converts a media timing function into a unit bezier object usable in mbgl. mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function) { if (!function) { @@ -151,7 +158,7 @@ public: /// Cross-platform map view controller. mbgl::Map *_mbglMap; MGLMapViewImpl *_mbglView; - + NSPanGestureRecognizer *_panGestureRecognizer; NSMagnificationGestureRecognizer *_magnificationGestureRecognizer; NSRotationGestureRecognizer *_rotationGestureRecognizer; @@ -159,7 +166,7 @@ public: CLLocationDirection _directionAtBeginningOfGesture; CGFloat _pitchAtBeginningOfGesture; BOOL _didHideCursorDuringGesture; - + MGLAnnotationContextMap _annotationContextsByAnnotationTag; MGLAnnotationTag _selectedAnnotationTag; MGLAnnotationTag _lastSelectedAnnotationTag; @@ -170,22 +177,22 @@ public: BOOL _wantsToolTipRects; /// True if any annotation images that have custom cursors have been installed. BOOL _wantsCursorRects; - + // Cached checks for delegate method implementations that may be called from // MGLMultiPointDelegate methods. - + BOOL _delegateHasAlphasForShapeAnnotations; BOOL _delegateHasStrokeColorsForShapeAnnotations; BOOL _delegateHasFillColorsForShapeAnnotations; BOOL _delegateHasLineWidthsForShapeAnnotations; - + /// True if the current process is the Interface Builder designable /// renderer. When drawing the designable, the map is paused, so any call to /// it may hang the process. BOOL _isTargetingInterfaceBuilder; CLLocationDegrees _pendingLatitude; CLLocationDegrees _pendingLongitude; - + /// True if the view is currently printing itself. BOOL _isPrinting; } @@ -225,7 +232,7 @@ public: - (void)awakeFromNib { [super awakeFromNib]; - + self.styleURL = nil; } @@ -234,11 +241,13 @@ public: } - (void)commonInit { + MGLinitializeRunLoop(); + _isTargetingInterfaceBuilder = NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent; - + // Set up cross-platform controllers and resources. _mbglView = new MGLMapViewImpl(self, [NSScreen mainScreen].backingScaleFactor); - + // Delete the pre-offline ambient cache at // ~/Library/Caches/com.mapbox.sdk.ios/cache.db. NSURL *cachesDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory @@ -250,36 +259,36 @@ public: [NSBundle mgl_frameworkBundle].bundleIdentifier]; NSURL *legacyCacheURL = [cachesDirectoryURL URLByAppendingPathComponent:@"cache.db"]; [[NSFileManager defaultManager] removeItemAtURL:legacyCacheURL error:NULL]; - + mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; _mbglMap = new mbgl::Map(*_mbglView, *mbglFileSource, mbgl::MapMode::Continuous, mbgl::GLContextMode::Unique, mbgl::ConstrainMode::None, mbgl::ViewportMode::Default); [self validateTileCacheSize]; - + // Install the OpenGL layer. Interface Builder’s synchronous drawing means // we can’t display a map, so don’t even bother to have a map layer. self.layer = _isTargetingInterfaceBuilder ? [CALayer layer] : [MGLOpenGLLayer layer]; - + // Notify map object when network reachability status changes. MGLReachability *reachability = [MGLReachability reachabilityForInternetConnection]; reachability.reachableBlock = ^(MGLReachability *) { mbgl::NetworkStatus::Reachable(); }; [reachability startNotifier]; - + // Install ornaments and gesture recognizers. [self installZoomControls]; [self installCompass]; [self installLogoView]; [self installAttributionView]; [self installGestureRecognizers]; - + // Set up annotation management and selection state. _annotationImagesByIdentifier = [NSMutableDictionary dictionary]; _annotationContextsByAnnotationTag = {}; _selectedAnnotationTag = MGLAnnotationTagNotFound; _lastSelectedAnnotationTag = MGLAnnotationTagNotFound; _annotationsNearbyLastClick = {}; - + // Jump to Null Island initially. self.automaticallyAdjustsContentInsets = YES; mbgl::CameraOptions options; @@ -345,26 +354,26 @@ public: - (void)installAttributionView { _attributionView = [[NSView alloc] initWithFrame:NSZeroRect]; _attributionView.wantsLayer = YES; - + // Make the background and foreground translucent to be unobtrusive. _attributionView.layer.opacity = 0.6; - + // Blur the background to prevent text underneath the view from running into // the text in the view, rendering it illegible. CIFilter *attributionBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"]; [attributionBlurFilter setDefaults]; - + // Brighten the background. This is similar to applying a translucent white // background on the view, but the effect is a bit more subtle and works // well with the blur above. CIFilter *attributionColorFilter = [CIFilter filterWithName:@"CIColorControls"]; [attributionColorFilter setDefaults]; [attributionColorFilter setValue:@(0.1) forKey:kCIInputBrightnessKey]; - + // Apply the background effects and a standard button corner radius. _attributionView.backgroundFilters = @[attributionColorFilter, attributionBlurFilter]; _attributionView.layer.cornerRadius = 4; - + _attributionView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:_attributionView]; [self updateAttributionView]; @@ -376,27 +385,27 @@ public: _zoomEnabled = YES; _rotateEnabled = YES; _pitchEnabled = YES; - + _panGestureRecognizer = [[NSPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)]; _panGestureRecognizer.delaysKeyEvents = YES; [self addGestureRecognizer:_panGestureRecognizer]; - + NSClickGestureRecognizer *clickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleClickGesture:)]; clickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO; [self addGestureRecognizer:clickGestureRecognizer]; - + NSClickGestureRecognizer *rightClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleRightClickGesture:)]; rightClickGestureRecognizer.buttonMask = 0x2; [self addGestureRecognizer:rightClickGestureRecognizer]; - + NSClickGestureRecognizer *doubleClickGestureRecognizer = [[NSClickGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleClickGesture:)]; doubleClickGestureRecognizer.numberOfClicksRequired = 2; doubleClickGestureRecognizer.delaysPrimaryMouseButtonEvents = NO; [self addGestureRecognizer:doubleClickGestureRecognizer]; - + _magnificationGestureRecognizer = [[NSMagnificationGestureRecognizer alloc] initWithTarget:self action:@selector(handleMagnificationGesture:)]; [self addGestureRecognizer:_magnificationGestureRecognizer]; - + _rotationGestureRecognizer = [[NSRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleRotationGesture:)]; [self addGestureRecognizer:_rotationGestureRecognizer]; } @@ -405,7 +414,7 @@ public: /// hard-coded to the standard Mapbox and OpenStreetMap attribution. - (void)updateAttributionView { self.attributionView.subviews = @[]; - + for (NSUInteger i = 0; i < sizeof(MGLAttributions) / sizeof(MGLAttributions[0]); i++) { // For each attribution, add a borderless button that responds to clicks // and feels like a hyperlink. @@ -413,7 +422,7 @@ public: NSButton *button = [[MGLAttributionButton alloc] initWithTitle:MGLAttributions[i].title URL:url]; button.controlSize = NSMiniControlSize; button.translatesAutoresizingMaskIntoConstraints = NO; - + // Set the new button flush with the buttom of the container and to the // right of the previous button, with standard spacing. If there is no // previous button, align to the container instead. @@ -441,14 +450,14 @@ public: - (void)dealloc { [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; - + // Close any annotation callout immediately. [self.calloutForSelectedAnnotation close]; self.calloutForSelectedAnnotation = nil; - + // Removing the annotations unregisters any outstanding KVO observers. [self removeAnnotations:self.annotations]; - + if (_mbglMap) { delete _mbglMap; _mbglMap = nullptr; @@ -510,14 +519,14 @@ public: - (void)setDelegate:(id<MGLMapViewDelegate>)delegate { _delegate = delegate; - + // Cache checks for delegate method implementations that may be called in a // hot loop, namely the annotation style methods. _delegateHasAlphasForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:alphaForShapeAnnotation:)]; _delegateHasStrokeColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:strokeColorForShapeAnnotation:)]; _delegateHasFillColorsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:fillColorForPolygonAnnotation:)]; _delegateHasLineWidthsForShapeAnnotations = [_delegate respondsToSelector:@selector(mapView:lineWidthForPolylineAnnotation:)]; - + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) { @@ -549,7 +558,7 @@ public: if (_isTargetingInterfaceBuilder) { return; } - + // Default to Streets. if (!styleURL) { // An access token is required to load any default style, including @@ -559,7 +568,7 @@ public: } styleURL = [MGLStyle streetsStyleURLWithVersion:MGLStyleDefaultVersion]; } - + styleURL = styleURL.mgl_URLByStandardizingScheme; _mbglMap->setStyleURL(styleURL.absoluteString.UTF8String); } @@ -577,7 +586,7 @@ public: if (!self.dormant && !newWindow) { self.dormant = YES; } - + [self.window removeObserver:self forKeyPath:@"contentLayoutRect"]; [self.window removeObserver:self forKeyPath:@"titlebarAppearsTransparent"]; } @@ -587,11 +596,11 @@ public: if (self.dormant && window) { self.dormant = NO; } - + if (window && _mbglMap->getConstrainMode() == mbgl::ConstrainMode::None) { _mbglMap->setConstrainMode(mbgl::ConstrainMode::HeightOnly); } - + [window addObserver:self forKeyPath:@"contentLayoutRect" options:NSKeyValueObservingOptionInitial @@ -640,7 +649,7 @@ public: attribute:NSLayoutAttributeTrailing multiplier:1 constant:MGLOrnamentPadding]]; - + // Center the compass above the zoom controls, assuming that the compass is // narrower than the zoom controls. [self addConstraint: @@ -659,7 +668,7 @@ public: attribute:NSLayoutAttributeBottom multiplier:1 constant:8]]; - + // Place the logo view in the lower-left corner of the view, accounting for // the logo’s alignment rect. [self addConstraint: @@ -678,7 +687,7 @@ public: attribute:NSLayoutAttributeLeading multiplier:1 constant:MGLOrnamentPadding - _logoView.image.alignmentRect.origin.x]]; - + // Place the attribution view to the right of the logo view and size it to // fit the buttons inside. [self addConstraint:[NSLayoutConstraint constraintWithItem:_logoView @@ -709,7 +718,7 @@ public: attribute:NSLayoutAttributeTrailing multiplier:1 constant:8]]; - + [super updateConstraints]; } @@ -747,20 +756,20 @@ public: if (!_mbglMap) { return; } - + CGFloat zoomFactor = self.maximumZoomLevel - self.minimumZoomLevel + 1; CGFloat cpuFactor = [NSProcessInfo processInfo].processorCount; CGFloat memoryFactor = (CGFloat)[NSProcessInfo processInfo].physicalMemory / 1000 / 1000 / 1000; CGFloat sizeFactor = (NSWidth(self.bounds) / mbgl::util::tileSize) * (NSHeight(self.bounds) / mbgl::util::tileSize); - + NSUInteger cacheSize = zoomFactor * cpuFactor * memoryFactor * sizeFactor * 0.5; - + _mbglMap->setSourceTileCacheSize(cacheSize); } - (void)invalidate { MGLAssertIsMainThread(); - + [self.layer setNeedsDisplay]; } @@ -769,7 +778,7 @@ public: if (!_mbglMap) { return; } - + switch (change) { case mbgl::MapChangeRegionWillChange: case mbgl::MapChangeRegionWillChangeAnimated: @@ -786,7 +795,7 @@ public: // while animating. [self updateCompass]; [self updateAnnotationCallouts]; - + if ([self.delegate respondsToSelector:@selector(mapViewCameraIsChanging:)]) { [self.delegate mapViewCameraIsChanging:self]; } @@ -804,7 +813,7 @@ public: [self updateCompass]; [self updateAnnotationCallouts]; [self updateAnnotationTrackingAreas]; - + if ([self.delegate respondsToSelector:@selector(mapView:cameraDidChangeAnimated:)]) { BOOL animated = change == mbgl::MapChangeRegionDidChangeAnimated; [self.delegate mapView:self cameraDidChangeAnimated:animated]; @@ -865,6 +874,14 @@ public: } break; } + case mbgl::MapChangeDidFinishLoadingStyle: + { + if ([self.delegate respondsToSelector:@selector(mapView:didFinishLoadingStyle:)]) + { + [self.delegate mapView:self didFinishLoadingStyle:self.style]; + } + break; + } } } @@ -878,7 +895,7 @@ public: - (void)printWithImage:(NSImage *)image { NSImageView *imageView = [[NSImageView alloc] initWithFrame:self.bounds]; imageView.image = image; - + NSPrintOperation *op = [NSPrintOperation printOperationWithView:imageView]; [op runOperation]; } @@ -1045,7 +1062,7 @@ public: if ([self.camera isEqual:camera]) { return; } - + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera]; mbgl::AnimationOptions animationOptions; if (duration > 0) { @@ -1062,7 +1079,7 @@ public: }); }; } - + [self willChangeValueForKey:@"camera"]; _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -1081,7 +1098,7 @@ public: if ([self.camera isEqual:camera]) { return; } - + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera]; mbgl::AnimationOptions animationOptions; if (duration >= 0) { @@ -1103,7 +1120,7 @@ public: }); }; } - + [self willChangeValueForKey:@"camera"]; _mbglMap->flyTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -1145,7 +1162,7 @@ public: - (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(NSEdgeInsets)insets animated:(BOOL)animated { _mbglMap->cancelTransitions(); - + mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(insets); padding += MGLEdgeInsetsFromNSEdgeInsets(self.contentInsets); mbgl::CameraOptions cameraOptions = _mbglMap->cameraForLatLngBounds(MGLLatLngBoundsFromCoordinateBounds(bounds), padding); @@ -1153,7 +1170,7 @@ public: if (animated) { animationOptions.duration = MGLDurationInSeconds(MGLAnimationDuration); } - + [self willChangeValueForKey:@"visibleCoordinateBounds"]; animationOptions.transitionFinishFn = ^() { [self didChangeValueForKey:@"visibleCoordinateBounds"]; @@ -1196,7 +1213,7 @@ public: if (!_automaticallyAdjustsContentInsets) { return; } - + NSEdgeInsets contentInsets = self.contentInsets; if ((self.window.styleMask & NSFullSizeContentViewWindowMask) && !self.window.titlebarAppearsTransparent) { @@ -1210,7 +1227,7 @@ public: } else { contentInsets = NSEdgeInsetsZero; } - + self.contentInsets = contentInsets; } @@ -1222,7 +1239,7 @@ public: if (NSEdgeInsetsEqual(contentInsets, self.contentInsets)) { return; } - + // After adjusting the content insets, move the center coordinate from the // old frame of reference to the new one represented by the newly set // content insets. @@ -1243,12 +1260,12 @@ public: NSPoint delta = [gestureRecognizer translationInView:self]; NSPoint endPoint = [gestureRecognizer locationInView:self]; NSPoint startPoint = NSMakePoint(endPoint.x - delta.x, endPoint.y - delta.y); - + NSEventModifierFlags flags = [NSApp currentEvent].modifierFlags; if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { [self.window invalidateCursorRectsForView:self]; _mbglMap->setGestureInProgress(true); - + if (![self isPanningWithGesture]) { // Hide the cursor except when panning. CGDisplayHideCursor(kCGDirectMainDisplay); @@ -1258,7 +1275,7 @@ public: || gestureRecognizer.state == NSGestureRecognizerStateCancelled) { _mbglMap->setGestureInProgress(false); [self.window invalidateCursorRectsForView:self]; - + if (_didHideCursorDuringGesture) { _didHideCursorDuringGesture = NO; // Move the cursor back to the start point and show it again, creating @@ -1270,15 +1287,15 @@ public: CGDisplayShowCursor(kCGDirectMainDisplay); } } - + if (flags & NSShiftKeyMask) { // Shift-drag to zoom. if (!self.zoomEnabled) { return; } - + _mbglMap->cancelTransitions(); - + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { _scaleAtBeginningOfGesture = _mbglMap->getScale(); } else if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { @@ -1288,7 +1305,7 @@ public: } else if (flags & NSAlternateKeyMask) { // Option-drag to rotate and/or tilt. _mbglMap->cancelTransitions(); - + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { _directionAtBeginningOfGesture = self.direction; _pitchAtBeginningOfGesture = _mbglMap->getPitch(); @@ -1307,7 +1324,7 @@ public: } else if (self.scrollEnabled) { // Otherwise, drag to pan. _mbglMap->cancelTransitions(); - + if (gestureRecognizer.state == NSGestureRecognizerStateChanged) { delta.y *= -1; [self offsetCenterCoordinateBy:delta animated:NO]; @@ -1329,9 +1346,9 @@ public: if (!self.zoomEnabled) { return; } - + _mbglMap->cancelTransitions(); - + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { _mbglMap->setGestureInProgress(true); _scaleAtBeginningOfGesture = _mbglMap->getScale(); @@ -1357,7 +1374,7 @@ public: || [self subviewContainingGesture:gestureRecognizer]) { return; } - + NSPoint gesturePoint = [gestureRecognizer locationInView:self]; MGLAnnotationTag hitAnnotationTag = [self annotationTagAtPoint:gesturePoint persistingResults:YES]; if (hitAnnotationTag != MGLAnnotationTagNotFound) { @@ -1385,9 +1402,9 @@ public: || [self subviewContainingGesture:gestureRecognizer]) { return; } - + _mbglMap->cancelTransitions(); - + NSPoint gesturePoint = [gestureRecognizer locationInView:self]; [self scaleBy:2 atPoint:gesturePoint animated:YES]; } @@ -1396,9 +1413,9 @@ public: if (!self.zoomEnabled) { return; } - + _mbglMap->cancelTransitions(); - + // Tap with two fingers (“right-click”) to zoom out on mice but not trackpads. NSPoint gesturePoint = [self convertPoint:event.locationInWindow fromView:nil]; [self scaleBy:0.5 atPoint:gesturePoint animated:YES]; @@ -1409,9 +1426,9 @@ public: if (!self.rotateEnabled) { return; } - + _mbglMap->cancelTransitions(); - + if (gestureRecognizer.state == NSGestureRecognizerStateBegan) { _mbglMap->setGestureInProgress(true); _directionAtBeginningOfGesture = self.direction; @@ -1455,13 +1472,13 @@ public: && _rotationGestureRecognizer.state == NSGestureRecognizerStatePossible) { // Scroll to pan. _mbglMap->cancelTransitions(); - + CGFloat x = event.scrollingDeltaX; CGFloat y = event.scrollingDeltaY; if (x || y) { [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; } - + // Drift pan. if (event.momentumPhase != NSEventPhaseNone) { [self offsetCenterCoordinateBy:NSMakePoint(x, y) animated:NO]; @@ -1573,7 +1590,7 @@ public: if (_annotationContextsByAnnotationTag.empty()) { return nil; } - + // Map all the annotation tags to the annotations themselves. std::vector<id <MGLAnnotation>> annotations; std::transform(_annotationContextsByAnnotationTag.begin(), @@ -1590,7 +1607,7 @@ public: if (!_annotationContextsByAnnotationTag.count(tag)) { return nil; } - + MGLAnnotationContext &annotationContext = _annotationContextsByAnnotationTag[tag]; return annotationContext.annotation; } @@ -1600,7 +1617,7 @@ public: if (!annotation) { return MGLAnnotationTagNotFound; } - + for (auto &pair : _annotationContextsByAnnotationTag) { if (pair.second.annotation == annotation) { return pair.first; @@ -1619,21 +1636,21 @@ public: if (!annotations) { return; } - + [self willChangeValueForKey:@"annotations"]; - + BOOL delegateHasImagesForAnnotations = [self.delegate respondsToSelector:@selector(mapView:imageForAnnotation:)]; - + for (id <MGLAnnotation> annotation in annotations) { NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation"); - + if ([annotation isKindOfClass:[MGLMultiPoint class]]) { // Actual multipoints aren’t supported as annotations. if ([annotation isMemberOfClass:[MGLMultiPoint class]] || [annotation isMemberOfClass:[MGLMultiPointFeature class]]) { continue; } - + // The multipoint knows how to style itself (with the map view’s help). MGLMultiPoint *multiPoint = (MGLMultiPoint *)annotation; if (!multiPoint.pointCount) { @@ -1659,13 +1676,13 @@ public: if (!annotationImage) { annotationImage = self.defaultAnnotationImage; } - + NSString *symbolName = annotationImage.styleIconIdentifier; if (!symbolName) { symbolName = [MGLAnnotationSpritePrefix stringByAppendingString:annotationImage.reuseIdentifier]; annotationImage.styleIconIdentifier = symbolName; } - + if (!self.annotationImagesByIdentifier[annotationImage.reuseIdentifier]) { self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; [self installAnnotationImage:annotationImage]; @@ -1680,7 +1697,7 @@ public: context.annotation = annotation; context.imageReuseIdentifier = annotationImage.reuseIdentifier; _annotationContextsByAnnotationTag[annotationTag] = context; - + if ([annotation isKindOfClass:[NSObject class]]) { NSAssert(![annotation isKindOfClass:[MGLMultiPoint class]], @"Point annotation should not be MGLMultiPoint."); [(NSObject *)annotation addObserver:self forKeyPath:@"coordinate" options:0 context:(void *)(NSUInteger)annotationTag]; @@ -1694,7 +1711,7 @@ public: } [self didChangeValueForKey:@"annotations"]; - + [self updateAnnotationTrackingAreas]; } @@ -1716,36 +1733,23 @@ public: - (void)installAnnotationImage:(MGLAnnotationImage *)annotationImage { NSString *iconIdentifier = annotationImage.styleIconIdentifier; self.annotationImagesByIdentifier[annotationImage.reuseIdentifier] = annotationImage; - + NSImage *image = annotationImage.image; NSSize size = image.size; if (size.width == 0 || size.height == 0 || !image.valid) { // Can’t create an empty sprite. An image that hasn’t loaded is also useless. return; } - - // Create a bitmap image representation from the image, respecting backing - // scale factor and any resizing done on the image at runtime. - // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431 - [image lockFocus]; - NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:{ NSZeroPoint, size }]; - [image 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()); - auto cSpriteImage = std::make_shared<mbgl::SpriteImage>(std::move(cPremultipliedImage), - (float)(rep.pixelsWide / size.width)); - _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, cSpriteImage); - + + std::shared_ptr<mbgl::SpriteImage> sprite(annotationImage.image.mgl_spriteImage); + _mbglMap->addAnnotationIcon(iconIdentifier.UTF8String, sprite); + // Create a slop area with a “radius” equal to the annotation image’s entire // size, allowing the eventual click to be on any point within this image. // Union this slop area with any existing slop areas. _unionedAnnotationImageSize = NSMakeSize(MAX(_unionedAnnotationImageSize.width, size.width), MAX(_unionedAnnotationImageSize.height, size.height)); - + // Opt into potentially expensive cursor tracking areas. if (annotationImage.cursor) { _wantsCursorRects = YES; @@ -1762,12 +1766,12 @@ public: if (!annotations) { return; } - + [self willChangeValueForKey:@"annotations"]; for (id <MGLAnnotation> annotation in annotations) { NSAssert([annotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation"); - + MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; NSAssert(annotationTag != MGLAnnotationTagNotFound, @"No ID for annotation %@", annotation); @@ -1777,9 +1781,9 @@ public: if (annotationTag == _lastSelectedAnnotationTag) { _lastSelectedAnnotationTag = MGLAnnotationTagNotFound; } - + _annotationContextsByAnnotationTag.erase(annotationTag); - + if ([annotation isKindOfClass:[NSObject class]] && ![annotation isKindOfClass:[MGLMultiPoint class]]) { [(NSObject *)annotation removeObserver:self forKeyPath:@"coordinate" context:(void *)(NSUInteger)annotationTag]; @@ -1789,9 +1793,9 @@ public: _mbglMap->removeAnnotation(annotationTag); } - + [self didChangeValueForKey:@"annotations"]; - + [self updateAnnotationTrackingAreas]; } @@ -1805,11 +1809,11 @@ public: /** Returns the tag of the annotation at the given point in the view. - + This is more involved than it sounds: if multiple point annotations overlap near the point, this method cycles through them so that each of them is accessible to the user at some point. - + @param persist True to remember the cycleable set of annotations, so that a different annotation is returned the next time this method is called with the same point. Setting this parameter to false is useful for @@ -1825,13 +1829,13 @@ public: queryRect = NSInsetRect(queryRect, -MGLAnnotationImagePaddingForHitTest, -MGLAnnotationImagePaddingForHitTest); std::vector<MGLAnnotationTag> nearbyAnnotations = [self annotationTagsInRect:queryRect]; - + if (nearbyAnnotations.size()) { // Assume that the user is fat-fingering an annotation. NSRect hitRect = NSInsetRect({ point, NSZeroSize }, -MGLAnnotationImagePaddingForHitTest, -MGLAnnotationImagePaddingForHitTest); - + // Filter out any annotation whose image is unselectable or for which // hit testing fails. auto end = std::remove_if(nearbyAnnotations.begin(), nearbyAnnotations.end(), [&](const MGLAnnotationTag annotationTag) { @@ -1840,12 +1844,12 @@ public: if (!annotation) { return true; } - + MGLAnnotationImage *annotationImage = [self imageOfAnnotationWithTag:annotationTag]; if (!annotationImage.selectable) { return true; } - + // Filter out the annotation if the fattened finger didn’t land on a // translucent or opaque pixel in the image. NSRect annotationRect = [self frameOfImage:annotationImage.image @@ -1855,13 +1859,13 @@ public: }); nearbyAnnotations.resize(std::distance(nearbyAnnotations.begin(), end)); } - + MGLAnnotationTag hitAnnotationTag = MGLAnnotationTagNotFound; if (nearbyAnnotations.size()) { // The annotation tags need to be stable in order to compare them with // the remembered tags. std::sort(nearbyAnnotations.begin(), nearbyAnnotations.end()); - + if (nearbyAnnotations == _annotationsNearbyLastClick) { // The first selection in the cycle should be the one nearest to the // click. @@ -1875,7 +1879,7 @@ public: coordinateB.longitude - currentCoordinate.longitude); return distanceA < distanceB; }); - + // The last time we persisted a set of annotations, we had the same // set of annotations as we do now. Cycle through them. if (_lastSelectedAnnotationTag == MGLAnnotationTagNotFound @@ -1904,14 +1908,14 @@ public: if (persist) { _annotationsNearbyLastClick = nearbyAnnotations; } - + // Choose the first nearby annotation. if (nearbyAnnotations.size()) { hitAnnotationTag = nearbyAnnotations.front(); } } } - + return hitAnnotationTag; } @@ -1950,13 +1954,13 @@ public: if (!selectedAnnotations.count) { return; } - + id <MGLAnnotation> firstAnnotation = selectedAnnotations[0]; NSAssert([firstAnnotation conformsToProtocol:@protocol(MGLAnnotation)], @"Annotation does not conform to MGLAnnotation"); if ([firstAnnotation isKindOfClass:[MGLMultiPoint class]]) { return; } - + // Select the annotation if it’s visible. if (MGLCoordinateInCoordinateBounds(firstAnnotation.coordinate, self.visibleCoordinateBounds)) { [self selectAnnotation:firstAnnotation]; @@ -1969,29 +1973,29 @@ public: if (!annotation || [annotation isKindOfClass:[MGLMultiPoint class]]) { return; } - + id <MGLAnnotation> selectedAnnotation = self.selectedAnnotation; if (annotation == selectedAnnotation) { return; } - + // Deselect the annotation before reselecting it. [self deselectAnnotation:selectedAnnotation]; - + // Add the annotation to the map if it hasn’t been added yet. MGLAnnotationTag annotationTag = [self annotationTagForAnnotation:annotation]; if (annotationTag == MGLAnnotationTagNotFound) { [self addAnnotation:annotation]; } - + // The annotation can’t be selected if no part of it is hittable. NSRect positioningRect = [self positioningRectForCalloutForAnnotationWithTag:annotationTag]; if (NSIsEmptyRect(NSIntersectionRect(positioningRect, self.bounds))) { return; } - + self.selectedAnnotation = annotation; - + // For the callout to be shown, the annotation must have a title, its // callout must not already be shown, and the annotation must be able to // show a callout according to the delegate. @@ -2001,7 +2005,7 @@ public: && [self.delegate respondsToSelector:@selector(mapView:annotationCanShowCallout:)] && [self.delegate mapView:self annotationCanShowCallout:annotation]) { NSPopover *callout = [self calloutForAnnotation:annotation]; - + // Hang the callout off the right edge of the annotation image’s // alignment rect, or off the left edge in a right-to-left UI. callout.delegate = self; @@ -2049,7 +2053,7 @@ public: - (NSPopover *)calloutForAnnotation:(id <MGLAnnotation>)annotation { NSPopover *callout = [[NSPopover alloc] init]; callout.behavior = NSPopoverBehaviorTransient; - + NSViewController *viewController; if ([self.delegate respondsToSelector:@selector(mapView:calloutViewControllerForAnnotation:)]) { NSViewController *viewControllerFromDelegate = [self.delegate mapView:self @@ -2066,7 +2070,7 @@ public: // annotation. viewController.representedObject = annotation; callout.contentViewController = viewController; - + return callout; } @@ -2094,7 +2098,7 @@ public: if (!image) { return NSZeroRect; } - + NSRect positioningRect = [self frameOfImage:image centeredAtCoordinate:annotation.coordinate]; positioningRect = NSOffsetRect(image.alignmentRect, positioningRect.origin.x, positioningRect.origin.y); return NSInsetRect(positioningRect, -MGLAnnotationImagePaddingForCallout, @@ -2114,10 +2118,10 @@ public: || _annotationContextsByAnnotationTag.count(annotationTag) == 0) { return nil; } - + NSString *customSymbol = _annotationContextsByAnnotationTag.at(annotationTag).imageReuseIdentifier; NSString *symbolName = customSymbol.length ? customSymbol : MGLDefaultStyleMarkerSymbolName; - + return [self dequeueReusableAnnotationImageWithIdentifier:symbolName]; } @@ -2125,11 +2129,11 @@ public: if (!annotation || self.selectedAnnotation != annotation) { return; } - + // Close the callout popover gracefully. NSPopover *callout = self.calloutForSelectedAnnotation; [callout performClose:self]; - + self.selectedAnnotation = nil; } @@ -2159,8 +2163,18 @@ public: #pragma mark MGLMultiPointDelegate methods - (double)alphaForShapeAnnotation:(MGLShape *)annotation { + // The explicit -mapView:alphaForShapeAnnotation: delegate method is deprecated + // but still used, if implemented. When not implemented, call the stroke or + // fill color delegate methods and pull the alpha from the returned color. if (_delegateHasAlphasForShapeAnnotations) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" return [self.delegate mapView:self alphaForShapeAnnotation:annotation]; +#pragma clang diagnostic pop + } else if ([annotation isKindOfClass:[MGLPolygon class]]) { + return [self fillColorForPolygonAnnotation:(MGLPolygon *)annotation].a ?: 1.0; + } else if ([annotation isKindOfClass:[MGLShape class]]) { + return [self strokeColorForShapeAnnotation:annotation].a ?: 1.0; } return 1.0; } @@ -2169,14 +2183,14 @@ public: NSColor *color = (_delegateHasStrokeColorsForShapeAnnotations ? [self.delegate mapView:self strokeColorForShapeAnnotation:annotation] : [NSColor selectedMenuItemColor]); - return color.mbgl_color; + return color.mgl_color; } - (mbgl::Color)fillColorForPolygonAnnotation:(MGLPolygon *)annotation { NSColor *color = (_delegateHasFillColorsForShapeAnnotations ? [self.delegate mapView:self fillColorForPolygonAnnotation:annotation] : [NSColor selectedMenuItemColor]); - return color.mbgl_color; + return color.mgl_color; } - (CGFloat)lineWidthForPolylineAnnotation:(MGLPolyline *)annotation { @@ -2201,7 +2215,7 @@ public: id <MGLAnnotation> annotation = self.calloutForSelectedAnnotation.contentViewController.representedObject; self.calloutForSelectedAnnotation = nil; self.selectedAnnotation = nil; - + if ([self.delegate respondsToSelector:@selector(mapView:didDeselectAnnotation:)]) { [self.delegate mapView:self didDeselectAnnotation:annotation]; } @@ -2258,7 +2272,7 @@ public: } } } - + // Blow away any cursor tracking areas and rebuild them. That’s the // potentially expensive part. if (_wantsCursorRects) { @@ -2279,13 +2293,13 @@ public: [self addCursorRect:self.bounds cursor:[NSCursor closedHandCursor]]; return; } - + // The rest of this method can be expensive, so bail if no annotations have // ever had custom cursors. if (!_wantsCursorRects) { return; } - + std::vector<MGLAnnotationTag> annotationTags = [self annotationTagsInRect:self.bounds]; for (MGLAnnotationTag annotationTag : annotationTags) { id <MGLAnnotation> annotation = [self annotationWithTag:annotationTag]; @@ -2311,7 +2325,7 @@ public: - (NS_ARRAY_OF(id <MGLFeature>) *)visibleFeaturesAtPoint:(NSPoint)point inStyleLayersWithIdentifiers:(NS_SET_OF(NSString *) *)styleLayerIdentifiers { // Cocoa origin is at the lower-left corner. mbgl::ScreenCoordinate screenCoordinate = { point.x, NSHeight(self.bounds) - point.y }; - + mbgl::optional<std::vector<std::string>> optionalLayerIDs; if (styleLayerIdentifiers) { __block std::vector<std::string> layerIDs; @@ -2321,7 +2335,7 @@ public: }]; optionalLayerIDs = layerIDs; } - + std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenCoordinate, optionalLayerIDs); return MGLFeaturesFromMBGLFeatures(features); } @@ -2336,7 +2350,7 @@ public: { NSMinX(rect), NSHeight(self.bounds) - NSMaxY(rect) }, { NSMaxX(rect), NSHeight(self.bounds) - NSMinY(rect) }, }; - + mbgl::optional<std::vector<std::string>> optionalLayerIDs; if (styleLayerIdentifiers) { __block std::vector<std::string> layerIDs; @@ -2346,7 +2360,7 @@ public: }]; optionalLayerIDs = layerIDs; } - + std::vector<mbgl::Feature> features = _mbglMap->queryRenderedFeatures(screenBox, optionalLayerIDs); return MGLFeaturesFromMBGLFeatures(features); } @@ -2355,7 +2369,7 @@ public: - (void)prepareForInterfaceBuilder { [super prepareForInterfaceBuilder]; - + // Color the background a glorious Mapbox teal. self.layer.borderColor = [NSColor colorWithRed:59/255. green:178/255. @@ -2366,7 +2380,7 @@ public: green:178/255. blue:208/255. alpha:0.6].CGColor; - + // Place a playful marker right smack dab in the middle. self.layer.contents = MGLDefaultMarkerImage(); self.layer.contentsGravity = kCAGravityCenter; @@ -2425,7 +2439,7 @@ public: bounds.extend([self convertPoint:{ NSMaxX(rect), NSMinY(rect) } toLatLngFromView:view]); bounds.extend([self convertPoint:{ NSMaxX(rect), NSMaxY(rect) } toLatLngFromView:view]); bounds.extend([self convertPoint:{ NSMinX(rect), NSMaxY(rect) } toLatLngFromView:view]); - + // The world is wrapping if a point just outside the bounds is also within // the rect. mbgl::LatLng outsideLatLng; @@ -2440,13 +2454,13 @@ public: bounds.east() + 1, }; } - + // If the world is wrapping, extend the bounds to cover all longitudes. if (NSPointInRect([self convertLatLng:outsideLatLng toPointToView:view], rect)) { bounds.extend(mbgl::LatLng(bounds.south(), -180)); bounds.extend(mbgl::LatLng(bounds.south(), 180)); } - + return bounds; } @@ -2554,7 +2568,7 @@ public: mbgl::PremultipliedImage image { size[0], size[1] }; MBGL_CHECK_ERROR(glReadPixels(0, 0, size[0], size[1], GL_RGBA, GL_UNSIGNED_BYTE, image.data.get())); - + const size_t stride = image.stride(); auto tmp = std::make_unique<uint8_t[]>(stride); uint8_t *rgba = image.data.get(); @@ -2563,14 +2577,14 @@ public: std::memcpy(rgba + i * stride, rgba + j * stride, stride); std::memcpy(rgba + j * stride, tmp.get(), stride); } - + return image; } - + private: /// Cocoa map view that this adapter bridges to. __weak MGLMapView *nativeView = nullptr; - + /// Backing scale factor of the view. const float scaleFactor; }; diff --git a/platform/macos/src/MGLMapViewDelegate.h b/platform/macos/src/MGLMapViewDelegate.h index 8f4922d304..c5af93f8ad 100644 --- a/platform/macos/src/MGLMapViewDelegate.h +++ b/platform/macos/src/MGLMapViewDelegate.h @@ -125,6 +125,23 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(BOOL)fullyRendered; +/** + Tells the delegate that the map has just finished loading a style. + + This method is called during the initialization of the map view and after any + subsequent loading of a new style. This method is called between the + `-mapViewWillStartRenderingMap:` and `-mapViewDidFinishRenderingMap:` delegate + methods. Changes to sources or layers of the current style do not cause this + method to be called. + + This method is the earliest opportunity to modify the layout or appearance of + the current style before the map view is displayed to the user. + + @param mapView The map view that has just loaded a style. + @param style The style that was loaded. + */ +- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style; + #pragma mark Managing the Appearance of Annotations /** @@ -139,17 +156,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation; -/** - Returns the alpha value to use when rendering a shape annotation. - - A value of 0.0 results in a completely transparent shape. A value of 1.0, the - default, results in a completely opaque shape. - - @param mapView The map view rendering the shape annotation. - @param annotation The annotation being rendered. - @return An alpha value between 0 and 1.0. - */ -- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation; +- (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)annotation __attribute__((deprecated("Use -mapView:strokeColorForShapeAnnotation: or -mapView:fillColorForPolygonAnnotation:."))); /** Returns the color to use when rendering the outline of a shape annotation. @@ -157,6 +164,9 @@ NS_ASSUME_NONNULL_BEGIN The default stroke color is the selected menu item color. If a pattern color is specified, the result is undefined. + Opacity may be set by specifying an alpha component. The default alpha value is + `1.0` and results in a completely opaque stroke. + @param mapView The map view rendering the shape annotation. @param annotation The annotation being rendered. @return A color to use for the shape outline. @@ -169,6 +179,9 @@ NS_ASSUME_NONNULL_BEGIN The default fill color is the selected menu item color. If a pattern color is specified, the result is undefined. + Opacity may be set by specifying an alpha component. The default alpha value is + `1.0` and results in a completely opaque shape. + @param mapView The map view rendering the polygon annotation. @param annotation The annotation being rendered. @return The polygon’s interior fill color. diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 0217bca44a..67e3775100 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -30,7 +30,8 @@ FOUNDATION_EXPORT const unsigned char MapboxVersionString[]; #import "MGLShapeCollection.h" #import "MGLStyle.h" #import "MGLStyleLayer.h" -#import "MGLBaseStyleLayer.h" +#import "MGLForegroundStyleLayer.h" +#import "MGLVectorStyleLayer.h" #import "MGLFillStyleLayer.h" #import "MGLLineStyleLayer.h" #import "MGLSymbolStyleLayer.h" @@ -44,10 +45,4 @@ FOUNDATION_EXPORT const unsigned char MapboxVersionString[]; #import "MGLTilePyramidOfflineRegion.h" #import "MGLTypes.h" #import "NSValue+MGLAdditions.h" -#import "MGLStyleAttributeValue.h" -#import "MGLStyleAttributeFunction.h" -#import "NSColor+MGLStyleAttributeAdditions.h" -#import "NSNumber+MGLStyleAttributeAdditions.h" -#import "NSValue+MGLStyleAttributeAdditions.h" -#import "NSString+MGLStyleAttributeAdditions.h" -#import "NSArray+MGLStyleAttributeAdditions.h" +#import "MGLStyleValue.h" diff --git a/platform/macos/src/NSColor+MGLAdditions.h b/platform/macos/src/NSColor+MGLAdditions.h index 2ba38f7d90..8dd8c1c17c 100644 --- a/platform/macos/src/NSColor+MGLAdditions.h +++ b/platform/macos/src/NSColor+MGLAdditions.h @@ -8,13 +8,13 @@ /** Converts the color into an mbgl::Color in calibrated RGB space. */ -- (mbgl::Color)mbgl_color; +- (mbgl::Color)mgl_color; /** Instantiates `NSColor` from an `mbgl::Color` */ -+ (NSColor *)mbgl_colorWithColor:(mbgl::Color)color; ++ (NSColor *)mgl_colorWithColor:(mbgl::Color)color; -- (mbgl::style::PropertyValue<mbgl::Color>)mbgl_colorPropertyValue; +- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue; @end diff --git a/platform/macos/src/NSColor+MGLAdditions.mm b/platform/macos/src/NSColor+MGLAdditions.mm index a75dc488fd..e347fd1798 100644 --- a/platform/macos/src/NSColor+MGLAdditions.mm +++ b/platform/macos/src/NSColor+MGLAdditions.mm @@ -2,7 +2,7 @@ @implementation NSColor (MGLAdditions) -- (mbgl::Color)mbgl_color +- (mbgl::Color)mgl_color { CGFloat r, g, b, a; @@ -11,14 +11,14 @@ return { (float)r, (float)g, (float)b, (float)a }; } -+ (NSColor *)mbgl_colorWithColor:(mbgl::Color)color ++ (NSColor *)mgl_colorWithColor:(mbgl::Color)color { return [NSColor colorWithRed:color.r green:color.g blue:color.b alpha:color.a]; } -- (mbgl::style::PropertyValue<mbgl::Color>)mbgl_colorPropertyValue +- (mbgl::style::PropertyValue<mbgl::Color>)mgl_colorPropertyValue { - mbgl::Color color = self.mbgl_color; + mbgl::Color color = self.mgl_color; return {{ color.r, color.g, color.b, color.a }}; } diff --git a/platform/macos/src/NSImage+MGLAdditions.h b/platform/macos/src/NSImage+MGLAdditions.h new file mode 100644 index 0000000000..a2144e96a2 --- /dev/null +++ b/platform/macos/src/NSImage+MGLAdditions.h @@ -0,0 +1,9 @@ +#import <Cocoa/Cocoa.h> + +#include <mbgl/sprite/sprite_image.hpp> + +@interface NSImage (MGLAdditions) + +- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage; + +@end diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm new file mode 100644 index 0000000000..7d02271bb3 --- /dev/null +++ b/platform/macos/src/NSImage+MGLAdditions.mm @@ -0,0 +1,22 @@ +#import "NSImage+MGLAdditions.h" + +@implementation NSImage (MGLAdditions) + +- (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. + // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431 + [self lockFocus]; + 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), + (float)(rep.pixelsWide / self.size.width)); +} + +@end |