1236 lines
34 KiB
ObjectPascal

unit ugraph;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, bgrabitmap, bgrabitmaptypes, Graphics;
const
FrameDashLength = 4;
function RectUnion(const rect1, Rect2: TRect): TRect;
function RectOfs(const ARect: TRect; ofsX, ofsY: integer): TRect;
function GetShapeBounds(const pts: array of TPointF; Width: single): TRect;
procedure DrawCheckers(bmp: TBGRABitmap);
procedure DrawGrid(bmp: TBGRABitmap; sizex, sizey: single);
function ComputeAngle(dx, dy: single): single;
function GetSelectionCenter(bmp: TBGRABitmap): TPointF;
procedure ComputeSelectionMask(image: TBGRABitmap; destMask: TBGRABitmap);
procedure SubstractMask(image: TBGRABitmap; mask: TBGRABitmap);
procedure NicePoint(bmp: TBGRABitmap; x, y: single); overload;
procedure NicePoint(bmp: TBGRABitmap; ptF: TPointF); overload;
procedure NiceLine(bmp: TBGRABitmap; x1, y1, x2, y2: single);
function ComputeColorCircle(tx, ty: integer; light: word;
hueCorrection: boolean = True): TBGRABitmap;
function ChangeCanvasSize(bmp: TBGRABitmap; newWidth, newHeight: integer;
anchor: string; background: TBGRAPixel; repeatImage: boolean;
flipMode: boolean = False): TBGRABitmap; overload;
procedure RenderCloudsOn(bmp: TBGRABitmap; color: TBGRAPixel);
procedure RenderWaterOn(bmp: TBGRABitmap; waterColor, skyColor: TBGRAPixel);
function CreateMetalFloorTexture(tx: integer): TBGRABitmap;
function CreatePlastikTexture(tx, ty: integer): TBGRABitmap;
function CreateCamouflageTexture(tx, ty: integer): TBGRABitmap;
function CreateSnowPrintTexture(tx, ty: integer): TBGRABitmap;
function CreateRoundStoneTexture(tx, ty: integer): TBGRABitmap;
function CreateStoneTexture(tx, ty: integer): TBGRABitmap;
function CreateWaterTexture(tx, ty: integer): TBGRABitmap;
function CreateMarbleTexture(tx, ty: integer): TBGRABitmap;
function CreateWoodTexture(tx, ty: integer): TBGRABitmap;
function CreateVerticalWoodTexture(tx, ty: integer): TBGRABitmap;
function ClearTypeFilter(Source: TBGRACustomBitmap): TBGRACustomBitmap;
function ClearTypeInverseFilter(Source: TBGRACustomBitmap): TBGRACustomBitmap;
function DoResample(Source: TBGRABitmap; newWidth, newHeight: integer;
StretchMode: TResampleMode): TBGRABitmap;
implementation
uses Math, Types, LCLProc, BGRAGradients;
function RectUnion(const rect1, Rect2: TRect): TRect;
begin
if IsRectEmpty(rect1) then
begin
if IsRectEmpty(rect2) then
Result := EmptyRect
else
Result := rect2;
end
else
begin
Result := rect1;
if not IsRectEmpty(rect2) then
UnionRect(Result, Result, rect2);
end;
end;
function RectOfs(const ARect: TRect; ofsX, ofsY: integer): TRect;
begin
Result := ARect;
OffsetRect(Result, ofsX, ofsY);
end;
function GetShapeBounds(const pts: array of TPointF; Width: single): TRect;
var
ix, iy, i: integer;
begin
Width /= 2;
Result.Left := high(integer);
Result.Top := high(integer);
Result.Right := low(integer);
Result.Bottom := low(integer);
for i := 0 to high(pts) do
begin
ix := floor(pts[i].x - Width);
iy := floor(pts[i].y - Width);
if ix < Result.left then
Result.left := ix;
if iy < Result.Top then
Result.top := iy;
ix := ceil(pts[i].x + Width) + 2;
iy := ceil(pts[i].y + Width) + 2;
if ix > Result.right then
Result.right := ix;
if iy > Result.bottom then
Result.bottom := iy;
end;
if (Result.right <= Result.left) or (Result.bottom <= Result.top) then
Result := EmptyRect;
end;
procedure DrawCheckers(bmp: TBGRABitmap);
const
tx = 8;
ty = 8;
var
xb, yb, x, y: integer;
oddColor, evenColor: TBGRAPixel;
begin
oddColor := BGRA(220, 220, 220);
evenColor := BGRA(255, 255, 255);
y := 0;
for yb := 0 to (bmp.Height - 1) div ty do
begin
x := 0;
for xb := 0 to (bmp.Width - 1) div tx do
begin
if odd(xb + yb) then
bmp.FillRect(x, y, x + tx, y + ty, oddColor, dmSet)
else
bmp.FillRect(x, y, x + tx, y + ty, evenColor, dmSet);
Inc(x, tx);
end;
Inc(y, ty);
end;
end;
procedure DrawGrid(bmp: TBGRABitmap; sizex, sizey: single);
var
xb, yb: integer;
imgGrid: TBGRABitmap;
alpha: byte;
begin
imgGrid := TBGRABitmap.Create(bmp.Width, 1);
alpha := min(96, round((abs(sizex) + abs(sizey)) * (96 / 16 / 2)));
imgGrid.DrawLineAntialias(0, 0, imgGrid.Width - 1, 0, BGRA(255, 255, 255, alpha),
BGRA(0, 0, 0, alpha),
min(3, max(1, round(sizex / 8))), True);
for yb := 1 to trunc(bmp.Height / sizey) do
bmp.PutImage(0, round(yb * sizey), imgGrid, dmFastBlend);
imgGrid.Free;
imgGrid := TBGRABitmap.Create(1, bmp.Height);
imgGrid.DrawLineAntialias(0, 0, 0, imgGrid.Height - 1, BGRA(0, 0, 0, alpha),
BGRA(255, 255, 255, alpha),
min(3, max(1, round(sizey / 8))), True);
for xb := 1 to trunc(bmp.Width / sizex) do
bmp.PutImage(round(xb * sizex), 0, imgGrid, dmFastBlend);
imgGrid.Free;
end;
procedure RenderCloudsOn(bmp: TBGRABitmap; color: TBGRAPixel);
const
minDensity = 180;
maxDensity = 240;
var
i, k, x, y: integer;
fact, radius: single;
tempBmp: TBGRABitmap;
ptemp: PBGRAPixel;
begin
if color.alpha = 0 then
exit;
tempBmp := TBGRABitmap.Create(bmp.Width, bmp.Height, BGRABlack);
fact := (bmp.Width + bmp.Height) / 15;
for i := 120 downto 20 do
begin
for k := 1 to 2 do
begin
radius := ((i + random(50)) / 100) * fact;
x := random(bmp.Width);
y := random(bmp.Height);
tempBmp.GradientFill(floor(x - radius), floor(y - radius), ceil(
x + radius), ceil(y + radius), BGRA(255, 255, 255, 128), BGRAPixelTransparent,
gtRadial, pointf(x, y), pointf(x + radius + 0.5, y), dmFastBlend, False);
end;
end;
ptemp := tempBmp.Data;
for i := tempBmp.nbPixels - 1 downto 0 do
begin
if ptemp^.red < minDensity then
ptemp^ := BGRAPixelTransparent
else
if ptemp^.red > maxDensity then
ptemp^ := color
else
ptemp^ := BGRA(color.red, color.green, color.blue, color.alpha *
(ptemp^.red - minDensity) div (maxDensity - minDensity));
Inc(ptemp);
end;
bmp.PutImage(0, 0, tempBmp, dmDrawWithTransparency);
tempBmp.Free;
end;
procedure RenderWaterOn(bmp: TBGRABitmap; waterColor, skyColor: TBGRAPixel);
var
Noise, Temp: TBGRABitmap;
Phong: TPhongShading;
begin
Noise := CreateCyclicPerlinNoiseMap(bmp.Width, bmp.Height, 1, 1, 1.2);
Temp := Noise.FilterBlurRadial(1, rbFast) as TBGRABitmap;
Noise.Free;
Noise := Temp;
Noise.ApplyGlobalOpacity(waterColor.alpha);
waterColor.alpha := 255;
Phong := TPhongShading.Create;
Phong.NegativeDiffusionFactor := 0.1;
Phong.AmbientFactor := 0.7;
Phong.LightSourceDistanceFactor := 0;
Phong.LightDestFactor := 0;
Phong.LightSourceIntensity := 300;
Phong.LightPosition := Point(-500, -500);
Phong.LightColor := skyColor;
Phong.Draw(bmp, Noise, 30, 0, 0, waterColor);
Noise.Free;
Phong.Free;
end;
function Interp256(value1, value2, position: integer): integer; inline;
begin
Result := (value1 * (256 - position) + value2 * position) shr 8;
end;
function Interp256(color1, color2: TBGRAPixel; position: integer): TBGRAPixel; inline;
begin
Result.red := Interp256(color1.red, color2.red, position);
Result.green := Interp256(color1.green, color2.green, position);
Result.blue := Interp256(color1.blue, color2.blue, position);
Result.alpha := Interp256(color1.alpha, color2.alpha, position);
end;
function CreateWoodTexture(tx, ty: integer): TBGRABitmap;
var
colorOscillation, globalColorVariation: integer;
p: PBGRAPixel;
i: integer;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1.5, 1.5, 1, rfBestQuality);
p := Result.Data;
for i := 0 to Result.NbPixels - 1 do
begin
colorOscillation := round(sqrt((sin(p^.red * Pi / 16) + 1) / 2) * 256);
globalColorVariation := p^.red;
p^ := Interp256(Interp256(BGRA(247, 188, 120), BGRA(255, 218, 170),
colorOscillation), Interp256(BGRA(157, 97, 60), BGRA(202, 145, 112),
colorOscillation), globalColorVariation);
Inc(p);
end;
end;
function CreateVerticalWoodTexture(tx, ty: integer): TBGRABitmap;
var
globalPos: single;
colorOscillation, globalColorVariation: integer;
p: PBGRAPixel;
i: integer;
x, nbVertical: integer;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 1, rfBestQuality);
p := Result.Data;
x := 0;
nbVertical := tx div 128;
if nbVertical = 0 then
nbVertical := 1;
for i := 0 to Result.NbPixels - 1 do
begin
globalPos := p^.red * Pi / 32 + nbVertical * x * 2 * Pi / tx * 8;
colorOscillation := round(sqrt((sin(globalPos) + 1) / 2) * 256);
globalColorVariation := p^.red; //round(sin(globalPos/8)*128+128);
p^ := Interp256(Interp256(BGRA(247, 188, 120), BGRA(255, 218, 170),
colorOscillation), Interp256(BGRA(157, 97, 60), BGRA(202, 145, 112),
colorOscillation), globalColorVariation);
Inc(p);
Inc(x);
if x = tx then
x := 0;
end;
end;
function ClearTypeFilter(Source: TBGRACustomBitmap): TBGRACustomBitmap;
var
mul3, temp: TBGRACustomBitmap;
xb, yb: integer;
pmul3, pdest: PBGRAPixel;
a: byte;
begin
Source.ResampleFilter := rfSpline;
mul3 := Source.Resample(Source.Width * 3 - 2, Source.Height);
temp := Source.NewBitmap(Source.Width * 3, Source.Height);
temp.PutImage(1, 0, mul3, dmSet);
for yb := 0 to temp.Height - 1 do
begin
temp.SetPixel(0, yb, temp.GetPixel(1, yb));
temp.SetPixel(temp.Width - 1, yb, temp.GetPixel(temp.Width - 2, yb));
end;
mul3.Free;
mul3 := temp;
Result := Source.NewBitmap(Source.Width, Source.Height);
for yb := 0 to Result.Height - 1 do
begin
pmul3 := mul3.ScanLine[yb];
pdest := Result.ScanLine[yb];
for xb := Result.Width - 1 downto 0 do
begin
a := (pmul3 + 1)^.alpha;
if a = 0 then
pdest^ := BGRAPixelTransparent
else
begin
pdest^.alpha := a;
if pmul3^.alpha = 0 then
pdest^.red := 128
else
pdest^.red := pmul3^.red;
pdest^.green := (pmul3 + 1)^.green;
if (pmul3 + 2)^.alpha = 0 then
pdest^.blue := 128
else
pdest^.blue := (pmul3 + 2)^.blue;
end;
Inc(pdest);
Inc(pmul3, 3);
end;
end;
mul3.Free;
end;
function ClearTypeInverseSubFilter(Source: TBGRACustomBitmap): TBGRACustomBitmap;
const
blueA = 20;
blueB = 0;
blueC = 2;
redA = 20;
redB = 0;
redC = 2;
maxV = 255;
var
yb, xb: integer;
psrc, pdest, pgray: PBGRAPixel;
a, v: integer;
grayscale, temp: TBGRACustomBitmap;
function Merge3(c1, c2, c3: TBGRAPixel): TBGRAPixel;
var
c123: cardinal;
begin
if (c1.alpha = 0) then
Result := MergeBGRA(c2, c3)
else
if (c2.alpha = 0) then
Result := MergeBGRA(c1, c3)
else
if (c3.alpha = 0) then
Result := MergeBGRA(c1, c2)
else
begin
c123 := c1.alpha + c2.alpha + c3.alpha;
Result.red := (c1.red * c1.alpha + c2.red * c2.alpha + c3.red *
c3.alpha + c123 shr 1) div c123;
Result.green := (c1.green * c1.alpha + c2.green * c2.alpha +
c3.green * c3.alpha + c123 shr 1) div c123;
Result.blue := (c1.blue * c1.alpha + c2.blue * c2.alpha +
c3.blue * c3.alpha + c123 shr 1) div c123;
Result.alpha := (c123 + 1) div 3;
end;
end;
begin
if Source.Width <= 1 then
begin
Result := Source.duplicate;
exit;
end;
grayscale := Source;
temp := Source.NewBitmap(Source.Width, Source.Height);
for yb := 0 to Source.Height - 1 do
begin
psrc := Source.Scanline[yb];
pgray := grayscale.ScanLine[yb];
pdest := temp.Scanline[yb];
pdest^.red := psrc^.red;
pdest^.green := psrc^.green;
pdest^.alpha := psrc^.alpha;
a := (psrc^.alpha * blueA) - ((psrc + 1)^.alpha * (blueB));
if a > 0 then
begin
v := ((integer(psrc^.blue) * blueA) * psrc^.alpha - integer(
(psrc + 1)^.blue * blueB) * (psrc + 1)^.alpha) div a;
if v >= maxV then
pdest^.blue := 255
else
if v > 0 then
pdest^.blue := v
else
pdest^.blue := 0;
end
else
pdest^.blue := psrc^.blue;
Inc(pdest);
Inc(psrc);
Inc(pgray);
for xb := Source.Width - 3 downto 0 do
begin
pdest^.green := psrc^.green;
pdest^.alpha := psrc^.alpha;
a := (psrc^.alpha * redA) - ((psrc - 1)^.alpha * (redB));
if a > 0 then
begin
v := ((integer(psrc^.red) * redA) * psrc^.alpha - integer(
(psrc - 1)^.red * redB + ((pgray - 1)^.green - pgray^.green) * redC) *
(psrc - 1)^.alpha) div a;
if v >= maxV then
pdest^.red := 255
else
if v > 0 then
pdest^.red := v
else
pdest^.red := 0;
end
else
pdest^.red := psrc^.red;
a := (psrc^.alpha * blueA) - ((psrc + 1)^.alpha * (blueB));
if a > 0 then
begin
v := ((integer(psrc^.blue) * blueA) * psrc^.alpha - integer(
(psrc + 1)^.blue * blueB + ((pgray + 1)^.green - pgray^.green) * blueC) *
(psrc + 1)^.alpha) div a;
if v >= maxV then
pdest^.blue := 255
else
if v > 0 then
pdest^.blue := v
else
pdest^.blue := 0;
end
else
pdest^.blue := psrc^.blue;
Inc(pdest);
Inc(psrc);
Inc(pgray);
end;
pdest^.green := psrc^.green;
pdest^.blue := psrc^.blue;
pdest^.alpha := psrc^.alpha;
a := (psrc^.alpha * redA) - ((psrc - 1)^.alpha * (redB));
if a > 0 then
begin
v := ((integer(psrc^.red) * redA) * psrc^.alpha - integer(
(psrc - 1)^.red * redB) * (psrc - 1)^.alpha) div a;
if v >= maxV then
pdest^.red := 255
else
if v > 0 then
pdest^.red := v
else
pdest^.red := 0;
end
else
pdest^.red := psrc^.red;
end;
Result := temp;
end;
function ClearTypeSharpenFilter(Source, diffbmp: TBGRACustomBitmap): TBGRACustomBitmap;
const
factnum = 3;
factdenom = 5;
var
xb, yb, maxx: integer;
psrc, pdest, pdiff: PBGRAPixel;
d1, d2: integer;
function clamp(Value: integer): byte;
begin
if Value <= 0 then
Result := 0
else if Value >= 255 then
Result := 255
else
Result := Value;
end;
function adjustDiff(ref, v1, v2: integer): integer;
begin
v1 -= ref;
v2 -= ref;
Result := v1 + v2;
end;
begin
if diffbmp = nil then
diffbmp := Source;
if (Source.Width <= 1) or (diffbmp.Width <> Source.Width) or
(diffbmp.Height <> Source.Height) then
begin
Result := Source.Duplicate();
exit;
end;
Result := Source.NewBitmap(Source.Width, Source.Height);
for yb := 0 to Result.Height - 1 do
begin
psrc := Source.ScanLine[yb];
pdest := Result.ScanLine[yb];
pdiff := diffbmp.ScanLine[yb];
maxx := Result.Width - 1;
for xb := 0 to maxx do
begin
if psrc^.alpha <> 0 then
begin
if (xb > 0) and ((psrc - 1)^.alpha <> 0) and (xb < maxx) and
((psrc + 1)^.alpha <> 0) then
begin
d1 := BGRADiff((pdiff - 1)^, pdiff^);
d2 := BGRADiff((pdiff + 1)^, pdiff^);
if (d1 > 20) and (d2 > 20) and (d1 + d2 > 100) then
begin
pdest^.red := clamp(psrc^.red -
(adjustDiff(psrc^.red, (psrc + 1)^.red, (psrc - 1)^.red)) *
factnum div (2 * factdenom));
pdest^.green := psrc^.green;
pdest^.blue := clamp(psrc^.blue -
(adjustDiff(psrc^.blue, (psrc + 1)^.blue, (psrc - 1)^.blue)) *
factnum div (2 * factdenom));
pdest^.alpha := psrc^.alpha;
end
else
pdest^ := psrc^;
end
else
if (xb < maxx) and ((psrc + 1)^.alpha <> 0) then
begin
pdest^.red := clamp(psrc^.red - ((psrc + 1)^.red - psrc^.red) *
factnum div factdenom);
pdest^.green := psrc^.green;
pdest^.blue := clamp(psrc^.blue - ((psrc + 1)^.blue - psrc^.blue) *
factnum div factdenom);
pdest^.alpha := psrc^.alpha;
end
else
if (xb > 0) and ((psrc - 1)^.alpha <> 0) then
begin
pdest^.red := clamp(psrc^.red - ((psrc - 1)^.red - psrc^.red) *
factnum div factdenom);
pdest^.green := psrc^.green;
pdest^.blue := clamp(psrc^.blue - ((psrc - 1)^.blue - psrc^.blue) *
factnum div factdenom);
pdest^.alpha := psrc^.alpha;
end
else
pdest^ := psrc^;
end
else
pdest^ := BGRAPixelTransparent;
Inc(pdest);
Inc(psrc);
Inc(pdiff);
end;
end;
end;
function ClearTypeRemoveContradiction(Source: TBGRACustomBitmap): TBGRACustomBitmap;
var
xb, yb: integer;
dr, db: integer;
ratio: single;
psrc, pdest: PBGRAPixel;
begin
if Source.Width <= 1 then
begin
Result := Source.Duplicate();
exit;
end;
Result := Source.NewBitmap(Source.Width, Source.Height);
for yb := 0 to Result.Height - 1 do
begin
psrc := Source.ScanLine[yb];
pdest := Result.ScanLine[yb];
pdest^ := psrc^;
for xb := Result.Width - 2 downto 0 do
begin
(pdest +1)^ := (psrc + 1)^;
if (psrc^.alpha > 10) and ((psrc + 1)^.alpha > 10) then
begin
dr := psrc^.red - (psrc + 1)^.red;
db := psrc^.blue - (psrc + 1)^.blue;
if ((db < 0) and (dr > 0)) or ((db > 0) and (dr < 0)) then
begin
ratio := abs(dr / db);
if (ratio > 0.2) and (ratio < 5) then
begin
dr := (psrc^.red * psrc^.alpha + (psrc + 1)^.red * (psrc + 1)^.alpha) div
(psrc^.alpha + (psrc + 1)^.alpha);
db := (psrc^.blue * psrc^.alpha + (psrc + 1)^.blue * (psrc + 1)^.alpha) div
(psrc^.alpha + (psrc + 1)^.alpha);
pdest^.red := dr;
pdest^.blue := db;
(pdest +1)^.red := dr;
(pdest +1)^.blue := db;
end;
end;
end;
Inc(pdest);
Inc(psrc);
end;
end;
end;
function ClearTypeInverseFilter(Source: TBGRACustomBitmap): TBGRACustomBitmap;
var
mul3, temp: TBGRACustomBitmap;
xb, yb: integer;
pmul3, pdest: PBGRAPixel;
a: byte;
begin
Source.ResampleFilter := rfSpline;
mul3 := Source.Resample(Source.Width * 3 - 2, Source.Height);
temp := Source.NewBitmap(Source.Width * 3, Source.Height);
temp.PutImage(1, 0, mul3, dmSet);
for yb := 0 to temp.Height - 1 do
begin
temp.SetPixel(0, yb, temp.GetPixel(1, yb));
temp.SetPixel(temp.Width - 1, yb, temp.GetPixel(temp.Width - 2, yb));
end;
mul3.Free;
mul3 := temp;
Result := Source.NewBitmap(Source.Width, Source.Height);
for yb := 0 to Result.Height - 1 do
begin
pmul3 := mul3.ScanLine[yb];
pdest := Result.ScanLine[yb];
for xb := Result.Width - 1 downto 0 do
begin
a := (pmul3 + 1)^.alpha;
if a = 0 then
pdest^ := BGRAPixelTransparent
else
begin
pdest^.alpha := a;
if (pmul3 + 2)^.alpha = 0 then
pdest^.red := 128
else
pdest^.red := (pmul3 + 2)^.red;
pdest^.green := (pmul3 + 1)^.green;
if pmul3^.alpha = 0 then
pdest^.blue := 128
else
pdest^.blue := pmul3^.blue;
end;
Inc(pdest);
Inc(pmul3, 3);
end;
end;
mul3.Free;
temp := ClearTypeRemoveContradiction(Result);
Result.Free;
Result := temp;
temp := Result;
Result := ClearTypeSharpenFilter(temp, Source);
temp.Free;
temp := ClearTypeRemoveContradiction(Result);
Result.Free;
Result := temp;
end;
function DoResample(Source: TBGRABitmap; newWidth, newHeight: integer;
StretchMode: TResampleMode): TBGRABitmap;
begin
Result := Source.Resample(newWidth, newHeight, StretchMode) as TBGRABitmap;
end;
function CreateMarbleTexture(tx, ty: integer): TBGRABitmap;
var
colorOscillation: integer;
p: PBGRAPixel;
i: integer;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 0.5, 0.5, 0.8, rfBestQuality);
p := Result.Data;
for i := 0 to Result.NbPixels - 1 do
begin
colorOscillation := round(sqrt(sqrt((sin(p^.red * Pi / 128 + 0.5) + 1) / 2)) * 256);
p^ := Interp256(BGRA(161, 117, 105), BGRA(218, 197, 180), colorOscillation);
Inc(p);
end;
end;
function CreateWaterTexture(tx, ty: integer): TBGRABitmap;
const
blurSize = 5;
var
temp: TBGRABitmap;
phong: TPhongShading;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 1.2, rfBestQuality);
temp := Result.GetPart(rect(-blurSize, -blurSize, tx + blurSize, ty + blurSize)) as
TBGRABitmap;
BGRAReplace(temp, temp.FilterBlurRadial(blurSize, rbFast));
phong := TPhongShading.Create;
phong.LightSourceDistanceFactor := 0;
phong.LightDestFactor := 0;
phong.LightSourceIntensity := 150;
phong.LightPositionZ := 80;
phong.LightColor := BGRA(105, 233, 240);
phong.NegativeDiffusionFactor := 0.3;
phong.SpecularIndex := 20;
phong.AmbientFactor := 0.4;
phong.Draw(Result, temp, 20, -blurSize, -blurSize, BGRA(28, 139, 166));
phong.Free;
temp.Free;
end;
function CreateStoneTexture(tx, ty: integer): TBGRABitmap;
var
temp: TBGRABitmap;
phong: TPhongShading;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 0.6);
temp := Result.GetPart(rect(-2, -2, tx + 2, ty + 2)) as TBGRABitmap;
phong := TPhongShading.Create;
phong.LightSourceDistanceFactor := 0;
phong.LightDestFactor := 0;
phong.LightSourceIntensity := 100;
phong.LightPositionZ := 100;
phong.NegativeDiffusionFactor := 0.3;
phong.AmbientFactor := 0.5;
phong.Draw(Result, temp, 30, -2, -2, BGRA(170, 170, 170));
phong.Free;
temp.Free;
end;
function CreateRoundStoneTexture(tx, ty: integer): TBGRABitmap;
var
temp: TBGRABitmap;
phong: TPhongShading;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 1.2, rfBestQuality);
temp := Result.GetPart(rect(-2, -2, tx + 2, ty + 2)) as TBGRABitmap;
BGRAReplace(temp, temp.FilterBlurRadial(2, rbFast));
phong := TPhongShading.Create;
phong.LightSourceDistanceFactor := 0;
phong.LightDestFactor := 0;
phong.LightSourceIntensity := 70;
phong.LightPositionZ := 100;
phong.NegativeDiffusionFactor := 0;
phong.SpecularIndex := 10;
phong.AmbientFactor := 0.5;
phong.LightColor := BGRA(255, 255, 192);
phong.Draw(Result, temp, 30, -2, -2, BGRA(170, 170, 170));
phong.Free;
temp.Free;
end;
function CreateSnowPrintTexture(tx, ty: integer): TBGRABitmap;
var
v: single;
p: PBGRAPixel;
i: integer;
temp: TBGRABitmap;
phong: TPhongShading;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 1.2, rfBestQuality);
p := Result.Data;
for i := 0 to Result.NbPixels - 1 do
begin
v := p^.red;
if v > 80 then
v := (v - 80) / 10 + 80;
if v < 50 then
v := 50 - (50 - v) / 10;
p^ := MapHeightToBGRA(v / 255, 255);
Inc(p);
end;
temp := Result.GetPart(rect(-2, -2, tx + 2, ty + 2)) as TBGRABitmap;
phong := TPhongShading.Create;
phong.LightSourceDistanceFactor := 0;
phong.LightDestFactor := 0;
phong.LightSourceIntensity := 100;
phong.LightPositionZ := 100;
phong.NegativeDiffusionFactor := 0.3;
phong.Draw(Result, temp, 30, -2, -2, BGRAWhite);
phong.Free;
temp.Free;
end;
function CreateCamouflageTexture(tx, ty: integer): TBGRABitmap;
var
v: integer;
p: PBGRAPixel;
i: integer;
temp: TBGRABitmap;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 1, rfBestQuality);
p := Result.Data;
for i := 0 to Result.NbPixels - 1 do
begin
v := p^.red;
if v < 64 then
p^ := BGRA(31, 33, 46)
else
if v < 128 then
p^ := BGRA(89, 71, 57)
else
if v < 192 then
p^ := BGRA(80, 106, 67)
else
p^ := BGRA(161, 157, 121);
Inc(p);
end;
temp := Result.getPart(rect(-2, -2, tx + 2, ty + 2)) as TBGRABitmap;
BGRAReplace(temp, temp.FilterMedian(moMediumSmooth));
Result.PutImage(-2, -2, temp, dmSet);
temp.Free;
end;
function CreatePlastikTexture(tx, ty: integer): TBGRABitmap;
const
blurSize = 2;
var
temp: TBGRABitmap;
phong: TPhongShading;
p: PBGRAPixel;
i: integer;
v: byte;
begin
Result := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 1);
p := Result.Data;
for i := 0 to Result.NbPixels - 1 do
begin
v := p^.red;
if v < 32 then
v := v * 2
else
if (v > 32) and (v < 224) then
v := (v - 32) div 2 + 64
else
if v >= 224 then
v := (v - 224) * 2 + (224 - 32) div 2;
p^ := BGRA(v, v, v);
Inc(p);
end;
temp := Result.GetPart(rect(-blurSize, -blurSize, tx + blurSize, ty + blurSize)) as
TBGRABitmap;
BGRAReplace(temp, temp.FilterNormalize(False));
BGRAReplace(temp, temp.FilterBlurMotion(ty div 6, 90, False));
BGRAReplace(temp, temp.FilterBlurRadial(blurSize, rbFast));
phong := TPhongShading.Create;
phong.LightSourceDistanceFactor := 0;
phong.LightDestFactor := 0;
phong.LightSourceIntensity := 300;
phong.LightPositionZ := 10;
phong.NegativeDiffusionFactor := 0;
phong.AmbientFactor := 0.6;
phong.SpecularIndex := 25;
phong.SpecularFactor := 10;
phong.Draw(Result, temp, 10, -blurSize, -blurSize, BGRA(58, 206, 113));
phong.Free;
temp.Free;
end;
function CreateMetalFloorTexture(tx: integer): TBGRABitmap;
var
temp, noise: TBGRABitmap;
phong: TPhongShading;
ty: integer;
begin
ty := tx div 2;
Result := TBGRABitmap.Create(tx, ty, BGRABlack);
Result.FillEllipseAntialias(tx * 1.2 / 8, ty / 2, tx / 20, ty / 3,
BGRA(240, 240, 240));
Result.FillEllipseAntialias(tx * 2.8 / 8, ty / 2, tx / 20, ty / 3,
BGRA(240, 240, 240));
Result.FillEllipseAntialias(tx * 3 / 4, ty * 1.2 / 4, ty / 3, tx /
20, BGRA(240, 240, 240));
Result.FillEllipseAntialias(tx * 3 / 4, ty * 2.8 / 4, ty / 3, tx /
20, BGRA(240, 240, 240));
BGRAReplace(Result, Result.FilterBlurRadial(1, rbFast));
noise := CreateCyclicPerlinNoiseMap(tx, ty, 1, 1, 1);
noise.FillRect(0, 0, tx, ty, BGRA(0, 0, 0, 220), dmLinearBlend);
Result.BlendImage(0, 0, noise, boAdditive);
noise.Free;
temp := Result.GetPart(rect(-2, -2, tx + 2, ty + 2)) as TBGRABitmap;
phong := TPhongShading.Create;
phong.LightSourceDistanceFactor := 0;
phong.LightDestFactor := 0;
phong.LightSourceIntensity := 100;
phong.LightPositionZ := 80;
phong.NegativeDiffusionFactor := 0;
phong.AmbientFactor := 0.5;
phong.Draw(Result, temp, 10, -2, -2, BGRA(116, 116, 116));
phong.Free;
temp.Free;
end;
function ComputeAngle(dx, dy: single): single;
begin
if dy = 0 then
begin
if dx < 0 then
Result := 180
else
Result := 0;
end
else
if dx = 0 then
begin
if dy < 0 then
Result := -90
else
Result := 90;
end
else
begin
Result := ArcTan(dy / dx) * 180 / Pi;
if dx < 0 then
Result += 180;
end;
end;
function GetSelectionCenter(bmp: TBGRABitmap): TPointF;
var
xb, yb: integer;
p: PBGRAPixel;
xsum, ysum, asum, alpha: single;
begin
xsum := 0;
ysum := 0;
asum := 0;
for yb := 0 to bmp.Height - 1 do
begin
p := bmp.ScanLine[yb];
for xb := 0 to bmp.Width - 1 do
begin
alpha := p^.red / 255;
Inc(p);
xsum += xb * alpha;
ysum += yb * alpha;
asum += alpha;
end;
end;
if asum = 0 then
Result := pointF(bmp.Width / 2 - 0.5, bmp.Height / 2 - 0.5)
else
Result := pointF(xsum / asum, ysum / asum);
end;
procedure ComputeSelectionMask(image: TBGRABitmap; destMask: TBGRABitmap);
var
maxx, maxy: integer;
aimage: byte;
xb, yb: integer;
pimage, pmask: PBGRAPixel;
begin
maxx := min(image.Width, destMask.Width) - 1;
maxy := min(image.Height, destMask.Height) - 1;
for yb := 0 to maxy do
begin
pimage := image.ScanLine[yb];
pmask := destMask.ScanLine[yb];
for xb := 0 to maxx do
begin
aimage := pimage^.alpha;
pmask^ := BGRA(aimage, aimage, aimage, 255);
if aimage <> 0 then
pimage^.alpha := 255;
Inc(pimage);
Inc(pmask);
end;
end;
end;
procedure SubstractMask(image: TBGRABitmap; mask: TBGRABitmap);
var
maxx, maxy: integer;
xb, yb: integer;
pimage, pmask: PBGRAPixel;
aimage, amask: byte;
begin
maxx := min(image.Width, Mask.Width) - 1;
maxy := min(image.Height, Mask.Height) - 1;
for yb := 0 to maxy do
begin
pimage := image.ScanLine[yb];
pmask := Mask.ScanLine[yb];
for xb := 0 to maxx do
begin
amask := pmask^.red;
if amask <> 0 then
begin
aimage := pimage^.alpha;
if aimage > amask then
pimage^.alpha := aimage - amask
else
pimage^ := BGRAPixelTransparent;
end;
Inc(pimage);
Inc(pmask);
end;
end;
end;
procedure NicePoint(bmp: TBGRABitmap; x, y: single);
begin
bmp.EllipseAntialias(x, y, 4, 4, BGRA(0, 0, 0, 192), 1);
bmp.EllipseAntialias(x, y, 3, 3, BGRA(255, 255, 255, 192), 1);
bmp.EllipseAntialias(x, y, 2, 2, BGRA(0, 0, 0, 192), 1);
end;
procedure NicePoint(bmp: TBGRABitmap; ptF: TPointF);
begin
NicePoint(bmp, ptF.x, ptF.y);
end;
procedure NiceLine(bmp: TBGRABitmap; x1, y1, x2, y2: single);
begin
bmp.DrawLineAntialias(round(x1), round(y1), round(x2),
round(y2), BGRA(0, 0, 0, 192), 3, True);
bmp.DrawLineAntialias(round(x1), round(y1), round(x2),
round(y2), BGRA(255, 255, 255, 192), 1, True);
end;
function ComputeColorCircle(tx, ty: integer; light: word;
hueCorrection: boolean = True): TBGRABitmap;
var
xb, yb: integer;
pdest: PBGRAPixel;
angle, xc, yc: single;
ec: TExpandedPixel;
c: TBGRAPixel;
gray, level: word;
begin
Result := TBGRABitmap.Create(tx, ty);
Result.FillEllipseAntialias(tx / 2 - 0.5, ty / 2 - 0.5, tx / 2, ty / 2, BGRABlack);
xc := tx / 2 - 0.5;
yc := ty / 2 - 0.5;
for yb := 0 to ty - 1 do
begin
pdest := Result.scanline[yb];
for xb := 0 to tx - 1 do
begin
if pdest^.alpha <> 0 then
begin
ec.alpha := $FFFF;
angle := ComputeAngle(xb - xc, yb - yc);
if angle < 0 then
angle += 360;
if hueCorrection then
angle := GtoH(round(angle / 360 * 65536) and 65535) / 65536 * 360;
if angle < 60 then
begin
ec.red := $FFFF;
ec.green := round(angle / 60 * $FFFF);
ec.blue := $0000;
end
else
if angle < 120 then
begin
ec.red := $FFFF - round((angle - 60) / 60 * $FFFF);
ec.green := $FFFF;
ec.blue := $0000;
end
else
if angle < 180 then
begin
ec.red := $0000;
ec.green := $FFFF;
ec.blue := round((angle - 120) / 60 * $FFFF);
end
else
if angle < 240 then
begin
ec.red := $0000;
ec.green := $FFFFF - round((angle - 180) / 60 * $FFFF);
ec.blue := $FFFF;
end
else
if angle < 300 then
begin
ec.red := round((angle - 240) / 60 * $FFFF);
ec.green := $0000;
ec.blue := $FFFF;
end
else
begin
ec.red := $FFFF;
ec.green := $0000;
ec.blue := $FFFFF - round((angle - 300) / 60 * $FFFF);
end;
gray := min($FFFF, max(0, $FFFF - round(
(sqrt(sqr((xb - xc) / (tx / 2)) + sqr((yb - yc) / (ty / 2))) *
1.2 - 0.1) * $FFFF)));
level := max(max(ec.red, ec.green), ec.blue);
{$hints off}
ec.red := (ec.red * ($FFFF - gray) + level * gray) shr 16;
ec.green := (ec.green * ($FFFF - gray) + level * gray) shr 16;
ec.blue := (ec.blue * ($FFFF - gray) + level * gray) shr 16;
{$hints on}
ec.red := (ec.red * light) shr 16;
ec.green := (ec.green * light) shr 16;
ec.blue := (ec.blue * light) shr 16;
c := GammaCompression(ec);
c.alpha := pdest^.alpha;
pdest^ := c;
end;
Inc(pdest);
end;
end;
end;
function ChangeCanvasSize(bmp: TBGRABitmap; newWidth, newHeight: integer;
anchor: string; background: TBGRAPixel; repeatImage: boolean;
flipMode: boolean = False): TBGRABitmap;
var
origin: TPoint;
xb, yb: integer;
dx, dy: integer;
minx, miny, maxx, maxy: integer;
flippedImages: array[boolean, boolean] of TBGRABitmap;
begin
if (newWidth < 1) or (newHeight < 1) then
raise Exception.Create('Invalid canvas size');
origin := Point((newWidth - bmp.Width) div 2, (newHeight - bmp.Height) div 2);
anchor := LowerCase(anchor);
if (anchor = 'topleft') or (anchor = 'top') or (anchor = 'topright') then
origin.Y := 0;
if (anchor = 'bottomleft') or (anchor = 'bottom') or (anchor = 'bottomright') then
origin.Y := newHeight - bmp.Height;
if (anchor = 'topleft') or (anchor = 'left') or (anchor = 'bottomleft') then
origin.X := 0;
if (anchor = 'topright') or (anchor = 'right') or (anchor = 'bottomright') then
origin.X := newWidth - bmp.Width;
Result := TBGRABitmap.Create(newWidth, newHeight, background);
dx := bmp.Width;
dy := bmp.Height;
if repeatImage then
begin
minx := (0 - origin.X - bmp.Width + 1) div bmp.Width;
miny := (0 - origin.Y - bmp.Width + 1) div bmp.Width;
maxx := (newWidth - origin.X + bmp.Width - 1) div bmp.Width;
maxy := (newHeight - origin.Y + bmp.Width - 1) div bmp.Width;
end
else
begin
minx := 0;
miny := 0;
maxx := 0;
maxy := 0;
end;
if flipMode and repeatImage then
begin
flippedImages[False, False] := bmp;
if (minx <> 0) or (miny <> 0) or (maxx <> 0) or (maxy <> 0) then
begin
flippedImages[True, False] := bmp.Duplicate as TBGRABitmap;
flippedImages[True, False].HorizontalFlip;
flippedImages[True, True] := flippedImages[True, False].Duplicate as TBGRABitmap;
flippedImages[True, True].VerticalFlip;
flippedImages[False, True] := bmp.Duplicate as TBGRABitmap;
flippedImages[False, True].VerticalFlip;
end
else
begin
flippedImages[True, False] := nil; //never used
flippedImages[True, True] := nil;
flippedImages[False, True] := nil;
end;
for xb := minx to maxx do
for yb := miny to maxy do
Result.PutImage(origin.x + xb * dx, origin.Y + yb * dy,
flippedImages[odd(xb), odd(yb)], dmSet);
flippedImages[True, False].Free;
flippedImages[True, True].Free;
flippedImages[False, True].Free;
end
else
begin
for xb := minx to maxx do
for yb := miny to maxy do
Result.PutImage(origin.x + xb * dx, origin.Y + yb * dy, bmp, dmSet);
end;
end;
function MakeThumbnail(bmp: TBGRABitmap; Width, Height: integer): TBGRABitmap;
var
resampled: TBGRABitmap;
begin
Result := TBGRABitmap.Create(Width, Height);
if (Width <> 0) and (Height <> 0) and (bmp.Width <> 0) and (bmp.Height <> 0) then
begin
if bmp.Width / bmp.Height > Width / Height then
resampled := bmp.Resample(Width,
max(1, round(bmp.Height * (Width / bmp.Width)))) as TBGRABitmap
else
resampled := bmp.Resample(max(1, round(bmp.Width * (Height / bmp.Height))),
Height) as TBGRABitmap;
Result.PutImage((Result.Width - resampled.Width) div 2,
(Result.Height - resampled.Height) div 2, resampled, dmSet);
resampled.Free;
end;
end;
initialization
Randomize;
end.