1// tImageQOI.cpp 
2// 
3// This class knows how to load and save Quite OK Images (.qoi) files into tPixel arrays. These tPixels may be 'stolen' 
4// by the tPicture's constructor if a targa file is specified. After the array is stolen the tImageQOI is invalid. This 
5// 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 "Image/tImageQOI.h" 
19#include "Image/tPicture.h" 
20#define QOI_NO_STDIO 
21#define QOI_IMPLEMENTATION 
22#include <QOI/qoi.h> 
23namespace tImage 
24
25 
26 
27bool tImageQOI::Load(const tString& qoiFile
28
29 Clear(); 
30 
31 if (tSystem::tGetFileType(file: qoiFile) != tSystem::tFileType::QOI
32 return false
33 
34 if (!tSystem::tFileExists(file: qoiFile)) 
35 return false
36 
37 int numBytes = 0
38 uint8* qoiFileInMemory = tSystem::tLoadFile(file: qoiFile, buffer: nullptr, fileSize: &numBytes); 
39 bool success = Load(qoiFileInMemory, numBytes); 
40 delete[] qoiFileInMemory
41 
42 return success
43
44 
45 
46bool tImageQOI::Load(const uint8* qoiFileInMemory, int numBytes
47
48 Clear(); 
49 if ((numBytes <= 0) || !qoiFileInMemory
50 return false
51 
52 // Decode a QOI image from memory. The function either returns NULL on failure (invalid parameters or malloc failed) 
53 // or a pointer to the decoded pixels. On success, the qoi_desc struct is filled with the description from the file 
54 // header. The returned pixel data should be free()d after use. 
55 qoi_desc results
56 void* reversedPixels = qoi_decode(data: qoiFileInMemory, size: numBytes, desc: &results, channels: 4); 
57 if (!reversedPixels
58 return false
59 
60 Width = results.width;  
61 Height = results.height
62 PixelFormatSrc = (results.channels == 3) ? tPixelFormat::R8G8B8 : tPixelFormat::R8G8B8A8
63 PixelFormat = tPixelFormat::R8G8B8A8
64 ColourProfileSrc = (results.colorspace == QOI_LINEAR) ? tColourProfile::lRGB : tColourProfile::sRGB
65 ColourProfile = ColourProfileSrc
66 tAssert((Width > 0) && (Height > 0)); 
67 
68 // Reverse rows. 
69 Pixels = new tPixel4b[Width*Height]; 
70 int bytesPerRow = Width*4
71 for (int y = Height-1; y >= 0; y--) 
72 tStd::tMemcpy(dest: (uint8*)Pixels + ((Height-1)-y)*bytesPerRow, src: (uint8*)reversedPixels + y*bytesPerRow, numBytes: bytesPerRow); 
73 free(ptr: reversedPixels); 
74 
75 return true
76
77 
78 
79bool tImageQOI::Set(tPixel4b* pixels, int width, int height, bool steal
80
81 Clear(); 
82 if (!pixels || (width <= 0) || (height <= 0)) 
83 return false
84 
85 Width = width
86 Height = height
87 
88 if (steal
89
90 Pixels = pixels
91
92 else 
93
94 Pixels = new tPixel4b[Width*Height]; 
95 tStd::tMemcpy(dest: Pixels, src: pixels, numBytes: Width*Height*sizeof(tPixel4b)); 
96
97 
98 PixelFormatSrc = tPixelFormat::R8G8B8A8
99 PixelFormat = tPixelFormat::R8G8B8A8
100 ColourProfileSrc = tColourProfile::sRGB; // We assume pixels must be sRGB. 
101 ColourProfile = tColourProfile::sRGB
102 
103 return true
104
105 
106 
107bool tImageQOI::Set(tFrame* frame, bool steal
108
109 Clear(); 
110 if (!frame || !frame->IsValid()) 
111 return false
112 
113 PixelFormatSrc = frame->PixelFormatSrc
114 PixelFormat = tPixelFormat::R8G8B8A8
115 ColourProfileSrc = tColourProfile::sRGB; // We assume frame must be sRGB. 
116 ColourProfile = tColourProfile::sRGB
117 
118 Set(pixels: frame->GetPixels(steal), width: frame->Width, height: frame->Height, steal); 
119 if (steal
120 delete frame
121 
122 return true
123
124 
125 
126bool tImageQOI::Set(tPicture& picture, bool steal
127
128 Clear(); 
129 if (!picture.IsValid()) 
130 return false
131 
132 PixelFormatSrc = picture.PixelFormatSrc
133 PixelFormat = tPixelFormat::R8G8B8A8
134 // We don't know colour profile of tPicture. 
135 
136 // This is worth some explanation. If steal is true the picture becomes invalid and the 
137 // 'set' call will steal the stolen pixels. If steal is false GetPixels is called and the 
138 // 'set' call will memcpy them out... which makes sure the picture is still valid after and 
139 // no-one is sharing the pixel buffer. We don't check the success of 'set' because it must 
140 // succeed if picture was valid. 
141 tPixel4b* pixels = steal ? picture.StealPixels() : picture.GetPixels(); 
142 bool success = Set(pixels, width: picture.GetWidth(), height: picture.GetHeight(), steal); 
143 tAssert(success); 
144 return true
145
146 
147 
148tFrame* tImageQOI::GetFrame(bool steal
149
150 if (!IsValid()) 
151 return nullptr
152 
153 tFrame* frame = new tFrame(); 
154 frame->PixelFormatSrc = PixelFormatSrc
155 
156 if (steal
157
158 frame->StealFrom(src: Pixels, width: Width, height: Height); 
159 Pixels = nullptr
160
161 else 
162
163 frame->Set(srcPixels: Pixels, width: Width, height: Height); 
164
165 
166 return frame
167
168 
169 
170tImageQOI::tFormat tImageQOI::Save(const tString& qoiFile, tFormat format, tColourProfile profile) const 
171
172 SaveParams params
173 params.Format = format
174 params.ColourProfile = profile
175 return Save(qoiFile, params); 
176
177 
178 
179tImageQOI::tFormat tImageQOI::Save(const tString& qoiFile, const SaveParams& params) const 
180
181 tFormat format = params.Format
182 tColourProfile profile = params.ColourProfile
183 if (!IsValid() || (format == tFormat::Invalid)) 
184 return tFormat::Invalid
185 
186 if (tSystem::tGetFileType(file: qoiFile) != tSystem::tFileType::QOI
187 return tFormat::Invalid
188 
189 if (format == tFormat::Auto
190
191 if (IsOpaque()) 
192 format = tFormat::BPP24
193 else 
194 format = tFormat::BPP32
195
196 if (profile == tColourProfile::Auto
197 profile = ColourProfileSrc
198 
199 tFileHandle file = tSystem::tOpenFile(file: qoiFile.Chr(), mode: "wb"); 
200 if (!file
201 return tFormat::Invalid
202 
203 qoi_desc qoiDesc
204 qoiDesc.channels = (format == tFormat::BPP24) ? 3 : 4
205 
206 // This also catches space being set to invalid. Basically if it's not linear, it's sRGB. 
207 qoiDesc.colorspace = (profile == tColourProfile::lRGB) ? QOI_LINEAR : QOI_SRGB
208 qoiDesc.height = Height
209 qoiDesc.width = Width
210 
211 // No matter the format, we need to reverse the rows before saving. 
212 tPixel4b* reversedRows = new tPixel4b[Width*Height]; 
213 int bytesPerRow = Width*4
214 for (int y = Height-1; y >= 0; y--) 
215 tStd::tMemcpy(dest: (uint8*)reversedRows + ((Height-1)-y)*bytesPerRow, src: (uint8*)Pixels + y*bytesPerRow, numBytes: bytesPerRow); 
216 
217 // If we're saving in 24bit we need to convert our source data to 24bit. 
218 uint8* pixels = (uint8*)reversedRows
219 bool deletePixels = false
220 if (format == tFormat::BPP24
221
222 int numPixels = Width*Height
223 pixels = new uint8[numPixels*3]; 
224 for (int p = 0; p < numPixels; p++) 
225
226 pixels[p*3 + 0] = reversedRows[p].R
227 pixels[p*3 + 1] = reversedRows[p].G
228 pixels[p*3 + 2] = reversedRows[p].B
229
230 deletePixels = true
231
232 
233 // Encode raw RGB or RGBA pixels into a QOI image in memory. The function either returns NULL on failure (invalid 
234 // parameters or malloc failed) or a pointer to the encoded data on success. On success the out_len is set to the 
235 // size in bytes of the encoded data. The returned qoi data should be free()d after use. 
236 int outLength = 0
237 void* memImage = qoi_encode(data: pixels, desc: &qoiDesc, out_len: &outLength); 
238 if (deletePixels
239 delete[] pixels
240 delete[] reversedRows
241 
242 if (!memImage
243 return tFormat::Invalid
244  
245 tAssert(outLength); 
246 int numWritten = tSystem::tWriteFile(handle: file, buffer: memImage, sizeBytes: outLength); 
247 tSystem::tCloseFile(f: file); 
248 free(ptr: memImage); 
249  
250 if (numWritten != outLength
251 return tFormat::Invalid
252 
253 return format
254
255 
256 
257bool tImageQOI::IsOpaque() const 
258
259 for (int p = 0; p < (Width*Height); p++) 
260
261 if (Pixels[p].A < 255
262 return false
263
264 
265 return true
266
267 
268 
269tPixel4b* tImageQOI::StealPixels() 
270
271 tPixel4b* pixels = Pixels
272 Pixels = nullptr
273 Width = 0
274 Height = 0
275  
276 return pixels
277
278 
279 
280
281