156 lines
8.5 KiB
C++
156 lines
8.5 KiB
C++
#pragma once
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <memory_resource>
|
|
#include <vector>
|
|
#include "../Axis/Abs_Axis_p.h"
|
|
#include "../base/Frame_Memory.h"
|
|
#include "../base/Color.h"
|
|
#include "../base/Geometry.h"
|
|
#include "../render/Image.h"
|
|
#include "Interpolation.h"
|
|
namespace renderive {
|
|
struct Visible_Sample_Window {
|
|
double first_index{};
|
|
double last_index{};
|
|
int sample_count{};
|
|
explicit operator bool() const {
|
|
return sample_count > 0;
|
|
}
|
|
};
|
|
inline bool intersect_range(const Range& data_range, const Range& clip_range, Range& result) {
|
|
double data_min = std::min(data_range.origin, data_range.target);
|
|
double data_max = std::max(data_range.origin, data_range.target);
|
|
double clip_min = std::min(clip_range.origin, clip_range.target);
|
|
double clip_max = std::max(clip_range.origin, clip_range.target);
|
|
double lower = std::max(data_min, clip_min);
|
|
double upper = std::min(data_max, clip_max);
|
|
if (lower > upper)
|
|
return false;
|
|
result = data_range.origin <= data_range.target ? Range{lower, upper} : Range{upper, lower};
|
|
return true;
|
|
}
|
|
inline double source_index_to_coordinate(const Range& data_range, int total_count, double index) {
|
|
return data_range.origin + data_range.length() * index / static_cast<double>(total_count - 1);
|
|
}
|
|
inline double coordinate_to_source_index(const Range& data_range, int total_count, double coord) {
|
|
return static_cast<double>(total_count - 1) * (coord - data_range.origin) / data_range.length();
|
|
}
|
|
inline Visible_Sample_Window compute_visible_sample_window(const Axis_Frame_Snapshot& axis, const Range& data_range, int total_count, int available_count, bool visible_range_only) {
|
|
if (total_count < 2 || available_count < 1 || data_range.length() == 0.0)
|
|
return {};
|
|
Range available_range{data_range.origin, source_index_to_coordinate(data_range, total_count, available_count - 1)};
|
|
Range sample_range = available_range;
|
|
if (visible_range_only && !intersect_range(available_range, axis.coord_range, sample_range))
|
|
return {};
|
|
double first_index = coordinate_to_source_index(data_range, total_count, sample_range.origin);
|
|
double last_index = coordinate_to_source_index(data_range, total_count, sample_range.target);
|
|
double first_pixel = axis.coord_to_pixel(sample_range.origin);
|
|
double last_pixel = axis.coord_to_pixel(sample_range.target);
|
|
int source_samples = static_cast<int>(std::ceil(std::abs(last_index - first_index))) + 1;
|
|
int pixel_samples = static_cast<int>(std::ceil(std::abs(last_pixel - first_pixel))) + 1;
|
|
pixel_samples = std::min(pixel_samples, axis.pixel_size() + 1);
|
|
return {first_index, last_index, std::max(source_samples, pixel_samples)};
|
|
}
|
|
inline Visible_Sample_Window compute_visible_sample_window(Abs_Axis* axis, const Range& data_range, int total_count, int available_count, bool visible_range_only) {
|
|
return compute_visible_sample_window(Axis_Render_Access::snapshot(axis), data_range, total_count, available_count, visible_range_only);
|
|
}
|
|
template <typename Value_At>
|
|
double interpolate_sample_value(double index, int count, Line_Interpolation_Mode mode, Value_At value_at) {
|
|
index = std::clamp(index, 0.0, static_cast<double>(count - 1));
|
|
int left = static_cast<int>(std::floor(index));
|
|
int right = std::min(left + 1, count - 1);
|
|
double t = index - left;
|
|
double left_value = value_at(left);
|
|
double right_value = value_at(right);
|
|
switch (mode) {
|
|
case Line_Interpolation_Mode::Nearest_Sample:
|
|
return value_at(static_cast<int>(std::round(index)));
|
|
case Line_Interpolation_Mode::Linear_Value:
|
|
return left_value + (right_value - left_value) * t;
|
|
case Line_Interpolation_Mode::Linear_Power_Domain: {
|
|
double left_power = std::pow(10.0, left_value / 10.0);
|
|
double right_power = std::pow(10.0, right_value / 10.0);
|
|
return 10.0 * std::log10(left_power + (right_power - left_power) * t);
|
|
}
|
|
case Line_Interpolation_Mode::Step_Left:
|
|
return left_value;
|
|
case Line_Interpolation_Mode::Step_Right:
|
|
return t == 0.0 ? left_value : right_value;
|
|
case Line_Interpolation_Mode::Cubic_Value: {
|
|
double p0 = value_at(std::max(left - 1, 0));
|
|
double p1 = left_value;
|
|
double p2 = right_value;
|
|
double p3 = value_at(std::min(right + 1, count - 1));
|
|
double a = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3;
|
|
double b = p0 - 2.5 * p1 + 2.0 * p2 - 0.5 * p3;
|
|
double c = -0.5 * p0 + 0.5 * p2;
|
|
return ((a * t + b) * t + c) * t + p1;
|
|
}
|
|
}
|
|
return left_value;
|
|
}
|
|
template <typename Value_At>
|
|
std::pmr::vector<PointF> build_interpolated_line_points(const Axis_Mapping_2D& mapping, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at, std::pmr::memory_resource* resource = frame_memory_resource()) {
|
|
Visible_Sample_Window window = compute_visible_sample_window(mapping.domain, data_range, total_count, available_count, visible_range_only);
|
|
if (!window)
|
|
return {};
|
|
std::pmr::vector<PointF> points(window.sample_count, resource);
|
|
double denominator = static_cast<double>(std::max(window.sample_count - 1, 1));
|
|
for (int i = 0; i < window.sample_count; ++i) {
|
|
double index = window.first_index + (window.last_index - window.first_index) * static_cast<double>(i) / denominator;
|
|
double coord = source_index_to_coordinate(data_range, total_count, index);
|
|
double value = interpolate_sample_value(index, available_count, mode, value_at);
|
|
points[i] = mapping.map(coord, value);
|
|
}
|
|
return points;
|
|
}
|
|
template <typename Value_At>
|
|
std::pmr::vector<PointF> build_interpolated_line_points(Abs_Axis* domain_axis, Abs_Axis* value_axis, const Range& data_range, int total_count, int available_count, bool visible_range_only, Line_Interpolation_Mode mode, Value_At value_at) {
|
|
return build_interpolated_line_points(Axis_Render_Access::mapping(domain_axis, value_axis), data_range, total_count, available_count, visible_range_only, mode, value_at, frame_memory_resource());
|
|
}
|
|
inline double cubic_channel(double p0, double p1, double p2, double p3, double t) {
|
|
double a = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3;
|
|
double b = p0 - 2.5 * p1 + 2.0 * p2 - 0.5 * p3;
|
|
double c = -0.5 * p0 + 0.5 * p2;
|
|
return ((a * t + b) * t + c) * t + p1;
|
|
}
|
|
inline Pixel bicubic_pixel(const Image& source, double x, double y) {
|
|
int x1 = static_cast<int>(std::floor(x));
|
|
int y1 = static_cast<int>(std::floor(y));
|
|
double tx = x - x1;
|
|
double ty = y - y1;
|
|
double rows[4][4]{};
|
|
for (int row = -1; row <= 2; ++row) {
|
|
int source_y = std::clamp(y1 + row, 0, source.height() - 1);
|
|
const Pixel* source_line = source.row(source_y);
|
|
Pixel pixels[4]{};
|
|
for (int col = -1; col <= 2; ++col) {
|
|
int source_x = std::clamp(x1 + col, 0, source.width() - 1);
|
|
pixels[col + 1] = source_line[source_x];
|
|
}
|
|
rows[0][row + 1] = cubic_channel((pixels[0] >> 24) & 0xFF, (pixels[1] >> 24) & 0xFF, (pixels[2] >> 24) & 0xFF, (pixels[3] >> 24) & 0xFF, tx);
|
|
rows[1][row + 1] = cubic_channel((pixels[0] >> 16) & 0xFF, (pixels[1] >> 16) & 0xFF, (pixels[2] >> 16) & 0xFF, (pixels[3] >> 16) & 0xFF, tx);
|
|
rows[2][row + 1] = cubic_channel((pixels[0] >> 8) & 0xFF, (pixels[1] >> 8) & 0xFF, (pixels[2] >> 8) & 0xFF, (pixels[3] >> 8) & 0xFF, tx);
|
|
rows[3][row + 1] = cubic_channel(pixels[0] & 0xFF, pixels[1] & 0xFF, pixels[2] & 0xFF, pixels[3] & 0xFF, tx);
|
|
}
|
|
double channels[4]{};
|
|
for (int channel = 0; channel < 4; ++channel) {
|
|
channels[channel] = std::clamp(cubic_channel(rows[channel][0], rows[channel][1], rows[channel][2], rows[channel][3], ty), 0.0, 255.0);
|
|
}
|
|
return pack_rgba(static_cast<std::uint8_t>(channels[1]), static_cast<std::uint8_t>(channels[2]), static_cast<std::uint8_t>(channels[3]), static_cast<std::uint8_t>(channels[0]));
|
|
}
|
|
inline Image bicubic_scale(const Image& source, const RectF& source_rect, const Size& target_size) {
|
|
Image target(target_size.width, target_size.height);
|
|
for (int y = 0; y < target.height(); ++y) {
|
|
Pixel* line = target.row(y);
|
|
double source_y = source_rect.y + (static_cast<double>(y) + 0.5) * source_rect.height / target.height() - 0.5;
|
|
for (int x = 0; x < target.width(); ++x) {
|
|
double source_x = source_rect.x + (static_cast<double>(x) + 0.5) * source_rect.width / target.width() - 0.5;
|
|
line[x] = bicubic_pixel(source, source_x, source_y);
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
} // namespace renderive
|