Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve correctness of Sixel background fill #18260

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 58 additions & 13 deletions src/terminal/adapter/SixelParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ void SixelParser::_executeNextLine()
_imageCursor.y += _sixelHeight;
_availablePixelHeight -= _sixelHeight;
_resizeImageBuffer(_sixelHeight);
_fillImageBackgroundWhenScrolled();
}

void SixelParser::_executeMoveToHome()
Expand Down Expand Up @@ -341,6 +342,12 @@ void SixelParser::_initRasterAttributes(const VTInt macroParameter, const Dispat

// By default, the filled area will cover the maximum extent allowed.
_backgroundSize = { til::CoordTypeMax, til::CoordTypeMax };

// If the image ends up extending beyond the bottom of the page, we may need
// to perform additional background filling as the screen is scrolled, which
// requires us to track the area filled so far. This will be initialized, if
// necessary, in the _fillImageBackground method below.
_filledBackgroundHeight = std::nullopt;
}

void SixelParser::_updateRasterAttributes(const VTParameters& rasterAttributes)
Expand Down Expand Up @@ -656,24 +663,60 @@ void SixelParser::_fillImageBackground()
{
_backgroundFillRequired = false;

const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight);
const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth);
_resizeImageBuffer(backgroundHeight);

// When a background fill is requested, we prefill the buffer with the 0
// color index, up to the boundaries set by the raster attributes (or if
// none were given, up to the page boundaries). The actual image output
// isn't limited by the background dimensions though.
static constexpr auto backgroundPixel = IndexedPixel{};
const auto backgroundOffset = _imageCursor.y * _imageMaxWidth;
auto dst = std::next(_imageBuffer.begin(), backgroundOffset);
for (auto i = 0; i < backgroundHeight; i++)
{
std::fill_n(dst, backgroundWidth, backgroundPixel);
std::advance(dst, _imageMaxWidth);
}
const auto backgroundHeight = std::min(_backgroundSize.height, _availablePixelHeight);
_resizeImageBuffer(backgroundHeight);
_fillImageBackground(backgroundHeight);
// When the image extends beyond the page boundaries, and the screen is
// scrolled, we also need to fill the newly exposed lines, so we keep a
// record of the area filled so far. Initially this is considered to be
// the available height, even if it wasn't all filled to start with.
_filledBackgroundHeight = _imageCursor.y + _availablePixelHeight;
_fillImageBackgroundWhenScrolled();
}
}

_imageWidth = std::max(_imageWidth, backgroundWidth);
void SixelParser::_fillImageBackground(const int backgroundHeight)
{
static constexpr auto backgroundPixel = IndexedPixel{};
const auto backgroundWidth = std::min(_backgroundSize.width, _availablePixelWidth);
const auto backgroundOffset = _imageCursor.y * _imageMaxWidth;
auto dst = std::next(_imageBuffer.begin(), backgroundOffset);
for (auto i = 0; i < backgroundHeight; i++)
{
std::fill_n(dst, backgroundWidth, backgroundPixel);
std::advance(dst, _imageMaxWidth);
}
_imageWidth = std::max(_imageWidth, backgroundWidth);
}

void SixelParser::_fillImageBackgroundWhenScrolled()
{
// If _filledBackgroundHeight is set, that means a background fill has been
// requested, and we need to extend that area whenever the image is about to
// overrun it. The newly filled area is a multiple of the cell height (this
// is to match the behavior of the original hardware terminals).
const auto imageHeight = _imageCursor.y + _sixelHeight;
if (_filledBackgroundHeight && imageHeight > _filledBackgroundHeight) [[unlikely]]
{
_filledBackgroundHeight = (imageHeight + _cellSize.height - 1) / _cellSize.height * _cellSize.height;
const auto additionalFillHeight = _filledBackgroundHeight.value() - _imageCursor.y;
_resizeImageBuffer(additionalFillHeight);
_fillImageBackground(additionalFillHeight);
}
}

void SixelParser::_decreaseFilledBackgroundHeight(const int decreasedHeight)
{
// Sometimes the top of the image buffer may be clipped (e.g. when the image
// scrolls off the top of a margin area). When that occurs, our record of
// the filled height will need to be decreased to account for the new start.
if (_filledBackgroundHeight) [[unlikely]]
{
_filledBackgroundHeight = _filledBackgroundHeight.value() - decreasedHeight;
}
}

Expand Down Expand Up @@ -717,11 +760,13 @@ void SixelParser::_eraseImageBufferRows(const int rowCount, const til::CoordType
const auto bufferOffsetEnd = bufferOffset + pixelCount * _imageMaxWidth;
if (static_cast<size_t>(bufferOffsetEnd) >= _imageBuffer.size()) [[unlikely]]
{
_decreaseFilledBackgroundHeight(_imageCursor.y);
_imageBuffer.clear();
_imageCursor.y = 0;
}
else
{
_decreaseFilledBackgroundHeight(pixelCount);
_imageBuffer.erase(_imageBuffer.begin() + bufferOffset, _imageBuffer.begin() + bufferOffsetEnd);
_imageCursor.y -= pixelCount;
}
Expand Down
4 changes: 4 additions & 0 deletions src/terminal/adapter/SixelParser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ namespace Microsoft::Console::VirtualTerminal
til::CoordType _pendingTextScrollCount = 0;
til::size _backgroundSize;
bool _backgroundFillRequired = false;
std::optional<til::CoordType> _filledBackgroundHeight;

void _initColorMap(const VTParameter backgroundColor);
void _defineColor(const VTParameters& colorParameters);
Expand All @@ -109,6 +110,9 @@ namespace Microsoft::Console::VirtualTerminal
void _initImageBuffer();
void _resizeImageBuffer(const til::CoordType requiredHeight);
void _fillImageBackground();
void _fillImageBackground(const int backgroundHeight);
void _fillImageBackgroundWhenScrolled();
void _decreaseFilledBackgroundHeight(const int decreasedHeight);
void _writeToImageBuffer(const int sixelValue, const int repeatCount);
void _eraseImageBufferRows(const int rowCount, const til::CoordType startRow = 0) noexcept;
void _maybeFlushImageBuffer(const bool endOfSequence = false);
Expand Down
Loading