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;
Related
I have a question about BitmapSource.Create. I have the following code, and it's not behaving as expected:
reader.BaseStream.Position += BytesInMetadata;
var rawData = new UInt16[NumberOfPixels];
// Read in the raw image data in 16 bit format.
NumberOfPixels.Times((Action<int>)(i => rawData[i] = reader.ReadUInt16()));
var stats = new MsiStats()
{
Mean = rawData.Average(v => (Double)v),
StdDev = rawData.StandardDeviation(v => (Double)v),
Min = rawData.Min(),
Max = rawData.Max()
};
// Convert the 16-bit image to an 8-bit image that can actually be displayed.
var scaledData = ScaleData(rawData, 4.0f, CType);
GCHandle handle = GCHandle.Alloc(scaledData, GCHandleType.Pinned);
using (var bmp = new Bitmap(2048, 2048, 2048, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, handle.AddrOfPinnedObject()))
{
bmp.Save(#"C:\Users\icyr\Work Folders\COBRA_I-3\CAST Data\myOGBitmap.bmp");
}
handle.Free();
var src = BitmapSource.Create(NumberOfColumns, NumberOfRows,
96, 96,
PixelFormats.Gray8, null,
scaledData,
NumberOfRows);
using (var fileStream = new FileStream(#"C:\<somefolder>\myBitmap.bmp", FileMode.OpenOrCreate))
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(src));
enc.Save(fileStream);
}
I'm reading a 12 bit value from an proprietary image file, converting it to 8 bits, and then saving it as a bitmapsource object. However, when I read it back (or save it, as I do below) it saves it... wrong. I'm not even sure how to describe it. When I read the saved images in Matlab, the file saved from the Bitmapsource object only has pixel values that are multiples of 17. The saved file from the scaledData object has the full range of values.
What's going on here? Unfortuantely I'm working within a framework of code that I didn't write, and unless I want to overhaul the entire project (which I don't, nor do I have the time to) I need to continue to be able to use BitmapSource objects for my data storage purposes.
I'm at a loss of what to do here, so I'm hoping that you guys might have a better understanding of why this is occuring, and how to prevent it from doing so with minimal changes.
Apparently the issue was the use of the PixelFormat.Gray8. I changed it to PixelFormat.Indexed8, using BitmapPallettes.Gray256 for my pallette, and that seemed to fix my issue.
var src = BitmapSource.Create(NumberOfColumns, NumberOfRows,
96, 96,
PixelFormats.Indexed8, BitmapPalettes.Gray256,
scaledData,
NumberOfRows);
Still don't understand what was going on.
I have a puzzle game and I can create levels. When I save a level, it takes a snapshot of the canvas and then when I choose a level, it displays all the pictures of the levels next to their name as a thumbnail. However each image is around 1MB in size. I would like to get them to around 30KB in size. Also the file it makes cannot be edited by a photo editor to make it a smaller size even though it is a jpg. I see I have used a TiffBitmapEncoder whoops. Probably my issue with the photo editors.
Here is my code:
private void saveImage(object sender, EventArgs e)
{
string path = myImageNamePath;
FileStream fs = new FileStream(path, FileMode.Create);
RenderTargetBitmap bmp = new RenderTargetBitmap((int)myLevelDesigner.pbxMap.ActualWidth,
(int)myLevelDesigner.pbxMap.ActualHeight, 1 / 96, 1 / 96, PixelFormats.Pbgra32);
bmp.Render(myLevelDesigner.pbxMap);
BitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
encoder.Save(fs);
fs.Close();
}
Oh my, is there a particular reason you are using the TIFF file format? I highly recommend using PNG instead, it is lossless and compressed.
Use PngBitmapEncoder instead, most graphics programs support PNG.
I would also recommend making it into an extension method that you can reuse throughout. Something like this:
public static class CanvasExtender
{
public static void SaveToImageFile(this Canvas canvas, string outputFile)
{
canvas.UpdateLayout();
var bitmap = new RenderTargetBitmap(canvas.ActualWidth, canvas.ActualHeight, 96d, 96d, PixelFormats.Pbgra32);
bitmap.Render(canvas);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using(var outputStream = File.Create(outputFile))
encoder.Save(outputStream);
}
}
i am automatically resizing images in my ASP.NET application in order to create a low resolution thumbnail of that image. This code is working fine. After resizing i am trying to add a "thumbnail-sign", for example an small loupe or a plus, to that image, but the result differs depending on the image size.
Please note: The Original Image is only resized to a certain width, so the images differs in height.
My code looks like this:
private static byte[] InsertThumbnailSign(byte[] imageBuffer, string signPath)
{
byte[] output = null;
MemoryStream stream = new MemoryStream(imageBuffer);
Image image = Image.FromStream(stream);
// Add the thumbnail-sign
Image thumbNailSign = Image.FromFile(signPath);
Graphics graphic = Graphics.FromImage(image);
graphic.DrawImageUnscaled(thumbNailSign, image.Width - thumbNailSign.Width - 4, image.Height - thumbNailSign.Height - 4);
graphic.Flush();
MemoryStream memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Jpeg);
output = new byte[memoryStream.Length];
memoryStream.Position = 0;
memoryStream.Read(output, 0, (int)memoryStream.Length);
memoryStream.Close();
stream.Dispose();
graphic.Dispose();
memoryStream.Dispose();
return output;
}
In my opinion the thumbnail sign should have a constant size, but that is not the case. Do you have any ideas how to achieve this?
EDIT: Just edited the code to be aware of different resolutions. But it still does not work:
private static byte[] InsertThumbnailSign(byte[] imageBuffer, string signPath)
{
byte[] output = null;
MemoryStream stream = new MemoryStream(imageBuffer);
Image image = Image.FromStream(stream);
// Add the thumbnail sign with resolution of the containing image
Bitmap t = (Bitmap)Bitmap.FromFile(signPath);
t.SetResolution(image.HorizontalResolution, image.VerticalResolution);
Image thumbNailSign = t;
Graphics graphic = Graphics.FromImage(image);
graphic.DrawImageUnscaled(thumbNailSign, image.Width - thumbNailSign.Width - 4, image.Height - thumbNailSign.Height - 4);
graphic.Flush();
MemoryStream memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Png);
output = new byte[memoryStream.Length];
memoryStream.Position = 0;
memoryStream.Read(output, 0, (int)memoryStream.Length);
memoryStream.Close();
stream.Dispose();
graphic.Dispose();
memoryStream.Dispose();
return output;
}
Ensure that all your images are normalized to a consistent DPI/PPI before resizing and/or use that DPI when you create your "sign" image to overlay.
A 300 DPI 2" wide image is 600 pixels wide, where you can also have an image that is 600 pixels wide and have it be over 8" in width if the DPI is set to 72 DPI.
See this question which has a link with example code to get a little more detail about your graphics obj.
TombMedia is right. The resolution has been the point.
I discovered an error in my Resizing-Algorithm which caused firefox to resize the image and that caused the different size of the inserted image.
After fixing that error and adapting the resolution it works fine. Thanks for the hint!
I am using .NET4.5, Windows Forms and C#.
I am loading an image onto a button using:
theButton.BackgroundImage = Image.FromFile("file.png");
The issue is that my button is 128x128 and the image is 4000x8000. The line above consumes very large amounts of memory because file.png is so large.
Does anyone know of a technique I can use to reduce this memory footprint? I am thinking of some function like this:
Image.FromFile(file,width,height);
Any pointers? Thanks.
Yes it works. It's quite simple to resize the image and then display it on button.
But, I don't think that the above code maintains the aspect ratio of the image.
It's quite simple to resize the image with aspect ratio; and then display it on button.
Below is the sample code helps you to resize the image by maintaining the aspect ratio.
You can define a new class or implement the "ResizeImage" method in an existing class. Whichever is comfortable to you.
public class ImageManipulation
{
public static Bitmap ResizeImage(Bitmap originalBitmap, int newWidth, int maxHeight, bool onlyResizeIfWider)
{
if (onlyResizeIfWider)
{
if (originalBitmap.Width <= newWidth)
{
newWidth = originalBitmap.Width;
}
}
int newHeight = originalBitmap.Height * newWidth / originalBitmap.Width;
if (newHeight > maxHeight)
{
// Resize with height instead
newWidth = originalBitmap.Width * maxHeight / originalBitmap.Height;
newHeight = maxHeight;
}
var alteredImage = new Bitmap(originalBitmap, new Size(newWidth, newHeight));
alteredImage.SetResolution(72, 72);
return alteredImage;
}
}
USAGE:
private void DisplayPhoto()
{
// make sure the file is JPEG or GIF
System.IO.FileInfo testFile = new System.IO.FileInfo(myFile);
// Create a new stream to load this photo into
FileStream myFileStream = new FileStream(myFile, FileMode.Open, FileAccess.Read);
// Create a buffer to hold the stream of bytes
photo = new byte[myFileStream.Length];
// Read the bytes from this stream and put it into the image buffer
myStream.Read(photo, 0, (int)myFileStream.Length);
// Close the stream
myFileStream.Close();
// Create a new MemoryStream and write all the information from
// the byte array into the stream
MemoryStream myStream = new MemoryStream(photo, true);
myStream.Write(photo, 0, photo.Length);
// Use the MemoryStream to create the new BitMap object
Bitmap FinalImage = new Bitmap(myStream);
upicPhoto.Image = ImageManipulation.ResizeImage(
FinalImage,
upicPhoto.Width,
upicPhoto.Height,
true);
// Close the stream
myStream.Close();
}
I think your best path here is to just resize the image, to 128x128.
An image that large is always going to take up a lot of memory, no matter what you do with it.
This will also allow you to make the image something that will look good at that size.
This is quite a general problem, AFAIK you have few possibilities
Compress image before uploading , in real world this will not work.
Put a check on size and dimensions of image, in real world it works, even linkedin, facebook they won't allow us to upload images above there specified dimensions.
Use buffering, this is cleanest way you can do in .net
Use some third party plugins or development enviornment, I have done it in Silverlight
I was working on my college project where i was trying to change bit values of a Bitmap.
I am loading the bitmap to the memory stream, then extracting it to byte[] array. Now I was changing few central bits of this byte[] array and then converting it back to a bitmap image.
But i got a run time exception that "Invalid Bitmap".
Does a bitmap have some special format instead of simple bits???
Following is the code used by me:
MemoryStream mstream = new MemoryStream();
Bitmap b = new Bitmap(#"D:\my_pic.bmp");
b.Save(mstream, System.Drawing.Imaging.ImageFormat.Bmp);
byte[] ba = mstream.ToArray();
mstream.Close();
byte[] _Buffer = null;
System.IO.FileStream _FileStream = new System.IO.FileStream(_FileName, System.IO.FileMode.Open, System.IO.FileAccess.Read);
System.IO.BinaryReader _BinaryReader = new System.IO.BinaryReader(_FileStream);
long _TotalBytes = new System.IO.FileInfo(_FileName).Length;
_Buffer = _BinaryReader.ReadBytes((Int32)_TotalBytes);
// close file reader
_FileStream.Close();
_FileStream.Dispose();
_BinaryReader.Close();
int leng1 = ba.Length;
int leng2=_Buffer.Length;
for(i=(leng1)/2;i<leng1;i++)
{
for(j=0;j<leng2;j++)
{
ba[i]=_Buffer[j];
}
if(j==(leng2-1))
{
break;
}
}
TypeConverter tc = TypeDescriptor.GetConverter(typeof(Bitmap));
Bitmap bitmap1 = (Bitmap)tc.ConvertFrom(ba);
You must have your own goal to want to operate at this low level with a bitmap and that's fine. Unless you are performance bound it is way easier to do graphics at the graphics API level. Even if you are performance sensitive, other people have cut a path through the jungle already.
Back to the question. The BMP file format is simpler than some others but still comes in many varieties. Here is a detailed introduction to the gory details of the BMP format:
BMP file format
Now if you are just parsing your own BMP files and you know they are 32-bit RGB, and the header is going to be such-and-such a size which you can skip over, etc, then that might work for you. If you need to handle any old BMP it gets messy real fast which is why the libraries try to take care of everything for you.