Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

allow for user-provided symbol imagery (aka Sprites) #941

Merged
merged 41 commits into from
Jul 8, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7f705da
Sprite store API
kkaefer Jun 11, 2015
9991f9d
add RAII-style FixtureLog
kkaefer Jun 24, 2015
55ef14f
compress_png: void* => const void*
kkaefer Jun 24, 2015
a68b47b
ratio => pixelRatio
kkaefer Jun 24, 2015
a96a8fe
add a separate SpriteParser object
kkaefer Jun 24, 2015
04d57b8
check for presence of Sprite creation warnings
kkaefer Jun 24, 2015
54aaa43
add test for trying to add mismatched SpriteImage
kkaefer Jun 24, 2015
33a3388
add API for adding multiple sprites at once
kkaefer Jun 24, 2015
d880507
add missing headers
kkaefer Jun 24, 2015
086e72a
use a 64 bit CRC value instead of std::hash
kkaefer Jun 25, 2015
aa2dcf1
disallow changing an existing Sprite's size
kkaefer Jun 26, 2015
d2ee014
move SpriteAtlas to the SpriteStore backend
kkaefer Jul 1, 2015
afaf74e
use SpriteStore from the SpriteAtlas
kkaefer Jul 1, 2015
4f48874
we're no longer throwing exceptions when loading invalid sprite resou…
kkaefer Jul 2, 2015
bc41d3c
don't restrict pixelRatio in SpriteStore
kkaefer Jul 2, 2015
6271f8e
mark Image constructor as explicit to avoid accidental implicit argum…
kkaefer Jul 2, 2015
a0e391c
make sure that Sprite isn't marked as loaded until we actually added …
kkaefer Jul 2, 2015
70d0a26
key sprites in sprite atlas by name and wrap value
kkaefer Jul 2, 2015
f9601ae
add ability to set custom sprites on the Map object
kkaefer Jul 2, 2015
b2a3314
replace Style object immediately
kkaefer Jul 2, 2015
62ac43a
add ability to add random custom markers to test app
kkaefer Jul 6, 2015
746e4d2
explicitly name the sprite that couldn't be changed
kkaefer Jul 6, 2015
c5ed00a
fix sprite debug window code
kkaefer Jul 6, 2015
a57f3dd
add basic SpriteAtlas test
kkaefer Jul 6, 2015
d08ec53
add test for fractional pixel ratios
kkaefer Jul 6, 2015
3269933
add image to odd pixelRatio atlas
kkaefer Jul 6, 2015
d37bd54
make sure that the SpriteStore object correctly replaces the SpriteIm…
kkaefer Jul 6, 2015
0d4cfd5
test for sprite image that doesn't exist
kkaefer Jul 6, 2015
74b744a
test SpriteAtlas updates when SpriteStore was updated
kkaefer Jul 6, 2015
493c72b
move crc64() to test fixture
kkaefer Jul 6, 2015
c66ec87
show info message when Sprite does not exist
kkaefer Jul 6, 2015
1f4b5e7
add custom imagery visual test
kkaefer Jul 6, 2015
5ab58de
Cocoa custom marker imagery API
incanus Jul 7, 2015
5f9e675
stub annotation symbol cleanup
incanus Jul 7, 2015
e3b823b
nullability for MGLAnnotationImage
incanus Jul 7, 2015
e0da13d
fixes for runtime Cocoa imagery
incanus Jul 7, 2015
445d3af
fix nullability
incanus Jul 7, 2015
e0384a9
explicitly bring in header
incanus Jul 7, 2015
a1c2529
runtime imagery based on annotation title
incanus Jul 7, 2015
c701134
remove logging
incanus Jul 7, 2015
fb230dd
clean up remainder of sprite removal for now
incanus Jul 7, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions gyp/platform-ios.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
'../platform/ios/MGLPolygon.m',
'../include/mbgl/ios/MGLShape.h',
'../platform/ios/MGLShape.m',
'../include/mbgl/ios/MGLAnnotationImage.h',
'../platform/ios/MGLAnnotationImage.m',
'../platform/ios/NSBundle+MGLAdditions.h',
'../platform/ios/NSBundle+MGLAdditions.m',
'../platform/ios/NSException+MGLAdditions.h',
Expand Down
40 changes: 40 additions & 0 deletions include/mbgl/annotation/sprite_image.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#ifndef MBGL_ANNOTATIONS_SPRITE_IMAGE
#define MBGL_ANNOTATIONS_SPRITE_IMAGE

#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/geo.hpp>

#include <string>
#include <memory>
#include <cstdint>

namespace mbgl {

class SpriteImage : private util::noncopyable {
public:
SpriteImage(
uint16_t width, uint16_t height, float pixelRatio, std::string&& data, bool sdf = false);

// Logical dimensions of the sprite image.
const uint16_t width;
const uint16_t height;

// Pixel ratio of the sprite image.
const float pixelRatio;

// Physical dimensions of the sprite image.
const uint16_t pixelWidth;
const uint16_t pixelHeight;

// A string of an RGBA8 representation of the sprite. It must have exactly
// (width * ratio) * (height * ratio) * 4 (RGBA) bytes. The scan lines may
// not have gaps between them (i.e. stride == 0).
const std::string data;

// Whether this image should be interpreted as a signed distance field icon.
const bool sdf;
};

}

#endif
16 changes: 16 additions & 0 deletions include/mbgl/ios/MGLAnnotationImage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#import <UIKit/UIKit.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be audited for nullability. Since neither property should ever be nil, surround the header with NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END and import MGLTypes.h (to get the Xcode 6.1 compatibility shim).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grabbed this last night already: 56fbebf

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import MGLTypes.h

Is this strictly required if it's pulled in with MapboxGL.h?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, but then this header should pull in MGLTypes.h. Headers shouldn't have implicit dependencies.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


#import "MGLTypes.h"

NS_ASSUME_NONNULL_BEGIN

@interface MGLAnnotationImage : NSObject

@property (nonatomic, readonly) UIImage *image;
@property (nonatomic, readonly) NSString *reuseIdentifier;

+ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier;

@end

NS_ASSUME_NONNULL_END
21 changes: 20 additions & 1 deletion include/mbgl/ios/MGLMapView.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

NS_ASSUME_NONNULL_BEGIN

@class MGLAnnotationImage;
@class MGLUserLocation;
@class MGLPolyline;
@class MGLPolygon;
Expand Down Expand Up @@ -258,6 +259,18 @@ IB_DESIGNABLE
* @param annotations The array of annotations to remove. Objects in the array must conform to the MGLAnnotation protocol. */
- (void)removeAnnotations:(NS_ARRAY_OF(id <MGLAnnotation>) *)annotations;

/** Returns a reusable annotation image object located by its identifier.
*
* For performance reasons, you should generally reuse MGLAnnotationImage objects for annotations in your map views. Dequeueing saves time and memory during performance-critical operations such as scrolling.
*
* @param identifier A string identifying the annotation image to be reused. This string is the same one you specify when initially returning the annotation image object using the mapView:imageForAnnotation: method.
* @return An annotation image object with the specified identifier, or `nil` if no such object exists in the reuse queue. */
- (nullable MGLAnnotationImage *)dequeueReusableAnnotationImageWithIdentifier:(NSString *)identifier;

#pragma mark - Managing Annotation Selections

/** @name Managing Annotation Selections */

/** The annotations that are currently selected.
*
* Assigning a new array to this property selects only the first annotation in the array. */
Expand Down Expand Up @@ -336,7 +349,13 @@ IB_DESIGNABLE
* @param mapView The map view that requested the annotation symbol name.
* @param annotation The object representing the annotation that is about to be displayed.
* @return The marker symbol to display for the specified annotation or `nil` if you want to display the default symbol. */
- (nullable NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id <MGLAnnotation>)annotation;
- (nullable NSString *)mapView:(MGLMapView *)mapView symbolNameForAnnotation:(id <MGLAnnotation>)annotation __attribute__((unavailable("Use -mapView:imageForAnnotation:.")));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth leaving the existing symbol-based annotation API around? Seems like it’d continue to be the most convenient way to use Maki icons as annotation images (for instance).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, hadn't thought about Maki. You'd have to know which were in the style, though, as it's not all of them by default. I don't think there's much benefit, until such time that we maybe roll out an RMMarker-like constructor that lets you pass any Maki symbol name (which it would then probably fetch on demand and upload via the new runtime imagery API).


/** Returns an image object to use for the marker for the specified point annotation object.
* @param mapView The map view that requested the annotation image.
* @param annotation The object representing the annotation that is about to be displayed.
* @return The image object to display for the specified annotation or `nil` if you want to display the default marker image. */
- (nullable MGLAnnotationImage *)mapView:(MGLMapView *)mapView imageForAnnotation:(id <MGLAnnotation>)annotation;

/** Returns the alpha value to use when rendering a shape annotation. Defaults to `1.0`.
* @param mapView The map view rendering the shape annotation.
Expand Down
1 change: 1 addition & 0 deletions include/mbgl/ios/MapboxGL.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLAnnotationImage.h"
#import "MGLGeometry.h"
#import "MGLMapView.h"
#import "MGLMultiPoint.h"
Expand Down
5 changes: 5 additions & 0 deletions include/mbgl/map/map.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class View;
class MapData;
class MapContext;
class StillImage;
class SpriteImage;
class Transform;
class PointAnnotation;
class ShapeAnnotation;
Expand Down Expand Up @@ -141,6 +142,10 @@ class Map : private util::noncopyable {
AnnotationIDs getAnnotationsInBounds(const LatLngBounds&, const AnnotationType& = AnnotationType::Any);
LatLngBounds getBoundsForAnnotations(const AnnotationIDs&);

// Sprites
void setSprite(const std::string&, std::shared_ptr<const SpriteImage>);
void removeSprite(const std::string&);

// Memory
void setSourceTileCacheSize(size_t);
void onLowMemory();
Expand Down
4 changes: 4 additions & 0 deletions include/mbgl/platform/default/glfw_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,18 @@ class GLFWView : public mbgl::View {

private:
mbgl::LatLng makeRandomPoint() const;
static std::shared_ptr<const mbgl::SpriteImage>
makeSpriteImage(int width, int height, float pixelRatio);

void addRandomPointAnnotations(int count);
void addRandomShapeAnnotations(int count);
void addRandomCustomPointAnnotations(int count);

void clearAnnotations();
void popAnnotation();

mbgl::AnnotationIDs annotationIDs;
std::vector<std::string> spriteIDs;

private:
bool fullscreen = false;
Expand Down
5 changes: 5 additions & 0 deletions include/mbgl/util/exception.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ struct Exception : std::runtime_error {
inline Exception(const std::string &msg) : std::runtime_error(msg) {}
};

struct SpriteImageException : Exception {
inline SpriteImageException(const char *msg) : Exception(msg) {}
inline SpriteImageException(const std::string &msg) : Exception(msg) {}
};

struct GlyphRangeLoadingException : Exception {
inline GlyphRangeLoadingException(const char *msg) : Exception(msg) {}
inline GlyphRangeLoadingException(const std::string &msg) : Exception(msg) {}
Expand Down
4 changes: 2 additions & 2 deletions include/mbgl/util/image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
namespace mbgl {
namespace util {

std::string compress_png(int width, int height, void *rgba);
std::string compress_png(int width, int height, const void *rgba);


class Image {
public:
Image(const std::string &img);
explicit Image(const std::string &img);

inline const char *getData() const { return img.get(); }
inline uint32_t getWidth() const { return width; }
Expand Down
39 changes: 39 additions & 0 deletions ios/app/MBXViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,45 @@ - (void)dealloc

#pragma mark - MGLMapViewDelegate

- (MGLAnnotationImage *)mapView:(MGLMapView * __nonnull)mapView imageForAnnotation:(id <MGLAnnotation> __nonnull)annotation
{
NSString *title = [(MGLPointAnnotation *)annotation title];
NSString *lastTwoCharacters = [title substringFromIndex:title.length - 2];

MGLAnnotationImage *image = [mapView dequeueReusableAnnotationImageWithIdentifier:lastTwoCharacters];

if ( ! image)
{
CGRect rect = CGRectMake(0, 0, 20, 15);

UIGraphicsBeginImageContextWithOptions(rect.size, NO, [[UIScreen mainScreen] scale]);

CGContextRef ctx = UIGraphicsGetCurrentContext();

CGContextSetFillColorWithColor(ctx, [[[UIColor redColor] colorWithAlphaComponent:0.75] CGColor]);
CGContextFillRect(ctx, rect);

CGContextSetStrokeColorWithColor(ctx, [[UIColor blackColor] CGColor]);
CGContextStrokeRectWithWidth(ctx, rect, 2);

NSAttributedString *drawString = [[NSAttributedString alloc] initWithString:lastTwoCharacters attributes:@{
NSFontAttributeName: [UIFont fontWithName:@"Arial-BoldMT" size:12],
NSForegroundColorAttributeName: [UIColor whiteColor] }];
CGSize stringSize = drawString.size;
CGRect stringRect = CGRectMake((rect.size.width - stringSize.width) / 2,
(rect.size.height - stringSize.height) / 2,
stringSize.width,
stringSize.height);
[drawString drawInRect:stringRect];

image = [MGLAnnotationImage annotationImageWithImage:UIGraphicsGetImageFromCurrentImageContext() reuseIdentifier:lastTwoCharacters];

UIGraphicsEndImageContext();
}

return image;
}

- (BOOL)mapView:(__unused MGLMapView *)mapView annotationCanShowCallout:(__unused id <MGLAnnotation>)annotation
{
return YES;
Expand Down
2 changes: 1 addition & 1 deletion platform/darwin/image.mm
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace mbgl {
namespace util {

std::string compress_png(int width, int height, void *rgba) {
std::string compress_png(int width, int height, const void *rgba) {
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, rgba, width * height * 4, NULL);
if (!provider) {
return "";
Expand Down
47 changes: 47 additions & 0 deletions platform/default/glfw_view.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <mbgl/annotation/point_annotation.hpp>
#include <mbgl/annotation/shape_annotation.hpp>
#include <mbgl/annotation/sprite_image.hpp>
#include <mbgl/platform/default/glfw_view.hpp>
#include <mbgl/platform/gl.hpp>
#include <mbgl/platform/log.hpp>
Expand Down Expand Up @@ -120,6 +121,9 @@ void GLFWView::onKey(GLFWwindow *window, int key, int /*scancode*/, int action,
case GLFW_KEY_Q:
view->clearAnnotations();
break;
case GLFW_KEY_P: {
view->addRandomCustomPointAnnotations(1);
} break;
}
}

Expand Down Expand Up @@ -150,6 +154,49 @@ mbgl::LatLng GLFWView::makeRandomPoint() const {
return { lat, lon };
}

std::shared_ptr<const mbgl::SpriteImage>
GLFWView::makeSpriteImage(int width, int height, float pixelRatio) {
const int r = 255 * (double(std::rand()) / RAND_MAX);
const int g = 255 * (double(std::rand()) / RAND_MAX);
const int b = 255 * (double(std::rand()) / RAND_MAX);

const int w = std::ceil(pixelRatio * width);
const int h = std::ceil(pixelRatio * height);

std::string pixels(w * h * 4, '\x00');
auto data = reinterpret_cast<uint32_t*>(const_cast<char*>(pixels.data()));
const int dist = (w / 2) * (w / 2);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
const int dx = x - w / 2;
const int dy = y - h / 2;
const int diff = dist - (dx * dx + dy * dy);
if (diff > 0) {
const int a = std::min(0xFF, diff) * 0xFF / dist;
// Premultiply the rgb values with alpha
data[w * y + x] =
(a << 24) | ((a * r / 0xFF) << 16) | ((a * g / 0xFF) << 8) | (a * b / 0xFF);
}
}
}

return std::make_shared<mbgl::SpriteImage>(width, height, pixelRatio, std::move(pixels));
}

void GLFWView::addRandomCustomPointAnnotations(int count) {
std::vector<mbgl::PointAnnotation> points;

for (int i = 0; i < count; i++) {
static int spriteID = 1;
const auto name = std::string{ "marker-" } + std::to_string(spriteID++);
map->setSprite(name, makeSpriteImage(22, 22, 1));
spriteIDs.push_back(name);
points.emplace_back(makeRandomPoint(), name);
}

auto newIDs = map->addPointAnnotations(points);
annotationIDs.insert(annotationIDs.end(), newIDs.begin(), newIDs.end());
}

void GLFWView::addRandomPointAnnotations(int count) {
std::vector<mbgl::PointAnnotation> points;
Expand Down
2 changes: 1 addition & 1 deletion platform/default/image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const static bool png_version_check = []() {
namespace mbgl {
namespace util {

std::string compress_png(int width, int height, void *rgba) {
std::string compress_png(int width, int height, const void *rgba) {
png_voidp error_ptr = 0;
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, error_ptr, NULL, NULL);
if (!png_ptr) {
Expand Down
30 changes: 30 additions & 0 deletions platform/ios/MGLAnnotationImage.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#import "MGLAnnotationImage.h"

@interface MGLAnnotationImage ()

@property (nonatomic) UIImage *image;
@property (nonatomic) NSString *reuseIdentifier;

@end

@implementation MGLAnnotationImage

+ (instancetype)annotationImageWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier
{
return [[self alloc] initWithImage:image reuseIdentifier:reuseIdentifier];
}

- (instancetype)initWithImage:(UIImage *)image reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super init];

if (self)
{
_image = image;
_reuseIdentifier = [reuseIdentifier copy];
}

return self;
}

@end
Loading