text: #include <mbgl/map/map_snapshotter.hpp>
  
  #include <mbgl/actor/actor_ref.hpp>
  #include <mbgl/gfx/headless_frontend.hpp>
  #include <mbgl/map/map.hpp>
  #include <mbgl/map/map_options.hpp>
  #include <mbgl/map/transform.hpp>
  #include <mbgl/map/transform_state.hpp>
  #include <mbgl/renderer/renderer_observer.hpp>
  #include <mbgl/renderer/update_parameters.hpp>
  #include <mbgl/storage/resource_options.hpp>
  #include <mbgl/style/style.hpp>
  #include <mbgl/util/event.hpp>
  #include <mbgl/util/exception.hpp>
  #include <mbgl/util/logging.hpp>
  #include <mbgl/util/thread.hpp>
  #include <utility>
  
  namespace mbgl {
  
  // static
  MapSnapshotterObserver& MapSnapshotterObserver::nullObserver() {
      static MapSnapshotterObserver mapSnapshotterObserver;
      return mapSnapshotterObserver;
  }
  
  class ForwardingRendererObserver final : public RendererObserver {
  public:
      explicit ForwardingRendererObserver(RendererObserver& delegate_)
          : mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())), delegate(delegate_, mailbox) {}
  
      ~ForwardingRendererObserver() override { mailbox->close(); }
  
      void onInvalidate() override { delegate.invoke(&RendererObserver::onInvalidate); }
  
      void onResourceError(std::exception_ptr err) override { delegate.invoke(&RendererObserver::onResourceError, err); }
  
      void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded, bool placementChanged) override {
          delegate.invoke(&RendererObserver::onDidFinishRenderingFrame, mode, repaintNeeded, placementChanged);
      }
  
      void onStyleImageMissing(const std::string& image, const StyleImageMissingCallback& cb) override {
          delegate.invoke(&RendererObserver::onStyleImageMissing, image, cb);
      }
  
  private:
      std::shared_ptr<Mailbox> mailbox;
      ActorRef<RendererObserver> delegate;
  };
  
  class SnapshotterRenderer final : public RendererObserver {
  public:
      SnapshotterRenderer(Size size, float pixelRatio, const optional<std::string>& localFontFamily)
          : frontend(size,
                     pixelRatio,
                     gfx::HeadlessBackend::SwapBehaviour::NoFlush,
                     gfx::ContextMode::Unique,
                     localFontFamily) {}
  
      void reset() {
          hasPendingStillImageRequest = false;
          frontend.reset();
      }
  
      void onInvalidate() override { rendererObserver->onInvalidate(); }
  
      void onResourceError(std::exception_ptr err) override {
          hasPendingStillImageRequest = false;
          rendererObserver->onResourceError(err);
      }
  
      void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded, bool placementChanged) override {
          if (mode == RenderMode::Full && hasPendingStillImageRequest) {
              stillImage = frontend.readStillImage();
          }
          rendererObserver->onDidFinishRenderingFrame(mode, repaintNeeded, placementChanged);
      }
  
      void onStyleImageMissing(const std::string& id, const StyleImageMissingCallback& done) override {
          rendererObserver->onStyleImageMissing(id, done);
      }
  
      void setObserver(std::shared_ptr<RendererObserver> observer) {
          assert(observer);
          rendererObserver = std::move(observer);
          frontend.setObserver(*this);
      }
  
      void update(std::shared_ptr<UpdateParameters> params) {
          assert(params);
          hasPendingStillImageRequest = params->stillImageRequest;
          frontend.update(std::move(params));
      }
  
      void setSize(Size size) { frontend.setSize(size); }
  
      PremultipliedImage takeImage() {
          assert(stillImage.valid());
          return std::move(stillImage);
      }
  
  private:
      PremultipliedImage stillImage;
      bool hasPendingStillImageRequest = false;
      std::shared_ptr<RendererObserver> rendererObserver;
      HeadlessFrontend frontend;
  };
  
  class SnapshotterRendererFrontend final : public RendererFrontend {
  public:
      SnapshotterRendererFrontend(Size size, float pixelRatio, optional<std::string> localFontFamily)
          : renderer(std::make_unique<util::Thread<SnapshotterRenderer>>(
                "Snapshotter", size, pixelRatio, std::move(localFontFamily))) {}
  
      ~SnapshotterRendererFrontend() override = default;
  
      void reset() override { renderer->actor().invoke(&SnapshotterRenderer::reset); }
  
      void setObserver(RendererObserver& observer) override {
          renderer->actor().invoke(&SnapshotterRenderer::setObserver,
                                   std::make_unique<ForwardingRendererObserver>(observer));
      }
  
      void update(std::shared_ptr<UpdateParameters> parameters) override {
          updateParameters = std::move(parameters);
          renderer->actor().invoke(&SnapshotterRenderer::update, updateParameters);
      }
  
      void setSize(Size size) { renderer->actor().invoke(&SnapshotterRenderer::setSize, size); }
  
      const TransformState& getTransformState() const {
          assert(updateParameters);
          static TransformState defaultTransformState{};
          if (updateParameters) return updateParameters->transformState;
          return defaultTransformState;
      }
  
      PremultipliedImage takeImage() { return renderer->actor().ask(&SnapshotterRenderer::takeImage).get(); }
  
  private:
      std::shared_ptr<UpdateParameters> updateParameters;
      const std::unique_ptr<util::Thread<SnapshotterRenderer>> renderer;
  };
  
  class MapSnapshotter::Impl final : public MapObserver {
  public:
      Impl(Size size,
           float pixelRatio,
           const ResourceOptions& resourceOptions,
           MapSnapshotterObserver& observer_,
           optional<std::string> localFontFamily)
          : observer(observer_),
            frontend(size, pixelRatio, std::move(localFontFamily)),
            map(frontend,
                *this,
                MapOptions().withMapMode(MapMode::Static).withSize(size).withPixelRatio(pixelRatio),
                resourceOptions) {}
  
      void setRegion(const LatLngBounds& region) {
          mbgl::EdgeInsets insets{0, 0, 0, 0};
          std::vector<LatLng> latLngs = {region.southwest(), region.northeast()};
          map.jumpTo(map.cameraForLatLngs(latLngs, insets));
      }
  
      void snapshot(MapSnapshotter::Callback callback) {
          if (!callback) {
              Log::Error(Event::General, "MapSnapshotter::Callback is not set");
              return;
          }
  
          if (renderStillCallback) {
              callback(std::make_exception_ptr(util::MisuseException("MapSnapshotter is currently rendering an image")),
                       PremultipliedImage(),
                       {},
                       {},
                       {});
          }
  
          renderStillCallback = std::make_unique<Actor<MapSnapshotter::Callback>>(
              *Scheduler::GetCurrent(),
              [this, cb = std::move(callback)](std::exception_ptr ptr,
                                               PremultipliedImage image,
                                               Attributions attributions,
                                               PointForFn pfn,
                                               LatLngForFn latLonFn) {
                  cb(std::move(ptr), std::move(image), std::move(attributions), std::move(pfn), std::move(latLonFn));
                  renderStillCallback.reset();
              });
  
          map.renderStill([this, actorRef = renderStillCallback->self()](const std::exception_ptr& error) {
              // Create lambda that captures the current transform state
              // and can be used to translate for geographic to screen
              // coordinates
              assert(map.getCameraOptions().center);
              PointForFn pointForFn = [center = *map.getCameraOptions().center,
                                       transformState = frontend.getTransformState()](const LatLng& latLng) {
                  LatLng unwrappedLatLng = latLng.wrapped();
                  unwrappedLatLng.unwrapForShortestPath(center);
                  Transform transform{transformState};
                  return transform.latLngToScreenCoordinate(unwrappedLatLng);
              };
  
              // Create lambda that captures the current transform state
              // and can be used to translate for geographic to screen
              // coordinates
              LatLngForFn latLngForFn = [transformState =
                                             frontend.getTransformState()](const ScreenCoordinate& screenCoordinate) {
                  Transform transform{transformState};
                  return transform.screenCoordinateToLatLng(screenCoordinate);
              };
  
              // Collect all source attributions
              std::vector<std::string> attributions;
              for (auto source : map.getStyle().getSources()) {
                  auto attribution = source->getAttribution();
                  if (attribution) {
                      attributions.push_back(*attribution);
                  }
              }
  
              // Invoke callback
              actorRef.invoke(&MapSnapshotter::Callback::operator(),
                              error,
                              error ? PremultipliedImage() : frontend.takeImage(),
                              std::move(attributions),
                              std::move(pointForFn),
                              std::move(latLngForFn));
          });
      }
  
      // MapObserver overrides
      void onDidFailLoadingMap(MapLoadError, const std::string& error) override { observer.onDidFailLoadingStyle(error); }
      void onDidFinishLoadingStyle() override { observer.onDidFinishLoadingStyle(); }
      void onStyleImageMissing(const std::string& image) override { observer.onStyleImageMissing(image); }
  
      Map& getMap() { return map; }
      const Map& getMap() const { return map; }
      SnapshotterRendererFrontend& getRenderer() { return frontend; }
      void cancel() { renderStillCallback.reset(); }
  
  private:
      std::unique_ptr<Actor<MapSnapshotter::Callback>> renderStillCallback;
      MapSnapshotterObserver& observer;
      SnapshotterRendererFrontend frontend;
      Map map;
  };
  
  MapSnapshotter::MapSnapshotter(Size size,
                                 float pixelRatio,
                                 const ResourceOptions& resourceOptions,
                                 MapSnapshotterObserver& observer,
                                 optional<std::string> localFontFamily)
      : impl(std::make_unique<MapSnapshotter::Impl>(
            size, pixelRatio, resourceOptions, observer, std::move(localFontFamily))) {}
  
  MapSnapshotter::MapSnapshotter(Size size, float pixelRatio, const ResourceOptions& resourceOptions)
      : MapSnapshotter(size, pixelRatio, resourceOptions, MapSnapshotterObserver::nullObserver()) {}
  
  MapSnapshotter::~MapSnapshotter() = default;
  
  void MapSnapshotter::setStyleURL(const std::string& styleURL) {
      impl->getMap().getStyle().loadURL(styleURL);
  }
  
  std::string MapSnapshotter::getStyleURL() const {
      return impl->getMap().getStyle().getURL();
  }
  
  void MapSnapshotter::setStyleJSON(const std::string& styleJSON) {
      impl->getMap().getStyle().loadJSON(styleJSON);
  }
  
  std::string MapSnapshotter::getStyleJSON() const {
      return impl->getMap().getStyle().getJSON();
  }
  
  void MapSnapshotter::setSize(const Size& size) {
      impl->getMap().setSize(size);
      impl->getRenderer().setSize(size);
  }
  
  Size MapSnapshotter::getSize() const {
      return impl->getMap().getMapOptions().size();
  }
  
  void MapSnapshotter::setCameraOptions(const CameraOptions& options) {
      impl->getMap().jumpTo(options);
  }
  
  CameraOptions MapSnapshotter::getCameraOptions() const {
      EdgeInsets insets;
      return impl->getMap().getCameraOptions(insets);
  }
  
  void MapSnapshotter::setRegion(const LatLngBounds& region) {
      impl->setRegion(region);
  }
  
  LatLngBounds MapSnapshotter::getRegion() const {
      return impl->getMap().latLngBoundsForCamera(getCameraOptions());
  }
  
  style::Style& MapSnapshotter::getStyle() {
      return impl->getMap().getStyle();
  }
  
  const style::Style& MapSnapshotter::getStyle() const {
      return impl->getMap().getStyle();
  }
  
  void MapSnapshotter::snapshot(MapSnapshotter::Callback callback) {
      impl->snapshot(std::move(callback));
  }
  
  void MapSnapshotter::cancel() {
      impl->cancel();
  }
  
  } // namespace mbgl
  