Merging multiple TIF files in C# - c#

I am trying to combine multiple .tif files into one, but after merging, the new .tif file's image quality is very low.
How to increase that quality?
I want the new merged file quality as original quality. I am using this code to merged the tif file
string[] sa = path;
ImageCodecInfo info = null;
foreach (ImageCodecInfo ice in ImageCodecInfo.GetImageEncoders())
if (ice.MimeType == "image/tiff")
info = ice;
Encoder enc = Encoder.SaveFlag;
EncoderParameters ep = new EncoderParameters(1);
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.MultiFrame);
Bitmap pages = null;
int frame = 0;
foreach (string s in sa)
{
// using (FileStream fileStream = System.IO.File.Open(s, FileMode.Open))
{
if (frame == 0)
{
pages = (Bitmap)Image.FromFile(s);
//save the first frame
pages.Save(filepath, info, ep);
}
else
{
//save the intermediate frames
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.FrameDimensionPage);
Bitmap bm = (Bitmap)Image.FromFile(s);
pages.SaveAdd(bm, ep);
}
if (frame == sa.Length - 1)
{
//flush and close.
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.Flush);
pages.SaveAdd(ep);
}
frame++;
}
}

Not 100% sure about this one, but I believe Multi-Frame TIFFs are encoded using G3 by default. Just giving something to try, change this:
Encoder enc = Encoder.SaveFlag;
EncoderParameters ep = new EncoderParameters(1);
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.MultiFrame);
to this:
Encoder enc = Encoder.SaveFlag;
Encoder encComp = Encoder.Encoder.Compression;
EncoderParameters ep = new EncoderParameters(2);
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.MultiFrame);
ep.Param[1] = new EncoderParameter(encComp, (long)EncoderValue.CompressionLZW);
And try again (you could also use CompressionNone instead of CompressionLZW, but LZW is lossless so it should not reduce the quality)

Related

Merge multiple multi-page tiff images to a single tiff C#

In my scenario I have 3 or more multi-page tiff images which I need to merge into a single tiff image.
Below is the the code I have tried. It merges in to a single tiff image but only with first page of all tiff images.
private static void MergeTiff(string[] sourceFiles)
{
string[] sa = sourceFiles;
//get the codec for tiff files
ImageCodecInfo info = null;
foreach (ImageCodecInfo ice in ImageCodecInfo.GetImageEncoders())
if (ice.MimeType == "image/tiff")
info = ice;
//use the save encoder
Encoder enc = Encoder.SaveFlag;
EncoderParameters ep = new EncoderParameters(1);
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.MultiFrame);
Bitmap pages = null;
int frame = 0;
foreach (string s in sa)
{
if (frame == 0)
{
MemoryStream ms = new MemoryStream(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, #"C:\Data_Warehouse\SVNRepository\CD.BNS.W5555.LT45555C.D180306.T113850.Z0101\", s)));
pages = (Bitmap)Image.FromStream(ms);
var appDataPath = #"C:\Data_Warehouse\SVNRepository\Tiffiles\";
var filePath = Path.Combine(appDataPath, Path.GetRandomFileName() + ".tif");
//save the first frame
pages.Save(filePath, info, ep);
}
else
{
//save the intermediate frames
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.FrameDimensionPage);
try
{
MemoryStream mss = new MemoryStream(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, #"C:\Data_Warehouse\SVNRepository\CD.BNS.W5555.LT45555C.D180306.T113850.Z0101\", s)));
Bitmap bm = (Bitmap)Image.FromStream(mss);
pages.SaveAdd(bm, ep);
}
catch (Exception e)
{
//LogError(e, s);
}
}
if (frame == sa.Length - 1)
{
//flush and close.
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.Flush);
pages.SaveAdd(ep);
}
frame++;
}
}
I need to join multiple tiff images with all pages from each tiff image. Please advise!
Thanks
EDIT: Updated from below answer
if (frame == 0)
{
MemoryStream ms = new MemoryStream(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, #"C:\OMTest\Working\", s)));
pages = (Bitmap)Image.FromStream(ms);
var appDataPath = #"C:\Data_Warehouse\SVNRepository\Tiffiles\";
var filePath = Path.Combine(appDataPath, Path.GetRandomFileName() + ".tif");
//save the first frame
pages.Save(filePath, info, ep);
//Save the second frame if any
int frameCount1 = pages.GetFrameCount(FrameDimension.Page);
if (frameCount1 > 1)
{
for (int i = 1; i < frameCount1; i++)
{
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.FrameDimensionPage);
pages.SelectActiveFrame(FrameDimension.Page, i);
pages.SaveAdd(pages, ep);
}
}
}
else
{
//save the intermediate frames
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.FrameDimensionPage);
try
{
MemoryStream mss = new MemoryStream(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, #"C:\OMTest\Working\", s)));
Bitmap bm = (Bitmap)Image.FromStream(mss);
int frameCount = bm.GetFrameCount(FrameDimension.Page);
for (int i = 0; i < frameCount; i++)
{
bm.SelectActiveFrame(FrameDimension.Page, i);
pages.SaveAdd(bm, ep);
}
}
catch (Exception e)
{
//LogError(e, s);
}
}
You need to select the active frame to ensure you are getting all pages on the TIFF. In your code you need to get the count of frames and loop through these.
The code in your else block might look something like this:
MemoryStream mss = new MemoryStream(File.ReadAllBytes(Path.Combine(Environment.CurrentDirectory, #"C:\Data_Warehouse\SVNRepository\CD.BNS.W5555.LT45555C.D180306.T113850.Z0101\", s)));
Bitmap bm = (Bitmap)Image.FromStream(mss);
int frameCount = bm.GetFrameCount(FrameDimension.Page);
for(int i=0;i<frameCount;i++){
bm.SelectActiveFrame(FrameDimension.Page, i);
pages.SaveAdd(bm, ep);
}
You may have to tweak it as I haven't tested it.
The given code works great to merge single-page TIFF files into a single multi-page TIFF, however, if there are multi-page TIFF files as sources, it will only merge their first page in the resulting TIFF file: the other ones will be discarded.
Since we couldn't find any working samples that could work around this issue, we ended up coding a small C# helper class, which later became a full-fledged multi-platform console application written in .NET Core 2 and C#. We called the project MergeTIFF and we released the whole source code on GitHub under GNU v3 license, so that everyone else can use it as well; we also released the binaries for Windows and Linux (32-bit and 64-bit).
Here's the relevant excerpt of the C# code:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace MergeTiff.NET
{
/// <summary>
/// A small helper class to handle TIFF files
/// </summary>
public static class TiffHelper
{
/// <summary>
/// Merges multiple TIFF files (including multipage TIFFs) into a single multipage TIFF file.
/// </summary>
public static byte[] MergeTiff(params byte[][] tiffFiles)
{
byte[] tiffMerge = null;
using (var msMerge = new MemoryStream())
{
//get the codec for tiff files
ImageCodecInfo ici = null;
foreach (ImageCodecInfo i in ImageCodecInfo.GetImageEncoders())
if (i.MimeType == "image/tiff")
ici = i;
Encoder enc = Encoder.SaveFlag;
EncoderParameters ep = new EncoderParameters(1);
Bitmap pages = null;
int frame = 0;
foreach (var tiffFile in tiffFiles)
{
using (var imageStream = new MemoryStream(tiffFile))
{
using (Image tiffImage = Image.FromStream(imageStream))
{
foreach (Guid guid in tiffImage.FrameDimensionsList)
{
//create the frame dimension
FrameDimension dimension = new FrameDimension(guid);
//Gets the total number of frames in the .tiff file
int noOfPages = tiffImage.GetFrameCount(dimension);
for (int index = 0; index < noOfPages; index++)
{
FrameDimension currentFrame = new FrameDimension(guid);
tiffImage.SelectActiveFrame(currentFrame, index);
using (MemoryStream tempImg = new MemoryStream())
{
tiffImage.Save(tempImg, ImageFormat.Tiff);
{
if (frame == 0)
{
//save the first frame
pages = (Bitmap)Image.FromStream(tempImg);
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.MultiFrame);
pages.Save(msMerge, ici, ep);
}
else
{
//save the intermediate frames
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.FrameDimensionPage);
pages.SaveAdd((Bitmap)Image.FromStream(tempImg), ep);
}
}
frame++;
}
}
}
}
}
}
if (frame >0)
{
//flush and close.
ep.Param[0] = new EncoderParameter(enc, (long)EncoderValue.Flush);
pages.SaveAdd(ep);
}
msMerge.Position = 0;
tiffMerge = msMerge.ToArray();
}
return tiffMerge;
}
}
}
For additional info and/or to download it, you can take a look at the following resources that we published to better document the whole project:
MergeTIFF on GitHub
Specifications, dependencies and other info

C# how to compress .png without transparent background lost?

I use the following codes to compress an image file to jpg:
// _rawBitmap = a Bitmap object
ImageCodecInfo encoder = GetEncoder(ImageFormat.Jpeg);
System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 50L);
myEncoderParameters.Param[0] = myEncoderParameter;
ImageConverter imageConverter = new ImageConverter();
byte[] b = (byte[])imageConverter.ConvertTo(_rawBitmap, typeof(byte[]));
using (MemoryStream ms = new MemoryStream())
{
ms.Write(b, 0, b.Length);
ms.Seek(0, SeekOrigin.Begin);
rawBitmap.Save(ms, encoder, myEncoderParameters);
bmp = ToBitmap(ms.ToArray());
return (Bitmap)bmp.Clone();
}
but when I try to compress a png file with same way but only change:
ImageCodecInfo encoder = GetEncoder(ImageFormat.Jpeg);
to
ImageCodecInfo encoder = GetEncoder(ImageFormat.Png);
my png file lost transparent data.
so how to compress a PNG file properly?
There are a couple of problems here.
First, you don't need to set those EncoderParams for quality for PNG.
Second, you don't need ImageConverter
Third, you are writing whatever ImageConverter produces to your memory stream, rewinding, and then writing the encoded PNG over the top of it-- it is likely that you have a PNG file with a bunch of garbage at the end of it as a result.
The simplified approach should be:
using (MemoryStream ms = new MemoryStream())
{
rawBitmap.Save(ms, ImageFormat.Png);
}
If you want to load your bitmap back, open it from the stream, but don't close the stream (the stream will be disposed when your returned Bitmap is disposed):
var ms = new MemoryStream();
rawBitmap.Save(ms, ImageFormat.Png);
ms.Seek(0, SeekOrigin.Begin);
return Bitmap.FromStream(ms);
You can use nQuant (https://www.nuget.org/packages/nQuant/)
With it, you convert 32 bit PNGs to high quality 8 bit PNGs
private static int alphaTransparency = 10;
private static int alphaFader = 70;
var quantizer = new WuQuantizer();
using(var bitmap = new Bitmap(sourcePath))
{
using(var quantized = quantizer.QuantizeImage(bitmap, alphaTransparency, alphaFader))
{
quantized.Save(targetPath, ImageFormat.Png);
}
}

Compressing a bitmap using Encoder in C#

I am doing a WPF forms application for compress .bmp images in my project.
I want to compress an image using RLE compression algorithm and Huffman.
I tried below code by referring MSDN, but original image and compressed images have same sizes.
private List<double> HuffmanAndRle(int size)
{
try
{
if((size > _image.Width) || (size > _image.Height))
throw new Exception("size is too large");
Bitmap bitmaporg = crop(size);
Bitmap bitmapRLE = crop(size);
Bitmap bitmapHuffman = crop(size);
ImageCodecInfo myImageCodecInfo ;
myImageCodecInfo = GetEncoderInfo("image/bmp");
var myEncoderParameters = new EncoderParameters(1);
var enc = new Encoder(Encoder.Compression.Guid);
myEncoderParameters.Param[0] = new EncoderParameter(enc, (long)EncoderValue.CompressionCCITT4);
bitmapHuffman.Save("C:/Hffman.bmp", myImageCodecInfo, myEncoderParameters);
//bitmapHuffman.Save()
var myEncoderParameters1 = new EncoderParameters(1);
myEncoderParameters1.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionNone);
bitmaporg.Save("C:/org.bmp", myImageCodecInfo, myEncoderParameters1);
var myEncoderParameters2 = new EncoderParameters(1);
myEncoderParameters2.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionRle);
bitmapRLE.Save("C:/RLE.bmp", myImageCodecInfo, myEncoderParameters2);
var org =new BitmapImage(new Uri("C:/org.bmp"));
var rle = new BitmapImage(new Uri("C:/RLE.bmp"));
var huffman = new BitmapImage(new Uri("C:/Hffman.bmp"));
var dobles = new List<double>();
dobles.Add(ImageSize2(huffman)/ImageSize2(org));
dobles.Add(ImageSize2(rle)/ImageSize2(org));
return dobles;
}
catch (Exception e)
{
MessageBox.Show("Error : " + e.Message);
return null;
}
}
so where is the problem ??
Huffman algorithm is not good for compression for all files, if we have a small amount of characters our compress result will be better, but if we have more then compression will be less or in some cases more then original file size. The best compression we have when we used Huffman algorithm on text files.

MemoryTributary GetBuffer

My application is MVC5 C#, I use memorystream to generate images using the following:
using (var memStream = new MemoryStream())
{
const int quality = 90;
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)quality);
objImage.Save(memStream, GetImageCodeInfo("image/png"), encoderParameters);
data = this.File(memStream.GetBuffer(), "image/png");
memStream.Dispose();
}
However I get OutOfMemoryException with some files. I was reading about MemoryTributary but could not find a solution to GetBuffer! Would appreciate your suggestions.
How about leaving all the buffer stuff out?
var memStream = new MemoryStream();
const int quality = 90;
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)quality);
objImage.Save(memStream, GetImageCodeInfo("image/png"), encoderParameters);
memStream.Seek(0, SeekOrigin.Begin);
return File(memStream, "image/png");
The FileStreamResult will dispose the MemoryStream, no need to worry about that.

How can I optimize this code for creating TIFF files and/or what alternate libraries should I consider to improve performance?

I am creating a process to extract a TIFF, perform an operation on one of the images, and then rebuild the TIFF. The operation in the middle doesn't matter, however, extraction and rebuild are proving to be very costly in terms of processing.
const string tifPath = "14 page.TIFF";
MemoryStream[] imageStreams;
var stream = new MemoryStream();
//extract tiff
using (var imageFile = Image.FromFile(tifPath))
{
var imagePageCount = imageFile.GetFrameCount(FrameDimension.Page);
imageStreams = new MemoryStream[imagePageCount];
var frameDimension = new FrameDimension(imageFile.FrameDimensionsList[0]);
for (var i = 0; i < imagePageCount; i++)
{
imageFile.SelectActiveFrame(frameDimension, i);
var bmp = new Bitmap(imageFile);
bmp.Save(stream, ImageFormat.Bmp);
imageStreams[i] = stream;
}
}
//build tiff
var encoder = Encoder.SaveFlag;
var encoderInfo = ImageCodecInfo.GetImageEncoders().First(i => i.MimeType == "image/tiff");
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(encoder, (long)EncoderValue.MultiFrame);
stream = new MemoryStream();
var firstImage = new Bitmap(Image.FromStream(imageStreams[0]));
firstImage.Save(stream, encoderInfo, encoderParameters);
encoderParameters.Param[0] = new EncoderParameter(encoder, (long)EncoderValue.FrameDimensionPage);
for (var i = 1; i < imageStreams.Length; i++)
{
var img = Image.FromStream(imageStreams[i]);
firstImage.SaveAdd(img, encoderParameters);
}
encoderParameters.Param[0] = new EncoderParameter(encoder, (long)EncoderValue.Flush);
firstImage.SaveAdd(encoderParameters);
I found that clearing my stream helped in some places. With Microsoft's and other libraries (LeadTools and LibTiff) it averages around 5 seconds. I am not set on using a C# solution, but trying to find a way to squeeze it down as much as possible.
I couldn't find a good way to parallelize the creation process. Attempting to build an Image from a MemoryStream that was built outside of the Parallel.ForEach only threw a general GDI+ error.
const string tifPath = "14 Page.TIFF";
//extract tiff
var imageFile = Image.FromFile(tifPath);
var imagePageCount = imageFile.GetFrameCount(FrameDimension.Page);
var imageStreams = new Bitmap[imagePageCount];
var rangePartioner = Partitioner.Create(0, imagePageCount);
var frameDimension = new FrameDimension(imageFile.FrameDimensionsList[0]);
imageFile.Dispose();
Parallel.ForEach(rangePartioner, (range, loopState) =>
{
for (var i = range.Item1; i < range.Item2; i++)
{
using (var imageFile2 = Image.FromFile(tifPath))
{
imageFile2.SelectActiveFrame(frameDimension, i);
imageStreams[i] = new Bitmap(imageFile2);
}
}
});
//build tiff
var encoder = Encoder.SaveFlag;
var encoderInfo = ImageCodecInfo.GetImageEncoders().First(i => i.MimeType == "image/tiff");
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(encoder, (long) EncoderValue.MultiFrame);
var stream2 = new MemoryStream();
using(var firstImage = imageStreams[0])
{
firstImage.Save(stream2, encoderInfo, encoderParameters);
encoderParameters.Param[0] = new EncoderParameter(encoder, (long) EncoderValue.FrameDimensionPage);
for (var i = 1; i < imageStreams.Length; i++)
{
firstImage.SaveAdd(imageStreams[i], encoderParameters);
}
encoderParameters.Param[0] = new EncoderParameter(encoder, (long) EncoderValue.Flush);
firstImage.SaveAdd(encoderParameters);
}
This shaved off about 2 seconds. Still hunting for a good way to handle the second half.
EDIT: Added suggested changes of only converting the image and not saving it to a stream.
EDIT: Added remainder of fixes.
Start profiling your code and see which parts take time. It's hard to assume things. You can parallelize extracting TIFF, same with building. That would get you 4x speedup.

Categories

Resources