diff --git a/src/video/SDL_stb.c b/src/video/SDL_stb.c index 43b37412ad..1073ba658a 100644 --- a/src/video/SDL_stb.c +++ b/src/video/SDL_stb.c @@ -383,6 +383,9 @@ SDL_Surface *SDL_LoadPNG(const char *file) bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio) { bool retval = false; + Uint8 *plte = NULL; + Uint8 *trns = NULL; + bool free_surface = false; // Make sure we have somewhere to save CHECK_PARAM(!SDL_SurfaceValid(surface)) { @@ -395,17 +398,49 @@ bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio) } #ifdef SDL_HAVE_STB - bool free_surface = false; - if (surface->format != SDL_PIXELFORMAT_RGBA32) { - surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); - if (!surface) { + int plte_size = 0; + int trns_size = 0; + + if (SDL_ISPIXELFORMAT_INDEXED(surface->format)) { + if (!surface->palette) { + SDL_SetError("Indexed surfaces must have a palette"); goto done; } - free_surface = true; + + if (surface->format != SDL_PIXELFORMAT_INDEX8) { + surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_INDEX8); + if (!surface) { + goto done; + } + free_surface = true; + } + + plte_size = surface->palette->ncolors * 3; + trns_size = surface->palette->ncolors; + plte = (Uint8 *)SDL_malloc(plte_size); + trns = (Uint8 *)SDL_malloc(trns_size); + if (!plte || !trns) { + goto done; + } + SDL_Color *colors = surface->palette->colors; + for (int i = 0; i < surface->palette->ncolors; ++i) { + plte[i * 3 + 0] = colors[i].r; + plte[i * 3 + 1] = colors[i].g; + plte[i * 3 + 2] = colors[i].b; + trns[i] = colors[i].a; + } + } else { + if (surface->format != SDL_PIXELFORMAT_RGBA32) { + surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); + if (!surface) { + goto done; + } + free_surface = true; + } } size_t size = 0; - void *png = tdefl_write_image_to_png_file_in_memory(surface->pixels, surface->w, surface->h, SDL_BYTESPERPIXEL(surface->format), surface->pitch, &size); + void *png = tdefl_write_image_to_png_file_in_memory_ex(surface->pixels, surface->w, surface->h, SDL_BYTESPERPIXEL(surface->format), surface->pitch, &size, 6, MZ_FALSE, plte, plte_size, trns, trns_size); if (png) { if (SDL_WriteIO(dst, png, size)) { retval = true; @@ -415,14 +450,17 @@ bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio) SDL_SetError("Failed to convert and save image"); } - if (free_surface) { - SDL_DestroySurface(surface); - } #else SDL_SetError("SDL not built with STB image support"); #endif done: + if (free_surface) { + SDL_DestroySurface(surface); + } + SDL_free(plte); + SDL_free(trns); + if (dst && closeio) { retval &= SDL_CloseIO(dst); } diff --git a/src/video/miniz.h b/src/video/miniz.h index 4be4c73809..95c67c4eb9 100644 --- a/src/video/miniz.h +++ b/src/video/miniz.h @@ -875,8 +875,10 @@ MINIZ_STATIC size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len // Function returns a pointer to the compressed data, or NULL on failure. // *pLen_out will be set to the size of the PNG image file. // The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. -MINIZ_STATIC void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, int bpl, size_t *pLen_out, mz_uint level, mz_bool flip); +MINIZ_STATIC void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, int bpl, size_t *pLen_out, mz_uint level, mz_bool flip, mz_uint8 *plte, int plte_size, mz_uint8 *trns, int trns_size); +#ifndef MINIZ_SDL_NOUNUSED MINIZ_STATIC void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, int bpl, size_t *pLen_out); +#endif // Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); @@ -2911,29 +2913,27 @@ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int // Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at // http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. // This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. -MINIZ_STATIC void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, int bpl, size_t *pLen_out, mz_uint level, mz_bool flip) +MINIZ_STATIC void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, int bpl, size_t *pLen_out, mz_uint level, mz_bool flip, mz_uint8 *plte, int plte_size, mz_uint8 *trns, int trns_size) { // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; - tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, y, z; mz_uint32 c; *pLen_out = 0; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, y, z = 0; mz_uint32 c; *pLen_out = 0; size_t data_start, data_size; if (!pComp) return NULL; - MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } - // write dummy header - for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); - // compress image data - tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); - for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } - if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } - // write real header - *pLen_out = out_buf.m_size-41; + MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); + if (plte_size > 0) + out_buf.m_capacity += 12+plte_size; + if (trns_size > 0) + out_buf.m_capacity += 12+trns_size; + if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } + // write header { static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; - mz_uint8 pnghdr[41]={ 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a, + mz_uint8 pnghdr[33]={ 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a, 0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x49,0x44,0x41,0x54}; + 0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; pnghdr[18] = (mz_uint8)(w>>8); pnghdr[19] = (mz_uint8)(w>>0); @@ -2941,31 +2941,76 @@ MINIZ_STATIC void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage pnghdr[22] = (mz_uint8)(h>>8); pnghdr[23] = (mz_uint8)(h>>0); - pnghdr[25] = (chans[num_chans]); - - pnghdr[33] = (mz_uint8)(*pLen_out>>24); - pnghdr[34] = (mz_uint8)(*pLen_out>>16); - pnghdr[35] = (mz_uint8)(*pLen_out>> 8); - pnghdr[36] = (mz_uint8)(*pLen_out>> 0); + if (num_chans == 1 && plte_size > 0) + pnghdr[25] = 3; + else + pnghdr[25] = (chans[num_chans]); c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i] = (mz_uint8)(c>>24); - memcpy(out_buf.m_pBuf, pnghdr, 41); + if (!tdefl_output_buffer_putter(pnghdr, sizeof(pnghdr), &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } } + // write PLTE chunk + if (plte_size > 0) + { + mz_uint8 hdr[8]={0x00,0x00,0x00,0x00,0x50,0x4c,0x54,0x45}; + + hdr[0] = (mz_uint8)(plte_size>>24); + hdr[1] = (mz_uint8)(plte_size>>16); + hdr[2] = (mz_uint8)(plte_size>> 8); + hdr[3] = (mz_uint8)(plte_size>> 0); + if (!tdefl_output_buffer_putter(hdr, sizeof(hdr), &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + data_start = out_buf.m_size; + if (!tdefl_output_buffer_putter(plte, plte_size, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + if (!tdefl_output_buffer_putter("\0\0\0\0", 4, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+data_start-4, plte_size+4); + for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-4)[i] = (mz_uint8)(c >> 24); + } + // write tRNS chunk + if (trns_size > 0) + { + mz_uint8 hdr[8]={0x00,0x00,0x00,0x00,0x74,0x52,0x4E,0x53}; + + hdr[0] = (mz_uint8)(trns_size>>24); + hdr[1] = (mz_uint8)(trns_size>>16); + hdr[2] = (mz_uint8)(trns_size>> 8); + hdr[3] = (mz_uint8)(trns_size>> 0); + if (!tdefl_output_buffer_putter(hdr, sizeof(hdr), &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + data_start = out_buf.m_size; + if (!tdefl_output_buffer_putter(trns, trns_size, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + if (!tdefl_output_buffer_putter("\0\0\0\0", 4, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+data_start-4, trns_size+4); + for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-4)[i] = (mz_uint8)(c >> 24); + } + // write IDAT chunk + if (!tdefl_output_buffer_putter("\0\0\0\0\x49\x44\x41\x54", 8, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + data_start = out_buf.m_size; + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + // write IDAT size + data_size = out_buf.m_size-data_start; + (out_buf.m_pBuf+data_start-8)[0] = (mz_uint8)(data_size>>24); + (out_buf.m_pBuf+data_start-8)[1] = (mz_uint8)(data_size>>16); + (out_buf.m_pBuf+data_start-8)[2] = (mz_uint8)(data_size>> 8); + (out_buf.m_pBuf+data_start-8)[3] = (mz_uint8)(data_size>> 0); // write footer (IDAT CRC-32, followed by IEND chunk) if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } - c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+data_start-4, data_size+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); // compute final size of file, grab compressed data buffer and return - *pLen_out += 57; + *pLen_out = out_buf.m_size; MZ_FREE(pComp); return out_buf.m_pBuf; } +#ifndef MINIZ_SDL_NOUNUSED MINIZ_STATIC void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, int bpl, size_t *pLen_out) { // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) - return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, bpl, pLen_out, 6, MZ_FALSE); + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, bpl, pLen_out, 6, MZ_FALSE, NULL, 0, NULL, 0); } +#endif #ifdef _MSC_VER #pragma warning (pop) diff --git a/test/testautomation_surface.c b/test/testautomation_surface.c index e1713dbd4a..ffa170cb90 100644 --- a/test/testautomation_surface.c +++ b/test/testautomation_surface.c @@ -3,14 +3,6 @@ * Adapted/rewritten for test lib by Andreas Schiffler */ -/* Suppress C4996 VS compiler warnings for unlink() */ -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_DEPRECATE) -#define _CRT_SECURE_NO_DEPRECATE -#endif -#if defined(_MSC_VER) && !defined(_CRT_NONSTDC_NO_DEPRECATE) -#define _CRT_NONSTDC_NO_DEPRECATE -#endif - #include #ifndef _MSC_VER #include @@ -337,6 +329,13 @@ static int SDLCALL surface_testSaveLoad(void *arg) const char *sampleFilename = "testSaveLoad.tmp"; SDL_Surface *face; SDL_Surface *rface; + SDL_Palette *palette; + SDL_Color colors[] = { + { 255, 0, 0, SDL_ALPHA_OPAQUE }, /* Red */ + { 0, 255, 0, SDL_ALPHA_OPAQUE } /* Green */ + }; + SDL_IOStream *stream; + Uint8 r, g, b, a; /* Create sample surface */ face = SDLTest_ImageFace(); @@ -346,7 +345,7 @@ static int SDLCALL surface_testSaveLoad(void *arg) } /* Delete test file; ignore errors */ - unlink(sampleFilename); + SDL_RemovePath(sampleFilename); /* Save a BMP surface */ ret = SDL_SaveBMP(face, sampleFilename); @@ -366,7 +365,7 @@ static int SDLCALL surface_testSaveLoad(void *arg) } /* Delete test file; ignore errors */ - unlink(sampleFilename); + SDL_RemovePath(sampleFilename); /* Save a PNG surface */ ret = SDL_SavePNG(face, sampleFilename); @@ -386,12 +385,95 @@ static int SDLCALL surface_testSaveLoad(void *arg) } /* Delete test file; ignore errors */ - unlink(sampleFilename); + SDL_RemovePath(sampleFilename); /* Clean up */ SDL_DestroySurface(face); face = NULL; + /* Create an 8-bit image */ + face = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_INDEX8); + SDLTest_AssertCheck(face != NULL, "Verify 8-bit surface is not NULL"); + if (face == NULL) { + return TEST_ABORTED; + } + + palette = SDL_CreatePalette(2); + SDLTest_AssertCheck(palette != NULL, "Verify palette is not NULL"); + if (palette == NULL) { + return TEST_ABORTED; + } + SDL_SetPaletteColors(palette, colors, 0, SDL_arraysize(colors)); + SDL_SetSurfacePalette(face, palette); + SDL_DestroyPalette(palette); + + /* Set a green pixel */ + *(Uint8 *)face->pixels = 1; + + /* Save and reload as a BMP */ + stream = SDL_IOFromDynamicMem(); + SDLTest_AssertCheck(stream != NULL, "Verify iostream is not NULL"); + if (stream == NULL) { + return TEST_ABORTED; + } + ret = SDL_SaveBMP_IO(face, stream, false); + SDLTest_AssertPass("Call to SDL_SaveBMP()"); + SDLTest_AssertCheck(ret == true, "Verify result from SDL_SaveBMP, expected: true, got: %i", ret); + SDL_SeekIO(stream, 0, SDL_IO_SEEK_SET); + rface = SDL_LoadBMP_IO(stream, false); + SDLTest_AssertPass("Call to SDL_LoadBMP()"); + SDLTest_AssertCheck(rface != NULL, "Verify result from SDL_LoadBMP is not NULL"); + if (rface != NULL) { + SDLTest_AssertCheck(face->w == rface->w, "Verify width of loaded surface, expected: %i, got: %i", face->w, rface->w); + SDLTest_AssertCheck(face->h == rface->h, "Verify height of loaded surface, expected: %i, got: %i", face->h, rface->h); + SDLTest_AssertCheck(rface->format == SDL_PIXELFORMAT_INDEX8, "Verify format of loaded surface, expected: %s, got: %s", SDL_GetPixelFormatName(face->format), SDL_GetPixelFormatName(rface->format)); + SDL_ReadSurfacePixel(rface, 0, 0, &r, &g, &b, &a); + SDLTest_AssertCheck(r == colors[1].r && + g == colors[1].g && + b == colors[1].b && + a == colors[1].a, + "Verify color of loaded surface, expected: %d,%d,%d,%d, got: %d,%d,%d,%d", + r, g, b, a, + colors[1].r, colors[1].g, colors[1].b, colors[1].a); + SDL_DestroySurface(rface); + rface = NULL; + } + SDL_CloseIO(stream); + stream = NULL; + + /* Save and reload as a PNG */ + stream = SDL_IOFromDynamicMem(); + SDLTest_AssertCheck(stream != NULL, "Verify iostream is not NULL"); + if (stream == NULL) { + return TEST_ABORTED; + } + ret = SDL_SavePNG_IO(face, stream, false); + SDLTest_AssertPass("Call to SDL_SavePNG()"); + SDLTest_AssertCheck(ret == true, "Verify result from SDL_SavePNG, expected: true, got: %i", ret); + SDL_SeekIO(stream, 0, SDL_IO_SEEK_SET); + rface = SDL_LoadPNG_IO(stream, false); + SDLTest_AssertPass("Call to SDL_LoadPNG()"); + SDLTest_AssertCheck(rface != NULL, "Verify result from SDL_LoadPNG is not NULL"); + if (rface != NULL) { + SDLTest_AssertCheck(face->w == rface->w, "Verify width of loaded surface, expected: %i, got: %i", face->w, rface->w); + SDLTest_AssertCheck(face->h == rface->h, "Verify height of loaded surface, expected: %i, got: %i", face->h, rface->h); + SDLTest_AssertCheck(rface->format == SDL_PIXELFORMAT_INDEX8, "Verify format of loaded surface, expected: %s, got: %s", SDL_GetPixelFormatName(face->format), SDL_GetPixelFormatName(rface->format)); + SDL_ReadSurfacePixel(rface, 0, 0, &r, &g, &b, &a); + SDLTest_AssertCheck(r == colors[1].r && + g == colors[1].g && + b == colors[1].b && + a == colors[1].a, + "Verify color of loaded surface, expected: %d,%d,%d,%d, got: %d,%d,%d,%d", + r, g, b, a, + colors[1].r, colors[1].g, colors[1].b, colors[1].a); + SDL_DestroySurface(rface); + rface = NULL; + } + SDL_CloseIO(stream); + stream = NULL; + + SDL_DestroySurface(face); + return TEST_COMPLETED; }