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