/* * (c) Copyright Ascensio System SIA 2010-2023 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) * version 3 as published by the Free Software Foundation. In accordance with * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect * that Ascensio System SIA expressly excludes the warranty of non-infringement * of any third-party rights. * * This program is distributed WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under * Section 5 of the GNU AGPL version 3. * * Pursuant to Section 7(b) of the License you must retain the original Product * logo when distributing the program. Pursuant to Section 7(e) we decline to * grant you any rights under trademark law for use of our trademarks. * * All the Product's GUI elements, including illustrations and icon sets, as * well as technical writing content are licensed under the terms of the * Creative Commons Attribution-ShareAlike 4.0 International. See the License * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ #include #include #include #include "../agg-2.4/include/agg_color_rgba.h" #include "../graphics/aggplustypes.h" #include "../graphics/Matrix.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #ifndef SHADING_INFO #define SHADING_INFO namespace NSStructures { /** * * */ template class ColorFunction /** * Реализацию произвольной функции в рантайме я решил сделать как массив, тк так проще всего * я еще не совсем понял как точно передается в пдфе функция, но такая реализация, позволяет пользователю * выбрать любой способ. * * Пока все копируется, т.к. в большинсве случаев вектор 2кБ по размеру и проблем нету * только если использовать двумерную функцию размер возрастает до МБ, но не хочется возится с укузателями * ради этого, т.к. судя по всему случай исключительный(только 1 шейдинг требует такую функцию). * Если надо будет, наверно можно будет переписать на юникптр. * * Есть возможность выставить обычную линейную интерполяцию, просто для тестирования * + так реализуется градиент стандартный. * * Пока у меня конструкторы по умолчанию, чтото заполняют, для тестирования опятьже * потом стоит все убрать, чтобы в кисти не таскать это все, когда оно не нужно, * если не выделять память то там в сумме будет <100B гдето, не думаю, что это будет так много, * чтобы писать отдельный интерфейс для кисти * * Плюс я вообще не знаю как обрабатывать, внештатные ситуации, в целом, можно вообще просто * эксепшены кидать если что или ничего не делать. */ { public: ColorFunction() : RESOLUTION(0), x_domain_min(0.0f), x_domain_max(0.0f) { } ColorFunction(size_t res, float xmin, float xmax) : RESOLUTION(res), x_domain_min(xmin), x_domain_max(xmax) { values = std::vector>(1, std::vector(RESOLUTION)); for (size_t i = 0; i < RESOLUTION; i++) { unsigned int value = (unsigned int)(255 - (255 * ((float)i / RESOLUTION))); values[0][i] = ColorT(value, value, value); } } ColorFunction(size_t res, float xmin, float xmax, float ymin, float ymax) : RESOLUTION(res), x_domain_min(xmin), x_domain_max(xmax), y_domain_min(ymin), y_domain_max(ymax) { values = std::vector>(RESOLUTION, std::vector(RESOLUTION)); for (size_t i = 0; i < RESOLUTION; i++) { for (size_t j = 0; j < RESOLUTION; j++) { unsigned int value = (unsigned int)(255 * sin(i * j * M_PI / RESOLUTION)); values[j][i] = ColorT(255, 0, 0, 255); } } } void set_x_min(float x_min) { x_domain_min = x_min; } void set_x_max(float x_max) { x_domain_max = x_max; } void set_y_min(float y_min) { y_domain_min = y_min; } void set_y_max(float y_max) { y_domain_max = y_max; } float get_x_min() { return x_domain_min; } float get_x_max() { return x_domain_max; } float get_y_min() { return y_domain_min; } float get_y_max() { return y_domain_max; } ColorT get_color(float x) { int index = get_x_index(x); return values[0][index]; } //used only in shading type 1 ColorT get_color(float x, float y) { int xi = get_x_index(x); int yi = get_y_index(y); //std::cout << x << ' ' << y << std::endl; return values[yi][xi]; } void set_color(float x, int r, int g, int b, int a) { int index = get_x_index(x); // pls dont set color out of bounds, it wont crush, but will work not as you max expected values[0][index].r = r; values[0][index].g = g; values[0][index].b = b; values[0][index].a = a; } void set_color(float x, float y, int r, int g, int b, int a) { int xindex = get_x_index(x); int yindex = get_y_index(y); values[yindex][xindex].r = r; values[yindex][xindex].g = g; values[yindex][xindex].b = b; values[yindex][xindex].a = a; } void set_color(size_t xindex, int r, int g, int b, int a) { values[0][xindex].r = r; values[0][xindex].g = g; values[0][xindex].b = b; values[0][xindex].a = a; } void set_color(size_t xindex, size_t yindex, int r, int g, int b, int a) { values[yindex][xindex].r = r; values[yindex][xindex].g = g; values[yindex][xindex].b = b; values[yindex][xindex].a = a; } // position must be sorted by incr ub otherwise // only for 1 in function int set_linear_interpolation(const std::vector &colors, const std::vector &positions) { if (colors.size() != positions.size()) { return -1; // error } std::vector indexes; for (float x : positions) { indexes.push_back(get_x_index(x)); } for (int i = 0; i < colors.size(); i++) { values[0][indexes[i]].r = hex2r(colors[i]); values[0][indexes[i]].g = hex2g(colors[i]); values[0][indexes[i]].b = hex2b(colors[i]); values[0][indexes[i]].a = hex2a(colors[i]); } for (int i = 0; i < positions.size() - 1; i++) { interpolate_indexes(indexes[i], indexes[i + 1]); } return 0; } size_t get_resolution() const { return RESOLUTION; } private: size_t RESOLUTION; std::vector> values; float x_domain_min, x_domain_max; float y_domain_min, y_domain_max; int get_x_index(float x) { int x_index = (int)(RESOLUTION - 1) * (x - x_domain_min) / (x_domain_max - x_domain_min); if (x_index < 0) return 0; if (x_index > RESOLUTION - 1) return RESOLUTION - 1; return x_index; } int get_y_index(float y) { int y_index = (int)(RESOLUTION - 1) * (y - y_domain_min) / (y_domain_max - y_domain_min); if (values.size() < RESOLUTION) { return 0; } if (y_index < 0) return 0; if (y_index > RESOLUTION - 1) return RESOLUTION - 1; return y_index; } /** * Линейная интерполяция для построения цветовой функции. */ int interpolate_indexes(size_t first, size_t second, size_t line = 0) { size_t len = second - first; ColorT f = values[line][first]; ColorT s = values[line][second]; for(size_t i = first + 1; i < second; i++) { values[line][i].r = f.r * (1 - (float)(i - first) / len ) + s.r * ((float)(i - first) / len ); values[line][i].g = f.g * (1 - (float)(i - first) / len ) + s.g * ((float)(i - first) / len ); ; values[line][i].b = f.b * (1 - (float)(i - first) / len ) + s.b * ((float)(i - first) / len ); ; values[line][i].a = f.a * (1 - (float)(i - first) / len ) + s.a * ((float)(i - first) / len ); ; } return 0; } unsigned int hex2a(uint32_t c) { unsigned int a = (c >> 24) & 0xFF; return a; } unsigned int hex2r(uint32_t c) { return (c >> 16) & 0xFF; } unsigned int hex2g(uint32_t c) { return (c >> 8) & 0xFF; } unsigned int hex2b(uint32_t c) { return c & 0xFF; } }; struct Point { Point():x(0),y(0){} Point(const float& _x, const float& _y):x(_x),y(_y){} Point(const int& _x, const int& _y):x((float)_x),y((float)_y){} Point(const double& _x, const double& _y):x((float)_x),y((float)_y){} float x, y; Point& operator+=(const Point &a) { x += a.x; y += a.y; return *this; } /** * Костыль от ошибок линковки. Чтобы время не терять пока что. */ friend Point operator*(const Point &a, float t) { return Point(a.x * t, a.y * t); } friend Point operator*(float t, const Point &a) { return Point(a.x * t, a.y * t); } friend Point operator+(const Point &a, const Point &b) { return Point(a.x + b.x, a.y + b.y); } friend Point operator-(const Point &a, const Point &b) { return Point(a.x - b.x, a.y - b.y); } }; /** * Тут хранится информация спецефичная для рендера ПДФ. * * Взял новую реализацию преобразований, т.к. готовая была на даблах, * а в такой точности смысла нету особо. * * Для шейдеров требуется поддерживать два способа вычисления (с параметром и без), * поэтому требуется много дополнительной инфы. * * Так же шейдер будет получать, в качетве параметров, границы, тут я пока не решил, вообще * можно оставить соблюдение границ, на откуп пользователю, т.к. все равно заполенение в конечном итоге будет * выполняться с помощью рисования замкнутого пути и команды Fill * */ struct ShadingInfo { public: ShadingInfo() : shading_type(Parametric), f_type(UseNew), inv_map(6){} enum ShadingType { FunctionOnly, Parametric, TriangleInterpolation, CurveInterpolation, TensorCurveInterpolation } shading_type; // if UseOld old function is called, look for IGraphicsRender.put_BrushGradientColors; enum ColorFunctionType { UseOld, UseNew } f_type; ColorFunction function; // Обратное преобразование из картинки в цветовую функцию std::vector mapping; std::vector inv_map; // Линейный градиент задается в pdf 2 точками bool set_two_points; Point point1, point2; // triangle shading std::vector triangle; std::vector triangle_colors; std::vector triangle_parameters; /** * Матрица 4 на 4 заполняется как в документации к пдф 7 тип * Если выбран тип 6 то значения (1,2) (2,1) (1,1) (2,2) * В массиве игнормруется и заполняются автоматически, следите за переданным типом градинта * (Нумерация от нуля) * * Наверное напишу адаптор который переводит порядок точек из 6 типа в общий. */ agg::rgba8 fill_color; std::vector> patch; std::vector> patch_colors; // 2 на 2 цвета по углам std::vector> patch_parameters; // 2 на 2 параметра }; // Containing additional info about gradient struct GradientInfo { GradientInfo() : littleRadius(0.0f), largeRadius(1.0f), centerX(0.0f), centerY(0.0f), angle(0.0f), discrete_step(0.0f), reflected(false), periods(0.5f), periodic(false), xsize(1.0f), ysize(1.0f), linstretch(1.0f), linoffset(0.0f), continue_shading_f(false), continue_shading_b(false), luminocity(false) { } void setAngleDegrees(float deg) { angle = deg / 180.f * (float)M_PI; } float getAngleDegrees() const { return angle / (float)M_PI * 180.f; } void setStepByNum(int n) // recommended to use { discrete_step = 1.0f / n; } bool checkLuminosity() const { if (shading.patch_colors.empty()) return false; for (const auto& ar : shading.patch_colors) for (const auto& c : ar) if (c.r != c.g || c.g != c.b) return false; return true; } bool colorEqual(const agg::rgba8& c1, const agg::rgba8& c2) const { return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c2.a; } bool isOneColor() const { switch (shading.shading_type) { case ShadingInfo::FunctionOnly: case ShadingInfo::Parametric: return false; case ShadingInfo::TriangleInterpolation: return colorEqual(shading.triangle_colors[0], shading.triangle_colors[1]) && colorEqual(shading.triangle_colors[1], shading.triangle_colors[2]); case ShadingInfo::CurveInterpolation: case ShadingInfo::TensorCurveInterpolation: return colorEqual(shading.patch_colors[0][0], shading.patch_colors[0][1]) && colorEqual(shading.patch_colors[0][1], shading.patch_colors[1][0]) && colorEqual(shading.patch_colors[1][0], shading.patch_colors[1][1]); default: return false; } } agg::rgba8 getFillColor() const { if (!shading.triangle_colors.empty()) return shading.triangle_colors[0]; if (!shading.patch_colors.empty()) return shading.patch_colors[0][0]; return agg::rgba8(0, 0, 0); } void setFillColor(const agg::rgba8& color) { shading.fill_color = color; luminocity = true; } void transform(const Aggplus::CMatrix& matrix) { // shading transform auto& point1 = shading.point1; auto& point2 = shading.point2; double point1_x = static_cast(point1.x); double point1_y = static_cast(point1.y); double point2_x = static_cast(point2.x); double point2_y = static_cast(point2.y); matrix.TransformPoint(point1_x, point1_y); matrix.TransformPoint(point2_x, point2_y); point1.x = static_cast(point1_x); point1.y = static_cast(point1_y); point2.x = static_cast(point2_x); point2.y = static_cast(point2_y); // triangle transform for (auto& p : shading.triangle) { double triangle_x = static_cast(p.x); double triangle_y = static_cast(p.y); matrix.TransformPoint(triangle_x, triangle_y); p.x = static_cast(triangle_x); p.y = static_cast(triangle_y); } // domains transform double x_domain_min = static_cast(shading.function.get_x_min()); double y_domain_min = static_cast(shading.function.get_y_min()); double x_domain_max = static_cast(shading.function.get_x_max()); double y_domain_max = static_cast(shading.function.get_y_max()); matrix.TransformPoint(x_domain_min, y_domain_min); matrix.TransformPoint(x_domain_max, y_domain_max); shading.function.set_x_min(static_cast(x_domain_min)); shading.function.set_y_min(static_cast(y_domain_min)); shading.function.set_x_max(static_cast(x_domain_max)); shading.function.set_y_max(static_cast(y_domain_max)); // center transform double center_x = static_cast(centerX); double center_y = static_cast(centerY); matrix.TransformPoint(center_x, center_y); double p0_x = static_cast(p0.x); double p0_y = static_cast(p0.y); double p1_x = static_cast(p1.x); double p1_y = static_cast(p1.y); matrix.TransformPoint(p0_x, p0_y); matrix.TransformPoint(p1_x, p1_y); p0.x = static_cast(p0_x); p0.y = static_cast(p0_y); p1.x = static_cast(p1_x); p1.y = static_cast(p1_y); for (size_t i = 0; i < shading.patch.size(); ++i) { for (size_t j = 0; j < shading.patch[i].size(); ++j) { double patch_x = static_cast(shading.patch[i][j].x); double patch_y = static_cast(shading.patch[i][j].y); matrix.TransformPoint(patch_x, patch_y); shading.patch[i][j].x = static_cast(patch_x); shading.patch[i][j].y = static_cast(patch_y); } } // sizes scale double sqrt_det = sqrt(fabs(matrix.Determinant())); r0 *= sqrt_det; r1 *= sqrt_det; } Point p0, p1; float r0, r1; float littleRadius, largeRadius; float centerX, centerY; // used in radial, diamond and conical gradient - offset relative to figure center float angle; // used in linear and conical gradient (rad) float discrete_step; // used to make discrete gradient. <= 0 to make continuous float xsize, ysize; // stretch image; can be negative to reflect relative to other axis; cannot be zero bool reflected; // 1234567 -> 1357531 works kind of like this bool periodic; float periods; // number of periods (best with to colours, works as saw function in color space) float linstretch; // stretch linear gradient, can be negative (eq angle = 180) can not be zero float linoffset; // offset relative to image size float continue_shading_b, continue_shading_f; bool luminocity; ShadingInfo shading; }; /** * Создает объект класса GradientInfo по заданным параметрам. * * Цветовую функцию надо заполнять вручную */ class GInfoConstructor { public: static GradientInfo get_functional(float xmin, float xmax, float ymin, float ymax, std::vector mapping) { GradientInfo ginfo; ginfo.shading.function = ColorFunction(256, xmin, xmax, ymin, ymax); ginfo.shading.f_type = ShadingInfo::UseNew; ginfo.shading.shading_type = ShadingInfo::FunctionOnly; ginfo.shading.mapping = mapping; return ginfo; } static GradientInfo get_linear(const Point &p1, const Point &p2, float t0 = 0.0f, float t1 = 1.0f, bool continue_shading_b = false, bool continue_shading_f = false) { GradientInfo ginfo; ginfo.shading.f_type = ShadingInfo::UseNew; ginfo.shading.shading_type = ShadingInfo::Parametric; ginfo.continue_shading_f = continue_shading_f; ginfo.continue_shading_b = continue_shading_b; ginfo.shading.function = ColorFunction(256, t0, t1); ginfo.shading.set_two_points = true; ginfo.shading.point1 = p1; ginfo.shading.point2 = p2; return ginfo; } static GradientInfo get_radial(const Point &c0, const Point &c1, float r0, float r1, float t0 = 0.0f, float t1 = 1.0f, bool continue_shading_b = false, bool continue_shading_f = false) { GradientInfo ginfo; ginfo.shading.f_type = ShadingInfo::UseNew; ginfo.shading.shading_type = ShadingInfo::Parametric; ginfo.continue_shading_f = continue_shading_f; ginfo.continue_shading_b = continue_shading_b; ginfo.shading.function = ColorFunction(256, t0, t1); ginfo.p0 = c0; ginfo.p1 = c1; ginfo.r0 = r0; ginfo.r1 = r1; return ginfo; } static GradientInfo get_triangle(const std::vector &points, const std::vector &colors, const std::vector ¶ms, bool parametric, float t0 = 0.0f, float t1 = 1.0f) { GradientInfo ginfo; ginfo.shading.triangle = points; ginfo.shading.shading_type = ShadingInfo::Parametric; ginfo.shading.function = ColorFunction(256, t0, t1); ginfo.continue_shading_f = false; ginfo.continue_shading_b = false; if (parametric) { ginfo.shading.triangle_parameters = params; ginfo.shading.shading_type = ShadingInfo::Parametric; } else { ginfo.shading.triangle_colors = colors; ginfo.shading.shading_type = ShadingInfo::TriangleInterpolation; } return ginfo; } /** * Набор из 12 точек для построения границ в порядке указанном в стандарте, * Порядок цветов или параметров как в стандарте. */ static GradientInfo get_curve(const std::vector &curve_points, const std::vector &curve_parametrs, const std::vector &curve_colors, bool parametric, float t0 = 0.0f, float t1 = 1.0f) { GradientInfo ginfo; ginfo.shading.patch.resize(4, std::vector(4)); ginfo.shading.patch[0][0] = curve_points[0]; ginfo.shading.patch[0][1] = curve_points[1]; ginfo.shading.patch[0][2] = curve_points[2]; ginfo.shading.patch[0][3] = curve_points[3]; ginfo.shading.patch[1][3] = curve_points[4]; ginfo.shading.patch[2][3] = curve_points[5]; ginfo.shading.patch[3][3] = curve_points[6]; ginfo.shading.patch[3][2] = curve_points[7]; ginfo.shading.patch[3][1] = curve_points[8]; ginfo.shading.patch[3][0] = curve_points[9]; ginfo.shading.patch[2][0] = curve_points[10]; ginfo.shading.patch[1][0] = curve_points[11]; ginfo.shading.f_type = ShadingInfo::UseNew; ginfo.shading.function = ColorFunction(256, t0, t1); ginfo.continue_shading_f = false; ginfo.continue_shading_b = false; if (parametric) { ginfo.shading.patch_parameters.resize(2, std::vector(2)); ginfo.shading.patch_parameters[0][0] = curve_parametrs[0]; ginfo.shading.patch_parameters[0][1] = curve_parametrs[1]; ginfo.shading.patch_parameters[1][0] = curve_parametrs[3]; ginfo.shading.patch_parameters[1][1] = curve_parametrs[2]; ginfo.shading.shading_type = ShadingInfo::Parametric; } else { ginfo.shading.patch_colors.resize(2, std::vector(2)); ginfo.shading.patch_colors[0][0] = curve_colors[0]; ginfo.shading.patch_colors[0][1] = curve_colors[1]; ginfo.shading.patch_colors[1][0] = curve_colors[3]; ginfo.shading.patch_colors[1][1] = curve_colors[2]; ginfo.shading.shading_type = ShadingInfo::CurveInterpolation; } return ginfo; } static GradientInfo get_tensor_curve(const std::vector> &curve_poits, const std::vector> &curve_parametrs, const std::vector> &curve_colors, bool parametric, bool luminosity = false, float t0 = 0.0f, float t1 = 1.0f) { GradientInfo ginfo; ginfo.shading.patch = curve_poits; ginfo.shading.f_type = ShadingInfo::UseNew; ginfo.shading.function = ColorFunction(256, t0, t1); ginfo.luminocity = luminosity; ginfo.continue_shading_f = false; ginfo.continue_shading_b = false; if (parametric) { ginfo.shading.patch_parameters = curve_parametrs; ginfo.shading.shading_type = ShadingInfo::Parametric; } else { ginfo.shading.patch_colors = curve_colors; ginfo.shading.shading_type = ShadingInfo::TensorCurveInterpolation; } return ginfo; } }; } #endif