Skip to content

Commit

Permalink
Allow sentry user to control resolution of captured Flutter screensho…
Browse files Browse the repository at this point in the history
…ts (#1288)
  • Loading branch information
denrase committed Mar 6, 2023
1 parent e501be6 commit c238d73
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,10 @@
- [changelog](https://github.com/getsentry/sentry-java/blob/main/CHANGELOG.md#6140)
- [diff](https://github.com/getsentry/sentry-java/compare/6.13.1...6.14.0)

### Features

- Allow sentry user to control resolution of captured Flutter screenshots ([#1288](https://github.com/getsentry/sentry-dart/pull/1288))

## 6.21.0

### Features
Expand Down
1 change: 1 addition & 0 deletions flutter/example/lib/main.dart
Expand Up @@ -50,6 +50,7 @@ Future<void> setupSentry(AppRunner appRunner, String dsn) async {
options.sendDefaultPii = true;
options.reportSilentFlutterErrors = true;
options.attachScreenshot = true;
options.screenshotQuality = SentryScreenshotQuality.low;
options.attachViewHierarchy = true;
// We can enable Sentry debug logging during development. This is likely
// going to log too much for your app, but can be useful when figuring out
Expand Down
1 change: 1 addition & 0 deletions flutter/lib/sentry_flutter.dart
Expand Up @@ -9,5 +9,6 @@ export 'src/flutter_sentry_attachment.dart';
export 'src/sentry_asset_bundle.dart';
export 'src/integrations/on_error_integration.dart';
export 'src/screenshot/sentry_screenshot_widget.dart';
export 'src/screenshot/sentry_screenshot_quality.dart';
export 'src/user_interaction/sentry_user_interaction_widget.dart';
export 'src/binding_wrapper.dart';
16 changes: 14 additions & 2 deletions flutter/lib/src/event_processor/screenshot_event_processor.dart
@@ -1,6 +1,8 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as ui show ImageByteFormat;
import 'dart:ui';

import 'package:sentry/sentry.dart';
import '../screenshot/sentry_screenshot_widget.dart';
Expand Down Expand Up @@ -44,9 +46,9 @@ class ScreenshotEventProcessor extends EventProcessor {
try {
final renderObject =
sentryScreenshotWidgetGlobalKey.currentContext?.findRenderObject();

if (renderObject is RenderRepaintBoundary) {
final image = await renderObject.toImage(pixelRatio: 1);
final pixelRatio = window.devicePixelRatio;
var image = await renderObject.toImage(pixelRatio: pixelRatio);
// At the time of writing there's no other image format available which
// Sentry understands.

Expand All @@ -56,7 +58,17 @@ class ScreenshotEventProcessor extends EventProcessor {
return null;
}

final targetResolution = _options.screenshotQuality.targetResolution();
if (targetResolution != null) {
var ratioWidth = targetResolution / image.width;
var ratioHeight = targetResolution / image.height;
var ratio = min(ratioWidth, ratioHeight);
if (ratio > 0.0 && ratio < 1.0) {
image = await renderObject.toImage(pixelRatio: ratio * pixelRatio);
}
}
final byteData = await image.toByteData(format: ui.ImageByteFormat.png);

final bytes = byteData?.buffer.asUint8List();
if (bytes?.isNotEmpty == true) {
return bytes;
Expand Down
20 changes: 20 additions & 0 deletions flutter/lib/src/screenshot/sentry_screenshot_quality.dart
@@ -0,0 +1,20 @@
/// The quality of the attached screenshot
enum SentryScreenshotQuality {
full,
high,
medium,
low;

int? targetResolution() {
switch (this) {
case SentryScreenshotQuality.full:
return null; // Keep current scale
case SentryScreenshotQuality.high:
return 1920;
case SentryScreenshotQuality.medium:
return 1280;
case SentryScreenshotQuality.low:
return 854;
}
}
}
4 changes: 4 additions & 0 deletions flutter/lib/src/sentry_flutter_options.dart
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/widgets.dart';

import 'binding_wrapper.dart';
import 'renderer/renderer.dart';
import 'screenshot/sentry_screenshot_quality.dart';

/// This class adds options which are only availble in a Flutter environment.
/// Note that some of these options require native Sentry integration, which is
Expand Down Expand Up @@ -163,6 +164,9 @@ class SentryFlutterOptions extends SentryOptions {
/// The [SentryScreenshotWidget] has to be the root widget of the app.
bool attachScreenshot = false;

/// The quality of the attached screenshot
SentryScreenshotQuality screenshotQuality = SentryScreenshotQuality.high;

/// Enable or disable automatic breadcrumbs for User interactions Using [Listener]
///
/// Requires adding the [SentryUserInteractionWidget] to the widget tree.
Expand Down
@@ -1,5 +1,8 @@
@Tags(['canvasKit']) // Web renderer where this test can run

import 'dart:math';
import 'dart:ui';

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sentry_flutter/src/event_processor/screenshot_event_processor.dart';
Expand All @@ -9,13 +12,15 @@ import 'package:sentry_flutter/sentry_flutter.dart';

void main() {
late Fixture fixture;

setUp(() {
fixture = Fixture();
TestWidgetsFlutterBinding.ensureInitialized();
});

Future<void> _addScreenshotAttachment(
WidgetTester tester, FlutterRenderer renderer, bool added) async {
WidgetTester tester, FlutterRenderer renderer, bool added,
{int? expectedMaxWidthOrHeight}) async {
// Run with real async https://stackoverflow.com/a/54021863
await tester.runAsync(() async {
final sut = fixture.getSut(renderer);
Expand All @@ -30,6 +35,16 @@ void main() {
await sut.apply(event, hint: hint);

expect(hint.screenshot != null, added);
if (expectedMaxWidthOrHeight != null) {
final bytes = await hint.screenshot?.bytes;
final codec = await instantiateImageCodec(bytes!);
final frameInfo = await codec.getNextFrame();
final image = frameInfo.image;
expect(
max(image.width, image.height).toDouble(),
moreOrLessEquals(expectedMaxWidthOrHeight.toDouble(), epsilon: 1.0),
);
}
});
}

Expand All @@ -51,6 +66,30 @@ void main() {
(tester) async {
await _addScreenshotAttachment(tester, FlutterRenderer.unknown, false);
});

testWidgets('does add screenshot in correct resolution for low',
(tester) async {
final height = SentryScreenshotQuality.low.targetResolution()!;
fixture.options.screenshotQuality = SentryScreenshotQuality.low;
await _addScreenshotAttachment(tester, FlutterRenderer.skia, true,
expectedMaxWidthOrHeight: height);
});

testWidgets('does add screenshot in correct resolution for medium',
(tester) async {
final height = SentryScreenshotQuality.medium.targetResolution()!;
fixture.options.screenshotQuality = SentryScreenshotQuality.medium;
await _addScreenshotAttachment(tester, FlutterRenderer.skia, true,
expectedMaxWidthOrHeight: height);
});

testWidgets('does add screenshot in correct resolution for high',
(tester) async {
final widthOrHeight = SentryScreenshotQuality.high.targetResolution()!;
fixture.options.screenshotQuality = SentryScreenshotQuality.high;
await _addScreenshotAttachment(tester, FlutterRenderer.skia, true,
expectedMaxWidthOrHeight: widthOrHeight);
});
}

class Fixture {
Expand Down

0 comments on commit c238d73

Please sign in to comment.