I am using D2D with D3D11. I have some code that uses GetCursorpos() from the windows API which is then converted to client coordinates and then draws a small circle at this position using D2D FillEllipse(). The screen to client coordinates work perfectly but for some reason D2D draws the circle a small distance from the expected location (tens of pixels) as if the coordinate had been scaled by a small factor so that the error gets larger as the circle is drawn further from (0, 0).
I noticed changing the dpi for the D2D1_RENDER_TARGET_PROPERTIES affects this 'scaling' so I suspect the problem has something to do with dpi. This is the code for creating the D2D render target from the DXGI surface I obtained from the swapchain in my D3D11 code.
// Create render target
float dpiX, dpiY;
this->factory->GetDesktopDpi(&dpiX, &dpiY);
D2D1_RENDER_TARGET_PROPERTIES rtDesc = D2D1::RenderTargetProperties(
D2D1_RENDER_TARGET_TYPE_HARDWARE,
D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
dpiX,
dpiY
);
AssertHResult(this->factory->CreateDxgiSurfaceRenderTarget(
surface.Get(),
&rtDesc,
&this->renderTarget
), "Failed to create D2D render target");
Here, dpiX and dpiY become 96 which I notice is also the constant that GetDpiForWindow() from the windows API returns when it is not dpi aware.
I want to know how I can fix my code so that it will draw the circle at the position given by GetCursorPos().
More relevant code:
Driver code
Vector3f cursPos = input.GetCursorPos();
DrawCircle(Colour::Green, cursPos.x, cursPos.y, 3/*radius*/);
Input
POINT pt{};
::GetCursorPos(&pt);
// Convert from screen pixels to client pixels
return ConvertPixelSpace(this->hWnd, (float)pt.x, (float)pt.x, PixelSpace::Screen, PixelSpace::Client);
Direct2D
void DrawCircle(const Colour& c, float centreX, float centreY, float radius, PixelSpace ps)
{
Vector3f centre = ConvertPixelSpace(this->gfx.hWnd, centreX, centreY, ps, PixelSpace::Client);
centreX = centre.x;
centreY = centre.y;
D2D1_ELLIPSE el{};
el.point.x = centreX;
el.point.y = centreY;
el.radiusX = radius;
el.radiusY = radius;
auto brush = this->CreateBrush(c);
this->renderTarget->FillEllipse(
&el,
brush.Get()
);
}
PixelSpace Conversion
Vector3f ConvertPixelSpace(HWND hWnd, float x, float y, PixelSpace curSpace, PixelSpace newSpace)
{
RECT rc = GetClientRectOfWindow(hWnd);
struct
{
float top, left, width, height;
} rectf;
rectf.top = static_cast<float>(rc.top);
rectf.left = static_cast<float>(rc.left);
rectf.width = static_cast<float>(rc.right - rc.left);
rectf.height = static_cast<float>(rc.bottom - rc.top);
// Convert to client space
if (curSpace == PixelSpace::Screen)
{
x -= rectf.left;
y -= rectf.top;
}
// Convert to new space
if (newSpace == PixelSpace::Screen)
{
x += rectf.left;
y += rectf.top;
}
return Vector3f(x, y);
}
RECT GetClientRectOfWindow(HWND hWnd)
{
RECT rc;
::GetClientRect(hWnd, &rc);
// Pretty sure these are valid casts.
// rc.top is stored directly after rc.left and this forms a POINT struct
ClientToScreen(hWnd, reinterpret_cast<POINT*>(&rc.left));
ClientToScreen(hWnd, reinterpret_cast<POINT*>(&rc.right));
return rc;
}
The problem was I was creating the D3D11 swapchain with the window area instead of the client area.
RECT rect{};
GetWindowRect(hWnd, &rect); // !!! This should be GetClientRect()
this->width = rect.right - rect.left;
this->height = rect.bottom - rect.top;
DXGI_SWAP_CHAIN_DESC scDesc{};
scDesc.BufferDesc.Width = width;
scDesc.BufferDesc.Height = height;
//...
Related
I have this function:
void Texture::render(int x, int y, int w, int h, SDL_Renderer *&renderer, double angle, SDL_Point* center, SDL_RendererFlip flip)
{
// Set a destination value to -1 to keep the current value
if (x < 0) { x = rect.x; }
if (y < 0) { y = rect.y; }
if (w < 0) { w = rect.w; }
if (h < 0) { h = rect.h; }
// Create destination rectangle
SDL_Rect dstRect = { x, y, w, h };
// Render to screen
SDL_RenderCopyEx(renderer, texture, &rect, &dstRect, angle, center, flip);
}
It works. It creates an image of the correct size at the location I want. But I want to add a chunk of code where it resizes the texture itself to be the size given in the destRect.
So, anyone who finds this and reads the conversation I had with Nelfeal in the comments will see that I had a misunderstanding of how SDL_RenderCopyEx works. There's no need to resize the texture. If you need to do something like that, you can just use the dstRect when you copy it.
Actually, as far as I can find, there isn't a method to resize the actual texture itself. I'm sure one exists, but it's definitely not something people are commonly using. Which is usually a sign that it's a bad idea.
I've tweaked my code to try and simplify it, for anybody who's trying to do something similar to me:
void render(SDL_Renderer *&renderer, SDL_Rect *dstRect=NULL, SDL_Rect &srcRect=NULL, double angle=0.0, SDL_Point* center=NULL, SDL_RendererFlip flip=SDL_FLIP_NONE);
void Texture::render(SDL_Renderer *&renderer, SDL_Rect *dstRect, SDL_Rect *srcRect, double angle, SDL_Point* center, SDL_RendererFlip flip)
{
// Check to see if a destination was provided
bool check = false;
if (dstRect == NULL)
{
check = true;
dstRect = new SDL_Rect();
dstRect->x = 0;
dstRect->y = 0;
dstRect->w = SCREEN_WIDTH;
dstRect->h = SCREEN_HEIGHT;
}
// Check to see if the entire texture is being copied
if (srcRect == NULL) { srcRect = ▭ }
// Render to screen
SDL_RenderCopyEx(renderer, texture, srcRect, dstRect, angle, center, flip);
// Free dstRect
if (check) delete dstRect;}
And it looks like this when using the function:
bgTex.render(renderer);
blobTex.render(renderer, &blobDstRect);
I am currently using the MoveWindow() function in the header Windows.h, and using this function, I can move and resize the output console window any way I want.
MoveWindow(GetConsoleWindow(), x, y, width, height, TRUE);
// x and y is the position of the topleft corner of window
However, I cannot figure out how to center the screen without hard-coding the position of the window. Is there a way to set the position of the window to change depending on the width and height that I set? Thanks!
P.S. I am pretty new to C++
Get the WindowRect ( not to confuse with the ClientWindow)of your screen and find middle position, but ClientRect will remain unchanged since we are not resizing.Try this snippet:
Edited: For proper centering and to allow user to specify the position
void MoveWindow(int posx, int posy)
{
RECT rectClient, rectWindow;
HWND hWnd = GetConsoleWindow();
GetClientRect(hWnd, &rectClient);
GetWindowRect(hWnd, &rectWindow);
MoveWindow(hWnd, posx, posy, rectClient.right - rectClient.left, rectClient.bottom - rectClient.top, TRUE);
}
void MoveCenter()
{
RECT rectClient, rectWindow;
HWND hWnd = GetConsoleWindow();
GetClientRect(hWnd, &rectClient);
GetWindowRect(hWnd, &rectWindow);
int posx, posy;
posx = GetSystemMetrics(SM_CXSCREEN) / 2 - (rectWindow.right - rectWindow.left) / 2,
posy = GetSystemMetrics(SM_CYSCREEN) / 2 - (rectWindow.bottom - rectWindow.top) / 2,
MoveWindow(hWnd, posx, posy, rectClient.right - rectClient.left, rectClient.bottom - rectClient.top, TRUE);
}
int main(int argc, char *argv[])
{
MoveWindow(10, 10);
return 0;
}
I have refereed below article to draw a custom frame area with DWM.
Custom Window Frame Using DWM
After removing the standard frame, non client area is not exist in the frame.
void CMainFrame::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
int nTHight = 30; /*The title bar height*/
RECT * rc;
RECT aRect;
RECT bRect;
RECT bcRect;
if(bCalcValidRects == TRUE)
{
CopyRect(&aRect,&lpncsp->rgrc[1]);
CopyRect(&bRect,&lpncsp->rgrc[0]);
bcRect.left = bRect.left;
bcRect.top = bRect.top - nTHight;
bcRect.right = bRect.right;
bcRect.bottom = bRect.bottom;
CopyRect(&lpncsp->rgrc[0],&bcRect);
CopyRect(&lpncsp->rgrc[1],&bRect);
CopyRect(&lpncsp->rgrc[2],&aRect);
}
else
{
rc = (RECT *)lpncsp;
rc->left = rc->left;
rc->top = rc->top - nTHight;
rc->right = rc->right;
rc->bottom = rc->bottom;
}
CFrameWnd::OnNcCalcSize(bCalcValidRects, lpncsp);
}
Because the entire window is client region, I have to adjust the UI control placement for the frame, but I don't know how to handle this problem.
For example, below red rectangle (all UI component) should be shifted into the original coordinate of the client area before removing the non client part.
CWnd::GetWindowRect gives you the rectangle of the window on screen. The dimensions of the caption, border, and scroll bars, if present, are included.
CWnd::GetClientRect gives you the client rectangel of the window. The left and top members will be 0. The right and bottom members will contain the width and height of the window.
CWnd::ScreenToClientand CWnd::ClientToScreen calculate a point or rectangle from the client area to screen coordinates and back to screen.
AdjustWindowRect calculates the required window rectangle, based on the client rectangle of the window.
Here is afunction which calcualtes the margins of a window:
void CalculateWndMargin( const CWnd &wnd, int &leftM, int &rightM , int &topM, int &bottomM )
{
CRect wndRect;
wnd.GetWindowRect( wndRect );
CRect screenRect;
wnd.GetClientRect( screenRect );
wnd.ClientToScreen( screenRect );
leftM = screenRect.left - wndRect.left;
rightM = wndRect.right - screenRect.right;
topM = screenRect.top - wndRect.top;
bottomM = wndRect.bottom - screenRect.bottom;
}
I'm putting this here because the algorithm for doing this is more difficult to find than it should be. Hopefully Google will cache this.
The problem is: you have a bitmap and a window. You want to draw the bitmap inside a window, filling the window, keeping the aspect ratio, as the window resizes.
You may also want to be able to fit it the other way, so that you can draw the image "over" the window, and all the area in the window will be filled. This will clip out some of the image. I present in the answer a simple algorithm for doing so.
Here's an implementation that uses integer math only.
The algorithm first stretches both dimensions, preserving aspect ratio. The new size is calculated, assuming that the respective other dimension occupies the entire space. Of these new dimensions, the one that overshoots the available area is set to the maximum possible value, while the other is scaled back, preserving aspect ratio. (For pan and scan (bScale is set to true) mode, the dimension that doesn't overshoot the available space is set to occupy the entire range.)
(Note: If sizePicture is an empty rectangle, this function returns a rectangle that stretches one pixel to the left and one pixel up, either from the origin, or the center.)
RECT size_rect( RECT& rcScreen,
RECT& sizePicture,
bool bCenter/*,
bool bScale*/ ) {
int clientWidth = rcScreen.right - rcScreen.left;
int clientHeight = rcScreen.bottom - rcScreen.top;
int picWidth = sizePicture.right - sizePicture.left;
int picHeight = sizePicture.bottom - sizePicture.top;
// Calculate new content size
int contentWidth = ::MulDiv( clientHeight, picWidth, picHeight );
int contentHeight = ::MulDiv( clientWidth, picHeight, picWidth );
// Adjust dimensions to fit inside client area
if ( contentWidth > clientWidth ) {
// To use the bScale parameter that allows the image to fill the entire
// client area, use the following if-clause instead.
//if ( ( bScale && ( contentWidth < clientWidth ) )
// || ( !bScale && ( contentWidth > clientWidth ) ) ) {
contentWidth = clientWidth;
contentHeight = ::MulDiv( contentWidth, picHeight, picWidth );
} else {
contentHeight = clientHeight;
contentWidth = ::MulDiv( contentHeight, picWidth, picHeight );
}
RECT rect = { 0 };
::SetRect( &rect, 0, 0, contentWidth, contentHeight );
if ( bCenter ) {
// Calculate offsets to center content
int offsetX = ( clientWidth - contentWidth ) / 2;
int offsetY = ( clientHeight - contentHeight ) / 2;
::OffsetRect( &rect, offsetX, offsetY );
}
return rect;
}
Make two RECT. One is the window you wish to fit to (passed into rcScreen), and the other holds the dimensions of the picture:
(pseudo-code)
RECT window;
GetClientRect(hwnd,&window)
RECT bitmap_rect;
BITMAP bitmap;
bitmap_rect.left = bitmap_rect.top = 0;
bitmap_rect.right = bitmap.bmWidth;
bitmap_rect.bottom = bitmap.bmHeight;
RECT draw_rect = size_rect(window,bitmap_rect,true,true);
Then StretchBlt it:
StretchBlt(toDC, draw_rect.left, draw_rect.top, draw_rect.right, draw_rect.bottom, fromDC, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
This is the function: (note there is no case for bCenter = false and Scale = true). **bCenter is flag for "center picture in window." Scale is flag for "pan and scan mode" instead of "letterbox," useful if you are using an image as a window background that you want resized but don't want to have letterboxes. **
RECT size_rect(RECT& rcScreen,
RECT& sizePicture,
bool bCenter,
bool Scale)
{
RECT rect = rcScreen;
double dWidth = rcScreen.right - rcScreen.left;
double dHeight = rcScreen.bottom - rcScreen.top;
double dAspectRatio = dWidth / dHeight;
double dPictureWidth = sizePicture.right - sizePicture.left;
double dPictureHeight = sizePicture.bottom - sizePicture.top;
double dPictureAspectRatio = dPictureWidth / dPictureHeight;
double nNewHeight = dHeight;
double nNewWidth = dWidth;
double nHeightCenteringFactor = 0;
double nWidthCenteringFactor = 0;
double xstart = rcScreen.left;
double ystart = rcScreen.top;
if (dPictureAspectRatio > dAspectRatio)
{
if (bCenter && Scale) {
nNewWidth = dPictureWidth*(1 / (dPictureHeight / dHeight));
xstart = rcScreen.left - ((nNewWidth / 2) - (dWidth / 2));
}
else {
nNewHeight = (int)(dWidth / dPictureWidth*dPictureHeight);
if (bCenter)
ystart = ((dHeight - nNewHeight) / 2) + rcScreen.top;
}
}
else if (dPictureAspectRatio < dAspectRatio)
{
if (bCenter && Scale) {
nNewHeight = dPictureHeight*(1 / (dPictureWidth / dWidth));
ystart = rcScreen.top - ((nNewHeight / 2) - (dHeight / 2));
}
else{
nNewWidth = (dHeight / dPictureHeight*dPictureWidth);
if (bCenter)
xstart = ((dWidth - nNewWidth) / 2) + rcScreen.left;
}
}
SetRect(&rect, xstart, ystart, nNewWidth, nNewHeight);
return rect;
}
I'm using the sdl library, but it dosent support scale / resize surface, so i downloaded the
SDL_image 1.2 & SDL_gfx Library. My function/code works, but the image appear in bad / low
quality.
Let say i got a image which is 100X100, if i scale down to 95X95 or scale up to 110X110 the
quality appear very low, but if i leave it at 100X100 which is the same size it appear in
good quality. Images most appear in good quality, if scaled down, but ... it dosent
my code is:
int drawImage(SDL_Surface* display, const char * filename, int x, int y, int xx, int yy , const double newwidth, const double newheight, int transparent = NULL)
{
SDL_Surface *image;
SDL_Surface *temp;
temp = IMG_Load(filename); if (temp == NULL) { printf("Unable to load image: %s\n", SDL_GetError()); return 1; }
image = SDL_DisplayFormat(temp); SDL_FreeSurface(temp);
// Zoom function uses doubles for rates of scaling, rather than
// exact size values. This is how we get around that:
double zoomx = newwidth / (float)image->w;
double zoomy = newheight / (float)image->h;
// This function assumes no smoothing, so that any colorkeys wont bleed.
SDL_Surface* sized = zoomSurface( image, zoomx, zoomy, SMOOTHING_OFF );
// If the original had an alpha color key, give it to the new one.
if( image->flags & SDL_SRCCOLORKEY )
{
// Acquire the original Key
Uint32 colorkey = image->format->colorkey;
// Set to the new image
SDL_SetColorKey( sized, SDL_SRCCOLORKEY, colorkey );
}
// The original picture is no longer needed.
SDL_FreeSurface( image );
// Set it instead to the new image.
image = sized;
SDL_Rect src, dest;
src.x = xx; src.y = yy; src.w = image->w; src.h = image->h; // size
dest.x = x; dest.y = y; dest.w = image->w; dest.h = image->h;
if(transparent == true )
{
//Set the color as transparent
SDL_SetColorKey(image,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(image->format,0x0,0x0,0x0));
}
else {
}
SDL_BlitSurface(image, &src, display, &dest);
return true;
}
drawImage(display, "Image.png", 50, 100, NULL, NULL, 100, 100,true);
An image that is scaled without allowing smoothing is going to have artifacts. You might have better luck if you start with SVG and render it at the scale that you want. Here's an SVG -> SDL surface library.