2 * Copyright (c) 2014 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.
18 #include "loader-ktx.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>
33 using Integration::Bitmap;
34 using Dali::Integration::PixelBuffer;
36 namespace TizenPlatform
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;
52 const Byte FileIdentifier[] = {
53 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
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
60 enum KtxInternalFormat
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,
75 // GLES 2 EXTENSION FORMATS:
76 KTX_ETC1_RGB8_OES = 0x8D64,
77 KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00,
81 const unsigned KtxInternalFormats[] =
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:
96 KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
107 uint32_t glInternalFormat;
108 uint32_t glBaseInternalFormat;
110 uint32_t pixelHeight;
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.
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
127 inline bool ReadHeader(FILE* fp, T& header)
129 unsigned int readLength = sizeof(T);
131 // Load the information directly into our structure
132 if (fread((void*)&header, 1, readLength, fp) != readLength)
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)
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;
153 * @returns True if the argument is a GLES compressed texture format that we support.
155 bool ValidInternalFormat(const unsigned format)
157 unsigned candidateFormat = 0;
158 for(unsigned iFormat = 0; (candidateFormat = KtxInternalFormats[iFormat]) != KTX_SENTINEL; ++iFormat)
160 if(format == candidateFormat)
165 DALI_LOG_ERROR("Rejecting unsupported compressed format when loading compressed texture from KTX file: 0x%x.\n", format);
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.
173 bool ConvertPixelFormat(const uint32_t ktxPixelFormat, Dali::Pixel::Format& format)
175 using namespace Dali::Pixel;
176 switch(ktxPixelFormat)
178 case KTX_COMPRESSED_R11_EAC:
180 format = Dali::Pixel::COMPRESSED_R11_EAC;
183 case KTX_COMPRESSED_SIGNED_R11_EAC:
185 format = COMPRESSED_SIGNED_R11_EAC;
188 case KTX_COMPRESSED_RG11_EAC:
190 format = COMPRESSED_RG11_EAC;
193 case KTX_COMPRESSED_SIGNED_RG11_EAC:
195 format = COMPRESSED_SIGNED_RG11_EAC;
198 case KTX_COMPRESSED_RGB8_ETC2:
200 format = COMPRESSED_RGB8_ETC2;
203 case KTX_COMPRESSED_SRGB8_ETC2:
205 format = COMPRESSED_SRGB8_ETC2;
208 case KTX_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
210 format = COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2;
213 case KTX_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
215 format = COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2;
218 case KTX_COMPRESSED_RGBA8_ETC2_EAC:
220 format = COMPRESSED_RGBA8_ETC2_EAC;
223 case KTX_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
225 format = COMPRESSED_SRGB8_ALPHA8_ETC2_EAC;
228 // GLES 2 extension compressed formats:
229 case KTX_ETC1_RGB8_OES:
231 format = COMPRESSED_RGB8_ETC1;
234 case KTX_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
236 format = COMPRESSED_RGB_PVRTC_4BPPV1;
248 bool LoadKtxHeader(FILE * const fp, unsigned int &width, unsigned int &height, KtxFileHeader &fileHeader)
250 // Pull the bytes of the file header in as a block:
251 if ( !ReadHeader(fp, fileHeader) )
255 width = fileHeader.pixelWidth;
256 height = fileHeader.pixelHeight;
258 if ( width > MAX_TEXTURE_DIMENSION || height > MAX_TEXTURE_DIMENSION )
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;
277 const bool headerIsValid = signatureGood && fileEndiannessMatchesSystemEndianness && glTypeIsCompressed &&
278 glTypeSizeCompatibleWithCompressedTex && glFormatCompatibleWithCompressedTex &&
279 textureIsNot3D && textureIsNotAnArray && textureIsNotACubemap && textureHasNoMipmapLevels &&
280 glInternalFormatIsSupportedCompressedTex & keyValueDataNotTooLarge;
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);
286 // Warn if there is space wasted in the file:
287 if( fileHeader.bytesOfKeyValueData > 0U )
289 DALI_LOG_WARNING("Loading of KTX file with key/value header data requested. This should be stripped in application asset/resource build.\n");
292 return headerIsValid;
296 } // unnamed namespace
298 // File loading API entry-point:
299 bool LoadKtxHeader(FILE * const fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height)
301 KtxFileHeader fileHeader;
302 bool ret = LoadKtxHeader(fp, width, height, fileHeader);
306 // File loading API entry-point:
307 bool LoadBitmapFromKtx( FILE * const fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
309 DALI_COMPILE_TIME_ASSERT( sizeof(Byte) == 1);
310 DALI_COMPILE_TIME_ASSERT( sizeof(uint32_t) == 4);
313 DALI_LOG_ERROR( "Null file handle passed to KTX compressed bitmap file loader.\n" );
316 KtxFileHeader fileHeader;
318 // Load the header info
319 unsigned int width, height;
321 if (!LoadKtxHeader(fp, width, height, fileHeader))
326 // Skip the key-values:
327 const long int imageSizeOffset = sizeof(KtxFileHeader) + fileHeader.bytesOfKeyValueData;
328 if(fseek(fp, imageSizeOffset, SEEK_SET))
330 DALI_LOG_ERROR( "Seek past key/vals in KTX compressed bitmap file failed.\n" );
334 // Load the size of the image data:
335 uint32_t imageByteCount = 0;
336 if (fread((void*)&imageByteCount, 1, 4, fp) != 4)
338 DALI_LOG_ERROR( "Read of image size failed.\n" );
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)
346 DALI_LOG_ERROR( "KTX file with too-large image-data field.\n" );
350 Pixel::Format pixelFormat;
351 const bool pixelFormatKnown = ConvertPixelFormat(fileHeader.glInternalFormat, pixelFormat);
352 if(!pixelFormatKnown)
354 DALI_LOG_ERROR( "No internal pixel format supported for KTX file pixel format.\n" );
358 // Load up the image bytes:
359 PixelBuffer * const pixels = bitmap.GetCompressedProfile()->ReserveBufferOfSize( pixelFormat, width, height, (size_t) imageByteCount );
362 DALI_LOG_ERROR( "Unable to reserve a pixel buffer to load the requested bitmap into.\n" );
365 const size_t bytesRead = fread(pixels, 1, imageByteCount, fp);
366 if(bytesRead != imageByteCount)
368 DALI_LOG_ERROR( "Read of image pixel data failed.\n" );
372 // Ignore all input requests from image attributes and set the available metadata:
373 attributes.SetSize(width, height);
378 } // namespace TizenPlatform