diff options
author | John Firebaugh <john.firebaugh@gmail.com> | 2015-11-24 10:07:18 -0800 |
---|---|---|
committer | John Firebaugh <john.firebaugh@gmail.com> | 2015-11-25 15:57:36 -0800 |
commit | 0c1e378bc9555f6cf826bb38b1a36fa742f8ce9b (patch) | |
tree | 9aec8e4f475ff715645072503b3e0ec78f0573af | |
parent | 2de0a351a0635192bd05116cebdf0103c2638d05 (diff) | |
download | qtlocation-mapboxgl-0c1e378bc9555f6cf826bb38b1a36fa742f8ce9b.tar.gz |
[core] Rewrite image handling
* Consolidate Image and StillImage
* Typecheck unassociated vs premultiplied images
* Rewrite default platform image decoding implementation
34 files changed, 307 insertions, 696 deletions
diff --git a/bin/render.cpp b/bin/render.cpp index 685f3ddac4..8a8bcc1a4b 100644 --- a/bin/render.cpp +++ b/bin/render.cpp @@ -1,5 +1,4 @@ #include <mbgl/map/map.hpp> -#include <mbgl/map/still_image.hpp> #include <mbgl/util/image.hpp> #include <mbgl/util/io.hpp> @@ -105,16 +104,14 @@ int main(int argc, char *argv[]) { uv_async_t *async = new uv_async_t; uv_async_init(uv_default_loop(), async, [](UV_ASYNC_PARAMS(as)) { - std::unique_ptr<const StillImage> image(reinterpret_cast<const StillImage *>(as->data)); + std::unique_ptr<UnassociatedImage> image(reinterpret_cast<UnassociatedImage*>(as->data)); uv_close(reinterpret_cast<uv_handle_t *>(as), [](uv_handle_t *handle) { delete reinterpret_cast<uv_async_t *>(handle); }); - - const std::string png = util::compress_png(image->width, image->height, image->pixels.get()); - util::write_file(output, png); + util::write_file(output, encodePNG(*image)); }); - map.renderStill([async](std::exception_ptr error, std::unique_ptr<const StillImage> image) { + map.renderStill([async](std::exception_ptr error, UnassociatedImage&& image) { try { if (error) { std::rethrow_exception(error); @@ -124,7 +121,7 @@ int main(int argc, char *argv[]) { exit(1); } - async->data = const_cast<StillImage *>(image.release()); + async->data = new UnassociatedImage(std::move(image)); uv_async_send(async); }); diff --git a/gyp/platform-android.gypi b/gyp/platform-android.gypi index c12c7d2600..e3e1141c51 100644 --- a/gyp/platform-android.gypi +++ b/gyp/platform-android.gypi @@ -15,7 +15,6 @@ '../platform/default/thread.cpp', '../platform/default/string_stdlib.cpp', '../platform/default/image.cpp', - '../platform/default/image_reader.cpp', '../platform/default/png_reader.cpp', '../platform/default/jpeg_reader.cpp', ], diff --git a/gyp/platform-linux.gypi b/gyp/platform-linux.gypi index f614b831e5..3ee39e2b70 100644 --- a/gyp/platform-linux.gypi +++ b/gyp/platform-linux.gypi @@ -16,7 +16,6 @@ '../platform/default/asset_root.cpp', '../platform/default/thread.cpp', '../platform/default/image.cpp', - '../platform/default/image_reader.cpp', '../platform/default/png_reader.cpp', '../platform/default/jpeg_reader.cpp', ], diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index c190088088..80b50c2fa4 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -2,6 +2,7 @@ #define MBGL_MAP_MAP #include <mbgl/util/chrono.hpp> +#include <mbgl/util/image.hpp> #include <mbgl/map/update.hpp> #include <mbgl/map/mode.hpp> #include <mbgl/util/geo.hpp> @@ -21,7 +22,6 @@ class FileSource; class View; class MapData; class MapContext; -class StillImage; class SpriteImage; class Transform; class PointAnnotation; @@ -58,7 +58,7 @@ public: // Register a callback that will get called (on the render thread) when all resources have // been loaded and a complete render occurs. - using StillImageCallback = std::function<void(std::exception_ptr, std::unique_ptr<const StillImage>)>; + using StillImageCallback = std::function<void (std::exception_ptr, UnassociatedImage&&)>; void renderStill(StillImageCallback callback); // Triggers a synchronous render. diff --git a/include/mbgl/map/still_image.hpp b/include/mbgl/map/still_image.hpp deleted file mode 100644 index 36922fe95a..0000000000 --- a/include/mbgl/map/still_image.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef MBGL_MAP_STILL_IMAGE -#define MBGL_MAP_STILL_IMAGE - -#include <mbgl/util/noncopyable.hpp> - -#include <cstdint> - -namespace mbgl { - -class StillImage : util::noncopyable { -public: - size_t width = 0; - size_t height = 0; - std::unique_ptr<uint8_t[]> pixels; -}; - -} - -#endif diff --git a/include/mbgl/map/view.hpp b/include/mbgl/map/view.hpp index fadd035e49..68bde5fe30 100644 --- a/include/mbgl/map/view.hpp +++ b/include/mbgl/map/view.hpp @@ -2,14 +2,14 @@ #define MBGL_MAP_VIEW #include <mbgl/util/chrono.hpp> -#include <functional> +#include <mbgl/util/image.hpp> +#include <functional> #include <memory> namespace mbgl { class Map; -class StillImage; enum MapChange : uint8_t { MapChangeRegionWillChange = 0, @@ -70,7 +70,7 @@ public: // Reads the pixel data from the current framebuffer. If your View implementation // doesn't support reading from the framebuffer, return a null pointer. - virtual std::unique_ptr<StillImage> readStillImage(); + virtual UnassociatedImage readStillImage(); // Notifies a watcher of map x/y/scale/rotation changes. // Must only be called from the same thread that caused the change. diff --git a/include/mbgl/platform/default/headless_view.hpp b/include/mbgl/platform/default/headless_view.hpp index 50edc48428..8b25620524 100644 --- a/include/mbgl/platform/default/headless_view.hpp +++ b/include/mbgl/platform/default/headless_view.hpp @@ -39,7 +39,7 @@ public: void invalidate() override; void beforeRender() override; void afterRender() override; - std::unique_ptr<StillImage> readStillImage() override; + UnassociatedImage readStillImage() override; void resizeFramebuffer(); void resize(uint16_t width, uint16_t height); diff --git a/include/mbgl/platform/default/image_reader.hpp b/include/mbgl/platform/default/image_reader.hpp deleted file mode 100644 index e46bb08ea2..0000000000 --- a/include/mbgl/platform/default/image_reader.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef MBGL_UTIL_IMAGE_READER_HPP -#define MBGL_UTIL_IMAGE_READER_HPP - -#include <mbgl/util/noncopyable.hpp> -// stl -#include <stdexcept> -#include <string> -#include <memory> - -namespace mbgl { namespace util { - -class ImageReaderException : public std::exception -{ -private: - std::string message_; -public: - ImageReaderException(std::string const& message) - : message_(message) {} - - ~ImageReaderException() throw() {} - - virtual const char* what() const throw() - { - return message_.c_str(); - } -}; - -struct ImageReader : private noncopyable -{ - virtual unsigned width() const=0; - virtual unsigned height() const=0; - virtual std::unique_ptr<uint8_t[]> read()=0; - virtual ~ImageReader() {} -}; - -std::unique_ptr<ImageReader> getImageReader(const uint8_t* data, size_t size); - -}} - -#endif diff --git a/include/mbgl/platform/default/jpeg_reader.hpp b/include/mbgl/platform/default/jpeg_reader.hpp deleted file mode 100644 index 2f988374c3..0000000000 --- a/include/mbgl/platform/default/jpeg_reader.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef MBGL_UTIL_JPEG_READER_HPP -#define MBGL_UTIL_JPEG_READER_HPP - -#include <mbgl/platform/default/image_reader.hpp> - -// jpeg -extern "C" -{ -#include <jpeglib.h> -} - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#pragma GCC diagnostic ignored "-Wunused-local-typedefs" -#pragma GCC diagnostic ignored "-Wshadow" -#include <boost/iostreams/stream.hpp> -#pragma GCC diagnostic pop - -namespace mbgl { namespace util { - -template <typename T> -class JpegReader : public ImageReader -{ -public: - using source_type = T; - using input_stream = boost::iostreams::stream<source_type>; - const static unsigned BUF_SIZE = 4096; -private: - struct jpeg_stream_wrapper - { - jpeg_source_mgr manager; - input_stream * stream; - JOCTET buffer[BUF_SIZE]; - }; - - struct jpeg_info_guard - { - jpeg_info_guard(jpeg_decompress_struct * cinfo) - : i_(cinfo) {} - - ~jpeg_info_guard() - { - jpeg_destroy_decompress(i_); - } - jpeg_decompress_struct * i_; - }; - -private: - source_type source_; - input_stream stream_; - unsigned width_; - unsigned height_; -public: - JpegReader(const uint8_t* data, size_t size); - ~JpegReader(); - unsigned width() const; - unsigned height() const; - std::unique_ptr<uint8_t[]> read(); -private: - void init(); - static void on_error(j_common_ptr cinfo); - static void on_error_message(j_common_ptr cinfo); - static void init_source(j_decompress_ptr cinfo); - static boolean fill_input_buffer(j_decompress_ptr cinfo); - static void skip(j_decompress_ptr cinfo, long count); - static void term(j_decompress_ptr cinfo); - static void attach_stream(j_decompress_ptr cinfo, input_stream* in); -}; - -}} - -#endif // MBGL_UTIL_JPEG_READER_HPP diff --git a/include/mbgl/platform/default/png_reader.hpp b/include/mbgl/platform/default/png_reader.hpp deleted file mode 100644 index c869e78ea4..0000000000 --- a/include/mbgl/platform/default/png_reader.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef MBGL_UTIL_PNG_READER_HPP -#define MBGL_UTIL_PNG_READER_HPP - -#include <mbgl/platform/default/image_reader.hpp> - -extern "C" -{ -#include <png.h> -} - -#include <cstring> -#include <memory> - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunknown-pragmas" -#pragma GCC diagnostic ignored "-Wunused-local-typedefs" -#pragma GCC diagnostic ignored "-Wshadow" -#include <boost/iostreams/stream.hpp> -#pragma GCC diagnostic pop - -namespace mbgl { namespace util { - -template <typename T> -class PngReader : public ImageReader -{ - using source_type = T; - using input_stream = boost::iostreams::stream<source_type>; - - struct png_struct_guard - { - png_struct_guard(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr) - : p_(png_ptr_ptr), - i_(info_ptr_ptr) {} - - ~png_struct_guard() - { - png_destroy_read_struct(p_,i_,0); - } - png_structpp p_; - png_infopp i_; - }; - -private: - source_type source_; - input_stream stream_; - unsigned width_; - unsigned height_; - int bit_depth_; - int color_type_; - bool has_alpha_; -public: - PngReader(const uint8_t* data, std::size_t size); - ~PngReader(); - unsigned width() const; - unsigned height() const; - std::unique_ptr<uint8_t[]> read(); -private: - void init(); - static void png_read_data(png_structp png_ptr, png_bytep data, png_size_t length); -}; - - -}} - -#endif // MBGL_UTIL_PNG_READER_HPP diff --git a/include/mbgl/util/image.hpp b/include/mbgl/util/image.hpp index fc4078501e..0604691dd1 100644 --- a/include/mbgl/util/image.hpp +++ b/include/mbgl/util/image.hpp @@ -5,30 +5,37 @@ #include <memory> namespace mbgl { -namespace util { - -std::string compress_png(size_t width, size_t height, const uint8_t* rgba); +enum ImageAlphaMode { + Unassociated, + Premultiplied +}; +template <ImageAlphaMode Mode> class Image { public: - explicit Image(const std::string& img); + Image() {} - inline const uint8_t* getData() const { return img.get(); } - inline uint32_t getWidth() const { return width; } - inline uint32_t getHeight() const { return height; } - inline operator bool() const { return img && width && height; } + Image(size_t w, size_t h) + : width(w), + height(h), + data(std::make_unique<uint8_t[]>(size())) {} -private: - // loaded image dimensions - uint32_t width = 0, height = 0; + size_t stride() const { return width * 4; } + size_t size() const { return width * height * 4; } - // the raw image data - std::unique_ptr<uint8_t[]> img; + size_t width = 0; + size_t height = 0; + std::unique_ptr<uint8_t[]> data; }; +using UnassociatedImage = Image<ImageAlphaMode::Unassociated>; +using PremultipliedImage = Image<ImageAlphaMode::Premultiplied>; + +// TODO: don't use std::string for binary data. +PremultipliedImage decodeImage(const std::string&); +std::string encodePNG(const UnassociatedImage&); -} } #endif diff --git a/platform/darwin/image.mm b/platform/darwin/image.mm index 8965f1ea93..e7187366b9 100644 --- a/platform/darwin/image.mm +++ b/platform/darwin/image.mm @@ -9,10 +9,9 @@ #endif namespace mbgl { -namespace util { -std::string compress_png(size_t width, size_t height, const uint8_t* rgba) { - CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, rgba, width * height * 4, NULL); +std::string encodePNG(const UnassociatedImage& src) { + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, src.data.get(), src.size(), NULL); if (!provider) { return ""; } @@ -23,7 +22,7 @@ std::string compress_png(size_t width, size_t height, const uint8_t* rgba) { return ""; } - CGImageRef image = CGImageCreate(width, height, 8, 32, 4 * width, color_space, + CGImageRef image = CGImageCreate(src.width, src.height, 8, 32, 4 * src.width, color_space, kCGBitmapByteOrderDefault | kCGImageAlphaLast, provider, NULL, false, kCGRenderingIntentDefault); if (!image) { @@ -66,23 +65,23 @@ std::string compress_png(size_t width, size_t height, const uint8_t* rgba) { return result; } -Image::Image(const std::string &source_data) { +PremultipliedImage decodeImage(const std::string &source_data) { CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const unsigned char *>(source_data.data()), source_data.size(), kCFAllocatorNull); if (!data) { - return; + throw std::runtime_error("CFDataCreateWithBytesNoCopy failed"); } CGImageSourceRef image_source = CGImageSourceCreateWithData(data, NULL); if (!image_source) { CFRelease(data); - return; + throw std::runtime_error("CGImageSourceCreateWithData failed"); } CGImageRef image = CGImageSourceCreateImageAtIndex(image_source, 0, NULL); if (!image) { CFRelease(image_source); CFRelease(data); - return; + throw std::runtime_error("CGImageSourceCreateImageAtIndex failed"); } CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); @@ -90,27 +89,24 @@ Image::Image(const std::string &source_data) { CGImageRelease(image); CFRelease(image_source); CFRelease(data); - return; + throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed"); } - width = uint32_t(CGImageGetWidth(image)); - height = uint32_t(CGImageGetHeight(image)); - CGRect rect = {{ 0, 0 }, { static_cast<CGFloat>(width), static_cast<CGFloat>(height) }}; + PremultipliedImage result { CGImageGetWidth(image), CGImageGetHeight(image) }; - img = std::make_unique<uint8_t[]>(width * height * 4); - CGContextRef context = CGBitmapContextCreate(img.get(), width, height, 8, width * 4, + CGContextRef context = CGBitmapContextCreate(result.data.get(), result.width, result.height, 8, result.stride(), color_space, kCGImageAlphaPremultipliedLast); if (!context) { CGColorSpaceRelease(color_space); CGImageRelease(image); CFRelease(image_source); CFRelease(data); - width = 0; - height = 0; - return; + throw std::runtime_error("CGBitmapContextCreate failed"); } CGContextSetBlendMode(context, kCGBlendModeCopy); + + CGRect rect = {{ 0, 0 }, { static_cast<CGFloat>(result.width), static_cast<CGFloat>(result.height) }}; CGContextDrawImage(context, rect, image); CGContextRelease(context); @@ -118,7 +114,8 @@ Image::Image(const std::string &source_data) { CGImageRelease(image); CFRelease(image_source); CFRelease(data); + + return result; } } -}
\ No newline at end of file diff --git a/platform/default/headless_view.cpp b/platform/default/headless_view.cpp index fa83f65967..d96b3ffcd1 100644 --- a/platform/default/headless_view.cpp +++ b/platform/default/headless_view.cpp @@ -3,9 +3,6 @@ #include <mbgl/platform/default/headless_display.hpp> #include <mbgl/platform/log.hpp> -#include <mbgl/map/still_image.hpp> - - #include <stdexcept> #include <sstream> #include <string> @@ -170,29 +167,25 @@ void HeadlessView::resize(const uint16_t width, const uint16_t height) { needsResize = true; } -std::unique_ptr<StillImage> HeadlessView::readStillImage() { +UnassociatedImage HeadlessView::readStillImage() { assert(isActive()); const unsigned int w = dimensions[0] * pixelRatio; const unsigned int h = dimensions[1] * pixelRatio; - auto image = std::make_unique<StillImage>(); - image->width = w; - image->height = h; - image->pixels = std::make_unique<uint8_t[]>(w * h * 4); - - MBGL_CHECK_ERROR(glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels.get())); + UnassociatedImage image { w, h }; + MBGL_CHECK_ERROR(glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.data.get())); - const int stride = w * 4; - auto tmp = std::make_unique<char[]>(stride); - char *rgba = reinterpret_cast<char *>(image->pixels.get()); + const int 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; + return std::move(image); } void HeadlessView::clearBuffers() { diff --git a/platform/default/image.cpp b/platform/default/image.cpp index 182b7d4c01..dc28c68ef9 100644 --- a/platform/default/image.cpp +++ b/platform/default/image.cpp @@ -1,16 +1,8 @@ #include <mbgl/util/image.hpp> -#include <mbgl/platform/log.hpp> #include <mbgl/util/string.hpp> #include <png.h> -#include <cassert> -#include <cstdlib> -#include <stdexcept> -#include <cstring> - -#include <mbgl/platform/default/image_reader.hpp> - #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-variable" // Check png library version. @@ -25,32 +17,28 @@ const static bool png_version_check = []() { return true; }(); #pragma GCC diagnostic pop - namespace mbgl { -namespace util { -std::string compress_png(size_t width, size_t height, const uint8_t* rgba) { +std::string encodePNG(const UnassociatedImage& src) { png_voidp error_ptr = 0; png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, error_ptr, NULL, NULL); if (!png_ptr) { - Log::Error(Event::Image, "couldn't create png_ptr"); - return ""; + throw std::runtime_error("couldn't create png_ptr"); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!png_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)0); - Log::Error(Event::Image, "couldn't create info_ptr"); - return ""; + throw std::runtime_error("couldn't create info_ptr"); } - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, + png_set_IHDR(png_ptr, info_ptr, src.width, src.height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); jmp_buf *jmp_context = (jmp_buf *)png_get_error_ptr(png_ptr); if (jmp_context) { png_destroy_write_struct(&png_ptr, &info_ptr); - return ""; + throw std::runtime_error("png error"); } std::string result; @@ -63,10 +51,10 @@ std::string compress_png(size_t width, size_t height, const uint8_t* rgba) { ptrs(size_t count) : rows(new png_bytep[count]) {} ~ptrs() { delete[] rows; } png_bytep *rows = nullptr; - } pointers(height); + } pointers(src.height); - for (size_t i = 0; i < height; i++) { - pointers.rows[i] = (png_bytep)((png_bytep)rgba + width * 4 * i); + for (size_t i = 0; i < src.height; i++) { + pointers.rows[i] = src.data.get() + src.stride() * i; } png_set_rows(png_ptr, info_ptr, pointers.rows); @@ -76,31 +64,28 @@ std::string compress_png(size_t width, size_t height, const uint8_t* rgba) { return result; } -Image::Image(std::string const& data) -{ - try - { - auto reader = getImageReader(reinterpret_cast<const uint8_t*>(data.c_str()), data.size()); - width = reader->width(); - height = reader->height(); - img = reader->read(); - } - catch (ImageReaderException const& ex) - { - Log::Error(Event::Image, "%s", ex.what()); - img.reset(); - width = 0; - height = 0; +PremultipliedImage decodePNG(const uint8_t*, size_t); +PremultipliedImage decodeJPEG(const uint8_t*, size_t); +PremultipliedImage decodeImage(const std::string& string) { + const uint8_t* data = reinterpret_cast<const uint8_t*>(string.data()); + const size_t size = string.size(); + + if (size >= 4) { + unsigned int magic = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; + if (magic == 0x89504E47U) { + return decodePNG(data, size); + } } - catch (...) // catch the rest - { - Log::Error(Event::Image, "exception in constructor"); - img.reset(); - width = 0; - height = 0; + + if (size >= 2) { + unsigned int magic = ((data[0] << 8) | data[1]) & 0xffff; + if (magic == 0xffd8) { + return decodeJPEG(data, size); + } } -} + throw std::runtime_error("unsupported image type"); } + } diff --git a/platform/default/image_reader.cpp b/platform/default/image_reader.cpp deleted file mode 100644 index 3d27db58c3..0000000000 --- a/platform/default/image_reader.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include <mbgl/platform/default/image_reader.hpp> -#include <mbgl/platform/default/png_reader.hpp> -#include <mbgl/platform/default/jpeg_reader.hpp> - -#include <boost/optional.hpp> -#include <boost/iostreams/device/array.hpp> - -namespace mbgl { namespace util { - -inline boost::optional<std::string> type_from_bytes(const uint8_t* data, size_t size) -{ - using result_type = boost::optional<std::string>; - if (size >= 4) - { - unsigned int magic = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; - if (magic == 0x89504E47U) - { - return result_type("png"); - } - else if (magic == 0x49492A00 || magic == 0x4D4D002A) - { - return result_type("tiff"); - } - } - if (size>=2) - { - unsigned int magic = ((data[0] << 8) | data[1]) & 0xffff; - if (magic == 0xffd8) - { - return result_type("jpeg"); - } - } - - if (size>=12) - { - if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F' && - data[8] == 'W' && data[9] == 'E' && data[10] == 'B' && data[11] == 'P') - { - return result_type("webp"); - } - } - return result_type(); -} - -std::unique_ptr<ImageReader> getImageReader(const uint8_t* data, size_t size) -{ - boost::optional<std::string> type = type_from_bytes(data, size); - if (type) - { - if (*type == "png") - { - return std::make_unique<PngReader<boost::iostreams::array_source>>(data, size); - } - else if (*type == "jpeg") - { - return std::make_unique<JpegReader<boost::iostreams::array_source>>(data, size); - } - } - throw ImageReaderException("ImageReader: can't determine type from input data"); -} - -}} diff --git a/platform/default/jpeg_reader.cpp b/platform/default/jpeg_reader.cpp index a537eeeb08..73b0b9c1b2 100644 --- a/platform/default/jpeg_reader.cpp +++ b/platform/default/jpeg_reader.cpp @@ -1,43 +1,39 @@ -#include <mbgl/platform/default/jpeg_reader.hpp> +#include <mbgl/util/image.hpp> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#pragma GCC diagnostic ignored "-Wshadow" +#include <boost/iostreams/stream.hpp> +#pragma GCC diagnostic pop -// boost #include <boost/iostreams/device/file.hpp> #include <boost/iostreams/device/array.hpp> +extern "C" +{ +#include <jpeglib.h> +} -// std -#include <cstdio> -#include <memory> +namespace mbgl { -namespace mbgl { namespace util { +using source_type = boost::iostreams::array_source; +using input_stream = boost::iostreams::stream<source_type>; -// ctor -template <typename T> -JpegReader<T>::JpegReader(const uint8_t* data, size_t size) - : source_(reinterpret_cast<const char*>(data), size), - stream_(source_), - width_(0), - height_(0) -{ - if (!stream_) throw ImageReaderException("cannot open image stream"); - init(); -} +const static unsigned BUF_SIZE = 4096; -// dtor -template <typename T> -JpegReader<T>::~JpegReader() {} +struct jpeg_stream_wrapper { + jpeg_source_mgr manager; + input_stream * stream; + JOCTET buffer[BUF_SIZE]; +}; -// jpeg stream wrapper -template <typename T> -void JpegReader<T>::init_source (j_decompress_ptr cinfo) -{ +static void init_source(j_decompress_ptr cinfo) { jpeg_stream_wrapper* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); wrap->stream->seekg(0,std::ios_base::beg); } -template <typename T> -boolean JpegReader<T>::fill_input_buffer (j_decompress_ptr cinfo) -{ +static boolean fill_input_buffer(j_decompress_ptr cinfo) { jpeg_stream_wrapper* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); wrap->stream->read(reinterpret_cast<char*>(&wrap->buffer[0]),BUF_SIZE); std::streamsize size = wrap->stream->gcount(); @@ -46,9 +42,7 @@ boolean JpegReader<T>::fill_input_buffer (j_decompress_ptr cinfo) return (size > 0) ? TRUE : FALSE; } -template <typename T> -void JpegReader<T>::skip(j_decompress_ptr cinfo, long count) -{ +static void skip(j_decompress_ptr cinfo, long count) { if (count <= 0) return; //A zero or negative skip count should be treated as a no-op. jpeg_stream_wrapper* wrap = reinterpret_cast<jpeg_stream_wrapper*>(cinfo->src); @@ -66,21 +60,14 @@ void JpegReader<T>::skip(j_decompress_ptr cinfo, long count) } } -template <typename T> -void JpegReader<T>::term (j_decompress_ptr /*cinfo*/) -{ -// no-op -} +static void term(j_decompress_ptr) {} -template <typename T> -void JpegReader<T>::attach_stream (j_decompress_ptr cinfo, input_stream* in) -{ - if (cinfo->src == 0) - { +static void attach_stream(j_decompress_ptr cinfo, input_stream* in) { + if (cinfo->src == 0) { cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(jpeg_stream_wrapper)); } - JpegReader::jpeg_stream_wrapper * src = reinterpret_cast<JpegReader::jpeg_stream_wrapper*> (cinfo->src); + jpeg_stream_wrapper * src = reinterpret_cast<jpeg_stream_wrapper*> (cinfo->src); src->manager.init_source = init_source; src->manager.fill_input_buffer = fill_input_buffer; src->manager.skip_input_data = skip; @@ -91,22 +78,29 @@ void JpegReader<T>::attach_stream (j_decompress_ptr cinfo, input_stream* in) src->stream = in; } -template <typename T> -void JpegReader<T>::on_error(j_common_ptr /*cinfo*/) -{ -} +static void on_error(j_common_ptr) {} -template <typename T> -void JpegReader<T>::on_error_message(j_common_ptr cinfo) -{ +static void on_error_message(j_common_ptr cinfo) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); - throw ImageReaderException(std::string("JPEG Reader: libjpeg could not read image: ") + buffer); + throw std::runtime_error(std::string("JPEG Reader: libjpeg could not read image: ") + buffer); } -template <typename T> -void JpegReader<T>::init() -{ +struct jpeg_info_guard { + jpeg_info_guard(jpeg_decompress_struct* cinfo) + : i_(cinfo) {} + + ~jpeg_info_guard() { + jpeg_destroy_decompress(i_); + } + + jpeg_decompress_struct* i_; +}; + +PremultipliedImage decodeJPEG(const uint8_t* data, size_t size) { + source_type source(reinterpret_cast<const char*>(data), size); + input_stream stream(source); + jpeg_decompress_struct cinfo; jpeg_info_guard iguard(&cinfo); jpeg_error_mgr jerr; @@ -114,91 +108,52 @@ void JpegReader<T>::init() jerr.error_exit = on_error; jerr.output_message = on_error_message; jpeg_create_decompress(&cinfo); - attach_stream(&cinfo, &stream_); + attach_stream(&cinfo, &stream); + int ret = jpeg_read_header(&cinfo, TRUE); if (ret != JPEG_HEADER_OK) - throw ImageReaderException("JPEG Reader: failed to read header"); + throw std::runtime_error("JPEG Reader: failed to read header"); + jpeg_start_decompress(&cinfo); - width_ = cinfo.output_width; - height_ = cinfo.output_height; if (cinfo.out_color_space == JCS_UNKNOWN) - { - throw ImageReaderException("JPEG Reader: failed to read unknown color space"); - } - if (cinfo.output_width == 0 || cinfo.output_height == 0) - { - throw ImageReaderException("JPEG Reader: failed to read image size of"); - } -} + throw std::runtime_error("JPEG Reader: failed to read unknown color space"); -template <typename T> -unsigned JpegReader<T>::width() const -{ - return width_; -} + if (cinfo.output_width == 0 || cinfo.output_height == 0) + throw std::runtime_error("JPEG Reader: failed to read image size"); -template <typename T> -unsigned JpegReader<T>::height() const -{ - return height_; -} + size_t width = cinfo.output_width; + size_t height = cinfo.output_height; + size_t components = cinfo.output_components; + size_t rowStride = components * width; -template <typename T> -std::unique_ptr<uint8_t[]> JpegReader<T>::read() -{ - std::unique_ptr<uint8_t[]> image = std::make_unique<uint8_t[]>(width_ * height_ * 4); + PremultipliedImage image { width, height }; + uint8_t* dst = image.data.get(); - stream_.clear(); - stream_.seekg(0, std::ios_base::beg); + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, rowStride, 1); - jpeg_decompress_struct cinfo; - jpeg_info_guard iguard(&cinfo); - jpeg_error_mgr jerr; - cinfo.err = jpeg_std_error(&jerr); - jerr.error_exit = on_error; - jerr.output_message = on_error_message; - jpeg_create_decompress(&cinfo); - attach_stream(&cinfo, &stream_); - int ret = jpeg_read_header(&cinfo, TRUE); - if (ret != JPEG_HEADER_OK) throw ImageReaderException("JPEG Reader read(): failed to read header"); - jpeg_start_decompress(&cinfo); - JSAMPARRAY buffer; - int row_stride; - unsigned char r,g,b; - row_stride = cinfo.output_width * cinfo.output_components; - buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); - - const std::unique_ptr<unsigned int[]> out_row(new unsigned int[width_]); - unsigned row = 0; - while (cinfo.output_scanline < cinfo.output_height) - { + while (cinfo.output_scanline < cinfo.output_height) { jpeg_read_scanlines(&cinfo, buffer, 1); - if (row < height_) - { - for (unsigned int x = 0; x < width_; ++x) - { - unsigned col = x; - r = buffer[0][cinfo.output_components * col]; - if (cinfo.output_components > 2) - { - g = buffer[0][cinfo.output_components * col + 1]; - b = buffer[0][cinfo.output_components * col + 2]; - } else { - g = r; - b = r; - } - out_row[x] = (0xff << 24) | (b << 16) | (g << 8) | r; + + for (size_t i = 0; i < width; ++i) { + dst[0] = buffer[0][components * i]; + dst[3] = 0xFF; + + if (components > 2) { + dst[1] = buffer[0][components * i + 1]; + dst[2] = buffer[0][components * i + 2]; + } else { + dst[1] = dst[0]; + dst[2] = dst[0]; } - std::copy((char*)out_row.get(), (char*)out_row.get() + width_*4, image.get() + row*width_*4); + + dst += 4; } - ++row; } + jpeg_finish_decompress(&cinfo); - return image; + return std::move(image); } -template class JpegReader<boost::iostreams::array_source>; - -}} +} diff --git a/platform/default/png_reader.cpp b/platform/default/png_reader.cpp index 1f7207337c..7577d9725c 100644 --- a/platform/default/png_reader.cpp +++ b/platform/default/png_reader.cpp @@ -1,35 +1,35 @@ -#include <mbgl/platform/default/png_reader.hpp> +#include <mbgl/util/image.hpp> #include <mbgl/platform/log.hpp> -#include <iostream> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#pragma GCC diagnostic ignored "-Wshadow" +#include <boost/iostreams/stream.hpp> +#pragma GCC diagnostic pop + +#include <boost/iostreams/device/file.hpp> +#include <boost/iostreams/device/array.hpp> + extern "C" { #include <png.h> } -// boost -#include <boost/iostreams/device/file.hpp> -#include <boost/iostreams/device/array.hpp> -#include <boost/iostreams/stream.hpp> - -// stl -#include <cstring> -#include <memory> +namespace mbgl { -namespace mbgl { namespace util { +using source_type = boost::iostreams::array_source; +using input_stream = boost::iostreams::stream<source_type>; -void user_error_fn(png_structp /*png_ptr*/, png_const_charp error_msg) -{ - throw ImageReaderException(std::string("failed to read invalid png: '") + error_msg + "'"); +static void user_error_fn(png_structp, png_const_charp error_msg) { + throw std::runtime_error(std::string("failed to read invalid png: '") + error_msg + "'"); } -void user_warning_fn(png_structp /*png_ptr*/, png_const_charp warning_msg) -{ +static void user_warning_fn(png_structp, png_const_charp warning_msg) { Log::Warning(Event::Image, "ImageReader (PNG): %s", warning_msg); } -template <typename T> -void PngReader<T>::png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) -{ +static void png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) { input_stream * fin = reinterpret_cast<input_stream*>(png_get_io_ptr(png_ptr)); fin->read(reinterpret_cast<char*>(data), length); std::streamsize read_count = fin->gcount(); @@ -39,122 +39,74 @@ void PngReader<T>::png_read_data(png_structp png_ptr, png_bytep data, png_size_t } } -template <typename T> -PngReader<T>::PngReader(const uint8_t* data, std::size_t size) - : source_(reinterpret_cast<const char*>(data), size), - stream_(source_), - width_(0), - height_(0), - bit_depth_(0), - color_type_(0), - has_alpha_(false) -{ +struct png_struct_guard { + png_struct_guard(png_structpp png_ptr_ptr, png_infopp info_ptr_ptr) + : p_(png_ptr_ptr), + i_(info_ptr_ptr) {} - if (!stream_) throw ImageReaderException("PNG reader: cannot open image stream"); - init(); -} + ~png_struct_guard() { + png_destroy_read_struct(p_,i_,0); + } + png_structpp p_; + png_infopp i_; +}; -template <typename T> -PngReader<T>::~PngReader() {} +PremultipliedImage decodePNG(const uint8_t* data, size_t size) { + source_type source(reinterpret_cast<const char*>(data), size); + input_stream stream(source); + png_byte header[8] = { 0 }; + stream.read(reinterpret_cast<char*>(header), 8); + if (stream.gcount() != 8) + throw std::runtime_error("PNG reader: Could not read image"); -template <typename T> -void PngReader<T>::init() -{ - png_byte header[8]; - std::memset(header,0,8); - stream_.read(reinterpret_cast<char*>(header),8); - if ( stream_.gcount() != 8) - { - throw ImageReaderException("PNG reader: Could not read image"); - } - int is_png=!png_sig_cmp(header,0,8); + int is_png = !png_sig_cmp(header, 0, 8); if (!is_png) - { - throw ImageReaderException("File or stream is not a png"); - } - png_structp png_ptr = png_create_read_struct - (PNG_LIBPNG_VER_STRING,0,0,0); + throw std::runtime_error("File or stream is not a png"); + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) - { - throw ImageReaderException("failed to allocate png_ptr"); - } + throw std::runtime_error("failed to allocate png_ptr"); // catch errors in a custom way to avoid the need for setjmp png_set_error_fn(png_ptr, png_get_error_ptr(png_ptr), user_error_fn, user_warning_fn); png_infop info_ptr; - png_struct_guard sguard(&png_ptr,&info_ptr); + png_struct_guard sguard(&png_ptr, &info_ptr); info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) throw ImageReaderException("failed to create info_ptr"); + if (!info_ptr) + throw std::runtime_error("failed to create info_ptr"); - png_set_read_fn(png_ptr, (png_voidp)&stream_, png_read_data); - - png_set_sig_bytes(png_ptr,8); + png_set_read_fn(png_ptr, &stream, png_read_data); + png_set_sig_bytes(png_ptr, 8); png_read_info(png_ptr, info_ptr); - png_uint_32 w, h; - png_get_IHDR(png_ptr, info_ptr, &w, &h, &bit_depth_, &color_type_,0,0,0); - has_alpha_ = (color_type_ & PNG_COLOR_MASK_ALPHA) || png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS); - width_=w; - height_=h; -} + unsigned width = 0; + unsigned height = 0; + int bit_depth = 0; + int color_type = 0; + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, 0, 0, 0); -template <typename T> -unsigned PngReader<T>::width() const -{ - return width_; -} + PremultipliedImage image { width, height }; -template <typename T> -unsigned PngReader<T>::height() const -{ - return height_; -} - -template <typename T> -std::unique_ptr<uint8_t[]> PngReader<T>::read() -{ - std::unique_ptr<uint8_t[]> image = std::make_unique<uint8_t[]>(width_ * height_ * 4); - - stream_.clear(); - stream_.seekg(0, std::ios_base::beg); - - png_structp png_ptr = png_create_read_struct - (PNG_LIBPNG_VER_STRING,0,0,0); - - if (!png_ptr) - { - throw ImageReaderException("failed to allocate png_ptr"); - } - - // catch errors in a custom way to avoid the need for setjmp - png_set_error_fn(png_ptr, png_get_error_ptr(png_ptr), user_error_fn, user_warning_fn); - - png_infop info_ptr; - png_struct_guard sguard(&png_ptr,&info_ptr); - info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) throw ImageReaderException("failed to create info_ptr"); - - png_set_read_fn(png_ptr, (png_voidp)&stream_, png_read_data); - png_read_info(png_ptr, info_ptr); - - if (color_type_ == PNG_COLOR_TYPE_PALETTE) + if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_expand(png_ptr); - if (color_type_ == PNG_COLOR_TYPE_GRAY && bit_depth_ < 8) + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand(png_ptr); + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) png_set_expand(png_ptr); - if (bit_depth_ == 16) + + if (bit_depth == 16) png_set_strip_16(png_ptr); - if (color_type_ == PNG_COLOR_TYPE_GRAY || - color_type_ == PNG_COLOR_TYPE_GRAY_ALPHA) + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); - // quick hack -- only work in >=libpng 1.2.7 - png_set_add_alpha(png_ptr,0xff,PNG_FILLER_AFTER); //rgba + png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER); double gamma; if (png_get_gAMA(png_ptr, info_ptr, &gamma)) @@ -164,19 +116,20 @@ std::unique_ptr<uint8_t[]> PngReader<T>::read() png_set_alpha_mode(png_ptr, PNG_ALPHA_PREMULTIPLIED, PNG_GAMMA_LINEAR); #endif - if (png_get_interlace_type(png_ptr,info_ptr) == PNG_INTERLACE_ADAM7) - { + if (png_get_interlace_type(png_ptr,info_ptr) == PNG_INTERLACE_ADAM7) { png_set_interlace_handling(png_ptr); // FIXME: libpng bug? // according to docs png_read_image // "..automatically handles interlacing, // so you don't need to call png_set_interlace_handling()" } + png_read_update_info(png_ptr, info_ptr); + // we can read whole image at once // alloc row pointers - const std::unique_ptr<png_bytep[]> rows(new png_bytep[height_]); - for (unsigned row = 0; row < height_; ++row) - rows[row] = (png_bytep)image.get() + row * width_ * 4 ; + const std::unique_ptr<png_bytep[]> rows(new png_bytep[height]); + for (unsigned row = 0; row < height; ++row) + rows[row] = image.data.get() + row * width * 4; png_read_image(png_ptr, rows.get()); #ifndef PNG_ALPHA_PREMULTIPLIED @@ -192,11 +145,9 @@ std::unique_ptr<uint8_t[]> PngReader<T>::read() } #endif - png_read_end(png_ptr,0); + png_read_end(png_ptr, 0); return image; } -template class PngReader<boost::iostreams::array_source>; - -}} +} diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index eb92205487..0ce7d9398b 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -3,7 +3,6 @@ #include "node_mapbox_gl_native.hpp" #include <mbgl/platform/default/headless_display.hpp> -#include <mbgl/map/still_image.hpp> #include <mbgl/util/exception.hpp> #include <mbgl/util/work_request.hpp> @@ -281,7 +280,7 @@ NAN_METHOD(NodeMap::Render) { auto options = ParseOptions(info[0]->ToObject()); assert(!nodeMap->callback); - assert(!nodeMap->image); + assert(!nodeMap->image.data); nodeMap->callback = std::make_unique<Nan::Callback>(info[1].As<v8::Function>()); try { @@ -301,12 +300,12 @@ void NodeMap::startRender(std::unique_ptr<NodeMap::RenderOptions> options) { map->setBearing(options->bearing); map->setPitch(options->pitch); - map->renderStill([this](const std::exception_ptr eptr, std::unique_ptr<const mbgl::StillImage> result) { + map->renderStill([this](const std::exception_ptr eptr, mbgl::UnassociatedImage&& result) { if (eptr) { error = std::move(eptr); uv_async_send(async); } else { - assert(!image); + assert(!image.data); image = std::move(result); uv_async_send(async); } @@ -338,7 +337,7 @@ void NodeMap::renderFinished() { // These have to be empty to be prepared for the next render call. assert(!callback); - assert(!image); + assert(!image.data); if (error) { std::string errorMessage; @@ -358,17 +357,16 @@ void NodeMap::renderFinished() { assert(!error); cb->Call(1, argv); - } else if (img) { + } else if (img.data) { v8::Local<v8::Object> pixels = Nan::NewBuffer( - reinterpret_cast<char *>(img->pixels.get()), img->width * img->height * 4, - - // Retain the StillImage object until the buffer is deleted. - [](char *, void *hint) { - delete reinterpret_cast<const mbgl::StillImage *>(hint); + reinterpret_cast<char *>(img.data.get()), img.size(), + // Retain the data until the buffer is deleted. + [](char *, void * hint) { + delete [] reinterpret_cast<uint8_t*>(hint); }, - const_cast<mbgl::StillImage *>(img.get()) + img.data.get() ).ToLocalChecked(); - img.release(); + img.data.release(); v8::Local<v8::Value> argv[] = { Nan::Null(), diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp index f610408d3f..455d38e957 100644 --- a/platform/node/src/node_map.hpp +++ b/platform/node/src/node_map.hpp @@ -49,7 +49,7 @@ public: std::unique_ptr<mbgl::Map> map; std::exception_ptr error; - std::unique_ptr<const mbgl::StillImage> image; + mbgl::UnassociatedImage image; std::unique_ptr<Nan::Callback> callback; // Async for delivering the notifications of render completion. diff --git a/src/mbgl/map/map_context.cpp b/src/mbgl/map/map_context.cpp index cf06dd06eb..f00b3eaf55 100644 --- a/src/mbgl/map/map_context.cpp +++ b/src/mbgl/map/map_context.cpp @@ -1,7 +1,6 @@ #include <mbgl/map/map_context.hpp> #include <mbgl/map/map_data.hpp> #include <mbgl/map/view.hpp> -#include <mbgl/map/still_image.hpp> #include <mbgl/platform/log.hpp> @@ -200,22 +199,22 @@ void MapContext::renderStill(const TransformState& state, const FrameData& frame } if (data.mode != MapMode::Still) { - fn(std::make_exception_ptr(util::MisuseException("Map is not in still image render mode")), nullptr); + fn(std::make_exception_ptr(util::MisuseException("Map is not in still image render mode")), {}); return; } if (callback) { - fn(std::make_exception_ptr(util::MisuseException("Map is currently rendering an image")), nullptr); + fn(std::make_exception_ptr(util::MisuseException("Map is currently rendering an image")), {}); return; } if (!style) { - fn(std::make_exception_ptr(util::MisuseException("Map doesn't have a style")), nullptr); + fn(std::make_exception_ptr(util::MisuseException("Map doesn't have a style")), {}); return; } if (style->getLastError()) { - fn(style->getLastError(), nullptr); + fn(style->getLastError(), {}); return; } @@ -246,7 +245,7 @@ bool MapContext::renderSync(const TransformState& state, const FrameData& frame) painter->render(*style, frame); if (data.mode == MapMode::Still) { - callback(nullptr, view.readStillImage()); + callback(nullptr, std::move(view.readStillImage())); callback = nullptr; } @@ -320,7 +319,7 @@ void MapContext::onResourceLoadingFailed(std::exception_ptr error) { assert(util::ThreadContext::currentlyOn(util::ThreadType::Map)); if (data.mode == MapMode::Still && callback) { - callback(error, nullptr); + callback(error, {}); callback = nullptr; } } diff --git a/src/mbgl/map/view.cpp b/src/mbgl/map/view.cpp index 5da290534a..fa8b7584cc 100644 --- a/src/mbgl/map/view.cpp +++ b/src/mbgl/map/view.cpp @@ -1,6 +1,5 @@ #include <mbgl/map/view.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/map/still_image.hpp> #include <cassert> @@ -11,8 +10,8 @@ void View::initialize(Map *map_) { map = map_; } -std::unique_ptr<StillImage> View::readStillImage() { - return nullptr; +UnassociatedImage View::readStillImage() { + return {}; } void View::notifyMapChange(MapChange) { diff --git a/src/mbgl/renderer/raster_bucket.cpp b/src/mbgl/renderer/raster_bucket.cpp index 4a7dfeda05..37e5c31cc4 100644 --- a/src/mbgl/renderer/raster_bucket.cpp +++ b/src/mbgl/renderer/raster_bucket.cpp @@ -23,7 +23,7 @@ void RasterBucket::render(Painter& painter, painter.renderRaster(*this, dynamic_cast<const RasterLayer&>(layer), id, matrix); } -bool RasterBucket::setImage(std::unique_ptr<util::Image> image) { +bool RasterBucket::setImage(PremultipliedImage image) { return raster.load(std::move(image)); } diff --git a/src/mbgl/renderer/raster_bucket.hpp b/src/mbgl/renderer/raster_bucket.hpp index 2882a94bbd..e12257d864 100644 --- a/src/mbgl/renderer/raster_bucket.hpp +++ b/src/mbgl/renderer/raster_bucket.hpp @@ -18,7 +18,7 @@ public: void render(Painter&, const StyleLayer&, const TileID&, const mat4&) override; bool hasData() const override; - bool setImage(std::unique_ptr<util::Image> image); + bool setImage(PremultipliedImage); void drawRaster(RasterShader& shader, StaticVertexBuffer &vertices, VertexArrayObject &array); diff --git a/src/mbgl/sprite/sprite_parser.cpp b/src/mbgl/sprite/sprite_parser.cpp index a8ed4f0e12..3b6464be9e 100644 --- a/src/mbgl/sprite/sprite_parser.cpp +++ b/src/mbgl/sprite/sprite_parser.cpp @@ -14,7 +14,7 @@ namespace mbgl { -SpriteImagePtr createSpriteImage(const util::Image& image, +SpriteImagePtr createSpriteImage(const PremultipliedImage& image, const uint16_t srcX, const uint16_t srcY, const uint16_t srcWidth, @@ -37,18 +37,18 @@ SpriteImagePtr createSpriteImage(const util::Image& image, std::string data(dstWidth * dstHeight * 4, '\0'); - auto srcData = reinterpret_cast<const uint32_t*>(image.getData()); + auto srcData = reinterpret_cast<const uint32_t*>(image.data.get()); auto dstData = reinterpret_cast<uint32_t*>(const_cast<char*>(data.data())); - const int32_t maxX = std::min(image.getWidth(), uint32_t(srcWidth + srcX)) - srcX; - assert(maxX <= int32_t(image.getWidth())); - const int32_t maxY = std::min(image.getHeight(), uint32_t(srcHeight + srcY)) - srcY; - assert(maxY <= int32_t(image.getHeight())); + const int32_t maxX = std::min(uint32_t(image.width), uint32_t(srcWidth + srcX)) - srcX; + assert(maxX <= int32_t(image.width)); + const int32_t maxY = std::min(uint32_t(image.height), uint32_t(srcHeight + srcY)) - srcY; + assert(maxY <= int32_t(image.height)); // Copy from the source image into our individual sprite image for (uint16_t y = 0; y < maxY; ++y) { const auto dstRow = y * dstWidth; - const auto srcRow = (y + srcY) * image.getWidth() + srcX; + const auto srcRow = (y + srcY) * image.width + srcX; for (uint16_t x = 0; x < maxX; ++x) { dstData[dstRow + x] = srcData[srcRow + x]; } @@ -105,10 +105,11 @@ SpriteParseResult parseSprite(const std::string& image, const std::string& json) using namespace rapidjson; Sprites sprites; + PremultipliedImage raster; - // Parse the sprite image. - const util::Image raster(image); - if (!raster) { + try { + raster = decodeImage(image); + } catch (...) { return std::string("Could not parse sprite image"); } diff --git a/src/mbgl/sprite/sprite_parser.hpp b/src/mbgl/sprite/sprite_parser.hpp index d385d77aea..468ba13c53 100644 --- a/src/mbgl/sprite/sprite_parser.hpp +++ b/src/mbgl/sprite/sprite_parser.hpp @@ -3,6 +3,7 @@ #include <mapbox/variant.hpp> +#include <mbgl/util/image.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/geo.hpp> @@ -12,18 +13,12 @@ namespace mbgl { -namespace util { - -class Image; - -} // namespace util - class SpriteImage; using SpriteImagePtr = std::shared_ptr<const SpriteImage>; // Extracts an individual image from a spritesheet from the given location. -SpriteImagePtr createSpriteImage(const util::Image& image, +SpriteImagePtr createSpriteImage(const PremultipliedImage&, uint16_t srcX, uint16_t srcY, uint16_t srcWidth, diff --git a/src/mbgl/util/raster.cpp b/src/mbgl/util/raster.cpp index 1c277040cd..c4d65ff320 100644 --- a/src/mbgl/util/raster.cpp +++ b/src/mbgl/util/raster.cpp @@ -25,13 +25,13 @@ bool Raster::isLoaded() const { return loaded; } -bool Raster::load(std::unique_ptr<util::Image> image) { +bool Raster::load(PremultipliedImage image) { img = std::move(image); - width = img->getWidth(); - height = img->getHeight(); + width = GLsizei(img.width); + height = GLsizei(img.height); std::lock_guard<std::mutex> lock(mtx); - if (img->getData()) { + if (img.data.get()) { loaded = true; } return loaded; @@ -44,7 +44,7 @@ void Raster::bind(bool linear) { return; } - if (img && !textured) { + if (img.data && !textured) { upload(); } else if (textured) { MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); @@ -59,7 +59,7 @@ void Raster::bind(bool linear) { } void Raster::upload() { - if (img && !textured) { + if (img.data && !textured) { texture = texturePool.getTextureID(); MBGL_CHECK_ERROR(glBindTexture(GL_TEXTURE_2D, texture)); #ifndef GL_ES_VERSION_2_0 @@ -67,8 +67,8 @@ void Raster::upload() { #endif MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); - MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img->getData())); - img.reset(); + MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img.data.get())); + img.data.reset(); textured = true; } } diff --git a/src/mbgl/util/raster.hpp b/src/mbgl/util/raster.hpp index adf3b2cbf7..5c65c46e9d 100644 --- a/src/mbgl/util/raster.hpp +++ b/src/mbgl/util/raster.hpp @@ -20,7 +20,7 @@ public: ~Raster(); // load image data - bool load(std::unique_ptr<util::Image> image); + bool load(PremultipliedImage); // bind current texture void bind(bool linear = false); @@ -58,7 +58,7 @@ private: GLint filter = 0; // the raw pixels - std::unique_ptr<util::Image> img; + PremultipliedImage img; }; } diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index 9940fb649c..00bdb5baee 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -17,8 +17,11 @@ public: void parseRasterTile(std::unique_ptr<RasterBucket> bucket, const std::shared_ptr<const std::string> data, std::function<void(RasterTileParseResult)> callback) { - std::unique_ptr<util::Image> image(new util::Image(*data)); - if (!(*image)) { + PremultipliedImage image; + + try { + image = decodeImage(*data); + } catch (...) { callback(RasterTileParseResult("error parsing raster image")); } diff --git a/test/api/annotations.cpp b/test/api/annotations.cpp index 55f5ca6749..b16ec71ef6 100644 --- a/test/api/annotations.cpp +++ b/test/api/annotations.cpp @@ -4,7 +4,6 @@ #include <mbgl/annotation/shape_annotation.hpp> #include <mbgl/sprite/sprite_image.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/map/still_image.hpp> #include <mbgl/platform/default/headless_display.hpp> #include <mbgl/platform/default/headless_view.hpp> #include <mbgl/storage/default_file_source.hpp> @@ -17,13 +16,11 @@ using namespace mbgl; std::string renderPNG(Map& map) { - std::promise<std::unique_ptr<const StillImage>> promise; - map.renderStill([&](std::exception_ptr, std::unique_ptr<const StillImage> image) { + std::promise<UnassociatedImage> promise; + map.renderStill([&](std::exception_ptr, UnassociatedImage&& image) { promise.set_value(std::move(image)); }); - - auto result = promise.get_future().get(); - return util::compress_png(result->width, result->height, result->pixels.get()); + return encodePNG(promise.get_future().get()); } TEST(Annotations, PointAnnotation) { diff --git a/test/api/api_misuse.cpp b/test/api/api_misuse.cpp index 1f54855323..4d5fe11042 100644 --- a/test/api/api_misuse.cpp +++ b/test/api/api_misuse.cpp @@ -2,7 +2,6 @@ #include "../fixtures/fixture_log_observer.hpp" #include <mbgl/map/map.hpp> -#include <mbgl/map/still_image.hpp> #include <mbgl/platform/default/headless_display.hpp> #include <mbgl/platform/default/headless_view.hpp> #include <mbgl/storage/default_file_source.hpp> @@ -46,7 +45,7 @@ TEST(API, RenderWithoutStyle) { Map map(view, fileSource, MapMode::Still); std::promise<std::exception_ptr> promise; - map.renderStill([&promise](std::exception_ptr error, std::unique_ptr<const StillImage>) { + map.renderStill([&promise](std::exception_ptr error, UnassociatedImage&&) { promise.set_value(error); }); diff --git a/test/api/repeated_render.cpp b/test/api/repeated_render.cpp index 3317b4e3a4..79349c67e7 100644 --- a/test/api/repeated_render.cpp +++ b/test/api/repeated_render.cpp @@ -2,7 +2,6 @@ #include "../fixtures/fixture_log_observer.hpp" #include <mbgl/map/map.hpp> -#include <mbgl/map/still_image.hpp> #include <mbgl/platform/default/headless_view.hpp> #include <mbgl/platform/default/headless_display.hpp> #include <mbgl/storage/default_file_source.hpp> @@ -26,28 +25,26 @@ TEST(API, RepeatedRender) { { map.setStyleJSON(style, ""); - std::promise<std::unique_ptr<const StillImage>> promise; - map.renderStill([&promise](std::exception_ptr, std::unique_ptr<const StillImage> image) { + std::promise<UnassociatedImage> promise; + map.renderStill([&promise](std::exception_ptr, UnassociatedImage&& image) { promise.set_value(std::move(image)); }); - auto result = promise.get_future().get(); - ASSERT_EQ(256, result->width); - ASSERT_EQ(512, result->height); - const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); - util::write_file("test/fixtures/api/1.png", png); + auto result = std::move(promise.get_future().get()); + ASSERT_EQ(256, result.width); + ASSERT_EQ(512, result.height); + util::write_file("test/fixtures/api/1.png", encodePNG(result)); } { map.setStyleJSON(style, "TEST_DATA/suite"); - std::promise<std::unique_ptr<const StillImage>> promise; - map.renderStill([&promise](std::exception_ptr, std::unique_ptr<const StillImage> image) { + std::promise<UnassociatedImage> promise; + map.renderStill([&promise](std::exception_ptr, UnassociatedImage&& image) { promise.set_value(std::move(image)); }); - auto result = promise.get_future().get(); - ASSERT_EQ(256, result->width); - ASSERT_EQ(512, result->height); - const std::string png = util::compress_png(result->width, result->height, result->pixels.get()); - util::write_file("test/fixtures/api/2.png", png); + auto result = std::move(promise.get_future().get()); + ASSERT_EQ(256, result.width); + ASSERT_EQ(512, result.height); + util::write_file("test/fixtures/api/2.png", encodePNG(result)); } auto observer = Log::removeObserver(); diff --git a/test/miscellaneous/bilinear.cpp b/test/miscellaneous/bilinear.cpp index 722e7265c0..740025bbbc 100644 --- a/test/miscellaneous/bilinear.cpp +++ b/test/miscellaneous/bilinear.cpp @@ -23,8 +23,9 @@ TEST(Bilinear, Scaling) { const uint32_t *srcData = reinterpret_cast<const uint32_t *>(src + 8); const vec2<uint32_t> srcSize { width, height }; const vec2<uint32_t> dstSize { 128, 128 }; - auto dst = std::make_unique<uint32_t[]>(dstSize.x * dstSize.y); - uint32_t *dstData = dst.get(); + + UnassociatedImage dst { dstSize.x, dstSize.y }; + uint32_t *dstData = reinterpret_cast<uint32_t*>(dst.data.get()); std::fill(dstData, dstData + dstSize.x * dstSize.y, 0xFFFF00FF); util::bilinearScale(srcData, srcSize, { 0, 0, 24, 24 }, dstData, dstSize, { 8, 8, 24, 24 }, false); @@ -42,7 +43,7 @@ TEST(Bilinear, Scaling) { util::bilinearScale(srcData, srcSize, { 252, 380, 12, 12 }, dstData, dstSize, { 18, 90, 24, 24 }, false); const std::string data { reinterpret_cast<char *>(dstData), dstSize.x * dstSize.y * sizeof(uint32_t) }; - util::write_file("test/fixtures/sprites/atlas_actual.png", util::compress_png(dstSize.x, dstSize.y, reinterpret_cast<uint8_t *>(dstData))); + util::write_file("test/fixtures/sprites/atlas_actual.png", encodePNG(dst)); util::write_file("test/fixtures/sprites/atlas_actual.bin", util::compress(data)); const std::string reference = util::decompress(util::read_file("test/fixtures/sprites/atlas_reference.bin")); diff --git a/test/sprite/sprite_parser.cpp b/test/sprite/sprite_parser.cpp index c2e4df3c58..dfa3a1055b 100644 --- a/test/sprite/sprite_parser.cpp +++ b/test/sprite/sprite_parser.cpp @@ -13,10 +13,10 @@ using namespace mbgl; TEST(Sprite, SpriteImageCreationInvalid) { FixtureLog log; - const util::Image image_1x(util::read_file("test/fixtures/annotations/emerald.png")); - ASSERT_TRUE(image_1x); - ASSERT_EQ(200u, image_1x.getWidth()); - ASSERT_EQ(299u, image_1x.getHeight()); + const PremultipliedImage image_1x = decodeImage(util::read_file("test/fixtures/annotations/emerald.png")); + + ASSERT_EQ(200u, image_1x.width); + ASSERT_EQ(299u, image_1x.height); ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 0, 16, 1, false)); // width == 0 ASSERT_EQ(nullptr, createSpriteImage(image_1x, 0, 0, 16, 0, 1, false)); // height == 0 @@ -34,10 +34,10 @@ TEST(Sprite, SpriteImageCreationInvalid) { } TEST(Sprite, SpriteImageCreation1x) { - const util::Image image_1x(util::read_file("test/fixtures/annotations/emerald.png")); - ASSERT_TRUE(image_1x); - ASSERT_EQ(200u, image_1x.getWidth()); - ASSERT_EQ(299u, image_1x.getHeight()); + const PremultipliedImage image_1x = decodeImage(util::read_file("test/fixtures/annotations/emerald.png")); + + ASSERT_EQ(200u, image_1x.width); + ASSERT_EQ(299u, image_1x.height); { // "museum_icon":{"x":177,"y":187,"width":18,"height":18,"pixelRatio":1,"sdf":false} const auto sprite = createSpriteImage(image_1x, 177, 187, 18, 18, 1, false); @@ -74,8 +74,7 @@ TEST(Sprite, SpriteImageCreation1x) { } TEST(Sprite, SpriteImageCreation2x) { - const util::Image image_2x(util::read_file("test/fixtures/annotations/emerald@2x.png")); - ASSERT_TRUE(image_2x); + const PremultipliedImage image_2x = decodeImage(util::read_file("test/fixtures/annotations/emerald@2x.png")); // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} const auto sprite = createSpriteImage(image_2x, 354, 374, 36, 36, 2, false); @@ -89,8 +88,7 @@ TEST(Sprite, SpriteImageCreation2x) { } TEST(Sprite, SpriteImageCreation1_5x) { - const util::Image image_2x(util::read_file("test/fixtures/annotations/emerald@2x.png")); - ASSERT_TRUE(image_2x); + const PremultipliedImage image_2x = decodeImage(util::read_file("test/fixtures/annotations/emerald@2x.png")); // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} const auto sprite = createSpriteImage(image_2x, 354, 374, 36, 36, 1.5, false); diff --git a/test/style/pending_resources.cpp b/test/style/pending_resources.cpp index 3ba59657de..58dc72937e 100644 --- a/test/style/pending_resources.cpp +++ b/test/style/pending_resources.cpp @@ -3,7 +3,6 @@ #include "../fixtures/util.hpp" #include <mbgl/map/map.hpp> -#include <mbgl/map/still_image.hpp> #include <mbgl/platform/default/headless_display.hpp> #include <mbgl/platform/default/headless_view.hpp> #include <mbgl/util/io.hpp> @@ -40,7 +39,7 @@ TEST_P(PendingResources, DeleteMapObjectWithPendingRequest) { const std::string style = util::read_file("test/fixtures/resources/style.json"); map->setStyleJSON(style, "."); - map->renderStill([&endTest](std::exception_ptr, std::unique_ptr<const StillImage>) { + map->renderStill([&endTest](std::exception_ptr, UnassociatedImage&&) { EXPECT_TRUE(false) << "Should never happen."; }); |