2 * Copyright (c) 2023 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali/internal/imaging/common/loader-pkm.h>
22 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/images/pixel.h>
25 #include <cstring> ///< for memcmp
28 #include <dali/internal/imaging/common/pixel-buffer-impl.h> ///< for Internal::Adaptor::PixelBuffer::New()
32 namespace TizenPlatform
36 // Max width or height of an image.
37 const unsigned MAX_TEXTURE_DIMENSION = 4096;
38 // Max bytes of image data allowed. Not a precise number, just a sanity check.
39 const unsigned MAX_IMAGE_DATA_SIZE = MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION;
41 const uint8_t PKM_10_VERSION_MAJOR = '1';
42 const uint8_t PKM_10_VERSION_MINOR = '0';
44 const uint8_t PKM_20_VERSION_MAJOR = '2';
45 const uint8_t PKM_20_VERSION_MINOR = '0';
49 // This bytes identify an PKM native file.
50 const Byte FileIdentifier[] =
58 using namespace Pixel;
60 // Convert from data type to Dali::Pixel:Format.
61 const Pixel::Format PKM_FORMAT_TABLE[] =
63 Pixel::Format::COMPRESSED_RGB8_ETC1, ///< 0x0000
64 Pixel::Format::COMPRESSED_RGB8_ETC2, ///< 0x0001
65 Pixel::Format::COMPRESSED_SRGB8_ETC2, ///< 0x0002
66 Pixel::Format::COMPRESSED_RGBA8_ETC2_EAC, ///< 0x0003
68 Pixel::Format::COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, ///< 0x0004
69 Pixel::Format::COMPRESSED_R11_EAC, ///< 0x0005
70 Pixel::Format::COMPRESSED_RG11_EAC, ///< 0x0006
71 Pixel::Format::COMPRESSED_SIGNED_R11_EAC, ///< 0x0007
72 Pixel::Format::COMPRESSED_SIGNED_RG11_EAC, ///< 0x0008
76 * @brief This struct defines the PKM file header values. From PKM specifications.
77 * Packed attribute stops the structure from being aligned to compiler defaults
78 * so we can be sure of reading the whole header from file in one call to fread().
79 * Note: members to not conform to coding standards in order to be consistent with PKM spec.
86 uint8_t dataType[2]; // Big Endian
87 uint8_t extendedWidth[2]; // Big Endian
88 uint8_t extendedHeight[2]; // Big Endian
89 uint8_t originalWidth[2]; // Big Endian
90 uint8_t originalHeight[2]; // Big Endian
91 } __attribute__((__packed__));
94 * @brief Helper function to get the integer value from Big Endian data array.
96 * @param[in] data 2-byte data
97 * @return The value of input data said
99 inline uint32_t GetBigEndianValue(const uint8_t data[2])
101 return (static_cast<uint32_t>(data[0]) << 8) | data[1];
105 * @brief Uses header information to return the respective PKM pixel format.
107 * @param[in] header A populated PkmFileHeader struct
108 * @return The pixel format, or INVALID if there is no valid pixel format.
110 Pixel::Format GetPkmPixelFormat(PkmFileHeader& header)
112 uint32_t pkmFormat = GetBigEndianValue(header.dataType);
113 if(DALI_LIKELY(pkmFormat < sizeof(PKM_FORMAT_TABLE) / sizeof(PKM_FORMAT_TABLE[0])))
115 return PKM_FORMAT_TABLE[pkmFormat];
117 return Pixel::INVALID;
121 * @brief Internal method to load PKM header info from a file.
123 * @param[in] filePointer The file pointer to the PKM file to read
124 * @param[out] width The width is output to this value
125 * @param[out] height The height is output to this value
126 * @param[out] fileHeader This will be populated with the header data
127 * @return True if the file is valid, false otherwise
129 bool LoadPkmHeader(FILE* const filePointer, unsigned int& width, unsigned int& height, PkmFileHeader& fileHeader)
131 // Pull the bytes of the file header in as a block:
132 const unsigned int readLength = sizeof(PkmFileHeader);
133 if(DALI_UNLIKELY(fread(&fileHeader, 1, readLength, filePointer) != readLength))
138 // Check the header contains the PKM native file identifier.
139 bool headerIsValid = memcmp(fileHeader.magic, FileIdentifier, sizeof(fileHeader.magic)) == 0;
140 if(DALI_UNLIKELY(!headerIsValid))
142 DALI_LOG_ERROR("File is not a valid PKM native file\n");
143 // Return here as otherwise, if not a valid PKM file, we are likely to pick up other header errors spuriously.
147 headerIsValid &= (fileHeader.versionMajor == PKM_10_VERSION_MAJOR) || (fileHeader.versionMajor == PKM_20_VERSION_MAJOR);
148 if(DALI_UNLIKELY(!headerIsValid))
150 DALI_LOG_ERROR("PKM version doesn't support. file version : %c.%c\n", static_cast<char>(fileHeader.versionMajor), static_cast<char>(fileHeader.versionMinor));
154 // Convert the 2-byte values for width and height to a single resultant value.
155 width = GetBigEndianValue(fileHeader.originalWidth);
156 height = GetBigEndianValue(fileHeader.originalHeight);
158 // Check image dimensions are within limits.
159 if(DALI_UNLIKELY((width > MAX_TEXTURE_DIMENSION) || (height > MAX_TEXTURE_DIMENSION)))
161 DALI_LOG_ERROR("PKM file has larger than supported dimensions: %d,%d\n", width, height);
162 headerIsValid = false;
165 return headerIsValid;
168 } // Unnamed namespace.
170 // File loading API entry-point:
171 bool LoadPkmHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
173 PkmFileHeader fileHeader;
174 return LoadPkmHeader(input.file, width, height, fileHeader);
177 // File loading API entry-point:
178 bool LoadBitmapFromPkm(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
180 FILE* const filePointer = input.file;
181 if(DALI_UNLIKELY(!filePointer))
183 DALI_LOG_ERROR("Null file handle passed to PKM compressed bitmap file loader.\n");
187 // Load the header info.
188 PkmFileHeader fileHeader;
189 unsigned int width, height;
191 if(DALI_UNLIKELY(!LoadPkmHeader(filePointer, width, height, fileHeader)))
193 DALI_LOG_ERROR("Could not load PKM Header from file.\n");
197 // Retrieve the pixel format from the PKM header.
198 Pixel::Format pixelFormat = GetPkmPixelFormat(fileHeader);
199 if(DALI_UNLIKELY(pixelFormat == Pixel::INVALID))
201 DALI_LOG_ERROR("No internal pixel format supported for PKM file pixel format.\n");
205 // Retrieve the file size.
206 if(DALI_UNLIKELY(fseek(filePointer, 0L, SEEK_END)))
208 DALI_LOG_ERROR("Could not seek through file.\n");
212 off_t fileSize = ftell(filePointer);
213 if(DALI_UNLIKELY(fileSize == -1L))
215 DALI_LOG_ERROR("Could not determine PKM file size.\n");
219 if(DALI_UNLIKELY(fseek(filePointer, sizeof(PkmFileHeader), SEEK_SET)))
221 DALI_LOG_ERROR("Could not seek through file.\n");
225 // Data size is file size - header size.
226 size_t imageByteCount = fileSize - sizeof(PkmFileHeader);
228 // Sanity-check the image data is not too large:
229 if(DALI_UNLIKELY(imageByteCount > MAX_IMAGE_DATA_SIZE))
231 DALI_LOG_ERROR("PKM file has too large image-data field.\n");
235 // allocate pixel data
236 auto* pixels = static_cast<uint8_t*>(malloc(imageByteCount));
238 if(DALI_UNLIKELY(pixels == nullptr))
240 DALI_LOG_ERROR("Buffer allocation failed. (required memory : %zu byte)\n", imageByteCount);
244 // Create bitmap who will use allocated buffer.
245 const auto& bitmapInternal = Internal::Adaptor::PixelBuffer::New(pixels, imageByteCount, width, height, 0, pixelFormat);
246 bitmap = Dali::Devel::PixelBuffer(bitmapInternal.Get());
248 // Load the image data.
249 const size_t bytesRead = fread(pixels, 1, imageByteCount, filePointer);
251 // Check the size of loaded data is what we expected.
252 if(DALI_UNLIKELY(bytesRead != imageByteCount))
254 DALI_LOG_ERROR("Read of image pixel data failed. (required image bytes : %zu, actual read from file : %zu\n", imageByteCount, bytesRead);
261 } // namespace TizenPlatform