Lays a single picture from disk over its input: fitted, filled, stretched, left at its own size or tiled, centred or pinned to the top left corner, nudged by an offset and mixed in by its opacity. The file it needs is a new kind of param, Path, so the interface shows a chooser and the CLI takes a file name, both without knowing an overlay exists. Projects write it relative to their own folder, as they do a source, and Image::load can now hand back the alpha channel instead of flattening it onto white, so a cut-out PNG keeps its cut-out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
2.8 KiB
C++
74 lines
2.8 KiB
C++
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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<std::size_t>(width_) * 3; }
|
|
bool empty() const noexcept { return width_ <= 0 || height_ <= 0; }
|
|
|
|
std::uint8_t* row(int y) noexcept { return pixels_.data() + static_cast<std::size_t>(y) * stride(); }
|
|
const std::uint8_t* row(int y) const noexcept {
|
|
return pixels_.data() + static_cast<std::size_t>(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<std::uint8_t>* alphaOut = nullptr);
|
|
|
|
// 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<std::uint8_t> 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 `onFile` must be thread-safe and must
|
|
// not block: the GUI only bumps a counter from it and wakes the main thread.
|
|
struct LoadProgress {
|
|
std::function<void(std::size_t done, std::size_t total, const std::string& path)> onFile;
|
|
std::atomic<bool>* cancel = nullptr;
|
|
};
|
|
|
|
// 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.
|
|
std::vector<Image> loadImages(const std::vector<std::string>& paths,
|
|
std::vector<LoadFailure>& failures,
|
|
const LoadProgress* progress = nullptr);
|
|
|
|
} // namespace effector
|