From c807c24456b282983773541ee81c6d5eaecf982b Mon Sep 17 00:00:00 2001 From: SikongJueluo Date: Thu, 3 Oct 2024 17:06:40 +0800 Subject: [PATCH] isp pipeline pass sim --- CMakeLists.txt | 42 +- rtl/Color/ColorBlender_Pipeline.sv | 2 +- rtl/Crop/Crop_Pipeline.sv | 2 +- rtl/Demosaic/Demosaic2.sv | 127 +- rtl/Demosaic/Demosaic_Pipeline.sv | 2 +- rtl/isp_Pipeline.sv | 8 +- src/img_process/bitmap_image.hpp | 5002 +++++++++++++++++ src/img_process/bmp.cpp | 87 - src/img_process/bmp.hpp | 43 - src/sc_main.cpp | 496 +- src/sc_main.cpp.back | 525 ++ src/{sc_main.cpp.bak => sc_main_pipeline.cpp} | 141 +- xmake.lua | 3 +- 13 files changed, 5924 insertions(+), 556 deletions(-) create mode 100644 src/img_process/bitmap_image.hpp delete mode 100755 src/img_process/bmp.cpp delete mode 100755 src/img_process/bmp.hpp mode change 100644 => 100755 src/sc_main.cpp create mode 100644 src/sc_main.cpp.back rename src/{sc_main.cpp.bak => sc_main_pipeline.cpp} (78%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 972da97..19fe2a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,23 +36,23 @@ find_package(Threads REQUIRED) find_package(SystemCLanguage QUIET) # Create software image process library -file(GLOB_RECURSE IMG_SRC ${PROJECT_SOURCE_DIR}/src/img_process/*.cpp) -add_library(img_process STATIC ${IMG_SRC}) +# file(GLOB_RECURSE IMG_SRC ${PROJECT_SOURCE_DIR}/src/img_process/*.cpp) +# add_library(img_process STATIC ${IMG_SRC}) -# Create a new executable target -file(GLOB_RECURSE VISP_SRC ${PROJECT_SOURCE_DIR}/src/*.cpp) -add_executable(Visp ${VISP_SRC}) +# ---------------------- EXE --------------------------- +# VISP +# ---------------------- EXE --------------------------- +add_executable(Visp ${PROJECT_SOURCE_DIR}/src/sc_main.cpp) target_compile_definitions(Visp PRIVATE INPUT_IMG="${PROJECT_SOURCE_DIR}/src/transform/test.bin" PRIVATE OUTPUT_DIR="${PROJECT_SOURCE_DIR}/logs/" ) target_include_directories(Visp PRIVATE ${PROJECT_SOURCE_DIR}/src/img_process) -target_link_libraries(Visp PRIVATE img_process) -# target_compile_features(Visp PUBLIC cxx_std_17) -# set_property(TARGET Visp PROPERTY CXX_STANDARD ${SystemC_CXX_STANDARD}) +# target_link_libraries(Visp PRIVATE img_process) -# Add the Verilated circuit to the target +# Get RTL source code dir SUBDIRLIST(RTL_SUBDIR ${PROJECT_SOURCE_DIR}/rtl) +# Add the Verilated circuit to the target verilate(Visp SYSTEMC COVERAGE TRACE INCLUDE_DIRS ${RTL_SUBDIR} VERILATOR_ARGS +librescan +libext+.v+.sv+.vh+.svh -y . -x-assign fast @@ -62,3 +62,27 @@ verilate(Visp SYSTEMC COVERAGE TRACE # SystemC Link verilator_link_systemc(Visp) + +# ---------------------- EXE --------------------------- +# VISP_Pipeline +# ---------------------- EXE --------------------------- +add_executable(Visp_Pipeline ${PROJECT_SOURCE_DIR}/src/sc_main_pipeline.cpp) +target_compile_definitions(Visp_Pipeline + PRIVATE INPUT_IMG="${PROJECT_SOURCE_DIR}/src/transform/test.bin" + PRIVATE OUTPUT_DIR="${PROJECT_SOURCE_DIR}/logs/" +) +target_include_directories(Visp_Pipeline PRIVATE ${PROJECT_SOURCE_DIR}/src/img_process) +# target_link_libraries(Visp_Pipeline PRIVATE img_process) + +# Get RTL source code dir +SUBDIRLIST(RTL_SUBDIR ${PROJECT_SOURCE_DIR}/rtl) +# Add the Verilated circuit to the target +verilate(Visp_Pipeline SYSTEMC COVERAGE TRACE + INCLUDE_DIRS ${RTL_SUBDIR} + VERILATOR_ARGS +librescan +libext+.v+.sv+.vh+.svh -y . -x-assign fast -Wno-WIDTHEXPAND + SOURCES ${PROJECT_SOURCE_DIR}/rtl/isp_Pipeline.sv + TOP_MODULE isp_Pipeline +) + +# SystemC Link +verilator_link_systemc(Visp_Pipeline) \ No newline at end of file diff --git a/rtl/Color/ColorBlender_Pipeline.sv b/rtl/Color/ColorBlender_Pipeline.sv index 8923678..a936f1c 100644 --- a/rtl/Color/ColorBlender_Pipeline.sv +++ b/rtl/Color/ColorBlender_Pipeline.sv @@ -1,7 +1,7 @@ `timescale 1ns / 1ps // 三通道图像合成一个RGB图像 -module ColorBlender #( +module ColorBlender_Pipeline #( parameter reg [4:0] IN_DEPTH = 12, // 输入图像的色深 parameter reg [4:0] OUT_DEPTH = 8 // 输出图像的色深 ) ( diff --git a/rtl/Crop/Crop_Pipeline.sv b/rtl/Crop/Crop_Pipeline.sv index b3afd55..6df57ef 100644 --- a/rtl/Crop/Crop_Pipeline.sv +++ b/rtl/Crop/Crop_Pipeline.sv @@ -1,5 +1,5 @@ `timescale 1ns / 1ps -module Crop #( +module Crop_Pipeline #( parameter IN_WIDTH = 512, parameter IN_HEIGHT = 512, parameter OFFSET_X = 120, diff --git a/rtl/Demosaic/Demosaic2.sv b/rtl/Demosaic/Demosaic2.sv index 4dde29c..ac475ec 100644 --- a/rtl/Demosaic/Demosaic2.sv +++ b/rtl/Demosaic/Demosaic2.sv @@ -4,7 +4,7 @@ module Demosaic2 #( parameter reg [15:0] IM_HEIGHT = 256, // 图像高度 parameter reg [ 1:0] RAW_TYPE = 3, // 0:grbg 1:rggb 2:bggr 3:gbrg parameter reg [ 4:0] DATA_SIZE = 16 -) ( + ) ( // 基本信号 input wire clk, input wire reset, @@ -22,7 +22,7 @@ module Demosaic2 #( output reg [DATA_SIZE - 1:0] out_r, output reg [DATA_SIZE - 1:0] out_g, output reg [DATA_SIZE - 1:0] out_b -); + ); // 常量,包括状态机 @@ -42,20 +42,29 @@ module Demosaic2 #( // 三段状态机实现,窗口滑动,颜色计算 // 状态切换 - always @(posedge clk) begin - if (reset) state <= READ_DATA; - else state <= nextState; + always @(posedge clk) + begin + if (reset) + state <= READ_DATA; + else + state <= nextState; end // 下一状态更新 - always @(*) begin + always @(*) + begin case (state) // 记录够3x3个数据后,进行rgb转换 - READ_DATA: nextState = (cnt_data >= 3) ? COLOR_GEN : READ_DATA; - COLOR_GEN: nextState = SEND_DATA; - SEND_DATA: nextState = (in_receive) ? SLIDE_WINDOW : SEND_DATA; - SLIDE_WINDOW: nextState = READ_DATA; - default: nextState = READ_DATA; + READ_DATA: + nextState = (cnt_data >= 3) ? COLOR_GEN : READ_DATA; + COLOR_GEN: + nextState = SEND_DATA; + SEND_DATA: + nextState = (in_receive) ? SLIDE_WINDOW : SEND_DATA; + SLIDE_WINDOW: + nextState = READ_DATA; + default: + nextState = READ_DATA; endcase end @@ -65,8 +74,10 @@ module Demosaic2 #( assign out_receive = (in_en && state == READ_DATA && !reset) ? 1 : 0; // 各状态执行的操作 - always @(posedge clk) begin - if (reset) begin + always @(posedge clk) + begin + if (reset) + begin // 外部输出初始化 out_en <= 0; out_r <= 0; @@ -78,12 +89,16 @@ module Demosaic2 #( pos_y <= 0; cnt_data <= 0; raw_type <= RAW_TYPE; - end else begin + end + else + begin // 状态机执行 case (state) // 读取数据 - READ_DATA: begin - if (in_en) begin + READ_DATA: + begin + if (in_en) + begin data_cache[0 + cnt_data * 3] <= in_data[0]; data_cache[1 + cnt_data * 3] <= in_data[1]; data_cache[2 + cnt_data * 3] <= in_data[2]; @@ -92,80 +107,107 @@ module Demosaic2 #( end end - COLOR_GEN: begin + COLOR_GEN: + begin // 生成rgb图像 // data case 0 case 1 case 2 case 3 // 0 3 6 G R G R G R B G B G B G // 1 4 7 B G B G B G G R G R G R // 2 5 8 G R G R G R B G B G B G case (raw_type) - 0: begin // Missing B, R on G + 0: + begin // Missing B, R on G blue <= (data_cache[1] + data_cache[7]) >> 1; red <= (data_cache[3] + data_cache[5]) >> 1; green <= data_cache[4]; end - 1: begin // Missing G, R on B + 1: + begin // Missing G, R on B green <= (data_cache[1] + data_cache[3] + data_cache[5] + data_cache[7]) >> 2; red <= (data_cache[0] + data_cache[2] + data_cache[6] + data_cache[8]) >> 2; blue <= data_cache[4]; end - 2: begin // Missing G, B on R + 2: + begin // Missing G, B on R green <= (data_cache[1] + data_cache[3] + data_cache[5] + data_cache[7]) >> 2; blue <= (data_cache[0] + data_cache[2] + data_cache[6] + data_cache[8]) >> 2; red <= data_cache[4]; end - 3: begin // Missing B, R on G + 3: + begin // Missing B, R on G red <= (data_cache[1] + data_cache[7]) >> 1; blue <= (data_cache[3] + data_cache[5]) >> 1; green <= data_cache[4]; end - default: ; + default: + ; endcase case (raw_type) - 0: raw_type <= 1; - 1: raw_type <= 0; - 2: raw_type <= 3; - 3: raw_type <= 2; + 0: + raw_type <= 1; + 1: + raw_type <= 0; + 2: + raw_type <= 3; + 3: + raw_type <= 2; endcase end - SEND_DATA: begin - if (in_ready && !in_receive) begin + SEND_DATA: + begin + if (in_ready && !in_receive) + begin out_en <= 1; out_r <= red; out_b <= blue; out_g <= green; - end else out_en <= 0; + pos_x <= pos_x + 1; + end + else + out_en <= 0; end - SLIDE_WINDOW: begin + SLIDE_WINDOW: + begin // 记录位置寄存器自增,并处理缓存数据 - pos_x <= pos_x + 1; - if (pos_x >= IM_WIDTH - 2 - 1) begin + if (pos_x >= IM_WIDTH - 2) + begin cnt_data <= 0; pos_x <= 0; pos_y <= pos_y + 1; - if (pos_y >= IM_HEIGHT - 2 - 1) begin + if (pos_y >= IM_HEIGHT - 2) + begin pos_y <= 0; end // 换行后切换Bayer格式 - if (pos_y % 2 == 1) begin + if (pos_y % 2 == 1) + begin raw_type <= RAW_TYPE; - end else begin + end + else + begin case (RAW_TYPE) - 0: raw_type <= 2; - 1: raw_type <= 3; - 2: raw_type <= 0; - 3: raw_type <= 1; - default: ; + 0: + raw_type <= 2; + 1: + raw_type <= 3; + 2: + raw_type <= 0; + 3: + raw_type <= 1; + default: + ; endcase end - end else begin + end + else + begin cnt_data <= 2; // 窗口右移 @@ -178,7 +220,8 @@ module Demosaic2 #( end end - default: ; + default: + ; endcase end end diff --git a/rtl/Demosaic/Demosaic_Pipeline.sv b/rtl/Demosaic/Demosaic_Pipeline.sv index e4d703b..b68c8e2 100644 --- a/rtl/Demosaic/Demosaic_Pipeline.sv +++ b/rtl/Demosaic/Demosaic_Pipeline.sv @@ -1,5 +1,5 @@ `timescale 1ns / 1ps -module Demosaic #( +module Demosaic_Pipeline #( parameter WINDOW_LENGTH = 3, parameter reg [15:0] TOTAL_WIDTH = 512+3, // 总图像宽度 parameter reg [15:0] TOTAL_HEIGHT = 256+3, // 总图像高度 diff --git a/rtl/isp_Pipeline.sv b/rtl/isp_Pipeline.sv index f83f180..5f846f1 100644 --- a/rtl/isp_Pipeline.sv +++ b/rtl/isp_Pipeline.sv @@ -1,6 +1,6 @@ `timescale 1ns / 1ps -module isp #( +module isp_Pipeline #( parameter reg [15:0] IN_WIDTH = 1936, parameter reg [15:0] IN_HEIGHT = 1088, parameter OFFSET_X = 7, @@ -59,7 +59,7 @@ module isp #( ); - Demosaic #( + Demosaic_Pipeline #( .WINDOW_LENGTH(3), .TOTAL_WIDTH (IN_WIDTH), .TOTAL_HEIGHT (IN_HEIGHT), @@ -78,7 +78,7 @@ module isp #( .out_fsync(Demosaic2_fsync) ); - ColorBlender #( + ColorBlender_Pipeline #( .IN_DEPTH(12), // 输入图像的色深 .OUT_DEPTH(COLOR_DEPTH) // 输出图像的色深 ) ColorBlender_inst ( @@ -101,7 +101,7 @@ module isp #( .enable (blender_enable) ); - Crop #( + Crop_Pipeline #( .IN_WIDTH (IN_WIDTH), .IN_HEIGHT (IN_HEIGHT), .OFFSET_X (OFFSET_X), diff --git a/src/img_process/bitmap_image.hpp b/src/img_process/bitmap_image.hpp new file mode 100644 index 0000000..94b9449 --- /dev/null +++ b/src/img_process/bitmap_image.hpp @@ -0,0 +1,5002 @@ +/* + ***************************************************************************** + * * + * Platform Independent * + * Bitmap Image Reader Writer Library * + * * + * Author: Arash Partow - 2002 * + * URL: http://partow.net/programming/bitmap/index.html * + * * + * Note: This library only supports 24-bits per pixel bitmap format files. * + * * + * Copyright notice: * + * Free use of the Platform Independent Bitmap Image Reader Writer Library * + * is permitted under the guidelines and in accordance with the most current * + * version of the MIT License. * + * http://www.opensource.org/licenses/MIT * + * * + ***************************************************************************** +*/ + + +#ifndef INCLUDE_BITMAP_IMAGE_HPP +#define INCLUDE_BITMAP_IMAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class bitmap_image +{ +public: + + enum channel_mode { + rgb_mode = 0, + bgr_mode = 1 + }; + + enum color_plane { + blue_plane = 0, + green_plane = 1, + red_plane = 2 + }; + + struct rgb_t + { + unsigned char red; + unsigned char green; + unsigned char blue; + }; + + bitmap_image() + : file_name_(""), + width_ (0), + height_ (0), + row_increment_ (0), + bytes_per_pixel_(3), + channel_mode_(bgr_mode) + {} + + bitmap_image(const std::string& filename) + : file_name_(filename), + width_ (0), + height_ (0), + row_increment_ (0), + bytes_per_pixel_(0), + channel_mode_(bgr_mode) + { + load_bitmap(); + } + + bitmap_image(const unsigned int width, const unsigned int height) + : file_name_(""), + width_ (width ), + height_(height), + row_increment_ (0), + bytes_per_pixel_(3), + channel_mode_(bgr_mode) + { + create_bitmap(); + } + + bitmap_image(const bitmap_image& image) + : file_name_(image.file_name_), + width_ (image.width_ ), + height_ (image.height_ ), + row_increment_ (0), + bytes_per_pixel_(3), + channel_mode_(bgr_mode) + { + create_bitmap(); + data_ = image.data_; + } + + bitmap_image& operator=(const bitmap_image& image) + { + if (this != &image) + { + file_name_ = image.file_name_; + bytes_per_pixel_ = image.bytes_per_pixel_; + width_ = image.width_; + height_ = image.height_; + row_increment_ = 0; + channel_mode_ = image.channel_mode_; + create_bitmap(); + data_ = image.data_; + } + + return *this; + } + + inline bool operator!() + { + return (data_.size() == 0) || + (width_ == 0) || + (height_ == 0) || + (row_increment_ == 0); + } + + inline void clear(const unsigned char v = 0x00) + { + std::fill(data_.begin(), data_.end(), v); + } + + inline unsigned char red_channel(const unsigned int x, const unsigned int y) const + { + return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 2)]; + } + + inline unsigned char green_channel(const unsigned int x, const unsigned int y) const + { + return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 1)]; + } + + inline unsigned char blue_channel (const unsigned int x, const unsigned int y) const + { + return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 0)]; + } + + inline void red_channel(const unsigned int x, const unsigned int y, const unsigned char value) + { + data_[(y * row_increment_) + (x * bytes_per_pixel_ + 2)] = value; + } + + inline void green_channel(const unsigned int x, const unsigned int y, const unsigned char value) + { + data_[(y * row_increment_) + (x * bytes_per_pixel_ + 1)] = value; + } + + inline void blue_channel (const unsigned int x, const unsigned int y, const unsigned char value) + { + data_[(y * row_increment_) + (x * bytes_per_pixel_ + 0)] = value; + } + + inline unsigned char* row(unsigned int row_index) const + { + return const_cast(&data_[(row_index * row_increment_)]); + } + + inline void get_pixel(const unsigned int x, const unsigned int y, + unsigned char& red, + unsigned char& green, + unsigned char& blue) const + { + const unsigned int y_offset = y * row_increment_; + const unsigned int x_offset = x * bytes_per_pixel_; + const unsigned int offset = y_offset + x_offset; + + blue = data_[offset + 0]; + green = data_[offset + 1]; + red = data_[offset + 2]; + } + + template + inline void get_pixel(const unsigned int x, const unsigned int y, RGB& colour) const + { + get_pixel(x, y, colour.red, colour.green, colour.blue); + } + + inline rgb_t get_pixel(const unsigned int x, const unsigned int y) const + { + rgb_t colour; + get_pixel(x, y, colour.red, colour.green, colour.blue); + return colour; + } + + inline void set_pixel(const unsigned int x, const unsigned int y, + const unsigned char red, + const unsigned char green, + const unsigned char blue) + { + const unsigned int y_offset = y * row_increment_; + const unsigned int x_offset = x * bytes_per_pixel_; + const unsigned int offset = y_offset + x_offset; + + data_[offset + 0] = blue; + data_[offset + 1] = green; + data_[offset + 2] = red; + } + + template + inline void set_pixel(const unsigned int x, const unsigned int y, const RGB& colour) + { + set_pixel(x, y, colour.red, colour.green, colour.blue); + } + + inline bool copy_from(const bitmap_image& image) + { + if ( + (image.height_ != height_) || + (image.width_ != width_ ) + ) + { + return false; + } + + data_ = image.data_; + + return true; + } + + inline bool copy_from(const bitmap_image& source_image, + const unsigned int& x_offset, + const unsigned int& y_offset) + { + if ((x_offset + source_image.width_ ) > width_ ) { return false; } + if ((y_offset + source_image.height_) > height_) { return false; } + + for (unsigned int y = 0; y < source_image.height_; ++y) + { + unsigned char* itr1 = row(y + y_offset) + x_offset * bytes_per_pixel_; + const unsigned char* itr2 = source_image.row(y); + const unsigned char* itr2_end = itr2 + source_image.width_ * bytes_per_pixel_; + + std::copy(itr2, itr2_end, itr1); + } + + return true; + } + + inline bool region(const unsigned int& x , + const unsigned int& y , + const unsigned int& width , + const unsigned int& height, + bitmap_image& dest_image ) const + { + if ((x + width ) > width_ ) { return false; } + if ((y + height) > height_) { return false; } + + if ( + (dest_image.width_ < width_ ) || + (dest_image.height_ < height_) + ) + { + dest_image.setwidth_height(width,height); + } + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr1 = row(r + y) + x * bytes_per_pixel_; + unsigned char* itr1_end = itr1 + (width * bytes_per_pixel_); + unsigned char* itr2 = dest_image.row(r); + + std::copy(itr1, itr1_end, itr2); + } + + return true; + } + + inline bool roi_from_center(const unsigned int& cx , + const unsigned int& cy , + const unsigned int& width , + const unsigned int& height, + bitmap_image& dest_image ) const + { + return region(cx - (width / 2), cy - (height / 2), + width, height, + dest_image); + } + + inline bool set_region(const unsigned int& x , + const unsigned int& y , + const unsigned int& width , + const unsigned int& height, + const unsigned char& value ) + { + if ((x + width ) > width_ ) { return false; } + if ((y + height) > height_) { return false; } + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr = row(r + y) + x * bytes_per_pixel_; + unsigned char* itr_end = itr + (width * bytes_per_pixel_); + + std::fill(itr, itr_end, value); + } + + return true; + } + + inline bool set_region(const unsigned int& x , + const unsigned int& y , + const unsigned int& width , + const unsigned int& height, + const color_plane color , + const unsigned char& value ) + { + if ((x + width ) > width_ ) { return false; } + if ((y + height) > height_) { return false; } + + const unsigned int color_plane_offset = offset(color); + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr = row(r + y) + x * bytes_per_pixel_ + color_plane_offset; + unsigned char* itr_end = itr + (width * bytes_per_pixel_); + + while (itr != itr_end) + { + *itr = value; + itr += bytes_per_pixel_; + } + } + + return true; + } + + inline bool set_region(const unsigned int& x , + const unsigned int& y , + const unsigned int& width , + const unsigned int& height, + const unsigned char& red , + const unsigned char& green , + const unsigned char& blue ) + { + if ((x + width ) > width_ ) { return false; } + if ((y + height) > height_) { return false; } + + for (unsigned int r = 0; r < height; ++r) + { + unsigned char* itr = row(r + y) + x * bytes_per_pixel_; + unsigned char* itr_end = itr + (width * bytes_per_pixel_); + + while (itr != itr_end) + { + *(itr++) = blue; + *(itr++) = green; + *(itr++) = red; + } + } + + return true; + } + + void reflective_image(bitmap_image& image, const bool include_diagnols = false) + { + image.setwidth_height(3 * width_, 3 * height_, true); + + image.copy_from(*this, width_, height_); + + vertical_flip(); + + image.copy_from(*this, width_, 0); + image.copy_from(*this, width_, 2 * height_); + + vertical_flip(); + horizontal_flip(); + + image.copy_from(*this, 0, height_); + image.copy_from(*this, 2 * width_, height_); + + horizontal_flip(); + + if (include_diagnols) + { + bitmap_image tile = *this; + + tile.vertical_flip(); + tile.horizontal_flip(); + + image.copy_from(tile, 0, 0); + image.copy_from(tile, 2 * width_, 0); + image.copy_from(tile, 2 * width_, 2 * height_); + image.copy_from(tile, 0 , 2 * height_); + } + } + + inline unsigned int width() const + { + return width_; + } + + inline unsigned int height() const + { + return height_; + } + + inline unsigned int bytes_per_pixel() const + { + return bytes_per_pixel_; + } + + inline unsigned int pixel_count() const + { + return width_ * height_; + } + + inline void setwidth_height(const unsigned int width, + const unsigned int height, + const bool clear = false) + { + data_.clear(); + width_ = width; + height_ = height; + + create_bitmap(); + + if (clear) + { + std::fill(data_.begin(), data_.end(), static_cast(0x00)); + } + } + + void save_image(const std::string& file_name) const + { + std::ofstream stream(file_name.c_str(),std::ios::binary); + + if (!stream) + { + std::cerr << "bitmap_image::save_image(): Error - Could not open file " + << file_name << " for writing!" << std::endl; + return; + } + + bitmap_information_header bih; + + bih.width = width_; + bih.height = height_; + bih.bit_count = static_cast(bytes_per_pixel_ << 3); + bih.clr_important = 0; + bih.clr_used = 0; + bih.compression = 0; + bih.planes = 1; + bih.size = bih.struct_size(); + bih.x_pels_per_meter = 0; + bih.y_pels_per_meter = 0; + bih.size_image = (((bih.width * bytes_per_pixel_) + 3) & 0x0000FFFC) * bih.height; + + bitmap_file_header bfh; + + bfh.type = 19778; + bfh.size = bfh.struct_size() + bih.struct_size() + bih.size_image; + bfh.reserved1 = 0; + bfh.reserved2 = 0; + bfh.off_bits = bih.struct_size() + bfh.struct_size(); + + write_bfh(stream,bfh); + write_bih(stream,bih); + + unsigned int padding = (4 - ((3 * width_) % 4)) % 4; + char padding_data[4] = { 0x00, 0x00, 0x00, 0x00 }; + + for (unsigned int i = 0; i < height_; ++i) + { + const unsigned char* data_ptr = &data_[(row_increment_ * (height_ - i - 1))]; + + stream.write(reinterpret_cast(data_ptr), sizeof(unsigned char) * bytes_per_pixel_ * width_); + stream.write(padding_data,padding); + } + + stream.close(); + } + + inline void set_all_ith_bits_low(const unsigned int bitr_index) + { + unsigned char mask = static_cast(~(1 << bitr_index)); + + for (unsigned char* itr = data(); itr != end(); ++itr) + { + *itr &= mask; + } + } + + inline void set_all_ith_bits_high(const unsigned int bitr_index) + { + unsigned char mask = static_cast(1 << bitr_index); + + for (unsigned char* itr = data(); itr != end(); ++itr) + { + *itr |= mask; + } + } + + inline void set_all_ith_channels(const unsigned int& channel, const unsigned char& value) + { + for (unsigned char* itr = (data() + channel); itr < end(); itr += bytes_per_pixel_) + { + *itr = value; + } + } + + inline void set_channel(const color_plane color,const unsigned char& value) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + *itr = value; + } + } + + inline void ror_channel(const color_plane color, const unsigned int& ror) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + *itr = static_cast(((*itr) >> ror) | ((*itr) << (8 - ror))); + } + } + + inline void set_all_channels(const unsigned char& value) + { + for (unsigned char* itr = data(); itr < end(); ) + { + *(itr++) = value; + } + } + + inline void set_all_channels(const unsigned char& r_value, + const unsigned char& g_value, + const unsigned char& b_value) + { + for (unsigned char* itr = (data() + 0); itr < end(); itr += bytes_per_pixel_) + { + *(itr + 0) = b_value; + *(itr + 1) = g_value; + *(itr + 2) = r_value; + } + } + + inline void invert_color_planes() + { + for (unsigned char* itr = data(); itr < end(); *itr = ~(*itr), ++itr); + } + + inline void add_to_color_plane(const color_plane color, const unsigned char& value) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + (*itr) += value; + } + } + + inline void convert_to_grayscale() + { + double r_scaler = 0.299; + double g_scaler = 0.587; + double b_scaler = 0.114; + + if (rgb_mode == channel_mode_) + { + std::swap(r_scaler, b_scaler); + } + + for (unsigned char* itr = data(); itr < end(); ) + { + unsigned char gray_value = static_cast + ( + (r_scaler * (*(itr + 2))) + + (g_scaler * (*(itr + 1))) + + (b_scaler * (*(itr + 0))) + ); + + *(itr++) = gray_value; + *(itr++) = gray_value; + *(itr++) = gray_value; + } + } + + inline const unsigned char* data() const + { + return data_.data(); + } + + inline unsigned char* data() + { + return const_cast(data_.data()); + } + + inline void bgr_to_rgb() + { + if ((bgr_mode == channel_mode_) && (3 == bytes_per_pixel_)) + { + reverse_channels(); + channel_mode_ = rgb_mode; + } + } + + inline void rgb_to_bgr() + { + if ((rgb_mode == channel_mode_) && (3 == bytes_per_pixel_)) + { + reverse_channels(); + channel_mode_ = bgr_mode; + } + } + + inline void reverse() + { + unsigned char* itr1 = data(); + unsigned char* itr2 = end() - bytes_per_pixel_; + + while (itr1 < itr2) + { + for (std::size_t i = 0; i < bytes_per_pixel_; ++i) + { + unsigned char* citr1 = itr1 + i; + unsigned char* citr2 = itr2 + i; + + std::swap(*citr1,*citr2); + } + + itr1 += bytes_per_pixel_; + itr2 -= bytes_per_pixel_; + } + } + + inline void horizontal_flip() + { + for (unsigned int y = 0; y < height_; ++y) + { + unsigned char* itr1 = row(y); + unsigned char* itr2 = itr1 + row_increment_ - bytes_per_pixel_; + + while (itr1 < itr2) + { + for (unsigned int i = 0; i < bytes_per_pixel_; ++i) + { + unsigned char* p1 = (itr1 + i); + unsigned char* p2 = (itr2 + i); + + std::swap(*p1,*p2); + } + + itr1 += bytes_per_pixel_; + itr2 -= bytes_per_pixel_; + } + } + } + + inline void vertical_flip() + { + for (unsigned int y = 0; y < (height_ / 2); ++y) + { + unsigned char* itr1 = row(y); + unsigned char* itr2 = row(height_ - y - 1); + + for (std::size_t x = 0; x < row_increment_; ++x) + { + std::swap(*(itr1 + x),*(itr2 + x)); + } + } + } + + inline void export_color_plane(const color_plane color, unsigned char* image) + { + for (unsigned char* itr = (data() + offset(color)); itr < end(); ++image, itr += bytes_per_pixel_) + { + (*image) = (*itr); + } + } + + inline void export_color_plane(const color_plane color, bitmap_image& image) + { + if ( + (width_ != image.width_ ) || + (height_ != image.height_) + ) + { + image.setwidth_height(width_,height_); + } + + image.clear(); + + unsigned char* itr1 = (data() + offset(color)); + unsigned char* itr1_end = end(); + unsigned char* itr2 = (image.data() + offset(color)); + + while (itr1 < itr1_end) + { + (*itr2) = (*itr1); + + itr1 += bytes_per_pixel_; + itr2 += bytes_per_pixel_; + } + } + + inline void export_response_image(const color_plane color, double* response_image) + { + double* resp_itr = response_image; + + for (unsigned char* itr = (data() + offset(color)); itr < end(); ++response_image, itr += bytes_per_pixel_) + { + *(resp_itr++) = (1.0 * (*itr)) / 256.0; + } + } + + inline void export_gray_scale_response_image(double* response_image) const + { + double* resp_itr = response_image; + + for (const unsigned char* itr = data(); itr < end(); itr += bytes_per_pixel_) + { + unsigned char gray_value = static_cast + ( + (0.299 * (*(itr + 2))) + + (0.587 * (*(itr + 1))) + + (0.114 * (*(itr + 0))) + ); + + *(resp_itr++) = (1.0 * gray_value) / 256.0; + } + } + + inline void export_rgb(double* red, double* green, double* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue ) = (1.0 * (*(itr++))) / 256.0; + (*green) = (1.0 * (*(itr++))) / 256.0; + (*red ) = (1.0 * (*(itr++))) / 256.0; + } + } + + inline void export_rgb(float* red, float* green, float* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue ) = (1.0f * (*(itr++))) / 256.0f; + (*green) = (1.0f * (*(itr++))) / 256.0f; + (*red ) = (1.0f * (*(itr++))) / 256.0f; + } + } + + inline void export_rgb(unsigned char* red, unsigned char* green, unsigned char* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue ) = *(itr++); + (*green) = *(itr++); + (*red ) = *(itr++); + } + } + + inline void export_ycbcr(double* y, double* cb, double* cr) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++y, ++cb, ++cr) + { + const double blue = (1.0 * (*(itr++))); + const double green = (1.0 * (*(itr++))); + const double red = (1.0 * (*(itr++))); + + ( *y) = clamp( 16.0 + (1.0/256.0) * ( 65.738 * red + 129.057 * green + 25.064 * blue),1.0,254); + (*cb) = clamp(128.0 + (1.0/256.0) * (- 37.945 * red - 74.494 * green + 112.439 * blue),1.0,254); + (*cr) = clamp(128.0 + (1.0/256.0) * ( 112.439 * red - 94.154 * green - 18.285 * blue),1.0,254); + } + } + + inline void export_rgb_normal(double* red, double* green, double* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue ) = (1.0 * (*(itr++))); + (*green) = (1.0 * (*(itr++))); + (*red ) = (1.0 * (*(itr++))); + } + } + + inline void export_rgb_normal(float* red, float* green, float* blue) const + { + if (bgr_mode != channel_mode_) + return; + + for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + (*blue ) = (1.0f * (*(itr++))); + (*green) = (1.0f * (*(itr++))); + (*red ) = (1.0f * (*(itr++))); + } + } + + inline void import_rgb(double* red, double* green, double* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(256.0 * (*blue )); + *(itr++) = static_cast(256.0 * (*green)); + *(itr++) = static_cast(256.0 * (*red )); + } + } + + inline void import_rgb(float* red, float* green, float* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(256.0f * (*blue )); + *(itr++) = static_cast(256.0f * (*green)); + *(itr++) = static_cast(256.0f * (*red )); + } + } + + inline void import_rgb(unsigned char* red, unsigned char* green, unsigned char* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = (*blue ); + *(itr++) = (*green); + *(itr++) = (*red ); + } + } + + inline void import_ycbcr(double* y, double* cb, double* cr) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++y, ++cb, ++cr) + { + double y_ = (*y); + double cb_ = (*cb); + double cr_ = (*cr); + + *(itr++) = static_cast(clamp((298.082 * y_ + 516.412 * cb_ ) / 256.0 - 276.836,0.0,255.0)); + *(itr++) = static_cast(clamp((298.082 * y_ - 100.291 * cb_ - 208.120 * cr_ ) / 256.0 + 135.576,0.0,255.0)); + *(itr++) = static_cast(clamp((298.082 * y_ + 408.583 * cr_ ) / 256.0 - 222.921,0.0,255.0)); + } + } + + inline void import_gray_scale_clamped(double* gray) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++gray) + { + unsigned char c = static_cast(clamp(256.0 * (*gray),0.0,255.0)); + + *(itr + 0) = c; + *(itr + 1) = c; + *(itr + 2) = c; + + itr += 3; + } + } + + inline void import_rgb_clamped(double* red, double* green, double* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(clamp(256.0 * (*blue ),0.0,255.0)); + *(itr++) = static_cast(clamp(256.0 * (*green),0.0,255.0)); + *(itr++) = static_cast(clamp(256.0 * (*red ),0.0,255.0)); + } + } + + inline void import_rgb_clamped(float* red, float* green, float* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(clamp(256.0f * (*blue ),0.0,255.0)); + *(itr++) = static_cast(clamp(256.0f * (*green),0.0,255.0)); + *(itr++) = static_cast(clamp(256.0f * (*red ),0.0,255.0)); + } + } + + inline void import_rgb_normal(double* red, double* green, double* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(*blue ); + *(itr++) = static_cast(*green); + *(itr++) = static_cast(*red ); + } + } + + inline void import_rgb_normal(float* red, float* green, float* blue) + { + if (bgr_mode != channel_mode_) + return; + + for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue) + { + *(itr++) = static_cast(*blue ); + *(itr++) = static_cast(*green); + *(itr++) = static_cast(*red ); + } + } + + inline void subsample(bitmap_image& dest) const + { + /* + Half sub-sample of original image. + */ + unsigned int w = 0; + unsigned int h = 0; + + bool odd_width = false; + bool odd_height = false; + + if (0 == (width_ % 2)) + w = width_ / 2; + else + { + w = 1 + (width_ / 2); + odd_width = true; + } + + if (0 == (height_ % 2)) + h = height_ / 2; + else + { + h = 1 + (height_ / 2); + odd_height = true; + } + + unsigned int horizontal_upper = (odd_width) ? (w - 1) : w; + unsigned int vertical_upper = (odd_height) ? (h - 1) : h; + + dest.setwidth_height(w,h); + dest.clear(); + + unsigned char* s_itr[3]; + const unsigned char* itr1[3]; + const unsigned char* itr2[3]; + + s_itr[0] = dest.data() + 0; + s_itr[1] = dest.data() + 1; + s_itr[2] = dest.data() + 2; + + itr1[0] = data() + 0; + itr1[1] = data() + 1; + itr1[2] = data() + 2; + + itr2[0] = data() + row_increment_ + 0; + itr2[1] = data() + row_increment_ + 1; + itr2[2] = data() + row_increment_ + 2; + + unsigned int total = 0; + + for (unsigned int j = 0; j < vertical_upper; ++j) + { + for (unsigned int i = 0; i < horizontal_upper; ++i) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + total = 0; + total += *(itr1[k]); + total += *(itr1[k]); + total += *(itr2[k]); + total += *(itr2[k]); + + itr1[k] += bytes_per_pixel_; + itr1[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + + *(s_itr[k]) = static_cast(total >> 2); + } + } + + if (odd_width) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + total = 0; + total += *(itr1[k]); + total += *(itr2[k]); + + itr1[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + + *(s_itr[k]) = static_cast(total >> 1); + } + } + + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + itr1[k] += row_increment_; + } + + if (j != (vertical_upper - 1)) + { + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + itr2[k] += row_increment_; + } + } + } + + if (odd_height) + { + for (unsigned int i = 0; i < horizontal_upper; ++i) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + total = 0; + total += *(itr1[k]); + total += *(itr2[k]); + + itr1[k] += bytes_per_pixel_; + itr2[k] += bytes_per_pixel_; + + *(s_itr[k]) = static_cast(total >> 1); + } + } + + if (odd_width) + { + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + (*(s_itr[k])) = *(itr1[k]); + } + } + } + } + + inline void upsample(bitmap_image& dest) const + { + /* + 2x up-sample of original image. + */ + + dest.setwidth_height(2 * width_ ,2 * height_); + dest.clear(); + + const unsigned char* s_itr[3]; + unsigned char* itr1[3]; + unsigned char* itr2[3]; + + s_itr[0] = data() + 0; + s_itr[1] = data() + 1; + s_itr[2] = data() + 2; + + itr1[0] = dest.data() + 0; + itr1[1] = dest.data() + 1; + itr1[2] = dest.data() + 2; + + itr2[0] = dest.data() + dest.row_increment_ + 0; + itr2[1] = dest.data() + dest.row_increment_ + 1; + itr2[2] = dest.data() + dest.row_increment_ + 2; + + for (unsigned int j = 0; j < height_; ++j) + { + for (unsigned int i = 0; i < width_; ++i) + { + for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k) + { + *(itr1[k]) = *(s_itr[k]); itr1[k] += bytes_per_pixel_; + *(itr1[k]) = *(s_itr[k]); itr1[k] += bytes_per_pixel_; + + *(itr2[k]) = *(s_itr[k]); itr2[k] += bytes_per_pixel_; + *(itr2[k]) = *(s_itr[k]); itr2[k] += bytes_per_pixel_; + } + } + + for (unsigned int k = 0; k < bytes_per_pixel_; ++k) + { + itr1[k] += dest.row_increment_; + itr2[k] += dest.row_increment_; + } + } + } + + inline void alpha_blend(const double& alpha, const bitmap_image& image) + { + if ( + (image.width_ != width_ ) || + (image.height_ != height_) + ) + { + return; + } + + if ((alpha < 0.0) || (alpha > 1.0)) + { + return; + } + + unsigned char* itr1 = data(); + const unsigned char* itr1_end = end(); + const unsigned char* itr2 = image.data(); + + double alpha_compliment = 1.0 - alpha; + + while (itr1 != itr1_end) + { + *(itr1) = static_cast((alpha * (*itr2)) + (alpha_compliment * (*itr1))); + ++itr1; + ++itr2; + } + } + + inline double psnr(const bitmap_image& image) + { + if ( + (image.width_ != width_ ) || + (image.height_ != height_) + ) + { + return 0.0; + } + + const unsigned char* itr1 = data(); + const unsigned char* itr2 = image.data(); + + double mse = 0.0; + + while (itr1 != end()) + { + const double v = (static_cast(*itr1) - static_cast(*itr2)); + + mse += v * v; + ++itr1; + ++itr2; + } + + if (mse <= 0.0000001) + { + return 1000000.0; + } + else + { + mse /= (3.0 * width_ * height_); + + return 20.0 * std::log10(255.0 / std::sqrt(mse)); + } + } + + inline double psnr(const unsigned int& x, + const unsigned int& y, + const bitmap_image& image) + { + if ((x + image.width() ) > width_ ) { return 0.0; } + if ((y + image.height()) > height_) { return 0.0; } + + double mse = 0.0; + + const unsigned int height = image.height(); + const unsigned int width = image.width(); + + for (unsigned int r = 0; r < height; ++r) + { + const unsigned char* itr1 = row(r + y) + x * bytes_per_pixel_; + const unsigned char* itr1_end = itr1 + (width * bytes_per_pixel_); + const unsigned char* itr2 = image.row(r); + + while (itr1 != itr1_end) + { + double v = (static_cast(*itr1) - static_cast(*itr2)); + mse += v * v; + ++itr1; + ++itr2; + } + } + + if (mse <= 0.0000001) + { + return 1000000.0; + } + else + { + mse /= (3.0 * image.width() * image.height()); + return 20.0 * std::log10(255.0 / std::sqrt(mse)); + } + } + + inline void histogram(const color_plane color, double hist[256]) const + { + std::fill(hist, hist + 256, 0.0); + + for (const unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_) + { + ++hist[(*itr)]; + } + } + + inline void histogram_normalized(const color_plane color, double hist[256]) const + { + histogram(color,hist); + + double* h_itr = hist; + const double* h_end = hist + 256; + const double pixel_count = static_cast(width_ * height_); + + while (h_end != h_itr) + { + *(h_itr++) /= pixel_count; + } + } + + inline unsigned int offset(const color_plane color) const + { + switch (channel_mode_) + { + case rgb_mode : { + switch (color) + { + case red_plane : return 0; + case green_plane : return 1; + case blue_plane : return 2; + default : return std::numeric_limits::max(); + } + } + + case bgr_mode : { + switch (color) + { + case red_plane : return 2; + case green_plane : return 1; + case blue_plane : return 0; + default : return std::numeric_limits::max(); + } + } + + default : return std::numeric_limits::max(); + } + } + + inline void incremental() + { + unsigned char current_color = 0; + + for (unsigned char* itr = data(); itr < end();) + { + (*itr++) = (current_color); + (*itr++) = (current_color); + (*itr++) = (current_color); + + ++current_color; + } + } + + inline void reverse_channels() + { + if (3 != bytes_per_pixel_) + return; + + for (unsigned char* itr = data(); itr < end(); itr += bytes_per_pixel_) + { + std::swap(*(itr + 0),*(itr + 2)); + } + } + +private: + + inline const unsigned char* end() const + { + return data_.data() + data_.size(); + } + + inline unsigned char* end() + { + return const_cast(data() + data_.size()); + } + + struct bitmap_file_header + { + unsigned short type; + unsigned int size; + unsigned short reserved1; + unsigned short reserved2; + unsigned int off_bits; + + unsigned int struct_size() const + { + return sizeof(type ) + + sizeof(size ) + + sizeof(reserved1) + + sizeof(reserved2) + + sizeof(off_bits ) ; + } + + void clear() + { + std::memset(this, 0x00, sizeof(bitmap_file_header)); + } + }; + + struct bitmap_information_header + { + unsigned int size; + unsigned int width; + unsigned int height; + unsigned short planes; + unsigned short bit_count; + unsigned int compression; + unsigned int size_image; + unsigned int x_pels_per_meter; + unsigned int y_pels_per_meter; + unsigned int clr_used; + unsigned int clr_important; + + unsigned int struct_size() const + { + return sizeof(size ) + + sizeof(width ) + + sizeof(height ) + + sizeof(planes ) + + sizeof(bit_count ) + + sizeof(compression ) + + sizeof(size_image ) + + sizeof(x_pels_per_meter) + + sizeof(y_pels_per_meter) + + sizeof(clr_used ) + + sizeof(clr_important ) ; + } + + void clear() + { + std::memset(this, 0x00, sizeof(bitmap_information_header)); + } + }; + + inline bool big_endian() const + { + unsigned int v = 0x01; + + return (1 != reinterpret_cast(&v)[0]); + } + + inline unsigned short flip(const unsigned short& v) const + { + return ((v >> 8) | (v << 8)); + } + + inline unsigned int flip(const unsigned int& v) const + { + return ( + ((v & 0xFF000000) >> 0x18) | + ((v & 0x000000FF) << 0x18) | + ((v & 0x00FF0000) >> 0x08) | + ((v & 0x0000FF00) << 0x08) + ); + } + + template + inline void read_from_stream(std::ifstream& stream,T& t) + { + stream.read(reinterpret_cast(&t),sizeof(T)); + } + + template + inline void write_to_stream(std::ofstream& stream,const T& t) const + { + stream.write(reinterpret_cast(&t),sizeof(T)); + } + + inline void read_bfh(std::ifstream& stream, bitmap_file_header& bfh) + { + read_from_stream(stream,bfh.type ); + read_from_stream(stream,bfh.size ); + read_from_stream(stream,bfh.reserved1); + read_from_stream(stream,bfh.reserved2); + read_from_stream(stream,bfh.off_bits ); + + if (big_endian()) + { + bfh.type = flip(bfh.type ); + bfh.size = flip(bfh.size ); + bfh.reserved1 = flip(bfh.reserved1); + bfh.reserved2 = flip(bfh.reserved2); + bfh.off_bits = flip(bfh.off_bits ); + } + } + + inline void write_bfh(std::ofstream& stream, const bitmap_file_header& bfh) const + { + if (big_endian()) + { + write_to_stream(stream,flip(bfh.type )); + write_to_stream(stream,flip(bfh.size )); + write_to_stream(stream,flip(bfh.reserved1)); + write_to_stream(stream,flip(bfh.reserved2)); + write_to_stream(stream,flip(bfh.off_bits )); + } + else + { + write_to_stream(stream,bfh.type ); + write_to_stream(stream,bfh.size ); + write_to_stream(stream,bfh.reserved1); + write_to_stream(stream,bfh.reserved2); + write_to_stream(stream,bfh.off_bits ); + } + } + + inline void read_bih(std::ifstream& stream,bitmap_information_header& bih) + { + read_from_stream(stream,bih.size ); + read_from_stream(stream,bih.width ); + read_from_stream(stream,bih.height ); + read_from_stream(stream,bih.planes ); + read_from_stream(stream,bih.bit_count ); + read_from_stream(stream,bih.compression ); + read_from_stream(stream,bih.size_image ); + read_from_stream(stream,bih.x_pels_per_meter); + read_from_stream(stream,bih.y_pels_per_meter); + read_from_stream(stream,bih.clr_used ); + read_from_stream(stream,bih.clr_important ); + + if (big_endian()) + { + bih.size = flip(bih.size ); + bih.width = flip(bih.width ); + bih.height = flip(bih.height ); + bih.planes = flip(bih.planes ); + bih.bit_count = flip(bih.bit_count ); + bih.compression = flip(bih.compression ); + bih.size_image = flip(bih.size_image ); + bih.x_pels_per_meter = flip(bih.x_pels_per_meter); + bih.y_pels_per_meter = flip(bih.y_pels_per_meter); + bih.clr_used = flip(bih.clr_used ); + bih.clr_important = flip(bih.clr_important ); + } + } + + inline void write_bih(std::ofstream& stream, const bitmap_information_header& bih) const + { + if (big_endian()) + { + write_to_stream(stream,flip(bih.size )); + write_to_stream(stream,flip(bih.width )); + write_to_stream(stream,flip(bih.height )); + write_to_stream(stream,flip(bih.planes )); + write_to_stream(stream,flip(bih.bit_count )); + write_to_stream(stream,flip(bih.compression )); + write_to_stream(stream,flip(bih.size_image )); + write_to_stream(stream,flip(bih.x_pels_per_meter)); + write_to_stream(stream,flip(bih.y_pels_per_meter)); + write_to_stream(stream,flip(bih.clr_used )); + write_to_stream(stream,flip(bih.clr_important )); + } + else + { + write_to_stream(stream,bih.size ); + write_to_stream(stream,bih.width ); + write_to_stream(stream,bih.height ); + write_to_stream(stream,bih.planes ); + write_to_stream(stream,bih.bit_count ); + write_to_stream(stream,bih.compression ); + write_to_stream(stream,bih.size_image ); + write_to_stream(stream,bih.x_pels_per_meter); + write_to_stream(stream,bih.y_pels_per_meter); + write_to_stream(stream,bih.clr_used ); + write_to_stream(stream,bih.clr_important ); + } + } + + inline std::size_t file_size(const std::string& file_name) const + { + std::ifstream file(file_name.c_str(),std::ios::in | std::ios::binary); + if (!file) return 0; + file.seekg (0, std::ios::end); + return static_cast(file.tellg()); + } + + void create_bitmap() + { + row_increment_ = width_ * bytes_per_pixel_; + data_.resize(height_ * row_increment_); + } + + void load_bitmap() + { + std::ifstream stream(file_name_.c_str(),std::ios::binary); + + if (!stream) + { + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - " + << "file " << file_name_ << " not found!" << std::endl; + return; + } + + width_ = 0; + height_ = 0; + + bitmap_file_header bfh; + bitmap_information_header bih; + + bfh.clear(); + bih.clear(); + + read_bfh(stream,bfh); + read_bih(stream,bih); + + if (bfh.type != 19778) + { + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - " + << "Invalid type value " << bfh.type << " expected 19778." << std::endl; + + bfh.clear(); + bih.clear(); + + stream.close(); + + return; + } + + if (bih.bit_count != 24) + { + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - " + << "Invalid bit depth " << bih.bit_count << " expected 24." << std::endl; + + bfh.clear(); + bih.clear(); + + stream.close(); + + return; + } + + if (bih.size != bih.struct_size()) + { + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - " + << "Invalid BIH size " << bih.size + << " expected " << bih.struct_size() << std::endl; + + bfh.clear(); + bih.clear(); + + stream.close(); + + return; + } + + width_ = bih.width; + height_ = bih.height; + + bytes_per_pixel_ = bih.bit_count >> 3; + + unsigned int padding = (4 - ((3 * width_) % 4)) % 4; + char padding_data[4] = { 0x00, 0x00, 0x00, 0x00 }; + + std::size_t bitmap_file_size = file_size(file_name_); + + std::size_t bitmap_logical_size = (height_ * width_ * bytes_per_pixel_) + + (height_ * padding) + + bih.struct_size() + + bfh.struct_size() ; + + if (bitmap_file_size != bitmap_logical_size) + { + std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - " + << "Mismatch between logical and physical sizes of bitmap. " + << "Logical: " << bitmap_logical_size << " " + << "Physical: " << bitmap_file_size << std::endl; + + bfh.clear(); + bih.clear(); + + stream.close(); + + return; + } + + create_bitmap(); + + for (unsigned int i = 0; i < height_; ++i) + { + unsigned char* data_ptr = row(height_ - i - 1); // read in inverted row order + + stream.read(reinterpret_cast(data_ptr), sizeof(char) * bytes_per_pixel_ * width_); + stream.read(padding_data,padding); + } + } + + template + inline T clamp(const T& v, const T& lower_range, const T& upper_range) const + { + if (v < lower_range) + return lower_range; + else if (v > upper_range) + return upper_range; + else + return v; + } + + std::string file_name_; + unsigned int width_; + unsigned int height_; + unsigned int row_increment_; + unsigned int bytes_per_pixel_; + channel_mode channel_mode_; + std::vector data_; +}; + +typedef bitmap_image::rgb_t rgb_t; + +inline bool operator==(const rgb_t& c0, const rgb_t& c1) +{ + return (c0.red == c1 .red) && + (c0.green == c1.green) && + (c0.blue == c1 .blue) ; +} + +inline bool operator!=(const rgb_t& c0, const rgb_t& c1) +{ + return (c0.red != c1 .red) || + (c0.green != c1.green) || + (c0.blue != c1 .blue) ; +} + +inline std::size_t hamming_distance(const rgb_t& c0, const rgb_t& c1) +{ + std::size_t result = 0; + + if (c0.red != c1 .red) ++result; + if (c0.green != c1.green) ++result; + if (c0.blue != c1 .blue) ++result; + + return result; +} + +inline rgb_t make_colour(const unsigned int& red, const unsigned int& green, const unsigned int& blue) +{ + rgb_t result; + + result.red = static_cast(red ); + result.green = static_cast(green); + result.blue = static_cast(blue ); + + return result; +} + +template +inline void generate_colours(const std::size_t& steps, const rgb_t c0, const rgb_t& c1, OutputIterator out) +{ + double dr = ((double)c1.red - (double)c0.red ) / steps; + double dg = ((double)c1.green - (double)c0.green ) / steps; + double db = ((double)c1.blue - (double)c0.blue ) / steps; + + for (std::size_t i = 0; i < steps; ++i) + { + rgb_t c; + + c.red = static_cast(c0.red + (i * dr)); + c.green = static_cast(c0.green + (i * dg)); + c.blue = static_cast(c0.blue + (i * db)); + + *(out++) = c; + } +} + +template +inline std::size_t convert_rsp_to_image(const ResponseImage& resp_image, const Palette& palette, bitmap_image& image) +{ + if ( + (resp_image.width () > image.width ()) || + (resp_image.height() > image.height()) + ) + return 0; + + for (std::size_t y = 0; y < resp_image.height(); ++y) + { + for (std::size_t x = 0; x < resp_image.width(); ++x) + { + const double v = resp_image(x,y); + + unsigned int index = static_cast((v < 0) ? 0 : v > (palette.size()) ? (palette.size() - 1) : v); + + image.set_pixel(x,y,palette[index]); + } + } + + return (resp_image.width() * resp_image.height()); +} + +inline void rgb_to_ycbcr(const unsigned int& length, double* red, double* green, double* blue, + double* y, double* cb, double* cr) +{ + unsigned int i = 0; + + while (i < length) + { + ( *y) = 16.0 + ( 65.481 * (*red) + 128.553 * (*green) + 24.966 * (*blue)); + (*cb) = 128.0 + ( -37.797 * (*red) + -74.203 * (*green) + 112.000 * (*blue)); + (*cr) = 128.0 + ( 112.000 * (*red) + -93.786 * (*green) - 18.214 * (*blue)); + + ++i; + ++red; ++green; ++blue; + ++y; ++cb; ++cr; + } +} + +inline void ycbcr_to_rgb(const unsigned int& length, double* y, double* cb, double* cr, + double* red, double* green, double* blue) +{ + unsigned int i = 0; + + while (i < length) + { + double y_ = (*y) - 16.0; + double cb_ = (*cb) - 128.0; + double cr_ = (*cr) - 128.0; + + (*red) = 0.000456621 * y_ + 0.00625893 * cr_; + (*green) = 0.000456621 * y_ - 0.00153632 * cb_ - 0.00318811 * cr_; + (*blue) = 0.000456621 * y_ + 0.00791071 * cb_; + + ++i; + ++red; ++green; ++blue; + ++y; ++cb; ++cr; + } +} + +inline void subsample(const unsigned int& width, + const unsigned int& height, + const double* source, + unsigned int& w, + unsigned int& h, + double*& dest) +{ + /* Single channel. */ + + w = 0; + h = 0; + + bool odd_width = false; + bool odd_height = false; + + if (0 == (width % 2)) + w = width / 2; + else + { + w = 1 + (width / 2); + odd_width = true; + } + + if (0 == (height % 2)) + h = height / 2; + else + { + h = 1 + (height / 2); + odd_height = true; + } + + unsigned int horizontal_upper = (odd_width) ? w - 1 : w; + unsigned int vertical_upper = (odd_height) ? h - 1 : h; + + dest = new double[w * h]; + + double* s_itr = dest; + const double* itr1 = source; + const double* itr2 = source + width; + + for (unsigned int j = 0; j < vertical_upper; ++j) + { + for (unsigned int i = 0; i < horizontal_upper; ++i, ++s_itr) + { + (*s_itr) = *(itr1++); + (*s_itr) += *(itr1++); + (*s_itr) += *(itr2++); + (*s_itr) += *(itr2++); + (*s_itr) /= 4.0; + } + + if (odd_width) + { + (*(s_itr++)) = ((*itr1++) + (*itr2++)) / 2.0; + } + + itr1 += width; + + if (j != (vertical_upper -1)) + { + itr2 += width; + } + } + + if (odd_height) + { + for (unsigned int i = 0; i < horizontal_upper; ++i, ++s_itr) + { + (*s_itr) += (*(itr1++)); + (*s_itr) += (*(itr1++)); + (*s_itr) /= 2.0; + } + + if (odd_width) + { + (*(s_itr++)) = (*itr1); + } + } +} + +inline void upsample(const unsigned int& width, + const unsigned int& height, + const double* source, + unsigned int& w, + unsigned int& h, + double*& dest) +{ + /* Single channel. */ + + w = 2 * width; + h = 2 * height; + + dest = new double[w * h]; + + const double* s_itr = source; + double* itr1 = dest; + double* itr2 = dest + w; + + for (unsigned int j = 0; j < height; ++j) + { + for (unsigned int i = 0; i < width; ++i, ++s_itr) + { + *(itr1++) = (*s_itr); + *(itr1++) = (*s_itr); + *(itr2++) = (*s_itr); + *(itr2++) = (*s_itr); + } + + itr1 += w; + itr2 += w; + } +} + +inline void checkered_pattern(const unsigned int x_width, + const unsigned int y_width, + const unsigned char value, + const bitmap_image::color_plane color, + bitmap_image& image) +{ + if ( + (x_width >= image.width ()) || + (y_width >= image.height()) + ) + { + return; + } + + bool setter_x = false; + bool setter_y = true; + + const unsigned int color_plane_offset = image.offset(color); + const unsigned int height = image.height(); + const unsigned int width = image.width(); + + for (unsigned int y = 0; y < height; ++y) + { + if (0 == (y % y_width)) + { + setter_y = !setter_y; + } + + unsigned char* row = image.row(y) + color_plane_offset; + + for (unsigned int x = 0; x < width; ++x, row += image.bytes_per_pixel()) + { + if (0 == (x % x_width)) + { + setter_x = !setter_x; + } + + if (setter_x ^ setter_y) + { + *row = value; + } + } + } +} + +inline void checkered_pattern(const unsigned int x_width, + const unsigned int y_width, + const unsigned char red, + const unsigned char green, + const unsigned char blue, + bitmap_image& image) +{ + if ( + (x_width >= image.width ()) || + (y_width >= image.height()) + ) + { + return; + } + + bool setter_x = false; + bool setter_y = true; + + const unsigned int height = image.height(); + const unsigned int width = image.width(); + + for (unsigned int y = 0; y < height; ++y) + { + if (0 == (y % y_width)) + { + setter_y = !setter_y; + } + + unsigned char* row = image.row(y); + + for (unsigned int x = 0; x < width; ++x, row += image.bytes_per_pixel()) + { + if (0 == (x % x_width)) + { + setter_x = !setter_x; + } + + if (setter_x ^ setter_y) + { + *(row + 0) = blue; + *(row + 1) = green; + *(row + 2) = red; + } + } + } +} + +inline void plasma(bitmap_image& image, + const double& x, const double& y, + const double& width, const double& height, + const double& c1, const double& c2, + const double& c3, const double& c4, + const double& roughness = 3.0, + const rgb_t colormap[] = 0) +{ + // Note: c1,c2,c3,c4 -> [0.0,1.0] + + const double half_width = ( width / 2.0); + const double half_height = (height / 2.0); + + if ((width >= 1.0) || (height >= 1.0)) + { + const double corner1 = (c1 + c2) / 2.0; + const double corner2 = (c2 + c3) / 2.0; + const double corner3 = (c3 + c4) / 2.0; + const double corner4 = (c4 + c1) / 2.0; + double center = (c1 + c2 + c3 + c4) / 4.0 + + ((1.0 * ::rand() /(1.0 * RAND_MAX)) - 0.5) * // should use a better rng + ((1.0 * half_width + half_height) / (image.width() + image.height()) * roughness); + + center = std::min(std::max(0.0,center),1.0); + + plasma(image, x, y, half_width, half_height, c1, corner1, center, corner4,roughness,colormap); + plasma(image, x + half_width, y, half_width, half_height, corner1, c2, corner2, center,roughness,colormap); + plasma(image, x + half_width, y + half_height, half_width, half_height, center, corner2, c3, corner3,roughness,colormap); + plasma(image, x, y + half_height, half_width, half_height, corner4, center, corner3, c4,roughness,colormap); + } + else + { + rgb_t color = colormap[static_cast(1000.0 * ((c1 + c2 + c3 + c4) / 4.0)) % 1000]; + + image.set_pixel(static_cast(x),static_cast(y),color); + } +} + +inline void plasma(bitmap_image& image, + const double& c1, const double& c2, + const double& c3, const double& c4, + const double& roughness = 3.0, + const rgb_t colormap[] = 0) +{ + plasma + ( + image, 0, 0, image.width(), image.height(), + c1, c2, c3, c4, + roughness, colormap + ); +} + +inline double psnr_region(const unsigned int& x, const unsigned int& y, + const unsigned int& width, const unsigned int& height, + const bitmap_image& image1, const bitmap_image& image2) +{ + if ( + (image1.width() != image2.width ()) || + (image1.height() != image2.height()) + ) + { + return 0.0; + } + + if ((x + width ) > image1.width() ) { return 0.0; } + if ((y + height) > image1.height()) { return 0.0; } + + double mse = 0.0; + + for (unsigned int r = 0; r < height; ++r) + { + const unsigned char* itr1 = image1.row(r + y) + x * image1.bytes_per_pixel(); + const unsigned char* itr1_end = itr1 + (width * image1.bytes_per_pixel()); + const unsigned char* itr2 = image2.row(r + y) + x * image2.bytes_per_pixel(); + + while (itr1 != itr1_end) + { + double v = (static_cast(*itr1) - static_cast(*itr2)); + mse += v * v; + ++itr1; + ++itr2; + } + } + + if (mse <= 0.0000001) + { + return 1000000.0; + } + else + { + mse /= (3.0 * width * height); + return 20.0 * std::log10(255.0 / std::sqrt(mse)); + } +} + +inline void hierarchical_psnr_r(const double& x, const double& y, + const double& width, const double& height, + const bitmap_image& image1, + bitmap_image& image2, + const double& threshold, + const rgb_t colormap[]) +{ + if ((width <= 4.0) || (height <= 4.0)) + { + const double psnr = psnr_region + ( + static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height), + image1, image2 + ); + + if (psnr < threshold) + { + rgb_t c = colormap[static_cast(1000.0 * (1.0 - (psnr / threshold)))]; + + image2.set_region + ( + static_cast(x), + static_cast(y), + static_cast(width + 1), + static_cast(height + 1), + c.red, c.green, c.blue + ); + } + } + else + { + const double half_width = ( width / 2.0); + const double half_height = (height / 2.0); + + hierarchical_psnr_r(x , y , half_width, half_height, image1, image2, threshold, colormap); + hierarchical_psnr_r(x + half_width, y , half_width, half_height, image1, image2, threshold, colormap); + hierarchical_psnr_r(x + half_width, y + half_height, half_width, half_height, image1, image2, threshold, colormap); + hierarchical_psnr_r(x , y + half_height, half_width, half_height, image1, image2, threshold, colormap); + } +} + +inline void hierarchical_psnr(bitmap_image& image1, bitmap_image& image2, const double threshold, const rgb_t colormap[]) +{ + if ( + (image1.width() != image2.width ()) || + (image1.height() != image2.height()) + ) + { + return; + } + + const double psnr = psnr_region + ( + 0, 0, image1.width(), image1.height(), + image1, image2 + ); + + if (psnr < threshold) + { + hierarchical_psnr_r + ( + 0, 0, image1.width(), image1.height(), + image1, image2, + threshold, + colormap + ); + } +} + +class image_drawer +{ +public: + + image_drawer(bitmap_image& image) + : image_(image), + pen_width_(1), + pen_color_red_ (0), + pen_color_green_(0), + pen_color_blue_ (0) + {} + + void rectangle(int x1, int y1, int x2, int y2) + { + line_segment(x1, y1, x2, y1); + line_segment(x2, y1, x2, y2); + line_segment(x2, y2, x1, y2); + line_segment(x1, y2, x1, y1); + } + + void triangle(int x1, int y1, int x2, int y2,int x3, int y3) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x1, y1); + } + + void quadix(int x1, int y1, int x2, int y2,int x3, int y3, int x4, int y4) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x4, y4); + line_segment(x4, y4, x1, y1); + } + + void line_segment(int x1, int y1, int x2, int y2) + { + int steep = 0; + int sx = ((x2 - x1) > 0) ? 1 : -1; + int sy = ((y2 - y1) > 0) ? 1 : -1; + int dx = abs(x2 - x1); + int dy = abs(y2 - y1); + + if (dy > dx) + { + std::swap(x1,y1); + std::swap(dx,dy); + std::swap(sx,sy); + + steep = 1; + } + + int e = 2 * dy - dx; + + for (int i = 0; i < dx; ++i) + { + if (steep) + plot_pen_pixel(y1,x1); + else + plot_pen_pixel(x1,y1); + + while (e >= 0) + { + y1 += sy; + e -= (dx << 1); + } + + x1 += sx; + e += (dy << 1); + } + + plot_pen_pixel(x2,y2); + } + + void horiztonal_line_segment(int x1, int x2, int y) + { + if (x1 > x2) + { + std::swap(x1,x2); + } + + for (int i = 0; i < (x2 - x1); ++i) + { + plot_pen_pixel(x1 + i,y); + } + } + + void vertical_line_segment(int y1, int y2, int x) + { + if (y1 > y2) + { + std::swap(y1,y2); + } + + for (int i = 0; i < (y2 - y1); ++i) + { + plot_pen_pixel(x, y1 + i); + } + } + + void ellipse(int centerx, int centery, int a, int b) + { + int t1 = a * a; + int t2 = t1 << 1; + int t3 = t2 << 1; + int t4 = b * b; + int t5 = t4 << 1; + int t6 = t5 << 1; + int t7 = a * t5; + int t8 = t7 << 1; + int t9 = 0; + + int d1 = t2 - t7 + (t4 >> 1); + int d2 = (t1 >> 1) - t8 + t5; + int x = a; + int y = 0; + + int negative_tx = centerx - x; + int positive_tx = centerx + x; + int negative_ty = centery - y; + int positive_ty = centery + y; + + while (d2 < 0) + { + plot_pen_pixel(positive_tx, positive_ty); + plot_pen_pixel(positive_tx, negative_ty); + plot_pen_pixel(negative_tx, positive_ty); + plot_pen_pixel(negative_tx, negative_ty); + + ++y; + + t9 = t9 + t3; + + if (d1 < 0) + { + d1 = d1 + t9 + t2; + d2 = d2 + t9; + } + else + { + x--; + t8 = t8 - t6; + d1 = d1 + (t9 + t2 - t8); + d2 = d2 + (t9 + t5 - t8); + negative_tx = centerx - x; + positive_tx = centerx + x; + } + + negative_ty = centery - y; + positive_ty = centery + y; + } + + do + { + plot_pen_pixel(positive_tx, positive_ty); + plot_pen_pixel(positive_tx, negative_ty); + plot_pen_pixel(negative_tx, positive_ty); + plot_pen_pixel(negative_tx, negative_ty); + + x--; + t8 = t8 - t6; + + if (d2 < 0) + { + ++y; + t9 = t9 + t3; + d2 = d2 + (t9 + t5 - t8); + negative_ty = centery - y; + positive_ty = centery + y; + } + else + d2 = d2 + (t5 - t8); + + negative_tx = centerx - x; + positive_tx = centerx + x; + } + while (x >= 0); + } + + void circle(int centerx, int centery, int radius) + { + int x = 0; + int d = (1 - radius) << 1; + + while (radius >= 0) + { + plot_pen_pixel(centerx + x, centery + radius); + plot_pen_pixel(centerx + x, centery - radius); + plot_pen_pixel(centerx - x, centery + radius); + plot_pen_pixel(centerx - x, centery - radius); + + if ((d + radius) > 0) + d -= ((--radius) << 1) - 1; + if (x > d) + d += ((++x) << 1) + 1; + } + } + + void plot_pen_pixel(int x, int y) + { + switch (pen_width_) + { + case 1 : plot_pixel(x,y); + break; + + case 2 : { + plot_pixel(x , y ); + plot_pixel(x + 1, y ); + plot_pixel(x + 1, y + 1); + plot_pixel(x , y + 1); + } + break; + + case 3 : { + plot_pixel(x , y - 1); + plot_pixel(x - 1, y - 1); + plot_pixel(x + 1, y - 1); + + plot_pixel(x , y ); + plot_pixel(x - 1, y ); + plot_pixel(x + 1, y ); + + plot_pixel(x , y + 1); + plot_pixel(x - 1, y + 1); + plot_pixel(x + 1, y + 1); + } + break; + + default : plot_pixel(x,y); + break; + } + } + + void plot_pixel(int x, int y) + { + if ( + (x < 0) || + (y < 0) || + (x >= static_cast(image_.width ())) || + (y >= static_cast(image_.height())) + ) + return; + + image_.set_pixel(x,y,pen_color_red_,pen_color_green_,pen_color_blue_); + } + + void pen_width(const unsigned int& width) + { + if ((width > 0) && (width < 4)) + { + pen_width_ = width; + } + } + + void pen_color(const unsigned char& red, + const unsigned char& green, + const unsigned char& blue) + { + pen_color_red_ = red; + pen_color_green_ = green; + pen_color_blue_ = blue; + } + + template + void pen_color(const RGB colour) + { + pen_color_red_ = colour.red; + pen_color_green_ = colour.green; + pen_color_blue_ = colour.blue; + } + +private: + + image_drawer(const image_drawer& id); + image_drawer& operator =(const image_drawer& id); + + bitmap_image& image_; + unsigned int pen_width_; + unsigned char pen_color_red_; + unsigned char pen_color_green_; + unsigned char pen_color_blue_; +}; + +class cartesian_canvas +{ +public: + + cartesian_canvas(const double x_length, const double y_length) + : width_div2_ (0.0), + height_div2_(0.0), + min_x_ (0.0), + min_y_ (0.0), + max_x_ (0.0), + max_y_ (0.0), + draw_ (image_) + { + setup_canvas(x_length,y_length); + } + + inline bool operator!() + { + return !image_; + } + + void rectangle(double x1, double y1, double x2, double y2) + { + line_segment(x1, y1, x2, y1); + line_segment(x2, y1, x2, y2); + line_segment(x2, y2, x1, y2); + line_segment(x1, y2, x1, y1); + } + + void triangle(double x1, double y1, double x2, double y2, double x3, double y3) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x1, y1); + } + + void quadix(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) + { + line_segment(x1, y1, x2, y2); + line_segment(x2, y2, x3, y3); + line_segment(x3, y3, x4, y4); + line_segment(x4, y4, x1, y1); + } + + void line_segment(double x1, double y1, double x2, double y2) + { + if (clip(x1, y1, x2, y2)) + { + const int sc_x1 = static_cast(cart_to_screen_x(x1)); + const int sc_x2 = static_cast(cart_to_screen_x(x2)); + const int sc_y1 = static_cast(cart_to_screen_y(y1)); + const int sc_y2 = static_cast(cart_to_screen_y(y2)); + + draw_.line_segment(sc_x1, sc_y1, sc_x2, sc_y2); + } + } + + void horiztonal_line_segment(double x1, double x2, double y) + { + x1 = clamp_x(x1); + x2 = clamp_x(x2); + y = clamp_y( y); + + const int sc_x1 = static_cast(cart_to_screen_x(x1)); + const int sc_x2 = static_cast(cart_to_screen_x(x2)); + const int sc_y = static_cast(cart_to_screen_y(y )); + + draw_.horiztonal_line_segment(sc_x1, sc_x2, sc_y); + } + + void vertical_line_segment(double y1, double y2, double x) + { + y1 = clamp_y(y1); + y2 = clamp_y(y2); + x = clamp_x( x); + + const int sc_y1 = static_cast(cart_to_screen_y(y1)); + const int sc_y2 = static_cast(cart_to_screen_y(y2)); + const int sc_x = static_cast(cart_to_screen_x(x )); + + draw_.vertical_line_segment(sc_y1, sc_y2, sc_x); + } + + void ellipse(double centerx, double centery, double a, double b) + { + + const int sc_cx = static_cast(cart_to_screen_x(centerx)); + const int sc_cy = static_cast(cart_to_screen_y(centery)); + + draw_.ellipse(sc_cx, sc_cy, static_cast(a), static_cast(b)); + } + + void circle(double centerx, double centery, double radius) + { + const int sc_cx = static_cast(cart_to_screen_x(centerx)); + const int sc_cy = static_cast(cart_to_screen_y(centery)); + + draw_.circle(sc_cx, sc_cy, static_cast(radius)); + } + + void fill_rectangle(double x1, double y1, double x2, double y2) + { + if (y1 > y2) + std::swap(y1, y2); + + for (double y = y1; y <= y2; y += 0.5) + { + line_segment(x1, y, x2, y); + } + } + + void fill_triangle(double x1, double y1, double x2, double y2, double x3, double y3) + { + typedef std::pair point_t; + + std::vector p; + + p.push_back(std::make_pair(x1,y1)); + p.push_back(std::make_pair(x2,y2)); + p.push_back(std::make_pair(x3,y3)); + + if (p[0].second > p[1].second) + std::swap(p[0],p[1]); + if (p[0].second > p[2].second) + std::swap(p[0],p[2]); + if (p[1].second > p[2].second) + std::swap(p[1],p[2]); + + class draw_modes + { + private: + + cartesian_canvas& canvas; + + // Needed for incompetent and broken msvc compiler versions + #ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable: 4822) + #endif + draw_modes& operator=(const draw_modes&); + #ifdef _MSC_VER + #pragma warning(pop) + #endif + + public: + + draw_modes(cartesian_canvas& c) + : canvas(c) + {} + + void bottom(const point_t& p0, const point_t& p1, const point_t& p2) + { + const double m0 = (p1.first - p0.first) / (2.0 * (p1.second - p0.second)); + const double m1 = (p2.first - p0.first) / (2.0 * (p2.second - p0.second)); + + double x0 = p0.first; + double x1 = p0.first; + + for (double y = p0.second; y <= p1.second; y += 0.5) + { + canvas.horiztonal_line_segment(x0, x1, y); + + x0 += m0; + x1 += m1; + } + } + + void top(const point_t& p0, const point_t& p1, const point_t& p2) + { + const double m0 = (p2.first - p0.first) / (2.0 * (p2.second - p0.second)); + const double m1 = (p2.first - p1.first) / (2.0 * (p2.second - p1.second)); + + double x0 = p2.first; + double x1 = p2.first; + + for (double y = p2.second; y >= p0.second; y -= 0.5) + { + canvas.horiztonal_line_segment(x0, x1, y); + + x0 -= m0; + x1 -= m1; + } + } + }; + + draw_modes dm(*this); + + const double eps = 0.00001; + + if (std::abs(p[1].second - p[2].second) < eps) + dm.bottom(p[0], p[1], p[2]); + else if (std::abs(p[0].second - p[1].second) < eps) + dm.top(p[0], p[1], p[2]); + else + { + point_t p3; + + p3.first = (p[0].first + ((p[1].second - p[0].second) / (p[2].second - p[0].second)) * (p[2].first - p[0].first)); + p3.second = p[1].second; + + dm.bottom(p[0], p[1], p3 ); + dm.top (p[1], p3 , p[2]); + } + } + + void fill_quadix(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) + { + fill_triangle(x1, y1, x2, y2, x3, y3); + fill_triangle(x1, y1, x3, y3, x4, y4); + } + + void fill_circle(double cx, double cy, double radius) + { + const double delta = 1.0; + double x = radius; + double y = 0.0; + double dx = delta - (2.0 * delta * radius); + double dy = 0.0; + double dr = 0.0; + + while (x >= y) + { + for (double i = cx - x; i <= cx + x; i += delta) + { + horiztonal_line_segment(cx - x, cx + x, cy + y); + horiztonal_line_segment(cx - x, cx + x, cy - y); + } + + for (double i = cx - y; i <= cx + y; i += delta) + { + horiztonal_line_segment(cx - y, cx + y, cy + x); + horiztonal_line_segment(cx - y, cx + y, cy - x); + } + + y += delta; + + dr += dy; + dy += 2.0 * delta; + + if ((2.0 * delta * dr + dx) > 0) + { + x -= delta; + dr += dx; + dx += 2.0 * delta; + } + } + } + + void plot_pen_pixel(double x, double y) + { + if ((x < min_x_) || (x > max_x_)) return; + if ((y < min_y_) || (y > max_y_)) return; + + const int sc_x = static_cast(cart_to_screen_x(x)); + const int sc_y = static_cast(cart_to_screen_y(y)); + + draw_.plot_pen_pixel(sc_x, sc_y); + } + + void plot_pixel(double x, double y) + { + if ((x < min_x_) || (x > max_x_)) return; + if ((y < min_y_) || (y > max_y_)) return; + + const int sc_x = static_cast(cart_to_screen_x(x)); + const int sc_y = static_cast(cart_to_screen_y(y)); + + draw_.plot_pixel(sc_x, sc_y); + } + + void pen_width(const unsigned int& width) + { + draw_.pen_width(width); + } + + void pen_color(const unsigned char& red, + const unsigned char& green, + const unsigned char& blue) + { + draw_.pen_color(red,green,blue); + } + + template + void pen_color(const RGB colour) + { + draw_.pen_color(colour); + } + + const bitmap_image& image() const + { + return image_; + } + + bitmap_image& image() + { + return image_; + } + + void set_widthheight(const double x_length, const double y_length) + { + setup_canvas(x_length, y_length); + } + + double min_x() const { return min_x_; } + double min_y() const { return min_y_; } + double max_x() const { return max_x_; } + double max_y() const { return max_y_; } + +private: + + void setup_canvas(const double x_length, const double y_length) + { + if ((x_length < 2.0) || (y_length < 2.0)) + return; + + width_div2_ = x_length / 2.0; + height_div2_ = y_length / 2.0; + + min_x_ = -width_div2_ ; + min_y_ = -height_div2_; + max_x_ = width_div2_ ; + max_y_ = height_div2_; + + image_.setwidth_height(static_cast(x_length) + 1, static_cast(y_length) + 1); + + image_.clear(0xFF); + } + + double clamp_x(const double& x) + { + if (x < min_x_) return min_x_; + else if (x > max_x_) return max_x_; + else return x; + } + + double clamp_y(const double& y) + { + if (y < min_y_) return min_y_; + else if (y > max_y_) return max_y_; + else return y; + } + + double cart_to_screen_x(const double& x) + { + return x + width_div2_; + } + + double cart_to_screen_y(const double& y) + { + return height_div2_ - y; + } + + enum clip_code + { + e_clip_bottom = 1, + e_clip_top = 2, + e_clip_left = 4, + e_clip_right = 8 + }; + + int out_code( + const double& x, const double& y, + const double& x1, const double& y1, + const double& x2, const double& y2 + ) + { + int result = 0; + if (y < y1) result |= e_clip_bottom; + else if (y > y2) result |= e_clip_top; + + if (x < x1) result |= e_clip_left; + else if (x > x2) result |= e_clip_right; + + return result; + } + + bool clip(double& x1, double& y1, double& x2, double& y2) + { + bool result = false; + double x = 0.0; + double y = 0.0; + + int outcode0 = out_code(x1, y1, min_x_, min_y_, max_x_, max_y_); + int outcode1 = out_code(x2, y2, min_x_, min_y_, max_x_, max_y_); + int outcodeout = 0; + + while ((outcode0 != 0) || (outcode1 != 0)) + { + if ((outcode0 & outcode1) != 0) + return result; + else + { + if (outcode0 != 0) + outcodeout = outcode0; + else + outcodeout = outcode1; + + double dx = (x2 - x1); + double dy = (y2 - y1); + + if ((outcodeout & e_clip_bottom) == e_clip_bottom) + { + x = x1 + dx * (min_y_ - y1) / dy; + y = min_y_; + } + else if ((outcodeout & e_clip_top) == e_clip_top) + { + x = x1 + dx * (max_y_ - y1) / dy; + y = max_y_; + } + else if ((outcodeout & e_clip_right) == e_clip_right) + { + y = y1 + dy * (max_x_ - x1) / dx; + x = max_x_; + } + else if ((outcodeout & e_clip_left) == e_clip_left) + { + y = y1 + dy * (min_x_ - x1) / dx; + x = min_x_; + } + + if (outcodeout == outcode0) + { + x1 = x; + y1 = y; + outcode0 = out_code(x1, y1, min_x_, min_y_, max_x_, max_y_); + } + else + { + x2 = x; + y2 = y; + outcode1 = out_code(x2, y2, min_x_, min_y_, max_x_, max_y_); + } + } + } + + return true; + } + + cartesian_canvas(const cartesian_canvas&); + cartesian_canvas operator=(const cartesian_canvas&); + + double width_div2_; + double height_div2_; + double min_x_; + double min_y_; + double max_x_; + double max_y_; + bitmap_image image_; + image_drawer draw_; +}; + +inline rgb_t convert_wave_length_nm_to_rgb(const double wave_length_nm) +{ + // Credits: Dan Bruton http://www.physics.sfasu.edu/astro/color.html + double red = 0.0; + double green = 0.0; + double blue = 0.0; + + if ((380.0 <= wave_length_nm) && (wave_length_nm <= 439.0)) + { + red = -(wave_length_nm - 440.0) / (440.0 - 380.0); + green = 0.0; + blue = 1.0; + } + else if ((440.0 <= wave_length_nm) && (wave_length_nm <= 489.0)) + { + red = 0.0; + green = (wave_length_nm - 440.0) / (490.0 - 440.0); + blue = 1.0; + } + else if ((490.0 <= wave_length_nm) && (wave_length_nm <= 509.0)) + { + red = 0.0; + green = 1.0; + blue = -(wave_length_nm - 510.0) / (510.0 - 490.0); + } + else if ((510.0 <= wave_length_nm) && (wave_length_nm <= 579.0)) + { + red = (wave_length_nm - 510.0) / (580.0 - 510.0); + green = 1.0; + blue = 0.0; + } + else if ((580.0 <= wave_length_nm) && (wave_length_nm <= 644.0)) + { + red = 1.0; + green = -(wave_length_nm - 645.0) / (645.0 - 580.0); + blue = 0.0; + } + else if ((645.0 <= wave_length_nm) && (wave_length_nm <= 780.0)) + { + red = 1.0; + green = 0.0; + blue = 0.0; + } + + double factor = 0.0; + + if ((380.0 <= wave_length_nm) && (wave_length_nm <= 419.0)) + factor = 0.3 + 0.7 * (wave_length_nm - 380.0) / (420.0 - 380.0); + else if ((420.0 <= wave_length_nm) && (wave_length_nm <= 700.0)) + factor = 1.0; + else if ((701.0 <= wave_length_nm) && (wave_length_nm <= 780.0)) + factor = 0.3 + 0.7 * (780.0 - wave_length_nm) / (780.0 - 700.0); + else + factor = 0.0; + + rgb_t result; + + const double gamma = 0.8; + const double intensity_max = 255.0; + + #define round(d) std::floor(d + 0.5) + + result.red = static_cast((red == 0.0) ? red : round(intensity_max * std::pow(red * factor, gamma))); + result.green = static_cast((green == 0.0) ? green : round(intensity_max * std::pow(green * factor, gamma))); + result.blue = static_cast((blue == 0.0) ? blue : round(intensity_max * std::pow(blue * factor, gamma))); + + #undef round + + return result; +} + +inline double weighted_distance(const unsigned char r0, const unsigned char g0, const unsigned char b0, + const unsigned char r1, const unsigned char g1, const unsigned char b1) +{ + const double diff_r = /*0.30 */ (r0 - r1); + const double diff_g = /*0.59 */ (g0 - g1); + const double diff_b = /*0.11 */ (b0 - b1); + + return std::sqrt((diff_r * diff_r) + (diff_g * diff_g) + (diff_b * diff_b)); +} + +inline double weighted_distance(const rgb_t c0, const rgb_t c1) +{ + return weighted_distance(c0.red, c0.green, c0.blue, + c1.red, c1.green, c1.blue); +} + +template +inline rgb_t find_nearest_color(const rgb_t& c, const Iterator begin, const Iterator end) +{ + if (0 == std::distance(begin,end)) + return c; + + double min_d = std::numeric_limits::max(); + rgb_t result = *begin; + + for (Iterator itr = begin; itr != end; ++itr) + { + if (c == (*itr)) + { + return (*itr); + } + + double curr_d = weighted_distance(c,*itr); + + if (curr_d < min_d) + { + min_d = curr_d; + result = *itr; + } + } + + return result; +} + +template