WHAT I AM TRYING TO DO
I am trying to draw multiple graphics to a Timage, These graphics that i Draw consist of ordered layers with Foodfills and lines.
I use multiple buffers to ensure ordering and double buffering.
WHAT I AM DOING
procedure DrawScene();
var
ObjLength,LineLength,Filllength,Obj,lin,angle,i:integer;
Npoints : array[0..1] of Tpoint;
Objmap:TBitmap;
wholemap:TBitmap;
begin
wholemap := TBitmap.Create;
wholemap.Width:=area;
wholemap.height:=area;
ObjLength:=length(Objects);
for Obj:=0 to (ObjLength-1) do
if objects[Obj].Visible then
begin
// create object bitmap
if Objects[obj].Tag='FBOX' then
begin
Objmap := TBitmap.Create;
Objmap.Width:=area;
Objmap.height:=area;
Objmap.Transparent:=true;
Objmap.Canvas.Rectangle((objects[obj].Boundleft-4)+objects[obj].Position.x,area-((objects[obj].boundtop+4)+objects[obj].Position.y),(objects[obj].boundright+4)+objects[obj].Position.x,area-((objects[obj].boundbottom-4)+objects[obj].Position.y));
end;
//draw object
LineLength:=length(objects[Obj].Lines)-1;
angle:=objects[Obj].Rotation;
for lin:=0 to (LineLength) do
begin
for i:=0 to 1 do
begin
Npoints[i] := PointAddition(RotatePoint(objects[obj].Lines[lin].Point[i],angle),objects[obj].Position,false);
end;
Objmap:=DrawLine(Npoints[0].x,Npoints[0].y,Npoints[1].x,Npoints[1].y,objects[obj].Lines[lin].Color,Objmap);
end;
Filllength:=length(objects[Obj].Fills)-1;
for i:=0 to Filllength do
begin
Npoints[0]:=PointAddition(RotatePoint(objects[Obj].Fills[i].Point,objects[Obj].Rotation),objects[Obj].Position,false);
Objmap:=fillpoint( Npoints[0].x, Npoints[0].y,objects[Obj].Fills[i].color,Objmap);
end;
//write object to step frame
wholemap.Canvas.Draw(0,0,Objmap);
Objmap.Free;
end;
// write step frame to Visible Canvas
mainwindow.bufferim.Canvas.Draw(0,0,wholemap);
mainwindow.RobotArea.Picture.Graphic:=mainwindow.bufferim.Picture.Graphic;
wholemap.Free;
end;
WHAT I EXPECT
I expect to see each image object layered on top of one another with each image layer being the complete image for that layer.
im my example it is a robot with a flag behind it.
the flag is drawn first and then the robot.
WHAT I GET(on a pc)
on a pc i get what i expect and all appears to be correct.
WHAT I GET(on a laptop)
On a nearly every laptop and some pc's i only see the robot.
i put in some statments to see if it is drawing the flag and it does. the game can even interact with the flag in the correct manner.
further investigation showed me that it was only showing the last image drawn my "drawscene", and when images were drawn directly to the wholecanvas everthing apeared(this cannot be done for overlapping fill layers reasons)
WHAT I THINK IS HAPPENING
so what i deduced is that the Timage.transparent property of the Timage is not working or is being computed differently on some machines..
i did a test to prove this and made a canvas red. then to that canvas i drew at 0,0 i Timage with property transparent=true with just one dot in the middle to the red canvas. the resuly was a white canvas with a dot in the middle.
I am assuming and findings indicate that machines with very basic graphics drivers seem to treat null or transparent as white where as more powerful machines seem to treat null or transparent as transparent.
this seems to be a bit of a failure due to the fact that the Timage.Transparent property was true.
EDIT:
UPDATE!!!
It would appear to be that on ATI graphics cards if a colour is "null" then it is interpreted in the format of PF24bit and therefore no alpha channel and no transparency.
on Nvidia cards it takes it as PF32bit and treats null as transparent.
the obvious way to get around that woulf be to set the bitmaptype to PF32bit, but that does still not work.
I then assumed that maybe that is not enough and I should make sure that the background is SET to transparent rather than being left as null.. but there are no tools ( that I can see) inside the Timage to set colour with alpha. all canvas drawing functions require a Tcolor that is RGB 24 bit and ony a TcolorRef with RGBA 32 bit would do....
is there a way of drawing with alpha 0?
WHAT I THINK I NEED TO KNOW
How to force the Transparent property to work on all machines
or a way to make laptops not paste in transparent as white
Or a way to achieve the same result.
Anyone have any solutions or ideas?
I have been using an Graphics library (AggPas) to help with drawing graphics and one of the things I've noticed is that I always need a line Bitmap.PixelFormat = pf32bit to get it to draw transparancies.
Having said that I use TransformImage from the AggPas library to copy the Image with a transparent background to another one and AggPas only accepts pf24bit or pf32bits as Pixel formats (otherwise it doesn't attach to the bitmap)
I've seen similar behaviour on different machines in the past (a few years back).
They were caused by different video cards and drivers.
Either the NVideo or the ATI ones were getting the wrong results, but I forgot which ones.
It could be reproduced on both laptops and regular PC's.
So: what video cards and drivers do you use?
--jeroen
I would explicitly set the bitmap format to pf32bit for each bitmap you create to ensure the problem isnt converting colors from 32 to 16 bit (if thats the native video resolution of the bitmaps getting created), which might interfere with how the transparency works. Also, I've had better luck specifically setting the transparency color in the past.
Another option - perhaps a better one in the long run, is to instead set the Alpha Channel on the images (ie, use PNG files), and use the GDI function AlphaBlend() to draw the graphics.
Related
I'm currently about to replace the drawing code for an old component from GDI + UniScribe to Direct2D and DirectWrite (the successors).
So far the transition was straight forward as most of the time all I need to do was to replace calls to the Canvas (class TCanvas) to a custom FDirect2DCanvas instance (class TDirect2DCanvas, from the unit Direct2D).
Unfortunately it doesn't seem that simple when trying to draw a glyph from a TImageList instance onto the FDirect2DCanvas as the draw method is only meant for TCanvas and not for the rather general TCustomCanvas (which is the ancestor of both TCanvas and TDirect2DCanvas).
A solution for this dilemma would be to draw the TImageList glyph to a temporary bitmap and draw this to the TDirect2DCanvas. However, I fear this will probably slow down the drawing performance a lot.
Has anyone so far done this so far? What options do I have?
If you look at how drawing graphic objects to TDirect2DCanvas is implemented you will find that it routes through this routine.
procedure TDirect2DCanvas.StretchDraw(const Rect: TRect; Graphic: TGraphic;
Opacity: Byte);
var
D2DBitmap: ID2D1Bitmap;
D2DRect: TD2DRectF;
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.Assign(Graphic);
D2DBitmap := CreateBitmap(Bitmap);
D2DRect.Left := Rect.Left;
D2DRect.Right := Rect.Right;
D2DRect.Top := Rect.Top;
D2DRect.Bottom := Rect.Bottom;
RenderTarget.DrawBitmap(D2DBitmap, #D2DRect, Opacity/255);
finally
Bitmap.Free;
end;
end;
Let's unpick the steps involved:
Create a temporary bitmap.
Copy the graphic into that bitmap.
Create a ID2D1Bitmap and copy the temporary bitmap into it.
Draw that ID2D1Bitmap onto the render target.
This already looks pretty inefficient. Certainly it would be galling to call this function passing in a TBitmap and have a copy made for no good reason.
This sort of thing is hard to avoid though when you try to blend two distinct graphics frameworks. Your image list is GDI based, and so bound to encounter friction when you try to send it to a Direct2D canvas. There simply is no way to pass GDI bitmaps directly to a Direct2D canvas, they have to be converted to Direct2D bitmaps first.
If performance is what matters to you then you should not be starting from an image list. That will inevitably incur costs as you extract the bitmap from the GDI image list, and then convert it into the equivalent Direct2D object, ID2D1Bitmap.
In order to achieve optimal performance, don't work with image lists. Extract each image from the image list and use TDirect2DCanvas.CreateBitmap to obtain a Direct2D bitmap, ID2D1Bitmap. Store these rather than the image list. Then when you need to draw, call DrawBitmap on RenderTarget, passing a ID2D1Bitmap.
I have an odd bug in FMX that has all the hallmarks of a pointer overrun or a hardware-specific bug, which I haven't been able to track down. Small apps to reproduce it, don't (yet.) I have code snippets below, but since I haven't managed to reproduce this in a small application they are only snippets.
The app loads some PNG files, and then creates in-memory bitmaps based on colour-coding in the PNG files - that is, it will create an in-memory bitmap which is big enough to bound all red areas of the original, and is initially blank. There is a byte array the same size as the in-memory bitmap which is a mask (zero, non-zero) indicating if that pixel corresponds to a red (say) area in the original or not. The user can paint in the app, painting on a temporary display bitmap, and then when they let go the mouse button that bitmap is scanned against the byte array to write on the in-memory bitmap.
With me so far? Basically, colour-coded subsets of an original, masks created for the colour areas, painting on the subset is masked. Here's a diagram:
This works fine on XP (GDI+ canvas) and Win7 (D2D canvas) and Win8.1 and most Win10 machines. However, on two Win10 machines, one with an Intel HD 4600 and another with an Intel Iris 5000, a client gets odd artifacts on the bitmap after doing the step of masking and setting pixels.
The artifacts are rectangles scattered apparently randomly over the bitmap, either large (100x20px, say) or small (10x8, say). I've also seen screenshots where parts of the rest of the UI, like button glyphs, are also present on the bitmap. Here are some example screenshots:
Here the user has already painted the dark red area, and painted the lighter colour somewhere else. These small flecked rectangles appear.
Here the user has painted the broad stripe down the middle. The dark red and lighter red rectangles are examples of the artifacts after masking and drawing to the bitmap.
To me that sounds like I'm writing pixels at the wrong stride or something. I've rigorously code-inspected for off-by-one errors in array or bitmap data access, and I'm using the following code to get or set pixels in bitmap data:
function GetBitmapColor(const M : PBitmapData; const X, Y : Integer) : TAlphaColor;
begin
Result := PixelToAlphaColor(
#PAlphaColorArray(M.Data)[Y * (M.Pitch div PixelFormatBytes[M.PixelFormat]) + X],
M.PixelFormat);
end;
procedure SetBitmapColor(const M : PBitmapData; const X, Y : Integer; const C : TAlphaColor);
begin
AlphaColorToPixel(C,
#PAlphaColorArray(M.Data)[Y * (M.Pitch div PixelFormatBytes[M.PixelFormat]) + X],
M.PixelFormat);
end;
These are based on the example code for accessing bitmap data in the Seattle documentation. Assertions that X and Y are in a valid range (in the bitmap size) are all ok. While this happens on the OS & hardware given above, it does not happen for the same OS (Win10) with other cards - another Intel HD, a Nvidia, etc; on a Win10 tablet; on a Win10 VM I tried; on my Win7 development machine; etc. As far as buffer overruns etc are concerned, compiling with range checks on does not give any errors, nor have any asserts of my own checking valid X/Y coordinates against bitmap size.
The error occurs with both XE6 and Seattle on the same machines.
I can't reproduce this. They're in the process of setting up remote access to this machine but it's still going to be tricky to figure out what's going on, so I wonder if anyone has seen something like this before, or has concrete suggestions of specific ways to check for bitmap pointer overruns or similar?
I am trying to draw a source image (GR32 TBitmap32) that contains some fully transparent and also partially transparent pixels on a normal (TBitmap) image while preserving the transparency in the source image.
I use this:
BMP32.DrawTo(BMP.Canvas.Handle, 0, 0);
but the image is not drawn transparently.
Code:
Everything is pretty basic: I have a background bitmap (Bkg) in which I load, at application start up, an image.
Then in procedure Apply I load a second image from disk (the one with transparency) and I draw the it over the background.
var Bkg: TBitmap;
procedure TfrmPhoto.FormCreate(Sender: TObject);
begin
Bkg:= LoadGraphEx(GetAppDir+ 'bkg.bmp'); { Load bitmap from file }
{
Bkg.Transparent:= TRUE;
Bkg.PixelFormat:= pf32bit;
Bkg.TransparentColor:= clPink;
}
end;
procedure TfrmPhoto.Apply2;
VAR
Loader: TBitmap;
BMP32: tbitmap32;
begin
BMP32:= TBitmap32.Create;
TRY
Loader:= TBitmap.Create;
TRY
Loader.LoadFromFile('c:\Transparent.BMP');
BMP32.Assign(Loader);
FINALLY
FreeAndNil(Loader);
END;
{ Mix images }
BMP32.DrawTo(Bkg.Canvas.Handle, 0, 0); <----- The problem is here
imgPreview.Picture.Assign(Bkg); { This is a TImage where I see the result }
FINALLY
FreeAndNil(BMP32);
END;
end;
It worked (but with 2 disadvantages) with TransparentBlt().
I am not happy with the way TransparentBlt handles the semitransparent pixels (the edge of the rotate image). After merging the src image into the background the edges look bad.
In order to use TransparentBlt, I had to define a color (clPink) in the source image as transparent. This means that if the source image has some pink in it, the result will look really nasty (it will be treated as transparent). Let's pray for non-pink images!
If you find a way to transfer the image (while preserving transparency) directly from Bitmap32 into the background please post and I will accept your answer!
A solution (still a hack) I see here is to process everything in a TBitmap32 and then 'export' the final result as TBitmap. I will try this tomorrow.
Update:
Solution:
This is how to merge two TBitmap32 images while preserving transparency:
Dst.CombineMode:= cmBlend;
Dst.DrawMode:= dmBlend;
Src.Draw(0, 0, Dst);
Dst and Src are TBitmap32 images. It doesn't work with TBitmap.
As already suggested by yourself, I would recommend performing blending operations entirely in the TBitmap32 domain! The reason for this is the fact that it offers more choices in regards of blending and it uses MMX/SSE/SSE2 where available (on modern machines always the case). With this one can typically get a notable performance boost in contrast to letting GDI perform the blending.
In particular this is not a good idea as GDI typically does not make use of any SIMD opcodes (MMX/SSE).
It also depends on how much each part changes. For example, typically the background is rather static and need to be transfered into a TBitmpa32 only occassionally on changes (e.g. on VCL style or UI theme changes). With each transfer (GR32 <-> TBitmap and alike) an implict conversion (DIB <-> DDB) might happen, which makes the entire blending an O(n²) operation. So you'd better transfer (with an implicit conversion) to TBitmap32 just once and then perform the blending entirely in the GR32 domain.
On the contrary, if the TBitmap32 is rather static and the TBitmap (or background) changes a lot, it might probably be better to leave GR32 alone and copy the TBitmap32 to TBitmap and perform the blending with GDI. Despite the fact that the GDI blending is slightly slower, you avoid the additional copy. Otherwise your bottleneck will likely be the memory throughput, while processing speed never can touch the limits.
It is dmBlend DrawMode of TBitmap32 to turn on the transparency, but it only works between two TBitmap32 bitmaps. Do not use TBitmap32 with transparent images to draw directly on HDC, Canvas or TBitmap. Use dmBlend and DrawTo or BlockTransfer() to draw on another TBitmap32. For example, to draw transparently on a TBitmap, create an intermediary cache TBitmap32:
Copy the image from TBitmap to the cache TBitmap32;
Apply your transparent image to the cache TBitmap32 using DrawTo or BlockTransfer(), avoid using Canvas or HDC to mix two images because they lose alpha layer information;
Copy the image back from the cache TBitmap32 to your TBitmap.
I'm using Graphics32 for image processing. Looking at its capabilities, it strikes me that I've yet to see a proper implementation of a clipping mask. I do see the term "clipping" pop up here and there, but it seems to refer to something else.
Simply put, I need one layer to function as a "peeking hole" to another; layer A should be projected onto layer B, but only where layer B is visible. (I see no further need to redefine what a clipping mask is.)
If it were just the bitmap of that other layer that I'd like to present, it wouldn't be so hard to do - then I could use this trick - but what complicates things, is that the bitmap of a layer does not tell much about what would be displayed by the layer; the layer can be:
(partially) invisible (when out of the view)
moved/stretched + optionally resampled
rotated
with no effects on its bitmap.
Is it actually so that there is no ready implementation for this? Any suggestions for doing this myself?
Progress
I found some useful elements in the source of Graphics32. For example, using this declaration:
type
TLayerAccess = class(TBitmapLayer);
to gain access to protected methods, I can call TLayerAccess(ABitmapLayer).Paint(ABitmap32) to have just this layer painted to a bitmap, exactly as it would to the screen.
Have a look at TByteMap and its writeTo method.
I have looked into this myself a year ago, and quickly resorted to using a black layer with transparent parts. It suited my then needs. But what you want is possible..
You want to couple one TBitmapLayer to another and consider it its mask. I want to avoid these references however (and its potential problems and Graphics32 rework) and see this only as a last resort.
There is a way to do without a dedicated TBitmapLayer, using your own pixel combiner. But it doesn't know about TBitmapLayers and its XY pixels.
To proper occlude (or leave out) parts of a TBitmapLayer while drawing to screen, you could use create a method of type TPixelCombineEvent and assigning this as its OnPixelCombine and setting TBitmapLayer.DrawMode to dmCustom.
Inside that TPixelCombineEvent method you decide what pixels result given the background B and the foreground F given the current M master alpha.
procedure TMyObj.MyPixCombine(F: TColor32; var B: TColor32; M: TColor32);
begin
if not PseudoThisPixelShouldBeMasked then B := F; // ugly and aliased
end;
Problem here is, that (the pseudo code) PseudoThisPixelShouldBeMasked doesn't really know what pixel this concerns and whether it is inside a mask. So you'd have to extract that value from a component of F, such as its Alpha value.
Back then i opted for the quite fast B := ColorMin(F,B); where F is black or white. This layer is on top always and results in black instead of transparent masks however.
This because any rendering to the TBitmapLayer would destroy the mask data and i need to reapply it. Using a TByteMap however as suggested below (who downvoted that?) by iamjoosy might be interesting, perhaps that performance penalty turns out negligible.
I'm trying to make a "blank" background to place an image on top of. It's not too difficult to create crosshatches by placing a TImage on a form and doing the following:
image1.Canvas.brush.Style := bsDiagCross;
image1.canvas.brush.color := clWhatever;
image1.canvas.FillRect(image1.clientrect);
This works, and I get a crosshatch pattern in clWhatever over a black background. But that's the problem. It's always black, and I can't find any way to specify a background color in case I want something other than black.
Does anyone know how to do this?
The color of a hatched brush is the foreground color - the color of the hatch itself.
The background color is set separately when using hatched brushes and as far as I know isn't exposed as a property of the TCanvas and thus requires the use of the Windows GDI API SetBkColor() function.
e.g. to draw a red hatch on a white background, add a call to set the background color before drawing using the canvas brush:
image1.Canvas.brush.Style := bsDiagCross;
image1.canvas.brush.color := clRed;
SetBkColor(image1.Canvas.Handle, ColorToRGB(clWhite));
image1.canvas.FillRect(image1.clientrect);
[Update:] NOTE: It appears that in Delphi 2010 (and possibly some earlier version/s) you should call SetBKColor() AFTER setting brush properties. Internally when the canvas creates it's brush it calls SetBKColor() which tramples on any explicit calls to SetBKColor() made prior to referencing the Canvas.Brush. The timing of when the internal canvas brush is created, or the internal use of SetBkColor(), appears to have changed between Delphi 2006 (used when testing the original posting) and Delphi 2010. Whatever the reason it is clearly more reliable to call SetBKColor immediately prior to using it.