/*
 * filter_drawing.cpp -- draws gps related graphics 
 * Copyright (c) 2015-2025 Meltytech, LLC
 * Original author: Daniel F
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include "filter_gpsgraphic.h"

//apply crop percentages to [min, max] then compute value at specific percentage
template<typename T>
static double get_value_from_percent_after_crop(double perc, T min, T max, double p_min, double p_max)
{
    double ret = 0;
    T delta = max - min;
    T new_min = min + p_min * delta / 100.0;
    T new_max = min + delta * p_max / 100.0;
    ret = (double) (new_min + perc * (new_max - new_min));
    // mlt_log_info(NULL, "get_value_from_percent_after_crop (perc=%f, min=%f, max=%f, p_min=%f, p_max=%f) rezult=%f [restricted to 0..1]\n", perc, min, max, p_min, p_max, ret);
    return ret;
}

//returns a 2d point of current source-type from gp, scaled to the rect area
static point_2d get_gpspoint_to_rect(
    mlt_filter filter, mlt_frame frame, gps_point_proc *gp, mlt_rect rect, s_base_crops used_crops)
{
    private_data *pdata = (private_data *) filter->child;
    point_2d tmp = {-1, -1};

    tmp.y = crop_and_normalize(get_crtval_bysrc(filter, 0, 0, gp),
                               get_min_bysrc(filter),
                               get_max_bysrc(filter),
                               used_crops.bot,
                               used_crops.top);
    if (pdata->graph_data_source == gspg_location_src) //if location, x-axis is longitude, else time
        tmp.x = crop_and_normalize(get_crtval_bysrc(filter, 0, gpsg_longitude_id, gp),
                                   get_min_bysrc(filter, gpsg_longitude_id),
                                   get_max_bysrc(filter, gpsg_longitude_id),
                                   used_crops.left,
                                   used_crops.right);
    else
        tmp.x = crop_and_normalize(gp->time,
                                   pdata->ui_crops.min_crop_time,
                                   pdata->ui_crops.max_crop_time,
                                   used_crops.left,
                                   used_crops.right);

    //scale point to rect area
    tmp.x = rect.x + tmp.x * rect.w;
    tmp.y = rect.y + rect.h - tmp.y * rect.h;
    return tmp;
}

//there isn't a Qt function for rect - line intersection check :(
static bool rect_intersects_line(QRectF rect, point_2d pt1, point_2d pt2)
{
    //if one point is inside the rect -> true
    if (rect.contains(pt1.x, pt1.y) || rect.contains(pt2.x, pt2.y))
        return true;

    //check if each side of the rect intersects (bounded) with the line
    QLineF line = QLineF(pt1.x, pt1.y, pt2.x, pt2.y);
    if (line.intersects(QLineF(rect.topLeft(), rect.topRight()), NULL)
        == QLineF::BoundedIntersection)
        return true;
    if (line.intersects(QLineF(rect.bottomLeft(), rect.bottomRight()), NULL)
        == QLineF::BoundedIntersection)
        return true;
    if (line.intersects(QLineF(rect.topLeft(), rect.bottomLeft()), NULL)
        == QLineF::BoundedIntersection)
        return true;
    if (line.intersects(QLineF(rect.topRight(), rect.bottomRight()), NULL)
        == QLineF::BoundedIntersection)
        return true;

    return false;
}

//get a smooth change in color when drawing individual lines for maps
//percentage is of the entire colors array, interpolation is done for the 2 nearby colors
static QColor interpolate_color_from_gradient(double p, QVector<QColor> &colors)
{
    QColor ret = Qt::black;
    p = CLAMP(p, 0, 1);
    if (colors.size() == 0)
        return ret;
    if (p == 1 || colors.size() == 1)
        return colors[colors.size() - 1];

    //the 2 colors we need to interpolate from
    int c1 = p * (colors.size() - 1);
    c1 = CLAMP(c1, 0, colors.size() - 1);
    int c2 = c1 + 1;
    c2 = CLAMP(c2, 0, colors.size() - 1);
    double ratio = (p * (colors.size() - 1)) - c1; //=the fractional part
    ratio = CLAMP(ratio, 0, 1);
//Result = (color2 - color1) * ratio + color1
#define interp(v1, v2, r) (v2 - v1) * r + v1
    ret.setRed(interp(colors[c1].red(), colors[c2].red(), ratio));
    ret.setGreen(interp(colors[c1].green(), colors[c2].green(), ratio));
    ret.setBlue(interp(colors[c1].blue(), colors[c2].blue(), ratio));
    ret.setAlpha(interp(colors[c1].alpha(), colors[c2].alpha(), ratio));
#undef interp
    return ret;
}

//draws 5 horizontal (+5 vertical for 2D map) with small text for each line showing the graph value at that point
void draw_legend_grid(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops)
{
    private_data *pdata = (private_data *) filter->child;
    mlt_rect rect = pdata->img_rect;
    mlt_properties properties = MLT_FILTER_PROPERTIES(filter);

    char *legend_unit = mlt_properties_get(properties, "legend_unit");
    int nr_legend_lines = 5;

    QPainterPath path_grid_lines;
    QPen pen_5lines;
    pen_5lines.setWidth(1);
    pen_5lines.setColor(Qt::white);
    QFont font = p.font();
    int text_size = MIN(rect.w, rect.h) / 25;
    font.setPixelSize(text_size);
    p.setFont(font);
    p.setPen(pen_5lines);

    p.setClipping(false); //legend text goes a bit outside the rect
    for (int i = 0; i < nr_legend_lines; i++) {
        path_grid_lines.moveTo(rect.x, rect.y + rect.h - (rect.h / (nr_legend_lines - 1) * i));
        //for each of the nr_legend_lines compute it's absolute value = percentage of min_max (after applying crop bot/top)
        double crt_val = get_value_from_percent_after_crop((double) i / (nr_legend_lines - 1),
                                                           get_min_bysrc(filter),
                                                           get_max_bysrc(filter),
                                                           used_crops.bot,
                                                           used_crops.top);
        if (pdata->graph_data_source == gspg_location_src) {
            crt_val = unproject_latitude(crt_val);
        }
        crt_val = convert_bysrc_to_format(filter, crt_val);
        p.drawText(path_grid_lines.currentPosition().x() + 3,
                   path_grid_lines.currentPosition().y() - 3,
                   QString::number(crt_val, 'f', decimals_needed_bysrc(filter, crt_val))
                       + legend_unit);
        path_grid_lines.lineTo(rect.x + rect.w, rect.y + rect.h - (rect.h / 4 * i));
    }

    //+vertical lines only for the map (longitude)
    if (pdata->graph_data_source == gspg_location_src) {
        for (int i = 0; i < nr_legend_lines; i++) {
            path_grid_lines.moveTo(rect.x + rect.w / (nr_legend_lines - 1) * i, rect.y);
            double crt_val
                = get_value_from_percent_after_crop((double) i / (nr_legend_lines - 1),
                                                    get_min_bysrc(filter, gpsg_longitude_id),
                                                    get_max_bysrc(filter, gpsg_longitude_id),
                                                    used_crops.left,
                                                    used_crops.right);
            crt_val = swap_180_if_needed(crt_val);
            p.drawText(path_grid_lines.currentPosition().x() + 3,
                       path_grid_lines.currentPosition().y() + text_size + 3,
                       QString::number(crt_val, 'f', 6));
            path_grid_lines.lineTo(rect.x + rect.w / (nr_legend_lines - 1) * i, rect.y + rect.h);
        }
    }
    p.drawPath(path_grid_lines);
    p.setClipping(true);
}

//draws a small dot/disc on the map/graph according to the current video time + sync
void draw_now_dot(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops)
{
    private_data *pdata = (private_data *) filter->child;
    mlt_rect rect = pdata->img_rect;
    mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
    mlt_position position = mlt_filter_get_position(filter, frame);
    mlt_position length = mlt_filter_get_length2(filter, frame);
    int thickness = mlt_properties_get_int(properties, "thickness");
    mlt_color dot_color
        = mlt_properties_anim_get_color(properties, "now_dot_color", position, length);
    dot_color = mlt_color_convert_trc(dot_color,
                                      mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "color_trc"));

    //disc with internal color = white and outer color=now dot color or last used for graph line
    QPen dot_pen = p.pen();
    dot_pen.setWidth(thickness / 1.5); //or fixed to 3px ?
    int disc_size = thickness * 1.5;
    if (dot_color.a != 0)
        dot_pen.setColor(QColor(dot_color.r, dot_color.g, dot_color.b, dot_color.a));
    p.setBrush(Qt::white);
    p.setPen(dot_pen);

    double px, py;
    bool inside_only = true; //the now_dot will never leave the rect area to help in visual sync
    gps_point_proc crt = get_now_weighted_gpspoint(filter, frame);
    if (get_crtval_bysrc(filter, 0, 0, &crt) != GPS_UNINIT) {
        py = crop_and_normalize(get_crtval_bysrc(filter, 0, 0, &crt),
                                get_min_bysrc(filter),
                                get_max_bysrc(filter),
                                used_crops.bot,
                                used_crops.top,
                                inside_only);
        if (pdata->graph_data_source == gspg_location_src)
            px = crop_and_normalize(get_crtval_bysrc(filter, 0, gpsg_longitude_id, &crt),
                                    get_min_bysrc(filter, gpsg_longitude_id),
                                    get_max_bysrc(filter, gpsg_longitude_id),
                                    used_crops.left,
                                    used_crops.right,
                                    inside_only);
        else //time
            px = crop_and_normalize(crt.time,
                                    pdata->ui_crops.min_crop_time,
                                    pdata->ui_crops.max_crop_time,
                                    used_crops.left,
                                    used_crops.right,
                                    inside_only);

        QPointF disc;
        disc.setX(rect.x + px * rect.w);
        disc.setY(rect.y + rect.h - py * rect.h);
        p.setClipping(false);
        p.drawEllipse(disc, disc_size, disc_size);
        p.setClipping(true);
    }
}

//draws a 2d graph of the chosen graph source (for non-location, second axis is time)
void draw_main_line_graph(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops)
{
    private_data *pdata = (private_data *) filter->child;
    mlt_rect rect = pdata->img_rect;
    QRectF qrect = QRectF(rect.x, rect.y, rect.w, rect.h);
    mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
    mlt_position position = mlt_filter_get_position(filter, frame);
    mlt_position length = mlt_filter_get_length2(filter, frame);
    int color_style = mlt_properties_get_int(properties, "color_style");
    int thickness = qAbs(mlt_properties_get_int(properties, "thickness"));
    int dots_only = mlt_properties_get_int(properties, "draw_individual_dots");
    char *legend_unit = mlt_properties_get(properties, "legend_unit");
    QVector<QColor> colors
        = get_graph_colors(properties, MLT_FRAME_PROPERTIES(frame), position, length);
    QPen last_graph_pen;

    if (colors.size() < 2)
        colors.append(colors[0]);

    //original state
    p.save();

    //draw legend grid lines + text if enabled
    if ((pdata->graph_type == gpsg_2d_map_graph || pdata->graph_type == gpsg_crop_center_graph)
        && mlt_properties_get_int(properties, "show_grid"))
        draw_legend_grid(filter, frame, p, used_crops);

    int i_now = get_now_gpspoint_index(filter, frame);
    gps_point_proc gps_now = get_now_weighted_gpspoint(filter, frame);
    point_2d crt_pt = {-1, -1}, next_pt = {-1, -1};

    QPen pen_solid_color0;
    pen_solid_color0.setWidth(thickness);
    pen_solid_color0.setColor(colors[0]);
    pen_solid_color0.setCapStyle(Qt::RoundCap);
    last_graph_pen = pen_solid_color0;

    QPen pen_solid_color1;
    pen_solid_color1.setWidth(thickness);
    pen_solid_color1.setColor(colors[1]);
    pen_solid_color1.setCapStyle(Qt::RoundCap);

    QPen pen_thin_color1;
    //1px dots are too small, 1px line feels just too small and sometimes aliaseses weirdly at an angle
    //meanwhile, I've added the "Two colors" style if the future points/lines are too small, might reconsider this
    if (thickness > 2)
        pen_thin_color1.setWidth(2);
    else
        pen_thin_color1.setWidth(1);
    pen_thin_color1.setColor(colors[1]);
    pen_thin_color1.setCapStyle(
        Qt::RoundCap); //( Qt::SquareCap ); // I don't remember what issue I had that made me use this, keeping it for now

    QPen pen_gradients;
    pen_gradients.setWidth(thickness);
    pen_gradients.setCapStyle(Qt::RoundCap);

    //enhancement TODO: if detected gap in GPS data draw a dashed line (but needs a bunch of extra changes)
    //pen_dash.setStyle( Qt::DashLine );

    //go through the [start_index..end_index] interval of gps_points_p and draw each pair of valid points
    for (int i = pdata->ui_crops.start_index; i < pdata->ui_crops.end_index; i++) {
        int next_i = get_next_valid_gpspoint_index(filter, i);
        if (i == next_i || get_crtval_bysrc(filter, i) == GPS_UNINIT
            || get_crtval_bysrc(filter, next_i) == GPS_UNINIT) {
            // mlt_log_info(filter, "incomplete pair (i=%d, %d) GPS_UNINIT, skipping drawing\n", i, next_i);
            continue;
        }

        crt_pt = get_gpspoint_to_rect(filter, frame, &pdata->gps_points_p[i], rect, used_crops);
        next_pt
            = get_gpspoint_to_rect(filter, frame, &pdata->gps_points_p[next_i], rect, used_crops);

        //don't draw line at all if it is completely outside the rect
        if (!rect_intersects_line(qrect, crt_pt, next_pt))
            continue;

        //apply color style
        if (color_style == gpsg_color_by_solid) {
            p.setPen(pen_solid_color0);
        } else if (color_style == gpsg_color_by_solid_past_future) {
            if (i <= i_now)
                p.setPen(pen_solid_color0);
            else if (i > i_now)
                p.setPen(pen_solid_color1);
        } else if ((color_style == gpsg_color_by_solid_past && i <= i_now)
                   || (color_style == gpsg_color_by_solid_future && i > i_now)) {
            p.setPen(pen_solid_color0);
        } else if ((color_style == gpsg_color_by_solid_past && i > i_now)
                   || (color_style == gpsg_color_by_solid_future && i <= i_now)) {
            p.setPen(pen_thin_color1);
        } else if (color_style == gpsg_color_by_vertical_gradient
                   || color_style == gpsg_color_by_horizontal_gradient) {
            QLinearGradient gradient;
            gradient.setStart(rect.x, rect.y);
            if (color_style == gpsg_color_by_vertical_gradient)
                gradient.setFinalStop(rect.x, rect.y + rect.h);
            else
                gradient.setFinalStop(rect.x + rect.w, rect.y);
            qreal step = 1.0 / (colors.size() - 1);
            for (int i = 0; i < colors.size(); i++)
                gradient.setColorAt((qreal) i * step, colors[i]);
            pen_gradients.setBrush(gradient);
            p.setPen(pen_gradients);
        } else if (color_style >= gpsg_color_by_duration
                   && color_style <= gpsg_color_by_grade_max20) {
//compute current value as a percentage of min..max
#define calc_perc(v, min, max) \
    (double) (v - min) / ((max - min) != 0 ? (max - min) : ((v - min) ? (v - min) : 1))
            double perc = 0;
            if (color_style == gpsg_color_by_duration) {
                //this one is relative to trim, not entire gps track
                perc = calc_perc(pdata->gps_points_p[i].time,
                                 pdata->ui_crops.min_crop_time,
                                 pdata->ui_crops.max_crop_time);
            } else if (color_style == gpsg_color_by_altitude) {
                perc = calc_perc(pdata->gps_points_p[i].ele,
                                 pdata->minmax.min_ele,
                                 pdata->minmax.max_ele);
            } else if (color_style == gpsg_color_by_hr) {
                perc = calc_perc(pdata->gps_points_p[i].hr,
                                 pdata->minmax.min_hr,
                                 pdata->minmax.max_hr);
            } else if (color_style == gpsg_color_by_speed
                       || color_style == gpsg_color_by_speed_max100) {
                //max 100km/h (27.777 m/s) variant to cover for bad GPS errors
                double used_max_speed = pdata->minmax.max_speed;
                if (color_style == gpsg_color_by_speed_max100 && used_max_speed > 27.777)
                    used_max_speed = 27.777;
                perc = calc_perc(pdata->gps_points_p[i].speed,
                                 pdata->minmax.min_speed,
                                 used_max_speed);
            } else if (color_style == gpsg_color_by_grade_max90
                       || color_style == gpsg_color_by_grade_max20) {
                //limit to 90* (100%) or 20* (36.397%) - only if max is over this value
                double max_allowed_percentage = MAX(abs(pdata->minmax.min_grade_p),
                                                    abs(pdata->minmax.max_grade_p));
                max_allowed_percentage = MIN(max_allowed_percentage,
                                             (color_style == gpsg_color_by_grade_max20 ? 36.397
                                                                                       : 100));
                double safe_grade_p = CLAMP(pdata->gps_points_p[i].grade_p,
                                            -max_allowed_percentage,
                                            max_allowed_percentage);
                //this one is special because middle color is always for value 0;
                if (pdata->gps_points_p[i].grade_p < 0)
                    perc = calc_perc(safe_grade_p, -max_allowed_percentage, 0) / 2.0;
                else
                    perc = calc_perc(safe_grade_p, 0, max_allowed_percentage) / 2.0 + 0.5;
            }
            //assign the interpolated color at p% in the colors array
            pen_gradients.setColor(interpolate_color_from_gradient(perc, colors));
            p.setPen(pen_gradients);
        } else {
            p.setPen(pen_solid_color0);
        }

        if (i == i_now)
            last_graph_pen = p.pen();

        //for the past/future segment we need to split it exactly at the now point into 2 different colors or it will look horrible if zoomed in enough
        if ((i == i_now)
            && (color_style == gpsg_color_by_solid_past || color_style == gpsg_color_by_solid_future
                || color_style == gpsg_color_by_solid_past_future)) {
            point_2d now_pt = get_gpspoint_to_rect(filter, frame, &gps_now, rect, used_crops);

            //if we got a valid intermediary point for the current location, we'll draw the past/future with different styles
            if (get_crtval_bysrc(filter, 0, 0, &gps_now) != GPS_UNINIT) {
                //"past" sub-segment
                if (color_style == gpsg_color_by_solid_past
                    || color_style == gpsg_color_by_solid_past_future)
                    p.setPen(pen_solid_color0);
                else if (color_style == gpsg_color_by_solid_future)
                    p.setPen(pen_thin_color1);

                if (dots_only)
                    p.drawPoint(QPointF(crt_pt.x, crt_pt.y));
                else
                    p.drawLine(QPointF(crt_pt.x, crt_pt.y), QPointF(now_pt.x, now_pt.y));

                //"future" sub-segment
                if (color_style == gpsg_color_by_solid_past)
                    p.setPen(pen_thin_color1);
                else if (color_style == gpsg_color_by_solid_future)
                    p.setPen(pen_solid_color0);
                else if (color_style == gpsg_color_by_solid_past_future)
                    p.setPen(pen_solid_color1);

                if (!dots_only)
                    p.drawLine(QPointF(now_pt.x, now_pt.y), QPointF(next_pt.x, next_pt.y));
            } else {
                //if invalid point, consider the entire line "future"
                if (color_style == gpsg_color_by_solid_past)
                    p.setPen(pen_thin_color1);
                else if (color_style == gpsg_color_by_solid_future)
                    p.setPen(pen_solid_color0);
                else if (color_style == gpsg_color_by_solid_past_future)
                    p.setPen(pen_solid_color1);

                if (dots_only)
                    p.drawPoint(QPointF(crt_pt.x, crt_pt.y));
                else
                    p.drawLine(QPointF(crt_pt.x, crt_pt.y), QPointF(next_pt.x, next_pt.y));
            }
        } else //= full segment lines not intersecting now_dot
        {
            //IMPORTANT: the function call without QPointF() cast loses precision due to int!! fun times debugging the small random wiggles from this one
            if (dots_only)
                p.drawPoint(QPointF(crt_pt.x, crt_pt.y));
            else
                p.drawLine(QPointF(crt_pt.x, crt_pt.y), QPointF(next_pt.x, next_pt.y));
        }
    }

    //draw the current value in the bot-right corner, big bold white text
    if (mlt_properties_get_int(properties, "show_now_text")) {
        double now_val = get_crtval_bysrc(filter, 0, 0, &gps_now);
        if (now_val != GPS_UNINIT) {
            QRectF now_text_rect = QRectF(rect.x, rect.y + rect.h / 2, rect.w, rect.h / 2);
            QFont font = p.font();
            font.setPixelSize(rect.w / 15);
            font.setWeight(QFont::Bold);
            p.setFont(font);
            p.setPen(Qt::white);

            if (pdata->graph_data_source == gspg_location_src) {
                now_val = unproject_latitude(now_val);
            }
            now_val = convert_bysrc_to_format(filter, now_val);
            QString now_text = QString::number(now_val, 'f', decimals_needed_bysrc(filter, now_val));
            if (pdata->graph_data_source == gspg_location_src) {
                now_val = get_crtval_bysrc(filter, 0, gpsg_longitude_id, &gps_now);
                now_val = swap_180_if_needed(now_val);
                now_text += ", "
                            + QString::number(now_val, 'f', decimals_needed_bysrc(filter, now_val));
            }
            now_text += QString(legend_unit);

            p.drawText(now_text_rect, Qt::AlignBottom | Qt::AlignRight, now_text);
        }
    }

    //restore to before legend + main_graph
    p.restore();
    //set color so by default the now_dot will be the same color as nearby graph
    p.setPen(last_graph_pen);

    //draw a circle for current point
    if (mlt_properties_get_int(properties, "show_now_dot") && pdata->graph_type <= 1)
        draw_now_dot(filter, frame, p, used_crops);
}

//draws a speedometer from the current data source (for map it displays % of total)
void draw_main_speedometer(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops)
{
    private_data *pdata = (private_data *) filter->child;
    mlt_rect rect = pdata->img_rect;
    mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
    mlt_position position = mlt_filter_get_position(filter, frame);
    mlt_position length = mlt_filter_get_length2(filter, frame);
    // int color_style = mlt_properties_get_int( properties, "color_style" ) ;
    // int thickness = qAbs( mlt_properties_get_int( properties, "thickness" ) );
    int show_grid = mlt_properties_get_int(properties, "show_grid");
    QVector<QColor> colors
        = get_graph_colors(properties, MLT_FRAME_PROPERTIES(frame), position, length);

    if (colors.size() == 1)
        colors.append(colors[0]);

    //draw the divisions
    double canvas_angle = mlt_properties_get_double(properties, "angle");
    const double max_angle = 240.0;
    const double start_angle = 90 + 30;
    const int nr_big_lines = 6;
    const int nr_small_lines = 3;
    int small_side = MIN(rect.w, rect.h);
    double big_line_width = small_side / 10.0;
    double big_line_height = big_line_width / 5.0;
    double big_angle = max_angle / nr_big_lines;
    double small_angle = big_angle / (nr_small_lines + 1);
    QRectF big_line(0, -big_line_height / 2, -big_line_width, big_line_height);
    QRectF small_line(0, big_line.y() / 2, big_line.width() / 2, big_line.height() / 2);

    //legend = big line numbers
    QPen pen_speedo_nr;
    pen_speedo_nr.setWidth(1);
    pen_speedo_nr.setColor(Qt::white);
    QFont font = p.font();
    int text_size = big_line_height * 3;
    font.setPixelSize(text_size);
    font.setWeight(QFont::Bold);
    p.setFont(font);
    p.setPen(pen_speedo_nr);

    p.translate(rect.x + rect.w / 2, rect.y + rect.h / 2);
    p.rotate(start_angle);
    for (int i = 0; i <= nr_big_lines; i++) {
        //big lines
        p.rotate(i * big_angle);
        p.translate(small_side / 2, 0);
        p.fillRect(big_line, colors[0]);
        //legend values
        if (show_grid) {
            p.save();

            double crt_val = 0;
            if (pdata->graph_data_source != gspg_location_src) {
                crt_val = get_value_from_percent_after_crop((double) i / nr_big_lines,
                                                            get_min_bysrc(filter),
                                                            get_max_bysrc(filter),
                                                            used_crops.bot,
                                                            used_crops.top);
                crt_val = convert_bysrc_to_format(filter, crt_val);
            } else //just show a percentage of total for map
            {
                crt_val = 100.0 * i / nr_big_lines;
            }
            //center & rotate the text: compute the text bounding box, translate to it's center, rotate, translate back and drawtext within the rect bounding box
            QString l_text = QString::number(crt_val, 'f', 0);
            int text_width = p.fontMetrics().horizontalAdvance(l_text)
                             + p.fontMetrics().horizontalAdvance(" ");
            int text_height = p.fontMetrics().height();
            QRectF text_rect = QRectF(-text_width, -text_height / 2, text_width, text_height);
            p.translate(-big_line_width, 0);
            p.translate(text_rect.center());
            p.rotate(-(canvas_angle + start_angle + i * big_angle));
            p.translate(-text_rect.center());
            p.drawText(text_rect, Qt::AlignCenter, l_text);

            p.restore();
        }

        // small lines
        p.translate(-small_side / 2, 0);
        for (int j = 1; j <= nr_small_lines && i != nr_big_lines; j++) {
            p.rotate(j * small_angle);
            p.translate(small_side / 2, 0);
            p.fillRect(small_line, colors[1]);
            p.translate(-small_side / 2, 0);
            p.rotate(-j * small_angle);
        }
        p.rotate(-i * big_angle);
    }

    //find out the needle's angle
    gps_point_proc gps_now = get_now_weighted_gpspoint(filter, frame);
    double now_value = 0, n_angle = 0;
    if (pdata->graph_data_source != gspg_location_src) {
        if (get_crtval_bysrc(filter, 0, 0, &gps_now) != GPS_UNINIT) {
            n_angle = crop_and_normalize(get_crtval_bysrc(filter, 0, 0, &gps_now),
                                         get_min_bysrc(filter),
                                         get_max_bysrc(filter),
                                         used_crops.bot,
                                         used_crops.top,
                                         true);
            now_value = convert_bysrc_to_format(filter, get_crtval_bysrc(filter, 0, 0, &gps_now));
        }
    } else //just show a percentage of total
    {
        n_angle = crop_and_normalize(gps_now.time,
                                     pdata->ui_crops.min_crop_time,
                                     pdata->ui_crops.max_crop_time,
                                     used_crops.left,
                                     used_crops.right,
                                     true);
        now_value = n_angle * 100;
    }

    p.rotate(n_angle * max_angle);

    //the needle considered as a "Now dot in UI"; just a long triangle
    if (mlt_properties_get_int(properties, "show_now_dot")) {
        mlt_color dot_color
            = mlt_properties_anim_get_color(properties, "now_dot_color", position, length);
        dot_color = mlt_color_convert_trc(dot_color,
                                          mlt_properties_get(MLT_FRAME_PROPERTIES(frame),
                                                             "color_trc"));
        if (dot_color.a == 0) // if transparent -> use main color
        {
            p.setBrush(colors[0]);
            p.setPen(colors[0]);
        } else {
            p.setBrush(QColor(dot_color.r, dot_color.g, dot_color.b, dot_color.a));
            p.setPen(QColor(dot_color.r, dot_color.g, dot_color.b, dot_color.a));
        }
        double needle_base_len = big_line_width / 8;
        QPointF needle[3] = {QPointF(-needle_base_len, -needle_base_len),
                             QPointF(-needle_base_len, needle_base_len),
                             QPointF(small_side / 2.0, 0)};
        p.drawConvexPolygon(needle, 3);

        //small dot in center
        double needle_circle_len = big_line_width / 2;
        p.drawEllipse(-needle_circle_len / 2,
                      -needle_circle_len / 2,
                      needle_circle_len,
                      needle_circle_len);
    }

    p.rotate(-n_angle * max_angle);
    p.rotate(-start_angle);

    //draw big bold text in white here
    if (mlt_properties_get_int(properties, "show_now_text")) {
        QRectF now_text_rect
            = QRectF(-small_side * 0.25, 0, small_side * 0.75, small_side / 2 - text_size * 2);
        QString now_text = QString::number(now_value, 'f', decimals_needed(now_value));
        int now_text_size = text_size * 3;
        font.setPixelSize(now_text_size);
        p.setFont(font);
        p.setPen(Qt::white);
        p.translate(now_text_rect.center());
        p.rotate(-canvas_angle);
        p.translate(-now_text_rect.center());
        p.drawText(now_text_rect, Qt::AlignBottom | Qt::AlignHCenter, now_text);

        //print the legend_unit with 1/3 smaller text size below
        QRectF now_text_unit_rect = QRectF(-small_side * 0.25,
                                           small_side / 2 - 2 * text_size,
                                           small_side * 0.75,
                                           text_size * 2);
        QString now_text_unit = mlt_properties_get(properties, "legend_unit");
        font.setPixelSize(text_size);
        p.setFont(font);
        p.setPen(Qt::white);
        p.drawText(now_text_unit_rect, Qt::AlignTop | Qt::AlignHCenter, now_text_unit);
    }
}

//inits drawing rect +clipping, antialiasing, and applies rotate
void prepare_canvas(mlt_filter filter,
                    mlt_frame frame,
                    QImage *qimg,
                    QPainter &p,
                    int width,
                    int height,
                    s_base_crops &used_crops)
{
    private_data *pdata = (private_data *) filter->child;
    mlt_properties properties = MLT_FILTER_PROPERTIES(filter);

    mlt_position position = mlt_filter_get_position(filter, frame);
    mlt_position length = mlt_filter_get_length2(filter, frame);
    mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter));
    mlt_rect rect = mlt_properties_anim_get_rect(properties, "rect", position, length);

    //rect area and scaling stuff
    if (strchr(mlt_properties_get(properties, "rect"), '%')) {
        rect.x *= qimg->width();
        rect.w *= qimg->width();
        rect.y *= qimg->height();
        rect.h *= qimg->height();
    }
    double scale = mlt_profile_scale_width(profile, width);
    rect.x *= scale;
    rect.w *= scale;
    scale = mlt_profile_scale_height(profile, height);
    rect.y *= scale;
    rect.h *= scale;

    pdata->img_rect = rect;
    QRectF r(rect.x, rect.y, rect.w, rect.h);

    // Apply rotation
    double angle = mlt_properties_get_double(properties, "angle");
    if (angle) {
        p.translate(r.x() + r.width() / 2, r.y() + r.height() / 2);
        p.rotate(angle);
        p.translate(-(r.x() + r.width() / 2), -(r.y() + r.height() / 2));
    }

    p.setClipRect(r);

    //this almost doubles processing time if using large backgrounds (from 6-7ms to 10-11ms/frame) but massively improves drawImage smoothness (visible neighbor pixel jumps from frame to frame if disabled)
    p.setRenderHint(QPainter::SmoothPixmapTransform, true);

    //draw background image (cropped and matched)
    if (pdata->bg_img_scaled_width != 0 && strlen(pdata->last_bg_img_path) > 0
        && !pdata->bg_img.isNull()) {
        //step 1 scales image to map aspect ratio (in process_filter_properties())
        //step 2) apply bg_scale_w/h to correctly match img to GPS track -> create rescaled_bg_rect instead of modifying image here
        double bg_width = pdata->bg_img_scaled.width();
        double bg_height = pdata->bg_img_scaled.height();

        double new_bg_width = bg_width * pdata->bg_img_scaled_width;
        double new_bg_height = bg_height * pdata->bg_img_scaled_height;
        double new_bg_x = (bg_width - new_bg_width) / 2;
        double new_bg_y = (bg_height - new_bg_height) / 2;
        QRectF rescaled_bg_rect = QRectF(new_bg_x, new_bg_y, new_bg_width, new_bg_height);

        //step 3) apply crop percentages to the rescaled_bg_rect to follow now_dot or user input
        QPointF top_left = QPointF(rescaled_bg_rect.x()
                                       + rescaled_bg_rect.width() * used_crops.left / 100.0,
                                   rescaled_bg_rect.y()
                                       + rescaled_bg_rect.height() * (1 - used_crops.top / 100.0));
        QPointF bot_right = QPointF(rescaled_bg_rect.bottomRight().x()
                                        - rescaled_bg_rect.width() * (1 - used_crops.right / 100.0),
                                    rescaled_bg_rect.bottomRight().y()
                                        - rescaled_bg_rect.height() * used_crops.bot / 100.0);
        QRectF crop_rect = QRectF(top_left, bot_right);
        // mlt_log_info(filter, "crop rect: x,y w,h= %f,%f %f,%f\n", crop_rect.x(), crop_rect.y(), crop_rect.width(), crop_rect.height());

        //step 4) crop the computed rect from the step 1) scaled image
        p.setOpacity(mlt_properties_get_double(properties, "bg_opacity"));
        p.drawImage(r, pdata->bg_img_scaled, crop_rect);
        p.setOpacity(1);
    }
    p.setRenderHint(QPainter::Antialiasing, true);
}
