screenshot.cpp

Go to the documentation of this file.
00001 /* $Id: screenshot.cpp 18756 2010-01-08 03:17:12Z glx $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "stdafx.h"
00013 #include "openttd.h"
00014 #include "fileio_func.h"
00015 #include "viewport_func.h"
00016 #include "gfx_func.h"
00017 #include "screenshot.h"
00018 #include "blitter/factory.hpp"
00019 #include "zoom_func.h"
00020 #include "core/endian_func.hpp"
00021 #include "map_func.h"
00022 #include "saveload/saveload.h"
00023 #include "company_func.h"
00024 #include "strings_func.h"
00025 #include "gui.h"
00026 
00027 #include "table/strings.h"
00028 
00029 
00030 char _screenshot_format_name[8];
00031 uint _num_screenshot_formats;
00032 uint _cur_screenshot_format;
00033 static char _screenshot_name[128];
00034 char _full_screenshot_name[MAX_PATH];
00035 
00036 /* called by the ScreenShot proc to generate screenshot lines. */
00037 typedef void ScreenshotCallback(void *userdata, void *buf, uint y, uint pitch, uint n);
00038 typedef bool ScreenshotHandlerProc(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette);
00039 
00040 struct ScreenshotFormat {
00041   const char *name;
00042   const char *extension;
00043   ScreenshotHandlerProc *proc;
00044 };
00045 
00046 /*************************************************
00047  **** SCREENSHOT CODE FOR WINDOWS BITMAP (.BMP)
00048  *************************************************/
00049 #if defined(_MSC_VER) || defined(__WATCOMC__)
00050 #pragma pack(push, 1)
00051 #endif
00052 
00054 struct BitmapFileHeader {
00055   uint16 type;
00056   uint32 size;
00057   uint32 reserved;
00058   uint32 off_bits;
00059 } GCC_PACK;
00060 assert_compile(sizeof(BitmapFileHeader) == 14);
00061 
00062 #if defined(_MSC_VER) || defined(__WATCOMC__)
00063 #pragma pack(pop)
00064 #endif
00065 
00067 struct BitmapInfoHeader {
00068   uint32 size;
00069   int32 width, height;
00070   uint16 planes, bitcount;
00071   uint32 compression, sizeimage, xpels, ypels, clrused, clrimp;
00072 };
00073 assert_compile(sizeof(BitmapInfoHeader) == 40);
00074 
00076 struct RgbQuad {
00077   byte blue, green, red, reserved;
00078 };
00079 assert_compile(sizeof(RgbQuad) == 4);
00080 
00092 static bool MakeBMPImage(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
00093 {
00094   uint bpp; // bytes per pixel
00095   switch (pixelformat) {
00096     case 8:  bpp = 1; break;
00097     /* 32bpp mode is saved as 24bpp BMP */
00098     case 32: bpp = 3; break;
00099     /* Only implemented for 8bit and 32bit images so far */
00100     default: return false;
00101   }
00102 
00103   FILE *f = fopen(name, "wb");
00104   if (f == NULL) return false;
00105 
00106   /* Each scanline must be aligned on a 32bit boundary */
00107   uint bytewidth = Align(w * bpp, 4); // bytes per line in file
00108 
00109   /* Size of palette. Only present for 8bpp mode */
00110   uint pal_size = pixelformat == 8 ? sizeof(RgbQuad) * 256 : 0;
00111 
00112   /* Setup the file header */
00113   BitmapFileHeader bfh;
00114   bfh.type = TO_LE16('MB');
00115   bfh.size = TO_LE32(sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + pal_size + bytewidth * h);
00116   bfh.reserved = 0;
00117   bfh.off_bits = TO_LE32(sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + pal_size);
00118 
00119   /* Setup the info header */
00120   BitmapInfoHeader bih;
00121   bih.size = TO_LE32(sizeof(BitmapInfoHeader));
00122   bih.width = TO_LE32(w);
00123   bih.height = TO_LE32(h);
00124   bih.planes = TO_LE16(1);
00125   bih.bitcount = TO_LE16(bpp * 8);
00126   bih.compression = 0;
00127   bih.sizeimage = 0;
00128   bih.xpels = 0;
00129   bih.ypels = 0;
00130   bih.clrused = 0;
00131   bih.clrimp = 0;
00132 
00133   /* Write file header and info header */
00134   if (fwrite(&bfh, sizeof(bfh), 1, f) != 1 || fwrite(&bih, sizeof(bih), 1, f) != 1) {
00135     fclose(f);
00136     return false;
00137   }
00138 
00139   if (pixelformat == 8) {
00140     /* Convert the palette to the windows format */
00141     RgbQuad rq[256];
00142     for (uint i = 0; i < 256; i++) {
00143       rq[i].red   = palette[i].r;
00144       rq[i].green = palette[i].g;
00145       rq[i].blue  = palette[i].b;
00146       rq[i].reserved = 0;
00147     }
00148     /* Write the palette */
00149     if (fwrite(rq, sizeof(rq), 1, f) != 1) {
00150       fclose(f);
00151       return false;
00152     }
00153   }
00154 
00155   /* Try to use 64k of memory, store between 16 and 128 lines */
00156   uint maxlines = Clamp(65536 / (w * pixelformat / 8), 16, 128); // number of lines per iteration
00157 
00158   uint8 *buff = MallocT<uint8>(maxlines * w * pixelformat / 8); // buffer which is rendered to
00159   uint8 *line = AllocaM(uint8, bytewidth); // one line, stored to file
00160   memset(line, 0, bytewidth);
00161 
00162   /* Start at the bottom, since bitmaps are stored bottom up */
00163   do {
00164     uint n = min(h, maxlines);
00165     h -= n;
00166 
00167     /* Render the pixels */
00168     callb(userdata, buff, h, w, n);
00169 
00170     /* Write each line */
00171     while (n-- != 0) {
00172       if (pixelformat == 8) {
00173         /* Move to 'line', leave last few pixels in line zeroed */
00174         memcpy(line, buff + n * w, w);
00175       } else {
00176         /* Convert from 'native' 32bpp to BMP-like 24bpp.
00177          * Works for both big and little endian machines */
00178         Colour *src = ((Colour *)buff) + n * w;
00179         byte *dst = line;
00180         for (uint i = 0; i < w; i++) {
00181           dst[i * 3    ] = src[i].b;
00182           dst[i * 3 + 1] = src[i].g;
00183           dst[i * 3 + 2] = src[i].r;
00184         }
00185       }
00186       /* Write to file */
00187       if (fwrite(line, bytewidth, 1, f) != 1) {
00188         free(buff);
00189         fclose(f);
00190         return false;
00191       }
00192     }
00193   } while (h != 0);
00194 
00195   free(buff);
00196   fclose(f);
00197 
00198   return true;
00199 }
00200 
00201 /*********************************************************
00202  **** SCREENSHOT CODE FOR PORTABLE NETWORK GRAPHICS (.PNG)
00203  *********************************************************/
00204 #if defined(WITH_PNG)
00205 #include <png.h>
00206 
00207 static void PNGAPI png_my_error(png_structp png_ptr, png_const_charp message)
00208 {
00209   DEBUG(misc, 0, "[libpng] error: %s - %s", message, (const char *)png_get_error_ptr(png_ptr));
00210   longjmp(png_jmpbuf(png_ptr), 1);
00211 }
00212 
00213 static void PNGAPI png_my_warning(png_structp png_ptr, png_const_charp message)
00214 {
00215   DEBUG(misc, 1, "[libpng] warning: %s - %s", message, (const char *)png_get_error_ptr(png_ptr));
00216 }
00217 
00218 static bool MakePNGImage(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
00219 {
00220   png_color rq[256];
00221   FILE *f;
00222   uint i, y, n;
00223   uint maxlines;
00224   uint bpp = pixelformat / 8;
00225   png_structp png_ptr;
00226   png_infop info_ptr;
00227 
00228   /* only implemented for 8bit and 32bit images so far. */
00229   if (pixelformat != 8 && pixelformat != 32) return false;
00230 
00231   f = fopen(name, "wb");
00232   if (f == NULL) return false;
00233 
00234   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (void *)name, png_my_error, png_my_warning);
00235 
00236   if (png_ptr == NULL) {
00237     fclose(f);
00238     return false;
00239   }
00240 
00241   info_ptr = png_create_info_struct(png_ptr);
00242   if (info_ptr == NULL) {
00243     png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
00244     fclose(f);
00245     return false;
00246   }
00247 
00248   if (setjmp(png_jmpbuf(png_ptr))) {
00249     png_destroy_write_struct(&png_ptr, &info_ptr);
00250     fclose(f);
00251     return false;
00252   }
00253 
00254   png_init_io(png_ptr, f);
00255 
00256   png_set_filter(png_ptr, 0, PNG_FILTER_NONE);
00257 
00258   png_set_IHDR(png_ptr, info_ptr, w, h, 8, pixelformat == 8 ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_RGB,
00259     PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
00260 
00261   if (pixelformat == 8) {
00262     /* convert the palette to the .PNG format. */
00263     for (i = 0; i != 256; i++) {
00264       rq[i].red   = palette[i].r;
00265       rq[i].green = palette[i].g;
00266       rq[i].blue  = palette[i].b;
00267     }
00268 
00269     png_set_PLTE(png_ptr, info_ptr, rq, 256);
00270   }
00271 
00272   png_write_info(png_ptr, info_ptr);
00273   png_set_flush(png_ptr, 512);
00274 
00275   if (pixelformat == 32) {
00276     png_color_8 sig_bit;
00277 
00278     /* Save exact colour/alpha resolution */
00279     sig_bit.alpha = 0;
00280     sig_bit.blue  = 8;
00281     sig_bit.green = 8;
00282     sig_bit.red   = 8;
00283     sig_bit.gray  = 8;
00284     png_set_sBIT(png_ptr, info_ptr, &sig_bit);
00285 
00286 #if TTD_ENDIAN == TTD_LITTLE_ENDIAN
00287     png_set_bgr(png_ptr);
00288     png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
00289 #else
00290     png_set_filler(png_ptr, 0, PNG_FILLER_BEFORE);
00291 #endif /* TTD_ENDIAN == TTD_LITTLE_ENDIAN */
00292   }
00293 
00294   /* use by default 64k temp memory */
00295   maxlines = Clamp(65536 / w, 16, 128);
00296 
00297   /* now generate the bitmap bits */
00298   void *buff = CallocT<uint8>(w * maxlines * bpp); // by default generate 128 lines at a time.
00299 
00300   y = 0;
00301   do {
00302     /* determine # lines to write */
00303     n = min(h - y, maxlines);
00304 
00305     /* render the pixels into the buffer */
00306     callb(userdata, buff, y, w, n);
00307     y += n;
00308 
00309     /* write them to png */
00310     for (i = 0; i != n; i++)
00311       png_write_row(png_ptr, (png_bytep)buff + i * w * bpp);
00312   } while (y != h);
00313 
00314   png_write_end(png_ptr, info_ptr);
00315   png_destroy_write_struct(&png_ptr, &info_ptr);
00316 
00317   free(buff);
00318   fclose(f);
00319   return true;
00320 }
00321 #endif /* WITH_PNG */
00322 
00323 
00324 /*************************************************
00325  **** SCREENSHOT CODE FOR ZSOFT PAINTBRUSH (.PCX)
00326  *************************************************/
00327 
00328 struct PcxHeader {
00329   byte manufacturer;
00330   byte version;
00331   byte rle;
00332   byte bpp;
00333   uint32 unused;
00334   uint16 xmax, ymax;
00335   uint16 hdpi, vdpi;
00336   byte pal_small[16 * 3];
00337   byte reserved;
00338   byte planes;
00339   uint16 pitch;
00340   uint16 cpal;
00341   uint16 width;
00342   uint16 height;
00343   byte filler[54];
00344 };
00345 assert_compile(sizeof(PcxHeader) == 128);
00346 
00347 static bool MakePCXImage(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
00348 {
00349   FILE *f;
00350   uint maxlines;
00351   uint y;
00352   PcxHeader pcx;
00353   bool success;
00354 
00355   if (pixelformat == 32) {
00356     DEBUG(misc, 0, "Can't convert a 32bpp screenshot to PCX format. Please pick another format.");
00357     return false;
00358   }
00359   if (pixelformat != 8 || w == 0)
00360     return false;
00361 
00362   f = fopen(name, "wb");
00363   if (f == NULL) return false;
00364 
00365   memset(&pcx, 0, sizeof(pcx));
00366 
00367   /* setup pcx header */
00368   pcx.manufacturer = 10;
00369   pcx.version = 5;
00370   pcx.rle = 1;
00371   pcx.bpp = 8;
00372   pcx.xmax = TO_LE16(w - 1);
00373   pcx.ymax = TO_LE16(h - 1);
00374   pcx.hdpi = TO_LE16(320);
00375   pcx.vdpi = TO_LE16(320);
00376 
00377   pcx.planes = 1;
00378   pcx.cpal = TO_LE16(1);
00379   pcx.width = pcx.pitch = TO_LE16(w);
00380   pcx.height = TO_LE16(h);
00381 
00382   /* write pcx header */
00383   if (fwrite(&pcx, sizeof(pcx), 1, f) != 1) {
00384     fclose(f);
00385     return false;
00386   }
00387 
00388   /* use by default 64k temp memory */
00389   maxlines = Clamp(65536 / w, 16, 128);
00390 
00391   /* now generate the bitmap bits */
00392   uint8 *buff = CallocT<uint8>(w * maxlines); // by default generate 128 lines at a time.
00393 
00394   y = 0;
00395   do {
00396     /* determine # lines to write */
00397     uint n = min(h - y, maxlines);
00398     uint i;
00399 
00400     /* render the pixels into the buffer */
00401     callb(userdata, buff, y, w, n);
00402     y += n;
00403 
00404     /* write them to pcx */
00405     for (i = 0; i != n; i++) {
00406       const uint8 *bufp = buff + i * w;
00407       byte runchar = bufp[0];
00408       uint runcount = 1;
00409       uint j;
00410 
00411       /* for each pixel... */
00412       for (j = 1; j < w; j++) {
00413         uint8 ch = bufp[j];
00414 
00415         if (ch != runchar || runcount >= 0x3f) {
00416           if (runcount > 1 || (runchar & 0xC0) == 0xC0)
00417             if (fputc(0xC0 | runcount, f) == EOF) {
00418               free(buff);
00419               fclose(f);
00420               return false;
00421             }
00422           if (fputc(runchar, f) == EOF) {
00423             free(buff);
00424             fclose(f);
00425             return false;
00426           }
00427           runcount = 0;
00428           runchar = ch;
00429         }
00430         runcount++;
00431       }
00432 
00433       /* write remaining bytes.. */
00434       if (runcount > 1 || (runchar & 0xC0) == 0xC0)
00435         if (fputc(0xC0 | runcount, f) == EOF) {
00436           free(buff);
00437           fclose(f);
00438           return false;
00439         }
00440       if (fputc(runchar, f) == EOF) {
00441         free(buff);
00442         fclose(f);
00443         return false;
00444       }
00445     }
00446   } while (y != h);
00447 
00448   free(buff);
00449 
00450   /* write 8-bit colour palette */
00451   if (fputc(12, f) == EOF) {
00452     fclose(f);
00453     return false;
00454   }
00455 
00456   /* Palette is word-aligned, copy it to a temporary byte array */
00457   byte tmp[256 * 3];
00458 
00459   for (uint i = 0; i < 256; i++) {
00460     tmp[i * 3 + 0] = palette[i].r;
00461     tmp[i * 3 + 1] = palette[i].g;
00462     tmp[i * 3 + 2] = palette[i].b;
00463   }
00464   success = fwrite(tmp, sizeof(tmp), 1, f) == 1;
00465 
00466   fclose(f);
00467 
00468   return success;
00469 }
00470 
00471 /*************************************************
00472  **** GENERIC SCREENSHOT CODE
00473  *************************************************/
00474 
00475 static const ScreenshotFormat _screenshot_formats[] = {
00476 #if defined(WITH_PNG)
00477   {"PNG", "png", &MakePNGImage},
00478 #endif
00479   {"BMP", "bmp", &MakeBMPImage},
00480   {"PCX", "pcx", &MakePCXImage},
00481 };
00482 
00483 void InitializeScreenshotFormats()
00484 {
00485   int i, j;
00486   for (i = 0, j = 0; i != lengthof(_screenshot_formats); i++)
00487     if (!strcmp(_screenshot_format_name, _screenshot_formats[i].extension)) {
00488       j = i;
00489       break;
00490     }
00491   _cur_screenshot_format = j;
00492   _num_screenshot_formats = lengthof(_screenshot_formats);
00493 }
00494 
00495 const char *GetScreenshotFormatDesc(int i)
00496 {
00497   return _screenshot_formats[i].name;
00498 }
00499 
00500 void SetScreenshotFormat(int i)
00501 {
00502   _cur_screenshot_format = i;
00503   strecpy(_screenshot_format_name, _screenshot_formats[i].extension, lastof(_screenshot_format_name));
00504 }
00505 
00506 /* screenshot generator that dumps the current video buffer */
00507 static void CurrentScreenCallback(void *userdata, void *buf, uint y, uint pitch, uint n)
00508 {
00509   Blitter *blitter = BlitterFactoryBase::GetCurrentBlitter();
00510   void *src = blitter->MoveTo(_screen.dst_ptr, 0, y);
00511   blitter->CopyImageToBuffer(src, buf, _screen.width, n, pitch);
00512 }
00513 
00521 static void LargeWorldCallback(void *userdata, void *buf, uint y, uint pitch, uint n)
00522 {
00523   ViewPort *vp = (ViewPort *)userdata;
00524   DrawPixelInfo dpi, *old_dpi;
00525   int wx, left;
00526 
00527   /* We are no longer rendering to the screen */
00528   DrawPixelInfo old_screen = _screen;
00529   bool old_disable_anim = _screen_disable_anim;
00530 
00531   _screen.dst_ptr = buf;
00532   _screen.width = pitch;
00533   _screen.height = n;
00534   _screen.pitch = pitch;
00535   _screen_disable_anim = true;
00536 
00537   old_dpi = _cur_dpi;
00538   _cur_dpi = &dpi;
00539 
00540   dpi.dst_ptr = buf;
00541   dpi.height = n;
00542   dpi.width = vp->width;
00543   dpi.pitch = pitch;
00544   dpi.zoom = ZOOM_LVL_WORLD_SCREENSHOT;
00545   dpi.left = 0;
00546   dpi.top = y;
00547 
00548   /* Render viewport in blocks of 1600 pixels width */
00549   left = 0;
00550   while (vp->width - left != 0) {
00551     wx = min(vp->width - left, 1600);
00552     left += wx;
00553 
00554     ViewportDoDraw(vp,
00555       ScaleByZoom(left - wx - vp->left, vp->zoom) + vp->virtual_left,
00556       ScaleByZoom(y - vp->top, vp->zoom) + vp->virtual_top,
00557       ScaleByZoom(left - vp->left, vp->zoom) + vp->virtual_left,
00558       ScaleByZoom((y + n) - vp->top, vp->zoom) + vp->virtual_top
00559     );
00560   }
00561 
00562   _cur_dpi = old_dpi;
00563 
00564   /* Switch back to rendering to the screen */
00565   _screen = old_screen;
00566   _screen_disable_anim = old_disable_anim;
00567 }
00568 
00569 static const char *MakeScreenshotName(const char *ext)
00570 {
00571   bool generate = StrEmpty(_screenshot_name);
00572 
00573   if (generate) {
00574     if (_game_mode == GM_EDITOR || _game_mode == GM_MENU || _local_company == COMPANY_SPECTATOR) {
00575       strecpy(_screenshot_name, "screenshot", lastof(_screenshot_name));
00576     } else {
00577       GenerateDefaultSaveName(_screenshot_name, lastof(_screenshot_name));
00578     }
00579   }
00580 
00581   /* Add extension to screenshot file */
00582   size_t len = strlen(_screenshot_name);
00583   snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, ".%s", ext);
00584 
00585   for (uint serial = 1;; serial++) {
00586     if (snprintf(_full_screenshot_name, lengthof(_full_screenshot_name), "%s%s", _personal_dir, _screenshot_name) >= (int)lengthof(_full_screenshot_name)) {
00587       /* We need more characters than MAX_PATH -> end with error */
00588       _full_screenshot_name[0] = '\0';
00589       break;
00590     }
00591     if (!generate) break; // allow overwriting of non-automatic filenames
00592     if (!FileExists(_full_screenshot_name)) break;
00593     /* If file exists try another one with same name, but just with a higher index */
00594     snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, "#%u.%s", serial, ext);
00595   }
00596 
00597   return _full_screenshot_name;
00598 }
00599 
00600 static bool MakeSmallScreenshot()
00601 {
00602   const ScreenshotFormat *sf = _screenshot_formats + _cur_screenshot_format;
00603   return sf->proc(MakeScreenshotName(sf->extension), CurrentScreenCallback, NULL, _screen.width, _screen.height, BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth(), _cur_palette);
00604 }
00605 
00606 static bool MakeWorldScreenshot()
00607 {
00608   ViewPort vp;
00609   const ScreenshotFormat *sf;
00610 
00611   vp.zoom = ZOOM_LVL_WORLD_SCREENSHOT;
00612   vp.left = 0;
00613   vp.top = 0;
00614   vp.virtual_left = -(int)MapMaxX() * TILE_PIXELS;
00615   vp.virtual_top = 0;
00616   vp.virtual_width = (MapMaxX() + MapMaxY()) * TILE_PIXELS;
00617   vp.width = vp.virtual_width;
00618   vp.virtual_height = (MapMaxX() + MapMaxY()) * TILE_PIXELS >> 1;
00619   vp.height = vp.virtual_height;
00620 
00621   sf = _screenshot_formats + _cur_screenshot_format;
00622   return sf->proc(MakeScreenshotName(sf->extension), LargeWorldCallback, &vp, vp.width, vp.height, BlitterFactoryBase::GetCurrentBlitter()->GetScreenDepth(), _cur_palette);
00623 }
00624 
00631 bool MakeScreenshot(ScreenshotType t, const char *name)
00632 {
00633   if (t == SC_VIEWPORT) {
00634     /* First draw the dirty parts of the screen and only then change the name
00635      * of the screenshot. This way the screenshot will always show the name
00636      * of the previous screenshot in the 'succesful' message instead of the
00637      * name of the new screenshot (or an empty name). */
00638     UndrawMouseCursor();
00639     DrawDirtyBlocks();
00640   }
00641 
00642   _screenshot_name[0] = '\0';
00643   if (name != NULL) strecpy(_screenshot_name, name, lastof(_screenshot_name));
00644 
00645   bool ret;
00646   switch (t) {
00647     case SC_VIEWPORT:
00648     case SC_RAW:
00649       ret = MakeSmallScreenshot();
00650       break;
00651 
00652     case SC_WORLD:
00653       ret = MakeWorldScreenshot();
00654       break;
00655 
00656     default:
00657       NOT_REACHED();
00658   }
00659 
00660   if (ret) {
00661     SetDParamStr(0, _screenshot_name);
00662     ShowErrorMessage(STR_MESSAGE_SCREENSHOT_SUCCESSFULLY, INVALID_STRING_ID, 0, 0);
00663   } else {
00664     ShowErrorMessage(STR_ERROR_SCREENSHOT_FAILED, INVALID_STRING_ID, 0, 0);
00665   }
00666 
00667   return ret;
00668 }

Generated on Fri Apr 30 21:55:25 2010 for OpenTTD by  doxygen 1.6.1