1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** $Id: r_tex_png.cpp 4297 2010-06-03 22:49:00Z firebrand_kh $
11 //**
12 //** Copyright (C) 1999-2006 Jānis Legzdiņš
13 //**
14 //** This program is free software; you can redistribute it and/or
15 //** modify it under the terms of the GNU General Public License
16 //** as published by the Free Software Foundation; either version 2
17 //** of the License, or (at your option) any later version.
18 //**
19 //** This program is distributed in the hope that it will be useful,
20 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 //** GNU General Public License for more details.
23 //**
24 //**************************************************************************
25
26 // HEADER FILES ------------------------------------------------------------
27
28 #if defined(CLIENT) || !defined(SERVER)
29 #include <png.h>
30 #endif
31
32 #include "gamedefs.h"
33 #include "r_tex.h"
34
35 // MACROS ------------------------------------------------------------------
36
37 // This one is missing in older versions of libpng
38 #ifndef png_jmpbuf
39 #define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
40 #endif
41
42 // TYPES -------------------------------------------------------------------
43
44 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
45
46 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
47
48 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
49
50 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
51
52 // PUBLIC DATA DEFINITIONS -------------------------------------------------
53
54 // PRIVATE DATA DEFINITIONS ------------------------------------------------
55
56 // CODE --------------------------------------------------------------------
57
58 //==========================================================================
59 //
60 // VPngTexture::Create
61 //
62 //==========================================================================
63
Create(VStream & Strm,int LumpNum)64 VTexture* VPngTexture::Create(VStream& Strm, int LumpNum)
65 {
66 guard(VPngTexture::Create);
67 if (Strm.TotalSize() < 29)
68 {
69 // File is too small.
70 return NULL;
71 }
72
73 vuint8 Id[8];
74
75 // Verify signature.
76 Strm.Seek(0);
77 Strm.Serialise(Id, 8);
78 if (Id[0] != 137 || Id[1] != 'P' || Id[2] != 'N' || Id[3] != 'G' ||
79 Id[4] != 13 || Id[5] != 10 || Id[6] != 26 || Id[7] != 10)
80 {
81 // Not a PNG file.
82 return NULL;
83 }
84
85 // Make sure it's followed by an image header.
86 Strm.Serialise(Id, 8);
87 if (Id[0] != 0 || Id[1] != 0 || Id[2] != 0 || Id[3] != 13 ||
88 Id[4] != 'I' || Id[5] != 'H' || Id[6] != 'D' || Id[7] != 'R')
89 {
90 // Assume it's a corupted file.
91 return NULL;
92 }
93
94 // Read image info.
95 vint32 Width;
96 vint32 Height;
97 vuint8 BitDepth;
98 vuint8 ColourType;
99 vuint8 Compression;
100 vuint8 Filter;
101 vuint8 Interlace;
102 vuint32 CRC;
103 Strm.SerialiseBigEndian(&Width, 4);
104 Strm.SerialiseBigEndian(&Height, 4);
105 Strm << BitDepth << ColourType << Compression << Filter << Interlace;
106 Strm << CRC;
107
108 // Scan other chunks looking for grAb chunk with offsets
109 vint32 SOffset = 0;
110 vint32 TOffset = 0;
111 while (Strm.TotalSize() - Strm.Tell() >= 12)
112 {
113 vuint32 Len;
114 Strm.SerialiseBigEndian(&Len, 4);
115 Strm.Serialise(Id, 4);
116 if (Id[0] == 'g' && Id[1] == 'r' && Id[2] == 'A' && Id[3] == 'b')
117 {
118 Strm.SerialiseBigEndian(&SOffset, 4);
119 Strm.SerialiseBigEndian(&TOffset, 4);
120 if (SOffset < -32768 || SOffset > 32767)
121 {
122 GCon->Logf("S-offset for PNG texture %s is bad: %d (0x%08x)",
123 *W_LumpName(LumpNum), SOffset, SOffset);
124 SOffset = 0;
125 }
126 if (TOffset < -32768 || TOffset > 32767)
127 {
128 GCon->Logf("T-offset for PNG texture %s is bad: %d (0x%08x)",
129 *W_LumpName(LumpNum), TOffset, TOffset);
130 TOffset = 0;
131 }
132 }
133 else
134 {
135 Strm.Seek(Strm.Tell() + Len);
136 }
137 Strm << CRC;
138 }
139
140 return new VPngTexture(LumpNum, Width, Height, SOffset, TOffset);
141 unguard;
142 }
143
144 //==========================================================================
145 //
146 // VPngTexture::VPngTexture
147 //
148 //==========================================================================
149
VPngTexture(int ALumpNum,int AWidth,int AHeight,int ASOffset,int ATOffset)150 VPngTexture::VPngTexture(int ALumpNum, int AWidth, int AHeight, int ASOffset,
151 int ATOffset)
152 : Pixels(0)
153 {
154 SourceLump = ALumpNum;
155 Name = W_LumpName(SourceLump);
156 Width = AWidth;
157 Height = AHeight;
158 SOffset = ASOffset;
159 TOffset = ATOffset;
160 }
161
162 //==========================================================================
163 //
164 // VPngTexture::~VPngTexture
165 //
166 //==========================================================================
167
~VPngTexture()168 VPngTexture::~VPngTexture()
169 {
170 guard(VPngTexture::~VPngTexture);
171 if (Pixels)
172 {
173 delete[] Pixels;
174 Pixels = NULL;
175 }
176 unguard;
177 }
178
179 //==========================================================================
180 //
181 // ReadFunc
182 //
183 //==========================================================================
184
185 #ifdef CLIENT
ReadFunc(png_structp png,png_bytep data,png_size_t len)186 static void ReadFunc(png_structp png, png_bytep data, png_size_t len)
187 {
188 guard(ReadFunc);
189 VStream* Strm = (VStream*)png_get_io_ptr(png);
190 Strm->Serialise(data, len);
191 unguard;
192 }
193 #endif
194
195 //==========================================================================
196 //
197 // VPngTexture::GetPixels
198 //
199 //==========================================================================
200
GetPixels()201 vuint8* VPngTexture::GetPixels()
202 {
203 guard(VPngTexture::GetPixels);
204 #ifdef CLIENT
205 // If we already have loaded pixels, return them.
206 if (Pixels)
207 {
208 return Pixels;
209 }
210
211 // Create reading structure.
212 png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
213 NULL, NULL, NULL);
214 if (!png_ptr)
215 {
216 Sys_Error("Couldn't create png_ptr");
217 }
218
219 // Create info structure.
220 png_infop info_ptr = png_create_info_struct(png_ptr);
221 if (!info_ptr)
222 {
223 Sys_Error("Couldn't create info_ptr");
224 }
225
226 // Create end info structure.
227 png_infop end_info = png_create_info_struct(png_ptr);
228 if (!end_info)
229 {
230 Sys_Error("Couldn't create end_info");
231 }
232
233 // Set up error handling.
234 if (setjmp(png_jmpbuf(png_ptr)))
235 {
236 Sys_Error("Error reading PNG file");
237 }
238
239 // Open stream.
240 VStream* Strm = W_CreateLumpReaderNum(SourceLump);
241
242 // Verify signature.
243 png_byte Signature[8];
244 Strm->Seek(0);
245 Strm->Serialise(Signature, 8);
246 if (png_sig_cmp(Signature, 0, 8))
247 {
248 Sys_Error("%s is not a valid PNG file", *Name);
249 }
250
251 // Set my read function.
252 Strm->Seek(0);
253 png_set_read_fn(png_ptr, Strm, ReadFunc);
254
255 // Read image info.
256 png_read_info(png_ptr, info_ptr);
257 Width = png_get_image_width(png_ptr, info_ptr);
258 Height = png_get_image_height(png_ptr, info_ptr);
259 int BitDepth = png_get_bit_depth(png_ptr, info_ptr);
260 int ColourType = png_get_color_type(png_ptr, info_ptr);
261
262 // Set up transformations.
263 if (ColourType == PNG_COLOR_TYPE_PALETTE)
264 {
265 png_set_palette_to_rgb(png_ptr);
266 }
267 if (ColourType == PNG_COLOR_TYPE_GRAY && BitDepth < 8)
268 {
269 png_set_expand_gray_1_2_4_to_8(png_ptr);
270 }
271 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
272 {
273 png_set_tRNS_to_alpha(png_ptr);
274 }
275 if (BitDepth == 16)
276 {
277 png_set_strip_16(png_ptr);
278 }
279 if (ColourType == PNG_COLOR_TYPE_PALETTE ||
280 ColourType == PNG_COLOR_TYPE_RGB ||
281 ColourType == PNG_COLOR_TYPE_GRAY)
282 {
283 png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);
284 }
285 if (ColourType == PNG_COLOR_TYPE_GRAY ||
286 ColourType == PNG_COLOR_TYPE_GRAY_ALPHA)
287 {
288 png_set_gray_to_rgb(png_ptr);
289 }
290
291 // Set up unpacking buffer and row pointers.
292 Format = TEXFMT_RGBA;
293 Pixels = new vuint8[Width * Height * 4];
294 png_bytep* RowPtrs = new png_bytep[Height];
295 for (int i = 0; i < Height; i++)
296 {
297 RowPtrs[i] = Pixels + i * Width * 4;
298 }
299 png_read_image(png_ptr, RowPtrs);
300
301 // Finish reading.
302 png_read_end(png_ptr, end_info);
303 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
304
305 delete[] RowPtrs;
306 RowPtrs = NULL;
307
308 // Free memory.
309 delete Strm;
310 Strm = NULL;
311 return Pixels;
312 #else
313 Sys_Error("ReadPixels on dedicated server");
314 return NULL;
315 #endif
316 unguard;
317 }
318
319 //==========================================================================
320 //
321 // VPngTexture::Unload
322 //
323 //==========================================================================
324
Unload()325 void VPngTexture::Unload()
326 {
327 guard(VPngTexture::Unload);
328 if (Pixels)
329 {
330 delete[] Pixels;
331 Pixels = NULL;
332 }
333 unguard;
334 }
335
336 //==========================================================================
337 //
338 // WritePNG
339 //
340 //==========================================================================
341
342 #ifdef CLIENT
WritePNG(const VStr & FileName,const void * Data,int Width,int Height,int Bpp,bool Bot2top)343 void WritePNG(const VStr& FileName, const void* Data, int Width, int Height,
344 int Bpp, bool Bot2top)
345 {
346 guard(WritePNG);
347 VStream* Strm = FL_OpenFileWrite(FileName);
348 if (!Strm)
349 {
350 GCon->Log("Couldn't write png");
351 return;
352 }
353
354 // Create writing structure.
355 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
356 NULL, NULL, NULL);
357 if (!png_ptr)
358 {
359 Sys_Error("Couldn't create png_ptr");
360 }
361
362 // Create info structure.
363 png_infop info_ptr = png_create_info_struct(png_ptr);
364 if (!info_ptr)
365 {
366 Sys_Error("Couldn't create info_ptr");
367 }
368
369 // Set up error handling.
370 if (setjmp(png_jmpbuf(png_ptr)))
371 {
372 Sys_Error("Error writing PNG file");
373 }
374
375 // Set my read function.
376 png_set_write_fn(png_ptr, Strm, ReadFunc, NULL);
377
378 png_set_IHDR(png_ptr, info_ptr, Width, Height, 8,
379 Bpp == 8 ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_RGB,
380 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
381 if (Bpp == 8)
382 {
383 png_set_PLTE(png_ptr, info_ptr, (png_colorp)r_palette, 256);
384 }
385 png_write_info(png_ptr, info_ptr);
386
387 TArray<png_bytep> RowPointers;
388 RowPointers.SetNum(Height);
389 for (int i = 0; i < Height; i++)
390 {
391 RowPointers[i] = ((byte*)Data) + (Bot2top ? Height - i - 1 : i) *
392 Width * (Bpp / 8);
393 }
394 png_write_image(png_ptr, RowPointers.Ptr());
395
396 png_write_end(png_ptr, NULL);
397 png_destroy_write_struct(&png_ptr, &info_ptr);
398
399 Strm->Close();
400 delete Strm;
401 Strm = NULL;
402 unguard;
403 }
404 #endif
405