capture static method

Future capture(
  1. GlobalKey<State<StatefulWidget>> key, {
  2. ScreenshotConfig config = const ScreenshotConfig(),
})

Captures a widget screenshot using the provided key.

Returns String (base64), Uint8List (bytes), or File based on config. Throws ScreenshotException on failure.

Implementation

static Future<dynamic> capture(
  GlobalKey key, {
  ScreenshotConfig config = const ScreenshotConfig(),
}) async {
  try {
    if (config.resultType == ScreenshotResultType.download &&
        (config.fileName == null || config.fileName!.isEmpty)) {
      throw ScreenshotException(
        'fileName is required when resultType is download',
      );
    }

    // Get render object before any async operations to avoid BuildContext issues
    final context = key.currentContext;
    if (context == null) {
      throw ScreenshotException(
        'Widget is not yet rendered. Make sure the GlobalKey is attached to a widget in the tree.',
      );
    }

    final renderObject = context.findRenderObject();
    if (renderObject == null) {
      throw ScreenshotException(
        'RenderObject not found. Make sure the widget is visible.',
      );
    }

    if (renderObject is! RenderRepaintBoundary) {
      throw ScreenshotException(
        'The widget must be wrapped in a RepaintBoundary.',
      );
    }

    if (config.captureDelay != null) {
      await Future.delayed(config.captureDelay!);
    }

    // Wait for the rendering pipeline to fully complete
    final boundary = renderObject;
    await WidgetsBinding.instance.endOfFrame;

    // Capture and immediately extract byte data, then dispose the native image
    final image = await boundary.toImage(pixelRatio: config.pixelRatio);
    final ByteData? byteData;
    try {
      byteData = await image.toByteData(
        format: config.format == ScreenshotFormat.png
            ? ui.ImageByteFormat.png
            : ui.ImageByteFormat.rawRgba,
      );
    } finally {
      image.dispose();
    }

    if (byteData == null) {
      throw ScreenshotException('Failed to convert image to byte data');
    }

    // Use precise offset and length to avoid reading beyond actual data
    final buffer = byteData.buffer.asUint8List(
      byteData.offsetInBytes,
      byteData.lengthInBytes,
    );

    if (kDebugMode && config.shouldShowDebugLogs) {
      debugPrint('Screenshot captured: ${buffer.length} bytes');
    }
    switch (config.resultType) {
      case ScreenshotResultType.base64:
        // Offload base64 encoding to a separate isolate on native platforms;
        // on web, isolates aren't supported so we chunk-encode to avoid
        // blocking the main thread (which would freeze animations).
        final base64String = kIsWeb
            ? await _chunkedBase64Encode(buffer)
            : await compute(base64Encode, buffer);
        if (kDebugMode && config.shouldShowDebugLogs) {
          debugPrint('Base64 length: ${base64String.length}');
        }
        return base64String;

      case ScreenshotResultType.bytes:
        return buffer;

      case ScreenshotResultType.download:
        // For download type, return bytes and let downloadScreenshot handle the file saving
        return buffer;
    }
  } catch (e) {
    if (kDebugMode) {
      debugPrint('Screenshot capture failed: $e');
    }
    if (e is ScreenshotException) {
      rethrow;
    }
    throw ScreenshotException('Failed to capture screenshot', e);
  }
}