1// tImageTIFF.cpp 
2// 
3// This knows how to load/save TIFFs. It knows the details of the tiff file format and loads the data into multiple 
4// tPixel arrays, one for each frame (in a TIFF thay are called pages). These arrays may be 'stolen' by tPictures. 
5// 
6// Copyright (c) 2020-2024 Tristan Grimmer. 
7// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
8// granted, provided that the above copyright notice and this permission notice appear in all copies. 
9// 
10// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
11// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
12// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
13// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
14// PERFORMANCE OF THIS SOFTWARE. 
15 
16#include <Foundation/tVersion.cmake.h> 
17#include <Foundation/tStandard.h> 
18#include <Foundation/tString.h> 
19#include <System/tFile.h> 
20#include <System/tScript.h> 
21#include "Image/tImageTIFF.h" 
22#include "Image/tPicture.h" 
23#include "LibTIFF/include/tiff.h" 
24#include "LibTIFF/include/tiffio.h" 
25#include "LibTIFF/include/tiffvers.h" 
26using namespace tSystem
27namespace tImage 
28
29 
30 
31int tImageTIFF::ReadSoftwarePageDuration(TIFF* tiff) const 
32
33 void* data = nullptr
34 int durationMilliSec = -1
35 int success = TIFFGetField(tif: tiff, TIFFTAG_SOFTWARE, &data); 
36 if (!success || !data
37 return durationMilliSec
38 
39 tString softwareStr((char*)data); 
40 tExprReader script(softwareStr, false); 
41 tExpression tacentView = script.First(); 
42 if (tacentView.GetAtomString() == "TacentLibrary"
43
44 tExpression tacentVers = tacentView.Next(); 
45 tExpression durationEx = tacentVers.Next(); 
46 tExpression durCmd = durationEx.Item0(); 
47 if (durCmd.GetAtomString() == "PageDur"
48
49 tExpression durVal = durationEx.Item1(); 
50 durationMilliSec = durVal.GetAtomInt(); 
51
52
53 
54 return durationMilliSec
55
56 
57 
58bool tImageTIFF::WriteSoftwarePageDuration(TIFF* tiff, int milliseconds) const 
59
60 tString softwareDesc
61 tsPrintf(dest&: softwareDesc, format: "TacentLibrary V%d.%d.%d [PageDur %d]", tVersion::Major, tVersion::Minor, tVersion::Revision, milliseconds); 
62 int success = TIFFSetField(tiff, TIFFTAG_SOFTWARE, softwareDesc.Chr()); 
63 return success ? true : false
64
65 
66 
67bool tImageTIFF::Load(const tString& tiffFile
68
69 Clear(); 
70 
71 if (tSystem::tGetFileType(file: tiffFile) != tSystem::tFileType::TIFF
72 return false
73 
74 if (!tFileExists(file: tiffFile)) 
75 return false
76 
77 TIFF* tiff = TIFFOpen(tiffFile.Chr(), "rb"); 
78 if (!tiff
79 return false
80 
81 // Create all frames. 
82 tPixelFormat srcFormat = tPixelFormat::R8G8B8A8
83 do 
84
85 int width = 0; int height = 0
86 TIFFGetField(tif: tiff, TIFFTAG_IMAGEWIDTH, &width); 
87 TIFFGetField(tif: tiff, TIFFTAG_IMAGELENGTH, &height); 
88 if ((width <= 0) || (height <= 0)) 
89 break
90 
91 int numPixels = width*height
92 int durationMilliSeconds = ReadSoftwarePageDuration(tiff); 
93 
94 uint32* pixels = (uint32*)_TIFFmalloc(s: numPixels * sizeof(uint32)); 
95 int successCode = TIFFReadRGBAImage(tiff, width, height, pixels, 0); 
96 if (!successCode
97
98 _TIFFfree(p: pixels); 
99 break
100
101 
102 tFrame* frame = new tFrame
103 frame->Width = width
104 frame->Height = height
105 frame->Pixels = new tPixel4b[width*height]; 
106 frame->PixelFormatSrc = srcFormat
107 
108 // If duration not set we use a default of 1 second. 
109 frame->Duration = (durationMilliSeconds >= 0) ? float(durationMilliSeconds)/1000.0f : 1.0f
110 
111 for (int p = 0; p < width*height; p++) 
112 frame->Pixels[p] = pixels[p]; 
113 
114 _TIFFfree(p: pixels); 
115 Frames.Append(item: frame); 
116 } while (TIFFReadDirectory(tif: tiff)); 
117 
118 TIFFClose(tif: tiff); 
119 if (Frames.GetNumItems() == 0
120 return false
121 
122 PixelFormatSrc = srcFormat
123 PixelFormat = tPixelFormat::R8G8B8A8
124 
125 // TIFF files are assumed to be in sRGB. 
126 ColourProfileSrc = tColourProfile::sRGB
127 ColourProfile = tColourProfile::sRGB
128 
129 return true
130
131 
132 
133bool tImageTIFF::Set(tList<tFrame>& srcFrames, bool stealFrames
134
135 Clear(); 
136 if (srcFrames.GetNumItems() <= 0
137 return false
138 
139 PixelFormatSrc = srcFrames.Head()->PixelFormatSrc
140 PixelFormat = tPixelFormat::R8G8B8A8
141 ColourProfileSrc = tColourProfile::sRGB; // We assume srcFrames must be sRGB. 
142 ColourProfile = tColourProfile::sRGB
143 
144 if (stealFrames
145
146 while (tFrame* frame = srcFrames.Remove()) 
147 Frames.Append(item: frame); 
148
149 else 
150
151 for (tFrame* frame = srcFrames.Head(); frame; frame = frame->Next()) 
152 Frames.Append(item: new tFrame(*frame)); 
153
154 
155 return true
156
157 
158 
159bool tImageTIFF::Set(tPixel4b* pixels, int width, int height, bool steal
160
161 Clear(); 
162 if (!pixels || (width <= 0) || (height <= 0)) 
163 return false
164 
165 tFrame* frame = new tFrame(); 
166 if (steal
167 frame->StealFrom(src: pixels, width, height); 
168 else 
169 frame->Set(srcPixels: pixels, width, height); 
170 Frames.Append(item: frame); 
171 
172 PixelFormatSrc = tPixelFormat::R8G8B8A8
173 PixelFormat = tPixelFormat::R8G8B8A8
174 ColourProfileSrc = tColourProfile::sRGB; // We assume pixels must be sRGB. 
175 ColourProfile = tColourProfile::sRGB
176 
177 return true
178
179 
180 
181bool tImageTIFF::Set(tFrame* frame, bool steal
182
183 Clear(); 
184 if (!frame || !frame->IsValid()) 
185 return false
186 
187 PixelFormatSrc = frame->PixelFormatSrc
188 PixelFormat = tPixelFormat::R8G8B8A8
189 ColourProfileSrc = tColourProfile::sRGB; // We assume frame must be sRGB. 
190 ColourProfile = tColourProfile::sRGB
191 
192 if (steal
193 Frames.Append(item: frame); 
194 else 
195 Frames.Append(item: new tFrame(*frame)); 
196 
197 return true
198
199 
200 
201bool tImageTIFF::Set(tPicture& picture, bool steal
202
203 Clear(); 
204 if (!picture.IsValid()) 
205 return false
206 
207 PixelFormatSrc = picture.PixelFormatSrc
208 PixelFormat = tPixelFormat::R8G8B8A8
209 // We don't know colour profile of tPicture. 
210 
211 // This is worth some explanation. If steal is true the picture becomes invalid and the 
212 // 'set' call will steal the stolen pixels. If steal is false GetPixels is called and the 
213 // 'set' call will memcpy them out... which makes sure the picture is still valid after and 
214 // no-one is sharing the pixel buffer. We don't check the success of 'set' because it must 
215 // succeed if picture was valid. 
216 tPixel4b* pixels = steal ? picture.StealPixels() : picture.GetPixels(); 
217 bool success = Set(pixels, width: picture.GetWidth(), height: picture.GetHeight(), steal); 
218 tAssert(success); 
219 return true
220
221 
222 
223tFrame* tImageTIFF::GetFrame(bool steal
224
225 if (!IsValid()) 
226 return nullptr
227 
228 return steal ? Frames.Remove() : new tFrame( *Frames.First() ); 
229
230 
231 
232bool tImageTIFF::IsOpaque() const 
233
234 for (tFrame* frame = Frames.Head(); frame; frame = frame->Next()) 
235 if (!frame->IsOpaque()) 
236 return false
237 
238 return true
239
240 
241 
242bool tImageTIFF::Save(const tString& tiffFilename, tFormat format, bool useZLibComp, int overrideFrameDuration) const 
243
244 SaveParams params
245 params.Format = format
246 params.UseZLibCompression = useZLibComp
247 params.OverrideFrameDuration = overrideFrameDuration
248 return Save(tiffFile: tiffFilename, params); 
249
250 
251 
252bool tImageTIFF::Save(const tString& tiffFile, const SaveParams& params) const 
253
254 if (!IsValid() || (params.Format == tFormat::Invalid)) 
255 return false
256 
257 if (tSystem::tGetFileType(file: tiffFile) != tSystem::tFileType::TIFF
258 return false
259 
260 TIFF* tiff = TIFFOpen(tiffFile.Chr(), "wb"); 
261 if (!tiff
262 return false
263 
264 int rowSize = 0
265 uint8* rowBuf = nullptr
266 for (tFrame* frame = Frames.First(); frame; frame = frame->Next()) 
267
268 // Writes image from last loop and starts a new directory. 
269 if (frame != Frames.First()) 
270 TIFFWriteDirectory(tiff); 
271 
272 bool isOpaque = frame->IsOpaque(); 
273 int bytesPerPixel = 0
274 switch (params.Format
275
276 case tFormat::Auto: bytesPerPixel = isOpaque ? 3 : 4; break
277 case tFormat::BPP24: bytesPerPixel = 3; break
278 case tFormat::BPP32: bytesPerPixel = 4; break
279
280 tAssert(bytesPerPixel); 
281 
282 int w = frame->Width
283 int h = frame->Height
284 
285 TIFFSetField(tiff, TIFFTAG_IMAGEWIDTH, w); 
286 TIFFSetField(tiff, TIFFTAG_IMAGELENGTH, h); 
287 TIFFSetField(tiff, TIFFTAG_SAMPLESPERPIXEL, bytesPerPixel); 
288 TIFFSetField(tiff, TIFFTAG_BITSPERSAMPLE, 8); 
289 TIFFSetField(tiff, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); 
290 TIFFSetField(tiff, TIFFTAG_COMPRESSION, params.UseZLibCompression ? COMPRESSION_DEFLATE : COMPRESSION_NONE); 
291 TIFFSetField(tiff, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); 
292 TIFFSetField(tiff, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); 
293 if (bytesPerPixel == 4
294
295 // Unassociated alpha means the extra channel is not a premultiplied alpha channel. 
296 uint16 extraSampleTypes[] = { EXTRASAMPLE_UNASSALPHA }; 
297 TIFFSetField(tiff, TIFFTAG_EXTRASAMPLES, tNumElements(extraSampleTypes), extraSampleTypes); 
298
299 
300 int pageDurMilliSec = (params.OverrideFrameDuration >= 0) ? params.OverrideFrameDuration : int(frame->Duration*1000.0f); 
301 WriteSoftwarePageDuration(tiff, milliseconds: pageDurMilliSec); 
302 
303 int rowSizeNeeded = TIFFScanlineSize(tif: tiff); 
304 tAssert(rowSizeNeeded == (w*bytesPerPixel)); 
305 
306 // Let's not reallocate the line buffer every frame. Often all frames will be the same size. 
307 if (rowSize != rowSizeNeeded
308
309 if (rowBuf
310 _TIFFfree(p: rowBuf); 
311 rowBuf = (uint8*)_TIFFmalloc(s: rowSizeNeeded); 
312 rowSize = rowSizeNeeded
313
314 
315 TIFFSetField(tiff, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif: tiff, request: w*bytesPerPixel)); 
316 for (int r = 0; r < h; r++) 
317
318 for (int x = 0; x < w; x++) 
319
320 int idx = (h-r-1)*w + x
321 rowBuf[x*bytesPerPixel + 0] = frame->Pixels[idx].R
322 rowBuf[x*bytesPerPixel + 1] = frame->Pixels[idx].G
323 rowBuf[x*bytesPerPixel + 2] = frame->Pixels[idx].B
324 if (bytesPerPixel == 4
325 rowBuf[x*bytesPerPixel + 3] = frame->Pixels[idx].A
326
327 
328 int errCode = TIFFWriteScanline(tif: tiff, buf: rowBuf, row: r, sample: 0); 
329 if (errCode < 0
330 continue
331
332
333 
334 if (rowBuf
335 _TIFFfree(p: rowBuf); 
336 TIFFClose(tif: tiff); 
337 
338 return true
339
340 
341 
342
343