1 /*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "image/loaders/image_loader_stb_image.h"
17 #if defined(USE_STB_IMAGE) && (USE_STB_IMAGE == 1)
18 #include <cstddef>
19 #include <cstdint>
20 #include <limits>
21
22 //
23 // Enabling only formats that are actually used.
24 // Others available:
25 // STBI_ONLY_BMP
26 // STBI_ONLY_PSD
27 // STBI_ONLY_TGA
28 // STBI_ONLY_GIF
29 // STBI_ONLY_HDR
30 // STBI_ONLY_PIC
31 // STBI_ONLY_PNM (.ppm and .pgm)
32 #define STBI_ONLY_JPEG
33 #define STBI_ONLY_PNG
34
35 // Without this a few gif functions are still included.
36 #define STBI_NO_GIF
37
38 // Currently we always load from memory so disabling support for loading directly from a file.
39 #define STBI_NO_STDIO
40
41 // This makes the stb_image implementation local to this compilation unit
42 // (So it wont interfere if someone else is using stb_image too).
43 #define STB_IMAGE_STATIC
44 #define STB_IMAGE_IMPLEMENTATION
45
46 // On x86, SSE2 will automatically be used when available based on a run-time
47 // test; if not, the generic C versions are used as a fall-back. On ARM targets,
48 // the typical path is to have separate builds for NEON and non-NEON devices
49 // Therefore, the NEON support is toggled by a build flag: define STBI_NEON to
50 // get NEON loops
51 //
52 // Assuming that NEON is supported by our target devices.
53 // (on NDK r21 and newer NEON is enabled by default anyway).
54 #if defined(__arm__) || defined(__aarch64__)
55 #define STBI_NEON
56 #endif
57
58 #include <stb/stb_image.h>
59
60 #include <base/containers/array_view.h>
61 #include <base/containers/string.h>
62 #include <base/containers/string_view.h>
63 #include <base/containers/type_traits.h>
64 #include <base/containers/unique_ptr.h>
65 #include <base/math/mathf.h>
66 #include <base/namespace.h>
67 #include <base/util/formats.h>
68 #include <core/image/intf_image_container.h>
69 #include <core/image/intf_image_loader_manager.h>
70 #include <core/io/intf_file.h>
71 #include <core/log.h>
72 #include <core/namespace.h>
73
74 #include "image/image_loader_manager.h"
75
76 CORE_BEGIN_NAMESPACE()
77 namespace {
78 using BASE_NS::array_view;
79 using BASE_NS::Format;
80 using BASE_NS::make_unique;
81 using BASE_NS::move;
82 using BASE_NS::string;
83 using BASE_NS::string_view;
84 using BASE_NS::unique_ptr;
85 using BASE_NS::vector;
86 using BASE_NS::Math::pow;
87 using BASE_NS::Math::round;
88
89 // NOTE: Reading the stb error code is NOT THREADSAFE.
90 // Enable this if you really need to know the error message.
91 constexpr const bool CORE_ENABLE_STB_NON_THREADSAFE_ERROR_MSG = false;
92
FreeStbImageBytes(void * imageBytes)93 void FreeStbImageBytes(void* imageBytes)
94 {
95 stbi_image_free(imageBytes);
96 }
97
98 uint8_t g_sRgbPremultiplyLookup[256u * 256u] = { 0 };
99
InitializeSRGBTable()100 void InitializeSRGBTable()
101 {
102 // Generate lookup table to premultiply sRGB encoded image in linear space and reencoding it to sRGB
103 // Formulas from https://en.wikipedia.org/wiki/SRGB
104 for (uint32_t a = 0; a < 256u; a++) {
105 const float alpha = static_cast<float>(a) / 255.f;
106 for (uint32_t sRGB = 0; sRGB < 256u; sRGB++) {
107 float color = static_cast<float>(sRGB) / 255.f;
108 if (color <= 0.04045f) {
109 color *= (1.f / 12.92f);
110 } else {
111 color = pow((color + 0.055f) * (1.f / 1.055f), 2.4f);
112 }
113 float premultiplied = color * alpha;
114 if (premultiplied <= 0.0031308f) {
115 premultiplied *= 12.92f;
116 } else {
117 premultiplied = 1.055f * pow(premultiplied, 1.f / 2.4f) - 0.055f;
118 }
119 g_sRgbPremultiplyLookup[a * 256u + sRGB] = static_cast<uint8_t>(round(premultiplied * 255.f));
120 }
121 }
122 }
123
PremultiplyAlpha(uint8_t * imageBytes,uint32_t width,uint32_t height,uint32_t channelCount,uint32_t bytesPerChannel,bool linear)124 bool PremultiplyAlpha(
125 uint8_t* imageBytes, uint32_t width, uint32_t height, uint32_t channelCount, uint32_t bytesPerChannel, bool linear)
126 {
127 // Only need to process images with color and alpha data. I.e. RGBA or grayscale + alpha.
128 if (channelCount != 4u && channelCount != 2u) {
129 return true;
130 }
131
132 const uint32_t pixelCount = width * height;
133
134 if (bytesPerChannel == 1) {
135 if (linear) {
136 uint8_t* img = imageBytes;
137 for (uint32_t i = 0; i < pixelCount; i++) {
138 // We know the alpha value is always last.
139 uint32_t alpha = img[channelCount - 1];
140 for (uint32_t j = 0; j < channelCount - 1; j++) {
141 *img = static_cast<uint8_t>(*img * alpha / 0xff);
142 img++;
143 }
144 img++; // Skip over the alpha value.
145 }
146 } else {
147 if (g_sRgbPremultiplyLookup[256u * 256u - 1] == 0) {
148 InitializeSRGBTable();
149 }
150 uint8_t* img = imageBytes;
151 for (uint32_t i = 0; i < pixelCount; i++) {
152 uint8_t* p = &g_sRgbPremultiplyLookup[img[channelCount - 1] * 256u];
153 for (uint32_t j = 0; j < channelCount - 1; j++) {
154 *img = p[*img];
155 img++;
156 }
157 img++;
158 }
159 }
160 } else if (bytesPerChannel == 2u) {
161 // Same for 16 bits per channel images.
162 uint16_t* img = reinterpret_cast<uint16_t*>(imageBytes);
163 for (uint32_t i = 0; i < pixelCount; i++) {
164 uint32_t alpha = img[channelCount - 1];
165 for (uint32_t j = 0; j < channelCount - 1; j++) {
166 *img = static_cast<uint16_t>(*img * alpha / 0xffff);
167 img++;
168 }
169 img++;
170 }
171 } else {
172 CORE_LOG_E("Format not supported.");
173 return false;
174 }
175 return true;
176 }
177
178 using StbImagePtr = unique_ptr<void, decltype(&FreeStbImageBytes)>;
179 } // namespace
180
181 class StbImage final : public IImageContainer {
182 public:
StbImage()183 StbImage() : IImageContainer(), imageDesc_(), imageBuffer_() {}
184
185 using Ptr = BASE_NS::unique_ptr<StbImage, Deleter>;
186
GetImageDesc() const187 const ImageDesc& GetImageDesc() const override
188 {
189 return imageDesc_;
190 }
191
GetData() const192 array_view<const uint8_t> GetData() const override
193 {
194 return array_view<const uint8_t>(static_cast<const uint8_t*>(imageBytes_.get()), imageBytesLength_);
195 }
196
GetBufferImageCopies() const197 array_view<const SubImageDesc> GetBufferImageCopies() const override
198 {
199 return array_view<const SubImageDesc>(&imageBuffer_, 1);
200 }
201
ResolveFormat(uint32_t loadFlags,uint32_t componentCount,bool is16bpc)202 static constexpr Format ResolveFormat(uint32_t loadFlags, uint32_t componentCount, bool is16bpc)
203 {
204 Format format {};
205 const bool forceLinear = (loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_LINEAR_RGB_BIT) != 0;
206
207 switch (componentCount) {
208 case 1u:
209 format = is16bpc ? Format::BASE_FORMAT_R16_UNORM
210 : (forceLinear ? Format::BASE_FORMAT_R8_UNORM : Format::BASE_FORMAT_R8_SRGB);
211 break;
212 case 2u:
213 format = is16bpc ? Format::BASE_FORMAT_R16G16_UNORM
214 : (forceLinear ? Format::BASE_FORMAT_R8G8_UNORM : Format::BASE_FORMAT_R8G8_SRGB);
215 break;
216 case 3u:
217 format = is16bpc ? Format::BASE_FORMAT_R16G16B16_UNORM
218 : (forceLinear ? Format::BASE_FORMAT_R8G8B8_UNORM : Format::BASE_FORMAT_R8G8B8_SRGB);
219 break;
220 case 4u:
221 format = is16bpc
222 ? Format::BASE_FORMAT_R16G16B16A16_UNORM
223 : (forceLinear ? Format::BASE_FORMAT_R8G8B8A8_UNORM : Format::BASE_FORMAT_R8G8B8A8_SRGB);
224 break;
225 default:
226 format = Format::BASE_FORMAT_UNDEFINED;
227 break;
228 }
229
230 return format;
231 }
232
ResolveImageDesc(Format format,uint32_t imageWidth,uint32_t imageHeight,uint32_t bitsPerPixel,bool generateMips,bool isPremultiplied)233 constexpr static ImageDesc ResolveImageDesc(Format format, uint32_t imageWidth, uint32_t imageHeight,
234 uint32_t bitsPerPixel, bool generateMips, bool isPremultiplied)
235 {
236 uint32_t mipCount = 1;
237
238 // 1D images not supported with stb loader
239 constexpr ImageType imageType = ImageType::TYPE_2D;
240 constexpr ImageViewType imageViewType = ImageViewType::VIEW_TYPE_2D;
241
242 uint32_t imageFlags = isPremultiplied ? ImageFlags::FLAGS_PREMULTIPLIED_ALPHA_BIT : 0;
243 if (generateMips) {
244 imageFlags |= ImageFlags::FLAGS_REQUESTING_MIPMAPS_BIT;
245 uint32_t mipsize = (imageWidth > imageHeight) ? imageWidth : imageHeight;
246 mipCount = 0;
247 while (mipsize > 0) {
248 mipCount++;
249 mipsize >>= 1;
250 }
251 }
252
253 return ImageDesc {
254 imageFlags, // imageFlags
255 1, // blockPixelWidth
256 1, // blockPixelHeight
257 1, // blockPixelDepth
258 bitsPerPixel, // bitsPerBlock
259
260 imageType, // imageType
261 imageViewType, // imageViewType
262 format, // format
263
264 static_cast<uint32_t>(imageWidth), // width
265 static_cast<uint32_t>(imageHeight), // height
266 1, // depth
267
268 mipCount, // mipCount
269 1, // layerCount
270 };
271 }
272
CreateImage(StbImagePtr imageBytes,uint32_t imageWidth,uint32_t imageHeight,uint32_t componentCount,uint32_t loadFlags,bool is16bpc)273 static ImageLoaderManager::LoadResult CreateImage(StbImagePtr imageBytes, uint32_t imageWidth, uint32_t imageHeight,
274 uint32_t componentCount, uint32_t loadFlags, bool is16bpc)
275 {
276 auto image = StbImage::Ptr(new StbImage);
277 if (!image) {
278 return ImageLoaderManager::ResultFailure("Loading image failed.");
279 }
280
281 // Premultiply alpha if requested.
282 bool isPremultiplied = false;
283 if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) == 0) {
284 if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_PREMULTIPLY_ALPHA) != 0) {
285 const uint32_t bytesPerChannel = is16bpc ? 2u : 1u;
286 const bool forceLinear = (loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_LINEAR_RGB_BIT) != 0;
287 isPremultiplied = PremultiplyAlpha(static_cast<uint8_t*>(imageBytes.get()), imageWidth, imageHeight,
288 componentCount, bytesPerChannel, forceLinear);
289 }
290 }
291
292 const Format format = ResolveFormat(loadFlags, componentCount, is16bpc);
293
294 const bool generateMips = (loadFlags & IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS) != 0;
295 const uint32_t bytesPerComponent = is16bpc ? 2u : 1u;
296 const uint32_t bitsPerPixel = bytesPerComponent * componentCount * 8u;
297
298 image->imageDesc_ =
299 ResolveImageDesc(format, imageWidth, imageHeight, bitsPerPixel, generateMips, isPremultiplied);
300 image->imageBytes_ = BASE_NS::move(imageBytes);
301 image->imageBytesLength_ = imageWidth * imageHeight * componentCount * bytesPerComponent;
302
303 image->imageBuffer_ = SubImageDesc {
304 0, // uint32_t bufferOffset
305 imageWidth, // uint32_t bufferRowLength
306 imageHeight, // uint32_t bufferImageHeight
307
308 0, // uint32_t mipLevel
309 1, // uint32_t layerCount
310
311 static_cast<uint32_t>(imageWidth),
312 static_cast<uint32_t>(imageHeight),
313 1,
314 };
315
316 return ImageLoaderManager::ResultSuccess(CORE_NS::move(image));
317 }
318
319 struct Info {
320 int width;
321 int height;
322 int componentCount;
323 bool is16bpc;
324 };
325
LoadFromMemory(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags,Info & info)326 static StbImagePtr LoadFromMemory(array_view<const uint8_t> imageFileBytes, uint32_t loadFlags, Info& info)
327 {
328 StbImagePtr imageBytes = nullptr;
329 // Convert 3 channels to 4 because 3 channel textures are not always supported.
330 // Also convert 2 channel (grayscale + alpha) to 4 because r + a in not supported.
331 int requestedComponentCount = (info.componentCount == 3 || info.componentCount == 2) ? 4 : 0;
332
333 // Force grayscale if requested.
334 if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_GRAYSCALE_BIT) != 0) {
335 requestedComponentCount = 1;
336 }
337
338 // Load the image byte data.
339 if (info.is16bpc) {
340 auto image = stbi_load_16_from_memory(imageFileBytes.data(), static_cast<int>(imageFileBytes.size()),
341 &info.width, &info.height, &info.componentCount, requestedComponentCount);
342 imageBytes = { image, FreeStbImageBytes };
343 } else {
344 auto image = stbi_load_from_memory(imageFileBytes.data(), static_cast<int>(imageFileBytes.size()),
345 &info.width, &info.height, &info.componentCount, requestedComponentCount);
346 imageBytes = { image, FreeStbImageBytes };
347 }
348
349 if (requestedComponentCount != 0) {
350 info.componentCount = requestedComponentCount;
351 }
352 return imageBytes;
353 }
354
355 // Actual stb_image loading implementation.
Load(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags)356 static ImageLoaderManager::LoadResult Load(array_view<const uint8_t> imageFileBytes, uint32_t loadFlags)
357 {
358 if (imageFileBytes.empty()) {
359 return ImageLoaderManager::ResultFailure("Input data must not be null.");
360 }
361
362 // Load the image info without decoding the image data
363 // (Just to check what the image format is so we can convert if necessary).
364 Info info {};
365
366 const int result = stbi_info_from_memory(imageFileBytes.data(), static_cast<int>(imageFileBytes.size()),
367 &info.width, &info.height, &info.componentCount);
368
369 info.is16bpc =
370 (stbi_is_16_bit_from_memory(imageFileBytes.data(), static_cast<int>(imageFileBytes.size())) != 0);
371
372 // Not supporting hdr images via stb_image.
373 #if !defined(NDEBUG)
374 if (stbi_is_hdr_from_memory(imageFileBytes.data(), static_cast<int>(imageFileBytes.size()))) {
375 CORE_LOG_D("HDR format detected.");
376 }
377 #endif
378 StbImagePtr imageBytes = nullptr;
379 if (result) {
380 if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) == 0) {
381 imageBytes = LoadFromMemory(imageFileBytes, loadFlags, info);
382 // Flip vertically if requested.
383 if (imageBytes && (loadFlags & IImageLoaderManager::IMAGE_LOADER_FLIP_VERTICALLY_BIT) != 0) {
384 stbi__vertical_flip(imageBytes.get(), info.width, info.height, info.componentCount);
385 }
386 } else {
387 imageBytes = { nullptr, FreeStbImageBytes };
388 }
389 }
390
391 if (!result || (((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) == 0) && !imageBytes)) {
392 if (CORE_ENABLE_STB_NON_THREADSAFE_ERROR_MSG) {
393 const string errorString = string_view("Loading image failed: ") + string_view(stbi_failure_reason());
394 return ImageLoaderManager::ResultFailure(errorString);
395 }
396 return ImageLoaderManager::ResultFailure("Loading image failed");
397 }
398
399 // Success. Populate the image info and image data object.
400 return CreateImage(CORE_NS::move(imageBytes), static_cast<uint32_t>(info.width),
401 static_cast<uint32_t>(info.height), static_cast<uint32_t>(info.componentCount), loadFlags, info.is16bpc);
402 }
403
404 protected:
Destroy()405 void Destroy() override
406 {
407 delete this;
408 }
409
410 private:
411 ImageDesc imageDesc_;
412 SubImageDesc imageBuffer_;
413
414 StbImagePtr imageBytes_;
415 size_t imageBytesLength_ = 0;
416 };
417
418 class ImageLoaderStbImage final : public ImageLoaderManager::IImageLoader {
419 public:
420 // Inherited via ImageManager::IImageLoader
Load(IFile & file,uint32_t loadFlags) const421 ImageLoaderManager::LoadResult Load(IFile& file, uint32_t loadFlags) const override
422 {
423 const uint64_t byteLength = file.GetLength();
424 // stb_image uses int for file sizes. Don't even try to read a file if the size does not fit to int.
425 if (byteLength > static_cast<uint64_t>(std::numeric_limits<int>::max())) {
426 return ImageLoaderManager::ResultFailure("File too big to read.");
427 }
428
429 // Read the file to a buffer.
430 unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(static_cast<size_t>(byteLength));
431 const uint64_t read = file.Read(buffer.get(), byteLength);
432 if (read != byteLength) {
433 return ImageLoaderManager::ResultFailure("Reading file failed.");
434 }
435
436 return StbImage::Load(array_view<const uint8_t>(buffer.get(), static_cast<size_t>(byteLength)), loadFlags);
437 }
438
Load(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags) const439 ImageLoaderManager::LoadResult Load(array_view<const uint8_t> imageFileBytes, uint32_t loadFlags) const override
440 {
441 // stb_image uses int for file sizes. Don't even try to read a file if the size does not fit to int.
442 // Not writing a test for this :)
443 if (imageFileBytes.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
444 return ImageLoaderManager::ResultFailure("Data too big to read.");
445 }
446
447 return StbImage::Load(imageFileBytes, loadFlags);
448 }
449
CanLoad(array_view<const uint8_t> imageFileBytes) const450 bool CanLoad(array_view<const uint8_t> imageFileBytes) const override
451 {
452 // stb_image uses int for file sizes. Don't even try to read a file if the size does not fit to int.
453 // Not writing a test for this :)
454 if (imageFileBytes.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
455 return false;
456 }
457
458 // Check for PNG
459 if ((imageFileBytes.size() >= 8) && imageFileBytes[0] == 137 && imageFileBytes[1] == 80 &&
460 imageFileBytes[2] == 78 && imageFileBytes[3] == 71 && imageFileBytes[4] == 13 && imageFileBytes[5] == 10 &&
461 imageFileBytes[6] == 26 && imageFileBytes[7] == 10) { // 6:index 26: pixle data 7:index 10: pixle
462 return true;
463 }
464
465 // Check for JPEG / JFIF / Exif / ICC_PROFILE tag
466 if ((imageFileBytes.size() >= 10) && imageFileBytes[0] == 0xff && imageFileBytes[1] == 0xd8 &&
467 imageFileBytes[2] == 0xff && // 2:index
468 ((imageFileBytes[3] == 0xe0 && imageFileBytes[6] == 'J' && imageFileBytes[7] == 'F' &&
469 imageFileBytes[8] == 'I' && imageFileBytes[9] == 'F') || // JFIF
470 (imageFileBytes[3] == 0xe1 && imageFileBytes[6] == 'E' && imageFileBytes[7] == 'x' &&
471 imageFileBytes[8] == 'i' && imageFileBytes[9] == 'f') || // Exif
472 (imageFileBytes[3] == 0xe2 && imageFileBytes[6] == 'I' && imageFileBytes[7] == 'C' &&
473 imageFileBytes[8] == 'C' && imageFileBytes[9] == '_'))) { // ICC_PROFILE
474 return true;
475 }
476
477 return false;
478 }
479
480 // No animation support
LoadAnimatedImage(IFile &,uint32_t)481 ImageLoaderManager::LoadAnimatedResult LoadAnimatedImage(IFile& /* file */, uint32_t /* loadFlags */) override
482 {
483 return ImageLoaderManager::ResultFailureAnimated("Animation not supported.");
484 }
485
LoadAnimatedImage(array_view<const uint8_t>,uint32_t)486 ImageLoaderManager::LoadAnimatedResult LoadAnimatedImage(
487 array_view<const uint8_t> /* imageFileBytes */, uint32_t /* loadFlags */) override
488 {
489 return ImageLoaderManager::ResultFailureAnimated("Animation not supported.");
490 }
491
GetSupportedTypes() const492 vector<IImageLoaderManager::ImageType> GetSupportedTypes() const override
493 {
494 return vector<IImageLoaderManager::ImageType>(std::begin(STB_IMAGE_TYPES), std::end(STB_IMAGE_TYPES));
495 }
496
497 protected:
Destroy()498 void Destroy() override
499 {
500 delete this;
501 }
502 };
503
CreateImageLoaderStbImage(PluginToken)504 IImageLoaderManager::IImageLoader::Ptr CreateImageLoaderStbImage(PluginToken)
505 {
506 return ImageLoaderManager::IImageLoader::Ptr { new ImageLoaderStbImage() };
507 }
508 CORE_END_NAMESPACE()
509 #endif // defined(USE_STB_IMAGE) && (USE_STB_IMAGE == 1)