How to get screenshot of browser in DCEF3?
I create browser like this without VCL. The TakePicture method will only work if
No debugger is used
If ShowWindow is used
var
info: TCefWindowInfo;
Settings: TCefBrowserSettings;
begin
FillChar(info, SizeOf(info), 0);
info.width := width;
info.height := height;
FillChar(Settings, SizeOf(TCefBrowserSettings), 0);
Settings.Size := SizeOf(TCefBrowserSettings);
GetSettings(Settings);
CefBrowserHostCreateBrowser(#info, FHandler, FDefaultUrl, #settings, nil);
end;
procedure TakePicture(const Browser: ICefBrowser; Height, Width: Integer);
var
DC: HDC;
Bmp : TBitmap;
Handle : HWND;
Rect : trect;
BarHeight : integer;
BarLeft : integer;
begin
Bmp := TBitmap.Create;
Bmp.PixelFormat := pf32bit;
Handle := Browser.Host.WindowHandle;
ShowWindow(handle, SW_RESTORE); // will work only if this is used otherwise black image!
BarLeft := GetSystemMetrics(SM_CXFRAME);
BarHeight := GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYFRAME);
GetWindowRect(Handle, Rect);
DC := GetDC(Handle);
Bmp.Width := Rect.Right - Rect.Left;
Bmp.Height := (Rect.Bottom - Rect.Top);
BitBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, DC, -BarLeft, -BarHeight, SRCCOPY);
ReleaseDC(Handle, DC);
Bmp.SaveToFile('c:\test.bmp');
Bmp.Free;
end;
This is basically off-screen rendering. In the demos folder of DCEF3 you'll find a project 'offscreen'. The code you're looking for is in the OnPaint event of TChromiumOSR. It renders to a TBitmap32, but any bitmap could be made to work. Notice that it has been optimized to only paint the so-called "dirty" areas (those that have changed since last painting), but if you're making a screenshot, that's not what you want. In my check-out of the repository there's a line commented out showing the naive case of just painting everything:
SomeBitmap.SetSize(width, height);
Move(buffer^, SomeBitmap32.Bits^, width * height * 4);
It's my best guess that the magic number 4 represents 4 bytes (32-bits).
I warmly recommend using Graphics32 but it you have to use a regular TBitmap, I'll leave it up to you to work out how to turn the array of bits into pixels. Be warmed it will probably be a lot slower.
Related
I need a little help...
I have a transparent PNG image in my application resources. Until now I was loading it in a TPngImage and draw it on the screen with Canvas.Draw(X, Y, PngImage);. And it was drawn transparently. Now I updated my application to be DpiAware and I need to scale all images. I need a quality resampler and I choose to use Graphics32. I managed to do the resampling but I don't know how to keep the transparecy... I try all that I cand think of... The result of the following code is the image drawn with black color in the transparent region...
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;
And this is my transparent PNG image compiled into resources:
https://postimg.cc/3yy3wrJB
I found here a similar question, but I don't use the image with a TImage, I draw it directly on the canvas. And in the single answer, David says:
Anyway, if that is so, I would combine the transparency support of
TImage with the re-sampling ability of TBitmap32 to build a solution
that way. Keep the original image in a TBitmap32 instance. Whenever
you need to load it into the TImage component, for example when
re-sizing, use TBitmap32 to perform an in-memory re-size and load that
re-sized image.
This is exactly what I'm trying to do, but I don't know why the transparecy is not working. Any ideas ?
Your issue seems to be an issue with drawing the Buffer to the screen. Bitmap32 uses StretchDIBits for painting which ignores the alpha channel.
You could use the AlphaBlend function in order to draw your image:
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;
Or convert your TBitmap32 to a Delphi TBitmap and paint that using the 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;
I have a code, that draws the message str directly to center of the screen without a visible window.
Why using this code first works OK, but after dozens of calls, it gives Out of system resources.
It seems to free BM ok, and I don't see that it allocates other resources at all.
procedure ttsplash.UpdateSplash(const Str: string);
var
R: TRect;
P: TPoint;
S: TPoint;
bm: TBitmap;
bf: TBlendFunction;
EXSTYLE: DWORD;
x, y: integer;
pixel: PRGBQuad;
TextRed,
TextGreen,
TextBlue: byte;
begin
if str='' then exit;
EXSTYLE := GetWindowLong(Handle, GWL_EXSTYLE);
SetWindowLong(Handle, GWL_EXSTYLE, EXSTYLE or $80000);
R := ClientRect;
bm := TBitmap.Create;
try
bm.PixelFormat := pf32bit;
// bm.SetSize(ClientWidth, ClientHeight);
bm.Width := clientwidth;
bm.height := clientheight;
bm.Canvas.Brush.Color := clBlack;
bm.Canvas.FillRect(ClientRect);
bm.Canvas.Font.Assign(Self.Font);
bm.Canvas.Font.Color := clWhite;
DrawText(bm.Canvas.Handle, PChar(Str), Length(Str), R,
DT_SINGLELINE or DT_VCENTER or DT_CENTER or DT_WORD_ELLIPSIS);
TextRed := GetRValue(ColorToRGB(Font.Color));
TextGreen := GetGValue(ColorToRGB(Font.Color));
TextBlue := GetBValue(ColorToRGB(Font.Color));
for y := 0 to bm.Height - 1 do
begin
pixel := bm.ScanLine[y];
x := 0;
while x < bm.Width do
begin
with pixel^ do
begin
rgbReserved := (rgbRed + rgbGreen + rgbBlue) div 3;
rgbBlue := TextBlue * rgbReserved div 255;
rgbGreen := TextGreen * rgbReserved div 255;
rgbRed := TextRed * rgbReserved div 255;
end;
inc(pixel);
inc(x);
end;
end;
P := Point(0, 0);
S := Point(bm.Width, bm.Height);
bf.BlendOp := AC_SRC_OVER;
bf.BlendFlags := 0;
bf.SourceConstantAlpha := 255;
bf.AlphaFormat := AC_SRC_ALPHA;
UpdateLayeredWindow(Handle, 0, nil, #S, bm.Canvas.Handle, #P, 0, #bf, ULW_ALPHA)
finally
bm.Free;
end;
end;
How to debug this.
Enable debug DCUs in your project options, disable optimizations.
When you get out of resources error, hit "Break".
Inspect call stack :
The problem happens in CopyBitmap when calling GDICheck -> double click GDICheck to go there.
Put a breakpoint. Run the program - count how many times it takes before the error shows up and break just before you expect the error.
Have a look around for anything that might be odd. A good place to start is the bitmap itself. Your first clue should be that each time you call this method your text is creeping away up into the corner of your invisible form.
Let's check the bitmap header and see what's going on :
Looks like your bitmap dimensions are negative. I wonder how that happened. In fact, if you watch each time this is called, your bitmap is shrinking each time. In fact, it is shrinking by 16px in width and 38px in height - the size of the window frame.
Each time you are calling UpdateLayeredWindow you are resizing your form (its outside dimension) to be the size of the client area - the size without the window frame. Your new window gets a new frame and the client area shrinks.
Eventually there is nothing left and you are trying to make a bitmap with negative dimensions. You should therefore take into account the frame size when building your bitmap. Use the form width and height rather than the client size :
bm.Width := Width;
bm.height := Height;
Also, when making API calls, please get into the habit of checking the return values for errors, as described in the documentation for the function in question. If you are not checking for errors you are asking for problems.
Without your feedback this remains a guess, but passing a device context with the size of your form's client area, you reduce the size of your form with each call to UpdateLayeredWindow. When, eventually, you request a negative value for the bitmap dimensions, CreateCompatibleBitmap in the code path returns an error.
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;
My application is developed in Delphi 6. This is a resource intesive application due to background processing and large volume of data (It consumes around 60MB - 120MB of physical memory). One of the functionality of this application is to create barcode images and print them. If user keeps on generating the Barcodes, then at least One out of Ten Barcode has missing lines in it.
I was able to resolve this issue using TExcellenImagePrinter component. But, it diminished the performance a lot. This resolution was rejected by my client and hence, now I am trying to replace the WinAPI StretchDIBits call with GDI+.
The original source code is as following:
procedure PrintBitmap(ARect:TRect; Bitmap:TBitmap);
var
Info: PBitmapInfo;
InfoSize: dword{Integer};
Image: Pointer;
ImageSize: dword{ integer};
iWidth,iHeight :integer;
iReturn : integer ;
begin
GetDIBSizes(Bitmap.handle,InfoSize,ImageSize);
if (LoadDIBFromTBitmap(Bitmap,Pointer(Info),Image,iWidth,iHeight)) then
begin
SetStretchBltMode(Printer.Canvas.handle,STRETCH_HALFTONE);
SetBrushOrgEx(Printer.Canvas.handle, 0, 0, NIL);
iReturn := StretchDIBits(Printer.Canvas.Handle, ARect.Left, ARect.Top,
ARect.Right - ARect.Left, ARect.Bottom - ARect.Top,
0, 0, Info^.bmiHeader.biWidth,
Info^.bmiHeader.biHeight, Image, Info^,DIB_RGB_COLORS, SRCCOPY);
end;
FreeMemEx(Info);
FreeMemEx(Image);
end;
I got the GDI+ header from the (http://www.progdigy.com/?page_id=7) as suggested by Joe in embarcadero forums (https://forums.embarcadero.com/thread.jspa?messageID=471501#471501).
I have modified my source code as following :
Created an object of TGPGraphics class and assigned the printer's handle to it.
gp := TGPGraphics.Create(Printer.Canvas.Handle);
Created an onject of TGPBitmap class and assigned the barcode image to it.
bmp := TGPBitmap.Create(Info^,Image);
Info is TBitmapInfo and Image is an pointer.
Assigned the Printer's dimension to the an instance rect of TGPRect record
Called the DrawImage function :
gp.DrawImage(bmp,rect);
But, after making these changes, empty image is shown in the printer's output.
Can you point out if I have missed something or my implementation is wrong.
Can you provide any pointers regarding this?
I have found the following code demonstrating creation of compatible bitmap (DBB) from a dib.
That should work for you. Probably it could be written better but all in all it just works ... at least for me..
procedure PRPrintBitmapOnCanvas(Canvas: TMetafileCanvas; Bitmap: TBitmap; posLeft, posTop: Integer);
var
lpbmih: TBitmapInfoHeader;
lpbmi: TBitmapInfo;
aBitmap: HBITMAP;
aDC: LongWord;
begin
Fillchar(lpbmih, SizeOf(lpbmih), 0);
lpbmih.biSize := SizeOf(lpbmih);
lpbmih.biWidth := bitmap.width;
lpbmih.biHeight := bitmap.height;
lpbmih.biPlanes := 1;
lpbmih.biBitCount := 32;
lpbmih.biCompression := BI_RGB;
Fillchar(lpbmi, SizeOf(lpbmi), 0);
lpbmi.bmiHeader.biSize := SizeOf(lpbmi.bmiHeader);
lpbmi.bmiHeader.biPlanes := 1;
lpbmi.bmiHeader.biBitCount := 32;
lpbmi.bmiHeader.biCompression := BI_RGB;
aBitmap := CreateDIBitmap(Canvas.Handle, lpbmih, 0, nil, lpbmi, DIB_RGB_COLORS);
if aBitmap = 0 then RaiseLastOSError;
try
aDC := CreateCompatibleDC(Canvas.Handle);
SelectObject(aDC, aBitmap);
BitBlt(aDC, 0, 0, bitmap.Width, bitmap.Height, bitmap.Canvas.Handle, 0, 0, SRCCOPY);
BitBlt(canvas.handle, posLeft, posTop, bitmap.Width, bitmap.Height, aDC, 0, 0, SRCCOPY);
DeleteDC(aDC);
finally
DeleteObject(aBitmap)
end;
end;
If I wanted to move / shift the pixels of a bitmap how could I do so?
procedure MovePixels(Bitmap: TBitmap; Horizontal, Vertical: Integer);
begin
{ move the Bitmap pixels to new position }
end;
Example:
By calling MovePixels(Image1.Picture.Bitmap, 20, 20) for example would output like so:
It would be useful to also specify / change the color of the canvas that is left showing after moving the pixels. So in this example that gray / brown color could be blue etc.
I noticed there is Bitmap.Canvas.Pixels and Bitmap.Canvas.MoveTo properties, is this what I would need to do this?
I really don't know and I bet it is so simple..
You can't easily move pixels, but you can make a copy.
var
Source, Dest: TRect;
....
Source := Rect(0, 0, Bitmap.Width, Bitmap.Height);
Dest := Source;
Dest.Offset(X, Y);
Bitmap.Canvas.CopyRect(Dest, Bitmap.Canvas, Source);
What remains is to fill in the space with the colour of your choice which I am sure you can do easily enough with a couple of calls to FillRect.
However, I think that it would be simpler not to attempt this in-place. Instead I would create a new bitmap. Perhaps like this:
function CreateMovedImage(Bitmap: TBitmap; X, Y: Integer; BackColor: TColor): TBitmap;
var
Source, Dest: TRect;
begin
Source := Rect(0, 0, Bitmap.Width, Bitmap.Height);
Dest := Source;
Dest.Offset(X, Y);
Result := TBitmap.Create;
Try
Result.SetSize(Bitmap.Width, Bitmap.Height);
Result.Canvas.Brush.Style := bsSolid;
Result.Canvas.Brush.Color := BackColor;
Result.Canvas.FillRect(Source);
Result.Canvas.CopyRect(Dest, Bitmap.Canvas, Source);
Except
Result.Free;
raise;
End;
end;