Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
cf90f057fe | |||
bca63534d1 | |||
e161d0d9a0 | |||
79efe1c14d | |||
aac25ed35a |
@@ -1,5 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(ReTexture
|
||||
project(ReImage
|
||||
VERSION 1.0
|
||||
LANGUAGES CXX
|
||||
)
|
||||
@@ -11,23 +11,36 @@ include(cmake/CPM.cmake)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME mcolor
|
||||
URL https://git.redacted.cc/maxine/mcolor/archive/Prerelease-4.zip
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE HEADERS "include/*.h")
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||
file(COPY "testImages" DESTINATION "${PROJECT_BINARY_DIR}")
|
||||
|
||||
find_package(PNG REQUIRED)
|
||||
|
||||
include_directories("include" "/usr/include/libpng16")
|
||||
|
||||
if (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
|
||||
message(FATAL_ERROR "In-Source builds are not allowed")
|
||||
endif()
|
||||
|
||||
add_library(ReTexture SHARED ${SOURCES} ${HEADERS})
|
||||
add_executable(ReTextureTest main.cpp)
|
||||
if (UNIX AND NOT APPLE)
|
||||
add_library(ReImage SHARED ${SOURCES} ${HEADERS})
|
||||
endif()
|
||||
|
||||
set_target_properties(ReTexture PROPERTIES LINKER_LANGUAGE CXX)
|
||||
set_target_properties(ReTextureTest PROPERTIES LINKER_LANGUAGE CXX)
|
||||
if (WIN32)
|
||||
add_library(ReImage STATIC ${SOURCES} ${HEADERS})
|
||||
endif()
|
||||
|
||||
target_link_libraries(ReTexture PUBLIC PNG::PNG)
|
||||
target_link_libraries(ReTextureTest PUBLIC ReTexture)
|
||||
target_include_directories(
|
||||
ReImage PUBLIC
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
${mcolor_SOURCE_DIR}/include
|
||||
|
||||
)
|
||||
add_executable(ReImageTest main.cpp)
|
||||
|
||||
set_target_properties(ReImage PROPERTIES LINKER_LANGUAGE CXX)
|
||||
set_target_properties(ReImageTest PROPERTIES LINKER_LANGUAGE CXX)
|
||||
target_link_libraries(ReImage PUBLIC mcolor)
|
||||
target_link_libraries(ReImageTest PUBLIC ReImage)
|
||||
|
31
include/ReImage/Image.h
Normal file
31
include/ReImage/Image.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <ReImage/flags.h>
|
||||
#include <Color4.hpp>
|
||||
|
||||
namespace ReImage {
|
||||
class Image {
|
||||
private:
|
||||
unsigned int width = 0;
|
||||
unsigned int height = 0;
|
||||
TextureFormat format;
|
||||
TextureFlag flags;
|
||||
Image(std::vector<unsigned char>& pixel_data, TextureFormat format, unsigned int width, unsigned int height);
|
||||
void load(const std::string& file);
|
||||
void loadBMP(const std::string& file);
|
||||
void loadPNG(const std::string& file);
|
||||
void invertY();
|
||||
public:
|
||||
std::vector<unsigned char> pixel_data;
|
||||
Image(const std::string& file, const TextureFlag& flags);
|
||||
Image(const Color4* data, const size_t& length, unsigned int width, unsigned int height, TextureFlag flags = TextureFlag::NONE);
|
||||
Image(const Color3* data, const size_t& length, unsigned int width, unsigned int height, TextureFlag flags = TextureFlag::NONE);
|
||||
Image downscale(unsigned int rhs);
|
||||
[[nodiscard]] unsigned int getWidth() const;
|
||||
[[nodiscard]] unsigned int getHeight() const;
|
||||
TextureFormat getTextureFormat();
|
||||
TextureFlag getFlags();
|
||||
};
|
||||
}
|
17
include/ReImage/flags.h
Normal file
17
include/ReImage/flags.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
namespace ReImage {
|
||||
enum TextureFlag {
|
||||
NONE = 0,
|
||||
INVERT_Y = 1,
|
||||
};
|
||||
|
||||
inline TextureFlag operator|(TextureFlag a, TextureFlag b) {
|
||||
return static_cast<TextureFlag>(static_cast<int>(a) | static_cast<int>(b));
|
||||
}
|
||||
|
||||
enum class TextureFormat : bool {
|
||||
RGB = false,
|
||||
RGBA = true
|
||||
};
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
enum class RTextureFlag : unsigned int {
|
||||
INVERT_Y = 0,
|
||||
INVERT_X = 1,
|
||||
};
|
||||
|
||||
enum class RTextureFormat : bool {
|
||||
RGB = false,
|
||||
RGBA = true
|
||||
};
|
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <ReTexture/flags.h>
|
||||
#include <png.h>
|
||||
|
||||
class RTexture {
|
||||
private:
|
||||
void load(const std::string& file);
|
||||
void loadBMP(const std::string& file);
|
||||
void loadPNG(const std::string& file);
|
||||
void invertY();
|
||||
public:
|
||||
uint width;
|
||||
uint height;
|
||||
RTextureFormat format;
|
||||
std::vector<unsigned char> pixelData;
|
||||
RTexture(const std::string& file, const std::vector<RTextureFlag>& args);
|
||||
};
|
7988
include/stb_image.h
Normal file
7988
include/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
10572
include/stb_image_resize2.h
Normal file
10572
include/stb_image_resize2.h
Normal file
File diff suppressed because it is too large
Load Diff
23
main.cpp
23
main.cpp
@@ -1,26 +1,27 @@
|
||||
#include <ReTexture/rTexture.h>
|
||||
#include <ReImage/Image.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace ReImage;
|
||||
int main() {
|
||||
auto* bmp = new RTexture("testImages/1.bmp", {RTextureFlag::INVERT_Y});
|
||||
std::cout << "Bitmap Width: " << bmp->width << std::endl;
|
||||
std::cout << "Bitmap Height: " << bmp->height << std::endl;
|
||||
auto* bmp = new Image("testImages/1.bmp", {TextureFlag::INVERT_Y});
|
||||
std::cout << "Bitmap Width: " << bmp->getWidth() << std::endl;
|
||||
std::cout << "Bitmap Height: " << bmp->getHeight() << std::endl;
|
||||
|
||||
if (bmp->format == RTextureFormat::RGB)
|
||||
if (bmp->getTextureFormat() == TextureFormat::RGB)
|
||||
std::cout << "Bitmap Format: RGB" << std::endl;
|
||||
|
||||
if (bmp->format == RTextureFormat::RGBA)
|
||||
if (bmp->getTextureFormat() == TextureFormat::RGBA)
|
||||
std::cout << "Bitmap Format: RGBA" << std::endl;
|
||||
delete bmp;
|
||||
|
||||
auto* png = new RTexture("testImages/1.png", {RTextureFlag::INVERT_Y});
|
||||
std::cout << "PNG width: " << png->width << std::endl;
|
||||
std::cout << "PNG height: " << png->height << std::endl;
|
||||
auto* png = new Image("testImages/1.png", TextureFlag::INVERT_Y);
|
||||
std::cout << "PNG width: " << png->getWidth() << std::endl;
|
||||
std::cout << "PNG height: " << png->getHeight() << std::endl;
|
||||
|
||||
if (png->format == RTextureFormat::RGB)
|
||||
if (png->getTextureFormat() == TextureFormat::RGB)
|
||||
std::cout << "PNG Format: RGB" << std::endl;
|
||||
|
||||
if (png->format == RTextureFormat::RGBA)
|
||||
if (png->getTextureFormat() == TextureFormat::RGBA)
|
||||
std::cout << "PNG Format: RGBA" << std::endl;
|
||||
delete png;
|
||||
}
|
||||
|
166
src/Image.cpp
Normal file
166
src/Image.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <ReImage/Image.h>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
#define STB_IMAGE_RESIZE_IMPLEMENTATION
|
||||
#include <stb_image_resize2.h>
|
||||
|
||||
namespace ReImage {
|
||||
|
||||
Image::Image(const std::string &file, const TextureFlag& flags) {
|
||||
load(file);
|
||||
if (flags& TextureFlag::INVERT_Y)
|
||||
invertY();
|
||||
|
||||
this->flags = flags;
|
||||
}
|
||||
|
||||
Image::Image(const Color4* data, const size_t& length, unsigned int width, unsigned int height, TextureFlag flags) {
|
||||
pixel_data.resize(length * 4);
|
||||
memcpy(pixel_data.data(), data, length * sizeof(Color4));
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->format = TextureFormat::RGBA;
|
||||
if (flags & INVERT_Y)
|
||||
invertY();
|
||||
|
||||
this->flags = flags;
|
||||
}
|
||||
|
||||
|
||||
void Image::load(const std::string& file) {
|
||||
std::ifstream ifStream(file, std::ios::in | std::ios::binary);
|
||||
unsigned char bmpFileHeader[14];
|
||||
|
||||
ifStream.read(reinterpret_cast<char *>(bmpFileHeader), 14);
|
||||
if (bmpFileHeader[0] == 'B' && bmpFileHeader[1] == 'M') {
|
||||
ifStream.close();
|
||||
loadBMP(file);
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO determine if it's a PNG using the file header instead.
|
||||
if (file.ends_with(".png"))
|
||||
loadPNG(file);
|
||||
}
|
||||
|
||||
void Image::loadBMP(const std::string &file) {
|
||||
std::ifstream bmpFile(file, std::ios::in | std::ios::binary);
|
||||
if (!bmpFile.is_open())
|
||||
return;
|
||||
|
||||
unsigned char bmpFileHeader[14];
|
||||
unsigned char bmpInfoHeader[40];
|
||||
|
||||
bmpFile.read(reinterpret_cast<char *>(bmpFileHeader), 14);
|
||||
bmpFile.read(reinterpret_cast<char *>(bmpInfoHeader), 40);
|
||||
|
||||
width = bmpInfoHeader[4] + (bmpInfoHeader[5] << 8) + (bmpInfoHeader[6] << 16) + (bmpInfoHeader[7] << 24);
|
||||
height = bmpInfoHeader[8] + (bmpInfoHeader[9] << 8) + (bmpInfoHeader[10] << 16) + (bmpInfoHeader[11] << 24);
|
||||
|
||||
int rowPadded = (width * 3 + 3) & (~3);
|
||||
pixel_data.resize(width * height * 3);
|
||||
|
||||
std::vector<unsigned char> rowData(rowPadded);
|
||||
for (int y = height - 1; y >= 0; --y) {
|
||||
bmpFile.read(reinterpret_cast<char *>(rowData.data()), rowPadded);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
pixel_data[(y * width + x) * 3 + 2] = rowData[x * 3 + 0];
|
||||
pixel_data[(y * width + x) * 3 + 1] = rowData[x * 3 + 1];
|
||||
pixel_data[(y * width + x) * 3 + 0] = rowData[x * 3 + 2];
|
||||
}
|
||||
}
|
||||
bmpFile.close();
|
||||
format = TextureFormat::RGB;
|
||||
}
|
||||
|
||||
void Image::loadPNG(const std::string& file) {
|
||||
int channels, w, h;
|
||||
|
||||
unsigned char* imageData = stbi_load(file.c_str(), &w, &h, &channels, 0);
|
||||
|
||||
if (imageData == nullptr)
|
||||
return;
|
||||
|
||||
width = w;
|
||||
height = h;
|
||||
if (channels == 3)
|
||||
format = TextureFormat::RGB;
|
||||
|
||||
if (channels == 4)
|
||||
format = TextureFormat::RGBA;
|
||||
|
||||
pixel_data.assign(imageData, imageData + width * height * channels);
|
||||
stbi_image_free(imageData);
|
||||
}
|
||||
|
||||
void Image::invertY() {
|
||||
unsigned int rowSize;
|
||||
if (format == TextureFormat::RGB)
|
||||
rowSize = width * 3;
|
||||
if (format == TextureFormat::RGBA)
|
||||
rowSize = width * 4;
|
||||
|
||||
std::vector<unsigned char> temp(rowSize);
|
||||
for (unsigned int y = 0; y < height / 2; ++y) {
|
||||
unsigned char *topRow = &pixel_data[y * rowSize];
|
||||
unsigned char *bottomRow = &pixel_data[(height - y - 1) * rowSize];
|
||||
std::copy(bottomRow, bottomRow + rowSize, temp.begin());
|
||||
std::copy(topRow, topRow + rowSize, bottomRow);
|
||||
std::copy(temp.begin(), temp.end(), topRow);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Image::getWidth() const {
|
||||
return width;
|
||||
}
|
||||
|
||||
unsigned int Image::getHeight() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
TextureFormat Image::getTextureFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
TextureFlag Image::getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
Image Image::downscale(unsigned int rhs) {
|
||||
std::vector<unsigned char> result;
|
||||
|
||||
if (format == TextureFormat::RGB)
|
||||
result.resize((width / rhs) * (height / rhs) * 3),
|
||||
stbir_resize_uint8_linear(pixel_data.data(), width, height, 0, result.data(), (width / rhs), (height / rhs), 0, stbir_pixel_layout::STBIR_RGB);
|
||||
|
||||
else if (format == TextureFormat::RGBA)
|
||||
result.resize((width / rhs) * (height / rhs) * 4),
|
||||
stbir_resize_uint8_linear(pixel_data.data(), width, height, 0, result.data(), (width / rhs), (height / rhs), 0, stbir_pixel_layout::STBIR_RGBA);
|
||||
|
||||
return Image(result, format, (width / rhs), (height / rhs));
|
||||
}
|
||||
|
||||
Image::Image(std::vector<unsigned char>& pixel_data, TextureFormat format, unsigned int width, unsigned int height) {
|
||||
this->pixel_data = pixel_data;
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->format = format;
|
||||
this->flags = TextureFlag::NONE;
|
||||
|
||||
}
|
||||
|
||||
Image::Image(const Color3* data, const size_t& length, unsigned int width, unsigned int height, TextureFlag flags) {
|
||||
pixel_data.resize(length * 3);
|
||||
memcpy(pixel_data.data(), data, length * sizeof(Color3));
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->format = TextureFormat::RGB;
|
||||
if (flags & INVERT_Y)
|
||||
invertY();
|
||||
|
||||
this->flags = flags;
|
||||
}
|
||||
}
|
144
src/rTexture.cpp
144
src/rTexture.cpp
@@ -1,144 +0,0 @@
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <ReTexture/rTexture.h>
|
||||
#include <iostream>
|
||||
void RTexture::load(const std::string& file) {
|
||||
std::ifstream ifStream(file, std::ios::in | std::ios::binary);
|
||||
unsigned char bmpFileHeader[14];
|
||||
|
||||
ifStream.read(reinterpret_cast<char*>(bmpFileHeader), 14);
|
||||
if (bmpFileHeader[0] == 'B' && bmpFileHeader[1] == 'M') {
|
||||
ifStream.close();
|
||||
loadBMP(file);
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO
|
||||
if (file.ends_with(".png"))
|
||||
loadPNG(file);
|
||||
}
|
||||
|
||||
void RTexture::loadBMP(const std::string& file) {
|
||||
std::ifstream bmpFile(file, std::ios::in | std::ios::binary);
|
||||
if (!bmpFile.is_open())
|
||||
return;
|
||||
|
||||
unsigned char bmpFileHeader[14];
|
||||
unsigned char bmpInfoHeader[40];
|
||||
|
||||
bmpFile.read(reinterpret_cast<char*>(bmpFileHeader), 14);
|
||||
bmpFile.read(reinterpret_cast<char*>(bmpInfoHeader), 40);
|
||||
|
||||
width = bmpInfoHeader[4] + (bmpInfoHeader[5] << 8) + (bmpInfoHeader[6] << 16) + (bmpInfoHeader[7] << 24);
|
||||
height = bmpInfoHeader[8] + (bmpInfoHeader[9] << 8) + (bmpInfoHeader[10] << 16) + (bmpInfoHeader[11] << 24);
|
||||
|
||||
int rowPadded = (width * 3 + 3) & (~3);
|
||||
pixelData.resize(width * height * 3);
|
||||
|
||||
std::vector<unsigned char> rowData(rowPadded);
|
||||
for (int y = height - 1; y >= 0; --y) {
|
||||
bmpFile.read(reinterpret_cast<char*>(rowData.data()), rowPadded);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
pixelData[(y * width + x) * 3 + 2] = rowData[x * 3 + 0];
|
||||
pixelData[(y * width + x) * 3 + 1] = rowData[x * 3 + 1];
|
||||
pixelData[(y * width + x) * 3 + 0] = rowData[x * 3 + 2];
|
||||
}
|
||||
}
|
||||
bmpFile.close();
|
||||
format = RTextureFormat::RGB;
|
||||
}
|
||||
|
||||
void RTexture::loadPNG(const std::string &file) {
|
||||
FILE *fp = fopen(file.c_str(), "rb");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "Could not open file %s for reading\n", file.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||
if (!png) {
|
||||
fprintf(stderr, "Could not create png read struct\n");
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
png_infop info = png_create_info_struct(png);
|
||||
if (!info) {
|
||||
fprintf(stderr, "Could not create png info struct\n");
|
||||
png_destroy_read_struct(&png, nullptr, nullptr);
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (setjmp(png_jmpbuf(png))) {
|
||||
fprintf(stderr, "Error during png creation\n");
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
|
||||
png_init_io(png, fp);
|
||||
png_read_info(png, info);
|
||||
|
||||
width = png_get_image_width(png, info);
|
||||
height = png_get_image_height(png, info);
|
||||
png_byte color_type = png_get_color_type(png, info);
|
||||
png_byte bit_depth = png_get_bit_depth(png, info);
|
||||
|
||||
if (bit_depth == 16)
|
||||
png_set_strip_16(png);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE)
|
||||
png_set_palette_to_rgb(png);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
|
||||
png_set_expand_gray_1_2_4_to_8(png);
|
||||
|
||||
if (png_get_valid(png, info, PNG_INFO_tRNS))
|
||||
png_set_tRNS_to_alpha(png);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||
png_set_gray_to_rgb(png);
|
||||
|
||||
png_read_update_info(png, info);
|
||||
|
||||
pixelData.resize(png_get_rowbytes(png, info) * height);
|
||||
std::vector<png_bytep> row_pointers(height);
|
||||
|
||||
for (unsigned y = 0; y < height; ++y) {
|
||||
row_pointers[y] = pixelData.data() + y * png_get_rowbytes(png, info);
|
||||
}
|
||||
|
||||
png_read_image(png, row_pointers.data());
|
||||
|
||||
fclose(fp);
|
||||
png_destroy_read_struct(&png, &info, nullptr);
|
||||
if (color_type & PNG_COLOR_MASK_ALPHA)
|
||||
format = RTextureFormat::RGBA;
|
||||
|
||||
if (!(color_type & PNG_COLOR_MASK_ALPHA))
|
||||
format = RTextureFormat::RGB;
|
||||
}
|
||||
|
||||
RTexture::RTexture(const std::string &file, const std::vector<RTextureFlag> &args) {
|
||||
load(file);
|
||||
if (std::find(args.begin(), args.end(), RTextureFlag::INVERT_Y) != args.end())
|
||||
invertY();
|
||||
}
|
||||
|
||||
void RTexture::invertY() {
|
||||
unsigned int rowSize;
|
||||
if (format == RTextureFormat::RGB)
|
||||
rowSize = width * 3;
|
||||
if (format == RTextureFormat::RGBA)
|
||||
rowSize = width * 4;
|
||||
|
||||
std::vector<unsigned char> temp(rowSize);
|
||||
for (uint y = 0; y < height / 2; ++y) {
|
||||
unsigned char* topRow = &pixelData[y * rowSize];
|
||||
unsigned char* bottomRow = &pixelData[(height - y - 1) * rowSize];
|
||||
std::copy(bottomRow, bottomRow + rowSize, temp.begin());
|
||||
std::copy(topRow, topRow + rowSize, bottomRow);
|
||||
std::copy(temp.begin(), temp.end(), topRow);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user