Revision: 15217 http://sourceforge.net/p/skim-app/code/15217 Author: hofman Date: 2025-05-16 14:40:23 +0000 (Fri, 16 May 2025) Log Message: ----------- Draw image for next page in presentation view on a background queue, so it will be available immediately on goToNextPage:
Modified Paths: -------------- trunk/SKPresentationView.h trunk/SKPresentationView.m Modified: trunk/SKPresentationView.h =================================================================== --- trunk/SKPresentationView.h 2025-05-15 21:22:20 UTC (rev 15216) +++ trunk/SKPresentationView.h 2025-05-16 14:40:23 UTC (rev 15217) @@ -73,6 +73,7 @@ #pragma mark - @interface SKPresentationView : SKPDFPageView { + NSMapTable *predrawnImages; SKNavigationWindow *navWindow; SKCursorStyleWindow *cursorWindow; NSInteger laserPointerColor; Modified: trunk/SKPresentationView.m =================================================================== --- trunk/SKPresentationView.m 2025-05-15 21:22:20 UTC (rev 15216) +++ trunk/SKPresentationView.m 2025-05-16 14:40:23 UTC (rev 15217) @@ -146,8 +146,8 @@ CGFloat scale = [[self window] backingScaleFactor]; if (fabs([pageLayer contentsScale] - scale) > 0.0) { [pageLayer setContentsScale:scale]; - if (page) - [self displayPage:nil]; + [self removePredrawnImageAtIndex:NSNotFound]; + [self displayPage:nil]; } } @@ -172,7 +172,14 @@ - (void)displayPage:(PDFPage *)newPage completionHandler:(void (^)(void))completionHandler { page = newPage; - [self displayPage:completionHandler]; + if (page) { + [self displayPage:completionHandler]; + } else { + [self removePredrawnImageAtIndex:NSNotFound]; + [pageLayer setContents:nil]; + if (completionHandler) + completionHandler(); + } [[NSNotificationCenter defaultCenter] postNotificationName:SKPresentationViewPageChangedNotification object:self]; } @@ -283,23 +290,53 @@ #pragma mark Drawing +- (NSImage *)predrawnImageAtIndex:(NSUInteger)pageIndex { return nil; } + +- (void)removePredrawnImageAtIndex:(NSUInteger)pageIndex {} + +- (NSImage *)imageWithImageRep:(NSBitmapImageRep *)imageRep page:(PDFPage *)aPage autoScales:(BOOL)autoScales { + NSRect bounds = {NSZeroPoint, [imageRep size]}; + NSRect pageRect = [aPage boundsForBox:kPDFDisplayBoxCropBox]; + if (([page rotation] % 180) != 0) + pageRect = NSMakeRect(0.0, 0.0, NSHeight(pageRect), NSWidth(pageRect)); + CGFloat scale = autoScales ? fmin(NSHeight(bounds) / NSHeight(pageRect), NSWidth(bounds) / NSWidth(pageRect)) : 1.0; + pageRect = NSInsetRect(bounds, 0.5 * (NSWidth(bounds) - scale * NSWidth(pageRect)), 0.5 * (NSHeight(bounds) - scale * NSHeight(pageRect))); + CGContextRef context = [[NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep] CGContext]; + + CGContextSaveGState(context); + CGContextSetFillColorWithColor(context, CGColorGetConstantColor(kCGColorWhite)); + CGContextFillRect(context, SKPixelAlignedRect(NSRectToCGRect(pageRect), context)); + CGContextRestoreGState(context); + CGContextSaveGState(context); + CGContextSetInterpolationQuality(context, [[NSUserDefaults standardUserDefaults] integerForKey:SKInterpolationQualityKey] + 1); + CGContextTranslateCTM(context, NSMinX(pageRect), NSMinY(pageRect)); + CGContextScaleCTM(context, scale, scale); + [aPage drawWithBox:kPDFDisplayBoxCropBox toContext:context]; + CGContextRestoreGState(context); + + NSImage *image = [[NSImage alloc] initWithSize:bounds.size]; + [image addRepresentation:imageRep]; + + return image; +} + - (void)displayPage:(void (^)(void))completionHandler { - if (page == nil) { - [pageLayer setContents:nil]; + if (page == nil) + return; + + NSUInteger pageIndex = [page pageIndex]; + NSImage *predrawnImage = [self predrawnImageAtIndex:pageIndex]; + + if (predrawnImage) { + [pageLayer setContents:predrawnImage]; + [self removePredrawnImageAtIndex:pageIndex]; if (completionHandler) completionHandler(); return; } - static dispatch_queue_t drawingQueue = nil; - if (drawingQueue == nil) { - dispatch_queue_attr_t queuePriority = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, 0); - drawingQueue = dispatch_queue_create("net.sourceforge.skim-app.skim.pageView", queuePriority); - } + NSBitmapImageRep *imageRep = [self bitmapImageRepForCachingDisplayInRect:[self bounds]]; - NSRect bounds = [self bounds]; - NSBitmapImageRep *imageRep = [self bitmapImageRepForCachingDisplayInRect:bounds]; - if (imageRep == nil) { if (completionHandler) completionHandler(); @@ -306,31 +343,19 @@ return; } + static dispatch_queue_t drawingQueue = nil; + if (drawingQueue == nil) { + dispatch_queue_attr_t queuePriority = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_UTILITY, 0); + drawingQueue = dispatch_queue_create("net.sourceforge.skim-app.skim.pageview.drawing", queuePriority); + } + PDFPage *thePage = page; - NSRect pageRect = [page boundsForBox:kPDFDisplayBoxCropBox]; - if (([page rotation] % 180) != 0) - pageRect = NSMakeRect(0.0, 0.0, NSHeight(pageRect), NSWidth(pageRect)); - CGFloat scale = [self autoScales] ? fmin(NSHeight(bounds) / NSHeight(pageRect), NSWidth(bounds) / NSWidth(pageRect)) : 1.0; - pageRect = NSInsetRect(bounds, 0.5 * (NSWidth(bounds) - scale * NSWidth(pageRect)), 0.5 * (NSHeight(bounds) - scale * NSHeight(pageRect))); - + BOOL autoScales = [self autoScales]; + dispatch_async(drawingQueue, ^{ - CGContextRef context = [[NSGraphicsContext graphicsContextWithBitmapImageRep:imageRep] CGContext]; + NSImage *image = [self imageWithImageRep:imageRep page:thePage autoScales:autoScales]; - CGContextSaveGState(context); - CGContextSetFillColorWithColor(context, CGColorGetConstantColor(kCGColorWhite)); - CGContextFillRect(context, SKPixelAlignedRect(NSRectToCGRect(pageRect), context)); - CGContextRestoreGState(context); - CGContextSaveGState(context); - CGContextSetInterpolationQuality(context, [[NSUserDefaults standardUserDefaults] integerForKey:SKInterpolationQualityKey] + 1); - CGContextTranslateCTM(context, NSMinX(pageRect), NSMinY(pageRect)); - CGContextScaleCTM(context, scale, scale); - [thePage drawWithBox:kPDFDisplayBoxCropBox toContext:context]; - CGContextRestoreGState(context); - - NSImage *image = [[NSImage alloc] initWithSize:bounds.size]; - [image addRepresentation:imageRep]; - dispatch_async(dispatch_get_main_queue(), ^{ if (thePage == page) @@ -341,6 +366,7 @@ }); }); + } - (NSBitmapImageRep *)bitmapImageRepCachingDisplay { @@ -396,18 +422,95 @@ return self; } +- (void)viewWillStartLiveResize { + [super viewWillStartLiveResize]; + [self removePredrawnImageAtIndex:NSNotFound]; +} + - (void)viewDidEndLiveResize { [super viewDidEndLiveResize]; + [self removePredrawnImageAtIndex:NSNotFound]; [self displayPage:nil]; } - (void)updatedAnnotationOnPage:(PDFPage *)aPage { if (page == aPage) { + [self removePredrawnImageAtIndex:[aPage pageIndex]]; [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(displayPage:) object:nil]; [self performSelector:@selector(displayPage:) withObject:nil afterDelay:0.0]; } } +- (NSImage *)predrawnImageAtIndex:(NSUInteger)pageIndex { + if (predrawnImages == nil) + return nil; + NSImage *image = (__bridge id)NSMapGet(predrawnImages, (void *)pageIndex); + if ([image isKindOfClass:[NSImage class]]) + return image; + return nil; +} + +- (void)removePredrawnImageAtIndex:(NSUInteger)pageIndex { + if (pageIndex == NSNotFound) + predrawnImages = nil; + else if (predrawnImages) + NSMapRemove(predrawnImages, (void *)pageIndex); +} + +- (void)displayPage:(void (^)(void))completionHandler { + if (page == nil) + return; + + [super displayPage:completionHandler]; + + // generate an image for the next page in the background, which is usually needed next for a presentation + + NSUInteger pageIndex = [page pageIndex] + 1; + + if (pageIndex >= [[page document] pageCount]) + return; + + if (predrawnImages == nil) + predrawnImages = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsOpaqueMemory | NSPointerFunctionsIntegerPersonality valueOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPersonality capacity:2]; + else if (NSMapGet(predrawnImages, (void *)pageIndex)) + return; + + // set NSNull so we can invalidate this image + NSMapInsert(predrawnImages, (void *)pageIndex, (__bridge void *)[NSNull null]); + + static dispatch_queue_t predrawingQueue = nil; + if (predrawingQueue == nil) { + dispatch_queue_attr_t queuePriority = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0); + predrawingQueue = dispatch_queue_create("net.sourceforge.skim-app.skim.pageview.predrawing", queuePriority); + } + + NSBitmapImageRep *imageRep = [self bitmapImageRepForCachingDisplayInRect:[self bounds]]; + + if (imageRep == nil) + return; + + PDFPage *thePage = [[page document] pageAtIndex:pageIndex]; + BOOL autoScales = [self autoScales]; + + dispatch_async(predrawingQueue, ^{ + + NSImage *image = [self imageWithImageRep:imageRep page:thePage autoScales:autoScales]; + + dispatch_async(dispatch_get_main_queue(), ^{ + + if (image && predrawnImages && (__bridge id)NSMapGet(predrawnImages, (void *)pageIndex) == [NSNull null]) { + if (page == thePage) + [pageLayer setContents:image]; + else + NSMapInsert(predrawnImages, (void *)pageIndex, (__bridge void *)image); + } + + }); + + }); + +} + #pragma mark Accessors - (BOOL)canBecomeKeyView { @@ -422,6 +525,7 @@ if (flag != pvFlags.autoScales) { pvFlags.autoScales = flag; [pageLayer setContentsGravity:flag ? kCAGravityResizeAspectFill : kCAGravityCenter]; + [self removePredrawnImageAtIndex:NSNotFound]; [self displayPage:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:SKPresentationViewAutoScalesChangedNotification object:self]; } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. _______________________________________________ Skim-app-commit mailing list Skim-app-commit@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/skim-app-commit