1212
1313#include < cppitertools/itertools.hpp>
1414#include < fmt/format.h>
15+ #include < gsl/gsl>
1516#define STB_IMAGE_IMPLEMENTATION
1617#include < stb_image.h>
1718
19+ #include < span>
20+
1821#include " abcgException.hpp"
1922
2023/* *
21- * @brief Loads an image from disk using stb_image .
24+ * @brief Constructs a new abcg::Image .
2225 *
2326 * Loads the file at the given path and decodes it into raw pixel data.
2427 * Optionally forces a specific number of channels.
2528 *
2629 * @param path Filesystem path to the image file.
2730 *
28- * @param forceChannels Number of channels to force (e.g., 3 or 4). Pass 0
29- * to preserve the file's original channels (default).
31+ * @param layout Channel layout. Use abcg::Image::ChannelLayout::FromFile
32+ * to preserve the file's original layout (default).
3033 *
3134 * @throw abcg::RuntimeError If the file cannot be opened or decoded.
35+ */
36+ abcg::Image::Image (std::filesystem::path const &path, ChannelLayout layout) {
37+ loadImage (path, layout);
38+ }
39+
40+ /* *
41+ * @brief Returns the dimensions (width, height, channel count) of the image.
42+ *
43+ * @returns Image dimensions.
44+ */
45+ abcg::Image::Dimensions const &abcg::Image::dimensions () const noexcept {
46+ return m_dimensions;
47+ }
48+
49+ /* *
50+ * @brief Returns a mutable span of the image data.
51+ *
52+ * @returns Image data in row-major order and interleaved (size = width *
53+ * height * channels).
54+ */
55+ std::span<std::uint8_t > abcg::Image::data () noexcept { return m_data; }
56+
57+ /* *
58+ * @brief Returns a view (immutable span) of the image data.
3259 *
33- * @return A fully populated Image structure containing width, height, channel
34- * count, and pixel data .
60+ * @returns Image data in row-major order and interleaved (size = width *
61+ * height * channels) .
3562 */
36- abcg::Image abcg::loadImage (std::string_view path, int forceChannels) {
37- int w{}, h{}, c{};
38- auto *pixels{stbi_load (path.data (), &w, &h, &c, forceChannels)};
39- if (!pixels) {
63+ std::span<std::uint8_t const > abcg::Image::data () const noexcept {
64+ return m_data;
65+ }
66+
67+ void abcg::Image::loadImage (std::filesystem::path const &path,
68+ ChannelLayout layout) {
69+ using StbiDeleter = decltype (&stbi_image_free);
70+ using StbiPtr = std::unique_ptr<stbi_uc, StbiDeleter>;
71+
72+ auto const requestedChannels{static_cast <int >(layout)};
73+
74+ int width{};
75+ int height{};
76+ int channels{};
77+ StbiPtr rawPixels{
78+ stbi_load (path.c_str (), &width, &height, &channels, requestedChannels),
79+ stbi_image_free};
80+ if (!rawPixels) {
4081 throw abcg::RuntimeError (
41- fmt::format (" Failed to load texture file {}" , path));
82+ fmt::format (" Failed to load image file {}" , path. string () ));
4283 }
4384
44- Image img{};
45- img.width = w;
46- img.height = h;
47- img.channels = (forceChannels != 0 ? forceChannels : c);
48- img.data .assign (pixels, pixels + (w * h * img.channels ));
49- stbi_image_free (pixels);
85+ auto const actualChannels{
86+ (layout != ChannelLayout::FromFile ? requestedChannels : channels)};
87+ m_dimensions = Dimensions{.width = gsl::narrow<size_t >(width),
88+ .height = gsl::narrow<size_t >(height),
89+ .channels = gsl::narrow<size_t >(actualChannels)};
5090
51- return img;
91+ auto const sizeInBytes{m_dimensions.width * m_dimensions.height *
92+ m_dimensions.channels };
93+ std::span<stbi_uc const > const pixels{rawPixels.get (), sizeInBytes};
94+
95+ m_data.assign (pixels.begin (), pixels.end ());
5296}
5397
5498/* *
55- * @brief Flips an image horizontally.
56- *
57- * Flips the image horizontally by swapping pixels across the vertical axis.
58- * This mirrors each scanline left-to-right in-place.
99+ * @brief Flips the image vertically.
59100 *
60- * @param img Image data loaded with abcg::loadImage.
101+ * Flips the image vertically by swapping entire scanlines.
102+ * This mirrors the image top-to-bottom in-place.
61103 */
62- void abcg::flipHorizontally (Image &img) {
63- auto const rowStride{img.width * img.channels };
64- for (auto const y : iter::range (img.height )) {
65- auto *row{img.data .data () + y * rowStride};
66- for (auto const x : iter::range (img.width / 2 )) {
67- auto *left{row + x * img.channels };
68- auto *right{row + (img.width - 1 - x) * img.channels };
69- for (auto const c : iter::range (img.channels )) {
70- std::swap (left[c], right[c]);
71- }
72- }
104+ void abcg::Image::flipVertically () noexcept {
105+ auto const &[width, height, channels]{m_dimensions};
106+ auto const rowStride{width * channels};
107+ auto const halfHeight{height / 2 };
108+ auto const img{std::span{m_data}};
109+ for (auto const y : iter::range (halfHeight)) {
110+ auto const top{img.subspan (y * rowStride, rowStride)};
111+ auto const bottom{img.subspan ((height - 1 - y) * rowStride, rowStride)};
112+ std::ranges::swap_ranges (top, bottom);
73113 }
74114}
75115
76116/* *
77- * @brief Flips an image vertically .
117+ * @brief Flips the image horizontally .
78118 *
79- * Flips the image vertically by swapping entire scanlines .
80- * This mirrors the image top -to-bottom in-place.
119+ * Flips the image horizontally by swapping pixels across the vertical axis .
120+ * This mirrors each scanline left -to-right in-place.
81121 *
82122 * @param img Image data loaded with abcg::loadImage.
83123 */
84- void abcg::flipVertically (Image &img) {
85- auto const rowStride{img.width * img.channels };
86- for (auto const y : iter::range (img.height / 2 )) {
87- auto *top{img.data .data () + y * rowStride};
88- auto *bottom{img.data .data () + (img.height - 1 - y) * rowStride};
89- std::swap_ranges (top, top + rowStride, bottom);
124+ void abcg::Image::flipHorizontally () noexcept {
125+ auto const &[width, height, channels]{m_dimensions};
126+ auto const rowStride{width * channels};
127+ auto const halfWidth{width / 2 };
128+ auto const img{std::span{m_data}};
129+ for (auto const y : iter::range (height)) {
130+ auto const row{img.subspan (y * rowStride, rowStride)};
131+ for (auto const x : iter::range (halfWidth)) {
132+ auto const left{row.subspan (x * channels, channels)};
133+ auto const right{row.subspan ((width - 1 - x) * channels, channels)};
134+ std::ranges::swap_ranges (left, right);
135+ }
90136 }
91- }
137+ }
0 commit comments