123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- /*
- * Image.cpp
- * ---------
- * Purpose: Bitmap and Vector image file handling using GDI+.
- * Notes : (currently none)
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "MPTrackUtil.h"
- #include "Image.h"
- #include "../common/FileReader.h"
- #include "../common/ComponentManager.h"
- // GDI+
- #include <atlbase.h>
- #define max(a, b) (((a) > (b)) ? (a) : (b))
- #define min(a, b) (((a) < (b)) ? (a) : (b))
- #if MPT_COMPILER_MSVC
- #pragma warning(push)
- #pragma warning(disable : 4458) // declaration of 'x' hides class member
- #endif
- #include <gdiplus.h>
- #if MPT_COMPILER_MSVC
- #pragma warning(pop)
- #endif
- #undef min
- #undef max
- OPENMPT_NAMESPACE_BEGIN
- GdiplusRAII::GdiplusRAII()
- {
- Gdiplus::GdiplusStartupInput gdiplusStartupInput;
- Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
- }
- GdiplusRAII::~GdiplusRAII()
- {
- Gdiplus::GdiplusShutdown(gdiplusToken);
- gdiplusToken = 0;
- }
- RawGDIDIB::RawGDIDIB(uint32 width, uint32 height)
- : width(width)
- , height(height)
- , pixels(width * height)
- {
- MPT_ASSERT(width > 0);
- MPT_ASSERT(height > 0);
- }
- namespace GDIP
- {
- static CComPtr<IStream> GetStream(mpt::const_byte_span data)
- {
- CComPtr<IStream> stream;
- #if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
- stream.Attach(SHCreateMemStream(mpt::byte_cast<const unsigned char *>(data.data()), mpt::saturate_cast<UINT>(data.size())));
- #else
- HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, data.size());
- if(hGlobal == NULL)
- {
- throw bad_image();
- }
- void * mem = GlobalLock(hGlobal);
- if(!mem)
- {
- hGlobal = GlobalFree(hGlobal);
- throw bad_image();
- }
- std::memcpy(mem, data.data(), data.size());
- GlobalUnlock(hGlobal);
- if(CreateStreamOnHGlobal(hGlobal, TRUE, &stream) != S_OK)
- {
- hGlobal = GlobalFree(hGlobal);
- throw bad_image();
- }
- hGlobal = NULL;
- #endif
- if(!stream)
- {
- throw bad_image();
- }
- return stream;
- }
- std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(mpt::const_byte_span file)
- {
- CComPtr<IStream> stream = GetStream(file);
- std::unique_ptr<Gdiplus::Bitmap> result = std::make_unique<Gdiplus::Bitmap>(stream, FALSE);
- if(result->GetLastStatus() != Gdiplus::Ok)
- {
- throw bad_image();
- }
- if(result->GetWidth() == 0 || result->GetHeight() == 0)
- {
- throw bad_image();
- }
- return result;
- }
- std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(FileReader file)
- {
- FileReader::PinnedView view = file.GetPinnedView();
- return LoadPixelImage(view.span());
- }
- std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(mpt::const_byte_span file)
- {
- CComPtr<IStream> stream = GetStream(file);
- std::unique_ptr<Gdiplus::Metafile> result = std::make_unique<Gdiplus::Metafile>(stream);
- if(result->GetLastStatus() != Gdiplus::Ok)
- {
- throw bad_image();
- }
- if(result->GetWidth() == 0 || result->GetHeight() == 0)
- {
- throw bad_image();
- }
- return result;
- }
- std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(FileReader file)
- {
- FileReader::PinnedView view = file.GetPinnedView();
- return LoadVectorImage(view.span());
- }
- static std::unique_ptr<Gdiplus::Bitmap> DoResize(Gdiplus::Image &src, double scaling, int spriteWidth, int spriteHeight)
- {
- const int width = src.GetWidth(), height = src.GetHeight();
- int newWidth = 0, newHeight = 0, newSpriteWidth = 0, newSpriteHeight = 0;
- if(spriteWidth <= 0 || spriteHeight <= 0)
- {
- newWidth = mpt::saturate_round<int>(width * scaling);
- newHeight = mpt::saturate_round<int>(height * scaling);
- } else
- {
- // Sprite mode: Source images consists of several sprites / icons that should be scaled individually
- newSpriteWidth = mpt::saturate_round<int>(spriteWidth * scaling);
- newSpriteHeight = mpt::saturate_round<int>(spriteHeight * scaling);
- newWidth = width * newSpriteWidth / spriteWidth;
- newHeight = height * newSpriteHeight / spriteHeight;
- }
- std::unique_ptr<Gdiplus::Bitmap> resizedImage = std::make_unique<Gdiplus::Bitmap>(newWidth, newHeight, PixelFormat32bppARGB);
- std::unique_ptr<Gdiplus::Graphics> resizedGraphics(Gdiplus::Graphics::FromImage(resizedImage.get()));
- if(scaling >= 1.5)
- {
- // Prefer crisp look on real high-DPI devices
- resizedGraphics->SetInterpolationMode(Gdiplus::InterpolationModeNearestNeighbor);
- resizedGraphics->SetPixelOffsetMode(Gdiplus::PixelOffsetModeHalf);
- resizedGraphics->SetSmoothingMode(Gdiplus::SmoothingModeNone);
- } else
- {
- resizedGraphics->SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
- resizedGraphics->SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
- resizedGraphics->SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
- }
- if(spriteWidth <= 0 || spriteHeight <= 0)
- {
- resizedGraphics->DrawImage(&src, 0, 0, newWidth, newHeight);
- } else
- {
- // Draw each source sprite individually into separate image to avoid neighbouring source sprites bleeding in
- std::unique_ptr<Gdiplus::Bitmap> spriteImage = std::make_unique<Gdiplus::Bitmap>(spriteWidth, spriteHeight, PixelFormat32bppARGB);
- std::unique_ptr<Gdiplus::Graphics> spriteGraphics(Gdiplus::Graphics::FromImage(spriteImage.get()));
- for(int srcY = 0, destY = 0; srcY < height; srcY += spriteHeight, destY += newSpriteHeight)
- {
- for(int srcX = 0, destX = 0; srcX < width; srcX += spriteWidth, destX += newSpriteWidth)
- {
- spriteGraphics->Clear({0, 0, 0, 0});
- spriteGraphics->DrawImage(&src, Gdiplus::Rect(0, 0, spriteWidth, spriteHeight), srcX, srcY, spriteWidth, spriteHeight, Gdiplus::UnitPixel);
- resizedGraphics->DrawImage(spriteImage.get(), destX, destY, newSpriteWidth, newSpriteHeight);
- }
- }
- }
- return resizedImage;
- }
- std::unique_ptr<Gdiplus::Image> ResizeImage(Gdiplus::Image &src, double scaling, int spriteWidth, int spriteHeight)
- {
- return DoResize(src, scaling, spriteWidth, spriteHeight);
- }
- std::unique_ptr<Gdiplus::Bitmap> ResizeImage(Gdiplus::Bitmap &src, double scaling, int spriteWidth, int spriteHeight)
- {
- return DoResize(src, scaling, spriteWidth, spriteHeight);
- }
- } // namespace GDIP
- std::unique_ptr<RawGDIDIB> ToRawGDIDIB(Gdiplus::Bitmap &bitmap)
- {
- Gdiplus::BitmapData bitmapData;
- Gdiplus::Rect rect{Gdiplus::Point{0, 0}, Gdiplus::Size{static_cast<INT>(bitmap.GetWidth()), static_cast<INT>(bitmap.GetHeight())}};
- std::unique_ptr<RawGDIDIB> result = std::make_unique<RawGDIDIB>(bitmap.GetWidth(), bitmap.GetHeight());
- if(bitmap.LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bitmapData) != Gdiplus::Ok)
- {
- throw bad_image();
- }
- RawGDIDIB::Pixel *dst = result->Pixels().data();
- for(uint32 y = 0; y < result->Height(); ++y)
- {
- const GDIP::Pixel *src = GDIP::GetScanline(bitmapData, y);
- for(uint32 x = 0; x < result->Width(); ++x)
- {
- *dst = GDIP::ToRawGDIDIB(*src);
- src++;
- dst++;
- }
- }
- bitmap.UnlockBits(&bitmapData);
- return result;
- }
- std::unique_ptr<RawGDIDIB> LoadPixelImage(mpt::const_byte_span file, double scaling, int spriteWidth, int spriteHeight)
- {
- auto bitmap = GDIP::LoadPixelImage(file);
- if(scaling != 1.0)
- bitmap = GDIP::ResizeImage(*bitmap, scaling, spriteWidth, spriteHeight);
- return ToRawGDIDIB(*bitmap);
- }
- std::unique_ptr<RawGDIDIB> LoadPixelImage(FileReader file, double scaling, int spriteWidth, int spriteHeight)
- {
- auto bitmap = GDIP::LoadPixelImage(file);
- if(scaling != 1.0)
- bitmap = GDIP::ResizeImage(*bitmap, scaling, spriteWidth, spriteHeight);
- return ToRawGDIDIB(*bitmap);
- }
- // Create a DIB for the current device from our PNG.
- bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, const RawGDIDIB &src)
- {
- BITMAPINFOHEADER bi;
- MemsetZero(bi);
- bi.biSize = sizeof(BITMAPINFOHEADER);
- bi.biWidth = src.Width();
- bi.biHeight = -static_cast<LONG>(src.Height());
- bi.biPlanes = 1;
- bi.biBitCount = 32;
- bi.biCompression = BI_RGB;
- bi.biSizeImage = src.Width() * src.Height() * 4;
- if(!dst.CreateCompatibleBitmap(&dc, src.Width(), src.Height()))
- {
- return false;
- }
- if(!SetDIBits(dc.GetSafeHdc(), dst, 0, src.Height(), src.Pixels().data(), reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS))
- {
- return false;
- }
- return true;
- }
- bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, Gdiplus::Image &src)
- {
- if(!dst.CreateCompatibleBitmap(&dc, src.GetWidth(), src.GetHeight()))
- {
- return false;
- }
- CDC memdc;
- if(!memdc.CreateCompatibleDC(&dc))
- {
- return false;
- }
- memdc.SelectObject(dst);
- Gdiplus::Graphics gfx(memdc);
- if(gfx.DrawImage(&src, 0, 0) != Gdiplus::Ok)
- {
- return false;
- }
- return true;
- }
- bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, mpt::const_byte_span file)
- {
- try
- {
- std::unique_ptr<Gdiplus::Bitmap> pBitmap = GDIP::LoadPixelImage(file);
- if(!CopyToCompatibleBitmap(dst, dc, *pBitmap))
- {
- return false;
- }
- } catch(...)
- {
- return false;
- }
- return true;
- }
- bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, FileReader file)
- {
- try
- {
- std::unique_ptr<Gdiplus::Bitmap> pBitmap = GDIP::LoadPixelImage(file);
- if(!CopyToCompatibleBitmap(dst, dc, *pBitmap))
- {
- return false;
- }
- } catch(...)
- {
- return false;
- }
- return true;
- }
- OPENMPT_NAMESPACE_END
|