Click to See Complete Forum and Search --> : GDI+ versus GDI and the winner iiiiiiIIIS


resander
August 17th, 2004, 08:04 PM
GDiiiiiIIIIIIII!!!


Shortly after posting 'GDI+ versus GDI' some 4 months ago,
I made a simple benchmark testing Rectangle, Ellipse,
Polygon, Text and Line. Then forgot all about it,
but a response yesterday to my post by 'Takeru Kooshiroo'
(Genki ka?) jogged my memory. The result might just be of
interest, so here it is:

Result (timing in ms on a 1.8GHz Pentium)

Rect Ellipse Poly Text Line Size

GDI+ 294 1313 949 74 181 >2000KB??

GDI 157 187 210 12 40 236KB

Not exactly what I had hoped, so I am still using GDI
and will not change.

Why is GDI+ so slow? My guess is that it is is too
'object-oriented'. Even the coordinates in the Polygon
method have to be fed in as objects, which I think
is going well over the top.


About the bench mark:

Each graphics object was drawn 1000 times using
GetTickCount to get elapsed time in ms. The same test
harness was used for both GDI+ and GDI. To make the
comparsion as fair as possible, I requested GDI+
to render at highest speed at the expense of quality.

Is this GDI+ slowness problem incurable. I wonder?

Any ideas/opinons/cures/refutations?

Here is (most of) the benchmark program:
(I have very little experience of GDI+, so if anyone
can spot mistakes that would make GDI+ come out in bad
light or even make the whole benchmark invalid, please
let me know.)


typedef int MYPEN ;
typedef int MYBRUSH ;
typedef int MYFONT ;
typedef int MYFONTCOLOR ;

MYPEN setpen ( int wd , int alpha , int r , int g , int b )
{
if ( usegplus )
{
return (MYPEN)new Pen(Color(alpha, r, g, b), (REAL)wd);
}
else
{
return (MYPEN)CreatePen ( PS_SOLID , wd , RGB(r,g,b) ) ;
} ;
}


MYBRUSH setbrush ( int alpha , int r , int g , int b )
{
if ( usegplus )
{
return (MYBRUSH)new SolidBrush(Color(alpha, r, g, b));
}
else
{
return (MYBRUSH)CreateSolidBrush ( RGB(r,g,b) ) ;
} ;
}


MYFONT setfont ( char * fontname , int ptsize ,
int weight )
{
if ( usegplus )
{
int len = strlen ( fontname ) ;
WCHAR wfnt [ 100 ] ;
// convert chars to wide chars
mbstowcs( wfnt , fontname, len );
return (MYFONT)new Font(wfnt , (float)ptsize);
}
else
{
LOGFONT lf ;
getscreenfont ( fontname , ptsize , weight , &lf ) ;
return (MYFONT)CreateFontIndirect ( &lf ) ;
}
}


MYFONTCOLOR setfontcolor ( int alpha , int r , int g , int b )
{
if ( usegplus )
{
return (MYFONTCOLOR)new SolidBrush(Color(alpha, r, g, b));
}
else
{
return (MYFONTCOLOR)CreatePen ( PS_SOLID , 1 , RGB(r,g,b) ) ;
}
}


void rect ( HDC hdc , MYPEN pen , MYBRUSH brush ,
int x , int y , int wd , int ht )
{
if ( usegplus )
{
Graphics g(hdc);
g.FillRectangle((SolidBrush *)brush , x, y, wd, ht);
g.DrawRectangle((Pen *)pen , x, y, wd, ht);
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HBRUSH)brush ) ;
Rectangle ( hdc , x , y , x+wd , y+ht ) ;
SelectObject ( hdc , prev ) ;
SelectObject ( hdc , prevb ) ;
} ;
}


void ellipse ( HDC hdc , MYPEN pen , MYBRUSH brush ,
int x , int y , int wd , int ht )
{
if ( usegplus )
{
Graphics g(hdc);
g.SetSmoothingMode(SmoothingModeHighSpeed);
g.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
g.SetInterpolationMode( InterpolationModeLowQuality ) ;
g.FillEllipse((SolidBrush *)brush , x, y, wd, ht);
g.DrawEllipse((Pen *)pen , x, y, wd, ht);
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HBRUSH)brush ) ;
Ellipse ( hdc , x , y , x+wd , y+ht ) ;
SelectObject ( hdc , prev ) ;
SelectObject ( hdc , prevb ) ;
} ;
}

void polygon ( HDC hdc , MYPEN pen , MYBRUSH brush ,
POINT pts[] , int n )
{
if ( usegplus )
{
Graphics g(hdc);
g.SetSmoothingMode(SmoothingModeHighSpeed);
g.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
g.SetInterpolationMode( InterpolationModeLowQuality ) ;
int k = 0 ;
Point * tmp ;
Point points [ 100 ] ;
while ( k < n )
{
tmp = new Point( pts [ k ].x , pts [ k ].y ) ;
points [ k ] = *tmp ;
k++ ;
} ;
g.FillPolygon((SolidBrush *)brush , points , n );
g.DrawPolygon((Pen *)pen , points , n );
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HBRUSH)brush ) ;
Polygon ( hdc , pts , n ) ;
SelectObject ( hdc , prev ) ;
SelectObject ( hdc , prevb ) ;
} ;
}


void text ( HDC hdc , MYFONT fnt , MYBRUSH brush ,
int x , int y , WCHAR * wtxt , char * txt ,
int lentxt )
{
if ( usegplus )
{
Graphics g(hdc);
g.SetTextRenderingHint( TextRenderingHintSingleBitPerPixel ) ;
g.SetSmoothingMode(SmoothingModeHighSpeed);
g.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
g.SetInterpolationMode( InterpolationModeLowQuality ) ;
PointF xy ((float)x, (float)y );
g.DrawString( wtxt , -1 , (Font *)fnt, xy,
(SolidBrush *)brush ) ;
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HFONT)fnt ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HPEN)brush ) ;
TextOut ( hdc , x , y , txt , lentxt ) ;
SelectObject ( hdc , prev ) ;
SelectObject ( hdc , prevb ) ;
} ;
}


void line ( HDC hdc , MYPEN pen , int x1 , int y1 ,
int x2 , int y2 )
{
if ( usegplus )
{
Graphics g(hdc);
g.SetSmoothingMode(SmoothingModeHighSpeed);
g.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
g.SetInterpolationMode( InterpolationModeLowQuality ) ;
g.DrawLine((Pen *)pen , x1 , y1 , x2 , y2 );
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
POINT pt ;
MoveToEx ( hdc , x1 , y1 , &pt ) ;
LineTo ( hdc , x2 , y2 );
SelectObject ( hdc , prev ) ;
} ;
}


Here is the test harness routine:

void benchmark ( HDC hdc , int limit , int diffs[] )
{
POINT pts [ 5 ] = { {300, 300} , { 200, 130 } ,
{150, 200} , {50, 200} ,
{0, 130} } ;
DWORD start = GetTickCount ( ) ;
MYPEN pen = setpen ( 2 , 255, 0,0,0 ) ;
MYBRUSH brush = setbrush ( 255, 255,0,0 ) ;
MYBRUSH brush2 = setbrush ( 255, 255,0,0 ) ;
MYBRUSH brush3 = setbrush ( 255, 0,255,0 ) ;
MYFONT fnt = setfont ( "Arial" , 10 , 400 ) ;
MYFONTCOLOR fcol = setfontcolor ( 255 , 0,0,0 ) ;
int k = 0 ;
while ( k < limit )
{
rect ( hdc , pen , brush , 10,10 ,
200 , 100 ) ;
k++ ;
} ;
DWORD end = GetTickCount ( ) ;
diffs [ 0 ] = end - start ;
start = GetTickCount ( ) ;
k = 0 ;
while ( k < limit )
{
ellipse ( hdc , pen , brush2 , 310,10 ,
200 , 100 ) ;
k++ ;
} ;
end = GetTickCount ( ) ;
diffs [ 1 ] = end - start ;
start = GetTickCount ( ) ;
k = 0 ;
while ( k < limit )
{
polygon ( hdc , pen , brush3 , pts , 5 ) ;
k++ ;
} ;
end = GetTickCount ( ) ;
diffs [ 2 ] = end - start ;
start = GetTickCount ( ) ;
k = 0 ;
while ( k < limit )
{
text ( hdc , fnt , fcol , 500 , 300 ,
L"Hello" , "Hello" ,5 ) ;
k++ ;
} ;
end = GetTickCount ( ) ;
diffs [ 3 ] = end - start ;
start = GetTickCount ( ) ;
k = 0 ;
while ( k < limit )
{
line ( hdc , pen , 10 , 10 , 200 , 100 ) ;
k++ ;
} ;
end = GetTickCount ( ) ;
diffs [ 4 ] = end - start ;
}


Content of the top-level routine:

// hdc has already been set up

int limit = 1000 ;
int countp [ 5 ] ;
int countc [ 5 ] ;

usegplus = true ;
benchmark ( hdc , limit , countp ) ; // gdi+

usegplus = false ;
benchmark ( hdc , limit , countc ) ; // gdi

sprintf ( msg ,
"Rect %d,%d Ellip %d,%d Poly %d,%d Text %d,%d Line %d,%d" ,
countp [ 0 ] , countc [ 0 ] ,
countp [ 1 ] , countc [ 1 ] ,
countp [ 2 ] , countc [ 2 ] ,
countp [ 3 ] , countc [ 3 ] ,
countp [ 4 ] , countc [ 4 ] );
displaymessage ( msg ) ;

Marc G
August 18th, 2004, 04:42 AM
In my opinion this benchmark is flawed!

For example, in your Ellipse function (and in all other drawing functions) you do something as follows for the GDI+ part:

Graphics g(hdc);
g.SetSmoothingMode(SmoothingModeHighSpeed);
g.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
g.SetInterpolationMode( InterpolationModeLowQuality ) ;
g.SetTextRenderingHint( TextRenderingHintSingleBitPerPixel ) ;

So for the GDI+ part you create (for each iteration in your loop !!) a new Graphics object and set the state of the graphics object! This is most probable your problem. For the GDI part, you don't recreate your DC each iteration, so why for the GDI+?
Try to create your Graphics object once outside your loop and pass a pointer or reference to your Graphics object to all your drawing functions. Also, move all the g.SetXXX outside of all your functions and put it before the loop.

Something similar is in your polygon function. You create your Points array on each iteration for the GDI+ case and you don't do this for the GDI case! Remove this. Create the Point array once outside your loop and pass in a reference to it in your polygon function.

Please, make these changes and try to benchmark it again.

Takeru Koushirou
August 18th, 2004, 04:48 AM
[g] Yeah so desu (but I prefer another transliteration, so I use ou -.^)

I think that GDI+ is an OO oriented interface to GDI is not the only reason, meaning the C++ overhead is not respondible on it's own.
Some other reason is the internal structure Microsoft created. GDI+ is a quite powerful tool with a high abstraction level, so it makes it easy for any developer to load, for example, an JPEG image without the need of searching for another library or implementing the JPEG decompression algorithm yourself (Well, there's another problem with this example, I already have read about people who wish to add new image types, which is simply said not possible/not supported).
The bad thing is, some elements of GDI+ are based on their own abstractions. Furthermore, some methods are "too high level abstracted". This isn't bad at all, but slows down the whole thing alot and also consumes alot more memory.
All in all it's a common problem. An OO library, which abstracts at a most possoble high level, will get slower. More methods are called internally for each abstraction and so on.
GDI+ also needs to call the original GDI functions. So the user calls some GDI+methods, which call GDI functions. These additional calls with high quantity will also slow down the whole thing.

So the benchmarkt results are'nt really wondering. So one question left: Should I use GDI+ at all? A (not so) general answer: Depending on your project. If it depends on alot of GDI calls which can also be done with native GDI, GDI is your choice. If you need some high level functions and really want to save alot of time in developing those, GDI+ is a good choice. So there is no general.

I hope I didn't write too much confusing here ^^ But thats my idea on this ^.^

MrViggy
August 18th, 2004, 10:50 AM
I would like to see the benckmark results, after Marc G's suggestions are taken into account. It wouldn't surprise me if the GDI+ results are a lot closer to the GDI results after all the un-necessary code is removed from the GDI+ test loops.

Viggy

resander
August 21st, 2004, 07:15 AM
Nice to get responses!

I did not explain the purpose of the benchmark properly.

The pupose is to measure the total effort required to
draw the objects including all steps.

For GDI+ plus these are:

- instantiate an graphics object
- use the draw methods

and for GDI:

- associate resources with graphics item via SelectObject
- use the draw functon, e.g. TextOut


If I had had a high resolution clock (microseconds, say),
then I would not have used a loop at all, but I was using
GetTickCount that ticks in ms only. Measuring once would
have given the value 0 for each measurement, so I had to
repeat drawing the object several times (1000 seemed a
good value) times in order to get reasonable timing values.
Lifting the graphics instantiaion and quality control
settings out of the loop would not have allowed GDI+
and GDI to be compared side by side, which is the
purpose of the benchmark.

I have done a benchmark part2, which compares drawing
speed only. The graphics instantiation, quality
settings and polygon points instantiation are done outside
the loop for GDI+ and so is SelectObject for GDI.

Here are the results:


Benchmark 1: (Total effort to get objects on to screen)

Rect Ellipse Poly Text Line Size

GDI+ 294 1313 949 74 181 >2000KB??

GDI 157 187 210 12 40 236KB

Benchmark 2: (measuring drawing speed only)

GDI+ 230 1212 826 52 154
GDI 163 193 218 13 41


Sadly, the results of benchmark2 are are essentially
the same as for benchmark 1.

GDI+ or GDI? I know what my answer is, but I am going
to hold fire until I know what you guys think of the
validity of the benchmarks.

Take-chan, sassa to isogu beki!


Here is the source of benchmark2

void benchmark2 ( HDC hdc , int limit , int diffs[] )
{
POINT pts [ 5 ] = { {300, 300} , { 200, 130 } , {150, 200} ,
{50, 200} , {0, 130} } ;
MYPEN pen = setpen ( 2 , 255, 0,0,0 ) ;
MYBRUSH brush = setbrush ( 255, 255,0,0 ) ;
MYBRUSH brush2 = setbrush ( 255, 255,0,0 ) ;
MYBRUSH brush3 = setbrush ( 255, 0,255,0 ) ;
MYFONT fnt = setfont ( "Arial" , 10 , 400 ) ;
MYFONTCOLOR fcol = setfontcolor ( 255 , 0,0,0 ) ;
int k ;

DWORD start = GetTickCount ( ) ;
if ( usegplus )
{
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
graphics.SetInterpolationMode( InterpolationModeLowQuality ) ;
k = 0 ;
while ( k < limit )
{
graphics.FillRectangle((SolidBrush *)brush ,
10,10 , 200 , 100);
graphics.DrawRectangle((Pen *)pen , 10,10 , 200 , 100);
k++ ;
}
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HBRUSH)brush ) ;
k = 0 ;
while ( k < limit )
{
Rectangle ( hdc , 10 , 10 , 210 , 110 ) ;
k++ ;
}
}
int end = GetTickCount ( ) ;
diffs [ 0 ] = end - start ;

start = GetTickCount ( ) ;
if ( usegplus )
{
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
graphics.SetInterpolationMode( InterpolationModeLowQuality ) ;
k = 0 ;
while ( k < limit )
{
graphics.FillEllipse((SolidBrush *)brush ,
310,10 , 200 , 100);
graphics.DrawEllipse((Pen *)pen , 310,10 , 200 , 100);
k++ ;
} ;
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HBRUSH)brush ) ;
k = 0 ;
while ( k < limit )
{
Ellipse ( hdc , 310 , 10 , 510 , 110 ) ;
k++ ;
}
}
end = GetTickCount ( ) ;
diffs [ 1 ] = end - start ;

start = GetTickCount ( ) ;
if ( usegplus )
{
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
graphics.SetInterpolationMode( InterpolationModeLowQuality ) ;
k = 0 ;
Point * tmp ;
Point points [ 100 ] ;
while ( k < 5 )
{
tmp = new Point( pts [ k ].x , pts [ k ].y ) ;
points [ k ] = *tmp ;
k++ ;
} ;
k = 0 ;
while ( k < limit )
{
graphics.FillPolygon((SolidBrush *)brush , points , 5 );
graphics.DrawPolygon((Pen *)pen , points , 5 );
k++ ;
}
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HBRUSH)brush ) ;
k = 0 ;
while ( k < limit )
{
polygon ( hdc , pen , brush3 , pts , 5 ) ;
k++ ;
} ;
}
end = GetTickCount ( ) ;
diffs [ 2 ] = end - start ;

start = GetTickCount ( ) ;
if ( usegplus )
{
Graphics graphics(hdc);
graphics.SetTextRenderingHint( TextRenderingHintSingleBitPerPixel ) ;
graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
graphics.SetInterpolationMode( InterpolationModeLowQuality ) ;
PointF xy ((float)500, (float)300 );
k = 0 ;
while ( k < limit )
{
graphics.DrawString( L"Hello" , -1 , (Font *)fnt, xy,
(SolidBrush *)brush ) ;
k++ ;
}
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HFONT)fnt ) ;
HGDIOBJ prevb = SelectObject ( hdc , (HPEN)brush ) ;
k = 0 ;
while ( k < limit )
{
TextOut ( hdc , 500 , 300 , "Hello" , 5 ) ;
k++ ;
}
} ;
end = GetTickCount ( ) ;
diffs [ 3 ] = end - start ;

start = GetTickCount ( ) ;
if ( usegplus )
{
Graphics graphics(hdc);
graphics.SetSmoothingMode(SmoothingModeHighSpeed);
graphics.SetCompositingQuality ( CompositingQualityHighSpeed ) ;
graphics.SetInterpolationMode( InterpolationModeLowQuality ) ;
k = 0 ;
while ( k < limit )
{
graphics.DrawLine((Pen *)pen , 10 , 10 , 200 , 100 );
k++ ;
} ;
#endif
}
else
{
HGDIOBJ prev = SelectObject ( hdc , (HPEN)pen ) ;
POINT pt ;
k = 0 ;
while ( k < limit )
{
MoveToEx ( hdc , 10 , 10 , &pt ) ;
LineTo ( hdc , 200 , 100 );
k++ ;
} ;
}

end = GetTickCount ( ) ;
diffs [ 4 ] = end - start ;
}

Marc G
August 21st, 2004, 07:27 AM
Ok, now I think the benchmark are more valid and indeed it appears that GDI+ is a bit slower than GDI, BUT, i did a few tests on my own and the quality of the drawings made by GDI+ is a lot better than the output of GDI. Even if I tweak the SetCompositingQuality etc settings of GDI+ to optimize for speed. You see this difference in quality quite good when drawing and ellipse. Just try it and see the difference in quality.

Now, GDI or GDI+?
It all depends on what you need. GDI+ is a powerfull higher level drawing API. If you don't need anything fancy like anti-aliased drawings, easily loading/saving to different image format, alpha blending, ... use GDI, otherwise use GDI+. And don't forget the drawing quality even without anti-aliasing is better in GDI+ than in GDI ;)

Amn
August 22nd, 2004, 08:43 AM
Yo! Good to be back here...took a long vacation from C++.

I have had a long experience with GDI+, we even shipped a software which was entirely based on it.

Microsoft states that GDI+, unlike GDI, does not have its own DDI (Device Driver Interface). Translating this into human language would mean that GDI+ actually performs its logic USING GDI ! In modern Windows two APIs exist which allow you to perform graphic tasks: GDI and DirectX. If you look onto Microsoft's diagrams of their structure, they explain that both talk DIRECTLY to the underlying graphics driver which operates in Kernel mode. I.e. an ATI graphics card comes supplied with the drivers written by ATI folks, but Microsoft has a GDI DLL which ships on ALL systems, which abstracts drivers functions to the GDI layer. This DLL layer hides implementation specifics, exposing common CreateFont, CreatePen, DrawRect etc. functions to the user mode applications. If a certain kernel mode driver cannot implement canvas rotation f.e. (SetMapping function I think manages that), the GDI DLL will implement it via x86 instructions (pure software emulation).

This is only part of the story. Since GDI+ introduces a lot of advanced graphics management features such as gamma ramps, native antialiasing (GDI also had it on NT) and extensive image file format support, there is no consistent way of sending these commands to the graphic driver, because no GDI+ DDI interface is defined. It was said before that Longhorn will introduce GDI+ DDI, which will mean that GDI+ will talk directly to the kernel mode driver via its own GDI+ DLL on Longhorn, but I no longer know for sure if that is the case.

So for now basically, GDI+ is a user mode DLL which either directs its commands to GDI (as is the case with simple non antialised primitives) or has its own functions of drawing antialised ellipses, lines and rectangles, not to mention all those JPEG, GIF, TIFF and PNG image code/decode support. Those are most likely pure x86 assembler non-privileged code (read: your average application level code) with possible optimisation via SSE, MMX and 3DNow! instructions.

So, do not discard GDI+ yet, it may have its own DDI soon, and as most of the graphic drivers already perform antialiasing on primitives natively and some even hardcode many image format manipulations, the speed of GDI+ should go tenfold.

GDI+ is NOT OBJECT ORIENTED by interface. It is indeed developed with object oriented design in mind, because all of its functions require several state-machine structures (Pen, Font, Graphics etc), but practically GDI+ is just DLL functions which can even be called from C language. The speed overhead of C++ wrappers is MINIMAL. Of course, calling constructors and destructors in a loop of drawing is stupid, but it is not only GDI+'s fault you do it ;) But I admit that its design is somewhat confusing because ALL of its objects are allocated on the heap. So, the rule number one dealing with GDI+ is: learn how the state machine works (how often do you need to allocate all those Pens, Graphics, Brushes and such) and call as few GDI+ functions as you need in a given time.

An alternative to GDI+ would be to create your own API which will render high quality graphics on a bitmap, and then blit it onto GDI or DirectX surface. I guarantee, that without DDI for GDI+, your API will be AS FAST if not faster (optimise it).

Marc G
August 22nd, 2004, 01:39 PM
Nice reply :thumb:
One question though:
So for now basically, GDI+ is a user mode DLL which either directs its commands to GDI (as is the case with simple non antialised primitives)
I'm not sure if GDI+ redirects primitive calls to GDI, because when i draw a simple (non-anti-aliased) ellipse with GDI+ and one with GDI, the GDI+ is of better quality.

Amn
August 28th, 2004, 09:09 AM
Nice reply :thumb:
One question though:

I'm not sure if GDI+ redirects primitive calls to GDI, because when i draw a simple (non-anti-aliased) ellipse with GDI+ and one with GDI, the GDI+ is of better quality.

I think you may be right there actually. It may well draw the lines itself, via Brezenhem or Wu. But it has to use GDI for at least blitting or screen bitmap mapping purposes, without DDI, it is the only way (aside DirectX surfaces) to draw anything on the screen. Remember the dark ages of Windows games, when developers couldn't draw fast enough because GDI32 was too slow, and DirectX 1 was in development ? ;)

Takeru Koushirou
August 28th, 2004, 09:41 AM
Yepp I also think so. And well it's the only best method to get good results: Using the Win32 native GDI I encountered some problems between different versions of Windows (98/Me, NT4, NT5+), like e.g. StretchBlt() and of course the underlaying graphics driver. So to get an unique interface, and in fact GDI+ tries to generate one, it needs to implement a lot of functions itself.
Furthermore, GDI+ has some additional options like anti-alias etc. which is not supported by the native GDI, so their algorithms need to be aware of these additional options. And if you already wrote an implementation for e.g. line drawing with anti-alias, is's surely a small prob to implement one without... :D

ovidiucucu
August 28th, 2004, 09:48 AM
Nice reply :thumb:
One question though:

I'm not sure if GDI+ redirects primitive calls to GDI, because when i draw a simple (non-anti-aliased) ellipse with GDI+ and one with GDI, the GDI+ is of better quality.
I think you may be right there actually. It may well draw the lines itself, via Brezenhem or Wu. But it has to use GDI for at least blitting or screen bitmap mapping purposes, without DDI, it is the only way (aside DirectX surfaces) to draw anything on the screen. Remember the dark ages of Windows games, when developers couldn't draw fast enough because GDI32 was too slow, and DirectX 1 was in development ? ;)
For sure GDI+ is not build over GDI32. A more simple example is drawing dotted lines.
Attached picture shows the differnce.

Mick
August 28th, 2004, 10:06 AM
For sure GDI+ is not build over GDI32. A more simple example is drawing dotted lines.
Attached picture shows the differnce.

Maybe not 'built over' but do not make the assumption it does not make some calls into GDI32.

ovidiucucu
August 28th, 2004, 10:30 AM
Maybe not 'built over' but do not make the assumption it does not make some calls into GDI32.
Right, the Depenency Walker can prove it. :cool:
I did not make the assumption it does not make some calls into GDI32.

Mick
August 28th, 2004, 10:33 AM
Right, the Depenency Walker can prove it. :cool:
So I do not make the assumption it does not make some calls into GDI32. :)

Umm yes it does, if you would like to bet on that, I take payment in beer :). And what dependency walker are you using, it clearly imports from gdi32. Would you like to see an actual trace of GDIPlus calling into gdi32...would that help?

Marc G
August 28th, 2004, 10:43 AM
For sure GDI+ is not build over GDI32. A more simple example is drawing dotted lines.
Attached picture shows the differnce.
Ah, finally decent dotted lines :)

ovidiucucu
August 28th, 2004, 10:47 AM
Umm yes it does, if you would like to bet on that, I take payment in beer. And what dependency walker are you using, it clearly imports from gdi32. Would you like to see an actual trace of GDIPlus calling into gdi32...would that help?
Bet on what? Sorry, but I did not say "it does not import".
Anyhow this imported functions list reported by my poor obsolete dependency walker is OK or I must search for another one? :D


AbortPath
AngleArc
Arc
ArcTo
BeginPath
BitBlt
Chord
CloseEnhMetaFile
CloseFigure
CombineRgn
CombineTransform
CopyEnhMetaFileA
CopyMetaFileA
CreateBitmap
CreateBrushIndirect
CreateCompatibleBitmap
CreateCompatibleDC
CreateDCA
CreateDIBPatternBrushPt
CreateDIBSection
CreateDIBitmap
CreateEnhMetaFileA
CreateEnhMetaFileW
CreateFontIndirectA
CreateFontIndirectW
CreateICA
CreatePalette
CreatePatternBrush
CreatePen
CreatePenIndirect
CreateRectRgn
CreateSolidBrush
DPtoLP
DeleteDC
DeleteEnhMetaFile
DeleteMetaFile
DeleteObject
Ellipse
EndPath
EnumEnhMetaFile
EnumFontFamiliesExA
EnumFontFamiliesExW
EnumMetaFile
Escape
ExcludeClipRect
ExtCreatePen
ExtCreateRegion
ExtEscape
ExtSelectClipRgn
ExtTextOutA
ExtTextOutW
FillPath
FillRgn
FlattenPath
GdiComment
GdiFlush
GetBkColor
GetBkMode
GetClipRgn
GetCurrentObject
GetCurrentPositionEx
GetDCOrgEx
GetDIBColorTable
GetDIBits
GetDeviceCaps
GetEnhMetaFileA
GetEnhMetaFileBits
GetEnhMetaFileHeader
GetEnhMetaFileW
GetGraphicsMode
GetMapMode
GetMetaFileA
GetMetaFileBitsEx
GetMetaFileW
GetNearestColor
GetNearestPaletteIndex
GetObjectA
GetObjectType
GetObjectW
GetPaletteEntries
GetPath
GetPixel
GetROP2
GetRandomRgn
GetRegionData
GetRgnBox
GetStockObject
GetSystemPaletteEntries
GetSystemPaletteUse
GetTextAlign
GetTextCharsetInfo
GetTextColor
GetTextFaceA
GetTextFaceW
GetTextMetricsA
GetTextMetricsW
GetViewportExtEx
GetViewportOrgEx
GetWinMetaFileBits
GetWindowExtEx
GetWindowOrgEx
GetWorldTransform
IntersectClipRect
LPtoDP
LineTo
ModifyWorldTransform
MoveToEx
OffsetClipRgn
OffsetViewportOrgEx
PatBlt
Pie
PlayEnhMetaFile
PlayEnhMetaFileRecord
PlayMetaFile
PlayMetaFileRecord
PlgBlt
PolyBezier
PolyBezierTo
PolyDraw
PolyPolygon
PolyPolyline
Polygon
Polyline
PolylineTo
RealizePalette
Rectangle
ResizePalette
RestoreDC
RoundRect
SaveDC
ScaleViewportExtEx
ScaleWindowExtEx
SelectClipPath
SelectClipRgn
SelectObject
SelectPalette
SetArcDirection
SetBitmapBits
SetBkColor
SetBkMode
SetBrushOrgEx
SetDIBColorTable
SetDIBits
SetEnhMetaFileBits
SetGraphicsMode
SetICMMode
SetMapMode
SetMapperFlags
SetMetaFileBitsEx
SetMetaRgn
SetMiterLimit
SetPaletteEntries
SetPolyFillMode
SetROP2
SetStretchBltMode
SetTextAlign
SetTextColor
SetTextJustification
SetViewportExtEx
SetViewportOrgEx
SetWindowExtEx
SetWindowOrgEx
SetWorldTransform
StretchBlt
StretchDIBits
StrokeAndFillPath
StrokePath
TranslateCharsetInfo
WidenPath

Mick
August 28th, 2004, 10:58 AM
No I think we are on the same page now ;) It was the original wording that seemed to me that you were saying the opposite :wave:

Marc G
August 28th, 2004, 11:28 AM
Anyhow this imported functions list reported by my poor obsolete dependency walker is OK or I must search for another one? :D
Strange that ellipse, arc, etc are all in that list because like i told i'm pretty sure GDI+ doesn't use GDI to draw these primitives. Ofcourse GDI+ uses GDI to do blitting etc, but not primitives, because the quality of the drawings in GDI+ is much better than GDI (I only tested this for ellipse). I did also put GDI+ in fastest mode for this test, meaning no antialiasing or other stuff.

Hokutata
August 29th, 2004, 08:00 AM
Mybe gdiplus calls gdi32 functions in dummy not exported/not dokumented functions only to make us crazy.
:D :D

Takeru Koushirou
August 29th, 2004, 09:30 AM
...or maybe they use it for "emergency backup mode", when whatever reason prohibits the own implemented algorithms to work they use the original GDI methods, which are capable of enumeration several tasks a graphics device driver is not capable of...

Hokutata
September 3rd, 2004, 09:42 AM
...or maybe they use it for "emergency backup mode", when whatever reason prohibits the own implemented algorithms to work they use the original GDI methods, which are capable of enumeration several tasks a graphics device driver is not capable of...
Very possible, who can know?... ;)

resander
September 4th, 2004, 02:51 AM
GDI+ can render at higher quality than GDI, but the
rendering quality of GDI is good enough for my applications,
so I am not taking sluggish giant GDI+ on board
(it's up to 5 times slower than GDI) .

The size of GDI+ is 2.5MB. It's part of Windows XP,
but not of Windows 98/Me/2K, so GDI+ would have to be
included in apps software distributions for these.
making them bigger and more expensive to prepare
and maintain. Are there any additional additional
problems with this (posting question in a separate
thread)

Some may find the class-based GDI+ API powerful and
easy to use, but the capabilities offered by GDI+OLE
APIs (with a bit of extra programming) are not far
behind. For example:


FEATURE in GDI+ API in GDI API

Shapes, yes yes
Paths,
Regions

Gradients yes no (easy to do) see 1

Opacity yes no (probably possible) see 2
(Alphablend)

Basic Imaging yes no (easy to output
JPEG, GIFs,
BMPs, WMFs via
OleLoadPicture and
Render method) see 31. Gradients

There are several examples on the CodeGuru site, for
example Philip Nicoletti's response to Leehal2000uk
'gradient fills' 29 Oct 2001. There are others too.


2. Opacity and alpha blending.

An AlphaBlend function has been available in GDI
since Windows 98. MSDN says 'The AlphaBlend function
displays bitmaps that have transparent or semitransparent
pixels'
but I have not been able to figure out how to use it.
I am sure there must be a way, so I am seeking your
assistance via a new thread.


3. JPEG and GIF output

Here are myLoadImage and drawImage routines that I
have been using for years (OleLoadPic was part of W95!)
for loading and drawing JPEG and GIFs.

#include <olectl.h>

HANDLE myLoadImage ( HDC hdc , char * imgfilename ,
int * widthret , int * heightret )
{
FILE * fil = fopen ( imgfilename , "rb+" ) ;
if ( fil == NULL )
{
return NULL ;
} ;
fseek ( fil , 0 , SEEK_END ) ;
int filesize = ftell ( fil ) ;
fseek ( fil , 0 , SEEK_SET ) ;
HGLOBAL hglobal = GlobalAlloc ( GMEM_MOVEABLE , filesize+4 ) ;
if ( hglobal == NULL )
{
return NULL ;
} ;
char * adr = (char *)GlobalLock ( hglobal ) ;
int nbytes = fread ( adr , 1 , filesize , fil ) ;
fclose ( fil ) ;
if ( nbytes != filesize )
{
return NULL ;
} ;

LPSTREAM pstm = NULL ;
GlobalUnlock ( hglobal ) ;
// create IStream* from global memory
HRESULT hr = CreateStreamOnHGlobal ( hglobal, TRUE, &pstm ) ;
if ( !SUCCEEDED( hr ) || (pstm == NULL) )
{
GlobalFree ( hglobal ) ;
return NULL ;
} ;

// Create IPicture COM object from image data

LPPICTURE pic ;
hr = ::OleLoadPicture( pstm , filesize , FALSE, IID_IPicture,
(LPVOID *)&pic ) ;
GlobalFree ( hglobal ) ;
pstm->Release ( ) ;
if ( !SUCCEEDED( hr ) || (pstm == NULL) )
{
return NULL ;
} ;
long hmWidth;
long hmHeight;
pic->get_Width ( &hmWidth ) ;
pic->get_Height ( &hmHeight ) ;
// convert himetric to pixels
*widthret = MulDiv ( hmWidth, GetDeviceCaps(hdc, LOGPIXELSX),
HIMETRIC_INCH);
*heightret = MulDiv ( hmHeight, GetDeviceCaps(hdc, LOGPIXELSY),
HIMETRIC_INCH);
return (HANDLE)pic ;
}


void drawImage ( HDC hdc , HANDLE himg , int left , int top )
{
if ( himg == NULL )
{
return ;
} ;
LPPICTURE pic = (LPPICTURE)himg ;
long hmWidth;
long hmHeight;
pic->get_Width ( &hmWidth ) ;
pic->get_Height ( &hmHeight ) ;
// convert himetric to pixels
int nWidth = MulDiv ( hmWidth, GetDeviceCaps(hdc, LOGPIXELSX),
HIMETRIC_INCH);
int nHeight = MulDiv ( hmHeight, GetDeviceCaps(hdc, LOGPIXELSY),
HIMETRIC_INCH);
pic->Render ( hdc , left , top , nWidth , nHeight , 0 , hmHeight ,
hmWidth , -hmHeight , NULL ) ;
}

Zahical
November 2nd, 2004, 05:11 PM
If you allow me to add one more opinion on the matter....


Translating this into human language would mean that GDI+ actually performs its logic USING GDI ! In modern Windows two APIs exist which allow you to perform graphic tasks: GDI and DirectX..


...But it has to use GDI for at least blitting or screen bitmap mapping purposes, without DDI, it is the only way (aside DirectX surfaces) to draw anything on the screen.


Not True. The original 1.0 GDI+ doesn't use GDI for the final blitting to the frame buffer. A clue for that can be found at http://support.microsoft.com/default.aspx?scid=kb;en-us;319261 where it's said that "GDI+ uses a mechanism that is named DCI to render directly into the front buffer".

If you wonder (just like me) what exactly DCI might be, and how you may have missed it in the PSDK it turns out that DCI is actually a third way to put something on-screen (in a rather DOS-ish way -- writing directly to the frame buffer) and it has been recently documented (I guess as a result of some disclosure agreement that MS signed) -- you may take a look at http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/lowlevelclientsupport/dxlowlevelclientsupport.asp.

In short, the DCI (Device Control Interface) turns out to be a family of several DCI* named functions (one can hardly say documented) that allow you given a HDC to acquire direct pointer to the display frame buffer and write whatever your heart desires there -- but only if you like messing with things like display color organization, screen color depth, strides and the like.

(I think it's interesting to mention that QuickTime has been using the non-documented DCI interface for quite a while -- just open QuickTime settings control panel and go to 'Video Settings' to see what I mean).

As of Windows XP SP1 there is a way to force GDI+ to use GDI for rendering -- see the PSS article mentioned above.

Amn
November 3rd, 2004, 04:19 AM
Great post, man ;) Thanks a lot. I will take a read there.