2021-06-07 21:02:42 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//
|
|
|
|
// EmulationStation Desktop Edition
|
|
|
|
// MiximageGenerator.cpp
|
|
|
|
//
|
2021-11-09 22:00:16 +00:00
|
|
|
// Generates miximages from screenshots, marquees, 3D boxes/covers and physical media images.
|
2021-06-08 20:25:53 +00:00
|
|
|
// Called from GuiScraperSearch and GuiOfflineGenerator.
|
2021-06-07 21:02:42 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "MiximageGenerator.h"
|
|
|
|
|
|
|
|
#include "Log.h"
|
|
|
|
#include "Settings.h"
|
|
|
|
#include "SystemData.h"
|
2021-07-07 18:03:42 +00:00
|
|
|
#include "utils/StringUtil.h"
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
2021-06-08 20:25:53 +00:00
|
|
|
MiximageGenerator::MiximageGenerator(FileData* game, std::string& resultMessage)
|
2022-01-16 11:09:55 +00:00
|
|
|
: mGame {game}
|
|
|
|
, mResultMessage {resultMessage}
|
|
|
|
, mWidth {1280}
|
|
|
|
, mHeight {960}
|
|
|
|
, mMarquee {false}
|
|
|
|
, mBox3D {false}
|
|
|
|
, mCover {false}
|
|
|
|
, mPhysicalMedia {false}
|
2021-06-07 21:02:42 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void MiximageGenerator::startThread(std::promise<bool>* miximagePromise)
|
|
|
|
{
|
|
|
|
mMiximagePromise = miximagePromise;
|
|
|
|
|
2021-06-07 21:14:43 +00:00
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): Creating miximage for \""
|
2021-07-07 18:03:42 +00:00
|
|
|
<< mGame->getFileName() << "\"";
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (mGame->getMiximagePath() != "" && !Settings::getInstance()->getBool("MiximageOverwrite")) {
|
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): File already exists and miximage "
|
2021-07-07 18:03:42 +00:00
|
|
|
"overwriting has not been enabled, aborting";
|
2021-06-07 21:02:42 +00:00
|
|
|
mMiximagePromise->set_value(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((mScreenshotPath = mGame->getScreenshotPath()) == "") {
|
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): "
|
2021-07-07 18:03:42 +00:00
|
|
|
"No screenshot image found, aborting";
|
|
|
|
mResultMessage = "No screenshot image found, couldn't generate miximage";
|
2021-06-07 21:02:42 +00:00
|
|
|
mMiximagePromise->set_value(true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Settings::getInstance()->getBool("MiximageIncludeMarquee")) {
|
|
|
|
if ((mMarqueePath = mGame->getMarqueePath()) != "") {
|
|
|
|
mMarquee = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): No marquee image found";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Settings::getInstance()->getBool("MiximageIncludeBox")) {
|
|
|
|
if ((mBox3DPath = mGame->get3DBoxPath()) != "") {
|
|
|
|
mBox3D = true;
|
|
|
|
}
|
|
|
|
else if (Settings::getInstance()->getBool("MiximageCoverFallback") &&
|
2021-07-07 18:03:42 +00:00
|
|
|
(mCoverPath = mGame->getCoverPath()) != "") {
|
2021-06-07 21:02:42 +00:00
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): "
|
2021-07-07 18:03:42 +00:00
|
|
|
"No 3D box image found, using cover image as fallback";
|
2021-06-07 21:02:42 +00:00
|
|
|
mCover = true;
|
|
|
|
}
|
|
|
|
else if (Settings::getInstance()->getBool("MiximageCoverFallback")) {
|
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): "
|
2021-07-07 18:03:42 +00:00
|
|
|
"No 3D box or cover images found";
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): No 3D box image found";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
if (Settings::getInstance()->getBool("MiximageIncludePhysicalMedia")) {
|
|
|
|
if ((mPhysicalMediaPath = mGame->getPhysicalMediaPath()) != "") {
|
|
|
|
mPhysicalMedia = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG(LogDebug)
|
|
|
|
<< "MiximageGenerator::MiximageGenerator(): No physical media image found";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
const auto startTime = std::chrono::system_clock::now();
|
|
|
|
|
|
|
|
if (generateImage()) {
|
|
|
|
LOG(LogError) << "Failed to generate miximage";
|
2021-06-08 20:25:53 +00:00
|
|
|
mMiximagePromise->set_value(true);
|
|
|
|
mResultMessage = mMessage;
|
|
|
|
return;
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
else {
|
2022-01-09 22:50:08 +00:00
|
|
|
LOG(LogDebug) << "MiximageGenerator::MiximageGenerator(): Processing completed in: "
|
|
|
|
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
|
|
std::chrono::system_clock::now() - startTime)
|
|
|
|
.count()
|
|
|
|
<< " ms";
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mResultMessage = mMessage;
|
|
|
|
mMiximagePromise->set_value(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MiximageGenerator::generateImage()
|
|
|
|
{
|
|
|
|
FREE_IMAGE_FORMAT fileFormat;
|
|
|
|
FIBITMAP* screenshotFile = nullptr;
|
|
|
|
FIBITMAP* marqueeFile = nullptr;
|
|
|
|
FIBITMAP* boxFile = nullptr;
|
2021-10-30 17:01:58 +00:00
|
|
|
FIBITMAP* physicalMediaFile = nullptr;
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
unsigned int fileWidth = 0;
|
|
|
|
unsigned int fileHeight = 0;
|
|
|
|
unsigned int filePitch = 0;
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-06-07 22:02:40 +00:00
|
|
|
fileFormat = FreeImage_GetFileTypeU(Utils::String::stringToWideString(mScreenshotPath).c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFileType(mScreenshotPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN)
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-06-07 22:02:40 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilenameU(
|
2021-07-07 18:03:42 +00:00
|
|
|
Utils::String::stringToWideString(mScreenshotPath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilename(mScreenshotPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN) {
|
|
|
|
LOG(LogError) << "Screenshot image in unknown image format, aborting";
|
2021-06-08 19:07:35 +00:00
|
|
|
mMessage = "Screenshot image in unknown format, couldn't generate miximage";
|
2021-06-07 21:02:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure that we can actually read this format.
|
|
|
|
if (FreeImage_FIFSupportsReading(fileFormat)) {
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
screenshotFile =
|
|
|
|
FreeImage_LoadU(fileFormat, Utils::String::stringToWideString(mScreenshotPath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
screenshotFile = FreeImage_Load(fileFormat, mScreenshotPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
LOG(LogError) << "Screenshot file format not supported";
|
2021-06-08 19:07:35 +00:00
|
|
|
mMessage = "Screenshot image in unsupported format, couldn't generate miximage";
|
2021-06-07 21:02:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!screenshotFile) {
|
|
|
|
LOG(LogError) << "Error loading screenshot image, corrupt file?";
|
2021-06-08 19:07:35 +00:00
|
|
|
mMessage = "Error loading screenshot image, couldn't generate miximage";
|
2021-06-07 21:02:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mMarquee) {
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
fileFormat =
|
|
|
|
FreeImage_GetFileTypeU(Utils::String::stringToWideString(mMarqueePath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFileType(mMarqueePath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN)
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-06-07 22:02:40 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilenameU(
|
2021-07-07 18:03:42 +00:00
|
|
|
Utils::String::stringToWideString(mMarqueePath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilename(mMarqueePath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN) {
|
|
|
|
LOG(LogDebug) << "Marquee in unknown format, skipping image";
|
|
|
|
mMarquee = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!FreeImage_FIFSupportsReading(fileFormat)) {
|
|
|
|
LOG(LogDebug) << "Marquee file format not supported, skipping image";
|
|
|
|
mMarquee = false;
|
|
|
|
}
|
|
|
|
else {
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-06-07 22:02:40 +00:00
|
|
|
marqueeFile = FreeImage_LoadU(fileFormat,
|
2021-07-07 18:03:42 +00:00
|
|
|
Utils::String::stringToWideString(mMarqueePath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
marqueeFile = FreeImage_Load(fileFormat, mMarqueePath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
if (!marqueeFile) {
|
|
|
|
LOG(LogError) << "Couldn't load marquee image, corrupt file?";
|
2021-06-08 19:07:35 +00:00
|
|
|
mMessage = "Error loading marquee image, corrupt file?";
|
2021-06-07 21:02:42 +00:00
|
|
|
mMarquee = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mBox3D) {
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-06-07 22:02:40 +00:00
|
|
|
fileFormat = FreeImage_GetFileTypeU(Utils::String::stringToWideString(mBox3DPath).c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFileType(mBox3DPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN)
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-06-07 22:02:40 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilenameU(
|
2021-07-07 18:03:42 +00:00
|
|
|
Utils::String::stringToWideString(mBox3DPath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilename(mBox3DPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN) {
|
|
|
|
LOG(LogDebug) << "3D box in unknown format, skipping image";
|
|
|
|
mBox3D = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!FreeImage_FIFSupportsReading(fileFormat)) {
|
|
|
|
LOG(LogDebug) << "3D box file format not supported, skipping image";
|
|
|
|
mBox3D = false;
|
|
|
|
}
|
|
|
|
else {
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
boxFile =
|
|
|
|
FreeImage_LoadU(fileFormat, Utils::String::stringToWideString(mBox3DPath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
boxFile = FreeImage_Load(fileFormat, mBox3DPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
if (!boxFile) {
|
|
|
|
LOG(LogError) << "Couldn't load 3D box image, corrupt file?";
|
2021-06-08 19:07:35 +00:00
|
|
|
mMessage = "Error loading 3d box image, corrupt file?";
|
2021-06-07 21:02:42 +00:00
|
|
|
mBox3D = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (mCover) {
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
fileFormat = FreeImage_GetFileTypeU(Utils::String::stringToWideString(mCoverPath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFileType(mCoverPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN)
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
2021-06-07 22:02:40 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilenameU(
|
2021-07-07 18:03:42 +00:00
|
|
|
Utils::String::stringToWideString(mCoverPath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
fileFormat = FreeImage_GetFIFFromFilename(mCoverPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN) {
|
|
|
|
LOG(LogDebug) << "Box cover in unknown format, skipping image";
|
|
|
|
mCover = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!FreeImage_FIFSupportsReading(fileFormat)) {
|
|
|
|
LOG(LogDebug) << "Box cover file format not supported, skipping image";
|
|
|
|
mCover = false;
|
|
|
|
}
|
|
|
|
else {
|
2021-07-07 18:03:42 +00:00
|
|
|
#if defined(_WIN64)
|
|
|
|
boxFile =
|
|
|
|
FreeImage_LoadU(fileFormat, Utils::String::stringToWideString(mCoverPath).c_str());
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
boxFile = FreeImage_Load(fileFormat, mCoverPath.c_str());
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
if (!boxFile) {
|
|
|
|
LOG(LogError) << "Couldn't load box cover image, corrupt file?";
|
2021-06-08 19:07:35 +00:00
|
|
|
mMessage = "Error loading box cover image, corrupt file?";
|
2021-06-07 21:02:42 +00:00
|
|
|
mCover = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
if (mPhysicalMedia) {
|
|
|
|
#if defined(_WIN64)
|
|
|
|
fileFormat =
|
|
|
|
FreeImage_GetFileTypeU(Utils::String::stringToWideString(mPhysicalMediaPath).c_str());
|
|
|
|
#else
|
|
|
|
fileFormat = FreeImage_GetFileType(mPhysicalMediaPath.c_str());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN)
|
|
|
|
#if defined(_WIN64)
|
|
|
|
fileFormat = FreeImage_GetFIFFromFilenameU(
|
|
|
|
Utils::String::stringToWideString(mPhysicalMediaPath).c_str());
|
|
|
|
#else
|
|
|
|
fileFormat = FreeImage_GetFIFFromFilename(mPhysicalMediaPath.c_str());
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (fileFormat == FIF_UNKNOWN) {
|
|
|
|
LOG(LogDebug) << "Physical media in unknown format, skipping image";
|
|
|
|
mPhysicalMedia = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!FreeImage_FIFSupportsReading(fileFormat)) {
|
|
|
|
LOG(LogDebug) << "Physical media file format not supported, skipping image";
|
|
|
|
mPhysicalMedia = false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
#if defined(_WIN64)
|
|
|
|
physicalMediaFile = FreeImage_LoadU(
|
|
|
|
fileFormat, Utils::String::stringToWideString(mPhysicalMediaPath).c_str());
|
|
|
|
#else
|
|
|
|
physicalMediaFile = FreeImage_Load(fileFormat, mPhysicalMediaPath.c_str());
|
|
|
|
#endif
|
|
|
|
if (!physicalMediaFile) {
|
|
|
|
LOG(LogError) << "Couldn't load physical media image, corrupt file?";
|
|
|
|
mMessage = "Error loading physical media image, corrupt file?";
|
|
|
|
mPhysicalMedia = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
unsigned int resolutionMultiplier = 0;
|
|
|
|
|
|
|
|
if (Settings::getInstance()->getString("MiximageResolution") == "640x480") {
|
|
|
|
mWidth = 640;
|
|
|
|
mHeight = 480;
|
|
|
|
resolutionMultiplier = 1;
|
|
|
|
}
|
|
|
|
else if (Settings::getInstance()->getString("MiximageResolution") == "1920x1440") {
|
|
|
|
mWidth = 1920;
|
|
|
|
mHeight = 1440;
|
|
|
|
resolutionMultiplier = 3;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
mWidth = 1280;
|
|
|
|
mHeight = 960;
|
|
|
|
resolutionMultiplier = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
const unsigned int screenshotWidth = 530 * resolutionMultiplier;
|
|
|
|
const unsigned int screenshotOffset = 20 * resolutionMultiplier;
|
2021-06-10 20:20:25 +00:00
|
|
|
const unsigned int screenshotFrameWidth = 6 * resolutionMultiplier;
|
2021-06-07 21:02:42 +00:00
|
|
|
const unsigned int screenshotHeight = 400 * resolutionMultiplier;
|
2021-06-10 20:20:25 +00:00
|
|
|
|
|
|
|
// These sizes are increased slightly when adding the drop shadow.
|
|
|
|
const unsigned int marqueeTargetWidth = 310 * resolutionMultiplier;
|
|
|
|
const unsigned int marqueeTargetHeight = 230 * resolutionMultiplier;
|
2021-10-30 17:01:58 +00:00
|
|
|
unsigned int boxTargetWidth = 0;
|
|
|
|
unsigned int boxTargetHeight = 0;
|
|
|
|
unsigned int coverTargetWidth = 0;
|
|
|
|
unsigned int physicalMediaTargetWidth = 0;
|
|
|
|
unsigned int physicalMediaTargetHeight = 0;
|
|
|
|
|
|
|
|
if (Settings::getInstance()->getString("MiximageBoxSize") == "small") {
|
|
|
|
boxTargetWidth = 264 * resolutionMultiplier;
|
|
|
|
boxTargetHeight = 254 * resolutionMultiplier;
|
|
|
|
coverTargetWidth = 212 * resolutionMultiplier;
|
|
|
|
}
|
|
|
|
else if (Settings::getInstance()->getString("MiximageBoxSize") == "large") {
|
|
|
|
boxTargetWidth = 372 * resolutionMultiplier;
|
|
|
|
boxTargetHeight = 360 * resolutionMultiplier;
|
|
|
|
coverTargetWidth = 300 * resolutionMultiplier;
|
|
|
|
}
|
|
|
|
else { // Medium size.
|
|
|
|
boxTargetWidth = 310 * resolutionMultiplier;
|
|
|
|
boxTargetHeight = 300 * resolutionMultiplier;
|
|
|
|
coverTargetWidth = 250 * resolutionMultiplier;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Settings::getInstance()->getString("MiximagePhysicalMediaSize") == "small") {
|
|
|
|
physicalMediaTargetWidth = 120 * resolutionMultiplier;
|
|
|
|
physicalMediaTargetHeight = 96 * resolutionMultiplier;
|
|
|
|
}
|
|
|
|
else if (Settings::getInstance()->getString("MiximagePhysicalMediaSize") == "large") {
|
|
|
|
physicalMediaTargetWidth = 196 * resolutionMultiplier;
|
|
|
|
physicalMediaTargetHeight = 156 * resolutionMultiplier;
|
|
|
|
}
|
|
|
|
else { // Medium size.
|
|
|
|
physicalMediaTargetWidth = 150 * resolutionMultiplier;
|
|
|
|
physicalMediaTargetHeight = 120 * resolutionMultiplier;
|
|
|
|
}
|
2021-06-10 20:20:25 +00:00
|
|
|
|
|
|
|
const unsigned int marqueeShadowSize = 6 * resolutionMultiplier;
|
|
|
|
const unsigned int boxShadowSize = 6 * resolutionMultiplier;
|
2021-10-30 17:01:58 +00:00
|
|
|
const unsigned int physicalMediaShadowSize = 6 * resolutionMultiplier;
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (FreeImage_GetBPP(screenshotFile) != 32) {
|
|
|
|
FIBITMAP* screenshotTemp = FreeImage_ConvertTo32Bits(screenshotFile);
|
|
|
|
FreeImage_Unload(screenshotFile);
|
|
|
|
screenshotFile = screenshotTemp;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileWidth = FreeImage_GetWidth(screenshotFile);
|
|
|
|
fileHeight = FreeImage_GetHeight(screenshotFile);
|
|
|
|
filePitch = FreeImage_GetPitch(screenshotFile);
|
|
|
|
|
|
|
|
std::vector<unsigned char> screenshotVector(fileWidth * fileHeight * 4);
|
|
|
|
|
|
|
|
FreeImage_ConvertToRawBits(reinterpret_cast<BYTE*>(&screenshotVector.at(0)), screenshotFile,
|
2021-07-07 18:03:42 +00:00
|
|
|
filePitch, 32, FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE, 1);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
CImg<unsigned char> screenshotImage(fileWidth, fileHeight, 1, 4, 0);
|
|
|
|
|
2021-06-12 18:05:28 +00:00
|
|
|
// Convert the RGBA image to CImg internal format.
|
|
|
|
Utils::CImg::convertRGBAToCImg(screenshotVector, screenshotImage);
|
2021-06-07 21:02:42 +00:00
|
|
|
screenshotVector.clear();
|
|
|
|
|
|
|
|
if (Settings::getInstance()->getBool("MiximageRemoveLetterboxes"))
|
2021-06-12 18:05:28 +00:00
|
|
|
Utils::CImg::cropLetterboxes(screenshotImage);
|
2021-06-07 21:02:42 +00:00
|
|
|
if (Settings::getInstance()->getBool("MiximageRemovePillarboxes"))
|
2021-06-12 18:05:28 +00:00
|
|
|
Utils::CImg::cropPillarboxes(screenshotImage);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (Settings::getInstance()->getString("MiximageScreenshotScaling") == "smooth") {
|
|
|
|
// Lanczos scaling is normally not recommended for low resolution graphics as
|
|
|
|
// it makes the pixels appear smooth when scaling, but for more modern game
|
|
|
|
// platforms it may be a good idea to use it.
|
|
|
|
screenshotImage.resize(screenshotWidth, screenshotHeight, 1, 4, 6);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Box interpolation gives completely sharp pixels, which is best suited for
|
|
|
|
// low resolution retro games.
|
|
|
|
screenshotImage.resize(screenshotWidth, screenshotHeight, 1, 4, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove any transparency information from the screenshot. There really should be no
|
|
|
|
// alpha channel for these images, but if there is, it could interfere with the compositing
|
|
|
|
// of the miximage.
|
|
|
|
screenshotImage.get_shared_channel(3).fill(255);
|
|
|
|
|
|
|
|
int xPosScreenshot = 0;
|
|
|
|
int yPosScreenshot = 0;
|
|
|
|
|
|
|
|
int xPosMarquee = 0;
|
|
|
|
int yPosMarquee = 0;
|
|
|
|
|
|
|
|
int xPosBox = 0;
|
|
|
|
int yPosBox = 0;
|
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
int xPosPhysicalMedia = 0;
|
|
|
|
int yPosPhysicalMedia = 0;
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
CImg<unsigned char> canvasImage(mWidth, mHeight, 1, 4, 0);
|
|
|
|
|
|
|
|
CImg<unsigned char> marqueeImage;
|
|
|
|
CImg<unsigned char> marqueeImageRGB;
|
|
|
|
CImg<unsigned char> marqueeImageAlpha;
|
|
|
|
|
|
|
|
CImg<unsigned char> boxImage;
|
|
|
|
CImg<unsigned char> boxImageRGB;
|
|
|
|
CImg<unsigned char> boxImageAlpha;
|
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
CImg<unsigned char> physicalMediaImage;
|
|
|
|
CImg<unsigned char> physicalMediaImageRGB;
|
|
|
|
CImg<unsigned char> physicalMediaImageAlpha;
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
CImg<unsigned char> frameImage(mWidth, mHeight, 1, 4, 0);
|
|
|
|
|
|
|
|
xPosScreenshot = canvasImage.width() / 2 - screenshotImage.width() / 2 + screenshotOffset;
|
|
|
|
yPosScreenshot = canvasImage.height() / 2 - screenshotImage.height() / 2;
|
|
|
|
|
|
|
|
if (mMarquee) {
|
|
|
|
if (FreeImage_GetBPP(marqueeFile) != 32) {
|
|
|
|
FIBITMAP* marqueeTemp = FreeImage_ConvertTo32Bits(marqueeFile);
|
|
|
|
FreeImage_Unload(marqueeFile);
|
|
|
|
marqueeFile = marqueeTemp;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileWidth = FreeImage_GetWidth(marqueeFile);
|
|
|
|
fileHeight = FreeImage_GetHeight(marqueeFile);
|
|
|
|
filePitch = FreeImage_GetPitch(marqueeFile);
|
|
|
|
|
|
|
|
std::vector<unsigned char> marqueeVector(fileWidth * fileHeight * 4);
|
|
|
|
|
|
|
|
FreeImage_ConvertToRawBits(reinterpret_cast<BYTE*>(&marqueeVector.at(0)), marqueeFile,
|
2021-07-07 18:03:42 +00:00
|
|
|
filePitch, 32, FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE, 1);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
marqueeImage = CImg<unsigned char>(FreeImage_GetWidth(marqueeFile),
|
2021-07-07 18:03:42 +00:00
|
|
|
FreeImage_GetHeight(marqueeFile), 1, 4, 0);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-06-12 18:05:28 +00:00
|
|
|
Utils::CImg::convertRGBAToCImg(marqueeVector, marqueeImage);
|
|
|
|
Utils::CImg::removeTransparentPadding(marqueeImage);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-06-10 20:20:25 +00:00
|
|
|
unsigned int marqueeWidth = static_cast<unsigned int>(marqueeImage.width());
|
|
|
|
unsigned int marqueeHeight = static_cast<unsigned int>(marqueeImage.height());
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-06-10 20:20:25 +00:00
|
|
|
calculateMarqueeSize(marqueeTargetWidth, marqueeTargetHeight, marqueeWidth, marqueeHeight);
|
|
|
|
|
|
|
|
// We use Lanczos3 which is the highest quality resampling method available.
|
|
|
|
marqueeImage.resize(marqueeWidth, marqueeHeight, 1, 4, 6);
|
|
|
|
|
2021-06-12 18:05:28 +00:00
|
|
|
// Add a drop shadow using 4 iterations of box blur.
|
2021-06-12 18:10:25 +00:00
|
|
|
Utils::CImg::addDropShadow(marqueeImage, marqueeShadowSize, 0.6f, 4);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
xPosMarquee = canvasImage.width() - marqueeImage.width();
|
|
|
|
yPosMarquee = 0;
|
|
|
|
|
|
|
|
// Only RGB channels for the image.
|
2021-07-07 18:03:42 +00:00
|
|
|
marqueeImageRGB = CImg<unsigned char>(marqueeImage.get_shared_channels(0, 2));
|
2021-06-07 21:02:42 +00:00
|
|
|
// Only alpha channel for the image.
|
|
|
|
marqueeImageAlpha = CImg<unsigned char>(marqueeImage.get_shared_channel(3));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mBox3D || mCover) {
|
|
|
|
if (FreeImage_GetBPP(boxFile) != 32) {
|
|
|
|
FIBITMAP* boxTemp = FreeImage_ConvertTo32Bits(boxFile);
|
|
|
|
FreeImage_Unload(boxFile);
|
|
|
|
boxFile = boxTemp;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileWidth = FreeImage_GetWidth(boxFile);
|
|
|
|
fileHeight = FreeImage_GetHeight(boxFile);
|
|
|
|
filePitch = FreeImage_GetPitch(boxFile);
|
|
|
|
|
|
|
|
std::vector<unsigned char> boxVector(fileWidth * fileHeight * 4);
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
FreeImage_ConvertToRawBits(reinterpret_cast<BYTE*>(&boxVector.at(0)), boxFile, filePitch,
|
|
|
|
32, FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE, 1);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
boxImage =
|
|
|
|
CImg<unsigned char>(FreeImage_GetWidth(boxFile), FreeImage_GetHeight(boxFile), 1, 4);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-06-12 18:05:28 +00:00
|
|
|
Utils::CImg::convertRGBAToCImg(boxVector, boxImage);
|
|
|
|
Utils::CImg::removeTransparentPadding(boxImage);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
float sizeRatio =
|
|
|
|
static_cast<float>(boxImage.width()) / static_cast<float>(boxImage.height());
|
|
|
|
|
|
|
|
if (sizeRatio > 1.14f && Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes"))
|
|
|
|
boxImage.rotate(90.0f);
|
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
float scaleFactor =
|
|
|
|
static_cast<float>(boxTargetHeight) / static_cast<float>(boxImage.height());
|
2021-06-07 21:02:42 +00:00
|
|
|
unsigned int width = static_cast<int>(static_cast<float>(boxImage.width()) * scaleFactor);
|
2021-06-10 20:20:25 +00:00
|
|
|
unsigned int targetWidth = 0;
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
// We make this distinction as some cover images are in square format and would cover
|
|
|
|
// too much surface otherwise.
|
|
|
|
if (mBox3D)
|
2021-06-10 20:20:25 +00:00
|
|
|
targetWidth = boxTargetWidth;
|
2021-06-07 21:02:42 +00:00
|
|
|
else
|
2021-06-10 20:20:25 +00:00
|
|
|
targetWidth = coverTargetWidth;
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-06-10 20:20:25 +00:00
|
|
|
if (width > targetWidth) {
|
|
|
|
scaleFactor = static_cast<float>(targetWidth) / static_cast<float>(boxImage.width());
|
2021-06-07 21:02:42 +00:00
|
|
|
int height = static_cast<int>(static_cast<float>(boxImage.height()) * scaleFactor);
|
|
|
|
// We use Lanczos3 which is the highest quality resampling method available.
|
2021-06-10 20:20:25 +00:00
|
|
|
boxImage.resize(targetWidth, height, 1, 4, 6);
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
else {
|
2021-06-10 20:20:25 +00:00
|
|
|
boxImage.resize(width, boxTargetHeight, 1, 4, 6);
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
|
2021-06-12 18:10:25 +00:00
|
|
|
Utils::CImg::addDropShadow(boxImage, boxShadowSize, 0.6f, 4);
|
2021-06-10 20:20:25 +00:00
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
xPosBox = 0;
|
|
|
|
yPosBox = canvasImage.height() - boxImage.height();
|
|
|
|
|
|
|
|
// Only RGB channels for the image.
|
2021-07-07 18:03:42 +00:00
|
|
|
boxImageRGB = CImg<unsigned char>(boxImage.get_shared_channels(0, 2));
|
2021-06-07 21:02:42 +00:00
|
|
|
// Only alpha channel for the image.
|
|
|
|
boxImageAlpha = CImg<unsigned char>(boxImage.get_shared_channel(3));
|
|
|
|
}
|
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
if (mPhysicalMedia) {
|
|
|
|
if (FreeImage_GetBPP(physicalMediaFile) != 32) {
|
|
|
|
FIBITMAP* physicalMediaTemp = FreeImage_ConvertTo32Bits(physicalMediaFile);
|
|
|
|
FreeImage_Unload(physicalMediaFile);
|
|
|
|
physicalMediaFile = physicalMediaTemp;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileWidth = FreeImage_GetWidth(physicalMediaFile);
|
|
|
|
fileHeight = FreeImage_GetHeight(physicalMediaFile);
|
|
|
|
filePitch = FreeImage_GetPitch(physicalMediaFile);
|
|
|
|
|
|
|
|
std::vector<unsigned char> physicalMediaVector(fileWidth * fileHeight * 4);
|
|
|
|
|
|
|
|
FreeImage_ConvertToRawBits(reinterpret_cast<BYTE*>(&physicalMediaVector.at(0)),
|
|
|
|
physicalMediaFile, filePitch, 32, FI_RGBA_RED, FI_RGBA_GREEN,
|
|
|
|
FI_RGBA_BLUE, 1);
|
|
|
|
|
|
|
|
physicalMediaImage = CImg<unsigned char>(FreeImage_GetWidth(physicalMediaFile),
|
|
|
|
FreeImage_GetHeight(physicalMediaFile), 1, 4, 0);
|
|
|
|
|
|
|
|
Utils::CImg::convertRGBAToCImg(physicalMediaVector, physicalMediaImage);
|
|
|
|
Utils::CImg::removeTransparentPadding(physicalMediaImage);
|
|
|
|
|
|
|
|
// Make sure the image size is not exceeding either the target width or height.
|
|
|
|
float scaleFactorX = static_cast<float>(physicalMediaTargetWidth) /
|
|
|
|
static_cast<float>(physicalMediaImage.width());
|
|
|
|
float scaleFactorY = static_cast<float>(physicalMediaTargetHeight) /
|
|
|
|
static_cast<float>(physicalMediaImage.height());
|
|
|
|
float scaleFactor = std::min(scaleFactorX, scaleFactorY);
|
|
|
|
|
|
|
|
unsigned int width =
|
|
|
|
static_cast<int>(static_cast<float>(physicalMediaImage.width()) * scaleFactor);
|
|
|
|
unsigned int height =
|
|
|
|
static_cast<int>(static_cast<float>(physicalMediaImage.height()) * scaleFactor);
|
|
|
|
|
|
|
|
// We use Lanczos3 which is the highest quality resampling method available.
|
|
|
|
physicalMediaImage.resize(width, height, 1, 4, 6);
|
|
|
|
|
|
|
|
// Add a drop shadow using 4 iterations of box blur.
|
|
|
|
Utils::CImg::addDropShadow(physicalMediaImage, physicalMediaShadowSize, 0.6f, 4);
|
|
|
|
|
|
|
|
// Place it to the right of the 3D box or cover with a small margin in between.
|
2021-10-31 08:47:05 +00:00
|
|
|
xPosPhysicalMedia = xPosBox + boxImage.width() + 16 * resolutionMultiplier;
|
2021-10-30 17:01:58 +00:00
|
|
|
yPosPhysicalMedia = canvasImage.height() - physicalMediaImage.height();
|
|
|
|
|
|
|
|
// Only RGB channels for the image.
|
|
|
|
physicalMediaImageRGB = CImg<unsigned char>(physicalMediaImage.get_shared_channels(0, 2));
|
|
|
|
// Only alpha channel for the image.
|
|
|
|
physicalMediaImageAlpha = CImg<unsigned char>(physicalMediaImage.get_shared_channel(3));
|
|
|
|
}
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
CImg<unsigned char> frameImageAlpha(frameImage.get_shared_channel(3));
|
|
|
|
frameImageAlpha.draw_image(xPosBox, yPosBox, boxImageAlpha);
|
2021-10-30 17:01:58 +00:00
|
|
|
frameImageAlpha.draw_image(xPosPhysicalMedia, yPosPhysicalMedia, physicalMediaImageAlpha);
|
2021-06-07 21:02:42 +00:00
|
|
|
frameImageAlpha.draw_image(xPosMarquee, yPosMarquee, marqueeImageAlpha);
|
|
|
|
|
|
|
|
// Set a frame color based on an average of the screenshot contents.
|
2021-08-17 16:41:45 +00:00
|
|
|
unsigned char frameColor[] = {0, 0, 0, 0};
|
2021-06-07 21:02:42 +00:00
|
|
|
sampleFrameColor(screenshotImage, frameColor);
|
|
|
|
|
|
|
|
// Upper / lower frame.
|
2021-07-07 18:03:42 +00:00
|
|
|
frameImage.draw_rectangle(xPosScreenshot + 2, yPosScreenshot - screenshotFrameWidth,
|
|
|
|
xPosScreenshot + screenshotWidth - 2,
|
|
|
|
yPosScreenshot + screenshotHeight + screenshotFrameWidth - 1,
|
|
|
|
frameColor);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
// Left / right frame.
|
2021-07-07 18:03:42 +00:00
|
|
|
frameImage.draw_rectangle(xPosScreenshot - screenshotFrameWidth, yPosScreenshot + 2,
|
|
|
|
xPosScreenshot + screenshotWidth + screenshotFrameWidth - 1,
|
|
|
|
yPosScreenshot + screenshotHeight - 2, frameColor);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
// We draw circles in order to get rounded corners for the frame.
|
2021-06-10 20:20:25 +00:00
|
|
|
const unsigned int circleRadius = 8 * resolutionMultiplier;
|
2021-06-07 21:02:42 +00:00
|
|
|
const unsigned int circleOffset = 2 * resolutionMultiplier;
|
|
|
|
|
|
|
|
// Upper left corner.
|
|
|
|
frameImage.draw_circle(xPosScreenshot + circleOffset, yPosScreenshot + circleOffset,
|
2021-07-07 18:03:42 +00:00
|
|
|
circleRadius, frameColor);
|
2021-06-07 21:02:42 +00:00
|
|
|
// Upper right corner.
|
|
|
|
frameImage.draw_circle(xPosScreenshot + screenshotWidth - circleOffset - 1,
|
2021-07-07 18:03:42 +00:00
|
|
|
yPosScreenshot + circleOffset, circleRadius, frameColor);
|
2021-06-07 21:02:42 +00:00
|
|
|
// Lower right corner.
|
|
|
|
frameImage.draw_circle(xPosScreenshot + screenshotWidth - circleOffset - 1,
|
2021-07-07 18:03:42 +00:00
|
|
|
yPosScreenshot + screenshotHeight - circleOffset - 1, circleRadius,
|
|
|
|
frameColor);
|
2021-06-07 21:02:42 +00:00
|
|
|
// Lower left corner.
|
|
|
|
frameImage.draw_circle(xPosScreenshot + circleOffset,
|
2021-07-07 18:03:42 +00:00
|
|
|
yPosScreenshot + screenshotHeight - circleOffset - 1, circleRadius,
|
|
|
|
frameColor);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
CImg<unsigned char> frameImageRGB(frameImage.get_shared_channels(0, 2));
|
|
|
|
|
|
|
|
canvasImage.draw_image(0, 0, frameImage);
|
|
|
|
canvasImage.draw_image(xPosScreenshot, yPosScreenshot, screenshotImage);
|
|
|
|
|
|
|
|
if (mMarquee)
|
2021-07-07 18:03:42 +00:00
|
|
|
canvasImage.draw_image(xPosMarquee, yPosMarquee, marqueeImageRGB, marqueeImageAlpha, 1,
|
|
|
|
255);
|
2021-06-07 21:02:42 +00:00
|
|
|
if (mBox3D || mCover)
|
|
|
|
canvasImage.draw_image(xPosBox, yPosBox, boxImageRGB, boxImageAlpha, 1, 255);
|
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
if (mPhysicalMedia)
|
|
|
|
canvasImage.draw_image(xPosPhysicalMedia, yPosPhysicalMedia, physicalMediaImageRGB,
|
|
|
|
physicalMediaImageAlpha, 1, 255);
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
std::vector<unsigned char> canvasVector;
|
|
|
|
|
2021-06-12 18:05:28 +00:00
|
|
|
// Convert the image from CImg internal format.
|
|
|
|
Utils::CImg::convertCImgToRGBA(canvasImage, canvasVector);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
FIBITMAP* mixImage = nullptr;
|
|
|
|
mixImage = FreeImage_ConvertFromRawBits(&canvasVector.at(0), canvasImage.width(),
|
2021-07-07 18:03:42 +00:00
|
|
|
canvasImage.height(), canvasImage.width() * 4, 32,
|
|
|
|
FI_RGBA_RED, FI_RGBA_GREEN, FI_RGBA_BLUE);
|
|
|
|
|
|
|
|
#if defined(_WIN64)
|
|
|
|
bool savedImage =
|
|
|
|
(FreeImage_SaveU(FIF_PNG, mixImage,
|
|
|
|
Utils::String::stringToWideString(getSavePath()).c_str()) != 0);
|
|
|
|
#else
|
2021-06-07 21:02:42 +00:00
|
|
|
bool savedImage = (FreeImage_Save(FIF_PNG, mixImage, getSavePath().c_str()) != 0);
|
2021-07-07 18:03:42 +00:00
|
|
|
#endif
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (!savedImage) {
|
|
|
|
LOG(LogError) << "Couldn't save miximage, permission problems or disk full?";
|
|
|
|
}
|
|
|
|
|
|
|
|
FreeImage_Unload(screenshotFile);
|
|
|
|
FreeImage_Unload(marqueeFile);
|
|
|
|
FreeImage_Unload(boxFile);
|
2021-10-30 17:01:58 +00:00
|
|
|
FreeImage_Unload(physicalMediaFile);
|
2021-06-07 21:02:42 +00:00
|
|
|
FreeImage_Unload(mixImage);
|
|
|
|
|
|
|
|
// Success.
|
|
|
|
if (savedImage)
|
|
|
|
return false;
|
|
|
|
else
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-06-10 20:20:25 +00:00
|
|
|
void MiximageGenerator::calculateMarqueeSize(const unsigned int& targetWidth,
|
2021-07-07 18:03:42 +00:00
|
|
|
const unsigned int& targetHeight,
|
|
|
|
unsigned int& width,
|
|
|
|
unsigned int& height)
|
2021-06-10 20:20:25 +00:00
|
|
|
{
|
|
|
|
unsigned int adjustedTargetWidth = 0;
|
|
|
|
float widthModifier = 0.5f;
|
|
|
|
float scaleFactor = 0.0f;
|
|
|
|
|
|
|
|
// The idea is to adjust the size of the marquee based on its surface area, so that
|
|
|
|
// wider but shorter images get a larger width than taller images in order to use
|
|
|
|
// an approximately equivalent amount of space on the miximage.
|
|
|
|
float widthRatio = static_cast<float>(width) / static_cast<float>(height);
|
|
|
|
|
2021-08-17 18:55:29 +00:00
|
|
|
widthModifier = glm::clamp(widthModifier + widthRatio / 6.5f, 0.0f, 1.0f);
|
2021-06-10 20:20:25 +00:00
|
|
|
|
|
|
|
// Hack to increase the size slightly for wider and shorter images.
|
|
|
|
if (widthRatio >= 4)
|
2021-08-17 18:55:29 +00:00
|
|
|
widthModifier += glm::clamp(widthRatio / 40.0f, 0.0f, 0.3f);
|
2021-06-10 20:20:25 +00:00
|
|
|
|
2021-07-07 18:03:42 +00:00
|
|
|
adjustedTargetWidth =
|
|
|
|
static_cast<unsigned int>(static_cast<float>(targetWidth) * widthModifier);
|
2021-06-10 20:20:25 +00:00
|
|
|
scaleFactor = static_cast<float>(adjustedTargetWidth) / static_cast<float>(width);
|
|
|
|
|
|
|
|
// For really tall and narrow images, we may have exceeded the target height.
|
2021-06-10 20:26:17 +00:00
|
|
|
if (static_cast<int>(scaleFactor * static_cast<float>(height)) >
|
2021-07-07 18:03:42 +00:00
|
|
|
static_cast<float>(targetHeight))
|
2021-06-10 20:20:25 +00:00
|
|
|
scaleFactor = static_cast<float>(targetHeight) / static_cast<float>(height);
|
|
|
|
|
|
|
|
width = static_cast<int>(static_cast<float>(width) * scaleFactor);
|
|
|
|
height = static_cast<int>(static_cast<float>(height) * scaleFactor);
|
|
|
|
}
|
|
|
|
|
2021-06-07 21:02:42 +00:00
|
|
|
void MiximageGenerator::sampleFrameColor(CImg<unsigned char>& screenshotImage,
|
2021-07-07 18:03:42 +00:00
|
|
|
unsigned char (&frameColor)[4])
|
2021-06-07 21:02:42 +00:00
|
|
|
{
|
|
|
|
// Calculate the number of samples relative to the configured resolution so we get
|
|
|
|
// the same result regardless of miximage target size seting.
|
|
|
|
unsigned int samples = static_cast<int>(static_cast<float>(mWidth) * 0.03125f);
|
|
|
|
|
|
|
|
unsigned int red = 0;
|
|
|
|
unsigned int green = 0;
|
|
|
|
unsigned int blue = 0;
|
|
|
|
|
|
|
|
unsigned int redLine = 0;
|
|
|
|
unsigned int greenLine = 0;
|
|
|
|
unsigned int blueLine = 0;
|
|
|
|
|
|
|
|
unsigned int counter = 0;
|
|
|
|
|
|
|
|
// This is a very simple method to get an average pixel value. It's limited in that it
|
|
|
|
// does not consider dominant colors and such, so the result could possibly be a value
|
|
|
|
// that does not match the perceived color palette of the image. In most cases it works
|
|
|
|
// good enough though.
|
|
|
|
for (int r = samples / 2; r < screenshotImage.height(); r += samples) {
|
|
|
|
for (int c = samples / 2; c < screenshotImage.width(); c += samples) {
|
|
|
|
red += screenshotImage(c, r, 0, 0);
|
|
|
|
green += screenshotImage(c, r, 0, 1);
|
|
|
|
blue += screenshotImage(c, r, 0, 2);
|
2021-11-17 16:35:34 +00:00
|
|
|
++counter;
|
2021-06-07 21:02:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (counter > 0) {
|
|
|
|
redLine += red / counter;
|
|
|
|
greenLine += green / counter;
|
|
|
|
blueLine += blue / counter;
|
|
|
|
counter = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-17 18:55:29 +00:00
|
|
|
unsigned char redC = glm::clamp(static_cast<int>(redLine / 255), 0, 255);
|
|
|
|
unsigned char greenC = glm::clamp(static_cast<int>(greenLine / 255), 0, 255);
|
|
|
|
unsigned char blueC = glm::clamp(static_cast<int>(blueLine / 255), 0, 255);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
// Convert to the HSL color space to be able to modify saturation and lightness.
|
2021-07-07 18:03:42 +00:00
|
|
|
CImg<float> colorHSL = CImg<>(1, 1, 1, 3).fill(redC, greenC, blueC).RGBtoHSL();
|
2021-06-07 21:02:42 +00:00
|
|
|
|
2021-07-09 17:58:21 +00:00
|
|
|
// float hue = colorHSL(0, 0, 0, 0);
|
2021-06-07 21:02:42 +00:00
|
|
|
float saturation = colorHSL(0, 0, 0, 1);
|
|
|
|
float lightness = colorHSL(0, 0, 0, 2);
|
|
|
|
|
|
|
|
// Decrease saturation slightly and increase lightness a bit, these adjustments
|
2021-06-11 15:23:46 +00:00
|
|
|
// makes the end result look better than the raw average pixel value. Also clamp
|
|
|
|
// the lightness to a low value so we don't get a frame that is nearly pitch black
|
|
|
|
// if the screenshot mostly contains blacks or dark colors.
|
2021-08-17 18:55:29 +00:00
|
|
|
colorHSL(0, 0, 0, 1) = glm::clamp(saturation * 0.9f, 0.0f, 1.0f);
|
|
|
|
colorHSL(0, 0, 0, 2) = glm::clamp(lightness * 1.25f, 0.10f, 1.0f);
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
const CImg<unsigned char> colorRGB = colorHSL.HSLtoRGB();
|
|
|
|
|
|
|
|
frameColor[0] = colorRGB(0, 0, 0, 0);
|
|
|
|
frameColor[1] = colorRGB(0, 0, 0, 1);
|
|
|
|
frameColor[2] = colorRGB(0, 0, 0, 2);
|
|
|
|
frameColor[3] = 255;
|
|
|
|
}
|
|
|
|
|
2021-10-30 17:01:58 +00:00
|
|
|
std::string MiximageGenerator::getSavePath() const
|
2021-06-07 21:02:42 +00:00
|
|
|
{
|
|
|
|
const std::string name = Utils::FileSystem::getStem(mGame->getPath());
|
|
|
|
std::string subFolders;
|
|
|
|
|
|
|
|
// Extract possible subfolders from the path.
|
|
|
|
if (mGame->getSystemEnvData()->mStartPath != "")
|
|
|
|
subFolders = Utils::String::replace(Utils::FileSystem::getParent(mGame->getPath()),
|
2021-07-07 18:03:42 +00:00
|
|
|
mGame->getSystemEnvData()->mStartPath, "");
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
std::string path = FileData::getMediaDirectory();
|
|
|
|
|
|
|
|
if (!Utils::FileSystem::exists(path))
|
|
|
|
Utils::FileSystem::createDirectory(path);
|
|
|
|
|
2021-06-09 18:38:34 +00:00
|
|
|
path += mGame->getSystemName() + "/miximages" + subFolders + "/";
|
2021-06-07 21:02:42 +00:00
|
|
|
|
|
|
|
if (!Utils::FileSystem::exists(path))
|
|
|
|
Utils::FileSystem::createDirectory(path);
|
|
|
|
|
|
|
|
path += name + ".png";
|
|
|
|
|
|
|
|
// Success.
|
|
|
|
return path;
|
|
|
|
}
|