1 /*
2 * Copyright (c) 2025 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_loader_png.h"
17
18 #include <functional>
19 #include <limits>
20 #include <memory>
21
22 #include <base/math/mathf.h>
23 #include <core/io/intf_file_manager.h>
24 #include <core/log.h>
25 #include <core/namespace.h>
26
27 #include "png.h"
28
29 using namespace BASE_NS;
30 using namespace CORE_NS;
31
32 namespace PNGPlugin {
33 namespace {
34 constexpr uint32_t MAX_IMAGE_EXTENT { 32767U };
35 constexpr int IMG_SIZE_LIMIT_2GB = std::numeric_limits<int>::max();
36 uint8_t g_sRgbPremultiplyLookup[256u * 256u] = { 0 };
37
InitializeSRGBTable()38 void InitializeSRGBTable()
39 {
40 // Generate lookup table to premultiply sRGB encoded image in linear space and reencoding it to sRGB
41 // Formulas from https://en.wikipedia.org/wiki/SRGB
42 for (uint32_t a = 0; a < 256u; a++) {
43 const float alpha = static_cast<float>(a) / 255.f;
44 for (uint32_t sRGB = 0; sRGB < 256u; sRGB++) {
45 float color = static_cast<float>(sRGB) / 255.f;
46 if (color <= 0.04045f) {
47 color *= (1.f / 12.92f);
48 } else {
49 color = pow((color + 0.055f) * (1.f / 1.055f), 2.4f);
50 }
51 float premultiplied = color * alpha;
52 if (premultiplied <= 0.0031308f) {
53 premultiplied *= 12.92f;
54 } else {
55 premultiplied = 1.055f * pow(premultiplied, 1.f / 2.4f) - 0.055f;
56 }
57 g_sRgbPremultiplyLookup[a * 256u + sRGB] = static_cast<uint8_t>(round(premultiplied * 255.f));
58 }
59 }
60 }
61
PremultiplyAlpha(uint8_t * imageBytes,uint32_t width,uint32_t height,uint32_t channelCount,uint32_t bytesPerChannel,bool linear)62 bool PremultiplyAlpha(
63 uint8_t* imageBytes, uint32_t width, uint32_t height, uint32_t channelCount, uint32_t bytesPerChannel, bool linear)
64 {
65 // Only need to process images with color and alpha data. I.e. RGBA or grayscale + alpha.
66 if (channelCount != 4u && channelCount != 2u) {
67 return true;
68 }
69
70 const uint32_t pixelCount = width * height;
71
72 if (bytesPerChannel == 1) {
73 if (linear) {
74 uint8_t* img = imageBytes;
75 for (uint32_t i = 0; i < pixelCount; i++) {
76 // We know the alpha value is always last.
77 uint32_t alpha = img[channelCount - 1];
78 for (uint32_t j = 0; j < channelCount - 1; j++) {
79 *img = static_cast<uint8_t>(*img * alpha / 0xff);
80 img++;
81 }
82 img++; // Skip over the alpha value.
83 }
84 } else {
85 if (g_sRgbPremultiplyLookup[256u * 256u - 1] == 0) {
86 InitializeSRGBTable();
87 }
88 uint8_t* img = imageBytes;
89 for (uint32_t i = 0; i < pixelCount; i++) {
90 uint8_t* p = &g_sRgbPremultiplyLookup[img[channelCount - 1] * 256u];
91 for (uint32_t j = 0; j < channelCount - 1; j++) {
92 *img = p[*img];
93 img++;
94 }
95 img++;
96 }
97 }
98 } else if (bytesPerChannel == 2u) {
99 // Same for 16 bits per channel images.
100 uint16_t* img = reinterpret_cast<uint16_t*>(imageBytes);
101 for (uint32_t i = 0; i < pixelCount; i++) {
102 uint32_t alpha = img[channelCount - 1];
103 for (uint32_t j = 0; j < channelCount - 1; j++) {
104 *img = static_cast<uint16_t>(*img * alpha / 0xffff);
105 img++;
106 }
107 img++;
108 }
109 } else {
110 CORE_LOG_E("Format not supported.");
111 return false;
112 }
113 return true;
114 }
115
ResultFailure(const string_view error)116 IImageLoaderManager::LoadResult ResultFailure(const string_view error)
117 {
118 IImageLoaderManager::LoadResult result {
119 false, // success
120 "", // error[128];
121 nullptr // image;
122 };
123
124 // Copy the error string
125 const auto count = Math::min(error.size(), sizeof(result.error) - 1);
126 error.copy(result.error, count);
127 result.error[count] = '\0';
128
129 return result;
130 }
131
ResultSuccess(IImageContainer::Ptr image)132 IImageLoaderManager::LoadResult ResultSuccess(IImageContainer::Ptr image)
133 {
134 return IImageLoaderManager::LoadResult {
135 true, // success
136 "", // error[128];
137 BASE_NS::move(image) // image;
138 };
139 }
140
ResultFailureAnimated(const string_view error)141 IImageLoaderManager::LoadAnimatedResult ResultFailureAnimated(const string_view error)
142 {
143 IImageLoaderManager::LoadAnimatedResult result {
144 false, // success
145 "", // error[128];
146 nullptr // image;
147 };
148
149 // Copy the error string
150 const auto count = Math::min(error.size(), sizeof(result.error) - 1);
151 error.copy(result.error, count);
152 result.error[count] = '\0';
153
154 return result;
155 }
156 } // namespace
157
158 class PNGImage final : public IImageContainer {
159 public:
PNGImage()160 PNGImage() : IImageContainer(), imageDesc_(), imageBuffer_() {}
161
162 ~PNGImage() override = default;
163
164 using Ptr = BASE_NS::unique_ptr<PNGImage, Deleter>;
165
GetImageDesc() const166 const ImageDesc& GetImageDesc() const override
167 {
168 return imageDesc_;
169 }
170
GetData() const171 array_view<const uint8_t> GetData() const override
172 {
173 return array_view<const uint8_t>(imageBytes_.get(), imageBytesLength_);
174 }
175
GetBufferImageCopies() const176 array_view<const SubImageDesc> GetBufferImageCopies() const override
177 {
178 return array_view<const SubImageDesc>(&imageBuffer_, 1);
179 }
180
ResolveFormat(uint32_t loadFlags,uint32_t componentCount,bool is16bpc)181 static constexpr Format ResolveFormat(uint32_t loadFlags, uint32_t componentCount, bool is16bpc) noexcept
182 {
183 Format format {};
184 const bool forceLinear = (loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_LINEAR_RGB_BIT) != 0;
185
186 switch (componentCount) {
187 case 1u:
188 format = is16bpc ? Format::BASE_FORMAT_R16_UNORM
189 : (forceLinear ? Format::BASE_FORMAT_R8_UNORM : Format::BASE_FORMAT_R8_SRGB);
190 break;
191 case 2u:
192 format = is16bpc ? Format::BASE_FORMAT_R16G16_UNORM
193 : (forceLinear ? Format::BASE_FORMAT_R8G8_UNORM : Format::BASE_FORMAT_R8G8_SRGB);
194 break;
195 case 3u:
196 format = is16bpc ? Format::BASE_FORMAT_R16G16B16_UNORM
197 : (forceLinear ? Format::BASE_FORMAT_R8G8B8_UNORM : Format::BASE_FORMAT_R8G8B8_SRGB);
198 break;
199 case 4u:
200 format = is16bpc
201 ? Format::BASE_FORMAT_R16G16B16A16_UNORM
202 : (forceLinear ? Format::BASE_FORMAT_R8G8B8A8_UNORM : Format::BASE_FORMAT_R8G8B8A8_SRGB);
203 break;
204 default:
205 format = Format::BASE_FORMAT_UNDEFINED;
206 break;
207 }
208
209 return format;
210 }
211
ResolveImageDesc(Format format,uint32_t imageWidth,uint32_t imageHeight,uint32_t bitsPerPixel,bool generateMips,bool isPremultiplied)212 constexpr static ImageDesc ResolveImageDesc(Format format, uint32_t imageWidth, uint32_t imageHeight,
213 uint32_t bitsPerPixel, bool generateMips, bool isPremultiplied) noexcept
214 {
215 uint32_t mipCount = 1;
216
217 // 1D images not supported with WebP loader
218 constexpr ImageType imageType = ImageType::TYPE_2D;
219 constexpr ImageViewType imageViewType = ImageViewType::VIEW_TYPE_2D;
220
221 uint32_t imageFlags = isPremultiplied ? ImageFlags::FLAGS_PREMULTIPLIED_ALPHA_BIT : 0;
222
223 if (generateMips) {
224 imageFlags |= ImageFlags::FLAGS_REQUESTING_MIPMAPS_BIT;
225
226 uint32_t mipsize = (imageWidth > imageHeight) ? imageWidth : imageHeight;
227 mipCount = 0;
228 while (mipsize > 0) {
229 mipCount++;
230 mipsize >>= 1;
231 }
232 }
233
234 return ImageDesc {
235 imageFlags, // imageFlags
236 1, // blockPixelWidth
237 1, // blockPixelHeight
238 1, // blockPixelDepth
239 bitsPerPixel, // bitsPerBlock
240 imageType, // imageType
241 imageViewType, // imageViewType
242 format, // format
243 imageWidth, // width
244 imageHeight, // height
245 1, // depth
246 mipCount, // mipCount
247 1, // layerCount
248 };
249 }
250
CreateImage(BASE_NS::unique_ptr<uint8_t[]> imageBytes,uint32_t imageWidth,uint32_t imageHeight,uint32_t componentCount,uint32_t loadFlags,bool is16bpc)251 static IImageLoaderManager::LoadResult CreateImage(BASE_NS::unique_ptr<uint8_t[]> imageBytes, uint32_t imageWidth,
252 uint32_t imageHeight, uint32_t componentCount, uint32_t loadFlags, bool is16bpc) noexcept
253 {
254 auto image = PNGImage::Ptr(new PNGImage);
255 if (!image) {
256 return ResultFailure("Loading image failed.");
257 }
258 const uint32_t bytesPerComponent = is16bpc ? 2u : 1u;
259
260 // Premultiply alpha if requested.
261 bool isPremultiplied = false;
262 if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) == 0) {
263 if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_PREMULTIPLY_ALPHA) != 0) {
264 const bool forceLinear = (loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_LINEAR_RGB_BIT) != 0;
265 isPremultiplied = PremultiplyAlpha(static_cast<uint8_t*>(imageBytes.get()), imageWidth, imageHeight,
266 componentCount, bytesPerComponent, forceLinear);
267 }
268 }
269
270 const Format format = ResolveFormat(loadFlags, componentCount, is16bpc);
271 const uint32_t bitsPerPixel = bytesPerComponent * componentCount * 8u;
272 const bool generateMips = (loadFlags & IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS) != 0;
273 image->imageDesc_ =
274 ResolveImageDesc(format, imageWidth, imageHeight, bitsPerPixel, generateMips, isPremultiplied);
275 image->imageBytes_ = BASE_NS::move(imageBytes);
276 image->imageBytesLength_ = imageWidth * imageHeight * componentCount * bytesPerComponent;
277
278 image->imageBuffer_ = SubImageDesc {
279 0, // uint32_t bufferOffset
280 imageWidth, // uint32_t bufferRowLength
281 imageHeight, // uint32_t bufferImageHeight
282
283 0, // uint32_t mipLevel
284 1, // uint32_t layerCount
285
286 static_cast<uint32_t>(imageWidth),
287 static_cast<uint32_t>(imageHeight),
288 1,
289 };
290
291 return ResultSuccess(BASE_NS::move(image));
292 }
293
UpdateColorType(png_structp pngPtr,png_infop infoPtr,uint32_t loadFlags)294 static void UpdateColorType(png_structp pngPtr, png_infop infoPtr, uint32_t loadFlags) noexcept
295 {
296 const auto colorType = png_get_color_type(pngPtr, infoPtr);
297 const auto bitDepth = png_get_bit_depth(pngPtr, infoPtr);
298 const bool forceGray = (loadFlags & IImageLoaderManager::IMAGE_LOADER_FORCE_GRAYSCALE_BIT) ==
299 IImageLoaderManager::IMAGE_LOADER_FORCE_GRAYSCALE_BIT;
300
301 // convert gray scale to RGB if needed
302 if ((colorType & (PNG_COLOR_MASK_PALETTE | PNG_COLOR_MASK_COLOR)) == 0) {
303 if (bitDepth < 8) { // 8: depth
304 png_set_expand_gray_1_2_4_to_8(pngPtr);
305 } else if (!forceGray) {
306 png_set_gray_to_rgb(pngPtr);
307 }
308 }
309 // convert palette to RGB
310 if (colorType & PNG_COLOR_MASK_PALETTE) {
311 png_set_palette_to_rgb(pngPtr);
312 }
313 // limit RGB to 8 bit
314 if (colorType & PNG_COLOR_MASK_COLOR) {
315 if (bitDepth > 8) { // 8: depth
316 png_set_scale_16(pngPtr);
317 }
318 }
319 if (forceGray) {
320 // conversion to gray scale requested
321 if (colorType & PNG_COLOR_MASK_COLOR) {
322 png_set_rgb_to_gray(pngPtr, 1, 0.299, 0.587); // 0.299、0.587: Specifications
323 }
324 if (colorType & PNG_COLOR_MASK_ALPHA) {
325 png_set_strip_alpha(pngPtr);
326 }
327 } else if ((colorType & PNG_COLOR_MASK_ALPHA) == 0) {
328 png_set_add_alpha(pngPtr, 0xff, PNG_FILLER_AFTER);
329 }
330 png_read_update_info(pngPtr, infoPtr);
331 }
332
333 // callback for fetching data from imageFileBytes to libpng
ReadData(png_structp pngPtr,png_bytep outBytes,png_size_t byteCountToRead)334 static void ReadData(png_structp pngPtr, png_bytep outBytes, png_size_t byteCountToRead) noexcept
335 {
336 png_voidp ioPtr = png_get_io_ptr(pngPtr);
337 if (!ioPtr) {
338 png_error(pngPtr, "Failed to read data.");
339 return;
340 }
341 array_view<const uint8_t>& imageFileBytes = *(array_view<const uint8_t>*)ioPtr;
342 // copy either the requested amount or what's left.
343 const auto read = BASE_NS::Math::min(imageFileBytes.size(), byteCountToRead);
344 BASE_NS::CloneData(outBytes, byteCountToRead, imageFileBytes.data(), read);
345 // move the start of imageFileBytes past the data which was copied.
346 imageFileBytes = array_view(imageFileBytes.data() + read, imageFileBytes.size() - read);
347 if (read != byteCountToRead) {
348 png_error(pngPtr, "Not enough data.");
349 return;
350 }
351 }
352
Load(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags)353 static IImageLoaderManager::LoadResult Load(array_view<const uint8_t> imageFileBytes, uint32_t loadFlags) noexcept
354 {
355 if (imageFileBytes.empty()) {
356 return ResultFailure("Input data must not be null.");
357 }
358
359 png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
360 if (!png) {
361 png_destroy_read_struct(&png, nullptr, nullptr);
362 return ResultFailure("Initialization failed.");
363 }
364 png_infop info = png_create_info_struct(png);
365 if (!info) {
366 png_destroy_read_struct(&png, nullptr, nullptr);
367 return ResultFailure("Initialization failed.");
368 }
369
370 // libpng uses longjmp for error handling. execution will jump to this if statement on error. it's not
371 // guaranteed that stack is cleaned up with longjmp, so have the big buffer pointer and pointers to start of
372 // each row available here, and we can call reset() to release memory if something fails.
373 BASE_NS::unique_ptr<uint8_t[]> image;
374 BASE_NS::unique_ptr<png_byte* []> rows;
375 if (setjmp(png_jmpbuf(png))) {
376 rows.reset();
377 image.reset();
378 png_destroy_read_struct(&png, &info, nullptr);
379 return ResultFailure("Decoding failed.");
380 }
381
382 // libpng will now call ReadData when it needs data.
383 png_set_read_fn(png, &imageFileBytes, ReadData);
384
385 // first ask for the image information.
386 png_read_info(png, info);
387 auto width = png_get_image_width(png, info);
388 auto height = png_get_image_height(png, info);
389 auto channels = png_get_channels(png, info);
390 auto is16bpc = png_get_bit_depth(png, info) == 16;
391
392 if ((loadFlags & IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) !=
393 IImageLoaderManager::IMAGE_LOADER_METADATA_ONLY) {
394 // ask libpng to do possible conversions according to loadFlags.
395 UpdateColorType(png, info, loadFlags);
396
397 // update the image information in case some conversions will be done.
398 width = png_get_image_width(png, info);
399 height = png_get_image_height(png, info);
400 channels = png_get_channels(png, info);
401 is16bpc = png_get_bit_depth(png, info) == 16; // 16: index
402 if (channels <= 0 || channels > 4) { // 0: invalid channel num, 4: RGBA
403 png_destroy_read_struct(&png, &info, nullptr);
404 return ResultFailure("Invalid number of color channels.");
405 }
406
407 const size_t imageSize = width * height * channels;
408 if ((width > MAX_IMAGE_EXTENT) || (height > MAX_IMAGE_EXTENT) || (imageSize > IMG_SIZE_LIMIT_2GB)) {
409 png_destroy_read_struct(&png, &info, nullptr);
410 return ResultFailure("Image too large.");
411 }
412 // allocate space for the whole image and and array of row pointers. libpng writes data to each row pointer.
413 // alternative would be to use a different api which writes only one row and feed it the correct address
414 // every time.
415 image = BASE_NS::make_unique<uint8_t[]>(imageSize);
416 rows = BASE_NS::make_unique<png_byte* []>(height);
417 // fill rows depending on should there be a vertical flip or not.
418 auto row = rows.get();
419 if (loadFlags & IImageLoaderManager::IMAGE_LOADER_FLIP_VERTICALLY_BIT) {
420 for (auto i = 0U; i < height; ++i) {
421 *row++ = image.get() + ((height - 1) - i) * (width * channels);
422 }
423 } else {
424 for (auto i = 0U; i < height; ++i) {
425 *row++ = image.get() + i * (width * channels);
426 }
427 }
428
429 // ask libpng to devoce the whole image.
430 png_read_image(png, rows.get());
431 png_read_end(png, info);
432 }
433 png_destroy_read_struct(&png, &info, nullptr);
434
435 // Success. Populate the image info and image data object.
436 return CreateImage(BASE_NS::move(image), width, height, channels, loadFlags, is16bpc);
437 }
438
439 protected:
Destroy()440 void Destroy() override
441 {
442 delete this;
443 }
444
445 private:
446 ImageDesc imageDesc_;
447 SubImageDesc imageBuffer_;
448
449 BASE_NS::unique_ptr<uint8_t[]> imageBytes_;
450 size_t imageBytesLength_ = 0;
451 };
452
453 class ImageLoaderPng final : public IImageLoaderManager::IImageLoader {
454 public:
455 // Inherited via ImageManager::IImageLoader
Load(IFile & file,uint32_t loadFlags) const456 IImageLoaderManager::LoadResult Load(IFile& file, uint32_t loadFlags) const override
457 {
458 const uint64_t byteLength = file.GetLength();
459 if (byteLength > static_cast<uint64_t>(std::numeric_limits<int>::max())) {
460 return ResultFailure("File too big.");
461 }
462
463 // Read the file to a buffer.
464 unique_ptr<uint8_t[]> buffer = make_unique<uint8_t[]>(static_cast<size_t>(byteLength));
465 const uint64_t read = file.Read(buffer.get(), byteLength);
466 if (read != byteLength) {
467 return ResultFailure("Reading file failed.");
468 }
469
470 return PNGImage::Load(array_view<const uint8_t>(buffer.get(), static_cast<size_t>(byteLength)), loadFlags);
471 }
472
Load(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags) const473 IImageLoaderManager::LoadResult Load(array_view<const uint8_t> imageFileBytes, uint32_t loadFlags) const override
474 {
475 if (imageFileBytes.size() > static_cast<size_t>(std::numeric_limits<int>::max())) {
476 return ResultFailure("File too big.");
477 }
478 return PNGImage::Load(imageFileBytes, loadFlags);
479 }
480
CanLoad(array_view<const uint8_t> imageFileBytes) const481 bool CanLoad(array_view<const uint8_t> imageFileBytes) const override
482 {
483 // Check for PNG
484 return png_sig_cmp(imageFileBytes.data(), 0, imageFileBytes.size()) == 0;
485 }
486
LoadAnimatedImage(IFile & file,uint32_t loadFlags)487 IImageLoaderManager::LoadAnimatedResult LoadAnimatedImage(IFile& file, uint32_t loadFlags) override
488 {
489 return ResultFailureAnimated("");
490 }
491
LoadAnimatedImage(array_view<const uint8_t> imageFileBytes,uint32_t loadFlags)492 IImageLoaderManager::LoadAnimatedResult LoadAnimatedImage(
493 array_view<const uint8_t> imageFileBytes, uint32_t loadFlags) override
494 {
495 return ResultFailureAnimated("");
496 }
497
GetSupportedTypes() const498 vector<IImageLoaderManager::ImageType> GetSupportedTypes() const override
499 {
500 return vector<IImageLoaderManager::ImageType>(std::begin(IMAGE_TYPES), std::end(IMAGE_TYPES));
501 }
502
503 protected:
504 ~ImageLoaderPng() = default;
Destroy()505 void Destroy() override
506 {
507 delete this;
508 }
509 };
510
CreateImageLoaderPng(CORE_NS::PluginToken)511 IImageLoaderManager::IImageLoader::Ptr CreateImageLoaderPng(CORE_NS::PluginToken)
512 {
513 return IImageLoaderManager::IImageLoader::Ptr { new ImageLoaderPng() };
514 }
515
516 } // namespace PNGPlugin
517