2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 // Licensed under the Flora License, Version 1.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://floralicense.org/license/
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.
17 #include "loader-ktx.h"
23 #include <dali/public-api/math/compile-time-assert.h>
24 #include <dali/public-api/dali-core.h>
25 #include <dali/integration-api/debug.h>
26 #include <dali/integration-api/bitmap.h>
27 #include <dali/public-api/images/pixel.h>
28 #include <dali/public-api/images/image-attributes.h>
32 using Integration::Bitmap;
33 using Dali::Integration::PixelBuffer;
41 /** Max width or height of an image. */
42 const unsigned MAX_TEXTURE_DIMENSION = 4096;
43 /** Max bytes of image data allowed. Not a precise number, just a sanity check. */
44 const unsigned MAX_IMAGE_DATA_SIZE = MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION;
45 /** We don't read any of this but limit it to a resonable amount in order to be
46 * friendly to files from random tools. */
47 const unsigned MAX_BYTES_OF_KEYVALUE_DATA = 65536U;
51 const Byte FileIdentifier[] = {
52 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
55 /** The formats we support inside a KTX file container.
56 * Currently only compressed formats are allowed as we'd rather
57 * use a PNG or JPEG with their own compression for the general
59 enum KtxInternalFormat
62 // GLES 3 Standard compressed formats (values same as in gl3.h):
63 KTX_COMPRESSED_R11_EAC = 0x9270,
64 KTX_COMPRESSED_SIGNED_R11_EAC = 0x9271,
65 KTX_COMPRESSED_RG11_EAC = 0x9272,
66 KTX_COMPRESSED_SIGNED_RG11_EAC = 0x9273,
67 KTX_COMPRESSED_RGB8_ETC2 = 0x9274,
68 KTX_COMPRESSED_SRGB8_ETC2 = 0x9275,
69 KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276,
70 KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277,
71 KTX_COMPRESSED_RGBA8_ETC2_EAC = 0x9278,
72 KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279,
74 // GLES 2 EXTENSION FORMATS:
75 KTX_ETC1_RGB8_OES = 0x8D64,
76 KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00,
80 const unsigned KtxInternalFormats[] =
82 // GLES 3 Standard compressed formats:
83 KTX_COMPRESSED_R11_EAC,
84 KTX_COMPRESSED_SIGNED_R11_EAC,
85 KTX_COMPRESSED_RG11_EAC,
86 KTX_COMPRESSED_SIGNED_RG11_EAC,
87 KTX_COMPRESSED_RGB8_ETC2,
88 KTX_COMPRESSED_SRGB8_ETC2,
89 KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2,
90 KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2,
91 KTX_COMPRESSED_RGBA8_ETC2_EAC,
92 KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC,
93 // GLES 2 EXTENSION FORMATS:
95 KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
106 uint32_t glInternalFormat;
107 uint32_t glBaseInternalFormat;
109 uint32_t pixelHeight;
111 uint32_t numberOfArrayElements;
112 uint32_t numberOfFaces;
113 uint32_t numberOfMipmapLevels;
114 uint32_t bytesOfKeyValueData;
115 } __attribute__ ( (__packed__));
116 // Packed attribute stops the structure from being aligned to compiler defaults
117 // so we can be sure of reading the whole thing from file in one call to fread.
120 * Template function to read from the file directly into our structure.
121 * @param[in] fp The file to read from
122 * @param[out] header The structure we want to store our information in
123 * @return true, if read successful, false otherwise
126 inline bool ReadHeader(FILE* fp, T& header)
128 unsigned int readLength = sizeof(T);
130 // Load the information directly into our structure
131 if (fread((void*)&header, 1, readLength, fp) != readLength)
139 /** Check whether the array passed in is the right size and matches the magic
140 * values defined to be at the start of a KTX file by the specification.*/
141 template<int BYTES_IN_SIGNATURE>
142 bool CheckFileIdentifier(const Byte * const signature)
144 const unsigned signatureSize = BYTES_IN_SIGNATURE;
145 const unsigned identifierSize = sizeof(FileIdentifier);
146 DALI_COMPILE_TIME_ASSERT(signatureSize == identifierSize);
147 const bool signatureGood = 0 == memcmp( signature, FileIdentifier, std::min( signatureSize, identifierSize ) );
148 return signatureGood;
152 * @returns True if the argument is a GLES compressed texture format that we support.
154 bool ValidInternalFormat(const unsigned format)
156 unsigned candidateFormat = 0;
157 for(unsigned iFormat = 0; (candidateFormat = KtxInternalFormats[iFormat]) != KTX_SENTINEL; ++iFormat)
159 if(format == candidateFormat)
164 DALI_LOG_ERROR("Rejecting unsupported compressed format when loading compressed texture from KTX file: 0x%x.\n", format);
169 * @returns The Pixel::Format Dali enum corresponding to the KTX internal format
170 * passed in, or Pixel::INVALID_PIXEL_FORMAT if the format is not valid.
172 bool ConvertPixelFormat(const uint32_t ktxPixelFormat, Dali::Pixel::Format& format)
174 using namespace Dali::Pixel;
175 switch(ktxPixelFormat)
177 case KTX_COMPRESSED_R11_EAC:
179 format = Dali::Pixel::COMPRESSED_R11_EAC;
182 case KTX_COMPRESSED_SIGNED_R11_EAC:
184 format = COMPRESSED_SIGNED_R11_EAC;
187 case KTX_COMPRESSED_RG11_EAC:
189 format = COMPRESSED_RG11_EAC;
192 case KTX_COMPRESSED_SIGNED_RG11_EAC:
194 format = COMPRESSED_SIGNED_RG11_EAC;
197 case KTX_COMPRESSED_RGB8_ETC2:
199 format = COMPRESSED_RGB8_ETC2;
202 case KTX_COMPRESSED_SRGB8_ETC2:
204 format = COMPRESSED_SRGB8_ETC2;
207 case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
209 format = COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
212 case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
214 format = COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
217 case KTX_COMPRESSED_RGBA8_ETC2_EAC:
219 format = COMPRESSED_RGBA8_ETC2_EAC;
222 case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
224 format = COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
227 // GLES 2 extension compressed formats:
228 case KTX_ETC1_RGB8_OES:
230 format = COMPRESSED_RGB8_ETC1;
233 case KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
235 format = COMPRESSED_RGB_PVRTC_4BPPV1;
247 bool LoadKtxHeader(FILE * const fp, unsigned int &width, unsigned int &height, KtxFileHeader &fileHeader)
249 // Pull the bytes of the file header in as a block:
250 if ( !ReadHeader(fp, fileHeader) )
254 width = fileHeader.pixelWidth;
255 height = fileHeader.pixelHeight;
257 if ( width > MAX_TEXTURE_DIMENSION || height > MAX_TEXTURE_DIMENSION )
262 // Validate file header contents meet our minimal subset:
263 const bool signatureGood = CheckFileIdentifier<sizeof(fileHeader.identifier)>(fileHeader.identifier);
264 const bool fileEndiannessMatchesSystemEndianness = fileHeader.endianness == 0x04030201; // Magic number from KTX spec.
265 const bool glTypeIsCompressed = fileHeader.glType == 0;
266 const bool glTypeSizeCompatibleWithCompressedTex = fileHeader.glTypeSize == 1;
267 const bool glFormatCompatibleWithCompressedTex = fileHeader.glFormat == 0;
268 const bool glInternalFormatIsSupportedCompressedTex = ValidInternalFormat(fileHeader.glInternalFormat);
269 // Ignore glBaseInternalFormat
270 const bool textureIsNot3D = fileHeader.pixelDepth == 0 || fileHeader.pixelDepth == 1;
271 const bool textureIsNotAnArray = fileHeader.numberOfArrayElements == 0 || fileHeader.numberOfArrayElements == 1;
272 const bool textureIsNotACubemap = fileHeader.numberOfFaces == 0 || fileHeader.numberOfFaces == 1;
273 const bool textureHasNoMipmapLevels = fileHeader.numberOfMipmapLevels == 0 || fileHeader.numberOfMipmapLevels == 1;
274 const bool keyValueDataNotTooLarge = fileHeader.bytesOfKeyValueData <= MAX_BYTES_OF_KEYVALUE_DATA;
276 const bool headerIsValid = signatureGood && fileEndiannessMatchesSystemEndianness && glTypeIsCompressed &&
277 glTypeSizeCompatibleWithCompressedTex && glFormatCompatibleWithCompressedTex &&
278 textureIsNot3D && textureIsNotAnArray && textureIsNotACubemap && textureHasNoMipmapLevels &&
279 glInternalFormatIsSupportedCompressedTex & keyValueDataNotTooLarge;
282 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);
285 // Warn if there is space wasted in the file:
286 if( fileHeader.bytesOfKeyValueData > 0U )
288 DALI_LOG_WARNING("Loading of KTX file with key/value header data requested. This should be stripped in application asset/resource build.\n");
291 return headerIsValid;
295 } // unnamed namespace
297 // File loading API entry-point:
298 bool LoadKtxHeader(FILE * const fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height)
300 KtxFileHeader fileHeader;
301 bool ret = LoadKtxHeader(fp, width, height, fileHeader);
305 // File loading API entry-point:
306 bool LoadBitmapFromKtx(FILE * const fp, Bitmap& bitmap, ImageAttributes& attributes)
308 DALI_COMPILE_TIME_ASSERT( sizeof(Byte) == 1);
309 DALI_COMPILE_TIME_ASSERT( sizeof(uint32_t) == 4);
312 DALI_LOG_ERROR( "Null file handle passed to KTX compressed bitmap file loader.\n" );
315 KtxFileHeader fileHeader;
317 // Load the header info
318 unsigned int width, height;
320 if (!LoadKtxHeader(fp, width, height, fileHeader))
325 // Skip the key-values:
326 const long int imageSizeOffset = sizeof(KtxFileHeader) + fileHeader.bytesOfKeyValueData;
327 if(fseek(fp, imageSizeOffset, SEEK_SET))
329 DALI_LOG_ERROR( "Seek past key/vals in KTX compressed bitmap file failed.\n" );
333 // Load the size of the image data:
334 uint32_t imageByteCount = 0;
335 if (fread((void*)&imageByteCount, 1, 4, fp) != 4)
337 DALI_LOG_ERROR( "Read of image size failed.\n" );
340 // Sanity-check the image size:
341 if( imageByteCount > MAX_IMAGE_DATA_SIZE ||
342 // A compressed texture should certainly be less than 2 bytes per texel:
343 imageByteCount > width * height * 2)
345 DALI_LOG_ERROR( "KTX file with too-large image-data field.\n" );
349 Pixel::Format pixelFormat;
350 const bool pixelFormatKnown = ConvertPixelFormat(fileHeader.glInternalFormat, pixelFormat);
351 if(!pixelFormatKnown)
353 DALI_LOG_ERROR( "No internal pixel format supported for KTX file pixel format.\n" );
357 // Load up the image bytes:
358 PixelBuffer * const pixels = bitmap.GetCompressedProfile()->ReserveBufferOfSize( pixelFormat, width, height, (size_t) imageByteCount );
361 DALI_LOG_ERROR( "Unable to reserve a pixel buffer to load the requested bitmap into.\n" );
364 const size_t bytesRead = fread(pixels, 1, imageByteCount, fp);
365 if(bytesRead != imageByteCount)
367 DALI_LOG_ERROR( "Read of image pixel data failed.\n" );
371 // Ignore all input requests from image attributes and set the available metadata:
372 attributes.SetSize(width, height);
373 attributes.SetPixelFormat(pixelFormat);
378 } // namespace SlpPlatform