Lagrange
RawInputImage.impl.h
1/*
2 * Copyright 2023 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <lagrange/image/RawInputImage.h>
15#include <lagrange/utils/warning.h>
16
17namespace lagrange {
18namespace image {
19
20
22template <typename Archive>
23inline void RawInputImage::serialize_impl(Archive& ar)
24{
26
27 // basic parameters
28 ar.object([&](auto& ar) {
29 ar("width") & m_width;
30 ar("height") & m_height;
31 ar("row_byte_stride") & m_row_byte_stride;
32 ar("pixel_precision") & m_pixel_precision;
33 ar("color_space") & m_color_space;
34 ar("tex_format") & m_tex_format;
35 ar("wrap_u") & m_wrap_u;
36 ar("wrap_v") & m_wrap_v;
37 ar("storage_format") & m_storage_format;
38
39 // aligned with sizeof(SerializationStorageOfRawInputImage::Scalar)
40 // because lerida does not support array of byte
41 // using ArrayScalar = SerializationStorageOfRawInputImage::Scalar;
42 using ArrayScalar = int32_t;
43 const bool padding_required = (0 != ((get_row_stride() * m_height) % sizeof(ArrayScalar)));
44 const size_t aligned_image_size =
45 get_row_stride() * m_height / sizeof(ArrayScalar) + (padding_required ? 1 : 0);
46
47 // create a buffer pixel storage
48 std::vector<unsigned char> buf(aligned_image_size * sizeof(ArrayScalar));
49 ArrayScalar* data = reinterpret_cast<ArrayScalar*>(buf.data());
50 if (ar.is_input()) {
51 set_pixel_data_buffer(std::move(buf));
52 } else {
53 std::copy_n(
54 static_cast<const unsigned char*>(get_pixel_data()) - get_pixel_data_offset(),
55 get_row_stride() * m_height,
56 buf.data());
57 }
58
59 // serialize pixels
60 ar("pixels").template array_of<ArrayScalar>(
61 aligned_image_size, // <-- only used during serialization
62 [&](size_t /*size*/) { // <-- only used during deserialization
63 // memory is already allocated above
64 },
65 [&](size_t i, auto& ar) { ar& data[i]; });
66 });
67
68 LA_IGNORE_SHADOW_WARNING_END
69}
70
71template <
72 typename TexcoordScalar,
73 typename Scalar,
74 uint32_t NumChannels,
75 typename InternalScalar,
76 uint32_t InternalNumChannels>
77typename RawInputImage::PixelTraits<Scalar, NumChannels>::Pixel RawInputImage::sample(
78 const TexcoordScalar u,
79 const TexcoordScalar v,
80 const texture_filtering filtering) const
81{
82 using ReturnTraits = PixelTraits<Scalar, NumChannels>;
83 using ReturnPixel = typename ReturnTraits::Pixel;
84 using InternalTraits = PixelTraits<InternalScalar, InternalNumChannels>;
85 using InternalPixel = typename InternalTraits::Pixel;
86
87 ReturnPixel rtn = ReturnTraits::zero();
88
89 // const for TexcoordScalar
90 const TexcoordScalar _0 = static_cast<TexcoordScalar>(0);
91 const TexcoordScalar _1 = static_cast<TexcoordScalar>(1);
92 const TexcoordScalar _05 = static_cast<TexcoordScalar>(0.5);
93
94 // static sanity check
95 static_assert(
96 std::is_same<TexcoordScalar, float>::value || std::is_same<TexcoordScalar, double>::value,
97 "RawInputImage::sample, texcoord must be float or double");
98 static_assert(
99 std::is_same<Scalar, unsigned char>::value || std::is_same<Scalar, Eigen::half>::value ||
100 std::is_same<Scalar, float>::value || std::is_same<Scalar, double>::value,
101 "RawInputImage::sample, unsupported Scalar");
102 static_assert(
103 0u < NumChannels && NumChannels <= (static_cast<uint32_t>(texture_format::max) >> 8u),
104 "RawInputImage::sample, unsupported NumChannels");
105 static_assert(
106 std::is_same<InternalScalar, unsigned char>::value ||
107 std::is_same<InternalScalar, Eigen::half>::value ||
108 std::is_same<InternalScalar, float>::value ||
109 std::is_same<InternalScalar, double>::value,
110 "RawInputImage::sample, unsupported InternalScalar");
111 static_assert(
112 0u < InternalNumChannels &&
113 InternalNumChannels <= (static_cast<uint32_t>(texture_format::max) >> 8u),
114 "RawInputImage::sample, unsupported InternalNumChannels");
115
116 // runtime sanity check
117 if (sizeof(InternalScalar) != get_size_precision()) {
118 throw std::runtime_error("RawInputImage::sample, INTERNAL_PRECISION is incorrect!");
119 }
120 if (InternalNumChannels != get_num_channels()) {
121 throw std::runtime_error("RawInputImage::sample, InternalNumChannels is incorrect!");
122 }
123
124 // something have not been implemented yet
125 if (std::is_same<Scalar, Eigen::half>::value ||
126 std::is_same<InternalScalar, Eigen::half>::value) {
127 throw std::runtime_error("RawInputImage::sample, half is not implemented yet!");
128 }
129 if (color_space::sRGB == m_color_space) {
130 throw std::runtime_error("RawInputImage::sample, sRGB is not implemented yet!");
131 }
132
133 // wrap coord
134 auto wrap = [_0, _1](const TexcoordScalar c, const wrap_mode m) -> TexcoordScalar {
135 if (_0 <= c && c <= _1) {
136 return c;
137 } else if (wrap_mode::repeat == m) {
138 return c - std::floor(c);
139 } else if (wrap_mode::clamp == m) {
140 return std::clamp(c, _0, _1);
141 } else if (wrap_mode::mirror == m) {
142 const auto f = std::floor(c);
143 if (static_cast<int>(f) & 1) {
144 return _1 - (c - f);
145 } else {
146 return c - f;
147 }
148 } else {
149 throw std::runtime_error("unknown wrap_mode!");
150 return c;
151 }
152 };
153 auto _u = wrap(u, m_wrap_u);
154 auto _v = wrap(v, m_wrap_v);
155 assert(_0 <= _u && _u <= _1);
156 assert(_0 <= _v && _v <= _1);
157
158 // v is bottom up in image space, convert it to memory space if storage is topdown
159 if (image_storage_format::first_pixel_row_at_top == m_storage_format) {
160 _v = _1 - _v;
161 }
162
163 // memory
164 const auto size_pixel = get_size_pixel();
165 const auto row_stride = get_row_stride();
166 const auto ptr = static_cast<const unsigned char*>(get_pixel_data()) - get_pixel_data_offset();
167 auto get_pixel = [&](size_t x, size_t y) -> InternalPixel {
168 assert(x < m_width && y < m_height);
169 return *reinterpret_cast<const InternalPixel*>(ptr + x * size_pixel + y * row_stride);
170 };
171
172 // sampling
173 const auto x_coord = _u * static_cast<TexcoordScalar>(m_width);
174 const auto y_coord = _v * static_cast<TexcoordScalar>(m_height);
175 const auto min_num_channels = std::min(NumChannels, InternalNumChannels);
176 if (filtering == texture_filtering::nearest) {
177 const auto x = std::clamp(
178 static_cast<size_t>(x_coord),
179 static_cast<size_t>(0),
180 static_cast<size_t>(m_width - 1));
181 const auto y = std::clamp(
182 static_cast<size_t>(y_coord),
183 static_cast<size_t>(0),
184 static_cast<size_t>(m_height - 1));
185 const auto pix = get_pixel(x, y);
186 for (uint32_t i = 0; i < min_num_channels; ++i) {
187 ReturnTraits::coeff(rtn, i) = static_cast<Scalar>(InternalTraits::coeff(pix, i));
188 }
189 } else if (filtering == texture_filtering::bilinear) {
190 auto sample_coord = [=](const TexcoordScalar coord, const size_t size, const wrap_mode wrap_)
191 -> std::tuple<size_t, size_t, TexcoordScalar> {
192 assert(_0 <= coord && coord <= static_cast<TexcoordScalar>(size));
193 size_t coord0, coord1;
194 TexcoordScalar t;
195 if (coord <= _05) {
196 coord0 = wrap_mode::repeat == wrap_ ? size - 1 : 0;
197 coord1 = 0;
198 t = _05 + coord;
199 } else if (coord + _05 >= static_cast<TexcoordScalar>(size)) {
200 coord0 = size - 1;
201 coord1 = wrap_mode::repeat == wrap_ ? 0 : size - 1;
202 t = coord - (static_cast<TexcoordScalar>(coord0) + _05);
203 } else {
204 assert(1 < size);
205 coord0 = std::min(size - 2, static_cast<size_t>(coord - _05));
206 coord1 = coord0 + 1;
207 t = coord - (static_cast<TexcoordScalar>(coord0) + _05);
208 }
209 return std::make_tuple(coord0, coord1, t);
210 };
211 const auto sample_x = sample_coord(x_coord, m_width, m_wrap_u);
212 const auto sample_y = sample_coord(y_coord, m_height, m_wrap_v);
213 InternalPixel pix[4] = {
214 get_pixel(std::get<0>(sample_x), std::get<0>(sample_y)),
215 get_pixel(std::get<1>(sample_x), std::get<0>(sample_y)),
216 get_pixel(std::get<0>(sample_x), std::get<1>(sample_y)),
217 get_pixel(std::get<1>(sample_x), std::get<1>(sample_y))};
218 TexcoordScalar weight[4] = {
219 (_1 - std::get<2>(sample_x)) * (_1 - std::get<2>(sample_y)),
220 std::get<2>(sample_x) * (_1 - std::get<2>(sample_y)),
221 (_1 - std::get<2>(sample_x)) * std::get<2>(sample_y),
222 std::get<2>(sample_x) * std::get<2>(sample_y)};
223 for (uint32_t i = 0; i < min_num_channels; ++i) {
224 TexcoordScalar sum = _0;
225 for (int j = 0; j < 4; ++j) {
226 sum += static_cast<TexcoordScalar>(InternalTraits::coeff(pix[j], i)) * weight[j];
227 }
228 ReturnTraits::coeff(rtn, i) = static_cast<Scalar>(sum);
229 }
230 } else {
231 throw std::runtime_error("RawInputImage::sample, unknown filtering type!");
232 }
233
234 return rtn;
235}
236
237template <typename TexcoordScalar, typename Scalar, uint32_t NumChannels>
238typename RawInputImage::PixelTraits<Scalar, NumChannels>::Pixel RawInputImage::sample(
239 const TexcoordScalar u,
240 const TexcoordScalar v,
241 const texture_filtering filtering) const
242{
243 using ReturnTraits = PixelTraits<Scalar, NumChannels>;
244 using ReturnPixel = typename ReturnTraits::Pixel;
245
246 ReturnPixel rtn = ReturnTraits::zero();
247
248 // the function is implemented assuming the max channels is 4
249 static_assert(
250 4u == (static_cast<uint32_t>(texture_format::max) >> 8u),
251 "the max channels are not 4 any more, need to update the following code");
252
253 const auto channels = get_num_channels();
254
255#define LA_RAWINPUTIMAGE_SAMPLE_IMPL(P, C, V) \
256 if (P == m_pixel_precision && C == channels) { \
257 return sample<TexcoordScalar, Scalar, NumChannels, V, C>(u, v, filtering); \
258 }
259
260 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::byte_p, 1u, unsigned char)
261 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::byte_p, 2u, unsigned char)
262 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::byte_p, 3u, unsigned char)
263 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::byte_p, 4u, unsigned char)
264
265 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::half_p, 1u, Eigen::half)
266 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::half_p, 2u, Eigen::half)
267 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::half_p, 3u, Eigen::half)
268 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::half_p, 4u, Eigen::half)
269
270 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::single_p, 1u, float)
271 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::single_p, 2u, float)
272 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::single_p, 3u, float)
273 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::single_p, 4u, float)
274
275 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::double_p, 1u, double)
276 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::double_p, 2u, double)
277 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::double_p, 3u, double)
278 LA_RAWINPUTIMAGE_SAMPLE_IMPL(precision_semantic::double_p, 4u, double)
279
280#undef LA_RAWINPUTIMAGE_SAMPLE_IMPL
281 throw std::runtime_error(
282 "RawInputImage::sample, cannot deduce InternalScalar or InternalNumChannels!");
283 return rtn;
284}
285
286template <typename TexcoordScalar, typename Scalar, uint32_t NumChannels>
287typename RawInputImage::PixelTraits<Scalar, NumChannels>::Pixel RawInputImage::sample_float(
288 const TexcoordScalar u,
289 const TexcoordScalar v,
290 const texture_filtering filtering) const
291{
292 static_assert(std::is_floating_point_v<Scalar>, "Pixel scalar type must be floating point");
293
294 auto pixel = sample<TexcoordScalar, Scalar, NumChannels>(u, v, filtering);
295 return (get_pixel_precision() == precision_semantic::byte_p ? pixel / Scalar(255) : pixel);
296}
297
298} // namespace image
299} // namespace lagrange
@ sRGB
pixel format is in sRGB space
wrap_mode
Definition: RawInputImage.h:70
@ clamp
clamp to the last pixel on the edge
@ mirror
tiles the texture, mirrored when the integer coord is odd
PixelTraits< Scalar, NumChannels >::Pixel sample(const TexcoordScalar u, const TexcoordScalar v, const texture_filtering filtering=texture_filtering::bilinear) const
Sample an image at a given location.
Definition: RawInputImage.impl.h:238
PixelTraits< Scalar, NumChannels >::Pixel sample_float(const TexcoordScalar u, const TexcoordScalar v, const texture_filtering filtering=texture_filtering::bilinear) const
Similar to sample, but remaps the output values from [0, 255] to [0, 1] if the pixel data is stored a...
Definition: RawInputImage.impl.h:287
texture_filtering
Definition: RawInputImage.h:76
@ nearest
nearest neighbor interpolation
void serialize_impl(Archive &ar)
Serialization.
Definition: RawInputImage.impl.h:23
void set_pixel_data_buffer(std::vector< unsigned char > pixel_data_buffer)
m_local_pixel_data takes over pixel_data_buffer m_pixel_data is set to nullptr
Definition: RawInputImage.cpp:35
#define LA_IGNORE_SHADOW_WARNING_BEGIN
Ignore shadow warnings.
Definition: warning.h:68
Main namespace for Lagrange.
Definition: AABBIGL.h:30
Definition: RawInputImage.h:222