EPPlus - very slow adding multiple images in a loop - c#

I have big problem with EPPlus. When I create simple Excel file with 20 images from local resource (disk C:) - everything is OK - it lasts ~3 second. But when I try to do it with >200 pictures - I have to wait ~20 sec to generate file. With 500 items I have to wait ~4 minutes... Why? What I can do to fix that problem?
That's my code:
Image image = Image.FromFile(filePath);
string name = "pic" + rowIndex.ToString() + columnIndex.ToString();
using (ExcelPicture picture = ws.Drawings.AddPicture(name, image))
{
image.Dispose();
picture.From.Column = columnIndex;
picture.To.Column = columnIndex;
picture.From.Row = rowIndex;
picture.To.Row = rowIndex;
picture.From.ColumnOff = Pixel2MTU(3);
picture.From.RowOff = Pixel2MTU(2);
picture.SetSize(width, height);
if (position > 0)
{
picture.SetPosition(0, position);
}
}

I have noticed that while AddPicture has improved speed after the latest update - however setting the row and column offset of the picture is getting progressively slower. And when i analysed the source code it seems for each of this property lot of background work is happening on xml.

I encountered the same problem, I have a solution, that is, I can calculate the size of each picture ,then calculate the row height according to the picture size, and adjust the height of the entire workbook according to their row height calculated from above info. finally add the picture,
in short ,set the desired rows height before you add any picture.
I hope it will be useful to you

Related

Wrong image width and height when read it with C#

I've got a strange problem. There is an image. Unfortunately it's too big to show it in question. But you can download it. If you can't do it from OneDrive this is another way.
This image seems to be ordinary, but it's not.
When we open properties we will see this:
We need to keep in mind dimensions of this picture: Width is 3000px and Height is 4000px. It looks correct because image is portret.
Then lets try to read it with C#:
private static void TestImage()
{
using (FileStream file1 = new FileStream("DSC_2446.JPG", FileMode.Open))
{
Console.WriteLine("DSC_2446.JPG :");
using (var img1 = System.Drawing.Image.FromStream(file1))
{
Console.WriteLine($" Width = {img1.Width}");
Console.WriteLine($" Height = {img1.Height}");
}
}
Console.Read();
}
And in results we see some magic!!!
So I've got completely wrong values. Values are switched between properties.
Does someone know why it can happens and how to detect/fix this behavior?
The problem is the EXIF version. You can use this site to get the real data https://exif.tools/meta/Exif-Version/0231 and you will see
Also according to this Post you can get your image's orientation by
var orientation = (int)img1.GetPropertyItem(274).Value[0];
//orientation = 6
the value 6 means rotate 90 degrees.
There is the reference of the value 6. https://exiftool.org/TagNames/EXIF.html

How to load a part of a TIFF image without loading whole image into memory?

How to load a part of a *.tif image without loading this image into memory.
I have to work with big TIFF files. (> 4 GB). I tried to read this file using BinaryReader, and using BitMiracle.LibTiff.Classic to convert bytes into image. But I didn't find example for how to read a specific pixel in a TIFF file.
Maybe be you have some solution for this task.
Lets say i have a BigScan.tif file and it is always:
Image Compression - NONE
Pixel Order - Interleaved (RGBRGB)
Byte Order - IBM PC
I have some variable:
ImagePart with User Defined Width
ImagePart with User Define Height
ImagePArt with User Defined Location
The question is, how could I get ImagePart from BigScan.tif?
But it would be best to have the ability to read information of the pixel in "BigScan.tif" with (x,y) coorinates.
I need to read a pixel from the BigScan.tif in specified place, with such function:
public Color GetPixelColorFromTiffImage(string TiffFileName, int PixelPositionX, int PixelPositionY)
{
//Some Code
return returnedColor;
}
Very strange but the support did`t unswer my quastion. May be somebody knows it. Why dose this part of the code from BitMiracle Samples wrote to 'raster' array numbers like "-11512229", "-11838376" and so on.
using (Tiff image = Tiff.Open(fullImageLocation, "r"))
{
// Find the width and height of the image
FieldValue[] value = image.GetField(TiffTag.IMAGEWIDTH);
width = value[0].ToInt();
value = image.GetField(TiffTag.IMAGELENGTH);
height = value[0].ToInt();
int imageSize = height * width;
int[] raster = new int[imageSize];
// Read the image into the memory buffer
if (!image.ReadRGBAImage(width, height, raster))
{
MessageBox.Show("Could not read image");
}
using (Bitmap btm = new Bitmap(200, 200))
{
for (int i = 0; i < btm.Width; ++i)
for (int j = 0; j < btm.Height; ++j)
btm.SetPixel(i, j, getSample(i + 330, j + 30, raster, width, height));
ReternedBitmap = btm;
}
}//using
Your question is unclear (you ask at least two different questions).
If you need to crop a part of a larger image then you need to:
read each relevant scanline of a source image
copy part of that scanline to a new image.
If you need to get color value of a single pixel in a given location than again you need to:
read relevant scanline
find relevant bytes in that scanline
pack those bytes into a Color structure or whatever else
You didn't specify what are Photometric, BitsPerSample and SamplesPerPixel values for your image, so it's hard to tell what exactly are you dealing with.
Most probably, you are faced with geo-images. If so, they are probably RGB, 24bit, tiled images.
For tiled images it is possible to read only small part (say, 256 x 256 pixels) of the image at once. But even if they are stripped, one scanline of such an image will take only about 1 MB of memory (219 000 pixels * 3 bytes per pixel). It's nothing if you are really need to process such big images.
I wouldn't recommend you to try developing your own parser. It's not that easy if you know only basics about TIFF format.

Change the inset of a specific page with abcpdf

I use abcpdf to create a pdf from a html string. The following snippet shows the way I do it:
var pdfDocument = new Doc();
pdfDocument.Page = pdfDocument.AddPage();
pdfDocument.Font = pdfDocument.AddFont("Times-Roman");
pdfDocument.FontSize = 12;
var documentId = pdfDocument.AddImageHtml(innerHtml);
var counter = 0;
while (true)
{
counter++;
if (!pdfDocument.Chainable(documentId))
{
break;
}
pdfDocument.Page = pdfDocument.AddPage();
// how to add a inset of 20, 0 on every page after the second? The following 2lines don't affect the pdf pages
if (counter >= 3)
pdfDocument.Rect.Inset(20, 0);
documentId = pdfDocument.AddImageToChain(documentId);
}
After the AddPage I want to add a new inset for every page with pagenumber > 2
Thanks in advance
I can assure you that your inset call will be having an effect. Try calling FrameRect on each page and you shuld be able to see this.
So why is it that you are not seeing the effect you are expecting?
Well your HTML has a fixed width at the point you call AddImageUrl/HTML. Each subsequent call to AddImageToChain utilizes this fixed width.
If in your 'inset' pages you reduce the height of the area on the page you will get the next chunk of the page truncated to that height.
If in your 'inset' pages you reduce the width of the area then things become more difficult. The width is fixed so it can't be changed. Instead ABCpdf will scale the page down so that it does fit.
So if you reduce the width from say 600 points to 580 points then the scale factor for this content would be 580/600 = 97%.
Most likely this is what is happening but because the scale factor is small you are not noticing it.
I work on ABCpdf and my replies may contain concepts based around ABCpdf. It's what I know. :-)
Comment #1 from SwissCoder was right. AddImageHtml already adds the first page. After also contacting the WebSuperGoo support, they recommend me to use PageCountof class Doc.
while (true)
{
if (!pdfDocument.Chainable(documentId))
{
break;
}
pdfDocument.Page = pdfDocument.AddPage();
if (pdfDocument.PageCount >= 3)
pdfDocument.Rect.Inset(0, 20);
documentId = pdfDocument.AddImageToChain(documentId);
}
The other solution would be to adjust the index to count unto required pagenumber - 1 as ÀddImageHtml already adds the first page to the document.

imagelist set imagesize doesn't resize subsequently added images

In a function to resize the displayed images in CurrentImages, the images, all of a sudden and only in this production version, not in a previous test version, don't seem to scale.
Instead they are clipped.
private void ResizeCurrentImages(double zoom)
{
foreach (Image image in this.CurrentImages.Images)
{
image.Dispose();
}
this.CurrentImages.Images.Clear();
this.CurrentImages.ImageSize = new Size((int)Math.Floor(this.MAX_WIDTH * zoom), (int)Math.Floor(this.MAX_HEIGHT * zoom));
foreach (Image image in this.OriginalImages.Images)
{
this.CurrentImages.Images.Add(image);
}
}
where MAX_WIDTH = 161 and MAX_HEIGHT = 256.
According to MSDN documentation, I'm following the right steps, first setting the new imagesize, and then adding the images.
Has anyone else had this problem before?
It turns out the error was somewhere else in the code. There is another section changing the images, and it created a Bitmap using an original size, instead of the zoomed size.

C# Image.Clone Out of Memory Exception

Why am I getting an out of memory exception?
So this dies in C# on the first time through:
splitBitmaps.Add(neededImage.Clone(rectDimensions, neededImage.PixelFormat));
Where splitBitmaps is a List<BitMap> BUT this works in VB for at least 4 iterations:
arlSplitBitmaps.Add(Image.Clone(rectDimensions, Image.PixelFormat))
Where arlSplitBitmaps is a simple array list. (And yes I've tried arraylist in c#)
This is the fullsection:
for (Int32 splitIndex = 0; splitIndex <= numberOfResultingImages - 1; splitIndex++)
{
Rectangle rectDimensions;
if (splitIndex < numberOfResultingImages - 1)
{
rectDimensions = new Rectangle(splitImageWidth * splitIndex, 0,
splitImageWidth, splitImageHeight);
}
else
{
rectDimensions = new Rectangle(splitImageWidth * splitIndex, 0,
sourceImageWidth - (splitImageWidth * splitIndex), splitImageHeight);
}
splitBitmaps.Add(neededImage.Clone(rectDimensions, neededImage.PixelFormat));
}
neededImage is a Bitmap by the way.
I can't find any useful answers on the intarweb, especially not why it works just fine in VB.
Update:
I actually found a reason (sort of) for this working but forgot to post it. It has to do with converting the image to a bitmap instead of just trying to clone the raw image if I remember.
Clone() may also throw an Out of memory exception when the coordinates specified in the Rectangle are outside the bounds of the bitmap. It will not clip them automatically for you.
I found that I was using Image.Clone to crop a bitmap and the width took the crop outside the bounds of the original image. This causes an Out of Memory error. Seems a bit strange but can beworth knowing.
I got this too when I tried to use the Clone() method to change the pixel format of a bitmap. If memory serves, I was trying to convert a 24 bpp bitmap to an 8 bit indexed format, naively hoping that the Bitmap class would magically handle the palette creation and so on. Obviously not :-/
This is a reach, but I've often found that if pulling images directly from disk that it's better to copy them to a new bitmap and dispose of the disk-bound image. I've seen great improvement in memory consumption when doing so.
Dave M. is on the money too... make sure to dispose when finished.
I struggled to figure this out recently - the answers above are correct. Key to solving this issue is to ensure the rectangle is actually within the boundaries of the image. See example of how I solved this.
In a nutshell, checked to if the area that was being cloned was outside the area of the image.
int totalWidth = rect.Left + rect.Width; //think -the same as Right property
int allowableWidth = localImage.Width - rect.Left;
int finalWidth = 0;
if (totalWidth > allowableWidth){
finalWidth = allowableWidth;
} else {
finalWidth = totalWidth;
}
rect.Width = finalWidth;
int totalHeight = rect.Top + rect.Height; //think same as Bottom property
int allowableHeight = localImage.Height - rect.Top;
int finalHeight = 0;
if (totalHeight > allowableHeight){
finalHeight = allowableHeight;
} else {
finalHeight = totalHeight;
}
rect.Height = finalHeight;
cropped = ((Bitmap)localImage).Clone(rect, System.Drawing.Imaging.PixelFormat.DontCare);
Make sure that you're calling .Dispose() properly on your images, otherwise unmanaged resources won't be freed up. I wonder how many images are you actually creating here -- hundreds? Thousands?

Categories