summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/screenshot.cpp139
1 files changed, 83 insertions, 56 deletions
diff --git a/src/screenshot.cpp b/src/screenshot.cpp
index 87951c91a..5d93f622c 100644
--- a/src/screenshot.cpp
+++ b/src/screenshot.cpp
@@ -46,6 +46,7 @@ struct ScreenshotFormat {
#pragma pack(push, 1)
#endif
+/** BMP File Header (stored in little endian) */
struct BitmapFileHeader {
uint16 type;
uint32 size;
@@ -58,6 +59,7 @@ assert_compile(sizeof(BitmapFileHeader) == 14);
#pragma pack(pop)
#endif
+/** BMP Info Header (stored in little endian) */
struct BitmapInfoHeader {
uint32 size;
int32 width, height;
@@ -66,47 +68,63 @@ struct BitmapInfoHeader {
};
assert_compile(sizeof(BitmapInfoHeader) == 40);
+/** Format of palette data in BMP header */
struct RgbQuad {
byte blue, green, red, reserved;
};
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(char *name, ScreenshotCallback *callb, void *userdata, uint w, uint h, int pixelformat, const Colour *palette)
{
- BitmapFileHeader bfh;
- BitmapInfoHeader bih;
- RgbQuad rq[256];
- FILE *f;
- uint i, padw;
- uint n, maxlines;
- uint pal_size = 0;
- uint bpp = pixelformat / 8;
-
- /* only implemented for 8bit and 32bit images so far. */
- if (pixelformat != 8 && pixelformat != 32) return false;
+ uint bpp; // bytes per pixel
+ switch (pixelformat) {
+ case 8: bpp = 1; break;
+ /* 32bpp mode is saved as 24bpp BMP */
+ case 32: bpp = 3; break;
+ /* Only implemented for 8bit and 32bit images so far */
+ default: return false;
+ }
- f = fopen(name, "wb");
+ FILE *f = fopen(name, "wb");
if (f == NULL) return false;
- /* each scanline must be aligned on a 32bit boundary */
- padw = w;
- if (pixelformat == 8) padw = Align(padw, 4);
+ /* Each scanline must be aligned on a 32bit boundary */
+ 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.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.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.width = TO_LE32(w);
bih.height = TO_LE32(h);
bih.planes = TO_LE16(1);
- bih.bitcount = TO_LE16(pixelformat);
+ bih.bitcount = TO_LE16(bpp * 8);
bih.compression = 0;
bih.sizeimage = 0;
bih.xpels = 0;
@@ -114,57 +132,66 @@ static bool MakeBmpImage(char *name, ScreenshotCallback *callb, void *userdata,
bih.clrused = 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) {
- /* convert the palette to the windows format */
- for (i = 0; i != 256; i++) {
+ /* Convert the palette to the windows format */
+ RgbQuad rq[256];
+ for (uint i = 0; i < 256; i++) {
rq[i].red = palette[i].r;
rq[i].green = palette[i].g;
rq[i].blue = palette[i].b;
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 */
- if (fwrite(&bfh, sizeof(bfh), 1, f) != 1 || fwrite(&bih, sizeof(bih), 1, f) != 1) {
- fclose(f);
- return false;
- }
- if (pixelformat == 8 && fwrite(rq, sizeof(rq), 1, f) != 1) {
- fclose(f);
- return false;
- }
-
- /* use by default 64k temp memory */
- maxlines = Clamp(65536 / padw, 16, 128);
+ /* Try to use 64k of memory, store between 16 and 128 lines */
+ uint maxlines = Clamp(65536 / (w * pixelformat / 8), 16, 128); // number of lines per iteration
- /* now generate the bitmap bits */
- uint8 *buff = CallocT<uint8>(padw * maxlines * bpp); // by default generate 128 lines at a time.
+ uint8 *buff = MallocT<uint8>(maxlines * w * pixelformat / 8); // buffer which is rendered to
+ uint8 *line = AllocaM(uint8, bytewidth); // one line, stored to file
+ memset(line, 0, bytewidth);
- /* start at the bottom, since bitmaps are stored bottom up. */
+ /* Start at the bottom, since bitmaps are stored bottom up */
do {
- /* determine # lines */
- n = min(h, maxlines);
+ uint n = min(h, maxlines);
h -= n;
- /* render the pixels */
- callb(userdata, buff, h, padw, n);
-
-#if TTD_ENDIAN == TTD_BIG_ENDIAN
- if (pixelformat == 32) {
- /* Data stored in BMP are always little endian,
- * but we have big endian data in buffer */
- uint32 *buff32 = (uint32 *)buff;
- for (i = 0; i < padw * n; i++) buff32[i] = BSWAP32(buff32[i]);
- }
-#endif /* TTD_ENDIAN == TTD_BIG_ENDIAN */
-
- /* write each line */
- while (n)
- if (fwrite(buff + (--n) * padw * bpp, padw * bpp, 1, f) != 1) {
+ /* Render the pixels */
+ callb(userdata, buff, h, w, n);
+
+ /* Write each line */
+ while (n-- != 0) {
+ 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);
fclose(f);
return false;
}
+ }
} while (h != 0);
free(buff);