Some attempts at fixing grayscale blue-noise
This commit is contained in:
parent
a8f0ec788a
commit
73aa21e92a
3 changed files with 221 additions and 33 deletions
|
@ -113,7 +113,21 @@ image::Bl dither::blue_noise_grayscale(int width, int height, int threads) {
|
||||||
int filter_size = (width + height) / 2;
|
int filter_size = (width + height) / 2;
|
||||||
std::vector<float> precomputed(internal::precompute_gaussian(filter_size));
|
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 min, max, min2, max2;
|
||||||
|
int prevmax = -1;
|
||||||
|
int prevmax2 = -1;
|
||||||
float tempPixel;
|
float tempPixel;
|
||||||
while(true) {
|
while(true) {
|
||||||
printf("Iteration %d\n", iterations);
|
printf("Iteration %d\n", iterations);
|
||||||
|
@ -121,9 +135,14 @@ image::Bl dither::blue_noise_grayscale(int width, int height, int threads) {
|
||||||
internal::compute_filter_grayscale(image,
|
internal::compute_filter_grayscale(image,
|
||||||
width, height, count,
|
width, height, count,
|
||||||
filter_size, filter_out,
|
filter_size, filter_out,
|
||||||
&precomputed, threads);
|
&precomputed, 0);
|
||||||
|
|
||||||
std::tie(min, max) = internal::filter_minmax(filter_out);
|
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);
|
printf("min == %4d, max == %4d", min, max);
|
||||||
tempPixel = image[max];
|
tempPixel = image[max];
|
||||||
image[max] = 0.0F;
|
image[max] = 0.0F;
|
||||||
|
@ -131,18 +150,33 @@ image::Bl dither::blue_noise_grayscale(int width, int height, int threads) {
|
||||||
internal::compute_filter_grayscale(image,
|
internal::compute_filter_grayscale(image,
|
||||||
width, height, count,
|
width, height, count,
|
||||||
filter_size, filter_out,
|
filter_size, filter_out,
|
||||||
&precomputed, threads);
|
&precomputed, 0);
|
||||||
|
|
||||||
std::tie(min2, max2) = internal::filter_minmax(filter_out);
|
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);
|
printf(", min2 == %4d, max2 == %4d\n", min2, max2);
|
||||||
|
|
||||||
if(utility::dist(min, min2, width) < 1.5F) {
|
if(min2 != min) {
|
||||||
image[max] = image[min2];
|
|
||||||
image[min2] = tempPixel;
|
|
||||||
} else {
|
|
||||||
image[max] = tempPixel;
|
image[max] = tempPixel;
|
||||||
break;
|
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
|
//#ifndef NDEBUG
|
||||||
if(iterations % 20 == 0) {
|
if(iterations % 20 == 0) {
|
||||||
|
@ -156,13 +190,62 @@ image::Bl dither::blue_noise_grayscale(int width, int height, int threads) {
|
||||||
name.append(std::to_string(iterations));
|
name.append(std::to_string(iterations));
|
||||||
name.append(".pgm");
|
name.append(".pgm");
|
||||||
image::Bl(image, width).writeToFile(image::file_type::PGM, true, name);
|
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
|
//#endif
|
||||||
++iterations;
|
++iterations;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
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);
|
return image::Bl(image, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <sys/sysinfo.h>
|
||||||
|
|
||||||
#include <CL/opencl.h>
|
#include <CL/opencl.h>
|
||||||
|
|
||||||
|
@ -64,7 +67,7 @@ namespace internal {
|
||||||
|
|
||||||
for(unsigned int i = 0; i < size; ++i) {
|
for(unsigned int i = 0; i < size; ++i) {
|
||||||
graynoise.push_back(static_cast<float>(i) / static_cast<float>(size - 1));
|
graynoise.push_back(static_cast<float>(i) / static_cast<float>(size - 1));
|
||||||
//graynoise[i] = dist(re);
|
//graynoise.push_back(dist(re));
|
||||||
}
|
}
|
||||||
for(unsigned int i = 0; i < size - 1; ++i) {
|
for(unsigned int i = 0; i < size - 1; ++i) {
|
||||||
std::uniform_int_distribution<unsigned int> range(i + 1, size - 1);
|
std::uniform_int_distribution<unsigned int> range(i + 1, size - 1);
|
||||||
|
@ -77,10 +80,12 @@ namespace internal {
|
||||||
return graynoise;
|
return graynoise;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr float mu_squared = 1.5f * 1.5f;
|
constexpr float mu = 1.5F;
|
||||||
|
constexpr float mu_squared = 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)/(2*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) {
|
||||||
|
@ -90,7 +95,8 @@ namespace internal {
|
||||||
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(gaussian(
|
||||||
(float)xy.first - size / 2.0f, (float)xy.second - size / 2.0f));
|
(float)xy.first - (float)size / 2.0f,
|
||||||
|
(float)xy.second - (float)size / 2.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
return precomputed;
|
return precomputed;
|
||||||
|
@ -166,11 +172,14 @@ namespace internal {
|
||||||
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) % 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;
|
||||||
sum += image[utility::twoToOne(p_prime, q_prime, width, height)]
|
sum += image[utility::twoToOne(p_prime, q_prime, width, height)]
|
||||||
* precomputed[utility::twoToOne(p, q, filter_size, filter_size)];
|
* precomputed[utility::twoToOne(p,
|
||||||
|
q,
|
||||||
|
filter_size,
|
||||||
|
filter_size)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +286,7 @@ namespace internal {
|
||||||
int count, int filter_size, std::vector<float> &filter_out,
|
int count, int filter_size, 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(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) {
|
||||||
|
@ -300,11 +310,62 @@ namespace internal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if(threads == 0) {
|
||||||
|
threads = get_nprocs();
|
||||||
|
if(threads == 0) {
|
||||||
|
throw std::runtime_error("0 threads detected, "
|
||||||
|
"should be impossible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::pair<int, int> filter_minmax(const std::vector<float>& filter) {
|
inline std::pair<int, int> filter_minmax(const std::vector<float>& filter) {
|
||||||
float min = std::numeric_limits<float>::infinity();
|
float min = std::numeric_limits<float>::infinity();
|
||||||
float max = 0.0f;
|
float max = -std::numeric_limits<float>::infinity();
|
||||||
int min_index = 0;
|
int min_index = 0;
|
||||||
int max_index = 0;
|
int max_index = 0;
|
||||||
|
|
||||||
|
@ -416,6 +477,44 @@ namespace internal {
|
||||||
|
|
||||||
return bwImage;
|
return bwImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::pair<int, int> filter_minmax_in_range(int start, int width,
|
||||||
|
int height,
|
||||||
|
int range,
|
||||||
|
const std::vector<float> &vec) {
|
||||||
|
float max = -std::numeric_limits<float>::infinity();
|
||||||
|
float min = std::numeric_limits<float>::infinity();
|
||||||
|
|
||||||
|
int maxIdx = -1;
|
||||||
|
int minIdx = -1;
|
||||||
|
|
||||||
|
auto startXY = utility::oneToTwo(start, width);
|
||||||
|
for(int y = startXY.second - range / 2; y <= startXY.second + range / 2; ++y) {
|
||||||
|
for(int x = startXY.first - range / 2; x <= startXY.first + range / 2; ++x) {
|
||||||
|
int idx = utility::twoToOne(x, y, width, height);
|
||||||
|
if(idx == start) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vec[idx] < min) {
|
||||||
|
min = vec[idx];
|
||||||
|
minIdx = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(vec[idx] > max) {
|
||||||
|
max = vec[idx];
|
||||||
|
maxIdx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(minIdx < 0) {
|
||||||
|
throw std::runtime_error("Invalid minIdx value");
|
||||||
|
} else if(maxIdx < 0) {
|
||||||
|
throw std::runtime_error("Invalid maxIdx value");
|
||||||
|
}
|
||||||
|
return {minIdx, maxIdx};
|
||||||
|
}
|
||||||
} // namespace dither::internal
|
} // namespace dither::internal
|
||||||
|
|
||||||
} // namespace dither
|
} // namespace dither
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
|
|
||||||
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) {
|
||||||
|
x += width;
|
||||||
|
}
|
||||||
|
while(y < 0) {
|
||||||
|
y += height;
|
||||||
|
}
|
||||||
x = x % width;
|
x = x % width;
|
||||||
y = y % height;
|
y = y % height;
|
||||||
return x + y * width;
|
return x + y * width;
|
||||||
|
|
Loading…
Reference in a new issue