1 (*
2  * Hedgewars, a free turn based strategy game
3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *)
18 
19 {$INCLUDE "options.inc"}
20 
21 unit uMisc;
22 interface
23 
24 uses SDLh, uConsts, GLunit, uTypes;
25 
26 procedure initModule;
27 procedure freeModule;
28 
doSurfaceConversionnull29 function  doSurfaceConversion(tmpsurf: PSDL_Surface): PSDL_Surface;
MakeScreenshotnull30 function MakeScreenshot(filename: shortstring; k: LongInt; dump: LongWord): boolean;
GetTeamStatStringnull31 function  GetTeamStatString(p: PTeam): shortstring;
SDL_RectMakenull32 function  SDL_RectMake(x, y, width, height: LongInt): TSDL_Rect; inline;
33 
34 implementation
35 uses uVariables, uUtils
36      {$IFDEF PNG_SCREENSHOTS}, PNGh, png {$ENDIF};
37 
38 type PScreenshot = ^TScreenshot;
39      TScreenshot = record
40          buffer: PByte;
41          filename: shortstring;
42          width, height: LongInt;
43          size: QWord;
44          end;
45 
46 var conversionFormat : PSDL_PixelFormat;
47 
48 {$IFDEF PNG_SCREENSHOTS}
49 // this funtion will be executed in separate thread
SaveScreenshotnull50 function SaveScreenshot(screenshot: pointer): LongInt; cdecl; export;
51 var i: LongInt;
52     png_ptr: ^png_struct;
53     info_ptr: ^png_info;
54     f: File;
55     image: PScreenshot;
56 begin
57 image:= PScreenshot(screenshot);
58 
59 png_ptr := png_create_write_struct(png_get_libpng_ver(nil), nil, nil, nil);
60 if png_ptr = nil then
61 begin
62     // AddFileLog('Error: Could not create png write struct.');
63     SaveScreenshot:= 0;
64     exit;
65 end;
66 
67 info_ptr := png_create_info_struct(png_ptr);
68 if info_ptr = nil then
69 begin
70     png_destroy_write_struct(@png_ptr, nil);
71     // AddFileLog('Error: Could not create png info struct.');
72     SaveScreenshot:= 0;
73     exit;
74 end;
75 
76 {$IOCHECKS OFF}
77 Assign(f, image^.filename);
78 Rewrite(f, 1);
79 if IOResult = 0 then
80     begin
81     png_init_pascal_io(png_ptr,@f);
82     png_set_IHDR(png_ptr, info_ptr, image^.width, image^.height,
83                  8, // bit depth
84                  PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
85                  PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
86     png_write_info(png_ptr, info_ptr);
87     // glReadPixels and libpng number rows in different order
88     for i:= image^.height-1 downto 0 do
89         png_write_row(png_ptr, image^.buffer + i*4*image^.width);
90     png_write_end(png_ptr, info_ptr);
91     Close(f);
92     end;
93 {$IOCHECKS ON}
94 
95 // free everything
96 png_destroy_write_struct(@png_ptr, @info_ptr);
97 FreeMem(image^.buffer, image^.size);
98 Dispose(image);
99 SaveScreenshot:= 0;
100 end;
101 
102 {$ELSE} //PNG_SCREENSHOTS
103 {$IFDEF WINDOWS}
SaveScreenshotnull104 function SaveScreenshot(screenshot: pointer): LongInt; cdecl; export;
105 var
106     surface: PSDL_Surface;
107     image: PScreenshot;
108     rowBuffer: PByte;
109     row, stride: LongInt;
110 begin
111     image:= PScreenshot(screenshot);
112     stride:= image^.width * 4;
113     rowBuffer:= PByte(GetMem(stride));
114 
115     for row:= 0 to (image^.height div 2) - 1 do
116         begin
117         Move((image^.buffer + (image^.height - row - 1) * stride)^,
118              rowBuffer^,
119              stride);
120         Move((image^.buffer + row * stride)^,
121              (image^.buffer + (image^.height - row - 1) * stride)^,
122              stride);
123         Move(rowBuffer^,
124              (image^.buffer + row * stride)^,
125              stride);
126         end;
127 
128     surface:= SDL_CreateRGBSurfaceFrom(
129         image^.buffer,
130         image^.width, image^.height, 32, stride,
131         $000000FF, $0000FF00, $00FF0000, $FF000000);
132 
133     if surface <> nil then
134         begin
135         IMG_SavePNG(surface, Str2PChar(image^.filename));
136         SDL_FreeSurface(surface);
137         end;
138 
139     FreeMem(rowBuffer, stride);
140     FreeMem(image^.buffer, image^.size);
141     Dispose(image);
142     SaveScreenshot:= 0;
143 end;
144 {$ELSE} //WINDOWS
145 // this funtion will be executed in separate thread
SaveScreenshotnull146 function SaveScreenshot(screenshot: pointer): LongInt; cdecl; export;
147 var f: file;
148     // Windows Bitmap Header
149     head: array[0..53] of Byte = (
150     $42, $4D,       // identifier ("BM")
151     0, 0, 0, 0,     // file size
152     0, 0, 0, 0,     // reserved
153     54, 0, 0, 0,    // starting offset
154     40, 0, 0, 0,    // header size
155     0, 0, 0, 0,     // width
156     0, 0, 0, 0,     // height
157     1, 0,           // color planes
158     32, 0,          // bit depth
159     0, 0, 0, 0,     // compression method (uncompressed)
160     0, 0, 0, 0,     // image size
161     96, 0, 0, 0,    // horizontal resolution
162     96, 0, 0, 0,    // vertical resolution
163     0, 0, 0, 0,     // number of colors (all)
164     0, 0, 0, 0      // number of important colors
165     );
166     image: PScreenshot;
167     size: QWord;
168     writeResult:LongInt;
169 begin
170 image:= PScreenshot(screenshot);
171 
172 size:= image^.Width*image^.Height*4;
173 
174 head[$02]:= (size + 54) and $ff;
175 head[$03]:= ((size + 54) shr 8) and $ff;
176 head[$04]:= ((size + 54) shr 16) and $ff;
177 head[$05]:= ((size + 54) shr 24) and $ff;
178 head[$12]:= image^.Width and $ff;
179 head[$13]:= (image^.Width shr 8) and $ff;
180 head[$14]:= (image^.Width shr 16) and $ff;
181 head[$15]:= (image^.Width shr 24) and $ff;
182 head[$16]:= image^.Height and $ff;
183 head[$17]:= (image^.Height shr 8) and $ff;
184 head[$18]:= (image^.Height shr 16) and $ff;
185 head[$19]:= (image^.Height shr 24) and $ff;
186 head[$22]:= size and $ff;
187 head[$23]:= (size shr 8) and $ff;
188 head[$24]:= (size shr 16) and $ff;
189 head[$25]:= (size shr 24) and $ff;
190 
191 {$IOCHECKS OFF}
192 Assign(f, image^.filename);
193 Rewrite(f, 1);
194 if IOResult = 0 then
195     begin
196     writeResult:= 0; // suppress fpc hint
197     BlockWrite(f, head, sizeof(head), writeResult);
198     BlockWrite(f, image^.buffer^, size, writeResult);
199     Close(f);
200     end
201 else
202     begin
203     //AddFileLog('Error: Could not write to ' + filename);
204     end;
205 {$IOCHECKS ON}
206 
207 // free everything
208 FreeMem(image^.buffer, image^.size);
209 Dispose(image);
210 SaveScreenshot:= 0;
211 end;
212 
213 {$ENDIF} // WINDOWS
214 {$ENDIF} // PNG_SCREENSHOTS
215 
216 {$IFDEF USE_VIDEO_RECORDING}
217 // make image k times smaller (useful for saving thumbnails)
218 procedure ReduceImage(img: PByteArray; width, height, k: LongInt);
219 var i, j, i0, j0, w, h, r, g, b: LongInt;
220 begin
221     w:= width  div k;
222     h:= height div k;
223 
224     // rescale inplace
225     if k <> 1 then
226     begin
227         for i:= 0 to h-1 do
228             for j:= 0 to w-1 do
229             begin
230                 r:= 0;
231                 g:= 0;
232                 b:= 0;
233                 for i0:= 0 to k-1 do
234                     for j0:= 0 to k-1 do
235                     begin
236                         inc(r, img^[4*(width*(i*k+i0) + j*k + j0)+0]);
237                         inc(g, img^[4*(width*(i*k+i0) + j*k + j0)+1]);
238                         inc(b, img^[4*(width*(i*k+i0) + j*k + j0)+2]);
239                     end;
240                 img^[4*(w*i + j)+0]:= r div (k*k);
241                 img^[4*(w*i + j)+1]:= g div (k*k);
242                 img^[4*(w*i + j)+2]:= b div (k*k);
243                 img^[4*(w*i + j)+3]:= 255;
244             end;
245     end;
246 end;
247 {$ENDIF}
248 
249 // captures and saves the screen. returns true on success.
250 // saved image will be k times smaller than original (useful for saving thumbnails).
MakeScreenshotnull251 function MakeScreenshot(filename: shortstring; k: LongInt; dump: LongWord): boolean;
252 var p: Pointer;
253     size: QWord;
254     image: PScreenshot;
255     format: GLenum;
256     ext: string[4];
257     x,y: LongWord;
258 begin
259 {$IFDEF PNG_SCREENSHOTS}
260 format:= GL_RGBA;
261 ext:= '.png';
262 {$ELSE}
263 {$IFDEF WINDOWS}
264 format:= GL_RGBA;
265 ext:= '.png';
266 {$ELSE}
267 format:= GL_BGRA;
268 ext:= '.bmp';
269 {$ENDIF}
270 {$ENDIF}
271 
272 if dump > 0 then
273      size:= LAND_WIDTH*LAND_HEIGHT*4
274 else size:= toPowerOf2(cScreenWidth) * toPowerOf2(cScreenHeight) * 4;
275 p:= GetMem(size); // will be freed in SaveScreenshot()
276 
277 // memory could not be allocated
278 if p = nil then
279 begin
280     AddFileLog('Error: Could not allocate memory for screenshot.');
281     MakeScreenshot:= false;
282     exit;
283 end;
284 
285 // read pixels from land array
286 if dump > 0 then
287     begin
288     for y:= 0 to LAND_HEIGHT-1 do
289         for x:= 0 to LAND_WIDTH-1 do
290             if dump = 2 then
291                 PLongWordArray(p)^[y*LAND_WIDTH+x]:= LandPixels[LAND_HEIGHT-1-y, x]
292             else
293                 begin
294                 if Land[LAND_HEIGHT-1-y, x] and lfIndestructible = lfIndestructible then
295                     PLongWordArray(p)^[y*LAND_WIDTH+x]:= (AMask or RMask)
296                 else if Land[LAND_HEIGHT-1-y, x] and lfIce = lfIce then
297                     PLongWordArray(p)^[y*LAND_WIDTH+x]:= (AMask or BMask)
298                 else if Land[LAND_HEIGHT-1-y, x] and lfBouncy = lfBouncy then
299                     PLongWordArray(p)^[y*LAND_WIDTH+x]:= (AMask or GMask)
300                 else if Land[LAND_HEIGHT-1-y, x] and lfObject = lfObject then
301                     PLongWordArray(p)^[y*LAND_WIDTH+x]:= $FFFFFFFF
302                 else if Land[LAND_HEIGHT-1-y, x] and lfBasic = lfBasic then
303                     PLongWordArray(p)^[y*LAND_WIDTH+x]:= AMask
304                 else
305                     PLongWordArray(p)^[y*LAND_WIDTH+x]:= 0
306                 end
307     end
308 else
309 // read pixels from the front buffer
310     begin
311     glReadPixels(0, 0, cScreenWidth, cScreenHeight, format, GL_UNSIGNED_BYTE, p);
312 {$IFDEF USE_VIDEO_RECORDING}
313     ReduceImage(p, cScreenWidth, cScreenHeight, k)
314 {$ENDIF}
315     end;
316 
317 // allocate and fill structure that will be passed to new thread
318 New(image); // will be disposed in SaveScreenshot()
319 if dump = 2 then
320      image^.filename:= shortstring(UserPathPrefix) + filename + '_landpixels' + ext
321 else if dump = 1 then
322      image^.filename:= shortstring(UserPathPrefix) + filename + '_land' + ext
323 else image^.filename:= shortstring(UserPathPrefix) + filename + ext;
324 
325 if dump <> 0 then
326     begin
327     image^.width:= LAND_WIDTH;
328     image^.height:= LAND_HEIGHT
329     end
330 else
331     begin
332     image^.width:= cScreenWidth div k;
333     image^.height:= cScreenHeight div k
334     end;
335 image^.size:= size;
336 image^.buffer:= p;
337 
338 SDL_CreateThread(@SaveScreenshot, PChar('snapshot'), image);
339 MakeScreenshot:= true; // possibly it is not true but we will not wait for thread to terminate
340 end;
341 
342 // http://www.idevgames.com/forums/thread-5602-post-21860.html#pid21860
doSurfaceConversionnull343 function doSurfaceConversion(tmpsurf: PSDL_Surface): PSDL_Surface;
344 var convertedSurf: PSDL_Surface;
345 begin
346     doSurfaceConversion:= tmpsurf;
347     if ((tmpsurf^.format^.bitsperpixel = 32) and (tmpsurf^.format^.rshift > tmpsurf^.format^.bshift)) or
348        (tmpsurf^.format^.bitsperpixel = 24) then
349     begin
350         convertedSurf:= SDL_ConvertSurface(tmpsurf, conversionFormat, SDL_SWSURFACE);
351         SDL_FreeSurface(tmpsurf);
352         doSurfaceConversion:= convertedSurf;
353     end;
354 end;
355 
SDL_RectMakenull356 function SDL_RectMake(x, y, width, height: LongInt): TSDL_Rect; inline;
357 begin
358     SDL_RectMake.x:= x;
359     SDL_RectMake.y:= y;
360     SDL_RectMake.w:= width;
361     SDL_RectMake.h:= height;
362 end;
363 
GetTeamStatStringnull364 function GetTeamStatString(p: PTeam): shortstring;
365 var s: shortstring;
366 begin
367     s:= p^.TeamName + ':' + IntToStr(p^.TeamHealth) + ':';
368     GetTeamStatString:= s;
369 end;
370 
371 procedure initModule;
372 const SDL_PIXELFORMAT_ABGR8888 = (1 shl 28) or (6 shl 24) or (7 shl 20) or (6 shl 16) or (32 shl 8) or 4;
373 begin
374     conversionFormat:= SDL_AllocFormat(SDL_PIXELFORMAT_ABGR8888);
375 end;
376 
377 procedure freeModule;
378 begin
379     SDL_FreeFormat(conversionFormat);
380 end;
381 
382 end.
383