Combine a lot (~1000-2000) of Jpeg images into one - c#

I'm using the following code right now, and it works for ~300 images, but I have to merge more than one thousand.
private static void CombineThumbStripImages(string[] imageFiles)
{
int index = 0;
using (var result = new Bitmap(192 * imageFiles.Length, 112))
{
using (var graphics = Graphics.FromImage(result))
{
graphics.Clear(Color.White);
int leftPosition = 0;
for (index = 0; index < imageFiles.Length; index++)
{
string file = imageFiles[index];
using (var image = new Bitmap(file))
{
var rect = new Rectangle(leftPosition, 0, 192, 112);
graphics.DrawImage(image, rect);
leftPosition += 192;
}
}
}
result.Save("result.jpg", ImageFormat.Jpeg);
}
}
It throws the following exception:
An unhandled exception of type 'System.Runtime.InteropServices.ExternalException' occurred in System.Drawing.dll
Additional information: A generic error occurred in GDI+.
Can somebody help?

Why not use something like xna or opengl to do this?
I know you can with texture2d ... and as i am currently learning opengl id like to think you can with that but do not know how.
http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.texture2d_members.aspx (SaveAsJPEG on public)
I have done something similar to make sprite sheets, you basically create a huge texture2d using whatever tesselation or organizing algorithm to optimize space usage. There are lots of ways to do this though.
such as
http://xbox.create.msdn.com/en-US/education/catalog/sample/sprite_sheet
would probably only take a couple of hours to do. XNA v easy especially if you are just leaning on the externals. Doing it this was should work pretty well.

The problems lies in this line of code.
result.Save("result.jpg", ImageFormat.Jpeg);
Looks like it throws an error saving the jpeg/png format. I've loaded a copy of your code into VS2008 + Windows 7.
If you want to use the same code, changed your image format to bmp or tiff
result.Save("result.bmp", ImageFormat.Bmp); // this works, but the file size is huge
or
result.Save("result.tiff", ImageFormat.Tiff); // this also works, files is not as big

Related

How do I open a bitmap without C# changing the PixelFormat? [duplicate]

This question already has answers here:
C# - Loading an indexed color image file correctly
(2 answers)
Closed 3 months ago.
I have a bitmap image, myImage.png say. The png has been saved with pixel format Format8bppIndexed, which is something I specifically chose to do. But when I open it in C# using new Bitmap("myImage.png"), I find that it is provided to me as a bitmap in format Format32bppRgb. This isn't what I want, which is why I didn't save it in that format.
I've written code specifically to do turtle-graphics manipulation of a 256-colour indexed raster image; I don't want to rewrite that code to do it with a 32bpp image; I don't see why I should have to. How do I force C# to open my image and just give it to me as it comes, without converting it to a different pixel format? I need an overload of the Bitmap constructor that tells it, "don't try to be helpful, I know what I'm doing". But I can't see one.
If I load an image that's in Format1bppIndexed, C# doesn't do this - I get the binary PNG just as it is, not converted at all.
Under .NET Framework 4.8 and .NET 6.0 for Windows, all four alternatives work for me.
Resulting format is Format8bppIndexed
var imagePath = "image1.png";
using (var bm1 = new Bitmap(imagePath))
{
Console.WriteLine(bm1.PixelFormat.ToString());
}
using (Stream stream = new FileStream(imagePath, FileMode.Open))
{
var bm2 = new Bitmap(stream);
Console.WriteLine(bm2.PixelFormat.ToString());
}
using (Stream stream = new FileStream(imagePath, FileMode.Open))
{
var bm3 = new Bitmap(stream, useIcm: true);
Console.WriteLine(bm3.PixelFormat.ToString());
}
using (var bm4 = new Bitmap(imagePath, useIcm: true))
{
Console.WriteLine(bm4.PixelFormat.ToString());
}
The question and answer linked by Axel substantially answers the question. Here are some details of how I solved my problem.
The problem occurs when you try to open an 8bpp (256-colour indexed) PNG that has palette entries that specify transparency (alpha values less than 255). It does not matter whether the palette entry is actually used for any pixels in the image; the fact that the palette entry exists is enough to cause the problem. When an 8bpp bitmap is created using the System.Drawing library, it is given a default palette, and this default palette may contain colours with transparency, so it may cause this problem if you do not overwrite those palette entries.
There is no easy fix for the issue if you already have the PNG file or if you need to support transparency. In my case I do not actually need transparency in the images I am working with. It was easy enough for me to change the code that creates the PNG files so that it overwrites all the palette entries with dummy entries that are opaque (alpha values of 255).
public static void SetPalette(Bitmap bmp, int numEntriesExcludingBackground)
{
// Ref. https://stackoverflow.com/a/51111141
var palette = bmp.Palette;
int index = 1;
foreach (var entry in GeneratePaletteEntries(numEntriesExcludingBackground))
{
palette.Entries[index] = entry;
++index;
}
for (; index < palette.Entries.Length; ++index) // *
{ // *
palette.Entries[index] = GenerateDummyPaletteEntry(index); // *
} // *
bmp.Palette = palette;
}
(the "//*" decorates lines that were added to address this problem)

fo-Dicom - How do I extract image frames from the DicomFile

I need to extract all image frames from a DICOM SC using fo-DICOM. I have a test app that extracts and displays the images, which works fine. However, I need to save the individual images to a database, and am running into problems.
I have the following code so far:
public void SetImages(DicomFile dicom, ThumbResults results)
{
var images = new DicomImage(dicom.Dataset);
for(var count = 0; count < images.NumberOfFrames; count++)
{
var image = images.RenderImage(count).As<Bitmap>();
using(var stream = new MemoryStream())
{
image.Save(stream, ImageFormat.Jpeg);
results.Images.Add(Convert.ToBase64String(stream.ToArray()));
}
}
}
I get a DicomImagingException, "Cannot cast to 'Bitmap'" on images.RenderImage. It works in my test code, when I call PictureBox.Image = _image.RenderImage(count).As<Bitmap>(); so I figure RenderImage must be specifically for rendering (as the name implies).
How should I go about extracting individual frames to a string that will be saved to the database?
In case someone else runs into this problem, the issue was the original code was in .NET Framework, but the new code was in .NET Core. In Core, the ImageManager does not use the WindowsImageManager by default, so you need to set it manually.
ImageManager.SetImplementation(WinFormsImageManager.Instance);

GDI+ Error converting 24bpp JPG to 8bpp Indexed format (PNG or GIF)

I need a generic function to convert images to a target format. (Format8bppIndexed in this case) The goal is to be able to handle a reasonable range of regular .NET supported images. We have many clients with hundreds of Terabytes of images of varying types and I plan to loop through them all with this code.
Here is an example Image I am trying to convert which throws the errors:
I realize this code has multiple inner try-catches, however I wanted to illustrate the problem.
Within each try below I have comments showing the exception and error I receive.
public static Bitmap ConvertToFormat(this Bitmap Source, PixelFormat TargetFormat)
{
try
{
//This throws OutOfMemoryException: "Out of memory."
return Source.Clone(new Rectangle(0, 0, Source.Width, Source.Height), TargetFormat);
}
catch (OutOfMemoryException)
{
try
{
MemoryStream ResultStream = new MemoryStream();
// This throws ExternalException: "A generic error occurred in GDI+"
Source.Save(ResultStream, ImageFormat.Gif);
ResultStream.Position = 0;
return new Bitmap(ResultStream);
}
catch (ExternalException)
{
// this is just an attempt to break the process down further to try and find the cause:
ImageCodecInfo myImageCodecInfo = GetCodecInfo(ImageFormat.Gif);
EncoderParameters myEncoderParameters = new EncoderParameters(2);
myEncoderParameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionLZW); ;
myEncoderParameters.Param[1] = new EncoderParameter(Encoder.Quality, 0L);
MemoryStream ResultStream = new MemoryStream();
// This throws ExternalException: "A generic error occurred in GDI+"
Source.Save(ResultStream, myImageCodecInfo, myEncoderParameters);
ResultStream.Position = 0;
return new Bitmap(ResultStream);
}
}
}
private static ImageCodecInfo GetCodecInfo(ImageFormat TargetFormat)
{
return ImageCodecInfo.GetImageEncoders().ToList().Find(
delegate (ImageCodecInfo codec)
{ return codec.FormatID == TargetFormat.Guid; });
}
I know the source image is good as I can read the pixels just fine using LockBits(). I am considering using a loop to create a new image pixel by pixel using this, but I would prefer to use the more direct clone option as it automatically handles color palette creation, color matching and dithering.
UPDATE:
I found the code causing the issue, but I am unsure why.
I did not want the file to be locked, so I was using the code below to load the image into memory and then unlock the file. Apparently this was causing issues, but only when calling the Clone() method. When I use that same image with LockBits() it allows me to access every pixel via the memory pointer just fine and also it displays just fine in a PictureBox. Does anyone know why this method is causing a .Clone() error and also how can I load an Image file into memory and then immediatly release the file?
using (Stream s = File.OpenRead(SourceFileName))
{
return (Bitmap)Bitmap.FromStream(s);
}

How to produce quality thumbnails of images without DrawImage

I have a method that I use to get a thumbnail as a Byte[] from an image. The method receives the path to the image.
The class works pretty well, until you reach a size threshold, after which performance drops to pitiful low levels. No, I haven't nailed down the threshold...mainly because after some research, it seems like I'm using a methodology that is inefficient and not very scalable.
My scenario seems to be confirmed in this SO thread.
That question lays out exactly what I am experiencing. The reason it isn't a solution is because the answer talks of using other graphics APIs and Paint overrides, which obviously doesn't apply here. I tried the miscellaneous things, like setting graphics parameters, but that made little difference.
One example of a large image I am dealing with is 3872 x 2592 and about 3.5Mb in size. Some a lot more, and many that size or smaller.
My searching has not yielded much. In fact, it seems that I can only find advice that includes the use of System.Drawing.Graphics.DrawImage(). In one exception, it was suggested to include assemblies to attempt use of PresentationFramework. This is a WinForms app, so that seems a bit much just to grab a thumbnail image.
Another suggestion I came across had to do with extracting Exif information from the file (if I recall) and attempting to grab just that data rather than the entire image. I'm not opposed, but I have yet to find a complete enough example of how that is carried out.
I wonder about P/Invoke options. Better performance than what GDI+ is (apparently) capable of delivering. But, by all means, if there's an optimization I am missing in this code, please point it out.
Here is my current method:
public static Byte[] GetImageThumbnailAsBytes(String FilePath)
{
if (File.Exists(FilePath))
{
Byte[] ba = File.ReadAllBytes(FilePath);
if (ba != null)
{
using (MemoryStream ms = new MemoryStream(ba, false))
{
Int32 thWidth = _MaxThumbWidth;
Int32 thHeight = _MaxThumbHeight;
Image i = Image.FromStream(ms, true, false);
ImageFormat imf = i.RawFormat;
Int32 w = i.Width;
Int32 h = i.Height;
Int32 th = thWidth;
Int32 tw = thWidth;
if (h > w)
{
Double ratio = (Double)w / (Double)h;
th = thHeight < h ? thHeight : h;
tw = thWidth < w ? (Int32)(ratio * thWidth) : w;
}
else
{
Double ratio = (Double)h / (Double)w;
th = thHeight < h ? (Int32)(ratio * thHeight) : h;
tw = thWidth < w ? thWidth : w;
}
Bitmap target = new Bitmap(tw, th);
Graphics g = Graphics.FromImage(target);
g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.Bilinear; //NearestNeighbor
g.CompositingMode = CompositingMode.SourceCopy;
Rectangle rect = new Rectangle(0, 0, tw, th);
g.DrawImage(i, rect, 0, 0, w, h, GraphicsUnit.Pixel);
using (MemoryStream ms2 = new MemoryStream())
{
target.Save(ms2, imf);
target.Dispose();
i.Dispose();
return ms2.ToArray();
}
}
}
}
return new Byte[] { };
}
P.S. I got here in the first place by using the Visual Studio 2012 profiler, which told me that DrawImage() is responsible for 97.7% of the CPU load while loading images (I did a pause/start to isolate the loading code).

Reduce image quality/size without loading into memory?

I am totally stuck on image resizing because I am getting OutOfMemoryException using the typical examples of image resizing that can be found on the many questions that feature OOMs.
I even tried DynamicImage, which can be found on Nuget, and this also threw an OutOfMemoryException.
Can anyone tell me how I can reduce the quality/size of an image in C#, without loading it into memory?
Edit: I want the c# equivalent to this, if there is one?
Edit: I give up with the typical methods of resizing, as I just can't avoid OutOfMemoryExceptions on my live site, which is running on an old server.
Further Edit: My server's OS is Microsoft Server 2003 Standard Edition
I can post examples of my code, but I'm trying to find a way around OutOfMemoryExceptions.
public static void ResizeImage(string imagePath, int imageWidth, int imageHeight, bool upscaleImage) {
using (Image image = Image.FromFile(imagePath, false)) {
int width = image.Width;
int height = image.Height;
if (width > imageWidth || height > imageHeight || upscaleImage) {
image.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
image.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipX);
float ratio = 0;
if (width > height) {
ratio = (float)width / (float)height;
width = imageWidth;
height = Convert.ToInt32(Math.Round((float)width / ratio));
}
else {
ratio = (float)height / (float)width;
height = imageHeight;
width = Convert.ToInt32(Math.Round((float)height / ratio));
}
using (Bitmap bitmap = new Bitmap(width, height)) {
bitmap.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphic = Graphics.FromImage(bitmap)) {
graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphic.DrawImage(image, 0, 0, width, height);
string extension = ".jpg"; // Path.GetExtension(originalFilePath);
using (EncoderParameters encoderParameters = new EncoderParameters(1)) {
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
using (MemoryStream imageMemoryStream = new MemoryStream()) {
bitmap.Save(imageMemoryStream, GetImageCodec(extension), encoderParameters);
using (Image result = Image.FromStream(imageMemoryStream, true, false)) {
string newFullPathName = //path;
result.Save(newFullPathName);
}
}
}
}
}
}
}
}
I also tried this code as I hoped GetThumbnailImage would reduce the picture quality/size for me, but this is also throwing an OOM exception:
viewModel.File.SaveAs(path);
Image image = Image.FromFile(path);
Image thumbnail = image.GetThumbnailImage(600, 600, null, new IntPtr());
image.Dispose();
File.Delete(path);
thumbnail.Save(path);
thumbnail.Dispose();
Again, both my code examples work for me in my local machine, so I am not trying to find faults/fixes in the code as they should be fine. I'm looking for any solution to avoid the OOM exceptions, I had the idea of reducing the fize size somehow without loading the image into memory, but any alternative ideas that can help me would be appreciated.
You can try using ImageMagick via command line or the .NET bindings. ImageMagick has some options to resize as the file is being read, which should reduce memory consumption.
It may be that the "image" you are using is either not a supported format or is corrupted.
I failed to mention that I inherited legacy code that was not disposing of images properly, because I did not think it was relevant since fixing it. The strange thing is, after restarting the website and the AppPool, I was able to upload pictures again without getting an OutOfMemoryExcepiton. I'm struggling to understand why this happened as I have changed the code to dispose of images properly and have done several deploys since, so I would expect that to clear any undisposed images from memory? All the code for picture resizing and uploading was in a static class and I believe that GC.collect() does not work on static variables?
My theory is that the undisposed images have built up in memory and have remained even when I have redepolyed to the site, as that's the only conclusion I can reach since the code began working again after restarting the app pool.
I would delete my question but it has been answered now, happy to reassign the answer if anyone can help explain what was going on here.

Categories