My little code creates modern art to the full screen size Form canvas with different kind of shapes.
I can make ellipse, rectangle and line work but not the polycon. Can any one help me? (Version: Delphi community edition )
uses .... GDIPAPI, GDIPOBJ, GDIPUTIL;
procedure TForm1.Button1Click(Sender: TObject);
var
graphics: TGPGraphics;
SolidPen: TGPPen;
SolidBrush : TGPBrush;
x,y,x2,y2,x3,y3 : integer;
begin
graphics := TGPGraphics.Create(Canvas.Handle);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
SolidPen := TGPPen.Create(MakeColor(255, random(255), random(255), random(255)), random(4)+1);
SolidBrush := TGPSolidBrush.Create(MakeColor(255, random(255), random(255), random(255)));
SolidPen.SetStartCap(LineCapRound);
SolidPen.SetEndCap(LineCapRound);
//POLYCON, not working.------------------
// PROBLEM HERE: it's complaining: 'Oridinal type required' ,
// 'incompatible type: integer and TPoint'
x:= 150; y := 50; x2 := 50; y2 := 250; x3 := 250; y3 := 250;
graphics.FillPolygon(SolidBrush, [Point(x, y), Point(x2, y2), Point(x3, y3)]);
graphics.DrawPolygon(SolidPen, [Point(x, y), Point(x2, y2), Point(x3, y3)]);
//--------------------------------------------
// ELLIPSE, ok
x := random(Form1.width); y := random(Form1.height); x2 := random(200); y2 := random(200);
graphics.FillEllipse(SolidBrush,x, y, x2, y2);
graphics.DrawEllipse(SolidPen,x, y, x2, y2);
// RECTANGLE, ok
x := random(Form1.width); y := random(Form1.height); x2 := random(200); y2 := random(200);
graphics.FillRectangle(SolidBrush, x, y, x2, y2);
graphics.DrawRectangle(SolidPen, x, y, x2, y2);
// LINE, ok
x := random(Form1.width); y := random(Form1.height); x2 := random(Form1.width); y2 := random(Form1.height);
graphics.DrawLine(SolidPen, x, y, x2, y2);
procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.Height := Screen.Height;
Form1.Width := Screen.Width;
end;
You are not passing the points of the polygon correctly.
Looking at the two overloaded DrawPolygon() declarations:
function TGPGraphics.DrawPolygon(pen: TGPPen; points: PGPPointF; count: Integer): TStatus;
function TGPGraphics.DrawPolygon(pen: TGPPen; points: PGPPoint; count: Integer): TStatus;
you can see that the points are passed as a PGPPointF or PGPPoint. The definitions of these types are found in Winapi.GDIPAPI and there we see that the coordinates are either single or integer
Since you are using integer coordinates look at the definition of PGPPoint in Winapi.GDIPAPI
type
PGPPoint = ^TGPPoint;
TGPPoint = record
X : Integer;
Y : Integer;
end;
TPointDynArray = array of TGPPoint;
function MakePoint(X, Y: Integer): TGPPoint; overload;
{$EXTERNALSYM MakePoint}
So, declare a variable
ArrOfPoint: TPointDynArray;
and fill it with your points:
SetLength(ArrOfPoint, 3);
ArrOfPoint[0] := MakePoint(x, y);
ArrOfPoint[1] := MakePoint(x2, y2);
ArrOfPoint[2] := MakePoint(x3, y3);
Finally replace your call to e.g. DrawPolygon() with
graphics.DrawPolygon(SolidPen, PGPPoint(#ArrOfPoint[0]), 3);
meaning you pass the address of the first point as a PGPPoint type.
So, the corrected working code for the polygon is following (at the moment):
uses
... GDIPAPI, GDIPOBJ, GDIPUTIL;
procedure TForm1.Button1Click(Sender: TObject);
var
graphics: TGPGraphics;
SolidPen: TGPPen;
SolidBrush : TGPBrush;
ArrOfPoint: TPointDynArray;
x, y, x2, y2, x3, y3 : integer;
begin
graphics := TGPGraphics.Create(Canvas.Handle);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
SolidPen := TGPPen.Create(MakeColor(255, random(255), random(255), random(255)), random(4)+1);
SolidBrush := TGPSolidBrush.Create(MakeColor(255, random(255), random(255), random(255)));
x := random(Form1.Width); y := random(Form1.Height);
x2 := random(Form1.Width); y2 := random(Form1.Height);
x3 := random(Form1.Width); y3 := random(Form1.Height);
SetLength(ArrOfPoint, 3);
ArrOfPoint[0] := MakePoint(x, y);
ArrOfPoint[1] := MakePoint(x2, y2);
ArrOfPoint[2] := MakePoint(x3, y3);
graphics.FillPolygon(SolidBrush,PGPPoint(#ArrOfPoint[0]), 3);
graphics.DrawPolygon(SolidPen, PGPPoint(#ArrOfPoint[0]), 3);
end;
Related
So I want to print a string grid in the middle/center of another printed page that already has a printed/header and footer, but I'm not sure how to print the string grid in the middle/center of the page?
Is there anything in the code below that I can change to do that? Or do I have to do something else completely?
Thanks in advance for all the help!
Example of the type of page I have to print on (In the open part of the page):
Code I've used for one of the other buttons that also prints:
procedure PrintGrid(sGrid: TStringGrid; sTitle: string);
var
X1, X2: Integer;
Y1, Y2: Integer;
TmpI: Integer;
F: Integer;
TR: TRect;
begin
Printer.Title := sTitle;
Printer.BeginDoc;
Printer.Canvas.Pen.Color := 0;
Printer.Canvas.Font.Name := 'Times New Roman';
Printer.Canvas.Font.Size := 12;
Printer.Canvas.Font.Style := [fsBold, fsUnderline];
Printer.Canvas.TextOut(0, 100, Printer.Title);
for F := 1 to sGrid.ColCount - 1 do
begin
X1 := 0;
for TmpI := 1 to (F - 1) do
X1 := X1 + 5 * (sGrid.ColWidths[TmpI]);
Y1 := 300;
X2 := 0;
for TmpI := 1 to F do
X2 := X2 + 5 * (sGrid.ColWidths[TmpI]);
Y2 := 450;
TR := Rect(X1, Y1, X2 - 30, Y2);
Printer.Canvas.Font.Style := [fsBold];
Printer.Canvas.Font.Size := 7;
Printer.Canvas.TextRect(TR, X1 + 50, 350, sGrid.Cells[F, 0]);
Printer.Canvas.Font.Style := [];
for TmpI := 1 to sGrid.RowCount - 1 do
begin
Y1 := 150 * TmpI + 300;
Y2 := 150 * (TmpI + 1) + 300;
TR := Rect(X1, Y1, X2 - 30, Y2);
Printer.Canvas.TextRect(TR, X1 + 50, Y1 + 50, sGrid.Cells[F, TmpI]);
end;
end;
Printer.EndDoc;
end;
Kind Regards
PrimeBeat
You can get printer width and height in pixel (Printer.PageWidth and Printer.PageHeight). You can get text with and height using Printer.Canvas.TextExtent. You have your grid so you know the number of rows and column. The rest is some easy computing. You can adapt the font size so that the grid fits in the given space.
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 need develop a circular progress bar in delphi 2007, I can't use third-party components (company policy).
I'm using a Canvas, drawing an arc, that's works fine, but the image is at a very low resolution. It's possible to improve the resolution in canvas drawing?
Code sample:
procedure TForm1.DrawPieSlice(const Canvas: TCanvas; const Center: TPoint;
const Radius: Integer; const StartDegrees, StopDegrees: Double);
//Get it in http://delphidabbler.com/tips/148
const
Offset = 90;
var
X1, X2, X3, X4: Integer;
Y1, Y2, Y3, Y4: Integer;
begin
X1 := Center.X - Radius;
Y1 := Center.Y - Radius;
X2 := Center.X + Radius;
Y2 := Center.Y + Radius;
X4 := Center.X + Round(Radius * Cos(DegToRad(Offset + StartDegrees)));
Y4 := Center.y - Round(Radius * Sin(DegToRad(Offset + StartDegrees)));
X3 := Center.X + Round(Radius * Cos(DegToRad(Offset + StopDegrees)));
Y3 := Center.y - Round(Radius * Sin(DegToRad(Offset + StopDegrees)));
Canvas.Arc(X1, Y1, X2, Y2, X3, Y3, X4, Y4);
end;
procedure TForm1.SpinEdit1Change(Sender: TObject);
var
Center: TPoint;
Bitmap: TBitmap;
Radius: Integer;
p: Pointer;
begin
Label1.Caption:= SpinEdit1.Text+'%';
Bitmap := TBitmap.Create;
try
Bitmap.Width := Image1.Width;
Bitmap.Height := Image1.Height;
Bitmap.PixelFormat := pf24bit;
Bitmap.HandleType := bmDIB;
Bitmap.ignorepalette := true;
Bitmap.Canvas.Brush.Color := clBlack;
Bitmap.Canvas.Pen.Color := clHighlight;
Bitmap.Canvas.Pen.Width := 10;
Center := Point(Bitmap.Width div 2, Bitmap.Height div 2);
Radius := 61;
DrawPieSlice(Bitmap.Canvas, Center, Radius,0,round(SpinEdit1.Value * -3.6));
Image1.Picture.Graphic := Bitmap;
finally
Bitmap.Free;
end;
end;
Result:
I am open to proposals for other solutions.
If you are not allowed to use any third-party graphic library with anti-aliasing possibilities, consider using GDI+, which is included in Windows, and Delphi has a wrapper for it.
uses
..., GDIPAPI, GDIPOBJ, GDIPUTIL //included in Delphi standard modules
var
graphics: TGPGraphics;
SolidPen: TGPPen;
begin
graphics := TGPGraphics.Create(Canvas.Handle);
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
SolidPen := TGPPen.Create(MakeColor(255, 0, 0, 255), 31);
SolidPen.SetStartCap(LineCapRound);
SolidPen.SetEndCap(LineCapRound);
graphics.DrawArc(SolidPen, 100, 100, 100, 100, 0, 270);
graphics.Free;
SolidPen.Free;
Not sure if Direct2D units already exist in Delphi 2007, but it maybe a better option to use Direct2D since it is rendered using GPU, not CPU.
uses Vcl.Direct2D, Winapi.D2D1;
...
var
D2DCanvas: TDirect2DCanvas;
begin
if TDirect2DCanvas.Supported then
begin
D2DCanvas := TDirect2DCanvas.Create(PaintBox.Canvas, PaintBox.ClientRect);
try
D2DCanvas.RenderTarget.BeginDraw;
D2DCanvas.RenderTarget.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
D2DCanvas.Pen.Color := TColors.Blue;
D2DCanvas.Pen.Width := 10;
D2DCanvas.Arc(100, 100, 200, 200, 100, 150, 150, 100);
D2DCanvas.RenderTarget.EndDraw;
finally
D2DCanvas.Free;
end;
end
end;
a very simple solution would be to draw your circle at a higher resolution (like 1.5x or 2x) on a temp bitmap and then resize it to your resolution (because the resize process will add antialias to your circle) and finally draw directly this bitmap to the canvas. in fact it's like this that many algorithm work to add antialias.
you may use the following unit (work in progress)
All you need to do is to add it to your "uses" and the supported TCanvas operations will be converted to GDI+
The "magic" is done by a TCanvas class helper that overrides the functions
supports: ellipse, polygon, polyline, lineTo
arc is not yet supported - because I did not need it so far...
unit uAntiAliasedCanvas;
interface
uses Graphics, types, UITypes, GdiPlus;
type TAntiAliasedCanvas = class helper for TCanvas
private
function Graphics : IGPGraphics;
function Pen : IGPPen;
function Brush: IGPBrush;
function path(const points : array of TPoint; close : boolean = false) : TGPGraphicsPath;
function TGPcolorFromVCLColor(color : TColor) : TGPColor;
private
class var antiAliased : boolean;
public
procedure Ellipse(X1, Y1, X2, Y2: Integer);
procedure Polyline(const Points: array of TPoint);
procedure Polygon(const Points: array of TPoint);
procedure lineTo(x,y : integer);
class procedure setAntiAliasing(value : boolean);
end;
implementation
{ TAntiAliasedCanvas }
uses WinAPI.Windows;
function TAntiAliasedCanvas.Brush: IGPBrush;
begin
result := TGPSolidBrush.Create(
TGPColor.Create(
TGPcolorFromVCLColor(
(inherited brush).color)));
end;
procedure TAntiAliasedCanvas.Ellipse(X1, Y1, X2, Y2: Integer);
begin
if antiAliased then
begin
Graphics.fillEllipse(brush, X1, Y1, 1+X2-X1, 1+Y2-Y1);
Graphics.drawEllipse(Pen, X1, Y1, 1+X2-X1, 1+Y2-Y1)
end
else
inherited Ellipse(X1, Y1, X2, Y2)
end;
function TAntiAliasedCanvas.Graphics: IGPGraphics;
begin
result := TGPGraphics.Create(Handle);
result.SmoothingMode := SmoothingModeAntiAlias
end;
procedure TAntiAliasedCanvas.lineTo(x, y: integer);
begin
if antiAliased then
graphics.DrawLine(pen, penPos.X, penPos.Y, X, Y)
else
inherited lineTo(x,y)
end;
function TAntiAliasedCanvas.path(const points: array of TPoint;
close : boolean = false): TGPGraphicsPath;
var
GPPoints: array of TGPPointF;
ptTypes : array of byte;
i : integer;
begin
setLength(GPPoints, length(points) + ord(close));
setLength(ptTypes, length(points) + ord(close));
for i := 0 to high(Points) + ord(close) do
with points[i mod length(points)] do
begin
GPPoints[i] := TGPPointF.Create(x,y);
ptTypes[i] := byte(PathPointTypeLine);
end;
result := TGPGraphicsPath.Create(GPPoints,ptTypes)
end;
function TAntiAliasedCanvas.pen: IGPpen;
begin
result := TGPpen.Create(
TGPColor.Create(
TGPcolorFromVCLColor(
(inherited pen).color)),
(inherited pen).width);
end;
procedure TAntiAliasedCanvas.Polygon(const Points: array of TPoint);
var
aPath : TGPGraphicsPath;
aPen : IGPPen;
begin
if antiAliased then
begin
aPath := path(points, true);
graphics.FillPath(brush, aPath);
aPen := pen();
aPen.SetLineJoin(LineJoinRound);
graphics.DrawPath(aPen, aPath);
end
else
inherited Polygon(points);
end;
procedure TAntiAliasedCanvas.Polyline(const Points: array of TPoint);
var
aPen : IGPPen;
begin
if antiAliased then
begin
aPen := pen();
aPen.SetLineJoin(LineJoinRound);
graphics.DrawPath(aPen, path(points))
end
else
inherited polyline(points)
end;
class procedure TAntiAliasedCanvas.setAntiAliasing(value: boolean);
begin
antiAliased := value
end;
function TAntiAliasedCanvas.TGPcolorFromVCLColor(color: TColor): TGPColor;
begin
if Color < 0 then
color := GetSysColor(Color and $000000FF);
result := TGPColor.Create(
color and $FF,
(color and $FF00) shr 8,
(color and $FF0000) shr 16)
end;
begin
TCanvas.setAntiAliasing(true)
end.
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/
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;