I have an array of bitmaps which need compiling into a single, multi-page tiff image, however when saving the bitmap to a MemoryStream object I get the "Parameter is invalid" error message with no other detail.
The problem code:
private static MemoryStream convertToStream(Bitmap b)
{
using (MemoryStream ms = new MemoryStream())
{
b.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff);
return ms;
}
}
The bitmaps are in 600x600 DPI with an average of 35 pages (equating to 35 bitmaps)
What I have tried:
I have confirmed the bitmap array contains the expected contents by saving them to disk
Any help is appreciated.
Stack trace:
at System.Drawing.Image.Save(Stream stream, ImageCodecInfo encoder, EncoderParameters encoderParams)
at System.Drawing.Image.Save(Stream stream, ImageFormat format)
at ConfigureTiffDPI.Program.convertToStream(Bitmap b) in C:\Users<user>\source\repos\ConfigureTiffDPI\ConfigureTiffDPI\Program.cs:line 37
at ConfigureTiffDPI.Program.CompileBitmaps(Bitmap[] bitmaps) in C:\Users<user>\source\repos\ConfigureTiffDPI\ConfigureTiffDPI\Program.cs:line 44
at ConfigureTiffDPI.Program.Main(String[] args) in C:\Users<user>\source\repos\ConfigureTiffDPI\ConfigureTiffDPI\Program.cs:line 29
[EDIT]
Given the comments I have edited the code as such:
MemoryStream ms = new MemoryStream();
b.Save(ms, System.Drawing.Imaging.ImageFormat.Tiff);
return ms;
However the error still persists
It's not possible to know why calling Save(stream) fails without knowing what the bitmap object contains exactly. But in general, to save multi-page TIFF, you need to call System.Drawing.Image.Save() once, followed by repeated calls to SaveAdd. The approach is the same whether you're saving to disk file or to memory stream.
There's a code sample from Microsoft on this page that saves 3 pages to a TIFF file on disk. I changed the first parameter of multi.Save() from "Multiframe.tiff" to ms, which is the MemoryStream, and the code produced a valid multi-page TIFF in the stream. Here's the modified code:
Bitmap multi;
Bitmap page2;
Bitmap page3;
ImageCodecInfo myImageCodecInfo = null;
Encoder myEncoder;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;
// Create three Bitmap objects. imageFiles is a list of strings holding file names
multi = new Bitmap(imageFiles[0]);
page2 = new Bitmap(imageFiles[1]);
page3 = new Bitmap(imageFiles[2]);
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
if (encoders[j].MimeType == "image/tiff"/*mimeType*/)
{
myImageCodecInfo = encoders[j];
break;
}
myEncoder = Encoder.SaveFlag;
myEncoderParameters = new EncoderParameters(1);
myEncoderParameter = new EncoderParameter(myEncoder, (long)EncoderValue.MultiFrame);
myEncoderParameters.Param[0] = myEncoderParameter;
multi.Save(ms, myImageCodecInfo, myEncoderParameters);
// Save the second page (frame).
myEncoderParameter = new EncoderParameter(myEncoder, (long)EncoderValue.FrameDimensionPage);
myEncoderParameters.Param[0] = myEncoderParameter;
multi.SaveAdd(page2, myEncoderParameters);
// Save the third page (frame).
myEncoderParameter = new EncoderParameter(myEncoder, (long)EncoderValue.FrameDimensionPage);
myEncoderParameters.Param[0] = myEncoderParameter;
multi.SaveAdd(page3, myEncoderParameters);
// Close the multiple-frame file.
myEncoderParameter = new EncoderParameter(myEncoder, (long)EncoderValue.Flush);
myEncoderParameters.Param[0] = myEncoderParameter;
multi.SaveAdd(myEncoderParameters);
Another option to save multi-page TIFF to a stream is to use a professional imaging SDK like LEADTOOLS. (Disclosure: I work for its vendor). The code to load a list of images and save them into a TIFF stream is simply this:
using (RasterCodecs codecs = new RasterCodecs())
foreach (string imageFile in imageFiles) // list of strings holding file names
{
RasterImage imagePage = codecs.Load(imageFile);
ms.Position = 0; //rewind the memory stream to the start
codecs.Save(imagePage, ms, RasterImageFormat.TifLzw, 24, 1, 1, -1, CodecsSavePageMode.Append);
}
Another advantage of using this SDK is the flexibility and added features. For example, to change the compression used for any page in the TIFF, replace RasterImageFormat.TifLzw with a different other value such as RasterImageFormat.TifJpeg411 or RasterImageFormat.CcittGroup4 to save using JPEG or FaxG4 compression, respectively. If you would like to try LEADTOOLS, There's a free evaluation here.
Related
I'm trying to import png file as Bitmap and save it as same type(png) but the hash is not the same.
Is it possible to save the bitmap with the same file to get the same hash as before?
What i tried is:
private static void VaryQualityLevel(string filename)
{
// Get a bitmap.
Bitmap bmp1 = new Bitmap(filename);
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Png);
// Create an Encoder object based on the GUID
// for the Quality parameter category.
System.Drawing.Imaging.Encoder myEncoder =
System.Drawing.Imaging.Encoder.Quality;
// Create an EncoderParameters object.
// An EncoderParameters object has an array of EncoderParameter
// objects. In this case, there is only one
// EncoderParameter object in the array.
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 50L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp1.Save(#"c:\TestPhotoQualityFifty.png", jpgEncoder, myEncoderParameters);
myEncoderParameter = new EncoderParameter(myEncoder, 100L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp1.Save(#"c:\TestPhotoQualityHundred.png", jpgEncoder, myEncoderParameters);
// Save the bitmap as a JPG file with zero quality level compression.
myEncoderParameter = new EncoderParameter(myEncoder, 0L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp1.Save(#"test5.png", jpgEncoder, myEncoderParameters);
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Fundamentally, I wouldn't expect loading an image and then encoding it again to result in the exact same bytes, which is what your hash would depend on. Different encoders will have different implementations - I could imagine situations where even encoders which encoded semantically equivalent information could do so using a different byte output. (In some cases, even the same encoder could do that, if it included a timestamp or a GUID or something similar.)
If you require the exact same bytes as the original file, I'd just copy the file instead.
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);
}
}
I want to pass compressed bitmap to the Bitmap bmp(variable). This code saves compressed bitmap to the file(compressed) but doesn't change bitmap size which is "in variable", I want to pass it to the variable without creating new file and then reading it. Any ideas? Or maybe I just missed some suitable method?
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);
System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 30L);
myEncoderParameters.Param[0] = myEncoderParameter;
Bitmap bmp = CaptureDesktopWithCursor2();
bmp.Save(#"C:\Users\sebb\Desktop\TestPhotoQualityFifty.jpg", jpgEncoder, myEncoderParameters);
byte[] lol = imageToByteArray(bmp);
Have you consider to store (or wrap) your data inside a memorystream instead of use a real file ?
MemoryStream memoryStream = new MemoryStream();
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Jpeg);
See that class provided as example
public class BitmapWrapper
{
private MemoryStream _stream = new MemoryStream () ;
public Bitmap GetBitmap ()
{
return new Bitmap ( _stream ) ;
}
public void SetBitmap ( Bitmap value , ImageFormat format )
{
value.Save ( _stream, format ) ;
}
}
I need to detect if a multipage tiff's page pixel formats and change them if they are not PixelFormat.Format24bppRgb. Normally that would be simple enough for me to do. For the same reason I need to do the conversions, I seem to be having issues with coming up with an ideal solution.
if the active frame of the image is PixelFormat.Format1bppIndexed and then I try to change to a frame with that has PixelFormat.Format24bppRgb, WorkingImage.SelectActiveFrame(FrameDimension.Page, i); throws an exception.
An unhandled exception of type 'System.Runtime.InteropServices.ExternalException' occurred in System.Drawing.dll
Additional information: A generic error occurred in GDI+.
I suppose I could catch the exception and reopen the image/image stream and continue checking but I'm hoping that this isn't my only option.
Any help is very much appreciated!
Working with multipage TIF using System.Drawing can be tricky to say the least. That's probably why there are dedicated imaging libraries that contain much more elaborate TIFF support.
The following code may not be the ideal solution to your specific problem, but it worked when tested with a TIFF file that had 1-bit, 8-bit and 24-bit pages, converting them all to 24-bit. The code creates new 24-bit pages and appends them all to a new file, leaving the original unchanged.
if (System.IO.File.Exists(outputTif))
System.IO.File.Delete(outputTif);
Bitmap img = new Bitmap(inputTif);
Bitmap multiPageImage = (Bitmap)img.Clone();
multiPageImage.SelectActiveFrame(FrameDimension.Page, 0);
Bitmap firstPage = new Bitmap(multiPageImage.Width, multiPageImage.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(firstPage);
g.DrawImageUnscaled(multiPageImage, 0, 0);
g.Dispose();
ImageCodecInfo TiffCodec = null;
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
if (codec.MimeType == "image/tiff")
{
TiffCodec = codec;
break;
}
EncoderParameters parameters = new EncoderParameters(2);
parameters.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.MultiFrame);
parameters.Param[1] = new EncoderParameter(Encoder.ColorDepth, (long)24);
//save the first page in a new file
firstPage.Save(outputTif, TiffCodec, parameters);
parameters = new EncoderParameters(2);
parameters.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage);
parameters.Param[1] = new EncoderParameter(Encoder.ColorDepth, (long)24);
var pageCount = multiPageImage.GetFrameCount(System.Drawing.Imaging.FrameDimension.Page);
//now append pages from second to last
for (int i = 1; i < pageCount; ++i)
{
multiPageImage = (Bitmap)img.Clone();
multiPageImage.SelectActiveFrame(FrameDimension.Page, i);
Bitmap nextPage = new Bitmap(multiPageImage.Width, multiPageImage.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
g = Graphics.FromImage(nextPage);
g.DrawImageUnscaled(multiPageImage, 0, 0);
g.Dispose();
firstPage.SaveAdd(nextPage, parameters);
nextPage.Dispose();
}
parameters = new EncoderParameters(2);
parameters.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush);
parameters.Param[1] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.LastFrame);
firstPage.SaveAdd(parameters);
firstPage.Dispose();
I have a method where I take an image, make a copy for resizing, and then return it as a byte array. I have attempted to add the ability to adjust the quality of the image per this doc:
http://msdn.microsoft.com/en-us/library/bb882583(v=vs.110).aspx
Image image = Image.FromFile(_filepath);
Image newImage = new Bitmap(newWidth, newHeight);
using (Graphics graphicsHandle = Graphics.FromImage(newImage))
{
graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight);
}
MemoryStream memStream = new MemoryStream();
EncoderParameters myEncoderParameters = new EncoderParameters(1);
ImageCodecInfo encoder = ImageCodecInfo.GetImageDecoders().SingleOrDefault(c => c.MimeType == contentType);
System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 50L);
newImage.Save(memStream, encoder, myEncoderParameters);
return memStream.ToArray();
When I call newImage.Save(), I get this error:
An exception of type 'System.ArgumentNullException' occurred in System.Drawing.dll but was not handled in user code
Message is:
Value cannot be null.
Parameter name: structure
Forgot a step. I created the EncoderParameter object, but didn't include it in the EncoderParameters collection. The message confused me a bit.
All resolved once I added:
myEncoderParameters.Param[0] = myEncoderParameter;
MSDN says that you have a ArgumentNullException when calling this Save() overload only if the stream is NULL.
Are you sure that no other (omitted?) code is not nullifying this stream?
Are you sure this is the site of the exception?