So I've come across a rather odd situation.
I'm using the following to Save a PNG (lossless) image,
public static void SaveJpeg(string path, Image image, int quality)
{
if ((quality < 0) || (quality > 100))
{
string error = string.Format("Jpeg image quality must be between 0 and 100, with 100 being the highest quality. A value of {0} was specified.", quality);
throw new ArgumentOutOfRangeException(error);
}
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
ImageCodecInfo imgCodec = GetEncoderInfo("image/png");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
image.Save(path, imgCodec, encoderParams);
}
public static ImageCodecInfo GetEncoderInfo(string mimeType)
{
string lookupKey = mimeType.ToLower();
ImageCodecInfo foundCodec = null;
if (Encoders.ContainsKey(lookupKey))
{
foundCodec = Encoders[lookupKey];
}
return foundCodec;
}
This code works great in XP, the image gets saved lossless, when I zoom in, I see no pixelation whatsoever however, when this same compiled application is ran on a windows7 machine, the saved image looks pixelated.
Is this due to the way I'm saving the image or perhaps something changing with the image save functionality / encoding in windows 7?
Your Win7 image is anti-aliased.
This is good, not bad; it makes the text smoother.
It's controlled by properties in the Graphics class.
Related
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.
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;
Here's the deal. I'm trying to use a function to compress an image from my notebook camera while using a standard code to do that:
public static byte[] EncodeImage(this Image image)
{
using (MemoryStream ms = new MemoryStream())
{
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 80);
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
image.Save(ms, jpegCodec, encoderParams);
return ms.ToArray();
}
}
After the compression is done, I save it to the database. Works good, providing me with a good amount of compression (500 000 bytes vs 32 000). Some time later, I need to show the image to the user in a winforms application.
using (MemoryStream ms = new MemoryStream(entity.Photo))
fotoPictureBox.Image = Image.FromStream(ms);
Again, this displays a nice image. However, then I want the user to be able to download the image and save where he sees suitable. And this is when all the problems arise.
If I use this code:
fotoPictureBox.Image.Save(imageSaveFileDialog.FileName);
I receieve a typical generic GDI+ error.
If I try to save the image by using the code I've encoded it
public static void SaveEncodedImage(this Image image, string targetPath)
{
ImageCodecInfo jpegCodec = Pomocne.GetEncoderInfo("image/jpeg");
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 80);
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
image.Save(targetPath, jpegCodec, encoderParams);
}
I get this error (pointing to the line image.Save(targetPath, jpegCodec, encoderParams);):
An unhandled exception of type System.ArgumentException occurred in System.Drawing.dll
Additional information: Parameter is not valid.
Can please anyone help? It's rather urgent, thank you.
To address some questions/answers you will probably start with - if I don't compress the image with this code, I can save the image to the chosen path without any problems. So there's clearly some problem with decoding or something like that. Don't ask me if I have sufficient permissions, because I have.
For some reason this code works great:
File.WriteAllBytes(targetPath, byteArray);
I guess sometimes the easiest solution is the right one. Not sure about the exact explanation (you can provide one, I'll surely vote up), but this is resolved.
Thanks anyway.
I am loading images from a database and want to dynamically resize them according to some input.
Code is something like this:
public ActionResult GetImage(string imageID, int? width, int? height, bool constrain)
{
ValidateImageInput(width, height, constrain);
ImageWithMimeType info = LoadFromDatabase(imageID);
if(info == null)
throw new HttpException(404, "Image with that name or id was not found.");
Resize(info.Bytedata, width, height, constrain, info.MimeType);
return File(info.Data, info.MimeType);
}
How would I implement Resize in a way that preserves encoding type etc? I've looked at Image resizing efficiency in C# and .NET 3.5 but don't see how it would preserve encoding - since creating a new Bitmap surely isn't encoded?
Fact is, I managed to solve it with some help of google eventually. Guess I was a bit too trigger happy with the question. Anyway, the basic bits is that I look up the proper ImageFormat from the mimetype using ImageCodecInfo.GetImageEncoders(), then save using the correct encoding, as following:
private ImageFormat GetEncoderInfo(string mimeType)
{
// Get image codecs for all image formats
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
// Find the correct image codec
for (int i = 0; i < codecs.Length; i++)
if (codecs[i].MimeType == mimeType)
return new ImageFormat(codecs[i].FormatID);
return null;
}
This is a slightly different version I made of the code on http://www.switchonthecode.com/tutorials/csharp-tutorial-image-editing-saving-cropping-and-resizing
Using the ImageFormat I can simply do
image.Save(dest, GetEncoderInfo(mimetype));
To preserve the filetype you have to look at that filetype the original file has and when saving the file you specify the file format.
Bitmap b = new Bitmap("foo.jpg");
b.Save("bar.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
In your case you would probably save to an MemoryStream that you later convert to the byte array (guessing that your info.Data is of type byte[]).
How do I get C# to force bitmap images that are saved to be saved as 24-bit images as can be seen when you get the right-click properties of the image in Windows. All the images I save are set to 32-bit.
I tried the below code with no luck. The source images are all 24-bit as well but are always saved as 32-bit images.
ImageCodecInfo bmpCodec = FindEncoder(ImageFormat.Bmp);
EncoderParameters parameters = new EncoderParameters();
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 24);
imgCheque.Save(DestinationFile.ToString(), bmpCodec, parameters);
The images have to be properly 24 bit as the are read by a different program that can't handle 32-bit images.
Thanks in advance,
Soultech
Is this any use?
// imgCheque source created somewhere else up here
using (Bitmap blankImage = new Bitmap(imgCheque.Width, imgCheque.Height, PixelFormat.Format24bppRgb))
{
using (Graphics g = Graphics.FromImage(blankImage))
{
g.DrawImageUnscaledAndClipped(imgCheque, new Rectangle(Point.Empty, imgCheque.Size));
}
ImageCodecInfo bmpCodec = FindEncoder(ImageFormat.Bmp);
blankImage.Save(#"C:\TEMP\output.bmp", bmpCodec, null);
}
Try this?
ImageCodecInfo bmpCodec = FindEncoder(ImageFormat.Bmp);
EncoderParameters parameters = new EncoderParameters();
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 24L);
imgCheque.Save(DestinationFile.ToString(), bmpCodec, parameters);