Image resize with GDI in .NET gives low saturation - c#

I'm fighting an issue where my resized images looses color saturation when I manipulate them using GDI.
I'm loading an JPG as original, resize it and the resulting image has a lot less saturation (color intensity) than the original picture. What can I do to improve that? this is my code:
using ( var original = System.Drawing.Image.FromStream( new MemoryStream( image.RawData ) ) )
{
using ( var dst = new Bitmap( width, height, PixelFormat.Format32bppArgb ) )
{
using ( var g = Graphics.FromImage( dst ) )
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage( original, 0, 0, dst.Width, dst.Height );
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage( original, 0, 0, dst.Width, dst.Height );
var jpgEncoder = GetEncoder( ImageFormat.Jpeg );
var myEncoderParameters = new EncoderParameters( 1 );
var quality = 95;
var myEncoderParameter = new EncoderParameter( Encoder.Quality, quality );
myEncoderParameters.Param[0] = myEncoderParameter;
dst.Save( buffer, jpgEncoder, myEncoderParameters );
}
}
}
I've tried with different pixelformats, removing all filters etc but I always get the same result. Is this some known issue with GDI, or have I missed something?
Addon:
When opening the image in Paint.NET, I get the same issue with low saturation, even without rescaling, so I guess it's the way GDI+ loads images (jpgs)?
This image was saved from photoshop with color profile sRGB, but afaik JPG doesn't have info about colorprofiles embedded. And even if it did, I believe that firefox doesn't obey them (which is what I've tested with)
More testing shows that it looks different in IE8 in contrast to firefox. JPGs seems to support color profiles, but most applications doesn't obey them. FF3.5 however, seems to do it. And it was Adobe RGB, not sRGB on the image.

I found the answer myself. It has to do with color-profiles not beeing applied by default in GDI+. Many people claims that you cannot automatically apply color-profiles using GDI, but apparently, the only change I needed to do was this:
using ( var original = System.Drawing.Image.FromStream( new MemoryStream( image.RawData ) ) )
to
using ( var original = new Bitmap( new MemoryStream( image.RawData ), true ) )
Apparently, Bitmap was a derived class of Image, and the constructor for Bitmap can take both a stream aswell as a boolean for "useIcm". That did the trick for me.

I have an image-rescaling code I use, and I don't see the effect you mention.
the main difference I see is that I use Format24bppRgb and not Format24bppARgb
Keep in mind that JPG has no Alpha channel anyway (afaik)

Coming at this from another angle, I would highly recommend ImageResizer if you want to take the pain out of doing this yourself. It even takes care of disk caching.

Been there, done that. Silly as it may sound, this makes difference:
var quality = 95;
var myEncoderParameter = new EncoderParameter( Encoder.Quality, (long)quality );
or
long quality = 95;
var myEncoderParameter = new EncoderParameter( Encoder.Quality, quality );
or
var quality = 95L;
var myEncoderParameter = new EncoderParameter( Encoder.Quality, quality );
Just pick one.

Related

C# saving bitmap to jpeg with colour space information

I've been trying to find a ways to write in information about jpeg colour space information, since regular saving does not do that, and barely anyone talks about it. Part of the code I use to save into jpeg:
using (Bitmap bmp = new Bitmap(bitmapPicture))
{
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, 100L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp.Save(textBox1.Text + "\\" + textBox2.Text + ".jpg", jpgEncoder, myEncoderParameters);
}
It does the trick, but it doesn't add colour space information which I think is one of the problems when opening that picture with different applications. The colours appears to be incorrect. Also for some reason bitmap brush only uses argb colour space (notice code below). If I'd type in '.FromSrgb' or '.FromRgb' it errors it out (apparently no such thing exists). This is example how I create bitmap which later on is being saved:
using (Graphics gfx = Graphics.FromImage(bitmapPicture))
using (SolidBrush brush = new SolidBrush(System.Drawing.Color.FromArgb(255, 255, 255)))
{
gfx.FillRectangle(brush, 0, 0, with, height);
gfx.DrawImage(addOtherImageOnSameBitmap, xCoordinate, yCoordinate, with2, height2);
}
Also tried doing that with Save File Dialog (code below), but the result is the same - jpeg colours is way off and has no information about colour space.
SaveFileDialog save = new SaveFileDialog();
save.Filter = "Images|*.jpg";
ImageFormat format = ImageFormat.Png;
if (save.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
string ext = System.IO.Path.GetExtension(save.FileName);
switch (ext)
{
case ".jpg":
format = ImageFormat.Jpeg;
break;
}
pictureBox1.Image.Save(save.FileName, format);
}
The problem is fixed when opening that image in photoshop and saving it with sRGB colour space. Is it possible to do that in C#? To save jpeg in sRGB and adding this information to jpeg file itself? Because going into photoshop to fix this problem seems to be a bit too much of a hassle (especially when dealing with a lot of pictures).
P.S. sorry if posted codes seems somewhat off, I just started learning C# couple days ago.

How can I get good-quality text when using a RenderTargetBitmap?

I need to create a small, simple TIFF image that is just black text on a white background, as part of a support library we use across several of our products. (Think a simple receipt or deposit ticket, that kind of thing). I would prefer not to have to use any third-party libraries, to avoid requiring us to add additional dependencies to all of the existing products that use the library.
I have almost gotten the problem solved using just WPF, based on the code from this question. The TIFF is created and rendered properly, but the text quality is horrible. As far as I can determine, the problem is that RenderTargetBitmap, at least the way I'm using it, isn't doing any kind of anti-aliasing, isn't using ClearType, or otherwise is just really bad at rendering plain text, e.g.:
I've read similar questions, such as this one or this one, but they all seem to discuss how to capture text from a visual control or window handle without reducing the quality. I'm doing this entirely off-screen, so I'm not clear if/how I can apply those solutions to my problem.
What I'm currently doing is:
var visual = new DrawingVisual();
using (var draw = visual.RenderOpen())
{
// 'background' is a pre-loaded blank white bitmap
draw.DrawImage(background, new Rect(0, 0, IMG_WIDTH, IMG_HEIGHT));
var font = new Typeface("Segoe UI");
Func<string, FormattedText> format = s => new FormattedText(
s,
CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
font,
16,
Brushes.Black);
draw.DrawText(format("Some text"), new Point(HEADER_LEFT, HEADER_TOP);
// Call draw.DrawText a whole bunch more times.
.
.
.
}
var render = new RenderTargetBitmap(IMG_WIDTH, IMG_HEIGHT, IMG_DPI, IMG_DPI, PixelFormats.Default);
render.Render(visual);
var encoder = new TiffBitmapEncoder()
{
Compression = TiffCompressOption.Ccitt4
};
encoder.Frames.Add(BitmapFrame.Create(render));
var stream = new MemoryStream();
encoder.Save(stream);
Is there something else I can put in there to get the text quality better than what it is?
Don't use Ccitt4, it's for black-and-white images only. Try Default instead.
UPDATE:
It's a TT font and you're trying to render it at a small size, no matter what you do it's going to look pretty crap. Still, if you insist then don't use a visual. Screen elements use hardware acceleration, when you use them with a render target as you are here it has to fall back to a software approach which isn't always pixel-for-pixel the same. And either way you always get at least some aliasing. If you really do want proper bitonal then you'll have to use a Graphics object which will use GDI:
var bitmap = new Bitmap(512, 256);
var graphics = Graphics.FromImage(bitmap);
graphics.Clear(System.Drawing.Color.White);
var font = new Font("Segoe UI", 16);
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
graphics.DrawString("Hello World", font, System.Drawing.Brushes.Black, 0, 0);
ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/tiff");
var myEncoderParameters = new EncoderParameters(1);
myEncoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
bitmap.Save(#"myimage.tif", myImageCodecInfo, myEncoderParameters);
graphics.Dispose();
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
Result (which SO is blurring a bit itself):

Converted Image to Gif, Works Partially?

I've written a short method for converting images(Image class) to a GifEncoder and finally saving that Gif to a file. However, when I go to open the resulting Gif created, it has some problems. 1) The Gif stops playing after 2 cycles in a browser. 2) When loaded through an image editor the colors seem to mix up a bit between pixels.
public void ConvertToGif( string DestinationPath , Image myImage , int myFrames ) {
Bitmap myBitmap = new Bitmap( myImage.Width / myFrames , myImage.Height );
GifBitmapEncoder myEncoder = new GifBitmapEncoder();
int i = 0;
while( i < myFrames ) {
Graphics GrDrw = Graphics.FromImage( myBitmap );
var DestRegion = new Rectangle( 0 , 0 , myBitmap.Width , myBitmap.Height );
var SrceRegion = new Rectangle( myBitmap.Width * i , 0 , myBitmap.Width , myBitmap.Height );
GrDrw.DrawImage( myImage , DestRegion , SrceRegion , GraphicsUnit.Pixel );
BitmapSource mySource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( myBitmap.GetHbitmap() , IntPtr.Zero , Int32Rect.Empty , BitmapSizeOptions.FromEmptyOptions() );
myEncoder.Frames.Add( BitmapFrame.Create( mySource , mySource ) );
GrDrw.Dispose();
i += 1;
}
System.IO.FileStream myStream = new System.IO.FileStream( #DestinationPath , System.IO.FileMode.Create );
myEncoder.Save( myStream );
}
The number of frames from an image created with ConvertToGif() also don't seem to register when running the resulting Gif through my other method, ConvertFromGif():
public Image ConvertFromGif( Image myImage ) {
var myDimensions = new FrameDimension( myImage.FrameDimensionsList[ 0 ] );
var myFrames = myImage.GetFrameCount( myDimensions );
var newImage = new Bitmap( myImage.Width * myFrames , myImage.Height );
for( int i = 0; i < myFrames; i ++ ) {
myImage.SelectActiveFrame( myDimensions , i );
var DestRegion = new Rectangle( myImage.Width * i , 0 , myImage.Width , myImage.Height );
var SrceRegion = new Rectangle( 0 , 0 , myImage.Width , myImage.Height );
Graphics GrDrw = Graphics.FromImage( newImage );
GrDrw.DrawImage( myImage , DestRegion , SrceRegion , GraphicsUnit.Pixel );
GrDrw.Dispose();
}
return newImage;
}
I can run any Gif through my ConvertFromGif() method, but Gifs made by my ConvertToGif() method won't work.
I had a Look around and discovered that GifBitmapEncoder is only really meant to read GIFs so they can be displayed in WPF this class will allow you to create multi-framed GIFs however it does not provide a Metadata handler that allows you to put key frame information like cycle count, delay between frame etc. They have left the Metadata part out for 3rd parties to implement
Unfortunately we don't expose a metadata reader or writer for GIFs
(animated or otherwise). In order to produce correct animated GIFs,
you need to write out some metadata with timing information. Since we
don't provide a GIF metadata handler, we do provide a mechanism for
3rd parties to write their own. Documentation for writing a metadata
handler can be found here:
http://msdn2.microsoft.com/en-us/library/ms737407.aspx as well as in
this article
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnlong/html/wiccodec.asp.
Quote from: http://social.msdn.microsoft.com/Forums/en-US/6ef358a7-d1ac-4267-91d9-166024aad8ca/creating-animated-gif-files-in-wpf?forum=wpf
Gifs have some default timing information so you are getting the 2 cycles but that is all it may also differ from other browsers etc.
Also while looking online I saw other users having the same issue with colour bleed between frames.
There is two avenues for you: Use a 3rd party .Net Library that allows you to write Gifs or write you own.
From your previous question Convert a List of Images to a GIf I would suggest you use NGif - http://www.codeproject.com/Articles/11505/NGif-Animated-GIF-Encoder-for-NET
However if you want to create your own metadata writer for the GifBitmapEncoder follow the links provided on the post above for the documentation for metadata read and writers.

Image size is drastically increasing after applying a simple watermark

I've a set of images that I'm programmatically drawing a simple watermark on them using System.Windows and System.Windows.Media.Imaging (yes, not with GDI+) by following a tutorial in here.
Most of the images are not more than 500Kb, but after applying a simple watermark, which is a text with a transparent background, the image size is drastically increasing.
For example, a 440Kb image is becoming 8.33MB after applying the watermark with the below method, and that is shocking me.
private static BitmapFrame ApplyWatermark(BitmapFrame image, string waterMarkText) {
const int x = 5;
var y = image.Height - 20;
var targetVisual = new DrawingVisual();
var targetContext = targetVisual.RenderOpen();
var brush = (SolidColorBrush)(new BrushConverter().ConvertFrom("#FFFFFF"));
brush.Opacity = 0.5;
targetContext.DrawImage(image, new Rect(0, 0, image.Width, image.Height));
targetContext.DrawRectangle(brush, new Pen(), new Rect(0, y, image.Width, 20));
targetContext.DrawText(new FormattedText(waterMarkText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface("Batang"), 13, Brushes.Black), new Point(x, y));
targetContext.Close();
var target = new RenderTargetBitmap((int)image.Width, (int)image.Height, 96, 96, PixelFormats.Default);
target.Render(targetVisual);
var targetFrame = BitmapFrame.Create(target);
return targetFrame;
}
I've noticed that the image quality is improved compared than the original image. The image is more smoother and colors are more lighter. But, you know I don't really want this. I want the image to be as it is, but include the watermark. No quality increases, and of course no drastic changes in image size.
Is there any settings that I'm missing in here to tell my program to keep the quality as same as source image? How can I prevent the significant change of the image size after the changes in my ApplyWatermark method?
Edit
1. This is how I convert BitmapFrame to Stream. Then I use that Stream to save the image to AmazonS3
private Stream EncodeBitmap(BitmapFrame image) {
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(image));
var memoryStream = new MemoryStream();
enc.Save(memoryStream);
return memoryStream;
}
2. This is how I get the BitmapFrame from Stream
private static BitmapFrame ReadBitmapFrame(Stream stream) {
var photoDecoder = BitmapDecoder.Create(
stream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.None);
return photoDecoder.Frames[0];
}
3. This is how I read the file from local directory
public Stream FindFileInLocalImageDir() {
try {
var path = #"D:\Some\Path\Image.png";
return !File.Exists(path) ? null : File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read);
} catch (Exception) {
return null;
}
}
The problem is that when you edit the image, the compression is gone. A 730x1108 JPG with 433kB disc size with 32bit (you mentioned transparency, so ARGB) will need at least 730 * 1108 * 4 = 3,09MB on disc. Of course you can compress it afterwards again (for disc, network stream of what else).
This is the reason why image software always needs much memory even when working with compressed data.
Conclusion: You will need the free memory to work with the image. Not possible to have it otherwise completly at hand.
The reason I asked my question in the comments earlier, is because I noticed there were several different encoders available. A bitmap usually has a significantly larger file size, due to the amount of information it's storing about your image.
I haven't tested this myself, but have you tried a different encoder?
var pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(ApplyWatermark(null, null));
MemoryStream stm = File.Create(image);
pngEncoder.Save(stm);
return stm;

EncodeParameters deinterlace bmp

I'm looking for a way to take in a 32 bit bitmap and save it again however deinterlacing the frames. When the image is taken two fields are visible but only the last one is necessary. Is this possible to do using EncoderParameters. This is what I've tried so far:
using (Image source = Image.FromFile(#"C:\Users\Martin vanPutten\Desktop\test.bmp"))
{
ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders().First(c => c.MimeType == "image/bmp");
EncoderParameters parameters = new EncoderParameters(3);
parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
parameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ScanMethod, (int)EncoderValue.LastFrame);
parameters.Param[2] = new EncoderParameter(System.Drawing.Imaging.Encoder.RenderMethod, (int)EncoderValue.RenderNonProgressive);
source.Save(#"C:\Users\Martin vanPutten\Desktop\test2.bmp", codec, parameters);
}
Is there another way to do this? All I need to do is remove the second overlapping frame in an image.
Quick update, its not that it has two frames, but 2 fields in 1 frame.

Categories

Resources