Added the physical media images to the miximages.

Also added an option to rotate horizontally oriented game boxes and size options for the box and physical media files.
This commit is contained in:
Leon Styhre 2021-10-30 19:01:58 +02:00
parent 2f09c21d3f
commit 7606e9cad6
4 changed files with 242 additions and 12 deletions

View file

@ -17,13 +17,14 @@
#include <chrono> #include <chrono>
MiximageGenerator::MiximageGenerator(FileData* game, std::string& resultMessage) MiximageGenerator::MiximageGenerator(FileData* game, std::string& resultMessage)
: mGame(game) : mGame{game}
, mResultMessage(resultMessage) , mResultMessage{resultMessage}
, mWidth(1280) , mWidth{1280}
, mHeight(960) , mHeight{960}
, mMarquee(false) , mMarquee{false}
, mBox3D(false) , mBox3D{false}
, mCover(false) , mCover{false}
, mPhysicalMedia{false}
{ {
} }
@ -79,6 +80,16 @@ void MiximageGenerator::startThread(std::promise<bool>* miximagePromise)
} }
} }
if (Settings::getInstance()->getBool("MiximageIncludePhysicalMedia")) {
if ((mPhysicalMediaPath = mGame->getPhysicalMediaPath()) != "") {
mPhysicalMedia = true;
}
else {
LOG(LogDebug)
<< "MiximageGenerator::MiximageGenerator(): No physical media image found";
}
}
const auto startTime = std::chrono::system_clock::now(); const auto startTime = std::chrono::system_clock::now();
if (generateImage()) { if (generateImage()) {
@ -106,6 +117,7 @@ bool MiximageGenerator::generateImage()
FIBITMAP* screenshotFile = nullptr; FIBITMAP* screenshotFile = nullptr;
FIBITMAP* marqueeFile = nullptr; FIBITMAP* marqueeFile = nullptr;
FIBITMAP* boxFile = nullptr; FIBITMAP* boxFile = nullptr;
FIBITMAP* physicalMediaFile = nullptr;
unsigned int fileWidth = 0; unsigned int fileWidth = 0;
unsigned int fileHeight = 0; unsigned int fileHeight = 0;
@ -269,6 +281,46 @@ bool MiximageGenerator::generateImage()
} }
} }
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;
}
}
}
unsigned int resolutionMultiplier = 0; unsigned int resolutionMultiplier = 0;
if (Settings::getInstance()->getString("MiximageResolution") == "640x480") { if (Settings::getInstance()->getString("MiximageResolution") == "640x480") {
@ -295,12 +347,44 @@ bool MiximageGenerator::generateImage()
// These sizes are increased slightly when adding the drop shadow. // These sizes are increased slightly when adding the drop shadow.
const unsigned int marqueeTargetWidth = 310 * resolutionMultiplier; const unsigned int marqueeTargetWidth = 310 * resolutionMultiplier;
const unsigned int marqueeTargetHeight = 230 * resolutionMultiplier; const unsigned int marqueeTargetHeight = 230 * resolutionMultiplier;
const unsigned int boxTargetWidth = 330 * resolutionMultiplier; unsigned int boxTargetWidth = 0;
const unsigned int boxTargetHeight = 300 * resolutionMultiplier; unsigned int boxTargetHeight = 0;
const unsigned int coverTargetWidth = 250 * resolutionMultiplier; 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;
}
const unsigned int marqueeShadowSize = 6 * resolutionMultiplier; const unsigned int marqueeShadowSize = 6 * resolutionMultiplier;
const unsigned int boxShadowSize = 6 * resolutionMultiplier; const unsigned int boxShadowSize = 6 * resolutionMultiplier;
const unsigned int physicalMediaShadowSize = 6 * resolutionMultiplier;
if (FreeImage_GetBPP(screenshotFile) != 32) { if (FreeImage_GetBPP(screenshotFile) != 32) {
FIBITMAP* screenshotTemp = FreeImage_ConvertTo32Bits(screenshotFile); FIBITMAP* screenshotTemp = FreeImage_ConvertTo32Bits(screenshotFile);
@ -354,6 +438,9 @@ bool MiximageGenerator::generateImage()
int xPosBox = 0; int xPosBox = 0;
int yPosBox = 0; int yPosBox = 0;
int xPosPhysicalMedia = 0;
int yPosPhysicalMedia = 0;
CImg<unsigned char> canvasImage(mWidth, mHeight, 1, 4, 0); CImg<unsigned char> canvasImage(mWidth, mHeight, 1, 4, 0);
CImg<unsigned char> marqueeImage; CImg<unsigned char> marqueeImage;
@ -364,6 +451,10 @@ bool MiximageGenerator::generateImage()
CImg<unsigned char> boxImageRGB; CImg<unsigned char> boxImageRGB;
CImg<unsigned char> boxImageAlpha; CImg<unsigned char> boxImageAlpha;
CImg<unsigned char> physicalMediaImage;
CImg<unsigned char> physicalMediaImageRGB;
CImg<unsigned char> physicalMediaImageAlpha;
CImg<unsigned char> frameImage(mWidth, mHeight, 1, 4, 0); CImg<unsigned char> frameImage(mWidth, mHeight, 1, 4, 0);
xPosScreenshot = canvasImage.width() / 2 - screenshotImage.width() / 2 + screenshotOffset; xPosScreenshot = canvasImage.width() / 2 - screenshotImage.width() / 2 + screenshotOffset;
@ -433,6 +524,12 @@ bool MiximageGenerator::generateImage()
Utils::CImg::convertRGBAToCImg(boxVector, boxImage); Utils::CImg::convertRGBAToCImg(boxVector, boxImage);
Utils::CImg::removeTransparentPadding(boxImage); Utils::CImg::removeTransparentPadding(boxImage);
float sizeRatio =
static_cast<float>(boxImage.width()) / static_cast<float>(boxImage.height());
if (sizeRatio > 1.14f && Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes"))
boxImage.rotate(90.0f);
float scaleFactor = float scaleFactor =
static_cast<float>(boxTargetHeight) / static_cast<float>(boxImage.height()); static_cast<float>(boxTargetHeight) / static_cast<float>(boxImage.height());
unsigned int width = static_cast<int>(static_cast<float>(boxImage.width()) * scaleFactor); unsigned int width = static_cast<int>(static_cast<float>(boxImage.width()) * scaleFactor);
@ -466,8 +563,60 @@ bool MiximageGenerator::generateImage()
boxImageAlpha = CImg<unsigned char>(boxImage.get_shared_channel(3)); boxImageAlpha = CImg<unsigned char>(boxImage.get_shared_channel(3));
} }
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.
xPosPhysicalMedia = xPosBox + boxImage.width() + 12 * resolutionMultiplier;
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));
}
CImg<unsigned char> frameImageAlpha(frameImage.get_shared_channel(3)); CImg<unsigned char> frameImageAlpha(frameImage.get_shared_channel(3));
frameImageAlpha.draw_image(xPosBox, yPosBox, boxImageAlpha); frameImageAlpha.draw_image(xPosBox, yPosBox, boxImageAlpha);
frameImageAlpha.draw_image(xPosPhysicalMedia, yPosPhysicalMedia, physicalMediaImageAlpha);
frameImageAlpha.draw_image(xPosMarquee, yPosMarquee, marqueeImageAlpha); frameImageAlpha.draw_image(xPosMarquee, yPosMarquee, marqueeImageAlpha);
// Set a frame color based on an average of the screenshot contents. // Set a frame color based on an average of the screenshot contents.
@ -515,6 +664,10 @@ bool MiximageGenerator::generateImage()
if (mBox3D || mCover) if (mBox3D || mCover)
canvasImage.draw_image(xPosBox, yPosBox, boxImageRGB, boxImageAlpha, 1, 255); canvasImage.draw_image(xPosBox, yPosBox, boxImageRGB, boxImageAlpha, 1, 255);
if (mPhysicalMedia)
canvasImage.draw_image(xPosPhysicalMedia, yPosPhysicalMedia, physicalMediaImageRGB,
physicalMediaImageAlpha, 1, 255);
std::vector<unsigned char> canvasVector; std::vector<unsigned char> canvasVector;
// Convert the image from CImg internal format. // Convert the image from CImg internal format.
@ -540,6 +693,7 @@ bool MiximageGenerator::generateImage()
FreeImage_Unload(screenshotFile); FreeImage_Unload(screenshotFile);
FreeImage_Unload(marqueeFile); FreeImage_Unload(marqueeFile);
FreeImage_Unload(boxFile); FreeImage_Unload(boxFile);
FreeImage_Unload(physicalMediaFile);
FreeImage_Unload(mixImage); FreeImage_Unload(mixImage);
// Success. // Success.
@ -645,7 +799,7 @@ void MiximageGenerator::sampleFrameColor(CImg<unsigned char>& screenshotImage,
frameColor[3] = 255; frameColor[3] = 255;
} }
std::string MiximageGenerator::getSavePath() std::string MiximageGenerator::getSavePath() const
{ {
const std::string name = Utils::FileSystem::getStem(mGame->getPath()); const std::string name = Utils::FileSystem::getStem(mGame->getPath());
std::string subFolders; std::string subFolders;

View file

@ -35,7 +35,7 @@ private:
unsigned int& height); unsigned int& height);
void sampleFrameColor(CImg<unsigned char>& screenshotImage, unsigned char (&frameColor)[4]); void sampleFrameColor(CImg<unsigned char>& screenshotImage, unsigned char (&frameColor)[4]);
std::string getSavePath(); std::string getSavePath() const;
FileData* mGame; FileData* mGame;
std::string& mResultMessage; std::string& mResultMessage;
@ -46,6 +46,7 @@ private:
std::string mMarqueePath; std::string mMarqueePath;
std::string mBox3DPath; std::string mBox3DPath;
std::string mCoverPath; std::string mCoverPath;
std::string mPhysicalMediaPath;
int mWidth; int mWidth;
int mHeight; int mHeight;
@ -53,6 +54,7 @@ private:
bool mMarquee; bool mMarquee;
bool mBox3D; bool mBox3D;
bool mCover; bool mCover;
bool mPhysicalMedia;
}; };
#endif // ES_APP_SCRAPERS_MIXIMAGE_GENERATOR_H #endif // ES_APP_SCRAPERS_MIXIMAGE_GENERATOR_H

View file

@ -461,6 +461,48 @@ void GuiScraperMenu::openMiximageOptions()
} }
}); });
// Box/cover size.
auto miximageBoxSize = std::make_shared<OptionListComponent<std::string>>(
mWindow, getHelpStyle(), "BOX SIZE", false);
std::string selectedBoxSize = Settings::getInstance()->getString("MiximageBoxSize");
miximageBoxSize->add("small", "small", selectedBoxSize == "small");
miximageBoxSize->add("medium", "medium", selectedBoxSize == "medium");
miximageBoxSize->add("large", "large", selectedBoxSize == "large");
// If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the box size to "medium" in this case.
if (miximageBoxSize->getSelectedObjects().size() == 0)
miximageBoxSize->selectEntry(0);
s->addWithLabel("BOX SIZE", miximageBoxSize);
s->addSaveFunc([miximageBoxSize, s] {
if (miximageBoxSize->getSelected() !=
Settings::getInstance()->getString("MiximageBoxSize")) {
Settings::getInstance()->setString("MiximageBoxSize", miximageBoxSize->getSelected());
s->setNeedsSaving();
}
});
// Physical media size.
auto miximagePhysicalMediaSize = std::make_shared<OptionListComponent<std::string>>(
mWindow, getHelpStyle(), "PHYSICAL MEDIA SIZE", false);
std::string selectedPhysicalMediaSize =
Settings::getInstance()->getString("MiximagePhysicalMediaSize");
miximagePhysicalMediaSize->add("small", "small", selectedPhysicalMediaSize == "small");
miximagePhysicalMediaSize->add("medium", "medium", selectedPhysicalMediaSize == "medium");
miximagePhysicalMediaSize->add("large", "large", selectedPhysicalMediaSize == "large");
// If there are no objects returned, then there must be a manually modified entry in the
// configuration file. Simply set the physical media size to "medium" in this case.
if (miximagePhysicalMediaSize->getSelectedObjects().size() == 0)
miximagePhysicalMediaSize->selectEntry(0);
s->addWithLabel("PHYSICAL MEDIA SIZE", miximagePhysicalMediaSize);
s->addSaveFunc([miximagePhysicalMediaSize, s] {
if (miximagePhysicalMediaSize->getSelected() !=
Settings::getInstance()->getString("MiximagePhysicalMediaSize")) {
Settings::getInstance()->setString("MiximagePhysicalMediaSize",
miximagePhysicalMediaSize->getSelected());
s->setNeedsSaving();
}
});
// Whether to generate miximages when scraping. // Whether to generate miximages when scraping.
auto miximage_generate = std::make_shared<SwitchComponent>(mWindow); auto miximage_generate = std::make_shared<SwitchComponent>(mWindow);
miximage_generate->setState(Settings::getInstance()->getBool("MiximageGenerate")); miximage_generate->setState(Settings::getInstance()->getBool("MiximageGenerate"));
@ -510,6 +552,20 @@ void GuiScraperMenu::openMiximageOptions()
} }
}); });
// Whether to rotate horizontally oriented boxes.
auto miximageRotateBoxes = std::make_shared<SwitchComponent>(mWindow);
miximageRotateBoxes->setState(
Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes"));
s->addWithLabel("ROTATE HORIZONTALLY ORIENTED BOXES", miximageRotateBoxes);
s->addSaveFunc([miximageRotateBoxes, s] {
if (miximageRotateBoxes->getState() !=
Settings::getInstance()->getBool("MiximageRotateHorizontalBoxes")) {
Settings::getInstance()->setBool("MiximageRotateHorizontalBoxes",
miximageRotateBoxes->getState());
s->setNeedsSaving();
}
});
// Whether to include marquee images. // Whether to include marquee images.
auto miximage_marquee = std::make_shared<SwitchComponent>(mWindow); auto miximage_marquee = std::make_shared<SwitchComponent>(mWindow);
miximage_marquee->setState(Settings::getInstance()->getBool("MiximageIncludeMarquee")); miximage_marquee->setState(Settings::getInstance()->getBool("MiximageIncludeMarquee"));
@ -547,6 +603,20 @@ void GuiScraperMenu::openMiximageOptions()
} }
}); });
// Whether to include physical media images.
auto miximagePhysicalMedia = std::make_shared<SwitchComponent>(mWindow);
miximagePhysicalMedia->setState(
Settings::getInstance()->getBool("MiximageIncludePhysicalMedia"));
s->addWithLabel("INCLUDE PHYSICAL MEDIA IMAGE", miximagePhysicalMedia);
s->addSaveFunc([miximagePhysicalMedia, s] {
if (miximagePhysicalMedia->getState() !=
Settings::getInstance()->getBool("MiximageIncludePhysicalMedia")) {
Settings::getInstance()->setBool("MiximageIncludePhysicalMedia",
miximagePhysicalMedia->getState());
s->setNeedsSaving();
}
});
// Miximage offline generator. // Miximage offline generator.
ComponentListRow offline_generator_row; ComponentListRow offline_generator_row;
offline_generator_row.elements.clear(); offline_generator_row.elements.clear();

View file

@ -117,13 +117,17 @@ void Settings::setDefaults()
mStringMap["MiximageResolution"] = {"1280x960", "1280x960"}; mStringMap["MiximageResolution"] = {"1280x960", "1280x960"};
mStringMap["MiximageScreenshotScaling"] = {"sharp", "sharp"}; mStringMap["MiximageScreenshotScaling"] = {"sharp", "sharp"};
mStringMap["MiximageBoxSize"] = {"medium", "medium"};
mStringMap["MiximagePhysicalMediaSize"] = {"medium", "medium"};
mBoolMap["MiximageGenerate"] = {true, true}; mBoolMap["MiximageGenerate"] = {true, true};
mBoolMap["MiximageOverwrite"] = {true, true}; mBoolMap["MiximageOverwrite"] = {true, true};
mBoolMap["MiximageRemoveLetterboxes"] = {true, true}; mBoolMap["MiximageRemoveLetterboxes"] = {true, true};
mBoolMap["MiximageRemovePillarboxes"] = {true, true}; mBoolMap["MiximageRemovePillarboxes"] = {true, true};
mBoolMap["MiximageRotateHorizontalBoxes"] = {true, true};
mBoolMap["MiximageIncludeMarquee"] = {true, true}; mBoolMap["MiximageIncludeMarquee"] = {true, true};
mBoolMap["MiximageIncludeBox"] = {true, true}; mBoolMap["MiximageIncludeBox"] = {true, true};
mBoolMap["MiximageCoverFallback"] = {true, true}; mBoolMap["MiximageCoverFallback"] = {true, true};
mBoolMap["MiximageIncludePhysicalMedia"] = {true, true};
mStringMap["ScraperRegion"] = {"eu", "eu"}; mStringMap["ScraperRegion"] = {"eu", "eu"};
mStringMap["ScraperLanguage"] = {"en", "en"}; mStringMap["ScraperLanguage"] = {"en", "en"};