ImageEn for Delphi and C++ Builder ImageEn for Delphi and C++ Builder

 

ImageEn Forum
Profile    Join    Active Topics    Forum FAQ    Search this forumSearch
Forum membership is Free!  Click Join to sign-up
Username:
Password:
Save Password
Forgot your Password?

 All Forums
 ImageEn Library for Delphi, C++ and .Net
 ImageEn and IEvolution Support Forum
 Radial Gradient
 New Topic  Reply to Topic
Author Previous Topic Topic Next Topic  

Fellafoo

USA
60 Posts

Posted - Mar 26 2025 :  11:07:07  Show Profile  Reply
I don't think there is already a built-in way to do this. So, I've prototyped the following function to return a TIEBitmap filled with a radial gradient (with or without an alpha gradient). I tried manipulating the RGBA pixels directly, but that did not work for me.


function RadialGradientFill(Width, Height: Integer; Center: TPoint; Radius: Integer; ClrBeg, ClrEnd: TColor; OpaBeg, OpaEnd: Byte): TIEBitmap;
var
  x, y: Integer;
  dx, dy: Integer;
  Distance: Double;
  Ratio: Double;
  BegR, BegG, BegB: Byte;
  EndR, EndG, EndB: Byte;
  CurR, CurG, CurB, CurOpa: Byte;
  pPix, pPixA: pRGB{A};
  x1, x2, y1, y2: Integer; { Bounding rectangle for fill circle }
  ieBmpAlpha: TIEBitmap; { Temp bitmap for alpha fill }

begin
  Result := nil;

  { Create TIEBitmap and set pixel format }
  Result := TIEBitmap.Create(Width, Height, ie24RGB);
  try
    ieBmpAlpha := TIEBitmap.Create(Width, Height, ie24RGB);
    try
      { Convert the TColor values to their RGB components. }
      BegR := GetRValue(ColorToRGB(ClrBeg));
      BegG := GetGValue(ColorToRGB(ClrBeg));
      BegB := GetBValue(ColorToRGB(ClrBeg));

      EndR := GetRValue(ColorToRGB(ClrEnd));
      EndG := GetGValue(ColorToRGB(ClrEnd));
      EndB := GetBValue(ColorToRGB(ClrEnd));

      { Determine the bounding box of the circle. }
      x1 := Max(0, Center.X - Radius);
      y1 := Max(0, Center.Y - Radius);
      x2 := Min(Width - 1, Center.X + Radius);
      y2 := Min(Height - 1, Center.Y + Radius);

      { Loop over each row of the bounding box. }
      for y := y1 to y2 do begin
        { Pointer to the first byte of the current scanline. }
        pPix := Result.Scanline[y];
        pPixA := ieBmpAlpha.Scanline[y];

        { Loop over each pixel in the current row. }
        for x := x1 to x2 do begin
          dx := x - Center.X;
          dy := y - Center.Y;
          Distance := Sqrt(dx * dx + dy * dy);
          if (Distance <= Radius) then begin
            { Calculate the gradient ratio (0 at center, 1 at the perimeter). }
            Ratio := Distance / Radius;

            { Linearly interpolate each color component. }
            CurR := Round(BegR + Ratio * (EndR - BegR));
            CurG := Round(BegG + Ratio * (EndG - BegG));
            CurB := Round(BegB + Ratio * (EndB - BegB));

            { Interpolate the alpha (opacity) value. }
            CurOpa := Round(OpaBeg + Ratio * (OpaEnd - OpaBeg));

            { Color pixels }
            pPix^.r := CurB;
            pPix^.g := CurG;
            pPix^.b := CurR;

            { Alpha pixels }
            pPixA^.r := CurOpa;
            pPixA^.g := CurOpa;
            pPixA^.b := CurOpa;

            Inc(pPix);
            Inc(pPixA);
          end;
        end;
      end;

      Result.AlphaChannel.Assign(ieBmpAlpha);
      Result.SyncAlphaChannel(); { Convert to 8 bit }
    finally
      ieBmpAlpha.Free;
    end;
  except
    Result.Free;
  end;
end; { RadialGradientFill }


If there's a way to do this with a single bitmap, please let me know.



Thank You,

MFM

xequte

38899 Posts

Posted - Mar 27 2025 :  00:07:08  Show Profile  Reply
Hi

Your code is fine, and there will be little performance benefit from using a single bitmap, but if you want to you can just treat the alphachannel as regular bitmap as follows (untested):

function RadialGradientFill(Width, Height: Integer; Center: TPoint; Radius: Integer; ClrBeg, ClrEnd: TColor; OpaBeg, OpaEnd: Byte): TIEBitmap;
var
  x, y: Integer;
  dx, dy: Integer;
  Distance: Double;
  Ratio: Double;
  BegR, BegG, BegB: Byte;
  EndR, EndG, EndB: Byte;
  CurR, CurG, CurB, CurOpa: Byte;
  pPix, pPixA: pRGB{A};
  x1, x2, y1, y2: Integer; { Bounding rectangle for fill circle }
begin
  Result := nil;

  { Create TIEBitmap and set pixel format }
  Result := TIEBitmap.Create(Width, Height, ie24RGB);
  try
    Result.AlphaChannel.Allocate(Width, Height, ie24RGB); // Temporarily promote to 24bit for smoother gradient      
    try
      { Convert the TColor values to their RGB components. }
      BegR := GetRValue(ColorToRGB(ClrBeg));
      BegG := GetGValue(ColorToRGB(ClrBeg));
      BegB := GetBValue(ColorToRGB(ClrBeg));

      EndR := GetRValue(ColorToRGB(ClrEnd));
      EndG := GetGValue(ColorToRGB(ClrEnd));
      EndB := GetBValue(ColorToRGB(ClrEnd));

      { Determine the bounding box of the circle. }
      x1 := Max(0, Center.X - Radius);
      y1 := Max(0, Center.Y - Radius);
      x2 := Min(Width - 1, Center.X + Radius);
      y2 := Min(Height - 1, Center.Y + Radius);

      { Loop over each row of the bounding box. }
      for y := y1 to y2 do begin
        { Pointer to the first byte of the current scanline. }
        pPix := Result.Scanline[y];
        pPixA := Result.AlphaChannel.Scanline[y];

        { Loop over each pixel in the current row. }
        for x := x1 to x2 do begin
          dx := x - Center.X;
          dy := y - Center.Y;
          Distance := Sqrt(dx * dx + dy * dy);
          if (Distance <= Radius) then begin
            { Calculate the gradient ratio (0 at center, 1 at the perimeter). }
            Ratio := Distance / Radius;

            { Linearly interpolate each color component. }
            CurR := Round(BegR + Ratio * (EndR - BegR));
            CurG := Round(BegG + Ratio * (EndG - BegG));
            CurB := Round(BegB + Ratio * (EndB - BegB));

            { Interpolate the alpha (opacity) value. }
            CurOpa := Round(OpaBeg + Ratio * (OpaEnd - OpaBeg));

            { Color pixels }
            pPix^.r := CurB;
            pPix^.g := CurG;
            pPix^.b := CurR;

            { Alpha pixels }
            pPixA^.r := CurOpa;
            pPixA^.g := CurOpa;
            pPixA^.b := CurOpa;

            Inc(pPix);
            Inc(pPixA);
          end;
        end;
      end;

    finally
      Result.SyncAlphaChannel(); { Convert to 8 bit }
    end;

  except
    Result.Free;
  end;
end;


Nigel
Xequte Software
www.imageen.com
Go to Top of Page

Fellafoo

USA
60 Posts

Posted - Mar 27 2025 :  07:12:25  Show Profile  Reply
Thank you, Nigel. Your updated method does work.

Out of curiosity, is it possible to use a single pointer for the pixel access?

For example...

var
  pPix: PRGBA;

begin
...
  pPix^.r := CurR;
  pPix^.g := CurG;
  pPix^.b := CurB;
  pPix^.a := CurOpa;


I recall trying this previously without success.

MFM
Go to Top of Page

xequte

38899 Posts

Posted - Mar 27 2025 :  20:22:20  Show Profile  Reply
Yes, but only if you set the pixel format to ie32RGB. Then the scanline will be 32bit RGBA:

http://www.imageen.com/help/TIEBitmap.Scanline.html

And you an also use (still must be ie32RGB):

http://www.imageen.com/help/TIEBitmap.Pixels_ie32RGB.html


However you are best revert to ImageEn's 24RGB + Alpha format after you are done with:

http://www.imageen.com/help/TIEBitmap.SynchronizeRGBA.html


We have partial support for natively using RGBA format via AlphaLocation, but it relatively new and there are likely to be issues:

http://www.imageen.com/help/TIEBitmap.AlphaLocation.html


Nigel
Xequte Software
www.imageen.com
Go to Top of Page
  Previous Topic Topic Next Topic  
 New Topic  Reply to Topic
Jump To: