Get file Thumbnail image with transparency - c#

I want to get file's thumbnail with transparency.
I have the following code to achieve it:
BitmapImage GetThumbnail(string filePath)
{
ShellFile shellFile = ShellFile.FromFilePath(filePath);
BitmapSource shellThumb = shellFile.Thumbnail.ExtraLargeBitmapSource;
Bitmap bmp = new Bitmap(shellThumb.PixelWidth, shellThumb.PixelHeight, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(System.Drawing.Point.Empty, bmp.Size), ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
shellThumb.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);
bmp.UnlockBits(data);
MemoryStream ms = new MemoryStream();
bmp.Save(ms, ImageFormat.Png);
ms.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.CacheOption = BitmapCacheOption.None;
bi.EndInit();
return bi;
}
I mixed the codes from here:
Is there a good way to convert between BitmapSource and Bitmap?
and
Load a WPF BitmapImage from a System.Drawing.Bitmap
With this way, I convert BitmapSource to Bitmap, then I covert the Bitmap to BitmapImage.
I am pretty sure there's a way to covert BitmapSource directly to BitmapImage while saving the transparency.

You will need to encode the BitmapSource to a BitmapImage, you can choose any encoder you want in this example I use PngBitmapEncoder
Example:
private BitmapImage GetThumbnail(string filePath)
{
ShellFile shellFile = ShellFile.FromFilePath(filePath);
BitmapSource shellThumb = shellFile.Thumbnail.ExtraLargeBitmapSource;
BitmapImage bImg = new BitmapImage();
PngBitmapEncoder encoder = new PngBitmapEncoder();
var memoryStream = new MemoryStream();
encoder.Frames.Add(BitmapFrame.Create(shellThumb));
encoder.Save(memoryStream);
bImg.BeginInit();
bImg.StreamSource = memoryStream;
bImg.EndInit();
return bImg;
}

Did you try: System.Drawing.Imaging.PixelFormat.Format32bppArgb (without the P between Format32-P-Argb)
MSDN:
Format32bppArgb -> Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.
Format32bppPArgb -> Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied, according to the alpha component.

Related

Convert ImageSource to Bitmap [duplicate]

As far as I can tell the only way to convert from BitmapSource to Bitmap is through unsafe code... Like this (from Lesters WPF blog):
myBitmapSource.CopyPixels(bits, stride, 0);
unsafe
{
fixed (byte* pBits = bits)
{
IntPtr ptr = new IntPtr(pBits);
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(
width,
height,
stride,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb,ptr);
return bitmap;
}
}
To do the reverse:
System.Windows.Media.Imaging.BitmapSource bitmapSource =
System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
bitmap.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
Is there an easier way in the framework? And what is the reason it isn't in there (if it's not)? I would think it's fairly usable.
The reason I need it is because I use AForge to do certain image operations in an WPF app. WPF wants to show BitmapSource/ImageSource but AForge works on Bitmaps.
It is possible to do without using unsafe code by using Bitmap.LockBits and copy the pixels from the BitmapSource straight to the Bitmap
Bitmap GetBitmap(BitmapSource source) {
Bitmap bmp = new Bitmap(
source.PixelWidth,
source.PixelHeight,
PixelFormat.Format32bppPArgb);
BitmapData data = bmp.LockBits(
new Rectangle(Point.Empty, bmp.Size),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppPArgb);
source.CopyPixels(
Int32Rect.Empty,
data.Scan0,
data.Height * data.Stride,
data.Stride);
bmp.UnlockBits(data);
return bmp;
}
You can just use these two methods:
public static BitmapSource ConvertBitmap(Bitmap source)
{
return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
source.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
public static Bitmap BitmapFromSource(BitmapSource bitmapsource)
{
Bitmap bitmap;
using (var outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapsource));
enc.Save(outStream);
bitmap = new Bitmap(outStream);
}
return bitmap;
}
It works perfectly for me.
Is this what your looking for?
Bitmap bmp = System.Drawing.Image.FromHbitmap(pBits);
Here a code to set transparent background to any bitmap resource within a Resource Dictionary (not Resources.resx often used in Windows.Forms age). I call this methode before InitializeComponent() - methode. The methodes 'ConvertBitmap(Bitmap source)' and BitmapFromSource(BitmapSource bitmapsource) are mentioned in post from melvas above.
private void SetBitmapResourcesTransparent()
{
Image img;
BitmapSource bmpSource;
System.Drawing.Bitmap bmp;
foreach (ResourceDictionary resdict in Application.Current.Resources.MergedDictionaries)
{
foreach (DictionaryEntry dictEntry in resdict)
{
// search for bitmap resource
if ((img = dictEntry.Value as Image) is Image
&& (bmpSource = img.Source as BitmapSource) is BitmapSource
&& (bmp = BitmapFromSource(bmpSource)) != null)
{
// make bitmap transparent and assign it back to ressource
bmp.MakeTransparent(System.Drawing.Color.Magenta);
bmpSource = ConvertBitmap(bmp);
img.Source = bmpSource;
}
}
}
}
This is neat and faster than light:
return Imaging.CreateBitmapSourceFromHBitmap( bitmap.GetHbitmap(), IntPtr.Zero,
Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions() );
You can share the pixeldata between both namespaces. You don't have to convert.
Use the SharedBitmapSource. https://stackoverflow.com/a/32841840/690656

Converting a System.Windows.Media.Drawing object to Bitmap bytes

I have a System.Windows.Media.Drawing object that I am wanting to convert into a Bitmap object, and then from there extract the bytes that represent the image. I've looked about the internet and I can't seem to find how to do what I need, so any help would be appreciated.
So I finally found a way to convert a System.Windows.Media.Drawing object to a System.Drawing.Bitmap object, and from that get a byte[] object representing the image data. The following is not pretty but does actually work.
public static byte[] DrawingToBytes(Drawing drawing)
{
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
// If using the BitmapEncoder uncomment the following line to get a white background.
// context.DrawRectangle(Brushes.White, null, drawing.bounds);
context.DrawDrawing(drawing);
}
int width = (int)(drawing.Bounds.Width)
int height = (int)(drawing.Bounds.Height)
Bitmap bmp = new Bitmap(width, height);
Bitmap bmpOut;
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(System.Drawing.Color.White);
RenderTargetBitmap rtBmp = new RenderTargetBitmap(width, height,
bmp.HorizontalResolution,
bmp.VerticalResolution,
PixelFormats.Pbgra32);
rtBmp.Render(visual);
// Alternative using BmpBitmapEncoder, use in place of what comes after if you wish.
// MemoryStream stream = new MemoryStream();
// BitmapEncoder encoder = new BmpBitmapEncoder();
// encoder.Frames.Add(BitmapFrame.Create(rtBmp));
// encoder.save(stream);
int stride = width * ((rtBmp.Format.BitsPerPixel + 7) / 8);
byte[] bits = new byte[height * stride];
bitmapSource.CopyPixels(bits, stride, 0);
unsafe
{
fixed (byte* pBits = bits)
{
IntPtr ptr = new IntPtr(pBits);
bmpOut = new Bitmap(width, height, stride,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb, ptr);
}
}
g.DrawImage(bmpOut, 0, 0, bmp.Width, bmp.Height);
}
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
bmp.Save(ms, ImageFormat.bmp);
data = ms.ToArray();
}
return bytes;
}
So yeah, it's horrible but it actually works.
You can try that:
byte[] ImageToByte(Image image)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
This work also for Bitmap.

Image resize when rotate

I'm trying to rotate the image.. I have a pictureBox 369x276. But when I rotate, this size decrease.
The pictureBox sizeMode is PictureBoxSizeMode.StretchImage
here is my code:
Bitmap oldBitmap = (Bitmap)pictureBox1.Image;
float angle = 90;
var newBitmap = new Bitmap(oldBitmap.Width, oldBitmap.Height);
var graphics = Graphics.FromImage(newBitmap);
graphics.TranslateTransform((float)oldBitmap.Width / 2, (float)oldBitmap.Height / 2);
graphics.RotateTransform(angle);
graphics.TranslateTransform(-(float)oldBitmap.Width / 2, -(float)oldBitmap.Height / 2);
graphics.DrawImage(oldBitmap, new Point(0, 0));
pictureBox1.Image = newBitmap;
Just use RotateFlip:
Bitmap oldBitmap = (Bitmap)pictureBox1.Image;
oldBitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
pictureBox1.Image = oldBitmap;
As #Dan-o has pointed out, this allows a rotation of any of the degree's in the System.Drawing.RotateFlipType enum.
To rotate a Bitmap any angle without losing the size, you could do the following, but it's a bit convoluted!
One - Add the WriteableBitmapEx library to your project
Two - Add the XAML, WindowsBase and PresentationCore libraries to your project
Three - Use the following to rotate your Bitmap any amount of degrees:
class Program
{
static void Main(string[] args)
{
Bitmap oldBitmap = (Bitmap)pictureBox1.Image;;
var bitmapAsWriteableBitmap = new WriteableBitmap(BitmapToBitmapImage(oldBitmap));
bitmapAsWriteableBitmap.RotateFree(23);
var rotatedImageAsMemoryStream = WriteableBitmapToMemoryStream(bitmapAsWriteableBitmap);
oldBitmap = new Bitmap(rotatedImageAsMemoryStream);
}
public static BitmapImage BitmapToBitmapImage(Bitmap bitmap)
{
var memStream = BitmapToMemoryStream(bitmap);
return MemoryStreamToBitmapImage(memStream);
}
public static MemoryStream BitmapToMemoryStream(Bitmap image)
{
var memoryStream = new MemoryStream();
image.Save(memoryStream, ImageFormat.Bmp);
return memoryStream;
}
public static BitmapImage MemoryStreamToBitmapImage(MemoryStream ms)
{
ms.Position = 0;
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = ms;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
private static MemoryStream WriteableBitmapToMemoryStream(WriteableBitmap writeableBitmap)
{
var ms = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(writeableBitmap));
encoder.Save(ms);
return ms;
}
}
Pain in the ass, but works!
The smaller image size is to be expected. I've never figured out why, but the Graphics.DrawImage really only works if you provide it not only a start location, but also a size. One of the overloads allows you to include size.

How can I create a 4-bit PNG with C#?

I am trying to create a 4-bit PNG file in C# but my code does not work.
Here is the code:
Bitmap bmp = new Bitmap(200, 50, PixelFormat.Format4bppIndexed);
string f = bmp.PixelFormat.ToString();
Graphics gImage = Graphics.FromImage(bmp);
gImage.FillRectangle(Brushes.Red, 0, 0, bmp.Width - 20, bmp.Height - 20);
gImage.DrawRectangle(Pens.White, 0, 0, bmp.Width - 20, bmp.Height - 20);
gImage.DrawString("Test", SystemFonts.DefaultFont, Brushes.White, 5, 8);
bmp.Save("C:\\buttons_normal1.png",ImageFormat.Png);
The code throws an exception at Graphics gImage line due to PixelFormat set to Format4bppIndexed. I saw a solution here suggesting that the final bitmap can be converted to 4-bit, but that code never worked for me.
Any suggestions?
The problem is that you aren't allowed to create a Graphics object with an indexed pixel format.
One solution would be to create a Graphics object in a different format to do your drawing, and create an empty Bitmap in PixelFormat.Format4bppIndexed format, and copy each pixel from one image to the other.
Create a non-4bit, and then convert to 4bit using the System.Windows.Media.Imaging library:
public void to4bit(Bitmap sourceBitmap, Stream outputStream)
{
BitmapImage myBitmapImage = ToBitmapImage(sourceBitmap);
FormatConvertedBitmap fcb = new FormatConvertedBitmap();
fcb.BeginInit();
myBitmapImage.DecodePixelWidth = sourceBitmap.Width;
fcb.Source = myBitmapImage;
fcb.DestinationFormat = System.Windows.Media.PixelFormats.Gray4;
fcb.EndInit();
PngBitmapEncoder bme = new PngBitmapEncoder();
bme.Frames.Add(BitmapFrame.Create(fcb));
bme.Save(outputStream);
}
private BitmapImage ToBitmapImage(Bitmap sourceBitmap)
{
using (var memory = new MemoryStream())
{
sourceBitmap.Save(memory, ImageFormat.Png);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
}

Why does the PngBitmapEncoder class make my images look grainy?

I'm using this code to convert a jpg image into a png 8.
The code works but the image looks grainy. I did an export in Photoshop as png 8 and it looks smoother and no grain.
Note: 8-bit png
Any image gurus out there that can help?
My code:
Image ImageFile = Image.FromFile(#"C:\Documents and Settings\dvela\My Documents\Downloads\pngtest\Batman01.jpg");
Rectangle NewSize = new Rectangle();
NewSize.Width = 112;
NewSize.Height = (NewSize.Width * ImageFile.Height) / ImageFile.Width;
Bitmap image = new Bitmap(NewSize.Width, NewSize.Height);
Graphics graphics = Graphics.FromImage(image);
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.DrawImage(ImageFile, NewSize);
FormatConvertedBitmap fcb = new FormatConvertedBitmap(System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(image.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(NewSize.Width, NewSize.Height)), PixelFormats.Indexed8, BitmapPalettes.Halftone256Transparent, 0.5);
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Interlace = PngInterlaceOption.Off;
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(fcb));
using (Stream fileStream = File.Open(#"C:\Documents and Settings\dvela\My Documents\Downloads\pngtest\test.png", FileMode.Create))
{
pngBitmapEncoder.Save(fileStream);
}
You are using a fixed palette with 256 colors for your converted image. Photoshop probably uses a palette that is created specially for that image and contains all of it's colors (or most of them).
You could use dithering, or somehow generate a custom palette, or both.
some more information is also here.
I found this library that does Palette-based and Octree-based color Quantization.
This MSDN article explains the difference between the algorithms with code.

Categories

Resources