Statistics
| Branch: | Tag: | Revision:

root / Imaging / ImagingCanvases.pas @ 0:95bd93c28625

History | View | Annotate | Download (34.4 kB)

1
{
2
  $Id: ImagingCanvases.pas 103 2007-09-15 01:11:14Z galfar $
3
  Vampyre Imaging Library
4
  by Marek Mauder
5
  http://imaginglib.sourceforge.net
6
7
  The contents of this file are used with permission, subject to the Mozilla
8
  Public License Version 1.1 (the "License"); you may not use this file except
9
  in compliance with the License. You may obtain a copy of the License at
10
  http://www.mozilla.org/MPL/MPL-1.1.html
11
12
  Software distributed under the License is distributed on an "AS IS" basis,
13
  WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14
  the specific language governing rights and limitations under the License.
15
16
  Alternatively, the contents of this file may be used under the terms of the
17
  GNU Lesser General Public License (the  "LGPL License"), in which case the
18
  provisions of the LGPL License are applicable instead of those above.
19
  If you wish to allow use of your version of this file only under the terms
20
  of the LGPL License and not to allow others to use your version of this file
21
  under the MPL, indicate your decision by deleting  the provisions above and
22
  replace  them with the notice and other provisions required by the LGPL
23
  License.  If you do not delete the provisions above, a recipient may use
24
  your version of this file under either the MPL or the LGPL License.
25
26
  For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
27
}
28
29
{
30
  This unit contains canvas classes for drawing and applying effects.
31
}
32
unit ImagingCanvases;
33
34
{$I ImagingOptions.inc}
35
36
interface
37
38
uses
39
  SysUtils, Types, Classes, ImagingTypes, Imaging, ImagingClasses,
40
  ImagingFormats, ImagingUtility;
41
42
const
43
  { Color constants in ifA8R8G8B8 format.}
44
  pcClear   = $00000000;
45
  pcBlack   = $FF000000;
46
  pcWhite   = $FFFFFFFF;
47
  pcMaroon  = $FF800000;
48
  pcGreen   = $FF008000;
49
  pcOlive   = $FF808000;
50
  pcNavy    = $FF000080;
51
  pcPurple  = $FF800080;
52
  pcTeal    = $FF008080;
53
  pcGray    = $FF808080;
54
  pcSilver  = $FFC0C0C0;
55
  pcRed     = $FFFF0000;
56
  pcLime    = $FF00FF00;
57
  pcYellow  = $FFFFFF00;
58
  pcBlue    = $FF0000FF;
59
  pcFuchsia = $FFFF00FF;
60
  pcAqua    = $FF00FFFF;
61
  pcLtGray  = $FFC0C0C0;
62
  pcDkGray  = $FF808080;
63
64
  MaxPenWidth = 256;
65
type
66
  EImagingCanvasError = class(EImagingError);
67
68
  { Fill mode used when drawing filled objects on canvas.}
69
  TFillMode = (
70
    fmSolid,  // Solid fill using current fill color
71
    fmClear   // No filling done
72
  );
73
74
  { Pen mode used when drawing lines, object outlines, and similar on canvas.}
75
  TPenMode = (
76
    pmSolid,  // Draws solid lines using current pen color. 
77
    pmClear   // No drawing done
78
  );
79
80
  { Represents 3x3 convolution filter kernel.}
81
  TConvolutionFilter3x3 = record
82
    Kernel: array[0..2, 0..2] of LongInt;
83
    Divisor: LongInt;
84
    Bias: Single;
85
  end;
86
87
  { Represents 5x5 convolution filter kernel.}
88
  TConvolutionFilter5x5 = record
89
    Kernel: array[0..4, 0..4] of LongInt;
90
    Divisor: LongInt;
91
    Bias: Single;
92
  end;
93
94
  { Base canvas class for drawing objects, applying effects, and other.
95
    Constructor takes TBaseImage (or pointer to TImageData). Source image
96
    bits are not copied but referenced so all canvas functions affect
97
    source image and vice versa. When you change format or resolution of
98
    source image you must call UpdateCanvasState method (so canvas could
99
    recompute some data size related stuff).
100
101
    TImagingCanvas works for all image data formats except special ones
102
    (compressed). Because of this its methods are quite slow (they work
103
    with colors in ifA32R32G32B32F format). If you want fast drawing you
104
    can use one of fast canvas clases. These descendants of TImagingCanvas
105
    work only for few select formats (or only one) but they are optimized thus
106
    much faster.
107
108
    --
109
    Canvas in this Imaging version (0.20) is very basic and its purpose is to
110
    act like sort of a preview of things to come.
111
    Update 0.22: Some new stuff added but not much yet.
112
  }
113
  TImagingCanvas = class(TObject)
114
  private
115
    FDataSizeOnUpdate: LongInt;
116
    FLineRecursion: Boolean;
117
    function GetPixel32(X, Y: LongInt): TColor32; virtual;
118
    function GetPixelFP(X, Y: LongInt): TColorFPRec; virtual;
119
    function GetValid: Boolean; {$IFDEF USE_INLINE}inline;{$ENDIF}
120
    procedure SetPixel32(X, Y: LongInt; const Value: TColor32); virtual;
121
    procedure SetPixelFP(X, Y: LongInt; const Value: TColorFPRec); virtual;
122
    procedure SetPenColor32(const Value: TColor32); {$IFDEF USE_INLINE}inline;{$ENDIF}
123
    procedure SetPenColorFP(const Value: TColorFPRec); {$IFDEF USE_INLINE}inline;{$ENDIF}
124
    procedure SetPenWidth(const Value: LongInt); {$IFDEF USE_INLINE}inline;{$ENDIF}
125
    procedure SetFillColor32(const Value: TColor32); {$IFDEF USE_INLINE}inline;{$ENDIF}
126
    procedure SetFillColorFP(const Value: TColorFPRec); {$IFDEF USE_INLINE}inline;{$ENDIF}
127
    procedure SetClipRect(const Value: TRect);
128
  protected
129
    FPData: PImageData;
130
    FClipRect: TRect;
131
    FPenColorFP: TColorFPRec;
132
    FPenColor32: TColor32;
133
    FPenMode: TPenMode;
134
    FPenWidth: LongInt;
135
    FFillColorFP: TColorFPRec;
136
    FFillColor32: TColor32;
137
    FFillMode: TFillMode;
138
    FNativeColor: TColorFPRec;
139
    FFormatInfo: TImageFormatInfo;
140
141
    { Returns pointer to pixel at given position.}
142
    function GetPixelPointer(X, Y: LongInt): Pointer; {$IFDEF USE_INLINE}inline;{$ENDIF}
143
    { Translates given FP color to native format of canvas and stores it
144
      in FNativeColor field (its bit copy) or user pointer (in overloaded method).}
145
    procedure TranslateFPToNative(const Color: TColorFPRec); overload; {$IFDEF USE_INLINE}inline;{$ENDIF}
146
    procedure TranslateFPToNative(const Color: TColorFPRec; Native: Pointer); overload; {$IFDEF USE_INLINE}inline;{$ENDIF}
147
    { Clipping function used by horizontal and vertical line drawing functions.}
148
    function ClipAxisParallelLine(var A1, A2, B: LongInt;
149
      AStart, AStop, BStart, BStop: LongInt): Boolean;
150
    { Internal horizontal line drawer used mainly for filling inside of objects
151
      like ellipses and circles.}
152
    procedure HorzLineInternal(X1, X2, Y: LongInt; Color: Pointer; Bpp: LongInt); virtual;
153
    procedure CopyPixelInternal(X, Y: LongInt; Pixel: Pointer; Bpp: LongInt); {$IFDEF USE_INLINE}inline;{$ENDIF}
154
  public
155
    constructor CreateForData(ImageDataPointer: PImageData);
156
    constructor CreateForImage(Image: TBaseImage);
157
    destructor Destroy; override;
158
159
    { Call this method when you change size or format of image this canvas
160
      operates on (like calling ResizeImage, ConvertImage, or changing Format
161
      property of TBaseImage descendants).}
162
    procedure UpdateCanvasState; virtual;
163
    { Resets clipping rectangle to Rect(0, 0, ImageWidth, ImageHeight).}
164
    procedure ResetClipRect;
165
166
    { Clears entire canvas with current fill color (ignores clipping rectangle
167
      and always uses fmSolid fill mode).}
168
    procedure Clear;
169
170
    { Draws horizontal line with current pen settings.}
171
    procedure HorzLine(X1, X2, Y: LongInt); virtual;
172
    { Draws vertical line with current pen settings.}
173
    procedure VertLine(X, Y1, Y2: LongInt); virtual;
174
    { Draws line from [X1, Y1] to [X2, Y2] with current pen settings.}
175
    procedure Line(X1, Y1, X2, Y2: LongInt); virtual;
176
    { Draws a rectangle using current pen settings.}
177
    procedure FrameRect(const Rect: TRect);
178
    { Fills given rectangle with current fill settings.}
179
    procedure FillRect(const Rect: TRect); virtual;
180
    { Draws rectangle which is outlined by using the current pen settings and
181
      filled by using the current fill settings.}
182
    procedure Rectangle(const Rect: TRect);
183
    { Draws ellipse which is outlined by using the current pen settings and
184
      filled by using the current fill settings. Rect specifies bounding rectangle
185
      of ellipse to be drawn.}
186
    procedure Ellipse(const Rect: TRect);
187
188
    { Convolves canvas' image with given 3x3 filter kernel. You can use
189
      predefined filter kernels or define your own.}
190
    procedure ApplyConvolution3x3(const Filter: TConvolutionFilter3x3);
191
    { Convolves canvas' image with given 5x5 filter kernel. You can use
192
      predefined filter kernels or define your own.}
193
    procedure ApplyConvolution5x5(const Filter: TConvolutionFilter5x5);
194
    { Computes 2D convolution of canvas' image and given filter kernel.
195
      Kernel is in row format and KernelSize must be odd number >= 3. Divisor
196
      is normalizing value based on Kernel (usually sum of all kernel's cells).
197
      The Bias number shifts each color value by a fixed amount (color values
198
      are usually in range [0, 1] during processing). If ClampChannels
199
      is True all output color values are clamped to [0, 1]. You can use
200
      predefined filter kernels or define your own.}
201
    procedure ApplyConvolution(Kernel: PLongInt; KernelSize, Divisor: LongInt;
202
      Bias: Single = 0.0; ClampChannels: Boolean = True); virtual;
203
204
    { Color used when drawing lines, frames, and outlines of objects.}
205
    property PenColor32: TColor32 read FPenColor32 write SetPenColor32;
206
    { Color used when drawing lines, frames, and outlines of objects.}
207
    property PenColorFP: TColorFPRec read FPenColorFP write SetPenColorFP;
208
    { Pen mode used when drawing lines, object outlines, and similar on canvas.}
209
    property PenMode: TPenMode read FPenMode write FPenMode;
210
    { Width with which objects like lines, frames, etc. (everything which uses
211
      PenColor) are drawn.}
212
    property PenWidth: LongInt read FPenWidth write SetPenWidth;
213
    { Color used for filling when drawing various objects.}
214
    property FillColor32: TColor32 read FFillColor32 write SetFillColor32;
215
    { Color used for filling when drawing various objects.}
216
    property FillColorFP: TColorFPRec read FFillColorFP write SetFillColorFP;
217
    { Fill mode used when drawing filled objects on canvas.}
218
    property FillMode: TFillMode read FFillMode write FFillMode;
219
    { Specifies the current color of the pixels of canvas. Native pixel is
220
      read from canvas and then translated to 32bit ARGB. Reverse operation
221
      is made when setting pixel color.}
222
    property Pixels32[X, Y: LongInt]: TColor32 read GetPixel32 write SetPixel32;
223
    { Specifies the current color of the pixels of canvas. Native pixel is
224
      read from canvas and then translated to FP ARGB. Reverse operation
225
      is made when setting pixel color.}
226
    property PixelsFP[X, Y: LongInt]: TColorFPRec read GetPixelFP write SetPixelFP;
227
    { Clipping rectangle of this canvas. No pixels outside this rectangle are
228
      altered by canvas methods if Clipping property is True. Clip rect gets
229
      reseted when UpdateCanvasState is called.}
230
    property ClipRect: TRect read FClipRect write SetClipRect;
231
    { Extended format information.}
232
    property FormatInfo: TImageFormatInfo read FFormatInfo;
233
    { Indicates that this canvas is in valid state. If False canvas oprations
234
      may crash.}
235
    property Valid: Boolean read GetValid;
236
237
    { Returns all formats supported by this canvas class.}
238
    class function GetSupportedFormats: TImageFormats; virtual;
239
  end;
240
241
  TImagingCanvasClass = class of TImagingCanvas;
242
243
  TScanlineArray = array[0..MaxInt div SizeOf(Pointer) - 1] of PColor32RecArray;
244
  PScanlineArray = ^TScanlineArray;
245
246
  { Fast canvas class for ifA8R8G8B8 format images.}
247
  TFastARGB32Canvas = class(TImagingCanvas)
248
  protected
249
    FScanlines: PScanlineArray;
250
    function GetPixel32(X, Y: LongInt): TColor32; override;
251
    procedure SetPixel32(X, Y: LongInt; const Value: TColor32); override;
252
  public
253
    destructor Destroy; override;
254
255
    procedure UpdateCanvasState; override;
256
257
    property Scanlines: PScanlineArray read FScanlines;
258
259
    class function GetSupportedFormats: TImageFormats; override;
260
  end;
261
262
const
263
  { Kernel for 3x3 average smoothing filter.}
264
  FilterAverage3x3: TConvolutionFilter3x3 = (
265
    Kernel: ((1, 1, 1),
266
             (1, 1, 1),
267
             (1, 1, 1));
268
    Divisor: 9);
269
270
  { Kernel for 5x5 average smoothing filter.}
271
  FilterAverage5x5: TConvolutionFilter5x5 = (
272
    Kernel: ((1, 1, 1, 1, 1),
273
             (1, 1, 1, 1, 1),
274
             (1, 1, 1, 1, 1),
275
             (1, 1, 1, 1, 1),
276
             (1, 1, 1, 1, 1));
277
    Divisor: 25);
278
279
  { Kernel for 3x3 Gaussian smoothing filter.}
280
  FilterGaussian3x3: TConvolutionFilter3x3 = (
281
    Kernel: ((1, 2, 1),
282
             (2, 4, 2),
283
             (1, 2, 1));
284
    Divisor: 16);
285
286
  { Kernel for 5x5 Gaussian smoothing filter.}
287
  FilterGaussian5x5: TConvolutionFilter5x5 = (
288
    Kernel: ((1,  4,  6,  4, 1),
289
             (4, 16, 24, 16, 4),
290
             (6, 24, 36, 24, 6),
291
             (4, 16, 24, 16, 4),
292
             (1,  4,  6,  4, 1));
293
    Divisor: 256);
294
295
  { Kernel for 3x3 Sobel horizontal edge detection filter (1st derivative approximation).}
296
  FilterSobelHorz3x3: TConvolutionFilter3x3 = (
297
    Kernel: (( 1,  2,  1),
298
             ( 0,  0,  0),
299
             (-1, -2, -1));
300
    Divisor: 1);
301
302
  { Kernel for 3x3 Sobel vertical edge detection filter (1st derivative approximation).}
303
  FilterSobelVert3x3: TConvolutionFilter3x3 = (
304
    Kernel: ((-1, 0, 1),
305
             (-2, 0, 2),
306
             (-1, 0, 1));
307
    Divisor: 1);
308
309
  { Kernel for 3x3 Prewitt horizontal edge detection filter.}
310
  FilterPrewittHorz3x3: TConvolutionFilter3x3 = (
311
    Kernel: (( 1,  1,  1),
312
             ( 0,  0,  0),
313
             (-1, -1, -1));
314
    Divisor: 1);
315
316
  { Kernel for 3x3 Prewitt vertical edge detection filter.}
317
  FilterPrewittVert3x3: TConvolutionFilter3x3 = (
318
    Kernel: ((-1, 0, 1),
319
             (-1, 0, 1),
320
             (-1, 0, 1));
321
    Divisor: 1);
322
323
  { Kernel for 3x3 Kirsh horizontal edge detection filter.}
324
  FilterKirshHorz3x3: TConvolutionFilter3x3 = (
325
    Kernel: (( 5,  5,  5),
326
             (-3,  0, -3),
327
             (-3, -3, -3));
328
    Divisor: 1);
329
330
  { Kernel for 3x3 Kirsh vertical edge detection filter.}
331
  FilterKirshVert3x3: TConvolutionFilter3x3 = (
332
    Kernel: ((5, -3, -3),
333
             (5,  0, -3),
334
             (5, -3, -3));
335
    Divisor: 1);
336
337
  { Kernel for 3x3 Laplace omni-directional edge detection filter
338
    (2nd derivative approximation).}
339
  FilterLaplace3x3: TConvolutionFilter3x3 = (
340
    Kernel: ((-1, -1, -1),
341
             (-1,  8, -1),
342
             (-1, -1, -1));
343
    Divisor: 1);
344
345
  { Kernel for 5x5 Laplace omni-directional edge detection filter
346
    (2nd derivative approximation).}
347
  FilterLaplace5x5: TConvolutionFilter5x5 = (
348
    Kernel: ((-1, -1, -1, -1, -1),
349
             (-1, -1, -1, -1, -1),
350
             (-1, -1, 24, -1, -1),
351
             (-1, -1, -1, -1, -1),
352
             (-1, -1, -1, -1, -1));
353
    Divisor: 1);
354
355
  { Kernel for 3x3 spharpening filter (Laplacian + original color).}
356
  FilterSharpen3x3: TConvolutionFilter3x3 = (
357
    Kernel: ((-1, -1, -1),
358
             (-1,  9, -1),
359
             (-1, -1, -1));
360
    Divisor: 1);
361
362
  { Kernel for 5x5 spharpening filter (Laplacian + original color).}
363
  FilterSharpen5x5: TConvolutionFilter5x5 = (
364
    Kernel: ((-1, -1, -1, -1, -1),
365
             (-1, -1, -1, -1, -1),
366
             (-1, -1, 25, -1, -1),
367
             (-1, -1, -1, -1, -1),
368
             (-1, -1, -1, -1, -1));
369
    Divisor: 1);
370
371
  { Kernel for 5x5 glow filter.}
372
  FilterGlow5x5: TConvolutionFilter5x5 = (
373
    Kernel: (( 1, 2,   2, 2, 1),
374
             ( 2, 0,   0, 0, 2),
375
             ( 2, 0, -20, 0, 2),
376
             ( 2, 0,   0, 0, 2),
377
             ( 1, 2,   2, 2, 1));
378
    Divisor: 8);
379
380
  { Kernel for 3x3 edge enhancement filter.}
381
  FilterEdgeEnhance3x3: TConvolutionFilter3x3 = (
382
    Kernel: ((-1, -2, -1),
383
             (-2, 16, -2),
384
             (-1, -2, -1));
385
    Divisor: 4);
386
387
  FilterTraceControur3x3: TConvolutionFilter3x3 = (
388
    Kernel: ((-6, -6, -2),
389
             (-1, 32, -1),
390
             (-6, -2, -6));
391
    Divisor: 4;
392
    Bias:    240/255);
393
394
  { Kernel for filter that negates all images pixels.}
395
  FilterNegative3x3: TConvolutionFilter3x3 = (
396
    Kernel: ((0,  0, 0),
397
             (0, -1, 0),
398
             (0,  0, 0));
399
    Divisor: 1;
400
    Bias:    1);
401
402
  { Kernel for 3x3 horz/vert embossing filter.}  
403
  FilterEmboss3x3: TConvolutionFilter3x3 = (
404
    Kernel: ((2,  0,  0),
405
             (0, -1,  0),
406
             (0,  0, -1));
407
    Divisor: 1;
408
    Bias:    0.5);
409
410
411
{ You can register your own canvas class. List of registered canvases is used
412
  by FindBestCanvasForImage functions to find best canvas for given image.
413
  If two different canvases which support the same image data format are
414
  registered then the one that was registered later is returned (so you can
415
  override builtin Imaging canvases).}
416
procedure RegisterCanvas(CanvasClass: TImagingCanvasClass);
417
{ Returns best canvas for given TImageFormat.}
418
function FindBestCanvasForImage(ImageFormat: TImageFormat): TImagingCanvasClass; overload;
419
{ Returns best canvas for given TImageData.}
420
function FindBestCanvasForImage(const ImageData: TImageData): TImagingCanvasClass; overload;
421
{ Returns best canvas for given TBaseImage.}
422
function FindBestCanvasForImage(Image: TBaseImage): TImagingCanvasClass; overload;
423
424
implementation
425
426
resourcestring
427
  SConstructorInvalidPointer = 'Invalid pointer (%p) to TImageData passed to TImagingCanvas constructor.';
428
  SConstructorInvalidImage = 'Invalid image data passed to TImagingCanvas constructor (%s).';
429
  SConstructorUnsupportedFormat = 'Image passed to TImagingCanvas constructor is in unsupported format (%s)';
430
431
var
432
  // list with all registered TImagingCanvas classes
433
  CanvasClasses: TList = nil;
434
435
procedure RegisterCanvas(CanvasClass: TImagingCanvasClass);
436
begin
437
  Assert(CanvasClass <> nil);
438
  if CanvasClasses = nil then
439
    CanvasClasses := TList.Create;
440
  if CanvasClasses.IndexOf(CanvasClass) < 0 then
441
    CanvasClasses.Add(CanvasClass);
442
end;
443
444
function FindBestCanvasForImage(ImageFormat: TImageFormat): TImagingCanvasClass; overload;
445
var
446
  I: LongInt;
447
begin
448
  for I := CanvasClasses.Count - 1 downto 0 do
449
  begin
450
    if ImageFormat in TImagingCanvasClass(CanvasClasses[I]).GetSupportedFormats then
451
    begin
452
      Result := TImagingCanvasClass(CanvasClasses[I]);
453
      Exit;
454
    end;
455
  end;
456
  Result := TImagingCanvas;
457
end;
458
459
function FindBestCanvasForImage(const ImageData: TImageData): TImagingCanvasClass;
460
begin
461
  Result := FindBestCanvasForImage(ImageData.Format);
462
end;
463
464
function FindBestCanvasForImage(Image: TBaseImage): TImagingCanvasClass;
465
begin
466
  Result := FindBestCanvasForImage(Image.Format);
467
end;
468
469
{ TImagingCanvas }
470
471
constructor TImagingCanvas.CreateForData(ImageDataPointer: PImageData);
472
begin
473
  if ImageDataPointer = nil then
474
    raise EImagingCanvasError.CreateFmt(SConstructorInvalidPointer, [ImageDataPointer]);
475
476
  if not TestImage(ImageDataPointer^) then
477
    raise EImagingCanvasError.CreateFmt(SConstructorInvalidImage, [Imaging.ImageToStr(ImageDataPointer^)]);
478
479
  if not (ImageDataPointer.Format in GetSupportedFormats) then
480
    raise EImagingCanvasError.CreateFmt(SConstructorUnsupportedFormat, [Imaging.ImageToStr(ImageDataPointer^)]);
481
482
  FPData := ImageDataPointer;
483
  FPenWidth := 1;
484
  SetPenColor32(pcWhite);
485
  SetFillColor32(pcBlack);
486
  FFillMode := fmSolid;
487
488
  UpdateCanvasState;
489
end;
490
491
constructor TImagingCanvas.CreateForImage(Image: TBaseImage);
492
begin
493
  CreateForData(Image.ImageDataPointer);
494
end;
495
496
destructor TImagingCanvas.Destroy;
497
begin
498
  inherited Destroy;
499
end;
500
501
function TImagingCanvas.GetPixel32(X, Y: LongInt): TColor32;
502
begin
503
  Result := Imaging.GetPixel32(FPData^, X, Y).Color;
504
end;
505
506
function TImagingCanvas.GetPixelFP(X, Y: LongInt): TColorFPRec;
507
begin
508
  Result := Imaging.GetPixelFP(FPData^, X, Y);
509
end;
510
511
function TImagingCanvas.GetValid: Boolean;
512
begin
513
  Result := (FPData <> nil) and (FDataSizeOnUpdate = FPData.Size);
514
end;
515
516
procedure TImagingCanvas.SetPixel32(X, Y: LongInt; const Value: TColor32);
517
begin
518
  if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and
519
    (X < FClipRect.Right) and (Y < FClipRect.Bottom) then
520
  begin
521
    Imaging.SetPixel32(FPData^, X, Y, TColor32Rec(Value));
522
  end;
523
end;
524
525
procedure TImagingCanvas.SetPixelFP(X, Y: LongInt; const Value: TColorFPRec);
526
begin
527
  if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and
528
    (X < FClipRect.Right) and (Y < FClipRect.Bottom) then
529
  begin
530
    Imaging.SetPixelFP(FPData^, X, Y, TColorFPRec(Value));
531
  end;
532
end;
533
534
procedure TImagingCanvas.SetPenColor32(const Value: TColor32);
535
begin
536
  FPenColor32 := Value;
537
  TranslatePixel(@FPenColor32, @FPenColorFP, ifA8R8G8B8, ifA32R32G32B32F, nil, nil);
538
end;
539
540
procedure TImagingCanvas.SetPenColorFP(const Value: TColorFPRec);
541
begin
542
  FPenColorFP := Value;
543
  TranslatePixel(@FPenColorFP, @FPenColor32, ifA32R32G32B32F, ifA8R8G8B8, nil, nil);
544
end;
545
546
procedure TImagingCanvas.SetPenWidth(const Value: LongInt);
547
begin
548
  FPenWidth := ClampInt(Value, 0, MaxPenWidth);
549
end;
550
551
procedure TImagingCanvas.SetFillColor32(const Value: TColor32);
552
begin
553
  FFillColor32 := Value;
554
  TranslatePixel(@FFillColor32, @FFillColorFP, ifA8R8G8B8, ifA32R32G32B32F, nil, nil);
555
end;
556
557
procedure TImagingCanvas.SetFillColorFP(const Value: TColorFPRec);
558
begin
559
  FFillColorFP := Value;
560
  TranslatePixel(@FFillColorFP, @FFillColor32, ifA32R32G32B32F, ifA8R8G8B8, nil, nil);
561
end;
562
563
procedure TImagingCanvas.SetClipRect(const Value: TRect);
564
begin
565
  FClipRect := Value;
566
  SwapMin(FClipRect.Left, FClipRect.Right);
567
  SwapMin(FClipRect.Top, FClipRect.Bottom);
568
  IntersectRect(FClipRect, FClipRect, Rect(0, 0, FPData.Width, FPData.Height));
569
end;
570
571
function TImagingCanvas.GetPixelPointer(X, Y: LongInt): Pointer;
572
begin
573
  Result := @PByteArray(FPData.Bits)[(Y * FPData.Width + X) * FFormatInfo.BytesPerPixel]
574
end;
575
576
procedure TImagingCanvas.TranslateFPToNative(const Color: TColorFPRec);
577
begin
578
  TranslateFPToNative(Color, @FNativeColor);
579
end;
580
581
procedure TImagingCanvas.TranslateFPToNative(const Color: TColorFPRec;
582
  Native: Pointer);
583
begin
584
  ImagingFormats.TranslatePixel(@Color, Native, ifA32R32G32B32F,
585
    FPData.Format, nil, FPData.Palette);
586
end;
587
588
procedure TImagingCanvas.UpdateCanvasState;
589
begin
590
  FDataSizeOnUpdate := FPData.Size;
591
  ResetClipRect;
592
  Imaging.GetImageFormatInfo(FPData.Format, FFormatInfo)
593
end;
594
595
procedure TImagingCanvas.ResetClipRect;
596
begin
597
  FClipRect := Rect(0, 0, FPData.Width, FPData.Height)
598
end;
599
600
procedure TImagingCanvas.Clear;
601
begin
602
  TranslateFPToNative(FFillColorFP);
603
  Imaging.FillRect(FPData^, 0, 0, FPData.Width, FPData.Height, @FNativeColor);
604
end;
605
606
function TImagingCanvas.ClipAxisParallelLine(var A1, A2, B: LongInt;
607
  AStart, AStop, BStart, BStop: LongInt): Boolean;
608
begin
609
  if (B >= BStart) and (B < BStop) then
610
  begin
611
    SwapMin(A1, A2);
612
    if A1 < AStart then A1 := AStart;
613
    if A2 >= AStop then A2 := AStop - 1;
614
    Result := True;
615
  end
616
  else
617
    Result := False;
618
end;
619
620
procedure TImagingCanvas.HorzLineInternal(X1, X2, Y: LongInt; Color: Pointer;
621
  Bpp: LongInt);
622
var
623
  I, WidthBytes: LongInt;
624
  PixelPtr: PByte;
625
begin
626
  if (Y >= FClipRect.Top) and (Y < FClipRect.Bottom) then
627
  begin
628
    SwapMin(X1, X2);
629
    X1 := Max(X1, FClipRect.Left);
630
    X2 := Min(X2, FClipRect.Right);
631
    PixelPtr := GetPixelPointer(X1, Y);
632
    WidthBytes := (X2 - X1) * Bpp;
633
    case Bpp of
634
      1: FillMemoryByte(PixelPtr, WidthBytes, PByte(Color)^);
635
      2: FillMemoryWord(PixelPtr, WidthBytes, PWord(Color)^);
636
      4: FillMemoryLongWord(PixelPtr, WidthBytes, PLongWord(Color)^);
637
    else
638
      for I := X1 to X2 do
639
      begin
640
        ImagingFormats.CopyPixel(Color, PixelPtr, Bpp);
641
        Inc(PixelPtr, Bpp);
642
       end;
643
    end;
644
  end;
645
end;
646
647
procedure TImagingCanvas.CopyPixelInternal(X, Y: LongInt; Pixel: Pointer;
648
  Bpp: LongInt);
649
begin
650
  if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and
651
    (X < FClipRect.Right) and (Y < FClipRect.Bottom) then
652
  begin
653
    ImagingFormats.CopyPixel(Pixel, GetPixelPointer(X, Y), Bpp);
654
  end;
655
end;
656
657
procedure TImagingCanvas.HorzLine(X1, X2, Y: LongInt);
658
var
659
  DstRect: TRect;
660
begin
661
  if FPenMode = pmClear then Exit;
662
  SwapMin(X1, X2);
663
  if IntersectRect(DstRect, Rect(X1, Y - FPenWidth div 2, X2,
664
    Y + FPenWidth div 2 + FPenWidth mod 2), FClipRect) then
665
  begin
666
    TranslateFPToNative(FPenColorFP);
667
    Imaging.FillRect(FPData^, DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left,
668
      DstRect.Bottom - DstRect.Top, @FNativeColor);
669
  end;
670
end;
671
672
procedure TImagingCanvas.VertLine(X, Y1, Y2: LongInt);
673
var
674
  DstRect: TRect;
675
begin
676
  if FPenMode = pmClear then Exit;
677
  SwapMin(Y1, Y2);
678
  if IntersectRect(DstRect, Rect(X - FPenWidth div 2, Y1,
679
    X + FPenWidth div 2 + FPenWidth mod 2, Y2), FClipRect) then
680
  begin
681
    TranslateFPToNative(FPenColorFP);
682
    Imaging.FillRect(FPData^, DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left,
683
      DstRect.Bottom - DstRect.Top, @FNativeColor);
684
  end;
685
end;
686
687
procedure TImagingCanvas.Line(X1, Y1, X2, Y2: LongInt);
688
var
689
  Steep: Boolean;
690
  Error, YStep, DeltaX, DeltaY, X, Y, I, Bpp, W1, W2, Code1, Code2: LongInt;
691
begin
692
  if FPenMode = pmClear then Exit;
693
694
  // If line is vertical or horizontal just call appropriate method
695
  if X2 - X1 = 0 then
696
  begin
697
    HorzLine(X1, X2, Y1);
698
    Exit;
699
  end;
700
  if Y2 - Y1 = 0 then
701
  begin
702
    VertLine(X1, Y1, Y2);
703
    Exit;
704
  end;
705
706
  // Determine if line is steep (angle with X-axis > 45 degrees)
707
  Steep := Abs(Y2 - Y1) > Abs(X2 - X1);
708
709
  // If we need to draw thick line we just draw more 1 pixel lines around
710
  // the one we already drawn. Setting FLineRecursion assures that we
711
  // won't be doing recursions till the end of the world.
712
  if (FPenWidth > 1) and not FLineRecursion then
713
  begin
714
    FLineRecursion := True;
715
    W1 := FPenWidth div 2;
716
    W2 := W1;
717
    if FPenWidth mod 2 = 0 then
718
      Dec(W1);
719
    if Steep then
720
    begin
721
      // Add lines left/right
722
      for I := 1 to W1 do
723
        Line(X1, Y1 - I, X2, Y2 - I);
724
      for I := 1 to W2 do
725
        Line(X1, Y1 + I, X2, Y2 + I);
726
    end
727
    else
728
    begin
729
      // Add lines above/under
730
      for I := 1 to W1 do
731
        Line(X1 - I, Y1, X2 - I, Y2);
732
      for I := 1 to W2 do
733
        Line(X1 + I, Y1, X2 + I, Y2);
734
    end;
735
    FLineRecursion := False;
736
  end;
737
738
  with FClipRect do
739
  begin
740
    // Use part of Cohen-Sutherland line clipping to determine if any part of line
741
    // is in ClipRect
742
    Code1 := Ord(X1 < Left) + Ord(X1 > Right) shl 1 + Ord(Y1 < Top) shl 2 + Ord(Y1 > Bottom) shl 3;
743
    Code2 := Ord(X2 < Left) + Ord(X2 > Right) shl 1 + Ord(Y2 < Top) shl 2 + Ord(Y2 > Bottom) shl 3;
744
  end;
745
746
  if (Code1 and Code2) = 0 then
747
  begin
748
    TranslateFPToNative(FPenColorFP);
749
    Bpp := FFormatInfo.BytesPerPixel;
750
751
    // If line is steep swap X and Y coordinates so later we just have one loop
752
    // of two (where only one is used according to steepness).
753
    if Steep then
754
    begin
755
      SwapValues(X1, Y1);
756
      SwapValues(X2, Y2);
757
    end;
758
    if X1 > X2 then
759
    begin
760
      SwapValues(X1, X2);
761
      SwapValues(Y1, Y2);
762
    end;
763
764
    DeltaX := X2 - X1;
765
    DeltaY := Abs(Y2 - Y1);
766
    YStep := Iff(Y2 > Y1, 1, -1);
767
    Error := 0;
768
    Y := Y1;
769
770
    // Draw line using Bresenham algorithm. No real line clipping here,
771
    // just don't draw pixels outsize clip rect.
772
    for X := X1 to X2 do
773
    begin
774
      if Steep then
775
        CopyPixelInternal(Y, X, @FNativeColor, Bpp)
776
      else
777
        CopyPixelInternal(X, Y, @FNativeColor, Bpp);
778
      Error := Error + DeltaY;
779
      if Error * 2 >= DeltaX then
780
      begin
781
        Inc(Y, YStep);
782
        Dec(Error, DeltaX);
783
      end;
784
    end;
785
  end;
786
end;
787
788
procedure TImagingCanvas.FrameRect(const Rect: TRect);
789
var
790
  HalfPen, PenMod: LongInt;
791
begin
792
  if FPenMode = pmClear then Exit;
793
  HalfPen := FPenWidth div 2;
794
  PenMod := FPenWidth mod 2;
795
  HorzLine(Rect.Left - HalfPen, Rect.Right + HalfPen + PenMod - 1, Rect.Top);
796
  HorzLine(Rect.Left - HalfPen, Rect.Right + HalfPen + PenMod - 1, Rect.Bottom - 1);
797
  VertLine(Rect.Left, Rect.Top, Rect.Bottom);
798
  VertLine(Rect.Right - 1, Rect.Top, Rect.Bottom);
799
end;
800
801
procedure TImagingCanvas.FillRect(const Rect: TRect);
802
var
803
  DstRect: TRect;
804
begin
805
  if (FFillMode <> fmClear) and IntersectRect(DstRect, Rect, FClipRect) then
806
  begin
807
    TranslateFPToNative(FFillColorFP);
808
    Imaging.FillRect(FPData^, DstRect.Left, DstRect.Top, DstRect.Right - DstRect.Left,
809
      DstRect.Bottom - DstRect.Top, @FNativeColor);
810
  end;
811
end;
812
813
procedure TImagingCanvas.Rectangle(const Rect: TRect);
814
begin
815
  FillRect(Rect);
816
  FrameRect(Rect);
817
end;
818
819
procedure TImagingCanvas.Ellipse(const Rect: TRect);
820
var
821
 RadX, RadY, DeltaX, DeltaY, R, RX, RY: LongInt;
822
 X1, X2, Y1, Y2, Bpp, OldY: LongInt;
823
 Fill, Pen: TColorFPRec;
824
begin
825
  // TODO: Use PenWidth
826
  X1 := Rect.Left;
827
  X2 := Rect.Right;
828
  Y1 := Rect.Top;
829
  Y2 := Rect.Bottom;
830
831
  TranslateFPToNative(FPenColorFP, @Pen);
832
  TranslateFPToNative(FFillColorFP, @Fill);
833
  Bpp := FFormatInfo.BytesPerPixel;
834
835
  SwapMin(X1, X2);
836
  SwapMin(Y1, Y2);
837
838
  RadX := (X2 - X1) div 2;
839
  RadY := (Y2 - Y1) div 2;
840
841
  Y1 := Y1 + RadY;
842
  Y2 := Y1;
843
  OldY := Y1;
844
845
  DeltaX := (RadX * RadX);
846
  DeltaY := (RadY * RadY);
847
  R  := RadX * RadY * RadY;
848
  RX := R;
849
  RY := 0;
850
851
  if (FFillMode <> fmClear) then
852
    HorzLineInternal(X1, X2, Y1, @Fill, Bpp);
853
  CopyPixelInternal(X1, Y1, @Pen, Bpp);
854
  CopyPixelInternal(X2, Y1, @Pen, Bpp);
855
856
  while RadX > 0 do
857
  begin
858
    if R > 0 then
859
    begin
860
      Inc(Y1);
861
      Dec(Y2);
862
      Inc(RY, DeltaX);
863
      Dec(R, RY);
864
    end;
865
    if R <= 0 then
866
    begin
867
      Dec(RadX);
868
      Inc(X1);
869
      Dec(X2);
870
      Dec(RX, DeltaY);
871
      Inc(R, RX);
872
    end;
873
874
    if (OldY <> Y1) and (FFillMode <> fmClear) then
875
    begin
876
      HorzLineInternal(X1, X2, Y1, @Fill, Bpp);
877
      HorzLineInternal(X1, X2, Y2, @Fill, Bpp);
878
    end;
879
    OldY := Y1;
880
881
    CopyPixelInternal(X1, Y1, @Pen, Bpp);
882
    CopyPixelInternal(X2, Y1, @Pen, Bpp);
883
    CopyPixelInternal(X1, Y2, @Pen, Bpp);
884
    CopyPixelInternal(X2, Y2, @Pen, Bpp);
885
  end;
886
end;
887
888
procedure TImagingCanvas.ApplyConvolution(Kernel: PLongInt; KernelSize,
889
  Divisor: LongInt; Bias: Single; ClampChannels: Boolean);
890
var
891
  X, Y, I, J, PosY, PosX, SizeDiv2, KernelValue, WidthBytes, Bpp: LongInt;
892
  R, G, B, DivFloat: Single;
893
  Pixel: TColorFPRec;
894
  TempImage: TImageData;
895
  DstPointer, SrcPointer: PByte;
896
begin
897
  SizeDiv2 := KernelSize div 2;
898
  DivFloat := IffFloat(Divisor > 1, 1.0 / Divisor, 1.0);
899
  Bpp := FFormatInfo.BytesPerPixel;
900
  WidthBytes := FPData.Width * Bpp;
901
902
  InitImage(TempImage);
903
  CloneImage(FPData^, TempImage);
904
905
  try
906
    // For every pixel in clip rect
907
    for Y := FClipRect.Top to FClipRect.Bottom - 1 do
908
    begin
909
      DstPointer := @PByteArray(FPData.Bits)[Y * WidthBytes + FClipRect.Left * Bpp];
910
911
      for X := FClipRect.Left to FClipRect.Right - 1 do
912
      begin
913
        // Reset accumulators
914
        R := 0.0;
915
        G := 0.0;
916
        B := 0.0;
917
918
        for J := 0 to KernelSize - 1 do
919
        begin
920
          PosY := ClampInt(Y + J - SizeDiv2, FClipRect.Top, FClipRect.Bottom);
921
922
          for I := 0 to KernelSize - 1 do
923
          begin
924
            PosX := ClampInt(X + I - SizeDiv2, FClipRect.Left, FClipRect.Right);
925
            SrcPointer := @PByteArray(TempImage.Bits)[PosY * WidthBytes + PosX * Bpp];
926
927
            // Get pixels from neighbourhood of current pixel and add their
928
            // colors to accumulators weighted by filter kernel values
929
            Pixel := FFormatInfo.GetPixelFP(SrcPointer, @FFormatInfo, TempImage.Palette);
930
            KernelValue := PLongIntArray(Kernel)[J * KernelSize + I];
931
932
            R := R + Pixel.R * KernelValue;
933
            G := G + Pixel.G * KernelValue;
934
            B := B + Pixel.B * KernelValue;
935
          end;
936
        end;
937
938
        Pixel := FFormatInfo.GetPixelFP(DstPointer, @FFormatInfo, FPData.Palette);
939
940
        Pixel.R := R * DivFloat + Bias;
941
        Pixel.G := G * DivFloat + Bias;
942
        Pixel.B := B * DivFloat + Bias;
943
944
        if ClampChannels then
945
          ClampFloatPixel(Pixel);
946
947
        // Set resulting pixel color
948
        FFormatInfo.SetPixelFP(DstPointer, @FFormatInfo, FPData.Palette, Pixel);
949
950
        Inc(DstPointer, Bpp);
951
      end;
952
    end;
953
954
  finally
955
    FreeImage(TempImage);
956
  end;
957
end;
958
959
procedure TImagingCanvas.ApplyConvolution3x3(const Filter: TConvolutionFilter3x3);
960
begin
961
  ApplyConvolution(@Filter.Kernel, 3, Filter.Divisor, Filter.Bias, True);
962
end;
963
964
procedure TImagingCanvas.ApplyConvolution5x5(const Filter: TConvolutionFilter5x5);
965
begin
966
  ApplyConvolution(@Filter.Kernel, 5, Filter.Divisor, Filter.Bias, True);
967
end;
968
969
class function TImagingCanvas.GetSupportedFormats: TImageFormats;
970
begin
971
  Result := [ifIndex8..Pred(ifDXT1)];
972
end;
973
974
975
{ TFastARGB32Canvas }
976
977
destructor TFastARGB32Canvas.Destroy;
978
begin
979
  FreeMem(FScanlines);
980
  inherited Destroy;
981
end;
982
983
function TFastARGB32Canvas.GetPixel32(X, Y: LongInt): TColor32;
984
begin
985
  Result := FScanlines[Y, X].Color;
986
end;
987
988
procedure TFastARGB32Canvas.SetPixel32(X, Y: LongInt; const Value: TColor32);
989
begin
990
  if (X >= FClipRect.Left) and (Y >= FClipRect.Top) and
991
    (X < FClipRect.Right) and (Y < FClipRect.Bottom) then
992
  begin
993
    FScanlines[Y, X].Color := Value;
994
  end;
995
end;
996
997
procedure TFastARGB32Canvas.UpdateCanvasState;
998
var
999
  I: LongInt;
1000
  ScanPos: PLongWord;
1001
begin
1002
  inherited UpdateCanvasState;
1003
1004
  // Realloc and update scanline array
1005
  ReallocMem(FScanlines, FPData.Height * SizeOf(PColor32RecArray));
1006
  ScanPos := FPData.Bits;
1007
1008
  for I := 0 to FPData.Height - 1 do
1009
  begin
1010
    FScanlines[I] := PColor32RecArray(ScanPos);
1011
    Inc(ScanPos, FPData.Width);
1012
  end;
1013
end;
1014
1015
class function TFastARGB32Canvas.GetSupportedFormats: TImageFormats;
1016
begin
1017
  Result := [ifA8R8G8B8];
1018
end;
1019
1020
initialization
1021
  RegisterCanvas(TFastARGB32Canvas);
1022
1023
finalization
1024
  FreeAndNil(CanvasClasses);
1025
1026
{
1027
  File Notes:
1028
1029
  -- TODOS ----------------------------------------------------
1030
    - more more more ... 
1031
    - implement pen width everywhere
1032
    - add blending (image and object drawing)
1033
    - add image drawing
1034
    - more objects (arc, polygon)
1035
    - add channel write/read masks (like apply conv only on Red channel,...) 
1036
1037
  -- 0.21 Changes/Bug Fixes -----------------------------------
1038
    - Added some new filter kernels for convolution.
1039
    - Added FillMode and PenMode properties.
1040
    - Added FrameRect, Rectangle, Ellipse, and Line methods.
1041
    - Removed HorzLine and VertLine from TFastARGB32Canvas - new versions
1042
      in general canvas is now as fast as those in TFastARGB32Canvas
1043
      (only in case of A8R8G8B8 images of course). 
1044
    - Added PenWidth property, updated HorzLine and VertLine to use it.
1045
1046
  -- 0.19 Changes/Bug Fixes -----------------------------------
1047
    - added TFastARGB32Canvas
1048
    - added convolutions, hline, vline
1049
    - unit created, intial stuff added
1050
1051
}
1052
1053
end.
1054