Write more efficient dithered pngs using palettes

This commit is contained in:
Stephen Seo 2021-11-27 16:00:31 +09:00
parent 6f0a0219b3
commit 7705170f9a
2 changed files with 134 additions and 15 deletions

View file

@ -1,23 +1,49 @@
#include "image.h" #include "image.h"
#include <array> #include <array>
#include <cassert>
#include <cmath> #include <cmath>
#include <cstdio> #include <cstdio>
#include <ctime> #include <ctime>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <png.h>
const char *Image::opencl_grayscale_kernel_ = nullptr; const char *Image::opencl_grayscale_kernel_ = nullptr;
const char *Image::opencl_color_kernel_ = nullptr; const char *Image::opencl_color_kernel_ = nullptr;
Image::Image() : data_(), width_(0), height_(0), is_grayscale_(true) {} const std::array<png_color, 2> Image::dither_bw_palette_ = {
png_color{0, 0, 0}, // black
png_color{255, 255, 255} // white
};
const std::array<png_color, 8> Image::dither_color_palette_ = {
png_color{0, 0, 0}, // black
png_color{255, 255, 255}, // white
png_color{255, 0, 0}, // red
png_color{0, 255, 0}, // green
png_color{0, 0, 255}, // blue
png_color{255, 255, 0}, // yellow
png_color{255, 0, 255}, // magenta
png_color{0, 255, 255}, // cyan
};
Image::Image()
: data_(),
width_(0),
height_(0),
is_grayscale_(true),
is_dithered_grayscale_(false),
is_dithered_color_(false) {}
Image::Image(const char *filename) : Image(std::string(filename)) {} Image::Image(const char *filename) : Image(std::string(filename)) {}
Image::Image(const std::string &filename) Image::Image(const std::string &filename)
: data_(), width_(0), height_(0), is_grayscale_(true) { : data_(),
width_(0),
height_(0),
is_grayscale_(true),
is_dithered_grayscale_(false),
is_dithered_color_(false) {
if (filename.compare(filename.size() - 4, filename.size(), ".png") == 0) { if (filename.compare(filename.size() - 4, filename.size(), ".png") == 0) {
// filename expected to be .png // filename expected to be .png
std::cout << "INFO: PNG filename extension detected, decoding..." std::cout << "INFO: PNG filename extension detected, decoding..."
@ -115,19 +141,103 @@ bool Image::SaveAsPNG(const std::string &filename, bool overwrite) {
// set image information // set image information
if (is_grayscale_) { if (is_grayscale_) {
png_set_IHDR(png_ptr, png_info_ptr, width_, height_, 8, PNG_COLOR_TYPE_GRAY, if (is_dithered_grayscale_) {
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, png_set_IHDR(png_ptr, png_info_ptr, width_, height_, 1,
PNG_FILTER_TYPE_DEFAULT); PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_PLTE(png_ptr, png_info_ptr, dither_bw_palette_.data(),
dither_bw_palette_.size());
} else {
png_set_IHDR(png_ptr, png_info_ptr, width_, height_, 8,
PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
}
} else {
if (is_dithered_color_) {
png_set_IHDR(png_ptr, png_info_ptr, width_, height_, 4,
PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_PLTE(png_ptr, png_info_ptr, dither_color_palette_.data(),
dither_color_palette_.size());
} else { } else {
png_set_IHDR(png_ptr, png_info_ptr, width_, height_, 8, png_set_IHDR(png_ptr, png_info_ptr, width_, height_, 8,
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
} }
}
// write png info // write png info
png_write_info(png_ptr, png_info_ptr); png_write_info(png_ptr, png_info_ptr);
// write rows of image data // write rows of image data
if (is_dithered_grayscale_) {
// using 1-bit palette in 1-bit color depth
std::vector<unsigned char> row;
unsigned char temp;
unsigned int bidx;
for (unsigned int y = 0; y < height_; ++y) {
row.clear();
temp = 0;
bidx = 0;
for (unsigned int x = 0; x < width_; ++x) {
if (data_.at(x + y * width_) != 0) {
temp |= 0x80 >> bidx;
}
++bidx;
if (bidx >= 8) {
row.push_back(temp);
temp = 0;
bidx = 0;
}
}
row.push_back(temp);
png_write_row(png_ptr, row.data());
}
} else if (is_dithered_color_) {
// using 3-bit palette in 4-bit color depth
std::vector<unsigned char> row;
unsigned char temp;
unsigned int bidx;
unsigned char red, green, blue;
for (unsigned int y = 0; y < height_; ++y) {
row.clear();
temp = 0;
bidx = 0;
for (unsigned int x = 0; x < width_; ++x) {
red = data_.at(x * 4 + y * 4 * width_);
green = data_.at(x * 4 + y * 4 * width_ + 1);
blue = data_.at(x * 4 + y * 4 * width_ + 2);
if (red == 0 && green == 0 && blue == 0) {
// noop
} else if (red != 0 && green != 0 && blue != 0) {
temp |= 0x10 >> (bidx * 4);
} else if (red != 0 && green == 0 && blue == 0) {
temp |= 0x20 >> (bidx * 4);
} else if (red == 0 && green != 0 && blue == 0) {
temp |= 0x30 >> (bidx * 4);
} else if (red == 0 && green == 0 && blue != 0) {
temp |= 0x40 >> (bidx * 4);
} else if (red != 0 && green != 0 && blue == 0) {
temp |= 0x50 >> (bidx * 4);
} else if (red != 0 && green == 0 && blue != 0) {
temp |= 0x60 >> (bidx * 4);
} else if (red == 0 && green != 0 && blue != 0) {
temp |= 0x70 >> (bidx * 4);
} else {
assert(!"Unreachable");
}
++bidx;
if (bidx >= 2) {
bidx = 0;
row.push_back(temp);
temp = 0;
}
}
row.push_back(temp);
png_write_row(png_ptr, row.data());
}
} else {
// using full 8-bit per-channel colors
for (unsigned int y = 0; y < height_; ++y) { for (unsigned int y = 0; y < height_; ++y) {
if (is_grayscale_) { if (is_grayscale_) {
png_write_row(png_ptr, &data_.at(y * width_)); png_write_row(png_ptr, &data_.at(y * width_));
@ -135,6 +245,7 @@ bool Image::SaveAsPNG(const std::string &filename, bool overwrite) {
png_write_row(png_ptr, &data_.at(y * width_ * 4)); png_write_row(png_ptr, &data_.at(y * width_ * 4));
} }
} }
}
// finish writing image data // finish writing image data
png_write_end(png_ptr, png_info_ptr); png_write_end(png_ptr, png_info_ptr);
@ -258,6 +369,7 @@ std::unique_ptr<Image> Image::ToGrayscaleDitheredWithBlueNoise(
<< std::endl; << std::endl;
return {}; return {};
} }
grayscale_image->is_dithered_grayscale_ = true;
auto opencl_handle = GetOpenCLHandle(); auto opencl_handle = GetOpenCLHandle();
if (!opencl_handle) { if (!opencl_handle) {
std::cout std::cout
@ -594,6 +706,7 @@ std::unique_ptr<Image> Image::ToColorDitheredWithBlueNoise(Image *blue_noise) {
std::unique_ptr<Image> result_image = std::unique_ptr<Image> result_image =
std::unique_ptr<Image>(new Image(*this)); std::unique_ptr<Image>(new Image(*this));
result_image->is_dithered_color_ = true;
if (!opencl_handle->GetBufferData(kid, output_buffer_id, if (!opencl_handle->GetBufferData(kid, output_buffer_id,
result_image->GetSize(), result_image->GetSize(),

View file

@ -6,6 +6,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <png.h>
#include "opencl_handle.h" #include "opencl_handle.h"
class Image { class Image {
@ -128,12 +130,16 @@ class Image {
private: private:
static const char *opencl_grayscale_kernel_; static const char *opencl_grayscale_kernel_;
static const char *opencl_color_kernel_; static const char *opencl_color_kernel_;
static const std::array<png_color, 2> dither_bw_palette_;
static const std::array<png_color, 8> dither_color_palette_;
OpenCLHandle::Ptr opencl_handle_; OpenCLHandle::Ptr opencl_handle_;
/// Internally holds rgba /// Internally holds rgba
std::vector<uint8_t> data_; std::vector<uint8_t> data_;
unsigned int width_; unsigned int width_;
unsigned int height_; unsigned int height_;
bool is_grayscale_; bool is_grayscale_;
bool is_dithered_grayscale_;
bool is_dithered_color_;
void DecodePNG(const std::string &filename); void DecodePNG(const std::string &filename);
void DecodePGM(const std::string &filename); void DecodePGM(const std::string &filename);