{ Drawing engine for RGBGraphics library (C) 2014 ti_dic@hotmail.com License: modified LGPL with linking exception (like RTL, FCL and LCL) See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution, for details about the license. See also: https://wiki.lazarus.freepascal.org/FPC_modified_LGPL } unit mvDE_RGBGraphics; {$mode objfpc}{$H+} interface uses Classes, SysUtils, Types, Graphics, IntfGraphics, mvDrawingEngine, mvCache, rgbGraphics, rgbTypes; type { TRGB32BitmapCacheItem } TRGB32BitmapCacheItem = class(TPictureCacheItem) private FImage: TRGB32Bitmap; function GetImage: TRGB32Bitmap; protected function GetImageObject: TObject; override; public constructor Create(AStream: TStream); override; destructor Destroy; override; property Image: TRGB32Bitmap read GetImage; end; { TMvRGBGraphicsDrawingEngine } TMvRGBGraphicsDrawingEngine = class(TMvCustomDrawingEngine) private FBuffer: TRGB32Bitmap; FBrushStyle: TBrushStyle; FFontName: String; FFontColor: TColor; FFontSize: Integer; FFontStyle: TFontStyles; FPenStyle: TPenStyle; FPenWidth: Integer; protected function GetPenStyle: TPenStyle; override; function GetBrushColor: TColor; override; function GetBrushStyle: TBrushStyle; override; function GetFontColor: TColor; override; function GetFontName: String; override; function GetFontSize: Integer; override; function GetFontStyle: TFontStyles; override; function GetPenColor: TColor; override; function GetPenWidth: Integer; override; procedure SetPenStyle(AValue: TPenStyle); override; procedure SetBrushColor(AValue: TColor); override; procedure SetBrushStyle(AValue: TBrushStyle); override; procedure SetFontColor(AValue: TColor); override; procedure SetFontName(AValue: String); override; procedure SetFontSize(AValue: Integer); override; procedure SetFontStyle(AValue: TFontStyles); override; procedure SetPenColor(AValue: TColor); override; procedure SetPenWidth(AValue: Integer); override; procedure SetDrawMode; public destructor Destroy; override; procedure CreateBuffer(AWidth, AHeight: Integer); override; procedure DrawBitmap(X, Y: Integer; ABitmap: TCustomBitmap; UseAlphaChannel: Boolean); override; procedure DrawCacheItem(X, Y: Integer; AImg: TPictureCacheItem; ADrawMode: TItemDrawMode = idmDraw; AOpacity: Single = 1.0); override; procedure DrawScaledCacheItem(DestRect, SrcRect: TRect; ASrcImg: TPictureCacheItem); override; procedure Ellipse(X1, Y1, X2, Y2: Integer); override; procedure FillPixels(X1, Y1, X2, Y2: Integer; AColor: TColor); override; procedure FillRect(X1, Y1, X2, Y2: Integer); override; procedure Line(X1, Y1, X2, Y2: Integer); override; procedure Polyline(const Points: array of TPoint); override; procedure Polygon(const Points: array of TPoint); override; procedure PolyBezier(const Points: array of TPoint; Filled: Boolean = False; Continuous: Boolean = True); override; procedure PaintToCanvas(ACanvas: TCanvas; Origin: TPoint); override; procedure Rectangle(X1, Y1, X2, Y2: Integer); override; function SaveToImage(AClass: TRasterImageClass): TRasterImage; override; function TextExtent(const AText: String): TSize; override; procedure TextOut(X, Y: Integer; const AText: String); override; function GetCacheItemClass: TPictureCacheItemClass; override; end; procedure Register; implementation uses GraphType, LCLType, FPImage, Math, mvTypes, RGBRoutines; procedure Register; begin RegisterComponents(PALETTE_PAGE, [TMvRGBGraphicsDrawingEngine]); end; procedure AlphaBlendRGB32Pixel(Alpha: Byte; P1, P2, AResult: PRGB32Pixel); inline; var r1, g1, b1: Byte; r2, g2, b2: Byte; begin r1 := P1^; g1 := P1^ shr 8; b1 := P1^ shr 16; r2 := P2^; g2 := P2^ shr 8; b2 := P2^ shr 16; r1 := (word((255 - Alpha) * r1) + word(Alpha) * r2) shr 8; g1 := (word((255 - Alpha) * g1) + word(Alpha) * g2) shr 8; b1 := (word((255 - Alpha) * b1) + word(Alpha) * b2) shr 8; AResult^ := r1 + g1 shl 8 + b1 shl 16; end; procedure AlphaBlendImages(ABackground, ABitmap: TRGB32Bitmap; X, Y: Integer; AAlpha: Byte); var BkgX, BkgY, XMax, YMax, I, J, ImgX, ImgY: Integer; P1, P2: PRGB32Pixel; alpha: PRGB8Pixel; begin BkgX := Max(0, X); BkgY := Max(0, Y); ImgX := Max(0, -X); ImgY := Max(0, -Y); XMax := Min(ABitmap.Width - ImgX, ABackground.Width - BkgX) - 1; YMax := Min(ABitmap.Height - ImgY, ABackground.Height - BkgY) - 1; for J := YMax downto 0 do begin P1 := ABackground.Get32PixelPtr(BkgX, BkgY + J); P2 := ABitmap.Get32PixelPtr(ImgX, ImgY + J); if AAlpha > 0 then for I := 0 to XMax do begin AlphaBlendRGB32Pixel(AAlpha, P1, P2, P1); Inc(P1); Inc(P2); end else begin alpha := ABitmap.Mask.Get8PixelPtr(ImgX, ImgY + J); for I := 0 to XMax do begin AlphaBlendRGB32Pixel(alpha^, P1, P2, P1); Inc(P1); Inc(P2); Inc(alpha); end; end; end; end; { TRGB32BitmapCacheItem } function TRGB32BitmapCacheItem.GetImage: TRGB32Bitmap; begin Result := FImage; end; function TRGB32BitmapCacheItem.GetImageObject: TObject; begin Result := FImage; end; constructor TRGB32BitmapCacheItem.Create(AStream: TStream); var Reader: TFPCustomImageReader; begin FImage := Nil; Reader := GetImageReader(AStream); if not Assigned(Reader) then raise EInvalidGraphic.Create('PNG/JPG expected.'); try try FImage := TRGB32Bitmap.CreateFromStream(AStream, Reader); except FreeAndNil(FImage); end; finally FreeAndNil(Reader); end; end; destructor TRGB32BitmapCacheItem.Destroy; begin FImage.Free; inherited Destroy; end; destructor TMvRGBGraphicsDrawingEngine.Destroy; begin FBuffer.Free; inherited; end; procedure TMvRGBGraphicsDrawingEngine.CreateBuffer(AWidth, AHeight: Integer); begin FreeAndNil(FBuffer); FBuffer := TRGB32Bitmap.Create(AWidth, AHeight); end; procedure TMvRGBGraphicsDrawingEngine.DrawBitmap(X,Y: Integer; ABitmap: TCustomBitmap; UseAlphaChannel: Boolean); var intfImg: TLazIntfImage; i, j: Integer; cimg, cbuf: TFPColor; alpha: Double; begin intfImg := ABitmap.CreateIntfImage; try if UseAlphaChannel then begin for j := 0 to intfImg.Height - 1 do for i := 0 to intfImg.Width - 1 do begin cimg := intfImg.Colors[i, j]; alpha := cimg.Alpha / word($FFFF); cbuf := TColorToFPColor(FBuffer.Canvas.GetColor(i + X, j + Y)); cbuf.Red := Round(alpha * cimg.Red + (1 - alpha) * cbuf.Red); cbuf.Green := Round(alpha * cimg.Green + (1 - alpha) * cbuf.Green); cbuf.Blue := Round(alpha * cimg.Blue + (1 - alpha) * cbuf.Blue); FBuffer.Canvas.SetColor(i + X, j + Y, FPColorToTColor(cbuf)); end; end else for j := 0 to intfImg.Height - 1 do for i := 0 to intfImg.Width - 1 do FBuffer.Canvas.SetColor(i + X, j + Y, FPColorToTColor(intfImg.Colors[i, j])); finally intfimg.Free; end; end; procedure TMvRGBGraphicsDrawingEngine.DrawCacheItem(X, Y: Integer; AImg: TPictureCacheItem; ADrawMode: TItemDrawMode; AOpacity: Single); var Item: TRGB32BitmapCacheItem; Alpha: Byte; begin Item := (AImg as TRGB32BitmapCacheItem); if ADrawMode = idmDraw then FBuffer.Draw(X, Y, Item.Image) else begin if ADrawMode = idmUseOpacity then Alpha := Round(AOpacity * 255) else Alpha := 0; AlphaBlendImages(FBuffer, Item.Image, X, Y, Alpha); end; end; procedure TMvRGBGraphicsDrawingEngine.DrawScaledCacheItem(DestRect, SrcRect: TRect; ASrcImg: TPictureCacheItem); var srcBmp, SrcImg: TRGB32Bitmap; y, w, ww, h: Integer; begin w := SrcRect.Right - SrcRect.Left; h := SrcRect.Bottom - SrcRect.Top; SrcImg := (ASrcImg as TRGB32BitmapCacheItem).Image; srcBmp := TRGB32Bitmap.Create(w, h); try ww := w * SizeOf(TRGB32Pixel); for y := 0 to h-1 do Move(SrcImg.Get32PixelPtr(SrcRect.Left, SrcRect.Top + y)^, srcBmp.Get32PixelPtr(0, y)^, ww); srcBmp.StretchTrunc(DestRect.Right - DestRect.Left, DestRect.Bottom - DestRect.Top); FBuffer.Draw(DestRect.Left, DestRect.Top, srcBmp); finally srcBmp.Free; end; end; procedure TMvRGBGraphicsDrawingEngine.Ellipse(X1, Y1, X2, Y2: Integer); begin FBuffer.Canvas.Ellipse(X1, Y1, X2, Y2); end; procedure TMvRGBGraphicsDrawingEngine.FillPixels(X1, Y1, X2, Y2: Integer; AColor: TColor); var x, y: Integer; c: TRGB32Pixel; begin if (X1 >= FBuffer.Width) or (X2 < 0) or (Y1 >= FBuffer.Height) or (Y2 < 0) then exit; if X1 < 0 then X1 := 0; if Y1 < 0 then Y1 := 0; if X2 >= FBuffer.Width then X2 := FBuffer.Width - 1; if Y2 >= FBuffer.Height then Y2 := FBuffer.Height - 1; c := ColorToRGB32Pixel(AColor); for y := Y1 to Y2 do for x := X1 to X2 do FBuffer.Set32Pixel(x, y, c); end; procedure TMvRGBGraphicsDrawingEngine.FillRect(X1, Y1, X2, Y2: Integer); begin FBuffer.Canvas.FillRect(X1, Y1, X2, Y2); end; function TMvRGBGraphicsDrawingEngine.GetPenStyle: TPenStyle; begin Result := FPenStyle; end; function TMvRGBGraphicsDrawingEngine.GetBrushColor: TColor; begin Result := FBuffer.Canvas.FillColor; end; function TMvRGBGraphicsDrawingEngine.GetBrushStyle: TBrushStyle; begin Result := FBrushStyle; end; function TMvRGBGraphicsDrawingEngine.GetFontColor: TColor; begin Result := FFontColor; end; function TMvRGBGraphicsDrawingEngine.GetFontName: String; begin Result := FFontName; end; function TMvRGBGraphicsDrawingEngine.GetFontSize: Integer; begin Result := FFontSize; end; function TMvRGBGraphicsDrawingEngine.GetFontStyle: TFontStyles; begin Result := FFontStyle; end; function TMvRGBGraphicsDrawingEngine.GetPenColor: TColor; begin Result := FBuffer.Canvas.OutlineColor; end; function TMvRGBGraphicsDrawingEngine.GetPenWidth: Integer; begin Result := FPenWidth; end; procedure TMvRGBGraphicsDrawingEngine.SetPenStyle(AValue: TPenStyle); begin FPenStyle := AValue; SetDrawMode; end; procedure TMvRGBGraphicsDrawingEngine.Line(X1, Y1, X2, Y2: Integer); var MY, MX: Double; PWX, PWY: Integer; begin if FPenWidth = 1 then FBuffer.Canvas.Line(X1, Y1, X2, Y2) else begin if not OrthoVec(X1, Y1, X2, Y2, MX, MY) then Exit; PWX := Trunc(FPenWidth * MX * 0.5); PWY := Trunc(FPenWidth * MY * 0.5); DoScanFill([ Point(X1 - PWX, Y1 - PWY), Point(X1 + PWX, Y1 + PWY), Point(X2 + PWX, Y2 + PWY), Point(X2 - PWX, Y2 - PWY) ], @FBuffer.Canvas.Line); end; end; procedure TMvRGBGraphicsDrawingEngine.Polyline(const Points: array of TPoint); var I: Integer; begin for I := 1 to High(Points) do Line(Points[I - 1].X, Points[I - 1].Y, Points[I].X, Points[I].Y); end; procedure TMvRGBGraphicsDrawingEngine.Polygon(const Points: array of TPoint); var OldColor: TColor; OldWidth: Integer; OldStyle: TPenStyle; begin if BrushStyle <> bsClear then begin OldColor := PenColor; OldWidth := PenWidth; OldStyle := PenStyle; try PenColor := BrushColor; PenWidth := 1; PenStyle := psSolid; DoScanFill(Points, @FBuffer.Canvas.Line); finally PenColor := OldColor; PenWidth := OldWidth; PenStyle := OldStyle; end; end; if PenStyle <> psClear then begin Polyline(Points); if Length(Points) > 1 then Line(Points[High(Points)].X, Points[High(Points)].Y, Points[0].X, Points[0].Y); end; end; procedure TMvRGBGraphicsDrawingEngine.PolyBezier(const Points: array of TPoint; Filled: Boolean; Continuous: Boolean); var PtDyn: array of TPoint; begin CalcBezier(Points, Continuous, PtDyn); if Filled then Polygon(PtDyn) else Polyline(PtDyn); end; procedure TMvRGBGraphicsDrawingEngine.PaintToCanvas(ACanvas: TCanvas; Origin: TPoint); begin FBuffer.Canvas.DrawTo(ACanvas, Origin.X, Origin.Y); end; procedure TMvRGBGraphicsDrawingEngine.Rectangle(X1, Y1, X2, Y2: Integer); begin FBuffer.Canvas.Rectangle(X1, Y1, X2, Y2); end; function TMvRGBGraphicsDrawingEngine.SaveToImage(AClass: TRasterImageClass): TRasterImage; begin Result := AClass.Create; Result.Width := FBuffer.Width; Result.Height := FBuffer.Height; Result.Canvas.FillRect(0, 0, FBuffer.Width, FBuffer.Height); FBuffer.Canvas.DrawTo(Result.Canvas, 0, 0); end; procedure TMvRGBGraphicsDrawingEngine.SetBrushColor(AValue: TColor); begin FBuffer.Canvas.FillColor := AValue; end; procedure TMvRGBGraphicsDrawingEngine.SetBrushStyle(AValue: TBrushStyle); begin FBrushStyle := AValue; SetDrawMode; // No direct brush style support in RGB32Bitmap end; procedure TMvRGBGraphicsDrawingEngine.SetFontColor(AValue: TColor); begin FFontColor := AValue; end; procedure TMvRGBGraphicsDrawingEngine.SetFontName(AValue: String); begin FFontName := AValue; end; procedure TMvRGBGraphicsDrawingEngine.SetFontSize(AValue: Integer); begin FFontSize := AValue; end; procedure TMvRGBGraphicsDrawingEngine.SetFontStyle(AValue: TFontStyles); begin FFontStyle := AValue; end; procedure TMvRGBGraphicsDrawingEngine.SetPenColor(AValue: TColor); begin FBuffer.Canvas.OutlineColor := AValue; end; procedure TMvRGBGraphicsDrawingEngine.SetPenWidth(AValue: Integer); begin FPenWidth := AValue; end; procedure TMvRGBGraphicsDrawingEngine.SetDrawMode; var B, P: Boolean; begin B := FBrushStyle <> bsClear; P := FPenStyle <> psClear; if P and B then FBuffer.Canvas.DrawMode := dmFillAndOutline else if B then FBuffer.Canvas.DrawMode := dmFill else // if P then FBuffer.Canvas.DrawMode := dmOutline; end; function TMvRGBGraphicsDrawingEngine.TextExtent(const AText: String): TSize; var bmp: TBitmap; begin bmp := TBitmap.Create; try bmp.SetSize(1, 1); bmp.Canvas.Font.Name := FFontName; bmp.Canvas.Font.Size := FFontSize; bmp.Canvas.Font.Style := FFontStyle; Result := bmp.Canvas.TextExtent(AText); finally bmp.Free; end; end; (* procedure TMvRGBGraphicsDrawingEngine.TextOut(X, Y: Integer; const AText: String); var bmp: TBitmap; ex: TSize; img: TLazIntfImage; brClr: TFPColor; imgClr: TFPColor; i, j: Integer; begin if (AText = '') then exit; bmp := TBitmap.Create; try bmp.PixelFormat := pf32Bit; bmp.SetSize(1, 1); bmp.Canvas.Font.Name := FFontName; bmp.Canvas.Font.Size := FFontSize; bmp.Canvas.Font.Style := FFontStyle; bmp.Canvas.Font.Color := FFontColor; ex := bmp.Canvas.TextExtent(AText); bmp.SetSize(ex.CX, ex.CY); bmp.Canvas.Brush.Color := GetBrushColor; if GetBrushStyle = bsClear then bmp.Canvas.Brush.Style := bsSolid else bmp.Canvas.Brush.Style := GetBrushStyle; bmp.Canvas.FillRect(0, 0, bmp.Width, bmp.Height); bmp.Canvas.TextOut(0, 0, AText); img := bmp.CreateIntfImage; try if GetBrushStyle = bsClear then begin brClr := TColorToFPColor(GetBrushColor); for j := 0 to img.Height - 1 do for i := 0 to img.Width - 1 do begin imgClr := img.Colors[i, j]; if (imgClr.Red = brClr.Red) and (imgClr.Green = brClr.Green) and (imgClr.Blue = brClr.Blue) then Continue; FBuffer.Canvas.SetColor(X + i, Y + j, FPColorToTColor(imgClr)); end; end else for j := 0 to img.Height - 1 do for i := 0 to img.Width - 1 do FBuffer.Canvas.SetColor(X + i, Y + j, FPColorToTColor(img.Colors[i, j])); finally img.Free; end; finally bmp.Free; end; end; *) procedure TMvRGBGraphicsDrawingEngine.TextOut(X, Y: Integer; const AText: String); var bmp: TBitmap; ex: TSize; begin if (AText = '') then exit; bmp := TBitmap.Create; try bmp.PixelFormat := pf32Bit; bmp.SetSize(1, 1); bmp.Canvas.Font.Name := FFontName; bmp.Canvas.Font.Size := FFontSize; bmp.Canvas.Font.Style := FFontStyle; bmp.Canvas.Font.Color := FFontColor; ex := bmp.Canvas.TextExtent(AText); bmp.SetSize(ex.CX, ex.CY); if GetBrushStyle <> bsClear then begin bmp.Canvas.Brush.Color := GetBrushColor; bmp.Canvas.FillRect(0, 0, bmp.Width, bmp.Height); bmp.Canvas.TextOut(0, 0, AText); DrawBitmap(X, Y, bmp, false); end else begin if FFontColor = clWhite then bmp.Canvas.Brush.Color := clBlack else bmp.Canvas.Brush.Color := clWhite; bmp.Canvas.FillRect(0, 0, bmp.Width, bmp.Height); bmp.Canvas.TextOut(0, 0, AText); DrawBitmapOT(X, Y, bmp, FFontColor, bmp.Canvas.Brush.Color); end; finally bmp.Free; end; end; function TMvRGBGraphicsDrawingEngine.GetCacheItemClass: TPictureCacheItemClass; begin Result := TRGB32BitmapCacheItem; end; end.