#include #include #include #include #include #include #include #include // Compatibility with FreeImage <3.1.3. Subtly changes Gosu's behavior though. #ifndef JPEG_EXIFROTATE #define JPEG_EXIFROTATE 0 #endif // With MSVC, add a suffix so FreeImage can be linked as a fallback for GDI+. // With MinGW, Gosu uses FreeImage all the time, so no suffix is needed. #ifdef _MSC_VER #define FI(x) x##_FreeImage #else #define FI(x) x #endif namespace { const int GOSU_FIFLAGS = (JPEG_EXIFROTATE | ICO_MAKEALPHA | PNG_IGNOREGAMMA); void reshuffleBitmap(Gosu::Bitmap& bitmap) { // Since FreeImage gracefully ignores the MASK parameters above, we // manually exchange the R and B channels. std::tr1::uint32_t* p = reinterpret_cast(bitmap.data()); for (int i = bitmap.width() * bitmap.height(); i > 0; --i, ++p) *p = (*p & 0xff00ff00) | ((*p << 16) & 0x00ff0000) | ((*p >> 16) & 0x000000ff); } FIBITMAP* ensure32bits(FIBITMAP* fib) { int bpp = FreeImage_GetBPP(fib); FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(fib); if (bpp != 32 && (image_type == FIT_BITMAP || image_type == FIT_RGBA16)) { FIBITMAP* fib32 = FreeImage_ConvertTo32Bits(fib); FreeImage_Unload(fib); fib = fib32; } return fib; } void fibToBitmap(Gosu::Bitmap& bitmap, FIBITMAP* fib, FREE_IMAGE_FORMAT fif) { bitmap.resize(FreeImage_GetWidth(fib), FreeImage_GetHeight(fib)); fib = ensure32bits(fib); FreeImage_ConvertToRawBits(reinterpret_cast(bitmap.data()), fib, bitmap.width() * 4, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, TRUE); FreeImage_Unload(fib); reshuffleBitmap(bitmap); if (fif == FIF_BMP) Gosu::applyColorKey(bitmap, Gosu::Color::FUCHSIA); } FIBITMAP* bitmapToFIB(Gosu::Bitmap bitmap, FREE_IMAGE_FORMAT fif) { reshuffleBitmap(bitmap); if (fif == FIF_BMP) unapplyColorKey(bitmap, Gosu::Color::FUCHSIA); return FreeImage_ConvertFromRawBits((BYTE*)bitmap.data(), bitmap.width(), bitmap.height(), bitmap.width() * 4, 32, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, true); } // Wrap Gosu::Writer as a FreeImageIO. unsigned DLL_CALLCONV WriteProc(void *buffer, unsigned size, unsigned count, fi_handle handle) { ((Gosu::Writer*)handle)->write(buffer, size * count); return count; } int DLL_CALLCONV SeekProc(fi_handle handle, long offset, int origin) { Gosu::Writer& writer = *(Gosu::Writer*)handle; switch (origin) { case SEEK_SET: writer.setPosition(offset); break; case SEEK_CUR: writer.seek(offset); break; case SEEK_END: writer.setPosition(writer.resource().size() - offset); break; }; return 0; } long DLL_CALLCONV TellProc(fi_handle handle) { return ((Gosu::Writer*)handle)->position(); } // TODO: This is not thread safe! std::string lastFreeImageError; void DLL_CALLCONV FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) { lastFreeImageError = (message && message[0]) ? message : "Unknown error"; if (fif != FIF_UNKNOWN) if (const char* format = FreeImage_GetFormatFromFIF(fif)) lastFreeImageError += std::string(" (in ") + format + " parser)"; } void checkForFreeImageErrors(bool value = true) { if (!value || !lastFreeImageError.empty()) { std::string message = lastFreeImageError; if (message.empty()) message = "Unknown error"; else lastFreeImageError.clear(); throw std::runtime_error(message); } } } namespace Gosu { void FI(loadImageFile)(Bitmap& bitmap, const std::wstring& filename) { #ifdef GOSU_IS_WIN FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeU(filename.c_str()); FIBITMAP* fib = FreeImage_LoadU(fif, filename.c_str(), GOSU_FIFLAGS); #else std::string utf8Filename = wstringToUTF8(filename); FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(utf8Filename.c_str()); FIBITMAP* fib = FreeImage_Load(fif, utf8Filename.c_str(), GOSU_FIFLAGS); #endif checkForFreeImageErrors(fib); fibToBitmap(bitmap, fib, fif); } void FI(loadImageFile)(Bitmap& bitmap, Gosu::Reader input) { // Read all available input std::vector data(input.resource().size() - input.position()); input.read(&data[0], data.size()); FIMEMORY* fim = FreeImage_OpenMemory(&data[0], data.size()); FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(fim); FIBITMAP* fib = FreeImage_LoadFromMemory(fif, fim, GOSU_FIFLAGS); checkForFreeImageErrors(fib); fibToBitmap(bitmap, fib, fif); } void FI(saveImageFile)(const Bitmap& bitmap, const std::wstring& filename) { std::string utf8Filename = wstringToUTF8(filename); FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(utf8Filename.c_str()); FIBITMAP* fib = bitmapToFIB(bitmap, fif); #ifdef GOSU_IS_WIN FreeImage_SaveU(fif, fib, filename.c_str()); #else FreeImage_Save(fif, fib, utf8Filename.c_str()); #endif FreeImage_Unload(fib); checkForFreeImageErrors(); } void FI(saveImageFile)(const Bitmap& bitmap, Gosu::Writer writer, const std::wstring& formatHint) { std::string utf8FormatHint = wstringToUTF8(formatHint); FREE_IMAGE_FORMAT fif = FreeImage_GetFIFFromFilename(utf8FormatHint.c_str()); FIBITMAP* fib = bitmapToFIB(bitmap, fif); FreeImageIO fio = { NULL, WriteProc, SeekProc, TellProc }; FreeImage_SaveToHandle(fif, fib, &fio, &writer); FreeImage_Unload(fib); checkForFreeImageErrors(); } }