• Shortcuts : 'n' next unread feed - 'p' previous unread feed • Styles : 1 2

» Publishers, Monetize your RSS feeds with FeedShow:  More infos  (Show/Hide Ads)


Date: Friday, 15 Oct 2004 10:35

Keeping track of all of this stuff is getting confusing, but Project Distributor is helping out a bit by providing a great tool-box where I can upload my code and samples. I'll try and use it as often as possible, that way people can just jump over and view my group which allows them to take a look at my projects. I'll also be providing direct project links where applicable, and of course, I'll still be uploading source code to my articles sections since I love to be able to google search my own source code.

Project Distributor::MyGroup::Project for Math Demos

Author: "Justin Rogers" Tags: "WinForms, GDI+"
Comments Send by mail Print  Save  Delicious 
Date: Thursday, 07 Oct 2004 13:26

A newsgroup user was using GDI to do some image copying from the screen. Now, just about everyone working with GDI always asks the question of whether or not there is a better alternative in .NET and GDI+. Normally there is, and they just don't know about it, so you enlighten these users with the power of the platform. There aren't equivalent screen scraping API's so you have to use the all powerful BitBlt. Since the user wanted a .NET style version, I've tried to stay true to just using available API calls. Note that I'm using a Graphics.FromHwnd hack to get the desktop DC. Even though you can create a Graphics over top of the desktop DC, you still can't use this Graphics object to perform operations on the desktop (at least my latest tests have failed miserably).

[DllImport( "gdi32.dll", CharSet = CharSet.Auto,

SetLastError = true, ExactSpelling = true )]

public static extern int BitBlt(

    HandleRef hDC, int x, int y, int nWidth, int nHeight,

    HandleRef hSrcDC, int xSrc, int ySrc, int dwRop );

 

public static void CopyFromScreen(

    Graphics gfx, int xSource, int ySource,

    int xDest, int yDest, Size blockRegionSize ) {

 

    HandleRef src, dest;

    Graphics hDCDesktop = Graphics.FromHwnd( IntPtr.Zero );

 

    try {

        dest = new HandleRef( null, gfx.GetHdc() );

        src = new HandleRef( null, hDCDesktop.GetHdc() );

 

        int result = BitBlt(

            dest, xDest, yDest,

            blockRegionSize.Width, blockRegionSize.Height,

            src, xSource, ySource, 0xcc0020 );

 

        if ( result == 0 ) { throw new Exception(); }

    } finally {

        if ( dest.Handle != IntPtr.Zero )

            gfx.ReleaseHdc();

        if ( src.Handle != IntPtr.Zero )

            hDCDesktop.ReleaseHdc();

    }

}

Couple of notes. HandleRef? Not sure why they use a HandleRef if they aren't worried about finalizable handles... Probably so that the shared API has a common signature for other cases. I'll leave it in there. I've moved the creation of the refs into the try block in case one of the GetHdc calls fails. In Whidbey I think there may be a resource leak in this method where they aren't accounting for a failure between allocating the first DC and then getting the second DC (they have their refs outside of the try...finally block). Device context handles shouldn't be zero'ed if they are valid, so I check those in order to release the handles in the finally block. Something about the above method just doesn't sit well with me. Doesn't feel clean and safe... I'll get some sleep and think on it more later.
Author: "Justin Rogers" Tags: "GDI+"
Comments Send by mail Print  Save  Delicious 
Date: Saturday, 25 Sep 2004 09:26

I'm starting to get all excited now. Before I go too much into Rumors .NET, the idea isn't entirely mine. During one of my projects working at Microsoft I had some chats with an excellent engineer on the concepts of messaging and rumor systems within games. That engineer was Mark Gabarra, and I told him that if I was ever able to get the time to publish or produce anything on the subject, I'd give him his due attribution.

The Meetings
Can't really call them meetings, more short term chats. The idea is simple, in that the people remember things, events travel, and people talk about whatever might be on their mind. The dynamics can be complex or simple and we really wanted something simple at first just to play with. To sum this up in a kind of gaming scenario, imagine that a monstrous invasion has occured in some area of the world. At this point in time, some scouts or messengers are dispatched to send the news to another town. By the time those guys get to the town or are killed several things will have happened. They may be unintelligible, they may have crossed far too much distance for the people to even care, the event in the remote town may be over, etc... You can start to see where NPC propagation of messages is important. Now, in that context the messages were all defined, and well defined, in order to be passed, perhaps with some properties that define how future NPCs will interact with them. Here is where the disconnect came for me and I wasn't able to produce anything interesting because the system was only as good as you were willing to spend time tuning the message protocol.

The Ponderings
Now, I've been working on the idea for at least a year now in sketches and other forms. Probably still only spent 5-10 hours, but spread that over a long period of time where I may have also pondered it while in the shower or burning time in the car while going somewhere. The idea still has great warrant and potential, but I just needed to find the right thing to tweak the system and make it independent of system generated messages and more dependent on player generated messages, or something I'm going to call rumors. Rumors are awesome because they embody the natural laws of communication without making any assumptions. What are some common assumptions?

  1. How much do I trust this person? Trust is an assumption that a person is going to tell the truth or lie.
  2. Requirements of belief. This is when you can't afford not to believe something.
  3. Shared vocabulary. This is where you know what the person is talking about and share vocabulary and definitions of words others might not understand.

Rumors break all of these down. In general, every time you hear a rumor, you examine it with as much as possible. You don't trust the source, or the source of the source, but rather question them both. You aren't required to believe rumors, because they are something extra. They do required shared vocabulary and context, but only in trying to understand them. The beauty of a rumor is the ability to not understand it, still take something away from the conversation, and not have any negative impacts from it. You see, understanding the rumor is unimportant, and nobody cares if the (woman/man) that fell in the (mud/puddle/elevator/stairwell) was wearing a (red/pink/brick) colored (dress/shirt/blazer).... We listen to rumors in most cases for shear enjoyment, but we also tend to be more accurate, more conniving when we run across rumors that hit close to home, perhaps about people we know. And this is where the system comes in.

The System
Rumors .NET is going to come with 2 problem sets that you'll get to set for all of the individuals involved. Normally, you'll host a town, and you'll be responsible for passing rumors to people in the town. Depending on how you set up your players, things will either go well or not. To add to the chaos, people will visit your town every now and then to pick up rumors. Call these people bards, merchants, Jay Leno, they are just out to get the dirt and find out some funny things to take back home. In effect your rumors may evolve and spread across several systems. Simple for now, so don't expect much. I may improve the logic later. The first set of attributes I call memory control, and the second set are relay control. I'll go more into them later, but for now they are listed.

  1. Intelligence, Forgetfullness, Vocabulary, Photographic, Adaptability
  2. Gossip, Social, Banter, Reminisce, Importance, Certainty

These will be outlayed on a specially designed UI that I'm showing off to the right. You can see the diagram is a basic polar coordinates or star graph. The nodes with drag handles are able to be changed, and are unlocked. Once you get something perfect, you can choose to lock the node so you don't mess things up. Every attribute will take from 0.0 to 1.0f points and currently there is no maximum to the number of points available. Later this may get changed.

I accidentally clipped the text, but each point is labelled. I'm not sure yet if I should add some input controls for manually setting the attributes as well? Maybe you guys can weigh in on that. I'm also not quite sure that the model shown is going to be very usable, so maybe you can weigh in on that as well. One thing to imagine is that eventually you may be capped to the number of points you can spend. Every time you change an attribute, you might conversely drop the others by some amount. So imagine that dragging one up say .1 points, will drop the other 4 down by say .025 points. Hopefully that explains why the star graph is being used and why locking is important.

I have some other UI concepts, but I welcome any input. Each game is going to consist of up to 16 or so characters per town, each of which will get controlled by the shown graph (of which there will be one for memory and one for relay control). The game itself is a sleeper, meaning it will run in real-time, so you'll be able to check each day and see if anyone has told you any rumors. Or you can proactively talk with various townsfolk to get the rumors they've accumulated. Again, expect nothing more than incoherent banter at first, but I suspect as each townsfolk builds a small dictionary of vocabulary, that they are able to pass rumors with much more efficiently.

Author: "Justin Rogers" Tags: "WinForms, Games4 .NET, GDI+"
Comments Send by mail Print  Save  Delicious 
Date: Tuesday, 24 Aug 2004 08:59

There may already be a few entries on bounding regions that I posted many months ago. At one point I think I described some different types of bounding region scenarios including strict and relaxed fit scenarios. To add some math to this a strict region fit is going to inscribe the shape that it is bounding. We'll take a look at inscribing a number of shapes inside of a bounding circle. A relaxed fit reverses the equation with the region begin inscribed inside of the shape.

Inscribing Shapes
I actually gave this as a challenge to Darren, but didn't really tell him how much it would aid him later. Inscribed shapes can be reduced to interpolated points along the curve of the circle. For instance, an equilateral triangle is simply three points equally spaced around the inside of the circle. Being three points we can divide the 360 degrees by 3, get 120, and each point has to be 120 degrees apart. You can rotate the triangle anywhere in the circle by picking a different starting angle and then adding 120 and 240 degrees respectively when computing the remaining points.

startAngle = ???
offsetAngle = 360 / n
point0angle = startAngle + (offsetAngle * 0)
...
pointnangle = startAngle + (offsetAngle * n)

Computing the locations of each point using the previously defined trig methods is easy. Plot the points, draw lines between them, and what you get is a shape of some number of points where every point touches the circle and the shape is entirely contained within the circle. What about computing the bounding circle for a given shape? Well this is a bit different, especially if the shape isn't regular. But let's see what we can do.

First, you start by taking the distance between all of the points or extremes in the shape. For a triangle, you'll be computing three edges or the distance between all three points. The longest distance becomes your diameter or taken by one half and you get your radius. The center point of the bounding circle is the center point of the longest line. As you start to get larger numbers of points in the shape or polygon, the number of distance checks that must occur increases geometrically. For four points you'll have to do 6 distance checks and for five points you are up to 10.

; Bounding circle for a triangle
radius = max(d(p1, p2), d(p2, p3), d(p3, p1)) / 2
center = mid(p?, p?)

; Bounding helpers for other shapes
distance checks = n(n-1)/2

Now you should be able to inscribe any regular shape given a circle and then turn around an create a bounding circle given any regular or irregular polygon.

Circumscribing Shapes
When you circumscribe a shape, you generally draw it such that it contains a circle using the least bounding area. Every edge of the circumscribed shape is drawn tangent to the circle so that it brushes it at a single point. You might think this process is hard, but it is actually quite easy if you recall that any regular shape can be quickly and easily drawn within a circle. That means we simply need the radius of the inscribing circle for the circumscribing shape. Confused?

Don't be. Making this realization only makes our life easier. It means that if we find a single point for the circumscribing shape, we can use that point to find the rest of them with ease. Finding a single point involves making use of the trigonemtric sine rules that allow us to take any two angles and a side and get the length of the remaining sides. Take a look at the figure for getting the first point of a circumscribing square. We are using a square for now because it is pretty easy.

We start by making the following assertions. The angle from the center of the circle to two points on the circumscribing square is going to be 360 / n. This is called the internal angle and is the same angle we used earlier for inscribing shapes as well. Now, we extend the legs of a triangle beyond the radius of the circle until the third leg drawn opposte our internal angle touches the circle at only one point. The distance from the center to where the point touches is the radius or Cr. This will be important given information in a second.

Because the far leg is drawn tangent to the circle, the radius is perfectly perpindicular. That means the radius forms a right angle with the tangent line breaking our larger triangle T(C, P1, P2) into two triangles T(C, P1, T1) and T(C, P2, T1) where C is the center point, P1 and P2 are the points on the circumscribing square we are creating and T1 is the tangent point. By definition the radius also becomes a perpindicular bisector splitting that internal angle of ours from 360 / n in half to 360 / n / 2. The third angle or cross side angle is whatever is left of the 180 degrees allowed in a triangle. We now have three angles and a side. The following sin rules now apply:

sideA / sin(angleA) = sideB / sin(angleB) = sideC / sin(angleC)

In the example, sideA will be Cr, angleA will be the cross side angle and angle b will be 90 degrees. This reduces the sideB equation to:

Cr / sin(cross side angle) * sin(90) = sideB
sin(90) = 1
Cr / sin(cross side angle) = sideB

Frigin sweet. We have a basic equation that gives us the distance between the center of our circle to the first point in the circumscribing square. Use this distance as the radius of a new circle given the center of the original. Now, inscribe the rest of the square within this larger circle and you are done. You've just circumscribed the circle within a square. You could just as easily have used another shape though.

Cr / sin(90 - (Internal Angle / 2)) = distance to point
Cr = 100

; Triangle
100 / sin(90 - (120 / 2)) = 200
; Square
100 / sin(90 - (90 / 2)) = 141
; Hexagon
100 / sin(90 - (72 / 2)) = 123.6
; Pentagon
100 / sin(90 - (60 / 2)) = 115

This is some of the basic math behind creating anywhere from simple to complex bounding regions. You can see where this might come in handy if you have a complex shape that you might need to create a clipping region for. You can start by creating a bounding circle and then using it to circumscribe a bounding square that can be used for the clipping region.

Where this really comes into play is clipping systems for sprite functions. A simple or complex sprite can easily be examined to create a simple bounding region such as a circle. Depending on the application, this region may be too strict, so you can make it less strict by playing with some math and reworking the regions programmatically, rather than having to eyeball the final bounding regions. It's up to you, but hopefully this helps out a bit.

Author: "Justin Rogers" Tags: "Games4 .NET, GDI+, Quick Tips, Algorithm..."
Comments Send by mail Print  Save  Delicious 
Date: Saturday, 21 Aug 2004 10:29

[Sample Download on Project Distributor]

I previously covered laying out text along a linear path and a circular path. You can view that code here Math Installment #2: I needed some circular oriented text. I'll be referencing the code so you really need to check it out if you haven't. Not to mention there are some great diagrams explaining the process. Adding the ability to align text to a curvy or wavy path requires some additional math and some new insights over the basic circular and linear layouts covered earlier. You'll find that GDI+ transformations really help even if there is quite a bit of code left to write.

Wavy Text
Wavy text consists of a line along which the text is going to be oriented. The line is defined as before with a start and end point. We'll also be grabbing that angle again and applying a transformation to align the final text to the direction of the line. In addition to the linear layout process, you'll have to additionally use a circle or period to describe the wave. Since we already have the code for the circle we'll use the radius to define period and compute the circumference and arc radius distance for layout purposes. All of this is covered in that last article, so read up.

Wavy text has a number of layout requirements. First you have to transform and rotate each character according to the location on the wave where it appears. We'll use the same angle features as before where we update the angle based on the arc distance and width of the text. To orient the text we have to realize that the rotation is going to oscillate between -90 and 90 degress depending on the location upon the curve. With circular text the angle of rotation covers a full 360 degrees, not just the 180 degress that occurs when fitting to a wavy line. To keep the range in bounds we'll use the cosine function.

cos(180) = -1
cos(0) = 1
// The cosine function alternates between -1 and 1
cos(angle) * 90
// This bounds the oscillation between -90 to +90

With the rotation complete we have to orient the text in the appropriate location. This is not a circle and so some special layout logic has to be used to keep the text moving along the x-axis. First we compute the base distance. This is the current angle divided by 180. For every 180 degrees we've traveled we need to add Cr * 2 to the base offset. Without this the text won't move across the x-axis and will just bunch up and overwrite in place.

baseX = floor(realAngle / 180) * Cr * 2

Now, depending on if the wave is currently in a crest or trough we have to switch how we compute the wave or crest local x offset. If the current angle is less than 180 degrees (this will be a trough) then we take the Cos of the angle and then subtract it from 2 and multiply by the current radius. For the crest regions we forego the offset by 2.

localX = (2 - (cos(radians) + 1)) * Cr
localX = (cos(radians) + 1) * Cr
// Edit - Clamping radians removes this problem
cradians = (angle % 180) / 180f * Math.PI;
localX = (2 - (cos(cradians) + 1)) * Cr

The final transformation is (baseX + localX, sin(radians) * Cr). At this point you would have wavy text along the x-axis. We want the wavy text to be aligned to an arbitrary axis so we need to rotate about the angle of the original line which has already been computed based on the starting and ending points. The last offset moves our transformed and rotated text to the starting offset of the line. This is a fairly complex orientation. Follows is a bit of code that puts a GDI+ face on the entire process.

// Translate to the line position
pe.Graphics.TranslateTransform(Px1, Py1);

// Rotate to bring in line with the line
pe.Graphics.RotateTransform(angle);

// Place the item on the X axis based on the current angle
// Use the Sin to find the Y axis location
float xOffset = ((int) (realAngle / 180)) * Cr * 2;
float curvePlacement = 0;
if ( realAngle % 360 < 180 ) {
    curvePlacement = (float) ((2 - (Math.Cos(radians) + 1)) * Cr);
} else {
    curvePlacement = (float) ((Math.Cos(radians) + 1) * Cr);
}
pe.Graphics.TranslateTransform(
    (float) xOffset + curvePlacement,
    (float) (Math.Sin(radians) * Cr)
);

// Rotate the text about the appropriate transform
pe.Graphics.RotateTransform((float) (Math.Cos(radians) * 90f));

// Center the Text
pe.Graphics.TranslateTransform(-(thisChar.Width/2), -heightOffset);
pe.Graphics.DrawString(T[i].ToString(), this.Font, Brushes.Black, 0, 0);
pe.Graphics.ResetTransform();

There are many different improvements that could be made to this algorithm. Many of the computations could be made simpler by incremental arithmetic. For the most part the computations made for each character do make the algorithm quite robust to odd combinations of period and font-size. I'm not sure covering all your bases here is going to be nearly that important though.

Author: "Justin Rogers" Tags: "GDI+, Quick Tips, Algorithms"
Comments Send by mail Print  Save  Delicious 
Date: Friday, 20 Aug 2004 15:04

[Sample Download on Project Distributor]

Okay, back with math installment #2... This time I needed some circular oriented text, or rather I needed some path oriented text, but that code is much harder so I'm starting with something a bit easier. There is quite a bit of math involved with laying out text that follows any sort of curve or line for that matter. A line is the easiest thing to lay out text on.

Linear Textual Layout
Laying out text to follow a line is one of the easier problems we can solve. A line is defined by a start point (Px1, Py1) and an end point (Px2, Py2). Now the lines are going to be oriented in GDI+ space which means Y increases down instead of up. This flips the quadrant layout of the normal Cartesian Coordinate plane but it won't affect us much. The first step for us now is going to be finding the angle of the line. This will be used to rotate the text before it is translated into the final position.

The angle of the line is found using some trig work on the slope of the line. We need the slope in two parts. The following equations point out the important items.

int dx = x2 - x1;
int dy = y2 - y1;
int m = dy/dx; // Classically (y2-y1)/(x2-x1)

Next we'll use the arc tangent in order to retrieve the angle in radians. Some optimizations can be made at this step. First, if x is 0, then either the angle is 90, 270 or 0 depending on dy. Our second check is for dy of 0. This either means the angle is either 0 or 180 depending on the value of dx. Finally if both dx and dy are greater than 0 we have some computations to do. If dx is less than 0 we find the arc tangent of the slope of the line, turn it into degrees and add 180 (otherwise we leave off the 180). Let's see what that looks like:

angle = atan(dx/dy) / pi * 180 + ((dx < 0) ? 180 : 0);

With the angle in hand, all we need is to set up the GDI+ rendering pipeline. GDI+ translations are done in the reverse order that they are popped onto the stack of matrix operations. That means we'll start with a TranslateTransform to the pointed identified by (x1, y1), and then a RotateTransform about the angle. The angle needs to be munged to operate with GDI+. That means taking the negative of the angle and adding 90. That'll get the unit circle angle in-line with the GDI+ rotation angle.

pe.Graphics.TranslateTransform(Px1, Py1);
pe.Graphics.RotateTransform((-angle) + 90);
pe.Graphics.DrawString("Line Text", this.Font, Brushes.Black, 0, 0);
pe.Graphics.ResetTransform();

Circular Textual Layout
The circular layout isn't much different, except that we already have our angle and we have to go backwards to get the location. Start out with what you are given. We need to first define the circle. We'll need a center point at (Cx, Cy), and a radius (Cr). Because we have to lay each textual character we'll need to offset it by some different angle each time. We fine this special angle by first computing the circumference of the circle using 2piCr (Cc) and then finding the arc distance for a single degree dividing by 360 (Carcd).

// Center of the circle
int Cx = ClientSize.Width / 2;
int Cy = ClientSize.Height / 2;
// Radius
int Cr = (int) (Cx * 0.9f);
// Circumference 2(Pi)(Cr)
float Cc = (float) (Math.PI * 2 * Cr);
// Arc Distance for 1 degree
float Carcd = Cc / 360f;

The text will be just big enough to fill a circle when the ClientSize is set to 400, 400. That is a Windows Forms thing and not part of the math. You need to start somewhere so the angle where we begin will be 135 (Tsangle). As we iterate through each character we take it's measurement first. This helps in the layout by giving us something to transform on before the rotation to make sure the text is rotated about its center. Next we get the coordinates where the character will appear. This is done using the sin and cosine functions about the angle.

radians = angle / 180 * PI
xoffset = cos(radians) * Cr
yoffset = sin(radians) * Cr

With that in hand you apply the transforms in reverse order. This time we want to translate the text to center, rotate it about center, then translate to final position. We have our offsets, the size of the text, our angle, and the center of the circle making this an easy task. Remember the transforms are in reverse order as to how they are applied.

pe.Graphics.TranslateTransform(Cx + x, Cy + y);
pe.Graphics.RotateTransform((-Tsangle) + 90);
pe.Graphics.TranslateTransform(-(thisChar.Width/2), -(thisChar.Height/2));
pe.Graphics.DrawString(T[i].ToString(), this.Font, Brushes.Black, 0, 0);
pe.Graphics.ResetTransform();

My mixture of code and math is probably confusing, but I've tried to take out as many implementation details as possible. Unfortunately the rendering process is dependent on GDI+ and the order of matrix operations. As each character is rendered you'll have to update the angle. Remember that Carcd knows exactly the arc length of a single degree. We can update the current angle by turning the width of the character into degrees based on the arc length, then subtracting this from the current angle. That moves us clockwise around the shape.

Tsangle -= (charWidth / Carcd);

A more proper way to do this might be to update the angle based not only on your character's width but also the previous character's width. That would allow a kind of conjugate offset allowing combinations of thin and wide characters to look smoother. Currently this algorithm doesn't show any major issues on sufficiently large circular regions, but there might be larger issues on smaller regions.

How can you go about improving this algorithm? Well, it already allows for an arbitrary start position and arbitrarily long text, but it doesn't support any sort of cut-off features. You might want to add those if the text is larger than the circle. You can implement line shifting so that the bottom or top of the text is aligned to the path circle. Currently if you render a circle or ellipse it will cut through the text. Even more fun is the GraphicsPath text renderer. However, it takes quite a bit more math including the calculation of normals, interpolation, and smoothing. Maybe I'll save that for another math tutorial. I welcome comments on ways to improve the discussion (maybe some diagrams?) and I'll post some test source if enough people are interested.

Author: "Justin Rogers" Tags: "GDI+, Quick Tips, Algorithms"
Comments Send by mail Print  Save  Delicious 
Date: Sunday, 15 Aug 2004 11:45

Turns out most of the code I write that does deal with math is layout code in order to manage how a number of assets, whether it be controls or graphics, is rendered to the screen. Over the past couple of days the kind of code I have been writing is in managing the various kinds of transforms that can be applied to images in order to display them in a number of different ways. Turns out rendering an image has to do with several factors. The first factor is the size of the original image. This will mean the height and the width of the image. The second factor is the size of the target area that you are rendering it into. Based on these factors you get quite a few options for transforming the source into the target. The following diagram shows just a few of the options available (actually most of them).

Let's quickly sum up the options. For the first transformation from a larger image to a smaller area you can either stretch to fit (technically shrink to fit) where the ratio between the height and width may not be preserved, perform an aspect ratio fit where the ratio between the height and width are maintained, create a scrollable view area where only a portion of the image is viewed at a time based on user input, or a fixed offset view where you display a certain portion of the image but don't allow scrolling. This last item, the fixed offset view area is most often realized as a centering option that displays only a center region of the source image in the target.

Going in the opposite direction we have some of the same options, and some different ones. This time the stretch to fit is going in the other way and the image is growing to fit the target region creating. Previously we lost information in the translation. Losing information in working with images is not so bad because you can keep the best information. Creating new information which happens when you grow or stretch the image is bad because you have to interpolate or guess using some functional methods. The chances of guessing wrong is pretty high. Thankfully some techniques exist to fix this problem. In addition to stretch to fit the center option comes back. Now the image really is centered with a border, often called a bezel. The aspect ratio fit also works well.

Time for some math though.

Stretch To Fit

float widthRatio = targetWidth / imageWidth;
float heightRatio = targetWidth / imageWidth;

// Whether growing or shrinking, the ratio will send source metric
// to the target metric through multiplication
targetWidth == widthRatio * imageWidth;

Aspect Ratio Fit

float optimalRatio = Math.Min(widthRatio, heightRatio);

// Taking the smallest of the two ratios we guarantee both
// result metrics are less than or equal to the target metrics
targetWidth >= optimalRatio * imageWidth;
targetHeight >= optimalRatio * imageHeight;

// Normally you center aspect ratio fitted contents. We'll
// see the math for that in the Center section

Center

int left = (int) ((targetWidth - computedWidth) / 2);
int top = (int) ((targetHeight - computedHeight) / 2);

Now that you are introduce to a few of the options, let's check out some of the derivations. First, the ratio that you are using to multiply the source to the target is called the zoom factor. The zoom factor is often a useful display to the user telling them how accurate the display they are seeing is to the original image. If an image is scaled up to 2x or 200% it's normal size then the user might just think the image is bad, but knowing there is a zoom factor the user can quickly determine the display is not the same as the original.

Zoom Factor

int zoomPercentage = (int) (optimalRatio * 100); // 1.0 would display as 100, .50 would display as 50

One of the most useful display modes is the aspect ratio fit. When images are larger than the target you almost always want to use this method when shrinking them down to ensure the final image that you see is a good representative of the original image. When the images are smaller than the target, even this method creates anti-aliasing or growing distortion even though the aspect ratio is still maintained. Instead what you want is to clamp the aspect ratio fit so that the image will never grow.

Clamped Aspect Ratio Fit

// We've previously computed the optimalRatio, now we perform the clamp operation
optimalRatio = Math.Min(optimalRatio, 1.0f); // MAke sure we don't display the image larger than it was

Clamped Aspect Ratio Fit and Center

Rectangle targetRect = new Rectangle(
    (targetWidth - (optimalRatio * imageWidth)) / 2,
    (targetHeight - (optimalRatio * imageHeight)) / 2,
    optimalRatio * imageWidth,
    optimalRatio * imageHeight);

There are many more opportunities for mathematics in image layout. This article only covers some of the basics. One of the more interesting problems is selection rectangles over a zoomed image. You have to convert the region on the screen into an actual region back in the original image. The aspect ratios or zoom factors can be used to perform these conversions. The same applies if you are growing an image and have to update the scrollbars to reflect scrolling the new viewport region. I can probably package up some of this code if people are interested even though the PictureBox in Whidbey has support for most of these features. If you intercept the Image being set on the PictureBox you can dynamically change the settings in order to support all of the features described above.

Author: "Justin Rogers" Tags: "GDI+, Quick Tips, Algorithms"
Comments Send by mail Print  Save  Delicious 
Date: Saturday, 07 Aug 2004 10:11

I'm waiting, waiting, waiting, oh wait, it isn't there yet. As the releases go passing by I'm just not seeing the option to disable image data verification. This verification process is a huge detriment to various types of image management applications. If you don't believe me, question why nearly every image management application written using .NET features 4 larger thumbnails instead of 20 or 30 smaller ones. The graphics area used is pretty much the same, but 4 images load in under a second quite easily, while 20+ take a long time. Without image data verification stuff just loads 10x faster, and yes that is a tested number. Rather than load 4 images you can load 40... As you start to load a large number of images the ratio does change a bit because of memory usage, but for the most part you can at least load images 6 to 1.

Well, as we get closer and closer to a .NET 2.0 release I'll continue looking for the missing fast image loading that has so much impact on everything ranging from ASP .NET image processing filters to the cold load time of a Windows Forms application. Until then I guess I'll keep using my hack.

ImageFast: A stand-alone library for quickly loading GDI+ images without validation.

Author: "Justin Rogers" Tags: "GDI+, Rants, Whidbey"
Comments Send by mail Print  Save  Delicious 
Date: Wednesday, 21 Jul 2004 09:45

Okay, so I was checking out the VB PowerToys, and I have to say they are kind of nice. I can always appreciate a free download especially if it saves me some time. However, you don't reinvent the wheel when creating a new component and then append the word Power to it. The VB Power Toys are relying on a partial combination of Windows Forms, Windows Forms hacks, and P/Invoke calls, but even with a license to use such features they only use them in a half-arsed manner.

Currently, for the animation, the developer uses ShowWindow in order to get around the activation issues with Windows Forms, but then turns around and uses custom animation routines. Custom animation routines when working with Forms really suck because they involve a LOT of screen popping as you continuously redraw the form contents. This is not bad if you animate an empty window, called a representative, and then show the actual form with controls at the end. This reduces repainting, so at the very least I would recommend the Power Toys use this method.

Otherwise, take advantage of the system darnit. AnimateWindow, a well documented Win32 API, is capable of doing all of the animation available in the NotificationWindow with a minimum of code. I applaud some of the cool designer features for blends and what not, but not taking advantage of a built-in, and very fast API for animating the window (much faster than custom animation) is just not cool. Anyway, if you want your own “Toast” windows without all of the extra garbage all you really need to do is make a call to AnimateWindow over top of your normal forms. A sample Form that hides then shows using a fade, as well as taking advantage of a Region while doing it, can be found in the following snip:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class AnimateWindowShow : Form {
    [DllImport("user32.dll")]
    static extern bool AnimateWindow(IntPtr hwnd, uint dwTime, uint dwFlags);
   
    public AnimateWindowShow() {
        ShowInTaskbar = false;
        Click += new EventHandler(Form_Click);
    }
   
    private void Form_Click(object sender, EventArgs e) {
        GraphicsPath gp = new GraphicsPath(); gp.AddEllipse(ClientRectangle);
        this.Region = new Region(gp);
   
        AnimateWindow(this.Handle, (uint) 1000, (uint) 0x90000);
       
        DateTime end = DateTime.Now.AddSeconds(3);
        while(end > DateTime.Now) { System.Threading.Thread.Sleep(0); }

        AnimateWindow(this.Handle, (uint) 1000, (uint) 0x80000);
    }
   
    [STAThread]
    private static void Main(string[] args) {
        Application.Run(new AnimateWindowShow());
    }
}

The above isn't complete at all, and isn't meant to be. The point is you don't need a PowerToy for most of this stuff because the functionality is one API call away and not 50+ lines of animation code. In addition to the Show methods in the PowerToy, AnimateWindow actually has many additional settings. Not to mention it handles the whole activation logic with a simple flag that gets Or'ed in with the rest of the flags. Slide, Blend, Roll, swipes from center, horiztonally or vertically. It is a cool API and won't put a large hurting on your system.

Even better is the SystemParametersInfo where you can get information on whether users even have animations enabled by default. By doing some checks with this method you can get loads of information on tuning your UI to make sure it works best with the user's system.

Don't get me wrong, some of the PowerToys are excellent and cool. Just be aware of what you are getting and the alternatives. Maybe the developer didn't know about the AnimateWindow API, or possibly there were some issues I'm overlooking. I'll be interested in hearing about it. There are some notes on the API about when certain flags are supported, but it returns a bool true/false and lets you know if it succeeds, so you can check that and return appropriate error information.

Author: "Justin Rogers" Tags: "WinForms, GDI+, Quick Tips"
Comments Send by mail Print  Save  Delicious 
Date: Monday, 19 Jul 2004 21:56

BlogShares is a project that I'd love to tackle in switching over to an ASP .NET site. They are racked constantly with visitors, they have some fairly detailed view screens, they are constantly aggregating and changing data, and constantly pulling from a large listing of RSS feeds. Recently they've added some graphs that look better and they've been adding some new game rules and such. The site is a behemoth of possible performance improvements and features.

Looking at their graphs, they probably haven't seen Dundas Charts or any of the really nice graph facilities available to .NET programmers. I hate to say it, but if our graphs went to the same school as their graphs, we'd definitely tease them in the lunch room. I'm thinking performance must be a really large goal for the charting, but I wouldn't foresee any problems making both high quality, high performance charts.

Anyway, if you haven't checked out BlogShares, you shold go check it out. A kind of fantasy stock market in blogs. I've been a member for a while, but all I do is insider trade with my own blog as time permits.

Author: "Justin Rogers" Tags: "GDI+, Performance, ASP .NET, Personal"
Comments Send by mail Print  Save  Delicious 
Date: Sunday, 11 Jul 2004 21:47

Well, I wasn't planning on pointing this out, but I did know about it. You see, when I first glanced at this particular feature, I started playing with the performance characteristics and you know what I found? Well, it speeds up double buffering immensely over using a Bitmap, after all a compatible DC is a compatible DC and a darn Bitmap is what its name implies. Two different objects.

What kinds of immense speeds are we talking about here? Well, my asynchronous ListView control rendered it's elements more than twice as fast. That was a huge perf win for me. Couple that with the ImageFast library for loading pictures about 10x faster and you are talking about some very fast image rendering.

So far in the beta I'm not seeing the built in fast image loading support. That sucks, since there was a hot-fix library that enabled it for Windows Forms and the System.Drawing namespace on V1.1 that was available through PSS. And for some reason the GDI stuff has been completely hijacked out. I guarantee the Windows Forms guys are using it to enable much faster UI operations.

Now, what could be the reasons? Well, they could say security, but aren't there new friend assemblies to enable all of your pretty strongly typed assemblies to work together nicely without having to make everything publicly accessible for use between libraries? Seems like this concept would allow Windows Forms to easily access GDI features they needed in the fastest of ways, while allowing the rest of the world to access all of the methods with the immense number of security checks. Anyway, I'm just sour at this point for losing about 15 thousand lines of code that had been reworked to use the GDI stuff. An entire game was based on it in fact. It is really sad when the Solitaire on the old Win3x platforms was capable of stable animation and the same animation is actually difficult to achieve with the immense step in processor and OS technologies. Back to Managed DirectX yet again.

Here is the old posting: http://weblogs.asp.net/justin_rogers/archive/2004/05/01/124592.aspx

Author: "Justin Rogers" Tags: "WinForms, GDI+, Rants, Whidbey"
Comments Send by mail Print  Save  Delicious 
Date: Sunday, 13 Jun 2004 15:09

Okay, so the BlogX engine now has a security word.  Well, that is fine I  guess.  These strips take advantage of the ability of the human mind to process patterns and make out words in a distorted image.  So I'll start by saying, I've failed the recognition test 5 or 6 times on the same form before.  The whole darn process becomes guesswork as the images become more and more distorted and the spammers get more highly qualified processing software.  Eventually we won't be able to process the images ourselves and the test will be that if the correct answer is given, then the user must be a spammer.

Some issues I have with the pattern matching anti-spam measures.

  • False sense of security - I remember a few years ago while I was working at Microsoft, that one of the employees there had actually written a bot that was able to process the images, and submit entries.  If I recall the entires were somehow linked to getting a small payout (possibly Paypal?), and the security mechanism was in place to simply prevent users from submitting thousands of entries and therefore turning the small money into an actual pay-day.  Well, the false sense of security the company had in their system would have cost them dearly.
  • I can't read them half the time - Half the time I can't read them.  I actually wrote a small processing application that I will be using to post comments to Chris's blog from now on, since I couldn't read the image supplied to me.  Maybe this won't always be the case, but in the case of the word I was given, I simply couldn't read it.
  • They suck for International Users - The features require not only the human ability to pattern match, but also the human ability to understand a written language.  That means they suck for children who are capable of reading well-formed text, but not obfuscated text, they suck for international users that might not even understand english, and they must really be a kick in the groin for users that spend 5 years learning english only to find out they can't make out the words.  So much for all that money you spent on english classes.

Anyway, in the interest of getting rid of these devices I'll give the spammers a little start.  If they weren't using .NET and GDI+ before, then they should be.  After running the below, you still need an OCR program to pull out words.  However, I have another piece of code that I use for non transformed fonts (hence the wavy lines that a lot of the sites are starting to use) that involves caching a bunch of font data  and super-imposing it over the resulting text I get from something like the algorith below.  It takes about 15 seconds unoptimized and gives you an 80% chance of getting the word right.  If you hook it up to a dictionary, it'll add a dictionary look-up to see if the word is real, the problem there is they are starting to use random letters and numbers.  The key there is they always use the same letter number formatting, so you know where to look for numbers and where to pattern match for letters.  These in my opinion are completely inferior as they let me cut my sample matching to just numbers or letters.

using System;
using System.Drawing;
using System.Drawing.Imaging;

public class FilterWord {
    private static void Main(string[] args) {
        Image img = Image.FromFile(args[0]);
        int delta = 3;
       
        Bitmap b = new Bitmap(img.Width, img.Height);
        b.SetResolution(img.HorizontalResolution, img.VerticalResolution);
        using(Graphics gfx = Graphics.FromImage(b)) {
            gfx.DrawImage(img, 0, 0);
            gfx.Dispose();
        }
       
       
       
        // Clear Space
        for(int i = 0; i < b.Height; i++) {
            for(int j = 0; j < b.Width; j++) {
                // Top/Bottom third Check
                if ( i > (b.Height * .35) && i < (b.Height * .7) ) {
                    // Grayscale check
                    Color check = b.GetPixel(j, i);
                    if ( check.R == check.G && check.G == check.B ) {
                        // Color range check
                        if ( check.R > 10 && check.R < 100 ) {
                            continue;
                        }
                    }
                }
               
                b.SetPixel(j, i, Color.White);
            }
        }

        // Clear dots
        for(int i = 1; i < b.Height - 1; i++) {
            for(int j = 1; j < b.Width - 1; j++) {
                // Up 3
                Color check1 = b.GetPixel(j-1,i-1);
                Color check2 = b.GetPixel(j,i-1);
                Color check3 = b.GetPixel(j+1,i-1);
               
                // Mid
                Color check4 = b.GetPixel(j-1,i);
                Color check5 = b.GetPixel(j, i);
                Color check6 = b.GetPixel(j+1,i);
               
                // Down 3
                Color check7 = b.GetPixel(j-1,i+1);
                Color check8 = b.GetPixel(j,i+1);
                Color check9 = b.GetPixel(j+1,i+1);

                if ( check5.R < 255 ) {
                    if ( check2.R == 255 && check4.R == 255 && check6.R == 255 && check8.R == 255 ){
                        b.SetPixel(j, i, Color.White);
                    }
                } else {
                    int surroundingDots = 0;
                   
                    // Left Right
                    if ( check4.R < 255 && check6.R < 255 ) {
                        // surroundingDots++;
                    }
                    // Up Down
                    if ( check2.R < 255 && check8.R < 255 ) {
                        surroundingDots++;
                    }
                   
                    if ( surroundingDots > 0 ) {
                        b.SetPixel(j, i, Color.Black);
                    }
                }
            }
        }
       
        b.Save(args[1], ImageFormat.Bmp);
    }
}

Author: "Justin Rogers" Tags: "GDI+, Rants, Algorithms"
Comments Send by mail Print  Save  Delicious 
Date: Friday, 14 May 2004 07:46

Boy is that embarassing when your code only works on YOUR machine.  And then it doesn't always do that apparently either.  Well, I found the problem and fixed it, so you should be able to use ImageFast in a number of scenarios.  You can use it within console applications, it still works well with Windows Forms, and you can also use it from within ASP .NET.  There are so many reasons that you might want to load images 10 to 100 times faster than you can with the validating loaders of the Image class.

ImageFast: A stand-alone library for quickly loading GDI+ images without validation.

Author: "Justin Rogers" Tags: "GDI+, Performance"
Comments Send by mail Print  Save  Delicious 
Date: Friday, 14 May 2004 05:29

[Edit] Looks like the exception is actually from me being stupid and not realizing the full scheme for starting GDI+.  Turns out you have to call GdiplusStartup and GdiplusShutdown, something that System.Drawing does for you automatically.  Throwing this into the mix for my application below causes my NullReferenceException to disappear.  So what is causing the failure over in ASP .NET land that my friend was seeing?

I've been playing with my ImageFast library lately.  When I first used the library I compiled and loaded it into a project where I was also making use of the System.Drawing namespace.  The library just worked, quickly loaded files without all of the validation crap, and enabled a much more responsive application.  I tried importing the library into a stand-alone application today, and low and behold, it crashes out.  Here is a simple repro scenario for me:

using System;
using System.Runtime.InteropServices;

public class LockImageAndLoad {
    [DllImport("gdiplus.dll", CharSet=CharSet.Unicode)]
    public static extern int GdipLoadImageFromFile(string filename, out IntPtr image);

    private static void Main(string[] args) {
        IntPtr pimg = IntPtr.Zero;
        GdipLoadImageFromFile(args[0], out pimg);
    }
}

Doesn't make sense that that would fail, but it does.  I get a NullReferenceException which means GdipLoadImageFromFile isn't getting bound somehow.  I'm taking the binding is failing because it can't find gdiplus.dll, and why should it, since gdiplus.dll is one of those special SOB's that doesn't exist in C:\Windows\System32 and is instead buried under C:\Windows\WinSxS.

Hum, so it is possible that a policy file could get me access to the old GDI+ I thought.  I looked around for a policy file that would enable System.Drawing.dll to bind to GDI+ and I couldn't find one.  I've given up on that.  Just to prove System.Drawing.dll has some magical link, you can run the following code and it will work.

using System;
using System.Drawing;
using System.Runtime.InteropServices;

public class LockImageAndLoad {
    [DllImport("gdiplus.dll", CharSet=CharSet.Unicode)]
    public static extern int GdipLoadImageFromFile(string filename, out IntPtr image);

    private static void Main(string[] args) {
        Bitmap b = new Bitmap(500, 500);
   
        IntPtr pimg = IntPtr.Zero;
        GdipLoadImageFromFile(args[0], out pimg);
    }
}

I'll get to the bottom of this one way or another, but perhaps some others have experienced the problem.  At least one of my friends has actually seen the NullReferenceException appear on his web server when trying to use GDI+ through System.Drawing.dll.  That means even System.Drawing.dll might sometimes lose the magic required to resolve gdiplus.dll. 

Oddly enough basic fixes simply don't seem to work.  I can't add one of the GDI+ dll's to my path.  I can't simply drop it into my program's running directory.  Somewhere, something is happening that prevents me from gaining access to the library.  There may be some hot-fix issues, I've always thought that might be the case.  There are 3 versions of GDI+ installed in the WinSxS directories.  I'd love for someone to point out how gdiplus.dll is getting resolved through the PInvoke implementation, why placing dll's in the current directory don't get loaded, and any number of other strange things that I've pointed out.

Author: "Justin Rogers" Tags: "GDI+, CLR Internals"
Comments Send by mail Print  Save  Delicious 
Date: Monday, 03 May 2004 19:46

In my previous article If you could pick the reason why GDI should be .NET accessible, what would it be? I talked about the new GDI namespace.  Now, while I was searching and searching for a way to do screen captures on the GDI classes, they instead added the screen capture to the actual Graphics class.  I could go into why this is such a band-aid, but I won't.  Instead I'll show you how to use it, then talk about how it is a band-aid ;-)

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

public class ScreenCap {
    private static void Main(string[] args) {
        if ( args.Length < 1 ) {
            return;
        }
       
        string fileName = args[0];
        ScreenCapture(fileName);
    }
   
    private static void ScreenCapture(string fileName) {
        Bitmap bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
        Graphics gfx = Graphics.FromImage(bmp);
        gfx.CopyFromScreen(0, 0, 0, 0, bmp.Size);
        gfx.Dispose();
        bmp.Save(fileName, ImageFormat.Jpeg);
    }
}

The above code makes a screen capture.  It takes a while to load and run, which I don't find very amusing, but what the heck, I can't complain.  You can extend this with my previous article to show how you might first copy the screen, modify it, and then write it back out for a screen saver.  I'll do something along these lines soon, since I have a large amount of GDI+ filters written that would be cool to render animated over the desktop.

Now I want to talk about how much of a band-aid this is.  Any copy operation from the screen is doing a BitBlt somewhere.  A BitBlt can and should be allowed between ANY two surfaces, but they are limiting access to the copy operation to only screen surfaces, and not allowing blt's between offscreen surfaces.  Rather than give a generic method for copying from any hDC or device context, which would have been really nice, especially for DirectX + GDI interfacing, they instead give you an operation to only copy the entire screen.  I'll stop now.  Enjoy your screen capture code and watch for my upcoming screen saver section.

Author: "Justin Rogers" Tags: "WinForms, GDI+, Whidbey"
Comments Send by mail Print  Save  Delicious 
» You can also retrieve older items : Read
» © All content and copyrights belong to their respective authors.«
» © FeedShow - Online RSS Feeds Reader