Profil de Libin绿色家园PhotosBlogListes Outils Aide

Blog


27 mars

Wykobi

Wykobi faq
 

What is Wykobi?

Wykobi is an efficient, robust and simple to use multi-platform 2D/3D computational geometry library. Wykobi provides a concise, predictable, and deterministic interface for geometric primitives and complex geometric routines using and conforming to the ISO/IEC 14882:2003 C++ language specification.

The design and structure of Wykobi lends itself to easy and seamless integration into projects of any scale that require a robust yet efficient 2D/3D computational geometry back-end.

Which C++ compilers are supported?

  • GCC 3.3.1+
  • Intel C++ Compiler 9.0+
  • Microsoft C++ Compiler 8.0+
  • Comeau C++ Compilter 4.0+
  • Digital Mars C++ 8.4+

Where is the documentation for Wykobi?

The Wykobi documentation is currently a work in progress and will be made publicly available as soon as it is ready. Also a technical FAQ is being produced that deals with common issues relating to using Wykobi and general problems faced by developers when integrating computational geometry in their applications. Though one must remember the Wykobi APIs are very self explanatory, straight forward and easy to use.

How is Wykobi different from other well established libraries such as CGAL?

Wykobi is not as object oriented as the 1001 other libraries one can find on the net. Wykobi is based around a structured design, with emphasis on rapid and efficient integration and use within projects without the clutter and overhead that is clearly evident in other libraries.

Wykobi is not intended to fully replace other extensive libraries such as CGAL but to compliment their use and to also provide simple solutions to projects that need only light weight computational geometry capabilities.

What is the license policy of Wykobi?

The license policy breaks down into two simple groups. The first group consisting of open source, academic and noncommercial use of Wykobi, which is governed by the GNU General Public License (version 2). The second group being commercial use of Wykobi, which requires the development entity to obtain a Wykobi Commercial License in order to use Wykobi within any of their commercial products.

In the situation where a product initially developed under the usage terms relating to the first group is to be commercialised, the product will then be considered as being in the second commercial usage group and hence will have to obtain a Wykobi Commercial License.

Where can I obtain a Wykobi Commercial License?

Issues regarding the Wykobi Commercial License and costs and requirements related to obtaining a license should be forwarded to the license enquiry e-mail address found on the contact page.

Is there commercial support for Wykobi?

The Wykobi Commerical License provides 6 months user and library support which is intended to help users smoothly integrate the library and become familiar with its use. The license also provides 18 months prioritised general support.

Customization and product specific software development and consulting services relating to Wykobi and its integration within a commercial product can also purchased through the Wykobi Commercial License.

What is the history behind Wykobi?

Wykobi began its life in 1997 as a simple open source library written in the pascal language known as FastGEO. As FastGEO was developed the base language changed to make use of the new extensions available in the Object Pascal language. As interest grew in FastGEO requests were made to port the library to other languages which had compilers that could take advantage of the capabilities of modern processors and the likes better than the current state of the art object pascal compilers ever could.

As a result of careful consideration and assessment of the direction of FastGEO it was decided that its future would be best served with a port to the C++ language.

How does one pronounce Wykobi?

Wykobi: Why-ko-bee (wI-Kó-Bé)  Wykobi Pronunciation

Computational Geometry, C++, and Wykobi

By Arash Partow.
A brief introduction into computational geometry processes using Wykobi and C++.
 

Wykobi Polygon Clipping - Copyright Arash Partow

Introduction

Good C++ computational geometry libraries to date have been hideously over-designed and incorporate usage patterns that in most cases require extensive redesigns and rewrites of code in order to functionally integrate within an existing project.

Sometimes a lightweight portable solution that has a bit of error is deemed to be more appropriate and reasonable. However these libraries even though being more than able to cater for such a requirement still burden the end user with undue code clutter, very steep learning curves and in some cases unnecessary overheads.

The solution to such a situation is to simplify use, implementation and application. This can be achieved by reducing the number of contact points between the computational geometry back-end and the developer-application combo. But at the same time giving full control of the computations and ensuing folding processes to the user to the extent where the user can decide between using a general solution for a particular problem or a more specialized solution.

For example, say you have two line segments and you want to know if they are intersecting, one could use a general solution, but say if you had prior knowledge that the line segments were either always going to be vertical or horizontal, this would allow one to use a more efficient method to obtain the same result. Typically (but not always) a generalized result is less efficient than a specialised result for the specialised case - by virtue of the fact that the generalised result has to take into account the 1001 other possible scenarios.

A possible solution to the above mentioned problem is Wykobi. Wykobi is an efficient, robust and simple to use multi-platform 2D/3D computational geometry library. Wykobi provides a concise, predictable, and deterministic interface for geometric primitives and complex geometric routines using and conforming to the ISO/IEC 14882:2003 C++ language specification.

The design and structure of Wykobi lends itself to easy and seamless integration into projects of any scale that require a robust yet efficient 2D/3D computational geometry back-end.

Wykobi as a library can be used to efficiently and seamlessly solve complex geometric problems such as collision and proximity detection, efficient spatial queries and geometric constructions used in areas as diverse as gaming, computer aided design and manufacture, electronic design and geographic information systems - just to name a few.

Wykobi provides a series of primitive geometric structures for use within the various algorithms of interest such as intersections, distances, inclusions and clipping operations.

The Wykobi Data Structures

The Point Type

Basic point types, which are zero dimensional entities that exist in either 2D, 3D or n-dimensions.

template<typename T = Float>
class point2d : public geometric_entity {};

template<typename T = Float>
class point3d : public geometric_entity {};

template<typename T = Float, std::size_t Dimension>
class pointnd : public geometric_entity {};

The Line Type

Line type, which is a 1 dimensional entity of infinite length that is described by two points within its present dimension.

Wykobi Line - Copyright Arash Partow

template <typename T, unsigned int Dimension>
class line : public geometric_entity {};

The Segment (Line-Segment) Type

Segment type, similar to the line type, but is of finite length bounded by the two points which describe it within its present dimension.

Wykobi Segment - Copyright Arash Partow

template <typename T, unsigned int Dimension>
class segment : public geometric_entity {};

The Ray Type

Ray type, A directed half-infinite line or half-line. A ray has an origin point and a vector that describes the direction in which all the points that are members of the set of points that make up the ray exist upon.

Wykobi Ray - Copyright Arash Partow

template <typename T, unsigned int Dimension>
class ray : public geometric_entity {};

The Triangle Type

Triangle type, A geometric primitive that is comprised of 3 unique points, which produce 3 unique edges.

Wykobi Triangle - Copyright Arash Partow

template <typename T, unsigned int Dimension>
class triangle : public geometric_entity {};

The Rectangle Type

Rectangle type, An axis aligned 4 sided geometric primitive, described by two bounding points in 2D. A rectangle's form in 3D and higher dimensions is a box.

Wykobi Rectangle - Copyright Arash Partow

template<typename T>
class rectangle : public geometric_entity {};

The Quadix (Quadrilateral) Type

Quadix type, A convex quadrilateral or polygon that comprises of 4 unique points which produce 4 unique edges. In the 3D and higher dimensions sense all 4 points have to be coplanar.

Wykobi Quadix - Copyright Arash Partow

template <typename T, unsigned int Dimension>
class quadix : public geometric_entity {};

The Polygon Type

Polygon type, A set of closed sequentially connected coplanar points.

Wykobi Polygon - Copyright Arash Partow

template<typename T, unsigned int Dimension>
class polygon : public geometric_entity {};

Using the code

There are many different things that can be done with the Wykobi Computational Geometry Library. The following are some of the slightly more interesting capabilities...

Calculating A Convex Hull

The convex hull of a set of points, is the subset of points from the original set that comprise a convex shaped polygon or polytope which bounds all the points in the original set.

Many different techniques exist for calculating the convex hull of a set of points. Various methods such as the Melkman algorithm rely on special properties of the points. Complexities for calculating the convex hull range from naive algorithms which have a complexity of O(N^3) to more specialised algorithms such Graham scan and Melkman that have complexities of O(nlogn) and O(n) respectively.

Graham Scan (Complexity O(nlogn))

Wykobi Graham Scan Convex Hull - Copyright Arash Partow

std::vector< point2d<T> > point_list(100000);
wykobi::polygon<T,2> convex_hull;
wykobi::generate_random_points<T>(0.0,0.0,500.0,500.0,point_list);
wykobi::algorithm::convex_hull_graham_scan< wykobi::point2d<T> >(point_list.begin(),point_list.end(),std::back_inserter(convex_hull));

Jarvis March (Complexity O(nh))

The Jarvis march algorithm is also known as the gift-wraping algorithm. It can be naturally extended to higher dimensions.

std::vector< wykobi::point2d<T> > point_list(100000);
wykobi::polygon<T,2> convex_hull;
wykobi::generate_random_points<T>(0.0,0.0,500.0,500.0,point_list);
wykobi::algorithm::convex_hull_jarvis_march< wykobi::point2d<T> >(point_list.begin(),point_list.end(),std::back_inserter(convex_hull));

Melkman (Complexity O(n))

The Melkman algorithm achieves a complexity of O(n) by assuming that the points in the set are ordered such that they represent a concave non-selfintersecting polygon or polyline.

Wykobi Melkman Convex Hull - Copyright Arash Partow

wykobi::polygon<T,2> polygon = wykobi::make_polygon<T>(make_circle<T>(
1000.0,1000.0,100.0));
wykobi::polygon<T,2> convex_hull;
wykobi::algorithm::convex_hull_melkman< wykobi::point2d<T> >(polygon.begin(),polygon.end(),std::back_inserter(convex_hull));

Calculating A Minimum Bounding Ball

Given a set of n k-dimensional points, the minimum bounding ball is the smallest circle, sphere or hypersphere that contains all the points. This problem is sometimes called the smallest enclosing circle or the smallest enclosing disk where by the points in contention must all be coplanar to each other.

Wykobi Minimum Bounding Ball - Copyright Arash Partow

Randomized Algorithm

The randomized algorithm is a stable algorithm which is used to solve the minimum bounding ball problem for 2D with a space and time complexity O(n).

std::vector< wykobi::point2d<T> > point_list(1000000);
wykobi::generate_random_points<T>(0.0,0.0,1000000.0,1000000.0,point_list);
wykobi::circle<T> minimum_bounding_ball
wykobi::algorithm::randomized_minimum_bounding_ball< wykobi::point2d<T> >(point_list.begin(),point_list.end(),minimum_bounding_ball);

Ritter Algorithm

An approximation algorithm devised by Jack Ritter [ritter 1990]. It has a complexity of O(n), can be easily extended to higher dimensions yet does not guarantee an optimal minimum bounding ball, just something very close.

std::vector< wykobi::point2d<T> > point_list(1000000);
wykobi::generate_random_points<T>(0.0,0.0,1000000.0,1000000.0,point_list);
wykobi::circle<T> minimum_bounding_ball
wykobi::algorithm::ritter_minimum_bounding_ball< wykobi::point2d<T> >(point_list.begin(),point_list.end(),minimum_bounding_ball);

Naive Algorithm (O(N^4))

std::vector< wykobi::point2d<T> > point_list(1000000);
wykobi::generate_random_points<T>(0.0,0.0,1000000.0,1000000.0,point_list);
wykobi::circle<T> minimum_bounding_ball
wykobi::algorithm::naive_minimum_bounding_ball< wykobi::point2d<T> >(point_list.begin(),point_list.end(),minimum_bounding_ball);

Note:

All the 2D minimum bounding ball algorithms have been extended to perform a convex hull filter operation before calculating the bounding ball. Even though obtaining the convex hull is not of linear complexity, the resulting points from the hull guarantee a somewhat better result with regards to the optimal minimum bounding ball when considering the random and ritter algorithms. When considering the naive algorithm there is a large linear scaling down of computing time though not of the complexity.

wykobi::algorithm::randomized_minimum_bounding_ball_with_ch_filter< wykobi::point2d<T> >(point_list.begin(),point_list.end(),minimum_bounding_ball);
wykobi::algorithm::ritter_minimum_bounding_ball_with_ch_filter< wykobi::point2d<T> >(point_list.begin(),point_list.end(),minimum_bounding_ball);
wykobi::algorithm::naive_minimum_bounding_ball_with_ch_filter< wykobi::point2d<T> >(point_list.begin(),point_list.end(),minimum_bounding_ball);

Sutherland Hodgman Polygon Clipping

Clipping one object or more precisely a polygon or polytope against another is essentially the process of computing the intersecting area or volume between the pair of objects. Depending on the structural nature of the objects such as convexity and disjointness, the resulting clipped object may itself be disjoint or may contain islands and other interesting properties.

The Sutherland Hodgman polygon clipping algorithm is a simplified clipping algorithm with the constraint that the clip boundary be convex where as the other object may be a concave non-self intersecting polygon.

Concave Polygon Clipped Against A 2D Triangle

Wykobi Sutherland Hodgman Triangle Polygon Clip - Copyright Arash Partow

wykobi::triangle<T,2> clip_boundry;
wykobi::generate_random_object<T>(0,0,1000.0,1000.0,clip_boundry);
wykobi::polygon<T,2> polygon;
generate_polygon_type1<T>(1000.0,1000.0,polygon); // generate a complex polygon
wykobi::polygon<T,2> clipped_polygon;
wykobi::algorithm::sutherland_hodgman_polygon_clipper< wykobi::point2d<T> >(clip_boundry,polygon,clipped_polygon);

Concave Polygon Clipped Against A 2D Quadix

Wykobi Sutherland Hodgman Quadix Polygon Clip - Copyright Arash Partow

wykobi::quadix<T,2> clip_boundry;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,clip_boundry);
wykobi::polygon<T,2> polygon;
generate_polygon_type1<T>(1000.0,1000.0,polygon); // generate a complex polygon
wykobi::polygon<T,2> clipped_polygon;
wykobi::algorithm::sutherland_hodgman_polygon_clipper< wykobi::point2d<T> >(clip_boundry,polygon,clipped_polygon);

Concave Polygon Clipped Against A 2D Convex Polygon

Wykobi Sutherland Hodgman Polygon Polygon Clip - Copyright Arash Partow

wykobi::circle<T> circle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,circle);
wykobi::polygon<T,2> clip_boundry = wykobi::make_polygon<T>(circle,9);
wykobi::polygon<T,2> polygon;
generate_polygon_type1<T>(1000.0,1000.0,polygon); // generate a complex polygon
wykobi::polygon<T,2> clipped_polygon;
wykobi::algorithm::sutherland_hodgman_polygon_clipper< wykobi::point2d<T> >(clip_boundry,polygon,clipped_polygon);

Cohen-Sutherland Line Segment Clipping

Line Segments Clipped Against An 2D Axis Aligned Bounding Box

Wykobi Cohen-Sutherland Line Segment Clipped Agains A Rectangle - Copyright Arash Partow

const std::size_t MAX_SEGMENTS = 100;
std::vector< wykobi::segment<T,2> > segment_list;
for(std::size_t i = 0; i < MAX_SEGMENTS; ++i)
{
   wykobi::segment<T,2> tmp_segment;
   wykobi::generate_random_object(0.0,0.0,1000.0,1000.0,tmp_segment);
   segment_list.push_back(tmp_segment);
}

wykobi::rectangle<T> rectangle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,rectangle);

std::vector< wykobi::segment<T,2> > clipped_segment_list;

for(std::size_t i = 0; i < segment_list.size(); ++i)
{
   wykobi::segment<T,2> clipped_segment;
   if (wykobi::clip(segment_list[i],rectangle,clipped_segment))
   {
      clipped_segment_list.push_back(clipped_segment);
   }
}

Line Segments Clipped Against A 2D Triangle

Wykobi Cohen-Sutherland Line Segment Clipped Agains A Triangle - Copyright Arash Partow

const std::size_t MAX_SEGMENTS = 100;
std::vector< wykobi::segment<T,2> > segment_list;
for(std::size_t i = 0; i < MAX_SEGMENTS; ++i)
{
   wykobi::segment<T,2> tmp_segment;
   wykobi::generate_random_object(0.0,0.0,1000.0,1000.0,tmp_segment);
   segment_list.push_back(tmp_segment);
}

wykobi::triangle<T,2> triangle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,triangle);

std::vector< wykobi::segment<T,2> > clipped_segment_list;

for(std::size_t i = 0; i < segment_list.size(); ++i)
{
   wykobi::segment<T,2> clipped_segment;
   if (wykobi::clip(segment_list[i],triangle,clipped_segment))
   {
      clipped_segment_list.push_back(clipped_segment);
   }
}

Line Segments Clipped Against A 2D Quadix

Wykobi Cohen-Sutherland Line Segment Clipped Agains A Quadix - Copyright Arash Partow

const std::size_t MAX_SEGMENTS = 100;
std::vector< wykobi::segment<T,2> > segment_list;
for(std::size_t i = 0; i < MAX_SEGMENTS; ++i)
{
   wykobi::segment<T,2> tmp_segment;
   wykobi::generate_random_object(0.0,0.0,1000.0,1000.0,tmp_segment);
   segment_list.push_back(tmp_segment);
}

wykobi::quadix<T,2> quadix;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,quadix);

std::vector< wykobi::segment<T,2> > clipped_segment_list;

for(std::size_t i = 0; i < segment_list.size(); ++i)
{
   wykobi::segment<T,2> clipped_segment;
   if (wykobi::clip(segment_list[i],quadix,clipped_segment))
   {
      clipped_segment_list.push_back(clipped_segment);
   }
}

Line Segments Clipped Against A Circle

Wykobi Cohen-Sutherland Line Segment Clipped Agains A Circle - Copyright Arash Partow

const std::size_t MAX_SEGMENTS = 100;
std::vector< wykobi::segment<T,2> > segment_list;
for(std::size_t i = 0; i < MAX_SEGMENTS; ++i)
{
   wykobi::segment<T,2> tmp_segment;
   wykobi::generate_random_object(0.0,0.0,1000.0,1000.0,tmp_segment);
   segment_list.push_back(tmp_segment);
}

wykobi::circle<T> circle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,circle);

std::vector< wykobi::segment<T,2> > clipped_segment_list;

for(std::size_t i = 0; i < segment_list.size(); ++i)
{
   wykobi::segment<T,2> clipped_segment;
   if (wykobi::clip(segment_list[i],circle,clipped_segment))
   {
      clipped_segment_list.push_back(clipped_segment);
   }
}

Rectangles Clipped Against A Rectangle

Wykobi Rectangles Clipped Against a Rectangle - Copyright Arash Partow

const std::size_t MAX_RECTANGLES = 10;
std::vector< wykobi::rectangle<T> > rectangle_list;
for(std::size_t i = 0; i < MAX_RECTANGLES; ++i)
{
   wykobi::rectangle<T> tmp_rectangle;
   wykobi::generate_random_object(0.0,0.0,1000.0,1000.0,tmp_rectangle);
   rectangle_list.push_back(tmp_rectangle);
}

wykobi::rectangle<T> clip_rectangle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,clip_rectangle);

std::vector< wykobi::rectangle<T> > clipped_rectangle_list;

for(std::size_t i = 0; i < rectangle_list.size(); ++i)
{
   wykobi::rectangle<T> clipped_rectangle;
   if (wykobi::clip(rectangle_list[i],clip_rectangle,clipped_rectangle))
   {
      clipped_rectangle_list.push_back(clipped_rectangle);
   }
}

Group Based Pairwise Intersections

Segment To Segment Intersections

Wykobi Segment to Segment Pairwise Intersections - Copyright Arash Partow

const std::size_t MAX_SEGMENTS = 100;
std::vector< wykobi::segment<T,2> > segment_list;
for(std::size_t i = 0; i < MAX_SEGMENTS; ++i)
{
   wykobi::segment<T,2> tmp_segment;
   wykobi::generate_random_object(0.0,0.0,1000.0,1000.0,tmp_segment);
   segment_list.push_back(tmp_segment);
}
std::vector< wykobi::point2d<T> > intersection_list;
wykobi::algorithm::naive_group_intersections< segment<T,2> >(segment_list.begin(),segment_list.end(),std::back_inserter(intersection_list));

Circle To Circle (Disk) Intersections

Wykobi Circle to Circle Pairwise Intersections - Copyright Arash Partow

const std::size_t MAX_CIRCLES = 100;
std::vector< wykobi::circle<T> > circle_list;
for(std::size_t i = 0; i < MAX_CIRCLES; ++i)
{
   wykobi::circle<T> tmp_circle;
   wykobi::generate_random_object(0.0,0.0,1000.0,1000.0,tmp_circle);
   circle_list.push_back(tmp_segment);
}
std::vector< wykobi::point2d<T> > intersection_list;
wykobi::algorithm::naive_group_intersections< circle<T> >(circle_list.begin(),circle_list.end(),std::back_inserter(intersection_list));

Simple Polygon Triangulation (Ear-Clipping Algorithm)

Wykobi Simple Polygon Triangulation - Copyright Arash Partow

Collapse
wykobi::polygon<T,2> polygon;
polygon.push_back(wykobi::make_point<T>( 25.0,191.0));
polygon.push_back(wykobi::make_point<T>( 55.0,191.0));
polygon.push_back(wykobi::make_point<T>( 52.0,146.0));
polygon.push_back(wykobi::make_point<T>( 98.0,134.0));
polygon.push_back(wykobi::make_point<T>(137.0,200.0));
polygon.push_back(wykobi::make_point<T>(157.0,163.0));
polygon.push_back(wykobi::make_point<T>(251.0,188.0));
polygon.push_back(wykobi::make_point<T>(151.0,138.0));
polygon.push_back(wykobi::make_point<T>(164.0,116.0));
polygon.push_back(wykobi::make_point<T>(125.0,141.0));
polygon.push_back(wykobi::make_point<T>( 78.0, 99.0));
polygon.push_back(wykobi::make_point<T>( 29.0,139.0));
std::vector< wykobi::triangle<T,2> > triangle_list;
wykobi::algorithm::polygon_triangulate< wykobi::point2d<T> >(polygon,std::back_inserter(triangle_list));

Wykobi Simple Convex Polygon Triangulation - Copyright Arash Partow

wykobi::polygon<T,2> polygon = wykobi::make_polygon(wykobi::make_circle<T>(
0.0,0.0,100.0),10);
std::vector< wykobi::triangle<T,2> > triangle_list;
wykobi::algorithm::polygon_triangulate< wykobi::point2d<T> >(polygon,std::back_inserter(triangle_list));

Wykobi Simple Convex Polygon Triangulation - Copyright Arash Partow

wykobi::polygon<T,2> polygon;
generate_polygon_type1<T>(1000.0,1000.0,polygon);  // generate simple complex polygon
std::vector< wykobi::triangle<T,2> > triangle_list;
wykobi::algorithm::polygon_triangulate< wykobi::point2d<T> >(polygon,std::back_inserter(triangle_list));

Calculating The Axis Projection Descriptor

In the book flatland and subsequent flatterland, the flatlanders would query an object's boundry to determine its identity, similar objects would have similar boundries. The projection of a 2D object onto various axises produces a view of that object on those axises. The combinations of views are somewhat unique to that object and using various normalisation methods and difference metrics can be used to define, in a somewhat invariant to rotation and scaling manner, how different or how similar one 2D object is from another. The concepts used in calculating the descriptor are very similar to the concepts used in the seperating axis theorem.

Wykobi Axis Projection Descriptor - Copyright Arash Partow

wykobi::quadix<T,2> quadix;
wykobi::generate_random_object<T>(0.0,0.0,10000.0,10000.0,quadix);
wykobi::polygon<T,2> polygon = wykobi::make_polygon(quadix);
std::vector<T> descriptor;
wykobi::algorithm::generate_axis_projection_descriptor<T>(wykobi::polygon,std::back_inserter(descriptor));

Beziers And Splines

Random 2D Quadratic Beziers

Wykobi Random Quadratic Beziers - Copyright Arash Partow

const std::size_t BEZIER_COUNT = 15;
const std::size_t RESOLUTION   = 1000;
std::vector< wykobi::quadratic_bezier<T,2> > bezier_list;
for(std::size_t i = 0; i < BEZIER_COUNT; ++i)
{
   wykobi::quadratic_bezier<T,2> bezier;
   bezier[0] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[1] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[2] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier_list.push_back(bezier)
}

for(std::size_t i = 0; i < bezier_list.size(); ++i)
{
   std::vector< point2d<T> > point_list;
   wykobi::generate_bezier(bezier_list[i],RESOLUTION,point_list);
   draw_polyline(point_list);
}

Random 2D Cubic Beziers

Wykobi Random Cubic Beziers - Copyright Arash Partow

const std::size_t BEZIER_COUNT = 15;
const std::size_t RESOLUTION   = 1000;
std::vector< wykobi::cubic_bezier<T,2> > bezier_list;
for(std::size_t i = 0; i < BEZIER_COUNT; ++i)
{
   wykobi::cubic_bezier<T,2> bezier;
   bezier[0] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[1] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[2] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[3] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier_list.push_back(bezier)
}

for(std::size_t i = 0; i < bezier_list.size(); ++i)
{
   std::vector< point2d<T> > point_list;
   wykobi::generate_bezier(bezier_list[i],RESOLUTION,point_list);
   draw_polyline(point_list);
}

Pairwise Segment To Quadratic Bezier Intersections

Note: The current method uses an iterative approximation approach. The correct method is to use a polynomial root solver to find the quadratic or cubic polynomial roots for every dimension.

Wykobi Pairwise Segment To Quadratic Bezier Intersections - Copyright Arash Partow

Collapse
const std::size_t BEZIER_COUNT  = 20;
const std::size_t SEGMENT_COUNT = 10;
const std::size_t RESOLUTION    = 1000;
std::vector< wykobi::quadratic_bezier<T,2> > bezier_list;
std::vector< wykobi::segment<T,2> > segment_list;

for(std::size_t i = 0; i < BEZIER_COUNT; ++i)
{
   wykobi::quadratic_bezier<T,2> bezier;
   bezier[0] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[1] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[2] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier_list.push_back(bezier)
}

for(std::size_t i = 0; i < SEGMENT_COUNT; ++i)
{
   wykobi::segment<T,2> segment;
   wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,segment);
   segment_list.push_back(segment);
}

std::vector< wykobi::point2d<T> > intersection_point_list;

for (std::size_t i = 0; i < bezier_list.size(); ++i)
{
   for (std::size_t j = 0; j < segment_list.size(); ++j)
   {
      wykobi::intersection_point(segment_list[j],bezier_list[i],intersection_point_list);
   }
}

Pairwise Segment To Cubic Bezier Intersections

Wykobi Pairwise Segment To Cubic Bezier Intersections - Copyright Arash Partow

Collapse
const std::size_t BEZIER_COUNT  = 20;
const std::size_t SEGMENT_COUNT = 10;
const std::size_t RESOLUTION    = 1000;
std::vector< wykobi::cubic_bezier<T,2> > bezier_list;
std::vector< wykobi::segment<T,2> > segment_list;

for(std::size_t i = 0; i < BEZIER_COUNT; ++i)
{
   wykobi::quadratic_bezier<T,2> bezier;
   bezier[0] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[1] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[2] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier[3] = wykobi::generate_random_point<T>(1000.0,1000.0);
   bezier_list.push_back(bezier)
}

for(std::size_t i = 0; i < SEGMENT_COUNT; ++i)
{
   wykobi::segment<T,2> segment;
   wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,segment);
   segment_list.push_back(segment);
}

std::vector< wykobi::point2d<T> > intersection_point_list;

for (std::size_t i = 0; i < bezier_list.size(); ++i)
{
   for (std::size_t j = 0; j < segment_list.size(); ++j)
   {
      wykobi::intersection_point(segment_list[j],bezier_list[i],intersection_point_list);
   }
}

Constructions, Triangles And Points Of Interest

Vertex Bisector

Wykobi Vertex Bisector - Copyright Arash Partow

wykobi::point2d<T> point_a = wykobi::make_point(...,...);
wykobi::point2d<T> point_b = wykobi::make_point(...,...);
wykobi::point2d<T> point_c = wykobi::make_point(...,...);
wykobi::line<T,2> bisector_line = wykobi::create_line_from_bisector(point_a,point_b,point_c);

Circle Tangent Line Segments

Wykobi Point To Circle Tangents - Copyright Arash Partow

wykobi::circle<T> circle = wykobi::make_circle<T>(0.0
,0.0,100.0);
wykobi::point2d<T> external_point = wykobi::make_point<T>(1000.0,1000.0);
wykobi::point2d<T> tangent_point1;
wykobi::point2d<T> tangent_point2;
wykobi::circle_tangent_points(circle,external_point,tangent_point1,tangent_point2);

Triangle Circumcircle And Inscribed Circle

Wykobi Circumcircle And Inscribed Circle - Copyright Arash Partow

wykobi::triangle<T,2> triangle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,triangle);
wykobi::circle<T> circumcircle = wykobi::circumcircle(triangle);
wykobi::circle<T> inscribed_circle = wykobi::inscribed_circle(triangle);

Construction Of Triangle's Excentral Triangle And Excircles

Wykobi Excentral Triangle - Copyright Arash Partow

wykobi::triangle<T,2> triangle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,triangle);
wykobi::circle<T> excircle1 = wykobi::excircle(triangle,0));
wykobi::circle<T> excircle2 = wykobi::excircle(triangle,1));
wykobi::circle<T> excircle3 = wykobi::excircle(triangle,2));
wykobi::triangle<T,2> excentral_triangle = wykobi::create_excentral_triangle(triangle);

Calculation Of The Torricelli Point (Fermat Point)

The Torricelli point, also known as the fermat point, is the point within the triangle constructed from 3 unique points that minimizes the total distance from each of the 3 points to itself.

Wykobi Torricelli Point - Copyright Arash Partow

wykobi::triangle<T,2> triangle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,triangle);
wykobi::point2d<T> torricelli_point = wykobi::torricelli_point(triangle);

Closest Point On Triangle From External Points

Wykobi Closest Point On Triangle From external Points - Copyright Arash Partow

wykobi::triangle<T,2> triangle;
wykobi::generate_random_object<T>(0.0,0.0,1000.0,1000.0,triangle);
wykobi::circle<T> circumcircle = wykobi::circumcircle(triangle);
wykobi::point2d<T> triangle_centroid = wykobi::centroid(triangle);
wykobi::point2d<T> external_point = wykobi::make_point(circumcircle) + (circumcircle.radius * T(2.1));
std::vector< wykobi::point2d<T> > closest_point_list;

for(std::size_t i = 0; i < 360; ++i)
{
   closest_point_list.push_back(wykobi::closest_point_on_triangle_from_point(triangle,external_point));
   external_point = wykobi::rotate(T(1.0),external_point,triangle_centroid);
}

Closest Point On Circle From An External Point

WykobiClosest Point On Circle From An External Point - Copyright Arash Partow

wykobi::circle<T> circle = wykobi::make_circle<T>(...,...,...);
wykobi::point2d<T> external_point = wykobi::make_point<T>(...,...);
wykobi::point2d<T> closest_point_on_circle = wykobi::closest_point_on_circle_from_point(circle,external_point);

Closest Point On Circle From An External Segment

Wykobi Closest Point On Circle From An External Segment - Copyright Arash Partow

wykobi::circle<T> circle = wykobi::make_circle<T>(...,...,...);
wykobi::segment<T,2> external_segment = wykobi::make_segment<T>(...,...,...,...);
wykobi::point2d<T> closest_point_on_circle = wykobi::closest_point_on_circle_from_segment(circle,external_segment);
wykobi::point2d<T> closest_point_on_segment = wykobi::closest_point_on_segment_from_point(external_segment,closest_point_on_circle);

Closest Point On Circle From Another Circle

Wykobi Closest Point On Circle From Another Circle - Copyright Arash Partow

wykobi::circle<T> circle_a = wykobi::make_circle<T>(...,...,...);
wykobi::circle<T> circle_b = wykobi::make_circle<T>(...,...,...);
wykobi::point2d<T> closest_point_on_circle_a = wykobi::closest_point_on_circle_from_circle(circle_a,circle_b);
wykobi::point2d<T> closest_point_on_circle_b = wykobi::closest_point_on_circle_from_circle(circle_b,circle_a);

Invert A Circle Across Another Circle

Wykobi Invert A Circle Across Another Circle - Copyright Arash Partow

wykobi::circle<T> circle_a = wykobi::make_circle<T>(
0.0,0.0,120.0);
wykobi::circle<T> circle_b = wykobi::make_circle<T>(180.0,-140.0,60.0);
wykobi::circle<T> circle_b_inverted = wykobi::invert_circle_across_circle(circle_b,circle_a);

Mirroring Of Objects About An Arbitary Axis

Wykobi Mirror Objects About An Arbitary Axis - Copyright Arash Partow Wykobi Mirror Objects About An Arbitary Axis - Copyright Arash Partow

wykobi::line<T,2> mirror_axis = wykobi::make_line<T>(...);
wykobi::triangle<T,2> triangle = wykobi::make_triangle<T>(...);
wykobi::circle<T> circle = wykobi::make_circle<T>(...);
wykobi::triangle<T,2> mirrored_triangle = wykobi::mirror(triangle,mirror_axis);
wykobi::circle<T> mirrored_circle = wykobi::mirror(circle,mirror_axis);

Other Various Triangle Constructions

Collapse
wykobi::triangle<T,2> result_triangle;
result_triangle = wykobi::create_morley_triangle(triangle);
result_triangle = wykobi::create_cevian_triangle(triangle,point);
result_triangle = wykobi::create_anticevian_triangle(triangle,point);
result_triangle = wykobi::create_anticomplementary_triangle(triangle);
result_triangle = wykobi::create_inner_napoleon_triangle(triangle);
result_triangle = wykobi::create_outer_napoleon_triangle(triangle);
result_triangle = wykobi::create_inner_vecten_triangle(triangle);
result_triangle = wykobi::create_outer_vecten_triangle(triangle);
result_triangle = wykobi::create_medial_triangle(triangle);
result_triangle = wykobi::create_contact_triangle(triangle);
result_triangle = wykobi::create_symmedial_triangle(triangle,point);
result_triangle = wykobi::create_orthic_triangle(triangle);
result_triangle = wykobi::create_pedal_triangle(point, triangle);
result_triangle = wykobi::create_antipedal_triangle(point,triangle);
result_triangle = wykobi::create_excentral_triangle(triangle);
result_triangle = wykobi::create_incentral_triangle(triangle);
result_triangle = wykobi::create_extouch_triangle(triangle);
result_triangle = wykobi::create_feuerbach_triangle(triangle);

wykobi::circle<T> result_circle;
result_circle = wykobi::inscribed_circle(triangle);
result_circle = wykobi::circumecircle(triangle);
result_circle = wykobi::nine_point_circle(triangle);

The above is a very brief overview of only some of the computational geometry algorithms and processes that are available in the Wykobi C++ computational geometry library. There is plenty more out there...

Wykobi In The Commercial Environment

Wykobi's simplicity and ease of use have made it a popular choice amoungst developers in projects requiring that extra bit more, but without all the overhead and integration issues. As a result Wykobi finds itself being used in some very interesting areas. Here are a few of them:

  • A major EDA vendor's PCB autorouter system
  • A GIS vendor's GPS based navigation suite
  • A VLSI vendor's floor planning system which is coupled with their place-and-route tool
  • Used as a geometric backend for analysing spatial metadata embedded in live video streams as part of a video exploitation system

History

The Wykobi C++ computational geometry library is currently at version 0.0.2. The most recent versions and updates can be obtained from the official Wykobi site www.wykobi.com

About Arash Partow

Synchronization in Multithreaded Applications with MFC

By Anton Milev.
Screenshot - arsynmtmfc.gif

Introduction

This article discusses the basic synchronization concepts and practices that are supposed to be useful for beginners to do multithreaded programming. By saying beginner, I don't mean those that are beginners in learning C++ language, but the people that are somewhat new in multithreaded programming. The main concentration of this article is on synchronization techniques. Thus this article is like a tutorial on synchronization.

The General View

During their execution, threads, more or less, are interoperating with each other. This interoperation may have various forms and may be of various kinds. For example, a thread, after performing the task it is assigned to, informs another thread about it. Then the second thread whose job is a logical continuation of the first thread starts operating.

All the forms of interoperations might be described by the term synchronization which can be supported in several ways. Most usable ones are those whose primary aim is to support synchronization itself. The following objects are intended to support the synchronization (this is not a complete list):

  • Semaphores
  • Mutexes
  • Critical Sections
  • Events

Each of these objects has a different special purpose and usage but the general purpose is to support synchronization. I will introduce them to you through this article later. There are other objects that can be used as synchronization mediums such as Process and Thread objects. Their usage enables a programmer to decide, for example, if a given process or thread has finished its execution or not.

To use the Process and Thread objects for synchronization purposes, we are supposed to use wait-functions. Before getting to learn these functions, you should learn a key concept, that is, any kernel object that can be used as a synchronization object can be in one of the two states; signaled state and nonsignaled state. Except for critical sections, all synchronization objects can be in either of these two states. For example, for Process and Thread objects, the nonsignaled state is encountered when they start their execution and the signaled state is encountered when they finish their execution. To decide whether a given process or thread has finished, we should find out whether their representative objects are in signaled state; to do that, we should turn to the wait-functions.

Wait-functions

The following function is the simplest wait-function amongst the other wait-functions. It has the following declaration format:

DWORD WaitForSingleObject
(
  HANDLE hHandle,
  DWORD dwMilliseconds
);

The parameter hHandle takes the descriptor of an object whose signaled or nonsignaled state is going to be examined. The parameter dwMilliseconds takes the time that the calling thread should wait until the examining object enters the signaled state. As soon as the object is signaled or the given time interval expires, the function returns the control to the caller thread. If dwMilliseconds takes INIFINITE value (-1), the thread will wait until the object becomes signaled. If it doesn't become signaled, the thread will wait forever.

For example, the following call checks whether a process [identified by hProcess descriptor] is in execution or not:

DWORD dw = WaitForSingleObject(hProcess, 0);
switch (dw)
{
   case WAIT_OBJECT_0:
      // the process has exited
      break;

   case WAIT_TIMEOUT:
      // the process is still executing
      break;

   case WAIT_FAILED:
      // failure
      break;
}

As you notice, we passed 0 to the function's dwMilliseconds parameter in which case the function instantly checks the object's state [signaled or nonsignaled] and immediately returns the control. If the object is signaled, the function returns WAIT_OBJECT_0. If it is nonsignaled - WAIT_TIMEOUT is returned. In case of failure, WAIT_FAILED is returned (a failure may occur when an invalid descriptor is passed to the function).

Next wait-function is similar to the previous one except that it takes a list of descriptors and waits until either one of them or all of them become signaled:

DWORD WaitForMultipleObjects
(
  DWORD nCount,
  CONST HANDLE *lpHandles,
  BOOL fWaitAll,
  DWORD dwMilliseconds
);

The parameter nCount takes the number of descriptors to be examined. The parameter lpHandles should point an array of descriptors. If the parameter fWaitAll is TRUE, the function will wait until all the objects become signaled. If it is FALSE, the function returns even if a single object becomes signaled [no matter what the others are]. dwMilliseconds is the same as in the previous function.

For example, the following code decides which process will exit first from the list of given HANDLEs:

HANDLE h[3];
h[0] = hThread1;
h[1] = hThread2;
h[2] = hThread3;

DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000);
switch (dw)
{
   case WAIT_FAILED:
      // failure
      break;

   case WAIT_TIMEOUT:
      // no processes exited during 5000ms
      break;

   case WAIT_OBJECT_0 + 0:
      // a process with h[0] descriptor has exited
      break;

   case WAIT_OBJECT_0 + 1:
      // a process with h[1] descriptor has exited
      break;

   case WAIT_OBJECT_0 + 2:
      // a process with h[2] descriptor has exited
      break;
}

As we see, the function can return different values which show the reason the function returned. You already know the meaning of the first two values. Next values are returned by the following logic; WAIT_OBJECT_0 + index is returned which shows that the object from the array of HANDLEs whose index is index, has got signaled. If fWaitAll parameter is TRUE, WAIT_OBJECT_0 will be returned [if all the objects become signaled].

A thread, if it calls a wait-function, enters the kernel mode from the user mode. This fact is both bad and good. It is bad because to enter the kernel mode, approximately 1000 processor cycles are required which may be too expensive in a concrete situation. The good point is that after entering the kernel mode, no processor usage is needed; the thread is asleep.

Let's turn to MFC and see what it can do for us. There are two classes that encapsulate calls to wait-functions; CSingleLock and CMultiLock. We will see their usage later in this article.

Synchronization object Equivalent C++ class
Events CEvent
Critical sections CCriticalSection
Mutexes CMutex
Semaphores CSemaphore

Each of these classes inherits a single class - CSyncObject whose most usable member is the overloaded HANDLE operator that returns the underlying descriptor of a given synchronization object. All these classes are declared in <AfxMt.h> include file.

Events

Generally, events are used in cases when a thread [or threads] is supposed to start doing its job after a specified action has occurred. For example, a thread might wait until the necessary data is gathered and then start saving them in the hard drive. There are two kinds of events; manual-reset and auto-reset. By using an event we simply can notify another thread that a specified action has occurred. With a first kind of event, that is manual-reset, a thread can notify more than one thread about a specified action. But with a second kind of event, that is auto-reset, only one can be notified. In MFC, there is CEvent class that encapsulates the event object (in terms of Windows, it is represented by an HANDLE value). The constructor of CEvent allows us to create both manual-reset and auto-reset events. By default, the second kind of event is created. To notify the waiting threads, we should use CEvent::SetEvent method, this means that this kind of call will make the event enter the signaled state. If the event is manual-reset, then it will stay in signaled state until a corresponding CEvent::ResetEvent call is invoked which will make the event enter the nonsignaled state. This is the feature that allows a thread to notify more than one thread by a single SetEvent call. If the event is auto-reset, then only one thread from all waiting threads will be able to receive the notification. After it is received by a thread, the event will automatically enter the nonsignaled state. The following two examples will illustrate these thoughts. The first example:
// create an auto-reset event
CEvent g_eventStart;

UINT ThreadProc1(LPVOID pParam)
{
    ::WaitForSingleObject(g_eventStart, INFINITE);

        ...

    return 0;
}

UINT ThreadProc2(LPVOID pParam)
{
    ::WaitForSingleObject(g_eventStart, INFINITE);

        ...

    return 0;
}

In this code, a global CEvent object is created of auto-reset type. In addition, there are two working threads which are waiting for that event in order to start their job. As soon as a third thread calls SetEvent for that object, one and only one thread from these two threads (note that nobody can say exactly which one) will receive the notification, and afterwards the event will enter the nonsignaled state which will not allow a second thread to catch the event. The code, though not very useful, illustrates how an auto-reset event works. Let's look at the second example:

// create a manual-reset event
CEvent g_eventStart(FALSE, TRUE);

UINT ThreadProc1(LPVOID pParam)
{
    ::WaitForSingleObject(g_eventStart, INFINITE);

        ...

    return 0;
}

UINT ThreadProc2(LPVOID pParam)
{
    ::WaitForSingleObject(g_eventStart, INFINITE);

        ...

    return 0;
}

This code differs from the previous one by only the CEvent constructor's parameters. But in sense of functionality, there is a principal difference in the way that the two threads may work. If a third thread calls SetEvent method for this object, then it will be possible to guarantee that the two threads will start working at the same (almost same) time. This is because a manual-reset event, after entering the signaled state, will not enter the nonsignaled state until a corresponding ResetEvent call is done.

Yet another method for working with events - CEvent::PulseEvent. This method first makes the event enter the signaled state and then makes it enter back into the nonsignaled state. If the event is of manual-reset type, the event enters the signaled state then all the waiting threads are getting notified, and then it enters the nonsignaled state. If the event is of auto-reset type, then only one thread will get notified even if there are many threads waiting. If no thread is waiting, the call to ResetEvent will do nothing.

Example - WorkerThreads

In this example I will show how to create worker threads and how to destroy them properly. Here we define a controlling function which is used by all threads. Every time we click the view, one thread is created. All the created threads use the mentioned controlling function which will draw a moving ellipse in the view's client area. Here a manual-reset event is used which informs all the working threads about their death. Besides, we will see how to make the primary thread wait until all the worker threads leave the scene.

Maxheap example

All the ellipses are traversing in the client area and are not leaving its boundaries

  1. You should have an SDI application open. Assume the project name is WorkerThreads.
  2. Let's have a WM_LBUTTONDOWN message handler for launching our threads.
  3. Declare the controlling function. A controlling function may be declared in any file; the point is that it should have global access. Assume we have a Threads.h/Threads.cpp file in which the controlling function is declared/defined as follows:
    // Threads.h
    #pragma once
    
    struct THREADINFO
    {
        HWND hWnd;
        POINT point;
    };
    
    
    UINT ThreadDraw(PVOID pParam);
    

    Collapse
    // Threads.cpp
    extern CEvent g_eventEnd;
    
    UINT ThreadDraw(PVOID pParam)
    {
        static int snCount = 0;
        snCount ++;
        TRACE("- ThreadDraw %d: started...\n", snCount);
    
        THREADINFO *pInfo = reinterpret_cast<threadinfo /> (pParam);
    
        CWnd *pWnd = CWnd::FromHandle(pInfo->hWnd);
    
        CClientDC dc(pWnd);
    
        int x = pInfo->point.x;
        int y = pInfo->point.y;
    
        srand((UINT)time(NULL));
        CRect rectEllipse(x - 25, y - 25, x + 25, y + 25);
    
        CSize sizeOffset(1, 1);
    
        CBrush brush(RGB(rand()% 256, rand()% 256, rand()% 256));
        CBrush *pOld = dc.SelectObject(&brush);
        while (WAIT_TIMEOUT == ::WaitForSingleObject(g_eventEnd, 0))
        {
            CRect rectClient;
            pWnd->GetClientRect(rectClient);
    
            if (rectEllipse.left < rectClient.left || 
                rectEllipse.right > rectClient.right)
                sizeOffset.cx *= -1;
    
            if (rectEllipse.top < rectClient.top || 
                rectEllipse.bottom > rectClient.bottom)
                sizeOffset.cy *= -1;
    
            dc.FillRect(rectEllipse, CBrush::FromHandle
                ((HBRUSH)GetStockObject(WHITE_BRUSH)));
    
            rectEllipse.OffsetRect(sizeOffset);
    
            dc.Ellipse(rectEllipse);
            Sleep(25);
        }
    
        dc.SelectObject(pOld);
    
        delete pInfo;
    
        TRACE("- ThreadDraw %d: exiting.\n", snCount --);
        return 0;
    }
    

    This function takes a single object via its PVOID parameter, that is, a struct whose fields are the handle of the view, in order to be able to draw on its client area, and the point from where to start the cycle. Note that we should pass the very handle and not a CWnd pointer to let each thread create a temporary C++ object over the handle and use it. Otherwise all of them would share a single C++ object which is a potential danger in sense of safe multithreaded programming. In its core, the controlling function renders a moving circle in the client area of the view. Besides, include <Afxmt.h> file in "StdAfx.h" file to make CEvent visible.

    Another key point here is that we prepare a structure THREADINFO to pass to the thread. This technique is mostly used when there is a need to pass more than one value to a thread (or get them from a thread). We need to pass the window handle of the view and the initial point of the cycle that is going to be created. Each thread deletes the THREADINFO object passed to itself. Beware that this deletion is done in regard to our convention; that is, the primary thread should reserve a heap memory for a THREADINFO object and the targeting thread should delete it. The idea is that the primary thread doesn't know when to do deletion as the object will have been owned by the secondary thread itself.

  4. Declare an array variable in CWorkerThreadView class. We should store the pointer to CWinThread objects to use them later:
    private:
        CArray<CWinThread *, CWinThread *> m_ThreadArray;
    

    Besides, include <AfxTempl.h>; file in "StdAfx.h" file to make CArray visible.

  5. Change the file WorkerThreadsView.cpp. First define a global CEvent manual-reset variable somewhere at the beginning of the file:
    // manual-reset event
    
    CEvent g_eventEnd(FALSE, TRUE);
    

    Now add code to the WM_LBUTTONDOWN message handler:

    void CWorkerThreadsView::OnLButtonDown()
    {
        THREADINFO *pInfo = new THREADINFO;
        pInfo->hWnd = GetSafeHwnd();
        pInfo->point = point;
    
        CWinThread *pThread = AfxBeginThread(ThreadDraw, 
        (PVOID) pInfo, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
        pThread->m_bAutoDelete = FALSE;
        pThread->ResumeThread();
        m_ThreadArray.Add(pThread);
    }
    

    Be aware that we exclude the auto-deletion property of a newly created thread but instead we store the pointer to that CWinThread object in our array. Note that we create an instance of THREADINFO in the heap and let the thread delete it after it finishes working with the structure. To make ThreadDraw and THREADINFO visible in WorkerThreadsView.cpp file, include "Threads.h " file.

  6. Take care to destroy the threads properly. As all threads are related to the view object (they are working with it), it will be reasonable to destroy them in the view's WM_DESTROY message handler:
    void CWorkerThreadsView::OnDestroy()
    {
        CView::OnDestroy();
    
        // TODO: Add your message handler code here
        g_eventEnd.SetEvent();
        for (int j = 0; j < m_ThreadArray.GetSize(); j ++)
        {
        ::WaitForSingleObject(m_ThreadArray[j]->m_hThread, INFINITE);
        delete m_ThreadArray[j];
        }
    }
    

    This function first makes the event become signaled to notify the working threads about their death, and then it uses WaitForSingleObject to make the primary thread wait for each worker thread until the later is destroyed fully. To do this we should have a valid CWinThread pointer even when the corresponding thread is destroyed; that is why we removed the auto-deletion property from CWinThread objects in the previous step. As soon as a worker thread exits, the second line of the for loop destroys the corresponding C++ object. Note that in each iteration a call to WaitForSingleObject is done which simply results in entering the kernel mode from the user mode. For example, for 10 iterations there will be wasted ~10000 processor cycles. To overcome this moment, we might use WaitForMultipleObjects. In this case we will need a C-array of thread descriptors. So, the above for loop could be replaced with the following code:

    //second way (comment in 'for' loop above)
    
    int nSize = m_ThreadArray.GetSize();
    HANDLE *p = new HANDLE[nSize];
    
    for (int j = 0; j < nSize; j ++)
    {
        p[j] = m_ThreadArray[j]->m_hThread;
    }
    
    ::WaitForMultipleObjects(nSize, p, TRUE, INFINITE);
    
    for (j = 0; j < nSize; j ++)
    {
        delete m_ThreadArray[j];
    }
    delete [] p;
    

    As the previous code executes only once and in addition at the end of the application, such improvements could hardly be valued much.

  7. This is all. You can test it.

Critical Sections

Unlike other synchronization objects, critical sections are working in the user mode unless there is a need to enter the kernel mode. If a thread tries to run a code that is caught be a critical section, it first does a spin blocking and after a specified amount of time, it enters the kernel mode to wait for the critical section. Actually, a critical section consists of a spin counter and a semaphore; the former is for the user mode waiting, and the later is for the kernel mode waiting (sleeping). In Win32 API, there is a CRITICAL_SECTION structure that represents critical section objects. In MFC, there is a class named CCriticalSection. Conceptually, a critical section is a sector of source code that is needed in integrated execution, that is, during the execution of that part of the code it should be guaranteed that the execution will not be interrupted by another thread. Such sectors of code may be required in cases when there is a need to grant a single thread the monopoly of using a shared resource. A simple case is using global variables by more than one thread. For example:

int g_nVariable = 0;

UINT Thread_First(LPVOID pParam)
{
    if (g_nVariable < 100)
    {
       ...
    }
    return 0;
}

UINT Thread_Second(LPVOID pParam)
{
    g_nVariable += 50;
    ...
    return 0;
}

This is not a safe code as no thread has a monopoly access to g_nVariable variable. Consider the following scenario; assume the initial value of g_nVariable is 80, the control is passed to the first thread which sees that the value of g_nVariable is less than 100 and thus it tries to execute the block under the condition. But at that time the processor switches to the second thread which adds 50 to the variable, so it becomes greater than 100. Afterwards, the processor switches back to the first thread and continues executing the if block. Guess what? Inside the if block the value of g_nVariable is greater than 100 though it is supposed to be less than 100. To cover this gap, we may use a critical section like so:

CCriticalSection g_cs;
int g_nVariable = 0;

UINT Thread_First(LPVOID pParam)
{
    g_cs.Lock();
    if (g_nVariable < 100)
    {
       ...
    }
    g_cs.Unlock();
    return 0;
}

UINT Thread_Second(LPVOID pParam)
{
    g_cs.Lock();
    g_nVariable += 20;
    g_cs.Unlock();
    ...
    return 0;
}

Here, two methods of CCriticalSection class are used. A call to Lock function informs the system that the execution of underlying code should not be interrupted until the same thread makes a call to Unlock function. In response to this call, the system first checks whether that code is not captured by another thread with the same critical section object. If it is, the thread waits until the capturing thread releases the critical section and than captures it itself.

If there are more than two shared resources to be protected, it would be a good practice to use a separate critical section per resource. Do not forget to match Unlock to each Lock. When using critical sections, one should be careful not to prepare mutual blocking situations for collaborating threads. This means that a thread could wait for a critical section to be freed by another thread, which in turn, waits for a critical section that is captured by the first thread. It is obvious that in such a case the two threads will wait forever.

There is a practice to embed critical sections into C++ classes and thus make them thread-safe. This kind of trick might be needed when the objects of a specific class are supposed to be used by more than one thread simultaneously. The big picture looks like this:

class CSomeClass
{
    CCriticalSection m_cs;
    int m_nData1;
    int m_nData2;

public:
    void SetData(int nData1, int nData2)
    {
        m_cs.Lock();
        m_nData1 = Function(nData1);
        m_nData2 = Function(nData2);
        m_cs.Unlock();
    }

    int GetResult()
    {
        m_cs.Lock();
        int nResult = Function(m_nData1, m_nData2);
        m_cs.Unlock();
        return nResult;
    }
};

It's possible that at the same time two or more threads call SetData and/or GetData methods for the same object of CSomeClass type. Therefore, by wrapping the content of those methods, we will prevent the data from getting distorted during those calls.

Mutexes

Mutexes, like critical sections, are designated to protect shared resources from simultaneous accesses. Mutexes are implemented inside the kernel and thus they enter the kernel mode to operate. A mutex can perform synchronization not only between different threads but also between different processes. Such a mutex should have a unique name to be recognized by another process (such mutexes are called named mutexes). MFC represents CMutex class for working with mutexes. A mutex might be used in this way:

CSingleLock singleLock(&m_Mutex);
singleLock.Lock();  // try to capture the shared resource
if (singleLock.IsLocked())  // we did it
{
    // use the shared resource ...

    // After we done, let other threads use the resource
    singleLock.Unlock();
}

Or the same by Win32 API functions:

// try to capture the shared resource
::WaitForSingleObject(m_Mutex, INFINITE);

// use the shared resource ...

// After we done, let other threads use the resource
::ReleaseMutex(m_Mutex);

A mutex can also be used to limit the number of running instances by a single one. The following code might be placed at the beginning of InitInstance method (or WinMain):

HANDLE h = CreateMutex(NULL, FALSE, "MutexUniqueName");
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
    AfxMessageBox("An instance is already running.");
    return(0);
}

To guarantee a globally unique name, use a GUID instead.

Semaphores

In order to limit the number of threads that use shared resources we should use semaphores. A semaphore is a kernel object. It stores a counter variable to keep track of the number of threads that are using the shared resource. For example, the following code creates a semaphore by the MFC CSemaphore class which could be used to guarantee that only 5 threads at a maximum would be able to use the shared resource in a given time period (this fact is indicated by the first parameter of the constructor). It is supposed that no threads have captured the resource initially (the second parameter):

CSemaphore g_Sem(5, 5);

As soon as a thread gets access to the shared resource, the counter variable of the semaphore is decremented by one. If it becomes equal to zero, then any further attempt to use the resource will be rejected until at least one thread that has captured the resource leaves it (in other words, releases the semaphore). We may turn to CSingleLock and/or CMultiLock classes to wait/capture/release a semaphore. We could also use the API functions as shown below:

// Try to use the shared resource

::WaitForSingleObject(g_Sem, INFINITE);
// Now the user's counter of the semaphore has decremented by one

//... Use the shared resource ...

// After we done, let other threads use the resource
::ReleaseSemaphore(g_Sem, 1, NULL);
// Now the user's counter of the semaphore has incremented by one

Communication between Secondary Threads and the Primary Thread

If a primary thread wants to inform a secondary thread about some action, it is convenient to use an event object. But doing vice-versa will be inefficient and not convenient for users since stopping the primary thread to wait for an event may (and mostly does) slow down the application. In this case it would be correct to use user-defined messages to interact with the primary thread. Such a message should be addressed to a specific window which means that the descriptor of such a window should be visible to callers (secondary threads).

To create a user-defined message, we firstly should define an identifier for that message (more correctly - define the message itself). Supposedly, such an identifier should be visible to both the primary thread and secondary threads:

#define WM_MYMSG WM_USER + 1

WM_USER+n messages are supposed to be unique through a window class but not through the application. A more secure [in sense of its uniqueness] way is to use WM_APP+n messages like so:

#define WM_MYMSG WM_APP + 1

Next, a handler method should be declared for the message inside the window class declaration to which (window) the message is going to be addressed:

afx_msg LRESULT OnMyMessage(WPARAM , LPARAM );

Of course, there should be some definition of the method:

LRESULT CMyWnd::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
    // A notification got
    // Do something ...
    return 0;
}

And finally, to assign the handler to the message identifier, ON_MESSAGE macro should be used inside BEGIN_MESSAGE_MAP and END_MESSAGE_MAP pairs:

BEGIN_MESSAGE_MAP(CMyWnd, CWnd)
    ...

    ON_MESSAGE(WM_MYMSG, OnMyMessage)
END_MESSAGE_MAP()

Now a secondary thread having a window handle [that lives in the primary thread], can notify it by the user-defined message as follows:

UINT ThreadProc(LPVOID pParam)
{
    HWND hWnd = (HWND) pParam;

    ...

    // notify the primary thread's window
    ::PostMessage(hWnd, WM_MYMSG, 0, 0);

    return 0;
}

History

This text was first written more than three years ago. At that time I was a two-year old programmer. My intention was to write a book about MFC. Funny? But I was too young to write a book, and thus my chapters have stayed in my computer only. Now I've rewritten a text from there and submitted it to you. And of course, any note you think is worth suggesting about this essay would be appreciated very much.

About Arman Z. Sahakyan

21 mars

Manual Installation Instructions for Express Editions

This page provides instructions for manually installing an Express Edition.

Installation Options

  • The recommended option for installing Express Editions is to use the bootstrapper installer that is available via the "Download Now" link on each Express Edition page.
  • If the bootstrapper installer fails to install on your computer because of networking problems, or if you want to download a full copy of an Express Edition to burn on CD, then please follow the manual installation instructions listed below.
  • After following the manual installation instructions, please be sure to register your copy of Express. You'll get a lot of free stuff such as royalty-free images from Corbis, icons from IconBuffet, and e-books from Microsoft Press. Learn more about registration benefits.

File Packaging and Download Paths

Each Express Edition is packaged in its entirety, including all optional components, as an image (img or iso) file. Below are links to the individual image files for each Express Edition.

Product

Total Size

Download

CRC Number

Visual Web Developer 2005 Express Edition   

449,848 KB

.IMG File | .ISO File

F972C10F

Visual Basic 2005 Express Edition

445,282 KB

.IMG File | .ISO File

BAC91B78

Visual C# 2005 Express Edition

445,282 KB

.IMG File | .ISO File

55884F2C

Visual C++ 2005 Express Edition

474,686 KB

.IMG File | .ISO File

3DE23D4A

Visual J# 2005 Express Edition

448,702 KB

.IMG File | .ISO File

91B03EA5

Manual Installation Options

Manually Installing an Express Edition on a local machine

Below are the steps required to manually install an Express Edition:

Note: The steps below use the img file as an example

  • Before you get started, make sure to uninstall all pre-release (beta) Express software from your computer.
    You must uninstall all Visual Studio 2005 and Express betas before installing a final release of any Visual Studio 2005 Edition, including an Express Edition . You must also uninstall the beta version of the .NET Framework 2.0 before installing any Express Edition final release.
  • Download the Express Edition Image (img) file using the hyperlinks provided in the File Packaging and Download Paths section.
  • To install the image directly from your hard drive you will need to install 3rd party software that is capable of understanding *.img file extensions. Popular software programs like Roxio's Easy Media Creator include a Disk Image Loader application that enables you to mount disc image files in a virtual drive as if they were physical discs in a physical drive. For the example below, we will use Smart-Projects.net IsoBuster 1.8.
  • Be sure to set the tool to handle long file names. The default setting for many third party tools does not handle long file names. Some tools extract using the 8.3 file convention. Visual Studio 2005 setup requires the files to be in their long filename format or else setup will fail. This feature can usually be found in the Advanced settings. If you can not find the setting, please refer to the ISO extraction tool help documentation or contact the software vendor for assistance.
  • In the IsoBuster setup, make sure to select the "(*.IMG) Image files" file extensions to associate IsoBuster with img file extensions.

    IsoBuster setup options for associating file extensions.
  • After installing IsoBuster, you will notice that files with a .img extension are now associated to open with IsoBuster as shown below:

    Visual Web Developer 2005 Express Image file.
  • Double click on the img file to start IsoBuster. You should see something similar to the screenshot below with a list of files contained inside the img file in the right hand pane.

    List of files in the Visual Web Developer .img image.
  • Select all the files in the right hand side by clicking CTRL+A:

    Select all files in the Visual Web Developer .img image.
  • Right click on the selected files and you will see a context menu to extract the selected objects. Click the "Extract Objects" option in the context menu.

    Context menu for extracting files.
  • Next, select the folder to extract the contents of the image file as shown below and click OK.

    Browse for Folder dialog.
  • This will start the extraction process of the image files as shown below. Extracting the contents from the image file typically takes five minutes.

    Image extraction progress dialog.
  • After the extraction process is complete, navigate to the directory you chose to extract the files in the previous step and click the setup.exe file to begin the Express Edition installer.

    Setup icon to start Express installation.

If you encounter issues installing a Visual Studio Express Edition, please visit the Express Forums for more help.

Burning a Visual Studio 2005 Express Edition to a CD

Below are the steps required to burn an Express Edition to a CD.

  • Before you get started, make sure to uninstall all Express beta software from your computer.
    You must uninstall all Visual Studio 2005 and Express betas before installing a final release of any Visual Studio 2005 Edition, including an Express Edition . You must also uninstall the beta version of the .NET Framework 2.0 before installing any Express Edition final release.
  • Download the Express Edition Image (img or iso) file using the hyperlinks provided in the File Packaging and Download Paths section above.
  • To burn the image directly to a CD, you will need to install 3rd party software that is capable of understanding and burning image (img or iso) files. Most popular CD burning software include support for burning images, as do popular CD burning software such as Nero 6 Ultra Edition and Roxio Easy Media Creator 7.5.
  • Follow the vendor-specific instructions to burn the image onto a CD.

Installing an Express Edition from CD

  • After following the CD burning process above, you are ready to install the Express Edition on another computer.

    Important: Before getting started, you will need to perform a series of preparation steps on your machine if you ever installed a pre-release of Visual Studio 2005 (such as Beta 2) on your machine. Please follow these important instructions prior to installation.

  • Now simply insert the CD created above into the computer to which you plan to install and click the setup.exe file to begin the installer.

If you encounter issues installing the Express Editions, please visit the Express Forums and report your issue for more assistance.

VC项目文件说明

dsp 项目参数配置文件,这个文件太重要,重点保护对象。.
.dsw 工作区文件,重要性一般,因为它信息不我,容易恢复。
以下文件在项目中是可丢弃的,有些文件删除后,VC会自动生成的。
.clw ClassWizard信息文件,实际上是INI文件的格式,有兴趣可以研究一下.有时候ClassWizard出问题,手工修改CLW文件可以解决.如果此文件不存在的话,每次用ClassWizard的时候会提示你是否重建.
.ncb 无编译浏览文件(no compile browser)。当自动完成功能出问题时可以删除此文件。build后会自动生成。
.opt 工程关于开发环境的参数文件。如工具条位置等信息;(可丢弃)
.aps (AppStudio File),资源辅助文件,二进制格式,一般不用去管他.
.plg 是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大.在Tools->Options里面有个选项可以控制这个文件的生成.
.hpj (Help Project)是生成帮助文件的工程,用microsfot Help Compiler可以处理.
.mdp (Microsoft DevStudio Project)是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的DSP格式.
.bsc 是用于浏览项目信息的,如果用Source Brower的话就必须有这个文件.如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,可以加快编译速度.
.map 是执行文件的映像信息纪录文件,除非对系统底层非常熟悉,这个文件一般用不着.
.pch (Pre-Compiled File)是预编译文件,可以加快编译速度,但是文件非常大.
.pdb (Program Database)记录了程序有关的一些数据和调试信息,在调试的时候可能有用.
.exp 只有在编译DLL的时候才会生成,记录了DLL文件中的一些信息.一般也没什么用.

VC Studio 使用技巧大全

1.检测程序中的括号是否匹配

把光标移动到需要检测的括号(如大括号{}、方括号[]、圆括号()和尖括号<>)前面,键入快捷键"Ctrl+]"。如果括号匹配正确,光标就跳到匹配的括号处,否则光标不移动,并且机箱喇叭还会发出一声警告声。


2.查看一个宏(或变量、函数)的宏定义
把光标移动到你想知道的一个宏上,就比如说最常见的DECLARE_MAP_MESSAGE上按一下F12(或右键菜单中的Go To Defition Of …),如果没有建立Browse files,会出现提示对话框,确定,然后就会跳到定义那些东西的地方。


3.格式化一段乱七八糟的源代码
选中那段源代码,按ATL+F8。


4.在编辑状态下发现成员变量或函数不能显示
删除该项目扩展名为.ncb文件,重新打开该项目。


5.如何整理ClassView视图中大量的类
可以在classview 视图中右键新建文件夹(new folder),再把具有相近性质的类拖到对应的文件夹中,使整个视图看上去清晰明了
.

6.定位预处理指定
在源文件中定位光标到对称的#if, #endif,使用Ctrl+K.


7.如何添加系统中Lib到当前项目
在Project | Settings | Link | Object/library modules:输入Lib名称,不同的Lib之间用空格格开.


8.如何添加系统中的头文件(.h)到当前项目.
#include <FileName.h>,告诉编译到VC系统目录去找;使用#include "FileName.h",告诉编译在当前目录找.


9.如何在Studio使用汇编调试
在WorkBench的Debugger状态下按CTRL+F7.


10.怎样处理ClassZiard找不到的系统消息
如果要在ClassWizard中处理WM_NCHITTEST等系统消息,请在ClassWizard中Class Info页中将Message filter改为Window就有了.


11.如何干净的删除一个类
先从Workspace中的FileView中删除对应的.h和.cpp文件,再关闭项目,从实际的文件夹中删除对应的.h和.cpp文件与.clw文件。


12.如果让控制台应用程序支持mfc类库
可以在控制台应用程序中include 来引入mfc库,但是控制台应用程序缺省是单线程的,mfc是多线程的,为解决该矛盾,在project setting->c/c++ 选项,选择code generation,在use run-time library 下拉框中选择debug multithread。


13.如何汉化只有可执行代码的.exe 文件
在nt 下利用vc open file 以resources方式打开*.exe 文件,直接修改资源文件,然后保存即可。


附:VC项目文件说明
.opt 工程关于开发环境的参数文件。如工具条位置等信息;
.aps (AppStudio File),资源辅助文件,二进制格式,一般不用去管他.
.clw ClassWizard信息文件,实际上是INI文件的格式,有兴趣可以研究一下.有时候ClassWizard出问题,手工修改CLW文件可以解决.如果此文件不存在的话,每次用ClassWizard的时候绘提示你是否重建.
.dsp (DeveloperStudio Project):项目文件,文本格式,不过不熟悉的话不要手工修改.DSW(DeveloperStudio Workspace)是工作区文件,其他特点和DSP差不多.
.plg 是编译信息文件,编译时的error和warning信息文件(实际上是一个html文件),一般用处不大.在Tools->Options里面有个选项可以控制这个文件的生成.
.hpj (Help Project)是生成帮助文件的工程,用microsfot Help Compiler可以处理.
.mdp (Microsoft DevStudio Project)是旧版本的项目文件,如果要打开此文件的话,会提示你是否转换成新的DSP格式.
.bsc 是用于浏览项目信息的,如果用Source Brower的话就必须有这个文件.如果不用这个功能的话,可以在Project Options里面去掉Generate Browse Info File,可以加快编译速度.
.map 是执行文件的映像信息纪录文件,除非对系统底层非常熟悉,这个文件一般用不着.
.pch (Pre-Compiled File)是预编译文件,可以加快编译速度,但是文件非常大.
.pdb (Program Database)记录了程序有关的一些数据和调试信息,在调试的时候可能有用.
.exp 只有在编译DLL的时候才会生成,记录了DLL文件中的一些信息.一般也没什么用.
.ncb 无编译浏览文件(no compile browser)。当自动完成功能出问题时可以删除此文件。build后会自动生成。

10 mars

载入JPGE图象文件到DirectDraw的表面中(Loading JPEGs to DirectDraw Surfaces)

by Johnny Wood
译: sea_bug

译者的话:这是一篇关于使用Intel JPGEs Library的文章,在翻译的同时,译者根据自己的使用经验对文章进行适当的添减章节,希望适合各位读者。

In order to keep the size of this article down, I've decided to make a few assumptions. First of all, I assume that you already know C/C++ and how to troubleshoot and debug code. I also assume that you are somewhat familiar with DirectDraw and that you have as a minimum the DirectX 7.0 libraries and the ability to work in 24 bit. Note: the source code in EXAMPLE.ZIP available at the end of this article provides conversions to 16bit and 32bit surfaces. 
为了保持这篇文章内的排列顺序,我先决定安排一些假设。首先,我假设你已经了解C/C++与如何对代码进行调试。我还假设你对DirectDraw有些了解与你拿到了DirectX 7.0的库文件,并且能够在24bit的情况下工作。注意:本文章附带的原代码 EXAMPLE.ZIP中提供转换到16bit和32bit表面的操作。


The first step to loading JPEGs is to download the Intel JPEG Library from Intel's website. The Library is loaded with documentation and examples, all of which we're really not interested in. What we really want are the IJL.H, IJL15.LIB, and IJL15.DLL files that come with the package. Once you have those files, include the IJL.H header to 
your source file, add the IJL15.LIB file to your project, and make sure the IJL15.DLL is in a valid location such as the C:\WINDOWS\SYSTEM folder. 

要载入JPGE图象文件首先要Intel的网站上去下载Intel JPEG Library,这个库包含了开发文档和例程,以及你不感兴趣的东西。我们真正要的是IJL.H,IJL15.LIB,和IJL15.DLL文件。一旦你拥有了这些文件,包含IJL.H头文件到你的代码文件中,添加IJL15.LIB文件到你的工程, 并且确定IJL15.DLL文件是在有效的位置,如C:\Windows\Sysstem文件夹,当然,也可以跟我们编译出来的程式执行档放置于同一文件夹。


There are a few more things we need to do before beginning. We need to make sure that we have a Direct Draw Surface to work with:
有些东西需要我们在开始之前先准备好,我们需要确定我们拥有可工作的DirectDraw表面:

LPDIRECTDRAWSURFACE7 Surface = NULL;

We need to also be sure to set our display bit depth to 24 bit: 
我们还需要设置我们的视频模式,深度为24bit:

DDObject->SetDisplayMode(640, 480, 24, 0, 0);

We're now ready to load a JPEG to our surface. Since we're using the Intel JPEG Library, we need to create a couple of objects: 
我们现在准备载入JPEG图象到我们的表面,既然我们要使用Intel JPEG Library,我们需要建立一个连接对象:

IJLERR jerr;
JPEG_CORE_PROPERTIES jcprops;


IJLERR holds return information for determining a pass or fail status. 
JPEG_CORE_PROPERTIES is our JPEG object. Once we have these two objects, we are ready to initialize them: 

IJLERR保存返回的终止或错误属性信息。
JPEG_CORE_PROPERTIES是我们的JPEG对象,一旦我们有这两个对象,我们准备对其进行初始化:


jerr = ijlInit(&jcprops);
if (jerr != IJL_OK)
    //report initialization error


The ijlInit function call initializes the JPEG_CORE_PROPERTIES object. We can check the status of this function call by testing whether or not our IJLERR object was initialized with the value IJL_OK. 
ijlInit函数调用初始化JPEG_CORE_PROPERTIES对象,我们能检测这个函数调用测试我们的IJLERR对象是否初始化属性是否为IJL_OK。

At this point, we must decide if we are going to load our JPEG image from a file or from a buffer. Because loading from a file takes fewer steps, we will do that here. However, I give an example of loading from both in the EXAMPLE.ZIP file available at the end of this article. We load from a file by changing our JPEG object's JPGFile member to a file name. We then call ijlRead to retrieve the file parameters. 
在这点,我们必须决定我们是从文件载入我们的JPEG图象,还是从数据缓冲。因为从文件载入所需的步骤较少,我们将用这方法。无论如何,在文章的例子 EXAMPLE.ZIP中,我会给出两种可用的的方法。我们从文件中载入并转换我们的JPEG对象的JPG文件成员到一个文件名,我们当调用ijlRead函数可以重新获得文件参数。

jcprops.JPGFile = FileName;
jerr = ijlRead(&jcprops, IJL_JFILE_READPARAMS);
if (jerr != IJL_OK)
    //report read error


This initial read fills our JPEG object with information about the file we are going to load. What we must now do is find a way of converting the JPEG to a device independent bitmap (DIB) so that we can attach it to our Direct Draw surface. 
这初始指定我们的JPGE对象的文件名,我们将根据这个进行载入。我们现在必须寻找一个转换的方式用于JPGE设备与bitmap(BID),因此我们能绑定它到我们的DirectDraw表面。

We start by creating a buffer to hold our image data. After the buffer is created, we must resize it to fit a 24Bit image: 
我们开始建立一个缓冲为保存我们的位图数据,在这个缓冲建立之后,我们必须调整大小以适合一个24bit的位图:

//Prepare a 24Bit buffer to receive image data
BYTE *buffer24;

//Determine the required size
long szbuff24 = (jcprops.JPGWidth * 24 + 7) / 8 * jcprops.JPGHeight;

//Resize the buffer and check for null
buffer24 = new BYTE [szbuff24];
if (buffer24 == NULL)
    //Report memory allocation error


Now we need to fill in the DIB portion of the JPEG object to get ready for the conversion from JPEG to DIB. 
现在我们需要为JPEG对象准备好转换到BID的部分进行填充。

jcprops.DIBWidth = jcprops.JPGWidth;
jcprops.DIBHeight = jcprops.JPGHeight; //Implies a bottom-up DIB.
jcprops.DIBChannels = 3;
jcprops.DIBColor = IJL_BGR;
jcprops.DIBPadBytes = IJL_DIB_PAD_BYTES(jcprops.JPGWidth, 3);
jcprops.DIBBytes = reinterpret_cast <BYTE*> (buffer24);


Let's look at some of these a little closer. The DIBBytes member points to the buffer that we created. When we retrieve the JPEG data, the information we get will be stored in this buffer. The DIBWidth and DIBHeight members specify the size of the DIB. The DIBColor member specifies that we want our image data in reverse order Blue Green Red. That's the way that DIBs are actually stored. They are also stored upside down. You can flip the retrieved image by negating the DIBHeight member: 
让我们看看这一些结构,DIBBytes成员变量指向一个我们已经建立好的数据缓冲,当我们重新获得JPEG数据,这些信息将是我们用于存储的缓冲;DIBWidth和DIBHeight成员指定DIB的大小;DIBColor成员指定我们要我们的位图数据是倒序的兰、绿、红。那是DIBs实际存储的方式,他们也是颠倒存放,你可以翻转它:

//This is what you should do if you find your images are coming out upside down.
jcprops.DIBHeight = - jcprops.JPGHeight ;

Before we read in the image, we have to check one more thing: the JPG color space:
在我们读数据之前,我们还要检测其它东西:JPG颜色空间

//Set the JPG color space ... this will always be somewhat of an
//educated guess at best because JPEG is "color blind" (i.e.,
//nothing in the bit stream tells you what color space the data was
//encoded from.
switch(jcprops.JPGChannels)
{
    case 1: jcprops.JPGColor = IJL_G;
        break;

    case 3: jcprops.JPGColor = IJL_YCBCR;
        break;

    default:
        //This catches everything else, but no color twist will be
        //performed by the IJL.
        jcprops.DIBColor = (IJL_COLOR)IJL_OTHER;
        jcprops.JPGColor = (IJL_COLOR)IJL_OTHER;
        break;
}


We are finally ready to retrieve the actual JPEG image. Thanks to Intel's JPEG Library - this is a trivial task: 
我们准备最终获得JPEG图象数据,感谢Intel的JPEG库—这是一个十分简单的任务:

//Read in image from file
jerr = ijlRead(&jcprops, IJL_JFILE_READWHOLEIMAGE);
if (jerr != IJL_OK)
    //Report read error


This function copies the image information into our buffer. At this point, if we were to insert a BITMAPFILEHEADER and a BITMAPINFOHEADER at the front of our buffer, we could dump the buffer to a binary file. This would effectively create a bitmap file saved to disk. However, we instead want to turn our image into a DIB and attach it to a Direct Draw surface. Therefore, we use the Windows API function CreateBitmap to build our DIB:
这个函数拷贝位图信息到我们的缓冲,在这个地方,如果我们是插入一个BITMAPFILEHEADER或一个BITMAPINFOHEADER到我们的缓冲前面,我们可以颠倒缓冲到一个二进制文件中,这将在磁盘上建立一个有效的BMP图象文件。无论如何,我们插入要旋转我们的位图到一个DIB中,并把它关联到一个DirectDraw的表面上。因此,我们使用Windows API函数CreateBitmap建立我们的DIB:

HBITMAP hbm;

//Create the bitmap and get a handle to it
hbm = CreateBitmap (jcprops.JPGWidth, jcprops.JPGHeight , 1, 24, buffer24);
if(hbm == NULL)
    //Report failure to create bitmap


The CreateBitmap function takes the dimmensions of the image, the number of channels, the number of bits per pixel, and the color bit information from our bitmap buffer and creates a bitmap for us. Upon success, we are given a handle to the newly created bitmap. 
CreateBitmap函数的任务是为我们创建一个位图,包括通道的数量、像素的bit数量、颜色bit信息。在成功的基础上,我们获得一个新建立位图的句柄。

Before we go any further, we need to make sure that we have a Direct Draw surface to copy our bitmap to. Set up the Direct Draw surface description and create the surface: 
在我们进入更深层次之前,我们需要确认我们有一个用于我们位图拷贝的DirectDraw的表面,设置DirectDraw表面结构并且建立表面:

DDSURFACEDESC2 ddsd;

ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = jcprops.JPGWidth;
ddsd.dwHeight = jcprops.JPGHeight;

Result = DDObject->CreateSurface(&ddsd, &Surface, NULL);
if (Result != DD_OK)
    //Report surface creation error


Now, all that is left is to copy our bitmap over to our Direct Draw surface. Fortunately, there is a function provided by Direct Draw that does just that. It can be found in the DDUTILS.CPP file:
现在,拷贝我们的位图到我们的DirectDraw表面上,幸运的是,倘若在DirectDraw正好是一个函数,它能建立在DDUTILS.CPP文件里:


DDCopyBitmap(Surface, hbm, 0, 0, 0, 0);

Before we test our image out, let's clean up some things that we don't need any more:
在我们测试我们的位图前,让我们清理一些东西,那是我们不需要的:

//We no longer need our image buffer
delete buffer24;

//Release the JPEG object
ijlFree(&jcprops);

Finally, the time has come to take our image for a test drive:
最终,我们对我们的位图进行测试:

RECT Image;

//Reset surface description
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof( ddsd );

//Get the surface description so that we can dynamically
//find the width and height of our surface
Result = Surface->GetSurfaceDesc(&ddsd);
if (Result == DD_OK)
{
    //Coordinates of image size
    Image.left = 0;
    Image.top = 0;
    Image.right = ddsd.dwWidth;
    Image.bottom = ddsd.dwHeight;

    //Blit image to back buffer
    while (true)
    {
        Result = BackBuffer->BltFast (0, 0, Surface, &Image, DDBLTFAST_WAIT | DDBLTFAST_NOCOLORKEY);
        if( Result == DD_OK ) break;
        if( Result == DDERR_SURFACELOST )
        {
            Result = RestoreAll();
            if( Result != DD_OK ) break;
        }
        if( Result != DDERR_WASSTILLDRAWING ) break;
    }
}


If everything goes smoothly, you should see your image pop up on the screen. Keep in mind that you still have to release your surface when you no longer need it, and that you may have to restore it as a result of ALT+TAB. You can restore the surface by following these exact steps, however, you will not need to create the surface again. 
如果一切正常,你将要看到你的位图显示在屏幕上,当你不再需要它,你依然要释放你的表面,或者你在ALT+TAB的时候需要对表面进行恢复。你按照严格的不走就能恢复表面,当然,你将不需要再此建立表面。

Good luck, and have fun with JPEGs!
祝你好运,并希望使用JPEGs得开心!

Intel JPEG Library可以
到Intel官方网站上下载。