GIFPlayer for OSX by the guys at Byte Inc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// GIFPlayer.h | |
// Byte | |
// | |
// Created by Dom Hofmann on 8/9/15. | |
// Copyright © 2015 Byte, Inc. All rights reserved. | |
// | |
#import "ByteObject.h" | |
#import <Cocoa/Cocoa.h> | |
typedef NS_ENUM(NSUInteger, GIFPlayerScaleType) { | |
GIFPlayerScaleTypeFit, | |
GIFPlayerScaleTypeFill | |
}; | |
@interface GIFPlayer : NSView <ByteObjectPrimitive> | |
@property (nonatomic) ByteObject *parentObject; | |
@property (nonatomic) GIFPlayerScaleType scaleType; | |
@property (nonatomic) NSData *imageData; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// GIFPlayer.m | |
// Byte | |
// | |
// Created by Dom Hofmann on 8/9/15. | |
// Copyright © 2015 Byte, Inc. All rights reserved. | |
// | |
#import "GIFPlayer.h" | |
#import <QuartzCore/QuartzCore.h> | |
@interface GIFPlayer () | |
@property (nonatomic) NSImage *image; | |
@property (nonatomic) CALayer *animationLayer; | |
@end | |
@implementation GIFPlayer | |
- (void)dealloc { | |
NSLog(@"Deallocating %@ %p", [self class], self); | |
} | |
- (BOOL)isFlipped { | |
return YES; | |
} | |
- (id)initWithFrame:(NSRect)frameRect { | |
if (self = [super initWithFrame:frameRect]) { | |
self.wantsLayer = YES; | |
self.layer.anchorPoint = CGPointMake(0.5, 0.5); | |
self.layer.frame = frameRect; | |
self.animationLayer = [CALayer new]; | |
[self.layer addSublayer:self.animationLayer]; | |
} | |
return self; | |
} | |
- (void)resizeSubviewsWithOldSize:(NSSize)oldSize { | |
[super resizeSubviewsWithOldSize:oldSize]; | |
if (self.image) { | |
NSRect targetFrame = NSZeroRect; | |
CGFloat containerAspect = self.layer.bounds.size.width / self.layer.bounds.size.height; | |
CGFloat imageAspect = self.image.size.width / self.image.size.height; | |
CGFloat ratio; | |
if (_scaleType == GIFPlayerScaleTypeFill) { | |
if (containerAspect > imageAspect) { | |
ratio = self.layer.bounds.size.width / self.image.size.width; | |
} else { | |
ratio = self.layer.bounds.size.height / self.image.size.height; | |
} | |
} else { | |
if (containerAspect > imageAspect) { | |
ratio = self.layer.bounds.size.height / self.image.size.height; | |
} else { | |
ratio = self.layer.bounds.size.width / self.image.size.width; | |
} | |
} | |
CGFloat targetW = self.image.size.width * ratio; | |
CGFloat targetH = self.image.size.height * ratio; | |
targetFrame = NSMakeRect(self.layer.bounds.size.width / 2 - targetW / 2, self.layer.bounds.size.height / 2 - targetH / 2, targetW, targetH); | |
[CATransaction begin]; | |
[CATransaction setDisableActions:YES]; | |
self.animationLayer.frame = targetFrame; | |
[CATransaction commit]; | |
} | |
} | |
- (void)setImageData:(NSData *)imageData { | |
_imageData = imageData; | |
self.image = [[NSImage alloc] initWithData:imageData]; | |
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL); | |
if (!imageSource) { | |
NSLog(@"Couldn't load image source for supplied data"); | |
return; | |
} | |
CFStringRef imageSourceContainerType = CGImageSourceGetType(imageSource); | |
BOOL isGIFData = UTTypeConformsTo(imageSourceContainerType, kUTTypeGIF); | |
if (!isGIFData) { | |
NSLog(@"The provided data is not a valid GIF"); | |
return; | |
} | |
NSArray * reps = [self.image representations]; | |
// build GIF frames | |
// adapted from procedures in this blog post: http://blog.pcitron.fr/2010/12/14/play-an-animated-gif-with-an-ikimageview/ | |
for (NSImageRep * rep in reps) { | |
if ([rep isKindOfClass:[NSBitmapImageRep class]] == YES) { | |
NSBitmapImageRep *bitmapRep = (NSBitmapImageRep *)rep; | |
// get the number of frames. If it's 0, it's not an animated gif, do nothing | |
int frameCount = [[bitmapRep valueForProperty:NSImageFrameCount] intValue]; | |
if (frameCount == 0) { | |
break; | |
} | |
// create an animation array which will contain all the frames of the animation | |
NSMutableArray *values = [NSMutableArray arrayWithCapacity:frameCount]; | |
// create an array of delay times which contain the time to delay between each frame | |
NSMutableArray *delayTimes = [NSMutableArray arrayWithCapacity:frameCount]; | |
// create a secondary array of Key times, which contain the cumulative time (on the animation timeline) that each frame should "hit" | |
__unused NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:frameCount]; | |
// we will increment this in our loop below to set total animation length | |
NSTimeInterval animationDuration = 0.0; | |
// loop through the frames | |
for (int i = 0; i < frameCount; i++) { | |
// set the current frame we're operating on | |
[bitmapRep setProperty:NSImageCurrentFrame withValue:[NSNumber numberWithInt:i]]; | |
NSDictionary *frameProperties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, NULL); | |
NSDictionary *framePropertiesGIF = [frameProperties objectForKey:(id)kCGImagePropertyGIFDictionary]; | |
// Try to use the unclamped delay time; fall back to the normal delay time. | |
NSNumber *delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFUnclampedDelayTime]; | |
if (!delayTime) { | |
delayTime = [framePropertiesGIF objectForKey:(id)kCGImagePropertyGIFDelayTime]; | |
} | |
// If we don't get a delay time from the properties, fall back to `kDelayTimeIntervalDefault` or carry over the preceding frame's value. | |
const NSTimeInterval kDelayTimeIntervalDefault = 0.1; | |
if (!delayTime) { | |
if (i == 0) { | |
NSLog(@"Falling back to default delay time for first frame, because a delay time was not found in GIF properties %@", frameProperties); | |
delayTime = @(kDelayTimeIntervalDefault); | |
} else { | |
NSLog(@"Falling back to preceding delay time for frame #%d, because a delay time was not found in GIF properties %@", i, frameProperties); | |
delayTime = delayTimes.lastObject; | |
} | |
} | |
[delayTimes addObject:delayTime]; | |
[values addObject:(id)[bitmapRep CGImage]]; | |
// increment the duration of the animation | |
animationDuration += delayTime.floatValue; | |
} | |
// create the animation | |
CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"]; | |
[animation setValues:values]; | |
[animation setCalculationMode:@"discrete"]; | |
[animation setDuration:(animationDuration - [delayTimes.lastObject floatValue])]; | |
[animation setRepeatCount:HUGE_VAL]; | |
// add the animation to the layer | |
[self.animationLayer addAnimation:animation forKey:@"contents"]; | |
// ignore any other representations | |
break; | |
} | |
} | |
[self resizeSubviewsWithOldSize:self.frame.size]; | |
} | |
- (void)setData:(NSMutableDictionary *)data isNew:(BOOL)isNew { | |
} | |
- (void)load:(CompletionBlock)completion { | |
if (completion) { | |
completion(YES); | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment