597 lines
23 KiB
Objective-C
597 lines
23 KiB
Objective-C
//
|
|
// WebPImageCoder.m
|
|
// WebPImage <https://github.com/ibireme/WebPImage>
|
|
//
|
|
// Created by ibireme on 15/5/13.
|
|
// Copyright (c) 2015 ibireme.
|
|
//
|
|
// This source code is licensed under the MIT-style license found in the
|
|
// LICENSE file in the root directory of this source tree.
|
|
//
|
|
|
|
#import "WebPImageCoder.h"
|
|
#import <CoreFoundation/CoreFoundation.h>
|
|
#import <ImageIO/ImageIO.h>
|
|
#import <Accelerate/Accelerate.h>
|
|
#import <objc/runtime.h>
|
|
#import <pthread.h>
|
|
|
|
#import "decode.h"
|
|
#import "encode.h"
|
|
#import "demux.h"
|
|
#import "mux.h"
|
|
|
|
|
|
|
|
static void WebPCGDataProviderReleaseDataCallback(void *info, const void *data, size_t size) {
|
|
if (info) free(info);
|
|
}
|
|
static inline size_t WebPImageByteAlign(size_t size, size_t alignment) {
|
|
return ((size + (alignment - 1)) / alignment) * alignment;
|
|
}
|
|
|
|
CGColorSpaceRef WebPCGColorSpaceGetDeviceRGB() {
|
|
static CGColorSpaceRef space;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
space = CGColorSpaceCreateDeviceRGB();
|
|
});
|
|
return space;
|
|
}
|
|
|
|
CGColorSpaceRef WebPCGColorSpaceGetDeviceGray() {
|
|
static CGColorSpaceRef space;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
space = CGColorSpaceCreateDeviceGray();
|
|
});
|
|
return space;
|
|
}
|
|
|
|
BOOL WebPCGColorSpaceIsDeviceRGB(CGColorSpaceRef space) {
|
|
return space && CFEqual(space, WebPCGColorSpaceGetDeviceRGB());
|
|
}
|
|
|
|
BOOL WebPCGColorSpaceIsDeviceGray(CGColorSpaceRef space) {
|
|
return space && CFEqual(space, WebPCGColorSpaceGetDeviceGray());
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
#pragma mark - Decoder
|
|
|
|
@implementation WebPImageFrame
|
|
|
|
- (id)copyWithZone:(NSZone *)zone {
|
|
WebPImageFrame *frame = [self.class new];
|
|
frame.index = _index;
|
|
frame.width = _width;
|
|
frame.height = _height;
|
|
frame.offsetX = _offsetX;
|
|
frame.offsetY = _offsetY;
|
|
frame.duration = _duration;
|
|
frame.dispose = _dispose;
|
|
frame.blend = _blend;
|
|
frame.image = _image.copy;
|
|
return frame;
|
|
}
|
|
@end
|
|
|
|
// Internal frame object.
|
|
@interface _WebPImageDecoderFrame : WebPImageFrame
|
|
@property (nonatomic, assign) BOOL hasAlpha; ///< Whether frame has alpha.
|
|
@property (nonatomic, assign) BOOL isFullSize; ///< Whether frame fill the canvas.
|
|
@property (nonatomic, assign) NSUInteger blendFromIndex; ///< Blend from frame index to current frame.
|
|
@end
|
|
|
|
@implementation _WebPImageDecoderFrame
|
|
- (id)copyWithZone:(NSZone *)zone {
|
|
_WebPImageDecoderFrame *frame = [super copyWithZone:zone];
|
|
frame.hasAlpha = _hasAlpha;
|
|
frame.isFullSize = _isFullSize;
|
|
frame.blendFromIndex = _blendFromIndex;
|
|
return frame;
|
|
}
|
|
@end
|
|
|
|
|
|
@implementation WebPImageDecoder {
|
|
pthread_mutex_t _lock; // recursive lock
|
|
|
|
BOOL _sourceTypeDetected;
|
|
WebPDemuxer *_webpSource;
|
|
|
|
dispatch_semaphore_t _framesLock;
|
|
NSArray *_frames;
|
|
BOOL _needBlend;
|
|
NSUInteger _blendFrameIndex;
|
|
CGContextRef _blendCanvas;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
if (_webpSource) WebPDemuxDelete(_webpSource);
|
|
if (_blendCanvas) CFRelease(_blendCanvas);
|
|
pthread_mutex_destroy(&_lock);
|
|
}
|
|
|
|
+ (instancetype)decoderWithData:(NSData *)data scale:(CGFloat)scale {
|
|
if (!data) return nil;
|
|
WebPImageDecoder *decoder = [[WebPImageDecoder alloc] initWithScale:scale];
|
|
[decoder updateData:data final:YES];
|
|
if (decoder.frameCount == 0) return nil;
|
|
return decoder;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
return [self initWithScale:[NSScreen mainScreen].backingScaleFactor];
|
|
}
|
|
|
|
- (instancetype)initWithScale:(CGFloat)scale {
|
|
self = [super init];
|
|
if (scale <= 0) scale = 1;
|
|
_scale = scale;
|
|
_framesLock = dispatch_semaphore_create(1);
|
|
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init (&attr);
|
|
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
|
|
pthread_mutex_init (&_lock, &attr);
|
|
pthread_mutexattr_destroy (&attr);
|
|
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)updateData:(NSData *)data final:(BOOL)final {
|
|
BOOL result = NO;
|
|
pthread_mutex_lock(&_lock);
|
|
result = [self _updateData:data final:final];
|
|
pthread_mutex_unlock(&_lock);
|
|
return result;
|
|
}
|
|
|
|
- (WebPImageFrame *)frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay {
|
|
WebPImageFrame *result = nil;
|
|
pthread_mutex_lock(&_lock);
|
|
result = [self _frameAtIndex:index decodeForDisplay:decodeForDisplay];
|
|
pthread_mutex_unlock(&_lock);
|
|
return result;
|
|
}
|
|
|
|
- (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index {
|
|
NSTimeInterval result = 0;
|
|
dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
|
|
if (index < _frames.count) {
|
|
result = ((_WebPImageDecoderFrame *)_frames[index]).duration;
|
|
}
|
|
dispatch_semaphore_signal(_framesLock);
|
|
return result;
|
|
}
|
|
- (BOOL)_updateData:(NSData *)data final:(BOOL)final {
|
|
if (_finalized) return NO;
|
|
if (data.length < _data.length) return NO;
|
|
_finalized = final;
|
|
_data = data;
|
|
|
|
_type = WebPImageTypeWebP;
|
|
[self _updateSource];
|
|
_sourceTypeDetected = YES;
|
|
return YES;
|
|
}
|
|
|
|
- (WebPImageFrame *)_frameAtIndex:(NSUInteger)index decodeForDisplay:(BOOL)decodeForDisplay {
|
|
if (index >= _frames.count) return 0;
|
|
_WebPImageDecoderFrame *frame = [(_WebPImageDecoderFrame *)_frames[index] copy];
|
|
BOOL decoded = NO;
|
|
BOOL extendToCanvas = NO;
|
|
if (decodeForDisplay) { // ICO contains multi-size frame and should not extend to canvas.
|
|
extendToCanvas = YES;
|
|
}
|
|
|
|
if (!_needBlend) {
|
|
CGImageRef imageRef = [self _newUnblendedImageAtIndex:index extendToCanvas:extendToCanvas decoded:&decoded];
|
|
if (!imageRef) return nil;
|
|
if (decodeForDisplay && !decoded) {
|
|
CGImageRef imageRefDecoded = WebPCGImageCreateDecodedCopy(imageRef, YES);
|
|
if (imageRefDecoded) {
|
|
CFRelease(imageRef);
|
|
imageRef = imageRefDecoded;
|
|
decoded = YES;
|
|
}
|
|
}
|
|
|
|
NSImage *image = [[NSImage alloc] initWithCGImage:imageRef size:NSMakeSize(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef))];
|
|
CFRelease(imageRef);
|
|
if (!image) return nil;
|
|
frame.image = image;
|
|
return frame;
|
|
}
|
|
|
|
// blend
|
|
if (![self _createBlendContextIfNeeded]) return nil;
|
|
CGImageRef imageRef = NULL;
|
|
|
|
if (_blendFrameIndex + 1 == frame.index) {
|
|
imageRef = [self _newBlendedImageWithFrame:frame];
|
|
_blendFrameIndex = index;
|
|
} else { // should draw canvas from previous frame
|
|
_blendFrameIndex = NSNotFound;
|
|
CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));
|
|
|
|
if (frame.blendFromIndex == frame.index) {
|
|
CGImageRef unblendedImage = [self _newUnblendedImageAtIndex:index extendToCanvas:NO decoded:NULL];
|
|
if (unblendedImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendedImage);
|
|
CFRelease(unblendedImage);
|
|
}
|
|
imageRef = CGBitmapContextCreateImage(_blendCanvas);
|
|
if (frame.dispose == WebPImageDisposeBackground) {
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
}
|
|
_blendFrameIndex = index;
|
|
} else { // canvas is not ready
|
|
for (uint32_t i = (uint32_t)frame.blendFromIndex; i <= (uint32_t)frame.index; i++) {
|
|
if (i == frame.index) {
|
|
if (!imageRef) imageRef = [self _newBlendedImageWithFrame:frame];
|
|
} else {
|
|
[self _blendImageWithFrame:_frames[i]];
|
|
}
|
|
}
|
|
_blendFrameIndex = index;
|
|
}
|
|
}
|
|
|
|
if (!imageRef) return nil;
|
|
NSImage *image = [[NSImage alloc] initWithCGImage:imageRef size:NSMakeSize(CGImageGetWidth(imageRef), CGImageGetHeight(imageRef))];
|
|
CFRelease(imageRef);
|
|
if (!image) return nil;
|
|
|
|
frame.image = image;
|
|
if (extendToCanvas) {
|
|
frame.width = _width;
|
|
frame.height = _height;
|
|
frame.offsetX = 0;
|
|
frame.offsetY = 0;
|
|
frame.dispose = WebPImageDisposeNone;
|
|
frame.blend = WebPImageBlendNone;
|
|
}
|
|
return frame;
|
|
}
|
|
|
|
|
|
#pragma private
|
|
|
|
- (void)_updateSource {
|
|
[self _updateSourceWebP];
|
|
}
|
|
|
|
- (void)_updateSourceWebP {
|
|
_width = 0;
|
|
_height = 0;
|
|
_loopCount = 0;
|
|
if (_webpSource) WebPDemuxDelete(_webpSource);
|
|
_webpSource = NULL;
|
|
dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
|
|
_frames = nil;
|
|
dispatch_semaphore_signal(_framesLock);
|
|
|
|
WebPData webPData = {0};
|
|
webPData.bytes = _data.bytes;
|
|
webPData.size = _data.length;
|
|
WebPDemuxer *demuxer = WebPDemux(&webPData);
|
|
if (!demuxer) return;
|
|
|
|
uint32_t webpFrameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
|
|
uint32_t webpLoopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
|
|
uint32_t canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
|
|
uint32_t canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
|
|
if (webpFrameCount == 0 || canvasWidth < 1 || canvasHeight < 1) {
|
|
WebPDemuxDelete(demuxer);
|
|
return;
|
|
}
|
|
|
|
NSMutableArray *frames = [NSMutableArray new];
|
|
BOOL needBlend = NO;
|
|
uint32_t iterIndex = 0;
|
|
uint32_t lastBlendIndex = 0;
|
|
WebPIterator iter = {0};
|
|
if (WebPDemuxGetFrame(demuxer, 1, &iter)) { // one-based index...
|
|
do {
|
|
_WebPImageDecoderFrame *frame = [_WebPImageDecoderFrame new];
|
|
[frames addObject:frame];
|
|
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND) {
|
|
frame.dispose = WebPImageDisposeBackground;
|
|
}
|
|
if (iter.blend_method == WEBP_MUX_BLEND) {
|
|
frame.blend = WebPImageBlendOver;
|
|
}
|
|
|
|
int canvasWidth = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
|
|
int canvasHeight = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
|
|
frame.index = iterIndex;
|
|
frame.duration = iter.duration / 1000.0;
|
|
frame.width = iter.width;
|
|
frame.height = iter.height;
|
|
frame.hasAlpha = iter.has_alpha;
|
|
frame.blend = iter.blend_method == WEBP_MUX_BLEND;
|
|
frame.offsetX = iter.x_offset;
|
|
frame.offsetY = canvasHeight - iter.y_offset - iter.height;
|
|
|
|
BOOL sizeEqualsToCanvas = (iter.width == canvasWidth && iter.height == canvasHeight);
|
|
BOOL offsetIsZero = (iter.x_offset == 0 && iter.y_offset == 0);
|
|
frame.isFullSize = (sizeEqualsToCanvas && offsetIsZero);
|
|
|
|
if ((!frame.blend || !frame.hasAlpha) && frame.isFullSize) {
|
|
frame.blendFromIndex = lastBlendIndex = iterIndex;
|
|
} else {
|
|
if (frame.dispose && frame.isFullSize) {
|
|
frame.blendFromIndex = lastBlendIndex;
|
|
lastBlendIndex = iterIndex + 1;
|
|
} else {
|
|
frame.blendFromIndex = lastBlendIndex;
|
|
}
|
|
}
|
|
if (frame.index != frame.blendFromIndex) needBlend = YES;
|
|
iterIndex++;
|
|
} while (WebPDemuxNextFrame(&iter));
|
|
WebPDemuxReleaseIterator(&iter);
|
|
}
|
|
if (frames.count != webpFrameCount) {
|
|
WebPDemuxDelete(demuxer);
|
|
return;
|
|
}
|
|
|
|
_width = canvasWidth;
|
|
_height = canvasHeight;
|
|
_frameCount = frames.count;
|
|
_loopCount = webpLoopCount;
|
|
_needBlend = needBlend;
|
|
_webpSource = demuxer;
|
|
dispatch_semaphore_wait(_framesLock, DISPATCH_TIME_FOREVER);
|
|
_frames = frames;
|
|
dispatch_semaphore_signal(_framesLock);
|
|
}
|
|
|
|
- (CGImageRef)_newUnblendedImageAtIndex:(NSUInteger)index
|
|
extendToCanvas:(BOOL)extendToCanvas
|
|
decoded:(BOOL *)decoded CF_RETURNS_RETAINED {
|
|
|
|
if (!_finalized && index > 0) return NULL;
|
|
if (_frames.count <= index) return NULL;
|
|
|
|
if (_webpSource) {
|
|
WebPIterator iter;
|
|
if (!WebPDemuxGetFrame(_webpSource, (int)(index + 1), &iter)) return NULL; // demux webp frame data
|
|
// frame numbers are one-based in webp -----------^
|
|
|
|
int frameWidth = iter.width;
|
|
int frameHeight = iter.height;
|
|
if (frameWidth < 1 || frameHeight < 1) return NULL;
|
|
|
|
int width = extendToCanvas ? (int)_width : frameWidth;
|
|
int height = extendToCanvas ? (int)_height : frameHeight;
|
|
if (width > _width || height > _height) return NULL;
|
|
|
|
const uint8_t *payload = iter.fragment.bytes;
|
|
size_t payloadSize = iter.fragment.size;
|
|
|
|
WebPDecoderConfig config;
|
|
if (!WebPInitDecoderConfig(&config)) {
|
|
WebPDemuxReleaseIterator(&iter);
|
|
return NULL;
|
|
}
|
|
if (WebPGetFeatures(payload , payloadSize, &config.input) != VP8_STATUS_OK) {
|
|
WebPDemuxReleaseIterator(&iter);
|
|
return NULL;
|
|
}
|
|
|
|
size_t bitsPerComponent = 8;
|
|
size_t bitsPerPixel = 32;
|
|
size_t bytesPerRow = WebPImageByteAlign(bitsPerPixel / 8 * width, 32);
|
|
size_t length = bytesPerRow * height;
|
|
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; //bgrA
|
|
|
|
void *pixels = calloc(1, length);
|
|
if (!pixels) {
|
|
WebPDemuxReleaseIterator(&iter);
|
|
return NULL;
|
|
}
|
|
|
|
config.output.colorspace = MODE_bgrA;
|
|
config.output.is_external_memory = 1;
|
|
config.output.u.RGBA.rgba = pixels;
|
|
config.output.u.RGBA.stride = (int)bytesPerRow;
|
|
config.output.u.RGBA.size = length;
|
|
VP8StatusCode result = WebPDecode(payload, payloadSize, &config); // decode
|
|
if ((result != VP8_STATUS_OK) && (result != VP8_STATUS_NOT_ENOUGH_DATA)) {
|
|
WebPDemuxReleaseIterator(&iter);
|
|
free(pixels);
|
|
return NULL;
|
|
}
|
|
WebPDemuxReleaseIterator(&iter);
|
|
|
|
if (extendToCanvas && (iter.x_offset != 0 || iter.y_offset != 0)) {
|
|
void *tmp = calloc(1, length);
|
|
if (tmp) {
|
|
vImage_Buffer src = {pixels, height, width, bytesPerRow};
|
|
vImage_Buffer dest = {tmp, height, width, bytesPerRow};
|
|
vImage_CGAffineTransform transform = {1, 0, 0, 1, iter.x_offset, -iter.y_offset};
|
|
uint8_t backColor[4] = {0};
|
|
vImage_Error error = vImageAffineWarpCG_ARGB8888(&src, &dest, NULL, &transform, backColor, kvImageBackgroundColorFill);
|
|
if (error == kvImageNoError) {
|
|
memcpy(pixels, tmp, length);
|
|
}
|
|
free(tmp);
|
|
}
|
|
}
|
|
|
|
CGDataProviderRef provider = CGDataProviderCreateWithData(pixels, pixels, length, WebPCGDataProviderReleaseDataCallback);
|
|
if (!provider) {
|
|
free(pixels);
|
|
return NULL;
|
|
}
|
|
pixels = NULL; // hold by provider
|
|
|
|
CGImageRef image = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, WebPCGColorSpaceGetDeviceRGB(), bitmapInfo, provider, NULL, false, kCGRenderingIntentDefault);
|
|
CFRelease(provider);
|
|
if (decoded) *decoded = YES;
|
|
return image;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
- (BOOL)_createBlendContextIfNeeded {
|
|
if (!_blendCanvas) {
|
|
_blendFrameIndex = NSNotFound;
|
|
_blendCanvas = CGBitmapContextCreate(NULL, _width, _height, 8, 0, WebPCGColorSpaceGetDeviceRGB(), kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
|
|
}
|
|
BOOL suc = _blendCanvas != NULL;
|
|
return suc;
|
|
}
|
|
|
|
- (void)_blendImageWithFrame:(_WebPImageDecoderFrame *)frame {
|
|
if (frame.dispose == WebPImageDisposePrevious) {
|
|
// nothing
|
|
} else if (frame.dispose == WebPImageDisposeBackground) {
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
} else { // no dispose
|
|
if (frame.blend == WebPImageBlendOver) {
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
} else {
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (CGImageRef)_newBlendedImageWithFrame:(_WebPImageDecoderFrame *)frame CF_RETURNS_RETAINED{
|
|
CGImageRef imageRef = NULL;
|
|
if (frame.dispose == WebPImageDisposePrevious) {
|
|
if (frame.blend == WebPImageBlendOver) {
|
|
CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas);
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
imageRef = CGBitmapContextCreateImage(_blendCanvas);
|
|
CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));
|
|
if (previousImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage);
|
|
CFRelease(previousImage);
|
|
}
|
|
} else {
|
|
CGImageRef previousImage = CGBitmapContextCreateImage(_blendCanvas);
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
imageRef = CGBitmapContextCreateImage(_blendCanvas);
|
|
CGContextClearRect(_blendCanvas, CGRectMake(0, 0, _width, _height));
|
|
if (previousImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(0, 0, _width, _height), previousImage);
|
|
CFRelease(previousImage);
|
|
}
|
|
}
|
|
} else if (frame.dispose == WebPImageDisposeBackground) {
|
|
if (frame.blend == WebPImageBlendOver) {
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
imageRef = CGBitmapContextCreateImage(_blendCanvas);
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
} else {
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
imageRef = CGBitmapContextCreateImage(_blendCanvas);
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
}
|
|
} else { // no dispose
|
|
if (frame.blend == WebPImageBlendOver) {
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
imageRef = CGBitmapContextCreateImage(_blendCanvas);
|
|
} else {
|
|
CGImageRef unblendImage = [self _newUnblendedImageAtIndex:frame.index extendToCanvas:NO decoded:NULL];
|
|
if (unblendImage) {
|
|
CGContextClearRect(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height));
|
|
CGContextDrawImage(_blendCanvas, CGRectMake(frame.offsetX, frame.offsetY, frame.width, frame.height), unblendImage);
|
|
CFRelease(unblendImage);
|
|
}
|
|
imageRef = CGBitmapContextCreateImage(_blendCanvas);
|
|
}
|
|
}
|
|
return imageRef;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
CGImageRef WebPCGImageCreateDecodedCopy(CGImageRef imageRef, BOOL decodeForDisplay) {
|
|
if (!imageRef) return NULL;
|
|
size_t width = CGImageGetWidth(imageRef);
|
|
size_t height = CGImageGetHeight(imageRef);
|
|
if (width == 0 || height == 0) return NULL;
|
|
|
|
if (decodeForDisplay) { //decode with redraw (may lose some precision)
|
|
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(imageRef) & kCGBitmapAlphaInfoMask;
|
|
BOOL hasAlpha = NO;
|
|
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
|
|
alphaInfo == kCGImageAlphaPremultipliedFirst ||
|
|
alphaInfo == kCGImageAlphaLast ||
|
|
alphaInfo == kCGImageAlphaFirst) {
|
|
hasAlpha = YES;
|
|
}
|
|
// BGRA8888 (premultiplied) or BGRX8888
|
|
// same as UIGraphicsBeginImageContext() and -[UIView drawRect:]
|
|
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
|
|
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
|
|
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, WebPCGColorSpaceGetDeviceRGB(), bitmapInfo);
|
|
if (!context) return NULL;
|
|
CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); // decode
|
|
CGImageRef newImage = CGBitmapContextCreateImage(context);
|
|
CFRelease(context);
|
|
return newImage;
|
|
|
|
} else {
|
|
CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
|
|
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
|
|
size_t bitsPerPixel = CGImageGetBitsPerPixel(imageRef);
|
|
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
|
|
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
|
|
if (bytesPerRow == 0 || width == 0 || height == 0) return NULL;
|
|
|
|
CGDataProviderRef dataProvider = CGImageGetDataProvider(imageRef);
|
|
if (!dataProvider) return NULL;
|
|
CFDataRef data = CGDataProviderCopyData(dataProvider); // decode
|
|
if (!data) return NULL;
|
|
|
|
CGDataProviderRef newProvider = CGDataProviderCreateWithCFData(data);
|
|
CFRelease(data);
|
|
if (!newProvider) return NULL;
|
|
|
|
CGImageRef newImage = CGImageCreate(width, height, bitsPerComponent, bitsPerPixel, bytesPerRow, space, bitmapInfo, newProvider, NULL, false, kCGRenderingIntentDefault);
|
|
CFRelease(newProvider);
|
|
return newImage;
|
|
}
|
|
}
|