| 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 |
|
|---|