{ $Id: ImagingCanvases.pas 103 2007-09-15 01:11:14Z galfar $ Vampyre Imaging Library by Marek Mauder http://imaginglib.sourceforge.net The contents of this file are used with permission, subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU Lesser General Public License (the "LGPL License"), in which case the provisions of the LGPL License are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the LGPL License and not to allow others to use your version of this file under the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the LGPL License. If you do not delete the provisions above, a recipient may use your version of this file under either the MPL or the LGPL License. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html } { This unit contains canvas classes for drawing and applying effects. } unit ImagingCanvases; {$I ImagingOptions.inc} interface uses SysUtils, Types, Classes, ImagingTypes, Imaging, ImagingClasses, ImagingFormats, ImagingUtility; const { Color constants in ifA8R8G8B8 format.} pcClear = $00000000; pcBlack = $FF000000; pcWhite = $FFFFFFFF; pcMaroon = $FF800000; pcGreen = $FF008000; pcOlive = $FF808000; pcNavy = $FF000080; pcPurple = $FF800080; pcTeal = $FF008080; pcGray = $FF808080; pcSilver = $FFC0C0C0; pcRed = $FFFF0000; pcLime = $FF00FF00; pcYellow = $FFFFFF00; pcBlue = $FF0000FF; pcFuchsia = $FFFF00FF; pcAqua = $FF00FFFF; pcLtGray = $FFC0C0C0; pcDkGray = $FF808080; MaxPenWidth = 256; type EImagingCanvasError = class(EImagingError); { Fill mode used when drawing filled objects on canvas.} TFillMode = ( fmSolid, // Solid fill using current fill color fmClear // No filling done ); { Pen mode used when drawing lines, object outlines, and similar on canvas.} TPenMode = ( pmSolid, // Draws solid lines using current pen color. pmClear // No drawing done ); { Represents 3x3 convolution filter kernel.} TConvolutionFilter3x3 = record Kernel: array[0..2, 0..2] of LongInt; Divisor: LongInt; Bias: Single; end; { Represents 5x5 convolution filter kernel.} TConvolutionFilter5x5 = record Kernel: array[0..4, 0..4] of LongInt; Divisor: LongInt; Bias: Single; end; { Base canvas class for drawing objects, applying effects, and other. Constructor takes TBaseImage (or pointer to TImageData). Source image bits are not copied but referenced so all canvas functions affect source image and vice versa. When you change format or resolution of source image you must call UpdateCanvasState method (so canvas could recompute some data size related stuff). TImagingCanvas works for all image data formats except special ones (compressed). Because of this its methods are quite slow (they work with colors in ifA32R32G32B32F format). If you want fast drawing you can use one of fast canvas clases. These descendants of TImagingCanvas work only for few select formats (or only one) but they are optimized thus much faster. -- Canvas in this Imaging version (0.20) is very basic and its purpose is to act like sort of a preview of things to come. Update 0.22: Some new stuff added but not much yet. } TImagingCanvas = class(TObject) private FDataSizeOnUpdate: LongInt; FLineRecursion: Boolean; function GetPixel32(X, Y: LongInt): TColor32; virtual; function GetPixelFP(X, Y: LongInt): TColorFPRec; virtual; function GetValid: Boolean; {$IFDEF USE_INLINE}inline;{$ENDIF} procedure SetPixel32(X, Y: LongInt; const Value: TColor32); virtual; procedure SetPixelFP(X, Y: LongInt; const Value: TColorFPRec); virtual; procedure SetPenColor32(const Value: TColor32); {$IFDEF USE_INLINE}inline;{$ENDIF} procedure SetPenColorFP(const Value: TColorFPRec); {$IFDEF USE_INLINE}inline;{$ENDIF} procedure SetPenWidth(const Value: LongInt); {$IFDEF USE_INLINE}inline;{$ENDIF} procedure SetFillColor32(const Value: TColor32); {$IFDEF USE_INLINE}inline;{$ENDIF} procedure SetFillColorFP(const Value: TColorFPRec); {$IFDEF USE_INLINE}inline;{$ENDIF} procedure SetClipRect(const Value: TRect); protected FPData: PImageData; FClipRect: TRect; FPenColorFP: TColorFPRec; FPenColor32: TColor32; FPenMode: TPenMode; FPenWidth: LongInt; FFillColorFP: TColorFPRec; FFillColor32: TColor32; FFillMode: TFillMode; FNativeColor: TColorFPRec; FFormatInfo: TImageFormatInfo; { Returns pointer to pixel at given position.} function GetPixelPointer(X, Y: LongInt): Pointer; {$IFDEF USE_INLINE}inline;{$ENDIF} { Translates given FP color to native format of canvas and stores it in FNativeColor field (its bit copy) or user pointer (in overloaded method).} procedure TranslateFPToNative(const Color: TColorFPRec); overload; {$IFDEF USE_INLINE}inline;{$ENDIF} procedure TranslateFPToNative(const Color: TColorFPRec; Native: Pointer); overload; {$IFDEF USE_INLINE}inline;{$ENDIF} { Clipping function used by horizontal and vertical line drawing functions.} function ClipAxisParallelLine(var A1, A2, B: LongInt; AStart, AStop, BStart, BStop: LongInt): Boolean; { Internal horizontal line drawer used mainly for filling inside of objects like ellipses and circles.} procedure HorzLineInternal(X1, X2, Y: LongInt; Color: Pointer; Bpp: LongInt); virtual; procedure CopyPixelInternal(X, Y: LongInt; Pixel: Pointer; Bpp: LongInt); {$IFDEF USE_INLINE}inline;{$ENDIF} public constructor CreateForData(ImageDataPointer: PImageData); constructor CreateForImage(Image: TBaseImage); destructor Destroy; override; { Call this method when you change size or format of image this canvas operates on (like calling ResizeImage, ConvertImage, or changing Format property of TBaseImage descendants).} procedure UpdateCanvasState; virtual; { Resets clipping rectangle to Rect(0, 0, ImageWidth, ImageHeight).} procedure ResetClipRect; { Clears entire canvas with current fill color (ignores clipping rectangle and always uses fmSolid fill mode).} procedure Clear; { Draws horizontal line with current pen settings.} procedure HorzLine(X1, X2, Y: LongInt); virtual; { Draws vertical line with current pen settings.} procedure VertLine(X, Y1, Y2: LongInt); virtual; { Draws line from [X1, Y1] to [X2, Y2] with current pen settings.} procedure Line(X1, Y1, X2, Y2: LongInt); virtual; { Draws a rectangle using current pen settings.} procedure FrameRect(const Rect: TRect); { Fills given rectangle with current fill settings.} procedure FillRect(const Rect: TRect); virtual; { Draws rectangle which is outlined by using the current pen settings and filled by using the current fill settings.} procedure Rectangle(const Rect: TRect); { Draws ellipse which is outlined by using the current pen settings and filled by using the current fill settings. Rect specifies bounding rectangle of ellipse to be drawn.} procedure Ellipse(const Rect: TRect); { Convolves canvas' image with given 3x3 filter kernel. You can use predefined filter kernels or define your own.} procedure ApplyConvolution3x3(const Filter: TConvolutionFilter3x3); { Convolves canvas' image with given 5x5 filter kernel. You can use predefined filter kernels or define your own.} procedure ApplyConvolution5x5(const Filter: TConvolutionFilter5x5); { Computes 2D convolution of canvas' image and given filter kernel. Kernel is in row format and KernelSize must be odd number >= 3. Divisor is normalizing value based on Kernel (usually sum of all kernel's cells). The Bias number shifts each color value by a fixed amount (color values are usually in range [0, 1] during processing). If ClampChannels is True all output color values are clamped to [0, 1]. You can use predefined filter kernels or define your own.} procedure ApplyConvolution(Kernel: PLongInt; KernelSize, Divisor: LongInt; Bias: Single = 0.0; ClampChannels: Boolean = True); virtual; { Color used when drawing lines, frames, and outlines of objects.} property PenColor32: TColor32 read FPenColor32 write SetPenColor32; { Color used when drawing lines, frames, and outlines of objects.} property PenColorFP: TColorFPRec read FPenColorFP write SetPenColorFP; { Pen mode used when drawing lines, object outlines, and similar on canvas.} property PenMode: TPenMode read FPenMode write FPenMode; { Width with which objects like lines, frames, etc. (everything which uses PenColor) are drawn.} property PenWidth: LongInt read FPenWidth write SetPenWidth; { Color used for filling when drawing various objects.} property FillColor32: TColor32 read FFillColor32 write SetFillColor32; { Color used for filling when drawing various objects.} property FillColorFP: TColorFPRec read FFillColorFP write SetFillColorFP; { Fill mode used when drawing filled objects on canvas.} property FillMode: TFillMode read FFillMode write FFillMode; { Specifies the current color of the pixels of canvas. Native pixel is read from canvas and then translated to 32bit ARGB. Reverse operation is made when setting pixel color.} property Pixels32[X, Y: LongInt]: TColor32 read GetPixel32 write SetPixel32; { Specifies the current color of the pixels of canvas. Native pixel is read from canvas and then translated to FP ARGB. Reverse operation is made when setting pixel color.} property PixelsFP[X, Y: LongInt]: TColorFPRec read GetPixelFP write SetPixelFP; { Clipping rectangle of this canvas. No pixels outside this rectangle are altered by canvas methods if Clipping property is True. Clip rect gets reseted when UpdateCanvasState is called.} property ClipRect: TRect read FClipRect write SetClipRect; { Extended format information.} property FormatInfo: TImageFormatInfo read FFormatInfo; { Indicates that this canvas is in valid state. If False canvas oprations may crash.} property Valid: Boolean read GetValid; { Returns all formats supported by this canvas class.} class function GetSupportedFormats: TImageFormats; virtual; end; TImagingCanvasClass = class of TImagingCanvas; TScanlineArray = array[0..MaxInt div SizeOf(Pointer) - 1] of PColor32RecArray; PScanlineArray = ^TScanlineArray; { Fast canvas class for ifA8R8G8B8 format images.} TFastARGB32Canvas = class(TImagingCanvas) protected FScanlines: PScanlineArray; function GetPixel32(X, Y: LongInt): TColor32; override; procedure SetPixel32(X, Y: LongInt; const Value: TColor32); override; public destructor Destroy; override; procedure UpdateCanvasState; override; property Scanlines: PScanlineArray read FScanlines; class function GetSupportedFormats: TImageFormats; override; end; const { Kernel for 3x3 average smoothing filter.} FilterAverage3x3: TConvolutionFilter3x3 = ( Kernel: ((1, 1, 1), (1, 1, 1), (1, 1, 1)); Divisor: 9); { Kernel for 5x5 average smoothing filter.} FilterAverage5x5: TConvolutionFilter5x5 = ( Kernel: ((1, 1, 1, 1, 1), (1, 1, 1, 1, 1), (1, 1, 1, 1, 1), (1, 1, 1, 1, 1), (1, 1, 1, 1, 1)); Divisor: 25); { Kernel for 3x3 Gaussian smoothing filter.} FilterGaussian3x3: TConvolutionFilter3x3 = ( Kernel: ((1, 2, 1), (2, 4, 2), (1, 2, 1)); Divisor: 16); { Kernel for 5x5 Gaussian smoothing filter.} FilterGaussian5x5: TConvolutionFilter5x5 = ( Kernel: ((1, 4, 6, 4, 1), (4, 16, 24, 16, 4), (6, 24, 36, 24, 6), (4, 16, 24, 16, 4), (1, 4, 6, 4, 1)); Divisor: 256); { Kernel for 3x3 Sobel horizontal edge detection filter (1st derivative approximation).} FilterSobelHorz3x3: TConvolutionFilter3x3 = ( Kernel: (( 1, 2, 1), ( 0, 0, 0), (-1, -2, -1)); Divisor: 1); { Kernel for 3x3 Sobel vertical edge detection filter (1st derivative approximation).} FilterSobelVert3x3: TConvolutionFilter3x3 = ( Kernel: ((-1, 0, 1), (-2, 0, 2), (-1, 0, 1)); Divisor: 1); { Kernel for 3x3 Prewitt horizontal edge detection filter.} FilterPrewittHorz3x3: TConvolutionFilter3x3 = ( Kernel: (( 1, 1, 1), ( 0, 0, 0), (-1, -1, -1)); Divisor: 1); { Kernel for 3x3 Prewitt vertical edge detection filter.} FilterPrewittVert3x3: TConvolutionFilter3x3 = ( Kernel: ((-1, 0, 1), (-1, 0, 1), (-1, 0, 1)); Divisor: 1); { Kernel for 3x3 Kirsh horizontal edge detection filter.} FilterKirshHorz3x3: TConvolutionFilter3x3 = ( Kernel: (( 5, 5, 5), (-3, 0, -3), (-3, -3, -3)); Divisor: 1); { Kernel for 3x3 Kirsh vertical edge detection filter.} FilterKirshVert3x3: TConvolutionFilter3x3 = ( Kernel: ((5, -3, -3), (5, 0, -3), (5, -3, -3)); Divisor: 1); { Kernel for 3x3 Laplace omni-directional edge detection filter (2nd derivative approximation).} FilterLaplace3x3: TConvolutionFilter3x3 = ( Kernel: ((-1, -1, -1), (-1, 8, -1), (-1, -1, -1)); Divisor: 1); { Kernel for 5x5 Laplace omni-directional edge detection filter (2nd derivative approximation).} FilterLaplace5x5: TConvolutionFilter5x5 = ( Kernel: ((-1, -1, -1, -1, -1), (-1, -1, -1, -1, -1), (-1, -1, 24, -1, -1), (-1, -1, -1, -1, -1), (-1, -1, -1, -1, -1)); Divisor: 1); { Kernel for 3x3 spharpening filter (Laplacian + original color).} FilterSharpen3x3: TConvolutionFilter3x3 = ( Kernel: ((-1, -1, -1), (-1, 9, -1), (-1, -1, -1)); Divisor: 1); { Kernel for 5x5 spharpening filter (Laplacian + original color).} FilterSharpen5x5: TConvolutionFilter5x5 = ( Kernel: ((-1, -1, -1, -1, -1), (-1, -1, -1, -1, -1), (-1, -1, 25, -1, -1), (-1, -1, -1, -1, -1), (-1, -1, -1, -1, -1)); Divisor: 1); { Kernel for 5x5 glow filter.} FilterGlow5x5: TConvolutionFilter5x5 = ( Kernel: (( 1, 2, 2, 2, 1), ( 2, 0, 0, 0, 2), ( 2, 0, -20, 0, 2), ( 2, 0, 0, 0, 2), ( 1, 2, 2, 2, 1)); Divisor: 8); { Kernel for 3x3 edge enhancement filter.} FilterEdgeEnhance3x3: TConvolutionFilter3x3 = ( Kernel: ((-1, -2, -1), (-2, 16, -2), (-1, -2, -1)); Divisor: 4); FilterTraceControur3x3: TConvolutionFilter3x3 = ( Kernel: ((-6, -6, -2), (-1, 32, -1), (-6, -2, -6)); Divisor: 4; Bias: 240/255); { Kernel for filter that negates all images pixels.} FilterNegative3x3: TConvolutionFilter3x3 = ( Kernel: ((0, 0, 0), (0, -1, 0), (0, 0, 0)); Divisor: 1; Bias: 1); { Kernel for 3x3 horz/vert embossing filter.} FilterEmboss3x3: TConvolutionFilter3x3 = ( Kernel: ((2, 0, 0), (0, -1, 0), (0, 0, -1)); Divisor: 1; Bias: 0.5); { You can register your own canvas class. List of registered canvases is used by FindBestCanvasForImage functions to find best canvas for given image. If two different canvases which support the same image data format are registered then the one that was registered later is returned (so you can override builtin Imaging canvases).} procedure RegisterCanvas(CanvasClass: TImagingCanvasClass); { Returns best canvas for given TImageFormat.} function FindBestCanvasForImage(ImageFormat: TImageFormat): TImagingCanvasClass; overload; { Returns best canvas for given TImageData.} function FindBestCanvasForImage(const ImageData: TImageData): TImagingCanvasClass; overload; { Returns best canvas for given TBaseImage.} function FindBestCanvasForImage(Image: TBaseImage): TImagingCanvasClass; overload; implementation resourcestring SConstructorInvalidPointer = 'Invalid pointer (%p) to TImageData passed to TImagingCanvas constructor.'; SConstructorInvalidImage = 'Invalid image data passed to TImagingCanvas constructor (%s).'; SConstructorUnsupportedFormat = 'Image passed to TImagingCanvas constructor is in unsupported format (%s)'; var // list with all registered TImagingCanvas classes CanvasClasses: TList = nil; procedure RegisterCanvas(CanvasClass: TImagingCanvasClass); begin Assert(CanvasClass <> nil); if CanvasClasses = nil then CanvasClasses := TList.Create; if CanvasClasses.IndexOf(CanvasClass) < 0 then CanvasClasses.Add(CanvasClass); end; function FindBestCanvasForImage(ImageFormat: TImageFormat): TImagingCanvasClass; overload; var I: LongInt; begin for I := CanvasClasses.Count - 1 downto 0 do begin if ImageFormat in TImagingCanvasClass(CanvasClasses[I]).GetSupportedFormats then begin Result := TImagingCanvasClass(CanvasClasses[I]); Exit; end; end; Result := TImagingCanvas; end; function FindBestCanvasForImage(const ImageData: TImageData): TImagingCanvasClass; begin Result := FindBestCanvasForImage(ImageData.Format); end; function FindBestCanvasForImage(Image: TBaseImage): TImagingCanvasClass; begin Result := FindBestCanvasForImage(Image.Format); end; { TImagingCanvas } constructor TImagingCanvas.CreateForData(ImageDataPointer: PImageData); begin if ImageDataPointer = nil then raise EImagingCanvasError.CreateFmt(SConstructorInvalidPointer, [ImageDataPointer]); if not TestImage(ImageDataPointer^) then raise EImagingCanvasError.CreateFmt(SConstructorInvalidImage, [Imaging.ImageToStr(ImageDataPointer^)]); if not (ImageDataPointer.Format in GetSupportedFormats) then raise EImagingCanvasError.CreateFmt(SConstructorUnsupportedFormat, [Imaging.ImageToStr(ImageDataPointer^)]); FPData := ImageDataPointer; FPenWidth := 1; SetPenColor32(pcWhite); SetFillColor32(pcBlack); FFillMode := fmSolid; UpdateCanvasState; end; constructor TImagingCanvas.CreateForImage(Image: TBaseImage); begin CreateForData(Image.ImageDataPointer); end; destructor TImagingCanvas.Destroy; begin inherited Destroy; end; function TImagingCanvas.GetPixel32(X, Y: LongInt): TColor32; begin Result := Imaging.GetPixel32(FPData^, X, Y).Color; end; function TImagingCanvas.GetPixelFP(X, Y: LongInt): TColorFPRec; begin Result := Imaging.GetPixelFP(FPData^, X, Y); end; function TImagingCanvas.GetValid: Boolean; begin Result := (FPData <> nil) and (FDataSizeOnUpdate = FPData.Size); end; procedure TImagingCanvas.SetPixel32(X, Y: LongInt; const Value: TColor32); begin if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and (X < FClipRect.Right) and (Y < FClipRect.Bottom) then begin Imaging.SetPixel32(FPData^, X, Y, TColor32Rec(Value)); end; end; procedure TImagingCanvas.SetPixelFP(X, Y: LongInt; const Value: TColorFPRec); begin if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and (X < FClipRect.Right) and (Y < FClipRect.Bottom) then begin Imaging.SetPixelFP(FPData^, X, Y, TColorFPRec(Value)); end; end; procedure TImagingCanvas.SetPenColor32(const Value: TColor32); begin FPenColor32 := Value; TranslatePixel(@FPenColor32, @FPenColorFP, ifA8R8G8B8, ifA32R32G32B32F, nil, nil); end; procedure TImagingCanvas.SetPenColorFP(const Value: TColorFPRec); begin FPenColorFP := Value; TranslatePixel(@FPenColorFP, @FPenColor32, ifA32R32G32B32F, ifA8R8G8B8, nil, nil); end; procedure TImagingCanvas.SetPenWidth(const Value: LongInt); begin FPenWidth := ClampInt(Value, 0, MaxPenWidth); end; procedure TImagingCanvas.SetFillColor32(const Value: TColor32); begin FFillColor32 := Value; TranslatePixel(@FFillColor32, @FFillColorFP, ifA8R8G8B8, ifA32R32G32B32F, nil, nil); end; procedure TImagingCanvas.SetFillColorFP(const Value: TColorFPRec); begin FFillColorFP := Value; TranslatePixel(@FFillColorFP, @FFillColor32, ifA32R32G32B32F, ifA8R8G8B8, nil, nil); end; procedure TImagingCanvas.SetClipRect(const Value: TRect); begin FClipRect := Value; SwapMin(FClipRect.Left, FClipRect.Right); SwapMin(FClipRect.Top, FClipRect.Bottom); IntersectRect(FClipRect, FClipRect, Rect(0, 0, FPData.Width, FPData.Height)); end; function TImagingCanvas.GetPixelPointer(X, Y: LongInt): Pointer; begin Result := @PByteArray(FPData.Bits)[(Y * FPData.Width + X) * FFormatInfo.BytesPerPixel] end; procedure TImagingCanvas.TranslateFPToNative(const Color: TColorFPRec); begin TranslateFPToNative(Color, @FNativeColor); end; procedure TImagingCanvas.TranslateFPToNative(const Color: TColorFPRec; Native: Pointer); begin ImagingFormats.TranslatePixel(@Color, Native, ifA32R32G32B32F, FPData.Format, nil, FPData.Palette); end; procedure TImagingCanvas.UpdateCanvasState; begin FDataSizeOnUpdate := FPData.Size; ResetClipRect; Imaging.GetImageFormatInfo(FPData.Format, FFormatInfo) end; procedure TImagingCanvas.ResetClipRect; begin FClipRect := Rect(0, 0, FPData.Width, FPData.Height) end; procedure TImagingCanvas.Clear; begin TranslateFPToNative(FFillColorFP); Imaging.FillRect(FPData^, 0, 0, FPData.Width, FPData.Height, @FNativeColor); end; function TImagingCanvas.ClipAxisParallelLine(var A1, A2, B: LongInt; AStart, AStop, BStart, BStop: LongInt): Boolean; begin if (B >= BStart) and (B < BStop) then begin SwapMin(A1, A2); if A1 < AStart then A1 := AStart; if A2 >= AStop then A2 := AStop - 1; Result := True; end else Result := False; end; procedure TImagingCanvas.HorzLineInternal(X1, X2, Y: LongInt; Color: Pointer; Bpp: LongInt); var I, WidthBytes: LongInt; PixelPtr: PByte; begin if (Y >= FClipRect.Top) and (Y < FClipRect.Bottom) then begin SwapMin(X1, X2); X1 := Max(X1, FClipRect.Left); X2 := Min(X2, FClipRect.Right); PixelPtr := GetPixelPointer(X1, Y); WidthBytes := (X2 - X1) * Bpp; case Bpp of 1: FillMemoryByte(PixelPtr, WidthBytes, PByte(Color)^); 2: FillMemoryWord(PixelPtr, WidthBytes, PWord(Color)^); 4: FillMemoryLongWord(PixelPtr, WidthBytes, PLongWord(Color)^); else for I := X1 to X2 do begin ImagingFormats.CopyPixel(Color, PixelPtr, Bpp); Inc(PixelPtr, Bpp); end; end; end; end; procedure TImagingCanvas.CopyPixelInternal(X, Y: LongInt; Pixel: Pointer; Bpp: LongInt); begin if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and (X < FClipRect.Right) and (Y < FClipRect.Bottom) then begin ImagingFormats.CopyPixel(Pixel, GetPixelPointer(X, Y), Bpp); end; end; procedure TImagingCanvas.HorzLine(X1, X2, Y: LongInt); var DstRect: TRect; begin if FPenMode = pmClear then Exit; SwapMin(X1, X2); if IntersectRect(DstRect, Rect(X1, Y - FPenWidth div 2, X2, Y + FPenWidth div 2 + FPenWidth mod 2), FClipRect) then begin TranslateFPToNative(FPenColorFP); Imaging.FillRect(FPData^, DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left, DstRect.Bottom - DstRect.Top, @FNativeColor); end; end; procedure TImagingCanvas.VertLine(X, Y1, Y2: LongInt); var DstRect: TRect; begin if FPenMode = pmClear then Exit; SwapMin(Y1, Y2); if IntersectRect(DstRect, Rect(X - FPenWidth div 2, Y1, X + FPenWidth div 2 + FPenWidth mod 2, Y2), FClipRect) then begin TranslateFPToNative(FPenColorFP); Imaging.FillRect(FPData^, DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left, DstRect.Bottom - DstRect.Top, @FNativeColor); end; end; procedure TImagingCanvas.Line(X1, Y1, X2, Y2: LongInt); var Steep: Boolean; Error, YStep, DeltaX, DeltaY, X, Y, I, Bpp, W1, W2, Code1, Code2: LongInt; begin if FPenMode = pmClear then Exit; // If line is vertical or horizontal just call appropriate method if X2 - X1 = 0 then begin HorzLine(X1, X2, Y1); Exit; end; if Y2 - Y1 = 0 then begin VertLine(X1, Y1, Y2); Exit; end; // Determine if line is steep (angle with X-axis > 45 degrees) Steep := Abs(Y2 - Y1) > Abs(X2 - X1); // If we need to draw thick line we just draw more 1 pixel lines around // the one we already drawn. Setting FLineRecursion assures that we // won't be doing recursions till the end of the world. if (FPenWidth > 1) and not FLineRecursion then begin FLineRecursion := True; W1 := FPenWidth div 2; W2 := W1; if FPenWidth mod 2 = 0 then Dec(W1); if Steep then begin // Add lines left/right for I := 1 to W1 do Line(X1, Y1 - I, X2, Y2 - I); for I := 1 to W2 do Line(X1, Y1 + I, X2, Y2 + I); end else begin // Add lines above/under for I := 1 to W1 do Line(X1 - I, Y1, X2 - I, Y2); for I := 1 to W2 do Line(X1 + I, Y1, X2 + I, Y2); end; FLineRecursion := False; end; with FClipRect do begin // Use part of Cohen-Sutherland line clipping to determine if any part of line // is in ClipRect Code1 := Ord(X1 < Left) + Ord(X1 > Right) shl 1 + Ord(Y1 < Top) shl 2 + Ord(Y1 > Bottom) shl 3; Code2 := Ord(X2 < Left) + Ord(X2 > Right) shl 1 + Ord(Y2 < Top) shl 2 + Ord(Y2 > Bottom) shl 3; end; if (Code1 and Code2) = 0 then begin TranslateFPToNative(FPenColorFP); Bpp := FFormatInfo.BytesPerPixel; // If line is steep swap X and Y coordinates so later we just have one loop // of two (where only one is used according to steepness). if Steep then begin SwapValues(X1, Y1); SwapValues(X2, Y2); end; if X1 > X2 then begin SwapValues(X1, X2); SwapValues(Y1, Y2); end; DeltaX := X2 - X1; DeltaY := Abs(Y2 - Y1); YStep := Iff(Y2 > Y1, 1, -1); Error := 0; Y := Y1; // Draw line using Bresenham algorithm. No real line clipping here, // just don't draw pixels outsize clip rect. for X := X1 to X2 do begin if Steep then CopyPixelInternal(Y, X, @FNativeColor, Bpp) else CopyPixelInternal(X, Y, @FNativeColor, Bpp); Error := Error + DeltaY; if Error * 2 >= DeltaX then begin Inc(Y, YStep); Dec(Error, DeltaX); end; end; end; end; procedure TImagingCanvas.FrameRect(const Rect: TRect); var HalfPen, PenMod: LongInt; begin if FPenMode = pmClear then Exit; HalfPen := FPenWidth div 2; PenMod := FPenWidth mod 2; HorzLine(Rect.Left - HalfPen, Rect.Right + HalfPen + PenMod - 1, Rect.Top); HorzLine(Rect.Left - HalfPen, Rect.Right + HalfPen + PenMod - 1, Rect.Bottom - 1); VertLine(Rect.Left, Rect.Top, Rect.Bottom); VertLine(Rect.Right - 1, Rect.Top, Rect.Bottom); end; procedure TImagingCanvas.FillRect(const Rect: TRect); var DstRect: TRect; begin if (FFillMode <> fmClear) and IntersectRect(DstRect, Rect, FClipRect) then begin TranslateFPToNative(FFillColorFP); Imaging.FillRect(FPData^, DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left, DstRect.Bottom - DstRect.Top, @FNativeColor); end; end; procedure TImagingCanvas.Rectangle(const Rect: TRect); begin FillRect(Rect); FrameRect(Rect); end; procedure TImagingCanvas.Ellipse(const Rect: TRect); var RadX, RadY, DeltaX, DeltaY, R, RX, RY: LongInt; X1, X2, Y1, Y2, Bpp, OldY: LongInt; Fill, Pen: TColorFPRec; begin // TODO: Use PenWidth X1 := Rect.Left; X2 := Rect.Right; Y1 := Rect.Top; Y2 := Rect.Bottom; TranslateFPToNative(FPenColorFP, @Pen); TranslateFPToNative(FFillColorFP, @Fill); Bpp := FFormatInfo.BytesPerPixel; SwapMin(X1, X2); SwapMin(Y1, Y2); RadX := (X2 - X1) div 2; RadY := (Y2 - Y1) div 2; Y1 := Y1 + RadY; Y2 := Y1; OldY := Y1; DeltaX := (RadX * RadX); DeltaY := (RadY * RadY); R := RadX * RadY * RadY; RX := R; RY := 0; if (FFillMode <> fmClear) then HorzLineInternal(X1, X2, Y1, @Fill, Bpp); CopyPixelInternal(X1, Y1, @Pen, Bpp); CopyPixelInternal(X2, Y1, @Pen, Bpp); while RadX > 0 do begin if R > 0 then begin Inc(Y1); Dec(Y2); Inc(RY, DeltaX); Dec(R, RY); end; if R <= 0 then begin Dec(RadX); Inc(X1); Dec(X2); Dec(RX, DeltaY); Inc(R, RX); end; if (OldY <> Y1) and (FFillMode <> fmClear) then begin HorzLineInternal(X1, X2, Y1, @Fill, Bpp); HorzLineInternal(X1, X2, Y2, @Fill, Bpp); end; OldY := Y1; CopyPixelInternal(X1, Y1, @Pen, Bpp); CopyPixelInternal(X2, Y1, @Pen, Bpp); CopyPixelInternal(X1, Y2, @Pen, Bpp); CopyPixelInternal(X2, Y2, @Pen, Bpp); end; end; procedure TImagingCanvas.ApplyConvolution(Kernel: PLongInt; KernelSize, Divisor: LongInt; Bias: Single; ClampChannels: Boolean); var X, Y, I, J, PosY, PosX, SizeDiv2, KernelValue, WidthBytes, Bpp: LongInt; R, G, B, DivFloat: Single; Pixel: TColorFPRec; TempImage: TImageData; DstPointer, SrcPointer: PByte; begin SizeDiv2 := KernelSize div 2; DivFloat := IffFloat(Divisor > 1, 1.0 / Divisor, 1.0); Bpp := FFormatInfo.BytesPerPixel; WidthBytes := FPData.Width * Bpp; InitImage(TempImage); CloneImage(FPData^, TempImage); try // For every pixel in clip rect for Y := FClipRect.Top to FClipRect.Bottom - 1 do begin DstPointer := @PByteArray(FPData.Bits)[Y * WidthBytes + FClipRect.Left * Bpp]; for X := FClipRect.Left to FClipRect.Right - 1 do begin // Reset accumulators R := 0.0; G := 0.0; B := 0.0; for J := 0 to KernelSize - 1 do begin PosY := ClampInt(Y + J - SizeDiv2, FClipRect.Top, FClipRect.Bottom); for I := 0 to KernelSize - 1 do begin PosX := ClampInt(X + I - SizeDiv2, FClipRect.Left, FClipRect.Right); SrcPointer := @PByteArray(TempImage.Bits)[PosY * WidthBytes + PosX * Bpp]; // Get pixels from neighbourhood of current pixel and add their // colors to accumulators weighted by filter kernel values Pixel := FFormatInfo.GetPixelFP(SrcPointer, @FFormatInfo, TempImage.Palette); KernelValue := PLongIntArray(Kernel)[J * KernelSize + I]; R := R + Pixel.R * KernelValue; G := G + Pixel.G * KernelValue; B := B + Pixel.B * KernelValue; end; end; Pixel := FFormatInfo.GetPixelFP(DstPointer, @FFormatInfo, FPData.Palette); Pixel.R := R * DivFloat + Bias; Pixel.G := G * DivFloat + Bias; Pixel.B := B * DivFloat + Bias; if ClampChannels then ClampFloatPixel(Pixel); // Set resulting pixel color FFormatInfo.SetPixelFP(DstPointer, @FFormatInfo, FPData.Palette, Pixel); Inc(DstPointer, Bpp); end; end; finally FreeImage(TempImage); end; end; procedure TImagingCanvas.ApplyConvolution3x3(const Filter: TConvolutionFilter3x3); begin ApplyConvolution(@Filter.Kernel, 3, Filter.Divisor, Filter.Bias, True); end; procedure TImagingCanvas.ApplyConvolution5x5(const Filter: TConvolutionFilter5x5); begin ApplyConvolution(@Filter.Kernel, 5, Filter.Divisor, Filter.Bias, True); end; class function TImagingCanvas.GetSupportedFormats: TImageFormats; begin Result := [ifIndex8..Pred(ifDXT1)]; end; { TFastARGB32Canvas } destructor TFastARGB32Canvas.Destroy; begin FreeMem(FScanlines); inherited Destroy; end; function TFastARGB32Canvas.GetPixel32(X, Y: LongInt): TColor32; begin Result := FScanlines[Y, X].Color; end; procedure TFastARGB32Canvas.SetPixel32(X, Y: LongInt; const Value: TColor32); begin if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and (X < FClipRect.Right) and (Y < FClipRect.Bottom) then begin FScanlines[Y, X].Color := Value; end; end; procedure TFastARGB32Canvas.UpdateCanvasState; var I: LongInt; ScanPos: PLongWord; begin inherited UpdateCanvasState; // Realloc and update scanline array ReallocMem(FScanlines, FPData.Height * SizeOf(PColor32RecArray)); ScanPos := FPData.Bits; for I := 0 to FPData.Height - 1 do begin FScanlines[I] := PColor32RecArray(ScanPos); Inc(ScanPos, FPData.Width); end; end; class function TFastARGB32Canvas.GetSupportedFormats: TImageFormats; begin Result := [ifA8R8G8B8]; end; initialization RegisterCanvas(TFastARGB32Canvas); finalization FreeAndNil(CanvasClasses); { File Notes: -- TODOS ---------------------------------------------------- - more more more ... - implement pen width everywhere - add blending (image and object drawing) - add image drawing - more objects (arc, polygon) - add channel write/read masks (like apply conv only on Red channel,...) -- 0.21 Changes/Bug Fixes ----------------------------------- - Added some new filter kernels for convolution. - Added FillMode and PenMode properties. - Added FrameRect, Rectangle, Ellipse, and Line methods. - Removed HorzLine and VertLine from TFastARGB32Canvas - new versions in general canvas is now as fast as those in TFastARGB32Canvas (only in case of A8R8G8B8 images of course). - Added PenWidth property, updated HorzLine and VertLine to use it. -- 0.19 Changes/Bug Fixes ----------------------------------- - added TFastARGB32Canvas - added convolutions, hline, vline - unit created, intial stuff added } end.