Given the following code snippet:
procedure TPicture.PaintLine(_Canvas: TCanvas; _Left, _Top, _Right, _Bottom: Integer);
begin
IntersectClipRect(_Canvas.Handle, _Left, _Top, _Right, _Bottom);
try
_Canvas.MoveTo(_Left - 10, _Top - 10);
_Canvas.LineTo(_Right + 10, _Bottom + 10);
// (This is an example only, the actual drawing is much more complex.)
finally
SelectClipRgn(_Canvas.Handle, 0); // This does too much
end;
end;
I want to undo the clipping effected by the call to IntersectClipRect so the previously active clipping becomes active again. In the above code, this is done by SelectClipRgn(...,0) which turns off clipping altogether. This works, kind of, but afterwards there is no clipping active so any drawing that is executed after the above will paint to areas that should not be painted to.
So, what is the correct way to undo only the effect of IntersectClipRect?
EDIT: Removed the unnecessary CreateRectRgn and DeleteObject code after I understood the comment from Sertac, to make the question more readable for others that might stumble upon it later.
You can save and restore the state of the DC:
var
// RGN: HRGN;
SavedDC: Integer;
begin
// RGN := CreateRectRgn(_Left, _Top, _Right, _Bottom);
SavedDC := SaveDC(_Canvas.Handle);
try
IntersectClipRect(_Canvas.Handle, _Left, _Top, _Right, _Bottom);
_Canvas.MoveTo(_Left - 10, _Top - 10);
_Canvas.LineTo(_Right + 10, _Bottom + 10);
// (This is an example only, the actual drawing is much more complex.)
finally
RestoreDC(_Canvas.Handle, SavedDC);
end;
...
IIRC, first store the current clip region using GetClipRgn, and after you're done, SelectClipRgn the stored region again.
Looking at your code, it should be enough to SelectClipRgnyour RGN again, because:
The IntersectClipRect function creates a new clipping region from the intersection of the current clipping region and the specified rectangle.
Related
I have an FMX form with a TLayout on it aligned to client. On the TLayout I have a TRectangle. I can move the TRectangle easily with the following code in a button click event:
Rectangle1->Position->X = Rectangle1->Position->X + 10;
Is there a clean way for me to do this (move the rectangle) with the mouse? Like click on the Rectangle and move it around to a new location? I'm just playing around trying to make a little drawing program to learn....
Using C++Builder 10.2 Version 25.0.29899.2631 and building in Win32.
UPDATE: I took Hans approach and ended up making it work nicely. I've added the full code as an answer below. Yay!
A way to drag components is to store the offset between the mouse position and the control position on mouse down, then use this offset to calculate the position of the control in the mouse move event.
In semi-pseudo code it would look like this:
Add the following to your TForm class:
fMouseIsDown: boolean;
fMouseDownOffset: TPointF;
procedure OnRectangleMouseDown(X,Y)
begin
fMouseIsDown := true;
fMouseDownOffset := PointF(Rectangle.Position.X-X, Rectangle.Position.Y-Y)
end;
procedure OnRectangleMouseMove(X,Y)
begin
if fMouseIsDown then
begin
Rectangle.Position.X := X+fMouseDownOffset.X;
Rectangle.Position.Y := Y+fMouseDownOffset.Y;
end;
end;
procedure OnRectangleMouseUp(X,Y);
begin
fMouseIsDown := false;
end;
Here is the complete code needed to left-click on and move a TRectangle on an FMX form in Win32 (haven't tried it on mobile yet). Just create a new FireMonkey multi-device application and put a TRectangle and a TButton on it.
Code to add to the forms's class declaration (in the .h file just under class TForm1 : public TForm {):
bool fMouseIsDown; // gets set to TRUE when left mouse click on the rectangle
TPointF fMousePosGlobal; // this is the mouses position relative to the screen
TPointF fMousePosForm; // this is the mouse pos relative to the form
TPointF captionOffset; // this is a small offset necessary since the form's TOP and LEFT are outside of form's client area due to caption bar and left edge of form
TPointF fMouseInRectAtClick; // this is the mouse pos relative to the rectangle (top left of rectangle is 0,0)
Code to add to the rectangle's Rectangle1MouseDown event:
if (Button == 0) { // 0 for left mb, 1 for right mb
fMouseIsDown = true;
fMouseInRectAtClick.X = X; //mouse pos with respect to rectangle at time of click
fMouseInRectAtClick.Y = Y;
}
Code to add to the rectangle's Rectangle1MouseMove event (add to the form's FormMouseMove too or sometimes you lose the rectangle on a fast drag):
fMousePosGlobal = Screen->MousePos(); //mouse global pos
fMousePosForm.X = fMousePosGlobal.X - Form1->Left; // mouse pos relative to the form
fMousePosForm.Y = fMousePosGlobal.Y - Form1->Top;
if (fMouseIsDown) {
Form1->Rectangle1->Position->X = fMousePosForm.X - captionOffset.X - fMouseInRectAtClick.X;
Form1->Rectangle1->Position->Y = fMousePosForm.Y - captionOffset.Y - fMouseInRectAtClick.Y;
}
Code to add to the Rectangle1MouseUp event:
fMouseIsDown = false; // add this to the form's MouseUp too in case you "lose" the rectangle on a drag. That only happened when i forget to set the offsets.
Code to add to the Button's Button1Click event:
captionOffset.X = 8.0; // this accounts for the width of the form left edge
captionOffset.Y = 30.0; // this accounts for the height of the form caption
// if you don't add this your "drag point" on the rectangle will jump as soon as you start the drag.
Thanks to Hans for the direction to start!
Also, i noticed the drag wasn't smooth when moving across other controls. If this bothers you then you need to set those other controls "HitTest" false so they ignore it. Add TEdit boxes if you want to see all the TPointF coordinates as you move mouse and rectangle - helps a bunch when trying to figure out coordinates.
I need help from those who know the pascal very well.
I need (teachers requirement) to use a variant record. Those variant record are defined like this:
Temperature = record
case scale : TemperatureScale of
celsius : (celsius_value : ScaleCelsius);
kelvin : (kelvin_value : ScaleKelvin);
end;
According to primary sources I found during my research about this topic, I could not find how to use variant record, only how to declare it.
My primary sources: google search(found nothing), Case-Freepascal and Record-Freepascal
Edit for those who wonder what i must use in implementation it is exacly this:
type
UkPolozka = ^Polozka;
UkHodnota = ^Hodnota;
TypUdaj=(typretez, typcele, typrealne, typlogik, typpole, typobjekt);
VarZaznam = record
case Udaj: TypUdaj of
typretez: (retez: string);
typcele: (cele: word);
typrealne: (realne: single);
typlogik: (logik: boolean);
typpole: (pole: UkHodnota);
typobjekt: (objekt: UkPolozka);
end;
Polozka = record
Nazev: string;
Hodn: VarZaznam;
Dalsi: UkPolozka
end;
Hodnota = record
Hodn: VarZaznam;
Dalsi: UkHodnota
end;
Consider example record from Delphi Help. I modified it with tag.
Tag usage is optional and usually is not used and doesn't provide useful information.
Note that the first line outputs radius with the same value as height - they share the same memory.
Then I explicitly set tag (but fields stay the same)
Also note size difference for no-tag record and tagged one.
Note again - in most cases programmers don't define or use tag value. It might be unsafe to rely on tag (until you follow strict rules in assigning tags). I did not set rectangle tag, but program consider zero tag as the first variant.
Just assign needed field by name. Just read needed field by name. For your example varVarZaznam.retez:= 'test';
type
TShapeList = (Rectangle, Triangle, Circle, Ellipse, Other);
TFigure = record
case shape: TShapeList of
Rectangle: (Height, Width: Real);
Triangle: (Side1, Side2, Angle: Real);
Circle: (Radius: Real);
Ellipse, Other: ();
end;
TFigureNoTag = record
case TShapeList of
Rectangle: (Height, Width: Real);
Triangle: (Side1, Side2, Angle: Real);
Circle: (Radius: Real);
Ellipse, Other: ();
end;
var
Fig: TFigure;
begin
Fig.Height := 5;
Fig.Width := 3;
//default tag is zero = Rectangle
if Fig.shape = Rectangle then
Writeln('Rect ', Fig.Height:3:0, Fig.Width:3:0, Fig.Radius:3:0)
else
Writeln('not Rect');
Fig.shape := Circle;
if Fig.shape <> Rectangle then
Writeln('not Rect');
Writeln('Fig size ', SizeOf(TFigure), ' noTag size ', SizeOf(TFigureNoTag));
Readln;
output in Delphi:
Rect 5 3 5
not Rect
Fig size 32 noTag size 24
I am trying to draw a word-wrapped string within centered both vertically and horizontally within a bitmap using WinAPI's DrawText function.
The problem is that if text is longer than the available space and "END ELLIPSIS" (...) is added to a cropped string, the reported drawing coordinates returned when using the "DT_CALCRECT" report the uncropped number of lines which messes with the vertical centering calculations.
I read many posts on this, and thought that "Delphi - Draw text multiline in the centre of a rect" may hold the answer, but it didn't (screenshot of the code output using the sample in the linked question http://zoomplayer.com/pix/font_vcenter.jpg). The author of the accepted answer suggested I create a new question so here it is.
For quick-reference, here is a slightly simplified (removing unrelated code) text rendering code from the linked accepted answer:
procedure DrawTextCentered(Canvas: TCanvas; const R: TRect; S: String);
var
DrawRect: TRect;
DrawFlags: Cardinal;
begin
DrawRect := R;
DrawFlags := DT_END_ELLIPSIS or DT_NOPREFIX or DT_WORDBREAK or
DT_EDITCONTROL or DT_CENTER;
DrawText(Canvas.Handle, PChar(S), -1, DrawRect, DrawFlags or DT_CALCRECT);
DrawRect.Right := R.Right;
if DrawRect.Bottom < R.Bottom then
OffsetRect(DrawRect, 0, (R.Bottom - DrawRect.Bottom) div 2)
else
DrawRect.Bottom := R.Bottom;
DrawTextEx(Canvas.Handle, PChar(S), -1, DrawRect, DrawFlags, nil);
end;
As you can see from the screenshot, the problem is after the initial call to DrawText with the "DT_CALCRECT" flag to measure the output height for later vertical centering, rendering the string "Trending in: Worldwide" returns a DrawRect.Bottom value representing 3 lines of text even though only 2 lines are drawn, breaking the vertical centering code.
I am using C++11 with GNU tool chain with gtkmm3, on Ubuntu 12.04 LTS 32 bit.
I have been playing wtih some of the examples for gtkmm3 in Programming with gtkmm 3.
Based on 17.2.1.Example there, I inherited from Gtk::DrawingArea (MyDrawingArea here) and overrode the on_draw() event handler as follows:
MyDrawingArea.hpp
...
protected:
bool on_draw ( const Cairo::RefPtr<Cairo::Context>& cr ) override;
MyDrawingArea.cpp
bool MyDrawingArea::on_draw( const Cairo::RefPtr<Cairo::Context>& cr )
{
Gtk::Allocation allocation = get_allocation( );
const int width = allocation.get_width( );
const int height = allocation.get_height( );
int coord1{ height - 3 };
cr->set_line_width( 3.0 );
this->get_window( )->freeze_updates( );
cr->set_source_rgb( 0, 0.40, 0.60 );
cr->move_to( 0, coord1 );
cr->line_to( width, coord1 );
cr->stroke( );
cr->set_source_rgb( 1, 0.05, 1 );
cr->move_to( mXStart, coord1 );
cr->line_to( mXStart, mYAxis * 1.5 );
cr->show_text( to_string( mYAxis ) );
cr->stroke( );
mXStart += 5;
this->get_window( )->thaw_updates( );
return true;
}
My goal is to draw a simple bar graph based on a calculation I do in a little test application, the idea being that each time the on_draw() event is called, the next bar would be moved 5 units to the right on mXAxis and a vertical line would be drawn based on the new mYaxis value, which is computed based on the results of the new calculation.
When I want to repaint my graph and trigger the MyDrawingArea::on_draw() event, I call MyDrawingArea.show_all() from my application after the calculation has completed, and new x and y axes have been set.
However, this does not work as I expected: MyDrawingArea.show_all() invalidates the entire drawing window and draws from scratch: the new graph line appears in its proper place, but the previous ones are erased. I also tried MyDrawingArea.queue_draw(), which had the same effect. But I want to persist the previous graph results so I can get a profile of the calculation results, as I calculate with different values.
This implementation is also causing the bottom line on my graph (my x axis on the graph)- drawn by the first stroke() call in my code example, to be rendered anew on each call to on_draw() - although this should not be necassary since this line persists for the lifetime of MyDrawingArea - it should not be necessary to invalidate and then re-draw it on each new on_draw() event, as my code is currently doing, because I am haven't yet found a way to handle this.
I am very new to Cairo, so I'm sure I'm probably doing this completely wrong, but explicit, task-oriented documentation appears to be sparse - have not found anything that explains how to do this, although I'm sure it is quite simple.
What do I need to do to draw a new line on Gtk::DrawingArea, while persisting previous graph lines that have already been drawn on previous passes, and establish graphics elements that will persist for the lifetime of the Gtk::DrawingArea widget. Obviously using show_all() or queue_draw() and doing it all in the on_draw() event is not the way to go.
In general, you must draw the entire widget and Cairo will clip the drawing to the predefined dirty region. See also GTK reference manual for the "GtkWidget::draw" signal for performance tips:
The signal handler will get a cr with a clip region already set to the
widget's dirty region, i.e. to the area that needs repainting.
Complicated widgets that want to avoid redrawing themselves completely
can get the full extents of the clip region with
gdk_cairo_get_clip_rectangle(), or they can get a finer-grained
representation of the dirty region with
cairo_copy_clip_rectangle_list().
So you may be able to redraw only the region you want with gtk_widget_queue_draw_area().
i am trying to render RichEdit to a bitmap. I want to make backgrond tranparent.
I could achieve that by the code above. The problem is that when i set bitmap as tranparent
only the transparent color becomes transparent. Is there any way to handle the pixels on the edge
which are diffrent from background color and also different from font color. i mean making the pixels
semi-tranparent on the edge which makes a smooth view.
Graphics::TBitmap *bitmap = new Graphics::TBitmap();
bitmap->Width = RichEdit1->Width ;
bitmap->Height = RichEdit1->Height ;
TRect BoundingBox(0,0,RichEdit1->Width, RichEdit1->Height) ;
// Render RichEdit to bitmap
TFormatRange formatRange;
int twipsPerPixel = 1440 / Screen->PixelsPerInch;
formatRange.hdc = bitmap->Canvas->Handle;
formatRange.hdcTarget = bitmap->Canvas->Handle;
formatRange.chrg.cpMin = 0;
formatRange.chrg.cpMax = -1;
formatRange.rc.top = 2 * twipsPerPixel;
formatRange.rc.bottom = (BoundingBox.Height() - 4) * twipsPerPixel + formatRange.rc.top;
formatRange.rc.left = 2 * twipsPerPixel;
formatRange.rc.right = (BoundingBox.Width() - 4) * twipsPerPixel;
// Measure text's height.
RichEdit1->Perform(EM_FORMATRANGE, 0, 0);
RichEdit1->Perform(EM_FORMATRANGE, 0, (LPARAM) &formatRange);
formatRange.rc.bottom = (BoundingBox.Height() - 4) * twipsPerPixel + formatRange.rc.top;
formatRange.rc.left = 2 * twipsPerPixel;
formatRange.rc.right = (BoundingBox.Width() - 4) * twipsPerPixel;
formatRange.rcPage = formatRange.rc;
/**
* Draw..
**************************************************************************/
RichEdit1->Perform(EM_FORMATRANGE, 1, (LPARAM) &formatRange);
RichEdit1->Perform(EM_FORMATRANGE, 0, 0);
// Draw background
// Use different background color to see the trasparency problem
this->Canvas->Brush->Color = clRed ;
this->Canvas->Rectangle(0,0,RichEdit1->Width , RichEdit1->Height );
// Draw the transparent bitmap
bitmap->Transparent = true ;
bitmap->TransparentColor = RichEdit1->Color ;
this->Canvas->Draw(0,0,bitmap);
Thanx.
Font smoothing works with partial transparency using an alpha channel. The Transparent and TransparentColor properties of TBitmap are therefore not applicable.
You haven't said which version of the C++ Builder/VCL you are using, but more modern versions have better support for partial transparency than some of the older ones.
To get this to work you will need to set the PixelFormat of your bitmap to be pf32bit. You may also need to set AlphaFormat to afDefined.
If you can't get TBitmap to do what you need then you'll have to revert to GDI commands to create a suitable HBITMAP. You can at least assign that to the Handle property of a TBitmap and usually from there everything behaves.
Note that I am not a user of C++ Builder but do know the VCL from Delphi.
UPDATE
I tried this out in Delphi and the following worked fine for me:
procedure TForm4.Button1ClickBMP(Sender: TObject);
var
BMP: TBitmap;
fmtRange: TFormatRange;
intPPI, Flags: Integer;
begin
BMP := TBitmap.Create;
Try
BMP.PixelFormat := pf32bit;
BMP.SetSize(RichEdit1.Width, RichEdit1.Height);
FillChar(fmtRange, SizeOf(fmtRange), 0);
with fmtRange do begin
hDC := BMP.Canvas.Handle;
hdcTarget := hDC;
intPPI := Screen.PixelsPerInch;
rc := Rect(
0,
0,
RichEdit1.Width*1440 div intPPI,
RichEdit1.Height*1440 div intPPI
);
rcPage := rc;
chrg.cpMin := 0;
chrg.cpMax := -1;
end;
Flags := 1;
RichEdit1.Perform(EM_FORMATRANGE, Flags, Longint(#fmtRange));
RichEdit1.Perform(EM_FORMATRANGE, 0, 0);
BMP.SaveToFile('c:\desktop\test.bmp');
Finally
FreeAndNil(BMP);
End;
end;
The output looks like this, blown up somewhat to see the anti-aliasing:
I hope this helps, because it looks like you are very nearly there!
As David has answered already, sub-pixel antialiasing requires knowledge of the background colour. This is explained in more detailed in this answer. In essence, when you do sub-pixel anti-aliasing you treat the three colour channels as having different spatial offsets (which is where the apparent increase in resolution comes from). This means they need different alpha values, but of course there is only one alpha channel.
You can of course do regular full-pixel grayscale antialiasing over a transparent background. Perhaps this would be good enough? Some of the other answers in the question linked above suggest ways to achieve this. Have a look at ANTIALIASED_QUALITY (vs. CLEARTYPE_QUALITY) in the LOGFONT structure. (I haven't tried this.)