1 | unit BGRAWriteBmpMioMap;
|
---|
2 |
|
---|
3 | {$mode objfpc}{$H+}
|
---|
4 |
|
---|
5 | interface
|
---|
6 |
|
---|
7 | uses
|
---|
8 | Classes, SysUtils, FPimage, BGRABitmapTypes, BGRAReadBmpMioMap;
|
---|
9 |
|
---|
10 | type
|
---|
11 |
|
---|
12 | { TBGRAWriterBmpMioMap }
|
---|
13 |
|
---|
14 | TBGRAWriterBmpMioMap = class (TFPCustomImageWriter)
|
---|
15 | protected
|
---|
16 | FHeader: TMioHeader;
|
---|
17 | FPalette: packed array of record
|
---|
18 | ColorValue: Word;
|
---|
19 | AlphaValue: Byte;
|
---|
20 | Padding: Byte;
|
---|
21 | end;
|
---|
22 | FPaletteIndexes: packed array[0..65535] of NativeInt;
|
---|
23 | FPaletteOffset: NativeInt;
|
---|
24 | FPaletteAlpha: boolean;
|
---|
25 | FChunks: array of TMemoryStream;
|
---|
26 | FCurrentChunk: TMemoryStream;
|
---|
27 | FMaxChunkSize: Word;
|
---|
28 | function IndexOfColor(const AColor: TBGRAPixel): NativeInt;
|
---|
29 | procedure InitHeader(Img: TFPCustomImage);
|
---|
30 | procedure InitPalette;
|
---|
31 | procedure InitChunks;
|
---|
32 | procedure FlushChunk;
|
---|
33 | procedure FreeChunks;
|
---|
34 | procedure NeedChunk;
|
---|
35 | procedure AppendToChunks(const Buffer; Count: integer);
|
---|
36 | procedure BuildPaletteAndChunks(Img: TFPCustomImage);
|
---|
37 | procedure WriteHeader(Str: TStream);
|
---|
38 | procedure WritePalette(Str: TStream);
|
---|
39 | procedure WriteChunks(Str: TStream);
|
---|
40 | procedure ReadScanline(Img: TFPCustomImage; Y: integer; ADest: PBGRAPixel);
|
---|
41 | procedure InternalWrite(Str: TStream; Img: TFPCustomImage); override;
|
---|
42 | public
|
---|
43 | constructor Create; override;
|
---|
44 | property MaxChunkSize: Word read FMaxChunkSize write FMaxChunkSize;
|
---|
45 | end;
|
---|
46 |
|
---|
47 | implementation
|
---|
48 |
|
---|
49 | { TBGRAWriterBmpMioMap }
|
---|
50 |
|
---|
51 | function TBGRAWriterBmpMioMap.IndexOfColor(const AColor: TBGRAPixel): NativeInt;
|
---|
52 | var searchedColorValue: Word;
|
---|
53 | searchedAlphaValue: Byte;
|
---|
54 | i,startSearch,endSearch: NativeInt;
|
---|
55 | begin
|
---|
56 | searchedColorValue:= BGRAToMioMap(AColor);
|
---|
57 | searchedAlphaValue:= AlphaToMioMap(AColor.alpha);
|
---|
58 | if length(FPalette)>0 then
|
---|
59 | begin
|
---|
60 | with FPalette[0] do
|
---|
61 | begin
|
---|
62 | if (ColorValue = searchedColorValue) and
|
---|
63 | (AlphaValue = searchedAlphaValue) then
|
---|
64 | begin
|
---|
65 | result := 0;
|
---|
66 | exit;
|
---|
67 | end;
|
---|
68 | end;
|
---|
69 | end;
|
---|
70 |
|
---|
71 | startSearch:= FPaletteOffset+1;
|
---|
72 | endSearch:= FPaletteOffset+$FC;
|
---|
73 | if endSearch >= FHeader.nbColors then
|
---|
74 | endSearch:= FHeader.nbColors-1;
|
---|
75 | for i := startSearch to endSearch do
|
---|
76 | with FPalette[i] do
|
---|
77 | begin
|
---|
78 | if (ColorValue = searchedColorValue)
|
---|
79 | and (AlphaValue = searchedAlphaValue) then
|
---|
80 | begin
|
---|
81 | result := i;
|
---|
82 | exit;
|
---|
83 | end;
|
---|
84 | end;
|
---|
85 |
|
---|
86 | result := FPaletteIndexes[searchedColorValue];
|
---|
87 | if (result <> -1) and (FPalette[result].AlphaValue <> searchedAlphaValue) then
|
---|
88 | result := -1;
|
---|
89 |
|
---|
90 | if result = -1 then
|
---|
91 | begin
|
---|
92 | if fheader.nbColors = 65535 then
|
---|
93 | raise exception.Create('Too many colors');
|
---|
94 | result := fheader.nbColors;
|
---|
95 | inc(FHeader.nbColors);
|
---|
96 | if length(FPalette) <= result then
|
---|
97 | setlength(FPalette, length(FPalette)*2 + 128);
|
---|
98 | with FPalette[result] do
|
---|
99 | begin
|
---|
100 | ColorValue := searchedColorValue;
|
---|
101 | AlphaValue := searchedAlphaValue;
|
---|
102 | end;
|
---|
103 | FPaletteIndexes[searchedColorValue] := result;
|
---|
104 | if (searchedAlphaValue > 0) and (searchedAlphaValue < 32) then
|
---|
105 | FPaletteAlpha := true;
|
---|
106 | end;
|
---|
107 | end;
|
---|
108 |
|
---|
109 | procedure TBGRAWriterBmpMioMap.InitHeader(Img: TFPCustomImage);
|
---|
110 | begin
|
---|
111 | if (Img.Width > 65535) or (Img.Height > 65535) then
|
---|
112 | raise exception.Create('Image too big to be saved in Bmp MioMap format');
|
---|
113 | FHeader.magic := MioMapMagicValue;
|
---|
114 | fheader.format:= 0;
|
---|
115 | FHeader.width := Img.Width;
|
---|
116 | FHeader.height := img.Height;
|
---|
117 | FHeader.nbColors := 0;
|
---|
118 | FHeader.nbChunks := 0;
|
---|
119 | end;
|
---|
120 |
|
---|
121 | procedure TBGRAWriterBmpMioMap.InitPalette;
|
---|
122 | var i: NativeInt;
|
---|
123 | begin
|
---|
124 | for i := 0 to high(FPaletteIndexes) do
|
---|
125 | FPaletteIndexes[i] := -1;
|
---|
126 | FPaletteOffset := 0;
|
---|
127 | FPaletteAlpha := false;
|
---|
128 | IndexOfColor(BGRAPixelTransparent); //define transparent color as zero
|
---|
129 | end;
|
---|
130 |
|
---|
131 | procedure TBGRAWriterBmpMioMap.InitChunks;
|
---|
132 | begin
|
---|
133 | FCurrentChunk := nil;
|
---|
134 | end;
|
---|
135 |
|
---|
136 | procedure TBGRAWriterBmpMioMap.FlushChunk;
|
---|
137 | begin
|
---|
138 | if FCurrentChunk <> nil then
|
---|
139 | begin
|
---|
140 | setlength(FChunks, length(FChunks)+1);
|
---|
141 | FChunks[high(FChunks)] := FCurrentChunk;
|
---|
142 | FCurrentChunk := nil;
|
---|
143 | FHeader.nbChunks += 1;
|
---|
144 | end;
|
---|
145 | end;
|
---|
146 |
|
---|
147 | procedure TBGRAWriterBmpMioMap.FreeChunks;
|
---|
148 | var
|
---|
149 | i: Integer;
|
---|
150 | begin
|
---|
151 | FreeAndNil(FCurrentChunk);
|
---|
152 | for i := 0 to high(FChunks) do
|
---|
153 | FChunks[i].Free;
|
---|
154 | FChunks := nil;
|
---|
155 | end;
|
---|
156 |
|
---|
157 | procedure TBGRAWriterBmpMioMap.NeedChunk;
|
---|
158 | begin
|
---|
159 | if FCurrentChunk = nil then
|
---|
160 | begin
|
---|
161 | if FHeader.nbChunks = 65535 then
|
---|
162 | raise exception.Create('Too many chunks');
|
---|
163 | FCurrentChunk := TMemoryStream.Create;
|
---|
164 | end;
|
---|
165 | end;
|
---|
166 |
|
---|
167 | procedure TBGRAWriterBmpMioMap.AppendToChunks(const Buffer; Count: integer);
|
---|
168 | begin
|
---|
169 | if Count > 65535 then
|
---|
170 | raise exception.Create('Buffer too big');
|
---|
171 | NeedChunk;
|
---|
172 | if FCurrentChunk.Size + Count > MaxChunkSize then
|
---|
173 | begin
|
---|
174 | FlushChunk;
|
---|
175 | NeedChunk;
|
---|
176 | end;
|
---|
177 | FCurrentChunk.WriteBuffer(Buffer,Count);
|
---|
178 | end;
|
---|
179 |
|
---|
180 | procedure TBGRAWriterBmpMioMap.BuildPaletteAndChunks(Img: TFPCustomImage);
|
---|
181 | var y,w: NativeInt;
|
---|
182 | PData,PDataEnd: PBGRAPixel;
|
---|
183 | p: PBGRAPixel;
|
---|
184 | currentColorIndex,
|
---|
185 | nextColorIndex,
|
---|
186 | repCount: NativeInt;
|
---|
187 | b: byte;
|
---|
188 | changeOfsRec: packed record
|
---|
189 | valFD: byte;
|
---|
190 | valLo: byte;
|
---|
191 | valHi: byte;
|
---|
192 | end;
|
---|
193 | repRec: packed record
|
---|
194 | valFE: byte;
|
---|
195 | relativeColorIndex: byte;
|
---|
196 | count: byte;
|
---|
197 | end;
|
---|
198 | repZeroRec: packed record
|
---|
199 | valFF: byte;
|
---|
200 | count: byte;
|
---|
201 | end;
|
---|
202 |
|
---|
203 | begin
|
---|
204 | w := Img.Width;
|
---|
205 | getmem(PData, w*sizeof(TBGRAPixel));
|
---|
206 | try
|
---|
207 | PDataEnd := PData+w;
|
---|
208 | for y := 0 to Img.Height-1 do
|
---|
209 | begin
|
---|
210 | ReadScanline(Img,Y,PData);
|
---|
211 | p := PData;
|
---|
212 | while p < PDataEnd do
|
---|
213 | begin
|
---|
214 | currentColorIndex:= IndexOfColor(p^);
|
---|
215 | nextColorIndex := currentColorIndex;
|
---|
216 | repCount:= 1;
|
---|
217 | inc(p);
|
---|
218 | while p < PDataEnd do
|
---|
219 | begin
|
---|
220 | nextColorIndex:= IndexOfColor(p^);
|
---|
221 | if nextColorIndex = currentColorIndex then
|
---|
222 | begin
|
---|
223 | inc(p);
|
---|
224 | inc(repCount);
|
---|
225 | if repCount = 255 then break;
|
---|
226 | end
|
---|
227 | else
|
---|
228 | break;
|
---|
229 | end;
|
---|
230 | if currentColorIndex = 0 then
|
---|
231 | begin
|
---|
232 | if repCount = 1 then
|
---|
233 | begin
|
---|
234 | b := 0;
|
---|
235 | AppendToChunks(b,1);
|
---|
236 | end else
|
---|
237 | begin
|
---|
238 | repZeroRec.valFF := $ff;
|
---|
239 | repZeroRec.count := repCount;
|
---|
240 | AppendToChunks(repZeroRec, sizeof(repZeroRec));
|
---|
241 | end;
|
---|
242 | end else
|
---|
243 | begin
|
---|
244 | if (currentColorIndex < FPaletteOffset+1)
|
---|
245 | or (currentColorIndex > FPaletteOffset+$FC) then
|
---|
246 | begin
|
---|
247 | if (abs(nextColorIndex-currentColorIndex) < $FC) then
|
---|
248 | begin
|
---|
249 | FPaletteOffset := (nextColorIndex+currentColorIndex) div 2 - 126;
|
---|
250 | end else
|
---|
251 | FPaletteOffset := currentColorIndex-126;
|
---|
252 | if FPaletteOffset < 0 then FPaletteOffset := 0;
|
---|
253 | changeOfsRec.valFD := $fd;
|
---|
254 | changeOfsRec.valLo := FPaletteOffset and 255;
|
---|
255 | changeOfsRec.valHi := FPaletteOffset shr 8;
|
---|
256 | AppendToChunks(changeOfsRec,sizeof(changeOfsRec));
|
---|
257 | end;
|
---|
258 | if (currentColorIndex < FPaletteOffset+1)
|
---|
259 | or (currentColorIndex > FPaletteOffset+$FC) then
|
---|
260 | raise exception.Create('Index out of range');
|
---|
261 | if repCount = 1 then
|
---|
262 | begin
|
---|
263 | b := currentColorIndex-FPaletteOffset;
|
---|
264 | AppendToChunks(b,1);
|
---|
265 | end else
|
---|
266 | if repCount = 2 then
|
---|
267 | begin
|
---|
268 | b := currentColorIndex-FPaletteOffset;
|
---|
269 | AppendToChunks(b,1);
|
---|
270 | AppendToChunks(b,1);
|
---|
271 | end else
|
---|
272 | begin
|
---|
273 | repRec.valFE:= $FE;
|
---|
274 | repRec.count := repCount;
|
---|
275 | repRec.relativeColorIndex := currentColorIndex-FPaletteOffset;
|
---|
276 | AppendToChunks(repRec, sizeof(repRec));
|
---|
277 | end;
|
---|
278 | end;
|
---|
279 | end;
|
---|
280 | FlushChunk;
|
---|
281 | end;
|
---|
282 | finally
|
---|
283 | freemem(PData);
|
---|
284 | end;
|
---|
285 | end;
|
---|
286 |
|
---|
287 | procedure TBGRAWriterBmpMioMap.WriteChunks(Str: TStream);
|
---|
288 | var
|
---|
289 | bigChunkDef: packed record
|
---|
290 | val255: byte;
|
---|
291 | valHi: byte;
|
---|
292 | valLo: byte;
|
---|
293 | end;
|
---|
294 | i: NativeInt;
|
---|
295 | begin
|
---|
296 | for i := 0 to high(FChunks) do
|
---|
297 | begin
|
---|
298 | if FChunks[i].Size > 254 then
|
---|
299 | begin
|
---|
300 | bigChunkDef.val255 := 255;
|
---|
301 | bigChunkDef.valHi := FChunks[i].Size shr 8;
|
---|
302 | bigChunkDef.valLo := FChunks[i].Size and 255;
|
---|
303 | Str.WriteBuffer(bigChunkDef, sizeof(bigChunkDef));
|
---|
304 | end else
|
---|
305 | Str.WriteByte(FChunks[i].Size);
|
---|
306 | end;
|
---|
307 | for i := 0 to high(FChunks) do
|
---|
308 | begin
|
---|
309 | FChunks[i].Position := 0;
|
---|
310 | if Str.CopyFrom(FChunks[i],FChunks[i].Size) <> FChunks[i].Size then
|
---|
311 | raise exception.Create('Unable to write chunk');
|
---|
312 | end;
|
---|
313 | end;
|
---|
314 |
|
---|
315 | procedure TBGRAWriterBmpMioMap.WriteHeader(Str: TStream);
|
---|
316 | var header: TMioHeader;
|
---|
317 | begin
|
---|
318 | if FPaletteAlpha then FHeader.format := 1;
|
---|
319 | FlushChunk;
|
---|
320 |
|
---|
321 | header := FHeader;
|
---|
322 | header.format:= NtoLE(header.format);
|
---|
323 | header.width:= NtoLE(header.width);
|
---|
324 | header.height:= NtoLE(header.height);
|
---|
325 | header.nbColors:= NtoLE(header.nbColors);
|
---|
326 | header.nbChunks:= NtoLE(header.nbChunks);
|
---|
327 | Str.WriteBuffer(header, sizeof(header));
|
---|
328 | end;
|
---|
329 |
|
---|
330 | procedure TBGRAWriterBmpMioMap.WritePalette(Str: TStream);
|
---|
331 | var
|
---|
332 | colors: packed array of Word;
|
---|
333 | alphas: packed array of byte;
|
---|
334 | i: NativeInt;
|
---|
335 | begin
|
---|
336 | setlength(Colors, FHeader.nbColors);
|
---|
337 | for i := 0 to FHeader.nbColors-1 do
|
---|
338 | colors[i] := NtoLE(FPalette[i].ColorValue);
|
---|
339 | Str.WriteBuffer(colors[0], length(Colors)*sizeof(word));
|
---|
340 | if FPaletteAlpha then
|
---|
341 | begin
|
---|
342 | setlength(alphas, FHeader.nbColors);
|
---|
343 | for i := 0 to FHeader.nbColors-1 do
|
---|
344 | alphas[i] := FPalette[i].AlphaValue;
|
---|
345 | Str.WriteBuffer(alphas[0], length(alphas)*sizeof(byte));
|
---|
346 | end;
|
---|
347 | end;
|
---|
348 |
|
---|
349 | procedure TBGRAWriterBmpMioMap.ReadScanline(Img: TFPCustomImage; Y: integer;
|
---|
350 | ADest: PBGRAPixel);
|
---|
351 | var
|
---|
352 | i: NativeInt;
|
---|
353 | begin
|
---|
354 | if Img is TBGRACustomBitmap then
|
---|
355 | Move(TBGRACustomBitmap(Img).ScanLine[Y]^, ADest^, Img.Width*sizeof(TBGRAPixel))
|
---|
356 | else
|
---|
357 | begin
|
---|
358 | for i := 0 to Img.Width-1 do
|
---|
359 | (ADest+i)^ := FPColorToBGRA(Img.Colors[y,i]);
|
---|
360 | end;
|
---|
361 | end;
|
---|
362 |
|
---|
363 | procedure TBGRAWriterBmpMioMap.InternalWrite(Str: TStream; Img: TFPCustomImage);
|
---|
364 | begin
|
---|
365 | try
|
---|
366 | InitHeader(Img);
|
---|
367 | InitPalette;
|
---|
368 | InitChunks;
|
---|
369 | BuildPaletteAndChunks(Img);
|
---|
370 | WriteHeader(Str);
|
---|
371 | WritePalette(Str);
|
---|
372 | WriteChunks(Str);
|
---|
373 | finally
|
---|
374 | FreeChunks;
|
---|
375 | end;
|
---|
376 | end;
|
---|
377 |
|
---|
378 | constructor TBGRAWriterBmpMioMap.Create;
|
---|
379 | begin
|
---|
380 | inherited Create;
|
---|
381 | MaxChunkSize := 254;
|
---|
382 | end;
|
---|
383 |
|
---|
384 | initialization
|
---|
385 |
|
---|
386 | DefaultBGRAImageWriter[ifBmpMioMap] := TBGRAWriterBmpMioMap;
|
---|
387 |
|
---|
388 | end.
|
---|
389 |
|
---|