drawImage method

bool drawImage(
  1. TerminalImage image, {
  2. required int x,
  3. required int y,
  4. required int width,
  5. required int height,
  6. int pixelWidth = 0,
  7. int pixelHeight = 0,
  8. int sourceX = 0,
  9. int sourceY = 0,
  10. int? sourceWidth,
  11. int? sourceHeight,
  12. ImageProtocol protocol = ImageProtocol.auto,
  13. int? clipX,
  14. int? clipY,
  15. int? clipWidth,
  16. int? clipHeight,
})

Draws image into a terminal-cell rectangle.

Pixel dimensions of zero let OpenTUI use its fallback cell aspect. The optional clip is pushed onto the native scissor stack so OpenTUI adjusts both the destination and proportional source crop as one operation.

Implementation

bool drawImage(
  TerminalImage image, {
  required int x,
  required int y,
  required int width,
  required int height,
  int pixelWidth = 0,
  int pixelHeight = 0,
  int sourceX = 0,
  int sourceY = 0,
  int? sourceWidth,
  int? sourceHeight,
  ImageProtocol protocol = ImageProtocol.auto,
  int? clipX,
  int? clipY,
  int? clipWidth,
  int? clipHeight,
}) {
  _checkValid();
  final info = image.info;
  final resolvedSourceWidth = sourceWidth ?? info.pixelWidth;
  final resolvedSourceHeight = sourceHeight ?? info.pixelHeight;
  _checkSigned32Abi(x, 'x');
  _checkSigned32Abi(y, 'y');
  for (final (name, value) in <(String, int)>[
    ('width', width),
    ('height', height),
    ('pixelWidth', pixelWidth),
    ('pixelHeight', pixelHeight),
    ('sourceX', sourceX),
    ('sourceY', sourceY),
    ('sourceWidth', resolvedSourceWidth),
    ('sourceHeight', resolvedSourceHeight),
  ]) {
    final maximum = switch (name) {
      'width' || 'height' || 'pixelWidth' || 'pixelHeight' => 0x7FFFFFFF,
      _ => 0xFFFFFFFF,
    };
    _checkUnsignedAbi(value, maximum, name);
  }
  if (width <= 0 || height <= 0) {
    throw ArgumentError('image width and height must be positive');
  }
  if (x + width > 0x7FFFFFFF || y + height > 0x7FFFFFFF) {
    throw RangeError('image destination exceeds signed 32-bit bounds');
  }
  if (resolvedSourceWidth <= 0 || resolvedSourceHeight <= 0) {
    throw ArgumentError('image source width and height must be positive');
  }
  final hasClip =
      clipX != null ||
      clipY != null ||
      clipWidth != null ||
      clipHeight != null;
  if (hasClip &&
      (clipX == null ||
          clipY == null ||
          clipWidth == null ||
          clipHeight == null)) {
    throw ArgumentError(
      'clipX, clipY, clipWidth, and clipHeight must be supplied together',
    );
  }
  if (hasClip) {
    _checkSigned32Abi(clipX!, 'clipX');
    _checkSigned32Abi(clipY!, 'clipY');
    _checkUnsignedAbi(clipWidth!, 0xFFFFFFFF, 'clipWidth');
    _checkUnsignedAbi(clipHeight!, 0xFFFFFFFF, 'clipHeight');
  }
  if (_materializeImagesAsBlocks) {
    final left = hasClip ? math.max(x, clipX!) : x;
    final top = hasClip ? math.max(y, clipY!) : y;
    final right = hasClip
        ? math.min(x + width, clipX! + clipWidth!)
        : x + width;
    final bottom = hasClip
        ? math.min(y + height, clipY! + clipHeight!)
        : y + height;
    for (
      var row = math.max(0, top);
      row < math.min(this.height, bottom);
      row++
    ) {
      for (
        var column = math.max(0, left);
        column < math.min(this.width, right);
        column++
      ) {
        setCell(column, row, '▀', Color.white, Color.black, 0);
      }
    }
    return true;
  }
  if (hasClip) {
    _bindings.bufferPushScissorRect(
      _ptr,
      clipX!,
      clipY!,
      clipWidth!,
      clipHeight!,
    );
  }
  try {
    return _bindings.bufferDrawImage(
      _ptr,
      image.handle,
      x: x,
      y: y,
      width: width,
      height: height,
      pixelWidth: pixelWidth,
      pixelHeight: pixelHeight,
      sourceX: sourceX,
      sourceY: sourceY,
      sourceWidth: resolvedSourceWidth,
      sourceHeight: resolvedSourceHeight,
      protocol: protocol.index,
    );
  } finally {
    if (hasClip) _bindings.bufferPopScissorRect(_ptr);
  }
}