mirror of https://github.com/OpenTTD/OpenTTD.git
(svn r17989) [0.7] -Backport from trunk:
- Fix: Use 24bpp BMP format instead of 32bpp for screenshots. Saves space and is supported by more image viewers (r17943) - Fix: Close BMP file when making screenshot fails (r17941) - Fix: Deadlock when trying to create screenshot with too long name (including path) (r17936) - Fix: 32bpp BMP screenshots were in wrong colours on big endian machines and broken when screen width was not a multiple of 4 (r17910, r17909)
This commit is contained in:
parent
7b6e449f15
commit
d8c4ed5118
|
@ -41,6 +41,7 @@ struct ScreenshotFormat {
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/** BMP File Header (stored in little endian) */
|
||||||
struct BitmapFileHeader {
|
struct BitmapFileHeader {
|
||||||
uint16 type;
|
uint16 type;
|
||||||
uint32 size;
|
uint32 size;
|
||||||
|
@ -53,6 +54,7 @@ assert_compile(sizeof(BitmapFileHeader) == 14);
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/** BMP Info Header (stored in little endian) */
|
||||||
struct BitmapInfoHeader {
|
struct BitmapInfoHeader {
|
||||||
uint32 size;
|
uint32 size;
|
||||||
int32 width, height;
|
int32 width, height;
|
||||||
|
@ -61,46 +63,63 @@ struct BitmapInfoHeader {
|
||||||
};
|
};
|
||||||
assert_compile(sizeof(BitmapInfoHeader) == 40);
|
assert_compile(sizeof(BitmapInfoHeader) == 40);
|
||||||
|
|
||||||
|
/** Format of palette data in BMP header */
|
||||||
struct RgbQuad {
|
struct RgbQuad {
|
||||||
byte blue, green, red, reserved;
|
byte blue, green, red, reserved;
|
||||||
};
|
};
|
||||||
assert_compile(sizeof(RgbQuad) == 4);
|
assert_compile(sizeof(RgbQuad) == 4);
|
||||||
|
|
||||||
/* generic .BMP writer */
|
/** Pixel data in 24bpp BMP */
|
||||||
|
struct RgbTriplet {
|
||||||
|
byte b, g, r;
|
||||||
|
};
|
||||||
|
assert_compile(sizeof(RgbTriplet) == 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic .BMP writer
|
||||||
|
* @param name file name including extension
|
||||||
|
* @param callb callback used for gathering rendered image
|
||||||
|
* @param userdata parameters forwarded to #callb
|
||||||
|
* @param w width in pixels
|
||||||
|
* @param h height in pixels
|
||||||
|
* @param pixelformat bits per pixel
|
||||||
|
* @param paletter colour paletter (for 8bpp mode)
|
||||||
|
* @return was everything ok?
|
||||||
|
*/
|
||||||
static bool MakeBmpImage(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
|
static bool MakeBmpImage(const char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
|
||||||
{
|
{
|
||||||
BitmapFileHeader bfh;
|
uint bpp; // bytes per pixel
|
||||||
BitmapInfoHeader bih;
|
switch (pixelformat) {
|
||||||
RgbQuad rq[256];
|
case 8: bpp = 1; break;
|
||||||
FILE *f;
|
/* 32bpp mode is saved as 24bpp BMP */
|
||||||
uint i, padw;
|
case 32: bpp = 3; break;
|
||||||
uint n, maxlines;
|
/* Only implemented for 8bit and 32bit images so far */
|
||||||
uint pal_size = 0;
|
default: return false;
|
||||||
uint bpp = pixelformat / 8;
|
}
|
||||||
|
|
||||||
/* only implemented for 8bit and 32bit images so far. */
|
FILE *f = fopen(name, "wb");
|
||||||
if (pixelformat != 8 && pixelformat != 32) return false;
|
|
||||||
|
|
||||||
f = fopen(name, "wb");
|
|
||||||
if (f == NULL) return false;
|
if (f == NULL) return false;
|
||||||
|
|
||||||
/* each scanline must be aligned on a 32bit boundary */
|
/* Each scanline must be aligned on a 32bit boundary */
|
||||||
padw = Align(w, 4);
|
uint bytewidth = Align(w * bpp, 4); // bytes per line in file
|
||||||
|
|
||||||
if (pixelformat == 8) pal_size = sizeof(RgbQuad) * 256;
|
/* Size of palette. Only present for 8bpp mode */
|
||||||
|
uint pal_size = pixelformat == 8 ? sizeof(RgbQuad) * 256 : 0;
|
||||||
|
|
||||||
/* setup the file header */
|
/* Setup the file header */
|
||||||
|
BitmapFileHeader bfh;
|
||||||
bfh.type = TO_LE16('MB');
|
bfh.type = TO_LE16('MB');
|
||||||
bfh.size = TO_LE32(sizeof(bfh) + sizeof(bih) + pal_size + padw * h * bpp);
|
bfh.size = TO_LE32(sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + pal_size + bytewidth * h);
|
||||||
bfh.reserved = 0;
|
bfh.reserved = 0;
|
||||||
bfh.off_bits = TO_LE32(sizeof(bfh) + sizeof(bih) + pal_size);
|
bfh.off_bits = TO_LE32(sizeof(BitmapFileHeader) + sizeof(BitmapInfoHeader) + pal_size);
|
||||||
|
|
||||||
/* setup the info header */
|
/* Setup the info header */
|
||||||
|
BitmapInfoHeader bih;
|
||||||
bih.size = TO_LE32(sizeof(BitmapInfoHeader));
|
bih.size = TO_LE32(sizeof(BitmapInfoHeader));
|
||||||
bih.width = TO_LE32(w);
|
bih.width = TO_LE32(w);
|
||||||
bih.height = TO_LE32(h);
|
bih.height = TO_LE32(h);
|
||||||
bih.planes = TO_LE16(1);
|
bih.planes = TO_LE16(1);
|
||||||
bih.bitcount = TO_LE16(pixelformat);
|
bih.bitcount = TO_LE16(bpp * 8);
|
||||||
bih.compression = 0;
|
bih.compression = 0;
|
||||||
bih.sizeimage = 0;
|
bih.sizeimage = 0;
|
||||||
bih.xpels = 0;
|
bih.xpels = 0;
|
||||||
|
@ -108,43 +127,66 @@ static bool MakeBmpImage(const char *name, ScreenshotCallback *callb, void *user
|
||||||
bih.clrused = 0;
|
bih.clrused = 0;
|
||||||
bih.clrimp = 0;
|
bih.clrimp = 0;
|
||||||
|
|
||||||
|
/* Write file header and info header */
|
||||||
|
if (fwrite(&bfh, sizeof(bfh), 1, f) != 1 || fwrite(&bih, sizeof(bih), 1, f) != 1) {
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (pixelformat == 8) {
|
if (pixelformat == 8) {
|
||||||
/* convert the palette to the windows format */
|
/* Convert the palette to the windows format */
|
||||||
for (i = 0; i != 256; i++) {
|
RgbQuad rq[256];
|
||||||
|
for (uint i = 0; i < 256; i++) {
|
||||||
rq[i].red = palette[i].r;
|
rq[i].red = palette[i].r;
|
||||||
rq[i].green = palette[i].g;
|
rq[i].green = palette[i].g;
|
||||||
rq[i].blue = palette[i].b;
|
rq[i].blue = palette[i].b;
|
||||||
rq[i].reserved = 0;
|
rq[i].reserved = 0;
|
||||||
}
|
}
|
||||||
|
/* Write the palette */
|
||||||
|
if (fwrite(rq, sizeof(rq), 1, f) != 1) {
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* write file header and info header and palette */
|
/* Try to use 64k of memory, store between 16 and 128 lines */
|
||||||
if (fwrite(&bfh, sizeof(bfh), 1, f) != 1) return false;
|
uint maxlines = Clamp(65536 / (w * pixelformat / 8), 16, 128); // number of lines per iteration
|
||||||
if (fwrite(&bih, sizeof(bih), 1, f) != 1) return false;
|
|
||||||
if (pixelformat == 8) if (fwrite(rq, sizeof(rq), 1, f) != 1) return false;
|
|
||||||
|
|
||||||
/* use by default 64k temp memory */
|
uint8 *buff = MallocT<uint8>(maxlines * w * pixelformat / 8); // buffer which is rendered to
|
||||||
maxlines = Clamp(65536 / padw, 16, 128);
|
uint8 *line = AllocaM(uint8, bytewidth); // one line, stored to file
|
||||||
|
memset(line, 0, bytewidth);
|
||||||
|
|
||||||
/* now generate the bitmap bits */
|
/* Start at the bottom, since bitmaps are stored bottom up */
|
||||||
uint8 *buff = CallocT<uint8>(padw * maxlines * bpp); // by default generate 128 lines at a time.
|
|
||||||
|
|
||||||
/* start at the bottom, since bitmaps are stored bottom up. */
|
|
||||||
do {
|
do {
|
||||||
/* determine # lines */
|
uint n = min(h, maxlines);
|
||||||
n = min(h, maxlines);
|
|
||||||
h -= n;
|
h -= n;
|
||||||
|
|
||||||
/* render the pixels */
|
/* Render the pixels */
|
||||||
callb(userdata, buff, h, padw, n);
|
callb(userdata, buff, h, w, n);
|
||||||
|
|
||||||
/* write each line */
|
/* Write each line */
|
||||||
while (n)
|
while (n-- != 0) {
|
||||||
if (fwrite(buff + (--n) * padw * bpp, padw * bpp, 1, f) != 1) {
|
if (pixelformat == 8) {
|
||||||
|
/* Move to 'line', leave last few pixels in line zeroed */
|
||||||
|
memcpy(line, buff + n * w, w);
|
||||||
|
} else {
|
||||||
|
/* Convert from 'native' 32bpp to BMP-like 24bpp.
|
||||||
|
* Works for both big and little endian machines */
|
||||||
|
Colour *src = ((Colour *)buff) + n * w;
|
||||||
|
RgbTriplet *dst = (RgbTriplet *)line;
|
||||||
|
for (uint i = 0; i < w; i++) {
|
||||||
|
dst[i].r = src[i].r;
|
||||||
|
dst[i].g = src[i].g;
|
||||||
|
dst[i].b = src[i].b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Write to file */
|
||||||
|
if (fwrite(line, bytewidth, 1, f) != 1) {
|
||||||
free(buff);
|
free(buff);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} while (h != 0);
|
} while (h != 0);
|
||||||
|
|
||||||
free(buff);
|
free(buff);
|
||||||
|
@ -539,7 +581,11 @@ static char *MakeScreenshotName(const char *ext)
|
||||||
snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, ".%s", ext);
|
snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, ".%s", ext);
|
||||||
|
|
||||||
for (serial = 1;; serial++) {
|
for (serial = 1;; serial++) {
|
||||||
snprintf(filename, lengthof(filename), "%s%s", _personal_dir, _screenshot_name);
|
if (snprintf(filename, lengthof(filename), "%s%s", _personal_dir, _screenshot_name) >= (int)lengthof(filename)) {
|
||||||
|
/* We need more characters than MAX_PATH -> end with error */
|
||||||
|
filename[0] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (!FileExists(filename)) break;
|
if (!FileExists(filename)) break;
|
||||||
/* If file exists try another one with same name, but just with a higher index */
|
/* If file exists try another one with same name, but just with a higher index */
|
||||||
snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, "#%d.%s", serial, ext);
|
snprintf(&_screenshot_name[len], lengthof(_screenshot_name) - len, "#%d.%s", serial, ext);
|
||||||
|
|
Loading…
Reference in New Issue