//================================================================
// lib2D
//
// - Drawing
//
// AUTHOR:  Gabor Nagy
// DATE:    2026-Jun-10 19:41:58
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#ifndef _EImageDraw_h
#define _EImageDraw_h


#include <stdio.h>

#ifndef _EImage_h
#include <Image/Image.h>
#endif

#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
extern "C" {
#endif



// Used for drawing polygons
//
typedef struct
{
	EIndex  Start, End;
} EpPolyXRange;


#define EpRegionINSIDE             0x01
#define EpRegionBOUNDARY           0x02
#define EpRegionOUTSIDE            0x04
#define EpRegionALL                0x07


//========================================================
// Collect starting and ending X positions of a 2D line
//========================================================
static inline char Ep_PLine(EpPolyXRange* PEpPolyXRanges,  const EIndex PX1, const EIndex PY1,  const EIndex PX2, const EIndex PY2,
 const EIndex PImageSizeX, const EIndex PImageSizeY)
{
	char    LXU;
	EIndex  LYD = PY2 - PY1, LYD2;
	EIndex  LErr = 0;

	EIndex  LXD = PX2 - PX1;
	EIndex  LX = PX1;

	if(PY1 == PY2) return(0);  // Horizontal line

	if(LYD < 0) LYD = -LYD;

	if(LXD < 0)
	{
		LXD = -LXD;LXU = -1;
	}
	else LXU = 1;

// This line makes some edge lines better.
// It's not essential.
//
	if(LYD > LXD) LErr = LYD >> 1;

//Printf(" %d to %d   LXD:%d  LXU:%d   LYD:%d\n", PX1, PX2, LXD, LXU, LYD);fflush(stdout);

// Record x values along line
//
	LYD2 = LYD - 1;

	if(LXU == 1)
	{
		if(PY1 > PY2)
		{
			EIndex  LY = PY1;for(; LY > PY2 - 1; LY--)
			{
				if(LY >= 0 && LY < PImageSizeY)
				{
					PEpPolyXRanges[LY].Start = LX;
				}
				LErr += LXD;
				if(LErr > LYD2) do
				{
					LErr -= LYD;LX++;
				} while(LErr >= LYD);
			}
			return(0);
		}
		else
		{
			EIndex  LY = PY1;for(;LY < PY2+1; LY++)
			{
				if(LY >= 0 && LY < PImageSizeY)
				{
					PEpPolyXRanges[LY].End = LX;
				}
				LErr += LXD;
				if(LErr > LYD2) do
				{
					LErr -= LYD;LX++;
				} while(LErr >= LYD);
			}
			return(0);
		}
	}
	else
	{
		if(PY1 > PY2)
		{
			EIndex  LY = PY1;for(;LY > PY2 - 1;LY--)
			{
				if(LY >= 0 && LY < PImageSizeY)
				{
					PEpPolyXRanges[LY].Start = LX;
				}
				LErr += LXD;
				if(LErr > LYD2) do
				{
					LErr -= LYD;LX--;
				} while(LErr >= LYD);
			}
			return(0);
		}
		else
		{
			EIndex  LY = PY1;for(;LY < PY2 + 1;LY++)
			{
				if(LY >= 0 && LY < PImageSizeY)
				{
					PEpPolyXRanges[LY].End = LX;
				}
				LErr += LXD;
				if(LErr > LYD2) do
				{
					LErr -= LYD;LX--;
				} while(LErr >= LYD);
			}
		}
	}

	return(0);
}


//========================================================================================================================
// Clip coordinate for drawing (destination buffer only, no source)
//
// Arguments
//  pDst       Starting position in the destination (in/out)
//  pDstSize   Size of the destination buffer (e.g. width of the destination image)
//  pDrawSize  Size of region drawn (in/out)
//  pDoDraw    Flag to be set to FALSE, if the coordinate is out of range (out)
//
// Description
//  Clips a coordinate, used for copying. It will adjust pSrc, pCopySize and pDst.
//  If the coordinates are completely out of range, this version sets the flag pDoDraw to FALSE.
//  This macro may be used for multiple dimensions / coordinates.
//  For example:
//   EBool  LDoDraw = TRUE;Ep_Clip1D(LSrcX, LSrcImageSizeX,  LDstX, LDstImageSizeX,  LCopySizeX,  LDoDraw);if(LDoDraw)
//   {
//     Ep_Clip1D(LSrcY, LSrcImageSizeY,  LDstY, LDstImageSizeY,  LCopySizeY);if(LDoDraw)...
//========================================================================================================================
#define Ep_Clip1D(pDst, pDstSize,  pDrawSize,  pDoDraw) do\
{\
	if(pDst < 0)\
	{\
		if(pDst + pDrawSize <= 0) (pDoDraw) = FALSE;\
		else\
		{\
			pDrawSize += pDst;if(pDrawSize < 1) (pDoDraw) = FALSE;\
			else { pDst = 0;if(pDst + pDrawSize >= pDstSize) pDrawSize = pDstSize - pDst; }\
		}\
	}\
	else\
	{\
		if(pDst >= pDstSize) (pDoDraw) = FALSE;\
		else if(pDst + pDrawSize >= pDstSize) pDrawSize = pDstSize - pDst;\
	}\
} while(0)


//========================================================================================================================
// Clip coordinate for drawing into a destination: supports explicit destination clip region (in addition to the destination size)
//
// Arguments
//  pType         Data type of the coordinate
//  pDst          Starting position in the destination
//  pDstSize      Size of the destination buffer (e.g. width of the destination image)
//  pDstClipMin   Clip region minimum
//  pDstClipSize  Clip region length. If 0, no explicint clipping (equivalent to Ep_Clip1D)
//  pDrawSize     Size of region drawn
//  pDoDraw       Flag to be set to FALSE, if the coordinate is out of range
//
// Description
//  Clips a coordinate, used for copying. It will adjust pSrc, pCopySize and pDst.
//  If the coordinates are completely out of range, this version sets the flag pDoDraw to FALSE.
//  This macro may be used for multiple dimensions / coordinates.
//  For example:
//   EBool  LDoDraw = TRUE;Ep_Clip1DC(int,  LSrcX, LSrcImageSizeX, LClipX, LClipSizeX,  LDstX, LDstImageSizeX,  LCopySizeX,  LDoDraw);if(LDoDraw)
//   {
//     Ep_Clip1DC(int,  LSrcY, LSrcImageSizeY, LClipY, LClipSizeY,  LDstY, LDstImageSizeY,  LCopySizeY);if(LDoDraw)...
//========================================================================================================================
#define Ep_Clip1DC(pType,  pDst, pDstSize, pDstClipMin, pDstClipSize,  pDrawSize,  pDoDraw) do\
{\
	if(pDst < pDstClipMin)\
	{\
		if(pDst + pDrawSize <= pDstClipMin) (pDoDraw) = FALSE;\
		else\
		{\
			const pType  lDstMax = pDstClipSize > 0 ? pDstClipMin + pDstClipSize : pDstSize;\
			if(pDst >= lDstMax) (pDoDraw) = FALSE;\
			else\
			{\
				pDst = pDstClipMin;\
				const pType  lDiff = pDstClipMin - pDst;\
				pDrawSize -= lDiff;if(pDst + pDrawSize >= lDstMax) pDrawSize = lDstMax - pDst;\
			}\
		}\
	}\
	else\
	{\
		const pType  lDstMax = pDstClipSize > 0 ? pDstClipMin + pDstClipSize : pDstSize;\
		if(pDst >= lDstMax) (pDoDraw) = FALSE;\
		else if(pDst + pDrawSize >= lDstMax) pDrawSize = lDstMax - pDst;\
	}\
} while(0)


//========================================================================================================================
// Clip coordinate for src->dst copying
//
// Arguments
//  pType      Data type of the coordinate
//  pSrc       Starting position in the source
//  pSrcSize   Size of the source buffer (e.g. width of the source image)
//  pDst       Starting position in the destination
//  pDstSize   Size of the destination buffer (e.g. width of the destination image)
//  pCopySize  Size of region copied
//
// Description
//  Clips a coordinate, used for copying. It will adjust pSrc, pCopySize and pDst.
//  If the coordinates are completely out of range, this version causes the calling function to return with E_CLIPPED.
//  This macro may be used for multiple dimensions / coordinates.
//  For example:
//   Ep_Clip1DCopy_r(int,  LSrcX, LSrcImageSizeX,  LDstX, LDstImageSizeX,  LCopySizeX)
//   Ep_Clip1DCopy_r(int,  LSrcY, LSrcImageSizeY,  LDstY, LDstImageSizeY,  LCopySizeY)
//========================================================================================================================
#define Ep_Clip1DCopy_r(pType,  pSrc, pSrcSize,  pDst, pDstSize,  pCopySize) do\
{\
	if(pSrc < 0)\
	{\
		if(pSrc + pCopySize <= 0) return(E_CLIPPED);\
		else\
		{\
			const pType  lDiff = -pSrc;\
			pDst += lDiff;pCopySize -= lDiff;if(pCopySize < 1 || pDst >= pDstSize) return(E_CLIPPED);\
			else { pSrc = 0;if(pSrc + pCopySize >= pSrcSize) pCopySize = pSrcSize - pSrc; }\
		}\
	}\
	else\
	{\
		if(pSrc >= pSrcSize) return(E_CLIPPED);\
		else if(pSrc + pCopySize >= pSrcSize) pCopySize = pSrcSize - pSrc;\
	}\
\
	if(pDst < 0)\
	{\
		if(pDst + pCopySize <= 0) return(E_CLIPPED);\
		else\
		{\
			const pType  lDiff = -pDst;\
			pSrc += lDiff;pCopySize -= lDiff;if(pCopySize < 1 || pSrc >= pSrcSize) return(E_CLIPPED);\
			else { pDst = 0;if(pDst + pCopySize >= pDstSize) pCopySize = pDstSize - pDst; }\
		}\
	}\
	else\
	{\
		if(pDst >= pDstSize) return(E_CLIPPED);\
		else if(pDst + pCopySize >= pDstSize) pCopySize = pDstSize - pDst;\
	}\
} while(0)


//========================================================================================================================
// Clip coordinate for src->dst copying: supports explicit destination clip region (in addition to the destination size)
//
// Arguments
//  pType         Data type of the coordinate
//  pSrc          Starting position in the source
//  pSrcSize      Size of the source buffer (e.g. width of the source image)
//  pDst          Starting position in the destination
//  pDstSize      Size of the destination buffer (e.g. width of the destination image)
//  pDstClipMin   Clip region minimum
//  pDstClipSize  Clip region length. If 0, no explicint clipping (equivalent to Ep_Clip1DCopy_r)
//  pCopySize     Size of region copied
//
// Description
//  Clips a coordinate, used for copying. It will adjust pSrc, pCopySize and pDst.
//  If the coordinates are completely out of range, this version causes the calling function to return with E_CLIPPED.
//  This macro may be used for multiple dimensions / coordinates.
//  For example:
//   Ep_Clip1DCopyC_r(int,  LSrcX, LSrcImageSizeX, LClipX, LClipSizeX,  LDstX, LDstImageSizeX,  LCopySizeX);
//   Ep_Clip1DCopyC_r(int,  LSrcY, LSrcImageSizeY, LClipY, LClipSizeY,  LDstY, LDstImageSizeY,  LCopySizeY);
//========================================================================================================================
#define Ep_Clip1DCopyC_r(pType,  pSrc, pSrcSize,  pDst, pDstSize, pDstClipMin, pDstClipSize,  pCopySize) do\
{\
	if(pSrc < 0)\
	{\
		if(pSrc + pCopySize <= 0) return(E_CLIPPED);\
		else\
		{\
			const pType  lDiff = -pSrc;\
			pDst += lDiff;pCopySize -= lDiff;if(pCopySize < 1 || pDst >= pDstSize) return(E_CLIPPED);\
			else { pSrc = 0;if(pSrc + pCopySize >= pSrcSize) pCopySize = pSrcSize - pSrc; }\
		}\
	}\
	else\
	{\
		if(pSrc >= pSrcSize) return(E_CLIPPED);\
		else if(pSrc + pCopySize >= pSrcSize) pCopySize = pSrcSize - pSrc;\
	}\
\
	if(pDst < pDstClipMin)\
	{\
		if(pDst + pCopySize <= pDstClipMin) return(E_CLIPPED);\
		else\
		{\
			const pType  lDiff = pDstClipMin - pDst;\
			pSrc += lDiff;\
			if(pSrc >= pSrcSize) return(E_CLIPPED);\
			else\
			{\
				pCopySize -= lDiff;\
				const pType  lDstMax = pDstClipSize > 0 ? pDstClipMin + pDstClipSize : pDstSize;pDst = pDstClipMin;\
				if(pDst + pCopySize >= lDstMax) pCopySize = lDstMax - pDst;\
			}\
		}\
	}\
	else\
	{\
		const pType  lDstMax = pDstClipSize > 0 ? pDstClipMin + pDstClipSize : pDstSize;\
		if(pDst >= lDstMax) return(E_CLIPPED);\
		else if(pDst + pCopySize >= lDstMax) pCopySize = lDstMax - pDst;\
	}\
} while(0)


extern void       Ep_DrawPoints2DRGBAp8(EpImage* PImage,  const EIndex2* PPoints, const int PCount,  const EcRGBAp8Color PSColor);
extern void       Ep_DrawPoints2DSetA8(EpImage* PImage,  const EIndex2* PPoints, const int PCount,  const uint8_t PA);
extern void       Ep_DrawPoints2DRGBAi(EpImage* PImage,  const EIndex2* PPoints, const int PCount,  const EcRGBAiColor* PSColor, const uint8_t PAlphaOp);
extern void       Ep_DrawColoredPoints2DRGBAi(EpImage* PImage,  const EpColoredPixelRGBAi* PPoints, const int PCount, const uint8_t PAlphaOp);

extern void       Ep_DrawHLineRGBAp8(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,  const EcRGBAp8Color PSColor);
extern void       Ep_DrawHLineRGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,  const EcRGBAf32Color PSColor);

extern void       Ep_DrawVLineRGBAp8(EpImage* PImage,  EIndex PX, EIndex PY,  EIndex PLength,  const EcRGBAp8Color PSColor);
extern void       Ep_DrawVLineRGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,  const EcRGBAf32Color PSColor);

extern void       Ep_DrawHLineBlendRGBA8(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,  const EcRGBAu8Color* PColor);
extern void       Ep_DrawHLineRGBAf32_SRC_plus_DSTxALPHA(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,
                   const EcRGBAf32Color* PColor, const uint16_t PAlphaSrc);

extern void       Ep_DrawHLineBlendRGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,  const EcRGBAf32Color* PColor);

extern void       Ep_DrawVLineBlendRGBA8(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,  const EcRGBAu8Color* PColor);
extern void       Ep_DrawVLineRGBAf32_SRC_plus_DSTxALPHA(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,
                   const EcRGBAf32Color* PColor, const uint16_t PAlphaSrc);

extern void       Ep_DrawVLineBlendRGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PLength,  const EcRGBAf32Color* PColor);

extern void       Ep_DrawRectangleRGBA8(EpImage* PImage,  const EcRGBAu8Color PColor,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,
                   const EIndex PThickness);

extern void       Ep_DrawRectangleRGBAp8(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EcRGBAp8Color PSColor);
extern void       Ep_DrawRectangleRGBAi(EpImage* PImage,  const EcRGBAiColor* PColor,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,
                   const EIndex PThickness);

extern void       Ep_DrawRectangleRGBf(EpImage* PImage, const EcRGBf32Color* PColor, const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,
                   const EIndex PThickness);

extern void       Ep_DrawRectangleRGBAf(EpImage* PImage, const EcRGBAf32Color* PColor, const EIndex PX, const EIndex PY, const EIndex PSizeX, const EIndex PSizeY);
extern void       Ep_DrawRectangleGrayf32(EpImage* PImage,  const float PColor,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EIndex PThickness);

extern void       EpImage_DrawRectangleRoundedRGBAf32
(
	EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,
	const int16_t PCornerTopLeftRadius, const int16_t PCornerTopRightRadius,
	const int16_t PCornerBottomLeftRadius, const int16_t PCornerBottomRightRadius,
	const EBool PDrawCorners,
	const EcRGBAf32Color* PColor,
	const uint16_t PInsideBlendMode, const uint16_t POutsideBlendMode
);

extern void       EpImage_FillRectangleRGBAp8(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EcRGBAp8Color PColor);
extern void       EpImage_FillRectangleRoundedRGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,
 const float PTLCornerRadius, const float PTRCornerRadius, const float PBLCornerRadius, const float PBRCornerRadius,
 const uint8_t PRegions,  const EcRGBAf32Color* PColor, const EcRGBAf32Color* POutsideColor,
 const uint16_t PTargetBlendFunction, const EBool PAntiAliasing);

extern void       EpImage_FillRectangleRGBAi(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EcRGBAiColor PColor);
extern void       EpImage_FillRectangleRGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EcRGBAf32Color PColor);

extern void       EpImage_FillRectangleBlendRGBA8_RGBAi(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EcRGBAiColor* PColor);
extern void       EpImage_FillRectangleRGBA8_RGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EcRGBAf32Color* PColor);
extern void       EpImage_FillRectangleRGBAf_RGBAf(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const EcRGBAf32Color* PColor);

extern uint32_t*  Ep_FillRectangle(EcRGBAp8Color* PRGBAPixels,  const EIndex PImageSizeX, const EIndex PImageSizeY,  const EIndex PX, const EIndex PY,
                   const EIndex PSizeX, const EIndex PSizeY,  const unsigned short PRed, const unsigned short PGreen, const unsigned short PBlue, const unsigned short PAlpha,
                   const short PChannels);

extern void       EpImage_FillGradientRectangleRGBAf(EpImage* PImage,  const int POrientation,  const EIndex PX, const EIndex PY, const EIndex PSizeX, const EIndex PSizeY,
                   const EcRGBAf32Color* PColor0, const EcRGBAf32Color* PColor1);

extern void       EpImage_FillGradientRectangleBlendRGBAf(EpImage* PImage,  const int POrientation,  const EIndex PX, const EIndex PY, const EIndex PSizeX, const EIndex PSizeY,
                   const EcRGBAf32Color* PColor0, const EcRGBAf32Color* PColor1,  const uint16_t PBlendFunc);

extern void       EpImage_FillGradientRectangleExpRGBAf(EpImage* PImage, const int POrientation,  const EIndex PX, const EIndex PY, const EIndex PSizeX, const EIndex PSizeY,
                   const EcRGBAf32Color* PColor0, const EcRGBAf32Color* PColor1,  const float PExponent);

extern int        Ep_DrawPolygon(EpGray8Pixel* PDGray8Image,  const EIndex PImageSizeX, const EIndex PImageSizeY,  const EIndex PNumOfPoints, const EIndex2* PPoints,
                   const EpGray8Pixel PColor);

extern int        EpImage_DrawDisc(EpImage* PImage, const EcRGBAf32Color* PColor,  const EIndex POriginX, const EIndex POriginY, const float PRadius, const float PSoftness,
                   const EBool PAntiAliasing);

extern void EpImage_DrawQuarterCircleRGBA8(
	EpImage* PImage, EcRGBAu8Color* PDstPixels,  const EcRGBAf32Color* PStartColor, const EcRGBAf32Color* PEndColor, const float PThickness,
	const EIndex POriginX, const EIndex POriginY, const EIndex PRadius, const uint8_t PQuadrant,
	const uint16_t PInsideBlendMode, const uint16_t POutsideBlendMode,
	const EBool PAntiAliasing
);

extern void EpImage_DrawQuarterDiscBandsRGBA8(EpImage* PImage, EcRGBAu8Color* PDstPixels,  const EIndex POriginX, const EIndex POriginY, const EIndex PRadius, const EpBand* PBands,
	const uint8_t PQuadrant,  const uint16_t PInsideBlendMode, const uint16_t PInsideAlphaOp,  const uint16_t POutsideBlendMode, const uint16_t POutsideAlphaOp,
	const EBool PAntiAliasing);

extern void EpImage_RectangleDrawCornerBands(EpImage* PImage, const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,
	const EIndex PTopLftRadius, const EpBand* PTopLftBands,  const EIndex PTopRgtRadius, const EpBand* PTopRgtBands,
	const EIndex PBtmLftRadius, const EpBand* PBtmLftBands,  const EIndex PBtmRgtRadius, const EpBand* PBtmRgtBands,
	const uint16_t PInsideBlendMode, const uint16_t PInsideAlphaOp,  const uint16_t POutsideBlendMode, const uint16_t POutsideAlphaOp);

extern int EpImage_RoundOffCorners(EpImage* PImage,  const EIndex PX, const EIndex PY,  const EIndex PSizeX, const EIndex PSizeY,  const float PTLCornerRadius,
  const float PTRCornerRadius, const float PBLCornerRadius, const float PBRCornerRadius,  const EcRGBAf32Color* PColor, const EcRGBAf32Color* POutsideColor,
  const uint16_t PRegions, const int16_t PMultiplyByAlpha, const uint16_t PAlphaOp, const EBool PAntiAliasing);


#define EpAAMAX_PIXEL_COMPONENTS  4  // RGBA for now
#define EpAA_ALPHA                3


// One vertex at any of the various stages of the pipeline
//
typedef struct EpAAVertex
{
	EFloat32_4  Position;
	float       Color[EpAAMAX_PIXEL_COMPONENTS];
} EpAAVertex;

static inline EpAAVertex EpAAVertex_s(const float PX, const float PY, const float PZ)
{
	const EpAAVertex  LV = { { .X= PX, .Y= PY, .Z= PZ, .W= 1.0 }, { 0.0, 0.0, 0.0, 1.0 } };
	return(LV);
}


extern void  EpImage_DrawAALine(EpImage* PImage,  const EpAAVertex* PV1, const EpAAVertex* PV2);

#if defined (__cplusplus) && (!(defined (__CPLUSLIBS)))
}	// Close scope of 'extern "C"' declaration
#endif


#endif // _EImageDraw_h
