1 | unit BGRACompressableBitmap;
|
---|
2 |
|
---|
3 | {$mode objfpc}{$H+}
|
---|
4 |
|
---|
5 | interface
|
---|
6 |
|
---|
7 | { This unit contains the TBGRACompressableBitmap class, which
|
---|
8 | can be used to temporarily compress bitmaps in memory.
|
---|
9 | To use it, create an instance with the bitmap you want
|
---|
10 | to compress. You can then free the original bitmap because
|
---|
11 | TBGRACompressableBitmap contains all information necessary
|
---|
12 | to build it again. To construct again your bitmap, call
|
---|
13 | the GetBitmap function.
|
---|
14 |
|
---|
15 | When you have your bitmap in TBGRACompressableBitmap,
|
---|
16 | you can call Compress function as many times as necessary
|
---|
17 | until all data is compressed. It does only a part of the
|
---|
18 | work at each call, so you can put it in a loop or in
|
---|
19 | a timer. When it's done, Compress returns false to
|
---|
20 | notify that it did nothing, which means you can
|
---|
21 | stop calling Compress.
|
---|
22 |
|
---|
23 | In this implementation, the memory usage grows during
|
---|
24 | the compression process and is lower only after it is
|
---|
25 | finished. So it is recommended to compress one bitmap
|
---|
26 | at a time. }
|
---|
27 |
|
---|
28 | uses
|
---|
29 | Classes, SysUtils, BGRABitmapTypes, BGRABitmap, zstream;
|
---|
30 |
|
---|
31 | type
|
---|
32 |
|
---|
33 | { TBGRACompressableBitmap }
|
---|
34 |
|
---|
35 | TBGRACompressableBitmap = class
|
---|
36 | private
|
---|
37 | FWidth,FHeight: integer;
|
---|
38 | FCaption: String;
|
---|
39 | FBounds: TRect;
|
---|
40 | FCompressedDataArray: array of TMemoryStream;
|
---|
41 | FUncompressedData: TMemoryStream;
|
---|
42 | FLineOrder: TRawImageLineOrder;
|
---|
43 | FCompressionProgress: Int64;
|
---|
44 | procedure Decompress;
|
---|
45 | procedure FreeData;
|
---|
46 | procedure Init;
|
---|
47 | public
|
---|
48 | CompressionLevel: Tcompressionlevel;
|
---|
49 | constructor Create; overload;
|
---|
50 | constructor Create(Source: TBGRABitmap); overload;
|
---|
51 | function GetBitmap: TBGRABitmap;
|
---|
52 |
|
---|
53 | //call Compress as many times as necessary
|
---|
54 | //when it returns false, it means that
|
---|
55 | //the image compression is finished
|
---|
56 | function Compress: boolean;
|
---|
57 | procedure WriteToStream(AStream: TStream);
|
---|
58 | procedure ReadFromStream(AStream: TStream);
|
---|
59 |
|
---|
60 | function UsedMemory: Int64;
|
---|
61 | procedure Assign(Source: TBGRABitmap);
|
---|
62 | destructor Destroy; override;
|
---|
63 | property Width : Integer read FWidth;
|
---|
64 | property Height: Integer read FHeight;
|
---|
65 | property Caption : string read FCaption write FCaption;
|
---|
66 |
|
---|
67 | end;
|
---|
68 |
|
---|
69 | implementation
|
---|
70 |
|
---|
71 | uses BGRAUTF8;
|
---|
72 |
|
---|
73 | // size of each chunk treated by Compress function
|
---|
74 | const maxPartSize = 524288;
|
---|
75 |
|
---|
76 | { TBGRACompressedBitmap }
|
---|
77 |
|
---|
78 | constructor TBGRACompressableBitmap.Create;
|
---|
79 | begin
|
---|
80 | Init;
|
---|
81 | end;
|
---|
82 |
|
---|
83 | constructor TBGRACompressableBitmap.Create(Source: TBGRABitmap);
|
---|
84 | begin
|
---|
85 | Init;
|
---|
86 | Assign(Source);
|
---|
87 | end;
|
---|
88 |
|
---|
89 | { Constructs the bitmap again, decompressing if necessary.
|
---|
90 | After this, the image is not compressed anymore so the
|
---|
91 | memoy usage grows again and the access becomes fast
|
---|
92 | because there is no need to decompress anymore. }
|
---|
93 | function TBGRACompressableBitmap.GetBitmap: TBGRABitmap;
|
---|
94 | var UsedPart: TBGRABitmap;
|
---|
95 | UsedNbPixels: Integer;
|
---|
96 | begin
|
---|
97 | Decompress;
|
---|
98 | if FUncompressedData = nil then
|
---|
99 | begin
|
---|
100 | result := nil;
|
---|
101 | exit;
|
---|
102 | end;
|
---|
103 | result := TBGRABitmap.Create(FWidth,FHeight);
|
---|
104 | result.Caption := FCaption;
|
---|
105 | FUncompressedData.Position := 0;
|
---|
106 | if (FBounds.Left <> 0) or (FBounds.Top <> 0)
|
---|
107 | or (FBounds.Right <> FWidth) or (FBounds.Bottom <> FHeight) then
|
---|
108 | begin
|
---|
109 | UsedNbPixels := (FBounds.Right-FBounds.Left)*(FBounds.Bottom-FBounds.Top);
|
---|
110 | if UsedNbPixels > 0 then
|
---|
111 | begin
|
---|
112 | UsedPart := TBGRABitmap.Create(FBounds.Right-FBounds.Left,FBounds.Bottom-FBounds.Top);
|
---|
113 | FUncompressedData.Read(UsedPart.Data^,UsedPart.NbPixels*Sizeof(TBGRAPixel));
|
---|
114 | if UsedPart.LineOrder <> FLineOrder then UsedPart.VerticalFlip;
|
---|
115 | If TBGRAPixel_RGBAOrder then UsedPart.SwapRedBlue;
|
---|
116 | result.PutImage(FBounds.Left,FBounds.Top,UsedPart,dmSet);
|
---|
117 | UsedPart.Free;
|
---|
118 | end;
|
---|
119 | end else
|
---|
120 | begin
|
---|
121 | FUncompressedData.Read(result.Data^,result.NbPixels*Sizeof(TBGRAPixel));
|
---|
122 | If TBGRAPixel_RGBAOrder then result.SwapRedBlue;
|
---|
123 | end;
|
---|
124 | end;
|
---|
125 |
|
---|
126 | { Returns the total memory used by this object for storing bitmap data }
|
---|
127 | function TBGRACompressableBitmap.UsedMemory: Int64;
|
---|
128 | var i: integer;
|
---|
129 | begin
|
---|
130 | result := 0;
|
---|
131 | for i := 0 to high(FCompressedDataArray) do
|
---|
132 | result += FCompressedDataArray[i].Size;
|
---|
133 | if FUncompressedData <> nil then result += FUncompressedData.Size;
|
---|
134 | end;
|
---|
135 |
|
---|
136 | { Do one compress step or return false }
|
---|
137 | function TBGRACompressableBitmap.Compress: boolean;
|
---|
138 | var comp: Tcompressionstream;
|
---|
139 | partSize: integer;
|
---|
140 | begin
|
---|
141 | if FCompressedDataArray = nil then FCompressionProgress := 0;
|
---|
142 | if (FUncompressedData = nil) or (FUncompressedData.Size = 0) then
|
---|
143 | begin
|
---|
144 | result := false;
|
---|
145 | exit;
|
---|
146 | end;
|
---|
147 | if FCompressionProgress < FUncompressedData.Size then
|
---|
148 | begin
|
---|
149 | setlength(FCompressedDataArray, length(FCompressedDataArray)+1);
|
---|
150 | FCompressedDataArray[high(FCompressedDataArray)] := TMemoryStream.Create;
|
---|
151 | FUncompressedData.Position := FCompressionProgress;
|
---|
152 | if FUncompressedData.Size - FCompressionProgress > maxPartSize then
|
---|
153 | partSize := maxPartSize else
|
---|
154 | partSize := integer(FUncompressedData.Size - FCompressionProgress);
|
---|
155 |
|
---|
156 | comp := Tcompressionstream.Create(CompressionLevel,FCompressedDataArray[high(FCompressedDataArray)],true);
|
---|
157 | LEWriteLongint(comp, partSize);
|
---|
158 | comp.CopyFrom(FUncompressedData,partSize);
|
---|
159 | comp.Free;
|
---|
160 | inc(FCompressionProgress, partSize);
|
---|
161 | end;
|
---|
162 | if FCompressionProgress >= FUncompressedData.Size then
|
---|
163 | FreeAndNil(FUncompressedData);
|
---|
164 | result := true;
|
---|
165 | end;
|
---|
166 |
|
---|
167 | procedure TBGRACompressableBitmap.WriteToStream(AStream: TStream);
|
---|
168 | var i:integer;
|
---|
169 | begin
|
---|
170 | repeat
|
---|
171 | until not Compress;
|
---|
172 | LEWriteLongint(AStream,FWidth);
|
---|
173 | LEWriteLongint(AStream,FHeight);
|
---|
174 | LEWriteLongint(AStream,length(FCaption));
|
---|
175 | AStream.Write(FCaption[1],length(FCaption));
|
---|
176 | if (FWidth=0) or (FHeight = 0) then exit;
|
---|
177 |
|
---|
178 | LEWriteLongint(AStream,FBounds.Left);
|
---|
179 | LEWriteLongint(AStream,FBounds.Top);
|
---|
180 | LEWriteLongint(AStream,FBounds.Right);
|
---|
181 | LEWriteLongint(AStream,FBounds.Bottom);
|
---|
182 | LEWriteLongint(AStream,ord(FLineOrder));
|
---|
183 |
|
---|
184 | LEWriteLongint(AStream,length(FCompressedDataArray));
|
---|
185 | for i := 0 to high(FCompressedDataArray) do
|
---|
186 | begin
|
---|
187 | LEWriteLongint(AStream,FCompressedDataArray[i].Size);
|
---|
188 | FCompressedDataArray[i].Position := 0;
|
---|
189 | AStream.CopyFrom(FCompressedDataArray[i],FCompressedDataArray[i].Size);
|
---|
190 | end;
|
---|
191 | end;
|
---|
192 |
|
---|
193 | procedure TBGRACompressableBitmap.ReadFromStream(AStream: TStream);
|
---|
194 | var size,i: integer;
|
---|
195 | begin
|
---|
196 | FreeData;
|
---|
197 | FWidth := LEReadLongint(AStream);
|
---|
198 | FHeight := LEReadLongint(AStream);
|
---|
199 | setlength(FCaption,LEReadLongint(AStream));
|
---|
200 | AStream.Read(FCaption[1],length(FCaption));
|
---|
201 | if (FWidth=0) or (FHeight = 0) then
|
---|
202 | begin
|
---|
203 | FUncompressedData := TMemoryStream.Create;
|
---|
204 | exit;
|
---|
205 | end;
|
---|
206 |
|
---|
207 | FBounds.Left := LEReadLongint(AStream);
|
---|
208 | FBounds.Top := LEReadLongint(AStream);
|
---|
209 | FBounds.Right := LEReadLongint(AStream);
|
---|
210 | FBounds.Bottom := LEReadLongint(AStream);
|
---|
211 | FLineOrder := TRawImageLineOrder(LEReadLongint(AStream));
|
---|
212 |
|
---|
213 | setlength(FCompressedDataArray,LEReadLongint(AStream));
|
---|
214 | for i := 0 to high(FCompressedDataArray) do
|
---|
215 | begin
|
---|
216 | size := LEReadLongint(AStream);
|
---|
217 | FCompressedDataArray[i] := TMemoryStream.Create;
|
---|
218 | FCompressedDataArray[i].CopyFrom(AStream,size);
|
---|
219 | end;
|
---|
220 |
|
---|
221 | if FCompressedDataArray = nil then
|
---|
222 | FUncompressedData := TMemoryStream.Create;
|
---|
223 | end;
|
---|
224 |
|
---|
225 | procedure TBGRACompressableBitmap.Decompress;
|
---|
226 | var decomp: Tdecompressionstream;
|
---|
227 | i: integer;
|
---|
228 | partSize: integer;
|
---|
229 | begin
|
---|
230 | if (FUncompressedData <> nil) or (FCompressedDataArray = nil) then exit;
|
---|
231 | FUncompressedData := TMemoryStream.Create;
|
---|
232 | for i := 0 to high(FCompressedDataArray) do
|
---|
233 | begin
|
---|
234 | FCompressedDataArray[i].Position := 0;
|
---|
235 | decomp := Tdecompressionstream.Create(FCompressedDataArray[i],true);
|
---|
236 | partSize := LEReadLongint(decomp);
|
---|
237 | FUncompressedData.CopyFrom(decomp,partSize);
|
---|
238 | decomp.Free;
|
---|
239 | FreeAndNil(FCompressedDataArray[i]);
|
---|
240 | end;
|
---|
241 | FCompressedDataArray := nil;
|
---|
242 | end;
|
---|
243 |
|
---|
244 | { Free all data }
|
---|
245 | procedure TBGRACompressableBitmap.FreeData;
|
---|
246 | var i: integer;
|
---|
247 | begin
|
---|
248 | if FCompressedDataArray <> nil then
|
---|
249 | begin
|
---|
250 | for i := 0 to high(FCompressedDataArray) do
|
---|
251 | FCompressedDataArray[I].Free;
|
---|
252 | FCompressedDataArray := nil;
|
---|
253 | end;
|
---|
254 | if FUncompressedData <> nil then FreeAndNil(FUncompressedData);
|
---|
255 | end;
|
---|
256 |
|
---|
257 | procedure TBGRACompressableBitmap.Init;
|
---|
258 | begin
|
---|
259 | FUncompressedData := nil;
|
---|
260 | FCompressedDataArray := nil;
|
---|
261 | FWidth := 0;
|
---|
262 | FHeight := 0;
|
---|
263 | FCaption := '';
|
---|
264 | FCompressionProgress := 0;
|
---|
265 | CompressionLevel := clfastest;
|
---|
266 | end;
|
---|
267 |
|
---|
268 | { Copy a bitmap into this object. As it is copied, you need not
|
---|
269 | keep a copy of the source }
|
---|
270 | procedure TBGRACompressableBitmap.Assign(Source: TBGRABitmap);
|
---|
271 | var
|
---|
272 | UsedPart: TBGRABitmap;
|
---|
273 | NbUsedPixels: integer;
|
---|
274 | begin
|
---|
275 | FreeData;
|
---|
276 | if Source = nil then
|
---|
277 | begin
|
---|
278 | FWidth := 0;
|
---|
279 | FHeight := 0;
|
---|
280 | FCaption := '';
|
---|
281 | exit;
|
---|
282 | end;
|
---|
283 | FWidth := Source.Width;
|
---|
284 | FHeight := Source.Height;
|
---|
285 | FCaption := Source.Caption;
|
---|
286 | FBounds := Source.GetImageBounds([cRed,cGreen,cBlue,cAlpha]);
|
---|
287 | NbUsedPixels := (FBounds.Right-FBounds.Left)*(FBounds.Bottom-FBounds.Top);
|
---|
288 | FUncompressedData := TMemoryStream.Create;
|
---|
289 | if NbUsedPixels = 0 then exit;
|
---|
290 |
|
---|
291 | if (FBounds.Left <> 0) or (FBounds.Top <> 0)
|
---|
292 | or (FBounds.Right <> Source.Width) or (FBounds.Bottom <> Source.Height) then
|
---|
293 | begin
|
---|
294 | UsedPart := Source.GetPart(FBounds) as TBGRABitmap;
|
---|
295 | If TBGRAPixel_RGBAOrder then UsedPart.SwapRedBlue;
|
---|
296 | FUncompressedData.Write(UsedPart.Data^,NbUsedPixels*Sizeof(TBGRAPixel));
|
---|
297 | FLineOrder := UsedPart.LineOrder;
|
---|
298 | UsedPart.Free;
|
---|
299 | end else
|
---|
300 | begin
|
---|
301 | If TBGRAPixel_RGBAOrder then Source.SwapRedBlue;
|
---|
302 | FUncompressedData.Write(Source.Data^,Source.NbPixels*Sizeof(TBGRAPixel));
|
---|
303 | If TBGRAPixel_RGBAOrder then Source.SwapRedBlue;
|
---|
304 | FLineOrder := Source.LineOrder;
|
---|
305 | end;
|
---|
306 | end;
|
---|
307 |
|
---|
308 | destructor TBGRACompressableBitmap.Destroy;
|
---|
309 | begin
|
---|
310 | FreeData;
|
---|
311 | inherited Destroy;
|
---|
312 | end;
|
---|
313 |
|
---|
314 | end.
|
---|
315 |
|
---|