diff --git a/res/blue_noise_64x64.png b/res/blue_noise_64x64.png new file mode 100644 index 0000000..3c1bdb5 Binary files /dev/null and b/res/blue_noise_64x64.png differ diff --git a/src/image.cc b/src/image.cc index 9b1c010..73ae9fe 100644 --- a/src/image.cc +++ b/src/image.cc @@ -10,6 +10,7 @@ #include const char *Image::opencl_grayscale_kernel_ = nullptr; +const char *Image::opencl_color_kernel_ = nullptr; Image::Image() : data_(), width_(0), height_(0), is_grayscale_(true) {} @@ -750,7 +751,7 @@ std::unique_ptr Image::ToGrayscaleDitheredWithBlueNoise( auto blue_noise_buffer_id = opencl_handle->CreateKernelBuffer( kid, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, blue_noise->data_.size(), - (void *)blue_noise->data_.data()); + blue_noise->data_.data()); if (blue_noise_buffer_id == 0) { std::cout << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set " "blue-noise buffer" @@ -874,6 +875,184 @@ std::unique_ptr Image::ToGrayscaleDitheredWithBlueNoise( return grayscale_image; } +std::unique_ptr Image::ToColorDitheredWithBlueNoise(Image *blue_noise) { + if (!blue_noise->IsGrayscale()) { + std::cout + << "ERROR ToColorDitheredWithBlueNoise: blue_noise is not grayscale" + << std::endl; + return {}; + } + + auto opencl_handle = GetOpenCLHandle(); + if (!opencl_handle) { + std::cout + << "ERROR ToColorDitheredWithBlueNoise: Failed to get OpenCLHandle" + << std::endl; + return {}; + } + + // set up kernel and buffers + auto kid = opencl_handle->CreateKernelFromSource(GetColorDitheringKernel(), + "ColorDither"); + if (kid == 0) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to create " + "OpenCL Kernel" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + + auto input_buffer_id = opencl_handle->CreateKernelBuffer( + kid, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, this->data_.size(), + this->data_.data()); + if (input_buffer_id == 0) { + std::cout + << "ERROR ToColorDitheredWithBlueNoise: Failed to set input buffer" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + + auto output_buffer_id = opencl_handle->CreateKernelBuffer( + kid, CL_MEM_WRITE_ONLY, this->data_.size(), nullptr); + if (output_buffer_id == 0) { + std::cout + << "ERROR ToColorDitheredWithBlueNoise: Failed to set output buffer" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + + auto blue_noise_buffer_id = opencl_handle->CreateKernelBuffer( + kid, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, blue_noise->data_.size(), + blue_noise->data_.data()); + if (blue_noise_buffer_id == 0) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set " + "blue-noise buffer" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + + std::srand(std::time(nullptr)); + std::array blue_noise_offsets = { + std::rand() % blue_noise->GetSize(), std::rand() % blue_noise->GetSize(), + std::rand() % blue_noise->GetSize()}; + + while (blue_noise_offsets[0] == blue_noise_offsets[1] || + blue_noise_offsets[1] == blue_noise_offsets[2] || + blue_noise_offsets[0] == blue_noise_offsets[2]) { + blue_noise_offsets = {std::rand() % blue_noise->GetSize(), + std::rand() % blue_noise->GetSize(), + std::rand() % blue_noise->GetSize()}; + } + + auto blue_noise_offsets_buffer_id = opencl_handle->CreateKernelBuffer( + kid, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, sizeof(unsigned int) * 3, + blue_noise_offsets.data()); + + // assign buffers/data to kernel parameters + if (!opencl_handle->AssignKernelBuffer(kid, 0, input_buffer_id)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 0" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + if (!opencl_handle->AssignKernelBuffer(kid, 1, blue_noise_buffer_id)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 1" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + if (!opencl_handle->AssignKernelBuffer(kid, 2, output_buffer_id)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 2" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + unsigned int input_width = this->GetWidth(); + if (!opencl_handle->AssignKernelArgument(kid, 3, sizeof(unsigned int), + &input_width)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 3" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + unsigned int input_height = this->GetHeight(); + if (!opencl_handle->AssignKernelArgument(kid, 4, sizeof(unsigned int), + &input_height)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 4" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + unsigned int blue_noise_width = blue_noise->GetWidth(); + if (!opencl_handle->AssignKernelArgument(kid, 5, sizeof(unsigned int), + &blue_noise_width)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 5" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + unsigned int blue_noise_height = blue_noise->GetHeight(); + if (!opencl_handle->AssignKernelArgument(kid, 6, sizeof(unsigned int), + &blue_noise_height)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 6" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + if (!opencl_handle->AssignKernelBuffer(kid, 7, + blue_noise_offsets_buffer_id)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 7" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + + auto work_group_size = opencl_handle->GetWorkGroupSize(kid); + std::cout << "Got work_group_size == " << work_group_size << std::endl; + + std::size_t work_group_size_0 = std::sqrt(work_group_size); + std::size_t work_group_size_1 = work_group_size_0; + + while (work_group_size_0 > 1 && input_width % work_group_size_0 != 0) { + --work_group_size_0; + } + while (work_group_size_1 > 1 && input_height % work_group_size_1 != 0) { + --work_group_size_1; + } + + std::cout << "Using WIDTHxHEIGHT: " << input_width << "x" << input_height + << " with work_group_sizes: " << work_group_size_0 << "x" + << work_group_size_1 << std::endl; + + if (!opencl_handle->ExecuteKernel2D(kid, input_width, input_height, + work_group_size_0, work_group_size_1, + true)) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to execute Kernel" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + + std::unique_ptr result_image = + std::unique_ptr(new Image(*this)); + + if (!opencl_handle->GetBufferData(kid, output_buffer_id, + result_image->GetSize(), + result_image->data_.data())) { + std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to get output " + "buffer data" + << std::endl; + opencl_handle->CleanupAllKernels(); + return {}; + } + + opencl_handle->CleanupAllKernels(); + return result_image; +} + const char *Image::GetGrayscaleDitheringKernel() { if (opencl_grayscale_kernel_ == nullptr) { opencl_grayscale_kernel_ = @@ -910,6 +1089,55 @@ const char *Image::GetGrayscaleDitheringKernel() { return opencl_grayscale_kernel_; } +const char *Image::GetColorDitheringKernel() { + if (opencl_color_kernel_ == nullptr) { + opencl_color_kernel_ = + "unsigned int BN_INDEX(\n" + "unsigned int x,\n" + "unsigned int y,\n" + "unsigned int o,\n" + "unsigned int bn_width,\n" + "unsigned int bn_height) {\n" + "unsigned int offset_x = (o % bn_width + x) % bn_width;\n" + "unsigned int offset_y = (o / bn_width + y) % bn_height;\n" + "return offset_x + offset_y * bn_width;\n" + "}\n" + "\n" + "__kernel void ColorDither(\n" + "__global const unsigned char *input,\n" + "__global const unsigned char *blue_noise,\n" + "__global unsigned char *output,\n" + "const unsigned int input_width,\n" + "const unsigned int input_height,\n" + "const unsigned int blue_noise_width,\n" + "const unsigned int blue_noise_height,\n" + "__global const unsigned int *blue_noise_offsets) {\n" + "unsigned int idx = get_global_id(0);\n" + "unsigned int idy = get_global_id(1);\n" + " unsigned int b_i[3] = {\n" + " BN_INDEX(idx, idy, blue_noise_offsets[0], blue_noise_width,\n" + " blue_noise_height),\n" + " BN_INDEX(idx, idy, blue_noise_offsets[1], blue_noise_width,\n" + " blue_noise_height),\n" + " BN_INDEX(idx, idy, blue_noise_offsets[2], blue_noise_width,\n" + " blue_noise_height)\n" + " };\n" + // input is 4 bytes per pixel, alpha channel is merely copied + " for (unsigned int i = 0; i < 4; ++i) {\n" + " unsigned int input_index = idx * 4 + idy * input_width * 4 + i;\n" + " if (i < 3) {\n" + " output[input_index] = input[input_index] > blue_noise[b_i[i]] ? " + " 255 : 0;\n" + " } else {\n" + " output[input_index] = input[input_index];\n" + " }\n" + " }\n" + "}\n"; + } + + return opencl_color_kernel_; +} + OpenCLHandle::Ptr Image::GetOpenCLHandle() { if (!opencl_handle_) { opencl_handle_ = OpenCLContext::GetHandle(); diff --git a/src/image.h b/src/image.h index d7051be..3e3ef4b 100644 --- a/src/image.h +++ b/src/image.h @@ -105,14 +105,29 @@ class Image { */ std::unique_ptr ToGrayscaleDitheredWithBlueNoise(Image *blue_noise); - /// Returns the Dithering Kernel function as a C string + /*! + * \brief Returns a colored dithered version of the current Image. + * + * Unlike the grayscaled version, this dithers the red, green, and blue + * channels. There may be mixed pixels as a result, such as yellow, cyan, + * magenta, or white. + * + * \return A std::unique_ptr holding an Image on success, empty otherwise. + */ + std::unique_ptr ToColorDitheredWithBlueNoise(Image *blue_noise); + + /// Returns the grayscale Dithering Kernel function as a C string static const char *GetGrayscaleDitheringKernel(); + /// Returns the color Dithering Kernel function as a C string + static const char *GetColorDitheringKernel(); + /// Returns the OpenCLHandle::Ptr instance OpenCLHandle::Ptr GetOpenCLHandle(); private: static const char *opencl_grayscale_kernel_; + static const char *opencl_color_kernel_; OpenCLHandle::Ptr opencl_handle_; /// Internally holds rgba std::vector data_; diff --git a/src/main.cc b/src/main.cc index bbdddd0..3172e32 100644 --- a/src/main.cc +++ b/src/main.cc @@ -18,7 +18,8 @@ int main(int argc, char **argv) { return 1; } - auto output = input.ToGrayscaleDitheredWithBlueNoise(&bluenoise); + // auto output = input.ToGrayscaleDitheredWithBlueNoise(&bluenoise); + auto output = input.ToColorDitheredWithBlueNoise(&bluenoise); if (!output || !output->IsValid()) { std::cout << "ERROR: output Image is invalid" << std::endl; return 1;