I'm working with an older C++ application and I need to draw text centered and rotated on an hDC. I've got the centered & rotated part working but the font that gets written to the Image is smaller than I'm expecting. If you take an Image and edit it or a word document and add a line of of Text "COPY" in font size 128 it fills up most of the width of the page. With my code it only covers about 1/3 of the page. What am I missing or doing wrong?
UINT nOptions = 0;//DT_CENTER;
RECT rect = {0,0, BITMAPWIDTH(&WatermarkHandle), BITMAPHEIGHT(&WatermarkHandle)}; //{0,0,FileInfo.Width, FileInfo.Height};
SetMapMode(hdcWatermark, MM_TEXT);
LOGFONT lf;
memset(&lf, 0, sizeof(lf));
lstrcpy(lf.lfFaceName, &sWatermarkFontName[0]);
lf.lfHeight = -MulDiv(lWatermarkFontSize, GetDeviceCaps(hdcWatermark, LOGPIXELSY), 72);
lf.lfEscapement = lWatermarkAngle * 10;
lf.lfOrientation = lf.lfEscapement;
lf.lfClipPrecision = CLIP_LH_ANGLES;// || CLIP_TT_ALWAYS;
lf.lfWeight = FW_THIN;
SetTextColor(hdcWatermark, black);
DrawRotatedText(hdcWatermark, &sWatermarkText[0], &rect, lf, lWatermarkAngle, nOptions);
void DrawRotatedText(HDC hdc, char *str, LPRECT rect, LOGFONT lf, double angle, UINT nOptions)
{
// convert angle to radian
double pi = 3.141592654;
double radian = (angle / 180 * pi);
HFONT hFontText = CreateFontIndirect(&lf);
HFONT hOldFontText = (HFONT)SelectObject(hdc, hFontText);
SIZE TextSize;
GetTextExtentPoint32(hdc, str, strlen(str), &TextSize);
// get the center of a not-rotated text
POINT center;
center.x = TextSize.cx / 2;
center.y = TextSize.cy / 2;
POINT rcenter;
rcenter.x = long(cos(radian) * center.x - sin(radian) * center.y);
rcenter.y = long(sin(radian) * center.x + cos(radian) * center.y);
// finally draw the text and move it to the center of the rectangle
SetTextAlign(hdc, TA_BASELINE);
SetBkMode(hdc, TRANSPARENT);
long lx = rect->left + ((rect->right - rect->left) / 2) - rcenter.x;
long ly = rect->top + ((rect->bottom - rect->top) / 2) + rcenter.y;
SetGraphicsMode(hdc,GM_ADVANCED);
ExtTextOut(hdc, lx, ly, nOptions, rect, str, strlen(str), NULL);
DeleteObject(hFontText);
SelectObject(hdc, hOldFontText);
return;
}
It all depends on what you mean by a "page".
A point is a physical unit of measure, just like an inch or a millimeter. In most modern contexts it's equal to 1/72 of an inch.
A page in Word corresponds to an actual piece of paper with physical dimensions. After subtracting out the margins it might be 6 inches wide, or 432 points. This will be zoomed by an amount to fill the screen.
Sizing a screen is a little more nebulous, since monitor sizes and resolutions may differ. Long ago Windows standardized on 96 pixels per inch for a default, as this was close enough to the typical monitor connected to a Windows system. This means a 1920x1080 monitor is considered by Windows to be 20 inches wide, or 1440 points, no matter how physically large it is.
In Word's case the screen is 432 points wide, while your program considers it to be 1440 points wide. This is very close to the 1/3 ratio you report. If you set Word to an exact 100% zoom the rendered text should be identical because the page will shrink.
The answer is to do as Word does, and zoom in by making everything 3x larger.
Edit: In your case it seems you want to emulate what Word is doing and show a facsimile of a page. That makes it even easier.
The formula given by Microsoft was from a time when floating point arithmetic was slow and avoided whenever possible. That's not the case today. The equivalent is:
lfHeight = -(int)(points * dpi / 72);
The question is what to use for dpi? You can calculate it directly! Since you know the size of the page you're displaying and the width of the window in pixels, it's a simple division:
double dpi = pixelWidth / (double)inches;
If you need to know what the effective zoom is, that's also a simple division: zoom = dpi / GetDeviceCaps(hdc, LOGPIXELSY).
Related
I built this example to quickly rotate images 90 degrees but I always get a cut of the image on the sides. After many tests, unfortunately I still don't understand the cause of the problem.
void rotate()
{
Graphics::TBitmap *SrcBitmap = new Graphics::TBitmap;
Graphics::TBitmap *DestBitmap = new Graphics::TBitmap;
SrcBitmap->LoadFromFile("Crayon.bmp");
DestBitmap->Width=SrcBitmap->Width;
DestBitmap->Height=SrcBitmap->Height;
SetGraphicsMode(DestBitmap->Canvas->Handle, GM_ADVANCED);
double myangle = (double)(90.0 / 180.0) * 3.1415926;
int x0=SrcBitmap->Width/2;
int y0=SrcBitmap->Height/2;
double cx=x0 - cos(myangle)*x0 + sin(myangle)*y0;
double cy=y0 - cos(myangle)*y0 - sin(myangle)*x0;
xForm.eM11 = (FLOAT) cos(myangle);
xForm.eM12 = (FLOAT) sin(myangle);
xForm.eM21 = (FLOAT) -sin(myangle);
xForm.eM22 = (FLOAT) cos(myangle);
xForm.eDx = (FLOAT) cx;
xForm.eDy = (FLOAT) cy;
SetWorldTransform(DestBitmap->Canvas->Handle, &xForm);
BitBlt(DestBitmap->Canvas->Handle,
0,
0,
SrcBitmap->Width,
SrcBitmap->Height,
SrcBitmap->Canvas->Handle,
0,
0,
SRCCOPY);
DestBitmap->SaveToFile("Crayon2.bmp");
delete DestBitmap;
delete SrcBitmap;
}
If rotating the whole image, the width and height for destination image should be flipped:
DestBitmap->Width = SrcBitmap->Height;
DestBitmap->Height = SrcBitmap->Width;
The transform routine was centering the image based on original width/height. We want to adjust x/y position to push the starting point to left/top for BitBlt
int offset = (SrcBitmap->Width - SrcBitmap->Height) / 2;
BitBlt(DestBitmap->Canvas->Handle, offset, offset, SrcBitmap->Width, SrcBitmap->Height,
SrcBitmap->Canvas->Handle, 0, 0, SRCCOPY);
Once I had a similar problem.
I'm wanted to rotate two images around a common rotation point. But I couldn't do it with the standard function, because it doesn't allow a rotation point decentralized to the center.
Nevertheless I had made notes to the standard function at that time. Maybe they help you.
I'm remember that it was important that the size of the target image is correct! If a portrait image becomes a landscape image, the image becomes wider, therefore the BitBlt function must also specify the size of the target image.
Here my note to standard function.
Filling the xForm parameters was not quite the same for me as in your code snippet.
This was then the function I used to rotate around any center.
On a C++ game, we're using Pango to render text with cairo and from there to an OpenGL texture.
I noticed this problem recently while working on implementing text-wrapping via Pango.
What I'm doing is calculating the width by translating our own coordinate system to pixels and then using that as a fraction of the window width in PANGO UNITS, or in code:
float screenEdge = _w * m * static_cast<float>(PANGO_SCALE);
float tempMaxWidth = _owner->m_wrapping.m_maxWidth;
tempMaxWidth = std::min(_owner->m_wrapping.m_maxWidth, (0.48f - _owner->dimensions().x1()));
float wrapWidth = screenEdge * tempMaxWidth;
maxWidth = static_cast<int>(std::round(wrapWidth));
pango_layout_set_width(_owner->m_pangoLayout.get(), maxWidth);
Then we get a rectangle from the layout an pass that to cairo with:
float border = 2.0f;
PangoRectangle lRec;
pango_layout_get_pixel_extents(_owner->m_pangoLayout.get(), nullptr, &lRec);
m_width = (lRec.width + border); // Add twice half a border for margins
m_height = (lRec.height + border);
std::unique_ptr<cairo_surface_t, decltype(&cairo_surface_destroy)> m_cairoSurface(
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, m_width, m_height),
&cairo_surface_destroy);
std::unique_ptr<cairo_t, decltype(&cairo_destroy)> m_cairoDc(
cairo_create(m_cairoSurface.get()),
&cairo_destroy);
cairo_set_antialias(m_cairoDc.get(), CAIRO_ANTIALIAS_FAST);
cairo_push_group_with_content (m_cairoDc.get(), CAIRO_CONTENT_COLOR_ALPHA);
cairo_set_operator(m_cairoDc.get(), CAIRO_OPERATOR_SOURCE);
// Add Pango line and path to proper position on the DC
cairo_move_to(m_cairoDc.get(), (0.5f * border), (0.5f * border)); // Margins needed for border stroke to fit in
But the output, as you can see below, is cut-off; this does not happen if I use PANGO_ALIGN_LEFT
Searching around here, I found Cairo + Pango layout non-left alignment
Which appears related to my issue but does not quite provide a solution (or I didn't get it properly); what I tried was to calculate the different between the x and y coordinates of the logical and ink rectangles and adding that to cairo_move_to but that made no real difference in the output, so I just pasted the original code here.
I'm using direct2d to draw a bitmap (play a video) in a window, and I want to get the absolute coordinates for any position in the playing space, whether transformations are applied or not. So if the resolution is 1280x720, then by hovering the cursor over the image, I should get values like x = 0 ... 1280, y = 0 ... 720.
The positions of the total video area are in the variable m_rcLiveWindowPos, while the variable m_rcDstVideoRect contains the positions of the actual video after adjusting for the aspect ratio. Finally, m_rcSrcVideoRect is just the video resolution (ex: left=0, top=0, right=1280, bottom=720).
Below, I applied a translation and then a scale to the renderTarget. The rawScaleFactor is a number representing the amount to scale the video: if rawScaleFactor=1, then the video should be played at 100%. If 2, then at 200%.
This all works great -- the video zooms in properly and I can click and drag the video around. The problem is that I want to get the absolute x and y coordinates of the video resolution while my cursor is hovering over the video. The first definitions of mousePosInImage work for videos with no zoom/panning with the m_rcDstVideoRect sitting in a "fitted" position, but the values are incorrect for a zoomed-in video.
if (rawScaleFactor != 0)
{
// Make the dragging more precise based on the scaling factor.
float dragPosX = (float)m_rawScaleOffsetX / (rawScaleFactor * 2.0f);
float dragPosY = (float)m_rawScaleOffsetY / (rawScaleFactor * 2.0f);
D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(dragPosX, dragPosY);
// Get the center point of the current image.
float centerPointX = float(m_rcLiveWindowPos.Width()) / 2;
float centerPointY = float(m_rcLiveWindowPos.Height()) / 2;
// Calculate the amount that the image must scaled by.
D2D1ScaleFactor = ((float)m_videoResolution.width / (float)(m_rcDstVideoRect.right - m_rcDstVideoRect.left)) * (float)rawScaleFactor;
D2D1_MATRIX_3X2_F scale = D2D1::Matrix3x2F::Scale(D2D1::Size(D2D1ScaleFactor, D2D1ScaleFactor),
D2D1::Point2F(centerPointX, centerPointY));
// First translate the image, then scale it.
m_pJRenderTarget->SetTransform(translation * scale);
int32_t width = ((int32_t)m_videoResolution.width);
int32_t height = ((int32_t)m_videoResolution.height);
// This works for non-zoomed in video:
m_mousePosInImageX = int32_t(width * (rawMousePosX - m_rcDstVideoRect.left) / (m_rcDstVideoRect.right - m_rcDstVideoRect.left));
m_mousePosInImageY = int32_t(height * (rawMousePosY - m_rcDstVideoRect.top) / (m_rcDstVideoRect.bottom - m_rcDstVideoRect.top));
// Does not work for all cases...
m_mousePosInImageX = int32_t((centerPointX * D2D1ScaleFactor) - (centerPointX) + (m_mousePosInImageX / D2D1ScaleFactor));
m_mousePosInImageY = int32_t((centerPointY * D2D1ScaleFactor) - (centerPointY) + (m_mousePosInImageY / D2D1ScaleFactor));
}
m_pJRenderTarget-> DrawBitmap(m_pJVideoBitmap,
m_rcDstVideoRect,
1.0f,
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR,
m_rcSrcVideoRect);
I need a way to "reflect" the changes that SetTransform() did in the mousePosInImage variables.
I've got a remote screen that takes mouse position then converts the position into the captured screens position but the position is always off. I'm not the greatest at math but I'm sure my conversion is correct. The image size I get in correlation with the screen along with the window size is correct. The only thing that ends up being off is my click point after conversion.
** Also I know I have no error checking. This is to help with readability.
EXAMPLE: I want to click on Screen point 1102x 999y. The Remote Window point is approx 676 584. After conversion the point gets translated to 1081.5 935.3. I would understand maybe a few pixels off because its hard to click on that exact spot through the RW but its getting translated 60 pixels up and 20 pixels left.
Lets say that my remote screen is 800 x 600 and the captured is 1280 by 1024.
to get the difference I use (1280 / 800)*Clickpointx and (1024 / 600)*Clickpointy.
My problem is sWidth and sHeight are always off by at least 20 and sometimes are off by upwards of 80 depending on where the click is inside the window.
POINT p;
float sWidth;
float sHeight;
GetCursorPos(&p);
RECT rect;
GetWindowRect(hWnd, &rect); // Get Window Size
ScreenToClient(hWnd, &p); // convert points relative to screen to points relative to window
float wWidth = rect.right - rect.left;
float wHeight = rect.bottom - rect.top;
Gdiplus::Image* image = Gdiplus::Image::FromStream(sRemote_Handler.istream); //Get Captured image Size;
float iWidth = image->GetWidth();
float iHeight = image->GetHeight();
INPUT input;
input.type = INPUT_MOUSE;
input.mi.time = 0;
float pointx = p.x;
float pointy = p.y;
sWidth = (iWidth / wWidth)*pointx; // divide image width by screen width
sHeight = (iHeight / wHeight)*pointy; // divide image height by screen height
input.mi.dx = sWidth*(65536.0f / iWidth); //convert to pixels
input.mi.dy = sHeight*(65536.0f / iHeight); // ^
input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
SendInput(1, &input, sizeof(input)); // move mouse
I've tried changing the conversion method to (Clickpointx*1280)/800 and (Clickpointy*1240)/600. Still the same results.
sWidth = (pointx * iWidth) / wWidth;
sHeight = (pointy * iHeight) / wHeight;
Changed the conversion method once more. Same Results
sWidth = (pointx / iWidth)*wWidth;
sHeight = (pointy / iHeight)*wHeight;
EDIT
I've came to the conclusion that its the ScreenToClient windows function.
When clicking in the bottom right corner which should be damn near 800,600 both values are around 50 less then what they should be. When I click in the top left both values are where they should be. The numbers are reporting as 0,1. It seems as though the further I get from the initial 0,0 point the less accurate ScreenToClient gets.
Making some experiments with ttf fonts, and tried to use famous font rendering library FreeType, version 2.5.3.
My code:
#include "ft2build.h"
#include FT_FREETYPE_H
#define FONTPATH "<font path here>"
const char* fontfile=FONTPATH "<fontname here>.TTF";
const int w=25;
const int h=25;
char* outbitmap;
int main() {
outbitmap=new char[w*h];
memset(outbitmap,0,w*h);
FT_Library ftl;
FT_Error err=FT_Init_FreeType(&ftl);
FT_Face fface;
err=FT_New_Face(ftl,fontfile,0,&fface);
err=FT_Set_Pixel_Sizes(fface,w,h);
FT_UInt ch;
//ch=0x3042; //あ
//ch='_';
ch='|';
FT_UInt chridx=FT_Get_Char_Index(fface,ch);
err=FT_Load_Glyph(fface,chridx,FT_LOAD_DEFAULT);
err=FT_Render_Glyph(fface->glyph,FT_RENDER_MODE_NORMAL);
for(int y=0;y<fface->glyph->bitmap.rows;++y) {
int outy=fface->glyph->bitmap.rows-fface->glyph->bitmap_top+y; //???how to get baseline position
for(int x=0;x<fface->glyph->bitmap.width;++x) {
int outx=fface->glyph->bitmap_left+x;
outbitmap[outy*w+outx]=fface->glyph->bitmap.buffer[fface->glyph->bitmap.width*y+x];
}
}
delete[] outbitmap;
err=FT_Done_Face(fface);
err=FT_Done_FreeType(ftl);
return 0;
}
So I have some questions.
Assume that I need render one character to byte array with fixed size, in correct position.
Character size must be exactly size that fits in output bitmap, no clipping allowed.
It's OK to ignore kerning completely.
I specified character width=height=25. But for '|' it gives fface->glyph->bitmap.rows==26.
How should I set height to get exactly 25px output, not 26, for any normal font? If it isn't
possible, then is there any way to calculate output character height exactly in pixels before
FT_Render_Glyph. FT_Set_Pixel_Sizes doesn't work well enough, so I getting +1px sometimes.
How do I get baseline position for any given font? If I have baseline, I can place character
in exactly right position. I have no 1000x1000 screen, just one bitmap 25x25.
This is super late but I just found out how to do this:
(Getting the character height in pixels). From what I understand setting the font size (e.g. with FT_Set_Char_Size) really just defines a scaling between font units and pixel units (this explains more). I.e., 12pt font at 72dpi does not necessarily mean the glyph is 12 pixels high. You can, however, get a bounding box that I am pretty sure will enclose every single glyph in the chosen font. This is how I did it:
//12pt font
double font_size = 12.0;
//Resolution means DPI here
double pixel_size = font_size * resolution / 72;
//Font height and width in pixels
int font_height = round((face->bbox.yMax - face->bbox.yMin)*pixel_size / face->units_per_EM);
int font_width = round((face->bbox.xMax - face->bbox.xMin)*pixel_size / face->units_per_EM);
This is how I got a baseline height. It's just the maximum descent below the baseline of any glyph. When paired with the above code, drawing your glyph with the baseline at this position should guarantee that all glyphs will fit in the bounding box.
//Convert this to pixel_size if your DPI is not 72
double font_size = 12.0;
//Height in pixels (using a double for sub-pixel precision)
double baseline_height = abs(face->descender) * font_size / face->units_per_EM;
(Where face is the font you have loaded).
EDIT: forgot to account for DPI, in my application I was just using 72 so it canceled out
Nobody answered anything, so posting some more workarounds than answers I found by myself.
For question 1, I can probably live with that after applying following:
FT_Set_Pixel_Sizes(fface,w-1,h-1);
or maybe even
FT_Set_Pixel_Sizes(fface,w-w%2,h-h%2);
For question 2 I don't know yet ...