1 // SPDX-License-Identifier: GPL-3.0-only
2 unit UStateType;
3 
4 {$mode objfpc}
5 
6 interface
7 
8 uses
9   Types, Classes, SysUtils, BGRABitmap, BGRABitmapTypes, BGRALayers,
10   BGRALayerOriginal, fgl, UImageBackup;
11 
12 const MinSizeToCompress = 512; //set to 1 if you want always compression
13 const MinSerializedSize = 16384;
14 
15 type
16   TState = class;
17 
18   { TStateDifference }
19 
20   TStateDifference = class
TryCompressnull21     function TryCompress: boolean; virtual;
22     procedure ApplyTo(AState: TState); virtual; abstract;
23     procedure UnapplyTo(AState: TState); virtual; abstract;
UsedMemorynull24     function UsedMemory: int64; virtual;
ToStringnull25     function ToString: ansistring; override;
26   end;
27 
28   TState = class
29     saved: boolean;
30     procedure ApplyDifference(ADifference: TStateDifference); virtual; abstract;
31     procedure ReverseDifference(ADifference: TStateDifference); virtual; abstract;
Duplicatenull32     function Duplicate: TState; virtual; abstract;
33   end;
34 
35   TImageDifferenceKind = (idkChangeImageAndSelection, idkChangeStack, idkChangeSelection,
36                            idkChangeImage);
37 
38   { TCustomImageDifference }
39 
40   TCustomImageDifference = class(TStateDifference)
41   protected
42     FSavedBefore, FSavedAfter: boolean;
GetIsIdentitynull43     function GetIsIdentity: boolean; virtual;
GetImageDifferenceKindnull44     function GetImageDifferenceKind: TImageDifferenceKind; virtual;
GetChangingBoundsnull45     function GetChangingBounds: TRect; virtual;
GetChangingBoundsDefinednull46     function GetChangingBoundsDefined: boolean; virtual;
GetCostnull47     function GetCost: integer; virtual;
48   public
49     constructor Create(AFromState: TState; AToState: TState);
50     constructor Create(AFromState: TState);
51     constructor Create(ASavedBefore : boolean; ASavedAfter: boolean);
52     procedure ApplyTo(AState: TState); override;
53     procedure UnapplyTo(AState: TState); override;
54     property SavedBefore: boolean read FSavedBefore write FSavedBefore;
55     property SavedAfter: boolean read FSavedAfter write FSavedAfter;
56     property Kind: TImageDifferenceKind read GetImageDifferenceKind;
57     property ChangingBounds: TRect read GetChangingBounds;
58     property ChangingBoundsDefined: boolean read GetChangingBoundsDefined;
59     property IsIdentity: boolean read GetIsIdentity;
60   end;
61 
62   TImageDifferenceList = specialize TFPGObjectList<TCustomImageDifference>;
63 
64   { TComposedImageDifference }
65 
66   TComposedImageDifference = class(TCustomImageDifference)
67   private
GetCountnull68     function GetCount: integer;
GetItemnull69     function GetItem(AIndex: integer): TCustomImageDifference;
GetTotalCountnull70     function GetTotalCount: integer;
71   protected
72     FDiffs: TImageDifferenceList;
73     FAgglutinate: boolean;
GetIsIdentitynull74     function GetIsIdentity: boolean; override;
GetImageDifferenceKindnull75     function GetImageDifferenceKind: TImageDifferenceKind; override;
GetChangingBoundsnull76     function GetChangingBounds: TRect; override;
GetChangingBoundsDefinednull77     function GetChangingBoundsDefined: boolean; override;
78   public
79     constructor Create(AAgglutinate: boolean = false);
80     procedure ReleaseDiffs;
81     procedure StopAgglutinate;
82     destructor Destroy; override;
TryCompressnull83     function TryCompress: boolean; override;
UsedMemorynull84     function UsedMemory: int64; override;
85     procedure Add(ADiff: TCustomImageDifference);
86     procedure AddRange(AComposed: TComposedImageDifference);
87     procedure ApplyTo(AState: TState); override;
88     procedure UnapplyTo(AState: TState); override;
89     procedure Clear;
90     procedure Delete(AIndex: integer);
91     procedure DeleteFrom(AIndex: integer);
GetLastnull92     function GetLast: TCustomImageDifference;
ToStringnull93     function ToString: ansistring; override;
94     property Count: integer read GetCount;
95     property TotalCount: integer read GetTotalCount;
96     property Agglutinate: boolean read FAgglutinate;
97     property Item[AIndex: integer]: TCustomImageDifference read GetItem; default;
98   end;
99 
100 {*********** Layer info *************}
101 
102 type
103   TLayerInfo = record
104     Id: integer;
105     Visible: boolean;
106     BlendOp: TBlendOperation;
107     Name: string;
108     Opactiy: byte;
109     Offset: TPoint;
110   end;
111 
112 procedure ApplyLayerInfo(AInfo: TLayerInfo; ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer);
GetLayerInfonull113 function GetLayerInfo(ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer): TLayerInfo;
114 
115 {*********** Inversible **************}
116 
117 type
118   TInversibleAction = (iaHorizontalFlip, iaHorizontalFlipLayer, iaVerticalFlip, iaVerticalFlipLayer, iaRotateCW, iaRotateCCW, iaRotate180, iaSwapRedBlue, iaLinearNegative);
119 
120 const
121   InversibleActionStr : array[TInversibleAction] of string =
122     ('HorizontalFlip', 'HorizontalFlipLayer', 'VerticalFlip', 'VerticalFlipLayer', 'RotateCW', 'RotateCCW', 'Rotate180', 'SwapRedBlue', 'LinearNegative');
123 
GetInverseActionnull124 function GetInverseAction(AAction: TInversibleAction): TInversibleAction;
CanCombineInversibleActionnull125 function CanCombineInversibleAction(AAction1, AAction2: TInversibleAction; out
126   ACombined: TInversibleAction): boolean;
127 
128 type
129   { TImageDiff }
130 
131   TImageDiff = class
132   private
133     FChangeRect: TRect;
134     FGrayscale: boolean;
135     FSavedFilename: string;
136     FImageBackup: TImageBackup;
137     FSizeBefore, FSizeAfter: TSize;
GetIsIdentitynull138     function GetIsIdentity: boolean;
139     procedure DiscardFile;
140     procedure Decompress;
141     procedure Init(Image1,Image2: TBGRABitmap; AGrayscale: boolean); overload;
142     procedure Init(Image1,Image2: TBGRABitmap; AGrayscale: boolean; AChangeRect: TRect); overload;
143   protected
CreateNewnull144     function CreateNew(AWidth, AHeight: integer): TBGRABitmap;
145   public
146     constructor Create(Image1: TBGRABitmap; AGrayscale: boolean);
147     constructor Create(Image1: TBGRABitmap; AGrayscale: boolean; AChangeRect: TRect);
148     constructor Create(Image1,Image2: TBGRABitmap; AGrayscale: boolean);
149     constructor Create(Image1,Image2: TBGRABitmap; AGrayscale: boolean; AChangeRect: TRect);
150     procedure ApplyInPlace(ADest: TBGRABitmap; AXor: boolean);
ApplyInNewnull151     function ApplyInNew(ASource: TBGRABitmap; AReverse: boolean; AXor: boolean): TBGRABitmap;
ApplyCanCreateNewnull152     function ApplyCanCreateNew(ASource: TBGRABitmap; AReverse: boolean; AXor: boolean): TBGRABitmap;
Compressnull153     function Compress: boolean;
154     destructor Destroy; override;
UsedMemorynull155     function UsedMemory: int64;
156     property ChangeRect: TRect read FChangeRect;
157     property IsIdentity: boolean read GetIsIdentity;
158     property SizeBefore: TSize read FSizeBefore;
159     property SizeAfter: TSize read FSizeAfter;
160   end;
161 
162 type
163   { TStoredImage }
164 
165   TStoredImage = class(TImageDiff)
166   public
GetBitmapnull167     function GetBitmap: TBGRABitmap;
168   end;
169 
170   { TStoredLayer }
171 
172   TStoredLayer = class(TStoredImage)
173   private
GetIdnull174     function GetId: integer;
GetOffsetnull175     function GetOffset: TPoint;
176   protected
177     FInfo: TLayerInfo;
178     FIndex: integer;
179     FOriginalData: TMemoryStream;
180     FOriginalBitmapStored: boolean;
181     FOriginalRenderStatus: TOriginalRenderStatus;
182     FOriginalMatrix: TAffineMatrix;
183     FOriginalDraft: boolean;
184     FOriginalGuid: TGuid;
185     FRegistryData: TMemoryStream;
186   public
187     constructor Create(ALayeredImage: TBGRALayeredBitmap; AIndex: integer);
188     constructor Create(ALayeredImage: TBGRALayeredBitmap; AIndex: integer;
189                        AAlwaysStoreBitmap: boolean);
190     destructor Destroy; override;
191     procedure Restore(ALayeredImage: TBGRALayeredBitmap);
192     procedure Replace(ALayeredImage: TBGRALayeredBitmap);
193     property LayerIndex: integer read FIndex;
194     property LayerId: integer read GetId;
195     property Offset: TPoint read GetOffset;
196   end;
197 
198 implementation
199 
200 uses Math, BGRALzpCommon, UFileSystem;
201 
202 { TComposedImageDifference }
203 
TComposedImageDifference.GetCountnull204 function TComposedImageDifference.GetCount: integer;
205 begin
206   result := FDiffs.Count;
207 end;
208 
GetItemnull209 function TComposedImageDifference.GetItem(AIndex: integer): TCustomImageDifference;
210 begin
211   result := FDiffs[AIndex];
212 end;
213 
GetTotalCountnull214 function TComposedImageDifference.GetTotalCount: integer;
215 var
216   i: Integer;
217 begin
218   result := 0;
219   for i := 0 to FDiffs.Count-1 do
220     if FDiffs[i] is TComposedImageDifference then
221       inc(result, TComposedImageDifference(FDiffs[i]).TotalCount)
222     else
223       inc(result, FDiffs[i].GetCost);
224 end;
225 
GetIsIdentitynull226 function TComposedImageDifference.GetIsIdentity: boolean;
227 var
228   i: Integer;
229 begin
230   if FAgglutinate then exit(false);
231   for i := 0 to FDiffs.Count-1 do
232     if not FDiffs[i].GetIsIdentity then exit(false);
233   exit(true);
234 end;
235 
TComposedImageDifference.GetImageDifferenceKindnull236 function TComposedImageDifference.GetImageDifferenceKind: TImageDifferenceKind;
237 var
238   i: Integer;
239 begin
240   result := idkChangeStack;
241   for i := 0 to FDiffs.Count-1 do
242     case FDiffs[i].GetImageDifferenceKind of
243       idkChangeImageAndSelection: result := idkChangeImageAndSelection;
244       idkChangeSelection: if result in[idkChangeImage,idkChangeImageAndSelection] then
245                             result := idkChangeImageAndSelection
246                           else result := idkChangeSelection;
247       idkChangeImage: if result in[idkChangeImageAndSelection,idkChangeSelection] then
248                             result := idkChangeImageAndSelection
249                           else result := idkChangeImage;
250     end;
251 end;
252 
TComposedImageDifference.GetChangingBoundsnull253 function TComposedImageDifference.GetChangingBounds: TRect;
254 var
255   i: Integer;
256   r: TRect;
257 begin
258   result:= EmptyRect;
259   for i := 0 to FDiffs.Count-1 do
260   begin
261     r := FDiffs[i].GetChangingBounds;
262     if not IsRectEmpty(r) then
263     begin
264       if IsRectEmpty(result) then result:= r
265       else UnionRect(result, result,r);
266     end;
267   end;
268 end;
269 
GetChangingBoundsDefinednull270 function TComposedImageDifference.GetChangingBoundsDefined: boolean;
271 var
272   i: Integer;
273 begin
274   for i := 0 to FDiffs.Count-1 do
275     if not FDiffs[i].GetChangingBoundsDefined then exit(false);
276   exit(true);
277 end;
278 
279 constructor TComposedImageDifference.Create(AAgglutinate: boolean);
280 begin
281   FDiffs := TImageDifferenceList.Create;
282   FAgglutinate:= AAgglutinate;
283 end;
284 
285 procedure TComposedImageDifference.ReleaseDiffs;
286 begin
287   FDiffs.FreeObjects:= false;
288   FDiffs.Clear;
289   FDiffs.FreeObjects:= true;
290 end;
291 
292 procedure TComposedImageDifference.StopAgglutinate;
293 begin
294   FAgglutinate:= false;
295 end;
296 
297 destructor TComposedImageDifference.Destroy;
298 begin
299   FDiffs.Free;
300   inherited Destroy;
301 end;
302 
TryCompressnull303 function TComposedImageDifference.TryCompress: boolean;
304 var
305   i: Integer;
306 begin
307   for i := 0 to FDiffs.Count-1 do
308     if FDiffs[i].TryCompress then exit(true);
309   exit(false);
310 end;
311 
UsedMemorynull312 function TComposedImageDifference.UsedMemory: int64;
313 var
314   i: Integer;
315 begin
316   result := 0;
317   for i := 0 to FDiffs.Count-1 do
318     inc(result, FDiffs[i].UsedMemory);
319 end;
320 
321 procedure TComposedImageDifference.Add(ADiff: TCustomImageDifference);
322 begin
323   FDiffs.Add(ADiff);
324 end;
325 
326 procedure TComposedImageDifference.AddRange(AComposed: TComposedImageDifference);
327 var
328   i: Integer;
329 begin
330   for i:= 0 to AComposed.Count-1 do
331     Add(AComposed.FDiffs[i]);
332 end;
333 
334 procedure TComposedImageDifference.ApplyTo(AState: TState);
335 var
336   i: Integer;
337 begin
338   for i := 0 to FDiffs.Count-1 do
339     FDiffs[i].ApplyTo(AState);
340 end;
341 
342 procedure TComposedImageDifference.UnapplyTo(AState: TState);
343 var
344   i: Integer;
345 begin
346   for i := FDiffs.Count-1 downto 0 do
347     FDiffs[i].UnapplyTo(AState);
348 end;
349 
350 procedure TComposedImageDifference.Clear;
351 begin
352   FDiffs.Clear;
353 end;
354 
355 procedure TComposedImageDifference.Delete(AIndex: integer);
356 begin
357   FDiffs.Delete(AIndex);
358 end;
359 
360 procedure TComposedImageDifference.DeleteFrom(AIndex: integer);
361 var
362   i: Integer;
363 begin
364   for i := Count-1 downto AIndex do
365     Delete(i);
366 end;
367 
GetLastnull368 function TComposedImageDifference.GetLast: TCustomImageDifference;
369 begin
370   if Count = 0 then result := nil
371   else result := FDiffs[Count-1];
372 end;
373 
TComposedImageDifference.ToStringnull374 function TComposedImageDifference.ToString: ansistring;
375 var
376   i: Integer;
377 begin
378   Result:= '[';
379   for i := 0 to Count-1 do
380   begin
381     if i <> 0 then result += ', ';
382     result += FDiffs[i].ToString;
383   end;
384   result += ']';
385 end;
386 
387 { TStateDifference }
388 
TryCompressnull389 function TStateDifference.TryCompress: boolean;
390 begin
391   result := false;
392 end;
393 
UsedMemorynull394 function TStateDifference.UsedMemory: int64;
395 begin
396   result := 0;
397 end;
398 
ToStringnull399 function TStateDifference.ToString: ansistring;
400 begin
401   Result:= ClassName;
402 end;
403 
404 { TCustomImageDifference }
405 
GetIsIdentitynull406 function TCustomImageDifference.GetIsIdentity: boolean;
407 begin
408   result := false;
409 end;
410 
GetImageDifferenceKindnull411 function TCustomImageDifference.GetImageDifferenceKind: TImageDifferenceKind;
412 begin
413   result := idkChangeImageAndSelection;
414 end;
415 
TCustomImageDifference.GetChangingBoundsnull416 function TCustomImageDifference.GetChangingBounds: TRect;
417 begin
418   result := rect(0,0,0,0);
419 end;
420 
GetChangingBoundsDefinednull421 function TCustomImageDifference.GetChangingBoundsDefined: boolean;
422 begin
423   result := false;
424 end;
425 
GetCostnull426 function TCustomImageDifference.GetCost: integer;
427 begin
428   result := 1;
429 end;
430 
431 constructor TCustomImageDifference.Create(AFromState: TState; AToState: TState);
432 begin
433   FSavedBefore:= AFromState.saved;
434   FSavedAfter:= AToState.saved;
435 end;
436 
437 constructor TCustomImageDifference.Create(AFromState: TState);
438 begin
439   FSavedBefore:= AFromState.saved;
440   FSavedAfter:= false;
441 end;
442 
443 constructor TCustomImageDifference.Create(ASavedBefore: boolean;
444   ASavedAfter: boolean);
445 begin
446   FSavedBefore := ASavedBefore;
447   FSavedAfter := ASavedAfter;
448 end;
449 
450 procedure TCustomImageDifference.ApplyTo(AState: TState);
451 begin
452   AState.saved:= FSavedAfter;
453 end;
454 
455 procedure TCustomImageDifference.UnapplyTo(AState: TState);
456 begin
457   AState.saved:= FSavedBefore;
458 end;
459 
460 {*********** Layer info *************}
461 
462 procedure ApplyLayerInfo(AInfo: TLayerInfo; ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer);
463 begin
464   if (AIndex < 0) or (AIndex >= ALayeredBitmap.NbLayers) then
465       raise exception.Create('Out of bounds');
466   ALayeredBitmap.LayerUniqueId[AIndex] := AInfo.Id;
467   ALayeredBitmap.LayerVisible[AIndex] := AInfo.Visible;
468   ALayeredBitmap.BlendOperation[AIndex] := AInfo.BlendOp;
469   ALayeredBitmap.LayerName[AIndex] := AInfo.Name;
470   ALayeredBitmap.LayerOpacity[AIndex] := AInfo.Opactiy;
471   if ALayeredBitmap.LayerOriginalGuid[AIndex] = GUID_NULL then
472     ALayeredBitmap.LayerOffset[AIndex] := AInfo.Offset;
473 end;
474 
GetLayerInfonull475 function GetLayerInfo(ALayeredBitmap: TBGRALayeredBitmap; AIndex: integer): TLayerInfo;
476 begin
477   if (AIndex < 0) or (AIndex >= ALayeredBitmap.NbLayers) then
478       raise exception.Create('Out of bounds');
479   result.Id := ALayeredBitmap.LayerUniqueId[AIndex];
480   result.Visible := ALayeredBitmap.LayerVisible[AIndex];
481   result.BlendOp := ALayeredBitmap.BlendOperation[AIndex];
482   result.Name := ALayeredBitmap.LayerName[AIndex];
483   result.Opactiy := ALayeredBitmap.LayerOpacity[AIndex];
484   result.Offset := ALayeredBitmap.LayerOffset[AIndex];
485 end;
486 
487 {*********** Inversible **************}
488 
GetInverseActionnull489 function GetInverseAction(AAction: TInversibleAction): TInversibleAction;
490 begin
491   case AAction of
492   iaRotateCW: result := iaRotateCCW;
493   iaRotateCCW: result := iaRotateCW;
494   else
495     result := AAction;
496   end;
497 end;
498 
CanCombineInversibleActionnull499 function CanCombineInversibleAction(AAction1, AAction2: TInversibleAction; out
500   ACombined: TInversibleAction): boolean;
501 begin
502   result := true;
503   ACombined:= iaHorizontalFlip; //some default value
504   case AAction1 of
505   iaSwapRedBlue: result := false;
506   iaRotate180: case AAction2 of
507                iaRotateCW: ACombined:= iaRotateCCW;
508                iaRotateCCW: ACombined := iaRotateCW;
509                iaHorizontalFlip: ACombined := iaVerticalFlip;
510                iaVerticalFlip: ACombined:= iaHorizontalFlip;
511                else result := false;
512                end;
513   iaHorizontalFlip: case AAction2 of
514                     iaVerticalFlip: ACombined:= iaRotate180;
515                     else result := false;
516                     end;
517   iaVerticalFlip: case AAction2 of
518                     iaHorizontalFlip: ACombined:= iaRotate180;
519                     else result := false;
520                     end;
521   iaRotateCW: case AAction2 of
522               iaRotateCW: ACombined:= iaRotate180;
523               iaRotate180: ACombined:= iaRotateCCW;
524               else result := false;
525               end;
526   iaRotateCCW: case AAction2 of
527                iaRotateCCW: ACombined:= iaRotate180;
528                iaRotate180: ACombined:= iaRotateCW;
529                else result := false;
530                end;
531   else
532     result := false;
533   end;
534 end;
535 
536 { TImageDiff }
537 
GetIsIdentitynull538 function TImageDiff.GetIsIdentity: boolean;
539 begin
540   result := (FSizeBefore.cx = FSizeAfter.cx) and (FSizeBefore.cy = FSizeAfter.cy)
541             and ChangeRect.IsEmpty;
542 end;
543 
544 procedure TImageDiff.DiscardFile;
545 begin
546   if FSavedFilename <> '' then
547   begin
548     try
549       if FileManager.FileExists(FSavedFilename) then
550         FileManager.DeleteFile(FSavedFilename);
551     except on ex:exception do begin end;
552     end;
553     FSavedFilename:= '';
554   end;
555 end;
556 
557 procedure TImageDiff.Decompress;
558 var
559   stream: TStream;
560 begin
561   if (FImageBackup = nil) and (FSavedFilename <> '') then
562   begin
563     stream := nil;
564     FImageBackup := TImageBackup.Create;
565     try
566       stream := FileManager.CreateFileStream(FSavedFilename,fmOpenRead or fmShareDenyWrite);
567       FImageBackup.LoadFromStream(stream);
568     except
569     end;
570     stream.free;
571   end;
572 end;
573 
574 procedure TImageDiff.Init(Image1, Image2: TBGRABitmap; AGrayscale: boolean);
575 var r: TRect;
576 begin
577   r := rect(0,0,0,0);
578   if Assigned(image1) then
579   begin
580     r.Right := image1.Width;
581     r.Bottom := image1.Height;
582   end;
583   if Assigned(image2) then
584   begin
585     r.Right := max(r.Right, image2.Width);
586     r.Bottom := max(r.Bottom, image2.Height);
587   end;
588   Init(Image1, Image2, AGrayscale, r);
589 end;
590 
591 procedure TImageDiff.Init(Image1, Image2: TBGRABitmap; AGrayscale: boolean;
592   AChangeRect: TRect);
593 begin
594   if Image1 = nil then FSizeBefore := Size(0, 0) else
595     FSizeBefore := Size(Image1.Width, Image1.Height);
596   if Image2 = nil then FSizeAfter := Size(0, 0) else
597     FSizeAfter := Size(Image2.Width, Image2.Height);
598 
599   FImageBackup := TImageBackup.Create(Image1, Image2, AGrayscale, AChangeRect);
600   FChangeRect := FImageBackup.Bounds;
601   FGrayscale := AGrayscale;
602 end;
603 
CreateNewnull604 function TImageDiff.CreateNew(AWidth, AHeight: integer): TBGRABitmap;
605 begin
606   if FGrayscale then
607     result := TBGRABitmap.Create(AWidth, AHeight, BGRABlack)
608   else
609     result := TBGRABitmap.Create(AWidth, AHeight);
610 end;
611 
612 constructor TImageDiff.Create(Image1: TBGRABitmap; AGrayscale: boolean);
613 begin
614   Init(Image1, nil, AGrayscale);
615 end;
616 
617 constructor TImageDiff.Create(Image1: TBGRABitmap; AGrayscale: boolean;
618   AChangeRect: TRect);
619 begin
620   Init(Image1, nil, AGrayscale, AChangeRect);
621 end;
622 
623 constructor TImageDiff.Create(Image1, Image2: TBGRABitmap; AGrayscale: boolean);
624 begin
625   Init(Image1, Image2, AGrayscale);
626 end;
627 
628 constructor TImageDiff.Create(Image1, Image2: TBGRABitmap; AGrayscale: boolean;
629   AChangeRect: TRect);
630 begin
631   Init(Image1, Image2, AGrayscale, AChangeRect);
632 end;
633 
634 procedure TImageDiff.ApplyInPlace(ADest: TBGRABitmap; AXor: boolean);
635 begin
636   if ADest = nil then raise exception.Create('Unexpected nil reference');
637   Decompress;
638   if Assigned(FImageBackup) then
639     FImageBackup.Restore(ADest, ChangeRect, AXor)
640   else
641   begin
642     if not AXor then
643       ADest.EraseRect(ChangeRect, 255);
644   end;
645 end;
646 
ApplyInNewnull647 function TImageDiff.ApplyInNew(ASource: TBGRABitmap; AReverse: boolean; AXor: boolean): TBGRABitmap;
648 var
649   destSize: TSize;
650 begin
651   if (self = nil) or IsIdentity then
652   begin
653     if ASource = nil then
654       result := nil
655     else
656       result := ASource.Duplicate as TBGRABitmap
657   end
658   else
659   begin
660     if AReverse then destSize := FSizeBefore else
661       destSize := FSizeAfter;
662 
663     if (destSize.cx = 0) or (destSize.cy = 0) then
664       result := nil
665     else
666     begin
667       result := CreateNew(destSize.cx, destSize.cy);
668       if ASource <> nil then
669         result.PutImage(0, 0, ASource, dmSet);
670       ApplyInPlace(result, AXor);
671     end;
672   end;
673 end;
674 
ApplyCanCreateNewnull675 function TImageDiff.ApplyCanCreateNew(ASource: TBGRABitmap; AReverse: boolean; AXor: boolean): TBGRABitmap;
676 begin
677   if (self = nil) or IsIdentity then exit(ASource); //keep
678 
679   if (ASource = nil) or
680      ((FSizeAfter.cx <> FSizeBefore.cx) or
681      (FSizeAfter.cy <> FSizeBefore.cy)) then
682      exit(ApplyInNew(ASource, AReverse, AXor))
683   else
684   begin
685     ApplyInPlace(ASource, AXor);
686     exit(ASource);
687   end;
688 end;
689 
Compressnull690 function TImageDiff.Compress: boolean;
691 var
692   savedFile: TStream;
693 begin
694   if Assigned(FImageBackup) and (FSavedFilename = '') then
695   begin
696     FSavedFilename := GetTempFileName;
697     try
698       savedFile := FileManager.CreateFileStream(FSavedFilename,fmCreate);
699       try
700         FImageBackup.SaveToStream(savedFile);
701         FreeAndNil(FImageBackup);
702         result := true;
703       finally
704         savedFile.Free;
705       end;
706     except
707       on ex: exception do
708       begin
709         if FileManager.FileExists(FSavedFilename) then FileManager.DeleteFile(FSavedFilename);
710         FSavedFilename := '';
711         result := false;
712       end;
713     end;
714   end else
715   if Assigned(FImageBackup) then
716   begin
717     FreeAndNil(FImageBackup);
718     result := true;
719   end else
720     result := false;
721 end;
722 
723 destructor TImageDiff.Destroy;
724 begin
725   FImageBackup.Free;
726   DiscardFile;
727   inherited Destroy;
728 end;
729 
TImageDiff.UsedMemorynull730 function TImageDiff.UsedMemory: int64;
731 begin
732   if Assigned(FImageBackup) then result := FImageBackup.UsedMemory
733   else result := 0;
734 end;
735 
736 { TStoredImage }
737 
GetBitmapnull738 function TStoredImage.GetBitmap: TBGRABitmap;
739 begin
740   result := ApplyInNew(nil, true, false);
741 end;
742 
743 { TStoredLayer }
744 
TStoredLayer.GetIdnull745 function TStoredLayer.GetId: integer;
746 begin
747   result := FInfo.Id;
748 end;
749 
TStoredLayer.GetOffsetnull750 function TStoredLayer.GetOffset: TPoint;
751 begin
752   result := FInfo.Offset;
753 end;
754 
755 constructor TStoredLayer.Create(ALayeredImage: TBGRALayeredBitmap;
756   AIndex: integer);
757 var
758   {%H-}orig: TBGRALayerCustomOriginal;
759   alwaysStoreBitmap: Boolean;
760 begin
761   alwaysStoreBitmap := false;
762   if (ALayeredImage.LayerOriginalGuid[AIndex]<>GUID_NULL) and
763     ALayeredImage.LayerOriginalKnown[AIndex] then
764   begin
765     try
766       orig := ALayeredImage.LayerOriginal[AIndex];
767     except
768       on ex:exception do
769         alwaysStoreBitmap:= true;
770     end;
771   end;
772   Create(ALayeredImage, AIndex, alwaysStoreBitmap);
773 end;
774 
775 constructor TStoredLayer.Create(ALayeredImage: TBGRALayeredBitmap;
776   AIndex: integer; AAlwaysStoreBitmap: boolean);
777 begin
778   FIndex := AIndex;
779   FInfo := GetLayerInfo(ALayeredImage, AIndex);
780   if ALayeredImage.LayerOriginalGuid[AIndex]<>GUID_NULL then
781   begin
782     FOriginalBitmapStored := AAlwaysStoreBitmap or not ALayeredImage.LayerOriginalKnown[AIndex];
783     FOriginalRenderStatus:= ALayeredImage.LayerOriginalRenderStatus[AIndex];
784 
785     if not FOriginalBitmapStored then
786       inherited Create(nil, False)
787     else
788       inherited Create(ALayeredImage.LayerBitmap[AIndex], False);
789 
790     FOriginalData := TMemoryStream.Create;
791     FOriginalGuid := ALayeredImage.LayerOriginalGuid[AIndex];
792     ALayeredImage.SaveOriginalToStream(FOriginalGuid, FOriginalData);
793     FOriginalMatrix := ALayeredImage.LayerOriginalMatrix[AIndex];
794     FOriginalDraft := ALayeredImage.LayerOriginalRenderStatus[AIndex] in[orsDraft,orsPartialDraft];
795   end else
796   begin
797     inherited Create(ALayeredImage.LayerBitmap[AIndex], False);
798     FOriginalData := nil;
799   end;
800   FRegistryData := TMemoryStream.Create;
801   ALayeredImage.SaveLayerRegistryToStream(AIndex, FRegistryData);
802 end;
803 
804 destructor TStoredLayer.Destroy;
805 begin
806   FRegistryData.Free;
807   FOriginalData.Free;
808   inherited Destroy;
809 end;
810 
811 procedure TStoredLayer.Restore(ALayeredImage: TBGRALayeredBitmap);
812 var
813   tempIdx, idxOrig: Integer;
814 begin
815   if Assigned(FOriginalData) then
816   begin
817     FOriginalData.Position:= 0;
818     idxOrig := ALayeredImage.IndexOfOriginal(FOriginalGuid);
819     if idxOrig = -1 then
820       idxOrig := ALayeredImage.AddOriginalFromStream(FOriginalData, FOriginalGuid, true);
821 
822     if not FOriginalBitmapStored then
823     begin
824       tempIdx := ALayeredImage.AddLayerFromOriginal(ALayeredImage.Original[idxOrig].Guid, FOriginalMatrix);
825       ALayeredImage.RenderLayerFromOriginal(tempIdx, FOriginalDraft);
826     end else
827     begin
828       tempIdx := ALayeredImage.AddOwnedLayer(GetBitmap);
829       ALayeredImage.LayerOffset[tempIdx] := FInfo.Offset;
830       ALayeredImage.LayerOriginalGuid[tempIdx] := ALayeredImage.OriginalGuid[idxOrig];
831       ALayeredImage.LayerOriginalMatrix[tempIdx] := FOriginalMatrix;
832       ALayeredImage.LayerOriginalRenderStatus[tempIdx] := FOriginalRenderStatus;
833     end;
834   end else
835     tempIdx := ALayeredImage.AddOwnedLayer(GetBitmap);
836 
837   ApplyLayerInfo(FInfo,ALayeredImage,tempIdx);
838   FRegistryData.Position := 0;
839   ALayeredImage.LoadLayerRegistryFromStream(tempIdx, FRegistryData);
840   ALayeredImage.InsertLayer(FIndex,tempIdx);
841 end;
842 
843 procedure TStoredLayer.Replace(ALayeredImage: TBGRALayeredBitmap);
844 var
845   idxOrig: Integer;
846 begin
847   if Assigned(FOriginalData) then
848   begin
849     FOriginalData.Position:= 0;
850     idxOrig := ALayeredImage.IndexOfOriginal(FOriginalGuid);
851     if idxOrig = -1 then
852       idxOrig := ALayeredImage.AddOriginalFromStream(FOriginalData, FOriginalGuid, true);
853     if not FOriginalBitmapStored then
854     begin
855       ALayeredImage.LayerOriginalGuid[FIndex] := ALayeredImage.OriginalGuid[idxOrig];
856       ALayeredImage.LayerOriginalMatrix[FIndex] := FOriginalMatrix;
857       ALayeredImage.RenderLayerFromOriginal(FIndex, FOriginalDraft);
858     end else
859     begin
860       ALayeredImage.SetLayerBitmap(FIndex, GetBitmap, True);
861       ALayeredImage.LayerOffset[FIndex] := FInfo.Offset;
862       ALayeredImage.LayerOriginalGuid[FIndex] := ALayeredImage.OriginalGuid[idxOrig];
863       ALayeredImage.LayerOriginalMatrix[FIndex] := FOriginalMatrix;
864       ALayeredImage.LayerOriginalRenderStatus[FIndex] := FOriginalRenderStatus;
865     end;
866   end else
867     ALayeredImage.SetLayerBitmap(FIndex, GetBitmap, True);
868   ALayeredImage.RemoveUnusedOriginals;
869 
870   ApplyLayerInfo(FInfo,ALayeredImage,FIndex);
871   FRegistryData.Position := 0;
872   ALayeredImage.LoadLayerRegistryFromStream(FIndex, FRegistryData);
873 end;
874 
875 end.
876 
877