find_package(Threads REQUIRED)
find_package(OpenCL REQUIRED)
+find_package(PNG REQUIRED)
add_executable(Dithering ${Dithering_SOURCES})
target_compile_features(Dithering PUBLIC cxx_std_17)
target_include_directories(Dithering PUBLIC
Threads::Threads
- ${OpenCL_INCLUDE_DIRS})
+ ${OpenCL_INCLUDE_DIRS}
+ ${PNG_INCLUDE_DIRS})
target_link_libraries(Dithering PUBLIC
Threads::Threads
- ${OpenCL_LIBRARIES})
+ ${OpenCL_LIBRARIES}
+ ${PNG_LIBRARIES})
+int twoToOne(x, y, width, height) {
+ while(x < 0) {
+ x += width;
+ }
+ while(y < 0) {
+ y += height;
+ }
+ x = x % width;
+ y = y % height;
+ return x + y * width;
+}
+
+//float gaussian(float x, float y) {
+// return exp(-(x*x + y*y) / (1.5F * 1.5F * 2.0F));
+//}
+
__kernel void do_filter(
- __global float *filter_out, __global float *precomputed,
- __global int *pbp, int width, int height, int filter_size) {
+ __global float *filter_out, __global const float *precomputed,
+ __global const int *pbp, const int width, const int height,
+ const int filter_size) {
int i = get_global_id(0);
if(i < 0 || i >= width * height) {
return;
float sum = 0.0f;
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;
for(int p = 0; p < filter_size; ++p) {
- int p_prime = (width + filter_size / 2 + x - p) % width;
- if(pbp[p_prime + q_prime * width] != 0) {
- sum += precomputed[p + q * filter_size];
+ int p_prime = width - filter_size / 2 + x + p;
+ if(pbp[twoToOne(p_prime, q_prime, width, height)] != 0) {
+ 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);
}
}
}
#include <fstream>
#include <memory>
#include <string>
+#include <unordered_set>
#include <CL/opencl.h>
}
std::cout << "OpenCL: Initialized, trying cl_impl..." << std::endl;
- std::vector<bool> result = internal::blue_noise_cl_impl(
+ std::vector<unsigned int> result = internal::blue_noise_cl_impl(
width, height, filter_size, context, device, program);
clReleaseProgram(program);
clReleaseContext(context);
if(!result.empty()) {
- return internal::toBl(result, width);
+ return internal::rangeToBl(result, width);
}
} while (false);
}
if(!using_opencl) {
std::cout << "OpenCL: Failed to setup/use or is not enabled, using regular impl..."
<< std::endl;
- return internal::toBl(internal::blue_noise_impl(width, height, threads), width);
+ return internal::rangeToBl(internal::blue_noise_impl(width, height, threads), width);
}
return {};
}
-image::Bl dither::blue_noise_grayscale(int width, int height, int threads) {
- int count = width * height;
- std::vector<float> filter_out;
- filter_out.resize(count);
-
- std::vector<float> image = internal::random_noise_grayscale(count);
-
- int iterations = 0;
- int filter_size = (width + height) / 2;
- std::vector<float> precomputed(internal::precompute_gaussian(filter_size));
-
- // TODO DEBUG
- //float pmax = 0.01F;
- //for(float value : precomputed) {
- // if(value > pmax) {
- // pmax = value;
- // }
- //}
- //for(float &value: precomputed) {
- // value /= pmax;
- //}
- //return image::Bl(precomputed, filter_size);
-
- int min, max, min2, max2;
- int prevmax = -1;
- int prevmax2 = -1;
- float tempPixel;
- while(true) {
- printf("Iteration %d\n", iterations);
-
- internal::compute_filter_grayscale(image,
- width, height, count,
- filter_size, filter_out,
- &precomputed, 0);
-
- std::tie(min, max) = internal::filter_minmax(filter_out);
- //std::tie(std::ignore, max) = internal::filter_minmax_in_range(max,
- // width,
- // height,
- // 7,
- // filter_out);
- printf("min == %4d, max == %4d", min, max);
- tempPixel = image[max];
- image[max] = 0.0F;
-
- internal::compute_filter_grayscale(image,
- width, height, count,
- filter_size, filter_out,
- &precomputed, 0);
-
- std::tie(min2, max2) = internal::filter_minmax(filter_out);
- //std::tie(min2, std::ignore) = internal::filter_minmax_in_range(min2,
- // width,
- // height,
- // 7,
- // filter_out);
- printf(", min2 == %4d, max2 == %4d\n", min2, max2);
-
- if(min2 != min) {
- image[max] = tempPixel;
- break;
- } else {
- image[max] = image[min];
- image[min] = tempPixel;
- }
- //if(prevmax == max && prevmax2 == max2) {
- // image[max] = tempPixel;
- // break;
- //} else {
- // image[max] = image[min2];
- // image[min2] = tempPixel;
- //}
-
- prevmax = max;
- prevmax2 = max2;
-
-//#ifndef NDEBUG
- if(iterations % 20 == 0) {
- std::string name;
- name.append("tempGrayscale");
- if(iterations < 10) {
- name.append("00");
- } else if(iterations < 100) {
- name.append("0");
- }
- name.append(std::to_string(iterations));
- name.append(".pgm");
- image::Bl(image, width).writeToFile(image::file_type::PGM, true, name);
-
- name.clear();
- name.append("tempFilter");
- if(iterations < 10) {
- name.append("00");
- } else if(iterations < 100) {
- name.append("0");
- }
- name.append(std::to_string(iterations));
- name.append(".pgm");
-
- internal::compute_filter_grayscale(image,
- width, height, count,
- filter_size, filter_out,
- &precomputed, 0);
- std::vector<float> normalizedFilter(filter_out);
- float fmax = -std::numeric_limits<float>::infinity();
- float fmin = std::numeric_limits<float>::infinity();
- for(float value : normalizedFilter) {
- if(value > fmax) {
- fmax = value;
- }
- if(value < fmin) {
- fmin = value;
- }
- }
- fmax -= fmin;
- for(float &value : normalizedFilter) {
- value = (value - fmin) / fmax;
- }
- image::Bl(normalizedFilter, width).writeToFile(image::file_type::PGM, true, name);
- }
-//#endif
- ++iterations;
- }
-
- internal::compute_filter_grayscale(image,
- width, height, count,
- filter_size, filter_out,
- &precomputed, 0);
- std::vector<float> normalizedFilter(filter_out);
- float fmax = -std::numeric_limits<float>::infinity();
- float fmin = std::numeric_limits<float>::infinity();
- for(float value : normalizedFilter) {
- if(value > fmax) {
- fmax = value;
- }
- if(value < fmin) {
- fmin = value;
- }
- }
- fmax -= fmin;
- for(float &value : normalizedFilter) {
- value = (value - fmin) / fmax;
- }
- image::Bl(normalizedFilter, width).writeToFile(image::file_type::PGM, true, "filterOut.pgm");
- return image::Bl(image, width);
-}
-
-std::vector<bool> dither::internal::blue_noise_impl(int width, int height, int threads) {
+std::vector<unsigned int> dither::internal::blue_noise_impl(int width, int height, int threads) {
int count = width * height;
std::vector<float> filter_out;
filter_out.resize(count);
// }
#endif
- int min, max, min_zero, max_one;
- std::tie(min, max) = internal::filter_minmax(filter_out);
- if(!pbp[max]) {
- max_one = internal::get_one_or_zero(pbp, true, max, width, height);
-#ifndef NDEBUG
- std::cout << "Post get_one(...)" << std::endl;
-#endif
- } else {
- max_one = max;
- }
- if(!pbp[max_one]) {
- std::cerr << "ERROR: Failed to find pbp[max] one" << std::endl;
- break;
- }
-
- if(pbp[min]) {
- min_zero = internal::get_one_or_zero(pbp, false, min, width, height);
-#ifndef NDEBUG
- std::cout << "Post get_zero(...)" << std::endl;
-#endif
- } else {
- min_zero = min;
- }
- if(pbp[min_zero]) {
- std::cerr << "ERROR: Failed to find pbp[min] zero" << std::endl;
- break;
- }
+ int min, max;
+ std::tie(min, max) = internal::filter_minmax(filter_out, pbp);
// remove 1
- pbp[max_one] = false;
+ pbp[max] = false;
// get filter values again
internal::compute_filter(pbp, width, height, count, filter_size,
// get second buffer's min
int second_min;
- std::tie(second_min, std::ignore) = internal::filter_minmax(filter_out);
- if(pbp[second_min]) {
- second_min = internal::get_one_or_zero(pbp, false, second_min, width, height);
- if(pbp[second_min]) {
- std::cerr << "ERROR: Failed to find pbp[second_min] zero" << std::endl;
- break;
- }
- }
+ std::tie(second_min, std::ignore) = internal::filter_minmax(filter_out, pbp);
- if(utility::dist(max_one, second_min, width) < 1.5f) {
- pbp[max_one] = true;
+ if(second_min == max) {
+ pbp[max] = true;
break;
} else {
- pbp[min_zero] = true;
+ pbp[second_min] = true;
}
if(iterations % 100 == 0) {
fclose(blue_noise_image);
//#endif
- return pbp;
+ std::cout << "Generating dither_array...\n";
+ std::vector<unsigned int> dither_array(count);
+ int min, max;
+ {
+ std::vector<bool> pbp_copy(pbp);
+ std::cout << "Ranking minority pixels...\n";
+ for (unsigned int i = pixel_count; i-- > 0;) {
+ std::cout << i << ' ';
+ internal::compute_filter(pbp, width, height, count, filter_size,
+ filter_out, precomputed.get(), threads);
+ std::tie(std::ignore, max) = internal::filter_minmax(filter_out, pbp);
+ pbp[max] = false;
+ dither_array[max] = i;
+ }
+ pbp = pbp_copy;
+ }
+ std::cout << "\nRanking remainder of first half of pixels...\n";
+ for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); ++i) {
+ std::cout << i << ' ';
+ internal::compute_filter(pbp, width, height, count, filter_size,
+ filter_out, precomputed.get(), threads);
+ std::tie(min, std::ignore) = internal::filter_minmax(filter_out, pbp);
+ pbp[min] = true;
+ dither_array[min] = i;
+ }
+ std::cout << "\nRanking last half of pixels...\n";
+ for (unsigned int i = (count + 1) / 2; i < (unsigned int)count; ++i) {
+ std::cout << i << ' ';
+ internal::compute_filter(pbp, width, height, count, filter_size,
+ filter_out, precomputed.get(), threads);
+ std::tie(std::ignore, max) = internal::filter_minmax(filter_out, pbp);
+ pbp[max] = true;
+ dither_array[max] = i;
+ }
+
+ return dither_array;
}
-std::vector<bool> dither::internal::blue_noise_cl_impl(
+std::vector<unsigned int> dither::internal::blue_noise_cl_impl(
int width, int height, int filter_size, cl_context context, cl_device_id device, cl_program program) {
cl_int err;
cl_kernel kernel;
return {};
}
- /*
- err = clEnqueueWriteBuffer(queue, d_pbp, CL_TRUE, 0, count * sizeof(int), &pbp_i[0], 0, nullptr, nullptr);
- if(err != CL_SUCCESS) {
- std::cerr << "OpenCL: Failed to write to d_pbp buffer\n";
- clReleaseMemObject(d_pbp);
- clReleaseMemObject(d_precomputed);
- clReleaseMemObject(d_filter_out);
- clReleaseCommandQueue(queue);
- return {};
- }
- */
-
kernel = clCreateKernel(program, "do_filter", &err);
if(err != CL_SUCCESS) {
std::cerr << "OpenCL: Failed to create kernel: ";
break;
}
- int min, max, min_zero, max_one;
- std::tie(min, max) = internal::filter_minmax(filter);
- if(!pbp[max]) {
- max_one = internal::get_one_or_zero(pbp, true, max, width, height);
- } else {
- max_one = max;
- }
- if(!pbp[max_one]) {
- std::cerr << "ERROR: Failed to find pbp[max] one" << std::endl;
- break;
- }
-
- if(pbp[min]) {
- min_zero = internal::get_one_or_zero(pbp, false, min, width, height);
- } else {
- min_zero = min;
- }
- if(pbp[min_zero]) {
- std::cerr << "ERROR: Failed to find pbp[min] zero" << std::endl;
- break;
- }
+ int min, max;
+ std::tie(min, max) = internal::filter_minmax(filter, pbp);
- pbp[max_one] = false;
+ pbp[max] = false;
if(!get_filter()) {
std::cerr << "OpenCL: Failed to execute do_filter\n";
// get second buffer's min
int second_min;
- std::tie(second_min, std::ignore) = internal::filter_minmax(filter);
- if(pbp[second_min]) {
- second_min = internal::get_one_or_zero(pbp, false, second_min, width, height);
- if(pbp[second_min]) {
- std::cerr << "ERROR: Failed to find pbp[second_min] zero" << std::endl;
- break;
- }
- }
+ std::tie(second_min, std::ignore) = internal::filter_minmax(filter, pbp);
- if(utility::dist(max_one, second_min, width) < 1.5f) {
- pbp[max_one] = true;
+ if(second_min == max) {
+ pbp[max] = true;
break;
} else {
- pbp[min_zero] = true;
+ pbp[second_min] = true;
}
if(iterations % 100 == 0) {
fclose(blue_noise_image);
}
+#ifndef NDEBUG
+ {
+ image::Bl pbp_image = toBl(pbp, width);
+ pbp_image.writeToFile(image::file_type::PNG, true, "debug_pbp_before.png");
+ }
+#endif
+
+ std::cout << "Generating dither_array...\n";
+ std::unordered_set<unsigned int> set;
+ std::vector<unsigned int> dither_array(count);
+ int min, max;
+ {
+ std::vector<bool> pbp_copy(pbp);
+ std::cout << "Ranking minority pixels...\n";
+ for (unsigned int i = pixel_count; i-- > 0;) {
+ std::cout << i << ' ';
+ get_filter();
+ std::tie(std::ignore, max) = internal::filter_minmax(filter, pbp);
+ pbp.at(max) = false;
+ dither_array.at(max) = i;
+ if (set.find(max) != set.end()) {
+ std::cout << "\nWARNING: Reusing index " << max << '\n';
+ } else {
+ set.insert(max);
+ }
+ }
+ pbp = pbp_copy;
+ }
+ std::cout << "\nRanking remainder of first half of pixels...\n";
+ for (unsigned int i = pixel_count; i < (unsigned int)((count + 1) / 2); ++i) {
+ std::cout << i << ' ';
+ get_filter();
+ std::tie(min, std::ignore) = internal::filter_minmax(filter, pbp);
+ pbp.at(min) = true;
+ dither_array.at(min) = i;
+ if (set.find(min) != set.end()) {
+ std::cout << "\nWARNING: Reusing index " << min << '\n';
+ } else {
+ set.insert(min);
+ }
+ }
+#ifndef NDEBUG
+ {
+ get_filter();
+ internal::write_filter(filter, width, "filter_mid.pgm");
+ image::Bl pbp_image = toBl(pbp, width);
+ pbp_image.writeToFile(image::file_type::PNG, true, "debug_pbp_mid.png");
+ }
+#endif
+ std::cout << "\nRanking last half of pixels...\n";
+ for (unsigned int i = (count + 1) / 2; i < (unsigned int)count; ++i) {
+ std::cout << i << ' ';
+ get_filter();
+ std::tie(std::ignore, max) = internal::filter_minmax(filter, pbp);
+ pbp.at(max) = true;
+ dither_array.at(max) = i;
+ if (set.find(max) != set.end()) {
+ std::cout << "\nWARNING: Reusing index " << max << '\n';
+ } else {
+ set.insert(max);
+ }
+ }
+ std::cout << std::endl;
+
+#ifndef NDEBUG
+ {
+ get_filter();
+ internal::write_filter(filter, width, "filter_after.pgm");
+ image::Bl pbp_image = toBl(pbp, width);
+ pbp_image.writeToFile(image::file_type::PNG, true, "debug_pbp_after.png");
+ }
+#endif
+
clReleaseKernel(kernel);
clReleaseMemObject(d_pbp);
clReleaseMemObject(d_precomputed);
clReleaseMemObject(d_filter_out);
clReleaseCommandQueue(queue);
- return pbp;
+ return dither_array;
}
#ifndef BLUE_NOISE_HPP
#define BLUE_NOISE_HPP
+#include <limits>
#include <vector>
#include <functional>
#include <unordered_set>
#include <random>
#include <cassert>
#include <stdexcept>
+#include <iostream>
#include <sys/sysinfo.h>
image::Bl blue_noise(int width, int height, int threads = 1, bool use_opencl = true);
-image::Bl blue_noise_grayscale(int width, int height, int threads = 1);
-
namespace internal {
- std::vector<bool> blue_noise_impl(int width, int height, int threads = 1);
- std::vector<bool> blue_noise_cl_impl(
+ std::vector<unsigned int> blue_noise_impl(int width, int height, int threads = 1);
+ std::vector<unsigned int> blue_noise_cl_impl(
int width, int height, int filter_size,
cl_context context, cl_device_id device, cl_program program);
return pbp;
}
- inline std::vector<float> random_noise_grayscale(unsigned int size) {
- std::vector<float> graynoise;
- graynoise.reserve(size);
- std::default_random_engine re(std::random_device{}());
- std::uniform_real_distribution<float> dist(0.0F, 1.0F);
-
- for(unsigned int i = 0; i < size; ++i) {
- graynoise.push_back(static_cast<float>(i) / static_cast<float>(size - 1));
- //graynoise.push_back(dist(re));
- }
- for(unsigned int i = 0; i < size - 1; ++i) {
- std::uniform_int_distribution<unsigned int> range(i + 1, size - 1);
- unsigned int ridx = range(re);
- float temp = graynoise[i];
- graynoise[i] = graynoise[ridx];
- graynoise[ridx] = temp;
- }
-
- return graynoise;
- }
-
constexpr float mu = 1.5F;
constexpr float mu_squared = mu * mu;
constexpr float double_mu_squared = 2.0F * mu * mu;
for(int i = 0; i < size * size; ++i) {
auto xy = utility::oneToTwo(i, size);
precomputed.push_back(gaussian(
- (float)xy.first - (float)size / 2.0f,
- (float)xy.second - (float)size / 2.0f));
+ (float)xy.first - (float)size / 2.0F + 0.5F,
+ (float)xy.second - (float)size / 2.0F + 0.5F));
}
return precomputed;
// 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
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) {
- 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)]) {
- sum += gaussian((float)p - filter_size/2.0f,
- (float)q - filter_size/2.0f);
+ sum += gaussian((float)p - filter_size/2.0F + 0.5F,
+ (float)q - filter_size/2.0F + 0.5F);
}
}
}
return sum;
}
- inline float filter_grayscale(
- const std::vector<float> &image,
- int x, int y,
- int width, int height, int filter_size) {
- float sum = 0.0F;
- for(int q = 0; q < filter_size; ++q) {
- int q_prime = (height + filter_size / 2 + y - q) % height;
- for(int p = 0; p < filter_size; ++p) {
- int p_prime = (width + filter_size / 2 + x - p) % width;
- sum += image[utility::twoToOne(p_prime, q_prime, width, height)]
- * gaussian((float)p - filter_size/2.0F,
- (float)q - filter_size/2.0F);
- }
- }
-
- return sum;
- }
-
inline float filter_with_precomputed(
const std::vector<bool>& pbp,
int x, int y,
float sum = 0.0f;
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) {
- 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)]) {
sum += precomputed[utility::twoToOne(p, q, filter_size, filter_size)];
}
return sum;
}
- inline float filter_with_precomputed_grayscale(
- const std::vector<float>& image,
- int x, int y,
- int width, int height, int filter_size,
- const std::vector<float> &precomputed) {
- float sum = 0.0F;
-
- for(int q = 0; q < filter_size; ++q) {
- int q_prime = (height - filter_size / 2 + y + q) % height;
- for(int p = 0; p < filter_size; ++p) {
- int p_prime = (width - filter_size / 2 + x + p) % width;
- sum += image[utility::twoToOne(p_prime, q_prime, width, height)]
- * precomputed[utility::twoToOne(p,
- q,
- filter_size,
- filter_size)];
- }
- }
-
- return sum;
- }
-
inline void compute_filter(
const std::vector<bool> &pbp, int width, int height,
int count, int filter_size, std::vector<float> &filter_out,
}
- inline void compute_filter_grayscale(
- const std::vector<float> &image, int width, int height,
- int count, int filter_size, std::vector<float> &filter_out,
- const std::vector<float> *precomputed = nullptr,
- int threads = 1) {
- if(threads == 1) {
- if(precomputed) {
- for(int y = 0; y < height; ++y) {
- for(int x = 0; x < width; ++x) {
- filter_out[utility::twoToOne(x, y, width, height)] =
- internal::filter_with_precomputed_grayscale(
- image,
- x, y,
- width, height,
- filter_size,
- *precomputed);
- }
- }
- } else {
- for(int y = 0; y < height; ++y) {
- for(int x = 0; x < width; ++x) {
- filter_out[utility::twoToOne(x, y, width, height)] =
- internal::filter_grayscale(image,
- x, y,
- width, height,
- filter_size);
- }
- }
+ inline std::pair<int, int> filter_minmax(const std::vector<float> &filter,
+ std::vector<bool> pbp) {
+ // ensure minority pixel is "true"
+ unsigned int count = 0;
+ for (bool value : pbp) {
+ if(value) {
+ ++count;
}
- } else {
- if(threads == 0) {
- threads = get_nprocs();
- if(threads == 0) {
- throw std::runtime_error("0 threads detected, "
- "should be impossible");
- }
+ }
+ if (count * 2 >= pbp.size()) {
+ for (unsigned int i = 0; i < pbp.size(); ++i) {
+ pbp[i] = !pbp[i];
}
+ }
- if(precomputed) {
- const auto tfn = [] (unsigned int ymin, unsigned int ymax,
- unsigned int width, unsigned int height,
- unsigned int filter_size,
- const std::vector<float> *const image,
- std::vector<float> *const filter_out,
- const std::vector<float> *const precomputed) {
- for(unsigned int y = ymin; y < ymax; ++y) {
- for(unsigned int x = 0; x < width; ++x) {
- (*filter_out)[utility::twoToOne(x, y, width, height)] =
- internal::filter_with_precomputed_grayscale(
- *image,
- x, y,
- width, height,
- filter_size,
- *precomputed);
- }
- }
- };
- unsigned int step = height / threads;
- std::vector<std::thread> threadHandles;
- for(int i = 0; i < threads; ++i) {
- unsigned int starty = i * step;
- unsigned int endy = (i + 1) * step;
- if(i + 1 == threads) {
- endy = height;
- }
- threadHandles.emplace_back(tfn, starty, endy,
- width, height,
- filter_size,
- &image,
- &filter_out,
- precomputed);
- }
- for(int i = 0; i < threads; ++i) {
- threadHandles[i].join();
- }
- } else {
- // TODO unimplemented
- throw std::runtime_error("Unimplemented");
+ float min = std::numeric_limits<float>::infinity();
+ float max = -std::numeric_limits<float>::infinity();
+ int min_index = -1;
+ int max_index = -1;
+
+ for(std::vector<float>::size_type i = 0; i < filter.size(); ++i) {
+ if(!pbp[i] && filter[i] < min) {
+ min_index = i;
+ min = filter[i];
+ }
+ if(pbp[i] && filter[i] > max) {
+ max_index = i;
+ max = filter[i];
}
}
+
+ return {min_index, max_index};
}
- inline std::pair<int, int> filter_minmax(const std::vector<float>& filter) {
+ inline std::pair<int, int> filter_abs_minmax(
+ const std::vector<float> &filter) {
float min = std::numeric_limits<float>::infinity();
float max = -std::numeric_limits<float>::infinity();
- int min_index = 0;
- int max_index = 0;
+ int min_index = -1;
+ int max_index = -1;
- for(std::vector<float>::size_type i = 0; i < filter.size(); ++i) {
+ std::default_random_engine re(std::random_device{}());
+ 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) {
+ if(filter[i] < min) {
+ min_index = i;
+ min = filter[i];
+ }
+ if(filter[i] > max) {
+ max_index = i;
+ max = filter[i];
+ }
+ }
+ for(std::vector<float>::size_type i = 0; i < startIdx; ++i) {
if(filter[i] < min) {
min_index = i;
min = filter[i];
inline void write_filter(const std::vector<float> &filter, int width, const char *filename) {
int min, max;
- std::tie(min, max) = filter_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]);
FILE *filter_image = fopen(filename, "w");
&& "New image::Bl size too small (pbp's size is not a multiple of width)");
for(unsigned int i = 0; i < pbp.size(); ++i) {
- bwImage.getData()[i] = pbp[i] ? 1 : 0;
+ bwImage.getData()[i] = pbp[i] ? 255 : 0;
}
return bwImage;
}
+ inline image::Bl rangeToBl(const std::vector<unsigned int> &values, int width) {
+ int min = std::numeric_limits<int>::max();
+ int max = std::numeric_limits<int>::min();
+
+ for (int value : values) {
+ if (value < min) {
+ min = value;
+ }
+ if (value > max) {
+ max = value;
+ }
+ }
+
+ std::cout << "rangeToBl: Got min == " << min << " and max == " << max << std::endl;
+
+ max -= min;
+
+ image::Bl grImage(width, values.size() / width);
+ assert((unsigned long)grImage.getSize() >= values.size()
+ && "New image::Bl size too small (values' size is not a multiple of width)");
+
+ for(unsigned int i = 0; i < values.size(); ++i) {
+ grImage.getData()[i] = ((float)((int)(values[i]) - min) / (float)max) * 255.0F;
+ }
+
+ return grImage;
+ }
+
inline std::pair<int, int> filter_minmax_in_range(int start, int width,
int height,
int range,
#include <cstdio>
#include <random>
+#include <iostream>
+
+#include <png.h>
image::Bl::Bl() :
data(),
case file_type::PBM:
case file_type::PGM:
case file_type::PPM:
+ case file_type::PNG:
return true;
default:
return false;
fclose(file);
}
+ if(type == file_type::PNG) {
+ FILE *outfile = fopen(filename, "wb");
+ if (outfile == nullptr) {
+ return false;
+ }
+ const static auto pngErrorLFn = [] (png_structp /* unused */,
+ png_const_charp message) {
+ std::cerr << "WARNING [libpng]: " << message << std::endl;
+ };
+ const static auto pngWarnLFn = [] (png_structp /* unused */,
+ png_const_charp message) {
+ std::cerr << "ERROR [libpng]: " << message << std::endl;
+ };
+
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
+ nullptr,
+ pngErrorLFn,
+ pngWarnLFn);
+
+ if (png_ptr == nullptr) {
+ fclose(outfile);
+ return false;
+ }
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == nullptr) {
+ png_destroy_write_struct(&png_ptr, nullptr);
+ fclose(outfile);
+ return false;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ fclose(outfile);
+ return false;
+ }
+
+ png_init_io(png_ptr, outfile);
+
+ png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_GRAY,
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ png_write_info(png_ptr, info_ptr);
+
+ //png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
+
+ for (unsigned int j = 0; j < this->data.size() / this->width; ++j) {
+ unsigned char *dataPtr = &this->data.at(j * this->width);
+ png_write_rows(png_ptr, &dataPtr, 1);
+ }
+
+ png_write_end(png_ptr, nullptr);
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ fclose(outfile);
+ return true;
+ }
+
switch(type) {
case file_type::PBM:
file = fopen(filename, "w");
PBM,
PGM,
PPM,
- // TODO PNG support
+ PNG,
};
class Base {
int main(int argc, char **argv) {
//#ifndef NDEBUG
-// std::cout << "Trying blue_noise..." << std::endl;
-// image::Bl bl = dither::blue_noise(100, 100, 8, true);
-// bl.writeToFile(image::file_type::PBM, true, "blueNoiseOut.pbm");
+ std::cout << "Trying blue_noise..." << std::endl;
+ image::Bl bl = dither::blue_noise(32, 32, 15, true);
+ bl.writeToFile(image::file_type::PNG, true, "blueNoiseOut.png");
//#endif
- image::Bl bl = dither::blue_noise_grayscale(64, 64);
- bl.writeToFile(image::file_type::PGM, true, "blueNoiseGrayscaleOut.pgm");
-
return 0;
}