Files
effector/src/core/image.hpp
T
SiandClaude Opus 5 e2252ecd30 Add an image overlay effect
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>
2026-09-18 19:31:09 +02:00

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