1// tImageASTC.cpp 
2// 
3// This class knows how to load and save ARM's Adaptive Scalable Texture Compression (.astc) files. The pixel data is 
4// stored in a tLayer. If decode was requested the layer will store raw pixel data. The layer may be 'stolen'. IF it 
5// is the tImageASTC is invalid afterwards. This is purely for performance. 
6// 
7// Copyright (c) 2022-2024 Tristan Grimmer. 
8// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
9// granted, provided that the above copyright notice and this permission notice appear in all copies. 
10// 
11// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
12// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
13// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
14// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
15// PERFORMANCE OF THIS SOFTWARE. 
16 
17#include <System/tFile.h> 
18#include <System/tMachine.h> 
19#include "Image/tImageASTC.h" 
20#include "Image/tPixelUtil.h" 
21#include "Image/tPicture.h" 
22#include "astcenc.h" 
23namespace tImage 
24
25 
26 
27namespace tASTC 
28
29 struct Header 
30
31 uint8_t Magic[4]; 
32 uint8_t BlockW
33 uint8_t BlockH
34 uint8_t BlockD
35 uint8_t DimX[3]; 
36 uint8_t DimY[3]; 
37 uint8_t DimZ[3]; 
38 }; 
39 tStaticAssert(sizeof(Header) == 16); 
40 
41 tPixelFormat GetFormatFromBlockDimensions(int blockW, int blockH); 
42}; 
43 
44 
45tPixelFormat tASTC::GetFormatFromBlockDimensions(int blockW, int blockH
46
47 int pixelsPerBlock = blockW * blockH
48 switch (pixelsPerBlock
49
50 case 16: return tPixelFormat::ASTC4X4
51 case 20: return tPixelFormat::ASTC5X4
52 case 25: return tPixelFormat::ASTC5X5
53 case 30: return tPixelFormat::ASTC6X5
54 case 36: return tPixelFormat::ASTC6X6
55 case 40: return tPixelFormat::ASTC8X5
56 case 48: return tPixelFormat::ASTC8X6
57 case 50: return tPixelFormat::ASTC10X5
58 case 60: return tPixelFormat::ASTC10X6
59 case 64: return tPixelFormat::ASTC8X8
60 case 80: return tPixelFormat::ASTC10X8
61 case 100: return tPixelFormat::ASTC10X10
62 case 120: return tPixelFormat::ASTC12X10
63 case 144: return tPixelFormat::ASTC12X12
64
65 return tPixelFormat::Invalid
66
67 
68 
69bool tImageASTC::Load(const tString& astcFile, const LoadParams& params
70
71 Clear(); 
72 
73 if (tSystem::tGetFileType(file: astcFile) != tSystem::tFileType::ASTC
74 return false
75 
76 if (!tSystem::tFileExists(file: astcFile)) 
77 return false
78 
79 int numBytes = 0
80 uint8* astcFileInMemory = tSystem::tLoadFile(file: astcFile, buffer: nullptr, fileSize: &numBytes); 
81 bool success = Load(astcFileInMemory, numBytes, params); 
82 delete[] astcFileInMemory
83 
84 return success
85
86 
87 
88bool tImageASTC::Load(const uint8* astcInMemory, int numBytes, const LoadParams& paramsIn
89
90 Clear(); 
91 
92 // This will deal with zero-sized files properly as well. Basically we need 
93 // the header and at least one block of data. All ASTC blocks are 16 bytes. 
94 if (!astcInMemory || (numBytes < (sizeof(tASTC::Header)+16))) 
95 return false
96 
97 LoadParams params(paramsIn); 
98 const uint8* astcCurr = astcInMemory
99 tASTC::Header& header = *((tASTC::Header*)astcInMemory); 
100 
101 // Check the magic. 
102 if ((header.Magic[0] != 0x13) || (header.Magic[1] != 0xAB) || (header.Magic[2] != 0xA1) || (header.Magic[3] != 0x5C)) 
103 return false
104 
105 int blockW = header.BlockW
106 int blockH = header.BlockH
107 int blockD = header.BlockD
108 
109 // We only support block depth of 1 (2D blocks) -- for now. 
110 if (blockD != 1
111 return false
112 
113 int width = header.DimX[0] + (header.DimX[1] << 8) + (header.DimX[2] << 16); 
114 int height = header.DimY[0] + (header.DimY[1] << 8) + (header.DimY[2] << 16); 
115 int depth = header.DimZ[0] + (header.DimZ[1] << 8) + (header.DimZ[2] << 16); 
116 
117 // We only support depth of 1 (2D images) -- for now. 
118 if ((width <= 0) || (height <= 0) || (depth > 1)) 
119 return false
120 
121 tPixelFormat format = tASTC::GetFormatFromBlockDimensions(blockW, blockH); 
122 if (!tIsASTCFormat(format)) 
123 return false
124 PixelFormat = format
125 PixelFormatSrc = format
126 
127 const uint8* astcData = astcInMemory + sizeof(tASTC::Header); 
128 int astcDataSize = numBytes - sizeof(tASTC::Header); 
129 tAssert(!Layer); 
130 
131 // Update the colour profiles. 
132 ColourProfileSrc = (params.Profile != tColourProfile::Unspecified) ? params.Profile : tColourProfile::sRGB
133 ColourProfile = (params.Profile != tColourProfile::Unspecified) ? params.Profile : tColourProfile::sRGB
134 
135 // If we were not asked to decode we just get the data over to the Layer and we're done. 
136 if (!(params.Flags & LoadFlag_Decode)) 
137
138 Layer = new tLayer(PixelFormat, width, height, (uint8*)astcData); 
139 return true
140
141 
142 // The gamma-compression load flags only apply when decoding. If the gamma mode is auto, we determine here 
143 // whether to apply sRGB compression. If the space is linear and a format that often encodes colours, we apply it. 
144 if (params.Flags & LoadFlag_AutoGamma
145
146 // Clear all related flags. 
147 params.Flags &= ~(LoadFlag_AutoGamma | LoadFlag_SRGBCompression | LoadFlag_GammaCompression); 
148 if (tMath::tIsProfileLinearInRGB(profile: params.Profile)) 
149 params.Flags |= LoadFlag_SRGBCompression
150
151 
152 tColour4f* decoded4f = nullptr
153 tColour4b* decoded4b = nullptr
154 
155 // ASTC data is always decoded into a float buffer. 
156 DecodeResult result = tImage::DecodePixelData_ASTC(format, data: astcData, dataSize: astcDataSize, w: width, h: height, decoded4f); 
157 if (result != DecodeResult::Success
158 return false
159 
160 // Apply any decode flags. 
161 tAssert(decoded4f); 
162 bool flagTone = (params.Flags & tImageASTC::LoadFlag_ToneMapExposure) ? true : false
163 bool flagSRGB = (params.Flags & tImageASTC::LoadFlag_SRGBCompression) ? true : false
164 bool flagGama = (params.Flags & tImageASTC::LoadFlag_GammaCompression)? true : false
165 if (flagTone || flagSRGB || flagGama
166
167 for (int p = 0; p < width*height; p++) 
168
169 tColour4f& colour = decoded4f[p]; 
170 if (flagTone
171 colour.TonemapExposure(exposure: params.Exposure, chans: tCompBit_RGB); 
172 if (flagSRGB
173 colour.LinearToSRGB(chans: tCompBit_RGB); 
174 if (flagGama
175 colour.LinearToGamma(gamma: params.Gamma, chans: tCompBit_RGB); 
176
177
178 
179 // We're decoded. Need to update the current colour profile. 
180 if (flagSRGB) ColourProfile = tColourProfile::sRGB
181 if (flagGama) ColourProfile = tColourProfile::gRGB
182 
183 // Converts to RGBA32 into the decoded4b array. Cleans up the float buffer. 
184 tAssert(!decoded4b); 
185 decoded4b = new tColour4b[width*height]; 
186 for (int p = 0; p < width*height; p++) 
187 decoded4b[p].Set(decoded4f[p]); 
188 delete[] decoded4f
189 
190 // Give decoded4b to layer. 
191 tAssert(!Layer); 
192 Layer = new tLayer(tPixelFormat::R8G8B8A8, width, height, (uint8*)decoded4b, true); 
193 tAssert(Layer->OwnsData); 
194 
195 // We've got one more chance to reverse the rows here (if we still need to) because we were asked to decode. 
196 if (params.Flags & tImageASTC::LoadFlag_ReverseRowOrder
197
198 // This shouldn't ever fail. Too easy to reverse RGBA 32-bit. 
199 uint8* reversedRowData = tImage::CreateReversedRowData(pixelData: Layer->Data, pixelDataFormat: Layer->PixelFormat, numBlocksW: width, numBlocksH: height); 
200 tAssert(reversedRowData); 
201 delete[] Layer->Data
202 Layer->Data = reversedRowData
203
204 
205 // Update the current pixel format -- but not the source format. 
206 PixelFormat = tPixelFormat::R8G8B8A8
207 
208 tAssert(IsValid()); 
209 return true
210
211 
212 
213bool tImageASTC::Set(tPixel4b* pixels, int width, int height, bool steal
214
215 Clear(); 
216 if (!pixels || (width <= 0) || (height <= 0)) 
217 return false
218 
219 Layer = new tLayer(tPixelFormat::R8G8B8A8, width, height, (uint8*)pixels, steal); 
220 
221 PixelFormatSrc = tPixelFormat::R8G8B8A8
222 PixelFormat = tPixelFormat::R8G8B8A8
223 ColourProfileSrc = tColourProfile::sRGB; // We assume pixels must be sRGB. 
224 ColourProfile = tColourProfile::sRGB
225 
226 return true
227
228 
229 
230bool tImageASTC::Set(tFrame* frame, bool steal
231
232 Clear(); 
233 if (!frame || !frame->IsValid()) 
234 return false
235 
236 PixelFormatSrc = frame->PixelFormatSrc
237 PixelFormat = tPixelFormat::R8G8B8A8
238 ColourProfileSrc = tColourProfile::sRGB; // We assume frame must be sRGB. 
239 ColourProfile = tColourProfile::sRGB
240 
241 tPixel4b* pixels = frame->GetPixels(steal); 
242 Set(pixels, width: frame->Width, height: frame->Height, steal); 
243 if (steal
244 delete frame
245 
246 return true
247
248 
249 
250bool tImageASTC::Set(tPicture& picture, bool steal
251
252 Clear(); 
253 if (!picture.IsValid()) 
254 return false
255 
256 PixelFormatSrc = picture.PixelFormatSrc
257 PixelFormat = tPixelFormat::R8G8B8A8
258 // We don't know colour profile of tPicture. 
259 
260 // This is worth some explanation. If steal is true the picture becomes invalid and the 
261 // 'set' call will steal the stolen pixels. If steal is false GetPixels is called and the 
262 // 'set' call will memcpy them out... which makes sure the picture is still valid after and 
263 // no-one is sharing the pixel buffer. We don't check the success of 'set' because it must 
264 // succeed if picture was valid. 
265 tPixel4b* pixels = steal ? picture.StealPixels() : picture.GetPixels(); 
266 bool success = Set(pixels, width: picture.GetWidth(), height: picture.GetHeight(), steal); 
267 tAssert(success); 
268 return true
269
270 
271 
272tFrame* tImageASTC::GetFrame(bool steal
273
274 // Data must be decoded for this to work. 
275 if (!IsValid() || (PixelFormat != tPixelFormat::R8G8B8A8)) 
276 return nullptr
277 
278 tFrame* frame = new tFrame(); 
279 frame->Width = Layer->Width
280 frame->Height = Layer->Height
281 frame->PixelFormatSrc = PixelFormatSrc
282 
283 if (steal
284
285 frame->Pixels = (tPixel4b*)Layer->StealData(); 
286 delete Layer
287 Layer = nullptr
288
289 else 
290
291 frame->Pixels = new tPixel4b[frame->Width * frame->Height]; 
292 tStd::tMemcpy(dest: frame->Pixels, src: (tPixel4b*)Layer->Data, numBytes: frame->Width * frame->Height * sizeof(tPixel4b)); 
293
294 
295 return frame
296
297 
298 
299bool tImageASTC::IsOpaque() const 
300
301 if (!IsValid()) 
302 return false
303  
304 if (Layer->PixelFormat == tPixelFormat::R8G8B8A8
305
306 tPixel4b* pixels = (tPixel4b*)Layer->Data
307 for (int p = 0; p < (Layer->Width * Layer->Height); p++) 
308
309 if (pixels[p].A < 255
310 return false
311
312
313 
314 return false
315
316 
317 
318
319