diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 1b97440126..0b535a3d4e 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -19,6 +19,7 @@ target_sources(common PRIVATE Exceptions.cpp FastJmp.cpp FileSystem.cpp + Image.cpp Misc.cpp MD5Digest.cpp PrecompiledHeader.cpp @@ -60,6 +61,7 @@ target_sources(common PRIVATE boost_spsc_queue.hpp Console.h CrashHandler.h + Easing.h EnumOps.h EventSource.h Exceptions.h @@ -67,6 +69,7 @@ target_sources(common PRIVATE FileSystem.h General.h HashCombine.h + Image.h LRUCache.h MemcpyFast.h MemsetFast.inl @@ -264,6 +267,8 @@ endif() target_link_libraries(common PRIVATE ${LIBC_LIBRARIES} + PNG::PNG + jpgd ) target_link_libraries(common PUBLIC diff --git a/common/Easing.h b/common/Easing.h new file mode 100644 index 0000000000..8284ccfccb --- /dev/null +++ b/common/Easing.h @@ -0,0 +1,258 @@ +#pragma once +#include "Pcsx2Defs.h" +#include + +// From https://github.com/nicolausYes/easing-functions/blob/master/src/easing.cpp + +namespace Easing { +static constexpr float pi = 3.1415926545f; + +template +__ri static T InSine(T t) +{ + return std::sin(1.5707963f * t); +} + +template +__ri static T OutSine(T t) +{ + return 1 + std::sin(1.5707963f * (--t)); +} + +template +__ri static T InOutSine(T t) +{ + return 0.5f * (1 + std::sin(3.1415926f * (t - 0.5f))); +} + +template +__ri static T InQuad(T t) +{ + return t * t; +} + +template +__ri static T OutQuad(T t) +{ + return t * (2 - t); +} + +template +__ri static T InOutQuad(T t) +{ + return t < 0.5f ? 2 * t * t : t * (4 - 2 * t) - 1; +} + +template +__ri static T InCubic(T t) +{ + return t * t * t; +} + +template +__ri static T OutCubic(T t) +{ + return 1 + (--t) * t * t; +} + +template +__ri static T InOutCubic(T t) +{ + return t < 0.5f ? 4 * t * t * t : 1 + (--t) * (2 * (--t)) * (2 * t); +} + +template +__ri static T InQuart(T t) +{ + t *= t; + return t * t; +} + +template +__ri static T OutQuart(T t) +{ + t = (--t) * t; + return 1 - t * t; +} + +template +__ri static T InOutQuart(T t) +{ + if (t < 0.5) + { + t *= t; + return 8 * t * t; + } + else + { + t = (--t) * t; + return 1 - 8 * t * t; + } +} + +template +__ri static T InQuint(T t) +{ + T t2 = t * t; + return t * t2 * t2; +} + +template +__ri static T OutQuint(T t) +{ + T t2 = (--t) * t; + return 1 + t * t2 * t2; +} + +template +__ri static T InOutQuint(T t) +{ + T t2; + if (t < 0.5) + { + t2 = t * t; + return 16 * t * t2 * t2; + } + else + { + t2 = (--t) * t; + return 1 + 16 * t * t2 * t2; + } +} + +template +__ri static T InExpo(T t) +{ + return (std::pow(2, 8 * t) - 1) / 255; +} + +template +__ri static T OutExpo(T t) +{ + return 1 - std::pow(2, -8 * t); +} + +template +__ri static T InOutExpo(T t) +{ + if (t < 0.5f) + { + return (std::pow(2, 16 * t) - 1) / 510; + } + else + { + return 1 - 0.5f * std::pow(2, -16 * (t - 0.5f)); + } +} + +template +__ri static T InCirc(T t) +{ + return 1 - std::sqrt(1 - t); +} + +template +__ri static T OutCirc(T t) +{ + return std::sqrt(t); +} + +template +__ri static T InOutCirc(T t) +{ + if (t < 0.5f) + { + return (1 - std::sqrt(1 - 2 * t)) * 0.5f; + } + else + { + return (1 + std::sqrt(2 * t - 1)) * 0.5f; + } +} + +template +__ri static T InBack(T t) +{ + return t * t * (2.70158f * t - 1.70158f); +} + +template +static T OutBack(T t) +{ + t -= 1; + return 1 + t * t * (2.70158f * t + 1.70158f); +} + +template +__ri static T InOutBack(T t) +{ + if (t < 0.5f) + { + return t * t * (7 * t - 2.5f) * 2; + } + else + { + return 1 + (--t) * t * 2 * (7 * t + 2.5f); + } +} + +template +__ri static T InElastic(T t) +{ + T t2 = t * t; + return t2 * t2 * std::sin(t * pi * 4.5f); +} + +template +__ri static T OutElastic(T t) +{ + T t2 = (t - 1) * (t - 1); + return 1 - t2 * t2 * std::cos(t * pi * 4.5f); +} + +template +__ri static T InOutElastic(T t) +{ + T t2; + if (t < 0.45f) + { + t2 = t * t; + return 8 * t2 * t2 * std::sin(t * pi * 9); + } + else if (t < 0.55f) + { + return 0.5f + 0.75f * std::sin(t * pi * 4); + } + else + { + t2 = (t - 1) * (t - 1); + return 1 - 8 * t2 * t2 * std::sin(t * pi * 9); + } +} + +template +__ri static T InBounce(T t) +{ + return std::pow(2, 6 * (t - 1)) * std::abs(sin(t * pi * 3.5f)); +} + +template +__ri static T OutBounce(T t) +{ + return 1 - std::pow(2, -6 * t) * std::abs(std::cos(t * pi * 3.5f)); +} + +template +__ri static T InOutBounce(T t) +{ + if (t < 0.5f) + { + return 8 * std::pow(2, 8 * (t - 1)) * std::abs(std::sin(t * pi * 7)); + } + else + { + return 1 - 8 * std::pow(2, -8 * t) * std::abs(std::sin(t * pi * 7)); + } +} + +} // namespace Easing diff --git a/common/Image.cpp b/common/Image.cpp new file mode 100644 index 0000000000..100f3ff749 --- /dev/null +++ b/common/Image.cpp @@ -0,0 +1,545 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" +#include "Image.h" +#include "FileSystem.h" +#include "Console.h" +#include "Path.h" +#include "ScopedGuard.h" +#include "StringUtil.h" + +#include "jpgd.h" +#include "jpge.h" +#include + +using namespace Common; + +static bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size); +static bool PNGBufferSaver(const RGBA8Image& image, std::vector* buffer, int quality); +static bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp); +static bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality); + +static bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size); +static bool JPEGBufferSaver(const RGBA8Image& image, std::vector* buffer, int quality); +static bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp); +static bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality); + +struct FormatHandler +{ + const char* extension; + bool (*buffer_loader)(RGBA8Image*, const void*, size_t); + bool (*buffer_saver)(const RGBA8Image&, std::vector*, int); + bool (*file_loader)(RGBA8Image*, const char*, std::FILE*); + bool (*file_saver)(const RGBA8Image&, const char*, std::FILE*, int); +}; + +static constexpr FormatHandler s_format_handlers[] = { + {"png", PNGBufferLoader, PNGBufferSaver, PNGFileLoader, PNGFileSaver}, + {"jpg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver}, + {"jpeg", JPEGBufferLoader, JPEGBufferSaver, JPEGFileLoader, JPEGFileSaver}, +}; + +static const FormatHandler* GetFormatHandler(const std::string_view& extension) +{ + for (const FormatHandler& handler : s_format_handlers) + { + if (StringUtil::compareNoCase(extension, handler.extension)) + return &handler; + } + + return nullptr; +} + +RGBA8Image::RGBA8Image() = default; + +RGBA8Image::RGBA8Image(const RGBA8Image& copy) + : Image(copy) +{ +} + +RGBA8Image::RGBA8Image(u32 width, u32 height, const u32* pixels) + : Image(width, height, pixels) +{ +} + +RGBA8Image::RGBA8Image(RGBA8Image&& move) + : Image(move) +{ +} + +RGBA8Image& RGBA8Image::operator=(const RGBA8Image& copy) +{ + Image::operator=(copy); + return *this; +} + +RGBA8Image& RGBA8Image::operator=(RGBA8Image&& move) +{ + Image::operator=(move); + return *this; +} + +bool RGBA8Image::LoadFromFile(const char* filename) +{ + auto fp = FileSystem::OpenManagedCFile(filename, "rb"); + if (!fp) + return false; + + return LoadFromFile(filename, fp.get()); +} + +bool RGBA8Image::SaveToFile(const char* filename, int quality) const +{ + auto fp = FileSystem::OpenManagedCFile(filename, "wb"); + if (!fp) + return false; + + if (SaveToFile(filename, fp.get(), quality)) + return true; + + // save failed + fp.reset(); + FileSystem::DeleteFilePath(filename); + return false; +} + +bool RGBA8Image::LoadFromFile(const char* filename, std::FILE* fp) +{ + const std::string_view extension(Path::GetExtension(filename)); + const FormatHandler* handler = GetFormatHandler(extension); + if (!handler || !handler->file_loader) + { + Console.Error("(RGBA8Image::LoadFromFile) Unknown extension '%.*s'", + static_cast(extension.size()), extension.data()); + return false; + } + + return handler->file_loader(this, filename, fp); +} + +bool RGBA8Image::LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size) +{ + const std::string_view extension(Path::GetExtension(filename)); + const FormatHandler* handler = GetFormatHandler(extension); + if (!handler || !handler->buffer_loader) + { + Console.Error("(RGBA8Image::LoadFromBuffer) Unknown extension '%.*s'", + static_cast(extension.size()), extension.data()); + return false; + } + + return handler->buffer_loader(this, buffer, buffer_size); +} + +bool RGBA8Image::SaveToFile(const char* filename, std::FILE* fp, int quality) const +{ + const std::string_view extension(Path::GetExtension(filename)); + const FormatHandler* handler = GetFormatHandler(extension); + if (!handler || !handler->file_saver) + { + Console.Error("(RGBA8Image::SaveToFile) Unknown extension '%.*s'", + static_cast(extension.size()), extension.data()); + return false; + } + + if (!handler->file_saver(*this, filename, fp, quality)) + return false; + + return (std::fflush(fp) == 0); +} + +std::optional> RGBA8Image::SaveToBuffer(const char* filename, int quality) const +{ + std::optional> ret; + + const std::string_view extension(Path::GetExtension(filename)); + const FormatHandler* handler = GetFormatHandler(extension); + if (!handler || !handler->file_saver) + { + Console.Error("(RGBA8Image::SaveToBuffer) Unknown extension '%.*s'", + static_cast(extension.size()), extension.data()); + return ret; + } + + ret = std::vector(); + if (!handler->buffer_saver(*this, &ret.value(), quality)) + ret.reset(); + + return ret; +} + +static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop info_ptr, + std::vector& new_data, std::vector& row_pointers) +{ + png_read_info(png_ptr, info_ptr); + + const u32 width = png_get_image_width(png_ptr, info_ptr); + const u32 height = png_get_image_height(png_ptr, info_ptr); + const png_byte color_type = png_get_color_type(png_ptr, info_ptr); + const png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); + + // Read any color_type into 8bit depth, RGBA format. + // See http://www.libpng.org/pub/png/libpng-manual.txt + + if (bit_depth == 16) + png_set_strip_16(png_ptr); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png_ptr); + + // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png_ptr); + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png_ptr); + + // These color_type don't have an alpha channel then fill it with 0xff. + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png_ptr); + + png_read_update_info(png_ptr, info_ptr); + + new_data.resize(width * height); + row_pointers.reserve(height); + for (u32 y = 0; y < height; y++) + row_pointers.push_back(reinterpret_cast(new_data.data() + y * width)); + + png_read_image(png_ptr, row_pointers.data()); + image->SetPixels(width, height, std::move(new_data)); + return true; +} + +bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp) +{ + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) + return false; + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + return false; + } + + ScopedGuard cleanup([&png_ptr, &info_ptr]() { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + }); + + std::vector new_data; + std::vector row_pointers; + + if (setjmp(png_jmpbuf(png_ptr))) + return false; + + png_init_io(png_ptr, fp); + return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers); +} + +bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size) +{ + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) + return false; + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + return false; + } + + ScopedGuard cleanup([&png_ptr, &info_ptr]() { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + }); + + std::vector new_data; + std::vector row_pointers; + + if (setjmp(png_jmpbuf(png_ptr))) + return false; + + struct IOData + { + const u8* buffer; + size_t buffer_size; + size_t buffer_pos; + }; + IOData data = {static_cast(buffer), buffer_size, 0}; + + png_set_read_fn(png_ptr, &data, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { + IOData* data = static_cast(png_get_io_ptr(png_ptr)); + const size_t read_size = std::min(data->buffer_size - data->buffer_pos, size); + if (read_size > 0) + { + std::memcpy(data_ptr, data->buffer + data->buffer_pos, read_size); + data->buffer_pos += read_size; + } + }); + + return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers); +} + +static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_infop info_ptr, int quality) +{ + png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9)); + png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png_ptr, info_ptr); + + for (u32 y = 0; y < image.GetHeight(); ++y) + png_write_row(png_ptr, (png_bytep)image.GetRowPixels(y)); + + png_write_end(png_ptr, nullptr); +} + +bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality) +{ + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + png_infop info_ptr = nullptr; + if (!png_ptr) + return false; + + ScopedGuard cleanup([&png_ptr, &info_ptr]() { + if (png_ptr) + png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr); + }); + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + return false; + + if (setjmp(png_jmpbuf(png_ptr))) + return false; + + png_set_write_fn( + png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { + if (std::fwrite(data_ptr, size, 1, static_cast(png_get_io_ptr(png_ptr))) != 1) + png_error(png_ptr, "file write error"); }, [](png_structp png_ptr) {}); + + PNGSaveCommon(image, png_ptr, info_ptr, quality); + return true; +} + +bool PNGBufferSaver(const RGBA8Image& image, std::vector* buffer, int quality) +{ + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + png_infop info_ptr = nullptr; + if (!png_ptr) + return false; + + ScopedGuard cleanup([&png_ptr, &info_ptr]() { + if (png_ptr) + png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr); + }); + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + return false; + + buffer->reserve(image.GetWidth() * image.GetHeight() * 2); + + if (setjmp(png_jmpbuf(png_ptr))) + return false; + + png_set_write_fn( + png_ptr, buffer, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { + std::vector* buffer = static_cast*>(png_get_io_ptr(png_ptr)); + const size_t old_pos = buffer->size(); + buffer->resize(old_pos + size); + std::memcpy(buffer->data() + old_pos, data_ptr, size); }, [](png_structp png_ptr) {}); + + PNGSaveCommon(image, png_ptr, info_ptr, quality); + return true; +} + +bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size) +{ + int width, height, file_comps; + u8* data = jpgd::decompress_jpeg_image_from_memory(static_cast(buffer), static_cast(buffer_size), + &width, &height, &file_comps, 4, 0); + if (!data) + { + Console.Error("jpgd::decompress_jpeg_image_from_memory() failed"); + return false; + } + + image->SetPixels(static_cast(width), static_cast(height), reinterpret_cast(data)); + std::free(data); + return true; +} + +bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp) +{ + class FileStream : public jpgd::jpeg_decoder_stream + { + std::FILE* m_fp; + bool m_error_flag = false; + bool m_eof_flag = false; + + public: + explicit FileStream(std::FILE* fp_) + : m_fp(fp_) + { + } + + int read(jpgd::uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag) override + { + if (m_eof_flag) + { + *pEOF_flag = true; + return 0; + } + + if (m_error_flag) + return -1; + + int bytes_read = static_cast(std::fread(pBuf, 1, max_bytes_to_read, m_fp)); + if (bytes_read < max_bytes_to_read) + { + if (std::ferror(m_fp)) + { + m_error_flag = true; + return -1; + } + + m_eof_flag = true; + *pEOF_flag = true; + } + + return bytes_read; + } + }; + + FileStream stream(fp); + int width, height, file_comps; + u8* data = jpgd::decompress_jpeg_image_from_stream(&stream, &width, &height, &file_comps, 4, 0); + if (!data) + { + Console.Error("jpgd::decompress_jpeg_image_from_stream() failed"); + return false; + } + + image->SetPixels(static_cast(width), static_cast(height), reinterpret_cast(data)); + std::free(data); + return true; +} + +static bool JPEGCommonSaver(const RGBA8Image& image, jpge::output_stream& stream, int quality) +{ + jpge::params params; + params.m_quality = quality; + + jpge::jpeg_encoder dst_image; + if (!dst_image.init(&stream, image.GetWidth(), image.GetHeight(), 3, params)) + return false; + + // for RGBA->RGB + std::vector row; + row.resize(image.GetWidth() * 3); + + for (uint pass_index = 0; pass_index < dst_image.get_total_passes(); pass_index++) + { + for (u32 i = 0; i < image.GetHeight(); i++) + { + const u8* row_in = reinterpret_cast(image.GetRowPixels(i)); + u8* row_out = row.data(); + for (u32 j = 0; j < image.GetWidth(); j++) + { + *(row_out++) = *(row_in++); + *(row_out++) = *(row_in++); + *(row_out++) = *(row_in++); + row_in++; + } + + if (!dst_image.process_scanline(row.data())) + return false; + } + if (!dst_image.process_scanline(NULL)) + return false; + } + + dst_image.deinit(); + + return true; +} + +bool JPEGBufferSaver(const RGBA8Image& image, std::vector* buffer, int quality) +{ + class BufferStream : public jpge::output_stream + { + std::vector* buffer; + + public: + explicit BufferStream(std::vector* buffer_) + : buffer(buffer_) + { + } + + bool put_buf(const void* Pbuf, int len) override + { + const size_t old_size = buffer->size(); + buffer->resize(buffer->size() + static_cast(len)); + std::memcpy(buffer->data() + old_size, Pbuf, static_cast(len)); + return true; + } + }; + + // give enough space to avoid reallocs + buffer->reserve(image.GetWidth() * image.GetHeight() * 2); + + BufferStream stream(buffer); + return JPEGCommonSaver(image, stream, quality); +} + +bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality) +{ + class FileStream : public jpge::output_stream + { + std::FILE* m_fp; + bool m_error_flag = false; + + public: + explicit FileStream(std::FILE* fp_) + : m_fp(fp_) + { + } + + bool put_buf(const void* Pbuf, int len) override + { + if (m_error_flag) + return false; + + if (std::fwrite(Pbuf, len, 1, m_fp) != 1) + { + m_error_flag = true; + return false; + } + + return true; + } + }; + + FileStream stream(fp); + return JPEGCommonSaver(image, stream, quality); +} diff --git a/common/Image.h b/common/Image.h new file mode 100644 index 0000000000..b2cb769141 --- /dev/null +++ b/common/Image.h @@ -0,0 +1,144 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#include "Pcsx2Defs.h" +#include +#include +#include +#include +#include + +namespace Common +{ + template + class Image + { + public: + Image() = default; + Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); } + Image(const Image& copy) + { + m_width = copy.m_width; + m_height = copy.m_height; + m_pixels = copy.m_pixels; + } + Image(Image&& move) + { + m_width = move.m_width; + m_height = move.m_height; + m_pixels = std::move(move.m_pixels); + move.m_width = 0; + move.m_height = 0; + } + + Image& operator=(const Image& copy) + { + m_width = copy.m_width; + m_height = copy.m_height; + m_pixels = copy.m_pixels; + return *this; + } + Image& operator=(Image&& move) + { + m_width = move.m_width; + m_height = move.m_height; + m_pixels = std::move(move.m_pixels); + move.m_width = 0; + move.m_height = 0; + return *this; + } + + __fi bool IsValid() const { return (m_width > 0 && m_height > 0); } + __fi u32 GetWidth() const { return m_width; } + __fi u32 GetHeight() const { return m_height; } + __fi u32 GetByteStride() const { return (sizeof(PixelType) * m_width); } + __fi const PixelType* GetPixels() const { return m_pixels.data(); } + __fi PixelType* GetPixels() { return m_pixels.data(); } + __fi const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; } + __fi PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; } + __fi void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; } + __fi PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; } + + void Clear(PixelType fill_value = static_cast(0)) + { + std::fill(m_pixels.begin(), m_pixels.end(), fill_value); + } + + void Invalidate() + { + m_width = 0; + m_height = 0; + m_pixels.clear(); + } + + void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast(0)) + { + m_width = new_width; + m_height = new_height; + m_pixels.resize(new_width * new_height); + Clear(fill_value); + } + + void SetPixels(u32 width, u32 height, const PixelType* pixels) + { + m_width = width; + m_height = height; + m_pixels.resize(width * height); + std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType)); + } + + void SetPixels(u32 width, u32 height, std::vector pixels) + { + m_width = width; + m_height = height; + m_pixels = std::move(pixels); + } + + std::vector TakePixels() + { + m_width = 0; + m_height = 0; + return std::move(m_pixels); + } + + protected: + u32 m_width = 0; + u32 m_height = 0; + std::vector m_pixels; + }; + + class RGBA8Image : public Image + { + public: + static constexpr int DEFAULT_SAVE_QUALITY = 85; + + RGBA8Image(); + RGBA8Image(u32 width, u32 height, const u32* pixels); + RGBA8Image(const RGBA8Image& copy); + RGBA8Image(RGBA8Image&& move); + + RGBA8Image& operator=(const RGBA8Image& copy); + RGBA8Image& operator=(RGBA8Image&& move); + + bool LoadFromFile(const char* filename); + bool LoadFromFile(const char* filename, std::FILE* fp); + bool LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size); + + bool SaveToFile(const char* filename, int quality = DEFAULT_SAVE_QUALITY) const; + bool SaveToFile(const char* filename, std::FILE* fp, int quality = DEFAULT_SAVE_QUALITY) const; + std::optional> SaveToBuffer(const char* filename, int quality = DEFAULT_SAVE_QUALITY) const; + }; +} // namespace Common \ No newline at end of file diff --git a/common/common.vcxproj b/common/common.vcxproj index 6e64ed511c..9f8972df5a 100644 --- a/common/common.vcxproj +++ b/common/common.vcxproj @@ -33,6 +33,8 @@ $(SolutionDir)3rdparty\d3d12memalloc\include;$(SolutionDir)3rdparty\glad\include;$(SolutionDir)3rdparty\glslang\glslang;%(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\libpng + %(AdditionalIncludeDirectories);$(SolutionDir)3rdparty\jpgd Async Use PrecompiledHeader.h @@ -64,6 +66,7 @@ + @@ -126,6 +129,7 @@ + @@ -135,6 +139,7 @@ + @@ -204,6 +209,9 @@ {ef6834a9-11f3-4331-bc34-21b325abb180} + + {ed2f21fd-0a36-4a8f-9b90-e7d92a2acb63} + diff --git a/common/common.vcxproj.filters b/common/common.vcxproj.filters index 0a240cf46c..ad01da23f2 100644 --- a/common/common.vcxproj.filters +++ b/common/common.vcxproj.filters @@ -184,6 +184,9 @@ Source Files + + Source Files + @@ -432,6 +435,12 @@ Header Files + + Header Files + + + Header Files +