diff --git a/CMakeLists.txt b/CMakeLists.txt index e0a44bcff2..7bd8c6c612 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,19 @@ endif (DISABLE_NETWORK) option(STATIC "Create a static build.") +# Not required yet +PKG_CHECK_MODULES(PNG libpng>=1.6) +if (NOT PNG_FOUND) + PKG_CHECK_MODULES(PNG libpng16) +endif (NOT PNG_FOUND) + +if (PNG_FOUND) + set (USE_LODEPNG FALSE) +else (PNG_FOUND) + set (USE_LODEPNG TRUE) + message("Falling back to deprecated Lodepng for PNG support. Please upgrade your system to libpng 1.6") +endif (PNG_FOUND) + # Handle creating the rct2 text and data files on OS X and Linux # See details in src/openrct2.c:openrct2_setup_rct2_segment for how the values # were derived. @@ -96,10 +109,16 @@ set(DEBUG_LEVEL 0 CACHE STRING "Select debug level for compilation. Use value in set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDEBUG=${DEBUG_LEVEL}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG=${DEBUG_LEVEL}") +if (USE_LODEPNG) + set (LODEPNG_SOURCES "lib/lodepng/*.c") +else (USE_LODEPNG) + add_definitions(-DUSE_LIBPNG) +endif (USE_LODEPNG) + # include lib include_directories("lib/") # add source files -file(GLOB_RECURSE ORCT2_SOURCES "src/*.c" "src/*.cpp" "lib/argparse/*.c" "lib/cutest/*.c" "lib/lodepng/*.c") +file(GLOB_RECURSE ORCT2_SOURCES "src/*.c" "src/*.cpp" "lib/argparse/*.c" "lib/cutest/*.c" ${LODEPNG_SOURCES}) if (APPLE) file(GLOB_RECURSE ORCT2_MM_SOURCES "src/*.m") set_source_files_properties(${ORCT2_MM_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c -fmodules") @@ -135,6 +154,12 @@ else (STATIC) SET(SDL2LIBS ${SDL2_LIBRARIES}) endif (STATIC) +if (STATIC) + SET(PNGLIBS ${PNG_STATIC_LIBRARIES}) +else (STATIC) + SET(PNGLIBS ${PNG_LIBRARIES}) +endif (STATIC) + if (NOT DISABLE_HTTP_TWITCH) PKG_CHECK_MODULES(LIBCURL REQUIRED libcurl) PKG_CHECK_MODULES(JANSSON REQUIRED jansson>=2.7) @@ -165,9 +190,9 @@ if (UNIX) set(DLLIB dl) endif (UNIX) -INCLUDE_DIRECTORIES(${SDL2_INCLUDE_DIRS} ${LIBCURL_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} ${SPEEX_INCLUDE_DIRS}) +INCLUDE_DIRECTORIES(${SDL2_INCLUDE_DIRS} ${LIBCURL_INCLUDE_DIRS} ${JANSSON_INCLUDE_DIRS} ${SPEEX_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS}) -LINK_DIRECTORIES(${SDL2_LIBRARY_DIRS} ${JANSSON_LIBRARY_DIRS} ${LIBCURL_LIBRARY_DIRS}) +LINK_DIRECTORIES(${SDL2_LIBRARY_DIRS} ${JANSSON_LIBRARY_DIRS} ${LIBCURL_LIBRARY_DIRS} ${PNG_LIBRARY_DIRS}) if (WIN32) # build as library for now, replace with add_executable @@ -200,7 +225,7 @@ endif (UNIX AND NOT APPLE) # libopenrct2.dll -> openrct2.dll set_target_properties(${PROJECT} PROPERTIES PREFIX "") -TARGET_LINK_LIBRARIES(${PROJECT} ${SDL2LIBS} ${ORCTLIBS_LIB} ${HTTPLIBS} ${NETWORKLIBS} ${SPEEX_LIBRARIES} ${DLLIB}) +TARGET_LINK_LIBRARIES(${PROJECT} ${SDL2LIBS} ${ORCTLIBS_LIB} ${HTTPLIBS} ${NETWORKLIBS} ${SPEEX_LIBRARIES} ${DLLIB} ${PNGLIBS}) if (APPLE OR STATIC) FIND_LIBRARY(ICONV_LIBRARIES NAMES iconv libiconv libiconv-2 c) diff --git a/openrct2.vcxproj b/openrct2.vcxproj index 9eba05d0b3..a430d4746c 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -49,6 +49,7 @@ + @@ -218,6 +219,7 @@ + diff --git a/openrct2.vcxproj.filters b/openrct2.vcxproj.filters index bd11a5e391..4d1a1f4363 100644 --- a/openrct2.vcxproj.filters +++ b/openrct2.vcxproj.filters @@ -558,6 +558,9 @@ Source\Windows + + Source + @@ -839,5 +842,8 @@ Source\Core + + Source + \ No newline at end of file diff --git a/scripts/linux/build.sh b/scripts/linux/build.sh index 5a796a00f5..bef6f53e17 100755 --- a/scripts/linux/build.sh +++ b/scripts/linux/build.sh @@ -17,7 +17,7 @@ if [[ $(uname -s) == "Darwin" ]]; then # keep in sync with version in Xcode project sha256sum=2cec3958352477fbb876a5b6398722077084b5ff7e95a7d3cd67492abf5012fc else - sha256sum=69ff98c9544838fb16384bc78af9dc1c452b9d01d919e43f5fec686d02c9bdd8 + sha256sum=31c5e19d9f794bd5f0e75f20c2b4c3c4664d736b0a4d50c8cde14a9a9007b62d fi libVFile="./libversion" libdir="./lib" @@ -62,10 +62,12 @@ pushd build PARENT=$(readlink -f ../) chmod a+rwx $(pwd) chmod g+s $(pwd) - docker run -u travis -v $PARENT:/work/openrct2 -w /work/openrct2/build -i -t openrct2/openrct2:32bit-only bash -c "cmake ../ $OPENRCT2_CMAKE_OPTS && make" + # CMAKE and MAKE opts from environment + docker run -u travis -v $PARENT:/work/openrct2 -w /work/openrct2/build -i -t openrct2/openrct2:32bit-only bash -c "cmake ../ $OPENRCT2_CMAKE_OPTS && make $OPENRCT_MAKE_OPTS" else cmake -DCMAKE_BUILD_TYPE=Debug $OPENRCT2_CMAKE_OPTS .. - make + # NOT the same variable as above + make $OPENRCT2_MAKE_OPTS fi popd diff --git a/scripts/linux/install.sh b/scripts/linux/install.sh index 466a7d44e0..c0bde470cd 100755 --- a/scripts/linux/install.sh +++ b/scripts/linux/install.sh @@ -170,8 +170,9 @@ elif [[ $(uname) == "Linux" ]]; then case "$TARGET" in "linux") sudo dpkg --add-architecture i386 + sudo add-apt-repository -y ppa:djcj/tools sudo apt-get update - sudo apt-get install --no-install-recommends -y --force-yes cmake libsdl2-dev:i386 libsdl2-ttf-dev:i386 gcc-4.8 pkg-config:i386 g++-4.8-multilib gcc-4.8-multilib libjansson-dev:i386 libspeex-dev:i386 libspeexdsp-dev:i386 libcurl4-openssl-dev:i386 libcrypto++-dev:i386 clang libfontconfig1-dev:i386 libfreetype6-dev:i386 libpng-dev:i386 + sudo apt-get install --no-install-recommends -y --force-yes cmake libsdl2-dev:i386 libsdl2-ttf-dev:i386 gcc-4.8 pkg-config:i386 g++-4.8-multilib gcc-4.8-multilib libjansson-dev:i386 libspeex-dev:i386 libspeexdsp-dev:i386 libcurl4-openssl-dev:i386 libcrypto++-dev:i386 clang libfontconfig1-dev:i386 libfreetype6-dev:i386 libpng-dev:i386 libpng16-dev:i386 download https://launchpad.net/ubuntu/+archive/primary/+files/libjansson4_2.7-1ubuntu1_i386.deb libjansson4_2.7-1ubuntu1_i386.deb download https://launchpad.net/ubuntu/+archive/primary/+files/libjansson-dev_2.7-1ubuntu1_i386.deb libjansson-dev_2.7-1ubuntu1_i386.deb sudo dpkg -i libjansson4_2.7-1ubuntu1_i386.deb diff --git a/src/cmdline_sprite.c b/src/cmdline_sprite.c index dff9acbf18..c7fc2eec5c 100644 --- a/src/cmdline_sprite.c +++ b/src/cmdline_sprite.c @@ -18,12 +18,12 @@ * along with this program. If not, see . *****************************************************************************/ -#include #include "cmdline.h" #include "drawing/drawing.h" +#include "image_io.h" +#include "openrct2.h" #include "platform/platform.h" #include "util/util.h" -#include "openrct2.h" #define MODE_DEFAULT 0 #define MODE_CLOSEST 1 @@ -168,33 +168,11 @@ bool sprite_file_export(int spriteIndex, const char *outPath) memcpy(spriteFilePalette, _standardPalette, 256 * 4); gfx_rle_sprite_to_buffer(spriteHeader->offset, pixels, (uint8*)spriteFilePalette, &dpi, IMAGE_TYPE_NO_BACKGROUND, 0, spriteHeader->height, 0, spriteHeader->width); - LodePNGState pngState; - unsigned int pngError; - unsigned char* pngData; - size_t pngSize; - - lodepng_state_init(&pngState); - pngState.info_raw.colortype = LCT_PALETTE; - lodepng_palette_add(&pngState.info_raw, 0, 0, 0, 0); - for (int i = 1; i < 256; i++) { - lodepng_palette_add( - &pngState.info_raw, - spriteFilePalette[i].r, - spriteFilePalette[i].g, - spriteFilePalette[i].b, - 255 - ); - } - - pngError = lodepng_encode(&pngData, &pngSize, pixels, spriteHeader->width, spriteHeader->height, &pngState); - if (pngError != 0) { - free(pngData); - fprintf(stderr, "Error creating PNG data, %u: %s", pngError, lodepng_error_text(pngError)); - return false; - } else { - lodepng_save_file(pngData, pngSize, outPath); - free(pngData); + if (image_io_png_write(&dpi, (rct_palette*)spriteFilePalette, outPath)) { return true; + } else { + fprintf(stderr, "Error writing PNG"); + return false; } } @@ -261,16 +239,10 @@ typedef struct { bool sprite_file_import(const char *path, rct_g1_element *outElement, uint8 **outBuffer, int *outBufferLength, int mode) { - unsigned char *pixels; - unsigned int width, height; - unsigned int pngError; - - memcpy(spriteFilePalette, _standardPalette, 256 * 4); - - pngError = lodepng_decode_file(&pixels, &width, &height, path, LCT_RGBA, 8); - if (pngError != 0) { - free(pixels); - fprintf(stderr, "Error creating PNG data, %u: %s", pngError, lodepng_error_text(pngError)); + uint8 *pixels; + uint32 width, height; + if (!image_io_png_read(&pixels, &width, &height, path)) { + fprintf(stderr, "Error reading PNG"); return false; } @@ -280,6 +252,8 @@ bool sprite_file_import(const char *path, rct_g1_element *outElement, uint8 **ou return false; } + memcpy(spriteFilePalette, _standardPalette, 256 * 4); + uint8 *buffer = malloc((height * 2) + (width * height * 16)); memset(buffer, 0, (height * 2) + (width * height * 16)); uint16 *yOffsets = (uint16*)buffer; diff --git a/src/drawing/drawing.h b/src/drawing/drawing.h index f4a6f885f7..0cd56e2cfb 100644 --- a/src/drawing/drawing.h +++ b/src/drawing/drawing.h @@ -69,6 +69,17 @@ typedef struct { void *data; } rct_gx; +typedef struct { + uint8 blue; + uint8 green; + uint8 red; + uint8 alpha; +} rct_palette_entry; + +typedef struct { + rct_palette_entry entries[256]; +} rct_palette; + #define SPRITE_ID_PALETTE_COLOUR_1(colourId) ((IMAGE_TYPE_USE_PALETTE << 28) | ((colourId) << 19)) extern const uint16 palette_to_g1_offset[]; diff --git a/src/image_io.c b/src/image_io.c new file mode 100644 index 0000000000..cc14cc012c --- /dev/null +++ b/src/image_io.c @@ -0,0 +1,371 @@ +#ifdef USE_LIBPNG + #include +#else + #include +#endif + +#include "image_io.h" + +#ifdef USE_LIBPNG + static void my_png_read_data(png_structp png_ptr, png_bytep data, png_size_t length); + static void my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length); + static void my_png_flush(png_structp png_ptr); +#endif + +bool image_io_png_read(uint8 **pixels, uint32 *width, uint32 *height, const utf8 *path) +{ +#ifdef USE_LIBPNG + png_structp png_ptr; + png_infop info_ptr; + unsigned int sig_read = 0; + + // Setup PNG structures + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + return false; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return false; + } + + // Open PNG file + SDL_RWops *fp = SDL_RWFromFile(path, "rb"); + if (fp == NULL) { + return false; + } + + // Set error handling + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + SDL_RWclose(fp); + return false; + } + + // Setup png reading + png_set_read_fn(png_ptr, fp, my_png_read_data); + png_set_sig_bytes(png_ptr, sig_read); + + // To simplify the reading process, convert 4-16 bit data to 24-32 bit data + png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL); + + // Read header + png_uint_32 pngWidth, pngHeight; + int bit_depth, color_type, interlace_type; + png_get_IHDR(png_ptr, info_ptr, &pngWidth, &pngHeight, &bit_depth, &color_type, &interlace_type, NULL, NULL); + + // Read pixels as 32bpp RGBA data + png_size_t rowBytes = png_get_rowbytes(png_ptr, info_ptr); + png_bytepp rowPointers = png_get_rows(png_ptr, info_ptr); + uint8 *pngPixels = (uint8*)malloc(pngWidth * pngHeight * 4); + uint8 *dst = pngPixels; + if (color_type == PNG_COLOR_TYPE_RGB) { + // 24-bit PNG (no alpha) + const png_size_t expectedRowSize = pngWidth * 3; + for (png_uint_32 i = 0; i < pngHeight; i++) { + assert(rowBytes == expectedRowSize); + + uint8 *src = rowPointers[i]; + for (png_uint_32 x = 0; x < pngWidth; x++) { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + *dst++ = 255; + } + } + } else { + // 32-bit PNG (with alpha) + const png_size_t expectedRowSize = pngWidth * 4; + for (png_uint_32 i = 0; i < pngHeight; i++) { + assert(rowBytes == expectedRowSize); + + memcpy(dst, rowPointers[i], rowBytes); + dst += rowBytes; + } + } + + // Close the PNG + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + SDL_RWclose(fp); + + // Return the output data + *pixels = (uint8*)pngPixels; + if (width != NULL) *width = pngWidth; + if (height != NULL) *height = pngHeight; + return true; +#else + // Read the pixels as 32bpp RGBA + unsigned char *pngPixels; + unsigned int pngWidth, pngHeight; + unsigned int pngError = lodepng_decode_file(&pngPixels, &pngWidth, &pngHeight, path, LCT_RGBA, 8); + if (pngError != 0) { + free(pngPixels); + log_error("Error creating PNG data, %u: %s", pngError, lodepng_error_text(pngError)); + return false; + } + + // Return the output data + *pixels = (uint8*)pngPixels; + if (width != NULL) *width = pngWidth; + if (height != NULL) *height = pngHeight; + return true; +#endif +} + +bool image_io_png_write(const rct_drawpixelinfo *dpi, const rct_palette *palette, const utf8 *path) +{ +#ifdef USE_LIBPNG + // Get image size + int stride = dpi->width + dpi->pitch; + + // Setup PNG + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png_ptr == NULL) { + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + return false; + } + + png_colorp png_palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH * sizeof(png_color)); + for (int i = 0; i < 256; i++) { + const rct_palette_entry *entry = &palette->entries[i]; + png_palette[i].blue = entry->blue; + png_palette[i].green = entry->green; + png_palette[i].red = entry->red; + } + + png_set_PLTE(png_ptr, info_ptr, png_palette, PNG_MAX_PALETTE_LENGTH); + + // Open file for writing + SDL_RWops *file = SDL_RWFromFile(path, "wb"); + if (file == NULL) { + png_free(png_ptr, png_palette); + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + return false; + } + png_set_write_fn(png_ptr, file, my_png_write_data, my_png_flush); + + // Set error handler + if (setjmp(png_jmpbuf(png_ptr))) { + png_free(png_ptr, png_palette); + png_destroy_write_struct(&png_ptr, &info_ptr); + SDL_RWclose(file); + return false; + } + + // Write header + png_set_IHDR( + png_ptr, info_ptr, dpi->width, dpi->height, 8, + PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT + ); + png_byte transparentIndex = 0; + png_set_tRNS(png_ptr, info_ptr, &transparentIndex, 1, NULL); + png_write_info(png_ptr, info_ptr); + + // Write pixels + uint8 *bits = dpi->bits; + for (int y = 0; y < dpi->height; y++) { + png_write_row(png_ptr, (png_const_bytep)bits); + bits += stride; + } + + // Finish + png_write_end(png_ptr, NULL); + SDL_RWclose(file); + + png_free(png_ptr, png_palette); + png_destroy_write_struct(&png_ptr, &info_ptr); + return true; +#else + unsigned int error; + unsigned char* png; + size_t pngSize; + LodePNGState state; + + lodepng_state_init(&state); + state.info_raw.colortype = LCT_PALETTE; + + // Get image size + int stride = dpi->width + dpi->pitch; + + lodepng_palette_add(&state.info_raw, 0, 0, 0, 0); + for (int i = 1; i < 256; i++) { + const rct_palette_entry *entry = &palette->entries[i]; + uint8 r = entry->red; + uint8 g = entry->green; + uint8 b = entry->blue; + uint8 a = 255; + lodepng_palette_add(&state.info_raw, r, g, b, a); + } + + error = lodepng_encode(&png, &pngSize, dpi->bits, stride, dpi->height, &state); + if (error != 0) { + log_error("Error creating PNG data, %u: %s", error, lodepng_error_text(error)); + free(png); + return false; + } else { + SDL_RWops *file = SDL_RWFromFile(path, "wb"); + if (file == NULL) { + free(png); + return false; + } + SDL_RWwrite(file, png, pngSize, 1); + SDL_RWclose(file); + } + + free(png); + return true; +#endif +} + +#ifdef USE_LIBPNG + +static void my_png_read_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + SDL_RWops *file = (SDL_RWops*)png_get_io_ptr(png_ptr); + SDL_RWread(file, data, length, 1); +} + +static void my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + SDL_RWops *file = (SDL_RWops*)png_get_io_ptr(png_ptr); + SDL_RWwrite(file, data, length, 1); +} + +static void my_png_flush(png_structp png_ptr) +{ + +} + +#endif + +// Bitmap header structs, for cross platform purposes +typedef struct { + uint16 bfType; + uint32 bfSize; + uint16 bfReserved1; + uint16 bfReserved2; + uint32 bfOffBits; +} BitmapFileHeader; + +typedef struct { + uint32 biSize; + sint32 biWidth; + sint32 biHeight; + uint16 biPlanes; + uint16 biBitCount; + uint32 biCompression; + uint32 biSizeImage; + sint32 biXPelsPerMeter; + sint32 biYPelsPerMeter; + uint32 biClrUsed; + uint32 biClrImportant; +} BitmapInfoHeader; + +/** + * + * rct2: 0x00683D20 + */ +bool image_io_bmp_write(const rct_drawpixelinfo *dpi, const rct_palette *palette, const utf8 *path) +{ + BitmapFileHeader header; + BitmapInfoHeader info; + + int i, y, width, height, stride; + uint8 *buffer, *row; + SDL_RWops *fp; + unsigned int bytesWritten; + + // Open binary file for writing + if ((fp = SDL_RWFromFile(path, "wb")) == NULL){ + return false; + } + + // Allocate buffer + buffer = malloc(0xFFFF); + if (buffer == NULL) { + SDL_RWclose(fp); + return false; + } + + // Get image size + width = dpi->width; + height = dpi->height; + stride = dpi->width + dpi->pitch; + + // File header + memset(&header, 0, sizeof(header)); + header.bfType = 0x4D42; + header.bfSize = height * stride + 1038; + header.bfOffBits = 1038; + + bytesWritten = SDL_RWwrite(fp, &header, sizeof(BitmapFileHeader), 1); + if (bytesWritten != 1) { + SDL_RWclose(fp); + SafeFree(buffer); + log_error("failed to save screenshot"); + return false; + } + + // Info header + memset(&info, 0, sizeof(info)); + info.biSize = sizeof(info); + info.biWidth = width; + info.biHeight = height; + info.biPlanes = 1; + info.biBitCount = 8; + info.biXPelsPerMeter = 2520; + info.biYPelsPerMeter = 2520; + info.biClrUsed = 246; + + bytesWritten = SDL_RWwrite(fp, &info, sizeof(BitmapInfoHeader), 1); + if (bytesWritten != 1) { + SDL_RWclose(fp); + SafeFree(buffer); + log_error("failed to save screenshot"); + return false; + } + + // Palette + memset(buffer, 0, 246 * 4); + for (i = 0; i < 246; i++) { + const rct_palette_entry *entry = &palette->entries[i]; + buffer[i * 4 + 0] = entry->blue; + buffer[i * 4 + 1] = entry->green; + buffer[i * 4 + 2] = entry->red; + } + + bytesWritten = SDL_RWwrite(fp, buffer, sizeof(char), 246 * 4); + if (bytesWritten != 246*4){ + SDL_RWclose(fp); + SafeFree(buffer); + log_error("failed to save screenshot"); + return false; + } + + // Image, save upside down + for (y = dpi->height - 1; y >= 0; y--) { + row = dpi->bits + y * (dpi->width + dpi->pitch); + + memset(buffer, 0, stride); + memcpy(buffer, row, dpi->width); + + bytesWritten = SDL_RWwrite(fp, buffer, sizeof(char), stride); + if (bytesWritten != stride){ + SDL_RWclose(fp); + SafeFree(buffer); + log_error("failed to save screenshot"); + return false; + } + } + + SDL_RWclose(fp); + free(buffer); + return true; +} diff --git a/src/image_io.h b/src/image_io.h new file mode 100644 index 0000000000..fa8c086b05 --- /dev/null +++ b/src/image_io.h @@ -0,0 +1,12 @@ +#ifndef _IMAGE_IO_H_ +#define _IMAGE_IO_H_ + +#include "common.h" +#include "drawing/drawing.h" + +bool image_io_png_read(uint8 **pixels, uint32 *width, uint32 *height, const utf8 *path); + +bool image_io_png_write(const rct_drawpixelinfo *dpi, const rct_palette *palette, const utf8 *path); +bool image_io_bmp_write(const rct_drawpixelinfo *dpi, const rct_palette *palette, const utf8 *path); + +#endif diff --git a/src/interface/screenshot.c b/src/interface/screenshot.c index 25af755b56..81b7693e0e 100644 --- a/src/interface/screenshot.c +++ b/src/interface/screenshot.c @@ -17,13 +17,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *****************************************************************************/ -#pragma pack(1) -#include #include "../addresses.h" #include "../config.h" #include "../drawing/drawing.h" #include "../game.h" +#include "../image_io.h" #include "../localisation/localisation.h" #include "../openrct2.h" #include "../platform/platform.h" @@ -109,250 +108,44 @@ int screenshot_dump() } } -// Bitmap header structs, for cross platform purposes -typedef struct { - uint16 bfType; - uint32 bfSize; - uint16 bfReserved1; - uint16 bfReserved2; - uint32 bfOffBits; -} BitmapFileHeader; - -typedef struct { - uint32 biSize; - sint32 biWidth; - sint32 biHeight; - uint16 biPlanes; - uint16 biBitCount; - uint32 biCompression; - uint32 biSizeImage; - sint32 biXPelsPerMeter; - sint32 biYPelsPerMeter; - uint32 biClrUsed; - uint32 biClrImportant; -} BitmapInfoHeader; - /** * * rct2: 0x00683D20 */ int screenshot_dump_bmp() { - BitmapFileHeader header; - BitmapInfoHeader info; - - int i, y, index, width, height, stride; - char path[MAX_PATH]; - uint8 *buffer, *row; - SDL_RWops *fp; - unsigned int bytesWritten; - // Get a free screenshot path - if ((index = screenshot_get_next_path(path, SCREENSHOT_FORMAT_BMP)) == -1) - return -1; - - // Open binary file for writing - if ((fp = SDL_RWFromFile(path, "wb")) == NULL){ + int index; + char path[MAX_PATH] = ""; + if ((index = screenshot_get_next_path(path, SCREENSHOT_FORMAT_BMP)) == -1) { return -1; } - // Allocate buffer - buffer = malloc(0xFFFF); - if (buffer == NULL) { - SDL_RWclose(fp); - return -1; - } - - // Get image size - width = RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_WIDTH, uint16); - height = RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_HEIGHT, uint16); - stride = (width + 3) & 0xFFFFFFFC; - - // File header - memset(&header, 0, sizeof(header)); - header.bfType = 0x4D42; - header.bfSize = height * stride + 1038; - header.bfOffBits = 1038; - - bytesWritten = SDL_RWwrite(fp, &header, sizeof(BitmapFileHeader), 1); - if (bytesWritten != 1) { - SDL_RWclose(fp); - SafeFree(buffer); - log_error("failed to save screenshot"); - return -1; - } - - // Info header - memset(&info, 0, sizeof(info)); - info.biSize = sizeof(info); - info.biWidth = width; - info.biHeight = height; - info.biPlanes = 1; - info.biBitCount = 8; - info.biXPelsPerMeter = 2520; - info.biYPelsPerMeter = 2520; - info.biClrUsed = 246; - - bytesWritten = SDL_RWwrite(fp, &info, sizeof(BitmapInfoHeader), 1); - if (bytesWritten != 1) { - SDL_RWclose(fp); - SafeFree(buffer); - log_error("failed to save screenshot"); - return -1; - } - - // Palette - memset(buffer, 0, 246 * 4); - for (i = 0; i < 246; i++) { - buffer[i * 4 + 0] = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 0]; - buffer[i * 4 + 1] = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 1]; - buffer[i * 4 + 2] = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 2]; - } - - bytesWritten = SDL_RWwrite(fp, buffer, sizeof(char), 246 * 4); - if (bytesWritten != 246*4){ - SDL_RWclose(fp); - SafeFree(buffer); - log_error("failed to save screenshot"); - return -1; - } - - // Image, save upside down rct_drawpixelinfo *dpi = RCT2_ADDRESS(RCT2_ADDRESS_SCREEN_DPI, rct_drawpixelinfo); - for (y = dpi->height - 1; y >= 0; y--) { - row = dpi->bits + y * (dpi->width + dpi->pitch); - - memset(buffer, 0, stride); - memcpy(buffer, row, dpi->width); - - bytesWritten = SDL_RWwrite(fp, buffer, sizeof(char), stride); - if (bytesWritten != stride){ - SDL_RWclose(fp); - SafeFree(buffer); - log_error("failed to save screenshot"); - return -1; - } + rct_palette *palette = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, rct_palette); + if (image_io_bmp_write(dpi, palette, path)) { + return index; + } else { + return -1; } - - SDL_RWclose(fp); - free(buffer); - - return index; } int screenshot_dump_png() { - rct_drawpixelinfo *dpi = RCT2_ADDRESS(RCT2_ADDRESS_SCREEN_DPI, rct_drawpixelinfo); - - int i, index, width, height, padding; - char path[MAX_PATH] = ""; - unsigned int error; - unsigned char r, g, b, a = 255; - - unsigned char* png; - size_t pngSize; - LodePNGState state; - // Get a free screenshot path - if ((index = screenshot_get_next_path(path, SCREENSHOT_FORMAT_PNG)) == -1) + int index; + char path[MAX_PATH] = ""; + if ((index = screenshot_get_next_path(path, SCREENSHOT_FORMAT_PNG)) == -1) { return -1; - - - lodepng_state_init(&state); - state.info_raw.colortype = LCT_PALETTE; - - // Get image size - width = RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_WIDTH, uint16); - height = RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_HEIGHT, uint16); - - padding = dpi->pitch; - - for (i = 0; i < 256; i++) { - b = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 0]; - g = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 1]; - r = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 2]; - - lodepng_palette_add(&state.info_raw, r, g, b, a); } - uint8* pixels = dpi->bits; - - if (padding > 0) { - pixels = malloc(width * height); - if (!pixels) { - return -1; - } - uint8* src = dpi->bits; - uint8* dst = pixels; - for (int y = height; y > 0; y--) { - for (int x = width; x > 0; x--) { - *dst++ = *src++; - } - src += padding; - } - } - - error = lodepng_encode(&png, &pngSize, pixels, width, height, &state); - if (error) { - log_error("Unable to save screenshot, %u: %s", lodepng_error_text(error)); - index = -1; + rct_drawpixelinfo *dpi = RCT2_ADDRESS(RCT2_ADDRESS_SCREEN_DPI, rct_drawpixelinfo); + rct_palette *palette = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, rct_palette); + if (image_io_png_write(dpi, palette, path)) { + return index; } else { - SDL_RWops *file = SDL_RWFromFile(path, "wb"); - if (file == NULL) { - log_error("Unable to save screenshot, %s", SDL_GetError()); - index = -1; - } else { - SDL_RWwrite(file, png, pngSize, 1); - SDL_RWclose(file); - } + return -1; } - - free(png); - if ((utf8*)pixels != (utf8*)dpi->bits) { - free(pixels); - } - return index; -} - -bool screenshot_write_png(rct_drawpixelinfo *dpi, const char *path) -{ - unsigned int error; - unsigned char* png; - size_t pngSize; - LodePNGState state; - - lodepng_state_init(&state); - state.info_raw.colortype = LCT_PALETTE; - - // Get image size - int stride = (dpi->width + 3) & ~3; - - for (int i = 0; i < 256; i++) { - unsigned char r, g, b, a = 255; - - b = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 0]; - g = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 1]; - r = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, uint8)[i * 4 + 2]; - - lodepng_palette_add(&state.info_raw, r, g, b, a); - } - - error = lodepng_encode(&png, &pngSize, dpi->bits, stride, dpi->height, &state); - if (error != 0) { - free(png); - return false; - } else { - SDL_RWops *file = SDL_RWFromFile(path, "wb"); - if (file == NULL) { - free(png); - return false; - } - SDL_RWwrite(file, png, pngSize, 1); - SDL_RWclose(file); - } - - free(png); - return true; } void screenshot_giant() @@ -436,7 +229,8 @@ void screenshot_giant() return; } - screenshot_write_png(&dpi, path); + rct_palette *palette = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, rct_palette); + image_io_png_write(&dpi, palette, path); free(dpi.bits); @@ -570,7 +364,8 @@ int cmdline_for_screenshot(const char **argv, int argc) viewport_render(&dpi, &viewport, 0, 0, viewport.width, viewport.height); - screenshot_write_png(&dpi, outputPath); + rct_palette *palette = RCT2_ADDRESS(RCT2_ADDRESS_PALETTE, rct_palette); + image_io_png_write(&dpi, palette, outputPath); free(dpi.bits); }