Use static_assert instead of DALI_COMPILE_TIME_ASSERT
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-astc.cpp
1 /*
2  * Copyright (c) 2020 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-astc.h>
20
21 // EXTERNAL INCLUDES
22 #include <cstring>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/images/pixel.h>
25 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
26 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
27
28 namespace Dali
29 {
30 namespace TizenPlatform
31 {
32 namespace
33 {
34
35 // Max width or height of an image.
36 const unsigned MAX_TEXTURE_DIMENSION = 4096;
37 // Max bytes of image data allowed. Not a precise number, just a sanity check.
38 const unsigned MAX_IMAGE_DATA_SIZE = MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION;
39
40 // Minimum and maximum possible sizes for ASTC blocks.
41 const unsigned int MINIMUM_ASTC_BLOCK_SIZE = 4;
42 const unsigned int MAXIMUM_ASTC_BLOCK_SIZE = 12;
43
44 typedef uint8_t Byte;
45
46 // This bytes identify an ASTC native file.
47 const Byte FileIdentifier[] = {
48    0x13, 0xAB, 0xA1, 0x5C
49 };
50
51
52 /**
53  * @brief This struct defines the ASTC file header values. From ASTC specifications.
54  * Packed attribute stops the structure from being aligned to compiler defaults
55  * so we can be sure of reading the whole header from file in one call to fread().
56  * Note: members to not conform to coding standards in order to be consistent with ASTC spec.
57  */
58 struct AstcFileHeader
59 {
60   unsigned char magic[ 4 ];
61   unsigned char blockdim_x;
62   unsigned char blockdim_y;
63   unsigned char blockdim_z;
64   unsigned char xsize[ 3 ];
65   unsigned char ysize[ 3 ];
66   unsigned char zsize[ 3 ];
67 } __attribute__ ( (__packed__));
68
69 using namespace Pixel;
70
71 /**
72  * @brief This table allows fast conversion from an ASTC block size ([height][width]) to a pixel format.
73  * This could be done within a switch, but this way we have a constant time function.
74  * Note: As 4 is the minimum block size, 4 is subtracted from both the width and height to optimise size.
75  * IE. Table format is: Increasing order of block width from left-to-right:  4 -> 12
76  *                      Increasing order of block height from top-to-bottom: 4 -> 12
77  */
78 Pixel::Format AstcLinearBlockSizeToPixelFormatTable[][( MAXIMUM_ASTC_BLOCK_SIZE - MINIMUM_ASTC_BLOCK_SIZE ) + 1] = {
79     { COMPRESSED_RGBA_ASTC_4x4_KHR, COMPRESSED_RGBA_ASTC_5x4_KHR, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID },
80     { INVALID, COMPRESSED_RGBA_ASTC_5x5_KHR, COMPRESSED_RGBA_ASTC_6x5_KHR, INVALID, COMPRESSED_RGBA_ASTC_8x5_KHR, INVALID, COMPRESSED_RGBA_ASTC_10x5_KHR, INVALID, INVALID },
81     { INVALID, INVALID, COMPRESSED_RGBA_ASTC_6x6_KHR, INVALID, COMPRESSED_RGBA_ASTC_8x6_KHR, INVALID, COMPRESSED_RGBA_ASTC_10x6_KHR, INVALID, INVALID },
82     { INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID },
83     { INVALID, INVALID, INVALID, INVALID, COMPRESSED_RGBA_ASTC_8x8_KHR, INVALID, COMPRESSED_RGBA_ASTC_10x8_KHR, INVALID, INVALID },
84     { INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID },
85     { INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, COMPRESSED_RGBA_ASTC_10x10_KHR, INVALID, COMPRESSED_RGBA_ASTC_12x10_KHR },
86     { INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID },
87     { INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, INVALID, COMPRESSED_RGBA_ASTC_12x12_KHR }
88 };
89
90 /**
91  * @brief Uses header information to return the respective ASTC pixel format.
92  *
93  * @param[in] header A populated AstcFileHeader struct
94  * @return    The pixel format, or INVALID if the block size was invalid
95  */
96 Pixel::Format GetAstcPixelFormat( AstcFileHeader& header )
97 {
98   // Check the block size is valid. This will also prevent an invalid read from the conversion table.
99   if( ( header.blockdim_x < MINIMUM_ASTC_BLOCK_SIZE ) || ( header.blockdim_x > MAXIMUM_ASTC_BLOCK_SIZE ) ||
100       ( header.blockdim_y < MINIMUM_ASTC_BLOCK_SIZE ) || ( header.blockdim_y > MAXIMUM_ASTC_BLOCK_SIZE ) )
101   {
102     return Pixel::INVALID;
103   }
104
105   // Read the equivalent pixel format from the conversion table.
106   return AstcLinearBlockSizeToPixelFormatTable[ header.blockdim_y - MINIMUM_ASTC_BLOCK_SIZE ][ header.blockdim_x - MINIMUM_ASTC_BLOCK_SIZE ];
107 }
108
109 /**
110  * @brief Internal method to load ASTC header info from a file.
111  *
112  * @param[in]  filePointer The file pointer to the ASTC file to read
113  * @param[out] width       The width is output to this value
114  * @param[out] height      The height is output to this value
115  * @param[out] fileHeader  This will be populated with the header data
116  * @return                 True if the file is valid, false otherwise
117  */
118 bool LoadAstcHeader( FILE * const filePointer, unsigned int& width, unsigned int& height, AstcFileHeader& fileHeader )
119 {
120   // Pull the bytes of the file header in as a block:
121   const unsigned int readLength = sizeof( AstcFileHeader );
122   if( fread( &fileHeader, 1, readLength, filePointer ) != readLength )
123   {
124     return false;
125   }
126
127   // Check the header contains the ASTC native file identifier.
128   bool headerIsValid = memcmp( fileHeader.magic, FileIdentifier, sizeof( fileHeader.magic ) ) == 0;
129   if( !headerIsValid )
130   {
131     DALI_LOG_ERROR( "File is not a valid ASTC native file\n" );
132     // Return here as otherwise, if not a valid ASTC file, we are likely to pick up other header errors spuriously.
133     return false;
134   }
135
136   // Convert the 3-byte values for width and height to a single resultant value.
137   width = fileHeader.xsize[0] | ( fileHeader.xsize[1] << 8 ) | ( fileHeader.xsize[2] << 16 );
138   height = fileHeader.ysize[0] | ( fileHeader.ysize[1] << 8 ) | ( fileHeader.ysize[2] << 16 );
139
140   const unsigned int zDepth = static_cast<unsigned int>( fileHeader.zsize[0] )
141                               + ( static_cast<unsigned int>( fileHeader.zsize[1] ) << 8 )
142                               + ( static_cast<unsigned int>( fileHeader.zsize[2] ) << 16 );
143
144   // Check image dimensions are within limits.
145   if( ( width > MAX_TEXTURE_DIMENSION ) || ( height > MAX_TEXTURE_DIMENSION ) )
146   {
147     DALI_LOG_ERROR( "ASTC file has larger than supported dimensions: %d,%d\n", width, height );
148     headerIsValid = false;
149   }
150
151   // Confirm the ASTC block does not have any Z depth.
152   if( zDepth != 1 )
153   {
154     DALI_LOG_ERROR( "ASTC files with z size other than 1 are not supported. Z size is: %d\n", zDepth );
155     headerIsValid = false;
156   }
157
158   return headerIsValid;
159 }
160
161 } // Unnamed namespace.
162
163
164 // File loading API entry-point:
165 bool LoadAstcHeader( const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height )
166 {
167   AstcFileHeader fileHeader;
168   return LoadAstcHeader( input.file, width, height, fileHeader );
169 }
170
171 // File loading API entry-point:
172 bool LoadBitmapFromAstc( const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap )
173 {
174   FILE* const filePointer = input.file;
175   if( !filePointer )
176   {
177     DALI_LOG_ERROR( "Null file handle passed to ASTC compressed bitmap file loader.\n" );
178     return false;
179   }
180
181   // Load the header info.
182   AstcFileHeader fileHeader;
183   unsigned int width, height;
184
185   if( !LoadAstcHeader( filePointer, width, height, fileHeader ) )
186   {
187     DALI_LOG_ERROR( "Could not load ASTC Header from file.\n" );
188     return false;
189   }
190
191   // Retrieve the pixel format from the ASTC block size.
192   Pixel::Format pixelFormat = GetAstcPixelFormat( fileHeader );
193   if( pixelFormat == Pixel::INVALID )
194   {
195     DALI_LOG_ERROR( "No internal pixel format supported for ASTC file pixel format.\n" );
196     return false;
197   }
198
199   // Retrieve the file size.
200   if( fseek( filePointer, 0L, SEEK_END ) )
201   {
202     DALI_LOG_ERROR( "Could not seek through file.\n" );
203     return false;
204   }
205
206   off_t fileSize = ftell( filePointer );
207   if( fileSize == -1L )
208   {
209     DALI_LOG_ERROR( "Could not determine ASTC file size.\n" );
210     return false;
211   }
212
213   if( fseek( filePointer, sizeof( AstcFileHeader ), SEEK_SET ) )
214   {
215     DALI_LOG_ERROR( "Could not seek through file.\n" );
216     return false;
217   }
218
219   // Data size is file size - header size.
220   size_t imageByteCount = fileSize - sizeof( AstcFileHeader );
221
222   // Sanity-check the image data is not too large and that it is at less than 2 bytes per texel:
223   if( ( imageByteCount > MAX_IMAGE_DATA_SIZE ) || ( imageByteCount > ( ( static_cast< size_t >( width ) * height ) << 1 ) ) )
224   {
225     DALI_LOG_ERROR( "ASTC file has too large image-data field.\n" );
226     return false;
227   }
228
229   // allocate pixel data
230   bitmap = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
231
232   // Compressed format won't allocate the buffer
233   auto pixels = bitmap.GetBuffer();
234   if( !pixels )
235   {
236     // allocate buffer manually
237     auto& impl = GetImplementation( bitmap );
238     impl.AllocateFixedSize( imageByteCount );
239     pixels = bitmap.GetBuffer();
240   }
241
242   // Load the image data.
243   const size_t bytesRead = fread( pixels, 1, imageByteCount, filePointer );
244
245   // Check the size of loaded data is what we expected.
246   if( bytesRead != imageByteCount )
247   {
248     DALI_LOG_ERROR( "Read of image pixel data failed.\n" );
249     return false;
250   }
251
252   return true;
253 }
254
255 } // namespace TizenPlatform
256
257 } // namespace Dali