winamp/Src/external_dependencies/openmpt-trunk/mptrack/Image.cpp
2024-09-24 14:54:57 +02:00

338 lines
8.8 KiB
C++

/*
* 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