]> git.seodisparate.com - EN605.617.81.FA21_StephenSeo_DitheringProject/commitdiff
Impl dithering on red/green/blue channels
authorStephen Seo <seo.disparate@gmail.com>
Sat, 27 Nov 2021 04:13:27 +0000 (13:13 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Sat, 27 Nov 2021 04:13:27 +0000 (13:13 +0900)
Also add res/blue_noise_64x64.png for use in dithering.

res/blue_noise_64x64.png [new file with mode: 0644]
src/image.cc
src/image.h
src/main.cc

diff --git a/res/blue_noise_64x64.png b/res/blue_noise_64x64.png
new file mode 100644 (file)
index 0000000..3c1bdb5
Binary files /dev/null and b/res/blue_noise_64x64.png differ
index 9b1c0103556b891d891e1628aadaaa852a3ef156..73ae9feefdfbe6ca31101b0a70530d5867669077 100644 (file)
@@ -10,6 +10,7 @@
 #include <png.h>
 
 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> 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> Image::ToGrayscaleDitheredWithBlueNoise(
   return grayscale_image;
 }
 
+std::unique_ptr<Image> 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<unsigned int, 3> 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<Image> result_image =
+      std::unique_ptr<Image>(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();
index d7051be6e24e2e061b4eeaa1e6fcd48ea3bd4dae..3e3ef4ba8e5aaec89d0820bd59987e68676c4fc4 100644 (file)
@@ -105,14 +105,29 @@ class Image {
    */
   std::unique_ptr<Image> 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<Image> 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<uint8_t> data_;
index bbdddd0f7b3a02c26652fba3e41f0fa377c456a0..3172e3285f2c48779434e01fa22967873203ae8c 100644 (file)
@@ -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;