TBitMap to PBitMap KOL - delphi

I would like to convert a TBitMap to a PBitMap in KOL.
I tried this but I get a black picture as an output:
function TbitMapToPBitMap (bitmap : TBitMap) : PbitMap;
begin
result := NIL;
if Assigned(bitmap) then begin
result := NewBitmap(bitmap.Width, bitmap.Height);
result.Draw(bitmap.Canvas.Handle, bitmap.Width, bitmap.Height);
end;
end;
Any idea what's wrong with it? I am using Delphi7.
Thank you for your help.
EDIT: New CODE:
function TbitMapToPBitMap (const src : TBitMap; var dest : PBitMap) : Bool;
begin
result := false;
if (( Assigned(src) ) and ( Assigned (dest) )) then begin
dest.Draw(src.Canvas.Handle, src.Width, src.Height);
result := true;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TBitMapTest : TBitMap;
PBitMapTest : PBitMap;
begin
TBitMapTest := TBitMap.Create;
TBitMapTest.LoadFromFile ('C:\test.bmp');
PBitMapTest := NewBitMap (TBitMapTest.Width, TBitMapTest.Height);
TbitMapToPBitMap (TBitMapTest, PBitMapTest);
PBitMapTest.SaveToFile ('C:\test2.bmp');
PBitMapTest.Free;
TBitMapTest.Free;
end;

To answer your question why are your target images black; it's because you were drawing those target images to source and black they were because the NewBitmap initializes images to black.
How to copy or convert if you want a TBitmap to KOL PBitmap I found only one way (maybe I missed such function in KOL, but even if so, the method used in the following code is very efficient). You can use the Windows GDI function for bit-block transfer, the BitBlt, which just copies the specified area from one canvas to another.
The following code, when you click on the button creates the VCL and KOL bitmap instances, loads the image to a VCL bitmap, call the VCL to KOL bitmap copy function and if this function succeed, draw the KOL bitmap to the form canvas and free both bitmap instances:
uses
Graphics, KOL;
function CopyBitmapToKOL(Source: Graphics.TBitmap; Target: PBitmap): Boolean;
begin
Result := False;
if Assigned(Source) and Assigned(Target) then
begin
Result := BitBlt(Target.Canvas.Handle, 0, 0, Source.Width, Source.Height,
Source.Canvas.Handle, 0, 0, SRCCOPY);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
KOLBitmap: PBitmap;
VCLBitmap: Graphics.TBitmap;
begin
VCLBitmap := Graphics.TBitmap.Create;
try
VCLBitmap.LoadFromFile('d:\CGLIn.bmp');
KOLBitmap := NewBitmap(VCLBitmap.Width, VCLBitmap.Height);
try
if CopyBitmapToKOL(VCLBitmap, KOLBitmap) then
KOLBitmap.Draw(Canvas.Handle, 0, 0);
finally
KOLBitmap.Free;
end;
finally
VCLBitmap.Free;
end;
end;

Related

Delphi - change ribbon menu color when VCL theme is applied

I'm using TRibbon on an Delphi XE7 application with VCL theme applied and I'd like to change the menu color (because it's difficult to see the items in dark themes), as following:
I've tried the following code, but it only works when style is disabled:
Ribbon1.ApplicationMenu.Menu.ColorMap.MenuColor:= clYellow;
Also no effect with this line:
Ribbon1.ApplicationMenu.Menu.ColorMap.MenuColor:= StyleServices.GetStyleColor(scButtonHot);
Does anyone know if it is possible?
Thanks a lot!
Create your own style with the color you like.
After some try, I found a solution. I don't know if it's the best approach, but it worked for me and could be useful for someone else.
The problem is the method bellow (Vcl.ActnMenus.pas), when StyleServices is enabled:
procedure TCustomActionPopupMenu.DrawBackground;
begin
inherited;
if StyleServices.Enabled and not StyleServices.IsSystemStyle then
StyleServices.DrawElement(Canvas.Handle, StyleServices.GetElementDetails(tmPopupBackground),
Rect(0, 0, Width, Height))
else
begin
Canvas.Brush.Color := ColorMap.MenuColor;
Canvas.FillRect(ClientRect);
end;
end;
So, in order to bypass this method, I just hooked it (adapting from here):
unit MethodHooker;
interface
uses Windows, Vcl.ActnMenus;
type
PInstruction = ^TInstruction;
TInstruction = packed record
Opcode: Byte;
Offset: Integer;
end;
TCustomActionPopupMenu = class(Vcl.ActnMenus.TCustomActionPopupMenu)
procedure DrawBackgroundEx;
end;
implementation
procedure Patch(Address: Pointer; const NewCode; Size: NativeUInt);
var
NumberOfBytes: NativeUInt;
begin
WriteProcessMemory(GetCurrentProcess, Address, #NewCode, Size, NumberOfBytes);
end;
procedure Redirect(OldAddress, NewAddress: Pointer);
var
NewCode: TInstruction;
begin
NewCode.Opcode := $E9;//jump relative
NewCode.Offset := Integer(NewAddress)-Integer(OldAddress)-SizeOf(NewCode);
Patch(OldAddress, NewCode, SizeOf(NewCode));
end;
{ TCustomActionPopupMenu }
procedure TCustomActionPopupMenu.DrawBackgroundEx;
begin
Canvas.Brush.Color := $00EEEAE9;
Canvas.FillRect(ClientRect);
end;
initialization
Redirect(#TCustomActionPopupMenu.DrawBackground, #TCustomActionPopupMenu.DrawBackgroundEx);
end.
That's it. Just save this unit and add it to the project. No need to call this anywhere.

Drawing on FMX canvas with WinApi functions

This question looks very simple, with VCL this is works fine (Image is TImage on VCL):
procedure TFormMain.btnDrawBackgroundClick(Sender: TObject);
var
theme: HTHEME;
begin
theme := OpenThemeData(0, 'TASKDIALOG');
if theme <> 0 then
try
DrawThemeBackground(theme,
Image.Canvas.Handle,
TDLG_SECONDARYPANEL,
0,
Image.ClientRect,
nil);
finally
CloseThemeData(theme);
end;
end;
Question: what I should change to get the same effect with FMX (on Windows)
Based on this answer you simply can't do that.
The problem is that with Firemonkey, you only have a single device
context for the form and not one for each component. When a component
needs to be redrawn, it gets passed the forms canvas but with clipping
and co-ordinates mapped to the components location.
But there is always some workaround and you can try something like this.
procedure TFormMain.btnDrawBackgroundClick(Sender: TObject);
var
lTheme : HTHEME;
lStream : TMemoryStream;
lBitmap : Vcl.Graphics.TBitmap;
begin
lTheme := OpenThemeData(0, 'TASKDIALOG');
if lTheme <> 0 then
try
lBitmap := Vcl.Graphics.TBitmap.Create;
try
lBitmap.Width := Round(Image.Width);
lBitmap.Height := Round(Image.Height);
DrawThemeBackground(lTheme, lBitmap.Canvas.Handle, TDLG_SECONDARYPANEL, 0,
Rect(0, 0, lBitmap.Width, lBitmap.Height), nil);
lStream := TMemoryStream.Create;
try
lBitmap.SaveToStream(lStream);
Image.Bitmap.LoadFromStream(lStream);
finally
lStream.Free;
end;
finally
lBitmap.Free;
end;
finally
CloseThemeData(lTheme);
end;
end;

How do I make a bitmap version of a WMF file that is loaded into a TImage.Picture and move that to a TSpeedButton.Glyph

For the sake of a minimal complete question, I have a WMF file loaded into a TImage control on a form. This control contains the property Picture, which is a TPicture type. I am trying to "rasterize" the WMF file that I loaded into the TImage, and store that into a TSpeedButton.Glyph.
What is interesting about this process is I am able to use this technique to create a resolution independent custom control (a button in my case) that will redraw its glyph for any resolution you like.
In real world usage, I would not have a TImage or a TSpeedButton, but this question is fundamentally about the process of moving content from TPicture to a TBitmap.
Here is the relevant semi-working code:
procedure CopyBitmap( Source:TImage; DestSpeedButton:TSpeedButton );
var
bmp: TBitmap;
begin
bmp:=TBitmap.Create;
try
// note: with WMF loaded, Source.Picture.Bitmap.Width and Height are 0.
bmp.Width := Source.Width; // originally I had Source.Picture.Bitmap.Width, which didn't work.
bmp.Height := Source.Height; //because Source.Picture.Bitmap.Height, doesn't work.
bmp.Canvas.Draw(0,0, Source.Picture.Graphic );
DestSpeedButton.Glyph:=bmp;
finally
bmp.Free;
end;
end;
Is this the correct approach? Why does the image invert during copy?
A sample WMF file, the exact file I'm using, is found here.
Thanks David, for suggesting that I draw the background. This works.
Note that in production I would change the code below to use Vcl.GraphUtils helper called ScaleImage as the results are much prettier. See the second code sample.
// Quick and Dirty : No sub-pixel anti-aliasing.
// Also does not modifies Source, so set Source's size before you
// call this.
procedure CopyBitmap( Source:TImage; DestSpeedButton:TSpeedButton );
var
bmp: TBitmap;
begin
bmp:=TBitmap.Create;
try
bmp.SetSize( Source.Width, Source.Height);
bmp.Canvas.Pen.Style := psClear;
bmp.Canvas.Brush.Style := bsSolid;
bmp.Canvas.Brush.Color := clFuchsia;
bmp.Canvas.Rectangle(0,0, Source.Width+1,Source.Height+1 );
bmp.Canvas.Draw(0,0, Source.Picture.Graphic );
bmp.TransparentColor := clFuchsia;
DestSpeedButton.Glyph:=bmp;
finally
bmp.Free;
end;
end;
Alternative that uses more memory, and is using the TPicture type instead of TImage because in real use I don't even have a TImage just a TPicture, also this looks nicer. Note that it is written around some custom control of my own design (or yours) that has some property type TBitmap. You have to substitute your own controls, or change TMyControlWithAGlyph to TSpeedButton if that's what you want to do:
// A Bit Better Looking. Uses Vcl.GraphUtils function ScaleImage
procedure CopyBitmap( Source:TPicture;
Dest:TMyControlWithAGlyph;
DestType:TCopyDestTypeEnum;
AWidth,AHeight:Integer;
DoInvert:Boolean;
TransparentColor:TColor=clFuchsia );
var
bmp,bmpFullSize: TBitmap;
ARect:TRect;
ScaleAmount:Double;
begin
if not Assigned(Source) then
exit;
if not Assigned(Dest) then
exit;
if not Assigned(Source.Graphic) then
exit;
bmp:=TBitmap.Create;
bmpFullSize := TBitmap.Create;
try
bmpFullSize.SetSize( Source.Width, Source.Height );
bmpFullSize.PixelFormat := pf24bit;
bmpFullSize.Canvas.Pen.Style := psClear;
bmpFullSize.Canvas.Brush.Style := bsSolid;
bmpFullSize.Canvas.Brush.Color := TransparentColor;
bmpFullSize.Canvas.Rectangle(0,0, Source.Width+1,Source.Height+1 );
bmpFullSize.Canvas.Draw(0,0, Source.Graphic );
bmp.SetSize( AWidth, AHeight);
bmp.PixelFormat := pf24bit;
// Vcl.GraphiUtil version needs a floating point scale.
ScaleAmount := AWidth / Source.Width;
ScaleImage(bmpFullSize,bmp,ScaleAmount );
// This lets me have a white icon and turn it black if I want to
// or vice versa
if DoInvert then
InvertBitmap(bmp);
if DestType=DestLargeGlyph then
begin
Dest.LargeGlyph := bmp;
end
else
begin
Dest.Glyph:=bmp;
end;
finally
bmp.Free;
bmpFullSize.Free;
end;
end;
The above code also calls this little helper:
function InvertBitmap(ABitmap: TBitmap): TBitmap;
var
x, y: Integer;
ByteArray: PByteArray;
begin
ABitmap.PixelFormat := pf24Bit;
for y := 0 to ABitmap.Height - 1 do
begin
ByteArray := ABitmap.ScanLine[y];
for x := 0 to ABitmap.Width * 3 - 1 do
begin
ByteArray[x] := 255 - ByteArray[x];
end;
end;
Result := ABitmap;
end;

png to bmp conversion (maintaining transparency)

I am using delphi XE-5 and I am loading button information from a JSON file, in order to create buttons on a TMS ADVToolBar control. Each button is 50X35 and in png format with transparency.
I am getting each url, using the idHTTP component to retrieve it to a stream and then load it into a png. I then draw it onto a transparent BMP. However, I dont think this is the correct way. Anyway, the bmp is then added to a TImageList where it is assigned to a button using the index. The Image shows up on the button, but with no transparency.
see my code below:
imgUrl:= //code to get img url from JSON file;
MS := TMemoryStream.Create;
png := TPngImage.Create;
png.Transparent:= True;
try
idHTTP1.get(imgUrl,MS);
Ms.Seek(0,soFromBeginning);
png.LoadFromStream(MS);
bmp:= TBitmap.Create;
bmp.Transparent:= True;
bmp.Width:= 50;
bmp.Height:= 50;
png.Draw(bmp.Canvas, Rect(7, 7, png.Width, png.Height));
ImageList1.Add(bmp, nil);
AdvGlowBtn.Images:= ImageList1;
AdvGlowBtn.Layout:= blGlyphTop;
AdvGlowBtn.WordWrap:= False;
AdvGlowBtn.AutoSize:= True;
AdvGlowBtn.ImageIndex:= ImageList1.Count-1;
bmp.Free;
finally
FreeAndNil(png);
FreeAndNil(MS);
end;
At first you have to enable the runtime themes (Project Manager) otherwise you will have no transparency of your images.
And this is the code to load the PNG image into your ImageList1
bmp := TBitmap.Create;
try
// everything done before to bmp has no effect
bmp.Assign( png );
// if for some reason the loaded image is smaller
// set the size to avoid the invalid image size error
bmp.Width := ImageList1.Width;
bmp.Height := ImageList1.Height;
AdvGlowBtn.Images:= ImageList1;
...
// now add the Bitmap to the ImageList
AdvGlowBtn.ImageIndex := ImageList1.Add( bmp, nil );
finally
bmp.Free;
end;
I have an old project in Delphi 5 and I still using it sometimes.
This is my solution using the png object.
procedure ImageList2Alpha(const ImageList: TImageList);
const
Mask: array[Boolean] of Longint = (0, ILC_MASK);
var
TempList: TImageList;
begin
if Assigned(ImageList) then
begin
TempList := TImageList.Create(nil);
try
TempList.Assign(ImageList);
with ImageList do
begin
Handle := ImageList_Create(Width, Height, ILC_COLOR32 or Mask[Masked], 0, AllocBy);
if not HandleAllocated then
raise EInvalidOperation.Create(SInvalidImageList);
end;
Imagelist.AddImages(TempList);
finally
FreeAndNil(TempList);
end;
end;
end;
procedure LoadPngToBmp(var Dest: TBitmap; AFilename: TFilename);
type
TRGB32 = packed record
B, G, R, A : Byte;
end;
PRGBArray32 = ^TRGBArray32;
TRGBArray32 = array[0..0] of TRGB32;
type
TRG24 = packed record
rgbtBlue, rgbtGreen, rgbtRed : Byte;
end;
PRGBArray24 = ^TPRGBArray24;
TPRGBArray24 = array[0..0] of TRG24;
type
TByteArray = Array[Word] of Byte;
PByteArray = ^TByteArray;
TPByteArray = array[0..0] of TByteArray;
var
BMP : TBitmap;
PNG: TPNGObject;
x, y: Integer;
BmpRow: PRGBArray32;
PngRow : PRGBArray24;
AlphaRow: PByteArray;
begin
Bmp := TBitmap.Create;
PNG := TPNGObject.Create;
try
if AFilename <> '' then
begin
PNG.LoadFromFile(AFilename);
BMP.PixelFormat := pf32bit;
BMP.Height := PNG.Height;
BMP.Width := PNG.Width;
if ( PNG.TransparencyMode = ptmPartial ) then
begin
for Y := 0 to BMP.Height-1 do
begin
BmpRow := PRGBArray32(BMP.ScanLine[Y]);
PngRow := PRGBArray24(PNG.ScanLine[Y]);
AlphaRow := PByteArray(PNG.AlphaScanline[Y]);
for X := 0 to BMP.Width - 1 do
begin
with BmpRow[X] do
begin
with PngRow[X] do
begin
R := rgbtRed; G := rgbtGreen; B := rgbtBlue;
end;
A := Byte(AlphaRow[X]);
end;
end;
end;
end else
begin
for Y := 0 to BMP.Height-1 do
begin
BmpRow := PRGBArray32(BMP.ScanLine[Y]);
PngRow := PRGBArray24(PNG.ScanLine[Y]);
for X := 0 to BMP.Width - 1 do
begin
with BmpRow[X] do
begin
with PngRow[X] do
begin
R := rgbtRed; G := rgbtGreen; B := rgbtBlue;
end;
A := 255;
end;
end;
end;
end;
Dest.Assign(BMP);
end;
finally
Bmp.Free;
PNG.Free;
end;
end;
Call ImageList2Alpha(YourImageList) on the OnCreate of the Form (FormCreate), and the ImageList will be ready to store your Bitmaps32 keeping the transparency.
Call the LoadPngToBmp procedure to convert a PNG to Bitmap32 and then, store it on your ImageList.
The TBitmap class uses Windows own libraries to manipulate Bitmaps. Depending on you Windows version, the underlying Operating System libraries does not support 32 bits BMPs, despite the libraries header files declares a BITMAPQUAD struct.
For newer versions of Windows (Vista and above afaik), the field BITMAPQUAD.reserved is used to store the alpha channel. For older versions, this field must remain zero (0x00).
If you are using a "recent" version of Windows, the only possible explanation I see is that the TBitmap class were not updated to support the alpha channel.
Using the class TPNGImage should not be an issue instead of converting it to BMP before using, unless you have some more specific needs.
Use it like that:
ABitmap.SetSize(png.Width, png.Height);
png.AssignTo(ABitmap);

How to preserve the PNGImage mask when loaded from resource using Delphi XE

I am upgrading my code to Delphi XE (from Delphi 7) and am trying to eliminate all unnecessary libraries. I've used PNGComponents for ages but it is time to move on and use the native TImageList and TPNGImage.
Part of my code loads an image list at runtime from linked in resources. My working PNGComponents code for this is:
function CreateAndLoadImageList( ASize : integer ) : TPngImageList;
var
PngObject : TPngObject;
I : integer;
begin
Result := TPngImageList.Create( nil );
Result.BeginUpdate;
try
Result.Width := ASize;
Result.Height := ASize;
PngObject := TPngObject.create;
try
For I := 0 to Length( ArtImageNames ) -1 do
begin
PngObject.LoadFromResourceName( hInstance, Format( 'AImg%d_%d', [ASize, I]));
Result.PngImages.Add( False).PngImage := PngObject;
end;
finally
PngObject.Free;
end;
finally
Result.EndUpdate;
end;
end;
Using an answer in this question I am now trying the code below which shows the images but with black backgrounds, presumably because the mask is lost. I guess I need a mask bitmap to pass to ImageList_Add where the '0' is but I'm poor on this stuff. Does anyone know how I might get this working?
function CreateAndLoadImageList( ASize : integer ) : TImageList;
var
PngImage : TPngImage;
bmp : TBitmap;
I : integer;
begin
Result := TImageList.Create( nil );
Result.Masked := False;
Result.DrawingStyle := dsTransparent;
Result.BeginUpdate;
try
Result.Width := ASize;
Result.Height := ASize;
Result.Masked := False;
PngImage := TPngImage.create;
try
For I := 0 to Length( ArtImageNames ) -1 do
begin
PngImage.LoadFromResourceName( hInstance, Format( 'AImg%d_%d', [ASize, I]));
bmp:=TBitmap.Create;
PngImage.AssignTo(bmp);
bmp.AlphaFormat:=afIgnored;
ImageList_Add( Result.Handle, bmp.Handle, 0);
Bmp.Free;
end;
finally
PngImage.Free;
end;
finally
Result.EndUpdate;
end;
end;
PNG images do partial transparency using alpha channels. They do not use masks. I imagine that your problem is that you are not retaining the alpha in your image list.
You should set your image list's ColorDepth to cd32Bit.
I would expect the bitmap's properties to be set correctly when you assign your PNG image to it so remove the line which sets AlphaFormat.
As an aside it is intended that you use Assign rather than AssignTo. AssignTo is an internal method that enables a mild form of double dispatch for TPersistent.

Resources