// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#ifndef QPIXELFORMAT_H
#define QPIXELFORMAT_H

#include <QtGui/qtguiglobal.h>

#include <QtCore/qobjectdefs.h>

QT_BEGIN_NAMESPACE

class QPixelFormat
{
    Q_GADGET_EXPORT(Q_GUI_EXPORT)

    // QPixelFormat basically is a glorified quint64, split into several fields.
    // We could use bit-fields, but GCC at least generates horrible, horrible code for them,
    // so we do the bit-twiddling ourselves.
    enum FieldWidth {
        ModelFieldWidth = 4,
        FirstFieldWidth = 6,
        SecondFieldWidth = FirstFieldWidth,
        ThirdFieldWidth = FirstFieldWidth,
        FourthFieldWidth = FirstFieldWidth,
        FifthFieldWidth = FirstFieldWidth,
        AlphaFieldWidth = FirstFieldWidth,
        AlphaUsageFieldWidth = 1,
        AlphaPositionFieldWidth = 1,
        PremulFieldWidth = 1,
        TypeInterpretationFieldWidth = 4,
        ByteOrderFieldWidth = 2,
        SubEnumFieldWidth = 6,
        UnusedFieldWidth = 9,

        TotalFieldWidthByWidths = ModelFieldWidth + FirstFieldWidth + SecondFieldWidth + ThirdFieldWidth +
                                  FourthFieldWidth + FifthFieldWidth + AlphaFieldWidth + AlphaUsageFieldWidth +
                                  AlphaPositionFieldWidth + PremulFieldWidth + TypeInterpretationFieldWidth +
                                  ByteOrderFieldWidth + SubEnumFieldWidth + UnusedFieldWidth
    };

    enum Field {
        ModelField = 0,
        // work around bug in old clang versions: when building webkit
        // with XCode 4.6 and older this fails compilation, thus cast to int
        FirstField = ModelField + int(ModelFieldWidth),
        SecondField = FirstField + int(FirstFieldWidth),
        ThirdField = SecondField + int(SecondFieldWidth),
        FourthField = ThirdField + int(ThirdFieldWidth),
        FifthField = FourthField + int(FourthFieldWidth),
        AlphaField = FifthField + int(FifthFieldWidth),
        AlphaUsageField = AlphaField + int(AlphaFieldWidth),
        AlphaPositionField = AlphaUsageField + int(AlphaUsageFieldWidth),
        PremulField = AlphaPositionField + int(AlphaPositionFieldWidth),
        TypeInterpretationField = PremulField + int(PremulFieldWidth),
        ByteOrderField = TypeInterpretationField + int(TypeInterpretationFieldWidth),
        SubEnumField = ByteOrderField + int(ByteOrderFieldWidth),
        UnusedField = SubEnumField + int(SubEnumFieldWidth),

        TotalFieldWidthByOffsets = UnusedField + int(UnusedFieldWidth)
    };

    static_assert(uint(TotalFieldWidthByWidths) == uint(TotalFieldWidthByOffsets));
    static_assert(uint(TotalFieldWidthByWidths) == 8 * sizeof(quint64));

    constexpr inline uchar get(Field offset, FieldWidth width) const noexcept
    { return uchar((data >> uint(offset)) & ((Q_UINT64_C(1) << uint(width)) - Q_UINT64_C(1))); }
    constexpr static inline quint64 set(Field offset, FieldWidth width, uchar value)
    { return (quint64(value) & ((Q_UINT64_C(1) << uint(width)) - Q_UINT64_C(1))) << uint(offset); }

public:
    enum ColorModel {
        RGB,
        BGR,
        Indexed,
        Grayscale,
        CMYK,
        HSL,
        HSV,
        YUV,
        Alpha
    };
    Q_ENUM(ColorModel)

    enum AlphaUsage {
        UsesAlpha,
        IgnoresAlpha
    };
    Q_ENUM(AlphaUsage)

    enum AlphaPosition {
        AtBeginning,
        AtEnd
    };
    Q_ENUM(AlphaPosition)

    enum AlphaPremultiplied {
        NotPremultiplied,
        Premultiplied
    };
    Q_ENUM(AlphaPremultiplied)

    enum TypeInterpretation {
        UnsignedInteger,
        UnsignedShort,
        UnsignedByte,
        FloatingPoint
    };
    Q_ENUM(TypeInterpretation)

    enum YUVLayout {
        YUV444,
        YUV422,
        YUV411,
        YUV420P,
        YUV420SP,
        YV12,
        UYVY,
        YUYV,
        NV12,
        NV21,
        IMC1,
        IMC2,
        IMC3,
        IMC4,
        Y8,
        Y16
    };
    Q_ENUM(YUVLayout)

    enum ByteOrder {
        LittleEndian,
        BigEndian,
        CurrentSystemEndian
    };
    Q_ENUM(ByteOrder)

    constexpr inline QPixelFormat() noexcept : data(0) {}
    constexpr inline QPixelFormat(ColorModel colorModel,
                                           uchar firstSize,
                                           uchar secondSize,
                                           uchar thirdSize,
                                           uchar fourthSize,
                                           uchar fifthSize,
                                           uchar alphaSize,
                                           AlphaUsage alphaUsage,
                                           AlphaPosition alphaPosition,
                                           AlphaPremultiplied premultiplied,
                                           TypeInterpretation typeInterpretation,
                                           ByteOrder byteOrder = CurrentSystemEndian,
                                           uchar subEnum = 0) noexcept;

    constexpr inline ColorModel colorModel() const  noexcept { return ColorModel(get(ModelField, ModelFieldWidth)); }
    constexpr inline uchar channelCount() const noexcept { return (get(FirstField, FirstFieldWidth) > 0) +
                                                                                 (get(SecondField, SecondFieldWidth) > 0) +
                                                                                 (get(ThirdField, ThirdFieldWidth) > 0) +
                                                                                 (get(FourthField, FourthFieldWidth) > 0) +
                                                                                 (get(FifthField, FifthFieldWidth) > 0) +
                                                                                 (get(AlphaField, AlphaFieldWidth) > 0); }

    constexpr inline uchar redSize() const noexcept { return get(FirstField, FirstFieldWidth); }
    constexpr inline uchar greenSize() const noexcept { return get(SecondField, SecondFieldWidth); }
    constexpr inline uchar blueSize() const noexcept { return get(ThirdField, ThirdFieldWidth); }

    constexpr inline uchar cyanSize() const noexcept { return get(FirstField, FirstFieldWidth); }
    constexpr inline uchar magentaSize() const noexcept { return get(SecondField, SecondFieldWidth); }
    constexpr inline uchar yellowSize() const noexcept { return get(ThirdField, ThirdFieldWidth); }
    constexpr inline uchar blackSize() const noexcept { return get(FourthField, FourthFieldWidth); }

    constexpr inline uchar hueSize() const noexcept { return get(FirstField, FirstFieldWidth); }
    constexpr inline uchar saturationSize() const noexcept { return get(SecondField, SecondFieldWidth); }
    constexpr inline uchar lightnessSize() const noexcept { return get(ThirdField, ThirdFieldWidth); }
    constexpr inline uchar brightnessSize() const noexcept { return get(ThirdField, ThirdFieldWidth); }

    constexpr inline uchar alphaSize() const noexcept { return get(AlphaField, AlphaFieldWidth); }

    constexpr inline uchar bitsPerPixel() const noexcept { return get(FirstField, FirstFieldWidth) +
                                                                                 get(SecondField, SecondFieldWidth) +
                                                                                 get(ThirdField, ThirdFieldWidth) +
                                                                                 get(FourthField, FourthFieldWidth) +
                                                                                 get(FifthField, FifthFieldWidth) +
                                                                                 get(AlphaField, AlphaFieldWidth); }

    constexpr inline AlphaUsage alphaUsage() const noexcept { return AlphaUsage(get(AlphaUsageField, AlphaUsageFieldWidth)); }
    constexpr inline AlphaPosition alphaPosition() const noexcept { return AlphaPosition(get(AlphaPositionField, AlphaPositionFieldWidth)); }
    constexpr inline AlphaPremultiplied premultiplied() const noexcept { return AlphaPremultiplied(get(PremulField, PremulFieldWidth)); }
    constexpr inline TypeInterpretation typeInterpretation() const noexcept { return TypeInterpretation(get(TypeInterpretationField, TypeInterpretationFieldWidth)); }
    constexpr inline ByteOrder byteOrder() const noexcept { return ByteOrder(get(ByteOrderField, ByteOrderFieldWidth)); }

    constexpr inline YUVLayout yuvLayout() const noexcept { return YUVLayout(get(SubEnumField, SubEnumFieldWidth)); }
    constexpr inline uchar subEnum() const noexcept { return get(SubEnumField, SubEnumFieldWidth); }

private:
    constexpr static inline ByteOrder resolveByteOrder(ByteOrder bo) { return resolveByteOrder(bo, UnsignedInteger ); }
    constexpr static inline ByteOrder resolveByteOrder(ByteOrder bo, TypeInterpretation ti)
    { return bo == CurrentSystemEndian ? ti == UnsignedByte || Q_BYTE_ORDER == Q_BIG_ENDIAN ? BigEndian : LittleEndian : bo ; }

private:
    quint64 data;

    friend Q_DECL_CONST_FUNCTION constexpr inline bool operator==(QPixelFormat fmt1, QPixelFormat fmt2)
    { return fmt1.data == fmt2.data; }

    friend Q_DECL_CONST_FUNCTION constexpr inline bool operator!=(QPixelFormat fmt1, QPixelFormat fmt2)
    { return !(fmt1 == fmt2); }

#ifndef QT_NO_DEBUG_STREAM
    friend Q_GUI_EXPORT QDebug operator<<(QDebug debug, const QPixelFormat &format);
#endif
};
static_assert(sizeof(QPixelFormat) == sizeof(quint64));
Q_DECLARE_TYPEINFO(QPixelFormat, Q_PRIMITIVE_TYPE);


namespace QtPrivate {
    QPixelFormat Q_GUI_EXPORT QPixelFormat_createYUV(QPixelFormat::YUVLayout yuvLayout,
                                                     uchar alphaSize,
                                                     QPixelFormat::AlphaUsage alphaUsage,
                                                     QPixelFormat::AlphaPosition alphaPosition,
                                                     QPixelFormat::AlphaPremultiplied premultiplied,
                                                     QPixelFormat::TypeInterpretation typeInterpretation,
                                                     QPixelFormat::ByteOrder byteOrder);
}

constexpr QPixelFormat::QPixelFormat(ColorModel mdl,
                                     uchar firstSize,
                                     uchar secondSize,
                                     uchar thirdSize,
                                     uchar fourthSize,
                                     uchar fifthSize,
                                     uchar alfa,
                                     AlphaUsage usage,
                                     AlphaPosition position,
                                     AlphaPremultiplied premult,
                                     TypeInterpretation typeInterp,
                                     ByteOrder b_order,
                                     uchar s_enum) noexcept
    : data(set(ModelField, ModelFieldWidth, uchar(mdl)) |
           set(FirstField, FirstFieldWidth, firstSize) |
           set(SecondField, SecondFieldWidth, secondSize) |
           set(ThirdField, ThirdFieldWidth, thirdSize) |
           set(FourthField, FourthFieldWidth, fourthSize) |
           set(FifthField, FifthFieldWidth, fifthSize) |
           set(AlphaField, AlphaFieldWidth, alfa) |
           set(AlphaUsageField, AlphaUsageFieldWidth, uchar(usage)) |
           set(AlphaPositionField, AlphaPositionFieldWidth, uchar(position)) |
           set(PremulField, PremulFieldWidth, uchar(premult)) |
           set(TypeInterpretationField, TypeInterpretationFieldWidth, uchar(typeInterp)) |
           set(ByteOrderField, ByteOrderFieldWidth, uchar(resolveByteOrder(b_order, typeInterp))) |
           set(SubEnumField, SubEnumFieldWidth, s_enum) |
           set(UnusedField, UnusedFieldWidth, 0))
{
}

constexpr inline QPixelFormat qPixelFormatRgba(uchar red,
                                               uchar green,
                                               uchar blue,
                                               uchar alfa,
                                               QPixelFormat::AlphaUsage usage,
                                               QPixelFormat::AlphaPosition position,
                                               QPixelFormat::AlphaPremultiplied pmul=QPixelFormat::NotPremultiplied,
                                               QPixelFormat::TypeInterpretation typeInt=QPixelFormat::UnsignedInteger) noexcept
{
    return QPixelFormat(QPixelFormat::RGB,
                        red,
                        green,
                        blue,
                        0,
                        0,
                        alfa,
                        usage,
                        position,
                        pmul,
                        typeInt);
}

constexpr inline QPixelFormat qPixelFormatGrayscale(uchar channelSize,
                                                    QPixelFormat::TypeInterpretation typeInt=QPixelFormat::UnsignedInteger) noexcept
{
    return QPixelFormat(QPixelFormat::Grayscale,
                        channelSize,
                        0,
                        0,
                        0,
                        0,
                        0,
                        QPixelFormat::IgnoresAlpha,
                        QPixelFormat::AtBeginning,
                        QPixelFormat::NotPremultiplied,
                        typeInt);
}

constexpr inline QPixelFormat qPixelFormatAlpha(uchar channelSize,
                                                QPixelFormat::TypeInterpretation typeInt=QPixelFormat::UnsignedInteger) noexcept
{
    return QPixelFormat(QPixelFormat::Alpha,
                        0,
                        0,
                        0,
                        0,
                        0,
                        channelSize,
                        QPixelFormat::UsesAlpha,
                        QPixelFormat::AtBeginning,
                        QPixelFormat::NotPremultiplied,
                        typeInt);
}

constexpr inline QPixelFormat qPixelFormatCmyk(uchar channelSize,
                                               uchar alfa=0,
                                               QPixelFormat::AlphaUsage usage=QPixelFormat::IgnoresAlpha,
                                               QPixelFormat::AlphaPosition position=QPixelFormat::AtBeginning,
                                               QPixelFormat::TypeInterpretation typeInt=QPixelFormat::UnsignedInteger) noexcept
{
    return QPixelFormat(QPixelFormat::CMYK,
                        channelSize,
                        channelSize,
                        channelSize,
                        channelSize,
                        0,
                        alfa,
                        usage,
                        position,
                        QPixelFormat::NotPremultiplied,
                        typeInt);
}

constexpr inline QPixelFormat qPixelFormatHsl(uchar channelSize,
                                              uchar alfa=0,
                                              QPixelFormat::AlphaUsage usage=QPixelFormat::IgnoresAlpha,
                                              QPixelFormat::AlphaPosition position=QPixelFormat::AtBeginning,
                                              QPixelFormat::TypeInterpretation typeInt=QPixelFormat::FloatingPoint) noexcept
{
    return QPixelFormat(QPixelFormat::HSL,
                        channelSize,
                        channelSize,
                        channelSize,
                        0,
                        0,
                        alfa,
                        usage,
                        position,
                        QPixelFormat::NotPremultiplied,
                        typeInt);
}

constexpr inline QPixelFormat qPixelFormatHsv(uchar channelSize,
                                              uchar alfa=0,
                                              QPixelFormat::AlphaUsage usage=QPixelFormat::IgnoresAlpha,
                                              QPixelFormat::AlphaPosition position=QPixelFormat::AtBeginning,
                                              QPixelFormat::TypeInterpretation typeInt=QPixelFormat::FloatingPoint) noexcept
{
    return QPixelFormat(QPixelFormat::HSV,
                        channelSize,
                        channelSize,
                        channelSize,
                        0,
                        0,
                        alfa,
                        usage,
                        position,
                        QPixelFormat::NotPremultiplied,
                        typeInt);
}

inline QPixelFormat qPixelFormatYuv(QPixelFormat::YUVLayout layout,
                                    uchar alfa=0,
                                    QPixelFormat::AlphaUsage usage=QPixelFormat::IgnoresAlpha,
                                    QPixelFormat::AlphaPosition position=QPixelFormat::AtBeginning,
                                    QPixelFormat::AlphaPremultiplied p_mul=QPixelFormat::NotPremultiplied,
                                    QPixelFormat::TypeInterpretation typeInt=QPixelFormat::UnsignedByte,
                                    QPixelFormat::ByteOrder b_order=QPixelFormat::BigEndian)
{
    return QtPrivate::QPixelFormat_createYUV(layout,
                                             alfa,
                                             usage,
                                             position,
                                             p_mul,
                                             typeInt,
                                             b_order);
}

QT_END_NAMESPACE

#endif //QPIXELFORMAT_H
