File: imagelistcache.pas

package info (click to toggle)
lazarus 2.0.10%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 219,188 kB
  • sloc: pascal: 1,867,962; xml: 265,716; cpp: 56,595; sh: 3,005; java: 609; makefile: 568; perl: 297; sql: 222; ansic: 137
file content (345 lines) | stat: -rw-r--r-- 9,621 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
{
 /***************************************************************************
                               ImageListCache.pp
                               ----------------
                   Initial Revision  : Sun Nov 18 00:04:00 GMT+07 2007


 ***************************************************************************/

 *****************************************************************************
  This file is part of the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************
}

unit ImageListCache;

{$mode objfpc}{$H+}
{ $DEFINE VerboseImageListCache}

interface

uses
  Classes, SysUtils, Graphics, ImgList, LCLProc, Forms;

type
  // interface that cache user should have to listen for cache changes
  IImageCacheListener = interface
    procedure CacheSetImageList(AImageList: TCustomImageList);
    procedure CacheSetImageIndex(AIndex, AImageIndex: Integer);
  end;

  // cache item
  TImageCacheItem = record
    FImageList: TCustomImageList;    // link to imagelist
    FListener: IImageCacheListener;  // link to listener
    FImageIndexes: array of Integer; // indexes of imagelist that listener reserved
  end;
  PImageCacheItem = ^TImageCacheItem;
  
  { TImageCacheItems }

  TImageCacheItems = class(TList)
  private
    function GetItem(AIndex: Integer): PImageCacheItem;
    function GetItemForListener(AListener: IImageCacheListener): PImageCacheItem;
    procedure SetItem(AIndex: Integer; const AValue: PImageCacheItem);
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    function GetNew: PImageCacheItem;
    property Items[AIndex: Integer]: PImageCacheItem read GetItem write SetItem; default;
  end;

  { TImageListCache }

  TImageListCache = class
  private
    FItems: TImageCacheItems;
    FImages: TList;
    FListeners: TInterfaceList;
    FObsoletedCount: Integer;
    procedure CheckRebuildNeed;
    function GetImageListFor(AWidth, AHeight: Integer): TCustomImageList;

    procedure UnregisterBitmaps(AListener: IImageCacheListener);
  public
    constructor Create;
    destructor Destroy; override;
    
    function RegisterListener(AListener: IImageCacheListener): Integer;
    procedure UnregisterListener(AListener: IImageCacheListener);
    procedure RegisterBitmap(AListener: IImageCacheListener; ABitmap: TBitmap; ABitmapCount: Integer = 1);
    procedure Rebuild;
  end;
  
  function GetImageListCache: TImageListCache;
  
implementation

const
  // number of cache changes that can happen w/o rebuild
{$IFDEF VerboseImageListCache}
  ImageListCacheRebuildThreshold = 1;
{$ELSE}
  ImageListCacheRebuildThreshold = 20;
{$ENDIF}

var
  FImageListCache: TImageListCache = nil;
  
function GetImageListCache: TImageListCache;
begin
  if FImageListCache = nil then
    FImageListCache := TImageListCache.Create;
  Result := FImageListCache;
end;


{ TImageListCache }

procedure TImageListCache.CheckRebuildNeed;
begin
  if (FObsoletedCount >= ImageListCacheRebuildThreshold) and not Application.Terminated then
    Rebuild;
end;

function TImageListCache.GetImageListFor(AWidth, AHeight: Integer): TCustomImageList;
var
  i: integer;
begin
  for i := 0 to FImages.Count - 1 do
    if (TCustomImageList(FImages[i]).Height = AHeight) and
       (TCustomImageList(FImages[i]).Width = AWidth) then
    begin
      Result := TCustomImageList(FImages[i]);
      exit;
    end;
  Result := TCustomImageList.Create(nil);
  FImages.Add(Result);
  with Result do
  begin
    Width := AWidth;
    Height := AHeight;
    Scaled := False;
{$IFDEF VerboseImageListCache}
    debugln('Creating new imagelist in cache for Width=',Width,' Height=', Height, ' Count = ', FImages.Count);
    if (Width <> 16) and (Width <> 24) then
      DumpStack;
{$ENDIF}
  end;
end;

procedure TImageListCache.UnregisterBitmaps(AListener: IImageCacheListener);
var
  Item: PImageCacheItem;
begin
  Item := FItems.GetItemForListener(AListener);

  if (Item <> nil) then
  begin
    Item^.FListener := nil;
    inc(FObsoletedCount, Length(Item^.FImageIndexes));
  end;
  CheckRebuildNeed;
end;

constructor TImageListCache.Create;
begin
  FObsoletedCount := 0;
  FItems := TImageCacheItems.Create;
  FImages := TList.Create;
  FListeners := TInterfaceList.Create;
end;

destructor TImageListCache.Destroy;
var
  i: integer;
begin
  FItems.Free;
  for i := 0 to FImages.Count - 1 do
    TObject(FImages[i]).Free;
  FImages.Free;
  FListeners.Free;
  inherited Destroy;
end;

function TImageListCache.RegisterListener(AListener: IImageCacheListener): Integer;
begin
  Result := FListeners.IndexOf(AListener);
  if Result = -1 then
    Result := FListeners.Add(AListener);
end;

procedure TImageListCache.UnregisterListener(AListener: IImageCacheListener);
var
  Index: Integer;
begin
  Index := FListeners.IndexOf(AListener);
  if Index <> -1 then
  begin
    UnregisterBitmaps(AListener);
    FListeners.Remove(AListener);
  end;
  if FListeners.Count = 0 then
  begin
    FImageListCache := nil;
    Free;
  end;
end;

procedure TImageListCache.RegisterBitmap(AListener: IImageCacheListener; ABitmap: TBitmap; ABitmapCount: Integer = 1);
var
  i, AStart, OldLen: Integer;
  Item: PImageCacheItem;
  OldOnChange: TNotifyEvent;
begin
  OldOnChange := ABitmap.OnChange;
  ABitmap.OnChange := nil; // prevent further updates

  try
    RegisterListener(AListener);
    Item := FItems.GetItemForListener(AListener);
    if Item = nil then
    begin
      Item := FItems.GetNew;
      Item^.FImageList := GetImageListFor(ABitmap.Width div ABitmapCount, ABitmap.Height);
      Item^.FListener := AListener;
    end;

    AStart := Item^.FImageList.AddSliced(ABitmap, ABitmapCount, 1);
    AListener.CacheSetImageList(Item^.FImageList);
    OldLen := Length(Item^.FImageIndexes);
    SetLength(Item^.FImageIndexes, OldLen + Item^.FImageList.Count - AStart);

    for i := AStart to Item^.FImageList.Count - 1 do
    begin
      Item^.FImageIndexes[OldLen + i - AStart] := i;
      AListener.CacheSetImageIndex(OldLen + i - AStart, i);
    end;
  finally
    ABitmap.OnChange := OldOnChange;
  end;
end;

// cache rebuild
procedure TImageListCache.Rebuild;
var
  i, j, k, ACount: integer;
  AListener: IImageCacheListener;
  ADeleted: TBits;
  AChanged: Boolean;
  AIndexes: array of Integer;
  AUpdates: TList;
begin
  // 1. check what items to be deleted (their listerners are not assigned)
  // 2. delete no more needed images from imagelists
  // 3. notify listeners about new image indexes
  
  // traverse all ImageLists
  for i := 0 to FImages.Count - 1 do
  begin
    ACount := TCustomImageList(FImages[i]).Count;
    ADeleted := TBits.Create(ACount);
    AChanged := False;
    AUpdates := TList.Create;
    // traverse for all items
    // if item is to be deleted then set flag in ADeleted, else add item to AUpdates array
    for j := FItems.Count - 1 downto 0 do
      if FItems[j]^.FImageList = TCustomImageList(FImages[i]) then
      begin
        for k := 0 to High(FItems[j]^.FImageIndexes) do
          ADeleted.Bits[FItems[j]^.FImageIndexes[k]] := FItems[j]^.FListener = nil;
        if FItems[j]^.FListener = nil then
        begin
          FItems.Delete(j);
          AChanged := True;
        end
        else
          AUpdates.Add(FItems[j]);
      end;
    // is something has been deleted from current imagelist then
    // we continue processing
    if AChanged then
    begin
      // AIndexes is our old=>new image indexes map
      // at first step we set old=old and at same moment clearing our imagelist
      SetLength(AIndexes, ACount);
      for j := High(AIndexes) downto 0 do
      begin
        AIndexes[j] := j;
        if ADeleted[j] then
          TCustomImageList(FImages[i]).Delete(j);
      end;
      // we traversing our indexes map and set new values for old values
      for j := 0 to High(AIndexes) do
        if ADeleted[j] then
        begin
          for k := j + 1 to High(AIndexes) do
            dec(AIndexes[k]);
        end;
      // all preparation done - we have old=>new map
      // process all Items that needs to be updated
      for j := 0 to AUpdates.Count - 1 do
      begin
        AListener := PImageCacheItem(AUpdates[j])^.FListener;
        for k := 0 to High(PImageCacheItem(AUpdates[j])^.FImageIndexes) do
        begin
          // update cache item and notify listener
          PImageCacheItem(AUpdates[j])^.FImageIndexes[k] := AIndexes[PImageCacheItem(AUpdates[j])^.FImageIndexes[k]];
          AListener.CacheSetImageIndex(k, PImageCacheItem(AUpdates[j])^.FImageIndexes[k]);
        end;
      end;
    end;
    AUpdates.Free;
    ADeleted.Free;
    SetLength(AIndexes, 0);
  end;

  FObsoletedCount := 0;
end;

{ TImageCacheItems }

function TImageCacheItems.GetItem(AIndex: Integer): PImageCacheItem;
begin
  Result := inherited Get(AIndex)
end;

procedure TImageCacheItems.SetItem(AIndex: Integer;
  const AValue: PImageCacheItem);
begin
  inherited Put(AIndex, AValue);
end;

procedure TImageCacheItems.Notify(Ptr: Pointer; Action: TListNotification);
begin
  if (Action = lnDeleted) and (Ptr <> nil) then
    Dispose(PImageCacheItem(Ptr));
end;

function TImageCacheItems.GetNew: PImageCacheItem;
begin
  New(Result);
  Add(Result);
end;

function TImageCacheItems.GetItemForListener(AListener: IImageCacheListener): PImageCacheItem;
var
  i: integer;
begin
  Result := nil;
  for i := 0 to Count - 1 do
    if Items[i]^.FListener = AListener then
    begin
      Result := Items[i];
      break;
    end;
end;

end.