Мне действительно нужно было иметь возможность создать полностью прозрачный (и в остальном пустой / пустой) TBitmap произвольного размера в 32-битном формате RGBA.Много раз.Lazarus может загрузить такой растровый файл в TBitmap, и после его загрузки вы можете манипулировать им с помощью scanline и других файлов, не использующих формат RGBA.Но это не работает, когда вы сами создаете TBitmap.Пиксельный формат, похоже, полностью игнорируется.Итак, то, что я сделал, настолько оригинально и просто, что это почти удивляет (!).Но это практично, работает очень хорошо, и полностью не зависит от LCL и любых сторонних библиотек.Даже не зависит от графического модуля, потому что он генерирует фактический 32-битный файл RGBA BMP (я генерирую его в TMemoryStream, вы можете генерировать по-другому).Затем, когда он у вас есть, в другом месте вашего кода вы можете просто загрузить его с помощью источника TBitmap.LoadFrom или источника TPicture.LoadFrom.
Фоновая история Сначала я хотел сгенерировать правильно отформатированный файл BMP.следуя формату, как описано здесь: http://www.fileformat.info/format/bmp/egff.htm Но было несколько вариантов формата BMP, и я не знал, по какому из них я должен был следовать.Поэтому я решил использовать метод обратного инжиниринга, но описание формата помогло мне позже.Я использовал графический редактор (я использовал GIMP) для создания пустого файла RGBA BMP размером 1x1 пикселя 32 и назвал его alpha1p.bmp, он содержал только прозрачность и ничего больше.Затем я изменил размер холста до 10x10 пикселей и сохранил как файл alpha10p.bmp.Затем я сравнил два файла: , сравнивающий два bmp-файла в vbindiff на Ubuntu Так что я обнаружил, что единственными отличиями были добавленные пиксели (каждый из которых составлял 4 байта, все нули RGBA) и несколько других байтов взаголовок.Из-за документации по формату в ссылке, которой я поделился, я понял, что это: FileSize
(в байтах), BitmapWidth
(в пикселях), BitmapHeight
(в пикселях) и BitmapDataSize
(в байтах).Последний был BitmapWidth*BitmapHeight*4
, потому что каждый пиксель в RGBA составляет 4 байта.Итак, теперь я мог бы просто сгенерировать всю последовательность байтов, как видно из файлов alpha1p.bmp, минус 4 байта с конца (1-й из BitmapData
), а затем добавить 4 байта (все нули) данных RGBA для каждого пикселяBMP, который я хочу сгенерировать, затем вернитесь к исходной последовательности и обновите переменные части: FileSize, width, height и размер данных BMP.И это работает без нареканий!Мне просто нужно было добавить тест на BigEndian, и перед записью менялись номера слов и слов.Это станет проблемой на платформах ARM, работающих в BigEndian.
Код
const
C_BLANK_ALPHA_BMP32_PREFIX : array[0..137]of byte
= ($42, $4D, $00, $00, $00, $00, $00, $00, $00, $00, $8A, $00, $00, $00, $7C, $00,
$00, $00, $0A, $00, $00, $00, $0A, $00, $00, $00, $01, $00, $20, $00, $03, $00,
$00, $00, $90, $01, $00, $00, $13, $0B, $00, $00, $13, $0B, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00, $00, $FF, $00, $00, $FF, $00, $00, $FF,
$00, $00, $FF, $00, $00, $00, $42, $47, $52, $73, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $02, $00, $00, $00, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00, $00, $00 );
(...)
Function RenderEmptyAlphaBitmap(AWidth,AHeight: integer): TMemoryStream;
var
buf : array[1..4096]of byte;
i,p : int64;
w : word;
dw : dword;
BE : Boolean;
begin
buf[low(buf)] := $00; //this is jyst to prevent compiler warning about not initializing buf variable
Result := TMemoryStream.Create;
if(AWidth <1)then AWidth := 1;
if(AHeight<1)then AHeight := 1;
//Write File Header:
Result.Write(C_BLANK_ALPHA_BMP32_PREFIX, SizeOf(C_BLANK_ALPHA_BMP32_PREFIX));
//Now start writing the pixels:
FillChar(buf[Low(buf)],Length(buf),$00);
p := Result.Position;
Result.Size := Result.Size+int64(AWidth)*int64(AHeight)*4;
Result.Position := p;
i := int64(AWidth)*int64(AHeight)*4; //4 because RGBA has 4 bytes
while(i>0)do
begin
if(i>Length(buf))
then w := Length(buf)
else w := i;
Result.Write(buf[Low(buf)], w);
dec(i,w);
end;
//Go back to the original header and update FileSize, Width, Height, and offset fields:
BE := IsBigEndian;
Result.Position := 2; dw := Result.Size;
if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
Result.Position := 18; dw := AWidth;
if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
Result.Position := 22; dw := AHeight;
if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
Result.Position := 34; dw := AWidth*AHeight*4;
if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
//Done:
Result.Position := 0;
end;
Обратите внимание, что константа C_BLANK_ALPHA_BMP32_PREFIX
в основном является копией последовательности байтов из моего образцаФайл alpha1p.bmp, за исключением последних 4 байтов, которые были пикселем RGBA.: D
Кроме того, я использую функцию IsBigEndian
, которая выглядит следующим образом:
Function IsBigEndian: Boolean;
type
Q = record case Boolean of
True : (i: Integer);
False : (p: array[1..4] of Byte);
end;
var
x : ^Q;
begin
New(x);
x^.i := 5;
Result := (x^.p[4]=5);
Dispose(x);
end;
Это скопировано из Lazarus Wiki: http://wiki.freepascal.org/Writing_portable_code_regarding_the_processor_architecture, вы можете пропустить эту часть, еслиВы не имеете дело с платформами BigEndian или можете использовать директиву IFDEF компилятора.Дело в том, что если вы используете {$IFDEF ENDIAN_BIG}
, то именно так обстоит дело с компилятором, тогда как функция фактически тестирует систему.Это объясняется в связанной вики.
Пример использования
Procedure TForm1.Button1Click(Sender: TObject);
var
MS : TMemoryStream;
begin
MS := RenderEmptyAlphaBitmap(Image1.Width, Image1.Height);
try
if Assigned(MS)then Image1.Picture.LoadFromStream(MS);
//you can also MS.SaveToFile('my_file.bmp'); if you want
finally
FreeAndNil(MS);
end;
end;