Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Map#addImage" / "Map#removeImage" #4404

Merged
merged 19 commits into from
Mar 16, 2017
Merged

Add "Map#addImage" / "Map#removeImage" #4404

merged 19 commits into from
Mar 16, 2017

Conversation

lucaswoj
Copy link
Contributor

@lucaswoj lucaswoj commented Mar 10, 2017

This PR adds the Map#addImage and Map#removeImage methods which give users the ability to modify their sprite at runtime.

Fixes #2059

Launch Checklist

  • briefly describe the changes in this PR
  • upgrade to latest shelf-pack version
  • make SpriteAtlas fire error events
  • make SpriteAtlas fire data events
  • add Map#removeImage
  • convince existing integration tests to pass
  • add integration test for Map#removeImage
  • add integration test which uses Map#addImage to create a repeating pattern
  • document Map#addImage and Map#removeImage
  • post benchmark scores
  • manually test the debug page
  • create a rad example / demo

@lucaswoj lucaswoj force-pushed the addimage branch 2 times, most recently from 3c3df32 to e555657 Compare March 13, 2017 18:29
@lucaswoj
Copy link
Contributor Author

benchmark master 787b3a2 addimage e069dd0
map-load 115 ms 120 ms
style-load 101 ms 99 ms
buffer 1,011 ms 1,000 ms
fps 60 fps 60 fps
frame-duration 4.2 ms, 0% > 16ms 4.1 ms, 0% > 16ms
query-point 1.21 ms 0.88 ms
query-box 66.14 ms 63.76 ms
geojson-setdata-small 7 ms 3 ms
geojson-setdata-large 89 ms 162 ms

Copy link
Contributor

@anandthakker anandthakker left a comment

Choose a reason for hiding this comment

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

🎉 This is exciting! Couple questions/comments, mostly around documentation.


const rect = this.allocateImage(width, height);
if (!rect) {
this.fire('error', {error: new Error('There is not enough space to add this image.')});
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this return early?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! 😰

delete this.images[name];

if (!image) {
this.fire('error', {error: new Error('No image with this name exists.')});
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs return

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! 😰

rect,
{pixelRatio: pixelRatio, x: 0, y: 0, width: image.width * pixelRatio, height: image.width * pixelRatio},
false
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it necessary to overwrite the existing image data with an empty image? If so, then it might be good to leave a one-line comment here noting why.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added this code out of fear of edge cases with semitransparent images & linear interpolation. Testing suggests these concerns are unwarranted. Removing it for now.

@@ -14,9 +16,11 @@ class AtlasImage {
}
}

class SpriteAtlas {
class SpriteAtlas extends Evented {
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't a comment about the changes in this PR, but just noting while I'm here: having never worked in this code before, I found it confusing, at first, to make sense of how/why SpriteAtlas works. E.g., "what's with the this.copy()?", "what's a rect?", "why does AtlasImage not have a reference to the underlying data?"

Reading more widely through the codebase, I think I've satisfied my questions, but this might be a nice time to add a brief, high-level description of what SpriteAtlas is/does. OTOH ticketing that separately is also totally fair 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All fair points!

I lost about a week trying to refactor, simplify, and document our whole style image subsystem and eventually decided that the minimal patch to add this feature was worth executing.

I'll push another commit with some targeted renames & comments to better explain SpriteAtlas until we get around to a bigger cleanup.

src/ui/map.js Outdated
* @param {number} options.width The pixel width of the `ArrayBufferView` image
* @param {number} options.height The pixel height of the `ArrayBufferView` image
* @param {Object} options.pixelRatio The ratio of pixels in the `ArrayBufferView` image to physical pixels on the screen
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this could result in an error when there isn't enough space for a new image, should we document that possibility?

Copy link
Contributor

@jfirebaugh jfirebaugh left a comment

Choose a reason for hiding this comment

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

LGTM other than a few minor documentation adjustments.

src/ui/map.js Outdated
@@ -818,6 +818,30 @@ class Map extends Camera {
}

/**
* Add an image to the style. This image can be used in `icon-image`,
* `background-pattern`, `fill-patern`, and `line-pattern`.
Copy link
Contributor

Choose a reason for hiding this comment

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

fill-paternfill-pattern

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

src/ui/map.js Outdated
* @param {Object} [options] Only required if passing an `ArrayBufferView`
* @param {number} options.width The pixel width of the `ArrayBufferView` image
* @param {number} options.height The pixel height of the `ArrayBufferView` image
* @param {Object} options.pixelRatio The ratio of pixels in the `ArrayBufferView` image to physical pixels on the screen
Copy link
Contributor

Choose a reason for hiding this comment

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

{Object}{number}. Also, should this be marked as optional? It looks like the implementation will default to screen pixelRatio if it's not provided.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@lucaswoj
Copy link
Contributor Author

I think this is ready to go. How does it look @anandthakker?

this.width = width;
this.height = height;

this.atlas = new ShelfPack(width, height);
this.shelfPack = new ShelfPack(width, height);
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure about this rename. ShelfPack is more of an algorithm name than a name that describes an instance. atlas is better IMO, and consistent with the class name (SpriteAtlas) since the latter is a wrapper around a generic atlas for image sprites.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see your point and agree that the semantics of the name are questionable. My intent was to reduce the size of the vocabulary and amount of indirection in this class.

  • SpriteAtlas has a shelfPack
  • SpriteAtlas#shelfPack is an instance of ShelfPack
  • SpriteAtlas#shelfPack has bins

  • SpriteAtlas has a atlas
  • SpriteAtlas#atlas is an instance of ShelfPack
  • SpriteAtlas#atlas has bins

Copy link
Contributor Author

Choose a reason for hiding this comment

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

a wrapper around a generic atlas for image sprites.

If SpriteAtlas#atlas were a generic texture atlas, I'd be keen on calling it such. As it stands, this member only provides the "shelf pack" algorithm and not the rest of the texture atlas functionality.

@anandthakker
Copy link
Contributor

LGTM @lucaswoj

@lucaswoj lucaswoj merged commit f50a304 into master Mar 16, 2017
@lucaswoj lucaswoj deleted the addimage branch March 16, 2017 17:24
@lucaswoj
Copy link
Contributor Author

Merging and working on an example / demo as a follow-up 😄

@pwilczynski
Copy link

@lucaswoj - thank you so much, this looks awesome, and is so exciting for us. No more flickering maps for us!

andrewharvey pushed a commit to andrewharvey/mapbox-gl-js that referenced this pull request Mar 17, 2017
* Update yarn.lock files

* Add "Map#addImage"

* Make `SpriteAtlas` fire `error` and `data` events

* Add "Map#removeImage"

* Relax requirement that styles using "icon-image" must have a "sprite"

* Add "pixelRatio" parameter to "Map#addImage"

* Enable "runtime-styling/image-add" integration test

* Add "runtime-styling/image-remove" integration test

* Add "runtime-styling/image-add-pattern" integration test

* Add docs for `Map#addImage` and `Map#removeImage`

* Add explanatory comment to `SpriteAtlas`

* Rename SpriteAtlas#atlas to SpriteAtlas#shelfPack

* Eliminate `AtlasImage`

* Moar comments

* Ensure `SpriteAtlas#addImage` returns early after an error

* Misc docs improvements

* Remove logic that clears texture when image is removed

* fixup! Eliminate `AtlasImage`

* Remove addImage debug page
@ChrisLoer
Copy link
Contributor

I'm way late to this party, but how do we handle stale icons in SymbolBucket after a call to Map#removeImage? It seems like if you remove an image that's currently displayed on a tile, add another one (thus overwriting the old one's position in the SpriteAtlas), and then call redoPlacement, you'll get symbols referring to a garbage location in the SpriteAtlas. It looks like the expected behavior is if you call Map#removeImage while there are tiles displaying the image, the image should just disappear from the tiles the next time layout happens.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants