diff --git a/src/mbgl/renderer/image_atlas.cpp b/src/mbgl/renderer/image_atlas.cpp index e4586ce9005..379cc80768c 100644 --- a/src/mbgl/renderer/image_atlas.cpp +++ b/src/mbgl/renderer/image_atlas.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -63,7 +64,44 @@ void populateImagePatches(ImagePositions& imagePositions, const auto updatedImage = imageManager.getSharedImage(name); if (updatedImage == nullptr) continue; - patches.emplace_back(*updatedImage, position.paddedRect); + Immutable imagePatch = *updatedImage; + + // The max patch size that fits the current Atlas is the paddedRect size and the padding + assert(position.paddedRect.w > ImagePosition::padding * 2); + assert(position.paddedRect.h > ImagePosition::padding * 2); + uint32_t maxPatchWidth = position.paddedRect.w - ImagePosition::padding * 2; + uint32_t maxPatchHeight = position.paddedRect.h - ImagePosition::padding * 2; + if (maxPatchWidth < imagePatch->image.size.width || maxPatchHeight < imagePatch->image.size.height) { + // imagePositions are created in makeImageAtlas + // User can update the image. e.g. an Android call to + // MapLibreMap.getStyle.addImage(imageId, imageBitmap), which will call ImageManager.updateImage + // If the updated image is larger than the previous image then position.paddedRect area in the atlas + // won't fit the new image. ImageManager is unaware of the the atlas packing. + // This code simply prints an error message and resizes the image to fit the atlas to avoid crashes + // A better solution (potentially expensive) is to repack the atlas: this requires keeping the + // previous atlas image and detect when a repack is required. + // Another possibility is to simply throw an exception and requires the user to provide different + // IDs for images with different sizes + const auto& imageImpl = *updatedImage->get(); + Log::Error(Event::General, + imageImpl.id + " does not fit the atlas. " + imageImpl.id + + " will be resized from:" + std::to_string(imageImpl.image.size.width) + "x" + + std::to_string(imageImpl.image.size.height) + " to:" + std::to_string(maxPatchWidth) + + "x" + std::to_string(maxPatchHeight)); + auto resizedImage = imagePatch->image.clone(); + auto newWidth = std::min(maxPatchWidth, imagePatch->image.size.width); + auto newHeight = std::min(maxPatchHeight, imagePatch->image.size.height); + resizedImage.resize({newWidth, newHeight}); + auto mutableImagePatch = makeMutable(imageImpl.id, + std::move(resizedImage), + imageImpl.pixelRatio, + imageImpl.sdf, + style::ImageStretches(), + style::ImageStretches()); + imagePatch = std::move(mutableImagePatch); + } + + patches.emplace_back(imagePatch, position.paddedRect); position.version = version; } } diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index ee711c72b7e..5b213a56a72 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -119,15 +119,14 @@ void GeometryTileRenderData::upload(gfx::UploadPass& uploadPass) { if (atlasTextures->icon && !imagePatches.empty()) { for (const auto& imagePatch : imagePatches) { // patch updated images. + uint32_t xDest = imagePatch.paddedRect.x + ImagePosition::padding; + uint32_t yDest = imagePatch.paddedRect.y + ImagePosition::padding; + assert(xDest + imagePatch.image->image.size.width <= atlasTextures->icon->getSize().width); + assert(yDest + imagePatch.image->image.size.height <= atlasTextures->icon->getSize().height); #if MLN_DRAWABLE_RENDERER - atlasTextures->icon->uploadSubRegion(imagePatch.image->image, - imagePatch.paddedRect.x + ImagePosition::padding, - imagePatch.paddedRect.y + ImagePosition::padding); + atlasTextures->icon->uploadSubRegion(imagePatch.image->image, xDest, yDest); #else - uploadPass.updateTextureSub(*atlasTextures->icon, - imagePatch.image->image, - imagePatch.paddedRect.x + ImagePosition::padding, - imagePatch.paddedRect.y + ImagePosition::padding); + uploadPass.updateTextureSub(*atlasTextures->icon, imagePatch.image->image, xDest, yDest); #endif } imagePatches.clear();