Прозрачное изображение PNG, загруженное из файла ресурсов, измененное с помощью Grapics32 и нарисованное на холсте

Мне нужна небольшая помощь...

У меня есть прозрачное изображение PNG в ресурсах моего приложения. До сих пор я загружал его в TPngImage и рисовал на экране с помощью Canvas.Draw(X, Y, PngImage);. И нарисовано прозрачно. Теперь я обновил свое приложение до DpiAware, и мне нужно масштабировать все изображения. Мне нужен качественный ресемплер, и я решил использовать Graphics32. Мне удалось сделать повторную выборку, но я не знаю, как сохранить прозрачность... Я пробую все, что могу придумать... Результатом следующего кода является изображение, нарисованное черным цветом в прозрачной области.. .

Foto32, Buff: TBitmap32;
FotoPng: TPngImage;

constructor TForm.Create(AOwner: TComponent);
const BkgHeight = 380;
var Res: TKernelResampler;
    SRect, DRect: TRect;
    ImgWidth: Integer;
begin
 inherited;
 Buff:= TBitmap32.Create;
 Res:= TKernelResampler.Create;
 Res.Kernel:= TLanczosKernel.Create;

 FotoPng:= TPngImage.Create;
 FotoPng.Transparent:= True;
 FotoPng.TransparentColor:= clBlack;
 FotoPng.LoadFromResourceName(HInstance, 'BKG_FOTO');
 Foto32:= TBitmap32.Create;
 Foto32.DrawMode:= dmBlend;
 Foto32.CombineMode:= cmMerge;
 Foto32.OuterColor:= clBlack;
 Foto32.Canvas.Brush.Style:= bsClear;
 Foto32.SetSize(FotoPng.Width, FotoPng.Height);
 FotoPng.Draw(Foto32.Canvas, Rect(0, 0, FotoPng.Width, FotoPng.Height));

 ImgWidth:= Round(Real(Foto32.Width / Foto32.Height) * BkgHeight);
 SRect:= Rect(0, 0, Foto32.Width, Foto32.Height);
 Buff.DrawMode:= dmBlend;
 Buff.CombineMode:= cmMerge;
 Buff.OuterColor:= clBlack;
 Buff.Canvas.Brush.Style:= bsClear;
 Buff.SetSize(Scale(ImgWidth), Scale(BkgHeight));
 DRect:= Rect(0, 0, Buff.Width, Buff.Height);
 Res.Resample(Buff, DRect, DRect, Foto32, SRect, dmTransparent {dmBlend}, nil);
end;

procedure TForm.Paint;
begin
 // ....
 Buff.DrawTo(Canvas.Handle, X, Y);
end;

А это мое прозрачное PNG-изображение, скомпилированное в ресурсы: https://postimg.cc/3yy3wrJB

Я нашел здесь похожий вопрос, но я не использую изображение с TImage я рисую прямо на холсте. И в единственном ответе Дэвид говорит:

В любом случае, если это так, я бы объединил поддержку прозрачности TImage с возможностью повторной выборки TBitmap32, чтобы построить решение таким образом. Сохраните исходное изображение в экземпляре TBitmap32. Всякий раз, когда вам нужно загрузить его в компонент TImage, например, при изменении размера, используйте TBitmap32, чтобы выполнить изменение размера в памяти и загрузить это изображение с измененным размером.

Это именно то, что я пытаюсь сделать, но я не знаю, почему прозрачность не работает. Любые идеи ?


person Marus Nebunu    schedule 24.01.2020    source источник
comment
Можете ли вы прикрепить изображение того, что ожидается, и текущей проблемы? Я имею в виду, если фон изображения на холсте простой (сплошной один цвет), то почему бы сначала не заполнить фон FOTO32, а затем нарисовать на нем FOTONG, а затем нарисовать на холсте?   -  person Nasreddine Galfout    schedule 25.01.2020
comment
Фон холста не сплошной, на нем другие рисунки.   -  person Marus Nebunu    schedule 25.01.2020
comment
FotoPng.Draw(Foto32.Canvas, ... -> Как только вы нарисуете png, все кончено. Вы можете рисовать прозрачно, но информация о прозрачности при этом теряется, рисование не передает альфа-канал в цель. Вам нужно передискретизировать сам png. См. здесь.   -  person Sertac Akyuz    schedule 29.01.2020
comment
@SertacAkyuz Я использовал этот код, и я получаю исключение Поддерживаются только форматы COLOR_RGBALPHA и COLOR_RGB ... потому что мое изображение png слишком большое, и мне пришлось сжать его с помощью `pngquant'. :(   -  person Marus Nebunu    schedule 30.01.2020
comment
Можно ли распаковать pngquant в обычный png в памяти?   -  person Marus Nebunu    schedule 30.01.2020
comment
Нет, преобразование с потерями необратимо.   -  person Sertac Akyuz    schedule 30.01.2020
comment
Я пробовал... Это не качество изменения размера. Он слишком гладкий и имеет некоторые артефакты по краям.   -  person Marus Nebunu    schedule 30.01.2020
comment
@SertacAkyuz Я не хочу снова делать это без потерь, я просто хочу изменить формат с COLOR_PALETTE (индексированный pngquant) на COLOR_RGBALPHA. Я думаю, что это должно быть возможно, но я не очень хорошо разбираюсь в графике в Delphi....   -  person Marus Nebunu    schedule 30.01.2020
comment
Я полагаю, что могут быть библиотеки, которые могут это сделать. Поиск был бы способом двигаться вперед. ... Я думаю, что я бы исследовал наоборот: оставил ресурс RGBA. загрузите изображение в png, измените его размер. Понизьте количество битов на плоскость перед рисованием, чтобы уменьшить накладные расходы в памяти ... Возможно, это не имеет смысла, не знаю. Какие трудности у вас возникают с размером png?   -  person Sertac Akyuz    schedule 30.01.2020
comment
Я не могу оставить ресурс RGBA, потому что он 2,8 МБ :)) Это половина размера приложения... Но если я использую pngquant, он уменьшается до 0,9 МБ. PNG должен быть высокого разрешения, чтобы хорошо выглядеть на дисплеях с высоким разрешением. Не накладные расходы на память, это моя проблема, а размер exe-файла...   -  person Marus Nebunu    schedule 30.01.2020
comment
Можете ли вы показать нам сам PNG? Я сильно подозреваю, что вы можете сохранить 1-битную маску прозрачности перед изменением размера, изменить ее размер отдельно, а затем просто повторно применить ее к изображению. Может случиться так, что подобное изображение можно будет сгенерировать программно на лету, что сделает изображение независимым от масштаба.   -  person hidefromkgb    schedule 31.01.2020
comment
Это PNG: postimg.cc/3yy3wrJB Нажмите на Download original image   -  person Marus Nebunu    schedule 31.01.2020


Ответы (1)


Ваша проблема связана с рисованием буфера на экране. Bitmap32 использует StretchDIBits для рисования, игнорируя альфа-канал.

Вы можете использовать функцию AlphaBlend, чтобы нарисовать изображение:

procedure TForm1.FormPaint(Sender: TObject);
var
  BF: TBlendFunction;
begin
  BF.BlendOp := AC_SRC_OVER;
  BF.BlendFlags := 0;
  BF.SourceConstantAlpha := 255;
  BF.AlphaFormat := AC_SRC_ALPHA;

  Winapi.Windows.AlphaBlend(Canvas.Handle, 0, 0, Buff.Width, Buff.Height,
    Buff.Canvas.Handle, 0, 0, Buff.Width, Buff.Height, BF);
end;

Или конвертируйте свой TBitmap32 в Delphi TBitmap и нарисуйте его с помощью VCL:

procedure TForm1.FormPaint(Sender: TObject);
var
  Bmp: TBitmap;
  I: Integer;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.PixelFormat := pf32bit;
    Bmp.AlphaFormat := afDefined;
    Bmp.SetSize(Buff.Width, Buff.Height);
    for I := 0 to Buff.Height - 1 do
      Move(Buff.ScanLine[I]^, Bmp.ScanLine[I]^, Buff.Width * 4);
    Canvas.Draw(0, 0, Bmp);
  finally
    Bmp.Free;
  end;
end;
person Sebastian Z    schedule 05.02.2020
comment
Bmp.AlphaFormat := afDefined; был ключом! :) Танки! - person Marus Nebunu; 06.02.2020