[macos,ios] don't roundtrip through encodePNG when converting images

This commit is contained in:
Konstantin Käfer
2017-01-25 17:28:23 +01:00
parent 46fe0df2a2
commit 19bd68d3a2
9 changed files with 140 additions and 137 deletions

View File

@@ -0,0 +1,12 @@
#pragma once
#include <mbgl/util/image.hpp>
#include <CoreGraphics/CGImage.h>
// Creates a CGImage from a PremultipliedImage, taking over the memory ownership.
CGImageRef CGImageFromMGLPremultipliedImage(mbgl::PremultipliedImage&&);
// Creates a PremultipliedImage by copying the pixels of the CGImage.
// Does not alter the retain count of the supplied CGImage.
mbgl::PremultipliedImage MGLPremultipliedImageFromCGImage(CGImageRef);

View File

@@ -1,126 +1,107 @@
#include <mbgl/util/image.hpp>
#include <mbgl/util/image+MGLAdditions.hpp>
#import <ImageIO/ImageIO.h>
#if TARGET_OS_IPHONE
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <CoreServices/CoreServices.h>
#endif
namespace {
template <typename T, typename S, void (*Releaser)(S)>
struct CFHandle {
CFHandle(T t_): t(t_) {}
~CFHandle() { Releaser(t); }
T operator*() { return t; }
operator bool() { return t; }
private:
T t;
};
} // namespace
using CGImageHandle = CFHandle<CGImageRef, CGImageRef, CGImageRelease>;
using CFDataHandle = CFHandle<CFDataRef, CFTypeRef, CFRelease>;
using CGImageSourceHandle = CFHandle<CGImageSourceRef, CFTypeRef, CFRelease>;
using CGDataProviderHandle = CFHandle<CGDataProviderRef, CGDataProviderRef, CGDataProviderRelease>;
using CGColorSpaceHandle = CFHandle<CGColorSpaceRef, CGColorSpaceRef, CGColorSpaceRelease>;
using CGContextHandle = CFHandle<CGContextRef, CGContextRef, CGContextRelease>;
CGImageRef CGImageFromMGLPremultipliedImage(mbgl::PremultipliedImage&& src) {
// We're converting the PremultipliedImage's backing store to a CGDataProvider, and are taking
// over ownership of the memory.
CGDataProviderHandle provider(CGDataProviderCreateWithData(
NULL, src.data.get(), src.bytes(), [](void*, const void* data, size_t) {
delete[] reinterpret_cast<const decltype(src.data)::element_type*>(data);
}));
if (!provider) {
return nil;
}
// If we successfully created the provider, it will take over management of the memory segment.
src.data.release();
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
if (!colorSpace) {
return nil;
}
constexpr const size_t bitsPerComponent = 8;
constexpr const size_t bytesPerPixel = 4;
constexpr const size_t bitsPerPixel = bitsPerComponent * bytesPerPixel;
const size_t bytesPerRow = bytesPerPixel * src.size.width;
return CGImageCreate(src.size.width, src.size.height, bitsPerComponent, bitsPerPixel,
bytesPerRow, *colorSpace,
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast, *provider,
NULL, false, kCGRenderingIntentDefault);
}
mbgl::PremultipliedImage MGLPremultipliedImageFromCGImage(CGImageRef src) {
const size_t width = CGImageGetWidth(src);
const size_t height = CGImageGetHeight(src);
mbgl::PremultipliedImage image({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) });
CGColorSpaceHandle colorSpace(CGColorSpaceCreateDeviceRGB());
if (!colorSpace) {
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
}
constexpr const size_t bitsPerComponent = 8;
constexpr const size_t bytesPerPixel = 4;
const size_t bytesPerRow = bytesPerPixel * width;
CGContextHandle context(CGBitmapContextCreate(
image.data.get(), width, height, bitsPerComponent, bytesPerRow, *colorSpace,
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast));
if (!context) {
throw std::runtime_error("CGBitmapContextCreate failed");
}
CGContextSetBlendMode(*context, kCGBlendModeCopy);
CGContextDrawImage(*context, CGRectMake(0, 0, width, height), src);
return image;
}
namespace mbgl {
std::string encodePNG(const PremultipliedImage& src) {
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, src.data.get(), src.bytes(), NULL);
if (!provider) {
return "";
}
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
if (!color_space) {
CGDataProviderRelease(provider);
return "";
}
CGImageRef image =
CGImageCreate(src.size.width, src.size.height, 8, 32, 4 * src.size.width, color_space,
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast, provider, NULL,
false, kCGRenderingIntentDefault);
if (!image) {
CGColorSpaceRelease(color_space);
CGDataProviderRelease(provider);
return "";
}
CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
if (!data) {
CGImageRelease(image);
CGColorSpaceRelease(color_space);
CGDataProviderRelease(provider);
return "";
}
CGImageDestinationRef image_destination = CGImageDestinationCreateWithData(data, kUTTypePNG, 1, NULL);
if (!image_destination) {
CFRelease(data);
CGImageRelease(image);
CGColorSpaceRelease(color_space);
CGDataProviderRelease(provider);
return "";
}
CGImageDestinationAddImage(image_destination, image, NULL);
CGImageDestinationFinalize(image_destination);
const std::string result {
reinterpret_cast<const char *>(CFDataGetBytePtr(data)),
static_cast<size_t>(CFDataGetLength(data))
};
CFRelease(image_destination);
CFRelease(data);
CGImageRelease(image);
CGColorSpaceRelease(color_space);
CGDataProviderRelease(provider);
return result;
}
PremultipliedImage decodeImage(const std::string &source_data) {
CFDataRef data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, reinterpret_cast<const unsigned char *>(source_data.data()), source_data.size(), kCFAllocatorNull);
PremultipliedImage decodeImage(const std::string& source) {
CFDataHandle data(CFDataCreateWithBytesNoCopy(
kCFAllocatorDefault, reinterpret_cast<const unsigned char*>(source.data()), source.size(),
kCFAllocatorNull));
if (!data) {
throw std::runtime_error("CFDataCreateWithBytesNoCopy failed");
}
CGImageSourceRef image_source = CGImageSourceCreateWithData(data, NULL);
if (!image_source) {
CFRelease(data);
CGImageSourceHandle imageSource(CGImageSourceCreateWithData(*data, NULL));
if (!imageSource) {
throw std::runtime_error("CGImageSourceCreateWithData failed");
}
CGImageRef image = CGImageSourceCreateImageAtIndex(image_source, 0, NULL);
CGImageHandle image(CGImageSourceCreateImageAtIndex(*imageSource, 0, NULL));
if (!image) {
CFRelease(image_source);
CFRelease(data);
throw std::runtime_error("CGImageSourceCreateImageAtIndex failed");
}
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
if (!color_space) {
CGImageRelease(image);
CFRelease(image_source);
CFRelease(data);
throw std::runtime_error("CGColorSpaceCreateDeviceRGB failed");
}
PremultipliedImage result({ static_cast<uint32_t>(CGImageGetWidth(image)),
static_cast<uint32_t>(CGImageGetHeight(image)) });
CGContextRef context =
CGBitmapContextCreate(result.data.get(), result.size.width, result.size.height, 8,
result.stride(), color_space, kCGImageAlphaPremultipliedLast);
if (!context) {
CGColorSpaceRelease(color_space);
CGImageRelease(image);
CFRelease(image_source);
CFRelease(data);
throw std::runtime_error("CGBitmapContextCreate failed");
}
CGContextSetBlendMode(context, kCGBlendModeCopy);
CGRect rect = { { 0, 0 },
{ static_cast<CGFloat>(result.size.width),
static_cast<CGFloat>(result.size.height) } };
CGContextDrawImage(context, rect, image);
CGContextRelease(context);
CGColorSpaceRelease(color_space);
CGImageRelease(image);
CFRelease(image_source);
CFRelease(data);
return result;
return MGLPremultipliedImageFromCGImage(*image);
}
}
} // namespace mbgl

View File

@@ -72,7 +72,7 @@ std::string encodePNG(const PremultipliedImage& pre) {
(12 + idat.size() /* IDAT */) + (12 /* IEND */));
png.append(preamble, 8);
addChunk(png, "IHDR", ihdr, 13);
addChunk(png, "IDAT", idat.data(), idat.size());
addChunk(png, "IDAT", idat.data(), static_cast<uint32_t>(idat.size()));
addChunk(png, "IEND");
return png;
}

View File

@@ -44,7 +44,9 @@ macro(mbgl_platform_core)
PRIVATE platform/default/utf.cpp
# Image handling
PRIVATE platform/darwin/mbgl/util/image+MGLAdditions.hpp
PRIVATE platform/darwin/src/image.mm
PRIVATE platform/default/png_writer.cpp
# Headless view
PRIVATE platform/default/mbgl/gl/headless_backend.cpp

View File

@@ -1,42 +1,31 @@
#import "UIImage+MGLAdditions.h"
#include <mbgl/util/image+MGLAdditions.hpp>
@implementation UIImage (MGLAdditions)
- (nullable instancetype)initWithMGLSpriteImage:(const mbgl::SpriteImage *)spriteImage
{
std::string png = encodePNG(spriteImage->image);
NSData *data = [[NSData alloc] initWithBytes:png.data() length:png.size()];
if (self = [self initWithData:data scale:spriteImage->pixelRatio])
CGImageRef image = CGImageFromMGLPremultipliedImage(spriteImage->image.clone());
if (!image) {
return nil;
}
if (self = [self initWithCGImage:image scale:spriteImage->pixelRatio orientation:UIImageOrientationUp])
{
if (spriteImage->sdf)
{
self = [self imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
}
}
CGImageRelease(image);
return self;
}
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage
{
CGImageRef cgImage = self.CGImage;
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
mbgl::PremultipliedImage cPremultipliedImage({ static_cast<uint32_t>(width), static_cast<uint32_t>(height) });
size_t bytesPerPixel = 4;
size_t bytesPerRow = bytesPerPixel * width;
size_t bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(cPremultipliedImage.data.get(),
width, height, bitsPerComponent, bytesPerRow,
colorSpace, kCGImageAlphaPremultipliedLast);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage {
BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate;
return std::make_unique<mbgl::SpriteImage>(std::move(cPremultipliedImage), float(self.scale), isTemplate);
return std::make_unique<mbgl::SpriteImage>(MGLPremultipliedImageFromCGImage(self.CGImage),
float(self.scale), isTemplate);
}
@end

View File

@@ -40,7 +40,9 @@ macro(mbgl_platform_core)
PRIVATE platform/default/utf.cpp
# Image handling
PRIVATE platform/darwin/mbgl/util/image+MGLAdditions.hpp
PRIVATE platform/darwin/src/image.mm
PRIVATE platform/default/png_writer.cpp
# Headless view
PRIVATE platform/default/mbgl/gl/headless_backend.cpp

View File

@@ -787,9 +787,7 @@ public:
if (_isPrinting) {
_isPrinting = NO;
std::string png = encodePNG(_mbglView->readStillImage());
NSData *data = [[NSData alloc] initWithBytes:png.data() length:png.size()];
NSImage *image = [[NSImage alloc] initWithData:data];
NSImage *image = [[NSImage alloc] initWithMGLPremultipliedImage:_mbglView->readStillImage()];
[self performSelector:@selector(printWithImage:) withObject:image afterDelay:0];
}

View File

@@ -6,6 +6,8 @@ NS_ASSUME_NONNULL_BEGIN
@interface NSImage (MGLAdditions)
- (nullable instancetype)initWithMGLPremultipliedImage:(mbgl::PremultipliedImage&&)image;
- (nullable instancetype)initWithMGLSpriteImage:(const mbgl::SpriteImage *)spriteImage;
- (std::unique_ptr<mbgl::SpriteImage>)mgl_spriteImage;

View File

@@ -1,11 +1,28 @@
#import "NSImage+MGLAdditions.h"
#include <mbgl/util/image+MGLAdditions.hpp>
@implementation NSImage (MGLAdditions)
- (nullable instancetype)initWithMGLPremultipliedImage:(mbgl::PremultipliedImage&&)src {
CGImageRef image = CGImageFromMGLPremultipliedImage(std::move(src));
if (!image) {
return nil;
}
self = [self initWithCGImage:image size:NSZeroSize];
CGImageRelease(image);
return self;
}
- (nullable instancetype)initWithMGLSpriteImage:(const mbgl::SpriteImage *)spriteImage {
std::string png = encodePNG(spriteImage->image);
NSData *data = [[NSData alloc] initWithBytes:png.data() length:png.size()];
NSBitmapImageRep *rep = [NSBitmapImageRep imageRepWithData:data];
CGImageRef image = CGImageFromMGLPremultipliedImage(spriteImage->image.clone());
if (!image) {
return nil;
}
NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithCGImage:image];
CGImageRelease(image);
if (self = [self initWithSize:NSMakeSize(spriteImage->getWidth(), spriteImage->getHeight())]) {
[self addRepresentation:rep];
[self setTemplate:spriteImage->sdf];