clang-format --style=google

This commit is contained in:
Stephen Seo 2023-03-31 18:47:33 +09:00
parent 62d15771ad
commit ecd65fc42b
9 changed files with 1446 additions and 1480 deletions

View file

@ -12,17 +12,21 @@ Args::Args()
output_filename_("output.png") {} output_filename_("output.png") {}
void Args::DisplayHelp() { void Args::DisplayHelp() {
std::cout std::cout << "[-h | --help] [-b <size> | --blue-noise <size>] [--usecl | "
<< "[-h | --help] [-b <size> | --blue-noise <size>] [--usecl | "
"--nousecl]\n" "--nousecl]\n"
" -h | --help\t\t\t\tDisplay this help text\n" " -h | --help\t\t\t\tDisplay this help text\n"
" -b <size> | --blue-noise <size>\tGenerate blue noise square with " " -b <size> | --blue-noise <size>\tGenerate blue noise "
"square with "
"size\n" "size\n"
" --usecl | --nousecl\t\t\tUse/Disable OpenCL (enabled by default)\n" " --usecl | --nousecl\t\t\tUse/Disable OpenCL (enabled by "
" -t <int> | --threads <int>\t\tUse CPU thread count when not using " "default)\n"
" -t <int> | --threads <int>\t\tUse CPU thread count when "
"not using "
"OpenCL\n" "OpenCL\n"
" -o <filelname> | --output <filename>\tOutput filename to use\n" " -o <filelname> | --output <filename>\tOutput filename to "
" --overwrite\t\t\t\tEnable overwriting of file (default disabled)\n"; "use\n"
" --overwrite\t\t\t\tEnable overwriting of file (default "
"disabled)\n";
} }
bool Args::ParseArgs(int argc, char **argv) { bool Args::ParseArgs(int argc, char **argv) {
@ -53,7 +57,8 @@ bool Args::ParseArgs(int argc, char **argv) {
std::strcmp(argv[0], "--threads") == 0)) { std::strcmp(argv[0], "--threads") == 0)) {
threads_ = std::strtoul(argv[1], nullptr, 10); threads_ = std::strtoul(argv[1], nullptr, 10);
if (threads_ == 0) { if (threads_ == 0) {
std::cout << "ERROR: Failed to parse thread count, using 4 by default" std::cout << "ERROR: Failed to parse thread count, using 4 by "
"default"
<< std::endl; << std::endl;
threads_ = 4; threads_ = 4;
} }

View file

@ -1,8 +1,8 @@
int twoToOne(int x, int y, int width, int height) { int twoToOne(int x, int y, int width, int height) {
while(x < 0) { while (x < 0) {
x += width; x += width;
} }
while(y < 0) { while (y < 0) {
y += height; y += height;
} }
x = x % width; x = x % width;
@ -10,16 +10,16 @@ int twoToOne(int x, int y, int width, int height) {
return x + y * width; return x + y * width;
} }
//float gaussian(float x, float y) { // float gaussian(float x, float y) {
// return exp(-(x*x + y*y) / (1.5F * 1.5F * 2.0F)); // return exp(-(x*x + y*y) / (1.5F * 1.5F * 2.0F));
//} // }
__kernel void do_filter( __kernel void do_filter(__global float *filter_out,
__global float *filter_out, __global const float *precomputed, __global const float *precomputed,
__global const int *pbp, const int width, const int height, __global const int *pbp, const int width,
const int filter_size) { const int height, const int filter_size) {
int i = get_global_id(0); int i = get_global_id(0);
if(i < 0 || i >= width * height) { if (i < 0 || i >= width * height) {
return; return;
} }
@ -27,13 +27,14 @@ __kernel void do_filter(
int y = i / width; int y = i / width;
float sum = 0.0F; float sum = 0.0F;
for(int q = 0; q < filter_size; ++q) { for (int q = 0; q < filter_size; ++q) {
int q_prime = height - filter_size / 2 + y + q; int q_prime = height - filter_size / 2 + y + q;
for(int p = 0; p < filter_size; ++p) { for (int p = 0; p < filter_size; ++p) {
int p_prime = width - filter_size / 2 + x + p; int p_prime = width - filter_size / 2 + x + p;
if(pbp[twoToOne(p_prime, q_prime, width, height)] != 0) { if (pbp[twoToOne(p_prime, q_prime, width, height)] != 0) {
sum += precomputed[twoToOne(p, q, filter_size, filter_size)]; sum += precomputed[twoToOne(p, q, filter_size, filter_size)];
//sum += gaussian(p - filter_size / 2.0F + 0.5F, q - filter_size / 2.0F + 0.5F); // sum += gaussian(p - filter_size / 2.0F + 0.5F, q -
// filter_size / 2.0F + 0.5F);
} }
} }
} }

View file

@ -38,15 +38,13 @@ image::Bl dither::blue_noise(int width, int height, int threads,
break; break;
} }
err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, nullptr);
nullptr);
if (err != CL_SUCCESS) { if (err != CL_SUCCESS) {
std::cerr << "OpenCL: Failed to get a device\n"; std::cerr << "OpenCL: Failed to get a device\n";
break; break;
} }
context = clCreateContext(nullptr, 1, &device, nullptr, nullptr, context = clCreateContext(nullptr, 1, &device, nullptr, nullptr, &err);
&err);
{ {
char buf[1024]; char buf[1024];
@ -59,36 +57,32 @@ image::Bl dither::blue_noise(int width, int height, int threads,
std::string program_string; std::string program_string;
while (program_file.good()) { while (program_file.good()) {
program_file.read(buf, 1024); program_file.read(buf, 1024);
if (int read_count = program_file.gcount(); if (int read_count = program_file.gcount(); read_count > 0) {
read_count > 0) {
program_string.append(buf, read_count); program_string.append(buf, read_count);
} }
} }
const char *string_ptr = program_string.c_str(); const char *string_ptr = program_string.c_str();
std::size_t program_size = program_string.size(); std::size_t program_size = program_string.size();
program = clCreateProgramWithSource(context, 1, program = clCreateProgramWithSource(
(const char **)&string_ptr, context, 1, (const char **)&string_ptr, &program_size, &err);
&program_size, &err);
if (err != CL_SUCCESS) { if (err != CL_SUCCESS) {
std::cerr << "OpenCL: Failed to create the program\n"; std::cerr << "OpenCL: Failed to create the program\n";
clReleaseContext(context); clReleaseContext(context);
break; break;
} }
err = clBuildProgram(program, 1, &device, nullptr, nullptr, err = clBuildProgram(program, 1, &device, nullptr, nullptr, nullptr);
nullptr);
if (err != CL_SUCCESS) { if (err != CL_SUCCESS) {
std::cerr << "OpenCL: Failed to build the program\n"; std::cerr << "OpenCL: Failed to build the program\n";
std::size_t log_size; std::size_t log_size;
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0,
0, nullptr, &log_size); nullptr, &log_size);
std::unique_ptr<char[]> log = std::unique_ptr<char[]> log = std::make_unique<char[]>(log_size + 1);
std::make_unique<char[]>(log_size + 1);
log[log_size] = 0; log[log_size] = 0;
clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size,
log_size, log.get(), nullptr); log.get(), nullptr);
std::cerr << log.get() << std::endl; std::cerr << log.get() << std::endl;
clReleaseProgram(program); clReleaseProgram(program);
@ -176,8 +170,8 @@ std::vector<unsigned int> dither::internal::blue_noise_impl(int width,
// } // }
#endif #endif
// get filter values // get filter values
internal::compute_filter(pbp, width, height, count, filter_size, internal::compute_filter(pbp, width, height, count, filter_size, filter_out,
filter_out, precomputed.get(), threads); precomputed.get(), threads);
// #ifndef NDEBUG // #ifndef NDEBUG
// for(int i = 0; i < count; ++i) { // for(int i = 0; i < count; ++i) {
@ -194,8 +188,8 @@ std::vector<unsigned int> dither::internal::blue_noise_impl(int width,
pbp[max] = false; pbp[max] = false;
// get filter values again // get filter values again
internal::compute_filter(pbp, width, height, count, filter_size, internal::compute_filter(pbp, width, height, count, filter_size, filter_out,
filter_out, precomputed.get(), threads); precomputed.get(), threads);
// get second buffer's min // get second buffer's min
int second_min; int second_min;
@ -217,8 +211,7 @@ std::vector<unsigned int> dither::internal::blue_noise_impl(int width,
for (int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
fprintf(blue_noise_image, "%d ", fprintf(blue_noise_image, "%d ",
pbp[utility::twoToOne(x, y, width, height)] ? 1 pbp[utility::twoToOne(x, y, width, height)] ? 1 : 0);
: 0);
} }
fputc('\n', blue_noise_image); fputc('\n', blue_noise_image);
} }
@ -258,21 +251,19 @@ std::vector<unsigned int> dither::internal::blue_noise_impl(int width,
#endif #endif
internal::compute_filter(pbp, width, height, count, filter_size, internal::compute_filter(pbp, width, height, count, filter_size,
filter_out, precomputed.get(), threads); filter_out, precomputed.get(), threads);
std::tie(std::ignore, max) = std::tie(std::ignore, max) = internal::filter_minmax(filter_out, pbp);
internal::filter_minmax(filter_out, pbp);
pbp[max] = false; pbp[max] = false;
dither_array[max] = i; dither_array[max] = i;
} }
pbp = pbp_copy; pbp = pbp_copy;
} }
std::cout << "\nRanking remainder of first half of pixels...\n"; std::cout << "\nRanking remainder of first half of pixels...\n";
for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); ++i) {
++i) {
#ifndef NDEBUG #ifndef NDEBUG
std::cout << i << ' '; std::cout << i << ' ';
#endif #endif
internal::compute_filter(pbp, width, height, count, filter_size, internal::compute_filter(pbp, width, height, count, filter_size, filter_out,
filter_out, precomputed.get(), threads); precomputed.get(), threads);
std::tie(min, std::ignore) = internal::filter_minmax(filter_out, pbp); std::tie(min, std::ignore) = internal::filter_minmax(filter_out, pbp);
pbp[min] = true; pbp[min] = true;
dither_array[min] = i; dither_array[min] = i;
@ -286,9 +277,8 @@ std::vector<unsigned int> dither::internal::blue_noise_impl(int width,
for (unsigned int i = 0; i < pbp.size(); ++i) { for (unsigned int i = 0; i < pbp.size(); ++i) {
reversed_pbp[i] = !pbp[i]; reversed_pbp[i] = !pbp[i];
} }
internal::compute_filter(reversed_pbp, width, height, count, internal::compute_filter(reversed_pbp, width, height, count, filter_size,
filter_size, filter_out, precomputed.get(), filter_out, precomputed.get(), threads);
threads);
std::tie(std::ignore, max) = internal::filter_minmax(filter_out, pbp); std::tie(std::ignore, max) = internal::filter_minmax(filter_out, pbp);
pbp[max] = true; pbp[max] = true;
dither_array[max] = i; dither_array[max] = i;
@ -318,9 +308,9 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
d_filter_out = clCreateBuffer(context, CL_MEM_WRITE_ONLY, d_filter_out = clCreateBuffer(context, CL_MEM_WRITE_ONLY,
count * sizeof(float), nullptr, nullptr); count * sizeof(float), nullptr, nullptr);
d_precomputed = clCreateBuffer(context, CL_MEM_READ_ONLY, d_precomputed =
precomputed.size() * sizeof(float), nullptr, clCreateBuffer(context, CL_MEM_READ_ONLY,
nullptr); precomputed.size() * sizeof(float), nullptr, nullptr);
d_pbp = clCreateBuffer(context, CL_MEM_READ_ONLY, count * sizeof(int), d_pbp = clCreateBuffer(context, CL_MEM_READ_ONLY, count * sizeof(int),
nullptr, nullptr); nullptr, nullptr);
@ -372,8 +362,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
return {}; return {};
} }
if (clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_filter_out) != if (clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_filter_out) != CL_SUCCESS) {
CL_SUCCESS) {
std::cerr << "OpenCL: Failed to set kernel arg 0\n"; std::cerr << "OpenCL: Failed to set kernel arg 0\n";
clReleaseKernel(kernel); clReleaseKernel(kernel);
clReleaseMemObject(d_pbp); clReleaseMemObject(d_pbp);
@ -382,8 +371,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
clReleaseCommandQueue(queue); clReleaseCommandQueue(queue);
return {}; return {};
} }
if (clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_precomputed) != if (clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_precomputed) != CL_SUCCESS) {
CL_SUCCESS) {
std::cerr << "OpenCL: Failed to set kernel arg 1\n"; std::cerr << "OpenCL: Failed to set kernel arg 1\n";
clReleaseKernel(kernel); clReleaseKernel(kernel);
clReleaseMemObject(d_pbp); clReleaseMemObject(d_pbp);
@ -432,8 +420,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
return {}; return {};
} }
} else { } else {
if (clSetKernelArg(kernel, 5, sizeof(int), &filter_size) != if (clSetKernelArg(kernel, 5, sizeof(int), &filter_size) != CL_SUCCESS) {
CL_SUCCESS) {
std::cerr << "OpenCL: Failed to set kernel arg 4\n"; std::cerr << "OpenCL: Failed to set kernel arg 4\n";
clReleaseKernel(kernel); clReleaseKernel(kernel);
clReleaseMemObject(d_pbp); clReleaseMemObject(d_pbp);
@ -455,19 +442,18 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
clReleaseCommandQueue(queue); clReleaseCommandQueue(queue);
return {}; return {};
} }
global_size = global_size = (std::size_t)std::ceil(count / (float)local_size) * local_size;
(std::size_t)std::ceil(count / (float)local_size) * local_size;
std::cout << "OpenCL: global = " << global_size std::cout << "OpenCL: global = " << global_size << ", local = " << local_size
<< ", local = " << local_size << std::endl; << std::endl;
std::vector<float> filter(count); std::vector<float> filter(count);
bool reversed_pbp = false; bool reversed_pbp = false;
const auto get_filter = [&queue, &kernel, &global_size, &local_size, const auto get_filter = [&queue, &kernel, &global_size, &local_size,
&d_filter_out, &d_pbp, &pbp, &pbp_i, &count, &d_filter_out, &d_pbp, &pbp, &pbp_i, &count, &filter,
&filter, &err, &reversed_pbp]() -> bool { &err, &reversed_pbp]() -> bool {
for (unsigned int i = 0; i < pbp.size(); ++i) { for (unsigned int i = 0; i < pbp.size(); ++i) {
if (reversed_pbp) { if (reversed_pbp) {
pbp_i[i] = pbp[i] ? 0 : 1; pbp_i[i] = pbp[i] ? 0 : 1;
@ -476,15 +462,13 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
} }
} }
if (clEnqueueWriteBuffer(queue, d_pbp, CL_TRUE, 0, count * sizeof(int), if (clEnqueueWriteBuffer(queue, d_pbp, CL_TRUE, 0, count * sizeof(int),
&pbp_i[0], 0, nullptr, &pbp_i[0], 0, nullptr, nullptr) != CL_SUCCESS) {
nullptr) != CL_SUCCESS) {
std::cerr << "OpenCL: Failed to write to d_pbp buffer\n"; std::cerr << "OpenCL: Failed to write to d_pbp buffer\n";
return false; return false;
} }
if (err = clEnqueueNDRangeKernel(queue, kernel, 1, nullptr, if (err = clEnqueueNDRangeKernel(queue, kernel, 1, nullptr, &global_size,
&global_size, &local_size, 0, nullptr, &local_size, 0, nullptr, nullptr);
nullptr);
err != CL_SUCCESS) { err != CL_SUCCESS) {
std::cerr << "OpenCL: Failed to enqueue task: "; std::cerr << "OpenCL: Failed to enqueue task: ";
switch (err) { switch (err) {
@ -530,9 +514,8 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
clFinish(queue); clFinish(queue);
clEnqueueReadBuffer(queue, d_filter_out, CL_TRUE, 0, clEnqueueReadBuffer(queue, d_filter_out, CL_TRUE, 0, count * sizeof(float),
count * sizeof(float), &filter[0], 0, nullptr, &filter[0], 0, nullptr, nullptr);
nullptr);
return true; return true;
}; };
@ -594,8 +577,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
// get second buffer's min // get second buffer's min
int second_min; int second_min;
std::tie(second_min, std::ignore) = std::tie(second_min, std::ignore) = internal::filter_minmax(filter, pbp);
internal::filter_minmax(filter, pbp);
if (second_min == max) { if (second_min == max) {
pbp[max] = true; pbp[max] = true;
@ -614,8 +596,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
for (int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
fprintf(blue_noise_image, "%d ", fprintf(blue_noise_image, "%d ",
pbp[utility::twoToOne(x, y, width, height)] ? 1 pbp[utility::twoToOne(x, y, width, height)] ? 1 : 0);
: 0);
} }
fputc('\n', blue_noise_image); fputc('\n', blue_noise_image);
} }
@ -645,8 +626,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
#ifndef NDEBUG #ifndef NDEBUG
{ {
image::Bl pbp_image = toBl(pbp, width); image::Bl pbp_image = toBl(pbp, width);
pbp_image.writeToFile(image::file_type::PNG, true, pbp_image.writeToFile(image::file_type::PNG, true, "debug_pbp_before.png");
"debug_pbp_before.png");
} }
#endif #endif
@ -678,13 +658,11 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
pbp = pbp_copy; pbp = pbp_copy;
#ifndef NDEBUG #ifndef NDEBUG
image::Bl min_pixels = internal::rangeToBl(dither_array, width); image::Bl min_pixels = internal::rangeToBl(dither_array, width);
min_pixels.writeToFile(image::file_type::PNG, true, min_pixels.writeToFile(image::file_type::PNG, true, "da_min_pixels.png");
"da_min_pixels.png");
#endif #endif
} }
std::cout << "\nRanking remainder of first half of pixels...\n"; std::cout << "\nRanking remainder of first half of pixels...\n";
for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); ++i) {
++i) {
#ifndef NDEBUG #ifndef NDEBUG
std::cout << i << ' '; std::cout << i << ' ';
#endif #endif
@ -703,8 +681,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
#ifndef NDEBUG #ifndef NDEBUG
{ {
image::Bl min_pixels = internal::rangeToBl(dither_array, width); image::Bl min_pixels = internal::rangeToBl(dither_array, width);
min_pixels.writeToFile(image::file_type::PNG, true, min_pixels.writeToFile(image::file_type::PNG, true, "da_mid_pixels.png");
"da_mid_pixels.png");
get_filter(); get_filter();
internal::write_filter(filter, width, "filter_mid.pgm"); internal::write_filter(filter, width, "filter_mid.pgm");
image::Bl pbp_image = toBl(pbp, width); image::Bl pbp_image = toBl(pbp, width);
@ -736,8 +713,7 @@ std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
get_filter(); get_filter();
internal::write_filter(filter, width, "filter_after.pgm"); internal::write_filter(filter, width, "filter_after.pgm");
image::Bl pbp_image = toBl(pbp, width); image::Bl pbp_image = toBl(pbp, width);
pbp_image.writeToFile(image::file_type::PNG, true, pbp_image.writeToFile(image::file_type::PNG, true, "debug_pbp_after.png");
"debug_pbp_after.png");
} }
#endif #endif

View file

@ -1,55 +1,58 @@
#ifndef BLUE_NOISE_HPP #ifndef BLUE_NOISE_HPP
#define BLUE_NOISE_HPP #define BLUE_NOISE_HPP
#include <limits> #include <CL/opencl.h>
#include <vector>
#include <functional>
#include <unordered_set>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <chrono>
#include <cstdio>
#include <queue>
#include <random>
#include <cassert>
#include <stdexcept>
#include <iostream>
#include <cmath>
#include <sys/sysinfo.h> #include <sys/sysinfo.h>
#include <CL/opencl.h> #include <cassert>
#include <chrono>
#include <cmath>
#include <condition_variable>
#include <cstdio>
#include <functional>
#include <iostream>
#include <limits>
#include <mutex>
#include <queue>
#include <random>
#include <stdexcept>
#include <thread>
#include <unordered_set>
#include <vector>
#include "utility.hpp"
#include "image.hpp" #include "image.hpp"
#include "utility.hpp"
namespace dither { namespace dither {
image::Bl blue_noise(int width, int height, int threads = 1, bool use_opencl = true); image::Bl blue_noise(int width, int height, int threads = 1,
bool use_opencl = true);
namespace internal { namespace internal {
std::vector<unsigned int> blue_noise_impl(int width, int height, int threads = 1); std::vector<unsigned int> blue_noise_impl(int width, int height,
std::vector<unsigned int> blue_noise_cl_impl( int threads = 1);
const int width, const int height, const int filter_size, std::vector<unsigned int> blue_noise_cl_impl(const int width, const int height,
cl_context context, cl_device_id device, cl_program program); const int filter_size,
cl_context context,
cl_device_id device,
cl_program program);
inline std::vector<bool> random_noise(int size, int subsize) { inline std::vector<bool> random_noise(int size, int subsize) {
std::vector<bool> pbp(size); std::vector<bool> pbp(size);
std::default_random_engine re(std::random_device{}()); std::default_random_engine re(std::random_device{}());
std::uniform_int_distribution<int> dist(0, size - 1); std::uniform_int_distribution<int> dist(0, size - 1);
// initialize pbp // initialize pbp
for(int i = 0; i < size; ++i) { for (int i = 0; i < size; ++i) {
if(i < subsize) { if (i < subsize) {
pbp[i] = true; pbp[i] = true;
} else { } else {
pbp[i] = false; pbp[i] = false;
} }
} }
// randomize pbp // randomize pbp
for(int i = 0; i < size-1; ++i) { for (int i = 0; i < size - 1; ++i) {
decltype(dist)::param_type range{i+1, size-1}; decltype(dist)::param_type range{i + 1, size - 1};
int ridx = dist(re, range); int ridx = dist(re, range);
// probably can't use std::swap since using std::vector<bool> // probably can't use std::swap since using std::vector<bool>
bool temp = pbp[i]; bool temp = pbp[i];
@ -58,37 +61,34 @@ namespace internal {
} }
return pbp; return pbp;
} }
constexpr float mu = 1.5F; constexpr float mu = 1.5F;
constexpr float mu_squared = mu * mu; constexpr float mu_squared = mu * mu;
constexpr float double_mu_squared = 2.0F * mu * mu; constexpr float double_mu_squared = 2.0F * mu * mu;
inline float gaussian(float x, float y) { inline float gaussian(float x, float y) {
return std::exp(-(x*x + y*y)/(double_mu_squared)); return std::exp(-(x * x + y * y) / (double_mu_squared));
} }
inline std::vector<float> precompute_gaussian(int size) { inline std::vector<float> precompute_gaussian(int size) {
std::vector<float> precomputed; std::vector<float> precomputed;
if (size % 2 == 0) { if (size % 2 == 0) {
++size; ++size;
} }
precomputed.reserve(size * size); precomputed.reserve(size * size);
for(int i = 0; i < size * size; ++i) { for (int i = 0; i < size * size; ++i) {
auto xy = utility::oneToTwo(i, size); auto xy = utility::oneToTwo(i, size);
precomputed.push_back(gaussian( precomputed.push_back(
xy.first - (size / 2), gaussian(xy.first - (size / 2), xy.second - (size / 2)));
xy.second - (size / 2)));
} }
return precomputed; return precomputed;
} }
inline float filter( inline float filter(const std::vector<bool> &pbp, int x, int y, int width,
const std::vector<bool>& pbp, int height, int filter_size) {
int x, int y,
int width, int height, int filter_size) {
float sum = 0.0f; float sum = 0.0f;
if (filter_size % 2 == 0) { if (filter_size % 2 == 0) {
@ -99,23 +99,20 @@ namespace internal {
// is 0 to M. // is 0 to M.
// p' = (M + x - (p - M/2)) % M = (3M/2 + x - p) % M // p' = (M + x - (p - M/2)) % M = (3M/2 + x - p) % M
// q' = (N + y - (q - M/2)) % N = (N + M/2 + y - q) % N // q' = (N + y - (q - M/2)) % N = (N + M/2 + y - q) % N
for(int q = 0; q < filter_size; ++q) { for (int q = 0; q < filter_size; ++q) {
int q_prime = (height - filter_size / 2 + y + q) % height; int q_prime = (height - filter_size / 2 + y + q) % height;
for(int p = 0; p < filter_size; ++p) { for (int p = 0; p < filter_size; ++p) {
int p_prime = (width - filter_size / 2 + x + p) % width; int p_prime = (width - filter_size / 2 + x + p) % width;
if(pbp[utility::twoToOne(p_prime, q_prime, width, height)]) { if (pbp[utility::twoToOne(p_prime, q_prime, width, height)]) {
sum += gaussian(p - filter_size/2, sum += gaussian(p - filter_size / 2, q - filter_size / 2);
q - filter_size/2);
} }
} }
} }
return sum; return sum;
} }
inline float filter_with_precomputed( inline float filter_with_precomputed(const std::vector<bool> &pbp, int x, int y,
const std::vector<bool>& pbp,
int x, int y,
int width, int height, int filter_size, int width, int height, int filter_size,
const std::vector<float> &precomputed) { const std::vector<float> &precomputed) {
float sum = 0.0f; float sum = 0.0f;
@ -124,59 +121,58 @@ namespace internal {
++filter_size; ++filter_size;
} }
for(int q = 0; q < filter_size; ++q) { for (int q = 0; q < filter_size; ++q) {
int q_prime = (height - filter_size / 2 + y + q) % height; int q_prime = (height - filter_size / 2 + y + q) % height;
for(int p = 0; p < filter_size; ++p) { for (int p = 0; p < filter_size; ++p) {
int p_prime = (width - filter_size / 2 + x + p) % width; int p_prime = (width - filter_size / 2 + x + p) % width;
if(pbp[utility::twoToOne(p_prime, q_prime, width, height)]) { if (pbp[utility::twoToOne(p_prime, q_prime, width, height)]) {
sum += precomputed[utility::twoToOne(p, q, filter_size, filter_size)]; sum += precomputed[utility::twoToOne(p, q, filter_size, filter_size)];
} }
} }
} }
return sum; return sum;
} }
inline void compute_filter( inline void compute_filter(const std::vector<bool> &pbp, int width, int height,
const std::vector<bool> &pbp, int width, int height, int count, int filter_size,
int count, int filter_size, std::vector<float> &filter_out, std::vector<float> &filter_out,
const std::vector<float> *precomputed = nullptr, const std::vector<float> *precomputed = nullptr,
int threads = 1) { int threads = 1) {
if(threads == 1) { if (threads == 1) {
if(precomputed) { if (precomputed) {
for(int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
for(int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
filter_out[utility::twoToOne(x, y, width, height)] = filter_out[utility::twoToOne(x, y, width, height)] =
internal::filter_with_precomputed( internal::filter_with_precomputed(pbp, x, y, width, height,
pbp, x, y, width, height, filter_size, *precomputed); filter_size, *precomputed);
} }
} }
} else { } else {
for(int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
for(int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
filter_out[utility::twoToOne(x, y, width, height)] = filter_out[utility::twoToOne(x, y, width, height)] =
internal::filter(pbp, x, y, width, height, filter_size); internal::filter(pbp, x, y, width, height, filter_size);
} }
} }
} }
} else { } else {
if(threads == 0) { if (threads == 0) {
threads = 10; threads = 10;
} }
int active_count = 0; int active_count = 0;
std::mutex cv_mutex; std::mutex cv_mutex;
std::condition_variable cv; std::condition_variable cv;
if(precomputed) { if (precomputed) {
for(int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
{ {
std::unique_lock lock(cv_mutex); std::unique_lock lock(cv_mutex);
active_count += 1; active_count += 1;
} }
std::thread t([] (int *ac, std::mutex *cvm, std::thread t(
std::condition_variable *cv, int i, [](int *ac, std::mutex *cvm, std::condition_variable *cv, int i,
const std::vector<bool> *pbp, int width, const std::vector<bool> *pbp, int width, int height,
int height, int filter_size, int filter_size, std::vector<float> *fout,
std::vector<float> *fout,
const std::vector<float> *precomputed) { const std::vector<float> *precomputed) {
int x, y; int x, y;
std::tie(x, y) = utility::oneToTwo(i, width); std::tie(x, y) = utility::oneToTwo(i, width);
@ -186,63 +182,61 @@ namespace internal {
*ac -= 1; *ac -= 1;
cv->notify_all(); cv->notify_all();
}, },
&active_count, &cv_mutex, &cv, i, &pbp, width, height, &active_count, &cv_mutex, &cv, i, &pbp, width, height, filter_size,
filter_size, &filter_out, precomputed); &filter_out, precomputed);
t.detach(); t.detach();
std::unique_lock lock(cv_mutex); std::unique_lock lock(cv_mutex);
while(active_count >= threads) { while (active_count >= threads) {
cv.wait_for(lock, std::chrono::seconds(1)); cv.wait_for(lock, std::chrono::seconds(1));
} }
} }
} else { } else {
for(int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
{ {
std::unique_lock lock(cv_mutex); std::unique_lock lock(cv_mutex);
active_count += 1; active_count += 1;
} }
std::thread t([] (int *ac, std::mutex *cvm, std::thread t(
std::condition_variable *cv, int i, [](int *ac, std::mutex *cvm, std::condition_variable *cv, int i,
const std::vector<bool> *pbp, int width, const std::vector<bool> *pbp, int width, int height,
int height, int filter_size, int filter_size, std::vector<float> *fout) {
std::vector<float> *fout) {
int x, y; int x, y;
std::tie(x, y) = utility::oneToTwo(i, width); std::tie(x, y) = utility::oneToTwo(i, width);
(*fout)[i] = internal::filter( (*fout)[i] =
*pbp, x, y, width, height, filter_size); internal::filter(*pbp, x, y, width, height, filter_size);
std::unique_lock lock(*cvm); std::unique_lock lock(*cvm);
*ac -= 1; *ac -= 1;
cv->notify_all(); cv->notify_all();
}, },
&active_count, &cv_mutex, &cv, i, &pbp, width, height, &active_count, &cv_mutex, &cv, i, &pbp, width, height, filter_size,
filter_size, &filter_out); &filter_out);
t.detach(); t.detach();
std::unique_lock lock(cv_mutex); std::unique_lock lock(cv_mutex);
while(active_count >= threads) { while (active_count >= threads) {
cv.wait_for(lock, std::chrono::seconds(1)); cv.wait_for(lock, std::chrono::seconds(1));
} }
} }
} }
std::unique_lock lock(cv_mutex); std::unique_lock lock(cv_mutex);
while(active_count > 0) { while (active_count > 0) {
cv.wait_for(lock, std::chrono::seconds(1)); cv.wait_for(lock, std::chrono::seconds(1));
} }
} }
}
} inline std::pair<int, int> filter_minmax(const std::vector<float> &filter,
inline std::pair<int, int> filter_minmax(const std::vector<float> &filter,
std::vector<bool> pbp) { std::vector<bool> pbp) {
// ensure minority pixel is "true" // ensure minority pixel is "true"
unsigned int count = 0; unsigned int count = 0;
for (bool value : pbp) { for (bool value : pbp) {
if(value) { if (value) {
++count; ++count;
} }
} }
if (count * 2 >= pbp.size()) { if (count * 2 >= pbp.size()) {
//std::cout << "MINMAX flip\n"; // DEBUG // std::cout << "MINMAX flip\n"; // DEBUG
for (unsigned int i = 0; i < pbp.size(); ++i) { for (unsigned int i = 0; i < pbp.size(); ++i) {
pbp[i] = !pbp[i]; pbp[i] = !pbp[i];
} }
@ -253,57 +247,56 @@ namespace internal {
int min_index = -1; int min_index = -1;
int max_index = -1; int max_index = -1;
for(std::vector<float>::size_type i = 0; i < filter.size(); ++i) { for (std::vector<float>::size_type i = 0; i < filter.size(); ++i) {
if(!pbp[i] && filter[i] < min) { if (!pbp[i] && filter[i] < min) {
min_index = i; min_index = i;
min = filter[i]; min = filter[i];
} }
if(pbp[i] && filter[i] > max) { if (pbp[i] && filter[i] > max) {
max_index = i; max_index = i;
max = filter[i]; max = filter[i];
} }
} }
return {min_index, max_index}; return {min_index, max_index};
} }
inline std::pair<int, int> filter_abs_minmax( inline std::pair<int, int> filter_abs_minmax(const std::vector<float> &filter) {
const std::vector<float> &filter) {
float min = std::numeric_limits<float>::infinity(); float min = std::numeric_limits<float>::infinity();
float max = -std::numeric_limits<float>::infinity(); float max = -std::numeric_limits<float>::infinity();
int min_index = -1; int min_index = -1;
int max_index = -1; int max_index = -1;
std::default_random_engine re(std::random_device{}()); std::default_random_engine re(std::random_device{}());
std::size_t startIdx = std::uniform_int_distribution<std::size_t>(0, filter.size() - 1)(re); std::size_t startIdx =
std::uniform_int_distribution<std::size_t>(0, filter.size() - 1)(re);
for(std::vector<float>::size_type i = startIdx; i < filter.size(); ++i) { for (std::vector<float>::size_type i = startIdx; i < filter.size(); ++i) {
if(filter[i] < min) { if (filter[i] < min) {
min_index = i; min_index = i;
min = filter[i]; min = filter[i];
} }
if(filter[i] > max) { if (filter[i] > max) {
max_index = i; max_index = i;
max = filter[i]; max = filter[i];
} }
} }
for(std::vector<float>::size_type i = 0; i < startIdx; ++i) { for (std::vector<float>::size_type i = 0; i < startIdx; ++i) {
if(filter[i] < min) { if (filter[i] < min) {
min_index = i; min_index = i;
min = filter[i]; min = filter[i];
} }
if(filter[i] > max) { if (filter[i] > max) {
max_index = i; max_index = i;
max = filter[i]; max = filter[i];
} }
} }
return {min_index, max_index}; return {min_index, max_index};
} }
inline int get_one_or_zero( inline int get_one_or_zero(const std::vector<bool> &pbp, bool get_one, int idx,
const std::vector<bool>& pbp, bool get_one, int width, int height) {
int idx, int width, int height) {
std::queue<int> checking_indices; std::queue<int> checking_indices;
auto xy = utility::oneToTwo(idx, width); auto xy = utility::oneToTwo(idx, width);
@ -312,9 +305,9 @@ namespace internal {
enum { D_DOWN = 0, D_LEFT = 1, D_UP = 2, D_RIGHT = 3 } dir = D_RIGHT; enum { D_DOWN = 0, D_LEFT = 1, D_UP = 2, D_RIGHT = 3 } dir = D_RIGHT;
int next; int next;
while(true) { while (true) {
if(count == 0) { if (count == 0) {
switch(dir) { switch (dir) {
case D_RIGHT: case D_RIGHT:
xy.first = (xy.first + 1) % width; xy.first = (xy.first + 1) % width;
++loops; ++loops;
@ -338,7 +331,7 @@ namespace internal {
break; break;
} }
} else { } else {
switch(dir) { switch (dir) {
case D_DOWN: case D_DOWN:
xy.second = (xy.second + 1) % height; xy.second = (xy.second + 1) % height;
--count; --count;
@ -358,45 +351,47 @@ namespace internal {
} }
} }
next = utility::twoToOne(xy.first, xy.second, width, height); next = utility::twoToOne(xy.first, xy.second, width, height);
if((get_one && pbp[next]) || (!get_one && !pbp[next])) { if ((get_one && pbp[next]) || (!get_one && !pbp[next])) {
return next; return next;
} }
} }
return idx; return idx;
} }
inline void write_filter(const std::vector<float> &filter, int width, const char *filename) { inline void write_filter(const std::vector<float> &filter, int width,
const char *filename) {
int min, max; int min, max;
std::tie(min, max) = filter_abs_minmax(filter); std::tie(min, max) = filter_abs_minmax(filter);
printf("Writing to %s, min is %.3f, max is %.3f\n", filename, filter[min], filter[max]); printf("Writing to %s, min is %.3f, max is %.3f\n", filename, filter[min],
filter[max]);
FILE *filter_image = fopen(filename, "w"); FILE *filter_image = fopen(filename, "w");
fprintf(filter_image, "P2\n%d %d\n255\n", width, (int)filter.size() / width); fprintf(filter_image, "P2\n%d %d\n255\n", width, (int)filter.size() / width);
for(std::vector<float>::size_type i = 0; i < filter.size(); ++i) { for (std::vector<float>::size_type i = 0; i < filter.size(); ++i) {
fprintf(filter_image, "%d ", fprintf(filter_image, "%d ",
(int)(((filter[i] - filter[min]) (int)(((filter[i] - filter[min]) / (filter[max] - filter[min])) *
/ (filter[max] - filter[min])) 255.0f));
* 255.0f)); if ((i + 1) % width == 0) {
if((i + 1) % width == 0) {
fputc('\n', filter_image); fputc('\n', filter_image);
} }
} }
fclose(filter_image); fclose(filter_image);
} }
inline image::Bl toBl(const std::vector<bool>& pbp, int width) { inline image::Bl toBl(const std::vector<bool> &pbp, int width) {
image::Bl bwImage(width, pbp.size() / width); image::Bl bwImage(width, pbp.size() / width);
assert((unsigned long)bwImage.getSize() >= pbp.size() assert((unsigned long)bwImage.getSize() >= pbp.size() &&
&& "New image::Bl size too small (pbp's size is not a multiple of width)"); "New image::Bl size too small (pbp's size is not a multiple of "
"width)");
for(unsigned int i = 0; i < pbp.size(); ++i) { for (unsigned int i = 0; i < pbp.size(); ++i) {
bwImage.getData()[i] = pbp[i] ? 255 : 0; bwImage.getData()[i] = pbp[i] ? 255 : 0;
} }
return bwImage; return bwImage;
} }
inline image::Bl rangeToBl(const std::vector<unsigned int> &values, int width) { inline image::Bl rangeToBl(const std::vector<unsigned int> &values, int width) {
int min = std::numeric_limits<int>::max(); int min = std::numeric_limits<int>::max();
int max = std::numeric_limits<int>::min(); int max = std::numeric_limits<int>::min();
@ -410,25 +405,27 @@ namespace internal {
} }
#ifndef NDEBUG #ifndef NDEBUG
std::cout << "rangeToBl: Got min == " << min << " and max == " << max << std::endl; std::cout << "rangeToBl: Got min == " << min << " and max == " << max
<< std::endl;
#endif #endif
max -= min; max -= min;
image::Bl grImage(width, values.size() / width); image::Bl grImage(width, values.size() / width);
assert((unsigned long)grImage.getSize() >= values.size() assert((unsigned long)grImage.getSize() >= values.size() &&
&& "New image::Bl size too small (values' size is not a multiple of width)"); "New image::Bl size too small (values' size is not a multiple of "
"width)");
for(unsigned int i = 0; i < values.size(); ++i) { for (unsigned int i = 0; i < values.size(); ++i) {
grImage.getData()[i] = std::round(((float)((int)(values[i]) - min) / (float)max) * 255.0F); grImage.getData()[i] =
std::round(((float)((int)(values[i]) - min) / (float)max) * 255.0F);
} }
return grImage; return grImage;
} }
inline std::pair<int, int> filter_minmax_in_range(int start, int width, inline std::pair<int, int> filter_minmax_in_range(
int height, int start, int width, int height, int range,
int range,
const std::vector<float> &vec) { const std::vector<float> &vec) {
float max = -std::numeric_limits<float>::infinity(); float max = -std::numeric_limits<float>::infinity();
float min = std::numeric_limits<float>::infinity(); float min = std::numeric_limits<float>::infinity();
@ -437,33 +434,35 @@ namespace internal {
int minIdx = -1; int minIdx = -1;
auto startXY = utility::oneToTwo(start, width); auto startXY = utility::oneToTwo(start, width);
for(int y = startXY.second - range / 2; y <= startXY.second + range / 2; ++y) { for (int y = startXY.second - range / 2; y <= startXY.second + range / 2;
for(int x = startXY.first - range / 2; x <= startXY.first + range / 2; ++x) { ++y) {
for (int x = startXY.first - range / 2; x <= startXY.first + range / 2;
++x) {
int idx = utility::twoToOne(x, y, width, height); int idx = utility::twoToOne(x, y, width, height);
if(idx == start) { if (idx == start) {
continue; continue;
} }
if(vec[idx] < min) { if (vec[idx] < min) {
min = vec[idx]; min = vec[idx];
minIdx = idx; minIdx = idx;
} }
if(vec[idx] > max) { if (vec[idx] > max) {
max = vec[idx]; max = vec[idx];
maxIdx = idx; maxIdx = idx;
} }
} }
} }
if(minIdx < 0) { if (minIdx < 0) {
throw std::runtime_error("Invalid minIdx value"); throw std::runtime_error("Invalid minIdx value");
} else if(maxIdx < 0) { } else if (maxIdx < 0) {
throw std::runtime_error("Invalid maxIdx value"); throw std::runtime_error("Invalid maxIdx value");
} }
return {minIdx, maxIdx}; return {minIdx, maxIdx};
} }
} // namespace dither::internal } // namespace internal
} // namespace dither } // namespace dither

View file

@ -1,149 +1,128 @@
#include "image.hpp" #include "image.hpp"
#include <cstdio>
#include <random>
#include <iostream>
#include <png.h> #include <png.h>
#include <cstdio>
#include <iostream>
#include <random>
bool image::Base::isValid() const { bool image::Base::isValid() const {
return getWidth() > 0 && getHeight() > 0 && getSize() > 0; return getWidth() > 0 && getHeight() > 0 && getSize() > 0;
} }
image::Bl::Bl() : image::Bl::Bl() : data(), width(0), height(0) {}
data(),
width(0),
height(0)
{}
image::Bl::Bl(int width, int height) : image::Bl::Bl(int width, int height)
data(width * height), : data(width * height), width(width), height(height) {}
width(width),
height(height)
{}
image::Bl::Bl(const std::vector<uint8_t> &data, int width) : image::Bl::Bl(const std::vector<uint8_t> &data, int width)
data(data), : data(data), width(width), height(data.size() / width) {}
width(width),
height(data.size() / width)
{}
image::Bl::Bl(std::vector<uint8_t> &&data, int width) : image::Bl::Bl(std::vector<uint8_t> &&data, int width)
data(std::move(data)), : data(std::move(data)), width(width), height(data.size() / width) {}
width(width),
height(data.size() / width)
{}
image::Bl::Bl(const std::vector<float> &data, int width) : image::Bl::Bl(const std::vector<float> &data, int width)
data{}, : data{}, width(width), height(data.size() / width) {
width(width), for (float gspixel : data) {
height(data.size() / width)
{
for(float gspixel : data) {
this->data.push_back(static_cast<uint8_t>(255.0F * gspixel)); this->data.push_back(static_cast<uint8_t>(255.0F * gspixel));
} }
} }
void image::Bl::randomize() { void image::Bl::randomize() {
if(!isValid()) { if (!isValid()) {
return; return;
} }
std::default_random_engine re(std::random_device{}()); std::default_random_engine re(std::random_device{}());
std::uniform_int_distribution<unsigned int> dist; std::uniform_int_distribution<unsigned int> dist;
for(unsigned int i = 0; i < data.size(); ++i) { for (unsigned int i = 0; i < data.size(); ++i) {
data[i] = i < data.size() / 2 ? 255 : 0; data[i] = i < data.size() / 2 ? 255 : 0;
} }
for(unsigned int i = 0; i < data.size() - 1; ++i) { for (unsigned int i = 0; i < data.size() - 1; ++i) {
int ridx = dist(re, decltype(dist)::param_type{i+1, (unsigned int)data.size()-1}); int ridx = dist(
re, decltype(dist)::param_type{i + 1, (unsigned int)data.size() - 1});
uint8_t temp = data[i]; uint8_t temp = data[i];
data[i] = data[ridx]; data[i] = data[ridx];
data[ridx] = temp; data[ridx] = temp;
} }
} }
unsigned int image::Bl::getSize() const { unsigned int image::Bl::getSize() const { return data.size(); }
return data.size();
}
uint8_t* image::Bl::getData() { uint8_t *image::Bl::getData() {
if(!isValid()) { if (!isValid()) {
return nullptr; return nullptr;
} }
return &data[0]; return &data[0];
} }
const uint8_t* image::Bl::getDataC() const { const uint8_t *image::Bl::getDataC() const {
if(!isValid()) { if (!isValid()) {
return nullptr; return nullptr;
} }
return &data[0]; return &data[0];
} }
unsigned int image::Bl::getWidth() const { unsigned int image::Bl::getWidth() const { return width; }
return width;
}
unsigned int image::Bl::getHeight() const { unsigned int image::Bl::getHeight() const { return height; }
return height;
}
bool image::Bl::canWriteFile(file_type type) { bool image::Bl::canWriteFile(file_type type) {
if(!isValid()) { if (!isValid()) {
std::cout << "Cannot write image because isValid() is false\n"; std::cout << "Cannot write image because isValid() is false\n";
return false; return false;
} }
switch(type) { switch (type) {
case file_type::PBM: case file_type::PBM:
case file_type::PGM: case file_type::PGM:
case file_type::PPM: case file_type::PPM:
case file_type::PNG: case file_type::PNG:
return true; return true;
default: default:
std::cout << "Cannot write image because received invalid file_type\n"; std::cout << "Cannot write image because received invalid "
"file_type\n";
return false; return false;
} }
} }
bool image::Bl::writeToFile(file_type type, bool canOverwrite, const char *filename) { bool image::Bl::writeToFile(file_type type, bool canOverwrite,
if(!isValid() || !canWriteFile(type)) { const char *filename) {
if (!isValid() || !canWriteFile(type)) {
std::cout << "ERROR: Image is not valid or cannot write file type\n"; std::cout << "ERROR: Image is not valid or cannot write file type\n";
return false; return false;
} }
FILE *file = fopen(filename, "r"); FILE *file = fopen(filename, "r");
if(file && !canOverwrite) { if (file && !canOverwrite) {
fclose(file); fclose(file);
std::cout << "ERROR: Will not overwite existing file \"" << filename std::cout << "ERROR: Will not overwite existing file \"" << filename << "\""
<< "\"" << std::endl; << std::endl;
return false; return false;
} }
if(file) { if (file) {
fclose(file); fclose(file);
} }
if(type == file_type::PNG) { if (type == file_type::PNG) {
FILE *outfile = fopen(filename, "wb"); FILE *outfile = fopen(filename, "wb");
if (outfile == nullptr) { if (outfile == nullptr) {
std::cout << "ERROR: Failed to open file for writing (png)\n"; std::cout << "ERROR: Failed to open file for writing (png)\n";
return false; return false;
} }
const static auto pngErrorLFn = [] (png_structp /* unused */, const static auto pngErrorLFn = [](png_structp /* unused */,
png_const_charp message) { png_const_charp message) {
std::cerr << "WARNING [libpng]: " << message << std::endl; std::cerr << "WARNING [libpng]: " << message << std::endl;
}; };
const static auto pngWarnLFn = [] (png_structp /* unused */, const static auto pngWarnLFn = [](png_structp /* unused */,
png_const_charp message) { png_const_charp message) {
std::cerr << "ERROR [libpng]: " << message << std::endl; std::cerr << "ERROR [libpng]: " << message << std::endl;
}; };
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, png_structp png_ptr = png_create_write_struct(
nullptr, PNG_LIBPNG_VER_STRING, nullptr, pngErrorLFn, pngWarnLFn);
pngErrorLFn,
pngWarnLFn);
if (png_ptr == nullptr) { if (png_ptr == nullptr) {
fclose(outfile); fclose(outfile);
@ -174,7 +153,7 @@ bool image::Bl::writeToFile(file_type type, bool canOverwrite, const char *filen
png_write_info(png_ptr, info_ptr); png_write_info(png_ptr, info_ptr);
//png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); // png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
for (unsigned int j = 0; j < this->data.size() / this->width; ++j) { for (unsigned int j = 0; j < this->data.size() / this->width; ++j) {
unsigned char *dataPtr = &this->data.at(j * this->width); unsigned char *dataPtr = &this->data.at(j * this->width);
@ -189,7 +168,7 @@ bool image::Bl::writeToFile(file_type type, bool canOverwrite, const char *filen
return true; return true;
} }
switch(type) { switch (type) {
case file_type::PBM: case file_type::PBM:
file = fopen(filename, "w"); file = fopen(filename, "w");
fprintf(file, "P1\n%d %d", width, height); fprintf(file, "P1\n%d %d", width, height);
@ -207,20 +186,20 @@ bool image::Bl::writeToFile(file_type type, bool canOverwrite, const char *filen
std::cout << "ERROR: Cannot write image file, invalid type\n"; std::cout << "ERROR: Cannot write image file, invalid type\n";
return false; return false;
} }
for(unsigned int i = 0; i < data.size(); ++i) { for (unsigned int i = 0; i < data.size(); ++i) {
if(type == file_type::PBM && i % width == 0) { if (type == file_type::PBM && i % width == 0) {
fprintf(file, "\n"); fprintf(file, "\n");
} }
switch(type) { switch (type) {
case file_type::PBM: case file_type::PBM:
fprintf(file, "%d ", data[i] == 0 ? 0 : 1); fprintf(file, "%d ", data[i] == 0 ? 0 : 1);
break; break;
case file_type::PGM: case file_type::PGM:
//fprintf(file, "%c ", data[i]); // fprintf(file, "%c ", data[i]);
fputc(data[i], file); fputc(data[i], file);
break; break;
case file_type::PPM: case file_type::PPM:
//fprintf(file, "%c %c %c ", data[i], data[i], data[i]); // fprintf(file, "%c %c %c ", data[i], data[i], data[i]);
fputc(data[i], file); fputc(data[i], file);
fputc(data[i], file); fputc(data[i], file);
fputc(data[i], file); fputc(data[i], file);
@ -236,6 +215,7 @@ bool image::Bl::writeToFile(file_type type, bool canOverwrite, const char *filen
return true; return true;
} }
bool image::Bl::writeToFile(file_type type, bool canOverwrite, const std::string &filename) { bool image::Bl::writeToFile(file_type type, bool canOverwrite,
const std::string &filename) {
return writeToFile(type, canOverwrite, filename.c_str()); return writeToFile(type, canOverwrite, filename.c_str());
} }

View file

@ -2,26 +2,26 @@
#define DITHERING_IMAGE_HPP #define DITHERING_IMAGE_HPP
#include <cstdint> #include <cstdint>
#include <vector>
#include <string> #include <string>
#include <vector>
namespace image { namespace image {
enum class color_type { enum class color_type {
Black, Black,
Red, Red,
Green, Green,
Blue, Blue,
Alpha, Alpha,
}; };
enum class file_type { enum class file_type {
PBM, PBM,
PGM, PGM,
PPM, PPM,
PNG, PNG,
}; };
class Base { class Base {
public: public:
Base() = default; Base() = default;
virtual ~Base() {} virtual ~Base() {}
@ -29,14 +29,14 @@ namespace image {
Base(const Base &other) = default; Base(const Base &other) = default;
Base(Base &&other) = default; Base(Base &&other) = default;
Base& operator=(const Base &other) = default; Base &operator=(const Base &other) = default;
Base& operator=(Base &&other) = default; Base &operator=(Base &&other) = default;
virtual void randomize() = 0; virtual void randomize() = 0;
virtual unsigned int getSize() const = 0; virtual unsigned int getSize() const = 0;
virtual uint8_t* getData() = 0; virtual uint8_t *getData() = 0;
virtual const uint8_t* getDataC() const = 0; virtual const uint8_t *getDataC() const = 0;
virtual unsigned int getWidth() const = 0; virtual unsigned int getWidth() const = 0;
virtual unsigned int getHeight() const = 0; virtual unsigned int getHeight() const = 0;
@ -46,12 +46,14 @@ namespace image {
virtual int getTypeStride(color_type type) = 0; virtual int getTypeStride(color_type type) = 0;
virtual bool canWriteFile(file_type type) = 0; virtual bool canWriteFile(file_type type) = 0;
virtual bool writeToFile(file_type type, bool canOverwrite, const char *filename) = 0; virtual bool writeToFile(file_type type, bool canOverwrite,
virtual bool writeToFile(file_type type, bool canOverwrite, const std::string &filename) = 0; const char *filename) = 0;
virtual bool writeToFile(file_type type, bool canOverwrite,
const std::string &filename) = 0;
bool isValid() const; bool isValid() const;
}; };
class Bl : public Base { class Bl : public Base {
public: public:
Bl(); Bl();
Bl(int width, int height); Bl(int width, int height);
@ -63,30 +65,33 @@ namespace image {
Bl(const Bl &other) = default; Bl(const Bl &other) = default;
Bl(Bl &&other) = default; Bl(Bl &&other) = default;
Bl& operator=(const Bl &other) = default; Bl &operator=(const Bl &other) = default;
Bl& operator=(Bl &&other) = default; Bl &operator=(Bl &&other) = default;
void randomize() override; void randomize() override;
unsigned int getSize() const override; unsigned int getSize() const override;
uint8_t* getData() override; uint8_t *getData() override;
const uint8_t* getDataC() const override; const uint8_t *getDataC() const override;
unsigned int getWidth() const override; unsigned int getWidth() const override;
unsigned int getHeight() const override; unsigned int getHeight() const override;
int getTypesCount() override { return 1; } int getTypesCount() override { return 1; }
std::vector<color_type> getTypes() override { return { color_type::Black }; } std::vector<color_type> getTypes() override { return {color_type::Black}; }
int getTypeStride(color_type) override { return 0; } int getTypeStride(color_type) override { return 0; }
bool canWriteFile(file_type type) override; bool canWriteFile(file_type type) override;
bool writeToFile(file_type type, bool canOverwrite, const char *filename) override; bool writeToFile(file_type type, bool canOverwrite,
bool writeToFile(file_type type, bool canOverwrite, const std::string &filename) override; const char *filename) override;
bool writeToFile(file_type type, bool canOverwrite,
const std::string &filename) override;
private: private:
std::vector<uint8_t> data; std::vector<uint8_t> data;
int width; int width;
int height; int height;
}; };
} } // namespace image
#endif #endif

View file

@ -1,25 +1,25 @@
#include <iostream>
#include <cstdio> #include <cstdio>
#include <iostream>
#include "arg_parse.hpp" #include "arg_parse.hpp"
#include "blue_noise.hpp" #include "blue_noise.hpp"
int main(int argc, char **argv) { int main(int argc, char **argv) {
Args args; Args args;
if(args.ParseArgs(argc, argv)) { if (args.ParseArgs(argc, argv)) {
return 0; return 0;
} }
// validation // validation
if (args.generate_blue_noise_) { if (args.generate_blue_noise_) {
if (args.output_filename_.empty()) { if (args.output_filename_.empty()) {
std::cout << "ERROR: Cannot generate blue-noise, output filename is not specified" std::cout << "ERROR: Cannot generate blue-noise, output filename "
"is not specified"
<< std::endl; << std::endl;
Args::DisplayHelp(); Args::DisplayHelp();
return 1; return 1;
} else if (args.blue_noise_size_ < 16) { } else if (args.blue_noise_size_ < 16) {
std::cout << "ERROR: blue-noise size is too small" std::cout << "ERROR: blue-noise size is too small" << std::endl;
<< std::endl;
Args::DisplayHelp(); Args::DisplayHelp();
return 1; return 1;
} else if (!args.overwrite_file_) { } else if (!args.overwrite_file_) {
@ -39,11 +39,11 @@ int main(int argc, char **argv) {
if (args.generate_blue_noise_) { if (args.generate_blue_noise_) {
std::cout << "Generating blue_noise..." << std::endl; std::cout << "Generating blue_noise..." << std::endl;
image::Bl bl = dither::blue_noise(args.blue_noise_size_, image::Bl bl =
args.blue_noise_size_, dither::blue_noise(args.blue_noise_size_, args.blue_noise_size_,
args.threads_, args.threads_, args.use_opencl_);
args.use_opencl_); if (!bl.writeToFile(image::file_type::PNG, args.overwrite_file_,
if(!bl.writeToFile(image::file_type::PNG, args.overwrite_file_, args.output_filename_)) { args.output_filename_)) {
std::cout << "ERROR: Failed to write blue-noise to file\n"; std::cout << "ERROR: Failed to write blue-noise to file\n";
} }
} }

View file

@ -1,33 +1,33 @@
#ifndef DITHERING_UTILITY_HPP #ifndef DITHERING_UTILITY_HPP
#define DITHERING_UTILITY_HPP #define DITHERING_UTILITY_HPP
#include <utility>
#include <cmath> #include <cmath>
#include <utility>
namespace utility { namespace utility {
inline int twoToOne(int x, int y, int width, int height) { inline int twoToOne(int x, int y, int width, int height) {
while(x < 0) { while (x < 0) {
x += width; x += width;
} }
while(y < 0) { while (y < 0) {
y += height; y += height;
} }
x = x % width; x = x % width;
y = y % height; y = y % height;
return x + y * width; return x + y * width;
} }
inline std::pair<int, int> oneToTwo(int i, int width) { inline std::pair<int, int> oneToTwo(int i, int width) {
return {i % width, i / width}; return {i % width, i / width};
} }
inline float dist(int a, int b, int width) { inline float dist(int a, int b, int width) {
auto axy = utility::oneToTwo(a, width); auto axy = utility::oneToTwo(a, width);
auto bxy = utility::oneToTwo(b, width); auto bxy = utility::oneToTwo(b, width);
float dx = axy.first - bxy.first; float dx = axy.first - bxy.first;
float dy = axy.second - bxy.second; float dy = axy.second - bxy.second;
return std::sqrt(dx * dx + dy * dy); return std::sqrt(dx * dx + dy * dy);
}
} }
} // namespace utility
#endif #endif