Skip to content

Commit

Permalink
[terminal] WIP: Good Image Protocol (PoC)
Browse files Browse the repository at this point in the history
  • Loading branch information
christianparpart committed Mar 20, 2022
1 parent fac7189 commit e0aff93
Show file tree
Hide file tree
Showing 14 changed files with 939 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ option(CONTOUR_COVERAGE "Builds with codecov [default: OFF]" OFF)
option(CONTOUR_SANITIZE "Builds with Address sanitizer enabled [default: OFF]" "OFF")
option(CONTOUR_STACKTRACE_ADDR2LINE "Uses addr2line to pretty-print SEGV stacktrace." ${ADDR2LINE_DEFAULT})
option(CONTOUR_BUILD_WITH_MIMALLOC "Builds with mimalloc [default: OFF]" OFF)
option(CONTOUR_GOOD_IMAGE_PROTOCOL "Enables Good Image Protocol support [default: ON]" ON)

if(CONTOUR_GOOD_IMAGE_PROTOCOL)
add_definitions(-DGOOD_IMAGE_PROTOCOL=1)
endif()

if(NOT WIN32 AND NOT CONTOUR_SANITIZE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CONTOUR_SANITIZE "OFF" CACHE STRING "Choose the sanitizer mode." FORCE)
Expand Down
129 changes: 129 additions & 0 deletions src/contour/ContourApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include <crispy/App.h>
#include <crispy/StackTrace.h>
#include <crispy/base64.h>
#include <crispy/utils.h>

#include <fmt/chrono.h>
Expand Down Expand Up @@ -54,6 +55,7 @@ using std::string_view;
using std::unique_ptr;

using namespace std::string_literals;
using namespace std::string_view_literals;

namespace CLI = crispy::cli;

Expand Down Expand Up @@ -239,6 +241,104 @@ int ContourApp::captureAction()
return EXIT_FAILURE;
}

#if defined(GOOD_IMAGE_PROTOCOL)
namespace
{
crispy::Size parseSize(string_view _text)
{
(void) _text;
return crispy::Size {}; // TODO
}

terminal::ImageAlignment parseImageAlignment(string_view _text)
{
(void) _text;
return terminal::ImageAlignment::TopStart; // TODO
}

terminal::ImageResize parseImageResize(string_view _text)
{
(void) _text;
return terminal::ImageResize::NoResize; // TODO
}

terminal::CellLocation parsePosition(string_view _text)
{
(void) _text;
return {}; // TODO
}

// TODO: chunkedFileReader(path) to return iterator over spans of data chunks.
std::vector<uint8_t> readFile(FileSystem::path const& _path)
{
auto ifs = std::ifstream(_path.string());
if (!ifs.good())
return {};

auto const size = FileSystem::file_size(_path);
auto text = std::vector<uint8_t>();
text.resize(size);
ifs.read((char*) &text[0], static_cast<std::streamsize>(size));
return text;
}

void displayImage(terminal::ImageResize _resizePolicy,
terminal::ImageAlignment _alignmentPolicy,
crispy::Size _screenSize,
string_view _fileName)
{
auto constexpr ST = "\033\\"sv;

cout << fmt::format("{}f={},c={},l={},a={},z={};",
"\033Ps"sv, // GIONESHOT
'0', // image format: 0 = auto detect
_screenSize.width,
_screenSize.height,
int(_alignmentPolicy),
int(_resizePolicy));

#if 1
auto const data = readFile(FileSystem::path(string(_fileName))); // TODO: incremental buffered read
auto encoderState = crispy::base64::EncoderState {};

std::vector<char> buf;
auto const writer = [&](string_view _data) {
for (auto ch: _data)
buf.push_back(ch);
};
auto const flush = [&]() {
cout.write(buf.data(), buf.size());
buf.clear();
};

for (uint8_t const byte: data)
{
crispy::base64::encode(byte, encoderState, writer);
if (buf.size() >= 4096)
flush();
}
flush();
#endif

cout << ST;
}
} // namespace

int ContourApp::imageAction()
{
auto const resizePolicy = parseImageResize(parameters().get<string>("contour.image.resize"));
auto const alignmentPolicy = parseImageAlignment(parameters().get<string>("contour.image.align"));
auto const size = parseSize(parameters().get<string>("contour.image.size"));
auto const fileName = parameters().verbatim.front();
// TODO: how do we wanna handle more than one verbatim arg (image)?
// => report error and EXIT_FAILURE as only one verbatim arg is allowed.
// FIXME: What if parameter `size` is given as `_size` instead, it should cause an
// invalid-argument error above already!
displayImage(resizePolicy, alignmentPolicy, size, fileName);
return EXIT_SUCCESS;
}
#endif

int ContourApp::parserTableAction()
{
terminal::parser::dot(std::cout, terminal::parser::ParserTable::get());
Expand Down Expand Up @@ -318,6 +418,35 @@ crispy::cli::Command ContourApp::parameterDefinition() const
"FILE",
CLI::Presence::Required },
} } } },
#if defined(GOOD_IMAGE_PROTOCOL)
CLI::Command {
"image",
"Sends an image to the terminal emulator for display.",
CLI::OptionList {
CLI::Option { "resize",
CLI::Value { "fit"s },
"Sets the image resize policy.\n"
"Policies available are:\n"
" - no (no resize),\n"
" - fit (resize to fit),\n"
" - fill (resize to fill),\n"
" - stretch (stretch to fill)." },
CLI::Option { "align",
CLI::Value { "center"s },
"Sets the image alignment policy.\n"
"Possible policies are: TopLeft, TopCenter, TopRight, MiddleLeft, "
"MiddleCenter, MiddleRight, BottomLeft, BottomCenter, BottomRight." },
CLI::Option { "size",
CLI::Value { ""s },
"Sets the amount of columns and rows to place the image onto. "
"The top-left of the this area is the current cursor position, "
"and it will be scrolled automatically if not enough rows are present." } },
CLI::CommandList {},
CLI::CommandSelect::Explicit,
CLI::Verbatim {
"IMAGE_FILE",
"Path to image to be displayed. Image formats supported are at least PNG, JPG." } },
#endif
CLI::Command {
"capture",
"Captures the screen buffer of the currently running terminal.",
Expand Down
1 change: 1 addition & 0 deletions src/contour/ContourApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ContourApp: public crispy::App
int terminfoAction();
int configAction();
int integrationAction();
int imageAction();
};

} // namespace contour
36 changes: 36 additions & 0 deletions src/contour/opengl/TerminalWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1219,4 +1219,40 @@ void TerminalWidget::discardImage(terminal::Image const& _image)
}
// }}}

optional<terminal::Image> TerminalWidget::decodeImage(crispy::span<uint8_t> _imageData)
{
QImage image;
image.loadFromData(_imageData.begin(), _imageData.size());

qDebug() << "decodeImage()" << image.format();
if (image.hasAlphaChannel() && image.format() != QImage::Format_ARGB32)
image = image.convertToFormat(QImage::Format_ARGB32);
else
image = image.convertToFormat(QImage::Format_RGB888);
qDebug() << "|> decodeImage()" << image.format() << image.sizeInBytes() << image.size();

static terminal::Image::Id nextImageId = 0;

terminal::Image::Data pixels;
auto* p = &pixels[0];
pixels.resize(image.bytesPerLine() * image.height());
for (int i = 0; i < image.height(); ++i)
{
memcpy(p, image.constScanLine(i), image.bytesPerLine());
p += image.bytesPerLine();
}

terminal::ImageFormat format = terminal::ImageFormat::RGBA;
switch (image.format())
{
case QImage::Format_RGBA8888: format = terminal::ImageFormat::RGBA; break;
case QImage::Format_RGB888: format = terminal::ImageFormat::RGB; break;
default: return nullopt;
}
crispy::Size size { image.width(), image.height() };

auto img = terminal::Image(nextImageId++, format, std::move(pixels), size);
return { std::move(img) };
}

} // namespace contour::opengl
2 changes: 2 additions & 0 deletions src/contour/opengl/TerminalWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ class TerminalWidget: public QOpenGLWidget, public TerminalDisplay, private QOpe
void discardImage(terminal::Image const&) override;
// }}}

std::optional<terminal::Image> decodeImage(crispy::span<uint8_t> _imageData);

public Q_SLOTS:
void onFrameSwapped();
void onScrollBarValueChanged(int _value);
Expand Down
3 changes: 3 additions & 0 deletions src/terminal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ set(terminal_HEADERS
InputGenerator.h
Line.h
MatchModes.h
MessageParser.h
MockTerm.h
Parser.h
Process.h
Expand Down Expand Up @@ -71,6 +72,7 @@ set(terminal_SOURCES
InputGenerator.cpp
Line.cpp
MatchModes.cpp
MessageParser.cpp
MockTerm.cpp
Parser.cpp
Process${PLATFORM_SUFFIX}.cpp
Expand Down Expand Up @@ -130,6 +132,7 @@ if(LIBTERMINAL_TESTING)
Selector_test.cpp
Functions_test.cpp
Grid_test.cpp
MessageParser_test.cpp
Parser_test.cpp
Screen_test.cpp
Terminal_test.cpp
Expand Down
21 changes: 19 additions & 2 deletions src/terminal/Functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,24 @@ constexpr inline auto RCOLORHIGHLIGHTBG = detail::OSC(117, "RCOLORHIGHLIGHTBG",
constexpr inline auto NOTIFY = detail::OSC(777, "NOTIFY", "Send Notification.");
constexpr inline auto DUMPSTATE = detail::OSC(888, "DUMPSTATE", "Dumps internal state to debug stream.");

// DCS: Good Image Protocol
#if defined(GOOD_IMAGE_PROTOCOL)
// TODO: use OSC instead of DCS?
constexpr inline auto GIUPLOAD = detail::DCS(std::nullopt, 0, 0, std::nullopt, 'u', VTType::VT525, "GIUPLOAD", "Uploads an image.");
constexpr inline auto GIRENDER = detail::DCS(std::nullopt, 0, 0, std::nullopt, 'r', VTType::VT525, "GIRENDER", "Renders an image.");
constexpr inline auto GIDELETE = detail::DCS(std::nullopt, 0, 0, std::nullopt, 'd', VTType::VT525, "GIDELETE", "Deletes an image.");
constexpr inline auto GIONESHOT = detail::DCS(std::nullopt, 0, 0, std::nullopt, 's', VTType::VT525, "GIONESHOT", "Uploads and renders an unnamed image.");
#endif

constexpr inline auto CaptureBufferCode = 314;

// clang-format on

inline auto const& functions() noexcept
{
static auto const funcs = []() constexpr
{ // {{{
{
// clang-format off
auto f = std::array {
// C0
EOT,
Expand Down Expand Up @@ -491,6 +501,12 @@ inline auto const& functions() noexcept
XTVERSION,

// DCS
#if defined(GOOD_IMAGE_PROTOCOL)
GIUPLOAD,
GIRENDER,
GIDELETE,
GIONESHOT,
#endif
STP,
DECRQSS,
DECSIXEL,
Expand Down Expand Up @@ -524,12 +540,13 @@ inline auto const& functions() noexcept
NOTIFY,
DUMPSTATE,
};
// clang-format off
crispy::sort(
f,
[](FunctionDefinition const& a, FunctionDefinition const& b) constexpr { return compare(a, b); });
return f;
}
(); // }}}
();

#if 0
for (auto [a, b] : crispy::indexed(funcs))
Expand Down
Loading

0 comments on commit e0aff93

Please sign in to comment.