2015-09-16 22:15:42 -05:00
// 7 september 2015
# include "area.h"
static ID2D1Factory * d2dfactory = NULL ;
HRESULT initDraw ( void )
{
D2D1_FACTORY_OPTIONS opts ;
ZeroMemory ( & opts , sizeof ( D2D1_FACTORY_OPTIONS ) ) ;
// TODO make this an option
opts . debugLevel = D2D1_DEBUG_LEVEL_NONE ;
return D2D1CreateFactory ( D2D1_FACTORY_TYPE_SINGLE_THREADED ,
& IID_ID2D1Factory ,
& opts ,
2015-09-17 09:31:22 -05:00
( void * * ) ( & d2dfactory ) ) ;
2015-09-16 22:15:42 -05:00
}
void uninitDraw ( void )
{
ID2D1Factory_Release ( d2dfactory ) ;
}
2015-09-16 22:42:24 -05:00
ID2D1HwndRenderTarget * makeHWNDRenderTarget ( HWND hwnd )
{
D2D1_RENDER_TARGET_PROPERTIES props ;
D2D1_HWND_RENDER_TARGET_PROPERTIES hprops ;
HDC dc ;
RECT r ;
ID2D1HwndRenderTarget * rt ;
2015-09-17 09:31:22 -05:00
HRESULT hr ;
2015-09-16 22:42:24 -05:00
// we need a DC for the DPI
// we *could* just use the screen DPI but why when we have a window handle and its DC has a DPI
dc = GetDC ( hwnd ) ;
if ( dc = = NULL )
logLastError ( " error getting DC to find DPI in makeHWNDRenderTarget() " ) ;
ZeroMemory ( & props , sizeof ( D2D1_RENDER_TARGET_PROPERTIES ) ) ;
props . type = D2D1_RENDER_TARGET_TYPE_DEFAULT ;
props . pixelFormat . format = DXGI_FORMAT_UNKNOWN ;
props . pixelFormat . alphaMode = D2D1_ALPHA_MODE_UNKNOWN ;
props . dpiX = GetDeviceCaps ( dc , LOGPIXELSX ) ;
props . dpiY = GetDeviceCaps ( dc , LOGPIXELSY ) ;
props . usage = D2D1_RENDER_TARGET_USAGE_NONE ;
props . minLevel = D2D1_FEATURE_LEVEL_DEFAULT ;
if ( ReleaseDC ( hwnd , dc ) = = 0 )
logLastError ( " error releasing DC for finding DPI in makeHWNDRenderTarget() " ) ;
if ( GetClientRect ( hwnd , & r ) = = 0 )
logLastError ( " error getting current size of window in makeHWNDRenderTarget() " ) ;
ZeroMemory ( & hprops , sizeof ( D2D1_HWND_RENDER_TARGET_PROPERTIES ) ) ;
hprops . hwnd = hwnd ;
hprops . pixelSize . width = r . right - r . left ;
hprops . pixelSize . height = r . bottom - r . top ;
hprops . presentOptions = D2D1_PRESENT_OPTIONS_NONE ;
2015-09-17 09:31:22 -05:00
hr = ID2D1Factory_CreateHwndRenderTarget ( d2dfactory ,
2015-09-16 22:42:24 -05:00
& props ,
& hprops ,
& rt ) ;
if ( hr ! = S_OK )
logHRESULT ( " error creating area HWND render target in makeHWNDRenderTarget() " , hr ) ;
return rt ;
}
2015-09-16 22:15:42 -05:00
struct uiDrawContext {
HDC dc ;
// source color
BOOL useRGB ;
BOOL useAlpha ;
uint8_t r ;
uint8_t g ;
uint8_t b ;
uint8_t a ;
} ;
uiDrawContext * newContext ( HDC dc )
{
uiDrawContext * c ;
// TODO use uiNew
c = ( uiDrawContext * ) malloc ( sizeof ( uiDrawContext ) ) ;
c - > dc = dc ;
return c ;
}
void uiDrawBeginPathRGB ( uiDrawContext * c , uint8_t r , uint8_t g , uint8_t b )
{
c - > useRGB = TRUE ;
c - > useAlpha = FALSE ;
c - > r = r ;
c - > g = g ;
c - > b = b ;
if ( BeginPath ( c - > dc ) = = 0 )
logLastError ( " error beginning new path in uiDrawBeginPathRGB() " ) ;
}
void uiDrawBeginPathRGBA ( uiDrawContext * c , uint8_t r , uint8_t g , uint8_t b , uint8_t a )
{
c - > useRGB = TRUE ;
c - > useAlpha = TRUE ;
c - > r = r ;
c - > g = g ;
c - > b = b ;
c - > a = a ;
if ( BeginPath ( c - > dc ) = = 0 )
logLastError ( " error beginning new path in uiDrawBeginPathRGB() " ) ;
}
void uiDrawMoveTo ( uiDrawContext * c , intmax_t x , intmax_t y )
{
if ( MoveToEx ( c - > dc , x , y , NULL ) = = 0 )
logLastError ( " error moving to point in uiDrawMoveTo() " ) ;
}
void uiDrawLineTo ( uiDrawContext * c , intmax_t x , intmax_t y )
{
if ( LineTo ( c - > dc , x , y ) = = 0 )
logLastError ( " error drawing line in uiDrawLineTo() " ) ;
}
void uiDrawRectangle ( uiDrawContext * c , intmax_t x , intmax_t y , intmax_t width , intmax_t height )
{
if ( Rectangle ( c - > dc , x , y , x + width , y + height ) = = 0 )
logLastError ( " error drawing rectangle in uiDrawRectangle() " ) ;
}
void uiDrawArcTo ( uiDrawContext * c , intmax_t xCenter , intmax_t yCenter , intmax_t radius , double startAngle , double endAngle , int lineFromCurrentPointToStart )
{
if ( ! lineFromCurrentPointToStart ) {
int bx , by , bx2 , by2 ;
int sx , sy , ex , ey ;
// see http://stackoverflow.com/questions/32465446/how-do-i-inhibit-the-initial-line-segment-of-an-anglearc
// the idea for floor(x + 0.5) is inspired by wine
// TODO make sure this is an accurate imitation of AngleArc()
bx = xCenter - radius ;
by = yCenter - radius ;
bx2 = xCenter + radius ;
by2 = yCenter + radius ;
sx = xCenter + floor ( ( double ) radius * cos ( startAngle ) ) ;
sy = yCenter - floor ( ( double ) radius * sin ( startAngle ) ) ;
ex = xCenter + floor ( ( double ) radius * cos ( endAngle ) ) ;
ey = yCenter - floor ( ( double ) radius * sin ( endAngle ) ) ;
if ( Arc ( c - > dc , bx , by , bx2 , by2 , sx , sy , ex , ey ) = = 0 )
logLastError ( " error drawing current point arc in uiDrawArc() " ) ;
return ;
}
// AngleArc() expects degrees
startAngle * = ( 180.0 / M_PI ) ;
endAngle * = ( 180.0 / M_PI ) ;
if ( AngleArc ( c - > dc ,
xCenter , yCenter ,
radius ,
startAngle ,
// the "sweep angle" is relative to the start angle, not to 0
endAngle - startAngle ) = = 0 )
logLastError ( " error drawing arc in uiDrawArc() " ) ;
}
void uiDrawBezierTo ( uiDrawContext * c , intmax_t c1x , intmax_t c1y , intmax_t c2x , intmax_t c2y , intmax_t endX , intmax_t endY )
{
POINT points [ 3 ] ;
points [ 0 ] . x = c1x ;
points [ 0 ] . y = c1y ;
points [ 1 ] . x = c2x ;
points [ 1 ] . y = c2y ;
points [ 2 ] . x = endX ;
points [ 2 ] . y = endY ;
if ( PolyBezierTo ( c - > dc , points , 3 ) = = 0 )
logLastError ( " error drawing bezier curve in uiDrawBezierTo() " ) ;
}
void uiDrawCloseFigure ( uiDrawContext * c )
{
if ( CloseFigure ( c - > dc ) = = 0 )
logLastError ( " error closing figure in uiDrawCloseFigure() " ) ;
}
static HPEN toPen ( uiDrawContext * c , uiDrawStrokeParams * p )
{
DWORD style ;
LOGBRUSH lb ;
HPEN pen ;
style = PS_GEOMETRIC ;
switch ( p - > Cap ) {
case uiDrawLineCapFlat :
style | = PS_ENDCAP_FLAT ;
break ;
case uiDrawLineCapRound :
style | = PS_ENDCAP_ROUND ;
break ;
case uiDrawLineCapSquare :
style | = PS_ENDCAP_SQUARE ;
break ;
}
switch ( p - > Join ) {
case uiDrawLineJoinMiter :
style | = PS_JOIN_MITER ;
break ;
case uiDrawLineJoinRound :
style | = PS_JOIN_ROUND ;
break ;
case uiDrawLineJoinBevel :
style | = PS_JOIN_BEVEL ;
break ;
}
ZeroMemory ( & lb , sizeof ( LOGBRUSH ) ) ;
if ( c - > useRGB ) {
lb . lbStyle = BS_SOLID ;
// we don't need to worry about alpha premultiplication; see below
lb . lbColor = RGB ( c - > r , c - > g , c - > b ) ;
} else {
// TODO
}
pen = ExtCreatePen ( style ,
p - > Thickness ,
& lb ,
0 ,
NULL ) ;
if ( pen = = NULL )
logLastError ( " error creating pen in toPen() " ) ;
return pen ;
}
// unfortunately alpha blending in GDI is limited to bitmap on bitmap
// to do alpha vector graphics, we have to fake it by drawing to an offscreen bitmap first
// also we can't just use regions, we have to copy the path...
// we don't need to worry about alpha premultiplication; see below
struct alpha {
HDC dc ;
HBITMAP bitmap ;
VOID * ppvBits ;
HBITMAP prevbitmap ;
RECT r ;
} ;
static void startAlpha ( uiDrawContext * c , struct alpha * a )
{
int n ;
POINT * points ;
BYTE * ops ;
HRGN region ;
BITMAPINFO bi ;
POINT prevWindowOrigin ;
DWORD le ;
ZeroMemory ( a , sizeof ( struct alpha ) ) ;
// first, get the path
// we need to do this first because PathToRegion() clears the path
SetLastError ( 0 ) ;
n = GetPath ( c - > dc , NULL , NULL , 0 ) ;
if ( n = = 0 ) {
le = GetLastError ( ) ;
SetLastError ( le ) ; // just in case
if ( le ! = 0 )
logLastError ( " error getting path point count in startAlpha() " ) ;
}
// TODO switch to uiAlloc
points = ( POINT * ) malloc ( n * sizeof ( POINT ) ) ;
ops = ( BYTE * ) malloc ( n * sizeof ( BYTE ) ) ;
if ( GetPath ( c - > dc , points , ops , n ) ! = n )
logLastError ( " error getting path in startAlpha() " ) ;
// next we need to know the bounding box of the path so we can create the bitmap
region = PathToRegion ( c - > dc ) ;
if ( region = = NULL )
logLastError ( " error getting path region in startAlpha() " ) ;
if ( GetRgnBox ( region , & ( a - > r ) ) = = 0 )
logLastError ( " error getting region bounding box in startAlpha() " ) ;
if ( DeleteObject ( region ) = = 0 )
logLastError ( " error deleting path region in startAlpha() " ) ;
// now create a DC compatible with the original that we can copy the path to and AlphaBlend() from
a - > dc = CreateCompatibleDC ( c - > dc ) ;
if ( a - > dc = = NULL )
logLastError ( " error creating compatible DC in startAlpha() " ) ;
// now create and select in a *device-independent* bitmap that will hold the data that we're going to alpha blend
ZeroMemory ( & bi , sizeof ( BITMAPINFO ) ) ;
bi . bmiHeader . biSize = sizeof ( BITMAPINFOHEADER ) ;
bi . bmiHeader . biWidth = a - > r . right - a - > r . left ;
bi . bmiHeader . biHeight = - ( a - > r . bottom - a - > r . top ) ; // negative to draw top-down
bi . bmiHeader . biPlanes = 1 ;
bi . bmiHeader . biBitCount = 32 ;
bi . bmiHeader . biCompression = BI_RGB ;
a - > bitmap = CreateDIBSection ( c - > dc , & bi , DIB_RGB_COLORS ,
& ( a - > ppvBits ) , NULL , 0 ) ;
if ( a - > bitmap = = NULL )
logLastError ( " error creating bitmap in startAlpha() " ) ;
a - > prevbitmap = SelectObject ( a - > dc , a - > bitmap ) ;
if ( a - > prevbitmap = = NULL )
logLastError ( " error selecting bitmap into DC in startAlpha() " ) ;
// now we can finally copy the path like we were going to earlier
// we need to change the window origin so the path draws in the right place
if ( SetWindowOrgEx ( a - > dc , a - > r . left , a - > r . top , & prevWindowOrigin ) = = 0 )
logLastError ( " error setting window origin in startAlpha() " ) ;
if ( BeginPath ( a - > dc ) = = 0 )
logLastError ( " error beginning path in startAlpha() " ) ;
if ( PolyDraw ( a - > dc , points , ops , n ) = = 0 )
logLastError ( " error copying path in startAlpha() " ) ;
if ( EndPath ( a - > dc ) = = 0 )
logLastError ( " error ending path in startAlpha() " ) ;
if ( SetWindowOrgEx ( a - > dc , prevWindowOrigin . x , prevWindowOrigin . y , NULL ) = = 0 )
logLastError ( " error resetting window origin in startAlpha() " ) ;
free ( points ) ;
free ( ops ) ;
}
static void finishAlpha ( uiDrawContext * c , struct alpha * a )
{
BLENDFUNCTION bf ;
bf . BlendOp = AC_SRC_OVER ;
bf . BlendFlags = 0 ;
// fortunately blending this is easy; we don't have to touch the pixels at all because BLENDFUNCTION has a way to take care of this...
bf . SourceConstantAlpha = c - > a ;
bf . AlphaFormat = 0 ; // no per-pixel apha
// a consequence of this setup is that the image data doesn't need to be alpha-premultiplied, because SourceConstantAlpha will be multiplied for us (according to the docs, anyway)
// TODO is this actually the case?
// and we can finally alpha-blend this!
if ( AlphaBlend ( c - > dc , a - > r . left , a - > r . top ,
a - > r . right - a - > r . left , a - > r . bottom - a - > r . top ,
a - > dc , 0 , 0 ,
a - > r . right - a - > r . left , a - > r . bottom - a - > r . top ,
bf ) = = FALSE )
logLastError ( " error alpha blending in finishAlpha() " ) ;
// and clean it all up
if ( SelectObject ( a - > dc , a - > prevbitmap ) ! = a - > bitmap )
logLastError ( " error deselecting bitmap in finishAlpha() " ) ;
if ( DeleteObject ( a - > bitmap ) = = 0 )
logLastError ( " error deleting object in finishAlpha() " ) ;
if ( DeleteDC ( a - > dc ) = = 0 )
logLastError ( " error deleting DC in finishAlpha() " ) ;
}
void uiDrawStroke ( uiDrawContext * c , uiDrawStrokeParams * p )
{
HPEN pen , prevpen ;
HDC strokeDC ;
struct alpha alpha ;
if ( EndPath ( c - > dc ) = = 0 )
logLastError ( " error ending path in uiDrawStroke() " ) ;
if ( c - > useAlpha ) {
startAlpha ( c , & alpha ) ;
strokeDC = alpha . dc ;
} else // no alpha; just stroke directly
strokeDC = c - > dc ;
pen = toPen ( c , p ) ;
if ( p - > Join = = uiDrawLineJoinMiter )
if ( SetMiterLimit ( strokeDC , p - > MiterLimit , NULL ) = = 0 )
logLastError ( " error setting miter limit in uiDrawStroke() " ) ;
prevpen = SelectObject ( strokeDC , pen ) ;
if ( prevpen = = NULL )
logLastError ( " error selecting pen into DC in uiDrawStroke() " ) ;
if ( StrokePath ( strokeDC ) = = 0 )
logLastError ( " error stroking path in uiDrawStroke() " ) ;
if ( SelectObject ( strokeDC , prevpen ) ! = pen )
logLastError ( " error deselecting pen from DC in uiDrawStroke() " ) ;
if ( DeleteObject ( pen ) = = 0 )
logLastError ( " error deleting pen in uiDrawStroke() " ) ;
if ( c - > useAlpha )
finishAlpha ( c , & alpha ) ;
}
static HBRUSH toBrush ( uiDrawContext * c )
{
HBRUSH brush ;
if ( c - > useRGB ) {
brush = CreateSolidBrush ( RGB ( c - > r , c - > g , c - > b ) ) ;
if ( brush = = NULL )
logLastError ( " error creating solid brush in toBrush() " ) ;
return brush ;
}
// TODO
return NULL ;
}
void uiDrawFill ( uiDrawContext * c , uiDrawFillMode mode )
{
HBRUSH brush , prevbrush ;
HDC fillDC ;
struct alpha alpha ;
int pfm ;
if ( EndPath ( c - > dc ) = = 0 )
logLastError ( " error ending path in uiDrawFill() " ) ;
if ( c - > useAlpha ) {
startAlpha ( c , & alpha ) ;
fillDC = alpha . dc ;
} else // no alpha; just stroke directly
fillDC = c - > dc ;
switch ( mode ) {
case uiDrawFillModeWinding :
pfm = WINDING ;
break ;
case uiDrawFillModeAlternate :
pfm = ALTERNATE ;
break ;
}
if ( SetPolyFillMode ( fillDC , pfm ) = = 0 )
logLastError ( " error setting fill mode in uiDrawFill() " ) ;
brush = toBrush ( c ) ;
prevbrush = SelectObject ( fillDC , brush ) ;
if ( prevbrush = = NULL )
logLastError ( " error selecting brush into DC in uiDrawFill() " ) ;
if ( FillPath ( fillDC ) = = 0 )
logLastError ( " error filling path in uiDrawFill() " ) ;
if ( SelectObject ( fillDC , prevbrush ) ! = brush )
logLastError ( " error deselecting brush from DC in uiDrawFill() " ) ;
if ( DeleteObject ( brush ) = = 0 )
logLastError ( " error deleting pen in uiDrawStroke() " ) ;
if ( c - > useAlpha )
finishAlpha ( c , & alpha ) ;
}