Initial community commit
This commit is contained in:
parent
537bcbc862
commit
fc06254474
16440 changed files with 4239995 additions and 2 deletions
337
Src/external_dependencies/openmpt-trunk/mptrack/Image.cpp
Normal file
337
Src/external_dependencies/openmpt-trunk/mptrack/Image.cpp
Normal file
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* 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
|
Loading…
Add table
Add a link
Reference in a new issue