Skip to content

Commit

Permalink
refactored image class & improved tile export
Browse files Browse the repository at this point in the history
  • Loading branch information
Redcrafter committed Jun 18, 2024
1 parent 7a10355 commit 8528298
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 217 deletions.
13 changes: 6 additions & 7 deletions src/glStuff.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,12 @@ struct Texture {
}

void Load(const Image& image) {
auto size = image.size();
width = size.x;
height = size.y;
width = image.width();
height = image.height();

glBindTexture(GL_TEXTURE_2D, id);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.x, size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.data());
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.data());

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
Expand All @@ -124,11 +123,11 @@ struct Texture {
LoadSubImage(x, y, Image(data));
}
void LoadSubImage(int x, int y, const Image& image) {
auto size = image.size();
assert(x >= 0 && x + size.x <= width && y >= 0 && y + size.y <= height);
auto [w, h] = image.size();
assert(x >= 0 && x + w <= width && y >= 0 && y + h <= height);

glBindTexture(GL_TEXTURE_2D, id);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, image.data());
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.data());
}
};

Expand Down
177 changes: 46 additions & 131 deletions src/image.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
#include "image.hpp"

#include <algorithm>
#include <cstring>
#include <span>
#include <stdexcept>
#include <string>

#pragma warning disable
#define STB_IMAGE_IMPLEMENTATION
Expand All @@ -14,168 +11,86 @@
#pragma warning restore

Image::Image(std::span<const uint8_t> data) {
data_ = (uint32_t*)stbi_load_from_memory(data.data(), data.size(), &width, &height, nullptr, 4);
data_ = (uint32_t*)stbi_load_from_memory(data.data(), data.size(), &width_, &height_, nullptr, 4);
if(data_ == nullptr) {
throw std::runtime_error("failed to load image");
}
}

Image::Image(const std::string& path) {
data_ = (uint32_t*)stbi_load(path.c_str(), &width, &height, nullptr, 4);
data_ = (uint32_t*)stbi_load(path.c_str(), &width_, &height_, nullptr, 4);
if(data_ == nullptr) {
throw std::runtime_error("failed to load image");
}
}

Image::Image(int width, int height) : width(width), height(height) {
Image::Image(int width, int height) : width_(width), height_(height) {
data_ = (uint32_t*)malloc(width * height * 4);
std::memset(data_, 0, width * height * 4);
}

Image::Image(const Image& other) {
width_ = other.width_;
height_ = other.height_;
data_ = (uint32_t*)malloc(width_ * height_ * 4);
std::memcpy(data_, other.data_, width_ * height_ * 4);
}

Image& Image::operator=(const Image& other) {
if(this != &other) {
free(data_);
width_ = other.width_;
height_ = other.height_;
data_ = (uint32_t*)malloc(width_ * height_ * 4);
std::memcpy(data_, other.data_, width_ * height_ * 4);
}
return *this;
}

Image::Image(Image&& other) noexcept {
data_ = other.data_;
width = other.width;
height = other.height;
width_ = other.width_;
height_ = other.height_;

other.data_ = nullptr;
other.width = 0;
other.height = 0;
other.width_ = 0;
other.height_ = 0;
}

Image::~Image() {
free(data_);
}

void Image::save_png(const std::string& path) {
stbi_write_png(path.c_str(), width, height, 4, data_, width * 4);
void Image::save_png(const std::string& path) const {
stbi_write_png(path.c_str(), width_, height_, 4, data_, width_ * 4);
}

Image pack(const std::vector<Image>& images, int width, int height) {
Image result(width, height);
std::vector<glm::ivec2> locations(images.size());

// if x > 0 stores max possible size at position
// if x < 0 stores next free location in x and start of used area in y
std::vector<glm::ivec2> avalible(width * height);

// stores images sorted by size
std::vector<const Image*> sorted(images.size());
for(size_t i = 0; i < images.size(); i++)
sorted[i] = &images[i];
std::stable_sort(sorted.begin(), sorted.end(), [](const Image* a, const Image* b) { return a->size().x * a->size().y > b->size().x * b->size().y; });

for(int y = 0; y < height; ++y) {
for(int x = 0; x < width; ++x) {
avalible[x + y * width] = {width - x, height - y};
void Image::copy_to(Image& other, int x, int y) const {
assert(x >= 0 && y >= 0 && x + width_ <= other.width_ && y + height_ <= other.height_);
for(int y1 = 0; y1 < height_; ++y1) {
for(int x1 = 0; x1 < width_; ++x1) {
other(x + x1, y + y1) = (*this)(x1, y1);
}
}
}

glm::ivec2 lastSize = {0, 0};
int x = 0;
int y = 0;

for(int i = 0; i < sorted.size(); i++) {
const auto size = sorted[i]->size();

// continue from same position if size has not changed
if(size != lastSize) {
x = 0;
y = 0;
}
lastSize = size;

while(y + size.y <= height) {
if(x + size.x > width) {
x = 0;
y++;
continue;
}

const auto v = avalible[x + y * width];
if(v.x < 0) { // skip over used section
x = -v.x;
continue;
}
if(size.x > v.x) { // skip to next section
x += v.x;
continue;
}
if(size.y > v.y) {
// search backwards to take reasonably big steps
auto step = size.x;
while(size.y <= avalible[(x + step - 1) + y * width].y) {
step = step / 2;
}
x += step;
continue;
}

// store image location
locations[i] = {x, y};

// set bottom row to stop up scan
for(int x1 = 0; x1 < size.x; x1++) {
avalible[(x + x1) + (y + size.y - 1) * width] = {-(x + size.x), x};
}

for(int y1 = 0; y1 < size.y; y1++) {
int l = x;
int r = x + size.x;

if(x > 0) {
const auto el = avalible[(x - 1) + (y + y1) * width];
if(el.x < 0) {
// left section is already used. set new left to start of that section
l = el.y;
} else {
// go left and update new max size
for(int x1 = x - 1; x1 >= 0; x1--) {
if(avalible[x1 + (y + y1) * width].x < 0) break;
avalible[x1 + (y + y1) * width].x = x - x1;
}
}
}

if(r < width) {
const auto el = avalible[r + (y + y1) * width];
if(el.x < 0) {
// right section is already used. set new right to end of that section
r = -el.x;
}
}

// update left and right pointers to increase skipping
avalible[(l) + (y + y1) * width].x = -r;
avalible[(r - 1) + (y + y1) * width].y = l;
}

// go up and update new max size
for(int x1 = 0; x1 < size.x; x1++) {
for(int y1 = y - 1; y1 >= 0; y1--) {
if(avalible[(x + x1) + y1 * width].x < 0) break;
avalible[(x + x1) + y1 * width].y = y - y1;
}
}

x += size.x;
break;
Image Image::slice(int x, int y, int width, int height) const {
assert(x >= 0 && y >= 0 && x + width <= this->width_ && y + height <= this->height_);
Image result(width, height);
for(int y1 = 0; y1 < height; ++y1) {
for(int x1 = 0; x1 < width; ++x1) {
result(x1, y1) = (*this)(x + x1, y + y1);
}
}
return result;
}

// copy images to result
for(int i = 0; i < sorted.size(); i++) {
const auto& img = *sorted[i];
auto size = img.size();
auto pos = locations[i];
void Image::fill(int x, int y, int width, int height, uint32_t color) {
assert(x >= 0 && y >= 0 && x + width <= this->width_ && y + height <= this->height_);

// copy img to result
for(int y1 = 0; y1 < size.y; y1++) {
for(int x1 = 0; x1 < size.x; x1++) {
// assert(result(x + x1, y + y1) == 0);
result(pos.x + x1, pos.y + y1) = img(x1, y1);
}
for(int y1 = 0; y1 < height; ++y1) {
for(int x1 = 0; x1 < width; ++x1) {
(*this)(x + x1, y + y1) = color;
}
}

return result;
}
53 changes: 36 additions & 17 deletions src/image.hpp
Original file line number Diff line number Diff line change
@@ -1,49 +1,68 @@
#pragma once

#include <cassert>
#include <span>
#include <string>

#include <glm/glm.hpp>
#include <utility>

class Image {
uint32_t* data_ = nullptr;
int width = 0, height = 0;
int width_ = 0, height_ = 0;

public:
Image() {}
Image() = default;
Image(std::span<const uint8_t> data);
Image(const std::string& path);
Image(int width, int height);
Image(glm::ivec2 size) : Image(size.x, size.y) {}

Image(Image&& other) noexcept;
Image(const Image&) = delete;
Image& operator=(const Image&) = delete;
Image(const Image& other);
Image& operator=(const Image& other);

~Image();

void save_png(const std::string& path);
void save_png(const std::string& path) const;

// copy this image to another image
void copy_to(Image& other, int x, int y) const;
Image slice(int x, int y, int width, int height) const;
void fill(int x, int y, int width, int height, uint32_t color);

int width() const { return width_; }
int height() const { return height_; }
std::pair<int, int> size() const { return {width_, height_}; }

glm::ivec2 size() const { return {width, height}; }
uint32_t* data() { return data_; }
const uint32_t* data() const { return data_; }

uint32_t operator()(int x, int y) const {
assert(x >= 0 && x < width && y >= 0 && y < height);
return data_[x + y * width];
assert(x >= 0 && x < width_ && y >= 0 && y < height_);
return data_[x + y * width_];
}
uint32_t& operator()(int x, int y) {
assert(x >= 0 && x < width && y >= 0 && y < height);
return data_[x + y * width];
assert(x >= 0 && x < width_ && y >= 0 && y < height_);
return data_[x + y * width_];
}

Image& operator=(Image&& other) noexcept {
free(data_);

data_ = other.data_;
width_ = other.width_;
height_ = other.height_;

other.data_ = nullptr;
other.width_ = 0;
other.height_ = 0;

return *this;
}

bool operator==(const Image& other) const {
if(width != other.width || height != other.height) return false;
for(int i = 0; i < width * height; ++i) {
if(width_ != other.width_ || height_ != other.height_) return false;
for(int i = 0; i < width_ * height_; ++i) {
if(data_[i] != other.data_[i]) return false;
}
return true;
}
};

Image pack(const std::vector<Image>& images, int width, int height);
Loading

0 comments on commit 8528298

Please sign in to comment.