Support *.pkm format file decode
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-pkm.cpp
1 /*
2  * Copyright (c) 2023 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/imaging/common/loader-pkm.h>
20
21 // EXTERNAL INCLUDES
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
26
27 // INTERNAL INCLUDES
28 #include <dali/internal/imaging/common/pixel-buffer-impl.h> ///< for Internal::Adaptor::PixelBuffer::New()
29
30 namespace Dali
31 {
32 namespace TizenPlatform
33 {
34 namespace
35 {
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;
40
41 const uint8_t PKM_10_VERSION_MAJOR = '1';
42 const uint8_t PKM_10_VERSION_MINOR = '0';
43
44 const uint8_t PKM_20_VERSION_MAJOR = '2';
45 const uint8_t PKM_20_VERSION_MINOR = '0';
46
47 typedef uint8_t Byte;
48
49 // This bytes identify an PKM native file.
50 const Byte FileIdentifier[] =
51   {
52     0x50,
53     0x4B,
54     0x4D,
55     0x20,
56 };
57
58 using namespace Pixel;
59
60 // Convert from data type to Dali::Pixel:Format.
61 const Pixel::Format PKM_FORMAT_TABLE[] =
62   {
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
67
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
73 };
74
75 /**
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.
80  */
81 struct PkmFileHeader
82 {
83   uint8_t magic[4];
84   uint8_t versionMajor;
85   uint8_t versionMinor;
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__));
92
93 /**
94  * @brief Helper function to get the integer value from Big Endian data array.
95  *
96  * @param[in] data 2-byte data
97  * @return The value of input data said
98  */
99 inline uint32_t GetBigEndianValue(const uint8_t data[2])
100 {
101   return (static_cast<uint32_t>(data[0]) << 8) | data[1];
102 }
103
104 /**
105  * @brief Uses header information to return the respective PKM pixel format.
106  *
107  * @param[in] header A populated PkmFileHeader struct
108  * @return    The pixel format, or INVALID if there is no valid pixel format.
109  */
110 Pixel::Format GetPkmPixelFormat(PkmFileHeader& header)
111 {
112   uint32_t pkmFormat = GetBigEndianValue(header.dataType);
113   if(DALI_LIKELY(pkmFormat < sizeof(PKM_FORMAT_TABLE) / sizeof(PKM_FORMAT_TABLE[0])))
114   {
115     return PKM_FORMAT_TABLE[pkmFormat];
116   }
117   return Pixel::INVALID;
118 }
119
120 /**
121  * @brief Internal method to load PKM header info from a file.
122  *
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
128  */
129 bool LoadPkmHeader(FILE* const filePointer, unsigned int& width, unsigned int& height, PkmFileHeader& fileHeader)
130 {
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))
134   {
135     return false;
136   }
137
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))
141   {
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.
144     return false;
145   }
146
147   headerIsValid &= (fileHeader.versionMajor == PKM_10_VERSION_MAJOR) || (fileHeader.versionMajor == PKM_20_VERSION_MAJOR);
148   if(DALI_UNLIKELY(!headerIsValid))
149   {
150     DALI_LOG_ERROR("PKM version doesn't support. file version : %c.%c\n", static_cast<char>(fileHeader.versionMajor), static_cast<char>(fileHeader.versionMinor));
151     return false;
152   }
153
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);
157
158   // Check image dimensions are within limits.
159   if(DALI_UNLIKELY((width > MAX_TEXTURE_DIMENSION) || (height > MAX_TEXTURE_DIMENSION)))
160   {
161     DALI_LOG_ERROR("PKM file has larger than supported dimensions: %d,%d\n", width, height);
162     headerIsValid = false;
163   }
164
165   return headerIsValid;
166 }
167
168 } // Unnamed namespace.
169
170 // File loading API entry-point:
171 bool LoadPkmHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
172 {
173   PkmFileHeader fileHeader;
174   return LoadPkmHeader(input.file, width, height, fileHeader);
175 }
176
177 // File loading API entry-point:
178 bool LoadBitmapFromPkm(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
179 {
180   FILE* const filePointer = input.file;
181   if(DALI_UNLIKELY(!filePointer))
182   {
183     DALI_LOG_ERROR("Null file handle passed to PKM compressed bitmap file loader.\n");
184     return false;
185   }
186
187   // Load the header info.
188   PkmFileHeader fileHeader;
189   unsigned int  width, height;
190
191   if(DALI_UNLIKELY(!LoadPkmHeader(filePointer, width, height, fileHeader)))
192   {
193     DALI_LOG_ERROR("Could not load PKM Header from file.\n");
194     return false;
195   }
196
197   // Retrieve the pixel format from the PKM header.
198   Pixel::Format pixelFormat = GetPkmPixelFormat(fileHeader);
199   if(DALI_UNLIKELY(pixelFormat == Pixel::INVALID))
200   {
201     DALI_LOG_ERROR("No internal pixel format supported for PKM file pixel format.\n");
202     return false;
203   }
204
205   // Retrieve the file size.
206   if(DALI_UNLIKELY(fseek(filePointer, 0L, SEEK_END)))
207   {
208     DALI_LOG_ERROR("Could not seek through file.\n");
209     return false;
210   }
211
212   off_t fileSize = ftell(filePointer);
213   if(DALI_UNLIKELY(fileSize == -1L))
214   {
215     DALI_LOG_ERROR("Could not determine PKM file size.\n");
216     return false;
217   }
218
219   if(DALI_UNLIKELY(fseek(filePointer, sizeof(PkmFileHeader), SEEK_SET)))
220   {
221     DALI_LOG_ERROR("Could not seek through file.\n");
222     return false;
223   }
224
225   // Data size is file size - header size.
226   size_t imageByteCount = fileSize - sizeof(PkmFileHeader);
227
228   // Sanity-check the image data is not too large:
229   if(DALI_UNLIKELY(imageByteCount > MAX_IMAGE_DATA_SIZE))
230   {
231     DALI_LOG_ERROR("PKM file has too large image-data field.\n");
232     return false;
233   }
234
235   // allocate pixel data
236   auto* pixels = static_cast<uint8_t*>(malloc(imageByteCount));
237
238   if(DALI_UNLIKELY(pixels == nullptr))
239   {
240     DALI_LOG_ERROR("Buffer allocation failed. (required memory : %zu byte)\n", imageByteCount);
241     return false;
242   }
243
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());
247
248   // Load the image data.
249   const size_t bytesRead = fread(pixels, 1, imageByteCount, filePointer);
250
251   // Check the size of loaded data is what we expected.
252   if(DALI_UNLIKELY(bytesRead != imageByteCount))
253   {
254     DALI_LOG_ERROR("Read of image pixel data failed. (required image bytes : %zu, actual read from file : %zu\n", imageByteCount, bytesRead);
255     return false;
256   }
257
258   return true;
259 }
260
261 } // namespace TizenPlatform
262
263 } // namespace Dali