1 // SPDX-License-Identifier: GPL-3.0-only
2 unit ULayerAction;
3 
4 {$mode objfpc}{$H+}
5 
6 interface
7 
8 uses
9   Classes, SysUtils, BGRABitmap, BGRABitmapTypes, UImageType,
10   UStateType, UImageState;
11 
12 type
13   TNotifyChangeEvent = procedure(ASender: TObject; ALayer: TBGRABitmap; ARect: TRect) of object;
14   TNotifyUndoEvent = procedure(ASender: TObject; AUndo: TCustomImageDifference; var Owned: boolean) of object;
15 
16   { TLayerAction }
17 
18   TLayerAction = class(TCustomLayerAction)
19   private
20     FChangeBoundsNotified: boolean;
21     FImageState: TImageState;
22     FOnDestroy: TNotifyEvent;
23     FOnNotifyChange: TNotifyChangeEvent;
24     FOnNotifyUndo: TNotifyUndoEvent;
25     FPrediff: TComposedImageDifference;
26     FBackupSelectedLayer, FBackupSelectionLayer, FBackupSelection: TBGRABitmap;
27     FBackupSelectedLayerDefined, FBackupSelectionLayerDefined, FBackupSelectionMaskDefined: boolean;
28     FSelectedImageLayerChangedArea, FSelectionLayerChangedArea, FSelectionMaskChangedArea: TRect;
29     FDone: boolean;
30     FOnTryStop: TOnTryStopEventHandler;
GetBackupDrawingLayernull31     function GetBackupDrawingLayer: TBGRABitmap;
GetBackupSelectedLayernull32     function GetBackupSelectedLayer: TBGRABitmap;
GetBackupSelectionnull33     function GetBackupSelection: TBGRABitmap;
GetBackupSelectionLayernull34     function GetBackupSelectionLayer: TBGRABitmap;
GetCurrentSelectionnull35     function GetCurrentSelection: TBGRABitmap;
GetCurrentStatenull36     function GetCurrentState: TImageState;
GetSelectedImageLayernull37     function GetSelectedImageLayer: TBGRABitmap;
GetDrawingLayernull38     function GetDrawingLayer: TBGRABitmap;
GetSelectedImageLayerOffsetnull39     function GetSelectedImageLayerOffset: TPoint;
GetSelectionLayerBoundsnull40     function GetSelectionLayerBounds: TRect;
41     procedure SetOnDestroy(AValue: TNotifyEvent);
42     procedure SetOnNotifyChange(AValue: TNotifyChangeEvent);
43     procedure SetOnNotifyUndo(AValue: TNotifyUndoEvent);
44   protected
45     procedure Cancel;
46     procedure NeedSelectionMaskBackup;
47     procedure NeedSelectedLayerBackup;
48     procedure NeedSelectionLayerBackup;
49     property CurrentState: TImageState read GetCurrentState;
50   public
51     constructor Create(AState: TImageState; AApplyOfsBefore: boolean = false; AApplySelectionTransformBefore: boolean = false);
52     procedure Validate;
53     procedure PartialValidate(ADiscardBackup: boolean = false);
54     procedure PartialCancel;
55     destructor Destroy; override;
56 
57     procedure QuerySelection;
58     procedure RemoveSelection;
59     procedure EraseSelectionInBitmap;
60     procedure MergeWithSelection(AApplyMask: boolean = true);
61     procedure ReleaseSelection;
62     procedure RetrieveSelection;
RetrieveSelectionIfLayerEmptynull63     function RetrieveSelectionIfLayerEmpty(removeFromBitmap: boolean = false): boolean;
64     procedure ApplySelectionMask;
65 
66     procedure ReplaceSelectionLayer(bmp: TBGRABitmap; AOwned: boolean);
67     procedure ReplaceCurrentSelection(AValue: TBGRABitmap);
68     procedure NotifyChange(ADest: TBGRABitmap; ARect: TRect);
69     procedure RestoreSelectionMask;
70     procedure RestoreDrawingLayer;
71     procedure RestoreSelectedLayer;
72     procedure RestoreSelectionLayer;
73 
74     procedure TryStop; override;
75 
GetOrCreateSelectionLayernull76     function GetOrCreateSelectionLayer: TBGRABitmap;
GetSelectionLayerIfExistsnull77     function GetSelectionLayerIfExists: TBGRABitmap;
78     property SelectedImageLayer: TBGRABitmap read GetSelectedImageLayer;
79     property SelectedImageLayerOffset: TPoint read GetSelectedImageLayerOffset;
80     property DrawingLayer: TBGRABitmap read GetDrawingLayer;
81     property CurrentSelection: TBGRABitmap read GetCurrentSelection;
82     property BackupSelection: TBGRABitmap read GetBackupSelection;
83     property BackupSelectionLayer: TBGRABitmap read GetBackupSelectionLayer;
84     property BackupSelectedLayer: TBGRABitmap read GetBackupSelectedLayer;
85     property BackupDrawingLayer: TBGRABitmap read GetBackupDrawingLayer;
86     property OnTryStop: TOnTryStopEventHandler read FOnTryStop write FOnTryStop;
87     property Done: boolean read FDone;
88     property ChangeBoundsNotified: boolean read FChangeBoundsNotified write FChangeBoundsNotified;
89     property SelectionLayerBounds: TRect read GetSelectionLayerBounds;
90     property OnNotifyChange: TNotifyChangeEvent read FOnNotifyChange write SetOnNotifyChange;
91     property OnNotifyUndo: TNotifyUndoEvent read FOnNotifyUndo write SetOnNotifyUndo;
92     property OnDestroy: TNotifyEvent read FOnDestroy write SetOnDestroy;
93     property Prediff: TComposedImageDifference read FPrediff;
94   end;
95 
96 implementation
97 
98 uses UGraph, Types, Dialogs, BGRATransform, BGRALayerOriginal, UImageDiff;
99 
100 { TLayerAction }
101 
GetSelectedImageLayernull102 function TLayerAction.GetSelectedImageLayer: TBGRABitmap;
103 begin
104   result := CurrentState.SelectedImageLayer;
105   if not Assigned(result) then
106     raise exception.Create('No image layer selected');
107   NeedSelectedLayerBackup;
108 end;
109 
TLayerAction.GetCurrentSelectionnull110 function TLayerAction.GetCurrentSelection: TBGRABitmap;
111 begin
112   NeedSelectionMaskBackup;
113   result := CurrentState.SelectionMask;
114 end;
115 
GetCurrentStatenull116 function TLayerAction.GetCurrentState: TImageState;
117 begin
118   result := FImageState;
119 end;
120 
GetBackupSelectedLayernull121 function TLayerAction.GetBackupSelectedLayer: TBGRABitmap;
122 begin
123   NeedSelectedLayerBackup;
124   result := FBackupSelectedLayer;
125 end;
126 
TLayerAction.GetBackupDrawingLayernull127 function TLayerAction.GetBackupDrawingLayer: TBGRABitmap;
128 begin
129   if CurrentState.SelectionMaskEmpty then result := BackupSelectedLayer else
130     result := BackupSelectionLayer;
131 end;
132 
TLayerAction.GetBackupSelectionnull133 function TLayerAction.GetBackupSelection: TBGRABitmap;
134 begin
135   NeedSelectionMaskBackup;
136   result := FBackupSelection;
137 end;
138 
TLayerAction.GetBackupSelectionLayernull139 function TLayerAction.GetBackupSelectionLayer: TBGRABitmap;
140 begin
141   NeedSelectionLayerBackup;
142   result := FBackupSelectionLayer;
143 end;
144 
TLayerAction.GetDrawingLayernull145 function TLayerAction.GetDrawingLayer: TBGRABitmap;
146 begin
147   if CurrentState.SelectionMaskEmpty then result := GetSelectedImageLayer else
148     result := GetOrCreateSelectionLayer;
149 end;
150 
GetSelectedImageLayerOffsetnull151 function TLayerAction.GetSelectedImageLayerOffset: TPoint;
152 begin
153   result := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
154 end;
155 
GetSelectionLayerBoundsnull156 function TLayerAction.GetSelectionLayerBounds: TRect;
157 begin
158   result := CurrentState.GetSelectionLayerBounds;
159 end;
160 
161 procedure TLayerAction.SetOnDestroy(AValue: TNotifyEvent);
162 begin
163   if FOnDestroy=AValue then Exit;
164   FOnDestroy:=AValue;
165 end;
166 
167 procedure TLayerAction.SetOnNotifyChange(AValue: TNotifyChangeEvent);
168 begin
169   if FOnNotifyChange=AValue then Exit;
170   FOnNotifyChange:=AValue;
171 end;
172 
173 procedure TLayerAction.SetOnNotifyUndo(AValue: TNotifyUndoEvent);
174 begin
175   if FOnNotifyUndo=AValue then Exit;
176   FOnNotifyUndo:=AValue;
177 end;
178 
179 procedure TLayerAction.Cancel;
180 begin
181   if FDone then raise Exception.Create('Already done');
182   RestoreSelectedLayer;
183   RestoreSelectionLayer;
184   RestoreSelectionMask;
185   if Assigned(FPrediff) then
186   begin
187     FPrediff.UnapplyTo(CurrentState);
188     if (FPrediff.Kind in [idkChangeImageAndSelection,idkChangeSelection]) and
189        Assigned(FImageState.SelectionMask) then
190       NotifyChange(FImageState.SelectionMask, rect(0,0,FImageState.SelectionMask.Width,FImageState.SelectionMask.Height));
191     FreeAndNil(FPrediff);
192   end;
193   FDone := true;
194 end;
195 
196 procedure TLayerAction.NeedSelectionMaskBackup;
197 begin
198   if not FBackupSelectionMaskDefined then
199   begin
200     FBackupSelection := DuplicateBitmap(CurrentState.SelectionMask);
201     FBackupSelectionMaskDefined := true;
202   end;
203 end;
204 
205 procedure TLayerAction.NeedSelectedLayerBackup;
206 begin
207   if not FBackupSelectedLayerDefined then
208   begin
209     FBackupSelectedLayer := DuplicateBitmap(CurrentState.SelectedImageLayer);
210     FBackupSelectedLayerDefined := true;
211   end;
212 end;
213 
214 procedure TLayerAction.NeedSelectionLayerBackup;
215 begin
216   if not FBackupSelectionLayerDefined then
217   begin
218     FBackupSelectionLayer := DuplicateBitmap(CurrentState.SelectionLayer);
219     FBackupSelectionLayerDefined := true;
220   end;
221 end;
222 
223 constructor TLayerAction.Create(AState: TImageState; AApplyOfsBefore: boolean;
224   AApplySelectionTransformBefore: boolean);
225 var
226   layerOfsDiff,selTransfDiff: TCustomImageDifference;
227 begin
228   FImageState := AState;
229   FBackupSelectedLayer := nil;
230   FBackupSelection := nil;
231   FBackupSelectionLayer := nil;
232   FBackupSelectedLayerDefined := false;
233   FBackupSelectionMaskDefined := false;
234   FBackupSelectionLayerDefined := false;
235   FSelectedImageLayerChangedArea := EmptyRect;
236   FSelectionLayerChangedArea := EmptyRect;
237   FSelectionMaskChangedArea := EmptyRect;
238   FDone := false;
239   FPrediff := TComposedImageDifference.Create;
240   if AApplyOfsBefore then
241   begin
242     with CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex] do
243       layerOfsDiff := CurrentState.ComputeLayerOffsetDifference(X,Y);
244     if layerOfsDiff.IsIdentity then FreeAndNil(layerOfsDiff)
245     else
246     begin
247       layerOfsDiff.ApplyTo(CurrentState);
248       FPrediff.Add(layerOfsDiff);
249     end;
250   end;
251   if AApplySelectionTransformBefore and not IsAffineMatrixIdentity(CurrentState.SelectionTransform) then
252   begin
253     selTransfDiff := CurrentState.ComputeSelectionTransformDifference;
254     if selTransfDiff.IsIdentity then FreeAndNil(selTransfDiff)
255     else
256     begin
257       selTransfDiff.ApplyTo(CurrentState);
258       FPrediff.Add(selTransfDiff);
259     end;
260   end;
261   if FPrediff.Count = 0 then FreeAndNil(FPrediff);
262 end;
263 
264 destructor TLayerAction.Destroy;
265 begin
266   if not FDone then Cancel;
267   FPrediff.Free;
268   FBackupSelectedLayer.Free;
269   FBackupSelection.Free;
270   FBackupSelectionLayer.Free;
271   if Assigned(FOnDestroy) then FOnDestroy(self);
272   inherited Destroy;
273 end;
274 
275 procedure TLayerAction.ReplaceCurrentSelection(AValue: TBGRABitmap);
276 begin
277   if AValue = CurrentState.SelectionMask then exit;
278   NeedSelectionMaskBackup;
279   if Assigned(AValue) and Assigned(CurrentState.SelectionMask) and
280     (AValue.Width = CurrentState.SelectionMask.Width) and
281     (AValue.Height = CurrentState.SelectionMask.Height) then
282     NotifyChange(CurrentState.SelectionMask, AValue.GetDifferenceBounds(CurrentState.SelectionMask))
283   else
284   begin
285     if Assigned(CurrentState.SelectionMask) then
286       NotifyChange(CurrentState.SelectionMask, rect(0,0,CurrentState.SelectionMask.Width,CurrentState.SelectionMask.Height));
287     if Assigned(AValue) then
288       NotifyChange(AValue, rect(0,0,AValue.Width,AValue.Height));
289   end;
290   CurrentState.SelectionMask.Free;
291   CurrentState.SelectionMask := AValue
292 end;
293 
294 procedure TLayerAction.NotifyChange(ADest: TBGRABitmap; ARect: TRect);
295 begin
296   if ADest = nil then exit;
297   if not IntersectRect(ARect, ARect, rect(0,0,CurrentState.Width,CurrentState.Height)) then exit;
298   if ADest = CurrentState.SelectionMask then
299     FSelectionMaskChangedArea := RectUnion(FSelectionMaskChangedArea, ARect)
300   else if ADest = CurrentState.SelectedImageLayer then
301     FSelectedImageLayerChangedArea := RectUnion(FSelectedImageLayerChangedArea, ARect)
302   else if ADest = CurrentState.SelectionLayer then
303     FSelectionLayerChangedArea := RectUnion(FSelectionLayerChangedArea, ARect);
304   if Assigned(FOnNotifyChange) then
305     FOnNotifyChange(self, ADest, ARect);
306 end;
307 
308 procedure TLayerAction.RestoreSelectionMask;
309 var prevClip: TRect;
310 begin
311   if FBackupSelectionMaskDefined then
312   begin
313     if not ChangeBoundsNotified then FSelectionMaskChangedArea := rect(0,0,CurrentState.Width,CurrentState.Height);
314     if IsRectEmpty(FSelectionMaskChangedArea) then exit;
315     prevClip := CurrentState.SelectionMask.ClipRect;
316     CurrentState.SelectionMask.ClipRect := FSelectionMaskChangedArea;
317     if Assigned(FBackupSelection) then
318       CurrentState.SelectionMask.PutImage(0,0,FBackupSelection,dmSet)
319     else
320       CurrentState.SelectionMask.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRABlack,dmSet);
321     CurrentState.SelectionMask.ClipRect := prevClip;
322     If Assigned(FOnNotifyChange) then
323       FOnNotifyChange(self, CurrentState.SelectionMask, FSelectionMaskChangedArea);
324     FSelectionMaskChangedArea := EmptyRect;
325   end;
326 end;
327 
328 procedure TLayerAction.RestoreDrawingLayer;
329 begin
330   if CurrentState.SelectionMaskEmpty then RestoreSelectedLayer
331     else RestoreSelectionLayer;
332 end;
333 
334 procedure TLayerAction.RestoreSelectedLayer;
335 var prevClip: TRect;
336 begin
337   if FBackupSelectedLayerDefined then
338   begin
339     if not ChangeBoundsNotified then FSelectedImageLayerChangedArea := rect(0,0,CurrentState.Width,CurrentState.Height);
340     if IsRectEmpty(FSelectedImageLayerChangedArea) then exit;
341     prevClip := CurrentState.SelectedImageLayer.ClipRect;
342     CurrentState.SelectedImageLayer.ClipRect := FSelectedImageLayerChangedArea;
343     if Assigned(FBackupSelectedLayer) then
344       CurrentState.SelectedImageLayer.PutImage(0,0,FBackupSelectedLayer,dmSet)
345     else
346       CurrentState.SelectedImageLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
347     CurrentState.SelectedImageLayer.ClipRect := prevClip;
348     If Assigned(FOnNotifyChange) then
349       FOnNotifyChange(self, CurrentState.SelectedImageLayer, FSelectedImageLayerChangedArea);
350     FSelectedImageLayerChangedArea := EmptyRect;
351   end;
352 end;
353 
354 procedure TLayerAction.RestoreSelectionLayer;
355 var prevClip: TRect;
356 begin
357   if FBackupSelectionLayerDefined and (CurrentState.SelectionLayer <> nil) then
358   begin
359     if not ChangeBoundsNotified then FSelectionLayerChangedArea := rect(0,0,CurrentState.Width,CurrentState.Height);
360     if IsRectEmpty(FSelectionLayerChangedArea) then exit;
361     prevClip := CurrentState.SelectionLayer.ClipRect;
362     CurrentState.SelectionLayer.ClipRect := FSelectionLayerChangedArea;
363     if Assigned(FBackupSelectionLayer) then
364       CurrentState.SelectionLayer.PutImage(0,0,FBackupSelectionLayer,dmSet)
365     else
366       CurrentState.SelectionLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
367     CurrentState.SelectionLayer.ClipRect := prevClip;
368     If Assigned(FOnNotifyChange) then
369       FOnNotifyChange(self, CurrentState.SelectionLayer, FSelectionLayerChangedArea);
370     FSelectionLayerChangedArea := EmptyRect;
371   end;
372 end;
373 
374 procedure TLayerAction.TryStop;
375 begin
376   if Assigned(FOnTryStop) then
377     FOnTryStop(self);
378 end;
379 
380 procedure TLayerAction.QuerySelection;
381 begin
382   NeedSelectionMaskBackup;
383   CurrentState.QuerySelectionMask;
384 end;
385 
386 procedure TLayerAction.RemoveSelection;
387 var bounds: TRect;
388 begin
389   if not CurrentState.SelectionMaskEmpty or (CurrentState.SelectionLayer <> nil) then
390   begin
391     NeedSelectionMaskBackup;
392     NeedSelectionLayerBackup;
393     bounds := CurrentState.GetTransformedSelectionMaskBounds;
394     NotifyChange(CurrentState.SelectionLayer, bounds);
395     CurrentState.RemoveSelection;
396   end;
397 end;
398 
399 procedure TLayerAction.EraseSelectionInBitmap;
400 var offs: TPoint;
401   r: TRect;
402 begin
403   if not CurrentState.SelectionMaskEmpty then
404   begin
405     NeedSelectedLayerBackup;
406     offs := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
407     r := CurrentState.GetSelectionMaskBounds;
408     SubstractMask(GetSelectedImageLayer,-offs.X+r.left,-offs.Y+r.top,CurrentState.SelectionMask,r);
409     OffsetRect(r,-offs.x,-offs.y);
410     NotifyChange(GetSelectedImageLayer,r);
411   end;
412 end;
413 
414 procedure TLayerAction.MergeWithSelection(AApplyMask: boolean);
415 var offs: TPoint;
416   sourceRect,destRect: TRect;
417 begin
418   if not IsAffineMatrixIdentity(CurrentState.SelectionTransform) then raise exception.Create('Unexpected selection transform');
419   if not CurrentState.SelectionLayerEmpty and not (AApplyMask and CurrentState.SelectionMaskEmpty) then
420   begin
421     sourceRect := CurrentState.GetSelectionLayerBounds;
422     if AApplyMask then
423     begin
424       CurrentState.SelectionLayer.ApplyMask(CurrentState.SelectionMask,CurrentState.GetSelectionLayerBounds);
425       IntersectRect(sourceRect,sourceRect,CurrentState.GetSelectionMaskBounds);
426       NotifyChange(CurrentState.SelectionLayer,CurrentState.GetSelectionLayerBounds);
427     end;
428     offs := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
429     destRect := sourceRect;
430     OffsetRect(destRect, -offs.x,-offs.y);
431     GetSelectedImageLayer.PutImagePart(destRect.left,destRect.top,CurrentState.SelectionLayer,sourceRect,dmDrawWithTransparency);
432     NotifyChange(GetSelectedImageLayer,destRect);
433     CurrentState.ReplaceSelectionLayer(nil,true);
434   end;
435 end;
436 
437 procedure TLayerAction.ReleaseSelection;
438 var bounds: TRect;
439 begin
440   if not IsAffineMatrixIdentity(CurrentState.SelectionTransform) then raise exception.Create('Unexpected selection transform');
441   if not CurrentState.SelectionMaskEmpty then
442   begin
443     bounds := CurrentState.GetSelectionMaskBounds;
444     NeedSelectionMaskBackup;
445     NotifyChange(CurrentState.SelectionMask, bounds);
446     if CurrentState.SelectionLayer <> nil then
447     begin
448       NeedSelectedLayerBackup;
449       NotifyChange(CurrentState.SelectedImageLayer, bounds);
450       NeedSelectionLayerBackup;
451       NotifyChange(CurrentState.SelectionLayer, bounds);
452     end;
453 
454     ApplySelectionMask;
455     CurrentState.SelectionMask.Free;
456     CurrentState.SelectionMask := nil;
457     MergeWithSelection(False);
458   end;
459 end;
460 
461 procedure TLayerAction.RetrieveSelection;
462 var temp : TBGRABitmap;
463   offs: TPoint;
464   r, maskBounds: TRect;
465 begin
466   if not IsAffineMatrixIdentity(CurrentState.SelectionTransform) then raise exception.Create('Unexpected selection transform');
467   if not CurrentState.SelectionMaskEmpty then
468   begin
469     NeedSelectedLayerBackup;
470     NeedSelectionLayerBackup;
471     MergeWithSelection;
472     offs := CurrentState.LayerOffset[CurrentState.SelectedImageLayerIndex];
473     maskBounds := CurrentState.GetSelectionMaskBounds;
474     r := maskBounds;
475     OffsetRect(r, -offs.x, -offs.y);
476     IntersectRect(r, r, rect(0,0,GetSelectedImageLayer.Width,GetSelectedImageLayer.Height));
477     temp := TBGRABitmap.Create(CurrentState.Width,CurrentState.Height);
478     temp.PutImagePart(r.left+offs.x,r.top+offs.y,GetSelectedImageLayer,r,dmSet);
479     temp.ApplyMask(CurrentState.SelectionMask,maskBounds);
480     BGRAReplace(CurrentState.SelectionLayer,temp);
481     NotifyChange(CurrentState.SelectionLayer,maskBounds);
482   end;
483 end;
484 
RetrieveSelectionIfLayerEmptynull485 function TLayerAction.RetrieveSelectionIfLayerEmpty(removeFromBitmap: boolean): boolean;
486 begin
487   if not IsAffineMatrixIdentity(CurrentState.SelectionTransform) then raise exception.Create('Unexpected selection transform');
488   NeedSelectedLayerBackup;
489   NeedSelectionLayerBackup;
490   if CurrentState.SelectionLayerEmpty then
491   begin
492     RetrieveSelection;
493     if removeFromBitmap then EraseSelectionInBitmap;
494     result := true;
495   end
496   else result := false;
497 end;
498 
TLayerAction.GetOrCreateSelectionLayernull499 function TLayerAction.GetOrCreateSelectionLayer: TBGRABitmap;
500 begin
501   NeedSelectionLayerBackup;
502   result := CurrentState.GetOrCreateSelectionLayer;
503 end;
504 
TLayerAction.GetSelectionLayerIfExistsnull505 function TLayerAction.GetSelectionLayerIfExists: TBGRABitmap;
506 begin
507   NeedSelectionLayerBackup;
508   result := CurrentState.SelectionLayer;
509 end;
510 
511 procedure TLayerAction.ReplaceSelectionLayer(bmp: TBGRABitmap; AOwned: boolean);
512 var dest:TBGRABitmap;
513 begin
514   NeedSelectionLayerBackup;
515   dest := GetSelectionLayerIfExists;
516   if (dest <> nil) and (bmp <> nil) and (bmp.Width = dest.Width) and
517     (bmp.Height = dest.Height) then
518     NotifyChange(dest, bmp.GetDifferenceBounds(dest))
519   else
520   begin
521     if dest <> nil then NotifyChange(dest, dest.GetImageBounds);
522     if bmp <> nil then NotifyChange(bmp, bmp.GetImageBounds);
523   end;
524   CurrentState.ReplaceSelectionLayer(bmp,AOwned);
525 end;
526 
527 procedure TLayerAction.ApplySelectionMask;
528 var r: TRect;
529 begin
530   NeedSelectionLayerBackup;
531   if (CurrentState.SelectionMask <> nil) and (CurrentState.SelectionLayer <> nil) then
532   begin
533     r := GetSelectionLayerBounds;
534     if not IsRectEmpty(r) then
535     begin
536       GetOrCreateSelectionLayer.ApplyMask(CurrentState.SelectionMask,r);
537       NotifyChange(CurrentState.GetOrCreateSelectionLayer,r);
538     end;
539   end;
540 end;
541 
542 procedure TLayerAction.Validate;
543 begin
544   if FDone then raise Exception.Create('Already done');
545   PartialValidate(True);
546   FDone := true;
547 end;
548 
549 procedure TLayerAction.PartialCancel;
550 begin
551   RestoreSelectedLayer;
552   RestoreSelectionLayer;
553   RestoreSelectionMask;
554 end;
555 
556 procedure TLayerAction.PartialValidate(ADiscardBackup: boolean = false);
557 var
558   imgDiff: TImageLayerStateDifference;
559   composedDiff: TComposedImageDifference;
560   owned, rasterizeOriginal: boolean;
561 
562   procedure NotifyPrediff;
563   begin
564     if Assigned(FPrediff) then
565     begin
566       if Assigned(FOnNotifyUndo) then
567       begin
568         owned := false;
569         FOnNotifyUndo(self, FPrediff, owned);
570         if not owned then FPrediff.Free;
571       end else
572         FPrediff.Free;
573       FPrediff := nil;
574     end;
575   end;
576 
577 begin
578   if (FBackupSelectedLayerDefined or FBackupSelectionMaskDefined or FBackupSelectionLayerDefined) and
579      not (ChangeBoundsNotified and IsRectEmpty(FSelectedImageLayerChangedArea) and IsRectEmpty(FSelectionMaskChangedArea) and
580          IsRectEmpty(FSelectionLayerChangedArea)) then
581   begin
582     if FBackupSelectionLayerDefined then
583     begin
584       if ChangeBoundsNotified then
585         CurrentState.DiscardSelectionLayerBounds(FSelectionLayerChangedArea)
586         else CurrentState.DiscardSelectionLayerBoundsCompletely;
587       if CurrentState.SelectionLayerEmpty then
588         CurrentState.ReplaceSelectionLayer(nil,True);
589     end;
590     if FBackupSelectionMaskDefined then
591     begin
592       if ChangeBoundsNotified then
593         CurrentState.DiscardSelectionMaskBounds(FSelectionMaskChangedArea)
594         else CurrentState.DiscardSelectionMaskBoundsCompletely;
595       if CurrentState.SelectionMaskEmpty then
596         CurrentState.RemoveSelection;
597     end;
598 
599     if ChangeBoundsNotified then
600       imgDiff := CurrentState.ComputeLayerDifference(FBackupSelectedLayer, FSelectedImageLayerChangedArea,
601         FBackupSelection, FSelectionMaskChangedArea,
602         FBackupSelectionLayer, FSelectionLayerChangedArea) as TImageLayerStateDifference
603     else
604       imgDiff := CurrentState.ComputeLayerDifference(FBackupSelectedLayer, FBackupSelectedLayerDefined,
605         FBackupSelection, FBackupSelectionMaskDefined,
606         FBackupSelectionLayer, FBackupSelectionLayerDefined) as TImageLayerStateDifference;
607     if imgDiff.IsIdentity then FreeAndNil(imgDiff);
608 
609     if ADiscardBackup then
610     begin
611       FreeAndNil(FBackupSelectionLayer);
612       FreeAndNil(FBackupSelectedLayer);
613       FreeAndNil(FBackupSelection);
614       FBackupSelectedLayerDefined := false;
615       FBackupSelectedLayerDefined := false;
616       FBackupSelectionMaskDefined := false;
617     end else
618     begin
619       if FBackupSelectionLayerDefined then
620       begin
621         if (FBackupSelectionLayer = nil) and (CurrentState.SelectionLayer <> nil) then
622         begin
623           if not CurrentState.SelectionLayerEmpty then
624             FBackupSelectionLayer := CurrentState.SelectionLayer.Duplicate as TBGRABitmap;
625         end else
626         if Assigned(FBackupSelectionLayer) then
627         begin
628           if ChangeBoundsNotified then FBackupSelectionLayer.ClipRect := FSelectionLayerChangedArea;
629           if Assigned(CurrentState.SelectionLayer) then
630             FBackupSelectionLayer.PutImage(0,0,CurrentState.SelectionLayer,dmSet)
631           else
632             FBackupSelectionLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
633           FBackupSelectionLayer.NoClip;
634         end;
635         FSelectionLayerChangedArea := EmptyRect;
636       end;
637       if FBackupSelectedLayerDefined then
638       begin
639         if ChangeBoundsNotified then FBackupSelectedLayer.ClipRect := FSelectedImageLayerChangedArea;
640         if Assigned(CurrentState.SelectedImageLayer) then
641           FBackupSelectedLayer.PutImage(0,0,CurrentState.SelectedImageLayer,dmSet)
642         else
643           FBackupSelectedLayer.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRAPixelTransparent,dmSet);
644         FBackupSelectedLayer.NoClip;
645         FSelectedImageLayerChangedArea := EmptyRect;
646       end;
647       if FBackupSelectionMaskDefined then
648       begin
649         if (FBackupSelection = nil) and (CurrentState.SelectionMask <> nil) then
650         begin
651           if not CurrentState.SelectionMaskEmpty then
652             FBackupSelection := CurrentState.SelectionMask.Duplicate as TBGRABitmap;
653         end else
654         if (FBackupSelection <> nil) then
655         begin
656           if ChangeBoundsNotified then FBackupSelection.ClipRect := FSelectionMaskChangedArea;
657           if Assigned(CurrentState.SelectionMask) then
658             FBackupSelection.PutImage(0,0,CurrentState.SelectionMask,dmSet)
659           else
660             FBackupSelection.FillRect(0,0,CurrentState.Width,CurrentState.Height,BGRABlack,dmSet);
661           FBackupSelection.NoClip;
662         end;
663         FSelectionMaskChangedArea := EmptyRect;
664       end;
665     end;
666 
667     if assigned(imgDiff) then
668     begin
669       rasterizeOriginal := CurrentState.LayerOriginalDefined[CurrentState.SelectedImageLayerIndex] and imgDiff.ChangeImageLayer;
670       if Assigned(FPrediff) or rasterizeOriginal then
671       begin
672         composedDiff := TComposedImageDifference.Create;
673         if Assigned(FPrediff) then
674         begin
675           composedDiff.AddRange(FPrediff);
676           FPrediff.ReleaseDiffs;
677           FreeAndNil(FPrediff);
678         end;
679         if rasterizeOriginal then
680           composedDiff.Add(TDiscardOriginalDifference.Create(CurrentState,
681             CurrentState.SelectedImageLayerIndex, true));
682         composedDiff.Add(imgDiff);
683         if Assigned(FOnNotifyUndo) then
684         begin
685           owned := false;
686           FOnNotifyUndo(self, composedDiff, owned);
687           if not owned then composedDiff.Free;
688         end else
689           composedDiff.Free;
690       end else
691       begin
692         if Assigned(FOnNotifyUndo) then
693         begin
694           owned := false;
695           FOnNotifyUndo(self, imgDiff, owned);
696           if not owned then imgDiff.Free;
697         end else
698           imgDiff.Free;
699       end;
700     end else NotifyPrediff;
701   end else NotifyPrediff;
702 end;
703 
704 
705 end.
706 
707