Skip to content

Commit 4175557

Browse files
authored
Fix iOS screenshot to include peer components (#4118)
* Fix iOS screenshot to include peer components * Include peer windows in iOS screenshots * Order captured windows by level for screenshots * Overlay peer components in iOS screenshots * Snapshot WKWebView peers when capturing screenshots * Capture WKWebView content via takeSnapshot * Add WKWebView fallback diagnostic overlay * Add hierarchy overlay diagnostics to iOS screenshots * Prevent repaint overwrite of iOS screenshots * Use native screenshot in animation demo tests * Fail animation demo screenshots when native capture is unavailable * Remove native screenshot repaint toggle * Revert Animation demo screenshot test changes * Stop repainting native screenshots in device runner helper * Remove iOS screenshot hierarchy overlay * Fix iOS screenshot rendering without debug overlays * Fix orientation of GL framebuffer capture * Simplify iOS screenshot capture to avoid GL readback
1 parent 8ed909e commit 4175557

File tree

4 files changed

+226
-11
lines changed

4 files changed

+226
-11
lines changed

CodenameOne/src/com/codename1/ui/Display.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5013,7 +5013,6 @@ public void screenshot(SuccessCallback<Image> callback) {
50135013
impl.screenshot(callback);
50145014
}
50155015

5016-
50175016
/**
50185017
* Convenience method to schedule a task to run on the EDT after {@literal timeout}ms.
50195018
*

Ports/iOSPort/nativeSources/IOSNative.m

Lines changed: 226 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5194,25 +5194,243 @@ void com_codename1_impl_ios_IOSNative_updatePersonWithRecordID___int_com_codenam
51945194
#endif
51955195
}
51965196

5197-
static UIImage* cn1_captureView(UIView *view) {
5197+
static BOOL cn1_renderViewIntoContext(UIView *renderView, UIView *rootView, CGContextRef ctx) {
5198+
if (renderView == nil || rootView == nil || ctx == NULL) {
5199+
return NO;
5200+
}
5201+
if (renderView.hidden || renderView.alpha <= 0.0f) {
5202+
return NO;
5203+
}
5204+
5205+
CGRect localBounds = renderView.bounds;
5206+
if (CGRectIsEmpty(localBounds) || localBounds.size.width <= 0.0f || localBounds.size.height <= 0.0f) {
5207+
return NO;
5208+
}
5209+
5210+
CGRect translatedRect = [rootView convertRect:localBounds fromView:renderView];
5211+
if (CGRectIsNull(translatedRect) || CGRectIsEmpty(translatedRect)) {
5212+
return NO;
5213+
}
5214+
5215+
CGContextSaveGState(ctx);
5216+
CGContextTranslateCTM(ctx, translatedRect.origin.x, translatedRect.origin.y);
5217+
BOOL drawn = NO;
5218+
#if defined(ENABLE_WKWEBVIEW) && defined(supportsWKWebKit)
5219+
if ([renderView isKindOfClass:[WKWebView class]]) {
5220+
WKWebView *webView = (WKWebView *)renderView;
5221+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
5222+
if (!drawn && @available(iOS 11.0, *)) {
5223+
CGRect snapshotRect = CGRectIntersection(webView.bounds, localBounds);
5224+
if (!CGRectIsNull(snapshotRect) && !CGRectIsEmpty(snapshotRect)) {
5225+
WKSnapshotConfiguration *config = [[WKSnapshotConfiguration alloc] init];
5226+
config.rect = snapshotRect;
5227+
if (snapshotRect.size.width > 0.0f) {
5228+
config.snapshotWidth = @(snapshotRect.size.width);
5229+
}
5230+
#ifdef __IPHONE_13_0
5231+
if (@available(iOS 13.0, *)) {
5232+
config.afterScreenUpdates = YES;
5233+
}
5234+
#endif
5235+
__block UIImage *snapshotImage = nil;
5236+
__block BOOL snapshotComplete = NO;
5237+
[webView takeSnapshotWithConfiguration:config completionHandler:^(UIImage * _Nullable image, NSError * _Nullable error) {
5238+
if (image != nil) {
5239+
snapshotImage = image;
5240+
} else if (error != nil) {
5241+
NSLog(@"WKWebView snapshot failed: %@", error);
5242+
}
5243+
snapshotComplete = YES;
5244+
}];
5245+
[config release];
5246+
5247+
if (!snapshotComplete) {
5248+
NSTimeInterval timeout = 1.0;
5249+
while (!snapshotComplete && timeout > 0) {
5250+
NSTimeInterval step = 0.01;
5251+
NSDate *stepDate = [NSDate dateWithTimeIntervalSinceNow:step];
5252+
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:stepDate];
5253+
timeout -= step;
5254+
}
5255+
}
5256+
5257+
if (snapshotImage != nil) {
5258+
[snapshotImage drawInRect:CGRectMake(0, 0, localBounds.size.width, localBounds.size.height)];
5259+
drawn = YES;
5260+
}
5261+
}
5262+
}
5263+
#endif
5264+
if (!drawn) {
5265+
UIView *snapshotView = [renderView snapshotViewAfterScreenUpdates:YES];
5266+
if (snapshotView != nil) {
5267+
BOOL snapshotDrawn = NO;
5268+
if ([snapshotView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
5269+
snapshotDrawn = [snapshotView drawViewHierarchyInRect:snapshotView.bounds afterScreenUpdates:YES];
5270+
}
5271+
if (!snapshotDrawn) {
5272+
[snapshotView.layer renderInContext:ctx];
5273+
}
5274+
drawn = YES;
5275+
}
5276+
}
5277+
}
5278+
#endif
5279+
if (!drawn && [renderView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
5280+
drawn = [renderView drawViewHierarchyInRect:localBounds afterScreenUpdates:YES];
5281+
}
5282+
if (!drawn) {
5283+
[renderView.layer renderInContext:ctx];
5284+
drawn = YES;
5285+
}
5286+
CGContextRestoreGState(ctx);
5287+
return drawn;
5288+
}
5289+
5290+
static void cn1_renderPeerComponents(UIView *rootView, CGContextRef ctx) {
5291+
CodenameOne_GLViewController *controller = [CodenameOne_GLViewController instance];
5292+
EAGLView *glView = [controller eaglView];
5293+
if (glView == nil || rootView == nil || ctx == NULL) {
5294+
return;
5295+
}
5296+
5297+
UIView *peerLayer = glView.peerComponentsLayer;
5298+
NSArray<UIView *> *peerCandidates = nil;
5299+
if (peerLayer != nil) {
5300+
[peerLayer layoutIfNeeded];
5301+
peerCandidates = peerLayer.subviews;
5302+
} else {
5303+
[glView layoutIfNeeded];
5304+
peerCandidates = glView.subviews;
5305+
}
5306+
5307+
if (peerCandidates.count == 0) {
5308+
return;
5309+
}
5310+
5311+
for (UIView *peerView in peerCandidates) {
5312+
if (![peerView isKindOfClass:[UIView class]]) {
5313+
continue;
5314+
}
5315+
cn1_renderViewIntoContext(peerView, rootView, ctx);
5316+
}
5317+
}
5318+
5319+
static UIView* cn1_rootViewForCapture(UIView *view) {
51985320
if (view == nil) {
51995321
return nil;
52005322
}
5201-
CGSize size = view.bounds.size;
5323+
5324+
UIView *rootView = view;
5325+
UIWindow *window = view.window;
5326+
5327+
if (window == nil) {
5328+
NSArray<UIWindow*> *windows = [UIApplication sharedApplication].windows;
5329+
for (UIWindow *candidate in windows) {
5330+
if ([view isDescendantOfView:candidate]) {
5331+
window = candidate;
5332+
break;
5333+
}
5334+
}
5335+
}
5336+
5337+
if (window == nil) {
5338+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
5339+
if (@available(iOS 13.0, *)) {
5340+
NSSet<UIScene *> *connectedScenes = [UIApplication sharedApplication].connectedScenes;
5341+
for (UIScene *scene in connectedScenes) {
5342+
if (![scene isKindOfClass:[UIWindowScene class]]) {
5343+
continue;
5344+
}
5345+
if (scene.activationState != UISceneActivationStateForegroundActive) {
5346+
continue;
5347+
}
5348+
UIWindowScene *windowScene = (UIWindowScene *)scene;
5349+
for (UIWindow *candidate in windowScene.windows) {
5350+
if ([view isDescendantOfView:candidate]) {
5351+
window = candidate;
5352+
break;
5353+
}
5354+
}
5355+
if (window != nil) {
5356+
break;
5357+
}
5358+
if (windowScene.windows.count > 0 && window == nil) {
5359+
window = windowScene.windows.firstObject;
5360+
}
5361+
}
5362+
}
5363+
#endif
5364+
}
5365+
5366+
if (window == nil) {
5367+
window = [UIApplication sharedApplication].keyWindow;
5368+
}
5369+
5370+
if (window != nil) {
5371+
rootView = window;
5372+
} else {
5373+
UIView *candidate = view;
5374+
while (candidate.superview != nil) {
5375+
candidate = candidate.superview;
5376+
}
5377+
rootView = candidate;
5378+
}
5379+
5380+
return rootView;
5381+
}
5382+
5383+
static UIImage* cn1_captureView(UIView *view) {
5384+
UIView *rootView = cn1_rootViewForCapture(view);
5385+
if (rootView == nil) {
5386+
return nil;
5387+
}
5388+
5389+
CGSize size = rootView.bounds.size;
52025390
if (size.width <= 0 || size.height <= 0) {
52035391
return nil;
52045392
}
52055393

5206-
UIGraphicsBeginImageContextWithOptions(size, view.opaque, 0.0);
5207-
BOOL ok = NO;
5208-
if ([view respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
5209-
ok = [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:YES];
5394+
UIGraphicsBeginImageContextWithOptions(size, rootView.opaque, 0.0);
5395+
CGContextRef ctx = UIGraphicsGetCurrentContext();
5396+
if (ctx == NULL) {
5397+
UIGraphicsEndImageContext();
5398+
return nil;
52105399
}
5211-
if (!ok) {
5212-
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
5400+
5401+
[rootView layoutIfNeeded];
5402+
5403+
cn1_renderViewIntoContext(view, rootView, ctx);
5404+
5405+
CodenameOne_GLViewController *controller = [CodenameOne_GLViewController instance];
5406+
EAGLView *glView = [controller eaglView];
5407+
if (glView != nil && glView != view) {
5408+
cn1_renderViewIntoContext(glView, rootView, ctx);
52135409
}
5410+
5411+
cn1_renderPeerComponents(rootView, ctx);
5412+
52145413
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
52155414
UIGraphicsEndImageContext();
5415+
5416+
if (rootView != view) {
5417+
CGRect targetFrame = [rootView convertRect:view.bounds fromView:view];
5418+
targetFrame = CGRectIntersection(targetFrame, CGRectMake(0, 0, size.width, size.height));
5419+
if (!CGRectIsNull(targetFrame) && targetFrame.size.width > 0 && targetFrame.size.height > 0) {
5420+
CGRect integralTarget = CGRectIntegral(targetFrame);
5421+
CGRect pixelRect = CGRectMake(integralTarget.origin.x * image.scale,
5422+
integralTarget.origin.y * image.scale,
5423+
integralTarget.size.width * image.scale,
5424+
integralTarget.size.height * image.scale);
5425+
CGImageRef cropped = CGImageCreateWithImageInRect(image.CGImage, pixelRect);
5426+
if (cropped != nil) {
5427+
UIImage *croppedImage = [UIImage imageWithCGImage:cropped scale:image.scale orientation:image.imageOrientation];
5428+
CGImageRelease(cropped);
5429+
image = croppedImage;
5430+
}
5431+
}
5432+
}
5433+
52165434
return image;
52175435
}
52185436

Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,6 @@ public void addCookie(Cookie c) {
300300
}
301301

302302
private static SuccessCallback<Image> screenshotCallback;
303-
304303
@Override
305304
public void screenshot(final SuccessCallback<Image> callback) {
306305
if (callback == null) {

scripts/device-runner-app/tests/Cn1ssDeviceRunnerHelper.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ static boolean emitCurrentFormScreenshot(String testName) {
6262
return false;
6363
}
6464
Image screenshot = img[0];
65-
current.paintComponent(screenshot.getGraphics(), true);
6665
try {
6766
ImageIO io = ImageIO.getImageIO();
6867
if (io == null || !io.isFormatSupported(ImageIO.FORMAT_PNG)) {

0 commit comments

Comments
 (0)