Как получить прозрачность растрового изображения без необходимости рисовать сначала? - PullRequest
9 голосов
/ 12 октября 2011

По умолчанию у вновь созданного растрового изображения (белый) фон. По крайней мере, запрос на свойство Pixels подтверждает. Но почему этот цвет фона не используется в качестве прозрачного цвета, если Transparent установлен в значение true?

Рассмотрим этот простой тестовый код:

procedure TForm1.Button1Click(Sender: TObject);
var
  Bmp: TBitmap;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.Width := 100;
    Bmp.Height := 100;
    Bmp.Transparent := True;
    Canvas.Draw(0, 0, Bmp);                              // A white block is drawn
    Bmp.Canvas.Brush.Color := Bmp.Canvas.Pixels[0, 99];  // = 'clWhite'
    Bmp.Canvas.FillRect(Rect(0, 0, 100, 100));
    Canvas.Draw(0, 100, Bmp);                            // "Nothing" is drawn
  finally
    Bmp.Free;
  end;
end;

По какой-то причине вся поверхность растрового изображения должна быть окрашена, прежде чем она станет прозрачной, что звучит странно.

Следующие варианты пытаются исключить призыв к FillRect, все с одинаковым результатом (без прозрачности):

  • Только настройка Brush.Color,
  • Brush.Handle := CreateSolidBrush(clWhite)
  • PixelFormat := pf32Bit
  • IgnorePalette := True * * 1023
  • TransparantColor := clWhite
  • TransparantMode := tmFixed
  • Canvas.Pixels[0, 99] := clWhite, что делает прозрачным только пиксель,
  • Modified := True.

Итак, желательно нарисовать только часть вновь созданного растрового изображения и сделать оставшуюся поверхность прозрачной.

Использование: Delphi 7, Win 7 / 64.

Ответы [ 3 ]

9 голосов
/ 12 октября 2011

Просто установите TransparentColor и Canvas.Brush.Color перед настройкой размеров растрового изображения.

3 голосов
/ 09 декабря 2017

Мне действительно нужно было иметь возможность создать полностью прозрачный (и в остальном пустой / пустой) 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;
1 голос
/ 12 октября 2011

Это нарисует красный квадрат, а остальное прозрачно.

procedure TForm1.btnDrawClick(Sender: TObject);
var
  Bmp: TBitmap;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.Width := 100;
    Bmp.Height := 100;
    Bmp.Transparent := TRUE;
    Bmp.TransparentColor := clWhite;

    Bmp.Canvas.Brush.Color := clWhite;
    Bmp.Canvas.FillRect(Rect(0, 0, Bmp.Width, Bmp.Height));
    Bmp.Canvas.Brush.Color := clRed;
    Bmp.Canvas.FillRect(Rect(42, 42, 20, 20));
    Canvas.Draw(12, 12, Bmp);
  finally
    Bmp.Free;
  end;
end;
...