// This file is part of Blend2D project <https://blend2d.com>
//
// See blend2d.h or LICENSE.md for license and copyright information
// SPDX-License-Identifier: Zlib

#ifndef BLEND2D_PIXELCONVERTER_H_INCLUDED
#define BLEND2D_PIXELCONVERTER_H_INCLUDED

#include "api.h"
#include "format.h"
#include "geometry.h"

//! \addtogroup bl_imaging
//! \{

//! \cond INTERNAL
//! \name BLPixelConverter - Types
//! \{

//! Pixel converter function.
typedef BLResult (BL_CDECL* BLPixelConverterFunc)(
  const BLPixelConverterCore* self,
  uint8_t* dstData, intptr_t dstStride,
  const uint8_t* srcData, intptr_t srcStride,
  uint32_t w, uint32_t h, const BLPixelConverterOptions* options) BL_NOEXCEPT;

//! \}
//! \endcond

//! \name BLPixelConverter - Constants
//! \{

//! Flags used by `BLPixelConverter::create()` function.
BL_DEFINE_ENUM(BLPixelConverterCreateFlags) {
  //! No flags.
  BL_PIXEL_CONVERTER_CREATE_NO_FLAGS = 0u,

  //! Specifies that the source palette in `BLFormatInfo` doesn't have to by copied by `BLPixelConverter`. The caller
  //! must ensure that the palette would stay valid until the pixel converter is destroyed.
  BL_PIXEL_CONVERTER_CREATE_FLAG_DONT_COPY_PALETTE = 0x00000001u,

  //! Specifies that the source palette in `BLFormatInfo` is alterable and the pixel converter can modify it when
  //! preparing the conversion. The modification can be irreversible so only use this flag when you are sure that
  //! the palette passed to `BLPixelConverter::create()` won't be needed outside of pixel conversion.
  //!
  //! \note The flag `BL_PIXEL_CONVERTER_CREATE_FLAG_DONT_COPY_PALETTE` must be set as well, otherwise this flag would
  //! be ignored.
  BL_PIXEL_CONVERTER_CREATE_FLAG_ALTERABLE_PALETTE = 0x00000002u,

  //! When there is no built-in conversion between the given pixel formats it's possible to use an intermediate format
  //! that is used during conversion. In such case the base pixel converter creates two more converters that are then
  //! used internally.
  //!
  //! This option disables such feature - creating a pixel converter would fail with `BL_ERROR_NOT_IMPLEMENTED` error
  //! if direct conversion is not possible.
  BL_PIXEL_CONVERTER_CREATE_FLAG_NO_MULTI_STEP = 0x00000004u

  BL_FORCE_ENUM_UINT32(BL_PIXEL_CONVERTER_CREATE_FLAG)
};

//! \}

//! \name BLPixelConverter - Structs
//! \{

//! Pixel conversion options.
struct BLPixelConverterOptions {
  BLPointI origin;
  size_t gap;
};

//! \}
//! \}

//! \addtogroup bl_c_api
//! \{

//! \name BLPixelConverter - C API
//! \{

//! Pixel converter [C API].
struct BLPixelConverterCore {
  union {
    struct {
      //! Converter function.
      BLPixelConverterFunc convertFunc;
      //! Internal flags used by the converter - non-zero value means initialized.
      uint8_t internalFlags;
    };

    //! Internal data not exposed to users, aligned to sizeof(void*).
    uint8_t data[80];
  };
};

BL_BEGIN_C_DECLS

BL_API BLResult BL_CDECL blPixelConverterInit(BLPixelConverterCore* self) BL_NOEXCEPT_C;
BL_API BLResult BL_CDECL blPixelConverterInitWeak(BLPixelConverterCore* self, const BLPixelConverterCore* other) BL_NOEXCEPT_C;
BL_API BLResult BL_CDECL blPixelConverterDestroy(BLPixelConverterCore* self) BL_NOEXCEPT_C;
BL_API BLResult BL_CDECL blPixelConverterReset(BLPixelConverterCore* self) BL_NOEXCEPT_C;
BL_API BLResult BL_CDECL blPixelConverterAssign(BLPixelConverterCore* self, const BLPixelConverterCore* other) BL_NOEXCEPT_C;
BL_API BLResult BL_CDECL blPixelConverterCreate(BLPixelConverterCore* self, const BLFormatInfo* dstInfo, const BLFormatInfo* srcInfo, BLPixelConverterCreateFlags createFlags) BL_NOEXCEPT_C;

BL_API BLResult BL_CDECL blPixelConverterConvert(const BLPixelConverterCore* self,
  void* dstData, intptr_t dstStride,
  const void* srcData, intptr_t srcStride,
  uint32_t w, uint32_t h, const BLPixelConverterOptions* options) BL_NOEXCEPT_C;

BL_END_C_DECLS

//! \}
//! \}

//! \addtogroup bl_imaging
//! \{

//! \name BLPixelConverter - C++ API
//! \{

#ifdef __cplusplus
//! Pixel converter [C++ API].
//!
//! Provides an interface to convert pixels between various pixel formats. The primary purpose of this class is to
//! allow efficient conversion between pixel formats used natively by Blend2D and pixel formats used elsewhere, for
//! example image codecs or native framebuffers.
//!
//! \note A default-initialized converter has a valid conversion function that would return \ref BL_ERROR_NOT_INITIALIZED
//! if invoked. Use `isInitialized()` member function to test whether the pixel converter was properly initialized.
class BLPixelConverter final : public BLPixelConverterCore {
public:
  //! Creates a new default-initialized pixel converter.
  BL_INLINE BLPixelConverter() noexcept {
    blPixelConverterInit(this);
  }

  //! Creates a copy of the `other` converter.
  //!
  //! If the `other` converter has dynamically allocated resources they will be properly managed (reference counting).
  //! Only very specific converters require such resources so this operation should be considered very cheap.
  BL_INLINE BLPixelConverter(const BLPixelConverter& other) noexcept {
    blPixelConverterInitWeak(this, &other);
  }

  //! Destroys the pixel-converter and releases all resources allocated by it.
  BL_INLINE ~BLPixelConverter() noexcept {
    blPixelConverterDestroy(this);
  }

  BL_INLINE BLPixelConverter& operator=(const BLPixelConverter& other) noexcept {
    blPixelConverterAssign(this, &other);
    return *this;
  }

  //! Tests whether if the converter is initialized.
  BL_NODISCARD
  BL_INLINE bool isInitialized() const noexcept {
    // Internal flags are non-zero when the pixel converter is initialized.
    return internalFlags != 0;
  }

  //! Reset the pixel converter.
  BL_INLINE BLResult reset() noexcept {
    return blPixelConverterReset(this);
  }

  //! Assigns the `other` pixel converter into this one.
  BL_INLINE BLResult assign(const BLPixelConverter& other) noexcept {
    return blPixelConverterAssign(this, &other);
  }

  //! Creates a new pixel converter that will convert pixels described by `srcInfo` into pixels described by `dstInfo`.
  //!
  //! Use `createFlags` to further specify the parameters of the conversion.
  //!
  //! \note Destination and source format informattion must be valid, otherwise `BL_ERROR_INVALID_VALUE` would be returned.
  BL_INLINE BLResult create(const BLFormatInfo& dstInfo, const BLFormatInfo& srcInfo, BLPixelConverterCreateFlags createFlags = BL_PIXEL_CONVERTER_CREATE_NO_FLAGS) noexcept {
    return blPixelConverterCreate(this, &dstInfo, &srcInfo, createFlags);
  }

  //! Converts a single span of pixels of `w` width.
  BL_INLINE BLResult convertSpan(void* dstData, const void* srcData, uint32_t w,
                                 const BLPixelConverterOptions* options = nullptr) const noexcept {
    return convertFunc(this, static_cast<uint8_t*>(dstData), 0, static_cast<const uint8_t*>(srcData), 0, w, 1, options);
  }

  //! Converts a rectangular area of pixels from source format to destination.
  BL_INLINE BLResult convertRect(void* dstData, intptr_t dstStride,
                                 const void* srcData, intptr_t srcStride,
                                 uint32_t w, uint32_t h, const BLPixelConverterOptions* options = nullptr) const noexcept {
    return convertFunc(this, static_cast<uint8_t*>(dstData), dstStride, static_cast<const uint8_t*>(srcData), srcStride, w, h, options);
  }
};

#endif
//! \}

//! \}

#endif // BLEND2D_PIXELCONVERTER_H_INCLUDED
