GDI+ Line Drawing Algorithm - c#

I cannot understand the way GDI+ is drawing line on a surface, may be it has some algorithm to do it.
For ex. lets take a surface 10x10 px.
Bitmap img = new Bitmap(10, 10);
Now lets draw a line on this surface, with width 5px and top offset 5px.
using (var g = Graphics.FromImage(img))
{
g.Clear(Color.White);
var pen = new Pen(Color.Brown);
pen.Width = 5;
g.DrawLine(pen, 0F, 5F, 10F, 5F);
}
We will get:
The drawing didn't begin at pixel #5, it began from pixel #4.
It is obvious, that the start point is calculated separately. But how?
I've tried to get a regularity, and got this:
y = offset + width/2 - 1
where y is real start point y, offset is selected start point y.
But in some cases this doesn't work. For example, lets take width=6, selected top offset = 0, we will get y=2, and it will be drawn this way:
It must show 6 pixels but it didn't.
So there must be more general algorythm for selecting the start point, but I really have no idea aboit what it can be.
Any help appreciated.

There is no offset in the line drawing. The co-ordinates you specify in the DrawLine method define the centre of the line. The top pixel is y - width / 2 and the bottom is y - width / 2 + width - 1. That second formula takes into account the fact that width / 2 is rounded down. Also, the top line is y = 0 and the bottom line is y = 9. So, for you first line:
top = 5 - (5 / 2) = 3
bottom = 5 - (5 / 2) + 5 - 1 = 7
and the second line:
top = 2 - (6 / 2) = -1
bottom = 2 - (6 / 2) + 6 - 1 = 4
The top edge is clipped to the edge of the bitmap so the line width is reduced.

In the first example, it looks like a line with a width of 5 pixels, centered on row 5 (counting starts at 0, not 1).
This seems like a reasonable outcome.
In the second example, it looks like a line of width 6, centered between rows 1 and 2, where the top row is cut off, because it extends beyond the borders of the image.

Coordinates in GDI+ don't designate the pixels itself but the (infinitly small) points at their center. Thus (0f,5f) means the center of the pixel in the first column and 6th row (since counting starts at zero). Therefore, I will differentiate between coordinates and pixel rows from now on.
When drawing a line, GDI+ conceptually moves a pen of the specified width along the line defined by these coordinates. Note that you can define the exact shape of this pen at the beginning and the end of the line by specifying Pen.StartCap and Pen.EndCap.
Since you specified width 5f and you're drawing a horizontal line, the line extends 2.5 pixels to the top and to the bottom thus covering complete pixel rows from #3 to (and including) #7. Note that the upper edge of pixel row #3 has a y-coordinate 2.5 and the lower edge of row #7 has 7.5 according to the above definition, which is 5f-2.5f and 5f+2.5f respectively
I do not get the same result as you for your second example (I tried in Try GDI+ application). Instead I get a line which covers the first three pixel rows. Theoretically, it should even cover the first 3.5 prows (since the coordinate designates the center of the first row, the upper part is simply clipped). But if antialiasing is turned off, the 'half row' at the bottom gets truncated.
This can be shown by setting g.SmoothingMode to SmoothingMode.AntiAlias in which case the fourth row is drawn transparently. When using antialiasing you'll also notice, that the first and last column are not completely painted since the start coordinate is, again, at the center of the column.

Related

How to change the coordinates of a text in a pdf page from lower left to upper left

I am using PDFBOX and itextsharp dll and processing a pdf.
so that I get the text coordinates of the text within a rectangle. the rectangle coordinates are extracted using the itextsharp.dll.
Basically I get the rectangle coordinates from itextsharp.dll, where itextsharp uses the coordinates system as lower left. And I get the pdf page text from PDFBOX, where PDFBOX uses the coordinates system as top upper left.
I need help in converting the Coordinates from lower left to upper left
Updating my question
Pardon me if you didn't understood my question and if not full information was provided.
well, Let me try to give more details from start.
I am working on a tool where I get a PDF in which a rectangle is drawn using some Drawing markups within a comment section. Now I am reading the rectangle coordinates using iTextsharp
PdfDictionary pageDict = pdReader.GetPageN(page_no);
PdfArray annotArray = pageDict.GetAsArray(PdfName.ANNOTS);
where pdReader is PdfReader.
And the page text along with its coordinates is extracted using PDFBOX. where as I have a class created pdfBoxTextExtraction in this I process the text and coordinate such that it returns the text and llx,lly,urx,ury "line by line" please note line by line not sentence wise.
So I want to extract the text that lays within the Rectangle coordinates. I got stuck when the coordinates of the rectangle returned from itextsharp i.e llx,lly,urx,ury of a rectangle has an origin at lower left where as the text coordinates returned from PDFBOX has an origin at upper left .then I realised I need to adjust the y-axis so that the origin moves from lower left to upper left. for the I got the height of the page and height of the cropbox
iTextSharp.text.Rectangle mediabox = reader.GetPageSize(page_no);
iTextSharp.text.Rectangle cropbox = reader.GetCropBox(page_no);
Did some basic adjustment
lly=mediabox.Top - lly
ury=mediabox.Top - ury
in some case the adjustment worked, whereas in some PDFs needed to do adjustment on cropbox
lly=cropbox .Top - lly
ury=cropbox .Top - ury
where as on some PDFs didn't worked.
All I need is help in adjusting the rectangle coordinates so that I get the text within the rectangle.
The coordinate system in PDF is defined in ISO-32000-1. This ISO standard explains that the X-axis is oriented towards the right, whereas the Y-axis has an upward orientation. This is the default. These are the coordinates that are returned by iText (behind the scenes, iText resolves all CTM transformations).
If you want to transform the coordinates returned by iText so that you get coordinates in a coordinate system where the Y axis has a downward orientation, you could for instance subtract the Y value returned by iText from the Y-coordinate of the top of the page.
An example: Suppose that we are dealing with an A4 page, where the Y coordinate of the bottom is 0 and the Y coordinate of the top is 842. If you have Y coordinates such as y1 = 806 and y2 = 36, then you can do this:
y = 842 - y;
Now y1 = 36 and y2 = 806. You have just reversed the orientation of the Y-axis using nothing more than simple high-school math.
Update based on an extra comment:
Each page has a media box. This defines the most important page boundaries. Other page boundaries may be present, but none of them shall exceed the media box (if they do, then your PDF is in violation with ISO-32000-1).
The crop box defines the visible area of the page. By default (for instance if a crop box entry is missing), the crop box coincides with the media box.
In your comment, you say that you subtract llx from the height. This is incorrect. llx is the lower-left x coordinate, whereas the height is a property measured on the Y axis, unless the page is rotated. Did you check if the page dictionary has a /Rotate value?
You also claim that the values returned by iText do not match the values returned by PdfBox. Note that the values returned by iText conform with the coordinate system as defined by the ISO standard. If PdfBox doesn't follow this standard, you should ask the people from PdfBox why they didn't follow the standard, and what coordinate system they are using instead.
Maybe that's what mkl's comment is about. He wrote:
Y' = Ymax - Y. X' = X - Xmin.
Maybe PdfBox searches for the maximum Y value Ymax and the minimum X value Xmin and then applies the above transformation on all coordinates. This is a useful transformation if you want to render a PDF, but it's unwise to perform such an operation if you want to use the coordinates, for instance to add content at specific positions relative to text on the page (because the transformed coordinates are no longer "PDF" coordinates).
Remark:
You say you need PdfBox to get the text of a page. Why do you need this extra tool? iText is perfectly capable of extracting and reordering the text on a page (assuming that you use the correct extraction strategy). If not, please clarify.
Note that we recently decided to support Type3 fonts, although we weren't convinced that this makes sense (see Text extraction is empty and unknown for text has type3 font using PDFBox,iText (difficult topic!) to understand why not).
What some consider "wrong extraction" can often be "wrong interpretation" of what is extracted as explained in this mailing-list answer: http://thread.gmane.org/gmane.comp.java.lib.itext.general/66829/focus=66830
There are other cases where we follow the spec, leading to results that are different than what PdfBox returns. Watch https://www.youtube.com/watch?v=wxGEEv7ibHE for more info.
if ((mediabox.Top - mediabox.Height) != 0)
{
topY = mediabox.Top;
heightY = mediabox.Height;
diffY = topY - heightY;
lly_adjust = (topY - ury) + diffY;
ury_adjust = (topY - lly) + diffY;
}
else if ((cropbox.Top - cropbox.Height) != 0)
{
topY = mediabox.Top;
heightY = cropbox.Top;
diffY = topY - heightY;
lly_adjust = (topY - ury) - diffY;
ury_adjust = (topY - lly) - diffY;
}
else
{
lly_adjust = mediabox.Top - ury;
ury_adjust = mediabox.Top - lly;
}
These are final adjustment done

How do I get rid of the outliers in this Point array?

Visual representation of the Point array (zoom to 800%): http://i.cubeupload.com/2Y3JPf.png
Please do not reupload to Imgur. Imgur's compression makes the image too blurry at high zooms.
(a zoomed in part of the image with the relevant dots highlighted:)
In the image, each red dot represents a Point. All the red dots combined represent the Point array. FYI, the Point array is ordered left to right, top to bottom. So pointArray[0] is in the top-left, pointArray[1] is in the top right, pointArray[2] is in the top-left but is also one line below pointArray[0] and pointArray[1].
As you can see, the majority of the red dots are in a grid but there are a couple of outliers in the top-left of the window and in the bottom-right where the number of mines are displayed. How would I get rid of the outliers? I've thought of parsing each point one by one to see if they are a constant number of pixels away from each other but at which point do I start? pointArray[0] is in the top-left so it's not even part of the grid which would mean that finding a constant pixel gap isn't going to work.
Any ideas?
Here is a method that should work for all grid sizes:
Count the number of dots in each row, and the number of dots in each column.
Then remove all dots in rows and columns that have less than the average number of dots.
You can construct such a histogram efficiently with a dictionary:
var numDotsPerRow = new Dictionary<int,int>();
var numDotsPerColumn = new Dictionary<int,int>();
foreach (var point in pointArray)
{
int count;
numDotsPerRow.TryGetValue(point.X, out count);
numDotsPerRow[point.X] = count+1;
numDotsPerColumn.TryGetValue(point.Y, out count);
numDotsPerColumn[point.Y] = count+1;
}

Rendering 1 pixel lines with DrawingVisual

I have seen several examples on rendering 1 pixel lines in WPF, but none seem to apply to my situation. I am using DrawingVisual and DrawingContext to draw some shapes and RenderTargetBitmap and PngBitmapEncoder to generate the image. In many cases the rectangles have a 2 pixel border even though I set it to 1. I am guessing this is due to the resolution independent rendering that is used.
I have found several solutions but they are either in XAML or apply to drawing controls. The closest thing I have found is XSnappingGuidelines/YSnappingGuidelines but I cannot find a single example of how to use it. The documentation is very much lacking on these properties.
How do I disable the resolution independent rendering for DrawingVisual?
UPDATE:
Here is what I am trying to do:
Declare a DrawingVisual:
DrawingVisual mainTemplate = new DrawingVisual();
Get Context:
using (DrawingContext context = mainTemplate.RenderOpen())
Draw rectangle:
penToUse = new Pen(new SolidColorBrush(Color.FromRgb(0xFF, 0xFF, 0xFF)), 1.0);
penToUse.DashStyle = DashStyles.Dash;
context.DrawRectangle(brushToUse, penToUse, new Rect(left, top, width, height));
Where do I set the rendering mode to align to pixels?
jorj
In WPF, when you draw a line, the line is centered on the coordinates you specify. So if on a device with 96 DPI you draw a vertical line from 10, 10 to 10, 20 and the width of the pen is 1, the line will actually be drawn between 9.5 and 10.5 taking two pixels. If you want to align the line on the pixel edge, you need to shift it by 0.5. On a 120 DPI monitor, the width of the line should be 0.8 to take a single pixel, and you need to shift it by 0.4 to align on the pixel edge.
You do not have to use GuidelineSet because it doesn't do more than this simple shifting but unnecessarily complicates the code.
The closest I've come to being able to render single pixels lines in WPF with a DrawingContext is this:
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(_bgRect.Left - 0.5);
guidelines.GuidelinesX.Add(_bgRect.Right + 0.5);
guidelines.GuidelinesY.Add(_bgRect.Top - 0.5);
guidelines.GuidelinesY.Add(_bgRect.Bottom + 0.5);
dc.PushGuidelineSet(guidelines);
dc.DrawRectangle(Background, _outlinePen, _bgRect);
if (BorderThickness.Left > 1)
dc.DrawLine(_leftPen, _bgRect.TopLeft, _bgRect.BottomLeft);
if (BorderThickness.Top > 1)
dc.DrawLine(_topPen, _bgRect.TopLeft, _bgRect.TopRight);
if (BorderThickness.Right > 1)
dc.DrawLine(_rightPen, _bgRect.TopRight, _bgRect.BottomRight);
if (BorderThickness.Bottom > 1)
dc.DrawLine(_bottomPen, _bgRect.BottomRight, _bgRect.BottomLeft);
dc.Pop();

Finding out max available print area

I'm trying to find out max available area on my printer. I've printed a simple rectangle trying out different boundary variables. My question is, why doesn't first two work correctly? They don't print a full rectangle on the paper, only the left and top sides are drawn. Why does only the third one prints a full rectangle? I was under the impression of that all three should be working correctly. What am I missing?
Here's my code:
this.printDocument1.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0);
...
private void PrintPage(object sender, PrintPageEventArgs e)
{
//Method 1, no right and bottom sides are printed
e.Graphics.DrawRectangle(new Pen(Color.Black, 1), e.PageBounds);
//Method 2, same as Method 1
e.Graphics.DrawRectangle(new Pen(Color.Black, 1), e.MarginBounds);
//Method 3, works correctly
e.Graphics.DrawRectangle(new Pen(Color.Black, 1), new Rectangle((int)e.Graphics.VisibleClipBounds.X, (int)e.Graphics.VisibleClipBounds.Y, (int)e.Graphics.VisibleClipBounds.Width, (int)e.Graphics.VisibleClipBounds.Height));
}
First one doesn't work because you are trying to print out of margins. Second one fails because you are trying to print over the margin, so the right and bottom lines fall 1 pixel off the bounds. Now 3rd one works IMO because, printing rectangle coordinates are floating point, and you are casting them to integers, thus rounding them down, so the rectangle falls inside the print area.
EDIT
Some additional info I found regarding your comment:
"If the Graphics object is using a nondefault PageUnit,[2] then VisibleClipBounds will be in different units than PageBounds (which is always in units of 100 dpi). To handle these variables, it's useful to have a helper method to return the "real" page bounds in a consistent unit of measure"
Check out this article , I believe it covers everything.
My first guess is that 2 is working with the margins you set previously (0,0,0,0) and that your printer doesn't actually support no-margin printing (most don't). Because of the fact that most printers require at least some margins, #1 will pretty much never work.
Method 3 appears to actually be querying the driver for the printable area of the page and then using that, so, it works.
As for why you get the top and left instead of nothing with #1 and #2, this is because you're just saying "print me a rectangle of these dimensions starting at the upper left hand corner of the printable area" not "print me a rectangle with these dimensions and start in the far upper left hand corner of the page where you can't actually print" so it's trying to, but it goes off the edge of the page since it's bigger than the printable area of the page.

LinearGradientBrush Artifact Workaround?

The LinearGradientBrush in .net (or even in GDI+ as a whole?) seems to have a severe bug: Sometimes, it introduces artifacts. (See here or here - essentially, the first line of a linear gradient is drawn in the endcolor, i.e. a gradient from White to Black will start with a Black line and then with the proper White to Black gradient)
I wonder if anyone found a working workaround for this? This is a really annoying bug :-(
Here is a picture of the Artifacts, note that there are 2 LinearGradientBrushes:
alt text http://img142.imageshack.us/img142/7711/gradientartifactmm6.jpg
I have noticed this as well when using gradient brushes. The only effective workaround I have is to always create the gradient brush rectangle 1 pixel bigger on all edges than the area that is going to be painted with it. That protects you against the issue on all four edges. The downside is that the colors used at the edges are a fraction off those you specify, but this is better than the drawing artifact problem!
You can use the nice Inflate(int i) method on a rectangle to get the bigger version.
I would finesse Phil's answer above (this is really a comment but I don't have that privilege). The behaviour I see is contrary to the documentation, which says:
The starting line is perpendicular to the orientation line and passes through one of the corners of the rectangle. All points on the starting line are the starting color. Then ending line is perpendicular to the orientation line and passes through one of the corners of the rectangle. All points on the ending line are the ending color.
Namely you get a single pixel wrap-around in some cases. As far as I can tell (by experimentation) I only get the problem when the width or height of the rectangle is odd. So to work around the bug I find it is adequate to increase the LinearGradientBrush rectangle by 1 pixel if and only if the dimension (before expansion) is an odd number. In other words, always round the brush rectangle up the the next even number of pixels in both width and height.
So to fill a rectangle r I use something like:
Rectangle gradientRect = r;
if (r.Width % 2 == 1)
{
gradientRect.Width += 1;
}
if (r.Height % 2 == 1)
{
gradientRect.Height += 1;
}
var lgb = new LinearGradientBrush(gradientRect, startCol, endCol, angle);
graphics.FillRectangle(lgb, r);
Insane but true.
At least with WPF you could try to use GradientStops to get 100% correct colors right at the edges, even when overpainting.
I experienced artifacts too in my C++ code. What solved the problem is setting a non-default SmoothingMode for the Graphics object. Please note that all non-default smoothing modes use coordinate system, which is bound to the center of a pixel. Thus, you have to correctly convert your rectangle from GDI to GDI+ coordinates:
Gdiplus::RectF brushRect;
graphics.SetSmoothingMode( Gdiplus::SmoothingModeHighQuality );
brushRect.X = rect.left - (Gdiplus::REAL)0.5;
brushRect.Y = rect.top - (Gdiplus::REAL)0.5;
brushRect.Width = (Gdiplus::REAL)( rect.right - rect.left );
brushRect.Height = (Gdiplus::REAL)( rect.bottom - rect.top );
It seems like LinearGradientBrush works correctly only in high-quality modes.

Categories