logo
Welcome Guest! To enable all features please Login or Register.

Notification

Icon
Error

Options
Go to last post Go to first unread
carrot  
#1 Posted : Monday, December 4, 2017 12:39:39 AM(UTC)
carrot

Rank: Newbie

Groups: Registered
Joined: 12/4/2017(UTC)
Posts: 9
Slovakia
Location: Bratislava

Hello,

I'm trying to create searchable PDF with smaller size of images.
Because of OCR, I need big image to get correct text recognized. This is OK.
But when I have a text in PDF with big image, I want to resize all pages(images), but preserve recognized text.
How can I do it? Or is there another solution?

Thank you very much.
Paul Rayman  
#2 Posted : Monday, December 4, 2017 5:26:05 AM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 1,138

Thanks: 10 times
Was thanked: 133 time(s) in 130 post(s)
Hi,

To resize scanned PDF you may try to use the following code.
Please note This is example only and it will not work with all PDF files, it just illustrates the idea.
But this example should work for all PDF documents created with OCR using the Tesseract.Net SDK.

Also please note, the images does not resized actually, so they size (in bytes) is same as original images

I have checked this code on the following file
Source document: multipage.pdf
resized document: multipage_resized.pdf (1,017kb) downloaded 17 time(s).


Code:

using Patagames.Pdf;
using Patagames.Pdf.Enums;
using Patagames.Pdf.Net;
using Patagames.Pdf.Net.BasicTypes;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;

namespace ConsoleApplication15
{
	class Program
	{
		//The example will not work with all PDF files, it just illustrates the idea.
		//But this example should work for all PDF documents created with recognition using the Tesseract.Net SDK.
		static void Main(string[] args)
		{
			PdfCommon.Initialize();

			float scaleFactor = 0.2f;

			var doc = PdfDocument.Load(@"d:\1\multipage.pdf");
			foreach(var page in doc.Pages)
			{
				//Get page's content
				string content = page.Dictionary["Contents"].As<PdfTypeStream>().DecodedText;

				//Set the size of the page. The size stored as MediaBox rectangle. It is an array of four numbers [0 0 width height]
				var array = page.Dictionary["MediaBox"].As<PdfTypeArray>();
				array[2].As<PdfTypeNumber>().FloatValue *= scaleFactor;
				array[3].As<PdfTypeNumber>().FloatValue *= scaleFactor;


				//Gets the images matrices
				var cmMatrices = ExtractMatrices(content, "cm");
				//Gets the matrices of the text objects
				var tmMatrices = ExtractMatrices(content, "Tm");
				//Multiply the matrix by the scaling factor. The resulting matrices are inserted back to the content line.
				//For image's matrices
				foreach (var matrix in cmMatrices)
				{
					matrix.Value.Scale(scaleFactor, scaleFactor);
					content = ReplaceMatrix(content, matrix.Value, matrix.Key, "cm");
				}
				//For text's matrices
				foreach (var matrix in tmMatrices)
				{
					matrix.Value.Scale(scaleFactor, scaleFactor);
					content = ReplaceMatrix(content, matrix.Value, matrix.Key, "Tm");
				}
				//Create new content stream
				var contentStream = CreateStream(doc, page);
				//Store modified content to content stream
				InitStream(Encoding.GetEncoding(1251).GetBytes(content), contentStream);
				//Page dispose
				page.Dispose();
			}
			//Save copy of the document
			doc.Save(@"d:\1\multipage_resized.pdf", SaveFlags.NoIncremental);
		}

		private static string ReplaceMatrix(string content, FS_MATRIX matrix, string key, string suffix)
		{
			return content.Replace(key, string.Format("{0} {1} {2} {3} {4} {5} {6}", matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f, suffix));
		}

		private static Dictionary<string, FS_MATRIX> ExtractMatrices(string content, string suffix)
		{
			Regex r = new Regex(@"(\d{1,5}\s|\d{1,5}\.\d{1,5}\s){6}"+suffix);
			var matches = r.Matches(content);

			var ret = new Dictionary<string, FS_MATRIX>();
			foreach (Match m in matches)
			{
				var key = m.Value;
				var matrix = BuildMatrix(key);
				ret.Add(key, matrix);
			}

			return ret;
		}

		private static FS_MATRIX BuildMatrix(string key)
		{
			char[] ch = { ' ' };
			var arr = key.Split(ch, StringSplitOptions.RemoveEmptyEntries);
			return new FS_MATRIX()
				{
					a = float.Parse(arr[0]),
					b = float.Parse(arr[1]),
					c = float.Parse(arr[2]),
					d = float.Parse(arr[3]),
					e = float.Parse(arr[4]),
					f = float.Parse(arr[5]),
				};
		}

		private static PdfTypeStream CreateStream(PdfDocument doc, PdfPage page)
		{
			//Set new content stream
			var list = PdfIndirectList.FromPdfDocument(doc);
			var contentStream = PdfTypeStream.Create();
			int objNum = list.Add(contentStream);
			var indirect = PdfTypeIndirect.Create(list, objNum);
			page.Dictionary["Contents"] = indirect;
			return contentStream;
		}

		private static void InitStream(byte[] data, PdfTypeStream contentStream)
		{
			var streamDict = PdfTypeDictionary.Create();
			streamDict.Add("Length", PdfTypeNumber.Create(data.Length));
			contentStream.Init(data, streamDict);
		}

	}

	static class Helpers
	{
		/// <summary>
		/// Converts the current instance to specified type.
		/// </summary>
		/// <typeparam name="T">The type the instance should be converted for.</typeparam>
		/// <returns>Returns instance of type of T or throw an exception if type of this istance is not T.</returns>
		/// <remarks>This instance may be a direct or inderect object of type T</remarks>
		public static T As<T>(this PdfTypeBase item) where T : PdfTypeBase
		{

			if (item is T)
				return item as T;

			if (item is PdfTypeIndirect)
			{
				var direct = (item as PdfTypeIndirect).Direct;
				if (direct is T)
					return direct as T;
			}

			throw new Exception(string.Format("Unexpected type of object. Expected: {0}; Actual: {1}", typeof(T), item.GetType()));
		}
	}
}

Edited by user Monday, December 4, 2017 5:36:39 AM(UTC)  | Reason: Not specified

Paul Rayman  
#3 Posted : Monday, December 4, 2017 5:33:47 AM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 1,138

Thanks: 10 times
Was thanked: 133 time(s) in 130 post(s)
Also you should know.
The code above uses extended features which available on multi-developer license only.
Like described here:
https://pdfium.patagames...censing_FullAPICalls.htm

Edited by user Wednesday, January 20, 2021 5:11:31 AM(UTC)  | Reason: Not specified

PataAndrey  
#4 Posted : Monday, December 4, 2017 5:46:20 AM(UTC)
PataAndrey

Rank: Member

Groups: Registered
Joined: 11/3/2017(UTC)
Posts: 7

Was thanked: 1 time(s) in 1 post(s)
Quote:

Also you should know.
The code above uses extended features which available on multi-developer license only.


Hi carrot! If you want to test this method and you have a single license, then you do not need to specify a key, check the effectiveness of this method in demo mode.
carrot  
#5 Posted : Monday, December 4, 2017 10:24:45 AM(UTC)
carrot

Rank: Newbie

Groups: Registered
Joined: 12/4/2017(UTC)
Posts: 9
Slovakia
Location: Bratislava

Thank you for quick answer.

The code with multipage.pdf gives me only empty pages (white).
I also tried different pdf files with same result.

I tried x64 and x86 target platform, with no license key set.

In the end I need smaller PDF (images) with text, so images must be resized as data not only their view.

Edited by user Monday, December 4, 2017 11:47:21 PM(UTC)  | Reason: Not specified

Paul Rayman  
#6 Posted : Tuesday, December 5, 2017 12:00:59 AM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 1,138

Thanks: 10 times
Was thanked: 133 time(s) in 130 post(s)
Originally Posted by: carrot Go to Quoted Post
The code with multipage.pdf gives me only empty pages (white).


looks like the content is broken.

Please compare content retrieved by this line
Code:

string content = page.Dictionary["Contents"].As<PdfTypeStream>().DecodedText;


For source PDF it should be
Quote:

q 595.2 0 0 841.92 0 0 cm /Im1 Do Q
BT
3 Tr 1 0 0 1 179.52 497.8 Tm /f-0-0 18 Tf 111.112 Tz [ <0053><0061><006D><0070><006C><0065> ] TJ 65.76 0 Td 135.112 Tz [ <0050><0044><0046> ] TJ 43.2 0 Td 118.666 Tz [ <0044><006F><0063><0075><006D><0065><006E><0074> ] TJ
-52.32 -53.76 Td /f-0-0 13 Tf 98.462 Tz [ <0052><006F><0062><0065><0072><0074> ] TJ 42.72 0 Td 115.2 Tz [ <004D><0061><0072><006F><006E> ] TJ
-62.88 -18.24 Td 101.538 Tz [ <0047><0072><007A><0065><0067><006F><0072><007A> ] TJ 57.12 0 Td 96 Tz [ <0047><0072><0075><0064><007A><0069><FB01><0073><006B><0069> ] TJ
-49.44 -34.08 Td 97.846 Tz [ <0046><0065><0062><0072><0075><0061><0072><0079> ] TJ 55.2 0 Td 86.154 Tz [ <0032><0030><002C> ] TJ 23.04 0 Td 101.538 Tz [ <0031><0039><0039><0039> ] TJ
ET

And after replacement
Quote:

q 119.04 0 0 168.384 0 0 cm /Im1 Do Q
BT
3 Tr 0.2 0 0 0.2 35.904 99.56 Tm /f-0-0 18 Tf 111.112 Tz [ <0053><0061><006D><0070><006C><0065> ] TJ 65.76 0 Td 135.112 Tz [ <0050><0044><0046> ] TJ 43.2 0 Td 118.666 Tz [ <0044><006F><0063><0075><006D><0065><006E><0074> ] TJ
-52.32 -53.76 Td /f-0-0 13 Tf 98.462 Tz [ <0052><006F><0062><0065><0072><0074> ] TJ 42.72 0 Td 115.2 Tz [ <004D><0061><0072><006F><006E> ] TJ
-62.88 -18.24 Td 101.538 Tz [ <0047><0072><007A><0065><0067><006F><0072><007A> ] TJ 57.12 0 Td 96 Tz [ <0047><0072><0075><0064><007A><0069><FB01><0073><006B><0069> ] TJ
-49.44 -34.08 Td 97.846 Tz [ <0046><0065><0062><0072><0075><0061><0072><0079> ] TJ 55.2 0 Td 86.154 Tz [ <0032><0030><002C> ] TJ 23.04 0 Td 101.538 Tz [ <0031><0039><0039><0039> ] TJ
ET


I'm guessing the following code produce comma (,) as a decimal delimiter for matrices coefficients instead of dot (.)
Code:

			return new FS_MATRIX()
				{
					a = float.Parse(arr[0]),
					b = float.Parse(arr[1]),
					c = float.Parse(arr[2]),
					d = float.Parse(arr[3]),
					e = float.Parse(arr[4]),
					f = float.Parse(arr[5]),
				};



Paul Rayman  
#7 Posted : Tuesday, December 5, 2017 1:23:46 AM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 1,138

Thanks: 10 times
Was thanked: 133 time(s) in 130 post(s)
Originally Posted by: carrot Go to Quoted Post
In the end I need smaller PDF (images) with text, so images must be resized as data not only their view.


Well it is possible too.

After resizing PDF you may resize all images using this technique
http://forum.patagames.c...sition-in-WPF-Pdf-Viewer


the imaged stored in Resource dictionary. And the name of that image is
Quote:
q 595.2 0 0 841.92 0 0 cm /Im1 Do Q
BT
...


It placed here



So you can just change the stream to new image
like shown here
http://forum.patagames.c...sition-in-WPF-Pdf-Viewer
Code:

IntPtr streamHandle = Pdfium.FPDFImageObj_GenerateStream(image.Handle, page.Handle);

carrot  
#8 Posted : Tuesday, December 12, 2017 1:59:46 AM(UTC)
carrot

Rank: Newbie

Groups: Registered
Joined: 12/4/2017(UTC)
Posts: 9
Slovakia
Location: Bratislava

Thank you for hints. I almost have it done.

While OcrApi creating text objects, it changes the image to grayscale.
So I need to replace (or remove current and add origin) image in pdf and preserve text objects.

When I use code from example for replace an image, it changes page.Dictionary["Contents"] to PdfTypeArray.
Than next when resizing whole PDF by example, it does not work, because it expects PdfTypeStream.

Edited by user Tuesday, December 12, 2017 2:41:29 AM(UTC)  | Reason: Not specified

Paul Rayman  
#9 Posted : Tuesday, December 12, 2017 3:16:40 AM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 1,138

Thanks: 10 times
Was thanked: 133 time(s) in 130 post(s)
Page content may be PdfTypeStream or an array(PdfTypeArray) of such streams.
So you should process both situation.

Edited by user Tuesday, December 12, 2017 3:19:02 AM(UTC)  | Reason: Not specified

carrot  
#10 Posted : Tuesday, December 12, 2017 3:41:39 AM(UTC)
carrot

Rank: Newbie

Groups: Registered
Joined: 12/4/2017(UTC)
Posts: 9
Slovakia
Location: Bratislava

But I want only one stream on page. Now I have two.
The old one I want to remove. So how can I replace, not add image?
Paul Rayman  
#11 Posted : Tuesday, December 12, 2017 5:54:40 PM(UTC)
Paul Rayman

Rank: Administration

Groups: Administrators
Joined: 1/5/2016(UTC)
Posts: 1,138

Thanks: 10 times
Was thanked: 133 time(s) in 130 post(s)
Hi,

I can suggest the following method to resize searchable PDF (including image reducing)
Please look at code below.
It is much simpler than previously suggested.

Code:

static void Main(string[] args)
{
	PdfCommon.Initialize();

	float scaleFactor = 0.2f;

	var doc = PdfDocument.Load(@"d:\1\multipage.pdf");

	foreach (var page in doc.Pages)
	{
		page.MediaBox = new FS_RECTF()
		{
			left = 0,
			bottom = 0,
			right = page.MediaBox.right * scaleFactor,
			top = page.MediaBox.top * scaleFactor
		};

		foreach (var obj in page.PageObjects)
		{
			if (obj is PdfTextObject)
			{
				var text = obj as PdfTextObject;
				var m = text.Matrix;
				m.Scale(scaleFactor, scaleFactor);
				text.Matrix = m;
			}
			else if (obj is PdfImageObject)
			{
				var img = obj as PdfImageObject;
				var m = img.Matrix;
				m.Scale(scaleFactor, scaleFactor);
				img.Matrix = m;

				//Change bitmap to another
				float imageScaleFactor = scaleFactor;
				//It's also may be not equal scaleFactor to control quality/size of image.
				imageScaleFactor = scaleFactor*2;
				var bmp = ReduceImage(img.Bitmap, imageScaleFactor);
				img.Bitmap = bmp;
			}
		}

		Pdfium.FPDFPage_GenerateContentEx(page.Handle);
		page.Dispose();
	}


	doc.Save(@"d:\1\multipage_resized.pdf", SaveFlags.NoIncremental);
}

private static PdfBitmap ReduceImage(PdfBitmap bitmap, float imageScaleFactor)
{
	var bmp = bitmap.Convert(BitmapFormats.FXDIB_8bppRgb);
	var ret = bmp.StretchTo((int)((float)bitmap.Width * imageScaleFactor), (int)((float)bitmap.Height * imageScaleFactor), 0);
	bmp.Dispose();
	return ret;
}


The above code has a nuance. It uses the Pdfium.FPDFPage_GenerateContentEx(page.Handle) method.
This method is not documented and not yet been fully tested.
It is not released officially.

Edited by user Tuesday, December 12, 2017 6:04:47 PM(UTC)  | Reason: Not specified

carrot  
#12 Posted : Wednesday, December 20, 2017 5:52:29 AM(UTC)
carrot

Rank: Newbie

Groups: Registered
Joined: 12/4/2017(UTC)
Posts: 9
Slovakia
Location: Bratislava

Thank you.

I can't use untested function in production.
Is it possible with your library achieve what I need?

Edited by user Thursday, December 21, 2017 1:13:00 AM(UTC)  | Reason: Not specified

Guest  
#13 Posted : Thursday, December 21, 2017 2:44:56 AM(UTC)
Guest

Rank: Guest

Groups: Guests
Joined: 1/5/2016(UTC)
Posts: 162

Was thanked: 5 time(s) in 5 post(s)
Yes it is possible.
If you do not want to use GenerateContent method then you need to go deeper to PDF internals.
Just use page resource dictionary. You may manually add or remove any images from it
Users browsing this topic
Guest (4)
Forum Jump  
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.