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.