void render(Map& map, OffscreenView& view) { PremultipliedImage result; map.renderStill(view, [&](std::exception_ptr) { result = view.readStillImage(); }); while (!result.valid()) { util::RunLoop::Get()->runOnce(); } }
PremultipliedImage render(Map& map) { PremultipliedImage result; map.renderStill([&result](std::exception_ptr, PremultipliedImage&& image) { result = std::move(image); }); while (!result.size()) { util::RunLoop::Get()->runOnce(); } return result; }
PremultipliedImage premultiply(UnassociatedImage&& src) { PremultipliedImage dst; dst.size = src.size; dst.data = std::move(src.data); uint8_t* data = dst.data.get(); for (size_t i = 0; i < dst.bytes(); i += 4) { uint8_t& r = data[i + 0]; uint8_t& g = data[i + 1]; uint8_t& b = data[i + 2]; uint8_t& a = data[i + 3]; r = (r * a + 127) / 255; g = (g * a + 127) / 255; b = (b * a + 127) / 255; } return dst; }
PremultipliedImage HeadlessView::readStillImage() { assert(active); const unsigned int w = dimensions[0] * pixelRatio; const unsigned int h = dimensions[1] * pixelRatio; PremultipliedImage image { w, h }; MBGL_CHECK_ERROR(glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.data.get())); const auto stride = image.stride(); auto tmp = std::make_unique<uint8_t[]>(stride); uint8_t* rgba = image.data.get(); for (int i = 0, j = h - 1; i < j; i++, j--) { std::memcpy(tmp.get(), rgba + i * stride, stride); std::memcpy(rgba + i * stride, rgba + j * stride, stride); std::memcpy(rgba + j * stride, tmp.get(), stride); } return image; }
PremultipliedImage HeadlessView::readStillImage(std::array<uint16_t, 2> size) { assert(active); if (!size[0] || !size[1]) { size[0] = dimensions[0] * pixelRatio; size[1] = dimensions[1] * pixelRatio; } PremultipliedImage image { size[0], size[1] }; MBGL_CHECK_ERROR(glReadPixels(0, 0, size[0], size[1], GL_RGBA, GL_UNSIGNED_BYTE, image.data.get())); const auto stride = image.stride(); auto tmp = std::make_unique<uint8_t[]>(stride); uint8_t* rgba = image.data.get(); for (int i = 0, j = size[1] - 1; i < j; i++, j--) { std::memcpy(tmp.get(), rgba + i * stride, stride); std::memcpy(rgba + i * stride, rgba + j * stride, stride); std::memcpy(rgba + j * stride, tmp.get(), stride); } return image; }
// Encode PNGs without libpng. std::string encodePNG(const PremultipliedImage& pre) { // Make copy of the image so that we can unpremultiply it. const auto src = util::unpremultiply(pre.clone()); // PNG magic bytes const char preamble[8] = { char(0x89), 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' }; // IHDR chunk for our RGBA image. const char ihdr[13] = { NETWORK_BYTE_UINT32(src.size.width), // width NETWORK_BYTE_UINT32(src.size.height), // height 8, // bit depth == 8 bits 6, // color type == RGBA 0, // compression method == deflate 0, // filter method == default 0, // interlace method == none }; // Prepare the (compressed) data chunk. const auto stride = src.stride(); std::string idat; for (uint32_t y = 0; y < src.size.height; y++) { // Every scanline needs to be prefixed with one byte that indicates the filter type. idat.append(1, 0); // filter type 0 idat.append((const char*)(src.data.get() + y * stride), stride); } idat = util::compress(idat); // Assemble the PNG. std::string png; png.reserve((8 /* preamble */) + (12 + 13 /* IHDR */) + (12 + idat.size() /* IDAT */) + (12 /* IEND */)); png.append(preamble, 8); addChunk(png, "IHDR", ihdr, 13); addChunk(png, "IDAT", idat.data(), static_cast<uint32_t>(idat.size())); addChunk(png, "IEND"); return png; }
TEST(API, RepeatedRender) { using namespace mbgl; util::RunLoop loop; const auto style = util::read_file("test/fixtures/api/water.json"); auto display = std::make_shared<mbgl::HeadlessDisplay>(); HeadlessView view(display, 1, 256, 512); #ifdef MBGL_ASSET_ZIP // Regenerate with `cd test/fixtures/api/ && zip -r assets.zip assets/` DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets.zip"); #else DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); #endif Log::setObserver(std::make_unique<FixtureLogObserver>()); Map map(view, fileSource, MapMode::Still); { map.setStyleJSON(style, ""); PremultipliedImage result; map.renderStill([&result](std::exception_ptr, PremultipliedImage&& image) { result = std::move(image); }); while (!result.size()) { loop.runOnce(); } ASSERT_EQ(256, result.width); ASSERT_EQ(512, result.height); #if !TEST_READ_ONLY util::write_file("test/fixtures/api/1.png", encodePNG(result)); #endif } { map.setStyleJSON(style, ""); PremultipliedImage result; map.renderStill([&result](std::exception_ptr, PremultipliedImage&& image) { result = std::move(image); }); while (!result.size()) { loop.runOnce(); } ASSERT_EQ(256, result.width); ASSERT_EQ(512, result.height); #if !TEST_READ_ONLY util::write_file("test/fixtures/api/2.png", encodePNG(result)); #endif } auto observer = Log::removeObserver(); auto flo = dynamic_cast<FixtureLogObserver*>(observer.get()); auto unchecked = flo->unchecked(); EXPECT_TRUE(unchecked.empty()) << unchecked; }
uint64_t crc64(const PremultipliedImage &image) { return crc64(reinterpret_cast<const char*>(image.data.get()), image.size()); }
std::shared_ptr<SpriteImage> namedMarker(const std::string &name) { PremultipliedImage image = decodeImage(util::read_file("test/fixtures/sprites/" + name)); return std::make_shared<SpriteImage>(image.width, image.height, 1.0, std::string(reinterpret_cast<char*>(image.data.get()), image.size())); }