Merge "Change "SLP" to "Tizen"" into tizen
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / tizen / image-loaders / loader-ktx.cpp
1 /*
2  * Copyright (c) 2014 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 #include "loader-ktx.h"
19
20 #include <cstdio>
21 #include <cstdlib>
22 #include <cstring>
23 #include <stdint.h>
24 #include <dali/public-api/dali-core.h>
25 #include <dali/public-api/common/compile-time-assert.h>
26 #include <dali/integration-api/debug.h>
27 #include <dali/integration-api/bitmap.h>
28 #include <dali/public-api/images/pixel.h>
29 #include <dali/public-api/images/image-attributes.h>
30
31 namespace Dali
32 {
33 using Integration::Bitmap;
34 using Dali::Integration::PixelBuffer;
35
36 namespace TizenPlatform
37 {
38
39 namespace
40 {
41
42 /** Max width or height of an image. */
43 const unsigned MAX_TEXTURE_DIMENSION = 4096;
44 /** Max bytes of image data allowed. Not a precise number, just a sanity check. */
45 const unsigned MAX_IMAGE_DATA_SIZE = MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION;
46 /** We don't read any of this but limit it to a resonable amount in order to be
47  * friendly to files from random tools. */
48 const unsigned MAX_BYTES_OF_KEYVALUE_DATA = 65536U;
49
50 typedef uint8_t Byte;
51
52 const Byte FileIdentifier[] = {
53    0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
54 };
55
56 /** The formats we support inside a KTX file container.
57  *  Currently only compressed formats are allowed as we'd rather
58  *  use a PNG or JPEG with their own compression for the general
59  *  cases. */
60 enum KtxInternalFormat
61 {
62   KTX_NOTEXIST = 0,
63   // GLES 3 Standard compressed formats (values same as in gl3.h):
64   KTX_COMPRESSED_R11_EAC                          = 0x9270,
65   KTX_COMPRESSED_SIGNED_R11_EAC                   = 0x9271,
66   KTX_COMPRESSED_RG11_EAC                         = 0x9272,
67   KTX_COMPRESSED_SIGNED_RG11_EAC                  = 0x9273,
68   KTX_COMPRESSED_RGB8_ETC2                        = 0x9274,
69   KTX_COMPRESSED_SRGB8_ETC2                       = 0x9275,
70   KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2    = 0x9276,
71   KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2   = 0x9277,
72   KTX_COMPRESSED_RGBA8_ETC2_EAC                   = 0x9278,
73   KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC            = 0x9279,
74
75   // GLES 2 EXTENSION FORMATS:
76   KTX_ETC1_RGB8_OES                               = 0x8D64,
77   KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG             = 0x8C00,
78   KTX_SENTINEL = ~0u,
79 };
80
81 const unsigned KtxInternalFormats[] =
82 {
83   // GLES 3 Standard compressed formats:
84   KTX_COMPRESSED_R11_EAC,
85   KTX_COMPRESSED_SIGNED_R11_EAC,
86   KTX_COMPRESSED_RG11_EAC,
87   KTX_COMPRESSED_SIGNED_RG11_EAC,
88   KTX_COMPRESSED_RGB8_ETC2,
89   KTX_COMPRESSED_SRGB8_ETC2,
90   KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
91   KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
92   KTX_COMPRESSED_RGBA8_ETC2_EAC,
93   KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
94   // GLES 2 EXTENSION FORMATS:
95   KTX_ETC1_RGB8_OES,
96   KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
97   KTX_SENTINEL
98 };
99
100 struct KtxFileHeader
101 {
102   Byte   identifier[12];
103   uint32_t endianness;
104   uint32_t glType;
105   uint32_t glTypeSize;
106   uint32_t glFormat;
107   uint32_t glInternalFormat;
108   uint32_t glBaseInternalFormat;
109   uint32_t pixelWidth;
110   uint32_t pixelHeight;
111   uint32_t pixelDepth;
112   uint32_t numberOfArrayElements;
113   uint32_t numberOfFaces;
114   uint32_t numberOfMipmapLevels;
115   uint32_t bytesOfKeyValueData;
116 } __attribute__ ( (__packed__));
117 // Packed attribute stops the structure from being aligned to compiler defaults
118 // so we can be sure of reading the whole thing from file in one call to fread.
119
120 /**
121  * Template function to read from the file directly into our structure.
122  * @param[in]  fp     The file to read from
123  * @param[out] header The structure we want to store our information in
124  * @return true, if read successful, false otherwise
125  */
126 template<typename T>
127 inline bool ReadHeader(FILE* fp, T& header)
128 {
129   unsigned int readLength = sizeof(T);
130
131   // Load the information directly into our structure
132   if (fread((void*)&header, 1, readLength, fp) != readLength)
133   {
134     return false;
135   }
136
137   return true;
138 }
139
140 /** Check whether the array passed in is the right size and matches the magic
141  *  values defined to be at the start of a KTX file by the specification.*/
142 template<int BYTES_IN_SIGNATURE>
143 bool CheckFileIdentifier(const Byte * const signature)
144 {
145   const unsigned signatureSize = BYTES_IN_SIGNATURE;
146   const unsigned identifierSize = sizeof(FileIdentifier);
147   DALI_COMPILE_TIME_ASSERT(signatureSize == identifierSize);
148   const bool signatureGood = 0 == memcmp( signature, FileIdentifier, std::min( signatureSize, identifierSize ) );
149   return signatureGood;
150 }
151
152 /**
153  * @returns True if the argument is a GLES compressed texture format that we support.
154  */
155 bool ValidInternalFormat(const unsigned format)
156 {
157   unsigned candidateFormat = 0;
158   for(unsigned iFormat = 0; (candidateFormat = KtxInternalFormats[iFormat]) != KTX_SENTINEL; ++iFormat)
159   {
160     if(format == candidateFormat)
161     {
162       return true;
163     }
164   }
165   DALI_LOG_ERROR("Rejecting unsupported compressed format when loading compressed texture from KTX file: 0x%x.\n", format);
166   return false;
167 }
168
169 /**
170  * @returns The Pixel::Format Dali enum corresponding to the KTX internal format
171  *          passed in, or Pixel::INVALID_PIXEL_FORMAT if the format is not valid.
172  **/
173 bool ConvertPixelFormat(const uint32_t ktxPixelFormat, Dali::Pixel::Format& format)
174 {
175   using namespace Dali::Pixel;
176   switch(ktxPixelFormat)
177   {
178     case KTX_COMPRESSED_R11_EAC:
179     {
180       format = Dali::Pixel::COMPRESSED_R11_EAC;
181       break;
182     }
183     case KTX_COMPRESSED_SIGNED_R11_EAC:
184     {
185       format = COMPRESSED_SIGNED_R11_EAC;
186       break;
187     }
188     case KTX_COMPRESSED_RG11_EAC:
189     {
190       format = COMPRESSED_RG11_EAC;
191       break;
192     }
193     case KTX_COMPRESSED_SIGNED_RG11_EAC:
194     {
195       format = COMPRESSED_SIGNED_RG11_EAC;
196       break;
197     }
198     case KTX_COMPRESSED_RGB8_ETC2:
199     {
200       format = COMPRESSED_RGB8_ETC2;
201       break;
202     }
203     case KTX_COMPRESSED_SRGB8_ETC2:
204     {
205       format = COMPRESSED_SRGB8_ETC2;
206       break;
207     }
208     case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
209     {
210       format = COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
211       break;
212     }
213     case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
214     {
215       format = COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
216       break;
217     }
218     case KTX_COMPRESSED_RGBA8_ETC2_EAC:
219     {
220       format = COMPRESSED_RGBA8_ETC2_EAC;
221       break;
222     }
223     case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
224     {
225       format = COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
226       break;
227     }
228     // GLES 2 extension compressed formats:
229     case KTX_ETC1_RGB8_OES:
230     {
231       format = COMPRESSED_RGB8_ETC1;
232       break;
233     }
234     case KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
235     {
236       format = COMPRESSED_RGB_PVRTC_4BPPV1;
237       break;
238     }
239
240     default:
241     {
242        return false;
243     }
244   }
245   return true;
246 }
247
248 bool LoadKtxHeader(FILE * const fp, unsigned int &width, unsigned int &height, KtxFileHeader &fileHeader)
249 {
250   // Pull the bytes of the file header in as a block:
251   if ( !ReadHeader(fp, fileHeader) )
252   {
253     return false;
254   }
255   width = fileHeader.pixelWidth;
256   height = fileHeader.pixelHeight;
257
258   if ( width > MAX_TEXTURE_DIMENSION || height > MAX_TEXTURE_DIMENSION )
259   {
260     return false;
261   }
262
263   // Validate file header contents meet our minimal subset:
264   const bool signatureGood                            = CheckFileIdentifier<sizeof(fileHeader.identifier)>(fileHeader.identifier);
265   const bool fileEndiannessMatchesSystemEndianness    = fileHeader.endianness == 0x04030201; // Magic number from KTX spec.
266   const bool glTypeIsCompressed                       = fileHeader.glType == 0;
267   const bool glTypeSizeCompatibleWithCompressedTex    = fileHeader.glTypeSize == 1;
268   const bool glFormatCompatibleWithCompressedTex      = fileHeader.glFormat == 0;
269   const bool glInternalFormatIsSupportedCompressedTex = ValidInternalFormat(fileHeader.glInternalFormat);
270   // Ignore glBaseInternalFormat
271   const bool textureIsNot3D                           = fileHeader.pixelDepth == 0 || fileHeader.pixelDepth == 1;
272   const bool textureIsNotAnArray                      = fileHeader.numberOfArrayElements == 0 || fileHeader.numberOfArrayElements == 1;
273   const bool textureIsNotACubemap                     = fileHeader.numberOfFaces == 0 || fileHeader.numberOfFaces == 1;
274   const bool textureHasNoMipmapLevels                 = fileHeader.numberOfMipmapLevels == 0 || fileHeader.numberOfMipmapLevels == 1;
275   const bool keyValueDataNotTooLarge                  = fileHeader.bytesOfKeyValueData <= MAX_BYTES_OF_KEYVALUE_DATA;
276
277   const bool headerIsValid = signatureGood && fileEndiannessMatchesSystemEndianness && glTypeIsCompressed &&
278                            glTypeSizeCompatibleWithCompressedTex && glFormatCompatibleWithCompressedTex &&
279                            textureIsNot3D && textureIsNotAnArray && textureIsNotACubemap && textureHasNoMipmapLevels &&
280                            glInternalFormatIsSupportedCompressedTex & keyValueDataNotTooLarge;
281   if( !headerIsValid )
282   {
283      DALI_LOG_ERROR( "KTX file invalid or using unsupported features. Header tests: sig: %d, endian: %d, gl_type: %d, gl_type_size: %d, gl_format: %d, internal_format: %d, depth: %d, array: %d, faces: %d, mipmap: %d, vey-vals: %d.\n", 0+signatureGood, 0+fileEndiannessMatchesSystemEndianness, 0+glTypeIsCompressed, 0+glTypeSizeCompatibleWithCompressedTex, 0+glFormatCompatibleWithCompressedTex, 0+glInternalFormatIsSupportedCompressedTex, 0+textureIsNot3D, 0+textureIsNotAnArray, 0+textureIsNotACubemap, 0+textureHasNoMipmapLevels, 0+keyValueDataNotTooLarge);
284   }
285
286   // Warn if there is space wasted in the file:
287   if( fileHeader.bytesOfKeyValueData > 0U )
288   {
289     DALI_LOG_WARNING("Loading of KTX file with key/value header data requested. This should be stripped in application asset/resource build.\n");
290   }
291
292   return headerIsValid;
293 }
294
295
296 } // unnamed namespace
297
298 // File loading API entry-point:
299 bool LoadKtxHeader(FILE * const fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height)
300 {
301   KtxFileHeader fileHeader;
302   bool ret = LoadKtxHeader(fp, width, height, fileHeader);
303   return ret;
304 }
305
306 // File loading API entry-point:
307 bool LoadBitmapFromKtx( FILE * const fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
308 {
309   DALI_COMPILE_TIME_ASSERT( sizeof(Byte) == 1);
310   DALI_COMPILE_TIME_ASSERT( sizeof(uint32_t) == 4);
311   if( fp == NULL )
312   {
313     DALI_LOG_ERROR( "Null file handle passed to KTX compressed bitmap file loader.\n" );
314     return false;
315   }
316   KtxFileHeader fileHeader;
317
318   // Load the header info
319   unsigned int width, height;
320
321   if (!LoadKtxHeader(fp, width, height, fileHeader))
322   {
323       return false;
324   }
325
326   // Skip the key-values:
327   const long int imageSizeOffset = sizeof(KtxFileHeader) + fileHeader.bytesOfKeyValueData;
328   if(fseek(fp, imageSizeOffset, SEEK_SET))
329   {
330     DALI_LOG_ERROR( "Seek past key/vals in KTX compressed bitmap file failed.\n" );
331     return false;
332   }
333
334   // Load the size of the image data:
335   uint32_t imageByteCount = 0;
336   if (fread((void*)&imageByteCount, 1, 4, fp) != 4)
337   {
338     DALI_LOG_ERROR( "Read of image size failed.\n" );
339     return false;
340   }
341   // Sanity-check the image size:
342   if( imageByteCount > MAX_IMAGE_DATA_SIZE ||
343       // A compressed texture should certainly be less than 2 bytes per texel:
344       imageByteCount > width * height * 2)
345   {
346     DALI_LOG_ERROR( "KTX file with too-large image-data field.\n" );
347     return false;
348   }
349
350   Pixel::Format pixelFormat;
351   const bool pixelFormatKnown = ConvertPixelFormat(fileHeader.glInternalFormat, pixelFormat);
352   if(!pixelFormatKnown)
353   {
354     DALI_LOG_ERROR( "No internal pixel format supported for KTX file pixel format.\n" );
355     return false;
356   }
357
358   // Load up the image bytes:
359   PixelBuffer * const pixels = bitmap.GetCompressedProfile()->ReserveBufferOfSize( pixelFormat, width, height, (size_t) imageByteCount );
360   if(!pixels)
361   {
362     DALI_LOG_ERROR( "Unable to reserve a pixel buffer to load the requested bitmap into.\n" );
363     return false;
364   }
365   const size_t bytesRead = fread(pixels, 1, imageByteCount, fp);
366   if(bytesRead != imageByteCount)
367   {
368     DALI_LOG_ERROR( "Read of image pixel data failed.\n" );
369     return false;
370   }
371
372   // Ignore all input requests from image attributes and set the available metadata:
373   attributes.SetSize(width, height);
374
375   return true;
376 }
377
378 } // namespace TizenPlatform
379
380 } // namespace Dali