return SaveAsPPM(std::string(filename), overwrite, packed);
}
-void Image::DecodePNG(const std::string &filename) {
- FILE *file = std::fopen(filename.c_str(), "rb");
-
- // Check header of file to check if it is actually a png file.
- {
- std::array<unsigned char, 8> buf;
- if (std::fread(buf.data(), 1, 8, file) != 8) {
- std::fclose(file);
- std::cout << "ERROR: File \"" << filename << "\" is smaller than 8 bytes"
- << std::endl;
- return;
- } else if (png_sig_cmp(reinterpret_cast<png_const_bytep>(buf.data()), 0,
- 8) != 0) {
- // not png file, do nothing
- std::fclose(file);
- std::cout << "ERROR: File \"" << filename << "\" is not a png file"
- << std::endl;
- return;
- }
- }
-
- // seek to head of file
- std::rewind(file);
+uint8_t Image::ColorToGray(uint8_t red, uint8_t green, uint8_t blue) {
+ // values taken from Wikipedia article about conversion of color to grayscale
+ double y_linear = 0.2126 * (red / 255.0) + 0.7152 * (green / 255.0) +
+ 0.0722 * (blue / 255.0);
- // init required structs for png decoding
- png_structp png_ptr =
- png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
- if (!png_ptr) {
- std::cout << "ERROR: Failed to initialize libpng (png_ptr) for decoding "
- "PNG file \""
- << filename << '"' << std::endl;
- std::fclose(file);
- return;
+ if (y_linear <= 0.0031308) {
+ return std::round((12.92 * y_linear) * 255.0);
+ } else {
+ return std::round((1.055 * std::pow(y_linear, 1 / 2.4) - 0.055) * 255.0);
}
+}
- png_infop png_info_ptr = png_create_info_struct(png_ptr);
- if (!png_info_ptr) {
- std::cout << "ERROR: Failed to initialize libpng (png_infop) for decoding "
- "PNG file \""
- << filename << '"' << std::endl;
- png_destroy_read_struct(&png_ptr, nullptr, nullptr);
- std::fclose(file);
- return;
+std::unique_ptr<Image> Image::ToGrayscale() const {
+ if (IsGrayscale()) {
+ return std::unique_ptr<Image>(new Image(*this));
}
- png_infop png_end_info_ptr = png_create_info_struct(png_ptr);
- if (!png_end_info_ptr) {
- std::cout << "ERROR: Failed to initialize libpng (end png_infop) for "
- "decoding PNG file \""
- << filename << '"' << std::endl;
- png_destroy_read_struct(&png_ptr, &png_info_ptr, nullptr);
- std::fclose(file);
- return;
- }
+ std::unique_ptr<Image> grayscale_image = std::unique_ptr<Image>(new Image{});
+ grayscale_image->width_ = this->width_;
+ grayscale_image->height_ = this->height_;
+ grayscale_image->data_.resize(width_ * height_);
- // required to handle libpng errors
- if (setjmp(png_jmpbuf(png_ptr))) {
- png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr);
- std::fclose(file);
- return;
+ for (unsigned int i = 0; i < width_ * height_; ++i) {
+ grayscale_image->data_.at(i) =
+ ColorToGray(this->data_.at(i * 4), this->data_.at(i * 4 + 1),
+ this->data_.at(i * 4 + 2));
}
- // pass the FILE pointer to libpng
- png_init_io(png_ptr, file);
-
- // have libpng process the png data
- png_read_png(png_ptr, png_info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
-
- // get image width (in pixels)
- width_ = png_get_image_width(png_ptr, png_info_ptr);
-
- // get image height (in pixels)
- height_ = png_get_image_height(png_ptr, png_info_ptr);
-
- // get channel count of image
- unsigned int channels = png_get_channels(png_ptr, png_info_ptr);
- if (channels == 1) {
- is_grayscale_ = true;
- } else {
- is_grayscale_ = false;
- }
+ return grayscale_image;
+}
- if (height_ > PNG_UINT_32_MAX) {
- png_error(png_ptr, "Image is too tall to process in memory");
- } else if (width_ > PNG_UINT_32_MAX) {
- png_error(png_ptr, "Image is too wide to process in memory");
+std::unique_ptr<Image> Image::ToGrayscaleDitheredWithBlueNoise(
+ Image *blue_noise) {
+ if (!blue_noise->IsGrayscale()) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: blue_noise is not grayscale"
+ << std::endl;
+ return {};
}
- png_byte **row_pointers = png_get_rows(png_ptr, png_info_ptr);
-
- data_.clear();
- if (channels == 3 || channels == 4) {
- data_.reserve(width_ * 4 * height_);
- } else if (channels == 1) {
- data_.reserve(width_ * height_);
- } else {
- std::cout << "ERROR: PNG has invalid channel count == " << channels
+ auto grayscale_image = ToGrayscale();
+ if (!grayscale_image) {
+ std::cout << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to get "
+ "grayscale Image"
<< std::endl;
- png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr);
- return;
+ return {};
}
- for (unsigned int y = 0; y < height_; ++y) {
- for (unsigned int x = 0; x < width_; ++x) {
- if (is_grayscale_) {
- data_.push_back(row_pointers[y][x]);
- } else if (channels == 3) {
- for (unsigned int c = 0; c < channels; ++c) {
- data_.push_back(row_pointers[y][x * channels + c]);
- }
- data_.push_back(255);
- } else /* if (channels == 4) */ {
- for (unsigned int c = 0; c < channels; ++c) {
- data_.push_back(row_pointers[y][x * channels + c]);
- }
- }
- }
+ auto opencl_handle = GetOpenCLHandle();
+ if (!opencl_handle) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to get OpenCLHandle"
+ << std::endl;
+ return {};
}
- // cleanup
- png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr);
- fclose(file);
+ // set up kernel and buffers
+ auto kid = opencl_handle->CreateKernelFromSource(
+ GetGrayscaleDitheringKernel(), "Dither");
+ if (kid == 0) {
+ std::cout << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to create "
+ "OpenCL Kernel"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
- // verify
- if (is_grayscale_) {
- if (data_.size() != width_ * height_) {
- std::cout << "WARNING: data_.size() doesn't match width_ * height_"
- << std::endl;
- }
- } else {
- if (data_.size() != 4 * width_ * height_) {
- std::cout << "WARNING: data_.size() doesn't match 4 * width_ * height_"
- << std::endl;
- }
+ auto input_buffer_id = opencl_handle->CreateKernelBuffer(
+ kid, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
+ grayscale_image->data_.size(), grayscale_image->data_.data());
+ if (input_buffer_id == 0) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set input buffer"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
}
-}
-void Image::DecodePGM(const std::string &filename) {
- is_grayscale_ = true;
+ auto output_buffer_id = opencl_handle->CreateKernelBuffer(
+ kid, CL_MEM_WRITE_ONLY, grayscale_image->data_.size(), nullptr);
+ if (output_buffer_id == 0) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set output buffer"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
- std::ifstream ifs(filename);
- if (!ifs.is_open()) {
- std::cout << "ERROR: Failed to open file \"" << filename << '"'
+ 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 ToGrayscaleDitheredWithBlueNoise: Failed to set "
+ "blue-noise buffer"
<< std::endl;
- return;
+ opencl_handle->CleanupAllKernels();
+ return {};
}
- std::string str_input;
- int int_input;
- ifs >> str_input;
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PGM first identifier) \""
- << filename << '"' << std::endl;
- return;
+ // assign buffers/data to kernel parameters
+ if (!opencl_handle->AssignKernelBuffer(kid, 0, input_buffer_id)) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 0"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
+ if (!opencl_handle->AssignKernelBuffer(kid, 1, blue_noise_buffer_id)) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 1"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
+ if (!opencl_handle->AssignKernelBuffer(kid, 2, output_buffer_id)) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 2"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
+ unsigned int width = grayscale_image->GetWidth();
+ if (!opencl_handle->AssignKernelArgument(kid, 3, sizeof(unsigned int),
+ &width)) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 3"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
+ unsigned int height = grayscale_image->GetHeight();
+ if (!opencl_handle->AssignKernelArgument(kid, 4, sizeof(unsigned int),
+ &height)) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: 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 ToGrayscaleDitheredWithBlueNoise: 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 ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 6"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
+ std::srand(std::time(nullptr));
+ unsigned int blue_noise_offset =
+ std::rand() % (blue_noise_width * blue_noise_height);
+ if (!opencl_handle->AssignKernelArgument(kid, 7, sizeof(unsigned int),
+ &blue_noise_offset)) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 7"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
}
- if (str_input.compare("P2") == 0) {
- // data stored in ascii format
-
- // get width
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PGM width) \"" << filename
- << '"' << std::endl;
- return;
- }
- width_ = int_input;
-
- // get height
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PGM height) \"" << filename
- << '"' << std::endl;
- return;
- }
- height_ = int_input;
-
- // get max_value
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PGM max) \"" << filename << '"'
- << std::endl;
- return;
- }
- float max_value = int_input;
-
- // parse data
- data_.clear();
- data_.reserve(width_ * height_);
- float value;
- for (unsigned int i = 0; i < width_ * height_; ++i) {
- ifs >> int_input;
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PGM data) \"" << filename
- << '"' << std::endl;
- return;
- }
- value = static_cast<float>(int_input) / max_value;
- data_.push_back(std::round(value * 255.0F));
- }
- } else if (str_input.compare("P5") == 0) {
- // data stored in raw format
-
- // get width
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PGM width) \"" << filename
- << '"' << std::endl;
- return;
- }
- width_ = int_input;
-
- // get height
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PGM height) \"" << filename
- << '"' << std::endl;
- return;
- }
- height_ = int_input;
+ // auto global_work_sizes = opencl_handle->GetGlobalWorkSize(kid);
+ auto work_group_size = opencl_handle->GetWorkGroupSize(kid);
+ std::cout << "Got work_group_size == " << work_group_size << std::endl;
- // get max_value
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PGM max) \"" << filename << '"'
- << std::endl;
- return;
- }
- int max_value_int = int_input;
- float max_value = int_input;
+ // auto max_work_group_size = opencl_handle->GetDeviceMaxWorkGroupSize();
+ // std::cout << "Got max_work_group_size == " << max_work_group_size
+ // << std::endl;
- // validate max_value
- if (max_value_int != 255 && max_value_int != 65535) {
- std::cout << "ERROR: Invalid max value for PGM (should be 255 or 65535) "
- "(filename \""
- << filename << "\")" << std::endl;
- return;
- }
+ std::size_t work_group_size_0 = std::sqrt(work_group_size);
+ std::size_t work_group_size_1 = work_group_size_0;
- // extract whitespace before data
- {
- int c = ifs.get();
- if (c != '\n' && c != ' ') {
- std::cout << "WARNING: File data after PGM max is not whitespace "
- "(filename \""
- << filename << "\")"
- << " value is " << c << std::endl;
- }
+ while (work_group_size_0 > 1 && width % work_group_size_0 != 0) {
+ --work_group_size_0;
+ }
+ while (work_group_size_1 > 1 && height % work_group_size_1 != 0) {
+ --work_group_size_1;
+ }
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PGM after whitespace) \""
- << filename << '"' << std::endl;
- return;
- }
- }
+ std::cout << "Using WIDTHxHEIGHT: " << width << "x" << height
+ << " with work_group_sizes: " << work_group_size_0 << "x"
+ << work_group_size_1 << std::endl;
- // parse raw data
- data_.clear();
- data_.reserve(width_ * height_);
- float value;
- for (unsigned int i = 0; i < width_ * height_; ++i) {
- if (max_value_int == 255) {
- value = ifs.get() / max_value;
- data_.push_back(std::round(value * 255.0F));
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PGM data) \"" << filename
- << '"' << std::endl;
- return;
- }
- } else /* if (max_value_int == 65535) */ {
- value = (ifs.get() & 0xFF) | ((ifs.get() << 8) & 0xFF00);
- value /= max_value;
- data_.push_back(std::round(value * 255.0F));
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PGM data 16-bit) \""
- << filename << '"' << std::endl;
- return;
- }
- }
- }
+ if (!opencl_handle->ExecuteKernel2D(kid, width, height, work_group_size_0,
+ work_group_size_1, true)) {
+ std::cout
+ << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to execute Kernel"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
+ }
- if (ifs.get() != decltype(ifs)::traits_type::eof()) {
- std::cout << "WARNING: Trailing data in PGM file \"" << filename << '"'
- << std::endl;
- }
- } else {
- std::cout << "ERROR: Invalid \"magic number\" in header of file \""
- << filename << '"' << std::endl;
+ if (!opencl_handle->GetBufferData(kid, output_buffer_id,
+ grayscale_image->GetSize(),
+ grayscale_image->data_.data())) {
+ std::cout << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to get output "
+ "buffer data"
+ << std::endl;
+ opencl_handle->CleanupAllKernels();
+ return {};
}
+
+ opencl_handle->CleanupAllKernels();
+ return grayscale_image;
}
-void Image::DecodePPM(const std::string &filename) {
- is_grayscale_ = false;
- std::ifstream ifs(filename);
- if (!ifs.is_open()) {
- std::cout << "ERROR: Failed to open file \"" << filename << '"'
- << std::endl;
- return;
+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 {};
}
- std::string str_input;
- int int_input;
- ifs >> str_input;
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PPM first identifier) \""
- << filename << '"' << std::endl;
- return;
+ if (this->IsGrayscale()) {
+ std::cout << "ERROR ToColorDitheredWithBlueNoise: current Image is not "
+ "non-grayscale"
+ << std::endl;
+ return {};
}
- if (str_input.compare("P3") == 0) {
- // data stored in ascii format
+ auto opencl_handle = GetOpenCLHandle();
+ if (!opencl_handle) {
+ std::cout
+ << "ERROR ToColorDitheredWithBlueNoise: Failed to get OpenCLHandle"
+ << std::endl;
+ return {};
+ }
- // get width
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PPM width) \"" << filename
- << '"' << std::endl;
- return;
- }
- width_ = int_input;
+ // 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 {};
+ }
- // get height
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PPM height) \"" << filename
- << '"' << std::endl;
- return;
- }
- height_ = int_input;
-
- // get max_value
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PPM max) \"" << filename << '"'
- << std::endl;
- return;
- }
- float max_value = int_input;
-
- // parse data
- data_.clear();
- data_.reserve(width_ * height_ * 4);
- float value;
- for (unsigned int i = 0; i < width_ * height_ * 3; ++i) {
- ifs >> int_input;
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PPM data) \"" << filename
- << '"' << std::endl;
- return;
- }
- value = static_cast<float>(int_input) / max_value;
- data_.push_back(std::round(value * 255.0F));
- if (i % 3 == 2) {
- // PPM is RGB but Image stores as RGBA
- data_.push_back(255);
- }
- }
- } else if (str_input.compare("P6") == 0) {
- // data stored in raw format
-
- // get width
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PPM width) \"" << filename
- << '"' << std::endl;
- return;
- }
- width_ = int_input;
-
- // get height
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PPM height) \"" << filename
- << '"' << std::endl;
- return;
- }
- height_ = int_input;
-
- // get max_value
- ifs >> int_input;
- if (!ifs.good() || int_input <= 0) {
- std::cout << "ERROR: Failed to parse file (PPM max) \"" << filename << '"'
- << std::endl;
- return;
- }
- int max_value_int = int_input;
- float max_value = int_input;
-
- // validate max_value
- if (max_value_int != 255 && max_value_int != 65535) {
- std::cout << "ERROR: Invalid max value for PPM (should be 255 or 65535) "
- "(filename \""
- << filename << "\")" << std::endl;
- return;
- }
-
- // extract whitespace before data
- {
- int c = ifs.get();
- if (c != '\n' && c != ' ') {
- std::cout
- << "WARNING: File data after PPM max is not whitespace (filename \""
- << filename << "\") value is " << c << std::endl;
- }
-
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PPM after whitespace) \""
- << filename << '"' << std::endl;
- return;
- }
- }
-
- // parse raw data
- data_.clear();
- data_.reserve(width_ * height_ * 4);
- float value;
- for (unsigned int i = 0; i < width_ * height_ * 3; ++i) {
- if (max_value_int == 255) {
- value = ifs.get() / max_value;
- data_.push_back(std::round(value * 255.0F));
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PPM data) \"" << filename
- << '"' << std::endl;
- return;
- }
- } else /* if (max_value_int == 65535) */ {
- value = (ifs.get() & 0xFF) | ((ifs.get() << 8) & 0xFF00);
- value /= max_value;
- data_.push_back(std::round(value * 255.0F));
- if (!ifs.good()) {
- std::cout << "ERROR: Failed to parse file (PPM data 16-bit) \""
- << filename << '"' << std::endl;
- return;
- }
- }
-
- if (i % 3 == 2) {
- // PPM is RGB but Image stores as RGBA
- data_.push_back(255);
- }
- }
-
- if (ifs.get() != decltype(ifs)::traits_type::eof()) {
- std::cout << "WARNING: Trailing data in PPM file \"" << filename << '"'
- << std::endl;
- }
- } else {
- std::cout << "ERROR: Invalid \"magic number\" in header of file \""
- << filename << '"' << std::endl;
- }
-}
-
-uint8_t Image::ColorToGray(uint8_t red, uint8_t green, uint8_t blue) {
- // values taken from Wikipedia article about conversion of color to grayscale
- double y_linear = 0.2126 * (red / 255.0) + 0.7152 * (green / 255.0) +
- 0.0722 * (blue / 255.0);
-
- if (y_linear <= 0.0031308) {
- return std::round((12.92 * y_linear) * 255.0);
- } else {
- return std::round((1.055 * std::pow(y_linear, 1 / 2.4) - 0.055) * 255.0);
- }
-}
-
-std::unique_ptr<Image> Image::ToGrayscale() const {
- if (IsGrayscale()) {
- return std::unique_ptr<Image>(new Image(*this));
- }
-
- std::unique_ptr<Image> grayscale_image = std::unique_ptr<Image>(new Image{});
- grayscale_image->width_ = this->width_;
- grayscale_image->height_ = this->height_;
- grayscale_image->data_.resize(width_ * height_);
-
- for (unsigned int i = 0; i < width_ * height_; ++i) {
- grayscale_image->data_.at(i) =
- ColorToGray(this->data_.at(i * 4), this->data_.at(i * 4 + 1),
- this->data_.at(i * 4 + 2));
- }
-
- return grayscale_image;
-}
-
-std::unique_ptr<Image> Image::ToGrayscaleDitheredWithBlueNoise(
- Image *blue_noise) {
- if (!blue_noise->IsGrayscale()) {
- std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: blue_noise is not grayscale"
- << std::endl;
- return {};
- }
-
- auto grayscale_image = ToGrayscale();
- if (!grayscale_image) {
- std::cout << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to get "
- "grayscale Image"
- << std::endl;
- return {};
- }
- auto opencl_handle = GetOpenCLHandle();
- if (!opencl_handle) {
- std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to get OpenCLHandle"
- << std::endl;
- return {};
- }
-
- // set up kernel and buffers
- auto kid = opencl_handle->CreateKernelFromSource(
- GetGrayscaleDitheringKernel(), "Dither");
- if (kid == 0) {
- std::cout << "ERROR ToGrayscaleDitheredWithBlueNoise: 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,
- grayscale_image->data_.size(), grayscale_image->data_.data());
- if (input_buffer_id == 0) {
- std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set input buffer"
- << 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, grayscale_image->data_.size(), nullptr);
+ kid, CL_MEM_WRITE_ONLY, this->data_.size(), nullptr);
if (output_buffer_id == 0) {
std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set output buffer"
+ << "ERROR ToColorDitheredWithBlueNoise: Failed to set output buffer"
<< std::endl;
opencl_handle->CleanupAllKernels();
return {};
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 ToGrayscaleDitheredWithBlueNoise: Failed to set "
+ 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 ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 0"
- << std::endl;
+ 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 ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 1"
- << std::endl;
+ 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 ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 2"
- << std::endl;
+ std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 2"
+ << std::endl;
opencl_handle->CleanupAllKernels();
return {};
}
- unsigned int width = grayscale_image->GetWidth();
+ unsigned int input_width = this->GetWidth();
if (!opencl_handle->AssignKernelArgument(kid, 3, sizeof(unsigned int),
- &width)) {
- std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 3"
- << std::endl;
+ &input_width)) {
+ std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 3"
+ << std::endl;
opencl_handle->CleanupAllKernels();
return {};
}
- unsigned int height = grayscale_image->GetHeight();
+ unsigned int input_height = this->GetHeight();
if (!opencl_handle->AssignKernelArgument(kid, 4, sizeof(unsigned int),
- &height)) {
- std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 4"
- << std::endl;
+ &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 ToGrayscaleDitheredWithBlueNoise: 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 ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 6"
- << std::endl;
- opencl_handle->CleanupAllKernels();
- return {};
- }
- std::srand(std::time(nullptr));
- unsigned int blue_noise_offset =
- std::rand() % (blue_noise_width * blue_noise_height);
- if (!opencl_handle->AssignKernelArgument(kid, 7, sizeof(unsigned int),
- &blue_noise_offset)) {
- std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to set parameter 7"
- << std::endl;
- opencl_handle->CleanupAllKernels();
- return {};
- }
-
- // auto global_work_sizes = opencl_handle->GetGlobalWorkSize(kid);
- auto work_group_size = opencl_handle->GetWorkGroupSize(kid);
- std::cout << "Got work_group_size == " << work_group_size << std::endl;
-
- // auto max_work_group_size = opencl_handle->GetDeviceMaxWorkGroupSize();
- // std::cout << "Got max_work_group_size == " << max_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 && width % work_group_size_0 != 0) {
- --work_group_size_0;
- }
- while (work_group_size_1 > 1 && height % work_group_size_1 != 0) {
- --work_group_size_1;
- }
-
- std::cout << "Using WIDTHxHEIGHT: " << width << "x" << height
- << " with work_group_sizes: " << work_group_size_0 << "x"
- << work_group_size_1 << std::endl;
-
- if (!opencl_handle->ExecuteKernel2D(kid, width, height, work_group_size_0,
- work_group_size_1, true)) {
- std::cout
- << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to execute Kernel"
- << std::endl;
- opencl_handle->CleanupAllKernels();
- return {};
- }
-
- if (!opencl_handle->GetBufferData(kid, output_buffer_id,
- grayscale_image->GetSize(),
- grayscale_image->data_.data())) {
- std::cout << "ERROR ToGrayscaleDitheredWithBlueNoise: Failed to get output "
- "buffer data"
- << std::endl;
- opencl_handle->CleanupAllKernels();
- return {};
- }
-
- opencl_handle->CleanupAllKernels();
- 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 {};
- }
-
- if (this->IsGrayscale()) {
- std::cout << "ERROR ToColorDitheredWithBlueNoise: current Image is not "
- "non-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;
+ std::cout << "ERROR ToColorDitheredWithBlueNoise: Failed to set parameter 5"
+ << std::endl;
opencl_handle->CleanupAllKernels();
return {};
}
return opencl_handle_;
}
+
+void Image::DecodePNG(const std::string &filename) {
+ FILE *file = std::fopen(filename.c_str(), "rb");
+
+ // Check header of file to check if it is actually a png file.
+ {
+ std::array<unsigned char, 8> buf;
+ if (std::fread(buf.data(), 1, 8, file) != 8) {
+ std::fclose(file);
+ std::cout << "ERROR: File \"" << filename << "\" is smaller than 8 bytes"
+ << std::endl;
+ return;
+ } else if (png_sig_cmp(reinterpret_cast<png_const_bytep>(buf.data()), 0,
+ 8) != 0) {
+ // not png file, do nothing
+ std::fclose(file);
+ std::cout << "ERROR: File \"" << filename << "\" is not a png file"
+ << std::endl;
+ return;
+ }
+ }
+
+ // seek to head of file
+ std::rewind(file);
+
+ // init required structs for png decoding
+ png_structp png_ptr =
+ png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (!png_ptr) {
+ std::cout << "ERROR: Failed to initialize libpng (png_ptr) for decoding "
+ "PNG file \""
+ << filename << '"' << std::endl;
+ std::fclose(file);
+ return;
+ }
+
+ png_infop png_info_ptr = png_create_info_struct(png_ptr);
+ if (!png_info_ptr) {
+ std::cout << "ERROR: Failed to initialize libpng (png_infop) for decoding "
+ "PNG file \""
+ << filename << '"' << std::endl;
+ png_destroy_read_struct(&png_ptr, nullptr, nullptr);
+ std::fclose(file);
+ return;
+ }
+
+ png_infop png_end_info_ptr = png_create_info_struct(png_ptr);
+ if (!png_end_info_ptr) {
+ std::cout << "ERROR: Failed to initialize libpng (end png_infop) for "
+ "decoding PNG file \""
+ << filename << '"' << std::endl;
+ png_destroy_read_struct(&png_ptr, &png_info_ptr, nullptr);
+ std::fclose(file);
+ return;
+ }
+
+ // required to handle libpng errors
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr);
+ std::fclose(file);
+ return;
+ }
+
+ // pass the FILE pointer to libpng
+ png_init_io(png_ptr, file);
+
+ // have libpng process the png data
+ png_read_png(png_ptr, png_info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
+
+ // get image width (in pixels)
+ width_ = png_get_image_width(png_ptr, png_info_ptr);
+
+ // get image height (in pixels)
+ height_ = png_get_image_height(png_ptr, png_info_ptr);
+
+ // get channel count of image
+ unsigned int channels = png_get_channels(png_ptr, png_info_ptr);
+ if (channels == 1) {
+ is_grayscale_ = true;
+ } else {
+ is_grayscale_ = false;
+ }
+
+ if (height_ > PNG_UINT_32_MAX) {
+ png_error(png_ptr, "Image is too tall to process in memory");
+ } else if (width_ > PNG_UINT_32_MAX) {
+ png_error(png_ptr, "Image is too wide to process in memory");
+ }
+
+ png_byte **row_pointers = png_get_rows(png_ptr, png_info_ptr);
+
+ data_.clear();
+ if (channels == 3 || channels == 4) {
+ data_.reserve(width_ * 4 * height_);
+ } else if (channels == 1) {
+ data_.reserve(width_ * height_);
+ } else {
+ std::cout << "ERROR: PNG has invalid channel count == " << channels
+ << std::endl;
+ png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr);
+ return;
+ }
+ for (unsigned int y = 0; y < height_; ++y) {
+ for (unsigned int x = 0; x < width_; ++x) {
+ if (is_grayscale_) {
+ data_.push_back(row_pointers[y][x]);
+ } else if (channels == 3) {
+ for (unsigned int c = 0; c < channels; ++c) {
+ data_.push_back(row_pointers[y][x * channels + c]);
+ }
+ data_.push_back(255);
+ } else /* if (channels == 4) */ {
+ for (unsigned int c = 0; c < channels; ++c) {
+ data_.push_back(row_pointers[y][x * channels + c]);
+ }
+ }
+ }
+ }
+
+ // cleanup
+ png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr);
+ fclose(file);
+
+ // verify
+ if (is_grayscale_) {
+ if (data_.size() != width_ * height_) {
+ std::cout << "WARNING: data_.size() doesn't match width_ * height_"
+ << std::endl;
+ }
+ } else {
+ if (data_.size() != 4 * width_ * height_) {
+ std::cout << "WARNING: data_.size() doesn't match 4 * width_ * height_"
+ << std::endl;
+ }
+ }
+}
+
+void Image::DecodePGM(const std::string &filename) {
+ is_grayscale_ = true;
+
+ std::ifstream ifs(filename);
+ if (!ifs.is_open()) {
+ std::cout << "ERROR: Failed to open file \"" << filename << '"'
+ << std::endl;
+ return;
+ }
+
+ std::string str_input;
+ int int_input;
+ ifs >> str_input;
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PGM first identifier) \""
+ << filename << '"' << std::endl;
+ return;
+ }
+
+ if (str_input.compare("P2") == 0) {
+ // data stored in ascii format
+
+ // get width
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PGM width) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ width_ = int_input;
+
+ // get height
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PGM height) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ height_ = int_input;
+
+ // get max_value
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PGM max) \"" << filename << '"'
+ << std::endl;
+ return;
+ }
+ float max_value = int_input;
+
+ // parse data
+ data_.clear();
+ data_.reserve(width_ * height_);
+ float value;
+ for (unsigned int i = 0; i < width_ * height_; ++i) {
+ ifs >> int_input;
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PGM data) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ value = static_cast<float>(int_input) / max_value;
+ data_.push_back(std::round(value * 255.0F));
+ }
+ } else if (str_input.compare("P5") == 0) {
+ // data stored in raw format
+
+ // get width
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PGM width) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ width_ = int_input;
+
+ // get height
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PGM height) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ height_ = int_input;
+
+ // get max_value
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PGM max) \"" << filename << '"'
+ << std::endl;
+ return;
+ }
+ int max_value_int = int_input;
+ float max_value = int_input;
+
+ // validate max_value
+ if (max_value_int != 255 && max_value_int != 65535) {
+ std::cout << "ERROR: Invalid max value for PGM (should be 255 or 65535) "
+ "(filename \""
+ << filename << "\")" << std::endl;
+ return;
+ }
+
+ // extract whitespace before data
+ {
+ int c = ifs.get();
+ if (c != '\n' && c != ' ') {
+ std::cout << "WARNING: File data after PGM max is not whitespace "
+ "(filename \""
+ << filename << "\")"
+ << " value is " << c << std::endl;
+ }
+
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PGM after whitespace) \""
+ << filename << '"' << std::endl;
+ return;
+ }
+ }
+
+ // parse raw data
+ data_.clear();
+ data_.reserve(width_ * height_);
+ float value;
+ for (unsigned int i = 0; i < width_ * height_; ++i) {
+ if (max_value_int == 255) {
+ value = ifs.get() / max_value;
+ data_.push_back(std::round(value * 255.0F));
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PGM data) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ } else /* if (max_value_int == 65535) */ {
+ value = (ifs.get() & 0xFF) | ((ifs.get() << 8) & 0xFF00);
+ value /= max_value;
+ data_.push_back(std::round(value * 255.0F));
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PGM data 16-bit) \""
+ << filename << '"' << std::endl;
+ return;
+ }
+ }
+ }
+
+ if (ifs.get() != decltype(ifs)::traits_type::eof()) {
+ std::cout << "WARNING: Trailing data in PGM file \"" << filename << '"'
+ << std::endl;
+ }
+ } else {
+ std::cout << "ERROR: Invalid \"magic number\" in header of file \""
+ << filename << '"' << std::endl;
+ }
+}
+
+void Image::DecodePPM(const std::string &filename) {
+ is_grayscale_ = false;
+ std::ifstream ifs(filename);
+ if (!ifs.is_open()) {
+ std::cout << "ERROR: Failed to open file \"" << filename << '"'
+ << std::endl;
+ return;
+ }
+
+ std::string str_input;
+ int int_input;
+ ifs >> str_input;
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PPM first identifier) \""
+ << filename << '"' << std::endl;
+ return;
+ }
+
+ if (str_input.compare("P3") == 0) {
+ // data stored in ascii format
+
+ // get width
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PPM width) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ width_ = int_input;
+
+ // get height
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PPM height) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ height_ = int_input;
+
+ // get max_value
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PPM max) \"" << filename << '"'
+ << std::endl;
+ return;
+ }
+ float max_value = int_input;
+
+ // parse data
+ data_.clear();
+ data_.reserve(width_ * height_ * 4);
+ float value;
+ for (unsigned int i = 0; i < width_ * height_ * 3; ++i) {
+ ifs >> int_input;
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PPM data) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ value = static_cast<float>(int_input) / max_value;
+ data_.push_back(std::round(value * 255.0F));
+ if (i % 3 == 2) {
+ // PPM is RGB but Image stores as RGBA
+ data_.push_back(255);
+ }
+ }
+ } else if (str_input.compare("P6") == 0) {
+ // data stored in raw format
+
+ // get width
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PPM width) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ width_ = int_input;
+
+ // get height
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PPM height) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ height_ = int_input;
+
+ // get max_value
+ ifs >> int_input;
+ if (!ifs.good() || int_input <= 0) {
+ std::cout << "ERROR: Failed to parse file (PPM max) \"" << filename << '"'
+ << std::endl;
+ return;
+ }
+ int max_value_int = int_input;
+ float max_value = int_input;
+
+ // validate max_value
+ if (max_value_int != 255 && max_value_int != 65535) {
+ std::cout << "ERROR: Invalid max value for PPM (should be 255 or 65535) "
+ "(filename \""
+ << filename << "\")" << std::endl;
+ return;
+ }
+
+ // extract whitespace before data
+ {
+ int c = ifs.get();
+ if (c != '\n' && c != ' ') {
+ std::cout
+ << "WARNING: File data after PPM max is not whitespace (filename \""
+ << filename << "\") value is " << c << std::endl;
+ }
+
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PPM after whitespace) \""
+ << filename << '"' << std::endl;
+ return;
+ }
+ }
+
+ // parse raw data
+ data_.clear();
+ data_.reserve(width_ * height_ * 4);
+ float value;
+ for (unsigned int i = 0; i < width_ * height_ * 3; ++i) {
+ if (max_value_int == 255) {
+ value = ifs.get() / max_value;
+ data_.push_back(std::round(value * 255.0F));
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PPM data) \"" << filename
+ << '"' << std::endl;
+ return;
+ }
+ } else /* if (max_value_int == 65535) */ {
+ value = (ifs.get() & 0xFF) | ((ifs.get() << 8) & 0xFF00);
+ value /= max_value;
+ data_.push_back(std::round(value * 255.0F));
+ if (!ifs.good()) {
+ std::cout << "ERROR: Failed to parse file (PPM data 16-bit) \""
+ << filename << '"' << std::endl;
+ return;
+ }
+ }
+
+ if (i % 3 == 2) {
+ // PPM is RGB but Image stores as RGBA
+ data_.push_back(255);
+ }
+ }
+
+ if (ifs.get() != decltype(ifs)::traits_type::eof()) {
+ std::cout << "WARNING: Trailing data in PPM file \"" << filename << '"'
+ << std::endl;
+ }
+ } else {
+ std::cout << "ERROR: Invalid \"magic number\" in header of file \""
+ << filename << '"' << std::endl;
+ }
+}