2019-08-17 19:40:58 +00:00
|
|
|
// Copyright 2019 Dolphin Emulator Project
|
|
|
|
// Licensed under GPLv2+
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
#include "InputCommon/ImageOperations.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
#include <cmath>
|
|
|
|
#include <limits>
|
|
|
|
#include <stack>
|
2020-11-26 03:36:04 +00:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
2019-08-17 19:40:58 +00:00
|
|
|
|
|
|
|
#include "Common/File.h"
|
|
|
|
#include "Common/FileUtil.h"
|
|
|
|
#include "Common/Image.h"
|
|
|
|
|
|
|
|
namespace InputCommon
|
|
|
|
{
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
Pixel SampleNearest(const ImagePixelData& src, double u, double v)
|
|
|
|
{
|
|
|
|
const u32 x = std::clamp(static_cast<u32>(u * src.width), 0u, src.width - 1);
|
|
|
|
const u32 y = std::clamp(static_cast<u32>(v * src.height), 0u, src.height - 1);
|
|
|
|
return src.pixels[x + y * src.width];
|
|
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
void CopyImageRegion(const ImagePixelData& src, ImagePixelData& dst, const Rect& src_region,
|
|
|
|
const Rect& dst_region)
|
|
|
|
{
|
|
|
|
if (src_region.GetWidth() != dst_region.GetWidth() ||
|
|
|
|
src_region.GetHeight() != dst_region.GetHeight())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (u32 x = 0; x < dst_region.GetWidth(); x++)
|
|
|
|
{
|
|
|
|
for (u32 y = 0; y < dst_region.GetHeight(); y++)
|
|
|
|
{
|
|
|
|
dst.pixels[(y + dst_region.top) * dst.width + x + dst_region.left] =
|
|
|
|
src.pixels[(y + src_region.top) * src.width + x + src_region.left];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<ImagePixelData> LoadImage(const std::string& path)
|
|
|
|
{
|
|
|
|
File::IOFile file;
|
|
|
|
file.Open(path, "rb");
|
|
|
|
std::vector<u8> buffer(file.GetSize());
|
|
|
|
file.ReadBytes(buffer.data(), file.GetSize());
|
|
|
|
|
|
|
|
ImagePixelData image;
|
|
|
|
std::vector<u8> data;
|
|
|
|
if (!Common::LoadPNG(buffer, &data, &image.width, &image.height))
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
image.pixels.resize(image.width * image.height);
|
|
|
|
for (u32 x = 0; x < image.width; x++)
|
|
|
|
{
|
|
|
|
for (u32 y = 0; y < image.height; y++)
|
|
|
|
{
|
|
|
|
const u32 index = y * image.width + x;
|
|
|
|
const auto pixel =
|
|
|
|
Pixel{data[index * 4], data[index * 4 + 1], data[index * 4 + 2], data[index * 4 + 3]};
|
|
|
|
image.pixels[index] = pixel;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool WriteImage(const std::string& path, const ImagePixelData& image)
|
|
|
|
{
|
|
|
|
std::vector<u8> buffer;
|
2020-11-26 03:36:04 +00:00
|
|
|
buffer.reserve(image.width * image.height * 4);
|
2019-08-17 19:40:58 +00:00
|
|
|
|
|
|
|
for (u32 y = 0; y < image.height; ++y)
|
|
|
|
{
|
2020-11-26 03:36:04 +00:00
|
|
|
for (u32 x = 0; x < image.width; ++x)
|
2019-08-17 19:40:58 +00:00
|
|
|
{
|
|
|
|
const auto index = x + y * image.width;
|
|
|
|
const auto pixel = image.pixels[index];
|
2020-11-26 03:36:04 +00:00
|
|
|
buffer.push_back(pixel.r);
|
|
|
|
buffer.push_back(pixel.g);
|
|
|
|
buffer.push_back(pixel.b);
|
|
|
|
buffer.push_back(pixel.a);
|
2019-08-17 19:40:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-26 03:36:04 +00:00
|
|
|
return Common::SavePNG(path, buffer.data(), Common::ImageByteFormat::RGBA, image.width,
|
|
|
|
image.height);
|
2019-08-17 19:40:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ImagePixelData Resize(ResizeMode mode, const ImagePixelData& src, u32 new_width, u32 new_height)
|
|
|
|
{
|
|
|
|
ImagePixelData result(new_width, new_height);
|
|
|
|
|
|
|
|
for (u32 x = 0; x < new_width; x++)
|
|
|
|
{
|
|
|
|
const double u = x / static_cast<double>(new_width - 1);
|
|
|
|
for (u32 y = 0; y < new_height; y++)
|
|
|
|
{
|
|
|
|
const double v = y / static_cast<double>(new_height - 1);
|
|
|
|
|
|
|
|
switch (mode)
|
|
|
|
{
|
|
|
|
case ResizeMode::Nearest:
|
|
|
|
result.pixels[y * new_width + x] = SampleNearest(src, u, v);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
ImagePixelData ResizeKeepAspectRatio(ResizeMode mode, const ImagePixelData& src, u32 new_width,
|
|
|
|
u32 new_height, const Pixel& background_color)
|
|
|
|
{
|
|
|
|
ImagePixelData result(new_width, new_height, background_color);
|
|
|
|
|
|
|
|
const double corrected_height = new_width * (src.height / static_cast<double>(src.width));
|
|
|
|
const double corrected_width = new_height * (src.width / static_cast<double>(src.height));
|
|
|
|
// initially no borders
|
|
|
|
u32 top = 0;
|
|
|
|
u32 left = 0;
|
|
|
|
|
|
|
|
ImagePixelData resized;
|
|
|
|
if (corrected_height <= new_height)
|
|
|
|
{
|
|
|
|
// Handle vertical padding
|
|
|
|
|
|
|
|
const int diff = new_height - std::trunc(corrected_height);
|
|
|
|
top = diff / 2;
|
|
|
|
if (diff % 2 != 0)
|
|
|
|
{
|
|
|
|
// If the difference is odd, we need to have one side be slightly larger
|
|
|
|
top += 1;
|
|
|
|
}
|
|
|
|
resized = Resize(mode, src, new_width, corrected_height);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Handle horizontal padding
|
|
|
|
|
|
|
|
const int diff = new_width - std::trunc(corrected_width);
|
|
|
|
left = diff / 2;
|
|
|
|
if (diff % 2 != 0)
|
|
|
|
{
|
|
|
|
// If the difference is odd, we need to have one side be slightly larger
|
|
|
|
left += 1;
|
|
|
|
}
|
|
|
|
resized = Resize(mode, src, corrected_width, new_height);
|
|
|
|
}
|
|
|
|
CopyImageRegion(resized, result, Rect{0, 0, resized.width, resized.height},
|
|
|
|
Rect{left, top, left + resized.width, top + resized.height});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace InputCommon
|