#pragma once #include #include #include #include #include #include "alvin/error.hpp" #include "alvin/types.hpp" namespace alvin { // Byte order in memory. Every capture backend normalises to BGRA8, which is // what OpenCV (CV_8UC4) and Win32 DIBs use natively. enum class PixelFormat : std::uint8_t { BGRA8, }; struct Color { std::uint8_t r = 0, g = 0, b = 0, a = 255; friend bool operator==(const Color&, const Color&) = default; }; class Frame { public: Frame() = default; Frame(int width, int height); // zero-filled BGRA8 int width() const { return width_; } int height() const { return height_; } Size size() const { return {width_, height_}; } std::size_t stride() const { return static_cast(width_) * 4; } PixelFormat format() const { return PixelFormat::BGRA8; } bool empty() const { return data_.empty(); } std::uint8_t* data() { return data_.data(); } const std::uint8_t* data() const { return data_.data(); } std::span bytes() const { return data_; } std::uint8_t* row(int y) { return data_.data() + y * stride(); } const std::uint8_t* row(int y) const { return data_.data() + y * stride(); } Color pixel(int x, int y) const; // Where this frame sits on the desktop, in logical pixels. Frames from a // HiDPI monitor have more pixels than their logical size; use // toDesktop()/fromDesktop() to convert instead of assuming 1:1. Rect desktopRect() const { return desktopRect_; } void setDesktopRect(Rect r) { desktopRect_ = r; } Point toDesktop(Point framePixel) const; Point fromDesktop(Point desktopPoint) const; std::int64_t timestampNs() const { return timestampNs_; } void setTimestampNs(std::int64_t t) { timestampNs_ = t; } Frame crop(Rect r) const; // r in frame pixels; clipped to bounds // Binary PPM (P6), dependency-free. Alpha is dropped. Status savePpm(const std::filesystem::path& path) const; private: int width_ = 0; int height_ = 0; std::vector data_; Rect desktopRect_; std::int64_t timestampNs_ = 0; }; } // namespace alvin