From 249bc962825e412d21d82edad376a6f0e15386d3 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 8 Nov 2025 19:14:11 +0200 Subject: [PATCH 01/18] Fix iOS screenshot to include peer components --- Ports/iOSPort/nativeSources/IOSNative.m | 97 +++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index cebdfce1d5..1fa040f6a1 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5194,25 +5194,110 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam #endif } -static UIImage* cn1_captureView(UIView *view) { +static UIView* cn1_rootViewForCapture(UIView *view) { if (view == nil) { return nil; } - CGSize size = view.bounds.size; + + UIView *rootView = view; + UIWindow *window = view.window; + + if (window == nil) { + NSArray *windows = [UIApplication sharedApplication].windows; + for (UIWindow *candidate in windows) { + if ([view isDescendantOfView:candidate]) { + window = candidate; + break; + } + } + } + + if (window == nil) { +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13.0, *)) { + NSSet *connectedScenes = [UIApplication sharedApplication].connectedScenes; + for (UIScene *scene in connectedScenes) { + if (![scene isKindOfClass:[UIWindowScene class]]) { + continue; + } + if (scene.activationState != UISceneActivationStateForegroundActive) { + continue; + } + UIWindowScene *windowScene = (UIWindowScene *)scene; + for (UIWindow *candidate in windowScene.windows) { + if ([view isDescendantOfView:candidate]) { + window = candidate; + break; + } + } + if (window != nil) { + break; + } + if (windowScene.windows.count > 0 && window == nil) { + window = windowScene.windows.firstObject; + } + } + } +#endif + } + + if (window == nil) { + window = [UIApplication sharedApplication].keyWindow; + } + + if (window != nil) { + rootView = window; + } else { + UIView *candidate = view; + while (candidate.superview != nil) { + candidate = candidate.superview; + } + rootView = candidate; + } + + return rootView; +} + +static UIImage* cn1_captureView(UIView *view) { + UIView *rootView = cn1_rootViewForCapture(view); + if (rootView == nil) { + return nil; + } + + CGSize size = rootView.bounds.size; if (size.width <= 0 || size.height <= 0) { return nil; } - UIGraphicsBeginImageContextWithOptions(size, view.opaque, 0.0); + UIGraphicsBeginImageContextWithOptions(size, rootView.opaque, 0.0); BOOL ok = NO; - if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { - ok = [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES]; + if ([rootView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + ok = [rootView drawViewHierarchyInRect:rootView.bounds afterScreenUpdates:YES]; } if (!ok) { - [view.layer renderInContext:UIGraphicsGetCurrentContext()]; + [rootView.layer renderInContext:UIGraphicsGetCurrentContext()]; } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); + + if (rootView != view) { + CGRect targetFrame = [rootView convertRect:view.bounds fromView:view]; + targetFrame = CGRectIntersection(targetFrame, CGRectMake(0, 0, size.width, size.height)); + if (!CGRectIsNull(targetFrame) && targetFrame.size.width > 0 && targetFrame.size.height > 0) { + CGRect integralTarget = CGRectIntegral(targetFrame); + CGRect pixelRect = CGRectMake(integralTarget.origin.x * image.scale, + integralTarget.origin.y * image.scale, + integralTarget.size.width * image.scale, + integralTarget.size.height * image.scale); + CGImageRef cropped = CGImageCreateWithImageInRect(image.CGImage, pixelRect); + if (cropped != nil) { + UIImage *croppedImage = [UIImage imageWithCGImage:cropped scale:image.scale orientation:image.imageOrientation]; + CGImageRelease(cropped); + image = croppedImage; + } + } + } + return image; } From bb9f8d586120111e46f1ca03553e084727d39ab8 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 8 Nov 2025 19:52:28 +0200 Subject: [PATCH 02/18] Include peer windows in iOS screenshots --- Ports/iOSPort/nativeSources/IOSNative.m | 53 ++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 1fa040f6a1..13d7d70b87 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5270,12 +5270,53 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam } UIGraphicsBeginImageContextWithOptions(size, rootView.opaque, 0.0); - BOOL ok = NO; - if ([rootView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { - ok = [rootView drawViewHierarchyInRect:rootView.bounds afterScreenUpdates:YES]; - } - if (!ok) { - [rootView.layer renderInContext:UIGraphicsGetCurrentContext()]; + CGContextRef ctx = UIGraphicsGetCurrentContext(); + if ([rootView isKindOfClass:[UIWindow class]]) { + UIWindow *targetWindow = (UIWindow *)rootView; + NSArray *windows = nil; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13.0, *)) { + if (targetWindow.windowScene != nil) { + windows = targetWindow.windowScene.windows; + } + } +#endif + if (windows == nil || windows.count == 0) { + windows = [UIApplication sharedApplication].windows; + } + // Render every visible window that shares the same screen as the Codename One GL window + // so that native peer components hosted outside of the GL view (e.g. BrowserComponent) + // are composited into the captured image. + for (UIWindow *window in windows) { + if (window.hidden || window.alpha <= 0.0f) { + continue; + } + if (window.screen != targetWindow.screen) { + continue; + } + CGContextSaveGState(ctx); + CGRect translated = window.bounds; + if (window != targetWindow) { + translated = [targetWindow convertRect:window.bounds fromWindow:window]; + } + CGContextTranslateCTM(ctx, translated.origin.x, translated.origin.y); + BOOL windowDrawn = NO; + if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + windowDrawn = [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES]; + } + if (!windowDrawn) { + [window.layer renderInContext:ctx]; + } + CGContextRestoreGState(ctx); + } + } else { + BOOL ok = NO; + if ([rootView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + ok = [rootView drawViewHierarchyInRect:rootView.bounds afterScreenUpdates:YES]; + } + if (!ok) { + [rootView.layer renderInContext:ctx]; + } } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); From a5fe31cea4906bacda34b46588335fb82b1293a8 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 8 Nov 2025 20:28:18 +0200 Subject: [PATCH 03/18] Order captured windows by level for screenshots --- Ports/iOSPort/nativeSources/IOSNative.m | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 13d7d70b87..12952b6e9e 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5286,8 +5286,33 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam } // Render every visible window that shares the same screen as the Codename One GL window // so that native peer components hosted outside of the GL view (e.g. BrowserComponent) - // are composited into the captured image. - for (UIWindow *window in windows) { + // are composited into the captured image. Windows need to be rendered from back to front + // based on their window level so that overlay windows (e.g. ones hosting peer components) + // appear above the GL view in the resulting screenshot. + NSArray *orderedWindows = windows; + if (windows.count > 1) { + orderedWindows = [windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow * _Nonnull lhs, UIWindow * _Nonnull rhs) { + CGFloat lhsLevel = lhs.windowLevel; + CGFloat rhsLevel = rhs.windowLevel; + if (lhsLevel < rhsLevel) { + return NSOrderedAscending; + } + if (lhsLevel > rhsLevel) { + return NSOrderedDescending; + } + NSUInteger lhsIndex = [windows indexOfObjectIdenticalTo:lhs]; + NSUInteger rhsIndex = [windows indexOfObjectIdenticalTo:rhs]; + if (lhsIndex < rhsIndex) { + return NSOrderedAscending; + } + if (lhsIndex > rhsIndex) { + return NSOrderedDescending; + } + return NSOrderedSame; + }]; + } + + for (UIWindow *window in orderedWindows) { if (window.hidden || window.alpha <= 0.0f) { continue; } From a05834cbbe8123439f3a425590fe8233e2c0e0e1 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 9 Nov 2025 03:47:57 +0200 Subject: [PATCH 04/18] Overlay peer components in iOS screenshots --- Ports/iOSPort/nativeSources/IOSNative.m | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 12952b6e9e..b74ffea207 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5194,6 +5194,65 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam #endif } +static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGContextRef ctx) { + if (renderView == nil || rootView == nil || ctx == NULL) { + return; + } + if (renderView.hidden || renderView.alpha <= 0.0f) { + return; + } + + CGRect localBounds = renderView.bounds; + if (CGRectIsEmpty(localBounds) || localBounds.size.width <= 0.0f || localBounds.size.height <= 0.0f) { + return; + } + + CGRect translatedRect = [rootView convertRect:localBounds fromView:renderView]; + if (CGRectIsNull(translatedRect) || CGRectIsEmpty(translatedRect)) { + return; + } + + CGContextSaveGState(ctx); + CGContextTranslateCTM(ctx, translatedRect.origin.x, translatedRect.origin.y); + BOOL drawn = NO; + if ([renderView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + drawn = [renderView drawViewHierarchyInRect:localBounds afterScreenUpdates:YES]; + } + if (!drawn) { + [renderView.layer renderInContext:ctx]; + } + CGContextRestoreGState(ctx); +} + +static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { + CodenameOne_GLViewController *controller = [CodenameOne_GLViewController instance]; + EAGLView *glView = [controller eaglView]; + if (glView == nil || rootView == nil || ctx == NULL) { + return; + } + + UIView *peerLayer = glView.peerComponentsLayer; + NSArray *peerCandidates = nil; + if (peerLayer != nil) { + [peerLayer layoutIfNeeded]; + peerCandidates = peerLayer.subviews; + } else { + [glView layoutIfNeeded]; + peerCandidates = glView.subviews; + } + + if (peerCandidates.count == 0) { + return; + } + + for (UIView *peerView in peerCandidates) { + if (![peerView isKindOfClass:[UIView class]]) { + continue; + } + cn1_renderViewIntoContext(peerView, rootView, ctx); + } +} + static UIView* cn1_rootViewForCapture(UIView *view) { if (view == nil) { return nil; @@ -5319,6 +5378,7 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam if (window.screen != targetWindow.screen) { continue; } + [window layoutIfNeeded]; CGContextSaveGState(ctx); CGRect translated = window.bounds; if (window != targetWindow) { @@ -5334,6 +5394,7 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam } CGContextRestoreGState(ctx); } + cn1_renderPeerComponents(rootView, ctx); } else { BOOL ok = NO; if ([rootView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { @@ -5342,6 +5403,7 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam if (!ok) { [rootView.layer renderInContext:ctx]; } + cn1_renderPeerComponents(rootView, ctx); } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); From 9a12e514457cd4a7a7759de7899f5f9bf9a2122c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 9 Nov 2025 04:18:04 +0200 Subject: [PATCH 05/18] Snapshot WKWebView peers when capturing screenshots --- Ports/iOSPort/nativeSources/IOSNative.m | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index b74ffea207..5ec58dadc3 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5215,7 +5215,22 @@ static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGCo CGContextSaveGState(ctx); CGContextTranslateCTM(ctx, translatedRect.origin.x, translatedRect.origin.y); BOOL drawn = NO; - if ([renderView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { +#if defined(ENABLE_WKWEBVIEW) && defined(supportsWKWebKit) + if ([renderView isKindOfClass:[WKWebView class]]) { + UIView *snapshotView = [renderView snapshotViewAfterScreenUpdates:YES]; + if (snapshotView != nil) { + BOOL snapshotDrawn = NO; + if ([snapshotView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + snapshotDrawn = [snapshotView drawViewHierarchyInRect:snapshotView.bounds afterScreenUpdates:YES]; + } + if (!snapshotDrawn) { + [snapshotView.layer renderInContext:ctx]; + } + drawn = YES; + } + } +#endif + if (!drawn && [renderView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { drawn = [renderView drawViewHierarchyInRect:localBounds afterScreenUpdates:YES]; } if (!drawn) { From c291de6a56ba7c5be0393ed7587facaaf669a172 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 9 Nov 2025 04:58:45 +0200 Subject: [PATCH 06/18] Capture WKWebView content via takeSnapshot --- Ports/iOSPort/nativeSources/IOSNative.m | 62 +++++++++++++++++++++---- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 5ec58dadc3..214a6c9adf 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5217,16 +5217,62 @@ static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGCo BOOL drawn = NO; #if defined(ENABLE_WKWEBVIEW) && defined(supportsWKWebKit) if ([renderView isKindOfClass:[WKWebView class]]) { - UIView *snapshotView = [renderView snapshotViewAfterScreenUpdates:YES]; - if (snapshotView != nil) { - BOOL snapshotDrawn = NO; - if ([snapshotView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { - snapshotDrawn = [snapshotView drawViewHierarchyInRect:snapshotView.bounds afterScreenUpdates:YES]; + WKWebView *webView = (WKWebView *)renderView; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 + if (@available(iOS 11.0, *)) { + CGRect snapshotRect = CGRectIntersection(webView.bounds, localBounds); + if (!CGRectIsNull(snapshotRect) && !CGRectIsEmpty(snapshotRect)) { + WKSnapshotConfiguration *config = [[WKSnapshotConfiguration alloc] init]; + config.rect = snapshotRect; + if (snapshotRect.size.width > 0.0f) { + config.snapshotWidth = @(snapshotRect.size.width); + } +#ifdef __IPHONE_13_0 + if (@available(iOS 13.0, *)) { + config.afterScreenUpdates = YES; + } +#endif + __block UIImage *snapshotImage = nil; + __block BOOL snapshotComplete = NO; + [webView takeSnapshotWithConfiguration:config completionHandler:^(UIImage * _Nullable image, NSError * _Nullable error) { + if (image != nil) { + snapshotImage = image; + } else if (error != nil) { + NSLog(@"WKWebView snapshot failed: %@", error); + } + snapshotComplete = YES; + }]; + [config release]; + + if (!snapshotComplete) { + NSTimeInterval timeout = 1.0; + while (!snapshotComplete && timeout > 0) { + NSTimeInterval step = 0.01; + NSDate *stepDate = [NSDate dateWithTimeIntervalSinceNow:step]; + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:stepDate]; + timeout -= step; + } + } + + if (snapshotImage != nil) { + [snapshotImage drawInRect:CGRectMake(0, 0, localBounds.size.width, localBounds.size.height)]; + drawn = YES; + } } - if (!snapshotDrawn) { - [snapshotView.layer renderInContext:ctx]; + } +#endif + if (!drawn) { + UIView *snapshotView = [renderView snapshotViewAfterScreenUpdates:YES]; + if (snapshotView != nil) { + BOOL snapshotDrawn = NO; + if ([snapshotView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { + snapshotDrawn = [snapshotView drawViewHierarchyInRect:snapshotView.bounds afterScreenUpdates:YES]; + } + if (!snapshotDrawn) { + [snapshotView.layer renderInContext:ctx]; + } + drawn = YES; } - drawn = YES; } } #endif From fadcba592bda6a348a6029f206852cfba63e95f5 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 9 Nov 2025 18:07:04 +0200 Subject: [PATCH 07/18] Add WKWebView fallback diagnostic overlay --- Ports/iOSPort/nativeSources/IOSNative.m | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 214a6c9adf..f032c4f2c9 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5218,6 +5218,18 @@ static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGCo #if defined(ENABLE_WKWEBVIEW) && defined(supportsWKWebKit) if ([renderView isKindOfClass:[WKWebView class]]) { WKWebView *webView = (WKWebView *)renderView; + // Paint a diagnostic fallback so we can tell if the WKWebView failed to render. + if (localBounds.size.width > 0.0f && localBounds.size.height > 0.0f) { + CGContextSaveGState(ctx); + CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor); + CGContextSetLineWidth(ctx, 4.0f); + CGContextMoveToPoint(ctx, 0.0f, 0.0f); + CGContextAddLineToPoint(ctx, localBounds.size.width, localBounds.size.height); + CGContextMoveToPoint(ctx, localBounds.size.width, 0.0f); + CGContextAddLineToPoint(ctx, 0.0f, localBounds.size.height); + CGContextStrokePath(ctx); + CGContextRestoreGState(ctx); + } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 if (@available(iOS 11.0, *)) { CGRect snapshotRect = CGRectIntersection(webView.bounds, localBounds); From 43f713f7ae8e1160936449b2f21e78c391cdf013 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sun, 9 Nov 2025 19:22:21 +0200 Subject: [PATCH 08/18] Add hierarchy overlay diagnostics to iOS screenshots --- Ports/iOSPort/nativeSources/IOSNative.m | 167 ++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index f032c4f2c9..f9ee11c30c 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5297,6 +5297,154 @@ static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGCo CGContextRestoreGState(ctx); } +static void cn1_collectViewHierarchy(UIView *view, NSMutableArray *lines, NSInteger depth, UIView *targetView, UIView *rootView) { + if (view == nil || lines == nil) { + return; + } + + NSMutableString *line = [NSMutableString string]; + for (NSInteger i = 0; i < depth; i++) { + [line appendString:@" "]; + } + + NSString *className = NSStringFromClass([view class]); + [line appendFormat:@"%@<%p>", className, view]; + + if (view == rootView) { + [line appendString:@" [captureRoot]"]; + } + + if (view == targetView) { + [line appendString:@" [targetView]"]; + } + + CGRect frame = view.frame; + [line appendFormat:@" frame=%@", NSStringFromCGRect(frame)]; + + [line appendFormat:@" hidden=%@", view.hidden ? @"YES" : @"NO"]; + [line appendFormat:@" alpha=%.2f", view.alpha]; + [line appendFormat:@" userInteraction=%@", view.userInteractionEnabled ? @"YES" : @"NO"]; + + if (view.tag != 0) { + [line appendFormat:@" tag=%ld", (long)view.tag]; + } + + NSString *identifier = nil; + if ([view respondsToSelector:@selector(accessibilityIdentifier)]) { + identifier = view.accessibilityIdentifier; + } + + if (identifier.length > 0) { + [line appendFormat:@" accessibilityIdentifier=%@", identifier]; + } + + [lines addObject:line]; + + for (UIView *subview in view.subviews) { + cn1_collectViewHierarchy(subview, lines, depth + 1, targetView, rootView); + } +} + +static void cn1_appendWindowHierarchyLines(NSArray *windows, UIWindow *targetWindow, UIView *targetView, NSMutableArray *lines) { + if (windows == nil || lines == nil || windows.count == 0) { + return; + } + + [lines addObject:@"Window capture order (back to front):"]; + + NSInteger index = 0; + for (UIWindow *window in windows) { + NSMutableString *header = [NSMutableString string]; + [header appendFormat:@"[%ld] %@<%p>", (long)index, NSStringFromClass([window class]), window]; + if (window == targetWindow) { + [header appendString:@" [targetWindow]"]; + } + [header appendFormat:@" level=%.2f", window.windowLevel]; + [header appendFormat:@" hidden=%@", window.hidden ? @"YES" : @"NO"]; + [header appendFormat:@" alpha=%.2f", window.alpha]; + [header appendFormat:@" frame=%@", NSStringFromCGRect(window.frame)]; + [lines addObject:header]; + cn1_collectViewHierarchy(window, lines, 1, targetView, targetWindow); + index++; + } +} + +static void cn1_drawHierarchyOverlay(CGContextRef ctx, UIView *rootView, CGRect targetRect, NSArray *lines) { + if (ctx == nil || rootView == nil || lines == nil || lines.count == 0) { + return; + } + + NSString *text = [lines componentsJoinedByString:@"\n"]; + if (text.length == 0) { + return; + } + + UIFont *font = nil; +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13.0, *)) { + font = [UIFont monospacedSystemFontOfSize:12.0 weight:UIFontWeightRegular]; + } +#endif + if (font == nil) { + font = [UIFont fontWithName:@"Menlo" size:12.0]; + } + if (font == nil) { + font = [UIFont systemFontOfSize:12.0]; + } + + NSDictionary *attributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : [UIColor whiteColor] }; + + CGSize canvasSize = rootView.bounds.size; + if (canvasSize.width <= 0.0f || canvasSize.height <= 0.0f) { + return; + } + + CGFloat outerPadding = 12.0f; + CGFloat innerPadding = 8.0f; + CGFloat availableWidth = MAX(0.0f, canvasSize.width - (outerPadding * 2.0f)); + CGSize constraint = CGSizeMake(availableWidth, CGFLOAT_MAX); + CGRect textBounds = [text boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil]; + textBounds.size.width = ceil(textBounds.size.width); + textBounds.size.height = ceil(textBounds.size.height); + + if (CGRectIsNull(targetRect) || CGRectIsInfinite(targetRect)) { + targetRect = rootView.bounds; + } + + CGPoint anchor = CGPointMake(CGRectGetMinX(targetRect) + outerPadding, CGRectGetMinY(targetRect) + outerPadding); + + CGRect backgroundRect = CGRectMake(anchor.x - innerPadding, + anchor.y - innerPadding, + textBounds.size.width + innerPadding * 2.0f, + textBounds.size.height + innerPadding * 2.0f); + + if (backgroundRect.origin.x < outerPadding) { + backgroundRect.origin.x = outerPadding; + } + if (backgroundRect.origin.y < outerPadding) { + backgroundRect.origin.y = outerPadding; + } + + if (CGRectGetMaxX(backgroundRect) > canvasSize.width - outerPadding) { + backgroundRect.origin.x = MAX(outerPadding, canvasSize.width - outerPadding - backgroundRect.size.width); + } + + if (CGRectGetMaxY(backgroundRect) > canvasSize.height - outerPadding) { + backgroundRect.origin.y = MAX(outerPadding, canvasSize.height - outerPadding - backgroundRect.size.height); + } + + CGRect textRect = CGRectMake(backgroundRect.origin.x + innerPadding, + backgroundRect.origin.y + innerPadding, + textBounds.size.width, + textBounds.size.height); + + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:backgroundRect cornerRadius:8.0f]; + [[UIColor colorWithWhite:0 alpha:0.7f] setFill]; + [path fill]; + + [text drawInRect:textRect withAttributes:attributes]; +} + static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { CodenameOne_GLViewController *controller = [CodenameOne_GLViewController instance]; EAGLView *glView = [controller eaglView]; @@ -5401,6 +5549,16 @@ static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { return nil; } + CGRect overlayTargetRect = rootView.bounds; + if (rootView != view && view != nil) { + CGRect converted = [rootView convertRect:view.bounds fromView:view]; + if (!CGRectIsNull(converted) && converted.size.width > 0 && converted.size.height > 0) { + overlayTargetRect = converted; + } + } + + NSMutableArray *hierarchyLines = [NSMutableArray array]; + UIGraphicsBeginImageContextWithOptions(size, rootView.opaque, 0.0); CGContextRef ctx = UIGraphicsGetCurrentContext(); if ([rootView isKindOfClass:[UIWindow class]]) { @@ -5444,6 +5602,8 @@ static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { }]; } + cn1_appendWindowHierarchyLines(orderedWindows, targetWindow, view, hierarchyLines); + for (UIWindow *window in orderedWindows) { if (window.hidden || window.alpha <= 0.0f) { continue; @@ -5478,6 +5638,13 @@ static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { } cn1_renderPeerComponents(rootView, ctx); } + + if (hierarchyLines.count == 0) { + cn1_collectViewHierarchy(rootView, hierarchyLines, 0, view, rootView); + } + + cn1_drawHierarchyOverlay(ctx, rootView, overlayTargetRect, hierarchyLines); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); From 0e5268e7d0878c1b452fc645ff106fdf8b738777 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:10:41 +0200 Subject: [PATCH 09/18] Prevent repaint overwrite of iOS screenshots --- .../codename1/impl/CodenameOneImplementation.java | 14 ++++++++++++++ CodenameOne/src/com/codename1/ui/Display.java | 14 ++++++++++++++ .../com/codename1/impl/ios/IOSImplementation.java | 11 +++++++++++ .../animations/AnimationDemosScreenshotTest.java | 4 +++- .../tests/Cn1ssDeviceRunnerHelper.java | 4 +++- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java index e24c30f36e..cd8034f54e 100644 --- a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java +++ b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java @@ -1214,6 +1214,20 @@ public void screenshot(SuccessCallback callback) { callback.onSucess(img); } + /** + * Indicates whether the Codename One implementation expects caller-side painting to run + * after a native screenshot has been captured. Implementations that composite peer + * components into the screenshot can override this to {@code false} so downstream code + * won't immediately repaint the captured image and accidentally hide native peers. + * + * @param screenshot the screenshot image produced by {@link #screenshot(SuccessCallback)} + * @return {@code true} if the caller should repaint Codename One components onto the + * screenshot, {@code false} otherwise. + */ + public boolean shouldPaintNativeScreenshot(Image screenshot) { + return true; + } + /** * Returns true if the platform supports a native image cache. The native image cache * is different than just {@link FileSystemStorage#hasCachesDir()}. A native image cache diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index 09531b029e..ac7e9dfd24 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -5013,6 +5013,20 @@ public void screenshot(SuccessCallback callback) { impl.screenshot(callback); } + /** + * Indicates whether callers should repaint Codename One components onto a screenshot image + * returned from {@link #screenshot(SuccessCallback)}. Platforms that already composite + * peer components into the native screenshot can return {@code false} to keep the captured + * pixels intact. + * + * @param screenshot the image returned from {@link #screenshot(SuccessCallback)}. + * @return {@code true} if the caller should repaint Codename One components onto the + * screenshot, {@code false} otherwise. + */ + public boolean shouldPaintNativeScreenshot(Image screenshot) { + return impl.shouldPaintNativeScreenshot(screenshot); + } + /** * Convenience method to schedule a task to run on the EDT after {@literal timeout}ms. diff --git a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java index cd1d416e17..7b823d6ae7 100644 --- a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java +++ b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java @@ -300,6 +300,7 @@ public void addCookie(Cookie c) { } private static SuccessCallback screenshotCallback; + private static volatile boolean lastScreenshotHasNativePeers; @Override public void screenshot(final SuccessCallback callback) { @@ -337,6 +338,7 @@ static void onScreenshot(final byte[] imageData) { final SuccessCallback callback = screenshotCallback; screenshotCallback = null; if (callback == null) { + lastScreenshotHasNativePeers = false; return; } @@ -366,6 +368,7 @@ public void run() { } if (image != null && image.getGraphics() != null) { + lastScreenshotHasNativePeers = true; callback.onSucess(image); return; } @@ -374,11 +377,19 @@ public void run() { Log.e(t); } } + lastScreenshotHasNativePeers = false; callback.onSucess(null); } }); } + @Override + public boolean shouldPaintNativeScreenshot(Image screenshot) { + boolean shouldPaint = !lastScreenshotHasNativePeers; + lastScreenshotHasNativePeers = false; + return shouldPaint; + } + /** * Used to enable/disable native cookies from native code. * @param cookiesArray diff --git a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java index 0bda882b98..2ccb793470 100644 --- a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java +++ b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java @@ -96,7 +96,9 @@ private void saveScreenshot(String storageKey, Image screenshot) throws IOExcept private Image capture(Form form) { Image screenshot = Image.createImage(form.getWidth(), form.getHeight()); - form.paintComponent(screenshot.getGraphics(), true); + if (Display.getInstance().shouldPaintNativeScreenshot(screenshot)) { + form.paintComponent(screenshot.getGraphics(), true); + } return screenshot; } diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java index a4361f6d96..4bdb60836a 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java @@ -62,7 +62,9 @@ static boolean emitCurrentFormScreenshot(String testName) { return false; } Image screenshot = img[0]; - current.paintComponent(screenshot.getGraphics(), true); + if (Display.getInstance().shouldPaintNativeScreenshot(screenshot)) { + current.paintComponent(screenshot.getGraphics(), true); + } try { ImageIO io = ImageIO.getImageIO(); if (io == null || !io.isFormatSupported(ImageIO.FORMAT_PNG)) { From f557d0d239083d2d38e6cfa5dcd0d4ba83c8a0a6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:01:21 +0200 Subject: [PATCH 10/18] Use native screenshot in animation demo tests --- .../AnimationDemosScreenshotTest.java | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java index 2ccb793470..f69758b6d8 100644 --- a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java +++ b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java @@ -95,10 +95,26 @@ private void saveScreenshot(String storageKey, Image screenshot) throws IOExcept } private Image capture(Form form) { - Image screenshot = Image.createImage(form.getWidth(), form.getHeight()); - if (Display.getInstance().shouldPaintNativeScreenshot(screenshot)) { + final Display display = Display.getInstance(); + final Image[] holder = new Image[1]; + display.screenshot(screen -> holder[0] = screen); + + display.invokeAndBlock(() -> { + long deadline = System.currentTimeMillis() + 2000L; + while (holder[0] == null && System.currentTimeMillis() < deadline) { + Util.sleep(20); + } + }); + + Image screenshot = holder[0]; + if (screenshot == null) { + screenshot = Image.createImage(form.getWidth(), form.getHeight()); + } + + if (screenshot.getGraphics() != null && display.shouldPaintNativeScreenshot(screenshot)) { form.paintComponent(screenshot.getGraphics(), true); } + return screenshot; } From 4b06eecb01a9425d06ee10399fd67524e3d38ae9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:12:42 +0200 Subject: [PATCH 11/18] Fail animation demo screenshots when native capture is unavailable --- .../animations/AnimationDemosScreenshotTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java index f69758b6d8..56067ca582 100644 --- a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java +++ b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java @@ -108,7 +108,8 @@ private Image capture(Form form) { Image screenshot = holder[0]; if (screenshot == null) { - screenshot = Image.createImage(form.getWidth(), form.getHeight()); + fail("Timed out waiting for native screenshot result."); + throw new IllegalStateException("Timed out waiting for native screenshot result."); } if (screenshot.getGraphics() != null && display.shouldPaintNativeScreenshot(screenshot)) { From c5bb71fdbb963ee41973abf65b64655894c76f2c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:12:47 +0200 Subject: [PATCH 12/18] Remove native screenshot repaint toggle --- .../codename1/impl/CodenameOneImplementation.java | 14 -------------- CodenameOne/src/com/codename1/ui/Display.java | 15 --------------- .../com/codename1/impl/ios/IOSImplementation.java | 12 ------------ .../animations/AnimationDemosScreenshotTest.java | 2 +- .../tests/Cn1ssDeviceRunnerHelper.java | 2 +- 5 files changed, 2 insertions(+), 43 deletions(-) diff --git a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java index cd8034f54e..e24c30f36e 100644 --- a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java +++ b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java @@ -1214,20 +1214,6 @@ public void screenshot(SuccessCallback callback) { callback.onSucess(img); } - /** - * Indicates whether the Codename One implementation expects caller-side painting to run - * after a native screenshot has been captured. Implementations that composite peer - * components into the screenshot can override this to {@code false} so downstream code - * won't immediately repaint the captured image and accidentally hide native peers. - * - * @param screenshot the screenshot image produced by {@link #screenshot(SuccessCallback)} - * @return {@code true} if the caller should repaint Codename One components onto the - * screenshot, {@code false} otherwise. - */ - public boolean shouldPaintNativeScreenshot(Image screenshot) { - return true; - } - /** * Returns true if the platform supports a native image cache. The native image cache * is different than just {@link FileSystemStorage#hasCachesDir()}. A native image cache diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index ac7e9dfd24..ba4fb1687d 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -5013,21 +5013,6 @@ public void screenshot(SuccessCallback callback) { impl.screenshot(callback); } - /** - * Indicates whether callers should repaint Codename One components onto a screenshot image - * returned from {@link #screenshot(SuccessCallback)}. Platforms that already composite - * peer components into the native screenshot can return {@code false} to keep the captured - * pixels intact. - * - * @param screenshot the image returned from {@link #screenshot(SuccessCallback)}. - * @return {@code true} if the caller should repaint Codename One components onto the - * screenshot, {@code false} otherwise. - */ - public boolean shouldPaintNativeScreenshot(Image screenshot) { - return impl.shouldPaintNativeScreenshot(screenshot); - } - - /** * Convenience method to schedule a task to run on the EDT after {@literal timeout}ms. * diff --git a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java index 7b823d6ae7..a20f5180ae 100644 --- a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java +++ b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java @@ -300,8 +300,6 @@ public void addCookie(Cookie c) { } private static SuccessCallback screenshotCallback; - private static volatile boolean lastScreenshotHasNativePeers; - @Override public void screenshot(final SuccessCallback callback) { if (callback == null) { @@ -338,7 +336,6 @@ static void onScreenshot(final byte[] imageData) { final SuccessCallback callback = screenshotCallback; screenshotCallback = null; if (callback == null) { - lastScreenshotHasNativePeers = false; return; } @@ -368,7 +365,6 @@ public void run() { } if (image != null && image.getGraphics() != null) { - lastScreenshotHasNativePeers = true; callback.onSucess(image); return; } @@ -377,19 +373,11 @@ public void run() { Log.e(t); } } - lastScreenshotHasNativePeers = false; callback.onSucess(null); } }); } - @Override - public boolean shouldPaintNativeScreenshot(Image screenshot) { - boolean shouldPaint = !lastScreenshotHasNativePeers; - lastScreenshotHasNativePeers = false; - return shouldPaint; - } - /** * Used to enable/disable native cookies from native code. * @param cookiesArray diff --git a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java index 56067ca582..2d5ebb3c75 100644 --- a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java +++ b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java @@ -112,7 +112,7 @@ private Image capture(Form form) { throw new IllegalStateException("Timed out waiting for native screenshot result."); } - if (screenshot.getGraphics() != null && display.shouldPaintNativeScreenshot(screenshot)) { + if (screenshot.getGraphics() != null) { form.paintComponent(screenshot.getGraphics(), true); } diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java index 4bdb60836a..5b5dbc1ae7 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java @@ -62,7 +62,7 @@ static boolean emitCurrentFormScreenshot(String testName) { return false; } Image screenshot = img[0]; - if (Display.getInstance().shouldPaintNativeScreenshot(screenshot)) { + if (screenshot.getGraphics() != null) { current.paintComponent(screenshot.getGraphics(), true); } try { From ff8ce2ea7779f19d7c5dfcc32934dbfc0face8cd Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 04:12:52 +0200 Subject: [PATCH 13/18] Revert Animation demo screenshot test changes --- .../AnimationDemosScreenshotTest.java | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java index 2d5ebb3c75..0bda882b98 100644 --- a/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java +++ b/docs/demos/common/src/test/java/com/codenameone/developerguide/animations/AnimationDemosScreenshotTest.java @@ -95,27 +95,8 @@ private void saveScreenshot(String storageKey, Image screenshot) throws IOExcept } private Image capture(Form form) { - final Display display = Display.getInstance(); - final Image[] holder = new Image[1]; - display.screenshot(screen -> holder[0] = screen); - - display.invokeAndBlock(() -> { - long deadline = System.currentTimeMillis() + 2000L; - while (holder[0] == null && System.currentTimeMillis() < deadline) { - Util.sleep(20); - } - }); - - Image screenshot = holder[0]; - if (screenshot == null) { - fail("Timed out waiting for native screenshot result."); - throw new IllegalStateException("Timed out waiting for native screenshot result."); - } - - if (screenshot.getGraphics() != null) { - form.paintComponent(screenshot.getGraphics(), true); - } - + Image screenshot = Image.createImage(form.getWidth(), form.getHeight()); + form.paintComponent(screenshot.getGraphics(), true); return screenshot; } From fdf7e644c17ad06d000293b648dfe6f3b2b7f60e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:35:36 +0200 Subject: [PATCH 14/18] Stop repainting native screenshots in device runner helper --- scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java index 5b5dbc1ae7..409ce0f1fc 100644 --- a/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java +++ b/scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java @@ -62,9 +62,6 @@ static boolean emitCurrentFormScreenshot(String testName) { return false; } Image screenshot = img[0]; - if (screenshot.getGraphics() != null) { - current.paintComponent(screenshot.getGraphics(), true); - } try { ImageIO io = ImageIO.getImageIO(); if (io == null || !io.isFormatSupported(ImageIO.FORMAT_PNG)) { From 7d227a250627f5bcde1ecdb48ed4d3622648069f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:12:14 +0200 Subject: [PATCH 15/18] Remove iOS screenshot hierarchy overlay --- Ports/iOSPort/nativeSources/IOSNative.m | 166 ------------------------ 1 file changed, 166 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index f9ee11c30c..99414d837f 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5297,154 +5297,6 @@ static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGCo CGContextRestoreGState(ctx); } -static void cn1_collectViewHierarchy(UIView *view, NSMutableArray *lines, NSInteger depth, UIView *targetView, UIView *rootView) { - if (view == nil || lines == nil) { - return; - } - - NSMutableString *line = [NSMutableString string]; - for (NSInteger i = 0; i < depth; i++) { - [line appendString:@" "]; - } - - NSString *className = NSStringFromClass([view class]); - [line appendFormat:@"%@<%p>", className, view]; - - if (view == rootView) { - [line appendString:@" [captureRoot]"]; - } - - if (view == targetView) { - [line appendString:@" [targetView]"]; - } - - CGRect frame = view.frame; - [line appendFormat:@" frame=%@", NSStringFromCGRect(frame)]; - - [line appendFormat:@" hidden=%@", view.hidden ? @"YES" : @"NO"]; - [line appendFormat:@" alpha=%.2f", view.alpha]; - [line appendFormat:@" userInteraction=%@", view.userInteractionEnabled ? @"YES" : @"NO"]; - - if (view.tag != 0) { - [line appendFormat:@" tag=%ld", (long)view.tag]; - } - - NSString *identifier = nil; - if ([view respondsToSelector:@selector(accessibilityIdentifier)]) { - identifier = view.accessibilityIdentifier; - } - - if (identifier.length > 0) { - [line appendFormat:@" accessibilityIdentifier=%@", identifier]; - } - - [lines addObject:line]; - - for (UIView *subview in view.subviews) { - cn1_collectViewHierarchy(subview, lines, depth + 1, targetView, rootView); - } -} - -static void cn1_appendWindowHierarchyLines(NSArray *windows, UIWindow *targetWindow, UIView *targetView, NSMutableArray *lines) { - if (windows == nil || lines == nil || windows.count == 0) { - return; - } - - [lines addObject:@"Window capture order (back to front):"]; - - NSInteger index = 0; - for (UIWindow *window in windows) { - NSMutableString *header = [NSMutableString string]; - [header appendFormat:@"[%ld] %@<%p>", (long)index, NSStringFromClass([window class]), window]; - if (window == targetWindow) { - [header appendString:@" [targetWindow]"]; - } - [header appendFormat:@" level=%.2f", window.windowLevel]; - [header appendFormat:@" hidden=%@", window.hidden ? @"YES" : @"NO"]; - [header appendFormat:@" alpha=%.2f", window.alpha]; - [header appendFormat:@" frame=%@", NSStringFromCGRect(window.frame)]; - [lines addObject:header]; - cn1_collectViewHierarchy(window, lines, 1, targetView, targetWindow); - index++; - } -} - -static void cn1_drawHierarchyOverlay(CGContextRef ctx, UIView *rootView, CGRect targetRect, NSArray *lines) { - if (ctx == nil || rootView == nil || lines == nil || lines.count == 0) { - return; - } - - NSString *text = [lines componentsJoinedByString:@"\n"]; - if (text.length == 0) { - return; - } - - UIFont *font = nil; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 - if (@available(iOS 13.0, *)) { - font = [UIFont monospacedSystemFontOfSize:12.0 weight:UIFontWeightRegular]; - } -#endif - if (font == nil) { - font = [UIFont fontWithName:@"Menlo" size:12.0]; - } - if (font == nil) { - font = [UIFont systemFontOfSize:12.0]; - } - - NSDictionary *attributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : [UIColor whiteColor] }; - - CGSize canvasSize = rootView.bounds.size; - if (canvasSize.width <= 0.0f || canvasSize.height <= 0.0f) { - return; - } - - CGFloat outerPadding = 12.0f; - CGFloat innerPadding = 8.0f; - CGFloat availableWidth = MAX(0.0f, canvasSize.width - (outerPadding * 2.0f)); - CGSize constraint = CGSizeMake(availableWidth, CGFLOAT_MAX); - CGRect textBounds = [text boundingRectWithSize:constraint options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil]; - textBounds.size.width = ceil(textBounds.size.width); - textBounds.size.height = ceil(textBounds.size.height); - - if (CGRectIsNull(targetRect) || CGRectIsInfinite(targetRect)) { - targetRect = rootView.bounds; - } - - CGPoint anchor = CGPointMake(CGRectGetMinX(targetRect) + outerPadding, CGRectGetMinY(targetRect) + outerPadding); - - CGRect backgroundRect = CGRectMake(anchor.x - innerPadding, - anchor.y - innerPadding, - textBounds.size.width + innerPadding * 2.0f, - textBounds.size.height + innerPadding * 2.0f); - - if (backgroundRect.origin.x < outerPadding) { - backgroundRect.origin.x = outerPadding; - } - if (backgroundRect.origin.y < outerPadding) { - backgroundRect.origin.y = outerPadding; - } - - if (CGRectGetMaxX(backgroundRect) > canvasSize.width - outerPadding) { - backgroundRect.origin.x = MAX(outerPadding, canvasSize.width - outerPadding - backgroundRect.size.width); - } - - if (CGRectGetMaxY(backgroundRect) > canvasSize.height - outerPadding) { - backgroundRect.origin.y = MAX(outerPadding, canvasSize.height - outerPadding - backgroundRect.size.height); - } - - CGRect textRect = CGRectMake(backgroundRect.origin.x + innerPadding, - backgroundRect.origin.y + innerPadding, - textBounds.size.width, - textBounds.size.height); - - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:backgroundRect cornerRadius:8.0f]; - [[UIColor colorWithWhite:0 alpha:0.7f] setFill]; - [path fill]; - - [text drawInRect:textRect withAttributes:attributes]; -} - static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { CodenameOne_GLViewController *controller = [CodenameOne_GLViewController instance]; EAGLView *glView = [controller eaglView]; @@ -5549,16 +5401,6 @@ static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { return nil; } - CGRect overlayTargetRect = rootView.bounds; - if (rootView != view && view != nil) { - CGRect converted = [rootView convertRect:view.bounds fromView:view]; - if (!CGRectIsNull(converted) && converted.size.width > 0 && converted.size.height > 0) { - overlayTargetRect = converted; - } - } - - NSMutableArray *hierarchyLines = [NSMutableArray array]; - UIGraphicsBeginImageContextWithOptions(size, rootView.opaque, 0.0); CGContextRef ctx = UIGraphicsGetCurrentContext(); if ([rootView isKindOfClass:[UIWindow class]]) { @@ -5602,8 +5444,6 @@ static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { }]; } - cn1_appendWindowHierarchyLines(orderedWindows, targetWindow, view, hierarchyLines); - for (UIWindow *window in orderedWindows) { if (window.hidden || window.alpha <= 0.0f) { continue; @@ -5639,12 +5479,6 @@ static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { cn1_renderPeerComponents(rootView, ctx); } - if (hierarchyLines.count == 0) { - cn1_collectViewHierarchy(rootView, hierarchyLines, 0, view, rootView); - } - - cn1_drawHierarchyOverlay(ctx, rootView, overlayTargetRect, hierarchyLines); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); From e6f883a74c6410bd330aeab0e6cc2a61dfc532e1 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:59:44 +0200 Subject: [PATCH 16/18] Fix iOS screenshot rendering without debug overlays --- Ports/iOSPort/nativeSources/IOSNative.m | 222 ++++++++++++++---------- 1 file changed, 131 insertions(+), 91 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 99414d837f..704709091a 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -101,6 +101,8 @@ #else #define CN1_AVPLAYERVIEWCONTROLLER id #endif +#import +#import extern int popoverSupported(); //#define CN1_INCLUDE_NOTIFICATIONS2 #define INCLUDE_CN1_PUSH2 @@ -5194,44 +5196,139 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam #endif } -static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGContextRef ctx) { +static void cn1_releaseImageBuffer(void *info, const void *data, size_t size) { + if (data != NULL) { + free((void *)data); + } +} + +static BOOL cn1_renderEAGLViewIntoContext(EAGLView *glView, CGContextRef ctx, CGRect localBounds) { + if (glView == nil || ctx == NULL) { + return NO; + } + + EAGLContext *activeContext = [EAGLContext currentContext]; + GLint previousFramebuffer = 0; + if (activeContext != nil) { + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer); + } + + EAGLContext *targetContext = glView.context; + if (targetContext == nil) { + return NO; + } + + [EAGLContext setCurrentContext:targetContext]; + + GLint glViewFramebuffer = 0; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &glViewFramebuffer); + [glView setFramebuffer]; + glFinish(); + + GLint viewport[4] = {0, 0, 0, 0}; + glGetIntegerv(GL_VIEWPORT, viewport); + GLint width = viewport[2]; + GLint height = viewport[3]; + if (width <= 0 || height <= 0) { + glBindFramebuffer(GL_FRAMEBUFFER, glViewFramebuffer); + [EAGLContext setCurrentContext:activeContext]; + if (activeContext != nil) { + glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer); + } + return NO; + } + + size_t dataLength = (size_t)width * (size_t)height * 4; + GLubyte *pixelData = (GLubyte *)malloc(dataLength); + if (pixelData == NULL) { + glBindFramebuffer(GL_FRAMEBUFFER, glViewFramebuffer); + [EAGLContext setCurrentContext:activeContext]; + if (activeContext != nil) { + glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer); + } + return NO; + } + + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData); + + glBindFramebuffer(GL_FRAMEBUFFER, glViewFramebuffer); + [EAGLContext setCurrentContext:activeContext]; + if (activeContext != nil) { + glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer); + } + + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixelData, dataLength, cn1_releaseImageBuffer); + if (provider == NULL) { + free(pixelData); + return NO; + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + if (colorSpace == NULL) { + CGDataProviderRelease(provider); + return NO; + } + + CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big; + CGImageRef imageRef = CGImageCreate(width, + height, + 8, + 32, + width * 4, + colorSpace, + bitmapInfo, + provider, + NULL, + NO, + kCGRenderingIntentDefault); + CGColorSpaceRelease(colorSpace); + CGDataProviderRelease(provider); + + if (imageRef == NULL) { + return NO; + } + + CGContextSaveGState(ctx); + CGContextTranslateCTM(ctx, 0.0f, localBounds.size.height); + CGContextScaleCTM(ctx, 1.0f, -1.0f); + CGContextDrawImage(ctx, CGRectMake(0.0f, 0.0f, localBounds.size.width, localBounds.size.height), imageRef); + CGContextRestoreGState(ctx); + + CGImageRelease(imageRef); + + return YES; +} + +static BOOL cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGContextRef ctx) { if (renderView == nil || rootView == nil || ctx == NULL) { - return; + return NO; } if (renderView.hidden || renderView.alpha <= 0.0f) { - return; + return NO; } CGRect localBounds = renderView.bounds; if (CGRectIsEmpty(localBounds) || localBounds.size.width <= 0.0f || localBounds.size.height <= 0.0f) { - return; + return NO; } CGRect translatedRect = [rootView convertRect:localBounds fromView:renderView]; if (CGRectIsNull(translatedRect) || CGRectIsEmpty(translatedRect)) { - return; + return NO; } CGContextSaveGState(ctx); CGContextTranslateCTM(ctx, translatedRect.origin.x, translatedRect.origin.y); BOOL drawn = NO; + if ([renderView isKindOfClass:[EAGLView class]]) { + drawn = cn1_renderEAGLViewIntoContext((EAGLView *)renderView, ctx, localBounds); + } #if defined(ENABLE_WKWEBVIEW) && defined(supportsWKWebKit) if ([renderView isKindOfClass:[WKWebView class]]) { WKWebView *webView = (WKWebView *)renderView; - // Paint a diagnostic fallback so we can tell if the WKWebView failed to render. - if (localBounds.size.width > 0.0f && localBounds.size.height > 0.0f) { - CGContextSaveGState(ctx); - CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor); - CGContextSetLineWidth(ctx, 4.0f); - CGContextMoveToPoint(ctx, 0.0f, 0.0f); - CGContextAddLineToPoint(ctx, localBounds.size.width, localBounds.size.height); - CGContextMoveToPoint(ctx, localBounds.size.width, 0.0f); - CGContextAddLineToPoint(ctx, 0.0f, localBounds.size.height); - CGContextStrokePath(ctx); - CGContextRestoreGState(ctx); - } #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 - if (@available(iOS 11.0, *)) { + if (!drawn && @available(iOS 11.0, *)) { CGRect snapshotRect = CGRectIntersection(webView.bounds, localBounds); if (!CGRectIsNull(snapshotRect) && !CGRectIsEmpty(snapshotRect)) { WKSnapshotConfiguration *config = [[WKSnapshotConfiguration alloc] init]; @@ -5293,8 +5390,10 @@ static void cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGCo } if (!drawn) { [renderView.layer renderInContext:ctx]; + drawn = YES; } CGContextRestoreGState(ctx); + return drawn; } static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { @@ -5403,82 +5502,23 @@ static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) { UIGraphicsBeginImageContextWithOptions(size, rootView.opaque, 0.0); CGContextRef ctx = UIGraphicsGetCurrentContext(); - if ([rootView isKindOfClass:[UIWindow class]]) { - UIWindow *targetWindow = (UIWindow *)rootView; - NSArray *windows = nil; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 - if (@available(iOS 13.0, *)) { - if (targetWindow.windowScene != nil) { - windows = targetWindow.windowScene.windows; - } - } -#endif - if (windows == nil || windows.count == 0) { - windows = [UIApplication sharedApplication].windows; - } - // Render every visible window that shares the same screen as the Codename One GL window - // so that native peer components hosted outside of the GL view (e.g. BrowserComponent) - // are composited into the captured image. Windows need to be rendered from back to front - // based on their window level so that overlay windows (e.g. ones hosting peer components) - // appear above the GL view in the resulting screenshot. - NSArray *orderedWindows = windows; - if (windows.count > 1) { - orderedWindows = [windows sortedArrayUsingComparator:^NSComparisonResult(UIWindow * _Nonnull lhs, UIWindow * _Nonnull rhs) { - CGFloat lhsLevel = lhs.windowLevel; - CGFloat rhsLevel = rhs.windowLevel; - if (lhsLevel < rhsLevel) { - return NSOrderedAscending; - } - if (lhsLevel > rhsLevel) { - return NSOrderedDescending; - } - NSUInteger lhsIndex = [windows indexOfObjectIdenticalTo:lhs]; - NSUInteger rhsIndex = [windows indexOfObjectIdenticalTo:rhs]; - if (lhsIndex < rhsIndex) { - return NSOrderedAscending; - } - if (lhsIndex > rhsIndex) { - return NSOrderedDescending; - } - return NSOrderedSame; - }]; - } + if (ctx == NULL) { + UIGraphicsEndImageContext(); + return nil; + } - for (UIWindow *window in orderedWindows) { - if (window.hidden || window.alpha <= 0.0f) { - continue; - } - if (window.screen != targetWindow.screen) { - continue; - } - [window layoutIfNeeded]; - CGContextSaveGState(ctx); - CGRect translated = window.bounds; - if (window != targetWindow) { - translated = [targetWindow convertRect:window.bounds fromWindow:window]; - } - CGContextTranslateCTM(ctx, translated.origin.x, translated.origin.y); - BOOL windowDrawn = NO; - if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { - windowDrawn = [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES]; - } - if (!windowDrawn) { - [window.layer renderInContext:ctx]; - } - CGContextRestoreGState(ctx); - } - cn1_renderPeerComponents(rootView, ctx); - } else { - BOOL ok = NO; - if ([rootView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { - ok = [rootView drawViewHierarchyInRect:rootView.bounds afterScreenUpdates:YES]; - } - if (!ok) { - [rootView.layer renderInContext:ctx]; - } - cn1_renderPeerComponents(rootView, ctx); + [rootView layoutIfNeeded]; + + cn1_renderViewIntoContext(view, rootView, ctx); + + CodenameOne_GLViewController *controller = [CodenameOne_GLViewController instance]; + EAGLView *glView = [controller eaglView]; + if (glView != nil && glView != view) { + cn1_renderViewIntoContext(glView, rootView, ctx); } + cn1_renderPeerComponents(rootView, ctx); + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); From 58d43dd84a44f22294cd92caf93718763f3e6ca6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 11 Nov 2025 03:16:16 +0200 Subject: [PATCH 17/18] Fix orientation of GL framebuffer capture --- Ports/iOSPort/nativeSources/IOSNative.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 704709091a..6bc8e56222 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -5290,8 +5290,8 @@ static BOOL cn1_renderEAGLViewIntoContext(EAGLView *glView, CGContextRef ctx, CG } CGContextSaveGState(ctx); - CGContextTranslateCTM(ctx, 0.0f, localBounds.size.height); - CGContextScaleCTM(ctx, 1.0f, -1.0f); + CGContextTranslateCTM(ctx, localBounds.size.width, localBounds.size.height); + CGContextScaleCTM(ctx, -1.0f, -1.0f); CGContextDrawImage(ctx, CGRectMake(0.0f, 0.0f, localBounds.size.width, localBounds.size.height), imageRef); CGContextRestoreGState(ctx); From 775acd76e0394159f47078f8c06cdb45eeb87c7a Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Tue, 11 Nov 2025 04:19:53 +0200 Subject: [PATCH 18/18] Simplify iOS screenshot capture to avoid GL readback --- Ports/iOSPort/nativeSources/IOSNative.m | 109 ------------------------ 1 file changed, 109 deletions(-) diff --git a/Ports/iOSPort/nativeSources/IOSNative.m b/Ports/iOSPort/nativeSources/IOSNative.m index 6bc8e56222..f590019be6 100644 --- a/Ports/iOSPort/nativeSources/IOSNative.m +++ b/Ports/iOSPort/nativeSources/IOSNative.m @@ -101,8 +101,6 @@ #else #define CN1_AVPLAYERVIEWCONTROLLER id #endif -#import -#import extern int popoverSupported(); //#define CN1_INCLUDE_NOTIFICATIONS2 #define INCLUDE_CN1_PUSH2 @@ -5196,110 +5194,6 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam #endif } -static void cn1_releaseImageBuffer(void *info, const void *data, size_t size) { - if (data != NULL) { - free((void *)data); - } -} - -static BOOL cn1_renderEAGLViewIntoContext(EAGLView *glView, CGContextRef ctx, CGRect localBounds) { - if (glView == nil || ctx == NULL) { - return NO; - } - - EAGLContext *activeContext = [EAGLContext currentContext]; - GLint previousFramebuffer = 0; - if (activeContext != nil) { - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer); - } - - EAGLContext *targetContext = glView.context; - if (targetContext == nil) { - return NO; - } - - [EAGLContext setCurrentContext:targetContext]; - - GLint glViewFramebuffer = 0; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &glViewFramebuffer); - [glView setFramebuffer]; - glFinish(); - - GLint viewport[4] = {0, 0, 0, 0}; - glGetIntegerv(GL_VIEWPORT, viewport); - GLint width = viewport[2]; - GLint height = viewport[3]; - if (width <= 0 || height <= 0) { - glBindFramebuffer(GL_FRAMEBUFFER, glViewFramebuffer); - [EAGLContext setCurrentContext:activeContext]; - if (activeContext != nil) { - glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer); - } - return NO; - } - - size_t dataLength = (size_t)width * (size_t)height * 4; - GLubyte *pixelData = (GLubyte *)malloc(dataLength); - if (pixelData == NULL) { - glBindFramebuffer(GL_FRAMEBUFFER, glViewFramebuffer); - [EAGLContext setCurrentContext:activeContext]; - if (activeContext != nil) { - glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer); - } - return NO; - } - - glPixelStorei(GL_PACK_ALIGNMENT, 4); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData); - - glBindFramebuffer(GL_FRAMEBUFFER, glViewFramebuffer); - [EAGLContext setCurrentContext:activeContext]; - if (activeContext != nil) { - glBindFramebuffer(GL_FRAMEBUFFER, previousFramebuffer); - } - - CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, pixelData, dataLength, cn1_releaseImageBuffer); - if (provider == NULL) { - free(pixelData); - return NO; - } - - CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); - if (colorSpace == NULL) { - CGDataProviderRelease(provider); - return NO; - } - - CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big; - CGImageRef imageRef = CGImageCreate(width, - height, - 8, - 32, - width * 4, - colorSpace, - bitmapInfo, - provider, - NULL, - NO, - kCGRenderingIntentDefault); - CGColorSpaceRelease(colorSpace); - CGDataProviderRelease(provider); - - if (imageRef == NULL) { - return NO; - } - - CGContextSaveGState(ctx); - CGContextTranslateCTM(ctx, localBounds.size.width, localBounds.size.height); - CGContextScaleCTM(ctx, -1.0f, -1.0f); - CGContextDrawImage(ctx, CGRectMake(0.0f, 0.0f, localBounds.size.width, localBounds.size.height), imageRef); - CGContextRestoreGState(ctx); - - CGImageRelease(imageRef); - - return YES; -} - static BOOL cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGContextRef ctx) { if (renderView == nil || rootView == nil || ctx == NULL) { return NO; @@ -5321,9 +5215,6 @@ static BOOL cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGCo CGContextSaveGState(ctx); CGContextTranslateCTM(ctx, translatedRect.origin.x, translatedRect.origin.y); BOOL drawn = NO; - if ([renderView isKindOfClass:[EAGLView class]]) { - drawn = cn1_renderEAGLViewIntoContext((EAGLView *)renderView, ctx, localBounds); - } #if defined(ENABLE_WKWEBVIEW) && defined(supportsWKWebKit) if ([renderView isKindOfClass:[WKWebView class]]) { WKWebView *webView = (WKWebView *)renderView;