I have a PDF with four pages. Two images on the first page, one on the second, and one on the third. When I retrieve the value of the image on the second page or fourth,, I get a negative height. I tried setting it to Absolute as a quick fix but the Y position of the image was still slightly off. Also, the height and positioning on page three was fine.
Update: So far, this only seems to be a problem with PDF's created in Google Docs.
My code to extract the PDF images was taken from this thread Using iText 7, what's the proper way to export a Flate encoded image?.
This is how I access the height
var currentPDFImageInfo = extractedImages[i];
var currentPDFImageMatrix = currentPDFImageInfo.RenderInfo.GetImageCtm();
float pdfImageWidth = currentPDFImageMatrix.Get(iText.Kernel.Geom.Matrix.I11);
How I retrieve the PDF image data
public static List<PDFImageInfo> ExtractImagesFromPDF(string filePath)
{
Reader = new PdfReader(filePath);
Document = new PdfDocument(Reader);
var strategy = new ImageRenderListener();
PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
for (int pageNumber = 1; pageNumber <= Document.GetNumberOfPages(); pageNumber++)
{
strategy.CurrentPageNumber = pageNumber;
parser.ProcessPageContent(Document.GetPage(pageNumber));
}
return strategy.ImageInfoList;
}
And of course the Strategy class
public class ImageRenderListener : IEventListener
{
public void EventOccurred(IEventData data, EventType type)
{
if (data is ImageRenderInfo imageData)
{
try
{
if (imageData.GetImage() == null)
{
Console.WriteLine("Image could not be read.");
}
else
{
var pdfImageInfo = new PDFImageInfo(CurrentPageNumber, imageData);
ImageInfoList.Add(pdfImageInfo);
}
}
catch (Exception ex)
{
Console.WriteLine("Image could not be read: {0}.", ex.Message);
}
}
}
public ICollection<EventType> GetSupportedEvents()
{
return null;
}
public int CurrentPageNumber { get; set; }
public List<PDFImageInfo> ImageInfoList { get; set; } = new List<PDFImageInfo>();
}
This is how I access the height
var currentPDFImageInfo = extractedImages[i];
var currentPDFImageMatrix = currentPDFImageInfo.RenderInfo.GetImageCtm();
float pdfImageWidth = currentPDFImageMatrix.Get(iText.Kernel.Geom.Matrix.I11);
This value is the height only under certain circumstances.
Some backgrounds: The contents of a PDF page are drawn by a sequence of instructions in some content stream. Some of these instructions can manipulate the so called current transformation matrix (CTM) which represents an affine transformation, i.e. some combination of a rotation, translation, mirroring, and skewing. Everything other instructions draw is manipulated by the CTM value at the time that instruction is executed.
When a bitmap image is drawn, it is conceptually first reduced to a 1×1 square which then is transformed by the CTM to the final form of the image on the page.
If the image is displayed upright, no rotation or anything else involved, then indeed the I11 value is the width of the displayed image and the I22 value is the height. The I12 and I21 values are 0 then
But often bitmaps are displayed at 90° clockwise or counterclockwise (e.g. because someone held the camera at an 90° angle while shooting). In these cases I11 and I22 are 0 while I12 and I21 are the height and width respectively, with one or the other having a negative sign depending on the direction of the rotation.
If the bitmap is rotated by 180°, I11 and I22 again contain width and height, but both with a negative sign. If it's mirrored along the x axis or the y axis, one of them is negative.
And if the transformation is something else, e.g. a rotation by an angle that's not a multiple of 90°, finding the height and width becomes more complicated.
Actually then it is not even clear what height and width of the skewed, rotated, and mirrored form shall mean.
Thus, as a start please define which values you exactly are after; based on that you can try and determine them from arbitrary transformation matrices.
Another possible cause for unexplainable coordinate data for pages after the first one is that your code re-uses the PdfCanvasProcessor for each page without resetting:
var strategy = new ImageRenderListener();
PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
for (int pageNumber = 1; pageNumber <= Document.GetNumberOfPages(); pageNumber++)
{
strategy.CurrentPageNumber = pageNumber;
parser.ProcessPageContent(Document.GetPage(pageNumber));
}
This causes the graphics state at the end of one page incorrectly to be used as starting graphics state of the next one. Instead you should either use a new PdfCanvasProcessor instance for each page or call parser.Reset() at the start of each page.
Related
For educational purposes, I'm trying to create something called a, "color detection" bot. I am trying to make Array of Point objects where colors I specify match at the exact location so that the program can "click" every single color it detects that matches. So if you want it to detect, red, green and yellow, I want it to click all of those in whatever order it finds them as long as they're part of the specified array.
I want it to add ANY match that fits the array of hexadecimal colors I've passed into the function.
It only works partially even if the same color is visible in multiple areas and doesn't add every color that matches on the screen to the array. It leaves so many colors that match, out. On top of that, it's very very slow to the point where you think the program froze.
I also wish I could add a color tolerance, so colors close to the hexadecimal would be accepted.
To paint a quick picture, I want the bot to click on specific objects in an inventory within a game that match the colors specified.
I've tried for loops inside and out for my hexadecimal array in attempts to try force the program to check each pixel for each color I've specified.
Here's part of my function that calls onto it
//Check what the current action is
if (action.ToLower().Contains("find colors and click"))
{
//Make sure to split up the action that looks like this: "Find Colors and Click|#3E5657 #867E7E #957D7C"
string[] splitString = action.Split('|');
//Make an array of Hexadecimal codes
string[] splitHexes = splitString[1].Split(' ');
//Create an Array of Points where SearchPixels detected a match
Point[] points = SearchPixels(splitHexes);
//Make sure it's not empty
if (points != null)
{
//Iterate through each point so we can click at the location of each point where every color we specified was detected even if it was detected many many times
foreach (Point point in points)
{
//Make sure the current point isn't empty
if (!point.IsEmpty)
{
//Move the mouse while updating the status
Status("Moving mouse to " + point.X + " " + point.Y + ". Waiting...");
SetCursorPos(point.X, point.Y);
Status("Moved mouse to " + point.X + " " + point.Y + ". Waiting...");
//Pause the thread for a moment so it doesn't spam click
new System.Threading.ManualResetEvent(false).WaitOne(250);
//Click while updating the status
Status("Clicked. Waiting...");
DoMouseClick();
}
}
}
Here's the code where the "magic" happens. I'll post both iterations that don't seem to work as they skip over so many colors that match or it doesn't even detect them. I'm not sure.
private Point[] SearchPixels(string[] hexcodes)
{
// Take an image from the screen
// Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); // Create an empty bitmap with the size of the current screen
Bitmap bitmap = new Bitmap(SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height); // Create an empty bitmap with the size of all connected screen
Graphics graphics = Graphics.FromImage(bitmap as Image); // Create a new graphics objects that can capture the screen
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size); // Screenshot moment → screen content to graphics object
// Create our list of Points that we will eventually return
List<Point> points = new List<Point>();
// Go one to the right and then check from top to bottom every pixel (next round -> go one to right and go down again)
for (int x = 0; x < SystemInformation.VirtualScreen.Width; x++)
{
for (int y = 0; y < SystemInformation.VirtualScreen.Height; y++)
{
// Get the current pixels color
Color currentPixelColor = bitmap.GetPixel(x, y);
// Finally compare the pixels hex color and the desired hex color (if they match we found a pixel)
// Go through each hex code to see if it matches the current pixel as it's one of our desired pixels
foreach (string str in hexcodes)
{
// Get the desired pixel color from the current hexcode
Color desiredPixelColor = ColorTranslator.FromHtml(str);
if (desiredPixelColor == currentPixelColor)
{
// Found Pixel - Now set the location and add it to the array
Point currentPoint = new Point(x, y);
// Make sure it isn't a duplicate.. I wish I could make it not add anything too close either
if (!points.Contains(currentPoint))
{
// Add the current point to the array
points.Add(currentPoint);
}
}
}
}
}
// Return the array
return points.ToArray();
}
Here's the second version of it
private Point[] SearchPixels(string[] hexcodes)
{
// Take an image from the screen
// Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); // Create an empty bitmap with the size of the current screen
Bitmap bitmap = new Bitmap(SystemInformation.VirtualScreen.Width, SystemInformation.VirtualScreen.Height); // Create an empty bitmap with the size of all connected screen
Graphics graphics = Graphics.FromImage(bitmap as Image); // Create a new graphics objects that can capture the screen
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size); // Screenshot moment → screen content to graphics object
// Create our list of Points that we will eventually return
List<Point> points = new List<Point>();
// Go through each hex code to see if it matches the current pixel as it's one of our desired pixels
foreach (string str in hexcodes)
{
// Get the desired pixel color from the current hexcode
Color desiredPixelColor = ColorTranslator.FromHtml(str);
// Go one to the right and then check from top to bottom every pixel (next round -> go one to right and go down again)
for (int x = 0; x < SystemInformation.VirtualScreen.Width; x++)
{
for (int y = 0; y < SystemInformation.VirtualScreen.Height; y++)
{
// Get the current pixels color
Color currentPixelColor = bitmap.GetPixel(x, y);
// Finally compare the pixels hex color and the desired hex color (if they match we found a pixel)
// Go through each hex code to see if it matches the current pixel as it's one of our desired pixels
if (desiredPixelColor == currentPixelColor)
{
// Found Pixel - Now set the location and add it to the array
Point currentPoint = new Point(x, y);
// Make sure it isn't a duplicate.. I wish I could make it not add anything too close either
if (!points.Contains(currentPoint))
{
// Add the current point to the array
points.Add(currentPoint);
}
}
}
}
// Return the array
return points.ToArray();
}
I'm expecting the bot to detect and return every single point on the screen that matches the color of any hexadecimal color inside the array of hexadecimals I've provided. It just doesn't do that. It matches maybe 1-5, then it gives up. Sometimes it only detects one color of the three provided, or sometimes only two.
As an example: Even if there are 28 objects of the same color in the inventory, it might only find and provide an array of 8 Points instead of 28.
I also want it to find these coordinates really fast as I don't want the program to be slow.
I'm super frustrated and I thank you for your help.
Edit: I have indeed debugged through my code multiple times.
I'm trying to get this c# function from within a asp.netcore razor project using itext7 (7.1.7) to output a div containing text that scales up to within the constraints given with height and width. However, for some reason it seems there's some kind of hidden margin that will not scale the text to the boundaries of the given width and height, even though the margins are set to 0.
At the moment the maximum text i get is only half of what it should be.
Also, the paragraph, nor the div is actually set to the min width and min height.
Any help how to fix this is appreciated
public Div MakeScaledTxtDiv(string _displayTxt ,PdfFont _Pdffont, float _maxHeight,
float _maxWidth, Canvas _canvas, bool _showBorder)
{
Div nameDiv = new Div();
iText.Kernel.Geom.Rectangle posRect = new iText.Kernel.Geom.Rectangle(_maxWidth,_maxHeight);
Paragraph txtPara = new Paragraph()
.SetVerticalAlignment(VerticalAlignment.MIDDLE)
.SetTextAlignment(iText.Layout.Properties.TextAlignment.CENTER);
if (_showBorder)
txtPara.SetBorder(new DashedBorder(1));
if (_maxHeight > 0)
txtPara.SetMaxHeight(_maxHeight);
if (_maxWidth > 0)
txtPara.SetMaxWidth(_maxWidth);
txtPara
.SetMargin(0f)
.SetPadding(0f)
.SetFont(_Pdffont)
.Add(_displayTxt);
float fontSizeToSet = 1;
float fontSizeMax = 42;
float curFontSize = fontSizeMax;
bool m_bGoodfit = false;
while (!m_bGoodfit)
{
txtPara.SetFontSize(curFontSize);
ParagraphRenderer renderer = (ParagraphRenderer)txtPara.CreateRendererSubTree().SetParent(_canvas.GetRenderer());
LayoutContext context = new LayoutContext(new LayoutArea(1, posRect));
var fits = renderer.Layout(context).GetStatus();
if (fits == LayoutResult.FULL)
{
fontSizeToSet = curFontSize;
m_bGoodfit = true;
}
else
{
curFontSize -= 1;
if (curFontSize == 1)
{
fontSizeToSet = curFontSize;
m_bGoodfit = true;
}
}
}
txtPara.SetFontSize(fontSizeToSet);
if (_maxHeight > 0)
{
nameDiv.SetMinHeight(_maxHeight)
.SetMaxHeight(_maxHeight)
.SetHeight(_maxHeight);
}
if (_maxWidth > 0)
{
nameDiv.SetMinWidth(_maxWidth)
.SetMaxWidth(_maxWidth)
.SetWidth(_maxWidth);
}
nameDiv
.SetMargin(0f)
.SetPadding(0f)
.SetVerticalAlignment(VerticalAlignment.MIDDLE)
.SetTextAlignment(iText.Layout.Properties.TextAlignment.CENTER);
if (_showBorder)
nameDiv.SetBorder(new DottedBorder(1));
nameDiv.Add(txtPara).SetVerticalAlignment(VerticalAlignment.MIDDLE);
return nameDiv;
}
What worked for me was to change the multiplier number 0.7f slightly bigger in the code below (code referenced from https://stackoverflow.com/a/57929067/5490413).
It seems as that multiplier gets bigger/smaller, the space between the text and field bottom border grows/shrinks accordingly. Maybe start with 1.2f and find one that fits.
Paragraph linePara = new Paragraph().Add(lineTxt)
.SetTextAlignment(iText.Layout.Properties.TextAlignment.CENTER).SetBorder(new DottedBorder(1))
.SetMultipliedLeading(0.7f);
lineDiv.Add(linePara);
FYI I came across the exact issue you were facing: How to scale text within a fixed rectangle with itext7?
I tried with the answer posted there https://stackoverflow.com/a/57929067/5490413 and faced the same hidden bottom margin issue here.
I am implementing another app that makes use of stamping using iTextsharp 7. I've had this issue on multiple instances. My stamping code resembles the following:
public Stamper(PdfDocument _doc, Rectangle _rect)
{
doc = _doc;
Location = _rect;
}
public void StampPDF()
{
PdfDocument srcDoc = new PdfDocument(new PdfReader(Settings.Default.DefaultAppearance.SignStamp));
// Create FormXObject and Canvas
PdfFormXObject page = srcDoc.GetFirstPage().CopyAsFormXObject(doc);
//Extract Page Dimensions
float xWidth = srcDoc.GetFirstPage().GetCropBox().GetWidth();
float xHeight = srcDoc.GetFirstPage().GetCropBox().GetHeight();
//Better safe than sorry....
double ratio = xHeight / xWidth;
ratio = (float)ratio;
float scaledXHeight = Location.GetHeight();
float scaledXWidth = (float)(scaledXHeight / ratio * 1.0);
//Rectangle location = new Rectangle(crop.GetLeft(), crop.GetBottom(), scaledXWidth , scaledXHeight );
PdfStampAnnotation stamp = new PdfStampAnnotation(Location).SetStampName(new PdfName(Settings.Default.DefaultAppearance.SignName+" Signature"));
PdfCanvas canvas = new PdfCanvas(doc.GetFirstPage().NewContentStreamBefore(), doc.GetFirstPage().GetResources(), doc);
// canvas.AddXObject(page,location.GetLeft(),location.GetBottom(),page.GetWidth());
stamp.SetNormalAppearance(page.GetPdfObject());
stamp.SetFlags(PdfAnnotation.PRINT);
doc.GetFirstPage().AddAnnotation(stamp);
srcDoc.Close();
Noting that I am merging another PDF as my stamp. Once I try to create a Rectangle to a specific slot, my stamp ends up being either OVER-STRETCHED , or ROTATED 90 degrees. I suspect that it is a consequence of the merged PDF having a rotation that is non-zero. However, even setting it's rotation to 0 doesn't fix the problem. Moreover, the issue is exacerbated if the page I am stamping on is rotated, using a reference point like the CropBox simply fails and stamps in an unintended area and orientation.
EDIT: Updated code to reflect my exact situation. The function is receiving and already existing (hardcoded) Rectangle called Location and is expected to stamp inside it while maintaining the correct proportions.
How do you compensate for the rotation issue?
I'm creating a gannt chart to show hundreds of calendars for individual instances of orders, currently using an algorithm to draw lines and rectangles to create a grid, the problem is I'm the bitmaps are becoming far to large to draw, taking up ram, I've tried multiple different methods including drawing the bitmaps at half size and scaling them up (comes out horribly fuzzy) and still to large.
I want to be able to draw SVGs as I figure for something that draws large simple shapes should reduce the size dramatically compared to bitmaps.
the problem is I cant find anything on msdn that includes any sort of c# library for drawing svgs and I dont want to use external code.
Do I need to create It in XAML or is there a library similar to how bitmaps are drawn ?
Windows Forms = GDI / GDI+
WPF/XAML = DirectX (where possible)
Best bet is to go with WPF/XAML which supports scalable vector graphics (not the same as the .svg file format)
You will need 3rd party code to do SVG in WinForms.
If you are sticking with WinForms, then bitmapping is the only way this can be achieved really. Take a look at PixelFormat - you might be able to reduce the size of your bitmap in memory by using a format which requires fewer bits-per-pixel for example.
There is no need to use external tools or SVGs. With a bit of simple math you can easily just render the necessary parts you want to display. All you need is to know the grid size, the range of dates and the range of line-items that are visible in your view. Let's call them:
DateTime dispStartDate;
DateTime dispEndDate;
int dispStartItem;
int dispEndItem;
int GridSize = 10; //nifty if you'd like a magnification factor
Let's also say you have a class for a Gantt chart item:
class gItem
{
DateTime StartDate{ get; set; }
DateTime EndDate{ get; set; }
int LineNumber{ get; set; }
int Length { get { return EndDate - StartDate; } }
//some other code and stuff you'd like to add
}
Now you need a list containing all of your Gantt chart entries:
List<gItem> GanttItems;
By now you should have assigned values to each of the above variables, now it's time to generate a list of rectangles that would be visible in the view and draw them:
List<Rectangle> EntryRects = new List<Rectangle>();
void UpdateDisplayBounds()
{
foreach(gItem gEntry in GanttItems)
{
if(gEntry.StartDate < dispEndDate && gEntry.EndDate > dispStartDate
&& gEntry.LineNumber >= dispStartItem && gEntry.LineNumber <= dispEndItem)
{
int x = (gEntry.StartDate - dispStartDate) * GridSize;
int y = (gEntry.LineNumber - dispStartItem) * GridSize;
int width = gEntry.Length * GridSize;
int height = GridSize;
EntryRects.Add(new Rectangle(x, y, width, height);
}
}
}
Now you have a list of rectangles only within the display bounds which you can render. So let's draw:
void DrawRectangles(Graphics canvas)//use a picturebox's graphics handler or something for the canvas
{
canvas.Clear(this.BackColor);
using(SolidBrush b = new SolidBrush(Color.Blue)) //Choose your color
{
foreach(Rectangle r in EntryRects)
{
canvas.FillRectangle(b, r);
}
}
}
The above code should get you started. With this you have a list of rectangles that you render on request and the only image taking space in memory is the currently displayed one.
I am plotting to my data to ZedGraph. Using FileStream to read files. Sometimes my data is greater than 200 megabyte. To draw this amount of data i should calculate peak values or must apply a window. However i want to see the all points of zoomed area. Please share any suggestion.
PointPairList list1 = new PointPairList();
int read;
int count = 0;
while (file.Position < file.Length)
{
read = file.Read(mainBuffer, 0, mainBuffer.Length);
for (int i = 0; i < read / window; i++)
{
list1.Add(count++, BitConverter.ToSingle(mainBuffer, i * window));
count++;
}
}
myCurve1 = zgc.MasterPane.PaneList[1].AddCurve(null, list1, Color.Lime, SymbolType.None);
myCurve1.IsX2Axis = true;
zgc.MasterPane.PaneList[1].XAxis.Scale.MaxAuto = true;
zgc.MasterPane.PaneList[1].XAxis.Scale.MinAuto = true;
zgc.AxisChange();
zgc.Invalidate();
window=2048 for file size between 100 megabyte to 300 megabyte.
Instead of using a PointPairList, I would suggest to use a FilteredPointList instead. By this way, you can keep every points in memory, ZedGraph will only show the points that are necessary for display.
The FilteredPointList class is well explained here.
You will have to change your code a bit this way:
// Load the X, Y points in two double arrays
// ...
var list1 = new FilteredPointList(xArray, yArray);
// ...
// Use the ZoomEvent to adjust the bounds of the filtered point list
void zedGraphControl1_ZoomEvent(ZedGraphControl sender, ZoomState oldState, ZoomState newState)
{
// The maximum number of point to displayed is based on the width of the graphpane, and the visible range of the X axis
list1.SetBounds(sender.GraphPane.XAxis.Scale.Min, sender.GraphPane.XAxis.Scale.Max, (int)zgc.GraphPane.Rect.Width);
// This refreshes the graph when the button is released after a panning operation
if (newState.Type == ZoomState.StateType.Pan)
sender.Invalidate();
}
Edit
If you can't not host all the points in memory, then you will have to provide your own IPointList implementation for ZedGraph using the logic in the code you describe above. You can inspire from the FilteredPointList itself.
I would use the SetBounds method to preload the points from the disk, based on the decimation algorithm you already implemented, using the min, max and MaxPts in parameters.