#include "image.h" #include #include #include #include #include #include "logger.h" namespace image { template void Image::fill_color(T color[]) { for (int c = 0; c < d_channels; c++) for (size_t i = 0; i < d_width * d_height; i++) channel(c)[i] = color[c]; } template void Image::fill(T val) { for (int c = 0; c < d_channels; c++) for (size_t i = 0; i < d_width * d_height; i++) channel(c)[i] = val; } template void Image::mirror(bool x, bool y) { if (y) // Mirror on the Y axis { T *tmp_col = new T[d_height]; for (int c = 0; c < d_channels; c++) { for (size_t col = 0; col < d_width; col++) { for (size_t i = 0; i < d_height; i++) // Buffer column tmp_col[i] = channel(c)[i * d_width + col]; for (size_t i = 0; i < d_height; i++) // Restore and mirror channel(c)[i * d_width + col] = tmp_col[(d_height - 1) - i]; } } delete[] tmp_col; } if (x) // Mirror on the X axis { T *tmp_row = new T[d_width]; for (int c = 0; c < d_channels; c++) { for (size_t row = 0; row < d_height; row++) { for (size_t i = 0; i < d_width; i++) // Buffer column tmp_row[i] = channel(c)[row * d_width + i]; for (size_t i = 0; i < d_width; i++) // Restore and mirror channel(c)[row * d_width + i] = tmp_row[(d_width - 1) - i]; } } delete[] tmp_row; } } template Image &Image::equalize(bool per_channel) { for (int c = 0; c < (per_channel ? channels() : 1); c++) { if (c == 3) //Do not individual equalize alpha channel break; T *data_ptr = channel(c); int nlevels = std::numeric_limits::max() + 1; int size = d_width * d_height * (per_channel ? 1 : d_channels); // Init histogram buffer int *histogram = new int[nlevels]; for (int i = 0; i < nlevels; i++) histogram[i] = 0; // Compute histogram for (int px = 0; px < size; px++) histogram[data_ptr[px]]++; // Cummulative histogram int *cummulative_histogram = new int[nlevels]; cummulative_histogram[0] = histogram[0]; for (int i = 1; i < nlevels; i++) cummulative_histogram[i] = histogram[i] + cummulative_histogram[i - 1]; // Scaling int *scaling = new int[nlevels]; for (int i = 0; i < nlevels; i++) scaling[i] = round(cummulative_histogram[i] * (float(std::numeric_limits::max()) / size)); // Apply for (int px = 0; px < size; px++) data_ptr[px] = clamp(scaling[data_ptr[px]]); // Cleanup delete[] cummulative_histogram; delete[] scaling; delete[] histogram; } return *this; } template Image &Image::normalize() { int max = 0; int min = std::numeric_limits::max(); // Get min and max for (size_t i = 0; i < data_size; i++) { int val = d_data[i]; if (val > max) max = val; if (val < min) min = val; } if (abs(max - min) == 0) // Avoid division by 0 return *this; // Compute scaling factor float factor = std::numeric_limits::max() / float(max - min); // Scale entire image for (size_t i = 0; i < data_size; i++) d_data[i] = clamp((d_data[i] - min) * factor); return *this; } template void Image::crop(int x0, int y0, int x1, int y1) { int new_width = x1 - x0; int new_height = y1 - y0; // Create new buffer T *new_data = new T[new_width * new_height * d_channels]; // Copy cropped area to new region for (int c = 0; c < d_channels; c++) for (int x = 0; x < new_width; x++) for (int y = 0; y < new_height; y++) new_data[(new_width * new_height * c) + y * new_width + x] = channel(c)[(y0 + y) * d_width + (x + x0)]; // Swap out buffer delete[] d_data; d_data = new_data; // Update info data_size = new_width * new_height * d_channels; d_width = new_width; d_height = new_height; } template void Image::crop(int x0, int x1) { crop(x0, 0, x1, d_height); } template Image Image::crop_to(int x0, int y0, int x1, int y1) { int new_width = x1 - x0; int new_height = y1 - y0; // Create new buffer Image new_data(new_width, new_height, d_channels); // Copy cropped area to new region for (int c = 0; c < d_channels; c++) for (int x = 0; x < new_width; x++) for (int y = 0; y < new_height; y++) new_data[(new_width * new_height * c) + y * new_width + x] = channel(c)[(y0 + y) * d_width + (x + x0)]; return new_data; } template Image Image::crop_to(int x0, int x1) { return crop_to(x0, 0, x1, d_height); } template void Image::resize(int width, int height) { double x_scale = double(d_width) / double(width); double y_scale = double(d_height) / double(height); Image tmp = *this; init(width, height, d_channels); for (int c = 0; c < d_channels; c++) { for (size_t x = 0; x < d_width; x++) { for (size_t y = 0; y < d_height; y++) { int xx = floor(double(x) * x_scale); int yy = floor(double(y) * y_scale); channel(c)[y * d_width + x] = tmp.channel(c)[yy * tmp.width() + xx]; } } } } template Image Image::resize_to(int width, int height) { double x_scale = double(d_width) / double(width); double y_scale = double(d_height) / double(height); Image ret(width, height, d_channels); for (int c = 0; c < d_channels; c++) { for (size_t x = 0; x < (size_t)width; x++) { for (size_t y = 0; y < (size_t)height; y++) { int xx = floor(double(x) * x_scale); int yy = floor(double(y) * y_scale); ret.channel(c)[y * ret.width() + x] = channel(c)[yy * d_width + xx]; } } } return ret; } template void Image::resize_bilinear(int width, int height, bool text_mode) { int a = 0, b = 0, c = 0, d = 0, x = 0, y = 0; size_t index; double x_scale = double(d_width - 1) / double(width); double y_scale = double(d_height - 1) / double(height); float x_diff, y_diff, val; Image tmp = *this; init(width, height, d_channels); size_t max_index = tmp.width() * tmp.height(); for (int cc = 0; cc < d_channels; cc++) { for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { x = (int)(x_scale * j); y = (int)(y_scale * i); x_diff = (x_scale * j) - x; y_diff = (y_scale * i) - y; index = (y * tmp.width() + x); a = tmp.channel(cc)[index]; if (index + 1 < max_index) b = tmp.channel(cc)[index + 1]; if (index + tmp.width() < max_index) c = tmp.channel(cc)[index + tmp.width()]; if (index + tmp.width() + 1 < max_index) d = tmp.channel(cc)[index + tmp.width() + 1]; val = a * (1 - x_diff) * (1 - y_diff) + b * (x_diff) * (1 - y_diff) + c * (y_diff) * (1 - x_diff) + d * (x_diff * y_diff); if (text_mode) // Special text mode, where we want to keep it clear whatever the res is channel(cc)[i * width + j] = val > 0 ? std::numeric_limits::max() : 0; else channel(cc)[i * width + j] = val; } } } } template int percentile(T *array, int size, float percentile) { float number_percent = (size + 1) * percentile / 100.0f; if (number_percent == 1) return array[0]; else if (number_percent == size) return array[size - 1]; else return array[(int)number_percent - 1] + (number_percent - (int)number_percent) * (array[(int)number_percent] - array[(int)number_percent - 1]); } template void Image::white_balance(float percentileValue) { float maxVal = std::numeric_limits::max(); T *sorted_array = new T[d_height * d_width]; for (int c = 0; c < d_channels; c++) { // Load the whole image band into our array std::memcpy(sorted_array, channel(c), d_width * d_height * sizeof(T)); // Sort it std::sort(&sorted_array[0], &sorted_array[d_width * d_height]); // Get percentiles int percentile1 = percentile(sorted_array, d_width * d_height, percentileValue); int percentile2 = percentile(sorted_array, d_width * d_height, 100.0f - percentileValue); for (size_t i = 0; i < d_width * d_height; i++) { long balanced = (channel(c)[i] - percentile1) * maxVal / (percentile2 - percentile1); if (balanced < 0) balanced = 0; else if (balanced > maxVal) balanced = maxVal; channel(c)[i] = balanced; } } delete[] sorted_array; } template void Image::brightness_contrast_old(float brightness, float contrast) { float brightness_v = brightness / 2.0f; float slant = tanf((contrast + 1.0f) * 0.78539816339744830961566084581987572104929234984378f); const float max = std::numeric_limits::max(); for (size_t i = 0; i < data_size; i++) { float v = d_data[i]; if (brightness_v < 0.0f) v = v * (max + brightness_v); else v = v + ((max - v) * brightness_v); v = (v - (max / 2)) * slant + (max / 2); d_data[i] = clamp(v * 2.0f); } } template void Image::linear_invert() { for (size_t i = 0; i < data_size; i++) d_data[i] = std::numeric_limits::max() - d_data[i]; } template void Image::simple_despeckle(int thresold) { for (int c = 0; c < d_channels; c++) { T *data_ptr = channel(c); int h = d_height; int w = d_width; for (int x = 0; x < h; x++) { for (int y = 0; y < w; y++) { unsigned short current = data_ptr[x * w + y]; unsigned short below = x + 1 == h ? 0 : data_ptr[(x + 1) * w + y]; unsigned short left = y - 1 == -1 ? 0 : data_ptr[x * w + (y - 1)]; unsigned short right = y + 1 == w ? 0 : data_ptr[x * w + (y + 1)]; if ((current - left > thresold && current - right > thresold) || (current - below > thresold && current - right > thresold)) { data_ptr[x * w + y] = (right + left) / 2; } } } } } template void Image::median_blur() { for (int c = 0; c < d_channels; c++) { T *data_ptr = channel(c); int h = d_height; int w = d_width; std::vector values(5); for (int x = 0; x < h; x++) { for (int y = 0; y < w; y++) { values[0] = values[1] = values[2] = values[3] = values[4] = data_ptr[x * w + y]; if (x != 0) values[1] = data_ptr[(x - 1) * w + y]; if (y != 0) values[2] = data_ptr[x * w + (y - 1)]; if (x != h - 1) values[3] = data_ptr[(x + 1) * w + y]; if (y != w - 1) values[4] = data_ptr[x * w + (y + 1)]; std::sort(values.begin(), values.end()); data_ptr[x * w + y] = values[2]; } } } } template void Image::kuwahara_filter() { const int radius = 1; const float num_pixels = (float)((radius + 1) * (radius + 1)); Image tmp = *this; init(d_width, d_height, d_channels); for (int c = 0; c < d_channels; c++) { #pragma omp parallel for for (int64_t y = 0; y < (int64_t)d_height; y++) { for (size_t x = 0; x < d_width; x++) { float average[4] = { 0 }; float variance[4] = { 0 }; // Calculate values for the four regions for (int j = -radius; j <= 0; ++j) for (int i = -radius; i <= 0; ++i) average[0] += wraparound_read(tmp.channel(c), x + i, y + j); average[0] /= num_pixels; for (int j = -radius; j <= 0; ++j) for (int i = -radius; i <= 0; ++i) variance[0] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[0], 2); for (int j = -radius; j <= 0; ++j) for (int i = 0; i <= radius; ++i) average[1] += wraparound_read(tmp.channel(c), x + i, y + j); average[1] /= num_pixels; for (int j = -radius; j <= 0; ++j) for (int i = 0; i <= radius; ++i) variance[1] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[1], 2); for (int j = 0; j <= radius; ++j) for (int i = 0; i <= radius; ++i) average[2] += wraparound_read(tmp.channel(c), x + i, y + j); average[2] /= num_pixels; for (int j = 0; j <= radius; ++j) for (int i = 0; i <= radius; ++i) variance[2] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[2], 2); for (int j = 0; j <= radius; ++j) for (int i = -radius; i <= 0; ++i) average[3] += wraparound_read(tmp.channel(c), x + i, y + j); average[3] /= num_pixels; for (int j = 0; j <= radius; ++j) for (int i = -radius; i <= 0; ++i) variance[3] += pow(wraparound_read(tmp.channel(c), x + i, y + j) - average[3], 2); // Find the region with the smallest variance and use its mean as the new pixel value float min_sigma2 = FLT_MAX; for (int k = 0; k < 4; k++) { variance[k] /= num_pixels - 1; // Find Sigma 2 if (variance[k] < 0) variance[k] = -variance[k]; if (variance[k] < min_sigma2) { min_sigma2 = variance[k]; channel(c)[y * d_width + x] = (T)average[k]; } } } } } } template T Image::wraparound_read(T* c, int x, int y) { if (x < 0) x += d_width; if (y < 0) y += d_height; if (x >= (int)d_width) x -= d_width; if (y >= (int)d_height) y -= d_height; return c[y * d_width + x]; } // Generate Images for uint16_t and uint8_t template class Image; template class Image; }