From 07343a34c54431073ed71a11069769f71008ff56 Mon Sep 17 00:00:00 2001 From: Ivo van Dongen Date: Wed, 30 Aug 2017 17:22:48 +0300 Subject: [darwin] snapshotter --- platform/darwin/src/MGLMapSnapshotter.h | 105 ++++++++++++++++++++ platform/darwin/src/MGLMapSnapshotter.mm | 163 +++++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 platform/darwin/src/MGLMapSnapshotter.h create mode 100644 platform/darwin/src/MGLMapSnapshotter.mm diff --git a/platform/darwin/src/MGLMapSnapshotter.h b/platform/darwin/src/MGLMapSnapshotter.h new file mode 100644 index 0000000000..a2a4f1b331 --- /dev/null +++ b/platform/darwin/src/MGLMapSnapshotter.h @@ -0,0 +1,105 @@ +#import +#import "MGLTypes.h" +#import "MGLGeometry.h" +#import "MGLMapCamera.h" + +NS_ASSUME_NONNULL_BEGIN + +MGL_EXPORT +/** + The options to use when creating images with the `MGLMapsnapshotter`. + */ +@interface MGLMapSnapshotOptions : NSObject + +/** + Creates a set of options with the minimum required information + @param styleURL the style url to use + @param camera the camera settings + @param size the image size + */ +- (instancetype)initWithStyleURL:(NSURL*)styleURL camera:(MGLMapCamera*)camera size:(CGSize)size; + +#pragma mark - Configuring the map + +/** + The style URL for these options. + */ +@property (nonatomic, readonly) NSURL* styleURL; + +/** + The zoom. Default is 0. + */ +@property (nonatomic) double zoom; + +/** + The `MGLMapcamera` options to use. + */ +@property (nonatomic) MGLMapCamera* camera; + +/** + A region to capture. Overrides the center coordinate + in the mapCamera options if set + */ +@property (nonatomic) MGLCoordinateBounds region; + +#pragma mark - Configuring the image + +/** + The size of the output image. Minimum is 64x64 + */ +@property (nonatomic, readonly) CGSize size; + +/** + The scale of the output image. Defaults to the main screen scale. + Minimum is 1. + */ +@property (nonatomic) CGFloat scale; + +@end + +/** + A block to processes the result or error of a snapshot request. + + The result will be either an `MGLImage` or a `NSError` + + @param snapshot The image that was generated or `nil` if an error occurred. + @param error The eror that occured or `nil` when succesful. + */ +typedef void (^MGLMapSnapshotCompletionHandler)(MGLImage* _Nullable snapshot, NSError* _Nullable error); + +/** + A utility object for capturing map-based images. + */ +MGL_EXPORT +@interface MGLMapSnapshotter : NSObject + +- (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; + +/** + Starts the snapshot creation and executes the specified block with the result. + + @param completionHandler The block to handle the result in. + */ +- (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completionHandler; + +/** + Starts the snapshot creation and executes the specified block with the result on the specified queue. + + @param queue The queue to handle the result on. + @param completionHandler The block to handle the result in. + */ +- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completionHandler; + +/** + Cancels the snapshot creation request, if any. + */ +- (void)cancel; + +/** + Indicates whether as snapshot is currently being generated. + */ +@property (nonatomic, readonly, getter=isLoading) BOOL loading; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm new file mode 100644 index 0000000000..c81fd39c4a --- /dev/null +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -0,0 +1,163 @@ +#import "MGLMapSnapshotter.h" + +#import +#import +#import +#import +#import +#import +#import +#import +#import + +#import "MGLOfflineStorage_Private.h" +#import "MGLGeometry_Private.h" +#import "NSBundle+MGLAdditions.h" + +#if TARGET_OS_IPHONE +#import "UIImage+MGLAdditions.h" +#else +#import "NSImage+MGLAdditions.h" +#endif + +@implementation MGLMapSnapshotOptions + +- (instancetype _Nonnull)initWithStyleURL:(NSURL* _Nonnull)styleURL camera:(MGLMapCamera*)camera size:(CGSize) size; +{ + self = [super init]; + if (self) { + _styleURL = styleURL; + _size = size; + _camera = camera; +#if TARGET_OS_IPHONE + _scale = [UIScreen mainScreen].scale; +#else + _scale = [NSScreen mainScreen].backingScaleFactor; +#endif + + } + return self; +} + +@end + +@implementation MGLMapSnapshotter { + + std::shared_ptr mbglThreadPool; + std::unique_ptr mbglMapSnapshotter; + std::unique_ptr> snapshotCallback; +} + +- (instancetype)initWithOptions:(MGLMapSnapshotOptions*)options; +{ + self = [super init]; + if (self) { + _loading = false; + + mbgl::DefaultFileSource *mbglFileSource = [MGLOfflineStorage sharedOfflineStorage].mbglFileSource; + mbglThreadPool = mbgl::sharedThreadPool(); + + std::string styleURL = std::string([options.styleURL.absoluteString UTF8String]); + + // Size; taking into account the minimum texture size for OpenGL ES + mbgl::Size size = { + static_cast(MAX(options.size.width, 64)), + static_cast(MAX(options.size.height, 64)) + }; + + float pixelRatio = MAX(options.scale, 1); + + // Camera options + mbgl::CameraOptions cameraOptions; + if (CLLocationCoordinate2DIsValid(options.camera.centerCoordinate)) { + cameraOptions.center = MGLLatLngFromLocationCoordinate2D(options.camera.centerCoordinate); + } + cameraOptions.angle = MAX(0, options.camera.heading) * mbgl::util::DEG2RAD; + cameraOptions.zoom = MAX(0, options.zoom); + cameraOptions.pitch = MAX(0, options.camera.pitch); + + // Region + mbgl::optional region; + if (!MGLCoordinateBoundsIsEmpty(options.region)) { + region = MGLLatLngBoundsFromCoordinateBounds(options.region); + } + + // Create the snapshotter + mbglMapSnapshotter = std::make_unique(*mbglFileSource, *mbglThreadPool, styleURL, size, pixelRatio, cameraOptions, region); + } + return self; +} + +- (void)startWithCompletionHandler:(MGLMapSnapshotCompletionHandler)completion; +{ + [self startWithQueue:dispatch_get_main_queue() completionHandler:completion]; +} + +- (void)startWithQueue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion; +{ + if ([self isLoading]) { + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Already started this snapshotter"}; + NSError *error = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; + dispatch_async(queue, ^{ + completion(nil, error); + }); + return; + } + + _loading = true; + + dispatch_async(queue, ^{ + snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image) { + _loading = false; + if (mbglError) { + NSString *description = @(mbgl::util::toString(mbglError).c_str()); + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; + NSError *error = [NSError errorWithDomain:MGLErrorDomain code:1 userInfo:userInfo]; + + // Dispatch result to origin queue + dispatch_async(queue, ^{ + completion(nil, error); + }); + } else { + MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; + + // Process image watermark in a work queue + dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(workQueue, ^{ +#if TARGET_OS_IPHONE + UIImage *logoImage = [UIImage imageNamed:@"mapbox" inBundle:[NSBundle mgl_frameworkBundle] compatibleWithTraitCollection:nil]; + + UIGraphicsBeginImageContext(mglImage.size); + + [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; + [logoImage drawInRect:CGRectMake(8, mglImage.size.height - (8 + logoImage.size.height), logoImage.size.width,logoImage.size.height)]; + UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); +#else + NSImage *logoImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mgl_frameworkBundle] pathForResource:@"mapbox" ofType:@"pdf"]]; + NSImage *compositedImage = mglImage; + + [compositedImage lockFocus]; + [logoImage drawInRect:CGRectMake(8, 8, logoImage.size.width,logoImage.size.height)]; + [compositedImage unlockFocus]; +#endif + + // Dispatch result to origin queue + dispatch_async(queue, ^{ + completion(compositedImage, nil); + }); + }); + } + }); + mbglMapSnapshotter->snapshot(snapshotCallback->self()); + }); +} + +- (void)cancel; +{ + snapshotCallback.reset(); + mbglMapSnapshotter.reset(); +} + +@end -- cgit v1.2.1