capture method

Future<CockpitCapturedScreenshot> capture({
  1. required GlobalKey<State<StatefulWidget>> repaintBoundaryKey,
  2. required CockpitScreenshotRequest request,
  3. CockpitSnapshot? snapshot,
  4. double pixelRatio = 1.0,
  5. Rect? cropRect,
})

Implementation

Future<CockpitCapturedScreenshot> capture({
  required GlobalKey repaintBoundaryKey,
  required CockpitScreenshotRequest request,
  CockpitSnapshot? snapshot,
  double pixelRatio = 1.0,
  ui.Rect? cropRect,
}) async {
  final binding = WidgetsBinding.instance;
  final frameWait = await waitForPendingCockpitFrame(
    phase: binding.schedulerPhase,
    hasScheduledFrame: binding.hasScheduledFrame,
    waitForEndOfFrame: () => binding.endOfFrame,
  );
  final degradationReason =
      frameWait == CockpitPendingFrameWaitResult.timedOut
      ? cockpitFrameTimeoutDegradationReason
      : null;

  final context = repaintBoundaryKey.currentContext;
  if (context == null) {
    throw StateError('CockpitSurface capture boundary is not mounted.');
  }

  final boundary = context.findRenderObject();
  if (boundary is! RenderRepaintBoundary) {
    throw StateError(
      'CockpitSurface capture boundary is not a RepaintBoundary.',
    );
  }

  var image = await boundary.toImage(pixelRatio: pixelRatio);
  ByteData byteData;
  try {
    if (cropRect != null) {
      final left = (cropRect.left * pixelRatio).floor().clamp(0, image.width);
      final top = (cropRect.top * pixelRatio).floor().clamp(0, image.height);
      final right = (cropRect.right * pixelRatio).ceil().clamp(
        left,
        image.width,
      );
      final bottom = (cropRect.bottom * pixelRatio).ceil().clamp(
        top,
        image.height,
      );
      final width = right - left;
      final height = bottom - top;
      if (width == 0 || height == 0) {
        throw StateError('Screenshot crop region is empty.');
      }
      final recorder = ui.PictureRecorder();
      final canvas = ui.Canvas(recorder);
      canvas.drawImageRect(
        image,
        ui.Rect.fromLTWH(
          left.toDouble(),
          top.toDouble(),
          width.toDouble(),
          height.toDouble(),
        ),
        ui.Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()),
        ui.Paint(),
      );
      final cropped = await recorder.endRecording().toImage(width, height);
      image.dispose();
      image = cropped;
    }
    final encoded = await image.toByteData(format: ui.ImageByteFormat.png);
    if (encoded == null) {
      throw StateError('Failed to encode CockpitSurface capture as PNG.');
    }
    byteData = encoded;
  } finally {
    image.dispose();
  }

  final bytes = byteData.buffer.asUint8List(
    byteData.offsetInBytes,
    byteData.lengthInBytes,
  );
  if (bytes.isEmpty) {
    throw StateError('CockpitSurface capture encoded an empty PNG.');
  }

  return CockpitCapturedScreenshot(
    artifact: CockpitArtifactRef(
      role: 'screenshot',
      relativePath: cockpitScreenshotRelativePathFor(request),
    ),
    bytes: bytes,
    snapshot: degradationReason == null
        ? snapshot
        : snapshot?.copyWith(
            degradationReason: _mergeDegradationReasons(
              snapshot.degradationReason,
              degradationReason,
            ),
          ),
    degradationReason: degradationReason,
  );
}