iTextSharp: Stamp rotated/placed incorrectly - c#

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?

Related

iText 7 ImageRenderInfo Matrix contains negative height on Even number Pages

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.

iTextSharp watermark not appearing on a scanned PDF

I have some code used to watermark PDFs using iTextSharp. The code works fine for most PDFs, but there has been one test case where the watermark is not visible on a PDF of a scanned document. (I have other scanned documents where it does appear though).
I am using the GetOverContent() method.
This is my code for adding the watermark;
using (PdfReader reader = new PdfReader(this.inputFilename))
{
// Set transparent - 1
PdfGState gstate = new PdfGState();
gstate.FillOpacity = 0.4f;
gstate.StrokeOpacity = 0.5f;
// 2
BaseFont baseFont = BaseFont.CreateFont(BaseFont.HELVETICA_BOLD, Encoding.ASCII.EncodingName, false);
using (var stream = new MemoryStream())
{
var pdfStamper = new PdfStamper(reader, stream);
// Must start at 1 because 0 is not an actual page.
for (int i = 1; i <= reader.NumberOfPages; i++)
{
Rectangle pageSize = reader.GetPageSizeWithRotation(i);
// Gets the content ABOVE the PDF, Another option is GetUnderContent(...)
// which will place the text below the PDF content.
PdfContentByte pdfPageContents = pdfStamper.GetOverContent(i);
pdfPageContents.BeginText(); // Start working with text.
// 1
pdfPageContents.SaveState();
pdfPageContents.SetGState(gstate);
float hypotenuse = (float)Math.Sqrt(Math.Pow(pageSize.Width, 2) + Math.Pow(pageSize.Height, 2));
float glyphWidth = baseFont.GetWidth("My watermark text");
float fontSize = 1000 * (hypotenuse * 0.8f) / glyphWidth;
float angle = (float)(Math.Atan(pageSize.Height / pageSize.Width) * (180 / Math.PI));
// Create a font to work with
pdfPageContents.SetFontAndSize(baseFont, fontSize);
pdfPageContents.SetRGBColorFill(128, 128, 128); // Sets the color of the font, GRAY in this instance
// Note: The x,y of the Pdf Matrix is from bottom left corner.
// This command tells iTextSharp to write the text at a certain location with a certain angle.
// Again, this will angle the text from bottom left corner to top right corner and it will
// place the text in the middle of the page.
pdfPageContents.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "My watermark text", pageSize.Width / 2, pageSize.Height / 2, angle);
pdfPageContents.EndText(); // Done working with text
pdfPageContents.RestoreState();
}
pdfStamper.FormFlattening = true; // enable this if you want the PDF flattened.
pdfStamper.FreeTextFlattening = true; // enable this if you want the PDF flattened.
pdfStamper.Close(); // Always close the stamper or you'll have a 0 byte stream.
return stream.ToArray();
}
}
Does anyone have any ideas as to why the watermark may not be appearing and what I can try to fix it?
Kind regards.
The code is based on an assumption it even documents as a fact:
// Note: The x,y of the Pdf Matrix is from bottom left corner.
// This command tells iTextSharp to write the text at a certain location with a certain angle.
// Again, this will angle the text from bottom left corner to top right corner and it will
// place the text in the middle of the page.
pdfPageContents.ShowTextAligned(PdfContentByte.ALIGN_CENTER, "My watermark text", pageSize.Width / 2, pageSize.Height / 2, angle);
The assumption that the x,y of the Pdf Matrix is from bottom left corner unfortunately is wrong: While it indeed is very often the case that the origin of the PDF coordinate system (the default user space coordinate system, to be more precise) is in the lower left corner of the page, this is not required, the origin actually can be literally anywhere (within reasonable limits).
Thus, one has to take the lower left coordinates of the Rectangle pageSize into consideration, too.
The OP meanwhile has confirmed:
I had assumed that the bottom left of the page would have co-ordinates of (0,0) but for this document the co-ordinates were (0, 7022).

Changing Sprite and keeping the same size

I'm writing a game using the 2d features of unity.
I'm designing a sort of inventory for the player character, and I have a gameobject with a number of placeholder images inside it. The intention is that when I actually load this gameobject, I'll replace the sprites of the placeholder images and I'll display what I want.
My issue is that when I change the sprite using code like this
var ren = item1.GetComponentInChildren<SpriteRenderer>();
ren.sprite = Resources.Load<Sprite>("DifferentSprite");
The image loaded is correct, however the scaling applies to the new sprite. The issue is that these sprites have all different sizes. So whilst the original placeholder image takes up a small square, the replacement might be tiny or massive enough to cover the whole screen depending on how the actual png was sized.
I basically want the sprite to replace the other and scale itself such that it has the same width and height as the placeholder image did. How can I do this?
EDIT - I've tried playing around with ratios. It's still not working perfectly, but its close.
var ren = item1.GetComponentInChildren<SpriteRenderer>();
Vector2 beforeSize = ren.transform.renderer.bounds.size;
ren.sprite = Resources.Load<Sprite>("Day0/LampOn");
Vector2 spriteSize = ren.sprite.bounds.size;
//Get the ratio between the wanted size and the sprite's size ratios
Vector3 scaleMultiplier = new Vector3 (beforeSize.x/spriteSize.x, beforeSize.y/spriteSize.y);
//Multiple the scale by this multiplier
ren.transform.localScale = new Vector3(ren.transform.localScale.x * scaleMultiplier.x,ren.transform.localScale.y * scaleMultiplier.y);
puzzle.Sprite = Sprite.Create(t, new Rect(0, 0, t.width, t.height),Vector2.one/2,256);
the last int is for the pixelperUnity, its the cause of changing size.
It's fixed in my project,
my default pixelPerUnit=256 and unity default = 100 so its bigger 2.56 times
tell me if it helps
How to modify width and height: use RectTransform.sizeDelta.
Example for this type of script
Vector3 originalDelta = imageToSwap.rectTransform.sizeDelta; //get the original Image's rectTransform's size delta and store it in a Vector called originalDelta
imageToSwap.sprite = newSprite; //swap out the new image
imageToSwap.rectTransform.sizeDelta = originalDelta; //size it as the old one was.
More about sizeDeltas here
I would expose two public variables in the script, with some default values:
public int placeholderWidth = 80;
public int placeholderHeight = 80;
Then, whenever you change sprites, set the width & height to those pre-defined values.

Dynamically scaling canvas in WPF by setting in- and outpoints

I'm working on plotting program in WPF using the canvas element. What I want to achieve is a scrollbar with draggable endpoints. Example of these kinds of scrollbars are in the After Effects video editing software by Adobe.
Basic functionality of such a scrollbar is that it is able to scroll trough content that is bigger then it's container, but both the left and right endpoint can be dragged to dynamically change the scale of the content.
I have implemented a similar scrollbar in the plotting program; users should be able to drag around the in and outpoint (Rectangles in a canvas), and the plot canvas should respond to this by scaling to the desired range.
Information I need for this:
Width of the total plot (amount of plotpoints)
Width of the container (static, 600px)
Percentage of the in and out points relative to the total width of the scrollbar canvas
Link to current screenshot
With this information I have created a MatrixTransform, using the ScaleAt() method to scale the plot canvas inside the container so that it matches the in and outpoints in the scrollbar below. For this I used the following code. resetTransform gets called FPS times a second to keep up with the incoming data and XMAX and YMAX are updated elsewhere to reflect this.
public void resetTransform(Boolean useSlider = false)
{
//Add transformgroup to plot
double yscale = plot.Height / view.YMAX; //YMAX is maximum plot value received
double xscale = plot.Width / view.XMAX; //XMAX is total ammount of plotted points
Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
if (useSlider)
{
double maxVal = zoomBar.ActualWidth - outPoint.Width;
double outP = Canvas.GetLeft(outPoint); //points position relative to the scrollbar
double inP = Canvas.GetLeft(inPoint);
double center = (((outP + inP) / 2) / maxVal) * plot.ActualWidth;
double delta = (outP-inP);
double factor = (maxVal/delta) * xscale;
double mappedinP = (inP / maxVal) * view.XMAX;
double anchorOut = (outP / maxVal) * view.XMAX;
double anchorIn = (inP / maxVal) * view.XMAX;
m.ScaleAt(factor, -yscale,center,0); //scale around the center point,
m.Translate(0, plot.Height); //to compensate the flipped graph, move it back down
}
scale = new ScaleTransform(m.M11, m.M22, 0, 0); //save scale factors in a scaletransform for reference
signals.scaleSignalStrokes(scale); //Scale the plotlines to compensate for canvas scaling
MatrixTransform matrixTrans = new MatrixTransform(m); //Create matrixtransform
plot.RenderTransform = matrixTrans; //Apply to canvas
}
Expectation: Everything should work and the plotted graph would scale nicely when the amount of plotpoints grows over time. Reality: The graph scales when moving the points around, but it is not representative; Moreover, the more plot points are added, the more the whole canvas shifts to the right and the less control I seem to have over the transformation. The algorithm as it is now is probably to wrong approach to get the result I need, but I have spent quite some time thinking how to do this right.
Update
I have uploaded a video to give a clearer picture on the interaction. In the video you can clearly see the canvas shifting to the right.
Screencapture video
How should I scale the canvas (plot) to fit within two boundaries?
So, after some struggling, I found the right algorithm to solve this problem. I will post the adjusted version of the resetTransform function below:
//Reset graph transform
public void resetTransform(Boolean useSlider = false)
{
double yscale = plot.Height / view.YMAX; //YMAX is maximum plot value received
double xscale = plot.Width / view.XMAX; //XMAX is total ammount of plotted points
Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
if (useSlider)
{
double maxVal = zoomBar.ActualWidth - outPoint.Width;
double outP = Canvas.GetLeft(outPoint); //points position relative to the scrollbar
double inP = Canvas.GetLeft(inPoint);
double delta = (outP-inP);
double factor = (maxVal/delta) * xscale;
anchorOut = (outP / maxVal) * view.XMAX; //Define anchorpoint coordinates
anchorIn = (inP / maxVal) * view.XMAX;
double center = (anchorOut +anchorIn)/2; //Define centerpoint
m.Translate(-anchorIn, 0); //Move graph to inpoint
m.ScaleAt(factor, -yscale,0,0); //scale around the inpoint, with a factor so that outpoint is plot.Height(=600px) further away
m.Translate(0, plot.Height); //to compensate the flipped graph, move it back down
}
scale = new ScaleTransform(m.M11, m.M22, 0, 0); //save scale factors in a scaletransform for reference
signals.scaleSignalStrokes(scale); //Scale the plotlines to compensate for canvas scaling
MatrixTransform matrixTrans = new MatrixTransform(m); //Create matrixtransform
plot.RenderTransform = matrixTrans; //Apply to canvas
}
So rather than scaling around the centre point, I should first translate the image and then scale around the origin of the canvas with a factor. This means the other side of the canvas is exactly plot.Height pixels away (with some added scaling)
Everything seems to work fine now, but because I am using custom controls (draggable Rectangles in a canvas) I notice these rectangles do not always fire the mousse events.
Since it is out of the scope of this question, I've described the issue further in this post

Novacode Docx Create Image from Bitmap

Background
My project is urgent and requires that I iterate a large XML file and return Base64 encoded images.
Each image must be inserted into an MS Word doc, and I am using the DocX library for that.
I am converting the Base64 strings to bitmap with no problem.
Problem
For the life of me, I can't seem to get the bitmaps into a Novacode.Image object which can then be inserted to the document. NOTE: I already know how to convert to System.Drawing.Image format. It is Novacode.Image format (DocX) that is giving me grief.
If I try to convert a la (Novacode.Image)somebitmap; I get Can not cast expression of type Image to Bitmap. If I try to initialize a new Novacode.Image object I get Can not access internal constructor Image here.
Using C#, .NET 4, Forms App, lots of coffee.
Question
Only Novacode.Image objects can be inserted into the MS Word doc via the library, so how the heck do I get my bitmap in there??
I am bleary-eyed at this point so perhaps I am just missing something simple.
You have to use the DocX.AddImage() method to create a Novacode.Image object.
Follow these 5 steps to add a image to a word document:
Save your picture into a memory stream.
Create the Novacode.Image object by calling AddImage() method.
Create a picture by calling CreatePicture() on the Novacode.Image object created in step 2.
Set the shape of the picture (if needed).
Insert your picture into a pragraph.
The sample below shows how to insert a image into a word document:
using (DocX doc = DocX.Create(#"Example.docx"))
{
using (MemoryStream ms = new MemoryStream())
{
System.Drawing.Image myImg = System.Drawing.Image.FromFile(#"test.jpg");
myImg.Save(ms, myImg.RawFormat); // Save your picture in a memory stream.
ms.Seek(0, SeekOrigin.Begin);
Novacode.Image img = doc.AddImage(ms); // Create image.
Paragraph p = doc.InsertParagraph("Hello", false);
Picture pic1 = img.CreatePicture(); // Create picture.
pic1.SetPictureShape(BasicShapes.cube); // Set picture shape (if needed)
p.InsertPicture(pic1, 0); // Insert picture into paragraph.
doc.Save();
}
}
Hope, this helps.
Thanks Hans and Martin, I was able to use this as a basis for ensuring large image files (photos) are always sized to fit on the page. The max width and max height can be changed depending on your page size.
using (MemoryStream ms = new MemoryStream())
{
System.Drawing.Image myImg = System.Drawing.Image.FromFile(imageDirectory + i.FileName);
double xScale = 1;
double yScale = 1;
double maxWidthInches = 6.1; // Max width to fit on a page
double maxHeightInches = 8.66; // Max height to fit on a page
// Normalise the Horizontal and Vertical scale for different resolutions
double hScale = ((double)96 / myImg.HorizontalResolution);
double vScale = ((double)96 / myImg.VerticalResolution);
// Scaling required to fit in x direction
double imageWidthInches = myImg.Width / myImg.HorizontalResolution; // in inches using DPI
if (imageWidthInches > maxWidthInches)
xScale = maxWidthInches / imageWidthInches * hScale;
// Scaling required to fit in y direction
double imageHeightInches = myImg.Height / myImg.VerticalResolution;
if (imageHeightInches > maxHeightInches)
yScale = maxHeightInches / imageHeightInches * vScale;
double finalScale = Math.Min(xScale, yScale); // Use the smallest of the two scales to ensure the picture will fit in both directions
myImg.Save(ms, myImg.RawFormat); // Save your picture in a memory stream.
ms.Seek(0, SeekOrigin.Begin);
Novacode.Image img = document.AddImage(ms); // Create image.
Paragraph p = document.InsertParagraph();
Picture pic = img.CreatePicture(); // Create picture.
//Apply final scale to height & width
double width = Math.Round((double)myImg.Width * finalScale);
double height = Math.Round((double)myImg.Height * finalScale);
pic.Width = (int)(width);
pic.Height = (int)(height);
p.InsertPicture(pic); // Insert picture into paragraph.
}
Thanks Hans. I had an Issue where the Image is inserted at the wrong size based on the DPI so I used this to scale the image based on DPI, 96 dpi seemed to be the basis of the scaled image in word:
using (MemoryStream ms = new MemoryStream())
{
System.Drawing.Image myImg = System.Drawing.Image.FromFile(path);
//Calculate Horizontal and Vertical scale
float Hscale = ((float)96 / myImg.HorizontalResolution);
float Vscale = ((float)96 / myImg.VerticalResolution );
myImg.Save(ms, myImg.RawFormat); // Save your picture in a memory stream.
ms.Seek(0, SeekOrigin.Begin);
Novacode.Image img = proposal.AddImage(ms); // Create image.
Picture pic1 = img.CreatePicture(); // Create picture.
//Apply scale to height & width
pic1.Height = (int)(myImg.Height * Hscale);
pic1.Width = (int)(myImg.Width * Vscale);
a.InsertPicture(pic1, 0); // Insert picture into paragraph.
}

Categories

Resources