From 213ad8e72cb6e838d15bf54145d661b081dca308 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 12 Feb 2006 01:57:31 +0000 Subject: [PATCH] added linear and concentric gradient fill functions (modified/fixed patch from Ryan Norton) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@37512 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 1 + docs/latex/wx/dc.tex | 29 +++++++ include/wx/dc.h | 26 ++++++ include/wx/msw/dc.h | 5 ++ samples/drawing/drawing.cpp | 45 +++++++++- src/common/dcbase.cpp | 159 ++++++++++++++++++++++++++++++++++++ src/msw/dc.cpp | 123 ++++++++++++++++++++++------ 7 files changed, 359 insertions(+), 29 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index debade9ee9..8fc84bce52 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -47,6 +47,7 @@ All: All (GUI): - Added wxTreebook (uses a wxTreeCtrl to control pages). +- Added wxDC::GradientFillLinear/Concentric() - Added wxKeyEvent::GetModifiers() - Added wxDialog::SetEscapeId(). - wxItemContainerImmutable::FindString unified (affects wxRadioBox, wxListBox, diff --git a/docs/latex/wx/dc.tex b/docs/latex/wx/dc.tex index 84ddcef551..b74a3341e9 100644 --- a/docs/latex/wx/dc.tex +++ b/docs/latex/wx/dc.tex @@ -786,6 +786,35 @@ Gets the current user scale factor (set by \helpref{SetUserScale}{wxdcsetusersca array {\tt ( x, y )}} +\membersection{wxDC::GradientFillConcentric}\label{wxdcgradientfillconcentric} + +\func{void}{GradientFillConcentric}{\param{const wxRect\&}{ rect}, \param{const wxColour\&}{ initialColour}, \param{const wxColour\&}{ destColour}} + +\func{void}{GradientFillConcentric}{\param{const wxRect\&}{ rect}, \param{const wxColour\&}{ initialColour}, \param{const wxColour\&}{ destColour}, \param{const wxPoint\& }{circleCenter}} + +Fill the area specified by rect with a radial gradient, starting from +\arg{initialColour} at the centre of the circle and fading to \arg{destColour} +on the circle outside. + +\arg{circleCenter} are the relative coordinates of centre of the circle in +the specified \arg{rect}. If not specified, the cercle is placed at the +centre of rect. + +\textbf{Note: } Currently this function is very slow, don't use it for +real-time drawing. + + +\membersection{wxDC::GradientFillLinear}\label{wxdcgradientfilllinear} + +\func{void}{GradientFillLinear}{\param{const wxRect\&}{ rect}, \param{const wxColour\&}{ initialColour}, \param{const wxColour\&}{ destColour}, \param{wxDirection}{ nDirection = wxEAST}} + +Fill the area specified by \arg{rect} with a linear gradient, starting from +\arg{initialColour} and eventually fading to \arg{destColour}. The +\arg{nDirection} specifies the direction of the colour change, default is to +use \arg{initialColour} on the left part of the rectangle and +\arg{destColour} on the right one. + + \membersection{wxDC::LogicalToDeviceX}\label{wxdclogicaltodevicex} \func{wxCoord}{LogicalToDeviceX}{\param{wxCoord}{ x}} diff --git a/include/wx/dc.h b/include/wx/dc.h index 4924efc592..e7683753d5 100644 --- a/include/wx/dc.h +++ b/include/wx/dc.h @@ -158,6 +158,27 @@ public: int style = wxFLOOD_SURFACE) { return DoFloodFill(pt.x, pt.y, col, style); } + // fill the area specified by rect with a radial gradient, starting from + // initialColour in the centre of the cercle and fading to destColour. + void GradientFillConcentric(const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour) + { GradientFillConcentric(rect, initialColour, destColour, + wxPoint(rect.GetWidth() / 2, + rect.GetHeight() / 2)); } + + void GradientFillConcentric(const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour, + const wxPoint& circleCenter); + + // fill the area specified by rect with a linear gradient + void GradientFillLinear(const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour, + wxDirection nDirection = wxEAST) + { DoGradientFillLinear(rect, initialColour, destColour, nDirection); } + bool GetPixel(wxCoord x, wxCoord y, wxColour *col) const { return DoGetPixel(x, y, col); } bool GetPixel(const wxPoint& pt, wxColour *col) const @@ -645,6 +666,11 @@ protected: virtual bool DoFloodFill(wxCoord x, wxCoord y, const wxColour& col, int style = wxFLOOD_SURFACE) = 0; + virtual void DoGradientFillLinear(const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour, + wxDirection nDirection = wxEAST); + virtual bool DoGetPixel(wxCoord x, wxCoord y, wxColour *col) const = 0; virtual void DoDrawPoint(wxCoord x, wxCoord y) = 0; diff --git a/include/wx/msw/dc.h b/include/wx/msw/dc.h index 0095fa710d..49927b84b3 100644 --- a/include/wx/msw/dc.h +++ b/include/wx/msw/dc.h @@ -164,6 +164,11 @@ protected: virtual bool DoFloodFill(wxCoord x, wxCoord y, const wxColour& col, int style = wxFLOOD_SURFACE); + virtual void DoGradientFillLinear(const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour, + wxDirection nDirection = wxEAST); + virtual bool DoGetPixel(wxCoord x, wxCoord y, wxColour *col) const; virtual void DoDrawPoint(wxCoord x, wxCoord y); diff --git a/samples/drawing/drawing.cpp b/samples/drawing/drawing.cpp index 9f598ddf9e..d135479395 100644 --- a/samples/drawing/drawing.cpp +++ b/samples/drawing/drawing.cpp @@ -60,7 +60,9 @@ enum ScreenToShow Show_Ops, Show_Regions, Show_Circles, - Show_Splines + Show_Splines, + Show_Gradient, + Show_Max }; // ---------------------------------------------------------------------------- @@ -164,6 +166,7 @@ protected: void DrawCircles(wxDC& dc); void DrawSplines(wxDC& dc); void DrawDefault(wxDC& dc); + void DrawGradients(wxDC& dc); void DrawRegionsHelper(wxDC& dc, wxCoord x, bool firstTime); @@ -200,7 +203,8 @@ enum File_ShowRegions, File_ShowCircles, File_ShowSplines, - MenuShow_Last = File_ShowSplines, + File_ShowGradients, + MenuShow_Last = File_ShowGradients, File_Clip, @@ -971,6 +975,36 @@ void MyCanvas::DrawSplines(wxDC& dc) #endif } +void MyCanvas::DrawGradients(wxDC& dc) +{ + // LHS: linear + wxRect r(10, 10, 100, 100); + dc.GradientFillLinear(r, *wxWHITE, *wxBLUE, wxRIGHT); + + r.Offset(0, 110); + dc.GradientFillLinear(r, *wxWHITE, *wxBLUE, wxLEFT); + + r.Offset(0, 110); + dc.GradientFillLinear(r, *wxWHITE, *wxBLUE, wxDOWN); + + r.Offset(0, 110); + dc.GradientFillLinear(r, *wxWHITE, *wxBLUE, wxUP); + + + // RHS: concentric + r = wxRect(200, 10, 100, 100); + dc.GradientFillConcentric(r, *wxBLUE, *wxWHITE); + + r.Offset(0, 110); + dc.GradientFillConcentric(r, *wxWHITE, *wxBLUE); + + r.Offset(0, 110); + dc.GradientFillConcentric(r, *wxBLUE, *wxWHITE, wxPoint(0, 0)); + + r.Offset(0, 110); + dc.GradientFillConcentric(r, *wxBLUE, *wxWHITE, wxPoint(100, 100)); +} + void MyCanvas::DrawRegions(wxDC& dc) { dc.DrawText(_T("You should see a red rect partly covered by a cyan one ") @@ -1105,6 +1139,10 @@ void MyCanvas::OnPaint(wxPaintEvent &WXUNUSED(event)) case Show_Ops: DrawWithLogicalOps(dc); break; + + case Show_Gradient: + DrawGradients(dc); + break; } } @@ -1161,7 +1199,8 @@ MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) menuFile->Append(File_ShowOps, _T("&ROP screen\tF7")); menuFile->Append(File_ShowRegions, _T("Re&gions screen\tF8")); menuFile->Append(File_ShowCircles, _T("&Circles screen\tF9")); - menuFile->Append(File_ShowSplines, _T("&Splines screen")); + menuFile->Append(File_ShowSplines, _T("&Splines screen\tF11")); + menuFile->Append(File_ShowGradients, _T("&Gradients screen\tF12")); menuFile->AppendSeparator(); menuFile->AppendCheckItem(File_Clip, _T("&Clip\tCtrl-C"), _T("Clip/unclip drawing")); menuFile->AppendSeparator(); diff --git a/src/common/dcbase.cpp b/src/common/dcbase.cpp index 092f356574..3326bbda29 100644 --- a/src/common/dcbase.cpp +++ b/src/common/dcbase.cpp @@ -680,6 +680,165 @@ void wxDCBase::DrawLabel(const wxString& text, CalcBoundingBox(x0 + width0, y0 + height); } + +void wxDCBase::DoGradientFillLinear(const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour, + wxDirection nDirection) +{ + // save old pen + wxPen oldPen = m_pen; + + wxUint8 nR1 = destColour.Red(); + wxUint8 nG1 = destColour.Green(); + wxUint8 nB1 = destColour.Blue(); + wxUint8 nR2 = initialColour.Red(); + wxUint8 nG2 = initialColour.Green(); + wxUint8 nB2 = initialColour.Blue(); + wxUint8 nR, nG, nB; + + if ( nDirection == wxEAST || nDirection == wxWEST ) + { + wxInt32 x = rect.GetWidth(); + wxInt32 w = x; // width of area to shade + wxInt32 xDelta = w/256; // height of one shade bend + if (xDelta < 1) + xDelta = 1; + + while (x >= xDelta) + { + x -= xDelta; + if (nR1 > nR2) + nR = nR1 - (nR1-nR2)*(w-x)/w; + else + nR = nR1 + (nR2-nR1)*(w-x)/w; + + if (nG1 > nG2) + nG = nG1 - (nG1-nG2)*(w-x)/w; + else + nG = nG1 + (nG2-nG1)*(w-x)/w; + + if (nB1 > nB2) + nB = nB1 - (nB1-nB2)*(w-x)/w; + else + nB = nB1 + (nB2-nB1)*(w-x)/w; + + SetPen(wxPen(wxColour(nR, nG, nB), 1, wxSOLID)); + if(nDirection == wxEAST) + DrawRectangle(rect.GetLeft()+x, rect.GetTop(), + xDelta, rect.GetHeight()); + else //nDirection == wxWEST + DrawRectangle(rect.GetRight()-x-xDelta, rect.GetTop(), + xDelta, rect.GetHeight()); + } + } + else // nDirection == wxNORTH || nDirection == wxSOUTH + { + wxInt32 y = rect.GetHeight(); + wxInt32 w = y; // height of area to shade + wxInt32 yDelta = w/255; // height of one shade bend + if (yDelta < 1) + yDelta = 1; + + while (y > 0) + { + y -= yDelta; + if (nR1 > nR2) + nR = nR1 - (nR1-nR2)*(w-y)/w; + else + nR = nR1 + (nR2-nR1)*(w-y)/w; + + if (nG1 > nG2) + nG = nG1 - (nG1-nG2)*(w-y)/w; + else + nG = nG1 + (nG2-nG1)*(w-y)/w; + + if (nB1 > nB2) + nB = nB1 - (nB1-nB2)*(w-y)/w; + else + nB = nB1 + (nB2-nB1)*(w-y)/w; + + SetPen(wxPen(wxColour(nR, nG, nB), 1, wxSOLID)); + if(nDirection == wxNORTH) + DrawRectangle(rect.GetLeft(), rect.GetTop()+y, + rect.GetWidth(), yDelta); + else //nDirection == wxSOUTH + DrawRectangle(rect.GetLeft(), rect.GetBottom()-y-yDelta, + rect.GetWidth(), yDelta); + } + } + + SetPen(oldPen); +} + +void wxDCBase::GradientFillConcentric(const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour, + const wxPoint& circleCenter) +{ + //save the old pen color + wxColour oldPenColour = m_pen.GetColour(); + + wxUint8 nR1 = destColour.Red(); + wxUint8 nG1 = destColour.Green(); + wxUint8 nB1 = destColour.Blue(); + wxUint8 nR2 = initialColour.Red(); + wxUint8 nG2 = initialColour.Green(); + wxUint8 nB2 = initialColour.Blue(); + wxUint8 nR, nG, nB; + + + //offsets of the current pixel + wxInt32 x, y; + + //Color difference + wxInt32 nGradient; + + //Radius + wxInt32 cx = rect.GetWidth() / 2; + wxInt32 cy = rect.GetHeight() / 2; + wxInt32 nRadius; + if (cx < cy) + nRadius = cx; + else + nRadius = cy; + + //Offset of circle + wxInt32 nCircleOffX = circleCenter.x - (rect.GetWidth() / 2); + wxInt32 nCircleOffY = circleCenter.y - (rect.GetHeight() / 2); + + for (x = 0; x < rect.GetWidth(); x++) + { + for (y = 0; y < rect.GetHeight(); y++) + { + //get color difference + nGradient = ( + (nRadius - + (wxInt32)sqrt( + pow(x - cx - nCircleOffX, 2) + + pow(y - cy - nCircleOffY, 2) + ) + ) * 100 + ) / nRadius; + + //normalize Gradient + if (nGradient < 0 ) + nGradient = 0; + + //get dest colors + nR = nR1 + ((nR2 - nR1) * nGradient / 100); + nG = nG1 + ((nG2 - nG1) * nGradient / 100); + nB = nB1 + ((nB2 - nB1) * nGradient / 100); + + //set the pixel + m_pen.SetColour(wxColour(nR,nG,nB)); + DrawPoint(wxPoint(x + rect.GetLeft(), y + rect.GetTop())); + } + } + //return old pen color + m_pen.SetColour(oldPenColour); +} + /* Notes for wxWidgets DrawEllipticArcRot(...) diff --git a/src/msw/dc.cpp b/src/msw/dc.cpp index 20bf623044..62d88bf832 100644 --- a/src/msw/dc.cpp +++ b/src/msw/dc.cpp @@ -196,6 +196,37 @@ private: DECLARE_NO_COPY_CLASS(StretchBltModeChanger) }; +// support for dynamic loading of msimg32.dll which we use for some functions +class wxMSImg32DLL +{ +public: + // return the symbol with the given name if the DLL not loaded or symbol + // not present + static void *GetSymbol(const wxChar *name) + { + wxLogNull noLog; + + if ( !ms_triedToLoad ) + { + ms_triedToLoad = true; + ms_dll.Load(_T("msimg32")); + } + + return ms_dll.IsLoaded() ? ms_dll.GetSymbol(name) : NULL; + } + +private: + static wxDynamicLibrary ms_dll; + static bool ms_triedToLoad; +}; + +wxDynamicLibrary wxMSImg32DLL::ms_dll; +bool wxMSImg32DLL::ms_triedToLoad = false; + +// helper macro for getting the symbols from msimg32.dll: it supposes that a +// type "name_t" is defined and casts the returned symbol to it automatically +#define wxMSIMG32_SYMBOL(name) (name ## _t)wxMSImg32DLL::GetSymbol(_T(#name)) + // =========================================================================== // implementation // =========================================================================== @@ -2483,32 +2514,7 @@ static bool AlphaBlt(HDC hdcDst, HDC,int,int,int,int, BLENDFUNCTION); - // bitmaps can be drawn only from GUI thread so there is no need to - // protect this static variable from multiple threads - static bool s_triedToLoad = false; - static AlphaBlend_t pfnAlphaBlend = NULL; - if ( !s_triedToLoad ) - { - s_triedToLoad = true; - - // don't give errors about the DLL being unavailable, we're - // prepared to handle this - wxLogNull nolog; - - wxDynamicLibrary dll(_T("msimg32.dll")); - if ( dll.IsLoaded() ) - { - pfnAlphaBlend = (AlphaBlend_t)dll.GetSymbol(_T("AlphaBlend")); - if ( pfnAlphaBlend ) - { - // we must keep the DLL loaded if we want to be able to - // call AlphaBlend() so just never unload it at all, not a - // big deal - dll.Detach(); - } - } - } - + static AlphaBlend_t pfnAlphaBlend = wxMSIMG32_SYMBOL(AlphaBlend); if ( pfnAlphaBlend ) { BLENDFUNCTION bf; @@ -2609,3 +2615,68 @@ wxAlphaBlend(HDC hdcDst, int xDst, int yDst, } #endif // #ifdef wxHAVE_RAW_BITMAP + +void wxDC::DoGradientFillLinear (const wxRect& rect, + const wxColour& initialColour, + const wxColour& destColour, + wxDirection nDirection) +{ + // use native function if we have compile-time support it and can load it + // during run-time (linking to it statically would make the program + // unusable on earlier Windows versions) +#if defined(GRADIENT_FILL_RECT_H) && wxUSE_DYNLIB_CLASS + typedef BOOL + (WINAPI *GradientFill_t)(HDC, PTRIVERTEX, ULONG, PVOID, ULONG, ULONG); + static GradientFill_t pfnGradientFill = wxMSIMG32_SYMBOL(GradientFill); + + if ( pfnGradientFill ) + { + GRADIENT_RECT grect; + grect.UpperLeft = 0; + grect.LowerRight = 1; + + // invert colours direction if not filling from left-to-right or + // top-to-bottom + int firstVertex = nDirection == wxNORTH || nDirection == wxWEST ? 1 : 0; + + // one vertex for upper left and one for upper-right + TRIVERTEX vertices[2]; + + vertices[0].x = rect.GetLeft(); + vertices[0].y = rect.GetTop(); + vertices[1].x = rect.GetRight(); + vertices[1].y = rect.GetBottom(); + + vertices[firstVertex].Red = initialColour.Red() << 8; + vertices[firstVertex].Green = initialColour.Green() << 8; + vertices[firstVertex].Blue = initialColour.Blue() << 8; + vertices[firstVertex].Alpha = 0; + vertices[1 - firstVertex].Red = destColour.Red() << 8; + vertices[1 - firstVertex].Green = destColour.Green() << 8; + vertices[1 - firstVertex].Blue = destColour.Blue() << 8; + vertices[1 - firstVertex].Alpha = 0; + + if (nDirection == wxWEST || + nDirection == wxEAST) + if ( (*pfnGradientFill) + ( + GetHdc(), + vertices, + WXSIZEOF(vertices), + &grect, + 1, + nDirection == wxWEST || nDirection == wxEAST + ? GRADIENT_FILL_RECT_H + : GRADIENT_FILL_RECT_V + ) ) + { + // skip call of the base class version below + return; + } + + wxLogLastError(_T("GradientFill")); + } +#endif // wxUSE_DYNLIB_CLASS + + wxDCBase::DoGradientFillLinear(rect, initialColour, destColour, nDirection); +}