MonoTouch: Memory leak when drawing PDF on a custom graphics context - c#

I have an app that's basically a fancy PDF reader. I download a PDF from the internet and generate thumbnails for that PDF. However, it seems that when I generate these thumbnails a lot of memory is being allocated (checked using Instruments), sometimes parts of this is collected by the GC but in the end, my app gives up. I've had memory usage of up to 38Mb when generating thumbnails for a single PDF (100x100 thumbs, ~60 pages).
I generate one thumbnail at a time, store it and then repeat the process, so under any circumstance there should only be one thumbnail in memory (while generating them, at least). My code for generating thumbnails looks like this:
public UIImage GetPageThumbnail(int pageNumber, SizeF size)
{
//If using retina display, make sure to scale-up the thumbnail as well.
size.Width = size.Width * UIScreen.MainScreen.Scale;
size.Height = size.Height * UIScreen.MainScreen.Scale;
UIGraphics.BeginImageContext(size);
CGContext tempContext = UIGraphics.GetCurrentContext();
CGPDFPage page = Document.GetPage(pageNumber);
RectangleF drawArea = new RectangleF(new PointF(0f, 0f), size);
CGAffineTransform transform = page.GetDrawingTransform( CGPDFBox.Crop, drawArea, 180, true); //fit PDF to context
transform.xx = -transform.xx; // }
transform.x0 = 0; // }flip horizontally
//Console.WriteLine("XX: " + transform.xx + ", YX:" + transform.yx + ", XY:" + transform.xy + ", YY:" + transform.yy + ", X0:" + transform.x0 + ", Y0:" + transform.y0);
tempContext.ConcatCTM(transform);
tempContext.DrawPDFPage (page);
UIImage returnImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return returnImage;
}
I've tried explicitly disposing the context and PDF page, but that had no effect (actually it seemed worse, but take that with a pinch of salt).
I've seen some posts about memory leakage with MonoTouch and PDF (basically this post), but that's pretty old. I'm using the newest MonoTouch (5.0.2).

Not sure where the problem in your code is, but here's my code for generating thumbs of PDF pages. It is working flawlessly. Maybe it helps you. I think your issue might be what you are doing with the returned image when you're don.
public static UIImageView GetLowResPagePreview (CGPDFPage oPdfPage, RectangleF oTargetRect)
{
RectangleF oOriginalPdfPageRect = oPdfPage.GetBoxRect (CGPDFBox.Media);
RectangleF oPdfPageRect = PdfViewerHelpers.RotateRectangle( oPdfPage.GetBoxRect (CGPDFBox.Media), oPdfPage.RotationAngle);
// Create a low res image representation of the PDF page to display before the TiledPDFView
// renders its content.
int iWidth = Convert.ToInt32 ( oPdfPageRect.Size.Width );
int iHeight = Convert.ToInt32 ( oPdfPageRect.Size.Height );
CGColorSpace oColorSpace = CGColorSpace.CreateDeviceRGB();
CGBitmapContext oContext = new CGBitmapContext(null, iWidth, iHeight, 8, iWidth * 4, oColorSpace, CGImageAlphaInfo.PremultipliedLast);
// First fill the background with white.
oContext.SetFillColor (1.0f, 1.0f, 1.0f, 1.0f);
oContext.FillRect (oOriginalPdfPageRect);
// Scale the context so that the PDF page is rendered
// at the correct size for the zoom level.
oContext.ConcatCTM ( oPdfPage.GetDrawingTransform ( CGPDFBox.Media, oPdfPageRect, 0, true ) );
oContext.DrawPDFPage (oPdfPage);
CGImage oImage = oContext.ToImage();
UIImage oBackgroundImage = UIImage.FromImage( oImage );
oContext.Dispose();
oImage.Dispose ();
oColorSpace.Dispose ();
UIImageView oBackgroundImageView = new UIImageView (oBackgroundImage);
oBackgroundImageView.Frame = new RectangleF (new PointF (0, 0), oPdfPageRect.Size);
oBackgroundImageView.ContentMode = UIViewContentMode.ScaleToFill;
oBackgroundImageView.UserInteractionEnabled = false;
oBackgroundImageView.AutoresizingMask = UIViewAutoresizing.None;
return oBackgroundImageView;
}

Related

Vuforia - Unity low image quality

I am using Vuforia with Unity for an Android AR app. I made it to take a screenshot of a specific area of the screen with the click of a button. It reads pixels in that specific area when the button is clicked.
The problem is, the resulting image is blurry/ low resolution. How can I increase the quality of the image?
I tried changing "Camera Device Mode" to "MODE_OPTIMIZE_QUALITY" and increasing "Number Divisions" in "Video Background" setting. But nothing happens. I also tried removing ARCore and changing "ARCore Requirement" to "DONT_USE" mode.
This is the code to take a snapshot :
yield return new WaitForEndOfFrame();
//Get the corners of RectTransform rect and store it in a array vector
Vector3[] corners = new Vector3[4];
_objToScreenshot.GetWorldCorners(corners);
//Remove 100 and you will get error
int width = ((int)corners[3].x - (int)corners[0].x) - 100;
int height = (int)corners[1].y - (int)corners[0].y;
var startX = corners[0].x;
var startY = corners[0].y;
//Make a temporary texture and read pixels from it
Texture2D ss = new Texture2D(width, height, TextureFormat.RGB24, false);
ss.ReadPixels(new Rect(startX, startY, width, height), 0, 0);
ss.Apply();
//Save the screenshot to disk
byte[] byteArray = ss.EncodeToPNG();
string savePath = Application.persistentDataPath + "/" + System.DateTime.Now.ToString("HH-mm-ss dd,MM, yyyy") + ".png";
System.IO.File.WriteAllBytes(savePath, byteArray);
//Debug.Log("Screenshot Path : " + savePath);
// Destroy texture to avoid memory leaks
Destroy(ss);

How to get pixel color of Direct2D bitmap on SharpDX

I use SharpDX and I don't understand how to get pixel color at bitmap. I found CopySubresourceRegion method, but it working on Direct3D.
I've strange idea:
I can create RenderForm and drawing my bitmap on form. Then get graphics of form. Then create bitmap via "new Bitmap(width, height, graphics)". And then get pixel color from new bitmap;
I written special function for getting pixel color. This solved my problem ;)
C# - SharpDX
Color4 GetPixel(Bitmap image, int x, int y, RenderTarget renderTarget) {
var deviceContext2d = renderTarget.QueryInterface<DeviceContext>();
var bitmapProperties = new BitmapProperties1();
bitmapProperties.BitmapOptions = BitmapOptions.CannotDraw | BitmapOptions.CpuRead;
bitmapProperties.PixelFormat = image.PixelFormat;
var bitmap1 = new Bitmap1(deviceContext2d, new Size2((int)image.Size.Width, (int)image.Size.Height), bitmapProperties);
bitmap1.CopyFromBitmap(image);
var map = bitmap1.Map(MapOptions.Read);
var size = (int)image.Size.Width * (int)image.Size.Height * 4;
byte[] bytes = new byte[size];
Marshal.Copy(map.DataPointer, bytes, 0, size);
bitmap1.Unmap();
bitmap1.Dispose();
deviceContext2d.Dispose();
var position = (y * (int)image.Size.Width + x) * 4;
return new Color4(bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3]);
}
If you are targeting Direct2D 1.1 (or higher), then you can use the ID2D1Bitmap1::Map method. This will require that you set D2D1_BITMAP_OPTIONS_CPU_READ and D2D1_BITMAP_OPTIONS_CANNOT_DRAW flags on the bitmap when creating it.

Load 32-bit greyscale TIFF image in C#

I'm developing a small C# tool that must be able to load a TIFF image, crop the image to a certain size, and save it as a PNG file.
I have large greyscale TIFF images of about 28000x256 pixels with 32-bit bit depth. When I try to process the images with my tool, it just outputs a blank white image.
Also, when I try to open the original TIFF images (not the ones processed with my tool) with the Windows Photo Viewer, it also shows a blank white image. Some other applications, e.g. ImageJ, display the image correctly. What is the problem here?
My code to load the images looks as follows:
Image image = Bitmap.FromFile(path.LocalPath);
int width = image.Width;
int height = image.Height;
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);
The problem is that C# (or better said the underlying API) can't handle Greyscale images with a Colordepth greater than 8bit.
I'd suggest using LibTiff.NET for Handling TIFF images.
When i faced such an problem, i loaded the TIFF image raw Data into an array
using (var inputImage = Tiff.Open(image, "r"))
{
width = inputImage.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
height = inputImage.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
inputImageData = new byte[width * height * bytePerPixel];
var offset = 0;
for (int i = 0; i < inputImage.NumberOfStrips(); i++)
{
offset += inputImage.ReadRawStrip(i, inputImageData, offset, (int)inputImage.RawStripSize(i));
}
}
The bytes then have to be converted into an array of uint (in my case, imagedata was only 16 bit, so i used ushort) Remember to take care of Endianness of the data!
// has to be done by hand to ensure endiannes is kept correctly.
var outputImageData = new ushort[inputImageData.Length / 2];
for (var i = 0; i < outputImageData.Length; i++)
{
outputImageData[i] = (ushort)((inputImageData[i * 2 + 1]) + (ushort)(inputImageData[i * 2] << 8));
}
You can then manipulate the image using normal Array Operations. I'd suggest you to use normal Array operations and not Lambda-Expressions, as they are much faster. (in My Scenario 100s vs 2s Runtime)
Finally you can save the image using LibTiff again
using (var output = Tiff.Open(imageout, "w"))
{
output.SetField(TiffTag.IMAGEWIDTH, width);
output.SetField(TiffTag.IMAGELENGTH, height);
output.SetField(TiffTag.SAMPLESPERPIXEL, 1);
output.SetField(TiffTag.BITSPERSAMPLE, 16);
output.SetField(TiffTag.ROWSPERSTRIP, height);
output.SetField(TiffTag.PHOTOMETRIC, Photometric.MINISBLACK);
output.SetField(TiffTag.FILLORDER, FillOrder.MSB2LSB);
// Transform to Byte-Array
var buffer = new byte[outputImageData.Length * sizeof(ushort)];
Buffer.BlockCopy(outputImageData, 0, buffer, 0, buffer.Length);
// Write it to Image
output.WriteRawStrip(0, buffer, buffer.Length);
}

generic error occurred in GDI+ saving bitmap to file in a loop witin c#

I'm saving a bitmap to a file on my hard drive inside of a loop (All the jpeg files within a directory are being saved to a database). The save works fine the first pass through the loop, but then gives the subject error on the second pass. I thought perhaps the file was getting locked so I tried generating a unique file name for each pass, and I'm also using Dispose() on the bitmap after the file get saved. Any idea what is causing this error?
Here is my code:
private string fileReducedDimName = #"c:\temp\Photos\test\filePhotoRedDim";
...
foreach (string file in files)
{
int i = 0;
//if the file dimensions are big, scale the file down
Stream photoStream = File.OpenRead(file);
byte[] photoByte = new byte[photoStream.Length];
photoStream.Read(photoByte, 0, System.Convert.ToInt32(photoByte.Length));
Image image = Image.FromStream(new MemoryStream(photoByte));
Bitmap bm = ScaleImage(image);
bm.Save(fileReducedDimName + i.ToString() + ".jpg", ImageFormat.Jpeg);//error occurs here
Array.Clear(photoByte,0, photoByte.Length);
bm.Dispose();
i ++;
}
...
Thanks
Here's the scale image code: (this seems to be working ok)
protected Bitmap ScaleImage(System.Drawing.Image Image)
{
//reduce dimensions of image if appropriate
int destWidth;
int destHeight;
int sourceRes;//resolution of image
int maxDimPix;//largest dimension of image pixels
int maxDimInch;//largest dimension of image inches
Double redFactor;//factor to reduce dimensions by
if (Image.Width > Image.Height)
{
maxDimPix = Image.Width;
}
else
{
maxDimPix = Image.Height;
}
sourceRes = Convert.ToInt32(Image.HorizontalResolution);
maxDimInch = Convert.ToInt32(maxDimPix / sourceRes);
//Assign size red factor based on max dimension of image (inches)
if (maxDimInch >= 17)
{
redFactor = 0.45;
}
else if (maxDimInch < 17 && maxDimInch >= 11)
{
redFactor = 0.65;
}
else if (maxDimInch < 11 && maxDimInch >= 8)
{
redFactor = 0.85;
}
else//smaller than 8" dont reduce dimensions
{
redFactor = 1;
}
destWidth = Convert.ToInt32(Image.Width * redFactor);
destHeight = Convert.ToInt32(Image.Height * redFactor);
Bitmap bm = new Bitmap(destWidth, destHeight,
PixelFormat.Format24bppRgb);
bm.SetResolution(Image.HorizontalResolution, Image.VerticalResolution);
Graphics grPhoto = Graphics.FromImage(bm);
grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
grPhoto.DrawImage(Image,
new Rectangle(0, 0, destWidth, destHeight),
new Rectangle(0, 0, Image.Width, Image.Height),
GraphicsUnit.Pixel);
grPhoto.Dispose();
return bm;
}
If I'm reading the code right, your i variable is zero every time through the loop.
It is hard to diagnose exactly what is wrong, I would recommend that you use using statements to ensure that your instances are getting disposed of properly, but it looks like they are.
I originally thought it might be an issue with the ScaleImage. So I tried a different resize function (C# GDI+ Image Resize Function) and it worked, but i is always set to zero at beginning of each loop. Once you move i's initialization outside of the loop your scale method works as well.
private void MethodName()
{
string fileReducedDimName = #"c:\pics";
int i = 0;
foreach (string file in Directory.GetFiles(fileReducedDimName, "*.jpg"))
{
//if the file dimensions are big, scale the file down
using (Image image = Image.FromFile(file))
{
using (Bitmap bm = ScaleImage(image))
{
bm.Save(fileReducedDimName + #"\" + i.ToString() + ".jpg", ImageFormat.Jpeg);//error occurs here
//this is all redundant code - do not need
//Array.Clear(photoByte, 0, photoByte.Length);
//bm.Dispose();
}
}
//ResizeImage(file, 50, 50, fileReducedDimName +#"\" + i.ToString()+".jpg");
i++;
}
}

PDF Sharp, Image stretch across multiple page

Im trying to allow users to save/view a gantt chart using PDF Sharp, my problem is when a user selects too many rows the image gets compressed and is unreadable, is there a way i can allow/set the image to stretch across multiple pages?
So im creating two images combining them then outputting to a pdf page, I have tried setting the page width to a higher but this didnt work.
Bitmap bitmap = new Bitmap(image.Width + imageTest.Width, Math.Max(image.Height, imageTest.Height));
using (Graphics combineG = Graphics.FromImage(bitmap))
{
combineG.DrawImage(imageTest, 0, 0);
combineG.DrawImage(image, imageTest.Width, 0);
}
//convert to pdf
PdfDocument document = new PdfDocument();
document.Info.Title = "Holiday_Year " + year + "&Quarter " + quarter;
PdfPage page = document.AddPage();
page.Orientation = PdfSharp.PageOrientation.Landscape;
XGraphics gfx = XGraphics.FromPdfPage(page);
XFont font = new XFont("Verdana", 20, XFontStyle.BoldItalic);
gfx.DrawImage(bitmap, new XRect(0, 100, page.Width, 100));
gfx.DrawString("Holiday - Quarter " + quarter + " & Year " + year, font, XBrushes.Black, new XRect(0, 0, page.Width, 40), XStringFormats.Center);
byte[] fileContents = null;
MemoryStream memoryStream = new MemoryStream();
document.Save(memoryStream, true);
fileContents = memoryStream.ToArray();
memoryStream.Close();
Response.Clear();
Response.ContentType = "application/force-download";
Response.AddHeader("content-disposition", "attachment; filename=IEMS-Holiday.pdf");
Response.BinaryWrite(fileContents);
Response.End();
To answer the question: PDFsharp has no built-in feature that allows images to spread across multiple pages.
An image can be drawn on several pages at different positions, displaying different parts of the image. After printing, these pages could be stitched together. The image will be included in the PDF file only once
PDFsharp supports custom page sizes.
I think i have sort of resolved my issue, i added in this code to check the image height, then output to the correct page size.
PdfPage page = document.AddPage();
if (image.Height > 1000)
{
page.Size = PageSize.A1;
}
else
{
page.Size = PageSize.A2;
}
this way it put a large image onto a A1 page etc, then i let adobe figure out how to print the page, seems to work well on viewing and printing
Took me a little while to get the numbers right, but here is what I got and seems to take a single image that is as long as you need it and cuts it up into smaller chunks, placing each chunk on their own page. Of course you will need to adjust the width depending on how much your doing, but again, it's just playing with the numbers.
PdfDocument doc = new PdfDocument();
XImage img = XImage.FromFile(ImgPath);
double captureHeight = 610;
double captureWidth = 790;
double totalHeight = img.PixelHeight / 1.5;
double totalWidth = img.PixelWidth;
PdfPage page = null;
int i = 0;
double saveHeight = 0;
while (saveHeight < totalHeight)
{
page = new PdfPage();
page.Size = PdfSharp.PageSize.Letter;
page.Orientation = PdfSharp.PageOrientation.Landscape;
doc.Pages.Add(page);
XGraphics xgr = XGraphics.FromPdfPage(doc.Pages[i]);
xgr.DrawImage(img, 0, (-i * captureHeight), captureWidth, totalHeight);
saveHeight += captureHeight;
i++;
}
doc.Save(PdfPath);
doc.Close();

Categories

Resources