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