GDI: Create Mountain Chart/Graph? - winapi

I can use Polyline() GDI function to plot values to create a graph but now I want the lower part of it filled in to create a mountain type chart. Is there something built-in to help create that? (I don't need gradient, but that would be a nice touch).
TIA!!

For this diagram type you need to draw a filled shape. The Polygon function can be used to draw an irregularly shaped filled object:
The polygon is outlined by using the current pen and filled by using the current brush [...].
The polygon points need to be constructed from the data points (left to right). To turn this into a closed shape, the bottom right and bottom left points of the diagram area need to be appended. The Polygon function then closes the shape automatically by drawing a line from the last vertex to the first.
The following implementation renders a single data set into a given device context's area:
/// \brief Renders a diagram into a DC
///
/// \param dc Device context to render into
/// \param area Diagram area in client coordinates
/// \param pen_col Diagram outline color
/// \param fill_col Diagram fill color
/// \param data Data points to render in data coordinate space
/// \param y_min Y-axis minimum in data coordinate space
/// \param y_max Y-axis maximum in data coordiante space
void render_diagram(HDC dc, RECT area,
COLORREF pen_col, COLORREF fill_col,
std::vector<int> const& data, int y_min, int y_max) {
// Make sure we have data
if (data.size() < 2) { return; }
// Make sure the diagram area isn't empty
if (::IsRectEmpty(&area)) { return; }
// Make sure the y-scale is sane
if (y_max <= y_min) { return; }
std::vector<POINT> polygon{};
// Reserve enough room for the data points plus bottom/right
// and bottom/left to close the shape
polygon.reserve(data.size() + 2);
auto const area_width{ area.right - area.left };
auto const area_height{ area.bottom - area.top };
// Translate coordinates from data space to diagram space
// In lieu of a `zip` view in C++ we're using a raw loop here
// (we need the index to scale the x coordinate, so we cannot
// use a range-based `for` loop)
for (int index{}; index < static_cast<int>(data.size()); ++index) {
// Scale x value
auto const x = ::MulDiv(index, area_width - 1, static_cast<int>(data.size()) - 1) + area.left;
// Flip y value so that the origin is in the bottom/left
auto const y_flipped = y_max - (data[index] - y_min);
// Scale y value
auto const y = ::MulDiv(y_flipped, area_height - 1, y_max - y_min);
polygon.emplace_back(POINT{ x, y });
}
// Semi-close the shape
polygon.emplace_back(POINT{ area.right - 1, area.bottom - 1 });
polygon.emplace_back(POINT{ area.left, area.bottom - 1 });
// Prepare the DC for rendering
auto const prev_pen{ ::SelectObject(dc, ::GetStockObject(DC_PEN)) };
auto const prev_brush{ ::SelectObject(dc, ::GetStockObject(DC_BRUSH)) };
::SetDCPenColor(dc, pen_col);
::SetDCBrushColor(dc, fill_col);
// Render the graph
::Polygon(dc, polygon.data(), static_cast<int>(polygon.size()));
// Restore DC (stock objects do not need to be destroyed)
::SelectObject(dc, prev_brush);
::SelectObject(dc, prev_pen);
}
Most of this function deals with translating and scaling data values into the target (client) coordinate space. The actual rendering is fairly compact in comparison, and starts from the comment reading // Prepare the DC for rendering.
To test this you can start from a standard Windows Desktop application, and dump the following into the WM_PAINT handler:
case WM_PAINT:
{
RECT rc{};
::GetClientRect(hWnd, &rc);
// Leave a 10px border around the diagram area
::InflateRect(&rc, -10, -10);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
auto pen_col = RGB(0x00, 0x91, 0x7C);
auto fill_col = RGB(0xCC, 0xE9, 0xE4);
render_diagram(hdc, rc, pen_col, fill_col, g_dataset1, 0, 100);
pen_col = RGB(0x02, 0x59, 0x55);
fill_col = RGB(0xCC, 0xDD, 0xDD);
render_diagram(hdc, rc, pen_col, fill_col, g_dataset2, 0, 100);
EndPaint(hWnd, &ps);
}
break;
g_dataset1/g_dataset2 are containers holding random values that serve as test input. It is important to understand, that the final diagram is rendered back to front, meaning that data sets with smaller values need to be rendered after data sets with higher values; the lower portion gets repeatedly overdrawn.
This produces output that looks something like this:
Note that on a HiDpi display device GDI rendering gets auto-scaled. This produces the following:
Looking closely you'll observe that the lines are wider than 1 device pixel. If you'd rather have a more crisp look, you can disable DPI virtualization by declaring the application as DPI aware. Things don't change for a standard DPI display; on a HiDpi display the rendering now looks like this:

Related

Why is DrawFocusRect affected by the Text color?

I was getting weird results with DrawFocusRect() in a TreeView custom draw routine. The outline was somewhat different, some were almost a solid line and others were dashed. I found out that it's the HDC SetTextColor() value that is affecting it. Even though the selection bars fill color is exactly the same, as I changed various text colors, indeed the drawn outline was different.
I ended up with setting the text color to match the fill color of the highlight bar which gives the same outline the default tree drawing routine gives.
Is this not documented anywhere? Is there even more to it?
Thanks?
DrawFocusRect draws the focus rectangle with a bitwise XOR operation. This is hinted at in the SDK documentation, under the "Remarks" section, where it says:
Because DrawFocusRect is an XOR function, calling it a second time with the same rectangle removes the rectangle from the screen.
This is the whole advantage of DrawFocusRect. It means that you (or the window manager) can draw the focus rectangle and erase it whenever needed without redrawing the entire underlying contents. (This was a big performance win in the era before double-buffering, gobs of memory, fast graphic cards, etc.) The focus rectangle is drawn wherever it needs to be drawn, then, when it needs to be erased, it is just drawn again on top of where it was drawn the first time. The only thing that you (or the window manager) need to keep track of is the coordinates of the rectangle.
You can achieve exactly the same effect as DrawFocusRect by using code like the following to draw an XOR rectangle manually:
void DrawXorRect(HDC hDC, const RECT* prc)
{
// (1)
static const WORD pattern[] = { 0x5555, 0xAAAA,
0x5555, 0xAAAA,
0x5555, 0xAAAA,
0x5555, 0xAAAA,
};
HBITMAP hbmpPattern = CreateBitmap(8, 8, 1, 1, pattern);
// (2)
HBRUSH hbrPattern = CreatePatternBrush(hbmpPattern);
// (3)
HBRUSH hbrOriginal = (HBRUSH)SelectObject(hDC, hbrPattern);
// (4)
UINT cx;
UINT cy;
SystemParametersInfo(SPI_GETFOCUSBORDERWIDTH , 0, &cx, 0);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &cy, 0);
// (5)
PatBlt(hDC, prc->left , prc->top , prc->right - prc->left, cy , PATINVERT); // top
PatBlt(hDC, prc->left , prc->bottom - cy, prc->right - prc->left, cy , PATINVERT); // bottom
PatBlt(hDC, prc->left , prc->top + cy, cx , prc->bottom - prc->top - (cy * 2), PATINVERT); // left
PatBlt(hDC, prc->right - cx, prc->top + cy, cx , prc->bottom - prc->top - (cy * 2), PATINVERT); // right
// (6)
SelectObject(hDC, hbrOriginal);
DeleteObject(hbrPattern);
DeleteObject(hbmpPattern);
}
Error-checking has been elided for brevity, but this code does work, and it does mimic DrawFocusRect exactly. (Yes, I even tested it.)
Let's go through the code, step by step, to see what it does, how it works, and what that means:
First, it creates a monochrome 8×8 bitmap (CreateBitmap()) consisting of an alternating pattern of bits (pattern[]): on, off, on, off, etc. (If you don't know why, open up the Windows calculator, switch it to hex mode, and paste in the hex values that comprise the pattern. Look at the bits: see how they alternate between 0s and 1s? Now, imagine creating a bitmap from that.)
Then, it uses that bitmap to create a brush (CreatePatternBrush()).
Next, it selects the newly-created brush into the DC (SelectObject()). It saves the return value, which is the brush that was originally selected in the DC because it needs to restore that later.
With all the drawing objects created, it calls SystemParametersInfo() to retrieve the width (SPI_GETFOCUSBORDER_WIDTH) and height (SPI_GETFOCUSBORDERHEIGHT) of the focus rectangle.
Armed with all the objects and information needed to do the drawing, it blits each of the 4 sides of the rectangle using the PATINVERT raster operation. What is PATINVERT?
Combines the colors of the specified pattern with the colors of the destination rectangle by using the Boolean XOR operator.
Finally, it cleans up after itself by restoring the originally-selected brush and deleting the drawing objects that it created.
So, basically, the focus rectangle is drawn by XORing an alternating pattern of on-and-off pixels (a checkerboard pattern) with the contents of the device context (i.e., whatever is on the screen). That's what PATINVERT does. But wait—we know what the colors of the device context are, but what are the colors of the brush? Well, remember that the brush was created from a monochrome bitmap. The SDK documentation for CreateBitmap says:
If the bitmap is monochrome, zeros represent the foreground color and ones represent the background color for the destination device context.
Aha! So when the brush's bitmap pattern is 0, the brush's color is your device context's foreground color (SetTextColor); when the brush's bitmap pattern is 1, the brush's color is your device context's background color (SetBackColor). This is how the foreground and background color of the device context come into play. These colors are merged together, via an XOR operation, with the color of the original pixel in the device context.
When using DrawFocusRect, your DC's foreground and background colors should be black and white, respectively. This is the default foreground and background colors for a device context, so if you haven't called the SetTextColor or SetBackColor functions, these will be its colors. If you have called either of those functions, you either need to:
Save the value returned by each and restore it later, once you are finished drawing, but before you call DrawFocusRect (just as the code above did with the return value of the first call to SelectObject), or
Call the SaveDC function at the beginning of your drawing code to save its state (by pushing it onto a stack internally), and then call the RestoreDC function at the end of your drawing code to restore it to its original state.
When the DC's foreground and background colors are black and white, respectively, then the DrawFocusRect function works as it is expected to. This is what you need to do if you want to match how the window manager draws focus rectangles around controls. (And you do, obviously, want to do this!)
If, for some reason, you don't want to reset the DC's colors, then you won't be able to use DrawFocusRect. You'll need to take another tack where you control the colors yourself. For example, you could create a color (non-monochrome) bitmap, with its colors fixed as white and black, and then create a brush from that. Alternatively, you could consider drawing with a pen and the R2_NOT ROP code; e.g.:
void DrawXorRect2(HDC hDC, const RECT* prc)
{
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(0, 0, 0); // black
HPEN hpenNew = ExtCreatePen(PS_COSMETIC | PS_ALTERNATE, 1, &lb, 0, NULL);
HPEN hpenOld = (HPEN)SelectObject(hDC, hpenNew);
int ropOld = SetROP2(hDC, R2_NOT);
int modeOld = SetBkMode(hDC, TRANSPARENT);
HBRUSH hbrOriginal = (HBRUSH)SelectObject(hDC, GetStockObject(NULL_BRUSH));
Rectangle(hDC, prc->left, prc->top, prc->right, prc->bottom);
SelectObject(hDC, hbrOriginal);
SetBkMode(hDC, modeOld);
SetROP2(hDC, ropOld);
SelectObject(hDC, hpenOld);
DeleteObject(hpenNew);
}
I'm not sure this is actually much better, as it still requires that you set attributes of the DC—namely, the ROP and the background mode. If you're going to set and restore attributes of the DC, why not just set and restore the foreground and background colors? Plus, this pen-based implementation has the additional disadvantage of not respecting the user's preference for the thickness of the focus rectangle, which is an accessibility bug. Also, the pixel grid is slightly different here compared to the built-in DrawFocusRect, but that is, admittedly, an extremely minor drawback.
Another approach would be to create a PS_GEOMETRIC pen with a custom pattern (PS_USERSTYLE, to make it alternating), and setting the ROP style to R2_XORPEN/R2_NOTXORPEN:
void DrawXorRect3(HDC hDC, const RECT* prc)
{
UINT cx;
UINT cy;
SystemParametersInfo(SPI_GETFOCUSBORDERWIDTH , 0, &cx, 0);
SystemParametersInfo(SPI_GETFOCUSBORDERHEIGHT, 0, &cy, 0);
LOGBRUSH lb;
lb.lbStyle = BS_SOLID;
lb.lbColor = RGB(0, 0, 0);
static const DWORD pattern[] = { 0, 2 };
HPEN hpenWidth = ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, cx, &lb, 2, pattern);
HPEN hpenHeight = ExtCreatePen(PS_GEOMETRIC | PS_USERSTYLE, cy, &lb, 2, pattern);
int ropOriginal = SetROP2(hDC, R2_NOTXORPEN);
HPEN hpenOriginal = (HPEN)SelectObject(hDC, hpenHeight);
MoveToEx(hDC, prc->left , prc->top , NULL); // \ top
LineTo (hDC, prc->right , prc->top ); // / edge
MoveToEx(hDC, prc->left + cx, prc->bottom - cy , NULL); // \ bottom
LineTo (hDC, prc->right - cx, prc->bottom - cy ); // / edge
SelectObject(hDC, hpenWidth);
MoveToEx(hDC, prc->left , prc->top + (cy * 2), NULL); // \ left
LineTo (hDC, prc->left , prc->bottom - cy ); // / edge
MoveToEx(hDC, prc->right - cx, prc->top + cy , NULL); // \ right
LineTo (hDC, prc->right - cx, prc->bottom ); // / edge
SelectObject(hDC, hpenOriginal);
SetROP2(hDC, ropOriginal);
DeleteObject(hpenHeight);
DeleteObject(hpenWidth);
}
This addresses the accessibility bug and makes the pixel pattern match that produced by DrawFocusRect, at the expense of creating an additional pen. It also, like the second attempt, works as expected regardless of what the DC's foreground or background colors are set to.
Note that all of these implementations of DrawXorRect become more efficient if you create the required drawing objects once and cache them, instead of creating and destroying them each time. This is surely a big part of the reason (along with convenience—who wants to write all this ugly code?) why the window manager provides the DrawFocusRect function for applications to use.

Why doesn't the RoundRect path with gradient fill produce the correct corners on right side?

I came up with a routine to create a gradient filled rounded rectangle (button), however if I omit the code that writes the outline, the lower-right corner looks square and the upper-right seems to be not quite right either . Why is that?
note: The owner-draw button was created 23x23.
//-------------------------------------------------------------------------
// Purpose: Draw a rounded rectangle for owner-draw button
//
// Input: dis - [i] owner-draw information structure
// undermouse - [i] flag if button is under mouse
//
// Output: na
//
// Notes: This creates a standard grey type rounded rectangle for owner
// drawn buttons.
//
// This routine does not currently use undermouse to change
// gradient
//
void DrawRoundedButtonRectangle(const DRAWITEMSTRUCT& dis, BOOL undermouse)
{
UNREFERENCED_PARAMETER(undermouse);
// save DC before we modify it.
SaveDC(dis.hDC);
// create a path for the round rectangle (right/bottom is RECT format of +1)
BeginPath(dis.hDC);
RoundRect(dis.hDC, dis.rcItem.left, dis.rcItem.top, dis.rcItem.right, dis.rcItem.bottom, 6, 6);
EndPath(dis.hDC);
// save DC before changing clipping region
SaveDC(dis.hDC);
// set clipping region to be the path
SelectClipPath(dis.hDC, RGN_COPY);
TRIVERTEX vertices[2];
// setup the starting location and color (light grey)
vertices[0].x = dis.rcItem.left;
vertices[0].y = dis.rcItem.top;
vertices[0].Red = MAKEWORDHL(211, 0);
vertices[0].Green = MAKEWORDHL(211, 0);
vertices[0].Blue = MAKEWORDHL(211, 0);
vertices[0].Alpha = 0xffff;
// setup the ending location and color (grey)
vertices[1].x = dis.rcItem.right; // should this be -1 ?
vertices[1].y = dis.rcItem.bottom; // should this be -1 ?
vertices[1].Red = MAKEWORDHL(150, 0);
vertices[1].Green = MAKEWORDHL(150, 0);
vertices[1].Blue = MAKEWORDHL(150, 0);
vertices[1].Alpha = 0xffff;
// setup index to use for left to right
GRADIENT_RECT r[1];
r[0].UpperLeft = 0;
r[0].LowerRight = 1;
// fill the DC with a vertical gradient
GradientFill(dis.hDC, vertices, _countof(vertices), r, _countof(r), GRADIENT_FILL_RECT_V);
// go back to original clipping area
RestoreDC(dis.hDC, -1);
// change the path to be the outline border
if (WidenPath(dis.hDC)) {
// set clipping region to be the path
SelectClipPath(dis.hDC, RGN_COPY);
// create a gradient on the outline
GradientFill(dis.hDC, vertices, _countof(vertices), r, _countof(r), GRADIENT_FILL_RECT_V);
}
// put back the DC as we received it
RestoreDC(dis.hDC, -1);
}
The red in the pics show the background.
The bad button is generated when the WidenPath section is removed.
According to your description, I think you may be talking about this situation.
BeginPath(dis.hDC);
// RoundRect(dis.hDC, dis.rcItem.left, dis.rcItem.top, dis.rcItem.right, dis.rcItem.bottom, 6, 6);
EndPath(dis.hDC);
Let me first analyze the reason why I got this shape.
When you redraw the button, if the length and width of the redrawn button are smaller than that of the button itself, only a part of the redrawn will occur.
case WM_CREATE:
{
//Button width:230 Button height:230
button = CreateRoundRectButton(hWnd, 500, 200, 230, 230, 30, 30, BTN_ID);
return 0;
}
break;
case WM_DRAWITEM:
{
DRAWITEMSTRUCT dis;
dis.CtlType = ODT_BUTTON;
dis.CtlID = BTN_ID;
dis.hDC = GetDC(button);
dis.rcItem.left = 0;
dis.rcItem.top = 0;
dis.rcItem.right = 200; //Width of redrawing
dis.rcItem.bottom = 200; //Height of redrawing
DrawRoundedButtonRectangle(dis, TRUE);
}
In order to see the effect more clearly, I will widen the width and height.
If I omit the code that writes the outline, It only executes the following code to implement the gradient.
// fill the DC with a vertical gradient
GradientFill(dis.hDC, vertices, _countof(vertices), r, _countof(r), GRADIENT_FILL_RECT_V);
If I change the XY coordinates of the redrawing.
Actually, when you disable RoundRect, the only thing that works is GradientFill.
Updated:
The redrawn area is based on rcItem. When you draw a path, it's only the inside area that is considered and the outline is not, so WidenPath then goes on the outline and that gives the true routed rect area.

seeing through triangles in GLKit

I am working on a simple iOS application to learn about OpenGLES 2.0. In the project, I'm rendering 4 triangles in the shape of a pyramid, with some sliders to adjust the height of the apex of the pyramid, and to rotate the modalViewMatrix about the y axis. I am trying to find the reason why.. after rotating this object counter-clockwise to the point where triangles appear in front of other triangles, I can see through the near triangles. However, when rotating in the clockwise direction to the same point, the near triangles are opaque and occlude the furthest triangles.
I assumed that the reason was a lack of a depth render buffer but after setting the property view.drawableDepthFormat = GLKViewDrawableDepthFormat16; the behavior persists.
For reference, this is my drawRect function where drawing is done. The only other code is done in viewDidLoad and in Global scope of the xcode project here.
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[self.baseEffect prepareToDraw];
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindBuffer(GL_ARRAY_BUFFER,pos);
glEnableVertexAttribArray(GLKVertexAttribPosition);
const GLvoid * off1 = NULL + offsetof(SceneVertex, position) ;
glVertexAttribPointer(GLKVertexAttribPosition, // Identifies the attribute to use
3, // number of coordinates for attribute
GL_FLOAT, // data is floating point
GL_FALSE, // no fixed point scaling
sizeof(SceneVertex), // total num bytes stored per vertex
off1);
glEnableVertexAttribArray(GLKVertexAttribNormal);
const GLvoid * off2 = NULL + offsetof(SceneVertex, normal) ;
glVertexAttribPointer(GLKVertexAttribNormal, // Identifies the attribute to use
3, // number of coordinates for attribute
GL_FLOAT, // data is floating point
GL_FALSE, // no fixed point scaling
sizeof(SceneVertex), // total num bytes stored per vertex
off2);
GLenum error = glGetError();
if(GL_NO_ERROR != error)
{
NSLog(#"GL Error: 0x%x", error);
}
int sizeOfTries = sizeof(triangles);
int sizeOfSceneVertex = sizeof(SceneVertex);
int numArraysToDraw = sizeOfTries / sizeOfSceneVertex;
glDrawArrays(GL_TRIANGLES, 0, numArraysToDraw);
}
It's not enough just to have a depth buffer, you need to tell OpenGL how you want to use it. Try adding the following lines:
glEnable(GL_DEPTH_TEST); // Enable depth testing
glDepthMask(GL_TRUE); // Enable depth write
glDepthFunc(GL_LEQUAL); // Choose the depth comparison function
While we're here, I'd recommend GLKViewDrawableDepthFormat24 over GLKViewDrawableDepthFormat16 for most use cases (better precision).
I'd also recommend familiarizing yourself with xcode's frame capture feature (doc), it really is an invaluable way to figure out what is going on when rendering is not working as intended.

Flicker does not reduce after double buffering in MFC CPaintDC

I am trying to make an interactive graph using MFC where the y axis of a sample point in the graph can be changed using mouse click. I implemented double buffering using this tutorial
enter link description here. I should also point out that I need to change the origins of the viewport from time to time for my program. However, when I click on the graph for the sample point to be updated, I can still see it flicker. It's not an inconvenience, but I need to extend this graph to include lots of sample points and other features such as gridlines, axes, labels, boundary areas, etc and I am worried that the flickering might become a problem for me in the future as the size of this project grows. Implementing double buffering did not seem to make any changes to the output. Moreover, now that I have implemented double buffering, the program seems to stop in the middle of execution (when I am running it in Debug mode in Visual Studio) with this error:
Unhandled exception at 0xffffff3a in graph_on_dlgbox.exe: 0xC0000005: Access violation reading location 0xffffff3a.
I am still not sure what causes it to appear, but seems to happen if I start randomly clicking around the graph area rapidly. Since I have not seen this error (yet) in my code that does not use double buffering, I am assuming it has something to do with the double buffering code, but I am not sure.
Anyway I would like to tackle this one problem at a time, and the first problem is the flicker. Here is my code without double buffering:
void Cgraph_on_dlgboxDlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// Dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the clinet origin
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
}
and here is the modified version with double buffering:
void Cgraph_on_dlgboxDlg::OnPaint()
{
// /*****
CPaintDC dc_blt(this);
CDC dc;
CBitmap bmpDC;
// CRect rcClient;
// GetClientRect(rcClient);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
dc.CreateCompatibleDC(&dc_blt);
// bmpDC.CreateCompatibleBitmap(&dc_blt, rcClient.Width(),rcClient.Height());
bmpDC.CreateCompatibleBitmap(&dc_blt, theGraph.width,theGraph.height );
dc.SelectObject(&bmpDC);
// ----------- After this point, do all operations considering (0,0) to be the origin of the bitmap
// consider bitmap coordinates a device coordinates for Viewport operations
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
// dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
// dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the client area origin
// New origin defined in terms of the Bitmap's origin
dc.SetViewportOrg(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
dc.SetViewportOrg(0, 0);
// dc_blt.BitBlt(rcClient.left+100,rcClient.top+50,rcClient.Width(), rcClient.Height(), &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dc, theGraph.x1, theGraph.y1, SRCCOPY);
dc_blt.BitBlt(theGraph.x1,theGraph.y1, theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// --- Bring the bitmap to this particular location on screen specified by (theGraph.x1,theGraph.y1, theGraph.width, theGraph.height)
// dc_blt.BitBlt(0,0,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(theGraph.x1,theGraph.y1,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// *****/
m_bMyDraw = FALSE;
}
Here is a sample screenshot of the output:
The y axis values of the sample points on the graph can be changed by clicking, and the program redraws the graph after every click by calling InvalidateRect() with the area of the graph as the rectangle to be repainted.
The coordinates of the sample points are stored in a array of CPoint objects, and it's members are updated every time the graph is clicked at the appropriate area. The graph then repaints, due to the call to InvalidateRect(), but with a flicker; unless of course, the program crashes in debug mode with this error:
How do I remove the flickering?
---- UPDATE ----
BOOL Cgraph_on_dlgboxDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
if ( m_bMyDraw )
return TRUE;
else
return CDialogEx::OnEraseBkgnd(pDC);
}
This function has been made this way since it was done like this in the tutorial I mentioned earlier
------ UPDATE 2 ----------
If I just put return TRUE; in the body of the above function, the flicker seems to vanish, but now the output looks like this
The dialog box background seems to have taken the contents of my Visual Studio window. How do I prevent this?
You're close! The idea of double buffering is to paint every pixel in your window exactly once. If it is painted zero times, artifacts like Visual Studio remain. And if it is painted a first time, and then painted again with a different color, you will see flicker. So, to make sure every pixel is painted, create your compatible dc the full width and height of the window so that when it is copied to the CPaintDC, it covers the entire area and not just theGraph. Keep returning TRUE in OnEraseBkgnd, so that the pixels are not first painted in OnEraseBkgnd, and then again in OnPaint.
Two things :
Have you made sure OnEraseBkgnd() just returns TRUE and doesn't call the base class to erase the view?
You don't need to do all that drawing for the double buffering in OnPaint(). All you need to do in the OnPaint() is the BitBlt. You can do the drawing to the memory bitmap in a UpdateRect() function which gets called whenever you need to update the screen, which then calls InvalidateRect() to update the screen. I've posted some code about a flicker-free double buffering method I've used many times here which might help.
The way that flicker prevention work is that first you return TRUE from OnEraseBkgnd to suppress the default erase. But then your OnPaint code must include a full erase of the window. You don't do that so you get the background image of your source code or whatever was there before. So add a FillSolidRect call to your OnPaint to clear the window.
Your creation of a CPaintDC before calling CDialogEx::OnPaint destroys the dialog's ability to properly paint itself, since that function also creates a CPaintDC. But only one call to CPaintDC is permitted for each paint message. To avoid this problem you need a completely different approach. The dialog should have a picture control on it (a CStatic), and you should paint your graph in a class you derive from CStatic.

How to transform one of DirectWrite text objects while keep others no change

I have several DirectWrite text objects on a same render target and I want to transform one of them at one time while keep others no change, In Direct2D, there are several type of transforms
Render target transform
Geometry transform
Brush transform
I can not use render target transform since it affects all the objects on it
I can not use geometry transform, since text is not a geometry, and there is no transform method for IDWriteTextLayout.
So the only choice for me is brush transform, but when I try to use the solid brush to draw a transformed rectangle, it still draw it in the original place(see code below), the sdk demo show an example of bitmap brush transform, so my question is: does transform works for other type of brush? like solid brush?
here is my code, first, calculate a translate matrix based on the time elapsed, then use this matrix to translate the brush and at last draw the rectangle. please take a look and tell me whether this is the case or if I can do it in some way else?
VOID CalculateTranslationMatrix(D2D1_MATRIX_3X2_F* matrix)
{
static float totalTime = 0.0f;
// Get start time
static DWORD startTime = timeGetTime();
// Get current time
DWORD currentTime = timeGetTime();
// Calculate time elapsed
float timeElapsed = (currentTime - startTime) * 0.001f;
// Accumulate total time elapsed
totalTime += timeElapsed;
// Build up the translation matrix
matrix->_11 = 1.0f;
matrix->_12 = 0.0f;
matrix->_21 = 0.0f;
matrix->_22 = 1.0f;
matrix->_31 = totalTime;
matrix->_32 = totalTime;
}
VOID DrawRectangle(HWND hwnd)
{
CreateD2DResource(hwnd) ;
g_pRenderTarget->BeginDraw() ;
// Clear background color to white
g_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White));
D2D1_MATRIX_3X2_F matrix;
CalculateTranslationMatrix(&matrix);
g_pBlackBrush->SetTransform(&matrix);
// Draw Rectangle
g_pRenderTarget->DrawRectangle(
D2D1::RectF(10.f, 10.f, 50.f, 50.f),
g_pBlackBrush
);
HRESULT hr = g_pRenderTarget->EndDraw() ;
if (FAILED(hr))
{
MessageBox(NULL, "Draw failed!", "Error", 0) ;
return ;
}
}
You could use render target transform, and just apply and restore states, eg:
Before draw, call g_pRenderTarget->GetTransform to get copy of the current transform
Set your desired object transform
Draw your object
use g_pRenderTarget->SetTransform using the value you saved before the draw.

Resources