I am rendering Text using Direct2D starting with a text Layout
HRESULT hr = m_spWriteFactory->CreateTextLayout(
m_wsText.c_str( ),
m_wsText.length( ),
m_spWriteTextFormat.Get( ),
m_rect.right - m_rect.left - m_spacing.right - m_spacing.left,
m_rect.bottom - m_rect.top - m_spacing.top - m_spacing.bottom,
&m_spTextLayout
);
and then rendering it to a bitmap which I later use with Direct3D
m_sp2DDeviceContext->DrawTextLayout(
D2D1::Point2F( m_spacing.left, m_spacing.top ),
m_spTextLayout.Get( ),
m_spTextBrush.Get( )
);
I would like to draw a simple thin flashing line as a caret. I know how to draw a line and how to make it appear / disappear.
Question: How do I get the starting point and the end point coordinates for my caret line?
Simplification: If it is much easier to assume that the text consists of one line only, then that's ok. But of course a more general solution is appreciated.
Use IDWriteTextLayout's hit-testing functions to determine these:
HitTestTextPosition for mapping a text position index (relative to the first character) to a rectangle.
HitTestTextRange for getting a whole range of rectangles such as for selection.
HitTestPoint for mapping a mouse coordinate to a text position index.
For carets, this below works for all horizontal reading directions and proportional/monospace fonts:
...
DWRITE_HIT_TEST_METRICS hitTestMetrics;
float caretX, caretY;
bool isTrailingHit = false; // Use the leading character edge for simplicity here.
// Map text position index to caret coordinate and hit-test rectangle.
textLayout->HitTestTextPosition(
textPosition,
isTrailingHit,
OUT &caretX,
OUT &caretY,
OUT &hitTestMetrics
);
// Respect user settings.
DWORD caretWidth = 1;
SystemParametersInfo(SPI_GETCARETWIDTH, 0, OUT &caretWidth, 0);
DWORD halfCaretWidth = caretWidth / 2u;
// Draw a thin rectangle.
D2D1::RectF caretRect = {
layoutOriginX + caretX - halfCaretWidth,
layoutOriginY + hitTestMetrics.top,
layoutOriginX + caretX + (caretWidth - halfCaretWidth),
layoutOriginY + hitTestMetrics.top + hitTestMetrics.height
};
solidColorBrush->SetColor(D2D1::ColorF::AliceBlue);
d2dRenderTarget->FillRectangle(&caretRect, solidColorBrush);
Notes:
The above code as-is doesn't account for vertical reading directions such as in Japanese newspapers. You would need to draw a wide flat caret instead of the tall thin one here when the DWRITE_READING_DIRECTION was either top-to-bottom or bottom-to-top.
IDWriteTextLayout::GetMetrics only gives the overall bounding box to you, not the caret position.
IDWriteTextLayout::HitTestPoint's isInside flag is true if it is over the text itself, not just the layout bounds.
You can get the layout's bounding rectangle via IDWriteTextLayout::GetMetrics.
DWRITE_TEXT_METRICS textMetrics;
textLayout->GetMetrics(&textMetrics);
Your rectangle is
D2D1::RectF( textMetrics.left,
textMetrics.top,
textMetrics.left + textMetrics.width,
textMetrics.top + textMetrics.height );
You can then draw the caret along the right boundary line.
Related
I have a QGraphicsView which contains many QGraphicsItem like rectangle, polylines etc.
I want to name each rectangle and name is on the rectangle.
Name should be centrally aligned i.e. half of the string name characters should be left side of mid position and half of the characters should be right side of mid position.
For that I used following psuedo code:
int firstPosition = GetMidPosition () - (strLength / 2);
int secondPosition = some points
DrawText( firstPosition , secondPosition );
Now if I print co-ordinates of firstPosition, secondPosition , GetMidPosition() they comes perfectly as I expect.
But, the text does not come properly. I have to manually adjust it.
int firstPosition = GetMidPosition () - 6 *(strLength / 2);
Now it appears centrally. Why this is happening ? Co-ordinates are correct then why I have to manually adjust it.
I want to avoid that adjustment (i.e. 6 * )
If tomorrow, I changed the font size of text, then I will have to
change the multiplication factor 6 into something else. That should
not be happened. How to write general purpose logic for this ?
As we all know, the console buffer size is composed like a 2D array. I'm trying to implement on click buttons (drawn buttons NOT child windows) but im having an accuracy problem.
Because the Console Window is movable and resizable, i have to take the Mouse Cursor position relative to the Console Window TopLeft corner (I've found a way of accurately doing that in pixels). But now the problem arrives. When i try to find out on which character square the Mouse Cursor is on, it becomes inacurate (errors of about 3 ~ 5 pixels) and this is a problem when implementing on click buttons.
These are the functions i use. Also keep in mind that we need to previously have the GetCurrentConsoleFont() declared. (find it here)
For ease of testing, I have implemented a little "Draw my thing" game in the main (see full code).
/** This returns the cursor position relative to any window (not just the console).*/
POINT GetCursPosRelWin(HWND hWindow)
{
POINT rCoord;
RECT windowCoord;
HWND hConsole = GetConsoleWindow();
GetWindowRect(hConsole,&windowCoord);
POINT ptCursor;
GetCursorPos(&ptCursor);
rCoord.x = ptCursor.x - windowCoord.left;
rCoord.y = ptCursor.y - windowCoord.top;
return rCoord;
}
WORD GetCurrentFontHeight()
{
CONSOLE_FONT_INFO cfi;
GetCurrentConsoleFont(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
return cfi.dwFontSize.Y;
}
WORD GetCurrentFontWidth()
{
CONSOLE_FONT_INFO cfi;
GetCurrentConsoleFont(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi);
return cfi.dwFontSize.X;
}
So, is there any way of making this method be more accurate?
EDIT: This is the most accurate way i managed to find though it is still not very precise.
/** See the full code for a better understanding */
/** In the main function as parameters of MoveConsoleCursor() */
MoveConsoleCursor(
(SHORT)((double)(ptCursor.x/GetCurrentFontWidth() - ((ptCursor.x/GetCurrentFontWidth())%10)/10 )),
(SHORT)((double)(ptCursor.y/GetCurrentFontHeight() - 0.5))
);
You can change your GetCursPosRelWin to:
POINT GetCursPosRelWin(HWND hWindow)
{
POINT ptCursor;
GetCursorPos(&ptCursor);
ScreenToClient(hWindow, &ptCursor);
return ptCursor;
}
And MoveConsoleCursor call to:
MoveConsoleCursor(ptCursor.x / GetCurrentFontWidth(), ptCursor.y / GetCurrentFontHeight());
This puts cursor in the center of a square, provided the scroll bars are not moved. Otherwise you have to account for the scrollbar offsets.
I am making a 2D board game. the game board grid is 8x8 and each cell of the grid is an object. So a board consists of 64 cell objects. My aim is to work out which cell the mouse is in. I am attempting this by tracking the mouse coordinates and comparing it to the grid coordinates.
my coordinate system is as follows:
gluOrtho2D(-4,4,-4,4);
I am trying to get the current mouse position by using the following in my update function:
POINT p
if (GetCursorPos(&p)){
}
if (ScreenToClient(hWnd, &p))
{
}
However although this is tracking the coordinates of the mouse it is not correctly tracking the world coordinates that I set with gluOrtho2D. How can I achieve this?
It depends on your glViewPort
Let's say you have:
glViewport(0,0, 640, 640);
The mouse position is (mousePos.x,mousePos.y) and the world position you want to know is (world.x, world.y)
And, give that the top/left corner of your screen is the (0, 0) coordinate
Then we can make the following:
world.x = -4.0 + (mousePos.x / 640.0) * (4*2)
world.y = 4.0 - (mousePos.y / 640.0) * (4*2)
What we are doing here is a linear interpolation using the normalize position of the mouse within the screen (mousePos.x / 640) and then multiplying this value to the width of the word (4*2).
Given that the top/left corner of the grid start at (-4, 4), we add the offset of the world position.
The following image of size 1x9 is being trimmed to 1x6 because presumably the pixel at the top is the same color as the pixel at the bottom and in the trim function, these pixels are being identified as the background color, even though the backgroundColor being reported before the execution of the trim function is #FFFFFF.
http://s1.postimage.org/a7r69yxsr/m_medium_bc.png
The only thing I am doing is executing trim on the Image. Explicitly setting backgroundColor and/or transparent() makes no difference.
Why is this occurring and is this the expected behavior?
Can this be fixed by configuration/property setting/without changing Graphicsk library code?
If not, when can this bug be fixed? Do you expect a bug of this nature to be fixed in the next few days?
Here is the code:
Magick::Image tempImage;
tempImage.read(name);
std::cout<<"size:"<<tempImage.columns()<<","<<tempImage.rows()<<std::endl;
temp=tempImage.backgroundColor();
std::cout<<"bg:"<<(std::string)temp<<std::endl;
tempImage.trim();
std::cout<<"size:"<<tempImage.columns()<<","<<tempImage.rows()<<std::endl;
I agree that this behaviour is strange, I am not a developer/maintainer of ImageMagick/Magick++ so can't comment further as to whether this is a bug or 'feature'. However I had the same issue and created this function as a workaround (note this is much faster than manually iterating the pixels, even with a pixel cache in place):
Magick::Geometry CalculateImageMagickBoundingBox( const Magick::Image & image, const Magick::Color & borderColor )
{
// Clone input image.
Magick::Image clone( image );
// Remember original image size.
const Magick::Geometry originalSize( image.columns( ), image.rows( ) );
// Extend geometry by two in width and height (one pixel border).
Magick::Geometry extendedSize( originalSize.width( ) + 2, originalSize.height( ) + 2 );
// Extend cloned canvas (center gravity so 1 pixel border of user specified colour).
clone.extent( extendedSize, borderColor, Magick::CenterGravity );
// Calculate bounding box (will use border colour, which we have set above).
Magick::Geometry boundingBox = clone.boundingBox( );
// We added 1 pixel border, so subtract this now.
boundingBox.xOff( boundingBox.xOff( ) - 1 );
boundingBox.yOff( boundingBox.yOff( ) - 1 );
// Clamp (required for cases where entire image is border colour, and therefore the right/top borders
// that we added are taken into account).
boundingBox.width( std::min( boundingBox.width( ), originalSize.width( ) ) );
boundingBox.height( std::min( boundingBox.height( ), originalSize.height( ) ) );
// Return bounding box.
return boundingBox;
}
In your particular case, you could use this function and then set the canvas size based on the geometry returned.
In my scene I have terrain that I want to "grab" and then have the camera pan (with its height, view vector, field of view, etc. all remaining the same) as I move the cursor.
So the initial "grab" point will be the working point in world space, and I'd like that point to remain under the cursor as I drag.
My current solution is to take the previous and current screen points, unproject them, subtract one from the other, and translate my camera with that vector. This is close to what I want, but the cursor doesn't stay exactly over the initial scene position, which can be problematic if you start near the edge of the terrain.
// Calculate scene points
MthPoint3D current_scene_point =
camera->screenToScene(current_point.x, current_point.y);
MthPoint3D previous_scene_point =
camera->screenToScene(previous_point.x, previous_point.y);
// Make sure the cursor didn't go off the terrain
if (current_scene_point.x != MAX_FLOAT &&
previous_scene_point.x != MAX_FLOAT)
{
// Move the camera to match the distance
// covered by the cursor in the scene
camera->translate(
MthVector3D(
previous_scene_point.x - current_scene_point.x,
previous_scene_point.y - current_scene_point.y,
0.0));
}
Any ideas are appreciated.
With some more sleep :
Get the initial position of your intersected point, in world space and in model space ( relative to the model's origin)
i.e use screenToScene()
Create a ray that goes from the camera through the mouse position : {ray.start, ray.dir}
ray.start is camera.pos, ray.dir is (screenToScene() - camera.pos)
Solve NewPos = ray.start + x * ray.dir knowing that NewPos.y = initialpos_worldspace.y;
-> ray.start.y + x*ray.dir.y = initialpos_worldspace.y
-> x = ( initialpos_worldspace.y - ray.start.y)/rad.dir.y (beware of dividebyzeroexception)
-> reinject x in NewPos_worldspace = ray.start + x * ray.dir
substract initialpos_modelspace from that to "re-center" the model
The last bit seems suspect, though.