From 7f2710a823a2f2eb01b49a58e3440ceaaf2b996c Mon Sep 17 00:00:00 2001 From: Bergmann89 Date: Sun, 3 Nov 2013 02:59:44 +0100 Subject: [PATCH] * SaveBMP for all supported formats --- glBitmap.pas | 399 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 275 insertions(+), 124 deletions(-) diff --git a/glBitmap.pas b/glBitmap.pas index 74b9233..68d9cde 100644 --- a/glBitmap.pas +++ b/glBitmap.pas @@ -1411,7 +1411,7 @@ type end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - TBitfieldFormat = class(TFormatDescriptor) + TbmpBitfieldFormat = class(TFormatDescriptor) private procedure SetRedMask (const aValue: UInt64); procedure SetGreenMask(const aValue: UInt64); @@ -1432,17 +1432,21 @@ type end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - TColorTableEnty = packed record + TbmpColorTableEnty = packed record b, g, r, a: Byte; end; - TColorTable = array of TColorTableEnty; - TColorTableFormat = class(TFormatDescriptor) + TbmpColorTable = array of TbmpColorTableEnty; + TbmpColorTableFormat = class(TFormatDescriptor) private - fColorTable: TColorTable; + fColorTable: TbmpColorTable; public property PixelSize: Single read fPixelSize write fPixelSize; - property ColorTable: TColorTable read fColorTable write fColorTable; + property ColorTable: TbmpColorTable read fColorTable write fColorTable; property Range: TglBitmapColorRec read fRange write fRange; + property Shift: TShiftRec read fShift write fShift; + property Format: TglBitmapFormat read fFormat write fFormat; + + procedure CreateColorTable; procedure Map(const aPixel: TglBitmapPixelData; var aData: PByte; var aMapData: Pointer); override; procedure Unmap(var aData: PByte; var aPixel: TglBitmapPixelData; var aMapData: Pointer); override; @@ -1549,11 +1553,41 @@ begin result.a := a; end; +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function glBitmapShiftRec(const r, g, b, a: Byte): TShiftRec; +begin + result.r := r; + result.g := g; + result.b := b; + result.a := a; +end; + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function FormatGetSupportedFiles(const aFormat: TglBitmapFormat): TglBitmapFileTypes; begin + result := [ftDDS, ftTGA]; + + if (aFormat in [ + //4 bbp + tfLuminance4, + + //8bpp + tfR3G3B2, tfLuminance8, + + //16bpp + tfRGB4, tfRGB5, tfR5G6B5, tfRGB5A1, tfRGBA4, + tfBGR4, tfBGR5, tfB5G6R5, tfBGR5A1, tfBGRA4, + + //24bpp + tfBGR8, tfRGB8, + + //32bpp + tfRGB10, tfRGB10A2, tfRGBA8, + tfBGR10, tfBGR10A2, tfBGRA8]) then + result := result + [ftBMP]; + //TODO Supported File Formats! - result := [ftDDS, ftTGA, ftBMP]; + (* {$IFDEF GLB_SUPPORT_PNG_WRITE} if aFormat in [ @@ -3363,31 +3397,31 @@ end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TBitfieldFormat///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TBitfieldFormat.SetRedMask(const aValue: UInt64); +procedure TbmpBitfieldFormat.SetRedMask(const aValue: UInt64); begin Update(aValue, fRange.r, fShift.r); end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TBitfieldFormat.SetGreenMask(const aValue: UInt64); +procedure TbmpBitfieldFormat.SetGreenMask(const aValue: UInt64); begin Update(aValue, fRange.g, fShift.g); end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TBitfieldFormat.SetBlueMask(const aValue: UInt64); +procedure TbmpBitfieldFormat.SetBlueMask(const aValue: UInt64); begin Update(aValue, fRange.b, fShift.b); end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TBitfieldFormat.SetAlphaMask(const aValue: UInt64); +procedure TbmpBitfieldFormat.SetAlphaMask(const aValue: UInt64); begin Update(aValue, fRange.a, fShift.a); end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TBitfieldFormat.Update(aMask: UInt64; out aRange: Cardinal; out +procedure TbmpBitfieldFormat.Update(aMask: UInt64; out aRange: Cardinal; out aShift: Byte); begin aShift := 0; @@ -3409,7 +3443,7 @@ begin end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TBitfieldFormat.Map(const aPixel: TglBitmapPixelData; var aData: PByte; var aMapData: Pointer); +procedure TbmpBitfieldFormat.Map(const aPixel: TglBitmapPixelData; var aData: PByte; var aMapData: Pointer); var data: UInt64; s: Integer; @@ -3434,7 +3468,7 @@ begin end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TBitfieldFormat.Unmap(var aData: PByte; var aPixel: TglBitmapPixelData; var aMapData: Pointer); +procedure TbmpBitfieldFormat.Unmap(var aData: PByte; var aPixel: TglBitmapPixelData; var aMapData: Pointer); var data: UInt64; s, i: Integer; @@ -3458,12 +3492,90 @@ end; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TColorTableFormat/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -procedure TColorTableFormat.Map(const aPixel: TglBitmapPixelData; var aData: PByte; var aMapData: Pointer); +procedure TbmpColorTableFormat.CreateColorTable; +var + bits: Byte; + len: Integer; + i: Integer; begin - raise EglBitmapException.Create('mapping of color table formats is not supported'); + if not (Format in [tfLuminance4, tfLuminance8, tfR3G3B2]) then + raise EglBitmapException.Create(UNSUPPORTED_FORMAT); + + if (Format = tfLuminance4) then + SetLength(fColorTable, 16) + else + SetLength(fColorTable, 256); + + case Format of + tfLuminance4: begin + for i := 0 to High(fColorTable) do begin + fColorTable[i].r := 16 * i; + fColorTable[i].g := 16 * i; + fColorTable[i].b := 16 * i; + fColorTable[i].a := 0; + end; + end; + + tfLuminance8: begin + for i := 0 to High(fColorTable) do begin + fColorTable[i].r := i; + fColorTable[i].g := i; + fColorTable[i].b := i; + fColorTable[i].a := 0; + end; + end; + + tfR3G3B2: begin + for i := 0 to High(fColorTable) do begin + fColorTable[i].r := Round(((i shr Shift.r) and Range.r) / Range.r * 255); + fColorTable[i].g := Round(((i shr Shift.g) and Range.g) / Range.g * 255); + fColorTable[i].b := Round(((i shr Shift.b) and Range.b) / Range.b * 255); + fColorTable[i].a := 0; + + s := SysUtils.Format('%.2X%.2X%.2X' + sLineBreak, [fColorTable[i].r, fColorTable[i].g, fColorTable[i].b]); + fs.Write(s[1], Length(s)); + end; + end; + end; end; -procedure TColorTableFormat.Unmap(var aData: PByte; var aPixel: TglBitmapPixelData; var aMapData: Pointer); +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TbmpColorTableFormat.Map(const aPixel: TglBitmapPixelData; var aData: PByte; var aMapData: Pointer); +var + d: Byte; +begin + if not (Format in [tfLuminance4, tfLuminance8, tfR3G3B2]) then + raise EglBitmapException.Create(UNSUPPORTED_FORMAT); + + case Format of + tfLuminance4: begin + if (aMapData = nil) then + aData^ := 0; + d := LuminanceWeight(aPixel) and Range.r; + aData^ := aData^ or (d shl (4 - PtrInt(aMapData))); + inc(aMapData, 4); + if (PtrInt(aMapData) >= 8) then begin + inc(aData); + aMapData := nil; + end; + end; + + tfLuminance8: begin + aData^ := LuminanceWeight(aPixel) and Range.r; + inc(aData); + end; + + tfR3G3B2: begin + aData^ := Round( + ((aPixel.Data.r and Range.r) shl Shift.r) or + ((aPixel.Data.g and Range.g) shl Shift.g) or + ((aPixel.Data.b and Range.b) shl Shift.b)); + inc(aData); + end; + end; +end; + +procedure TbmpColorTableFormat.Unmap(var aData: PByte; var aPixel: TglBitmapPixelData; var aMapData: Pointer); type PUInt64 = ^UInt64; var @@ -3500,7 +3612,7 @@ begin inc(aData, s); end; -destructor TColorTableFormat.Destroy; +destructor TbmpColorTableFormat.Destroy; begin SetLength(fColorTable, 0); inherited Destroy; @@ -5906,16 +6018,6 @@ type biClrImportant: Cardinal; end; - (* TODO: delete? - TBMPInfoOS = packed record - biSize: Cardinal; - biWidth: Longint; - biHeight: Longint; - biPlanes: Word; - biBitCount: Word; - end; - *) - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TglBitmap.LoadBMP(const aStream: TStream): Boolean; @@ -5952,10 +6054,10 @@ function TglBitmap.LoadBMP(const aStream: TStream): Boolean; end; end; - function ReadColorTable(var aFormat: TglBitmapFormat; const aInfo: TBMPInfo): TColorTableFormat; + function ReadColorTable(var aFormat: TglBitmapFormat; const aInfo: TBMPInfo): TbmpColorTableFormat; var i, c: Integer; - ColorTable: TColorTable; + ColorTable: TbmpColorTable; begin result := nil; if (aInfo.biBitCount >= 16) then @@ -5966,20 +6068,20 @@ function TglBitmap.LoadBMP(const aStream: TStream): Boolean; c := 1 shl aInfo.biBitCount; SetLength(ColorTable, c); for i := 0 to c-1 do begin - aStream.Read(ColorTable[i], SizeOf(TColorTableEnty)); + aStream.Read(ColorTable[i], SizeOf(TbmpColorTableEnty)); if (ColorTable[i].r <> ColorTable[i].g) or (ColorTable[i].g <> ColorTable[i].b) then aFormat := tfRGB8; end; - result := TColorTableFormat.Create; + result := TbmpColorTableFormat.Create; result.PixelSize := aInfo.biBitCount / 8; result.ColorTable := ColorTable; - result.Range := glBitmapColorRec($FF, $FF, $FF, $00); + result.Range := glBitmapColorRec($FF, $FF, $FF, $00); end; ////////////////////////////////////////////////////////////////////////////////////////////////// function CheckBitfields(var aFormat: TglBitmapFormat; const aMask: TglBitmapColorRec; - const aInfo: TBMPInfo): TBitfieldFormat; + const aInfo: TBMPInfo): TbmpBitfieldFormat; var TmpFormat: TglBitmapFormat; FormatDesc: TFormatDescriptor; @@ -5999,7 +6101,7 @@ function TglBitmap.LoadBMP(const aStream: TStream): Boolean; if (aMask.a <> 0) and not TFormatDescriptor.Get(aFormat).HasAlpha then aFormat := TFormatDescriptor.Get(aFormat).WithAlpha; - result := TBitfieldFormat.Create; + result := TbmpBitfieldFormat.Create; result.PixelSize := aInfo.biBitCount / 8; result.RedMask := aMask.r; result.GreenMask := aMask.g; @@ -6016,7 +6118,7 @@ var LineBuf, ImageData, TmpData: PByte; SourceMD, DestMD: Pointer; BmpFormat: TglBitmapFormat; - ColorTable: TColorTable; + ColorTable: TbmpColorTable; //records Mask: TglBitmapColorRec; @@ -6134,12 +6236,15 @@ procedure TglBitmap.SaveBMP(const aStream: TStream); var Header: TBMPHeader; Info: TBMPInfo; - pData, pTemp: pByte; + Converter: TbmpColorTableFormat; + FormatDesc: TFormatDescriptor; + SourceFD, DestFD: Pointer; + pData, srcData, dstData, ConvertBuffer: pByte; + Pixel: TglBitmapPixelData; PixelFormat: TglBitmapPixelData; - FormatDesc: TFormatDescriptor; - ImageSize, LineSize, Padding, LineIdx, ColorIdx: Integer; - Temp, RedMask, GreenMask, BlueMask, AlphaMask: Cardinal; + ImageSize, wbLineSize, rbLineSize, Padding, LineIdx, PixelIdx, i: Integer; + RedMask, GreenMask, BlueMask, AlphaMask: Cardinal; PaddingBuff: Cardinal; @@ -6152,8 +6257,11 @@ begin if not (ftBMP in FormatGetSupportedFiles(Format)) then raise EglBitmapUnsupportedFormatFormat.Create('SaveBMP - ' + UNSUPPORTED_FORMAT); - ImageSize := TFormatDescriptor.Get(Format).GetSize(Dimension); + Converter := nil; + FormatDesc := TFormatDescriptor.Get(Format); + ImageSize := FormatDesc.GetSize(Dimension); + FillChar(Header, SizeOf(Header), 0); Header.bfType := BMP_MAGIC; Header.bfSize := SizeOf(Header) + SizeOf(Info) + ImageSize; Header.bfReserved1 := 0; @@ -6167,100 +6275,142 @@ begin Info.biPlanes := 1; Info.biCompression := BMP_COMP_RGB; Info.biSizeImage := ImageSize; - case Format of - tfR3G3B2, tfLuminance8: begin - Info.biBitCount := 8; - Header.bfOffBits := Header.bfOffBits + 256 * SizeOf(Cardinal); - end; - - tfRGB5, tfRGB5A1, tfR5G6B5, tfRGB4, tfRGBA4, - tfBGR5, tfBGR5A1, tfB5G6R5, tfBGR4, tfBGRA4: begin - Info.biBitCount := 16; - Info.biCompression := BMP_COMP_BITFIELDS; - end; - tfBGR8, tfRGB8: begin - Info.biBitCount := 24; - end; + try + case Format of + tfLuminance4: begin + Info.biBitCount := 4; + Header.bfSize := Header.bfSize + 16 * SizeOf(Cardinal); + Header.bfOffBits := Header.bfOffBits + 16 * SizeOf(Cardinal); //16 ColorTable entries + Converter := TbmpColorTableFormat.Create; + Converter.PixelSize := 0.5; + Converter.Format := Format; + Converter.Range := glBitmapColorRec($F, $F, $F, $0); + Converter.CreateColorTable; + end; - tfRGB10, tfRGB10A2, tfRGBA8, - tfBGR10, tfBGR10A2, tfBGRA8: begin - Info.biBitCount := 32; - Info.biCompression := BMP_COMP_BITFIELDS; - end; - else - raise EglBitmapUnsupportedFormatFormat.Create('SaveBMP - ' + UNSUPPORTED_FORMAT); - end; - Info.biXPelsPerMeter := 2835; - Info.biYPelsPerMeter := 2835; + tfR3G3B2, tfLuminance8: begin + Info.biBitCount := 8; + Header.bfSize := Header.bfSize + 256 * SizeOf(Cardinal); + Header.bfOffBits := Header.bfOffBits + 256 * SizeOf(Cardinal); //256 ColorTable entries + Converter := TbmpColorTableFormat.Create; + Converter.PixelSize := 1; + Converter.Format := Format; + if (Format = tfR3G3B2) then begin + Converter.Range := glBitmapColorRec($7, $7, $3, $0); + Converter.Shift := glBitmapShiftRec(0, 3, 6, 0); + end else + Converter.Range := glBitmapColorRec($FF, $FF, $FF, $0); + Converter.CreateColorTable; + end; - // prepare bitmasks - if Info.biCompression = BMP_COMP_BITFIELDS then begin - Info.biSize := Info.biSize + 4 * SizeOf(Cardinal); - Header.bfSize := Header.bfSize + 4 * SizeOf(Cardinal); - Header.bfOffBits := Header.bfOffBits + 4 * SizeOf(Cardinal); + tfRGB4, tfRGB5, tfR5G6B5, tfRGB5A1, tfRGBA4, + tfBGR4, tfBGR5, tfB5G6R5, tfBGR5A1, tfBGRA4: begin + Info.biBitCount := 16; + Info.biCompression := BMP_COMP_BITFIELDS; + end; - FormatDesc := TFormatDescriptor.Get(Format); - RedMask := FormatDesc.RedMask; - GreenMask := FormatDesc.GreenMask; - BlueMask := FormatDesc.BlueMask; - AlphaMask := FormatDesc.AlphaMask; - end; + tfBGR8, tfRGB8: begin + Info.biBitCount := 24; + end; - // headers - aStream.Write(Header, SizeOf(Header)); - aStream.Write(Info, SizeOf(Info)); - - // colortable - if Info.biBitCount = 8 then begin - Temp := 0; - for ColorIdx := Low(Byte) to High(Byte) do begin - aStream.Write(Temp, 4); - Temp := Temp + $00010101; + tfRGB10, tfRGB10A2, tfRGBA8, + tfBGR10, tfBGR10A2, tfBGRA8: begin + Info.biBitCount := 32; + Info.biCompression := BMP_COMP_BITFIELDS; + end; + else + raise EglBitmapUnsupportedFormatFormat.Create('SaveBMP - ' + UNSUPPORTED_FORMAT); + end; + Info.biXPelsPerMeter := 2835; + Info.biYPelsPerMeter := 2835; + + // prepare bitmasks + if Info.biCompression = BMP_COMP_BITFIELDS then begin + Header.bfSize := Header.bfSize + 4 * SizeOf(Cardinal); + Header.bfOffBits := Header.bfOffBits + 4 * SizeOf(Cardinal); + + RedMask := FormatDesc.RedMask; + GreenMask := FormatDesc.GreenMask; + BlueMask := FormatDesc.BlueMask; + AlphaMask := FormatDesc.AlphaMask; end; - end; - - // bitmasks - if Info.biCompression = BMP_COMP_BITFIELDS then begin - aStream.Write(RedMask, SizeOf(Cardinal)); - aStream.Write(GreenMask, SizeOf(Cardinal)); - aStream.Write(BlueMask, SizeOf(Cardinal)); - aStream.Write(AlphaMask, SizeOf(Cardinal)); - end; - - // image data - LineSize := Trunc(Width * TFormatDescriptor.Get(Format).PixelSize); - Padding := GetLineWidth - LineSize; - PaddingBuff := 0; - pData := Data; - Inc(pData, (Height -1) * LineSize); + // headers + aStream.Write(Header, SizeOf(Header)); + aStream.Write(Info, SizeOf(Info)); + + // colortable + if Assigned(Converter) then + aStream.Write(Converter.ColorTable[0].b, + SizeOf(TbmpColorTableEnty) * Length(Converter.ColorTable)); + + // bitmasks + if Info.biCompression = BMP_COMP_BITFIELDS then begin + aStream.Write(RedMask, SizeOf(Cardinal)); + aStream.Write(GreenMask, SizeOf(Cardinal)); + aStream.Write(BlueMask, SizeOf(Cardinal)); + aStream.Write(AlphaMask, SizeOf(Cardinal)); + end; - // prepare row buffer. But only for RGB because RGBA supports color masks - // so it's possible to change color within the image. - if (Format = tfRGB8) then - GetMem(pTemp, fRowSize) - else - pTemp := nil; + // image data + rbLineSize := Round(Info.biWidth * FormatDesc.PixelSize); + wbLineSize := Round(Info.biWidth * Info.biBitCount / 8); + Padding := GetLineWidth - wbLineSize; + PaddingBuff := 0; + + pData := Data; + inc(pData, (Height-1) * rbLineSize); + + // prepare row buffer. But only for RGB because RGBA supports color masks + // so it's possible to change color within the image. + if Assigned(Converter) then begin + FormatDesc.PreparePixel(Pixel); + GetMem(ConvertBuffer, wbLineSize); + SourceFD := FormatDesc.CreateMappingData; + DestFD := Converter.CreateMappingData; + end else + ConvertBuffer := nil; - try - // write image data - for LineIdx := 0 to Height - 1 do begin - // preparing row - if Format = tfRGB8 then begin - Move(pData^, pTemp^, fRowSize); - SwapRGB(pTemp, Width, false); - end else - pTemp := pData; - aStream.Write(pTemp^, LineSize); - Dec(pData, LineSize); - if Padding > 0 then - aStream.Write(PaddingBuff, Padding); + try + for LineIdx := 0 to Height - 1 do begin + // preparing row + if Assigned(Converter) then begin + srcData := pData; + dstData := ConvertBuffer; + for PixelIdx := 0 to Info.biWidth-1 do begin + FormatDesc.Unmap(srcData, Pixel, SourceFD); + with FormatDesc do begin + //TODO use convert function + for i := 0 to 3 do + if (Converter.Range.arr[i] <> Range.arr[i]) then begin + if (Range.arr[i] > 0) then + Pixel.Data.arr[i] := Round(Pixel.Data.arr[i] / Range.arr[i] * Converter.Range.arr[i]) + else + Pixel.Data.arr[i] := 0; + end; + end; + Converter.Map(Pixel, dstData, DestFD); + end; + aStream.Write(ConvertBuffer^, wbLineSize); + end else begin + aStream.Write(pData^, rbLineSize); + end; + dec(pData, rbLineSize); + if (Padding > 0) then + aStream.Write(PaddingBuff, Padding); + end; + finally + // destroy row buffer + if Assigned(ConvertBuffer) then begin + FormatDesc.FreeMappingData(SourceFD); + Converter.FreeMappingData(DestFD); + FreeMem(ConvertBuffer); + end; end; finally - // destroy row buffer - if Format = tfRGB8 then - FreeMem(pTemp); + if Assigned(Converter) then + Converter.Free; end; end; @@ -7702,3 +7852,4 @@ finalization TFormatDescriptor.Finalize; end. +