#pragma once #include #include #include #include #include #include namespace effector { // Tightly packed 8-bit RGB. Row stride is always width * 3, which lets the // effects work with plain memcpy and hands GDK a buffer it can upload as is. class Image { public: Image() = default; Image(int width, int height); int width() const noexcept { return width_; } int height() const noexcept { return height_; } std::size_t stride() const noexcept { return static_cast(width_) * 3; } bool empty() const noexcept { return width_ <= 0 || height_ <= 0; } std::uint8_t* row(int y) noexcept { return pixels_.data() + static_cast(y) * stride(); } const std::uint8_t* row(int y) const noexcept { return pixels_.data() + static_cast(y) * stride(); } std::uint8_t* data() noexcept { return pixels_.data(); } const std::uint8_t* data() const noexcept { return pixels_.data(); } std::size_t byteSize() const noexcept { return pixels_.size(); } const std::string& path() const noexcept { return path_; } void setPath(std::string path) { path_ = std::move(path); } // Decodes via GdkPixbuf (PNG, JPEG, WebP, TIFF, BMP, ...), applying the // embedded EXIF orientation. Throws std::runtime_error on failure. // Transparency is flattened onto white, as a viewer would show it, unless // `alphaOut` is given: it then receives one byte per pixel (255 throughout // for a file without an alpha channel) and the colours are left untouched, // which is what an effect compositing the image needs. static Image load(const std::string& path, std::vector* alphaOut = nullptr); // A copy shrunk so that its longer side is at most `maxSide`, each pixel // the average of the ones it covers; an image already that small comes // back as it is. For previews: nothing rendered goes through it. Image scaledToFit(int maxSide) const; // Encodes by file extension; png is used when the extension is unknown. void save(const std::string& path) const; private: int width_ = 0; int height_ = 0; std::vector pixels_; std::string path_; }; struct LoadFailure { std::string path; std::string message; }; // Lets a caller follow a long batch decode and abort it. Both members are // touched from the decoding threads, so `onSized` and `onFile` must be // thread-safe and must not block: the GUI only bumps a counter from them and // wakes the main thread. `onSized` follows the first pass, which reads every // header to size the batch; `onFile` follows the decodes after it. struct LoadProgress { std::function onSized; std::function onFile; std::atomic* cancel = nullptr; }; // The pixel count of an image file from its header alone, without decoding // it; 0 when the header cannot be read. std::size_t imageFilePixels(const std::string& path); // Loads every path, collecting rather than throwing on individual failures. // When `progress->cancel` is set the remaining paths are skipped, so the result // is short and should be discarded rather than used. // // Before decoding anything the whole batch is sized from the file headers, // and a batch that would not fit in the free memory throws OutOfMemoryError // (core/memory.hpp) with nothing loaded; the decodes that run at once are // also held to what is left over. std::vector loadImages(const std::vector& paths, std::vector& failures, const LoadProgress* progress = nullptr); } // namespace effector