From a1776ff29fa6ddc0721b43a1f7b0d2a1666ef03e Mon Sep 17 00:00:00 2001 From: Dominic Yu Date: Thu, 18 Jan 2024 14:49:10 -0800 Subject: [PATCH] animated webp support --- DYImageView.h | 1 + DYImageView.m | 43 +++++++++++++++++++++++++++++++++++++++++-- SlideshowWindow.m | 10 ++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/DYImageView.h b/DYImageView.h index 1aa7f44..6e1c742 100644 --- a/DYImageView.h +++ b/DYImageView.h @@ -26,6 +26,7 @@ typedef NS_ENUM(char, DYImageViewZoomMode) { @interface DYImageView : NSView @property (nonatomic) NSImage *image; +@property (nonatomic) id webpImageSource; // CGImageSourceRef @property (nonatomic) int rotation; @property (nonatomic) BOOL scalesUp; @property (nonatomic) BOOL showActualSize; diff --git a/DYImageView.m b/DYImageView.m index 0c15485..4543e2c 100644 --- a/DYImageView.m +++ b/DYImageView.m @@ -17,6 +17,7 @@ @implementation DYImageView { NSImage *image; NSTimer *gifTimer; + NSArray *_webpFrameInfo; int _webpCurrentFrame; NSUInteger _webpFrameCount; int rotation; BOOL scalesUp, showActualSize, isImageFlipped; NSRect sourceRect; @@ -122,12 +123,28 @@ - (void)drawRect:(NSRect)rect { } NSGraphicsContext *cg = NSGraphicsContext.currentContext; NSImageInterpolation oldInterp = cg.imageInterpolation; - cg.imageInterpolation = zoom > 1 ? NSImageInterpolationNone : NSImageInterpolationHigh; - [image drawInRect:destinationRect fromRect:srcRect operation:NSCompositingOperationSourceOver fraction:1.0]; + cg.imageInterpolation = zoom >= 4 ? NSImageInterpolationNone : NSImageInterpolationHigh; + NSImage *toDraw = image; + if (_webpImageSource) { + CGImageRef webpFrame = CGImageSourceCreateImageAtIndex((CGImageSourceRef)_webpImageSource, _webpCurrentFrame, NULL); + if (webpFrame) { + toDraw = [[NSImage alloc] initWithCGImage:webpFrame size:NSZeroSize]; + if (!gifTimer || gifTimer.userInfo != _webpImageSource) { + float frameDuration = [_webpFrameInfo[_webpCurrentFrame][@"DelayTime"] floatValue]; + if (frameDuration < .01) frameDuration = .01; // minimum of 10ms is apparently standard + gifTimer = [NSTimer scheduledTimerWithTimeInterval:frameDuration target:self selector:@selector(animateWebp:) userInfo:_webpImageSource repeats:NO]; + gifTimer.tolerance = frameDuration*0.1; + } + } + CFRelease(webpFrame); + } + [toDraw drawInRect:destinationRect fromRect:srcRect operation:NSCompositingOperationSourceOver fraction:1.0]; cg.imageInterpolation = oldInterp; [transform invert]; [transform concat]; + + if (image != toDraw) return; // animating webp, so no need to check if animating gif id rep = image.representations[0]; if ([rep isKindOfClass:[NSBitmapImageRep class]] && [rep valueForProperty:NSImageFrameCount]) { @@ -153,9 +170,31 @@ - (void)animateGIF:(NSTimer *)t { [self setNeedsDisplay:YES]; } +- (void)setWebpImageSource:(id)src { + _webpImageSource = nil; + CFDictionaryRef props = CGImageSourceCopyProperties((CGImageSourceRef)src, NULL); + if (props) { + NSArray *frameInfo = ((__bridge NSDictionary *)props)[@"{WebP}"][@"FrameInfo"]; + if (frameInfo && (_webpFrameCount = frameInfo.count) > 1) { + _webpImageSource = src; + _webpFrameInfo = frameInfo; + } + CFRelease(props); + } +} + +- (void)animateWebp:(NSTimer *)t { + gifTimer = nil; + if (_webpImageSource != t.userInfo) return; + if (++_webpCurrentFrame == _webpFrameCount) _webpCurrentFrame = 0; + [self setNeedsDisplay:YES]; +} + - (void)setImage:(NSImage *)anImage { if (anImage != image) { image = anImage; + _webpImageSource = nil; + _webpCurrentFrame = 0; } zoomF = 0; rotation = 0; diff --git a/SlideshowWindow.m b/SlideshowWindow.m index 74c689c..d76c955 100644 --- a/SlideshowWindow.m +++ b/SlideshowWindow.m @@ -596,6 +596,16 @@ - (void)displayImage { if (hideInfoFld) infoFld.hidden = YES; // this must happen before setImage, for redraw purposes imgView.image = img; + if ([theFile.pathExtension.lowercaseString isEqualToString:@"webp"]) { + // check for animated webp + CGImageSourceRef src = CGImageSourceCreateWithURL((__bridge CFURLRef)[NSURL fileURLWithPath:theFile isDirectory:NO], NULL); + if (src) { + if (CGImageSourceGetCount(src) > 1) + imgView.webpImageSource = CFBridgingRelease(src); + else + CFRelease(src); + } + } if (r) imgView.rotation = r; if (imgFlipped) imgView.imageFlipped = YES; // ** see keyDown for specifics