1 /*
2 Copyright (c) 2014-2021 Timur Gafarov, Roman Chistokhodov
3
4 Boost Software License - Version 1.0 - August 17th, 2003
5
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28
29 /**
30 * Decode JPEG images
31 *
32 * Copyright: Timur Gafarov, Roman Chistokhodov 2014-2021.
33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).
34 * Authors: Timur Gafarov, Roman Chistokhodov
35 */
36 module dlib.image.io.tga;
37
38 import std.stdio;
39 import std.file;
40 import std.conv;
41 import dlib.core.memory;
42 import dlib.core.stream;
43 import dlib.core.compound;
44 import dlib.image.color;
45 import dlib.image.image;
46 import dlib.image.io;
47 import dlib.image.io.utils;
48 import dlib.filesystem.local;
49
50 // uncomment this to see debug messages:
51 //version = TGADebug;
52
53 struct TGAHeader
54 {
55 ubyte idLength;
56 ubyte type;
57 ubyte encoding;
58 short colmapStart;
59 short colmapLen;
60 ubyte colmapBits;
61 short xstart;
62 short ystart;
63 short width;
64 short height;
65 ubyte bpp;
66 ubyte descriptor;
67 }
68
69 enum TGAEncoding : ubyte {
70 Indexed = 1,
71 RGB = 2,
72 Grey = 3,
73 RLE_Indexed = 9,
74 RLE_RGB = 10,
75 RLE_Grey = 11
76 };
77
78 enum TgaOrigin : ubyte
79 {
80 Left = 0x00,
81 Right = 0x10,
82 Lower = 0x00,
83 Upper = 0x20
84 }
85
86 class TGALoadException: ImageLoadException
87 {
88 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
89 {
90 super(msg, file, line, next);
91 }
92 }
93
94 /**
95 * Load PNG from file using local FileSystem.
96 * Causes GC allocation
97 */
98 SuperImage loadTGA(string filename)
99 {
100 InputStream input = openForInput(filename);
101
102 try
103 {
104 return loadTGA(input);
105 }
106 catch (TGALoadException ex)
107 {
108 throw new Exception("'" ~ filename ~ "' :" ~ ex.msg, ex.file, ex.line, ex.next);
109 }
110 finally
111 {
112 input.close();
113 }
114 }
115
116 /**
117 * Load TGA from stream using default image factory.
118 * Causes GC allocation
119 */
120 SuperImage loadTGA(InputStream istrm)
121 {
122 Compound!(SuperImage, string) res =
123 loadTGA(istrm, defaultImageFactory);
124 if (res[0] is null)
125 throw new TGALoadException(res[1]);
126 else
127 return res[0];
128 }
129
130 /**
131 * Load TGA from stream using specified image factory.
132 * GC-free
133 */
134 Compound!(SuperImage, string) loadTGA(
135 InputStream istrm,
136 SuperImageFactory imgFac)
137 {
138 SuperImage img = null;
139
140 Compound!(SuperImage, string) error(string errorMsg)
141 {
142 if (img)
143 {
144 img.free();
145 img = null;
146 }
147 return compound(img, errorMsg);
148 }
149
150 TGAHeader readHeader()
151 {
152 TGAHeader hdr = readStruct!TGAHeader(istrm);
153 version(TGADebug)
154 {
155 writefln("idLength = %s", hdr.idLength);
156 writefln("type = %s", hdr.type);
157 /*
158 * Encoding flag:
159 * 1 = Raw indexed image
160 * 2 = Raw RGB
161 * 3 = Raw greyscale
162 * 9 = RLE indexed
163 * 10 = RLE RGB
164 * 11 = RLE greyscale
165 * 32 & 33 = Other compression, indexed
166 */
167 writefln("encoding = %s", hdr.encoding);
168
169 writefln("colmapStart = %s", hdr.colmapStart);
170 writefln("colmapLen = %s", hdr.colmapLen);
171 writefln("colmapBits = %s", hdr.colmapBits);
172 writefln("xstart = %s", hdr.xstart);
173 writefln("ystart = %s", hdr.ystart);
174 writefln("width = %s", hdr.width);
175 writefln("height = %s", hdr.height);
176 writefln("bpp = %s", hdr.bpp);
177 writefln("descriptor = %s", hdr.descriptor);
178 writeln("-------------------");
179 }
180 return hdr;
181 }
182
183 SuperImage readRawRGB(ref TGAHeader hdr)
184 {
185 uint channels = hdr.bpp / 8;
186 SuperImage res = imgFac.createImage(hdr.width, hdr.height, channels, 8);
187
188 if (hdr.descriptor & TgaOrigin.Upper) {
189 istrm.fillArray(res.data);
190 } else {
191 foreach(i; 0..hdr.height) {
192 istrm.fillArray(res.data[channels * hdr.width * (hdr.height-i-1)..channels * hdr.width * (hdr.height-i)]);
193 }
194 }
195
196 const ubyte alphaBits = cast(ubyte)(hdr.descriptor & 0xf);
197 version(TGADebug) {
198 writefln("Alpha bits: %s", alphaBits);
199 }
200
201 if (channels == 4) {
202 for (size_t i=0; i<res.data.length; i += channels) {
203 auto alphaIndex = i+3;
204 res.data[alphaIndex] = cast(ubyte)((res.data[alphaIndex] & ((1 << alphaBits)-1 )) << (8 - alphaBits));
205 }
206 }
207
208 return res;
209 }
210
211 SuperImage readRLERGB(ref TGAHeader hdr)
212 {
213 uint channels = hdr.bpp / 8;
214 uint imageSize = hdr.width * hdr.height * channels;
215 SuperImage res = imgFac.createImage(hdr.width, hdr.height, channels, 8);
216
217 // Calculate offset to image data
218 uint dataOffset = 18 + hdr.idLength;
219
220 // Add palette offset for indexed images
221 if (hdr.type == 1)
222 dataOffset += 768;
223
224 // Read compressed data
225 // TODO: take scanline order into account (bottom-up or top-down)
226 ubyte[] data = New!(ubyte[])(cast(uint)istrm.size - dataOffset);
227 istrm.fillArray(data);
228
229 uint ii = 0;
230 uint i = 0;
231 while (ii < imageSize)
232 {
233 ubyte b = data[i];
234 i++;
235
236 if (b & 0x80) // Run length chunk
237 {
238 // Get run length
239 uint runLength = b - 127;
240
241 // Repeat the next pixel runLength times
242 for (uint j = 0; j < runLength; j++)
243 {
244 foreach(pIndex; 0..channels)
245 res.data[ii + pIndex] = data[i + pIndex];
246
247 ii += channels;
248 }
249
250 i += channels;
251 }
252 else // Raw chunk
253 {
254 // Get run length
255 uint runLength = b + 1;
256
257 // Write the next runLength pixels directly
258 for (uint j = 0; j < runLength; j++)
259 {
260 foreach(pIndex; 0..channels)
261 res.data[ii + pIndex] = data[i + pIndex];
262
263 ii += channels;
264 i += channels;
265 }
266 }
267 }
268
269 Delete(data);
270
271 return res;
272 }
273
274 auto hdr = readHeader();
275
276 if (hdr.idLength)
277 {
278 char[] id = New!(char[])(hdr.idLength);
279 istrm.fillArray(id);
280
281 version(TGADebug)
282 {
283 writefln("id = %s", id);
284 }
285
286 Delete(id);
287 }
288
289 if (hdr.encoding == TGAEncoding.RGB) {
290 img = readRawRGB(hdr);
291 } else if (hdr.encoding == TGAEncoding.RLE_RGB) {
292 img = readRLERGB(hdr);
293 } else {
294 return error("loadTGA error: only RGB images are supported");
295 }
296
297 img.swapRGB();
298
299 return compound(img, "");
300 }
301
302 void swapRGB(SuperImage img)
303 {
304 foreach(x; 0..img.width) {
305 foreach(y; 0..img.height)
306 {
307 img[x, y] = Color4f(img[x, y].bgra);
308 }
309 }
310 }
311
312 /**
313 * Save TGA to file using local FileSystem.
314 * Causes GC allocation
315 */
316 void saveTGA(SuperImage img, string filename)
317 {
318 OutputStream output = openForOutput(filename);
319 Compound!(bool, string) res = saveTGA(img, output);
320 output.close();
321
322 if (!res[0])
323 throw new TGALoadException(res[1]);
324 }
325
326 /**
327 * Save TGA to stream.
328 * GC-free
329 */
330 Compound!(bool, string) saveTGA(SuperImage img, OutputStream output)
331 {
332 Compound!(bool, string) error(string errorMsg)
333 {
334 return compound(false, errorMsg);
335 }
336
337 enum ubyte[12] tgaStart = [0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0];
338 output.writeArray(tgaStart);
339 output.writeLE(cast(ushort)img.width);
340 output.writeLE(cast(ushort)img.height);
341
342 const bool hasAlpha = img.channels == 4;
343
344 if (img.channels == 3) {
345 output.writeLE(cast(ubyte)24);
346 output.writeLE(cast(ubyte)TgaOrigin.Upper);
347 } else if (img.channels == 4) {
348 output.writeLE(cast(ubyte)32);
349 output.writeLE(cast(ubyte)0x28);
350 } else {
351 return error("saveTGA error: unsupported number of channels");
352 }
353
354 foreach(y; 0..img.height) {
355 foreach(x; 0..img.width) {
356 ubyte[4] rgb;
357 ColorRGBA color = img[x, y].convert(8);
358 rgb[0] = cast(ubyte)color[2];
359 rgb[1] = cast(ubyte)color[1];
360 rgb[2] = cast(ubyte)color[0];
361
362 if (hasAlpha) {
363 rgb[3] = cast(ubyte)(color[3]);
364 output.writeArray(rgb[]);
365 } else {
366 output.writeArray(rgb[0..3]);
367 }
368 }
369 }
370
371 return compound(true, "");
372 }