Storing BMP in icon file - c#

I have a program to combine graphics files in an icon. Sizes include 16,24,32,48,256 32bit. These use PNG and works. I have correct header and directory/index record list.
However, for 8 bit I am using BMP with the first 14 bytes of the header of a BMP stripped off. This part of the icon file does not work. Having looked at a MS icon they have BMP stored again with the 14 byte header removed. Looking at their BMP data they have the second header as I do but for 16x16 the second header (BITMAPINFOHEADER) says 16x32. The BMP seams to be twice the width. Why? Is the image twice the width with a bit mask or something?
Here's my code: (Note image is 32x32 bitmap 32bit when passed.)
using (Bitmap imageAsBitmap = new Bitmap(image))
{
int colorCount = 0;
using (Bitmap bitmap = imageAsBitmap.ColourReduction256(out colorCount))
{
byte[] imageBytes = new byte[] { };
using (MemoryStream ms = new MemoryStream())
{
bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
imageBytes = ms.ToArray();
}
byte[] data = new byte[] { };
Array.Resize(ref data, imageBytes.Length - 14);
Array.Copy(imageBytes, 14, data, 0, data.Length);
enteries.Add(new IconEntry(data, image.Width, image.Height, 8));
}
}

Yes, you are right:
Images with less than 32 bits of color depth follow a particular
format: the image is encoded as a single image consisting of a color
mask (the "XOR mask") together with an opacity mask (the "AND mask")[..]
What results in:
[..] the masks must each be of the same dimensions, and the height
specified in the BMP header must be exactly twice the height specified
in the ICONDIRENTRY structure
Take a look here: https://en.wikipedia.org/wiki/ICO_(file_format)

Related

Using SharpAvi to save screenshots to an AVI produces 100 frames of blank video

My game takes a screenshot each game loop and stores it memory. The user can then press "print screen" to trigger "SaveScreenshot" (see code below) to store each screenshot as a PNG and also compile them into an AVI using SharpAvi. The saving of images works fine, and a ~2sec AVI is produced, but it doesn't show any video when played. It's just the placeholder VLC Player icon. I think this is very close to working, but I can't determine what's wrong. Please see my code below. If anyone has any ideas, I'd be very appreciative!
private Bitmap GrabScreenshot()
{
try
{
Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
System.Drawing.Imaging.BitmapData data =
bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, PixelFormat.Bgr, PixelType.UnsignedByte,
data.Scan0);
bmp.UnlockBits(data);
bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
return bmp;
} catch(Exception ex)
{
// occasionally getting GDI generic exception when rotating the image... skip that one.
return null;
}
}
private void SaveScreenshots()
{
var directory = "c:\\helioscreenshots\\";
var rootFileName = string.Format("{0}_", DateTime.UtcNow.Ticks);
var writer = new AviWriter(directory + rootFileName + ".avi")
{
FramesPerSecond = 30,
// Emitting AVI v1 index in addition to OpenDML index (AVI v2)
// improves compatibility with some software, including
// standard Windows programs like Media Player and File Explorer
EmitIndex1 = true
};
// returns IAviVideoStream
var aviStream = writer.AddVideoStream();
// set standard VGA resolution
aviStream.Width = this.ClientSize.Width;
aviStream.Height = this.ClientSize.Height;
// class SharpAvi.KnownFourCCs.Codecs contains FOURCCs for several well-known codecs
// Uncompressed is the default value, just set it for clarity
aviStream.Codec = KnownFourCCs.Codecs.Uncompressed;
// Uncompressed format requires to also specify bits per pixel
aviStream.BitsPerPixel = BitsPerPixel.Bpp32;
var index = 0;
while (this.Screenshots.Count > 0)
{
Bitmap screenshot = this.Screenshots.Dequeue();
var screenshotBytes = ImageToBytes(screenshot);
// write data to a frame
aviStream.WriteFrame(true, // is key frame? (many codecs use concept of key frames, for others - all frames are keys)
screenshotBytes, // array with frame data
0, // starting index in the array
screenshotBytes.Length); // length of the data
// save it!
// NOTE: compared jpeg, gif, and png. PNG had smallest file size.
index++;
screenshot.Save(directory + rootFileName + index + ".png", System.Drawing.Imaging.ImageFormat.Png);
}
// save the AVI!
writer.Close();
}
public static byte[] ImageToBytes(Image img)
{
using (var stream = new MemoryStream())
{
img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
return stream.ToArray();
}
}
From what I see, you're providing the byte-array in png-encoding, yet the stream is configured as KnownFourCCs.Codecs.Uncompressed.
Furthermore, from the manual:
AVI expects uncompressed data in format of standard Windows DIB, that is bottom-up bitmap of the specified bit-depth. For each frame, put its data in byte array and call IAviVideoStream.WriteFrame()
Next, all encoders expect input image data in specific format. It's BGR32 top-down - 32 bits per pixel, blue byte first, alpha byte not used, top line goes first. This is the format you can often get from existing images. [...] So, you simply pass an uncompressed top-down BGR32
I would retrieve the byte-array directly from the Bitmap using LockBits and Marshal.Copy as described in the manual.

bitmap file size vs byte[] size

bmp.ToByteArray(ImageFormat.Bmp).Length 3145782 int
but file system shows as 2,25 MB (2.359.350 bytes) and Size on disk 2,25 MB (2.363.392 bytes)
Why there is difference and how can I determine correct size of bitmap in byte[]
form?
string appPath = Application.StartupPath;
var bmp = new Bitmap(Image.FromFile(appPath + "\\Images\\Penguins.bmp"));
public static byte[] ToByteArray(this Image image, ImageFormat format)
{
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms, format);
return ms.ToArray();
}
}
Windows 7 / NTFS
I suspect that's because the file on disk doesn't contain an alpha channel, but in memory it does. On disk it's 3 bytes per pixel, but in memory it uses 4.
2359350*4/3 is 3145800 which is only slightly more than the value you see. I expect the slight difference is because on disk there is a header, but that's not actually part of the image.

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;

C#: Results are differing while trying to add a thumbnail-sign to an image

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!

C# Image: To preserve image's checksum

I have the following codes to convert an image(bitmap) to byte array:
public byte[] ConvertImageToByteArray(Image imageToConvert, ImageFormat formatOfImage)
{
byte[] Ret;
try
{
using (MemoryStream ms = new MemoryStream())
{
imageToConvert.Save(ms, formatOfImage);
Ret = ms.ToArray();
}
}
catch (Exception)
{
throw;
}
return Ret;
}
and Convert byte array back to image(bitmap):
public Bitmap ConvertByteArrayToImage(byte[] myByteArray)
{
Image newImage;
using (MemoryStream ms = new MemoryStream(myByteArray, 0, myByteArray.Length))
{
ms.Write(myByteArray, 0, myByteArray.Length);
newImage = Image.FromStream(ms, true);
}
return newImage;
}
Here's my Main Program:
byte[] test = ConvertImageToByteArray(Image.FromFile("oldImage.bmp"), ImageFormat.Bmp);
Bitmap bmp = ConvertByteArrayToImage(test);
bmp.Save("newImage.bmp");
But when I compare both of the image files(old & new bitmap images), their checksum appeared to be different. Any reason for that happening? How to fix it to maintain its integrity?
Basically, there are many ways an identical image can be encoded in a BMP file. If I try your example on a random image I found, I see the .NET Bitmap class saves the file without filling the biSizeImage field in the BITMAPINFOHEADER structure in the BMP header (but the original image produced by IrfanView has it filled), which is a completely correct and documented possibility. (“This may be set to zero for BI_RGB bitmaps.”)
And this is definitely not the only variable thing in the BMP format. For instance, there are multiple possible orderings of pixel data in the image (top-to-bottom, bottom-to-top), specified in the header. (“If biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower-left corner. If biHeight is negative, the bitmap is a top-down DIB and its origin is the upper-left corner.”)
So, if you receive any BMP file from a source not under your control and really need to produce an image using exactly the same BMP variant, you have a lot work to do, and I don’t think you could use the standard .NET helper classes for that.
See also this question: Save bitmap to file has zero in image size field
After chatting a bit, you solution comes down to reading and writing bytes, take the image object out the equation and just deal with the raw bytes.
To read the file:
MemoryStream ms = new MemoryStream(File.ReadAllBytes("filename"));
To write the file:
File.WriteAllBytes("outputfile", ms.ToArray());

Categories

Resources