genetic-images/src/methods.cc

379 lines
12 KiB
C++

#include "methods.hh"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <future>
#include <iostream>
#include <optional>
#include <utility>
auto const thread_nbr = std::thread::hardware_concurrency();
///////////////////////////////////////////////////////////////////////////////
// class implementation //
///////////////////////////////////////////////////////////////////////////////
// constructors ///////////////////////////////////////////////////////////////
ImageManipulator::ImageManipulator(std::filesystem::path const t_input_path,
std::filesystem::path const t_output_path,
int const t_iterations)
: reference_{cv::imread(t_input_path.native(), cv::IMREAD_COLOR)},
output_path_{t_output_path.native()},
diff_{euclidian_distance(generated_image_)},
total_iterations_{t_iterations}
{
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
spdlog::debug("Image loaded!");
spdlog::debug("Width:\t{}", reference_.size().width);
spdlog::debug("Height:\t{}", reference_.size().height);
}
ImageManipulator::ImageManipulator(cv::Mat const& t_origin_image,
int const t_iterations,
int const t_x,
int const t_y,
int const t_width,
int const t_height)
: reference_{t_origin_image(
cv::Range{t_y, std::min(t_y + t_height, t_origin_image.rows)},
cv::Range{t_x, std::min(t_x + t_width, t_origin_image.cols)})},
diff_{euclidian_distance(generated_image_)},
total_iterations_{t_iterations}
{
if (!reference_.data) {
spdlog::critical("Could not open or find image!\n");
exit(-1);
}
spdlog::debug("Image loaded!");
spdlog::debug("Width:\t{}", reference_.size().width);
spdlog::debug("Height:\t{}", reference_.size().height);
}
ImageManipulator::ImageManipulator(const ImageManipulator& other)
: colors_{other.colors_},
reference_{other.reference_},
generated_image_{other.generated_image_},
output_path_{other.output_path_},
diff_{other.diff_},
total_iterations_{other.total_iterations_},
remaining_iter_{other.remaining_iter_},
width_{other.width_},
height_{other.height_}
{
}
ImageManipulator::ImageManipulator(ImageManipulator&& other) noexcept
: colors_{std::move(other.colors_)},
reference_{std::move(other.reference_)},
generated_image_{std::move(other.generated_image_)},
output_path_{std::move(other.output_path_)},
diff_{std::move(other.diff_)},
total_iterations_{other.total_iterations_},
remaining_iter_{other.remaining_iter_},
width_{other.width_},
height_{other.height_}
{
}
// operators //////////////////////////////////////////////////////////////////
[[nodiscard]] auto ImageManipulator::operator=(const ImageManipulator& other)
-> ImageManipulator
{
return ImageManipulator(other);
}
[[nodiscard]] auto ImageManipulator::operator=(
ImageManipulator&& other) noexcept -> ImageManipulator
{
return ImageManipulator{std::move(other)};
}
// public methods /////////////////////////////////////////////////////////////
void ImageManipulator::exec_method(int const t_nb_method,
bool const t_controlled_size = false,
int const cols = 1,
int const rows = 0,
int const submethod = 1)
{
switch (t_nb_method) {
case 1: {
method1();
break;
}
case 2: {
method2();
break;
}
case 3: {
method3();
break;
}
case 4: {
method4(t_controlled_size);
break;
}
case 5: {
method5(t_controlled_size, cols, rows, submethod);
break;
}
default:
spdlog::error("Requested method {} is not implemented.", t_nb_method);
std::exit(-1);
}
}
void ImageManipulator::write_file() const
{
cv::imwrite(output_path_, generated_image_);
}
// private methods ////////////////////////////////////////////////////////////
[[nodiscard]] auto ImageManipulator::euclidian_distance(
cv::Mat const& t_img) const noexcept -> double
{
double euclidian = 0.0;
for (auto itr1 = reference_.begin<uchar>(), itr2 = t_img.begin<uchar>();
itr1 != reference_.end<uchar>() && itr2 != t_img.end<uchar>();
++itr1, ++itr2) {
euclidian += std::pow(*itr1 - *itr2, 2);
}
return std::sqrt(euclidian);
}
[[nodiscard]] auto ImageManipulator::random_color() const noexcept
{
return cv::Scalar(rand() % 255, rand() % 255, rand() % 255);
}
[[nodiscard]] auto ImageManipulator::get_square_values() const noexcept
{
int rand_x = rand() % reference_.size().width;
int rand_y = rand() % reference_.size().height;
int size = rand()
% std::min(reference_.size().width - rand_x,
reference_.size().height - rand_y);
return std::tuple<int, int, int>(rand_x, rand_y, size);
}
[[nodiscard]] auto ImageManipulator::get_controlled_square_values() const
noexcept
{
int rand_x = rand() % reference_.size().width;
int rand_y = rand() % reference_.size().height;
float const coef = static_cast<float>(remaining_iter_)
/ static_cast<float>(total_iterations_);
int const min_size
= static_cast<int>((static_cast<float>(std::min(reference_.size().width,
reference_.size().height))
/ 2.0f)
* coef);
int const max_size = min_size * 2 + 1;
int size = rand() % (max_size - min_size) + min_size;
return std::tuple<int, int, int>(rand_x, rand_y, size);
}
[[nodiscard]] auto ImageManipulator::create_candidate(
bool const t_controlled_size = false) const
{
auto temp_image = generated_image_.clone();
auto const [rand_x, rand_y, size] = t_controlled_size
? get_controlled_square_values()
: get_square_values();
auto const& color = colors_[rand() % colors_.size()];
draw_square(
temp_image, cv::Point{rand_x, rand_y}, size,
cv::Scalar{static_cast<double>(color[0]), static_cast<double>(color[1]),
static_cast<double>(color[2])});
auto new_diff = euclidian_distance(temp_image);
return (new_diff < diff_)
? std::optional<std::pair<cv::Mat, double>>{std::make_pair(
std::move(temp_image), new_diff)}
: std::nullopt;
}
void ImageManipulator::get_color_set()
{
for (int h = 0; h < reference_.size().height; h += thread_nbr) {
std::vector<std::thread> thread_list{};
for (auto i = 0u; i < thread_nbr; ++i) {
thread_list.push_back(
std::thread(&ImageManipulator::threaded_get_color, this, h + i));
}
for (auto& th : thread_list) {
th.join();
}
}
colors_.shrink_to_fit();
}
void ImageManipulator::threaded_get_color(int t_h)
{
if (t_h > reference_.size().height) {
return;
}
for (int w = 0; w < reference_.size().width; w += 3) {
std::array<uchar, 3> temp
= {reference_.at<uchar>(t_h, w), reference_.at<uchar>(t_h, w + 1),
reference_.at<uchar>(t_h, w + 2)};
auto pos = std::find(std::begin(colors_), std::end(colors_), temp);
if (pos == std::end(colors_)) {
colors_mutex_.lock();
colors_.push_back(std::move(temp));
colors_mutex_.unlock();
}
}
}
void ImageManipulator::adjust_size(cv::Point& t_top_left,
int const size) noexcept
{
int const shape_total_width = t_top_left.x + size;
int const shape_total_height = t_top_left.y + size;
if (int const diff = shape_total_height + height_; diff > 0) {
t_top_left.x += diff + 1;
}
if (int const diff = shape_total_width + width_; diff > 0) {
t_top_left.x += diff + 1;
}
}
void ImageManipulator::draw_square(cv::Mat& t_img,
cv::Point const& t_top_left,
int const t_size,
cv::Scalar const& t_color) const
{
auto points = std::make_unique<cv::Point[]>(4);
points[0] = t_top_left;
points[1] = cv::Point{t_top_left.x, t_top_left.y + t_size};
points[2] = cv::Point{t_top_left.x + t_size, t_top_left.y + t_size};
points[3] = cv::Point{t_top_left.x + t_size, t_top_left.y};
fillConvexPoly(t_img, points.get(), 4, t_color);
}
void ImageManipulator::update_gen_image(cv::Mat const& t_img,
double const t_diff)
{
diff_ = t_diff;
t_img.copyTo(generated_image_);
--remaining_iter_;
spdlog::debug("remaining iter: {}\tdiff: {}", remaining_iter_, diff_);
}
void ImageManipulator::method1()
{
spdlog::debug("Beginning method1, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
while (remaining_iter_ > 0 && diff_ > 0.0) {
auto temp_image = generated_image_.clone();
auto const [rand_x, rand_y, size] = get_square_values();
draw_square(temp_image, cv::Point{rand_x, rand_y}, size, random_color());
if (auto const new_diff = euclidian_distance(temp_image);
new_diff < diff_) {
update_gen_image(temp_image, new_diff);
}
}
}
void ImageManipulator::method2()
{
spdlog::debug("Beginning method2, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
spdlog::debug("Running on {} threads", thread_nbr);
get_color_set();
spdlog::debug("{} colors detected", colors_.size());
while (remaining_iter_ > 0 && diff_ > 0.0) {
if (auto result = create_candidate(); result.has_value()) {
update_gen_image(result->first, result->second);
}
}
}
void ImageManipulator::method3()
{
spdlog::debug("Beginning method3, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
spdlog::debug("Running on {} threads", thread_nbr);
get_color_set();
spdlog::debug("{} colors detected", colors_.size());
while (remaining_iter_ > 0 && diff_ > 0.0) {
auto temp_image = generated_image_.clone();
if (auto result = create_candidate(true); result.has_value()) {
update_gen_image(result->first, result->second);
}
}
}
void ImageManipulator::method4(bool const t_controlled_size)
{
spdlog::debug("Beginning method4, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
spdlog::debug("Running on {} threads", thread_nbr);
get_color_set();
spdlog::debug("{} colors detected", colors_.size());
while (remaining_iter_ > 0 && diff_ > 0.0) {
std::vector<std::future<std::optional<std::pair<cv::Mat, double>>>>
results{};
std::vector<std::pair<cv::Mat, double>> values{};
for (size_t i = 0; i < thread_nbr; ++i) {
results.push_back(std::async(std::launch::async,
&ImageManipulator::create_candidate, this,
t_controlled_size));
}
for (auto& elem : results) {
if (auto res = elem.get(); res.has_value() && res->second < diff_) {
values.push_back(*res);
}
}
if (values.size() > 0) {
size_t best = 0;
for (size_t i = 0; i < values.size(); ++i) {
if (values[i].second < values[best].second) {
best = i;
}
}
update_gen_image(values[best].first, values[best].second);
}
}
}
void ImageManipulator::method5(bool const t_controlled_size,
int cols,
int const rows,
int const submethod)
{
spdlog::debug("Beginning method5, initial difference: {}", diff_);
spdlog::debug("nb_total_iter: {}, nb_remaining_iter: {}", total_iterations_,
remaining_iter_);
spdlog::debug("Running on {} threads", thread_nbr);
if (cols == 0) {
cols = rows;
}
std::vector<std::vector<ImageManipulator>> tiles{};
int const tile_width = reference_.cols / cols;
int const tile_height = reference_.rows / rows;
spdlog::debug("tile: width:{}\theight:{}", tile_width, tile_height);
spdlog::debug("image: width:{}\theight:{}", reference_.cols, reference_.rows);
for (int i = 0; i < rows; ++i) {
std::vector<ImageManipulator> tile_row{};
for (int j = 0; j < cols; ++j) {
tile_row.emplace_back(
reference_, total_iterations_, i * tile_height, j * tile_width,
(i != rows - 1) ? tile_height
: tile_height + reference_.cols % tile_height,
(j != cols - 1) ? tile_width
: tile_width + reference_.cols % tile_width);
}
tiles.push_back(tile_row);
}
spdlog::debug("{} tiles", tiles.size());
}