diff --git a/src/terminal/Screen.cpp b/src/terminal/Screen.cpp index a464bc636b..207bc01f9b 100644 --- a/src/terminal/Screen.cpp +++ b/src/terminal/Screen.cpp @@ -3413,8 +3413,8 @@ unique_ptr Screen::hookSixel(Sequence const& _seq) case 3: return 3; case 2: return 5; case 1: - case 0: - default: return 2; + case 0: return 2; + default: return 1; } }(Pa); diff --git a/src/terminal/SixelParser.cpp b/src/terminal/SixelParser.cpp index 1a409fe70b..5f4d4b6fb1 100644 --- a/src/terminal/SixelParser.cpp +++ b/src/terminal/SixelParser.cpp @@ -23,6 +23,7 @@ using std::vector; // VT 340 sixel protocol is defined here: https://vt100.net/docs/vt3xx-gp/chapter14.html +using namespace std; namespace terminal { @@ -289,13 +290,17 @@ void SixelParser::leaveState() case State::RepeatIntroducer: break; case State::RasterSettings: - if (params_.size() == 4) + if (params_.size() > 1 && params_.size() < 5) { auto const pan = params_[0]; auto const pad = params_[1]; - auto const xPixels = Width(params_[2]); - auto const yPixels = Height(params_[3]); - events_.setRaster((int) pan, (int) pad, ImageSize { xPixels, yPixels }); + + auto const imageSize = + params_.size() > 2 + ? optional { ImageSize { Width(params_[2]), Height(params_[3]) } } + : std::nullopt; + + events_.setRaster(pan, pad, imageSize); state_ = State::Ground; } break; @@ -371,7 +376,9 @@ SixelImageBuilder::SixelImageBuilder(ImageSize _maxSize, buffer_(maxSize_.area() * 4), sixelCursor_ {}, currentColor_ { 0 }, - aspectRatio_ { _aspectVertical, _aspectHorizontal } + aspectRatio_(static_cast( + std::ceil(static_cast(_aspectVertical) / static_cast(_aspectHorizontal)))), + sixelBandHeight_(6 * aspectRatio_) { clear(_backgroundColor); } @@ -408,23 +415,20 @@ void SixelImageBuilder::write(CellLocation const& _coord, RGBColor const& _value if (!explicitSize_) { if (unbox(_coord.line) >= unbox(size_.height)) - size_.height = Height::cast_from(_coord.line + 1); + size_.height = Height::cast_from(_coord.line.as() + aspectRatio_); if (unbox(_coord.column) >= unbox(size_.width)) size_.width = Width::cast_from(_coord.column + 1); } - for (auto i = 0; i < aspectRatio_.nominator; ++i) + for (unsigned int i = 0; i < aspectRatio_; ++i) { - auto const base = unbox(_coord.line + i) - * unbox((explicitSize_ ? size_.width : maxSize_.width)) * 4 - + unbox(_coord.column) * 4; - for (auto j = 0; j < aspectRatio_.denominator; ++j) - { - buffer_[base + 0 + 4 * static_cast(j)] = _value.red; - buffer_[base + 1 + 4 * static_cast(j)] = _value.green; - buffer_[base + 2 + 4 * static_cast(j)] = _value.blue; - buffer_[base + 3 + 4 * static_cast(j)] = 0xFF; - } + auto const base = (_coord.line.as() + i) + * unbox((explicitSize_ ? size_.width : maxSize_.width)) * 4u + + unbox(_coord.column) * 4u; + buffer_[base + 0] = _value.red; + buffer_[base + 1] = _value.green; + buffer_[base + 2] = _value.blue; + buffer_[base + 3] = 0xFF; } } } @@ -447,19 +451,25 @@ void SixelImageBuilder::rewind() void SixelImageBuilder::newline() { sixelCursor_.column = {}; - if (unbox(sixelCursor_.line) + 6 < unbox(explicitSize_ ? size_.height : maxSize_.height)) - sixelCursor_.line.value += 6; + if (unbox(sixelCursor_.line) + sixelBandHeight_ + < unbox(explicitSize_ ? size_.height : maxSize_.height)) + sixelCursor_.line = LineOffset::cast_from(sixelCursor_.line.as() + sixelBandHeight_); } -void SixelImageBuilder::setRaster(int _pan, int _pad, ImageSize _imageSize) +void SixelImageBuilder::setRaster(unsigned int _pan, unsigned int _pad, optional _imageSize) { - aspectRatio_.nominator = _pan; - aspectRatio_.denominator = _pad; - size_.width = clamp(_imageSize.width, Width(0), maxSize_.width); - size_.height = clamp(_imageSize.height, Height(0), maxSize_.height); - - buffer_.resize(size_.area() * static_cast(_pad * _pan) * 4); - explicitSize_ = true; + if (_pad != 0) + aspectRatio_ = max( + 1u, static_cast(std::ceil(static_cast(_pan) / static_cast(_pad)))); + sixelBandHeight_ = 6 * aspectRatio_; + if (_imageSize) + { + _imageSize->height = Height::cast_from(_imageSize->height.value * aspectRatio_); + size_.width = clamp(_imageSize->width, Width(0), maxSize_.width); + size_.height = clamp(_imageSize->height, Height(0), maxSize_.height); + buffer_.resize(size_.area() * 4); + explicitSize_ = true; + } } void SixelImageBuilder::render(int8_t _sixel) @@ -468,9 +478,9 @@ void SixelImageBuilder::render(int8_t _sixel) auto const x = sixelCursor_.column; if (unbox(x) < unbox((explicitSize_ ? size_.width : maxSize_.width))) { - for (int i = 0; i < 6; ++i) + for (unsigned int i = 0; i < 6; ++i) { - auto const y = sixelCursor_.line + i; + auto const y = sixelCursor_.line + static_cast(i * aspectRatio_); auto const pos = CellLocation { y, x }; auto const pin = 1 << i; auto const pinned = (_sixel & pin) != 0; @@ -485,7 +495,7 @@ void SixelImageBuilder::finalize() { if (unbox(size_.height) == 1) { - size_.height = Height::cast_from(sixelCursor_.line * aspectRatio_.nominator); + size_.height = Height::cast_from(sixelCursor_.line.as() * aspectRatio_); buffer_.resize(size_.area() * 4); return; } diff --git a/src/terminal/SixelParser.h b/src/terminal/SixelParser.h index e4d81e795d..c2b47e2b94 100644 --- a/src/terminal/SixelParser.h +++ b/src/terminal/SixelParser.h @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -71,7 +72,7 @@ class SixelParser: public ParserExtension /// Defines the aspect ratio (pan / pad = aspect ratio) and image dimensions in pixels for /// the upcoming pixel data. - virtual void setRaster(int _pan, int _pad, ImageSize _imageSize) = 0; + virtual void setRaster(unsigned int _pan, unsigned int _pad, std::optional _imageSize) = 0; /// renders a given sixel at the current sixel-cursor position. virtual void render(int8_t _sixel) = 0; @@ -162,8 +163,7 @@ class SixelImageBuilder: public SixelParser::Events [[nodiscard]] ImageSize maxSize() const noexcept { return maxSize_; } [[nodiscard]] ImageSize size() const noexcept { return size_; } - [[nodiscard]] int aspectRatioNominator() const noexcept { return aspectRatio_.nominator; } - [[nodiscard]] int aspectRatioDenominator() const noexcept { return aspectRatio_.denominator; } + [[nodiscard]] unsigned int aspectRatio() const noexcept { return aspectRatio_; } [[nodiscard]] RGBColor currentColor() const noexcept { return colors_->at(currentColor_); } [[nodiscard]] RGBAColor at(CellLocation _coord) const noexcept; @@ -177,7 +177,7 @@ class SixelImageBuilder: public SixelParser::Events void useColor(unsigned _index) override; void rewind() override; void newline() override; - void setRaster(int _pan, int _pad, ImageSize _imageSize) override; + void setRaster(unsigned int _pan, unsigned int _pad, std::optional _imageSize) override; void render(int8_t _sixel) override; void finalize() override; @@ -193,12 +193,12 @@ class SixelImageBuilder: public SixelParser::Events Buffer buffer_; /// RGBA buffer CellLocation sixelCursor_; unsigned currentColor_; - struct - { - int nominator; - int denominator; - } aspectRatio_; bool explicitSize_ = false; + // This is an int because vt3xx takes the given ratio pan/pad and rounds up the ratio + // to nearest integers. So 1:3 = 0.33 and it becomes 1; + unsigned int aspectRatio_; + // Height of sixel band in pixels + unsigned int sixelBandHeight_; }; } // namespace terminal diff --git a/src/terminal/SixelParser_test.cpp b/src/terminal/SixelParser_test.cpp index 0a30300873..c0aebf7dd9 100644 --- a/src/terminal/SixelParser_test.cpp +++ b/src/terminal/SixelParser_test.cpp @@ -195,10 +195,29 @@ TEST_CASE("SixelParser.raster", "[sixel]") sp.done(); CHECK(ib.sixelCursor() == CellLocation { LineOffset(0), ColumnOffset(0) }); - CHECK(ib.aspectRatioNominator() == 12); - CHECK(ib.aspectRatioDenominator() == 34); + CHECK(ib.aspectRatio() == 1); CHECK(*ib.size().width == 32); CHECK(*ib.size().height == 24); + sp.parseFragment("\"12;34"); + sp.done(); + CHECK(ib.sixelCursor() == CellLocation { LineOffset(0), ColumnOffset(0) }); + CHECK(ib.aspectRatio() == 1); + sp.parseFragment("\""); + sp.done(); + CHECK(ib.sixelCursor() == CellLocation { LineOffset(0), ColumnOffset(0) }); + CHECK(ib.aspectRatio() == 1); + sp.parseFragment("\"0;0"); + sp.done(); + CHECK(ib.sixelCursor() == CellLocation { LineOffset(0), ColumnOffset(0) }); + CHECK(ib.aspectRatio() == 1); + sp.parseFragment("\"5;0"); + sp.done(); + CHECK(ib.sixelCursor() == CellLocation { LineOffset(0), ColumnOffset(0) }); + CHECK(ib.aspectRatio() == 1); + sp.parseFragment("\"15;2"); + sp.done(); + CHECK(ib.sixelCursor() == CellLocation { LineOffset(0), ColumnOffset(0) }); + CHECK(ib.aspectRatio() == 8); } TEST_CASE("SixelParser.rep", "[sixel]")