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