Any library/code to fade the edges of a bitmap in a gradient manner?
Something like this:
Edit: final code
Ok came up with this code after your example, it's ~10 times faster after optimization with scanlines. Ideally I think I should convert it to use a 32bit bitmap instead and modify the actual alpha layer, but this works for now, ty!
procedure FadeEdges(b: TBitmap; Depth, Start, Col: TColor);
Var f, x, y, i: Integer;
w,h: Integer;
pArrays: Array of pRGBArray;
xAlpha: Array of byte;
sR, sG, sB: Byte;
a,a2: Double;
r1,g1,b1: Double;
Lx,Lx2: Integer;
procedure AlphaBlendPixel(X, Y: Integer);
begin
pArrays[y,x].rgbtRed := Round(r1 + pArrays[y,x].rgbtRed * a2);
pArrays[y,x].rgbtGreen := Round(g1 + pArrays[y,x].rgbtGreen * a2);
pArrays[y,x].rgbtBlue := Round(b1 + pArrays[y,x].rgbtBlue * a2);
end;
procedure AlphaBlendRow(Row: Integer; Alpha: Byte);
Var bR, bG, bB, xA: Byte;
t: Integer;
s,s2: Double;
begin
s := alpha / 255;
s2 := (255 - Alpha) / 255;
for t := 0 to b.Width-1 do begin
bR := pArrays[Row,t].rgbtRed;
bG := pArrays[Row,t].rgbtGreen;
bB := pArrays[Row,t].rgbtBlue;
pArrays[Row,t].rgbtRed := Round(sR*s + bR*s2);
pArrays[Row,t].rgbtGreen := Round(sG*s + bG*s2);
pArrays[Row,t].rgbtBlue := Round(sB*s + bB*s2);
end;
end;
begin
b.PixelFormat := pf24bit;
// cache scanlines
SetLength(pArrays,b.Height);
for y := 0 to b.Height-1 do
pArrays[y] := pRGBArray(b.ScanLine[y]);
// pre-calc Alpha
SetLength(xAlpha,Depth);
for y := 0 to (Depth-1) do
xAlpha[y] := Round(Start + (255 - Start)*y/(Depth-1));
// pre-calc bg color
sR := GetRValue(Col);
sG := GetGValue(Col);
sB := GetBValue(Col);
// offsets
w := b.Width-Depth;
h := b.Height-Depth;
for i := 0 to (Depth-1) do begin
a := xAlpha[i] / 255;
a2 := (255 - xAlpha[i]) / 255;
r1 := sR * a;
g1 := sG * a;
b1 := sB * a;
Lx := (Depth-1)-i;
Lx2 := i+w;
for y := 0 to b.Height - 1 do begin
AlphaBlendPixel(Lx, y); // Left
AlphaBlendPixel(Lx2, y); // right
end;
end;
for i := 0 to (Depth-1) do begin
AlphaBlendRow((Depth-1)-i, xAlpha[i]); // top
AlphaBlendRow(i+(h), xAlpha[i]); // bottom
end;
SetLength(xAlpha,0);
SetLength(pArrays,0);
end;
Final result: (left = original, right = blended on hovering with a ListView)
edit: further speed improvements, twice as fast as original proc.
I can give you some code I wrote a couple of years ago to achieve this. It might be useful as a guide. The code is part of a class that manipulates a bitmap and this is the part that fades the left edge of the bitmap into a white background:
procedure TScreenShotEnhancer.FadeOutLeft(Position, Start: Integer);
var
X, Y: Integer;
F, N: Integer;
I: Integer;
begin
BeginUpdate;
try
N := Position;
for I := 0 to N - 1 do begin
X := Position - I - 1;
F := Round(Start + (255 - Start)*I/N);
for Y := 0 to Height - 1 do
AlphaBlendPixel(X, Y, clWhite, F);
end;
finally
EndUpdate;
end;
end;
The actual work is done in this method:
procedure TScreenShotEnhancer.AlphaBlendPixel(X, Y: Integer; Color: TColor;
Alpha: Byte);
var
backgroundColor: TColor;
displayColor: TColor;
dR, dG, dB: Byte;
bR, bG, bB: Byte;
sR, sG, sB: Byte;
begin
backgroundColor := Bitmap.Canvas.Pixels[X, Y];
bR := GetRValue(backgroundColor);
bG := GetGValue(backgroundColor);
bB := GetBValue(backgroundColor);
sR := GetRValue(Color);
sG := GetGValue(Color);
sB := GetBValue(Color);
dR := Round(sR * alpha / 255 + bR * (255 - alpha) / 255);
dG := Round(sG * alpha / 255 + bG * (255 - alpha) / 255);
dB := Round(sB * alpha / 255 + bB * (255 - alpha) / 255);
displayColor := RGB(dR, dG, dB);
Bitmap.Canvas.Pixels[X, Y] := displayColor;
end;
Related
I am trying to write my own quick resampler which allows to scale image down by 2,3,4 etc.
I compared it with StretchBlt and my resampler is about 2 times slower.
My code:
type TRGBA = record
B,G,R: Byte;
A: Byte;
end;
PRGBAArray = ^TRGBAArray;
TRGBAArray = array[0..32767] of TRGBA;
procedure DownsampleSys(Src, Dst: TBitmap; Times: Integer);
var ARect: TRect;
dc:HDC;
p:TPoint;
begin
Dst.Width := Src.Width div Times;
Dst.Height := Src.Height div Times;
ARect := Rect(0,0, Dst.Width, Dst.Height);
dc := Dst.Canvas.Handle;
GetBrushOrgEx(dc,p);
SetStretchBltMode(dc,HALFTONE);
SetBrushOrgEx(dc,p.x,p.y,#p);
StretchBlt(dc,
ARect.Left, ARect.Top,
ARect.Right- ARect.Left, ARect.Bottom- ARect.Top,
Src.Canvas.Handle,0,0,Src.Width,Src.Height,Dst.Canvas.CopyMode);
end;
procedure Downsample2(Src, Dst: TBitmap; Times: Integer);
var x,y: Integer;
xx,yy: Integer;
FromP, ToP: PRGBAArray;
SumR, SumG, SumB: Cardinal;
Times2: Integer;
xTimes, yTimes: Integer;
xxxTimes: Integer;
MarginL, MarginT: Integer;
begin
Dst.Width := floor(Src.Width/ Times);
Dst.Height := floor(Src.Height / Times);
Times2 := Times * Times;
MarginL := (Src.Width - (Dst.Width * Times)) div 2;
MarginT := (Src.Height - (Dst.Height * Times)) div 2;
for y:=0 to Dst.Height-1 do begin
ToP := Dst.Scanline[y];
yTimes := MarginT + y*Times;
for x:=0 to Dst.Width-1 do begin
SumR := 0;
SumG := 0;
SumB := 0;
xTimes := MarginL + x*Times;
for yy:=0 to Times-1 do begin
FromP := Src.Scanline[yy + yTimes];
for xx:=0 to Times-1 do begin
xxxTimes := xx + xTimes;
SumR := SumR + FromP[xxxTimes].R;
SumG := SumG + FromP[xxxTimes].G;
SumB := SumB + FromP[xxxTimes].B;
end;
end;
ToP[x].R := SumR div Times2;
ToP[x].G := SumG div Times2;
ToP[x].B := SumB div Times2;
end;
end;
end;
Usage:
InB := TBitmap.Create;
OutB := TBitmap.Create;
InB.LoadFromFile('2.bmp');
InB.PixelFormat := pf32bit;
OutB.PixelFormat := pf32bit;
Downsample2(InB, OutB, 4);
How can I make it even faster?
If you are still interested in an answer, you could give this thumbnail-routine a try. It's the result of a discussion way back on the Borland newsgroups. On my system it runs a bit faster than Stretch_Halftone, but the box-rescaling is a bit too foggy for my taste. I've abandoned all box-rescaling myself, because the system-rescaling, on my graphics at least, looks better; almost as if it was using bicubic scaling behind the scenes.
The speed gain is by use of lookup-tables, pointer- and integer-math.
// procedure MakeThumbnailMod
// Original source: Roy Magne Klever
// Altered to avoid division by 0
// and tried to make it a bit faster (RS)
//Integer math courtesy of Hagen Redmann
type
PRGB32 = ^TRGB32;
TRGB32 = packed record
b: byte;
g: byte;
r: byte;
a: byte;
end;
TLine32 = array [0 .. maxint div SizeOf(TRGB32) - 1] of TRGB32;
PLine32 = ^TLine32;
TIntArray = array of integer;
TDeltaArray = array of array of integer;
procedure MakeStepsAndWeights(xscale, yscale: Single; xw, yh: integer;
var dxmin, dymin: integer; var Weights: TDeltaArray;
var xsteps, ysteps: TIntArray);
var
i, j: integer;
x1, x2: integer;
dxmax, dymax, intscale: integer;
fact: Single;
begin
SetLength(xsteps, xw);
SetLength(ysteps, yh);
intscale := round(xscale * $10000);
// won't work if xcale > $10000/2, because then intscale
// exceeds 32bit integer. I don't see that happening.
x1 := 0;
x2 := intscale shr 16;
for i := 0 to xw - 1 do
begin
xsteps[i] := x2 - x1;
x1 := x2;
x2 := (i + 2) * intscale shr 16;
end;
dxmin := Ceil(xscale - 1);
dxmax := trunc(xscale + 1);
intscale := round(yscale * $10000);
x1 := 0;
x2 := intscale shr 16;
for i := 0 to yh - 1 do
begin
ysteps[i] := x2 - x1;
x1 := x2;
x2 := (i + 2) * intscale shr 16;
end;
dymin := Ceil(yscale - 1);
dymax := trunc(yscale + 1);
SetLength(weights, dxmax - dxmin + 1, dymax - dymin + 1);
for i := 0 to dxmax - dxmin do
begin
fact := 1 / (dxmin + i);
for j := 0 to dymax - dymin do
weights[i, j] := round(fact / (dymin + j) * $10000);
end;
end;
procedure MakeThumbNailMod(const Src, Dest: TBitmap;
NewWidth, NewHeight: integer);
var
xscale, yscale: Single;
x1: integer;
ix, iy: integer;
totalRed, totalGreen, totalBlue: integer;
ratio: integer;
p: PRGB32;
pt1: PRGB32;
ptrD, ptrS: integer;
x, y: integer;
r1, r2: TRect;
x3: integer;
RowDest, RowSource, RowSourceStart: integer;
weights: TDeltaArray;
xsteps, ysteps: TIntArray;
w, h, dxmin, dymin: integer;
dx, dy: integer;
begin
Dest.PixelFormat := pf32bit;
Src.PixelFormat:=pf32bit; //to be on the safe side
Dest.Width := NewWidth;
Dest.Height := NewHeight;
if (Dest.Width >= Src.Width) or (Dest.Height >= Src.Height) then
begin //we don't do upsampling
r1 := rect(0, 0, Src.Width, Src.Height);
r2 := r1;
OffsetRect(r2, (Dest.Width - Src.Width) div 2,
(Dest.Height - Src.Height) div 2);
Dest.Canvas.CopyRect(r2, Src.Canvas, r1);
exit;
end;
w := Dest.Width;
h := Dest.Height;
ptrD := (w * 32 + 31) and not 31;
ptrD := ptrD div 8; // BytesPerScanline
ptrS := (Src.Width * 32 + 31) and not 31;
ptrS := ptrS div 8;
xscale := Src.Width / w;
yscale := Src.Height / h; // turns div into mults
MakeStepsAndWeights(xscale, yscale, w, h, dxmin, dymin, weights, xsteps, ysteps);
// Make 3 lookup tables for the steps and the ratios
w := w - 1;
h := h - 1;
RowDest := integer(Dest.Scanline[0]);
RowSourceStart := integer(Src.Scanline[0]);
RowSource := RowSourceStart;
for y := 0 to h do
begin
dy := ysteps[y];
x1 := 0;
x3 := 0;
for x := 0 to w do
begin
dx := xsteps[x];
totalRed := 0;
totalGreen := 0;
totalBlue := 0;
RowSource := RowSourceStart;
for iy := 1 to dy do
begin
p := PRGB32(RowSource + x1);
for ix := 1 to dx do
begin
totalRed := totalRed + p^.r;
totalGreen := totalGreen + p^.g;
totalBlue := totalBlue + p^.b; //maybe add the alpha-channel optionally
inc(p);
end;
RowSource := RowSource - ptrS;
end;
pt1 := PRGB32(RowDest + x3);
ratio := weights[dx - dxmin, dy - dymin];
pt1^.r := (totalRed * ratio) shr 16; //"rounding"
pt1^.g := (totalGreen * ratio) shr 16;
pt1^.b := (totalBlue * ratio) shr 16;
x1 := x1 + 4 * dx;
x3 := x3 + 4;
end;
RowDest := RowDest - ptrD;
RowSourceStart := RowSource;
end;
//SharpenMod(Work, Dest, min(1 + 0.4 * (xscale - 1), 2.5));
//The sharpening makes the thumb look nicer, but is omitted here
end;
I use this code to scan very fast Bitmap.
Everything is Ok When PixelFormat = pf24bit but my program only work with 256 image color and for this reason PixelFormat = pf8bit.
When PixelFormat = pf8bit this code show error.
var
w, h: Integer;
CurrRow, OffSet: Integer;
x: byte;
pRed, pGreen, pBlue: PByte;
begin
CurrRow := Integer(aBitmap.Scanline[0]);
OffSet := Integer(aBitmap.Scanline[1]) - CurrRow;
Result:= False;
for h := 0 to aBitmap.Height - 1 do
begin
for w := 0 to aBitmap.Width - 1 do
begin
pBlue := PByte(CurrRow + w * 3);
pGreen := PByte(CurrRow + w * 3 + 1);
pRed := PByte(CurrRow + w * 3 + 2);
end;
inc(CurrRow, OffSet);
end;
now I use this code to swap color but dont work because scanline dont check color
procedure ReplaceColor(aBitmap: TBitmap; swap1, swap2: TRGBQuad);
var
w, h, k: Integer;
pScanline: pByte;
Red, Green, Blue, palindex: Byte;
PalEntries: array[0..255] of TRGBQuad;
begin
if aBitmap.Palette <> 0 then
GetPaletteEntries(aBitmap.Palette, 0, 255, PalEntries);
for h := 0 to aBitmap.Height - 1 do
begin
pScanline := pByte(aBitmap.Scanline[h]);
for w := 0 to aBitmap.Width - 1 do
begin
Blue:= PalEntries[pScanline^].rgbBlue ;
Red:= PalEntries[pScanline^].rgbRed ;
Green:= PalEntries[pScanline^].rgbGreen ;
if (Blue = swap1.rgbBlue) and (Red = swap1.rgbRed) and
(Green = swap1.rgbGreen) then
begin
Blue := swap2.rgbBlue;
Green := swap2.rgbGreen;
Red := swap2.rgbRed;
end
else if (Blue = swap2.rgbBlue) and (Red = swap2.rgbRed) and
(Green = swap2.rgbGreen) then
begin
Blue := swap1.rgbBlue;
Green := swap1.rgbGreen;
Red := swap1.rgbRed;
end;
Inc(pScanline);
end;
end;
end;
The code fails for pf8bit because it is not written to handle pf8bit. It is written to handle pf24bit instead.
The code is expecting each scanline to consist of width number of 3-byte (24 bits) pixels containing the actual RGB values. But in pf8bit, each scanline contains 1-byte (8 bit) pixels which are indexes into the bitmap's color palette. You are not accounting for that at all.
Try something more like this instead:
var
w, h: Integer;
pScanline: PByte;
Red, Green, Blue: Byte;
PalEntries: array[0..255] of TRGBQuad;
begin
Result := False;
if aBitmap.Palette <> 0 then
GetPaletteEntries(aBitmap.Palette, 0, 255, PalEntries);
for h := 0 to aBitmap.Height - 1 do
begin
pScanline := PByte(aBitmap.Scanline[h]);
for w := 0 to aBitmap.Width - 1 do
begin
case aBitmap.PixelFormat of
pf8Bit: begin
Blue := PalEntries[pScanline^].rgbBlue;
Green := PalEntries[pScanline^].rgbGreen;
Red := PalEntries[pScanline^].rgbRed;
Inc(pScanline);
end;
pf24Bit: begin
Blue := PRGBTriple(pScanline).rgbtBlue;
Green := PRGBTriple(pScanline).rgbtGreen;
Red := PRGBTriple(pScanline).rgbtRed;
Inc(pScanline, SizeOf(TRGBTriple));
end;
// etc for other color depths...
end;
end;
end;
end;
You need to do some research on how bitmaps actually work, particularly in relation to the format of the scanlines at the various color depths.
Bitmaps overview
i am trying to draw a rotated bitmap on a TImage canvas at some specified points, what i tried so far is i rotated the bitmap and then used stretched draw but i am not getting the results i want, the scenario goes like this
I map 4 points on a TImage canvas with mouse clicks and get its angle, the angle can be 0, 45, 90 anything, something like in the image i have attached
Now what i need is to draw another bitmap rotated and stretched on these points, i am having a hard time figuring this thing out
Regards
Many years ago, when stars was brighter and girls was younger, i wrote this code for unknown reason. It is VCL compatible, but can be adopter in order to be used in both VCL/FMX. It is simple class to draw arbitrary rectangle from bitmap to arbitrary rectangle at destination DC (so it could be bitmap or something else). It can paint destination picture with bilinear interpolation, then result looks not so ugly as with simple stretching. Maybe it can be useful for someone.
unit uBMPUtils;
interface
uses
windows, graphics, math, sysutils;
type
PIntegers = ^TIntegers;
TIntegers = array[0..high(integer) div sizeof(integer) - 16] of integer;
TDrawLine = procedure( pixelSize : integer;
var src;
srcLineAdd : integer;
src_x1, src_y1, src_x2, src_y2 : integer;
var dst;
dstLen : integer) of object;
TDrawMode = (dmSimple, dmBilinear);
TBitmapDrawer = class
protected
tmp : TBitmap;
koefs : array[0..4096*4-1] of integer; // addr(Ux, Vy) = 4 * ( (trunc(Ux*16) << 6) + trunc(Vy*16) )
calculated : boolean;
DrawModeFlag : TDrawMode;
DrawLine : TDrawLine;
procedure precalculate; // precalculate koefs for fast bilinear interpolation
procedure drawLineSimple( pixelSize : integer;
var src;
srcLineAdd : integer;
src_x1, src_y1, src_x2, src_y2 : integer;
var dst;
dstLen : integer);
procedure drawLineBilinear( pixelSize : integer;
var src;
srcLineAdd : integer;
src_x1, src_y1, src_x2, src_y2 : integer;
var dst;
dstLen : integer);
procedure setDrawMode(m : TDrawMode);
public
constructor Create;
destructor Destroy; override;
procedure DrawTriangle(src : TBitmap; // source bitmap (pf24 or pf32!)
dst_dc : cardinal; // destination DC
dstRect : TRect; // limiting rect for output
A1, A2, A3, // arbitrary rectange at Src bitmap
B1, B2, B3 : TPoint); // arbitrary rectange at DST_DC device
procedure DrawRectangle(src : TBitmap; // source bitmap (pf24 or pf32!)
dst_dc : cardinal; // destination DC
dstRect : TRect; // limiting rect for output
A1, A2, A3, A4, // arbitrary rectange at Src bitmap
B1, B2, B3, B4 : TPoint); // arbitrary rectange at DST_DC device
property DrawMode: TDrawMode read DrawModeFlag write setDrawMode; // Default: dmBilinear
end;
implementation
function HorAtLine(var x : integer; y, x1,y1,x2,y2 : integer):boolean;
begin
if y1 = y2 then result := false else
begin
result := (y >= y1) and (y <= y2) or (y >= y2) and (y <= y1);
if result then x := x1 + (x2 - x1) * (y - y1) div (y2 - y1);
end;
end;
procedure LineProportion(var src_x, src_y : integer;
src_x1, src_y1, src_x2, src_y2 : integer;
dst_x, dst_y : integer;
dst_x1, dst_y1, dst_x2, dst_y2 : integer);
begin
if abs(dst_x2 - dst_x1) > abs(dst_y2 - dst_y1) then begin // proportions form Y
src_x := src_x1 + (src_x2 - src_x1) * (dst_x - dst_x1) div (dst_x2 - dst_x1);
src_y := src_y1 + (src_y2 - src_y1) * (dst_x - dst_x1) div (dst_x2 - dst_x1);
end else begin
src_x := src_x1 + (src_x2 - src_x1) * (dst_y - dst_y1) div (dst_y2 - dst_y1);
src_y := src_y1 + (src_y2 - src_y1) * (dst_y - dst_y1) div (dst_y2 - dst_y1);
end;
end;
// ---------------------------------------------- TBitmapDrawer --------------------------------------------------------
procedure TBitmapDrawer.precalculate;
var
n, u, v : integer;
Uf, Vf, k1,k2,k3,k4 : double;
begin
calculated := true;
for V := 0 to 63 do
for U := 0 to 63 do
begin
Uf := U / 64;
Vf := V / 64;
k1 := (1 - Uf) * (1 - Vf);
k2 := Uf * (1 - Vf);
k3 := (1 - Uf) * Vf;
k4 := Uf * Vf;
n := ((U shl 6) + V) * 4;
koefs[n] := trunc(k1*65536);
koefs[n+1] := trunc(k2*65536);
koefs[n+2] := trunc(k3*65536);
koefs[n+3] := trunc(k4*65536);
end;
end;
constructor TBitmapDrawer.create;
begin
inherited create;
tmp := TBitmap.create;
tmp.Height := 1;
drawMode := dmBilinear;
precalculate;
end;
destructor TBitmapDrawer.Destroy;
begin
FreeandNil(tmp);
inherited;
end;
procedure TBitmapDrawer.DrawRectangle(src : TBitmap;
dst_dc : cardinal;
dstRect : TRect;
A1, A2, A3, A4,
B1, B2, B3, B4 : TPoint);
begin
DrawTriangle(src, dst_dc, dstRect, A1, A2, A3, B1, B2, B3);
DrawTriangle(src, dst_dc, dstRect, A1, A3, A4, B1, B3, B4);
end;
procedure TBitmapDrawer.DrawTriangle(src : TBitmap;
dst_dc : cardinal;
dstRect : TRect;
A1, A2, A3,
B1, B2, B3 : TPoint);
var
pixelSize, srcAdd, left_x, left_y, right_x, right_y: integer;
minx, maxx, x, y, top, bottom : integer;
pb : pointer;
begin
if src.height > 1 then srcAdd := integer(PAnsiChar(src.scanline[1]) - PAnsiChar(src.scanline[0])) else srcAdd := 0;
top := min(min(b1.y, b2.y), b3.y);
bottom := max(max(b1.y, b2.y), b3.y);
if (top > dstRect.Bottom) or (bottom < dstRect.Top) then exit;
if top < dstRect.Top then top := dstRect.Top;
if bottom > dstRect.Bottom then bottom := dstRect.Bottom;
case src.pixelFormat of
pf24bit : pixelsize := 3;
pf32bit : pixelsize := 4;
else raise exception.create('Error');
end;
if tmp.PixelFormat <> src.PixelFormat then tmp.PixelFormat := src.PixelFormat;
y := max(max(b1.X, b2.x), b3.x) - min(min(b1.X, b2.x), b3.x) + 1;
if (tmp.Width < y) then tmp.Width := y;
pb := tmp.scanline[0];
for y := top to bottom do // Y at destination picture
begin
minx := high(integer);
maxx := low(integer);
if HorAtLine(x,y, b1.X, b1.Y, b2.x, b2.Y) then
begin
if x < minx then begin
minx := x;
LineProportion(left_x, left_y, a1.X, a1.Y, a2.x, a2.Y, x,y, b1.X, b1.Y, b2.X, b2.Y);
end;
if x > maxx then begin
LineProportion(right_x, right_y, a1.X, a1.Y, a2.x, a2.Y, x,y, b1.X, b1.Y, b2.X, b2.Y);
maxx := x;
end;
end;
if HorAtLine(x,y, b2.X, b2.Y, b3.x, b3.Y) then
begin
if x < minx then begin
minx := x;
LineProportion(left_x, left_y, a2.X, a2.Y, a3.x, a3.Y, x,y, b2.X, b2.Y, b3.X, b3.Y);
end;
if x > maxx then begin
LineProportion(right_x, right_y, a2.X, a2.Y, a3.x, a3.Y, x,y, b2.X, b2.Y, b3.X, b3.Y);
maxx := x;
end;
end;
if HorAtLine(x,y, b3.X, b3.Y, b1.x, b1.Y) then
begin
if x < minx then begin
minx := x;
LineProportion(left_x, left_y, a3.X, a3.Y, a1.x, a1.Y, x,y, b3.X, b3.Y, b1.X, b1.Y);
end;
if x > maxx then begin
LineProportion(right_x, right_y, a3.X, a3.Y, a1.x, a1.Y, x,y, b3.X, b3.Y, b1.X, b1.Y);
maxx := x;
end;
end;
if minx > maxx then continue;
// destination line (minx, y) - (maxx, y) - now we can find it at source picture
drawLine(pixelSize, src.ScanLine[0]^, srcAdd, left_x, left_y, right_x, right_y, pb^, maxx - minx + 1);
bitblt(dst_dc, minx, y, maxx-minx+1, 1, tmp.Canvas.Handle, 0,0, srccopy);
end;
end;
procedure TBitmapDrawer.drawLineSimple( pixelSize : integer;
var src;
srcLineAdd : integer;
src_x1, src_y1, src_x2, src_y2 : integer;
var dst;
dstLen : integer);
var
dst_ptr : PAnsiChar;
i, px, py : integer;
begin
dst_ptr := #dst;
px := (src_x2 - src_x1) * 65536 div dstLen;
py := (src_y2 - src_y1) * 65536 div dstLen;
src_x1 := src_x1 * 65536;
src_y1 := src_y1 * 65536;
for i := 0 to dstLen - 1 do
begin
pinteger(dst_ptr)^ := pinteger( PAnsiChar(#src) +
((src_y1 + i * py) shr 16) * srcLineAdd +
((src_x1 + i * px) shr 16) * pixelSize
)^;
inc(dst_ptr, pixelSize);
end;
end;
procedure TBitmapDrawer.drawLineBilinear( pixelSize : integer;
var src;
srcLineAdd : integer;
src_x1, src_y1, src_x2, src_y2 : integer;
var dst;
dstLen : integer);
var
src_ptr, dst_ptr : PAnsiChar;
u,v,Uf,Vf : integer;
i : integer;
k : PIntegers;
c1,c2,c3,c4 : TColor;
begin
dst_ptr := #dst;
for i := 0 to dstLen - 1 do
begin
u := src_x1 + i * (src_x2 - src_x1) div dstLen;
v := src_y1 + i * (src_y2 - src_y1) div dstLen;
Uf := (src_x1 + i * (src_x2 - src_x1) * 64 div dstLen) and $3f;
Vf := (src_y1 + i * (src_y2 - src_y1) * 64 div dstLen) and $3f;
k := #koefs[4*((Uf shl 6) + Vf)];
src_ptr := PAnsiChar(#src) + v * srcLineAdd + u * pixelSize;
c1 := pinteger(src_ptr)^;
c2 := pinteger(src_ptr + 4)^;
c3 := pinteger(src_ptr + srcLineAdd)^;
c4 := pinteger(src_ptr + srcLineAdd + 4)^;
pinteger(dst_ptr)^ :=
( (c1 and $FF)*k[0] shr 16 +
(c2 and $FF)*k[1] shr 16 +
(c3 and $FF)*k[2] shr 16 +
(c4 and $FF)*k[3] shr 16 )
or
( ((c1 shr 8) and $FF)*k[0] shr 16 +
((c2 shr 8) and $FF)*k[1] shr 16 +
((c3 shr 8) and $FF)*k[2] shr 16 +
((c4 shr 8) and $FF)*k[3] shr 16 ) shl 8
or
( ((c1 shr 16) and $FF)*k[0] shr 16 +
((c2 shr 16) and $FF)*k[1] shr 16 +
((c3 shr 16) and $FF)*k[2] shr 16 +
((c4 shr 16) and $FF)*k[3] shr 16 ) shl 16
or $02000000;
inc(dst_ptr, pixelSize);
end;
end;
procedure TBitmapDrawer.setDrawMode(m: TDrawMode);
begin
drawModeFlag := m;
case drawModeFlag of
dmSimple : drawLine := drawLineSimple;
dmBilinear : drawLine := drawLineBilinear;
end;
end;
end.
I finally got the desired output using Projective transformation method in Graphics32 http://graphics32.org/wiki/
I need to generate a gradient bitmap that displays a rainbow gradient between two colors which are chosen by a user. To generate a rainbow is easy. The code below I got from Wiki and slightly adapted it. It has the advantage of being fast and simple.
function TColor_Dialog.GiveRainbowColor (fraction: double): TAlphaColor;
var
m: Double;
r, g, b, mt: Byte;
begin
if fraction <= 0 then m := 0 else
if fraction >= 1 then m := 6
else m := fraction * 6;
mt := (round (frac (m) * $FF));
case Trunc (m) of
0: begin
R := $FF;
G := mt;
B := 0;
end;
1: begin
R := $FF - mt;
G := $FF;
B := 0;
end;
2: begin
R := 0;
G := $FF;
B := mt;
end;
3: begin
R := 0;
G := $FF - mt;
B := $FF;
end;
4: begin
R := mt;
G := 0;
B := $FF;
end;
5: begin
R := $FF;
G := 0;
B := $FF - mt;
end;
end; // case
Result := ColorToQuad (r, g, b);
end; // GiveRainbowColor //
Trouble with this algorithm is it can't show a partial rainbow between two colors. Well, of course it can but than you have to approach the fraction of each color and I don't like that solution. I tried decomposing the color into its r, g, b channels but that did not work. The reason is quite obvious by hindsight. Suppose you need a gradient between FF0000 and 0000FF. You'll have a red color transforming from FF->00 and a blue from 00->FF. However, there is no green (00FF00) which is clearly present in a rainbow gradient.
What I need is a gradient function that I can give two colors and a fraction and it generates a color. Can anyone point me to an article, algorithm or even code?
Update
NGLN's answer is the right answer for this question. Both he and Warren wondered what to do when a a color is not a bright color (a color containing a 0, a $FF and a value). I tried several angles: up/downscaling and HSL interpolation. I finally settled down for the last one as being the most simple.
Basically you have two colors: from and to. Use RGBtoHSL to extract the HSL parameters from each color: RGBtoHSL (col_from, hf, sf, lf). Next compute the hue, saturation and luminance between both colors and reconstruct a new color. This is what NGLN mentions in his update about hue, but if you generalise this principle you have a rainbow between any color.
function TColor_Dialog.interpolate_hsl (col_from, col_to: TAlphaColor; fraction: double): TAlphaColor;
var af, at, ad: uInt8;
hf, ht, hd: single;
sf, st, sd: single;
lf, lt, ld: single;
begin
// Get each rgb color channel
af := GetAValue (col_from);
at := GetAValue (col_to);
RGBtoHSL (col_from, hf, sf, lf);
RGBtoHSL (col_to, ht, st, lt);
// Compute differences
ad := af + Round (fraction * (at - af));
hd := hf + fraction * (ht - hf);
sd := sf + fraction * (st - sf);
ld := lf + fraction * (lt - lf);
Result := MakeColor (HSLtoRGB (hd, sd, ld), ad);
end; // interpolate_hsl //
This gives a rainbow for all colors possible. I apply the same interpolation for the opacity, hence the use of MakeColor to 'fumble' the interpolated alpha channel into the color.
Then you need to calculate the position of a color in the Rainbow; the inverse of GiveRainbowColor:
function RainbowIndex(BrightColor: TColor): Double;
var
R: Byte;
G: Byte;
B: Byte;
begin
R := GetRValue(ColorToRGB(BrightColor));
G := GetGValue(ColorToRGB(BrightColor));
B := GetBValue(ColorToRGB(BrightColor));
if (R * G * B <> 0) or ((R <> 255) and (G <> 255) and (B <> 255)) then
Result := -1
else if B = 0 then
if R = 255 then
Result := 0 + G / 255
else
Result := 1 + (255 - R) / 255
else if R = 0 then
if G = 255 then
Result := 2 + B / 255
else
Result := 3 + (255 - G) / 255
else { G = 0 }
if B = 255 then
Result := 4 + R / 255
else
Result := 5 + (255 - B) / 255;
Result := Result / 6;
end;
(But this displays a problem for colors not having both a 0 and a 255 part. In other words: you would also need to calculate the bright color from a shaded, tinted or grayed color. See update below.)
Example usage to get the rainbow slice from clRed to clBlue:
procedure TForm1.FormPaint(Sender: TObject);
var
Start: Double;
Finish: Double;
X: Integer;
begin
Start := RainbowIndex(clRed);
Finish := RainbowIndex(clBlue);
for X := 0 to ClientWidth - 1 do
begin
Canvas.Brush.Color := GiveRainbowColor(0, ClientWidth - 1, X);
Canvas.FillRect(Bounds(X, 0, 1, 50));
Canvas.Brush.Color :=
GiveRainbowColor(0, ClientWidth - 1, Round(Start + (Finish - Start) * X));
Canvas.FillRect(Bounds(X, 50, 1, 50));
end;
end;
Update:
The RainbowIndex routine above really does nothing more then calculate the hue property of the color. The GraphUtil unit provides conversion routines for the HSL color model which makes this RainbowIndex routine kind of obsolete and gives the advantage to be able to feed any TColor value:
uses
GraphUtil;
const
HLSMAX = 240;
function Hue(AColor: TColor): Double;
var
Hue: Word;
Luminance: Word;
Saturation: Word;
begin
ColorRGBToHLS(ColorToRGB(AColor), Hue, Luminance, Saturation);
Result := Hue / HLSMAX;
end;
Example usage to get the rainbow slice from clMoneyGreen to clPurple:
function RainbowColor(Hue: Double): TColor; overload;
begin
Hue := EnsureRange(Hue, 0, 1) * 6;
case Trunc(Hue) of
0: Result := RGB(255, Round(Frac(Hue) * 255), 0);
1: Result := RGB(255 - Round(Frac(Hue) * 255), 255, 0);
2: Result := RGB(0, 255, Round(Frac(Hue) * 255));
3: Result := RGB(0, 255 - Round(Frac(Hue) * 255), 255);
4: Result := RGB(Round(Frac(Hue) * 255), 0, 255);
else
Result := RGB(255, 0, 255 - Round(Frac(Hue) * 255));
end;
end;
function RainbowColor(MinHue, MaxHue, Hue: Integer): TColor; overload;
begin
Result := RainbowColor((Hue - MinHue) / (MaxHue - MinHue + 1));
end;
procedure TForm1.FormPaint(Sender: TObject);
var
X: Integer;
Start: Double;
Finish: Double;
begin
Start := Hue(clMoneyGreen);
Finish := Hue(clPurple);
for X := 0 to ClientWidth - 1 do
begin
Canvas.Brush.Color := RainbowColor(0, ClientWidth - 1, X);
Canvas.FillRect(Bounds(X, 0, 1, 50));
Canvas.Brush.Color :=
RainbowColor(Start + (Finish - Start) * X / ClientWidth);
Canvas.FillRect(Bounds(X, 50, 1, 50));
end;
end;
Furthermore, the RainbowColor routine could be shortened to:
function RainbowColor(Hue: Double): TColor; overload;
begin
Result := ColorHLStoRGB(Round(Hue * HLSMAX), HLSMAX div 2, HLSMAX);
end;
hi there
here it is my code:
procedure TForm4.Button1Click(Sender: TObject);
var
png: TPNGImage;
data: PRGBQarray;
p: ^tagRGBQuad;
i, o: integer;
begin
png := TPNGImage.Create;
try
png.LoadFromFile('C:\Untitled.png');
for o := 1 to 100 do
begin
data:=png.Scanline[o];
for I := 1 to 400 do
begin
p := #data^[i];
p.rgbGreen := p.rgbBlue;
p.rgbRed := p.rgbGreen;
end;
end;
img.picture.Assign(png);
finally
png.Free;
end;
end;
it doesn't work and it makes the pic messy, I'm sure it's because of the rgbReserved.
what should i do?
This is how to greyify a bitmap. (And, yes, if you want to greyify a PNG, you first need to get the bitmap data out of it. I think the VCL will do this for you.)
type
PRGB32Array = ^TRGB32Array;
TRGB32Array = packed array[0..MaxInt div SizeOf(TRGBQuad)-1] of TRGBQuad;
procedure MakeGrey(Bitmap: TBitmap);
var
w, h: integer;
y: Integer;
sl: PRGB32Array;
x: Integer;
grey: byte;
begin
Bitmap.PixelFormat := pf32bit;
w := Bitmap.Width;
h := Bitmap.Height;
for y := 0 to h - 1 do
begin
sl := Bitmap.ScanLine[y];
for x := 0 to w - 1 do
with sl[x] do
begin
grey := (rgbBlue + rgbGreen + rgbRed) div 3;
rgbBlue := grey;
rgbGreen := grey;
rgbRed := grey;
end;
end;
end;
Sample usage:
procedure TForm4.Button1Click(Sender: TObject);
var
bm: TBitmap;
begin
bm := TBitmap.Create;
try
bm.LoadFromFile('C:\Users\Andreas Rejbrand\Pictures\Porträtt, litet, kvadratiskt.bmp');
MakeGrey(bm);
Canvas.Draw(0, 0, bm);
finally
bm.Free;
end;
end;
Andreas's answer will give you a good, fast approximation, but you'll lose some quality, because red, green and blue don't mix with equal intensities in the human eye. If you want to "get it right", instead of
grey := (rgbBlue + rgbGreen + rgbRed) div 3;
try this:
grey := round(rgbRed * .3) + round(rgbGreen * .59) + round(rgbBlue * .11);
You'll get a bit of a performance hit over the simple average, though it probably won't be noticeable unless you're on a very large image.
I know the question has already been answered but here is my 2c worth...
The following code comes from the PNGComponents package (PngFunctions.pas) produced by Thany.
//
//The Following code comes from the PNGComponents package from Thany...
//
procedure MakeImageGrayscale(Image: TPNGObject; Amount: Byte = 255);
procedure GrayscaleRGB(var R, G, B: Byte);
var
X: Byte;
begin
X := Round(R * 0.30 + G * 0.59 + B * 0.11);
R := Round(R / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
G := Round(G / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
B := Round(B / 256 * (256 - Amount - 1)) + Round(X / 256 * (Amount + 1));
end;
var
X, Y, PalCount: Integer;
Line: Pointer;
PaletteHandle: HPalette;
Palette: array[Byte] of TPaletteEntry;
begin
//Don't do anything if the image is already a grayscaled one
if not (Image.Header.ColorType in [COLOR_GRAYSCALE, COLOR_GRAYSCALEALPHA])
then begin
if Image.Header.ColorType = COLOR_PALETTE
then begin
//Grayscale every palette entry
PaletteHandle := Image.Palette;
PalCount := GetPaletteEntries(PaletteHandle, 0, 256, Palette);
for X := 0 to PalCount - 1
do GrayscaleRGB(Palette[X].peRed, Palette[X].peGreen, Palette[X].peBlue);
SetPaletteEntries(PaletteHandle, 0, PalCount, Palette);
Image.Palette := PaletteHandle;
end
else begin
//Grayscale every pixel
for Y := 0 to Image.Height - 1
do begin
Line := Image.Scanline[Y];
for X := 0 to Image.Width - 1
do GrayscaleRGB(PRGBLine(Line)^[X].rgbtRed, PRGBLine(Line)^[X].rgbtGreen, PRGBLine(Line)^[X].rgbtBlue);
end;
end;
end;
end;
There is a set of routines, that was originally published by the author of the PNGImage components, that can be found on Code Central that shows how to do other things like Alpha blending two images, rotation, overlay, etc. CodeCentral Link
This really should have been a comment to #Mason's routine to turn RGB into GreyScale, but since I don't know how to make a comment show code, I'm making it an answer instead.
This is how I do the conversion:
FUNCTION RGB2GRAY(R,G,B : BYTE) : BYTE; Register; ASSEMBLER;
ASM
IMUL EAX,19595
IMUL EDX,38470
IMUL ECX,7471
ADD EAX,EDX
ADD EAX,ECX
SHR EAX,16
END;
FUNCTION GreyScale(C : TColor) : TColor; Register; ASSEMBLER;
ASM
MOVZX EDX,AH
MOV ECX,EAX
SHR ECX,16
MOVZX EAX,AL
CALL RGB2GRAY
MOVZX EAX,AL
MOV AH,AL
SHL EAX,8
MOV AL,AH
END;
I don't know if it is NTSC formula or whatever, but they seem to work in my programs :-).