/*
 * kdenlivetitle_wrapper.cpp -- kdenlivetitle wrapper
 * Copyright (c) 2009 Marco Gittler <g.marco@freenet.de>
 * Copyright (c) 2009 Jean-Baptiste Mardelle <jb@kdenlive.org>
 * Copyright (c) 2025 Meltytech, LLC
 *
 * 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 "kdenlivetitle_wrapper.h"
#include "kdenlivegraphics.h"
#include "typewriter.h"

#include "common.h"

#include <math.h>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QGraphicsScene>
#include <QGraphicsSvgItem>
#include <QGraphicsTextItem>
#include <QImage>
#include <QMutex>
#include <QPainter>
#include <QString>
#include <QStyleOptionGraphicsItem>
#include <QSvgRenderer>
#include <QTextCursor>
#include <QTextDocument>

#include <framework/mlt_log.h>
#include <QColor>
#include <QDomElement>
#include <QRectF>
#include <QWidget>

#include <QGraphicsBlurEffect>
#include <QGraphicsDropShadowEffect>
#include <QGraphicsEffect>

#include <memory>

Q_DECLARE_METATYPE(QTextCursor);
Q_DECLARE_METATYPE(std::shared_ptr<TypeWriter>);

// Private Constants
static const double PI = 3.14159265358979323846;

class ImageItem : public QGraphicsItem
{
public:
    ImageItem(QImage img) { m_img = img; }

    virtual QRectF boundingRect() const { return QRectF(0, 0, m_img.width(), m_img.height()); }

    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget *)
    {
        painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
        painter->drawImage(QPoint(), m_img);
    }

private:
    QImage m_img;
};

void blur(QImage &image, int radius)
{
    int tab[] = {14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2};
    int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius - 1];

    int r1 = 0;
    int r2 = image.height() - 1;
    int c1 = 0;
    int c2 = image.width() - 1;

    int bpl = image.bytesPerLine();
    int rgba[4];
    unsigned char *p;

    int i1 = 0;
    int i2 = 3;

    for (int col = c1; col <= c2; col++) {
        p = image.scanLine(r1) + col * 4;
        for (int i = i1; i <= i2; i++)
            rgba[i] = p[i] << 4;

        p += bpl;
        for (int j = r1; j < r2; j++, p += bpl)
            for (int i = i1; i <= i2; i++)
                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
    }

    for (int row = r1; row <= r2; row++) {
        p = image.scanLine(row) + c1 * 4;
        for (int i = i1; i <= i2; i++)
            rgba[i] = p[i] << 4;

        p += 4;
        for (int j = c1; j < c2; j++, p += 4)
            for (int i = i1; i <= i2; i++)
                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
    }

    for (int col = c1; col <= c2; col++) {
        p = image.scanLine(r2) + col * 4;
        for (int i = i1; i <= i2; i++)
            rgba[i] = p[i] << 4;

        p -= bpl;
        for (int j = r1; j < r2; j++, p -= bpl)
            for (int i = i1; i <= i2; i++)
                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
    }

    for (int row = r1; row <= r2; row++) {
        p = image.scanLine(row) + c2 * 4;
        for (int i = i1; i <= i2; i++)
            rgba[i] = p[i] << 4;

        p -= 4;
        for (int j = c1; j < c2; j++, p -= 4)
            for (int i = i1; i <= i2; i++)
                p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4;
    }
}

class PlainTextItem : public QGraphicsItem
{
public:
    PlainTextItem(QString text,
                  QFont font,
                  double width,
                  double height,
                  QBrush brush,
                  QColor outlineColor,
                  double outline,
                  int align,
                  int lineSpacing,
                  int tabWidth)
        : m_metrics(QFontMetrics(font))
    {
        m_boundingRect = QRectF(0, 0, width, height);
        m_brush = brush;
        m_outline = outline;
        m_pen = QPen(outlineColor);
        m_pen.setWidthF(outline);
        m_pen.setJoinStyle(Qt::RoundJoin);
        m_font = font;
        m_lineSpacing = lineSpacing + m_metrics.lineSpacing();
        m_align = align;
        m_width = width;
        m_tabWidth = tabWidth;
        updateText(text);
    }

    void updateText(const QString text)
    {
        m_path.clear();
        // Calculate line width
        const QStringList lines = text.split('\n');
        double linePos = m_metrics.ascent();
        foreach (const QString &line, lines) {
            QPainterPath linePath;
            const QStringList tabLines = line.split(QLatin1Char('\t'));
            if (m_tabWidth > 0 && tabLines.count() > 1) {
                qreal pos = 0;
                qreal currentPos = 0;
                for (const QString &tline : tabLines) {
                    QPainterPath tabPath;
                    if (!tline.isEmpty()) {
                        tabPath.addText(pos, linePos, m_font, tline);
                        linePath.addPath(tabPath);
                        currentPos = pos + tabPath.boundingRect().width();
                    } else {
                        // Several chained tabs
                        currentPos = pos + m_tabWidth / 2;
                    }
                    int tabsCount = ceil(currentPos / m_tabWidth);
                    pos = tabsCount * m_tabWidth;
                }
            } else {
                linePath.addText(0, linePos, m_font, line);
            }
            linePos += m_lineSpacing;
            if (m_align == Qt::AlignHCenter) {
                double offset = (m_width - m_metrics.horizontalAdvance(line)) / 2;
                linePath.translate(offset, 0);
            } else if (m_align == Qt::AlignRight) {
                double offset = (m_width - m_metrics.horizontalAdvance(line));
                linePath.translate(offset, 0);
            }
            m_path.addPath(linePath);
        }
        m_path.setFillRule(Qt::WindingFill);
        if (!m_path.isEmpty()) {
            int minWidth = m_path.boundingRect().width();
            int minHeight = m_lineSpacing * lines.size();
            if (m_boundingRect.width() < minWidth) {
                m_boundingRect.setWidth(minWidth);
            }
            if (m_boundingRect.height() < minHeight) {
                m_boundingRect.setHeight(minHeight);
            }
        }
    }

    virtual QRectF boundingRect() const { return m_boundingRect; }

    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *w)
    {
        if (!m_shadow.isNull()) {
            painter->drawImage(m_shadowOffset, m_shadow);
        }
        if (m_outline > 0) {
            painter->strokePath(m_path.simplified(), m_pen);
        }
        painter->fillPath(m_path, m_brush);
    }

    void addShadow(QStringList params)
    {
        m_params = params;
        updateShadows();
    }

    void updateShadows()
    {
        if (m_params.count() < 5 || m_params.at(0).toInt() == false) {
            // Invalid or no shadow wanted
            return;
        }
        // Build shadow image
        QColor shadowColor = QColor(m_params.at(1));
        int blurRadius = m_params.at(2).toInt();
        int offsetX = m_params.at(3).toInt();
        int offsetY = m_params.at(4).toInt();
        m_shadow = QImage(m_boundingRect.width() + abs(offsetX) + 4 * blurRadius + 2 * m_outline,
                          m_boundingRect.height() + abs(offsetY) + 4 * blurRadius + 2 * m_outline,
                          QImage::Format_ARGB32_Premultiplied);
        m_shadow.fill(Qt::transparent);
        QPainterPath shadowPath = m_path;
        offsetX -= (2 * blurRadius + m_outline + 1);
        offsetY -= (2 * blurRadius + m_outline + 1);
        m_shadowOffset = QPoint(offsetX, offsetY);
        shadowPath.translate(2 * blurRadius + m_outline + 1, 2 * blurRadius + m_outline + 1);
        QPainter shadowPainter(&m_shadow);
        if (m_outline > 0) {
            QPainterPathStroker strokePath;
            strokePath.setWidth(m_outline);
            strokePath.setJoinStyle(Qt::RoundJoin);
            QPainterPath stroke = strokePath.createStroke(shadowPath);
            shadowPath.addPath(stroke);
        }
        shadowPainter.fillPath(shadowPath, QBrush(shadowColor));
        shadowPainter.end();
        blur(m_shadow, blurRadius);
    }

private:
    QRectF m_boundingRect;
    QImage m_shadow;
    QPoint m_shadowOffset;
    QPainterPath m_path;
    QBrush m_brush;
    QPen m_pen;
    QFont m_font;
    int m_lineSpacing;
    int m_align;
    double m_width;
    int m_tabWidth;
    QFontMetrics m_metrics;
    double m_outline;
    QStringList m_params;
};

QRectF stringToRect(const QString &s)
{
    const QStringList l = s.split(',');
    if (l.size() < 4)
        return QRectF();
    return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble())
        .normalized();
}

QColor stringToColor(const QString &s)
{
    const QStringList l = s.split(',');
    if (l.size() < 4)
        return QColor();
    return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());
    ;
}
QTransform stringToTransform(const QString &s)
{
    const QStringList l = s.split(',');
    if (l.size() < 9)
        return QTransform();
    return QTransform(l.at(0).toDouble(),
                      l.at(1).toDouble(),
                      l.at(2).toDouble(),
                      l.at(3).toDouble(),
                      l.at(4).toDouble(),
                      l.at(5).toDouble(),
                      l.at(6).toDouble(),
                      l.at(7).toDouble(),
                      l.at(8).toDouble());
}

static void qscene_delete(void *data)
{
    QGraphicsScene *scene = (QGraphicsScene *) data;
    delete scene;
    scene = NULL;
}

void loadFromXml(producer_ktitle self,
                 QGraphicsScene *scene,
                 const char *templateXml,
                 const char *templateText)
{
    scene->clear();
    mlt_producer producer = &self->parent;
    self->has_alpha = true;
    mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer);
    QDomDocument doc;
    QString data = QString::fromUtf8(templateXml);
    QString replacementText = QString::fromUtf8(templateText);
    doc.setContent(data);
    QDomElement title = doc.documentElement();

    // Check for invalid title
    if (title.isNull() || title.tagName() != "kdenlivetitle")
        return;

    // Check title locale
    if (title.hasAttribute("LC_NUMERIC")) {
        QString locale = title.attribute("LC_NUMERIC");
        QLocale::setDefault(QLocale(locale));
    }

    int originalWidth;
    int originalHeight;
    if (title.hasAttribute("width")) {
        originalWidth = title.attribute("width").toInt();
        originalHeight = title.attribute("height").toInt();
        scene->setSceneRect(0, 0, originalWidth, originalHeight);
    } else {
        originalWidth = scene->sceneRect().width();
        originalHeight = scene->sceneRect().height();
    }
    if (title.hasAttribute("out")) {
        mlt_properties_set_position(producer_props,
                                    "_animation_out",
                                    title.attribute("out").toDouble());
    } else {
        mlt_properties_set_position(producer_props,
                                    "_animation_out",
                                    mlt_producer_get_out(producer));
    }
    mlt_properties_set_int(producer_props, "meta.media.width", originalWidth);
    mlt_properties_set_int(producer_props, "meta.media.height", originalHeight);
    const QDir rootDir(QFileInfo(mlt_properties_get(producer_props, "resource")).absolutePath());

    QDomNode node;
    QDomNodeList items = title.elementsByTagName("item");
    int itemId = 1;
    for (int i = 0; i < items.count(); i++) {
        QGraphicsItem *gitem = NULL;
        node = items.item(i);
        QDomNamedNodeMap nodeAttributes = node.attributes();
        int zValue = nodeAttributes.namedItem("z-index").nodeValue().toInt();
        if (zValue > -1000) {
            if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsTextItem") {
                QDomNamedNodeMap txtProperties = node.namedItem("content").attributes();
                QFont font(txtProperties.namedItem("font").nodeValue());
                QDomNode propsNode = txtProperties.namedItem("font-bold");
                if (!propsNode.isNull()) {
                    // Old: Bold/Not bold.
                    font.setBold(propsNode.nodeValue().toInt());
                } else {
                    // New: Font weight (QFont::)
                    font.setWeight(
                        QFont::Weight(txtProperties.namedItem("font-weight").nodeValue().toInt()));
                }
                font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
                font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());

                int letterSpacing = txtProperties.namedItem("font-spacing").nodeValue().toInt();
                if (letterSpacing != 0) {
                    font.setLetterSpacing(QFont::AbsoluteSpacing, letterSpacing);
                }
                // Older Kdenlive version did not store pixel size but point size
                if (txtProperties.namedItem("font-pixel-size").isNull()) {
                    QFont f2;
                    f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
                    font.setPixelSize(QFontInfo(f2).pixelSize());
                } else {
                    font.setPixelSize(
                        txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
                }
                if (!txtProperties.namedItem("letter-spacing").isNull()) {
                    font.setLetterSpacing(QFont::AbsoluteSpacing,
                                          txtProperties.namedItem("letter-spacing")
                                              .nodeValue()
                                              .toInt());
                }
                QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
                QString text = node.namedItem("content").firstChild().nodeValue();
                if (!replacementText.isEmpty()) {
                    text = text.replace("%s", replacementText);
                }
                QColor outlineColor(
                    stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));

                int align = 1;
                if (txtProperties.namedItem("alignment").isNull() == false) {
                    align = txtProperties.namedItem("alignment").nodeValue().toInt();
                }
                int tabWidth = 0;
                if (txtProperties.namedItem("tab-width").isNull() == false) {
                    tabWidth = txtProperties.namedItem("tab-width").nodeValue().toInt();
                }

                double boxWidth = 0;
                double boxHeight = 0;
                if (txtProperties.namedItem("box-width").isNull()) {
                    // This is an old version title, find out dimensions from QGraphicsTextItem
                    QGraphicsTextItem *txt = scene->addText(text, font);
                    QRectF br = txt->boundingRect();
                    boxWidth = br.width();
                    boxHeight = br.height();
                    scene->removeItem(txt);
                    delete txt;
                } else {
                    boxWidth = txtProperties.namedItem("box-width").nodeValue().toDouble();
                    boxHeight = txtProperties.namedItem("box-height").nodeValue().toDouble();
                }
                QBrush brush;
                if (txtProperties.namedItem("gradient").isNull() == false) {
                    // Calculate gradient
                    QString gradientData = txtProperties.namedItem("gradient").nodeValue();
                    QStringList values = gradientData.split(";");
                    if (values.count() < 5) {
                        // invalid gradient, use default
                        values = QStringList({QStringLiteral("#ff0000"),
                                              QStringLiteral("#2e0046"),
                                              QStringLiteral("0"),
                                              QStringLiteral("100"),
                                              QStringLiteral("90")});
                    }
                    QLinearGradient gr;
                    gr.setColorAt(values.at(2).toDouble() / 100, values.at(0));
                    gr.setColorAt(values.at(3).toDouble() / 100, values.at(1));
                    double angle = values.at(4).toDouble();
                    if (angle <= 90) {
                        gr.setStart(0, 0);
                        gr.setFinalStop(boxWidth * cos(angle * PI / 180),
                                        boxHeight * sin(angle * PI / 180));
                    } else {
                        gr.setStart(boxWidth, 0);
                        gr.setFinalStop(boxWidth + boxWidth * cos(angle * PI / 180),
                                        boxHeight * sin(angle * PI / 180));
                    }
                    brush = QBrush(gr);
                } else {
                    brush = QBrush(col);
                }

                if (txtProperties.namedItem("compatibility").isNull()) {
                    // Workaround Qt5 crash in threaded drawing of QGraphicsTextItem, paint by ourselves
                    PlainTextItem *txt = new PlainTextItem(
                        text,
                        font,
                        boxWidth,
                        boxHeight,
                        brush,
                        outlineColor,
                        txtProperties.namedItem("font-outline").nodeValue().toDouble(),
                        align,
                        txtProperties.namedItem("line-spacing").nodeValue().toInt(),
                        tabWidth);
                    if (txtProperties.namedItem("shadow").isNull() == false) {
                        const QStringList values
                            = txtProperties.namedItem("shadow").nodeValue().split(";");
                        txt->addShadow(values);
                    }
                    if (!txtProperties.namedItem("typewriter").isNull()) {
                        // typewriter effect

                        const QStringList values
                            = txtProperties.namedItem("typewriter").nodeValue().split(";");
                        int enabled = (static_cast<bool>(values.at(0).toInt()));

                        if (enabled && values.count() >= 5) {
                            mlt_properties_set_int(producer_props, "_animated", 1);
                            std::shared_ptr<TypeWriter> tw(new TypeWriter());
                            tw->setFrameStep(values.at(1).toInt());
                            int macro = values.at(2).toInt();
                            tw->setStepSigma(values.at(3).toInt());
                            tw->setStepSeed(values.at(4).toInt());
                            QString pattern;
                            if (macro) {
                                char c = 0;
                                switch (macro) {
                                case 1:
                                    c = 'c';
                                    break;
                                case 2:
                                    c = 'w';
                                    break;
                                case 3:
                                    c = 'l';
                                    break;
                                default:
                                    break;
                                }
                                pattern = QStringLiteral(":%1{%2}").arg(c).arg(text);
                            } else {
                                pattern = text;
                            }

                            tw->setPattern(pattern.toStdString());
                            tw->parse();
                            tw->printParseResult();
                            scene->setProperty(QString::number(itemId).toLatin1(),
                                               QVariant::fromValue<std::shared_ptr<TypeWriter>>(tw));
                            txt->setData(0, itemId);
                            itemId++;
                        } else {
                            txt->setData(0, QVariant());
                        }
                    }
                    scene->addItem(txt);
                    gitem = txt;
                } else {
                    QGraphicsTextItem *txt = scene->addText(text, font);
                    gitem = txt;
                    if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
                        QTextDocument *doc = txt->document();
                        // Make sure some that the text item does not request refresh by itself
                        doc->blockSignals(true);
                        QTextCursor cursor(doc);
                        cursor.select(QTextCursor::Document);
                        QTextCharFormat format;
                        format.setTextOutline(
                            QPen(QColor(stringToColor(
                                     txtProperties.namedItem("font-outline-color").nodeValue())),
                                 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
                                 Qt::SolidLine,
                                 Qt::RoundCap,
                                 Qt::RoundJoin));
                        format.setForeground(QBrush(col));
                        cursor.mergeCharFormat(format);
                    } else {
                        txt->setDefaultTextColor(col);
                    }

                    // Effects
                    if (!txtProperties.namedItem("typewriter").isNull()) {
                        // typewriter effect
                        mlt_properties_set_int(producer_props, "_animated", 1);
                        const QStringList effetData
                            = {"typewriter",
                               text,
                               txtProperties.namedItem("typewriter").nodeValue()};
                        txt->setData(0, effetData);
                        if (!txtProperties.namedItem("textwidth").isNull())
                            txt->setData(1, txtProperties.namedItem("textwidth").nodeValue());
                    }

                    if (txtProperties.namedItem("alignment").isNull() == false) {
                        txt->setTextWidth(txt->boundingRect().width());
                        QTextOption opt = txt->document()->defaultTextOption();
                        opt.setAlignment(
                            (Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
                        txt->document()->setDefaultTextOption(opt);
                    }
                    if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
                        //txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
                    }
                    if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
                        //txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
                    }
                    if (!txtProperties.namedItem("preferred-width").isNull()) {
                        txt->setTextWidth(
                            txtProperties.namedItem("preferred-width").nodeValue().toInt());
                    }
                }
            } else if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsRectItem"
                       || nodeAttributes.namedItem("type").nodeValue() == "QGraphicsEllipseItem") {
                QDomNamedNodeMap rectProperties = node.namedItem("content").attributes();
                QRectF rect = stringToRect(rectProperties.namedItem("rect").nodeValue());
                QString pen_str = rectProperties.namedItem("pencolor").nodeValue();
                double penwidth = rectProperties.namedItem("penwidth").nodeValue().toDouble();
                double cornerRadius
                    = rectProperties.namedItem("cornerRadius").nodeValue().toDouble();
                QBrush brush;
                if (!rectProperties.namedItem("gradient").isNull()) {
                    // Calculate gradient
                    QString gradientData = rectProperties.namedItem("gradient").nodeValue();
                    QStringList values = gradientData.split(";");
                    if (values.count() < 5) {
                        // invalid gradient, use default
                        values = QStringList({QStringLiteral("#ff0000"),
                                              QStringLiteral("#2e0046"),
                                              QStringLiteral("0"),
                                              QStringLiteral("100"),
                                              QStringLiteral("90")});
                    }
                    QLinearGradient gr;
                    gr.setColorAt(values.at(2).toDouble() / 100, values.at(0));
                    gr.setColorAt(values.at(3).toDouble() / 100, values.at(1));
                    double angle = values.at(4).toDouble();
                    if (angle <= 90) {
                        gr.setStart(0, 0);
                        gr.setFinalStop(rect.width() * cos(angle * PI / 180),
                                        rect.height() * sin(angle * PI / 180));
                    } else {
                        gr.setStart(rect.width(), 0);
                        gr.setFinalStop(rect.width() + rect.width() * cos(angle * PI / 180),
                                        rect.height() * sin(angle * PI / 180));
                    }
                    brush = QBrush(gr);
                } else {
                    brush = QBrush(
                        stringToColor(rectProperties.namedItem("brushcolor").nodeValue()));
                }
                QPen pen;
                if (penwidth == 0) {
                    pen = QPen(Qt::NoPen);
                } else {
                    pen = QPen(QBrush(stringToColor(pen_str)),
                               penwidth,
                               Qt::SolidLine,
                               Qt::SquareCap,
                               Qt::RoundJoin);
                }
                if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsEllipseItem") {
                    QGraphicsEllipseItem *ellipse = scene->addEllipse(rect, pen, brush);
                    gitem = ellipse;
                } else {
                    // QGraphicsRectItem
                    KdenliveGraphicsRect *rec
                        = new KdenliveGraphicsRect(rect, brush, pen, cornerRadius);
                    scene->addItem(rec);
                    gitem = rec;
                }
            } else if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
                const QString base64
                    = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
                QImage img;
                if (!base64.isEmpty()) {
                    img.loadFromData(QByteArray::fromBase64(base64.toLatin1()));
                } else {
                    QString url
                        = node.namedItem("content").attributes().namedItem("url").nodeValue();
                    if (QFileInfo(url).isRelative()) {
                        url = QDir::cleanPath(rootDir.absoluteFilePath(url));
                    }
                    img.load(url);
                }
                ImageItem *rec = new ImageItem(img);
                scene->addItem(rec);
                gitem = rec;
            } else if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsSvgItem") {
                QString base64
                    = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
                QGraphicsSvgItem *rec = NULL;
                if (!base64.isEmpty()) {
                    rec = new QGraphicsSvgItem();
                    QSvgRenderer *renderer
                        = new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec);
                    rec->setSharedRenderer(renderer);
                } else {
                    QString url = items.item(i)
                                      .namedItem("content")
                                      .attributes()
                                      .namedItem("url")
                                      .nodeValue();
                    if (QFileInfo(url).isRelative()) {
                        url = QDir::cleanPath(rootDir.absoluteFilePath(url));
                    }
                    rec = new QGraphicsSvgItem(url);
                }
                if (rec) {
                    scene->addItem(rec);
                    gitem = rec;
                }
            }
        }
        //pos and transform
        if (gitem) {
            QPointF p(node.namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
                      node.namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
            gitem->setPos(p);
            gitem->setTransform(stringToTransform(
                node.namedItem("position").firstChild().firstChild().nodeValue()));
            int zValue = nodeAttributes.namedItem("z-index").nodeValue().toInt();
            gitem->setZValue(zValue);

            // effects
            QDomNode eff = items.item(i).namedItem("effect");
            if (!eff.isNull()) {
                QDomElement e = eff.toElement();
                if (e.attribute("type") == "blur") {
                    QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
                    blur->setBlurRadius(e.attribute("blurradius").toInt());
                    gitem->setGraphicsEffect(blur);
                } else if (e.attribute("type") == "shadow") {
                    QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
                    shadow->setBlurRadius(e.attribute("blurradius").toInt());
                    shadow->setOffset(e.attribute("xoffset").toInt(),
                                      e.attribute("yoffset").toInt());
                    gitem->setGraphicsEffect(shadow);
                }
            }
        }
    }

    QDomNode n = title.firstChildElement("background");
    if (!n.isNull()) {
        QColor color = QColor(stringToColor(n.attributes().namedItem("color").nodeValue()));
        self->has_alpha = color.alpha() != 255;
        if (color.alpha() > 0) {
            QGraphicsRectItem *rec = scene->addRect(0,
                                                    0,
                                                    scene->width(),
                                                    scene->height(),
                                                    QPen(Qt::NoPen),
                                                    QBrush(color));
            rec->setZValue(-1100);
        }
    }

    QString startRect;
    n = title.firstChildElement("startviewport");
    // Check if node exists, if it has an x attribute, it is an old version title, don't use viewport
    if (!n.isNull() && !n.toElement().hasAttribute("x")) {
        startRect = n.attributes().namedItem("rect").nodeValue();
    }
    n = title.firstChildElement("endviewport");
    // Check if node exists, if it has an x attribute, it is an old version title, don't use viewport
    if (!n.isNull() && !n.toElement().hasAttribute("x")) {
        QString rect = n.attributes().namedItem("rect").nodeValue();
        if (startRect != rect) {
            mlt_properties_set(producer_props, "_endrect", rect.toUtf8().data());
        } else {
            mlt_properties_clear(producer_props, "_endrect");
        }
    }
    if (!startRect.isEmpty()) {
        mlt_properties_set(producer_props, "_startrect", startRect.toUtf8().data());
    }
    return;
}

int initTitleProducer(mlt_producer producer)
{
    if (!createQApplicationIfNeeded(MLT_PRODUCER_SERVICE(producer))) {
        return false;
    }
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    if (!QMetaType::type("QTextCursor")) {
        qRegisterMetaType<QTextCursor>("QTextCursor");
    }
#else
    if (!QMetaType::fromType<QTextCursor>().isRegistered()) {
        qRegisterMetaType<QTextCursor>();
    }
    if (!QMetaType::fromType<std::shared_ptr<TypeWriter>>().isRegistered()) {
        qRegisterMetaType<std::shared_ptr<TypeWriter>>();
    }
#endif
    return true;
}

void drawKdenliveTitle(producer_ktitle self,
                       mlt_frame frame,
                       mlt_image_format format,
                       int width,
                       int height,
                       double position,
                       int force_refresh)
{
    // Obtain the producer
    mlt_producer producer = &self->parent;
    mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer));
    mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer);

    // Obtain properties of frame
    mlt_properties properties = MLT_FRAME_PROPERTIES(frame);

    pthread_mutex_lock(&self->mutex);

    // Check if user wants us to reload the image or if we need animation
    bool animated = mlt_properties_get(producer_props, "_endrect") != NULL;

    if (mlt_properties_get(producer_props, "_animated") != NULL || force_refresh == 1
        || width != self->current_width || height != self->current_height || animated) {
        if (!animated) {
            // Cache image only if no animation
            self->current_image = NULL;
            mlt_properties_set_data(producer_props, "_cached_image", NULL, 0, NULL, NULL);
        }
        mlt_properties_set_int(producer_props, "force_reload", 0);
    }
    int image_size = width * height * 4;
    if (self->current_image == NULL || animated) {
        // restore QGraphicsScene
        QGraphicsScene *scene = static_cast<QGraphicsScene *>(
            mlt_properties_get_data(producer_props, "qscene", NULL));
        self->current_alpha = NULL;

        if (force_refresh == 1 && scene) {
            scene = NULL;
            mlt_properties_set_data(producer_props, "qscene", NULL, 0, NULL, NULL);
        }

        if (scene == NULL) {
            scene = new QGraphicsScene();
            scene->setItemIndexMethod(QGraphicsScene::NoIndex);
            scene->setSceneRect(0,
                                0,
                                mlt_properties_get_int(properties, "width"),
                                mlt_properties_get_int(properties, "height"));
            if (mlt_properties_get(producer_props, "resource")
                && mlt_properties_get(producer_props, "resource")[0] != '\0') {
                // The title has a resource property, so we read all properties from the resource.
                // Do not serialize the xmldata
                loadFromXml(self,
                            scene,
                            mlt_properties_get(producer_props, "_xmldata"),
                            mlt_properties_get(producer_props, "templatetext"));
            } else {
                // The title has no resource, all data should be serialized
                loadFromXml(self,
                            scene,
                            mlt_properties_get(producer_props, "xmldata"),
                            mlt_properties_get(producer_props, "templatetext"));
            }
            mlt_properties_set_data(producer_props,
                                    "qscene",
                                    scene,
                                    0,
                                    (mlt_destructor) qscene_delete,
                                    NULL);
        }

        QRectF start = stringToRect(QString(mlt_properties_get(producer_props, "_startrect")));
        QRectF end = stringToRect(QString(mlt_properties_get(producer_props, "_endrect")));
        const QRectF source(0, 0, width, height);

        if (start.isNull()) {
            start = QRectF(0,
                           0,
                           mlt_properties_get_int(producer_props, "meta.media.width"),
                           mlt_properties_get_int(producer_props, "meta.media.height"));
        }

        // Effects
        QList<QGraphicsItem *> items = scene->items();
        PlainTextItem *titem = NULL;
        for (int i = 0; i < items.count(); i++) {
            titem = dynamic_cast<PlainTextItem *>(items.at(i));
            if (titem && !titem->data(0).isNull()) {
                int itemId = titem->data(0).toInt();
                std::shared_ptr<TypeWriter> ptr = scene->property(QString::number(itemId).toLatin1())
                                                      .value<std::shared_ptr<TypeWriter>>();
                titem->updateText(ptr->render(position).c_str());
                titem->updateShadows();
            }
        }

        //must be extracted from kdenlive title
        self->rgba_image = (uint8_t *) mlt_pool_alloc(image_size);
        // Initialize the QImage with the MLT image because the data formats match.
        QImage img(self->rgba_image, width, height, QImage::Format_RGBA8888);
        img.fill(0);
        QPainter p1;
        p1.begin(&img);
        p1.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                          | QPainter::HighQualityAntialiasing
#endif
        );
        //| QPainter::SmoothPixmapTransform );
        mlt_position anim_out = mlt_properties_get_position(producer_props, "_animation_out");

        if (end.isNull()) {
            scene->render(&p1, source, start, Qt::IgnoreAspectRatio);
        } else if (position > anim_out) {
            scene->render(&p1, source, end, Qt::IgnoreAspectRatio);
        } else {
            double percentage = 0;
            if (position && anim_out)
                percentage = position / anim_out;
            QPointF topleft = start.topLeft() + (end.topLeft() - start.topLeft()) * percentage;
            QPointF bottomRight = start.bottomRight()
                                  + (end.bottomRight() - start.bottomRight()) * percentage;
            const QRectF r1(topleft, bottomRight);
            scene->render(&p1, source, r1, Qt::IgnoreAspectRatio);
            if (profile && !profile->progressive) {
                int line = 0;
                double percentage_next_filed = (position + 0.5) / anim_out;
                QPointF topleft_next_field = start.topLeft()
                                             + (end.topLeft() - start.topLeft())
                                                   * percentage_next_filed;
                QPointF bottomRight_next_field = start.bottomRight()
                                                 + (end.bottomRight() - start.bottomRight())
                                                       * percentage_next_filed;
                const QRectF r2(topleft_next_field, bottomRight_next_field);
                QImage img1(width, height, QImage::Format_ARGB32);
                img1.fill(0);
                QPainter p2;
                p2.begin(&img1);
                p2.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
                                  | QPainter::HighQualityAntialiasing
#endif
                );
                scene->render(&p2, source, r2, Qt::IgnoreAspectRatio);
                p2.end();
                int next_field_line = (mlt_properties_get_int(producer_props, "top_field_first")
                                           ? 1
                                           : 0);
                for (line = next_field_line; line < height; line += 2) {
                    memcpy(img.scanLine(line), img1.scanLine(line), img.bytesPerLine());
                }
            }
        }
        p1.end();
        self->format = mlt_image_rgba;

        convert_qimage_to_mlt(&img, self->rgba_image, width, height);
        self->current_image = (uint8_t *) mlt_pool_alloc(image_size);
        memcpy(self->current_image, self->rgba_image, image_size);
        mlt_properties_set_data(producer_props,
                                "_cached_buffer",
                                self->rgba_image,
                                image_size,
                                mlt_pool_release,
                                NULL);
        mlt_properties_set_data(producer_props,
                                "_cached_image",
                                self->current_image,
                                image_size,
                                mlt_pool_release,
                                NULL);
        self->current_width = width;
        self->current_height = height;

        uint8_t *alpha = NULL;
        if ((alpha = mlt_frame_get_alpha(frame))) {
            self->current_alpha = (uint8_t *) mlt_pool_alloc(width * height);
            memcpy(self->current_alpha, alpha, width * height);
            mlt_properties_set_data(producer_props,
                                    "_cached_alpha",
                                    self->current_alpha,
                                    width * height,
                                    mlt_pool_release,
                                    NULL);
        }
    }

    // Convert image to requested format
    if (format != mlt_image_none && format != mlt_image_movit && format != self->format) {
        uint8_t *buffer = NULL;
        if (self->format != mlt_image_rgba) {
            // Image buffer was previously converted, revert to original rgba buffer
            self->current_image = (uint8_t *) mlt_pool_alloc(image_size);
            memcpy(self->current_image, self->rgba_image, image_size);
            mlt_properties_set_data(producer_props,
                                    "_cached_image",
                                    self->current_image,
                                    image_size,
                                    mlt_pool_release,
                                    NULL);
            self->format = mlt_image_rgba;
        }

        // First, set the image so it can be converted when we get it
        mlt_frame_replace_image(frame, self->current_image, self->format, width, height);
        mlt_frame_set_image(frame, self->current_image, image_size, NULL);
        self->format = format;

        // get_image will do the format conversion
        mlt_frame_get_image(frame, &buffer, &format, &width, &height, 0);

        // cache copies of the image and alpha buffers
        if (buffer) {
            image_size = mlt_image_format_size(format, width, height, NULL);
            self->current_image = (uint8_t *) mlt_pool_alloc(image_size);
            memcpy(self->current_image, buffer, image_size);
            mlt_properties_set_data(producer_props,
                                    "_cached_image",
                                    self->current_image,
                                    image_size,
                                    mlt_pool_release,
                                    NULL);
        }
        if ((buffer = mlt_frame_get_alpha(frame))) {
            self->current_alpha = (uint8_t *) mlt_pool_alloc(width * height);
            memcpy(self->current_alpha, buffer, width * height);
            mlt_properties_set_data(producer_props,
                                    "_cached_alpha",
                                    self->current_alpha,
                                    width * height,
                                    mlt_pool_release,
                                    NULL);
        }
    }

    pthread_mutex_unlock(&self->mutex);
    mlt_properties_set_int(properties, "width", self->current_width);
    mlt_properties_set_int(properties, "height", self->current_height);
}
