I am attempting to resize and save an image, which is fairly easy (for instance, see this example external example no longer valid).
However, using this code strips the metadata information from the image. I can't quite seem to figure out how to preserve the metadata for a jpeg image.
EDIT: Example Code
public static void ResizeMethodThree(string sourceFile, string targetFile)
{
byte[] baSource = File.ReadAllBytes(sourceFile);
PropertyItem[] propertyItems = new Bitmap(sourceFile).PropertyItems;
using (Stream streamPhoto = new MemoryStream(baSource))
{
BitmapFrame bfPhoto = ReadBitmapFrame(streamPhoto);
BitmapMetadata metaData = (BitmapMetadata)bfPhoto.Metadata;
int nNewPictureSize = 200;
int nWidth = 0;
int nHeight = 0;
if (bfPhoto.Width > bfPhoto.Height)
{
nWidth = nNewPictureSize;
nHeight = (int)(bfPhoto.Height * nNewPictureSize / bfPhoto.Width);
}
else
{
nHeight = nNewPictureSize;
nWidth = (int)(bfPhoto.Width * nNewPictureSize / bfPhoto.Height);
}
BitmapFrame bfResize = ResizeHelper(bfPhoto, nWidth, nHeight, BitmapScalingMode.HighQuality);
byte[] baResize = ToByteArray(bfResize);
File.WriteAllBytes(targetFile, baResize);
Image targetImage = new Bitmap(targetFile);
foreach (var propertyItem in propertyItems)
{
targetImage.SetPropertyItem(propertyItem);
}
targetImage.Save(targetFile);
}
}
public static BitmapFrame ResizeHelper(BitmapFrame photo, int width,
int height, BitmapScalingMode scalingMode)
{
var group = new DrawingGroup();
RenderOptions.SetBitmapScalingMode(
group, scalingMode);
group.Children.Add(
new ImageDrawing(photo,
new Rect(0, 0, width, height)));
var targetVisual = new DrawingVisual();
var targetContext = targetVisual.RenderOpen();
targetContext.DrawDrawing(group);
var target = new RenderTargetBitmap(
width, height, 96, 96, PixelFormats.Default);
targetContext.Close();
target.Render(targetVisual);
var targetFrame = BitmapFrame.Create(target);
return targetFrame;
}
private static byte[] ToByteArray(BitmapFrame bfResize)
{
using (MemoryStream msStream = new MemoryStream())
{
JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
jpgEncoder.Frames.Add(bfResize);
jpgEncoder.Save(msStream);
return msStream.ToArray();
}
}
private static BitmapFrame ReadBitmapFrame(Stream streamPhoto)
{
BitmapDecoder bdDecoder =
BitmapDecoder.Create(streamPhoto, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
return bdDecoder.Frames[0];
}
Use the Image.PropertyItems property on the source image to get the list of metadata items. Loop through the list, calling Image.SetPropertyItem on the destination image. You should normally avoid resizing and re-compressing a jpeg image, working from an uncompressed original is best to maintain quality and avoid artifacts.
Related
Capturing a screen shot is quite easy but cropping is a different story, or at least appears so. We are attempting to emulate the solution here in Xamarin using SkiaSharp (System.Drawing not supported in Xamarin).
Currently we are able to capture the screenshot and return the image BUT if we crop our image the image returned is all black.
How do we crop a captured screenshot correctly?
NOTE*: image = image.Subset(rec); under the "crop image" section is how we are trying to crop.
iOS screenshot
public byte[] Capture()
{
var capture = UIScreen.MainScreen.Capture();
using (NSData data = capture.AsPNG())
{
var bytes = new byte[data.Length];
Marshal.Copy(data.Bytes, bytes, 0, Convert.ToInt32(data.Length));
return bytes;
}
}
Droid screenshot
public byte[] Capture()
{
var rootView = context.Window.DecorView.RootView;
using (var screenshot = Bitmap.CreateBitmap(
rootView.Width,
rootView.Height,
Bitmap.Config.Argb8888))
{
var canvas = new Canvas(screenshot);
rootView.Draw(canvas);
using (var stream = new MemoryStream())
{
screenshot.Compress(Bitmap.CompressFormat.Png, 90, stream);
return stream.ToArray();
}
}
}
Attempting to crop captured screenshot
// Use this function to crop a screen shot to a specific element.
public byte[] test(byte[] screenshotData, View element)
{
// locate IntPtr to byte[] of uncropped screenshot
GCHandle gch = GCHandle.Alloc(screenshotData, GCHandleType.Pinned);
IntPtr addr = gch.AddrOfPinnedObject();
// assign initial bounds
SKImageInfo info = new SKImageInfo((int)App.Current.MainPage.Width,
(int)App.Current.MainPage.Height);
// create initial pixel map
using SKPixmap pixmap = new SKPixmap(info, addr);
// Release
gch.Free();
// create bitmap
using SKBitmap bitmap = new SKBitmap();
// assign pixel data
bitmap.InstallPixels(pixmap);
// create surface
using SKSurface surface = SKSurface.Create(info);
// create a canvas for drawing
using SKCanvas canvas = surface.Canvas;
// draw
canvas.DrawBitmap(bitmap, info.Rect);
// get an image subset to save
SKImage image = surface.Snapshot();
SKRectI rec = new SKRectI((int)element.Bounds.Left, (int)element.Bounds.Top,
(int)element.Bounds.Right, (int)element.Bounds.Bottom);
// crop image
image = image.Subset(rec);
byte[] bytes = SKBitmap.FromImage(image).Bytes;
image.Dispose();
return bytes;
}
EDIT: Alternative solution attempt (not working)
// Use this function to crop a screen shot to a specific element.
public byte[] test(byte[] screenshotData, View element)
{
// locate IntPtr to byte[] of uncropped screenshot
GCHandle gch = GCHandle.Alloc(screenshotData, GCHandleType.Pinned);
IntPtr addr = gch.AddrOfPinnedObject();
// assign initial bounds
SKImageInfo info = new SKImageInfo((int)App.Current.MainPage.Width,
(int)App.Current.MainPage.Height);
// create bitmap
SKBitmap bitmap = new SKBitmap();
bitmap.InstallPixels(info, addr);
// boundaries
SKRect cropRect = new SKRect((int)element.Bounds.Left, (int)element.Bounds.Top,
(int)element.Bounds.Right, (int)element.Bounds.Bottom);
SKBitmap croppedBitmap = new SKBitmap((int)cropRect.Width,
(int)cropRect.Height);
SKRect dest = new SKRect(0, 0, cropRect.Width, cropRect.Height);
SKRect source = new SKRect(cropRect.Left, cropRect.Top,
cropRect.Right, cropRect.Bottom);
// draw with destination and source rectangles
// to extract a subset of the original bitmap
using SKCanvas canvas = new SKCanvas(croppedBitmap);
canvas.DrawBitmap(bitmap, source, dest);
return croppedBitmap.Bytes;
//return bitmap.Bytes;
}
iOS solution - As seen here.
// crop the image, without resizing
private UIImage CropImage(UIImage sourceImage, int crop_x, int crop_y, int width, int height)
{
var imgSize = sourceImage.Size;
UIGraphics.BeginImageContextWithOptions(new System.Drawing.SizeF(width, height), false, 0.0f);
var context = UIGraphics.GetCurrentContext();
var clippedRect = new RectangleF(0, 0, width, height);
context.ClipToRect(clippedRect);
var drawRect = new RectangleF(-crop_x, -crop_y, imgSize.Width, imgSize.Height);
sourceImage.Draw(drawRect);
var modifiedImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return modifiedImage;
}
Android solution - getNavigationBarSize(context) as seen here
// crop the image, without resizing
private byte[] CropImage(byte[] screenshotBytes, int top)
{
Android.Graphics.Bitmap bitmap = Android.Graphics.BitmapFactory.DecodeByteArray(
screenshotBytes, 0, screenshotBytes.Length);
int viewStartY = (int)(top * 2.8f);
int viewHeight = (int)(bitmap.Height - (top * 2.8f));
var navBarXY = getNavigationBarSize(context);
int viewHeightMinusNavBar = viewHeight - navBarXY.Y;
Android.Graphics.Bitmap crop = Android.Graphics.Bitmap.CreateBitmap(bitmap,
0, viewStartY,
bitmap.Width, viewHeightMinusNavBar
);
bitmap.Dispose();
using MemoryStream stream = new MemoryStream();
crop.Compress(Android.Graphics.Bitmap.CompressFormat.Jpeg, 100, stream);
return stream.ToArray();
}
*NOTE: Unsure why a multiplication of 2.8 is required, though this works correctly. It should be stated testing was only done in the Android emulator. Perhaps it's emulator specific.
*NOTE2: x = 0 and width is equal to the entire width of the screen as that's per our requirement. Likewise top is Element.Bounds.Top.
Update: Clemens's solution is the fastest. I'll leave the alternative I found just in case:
While trying to create a minimal reproducible example as Peter Duniho suggested in the comment, I found that the wrong transparency values were coming from theBitmapImageToBitmapConverter()
It was messing up the whole image. I now load the png straight to a bitmap and scan it and it gives accurate results:
Bitmap bmp = new Bitmap("icon.png");
Console.WriteLine(TransparencyPercentageInImage(bmp));
Question was:
I have a few image controls in a list:
imagesList[index].Source = ReloadIcon(index);
They load images from ".png" files like so:
public BitmapImage ReloadIcon(int index)
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.UriSource = new Uri(iconPaths[index], UriKind.Absolute);
image.EndInit();
return image;
}
I then convert those to bitmaps using this converter:
private Bitmap BitmapImageToBitmapConverter(BitmapImage bitmapImage)
{
using (MemoryStream outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapImage));
enc.Save(outStream);
System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outStream);
return new Bitmap(bitmap);
}
}
To later scan each pixel for transparency using this code:
private double TransparencyPercentageInImage(Bitmap image)
{
double transpPercentage;
double transpPixelCount = 0;
double totalPixelCount = image.Height * image.Width;
Console.WriteLine("Total pixel count: " + totalPixelCount);
for (int y = 0; y < image.Height; ++y)
{
for (int x = 0; x < image.Width; ++x)
{
if (image.GetPixel(x, y).A == 0) //or !=255
{
transpPixelCount++;
}
}
}
transpPercentage = transpPixelCount / totalPixelCount * 100;
return transpPercentage;
}
Basically, what should I do to get an accurate transparent pixels percentage/count from a bitmap?
I'm looking for the count of absolutely transparent pixels, not semi-transparent.
I'm not really looking for speed here so any solution goes. I'm already using unsafe code, so that's welcome too.
You neither need a System.Drawing.Bitmap nor a WriteableBitmap to access the pixel values in a BitmapSource.
Just call CopyPixels to get the pixel buffer and count the pixels with an alpha value of 0. First make sure you access the buffer in the desired format:
private double GetTransparentPixelsPercentage(BitmapSource bitmap)
{
if (bitmap.Format != PixelFormats.Bgra32)
{
bitmap = new FormatConvertedBitmap(bitmap, PixelFormats.Bgra32, null, 0);
}
var pixelCount = bitmap.PixelWidth * bitmap.PixelHeight;
var pixels = new byte[4 * pixelCount];
bitmap.CopyPixels(pixels, 4 * bitmap.PixelWidth, 0);
var transparentPixelCount = 0;
for (var i = 3; i < 4 * pixelCount; i += 4) // start at first alpha value
{
if (pixels[i] == 0)
{
transparentPixelCount++;
}
}
return (double)transparentPixelCount / pixelCount;
}
I have byte[] that store some image of unknown size. I need to resize this bitmap into 160 x 160 and bind to Wpf window.
Should i convert byte[] to Bitmap, resize, make BitmapSource from it and then use in wpf?
//result of this method binds to wpf
private static BitmapSource SetImage(byte[] dataBytes)
{
var picture = BitmapHelper.ByteToBitmap(dataBytes);
var resizedPicture = BitmapHelper.Resize(picture, 160, 160);
var bitmapSource = BitmapHelper.GetSourceFromBitmap(resizedPicture);
bitmapSource.Freeze();
return bitmapSource;
}
//BitmapHelper class:
public static Bitmap ByteToBitmap(byte[] byteArray)
{
using (var ms = new MemoryStream(byteArray))
{
var img = (Bitmap)Image.FromStream(ms);
return img;
}
}
internal static Bitmap Resize(Bitmap bitmap, int width, int height)
{
var newImage = new Bitmap(width, height);
using (var gr = Graphics.FromImage(newImage))
{
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
gr.DrawImage(bitmap, new Rectangle(0, 0, width, height));
}
return newImage;
}
internal static BitmapSource GetSourceFromBitmap(Bitmap source)
{
Contract.Requires(source != null);
var ip = source.GetHbitmap();
BitmapSource bs;
int result;
try
{
bs = Imaging.CreateBitmapSourceFromHBitmap(ip,
IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
}
finally
{
result = NativeMethods.DeleteObject(ip);
}
if (result == 0)
throw new InvalidOperationException("NativeMethods.DeleteObject returns 0 (operation failed)");
return bs;
}
internal static class NativeMethods
{
[DllImport("gdi32")]
static internal extern int DeleteObject(IntPtr o);
}
What is the fastest and common method for that case?
I'd suggest you to use the DecodePixelWidth and / or DecodePixelHeight properties of the BitmapImageclass (which inherits from ImageSource).
Take a look here:
BitmapSource GetImage(byte[] dataBytes)
{
using (MemoryStream stream = new MemoryStream(dataBytes))
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
// This is important: the image should be loaded on setting the StreamSource property, afterwards the stream will be closed
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.DecodePixelWidth = 160;
// Set this to 160 to get exactly 160x160 image
// Comment it out to retain original aspect ratio having the image width 160 and auto calculated image height
bi.DecodePixelHeight = 160;
bi.StreamSource = stream;
bi.EndInit();
return bi;
}
}
I'm trying to create an 8-bit image with a solid background color. It seems like it should be pretty straight forward but the details on the file list it as 32-bit color depth. What am I missing?
public void CreateImage()
{
var bmpOut = new Bitmap(300, 300);
var g = Graphics.FromImage(bmpOut);
g.FillRectangle(new SolidBrush(Color.Gray), 0, 0, 300, 300);
var stream = new MemoryStream();
bmpOut.Save(stream, GetPngCodecInfo(), GetEncoderParameters());
bmpOut.Save(#"C:\image.png", GetPngCodecInfo(), GetEncoderParameters());
}
public EncoderParameters GetEncoderParameters()
{
var parameters = new EncoderParameters();
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8);
return parameters;
}
public ImageCodecInfo GetPngCodecInfo()
{
var encoders = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo codecInfo = null;
foreach (var imageCodecInfo in encoders)
{
if (imageCodecInfo.FormatID != ImageFormat.Png.Guid)
continue;
codecInfo = imageCodecInfo;
break;
}
return codecInfo;
}
Use this constructor to specify a pixel format : http://msdn.microsoft.com/en-us/library/3z132tat.aspx
Since you cannot create a Graphics from an indexed pixel format, you can only write raw pixels to a 8-bit image.
Bitmap bitmap = new Bitmap(32, 32, PixelFormat.Format8bppIndexed);
var bitmapData = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.ReadWrite, bitmap.PixelFormat);
Random random=new Random();
byte[] buffer=new byte[bitmap.Width*bitmap.Height];
random.NextBytes(buffer);
Marshal.Copy(buffer,0,bitmapData.Scan0,buffer.Length);
bitmap.UnlockBits(bitmapData);
bitmap.Save("test.bmp",ImageFormat.Bmp);
You can either use such code on WinForms : http://www.codeproject.com/Articles/17162/Fast-Color-Depth-Change-for-Bitmaps
Or if you can reference this class from WPF it will be much easier :
http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.formatconvertedbitmap(v=vs.85).aspx
You could also create the image at a higher bit rate and then convert it to 8 bits just before saving. This would allow you to use a Graphics context when creating the image. See this question for suggestions on how to convert to 8 bits:C# - How to convert an Image into an 8-bit color Image?
ImageExtensions.cs
using System.Runtime.InteropServices;
using System.Linq;
using System.Drawing.Imaging;
using System.Drawing;
using System;
public static partial class ImageExtensions {
public static ColorPalette ToGrayScale(this ColorPalette palette) {
var entries=palette.Entries;
for(var i=entries.Length; i-->0; entries[i]=entries[i].ToGrayScale())
;
return palette;
}
public static Color ToGrayScale(this Color color, double[] luminance=null) {
var list=(luminance??new[] { 0.2989, 0.5870, 0.1140 }).ToList();
var channel=new[] { color.R, color.G, color.B };
var c=(byte)Math.Round(list.Sum(x => x*channel[list.IndexOf(x)]));
return Color.FromArgb(c, c, c);
}
public static Bitmap To8bppIndexed(this Bitmap original) {
var rect=new Rectangle(Point.Empty, original.Size);
var pixelFormat=PixelFormat.Format8bppIndexed;
var destination=new Bitmap(rect.Width, rect.Height, pixelFormat);
using(var source=original.Clone(rect, PixelFormat.Format32bppArgb)) {
var destinationData=destination.LockBits(rect, ImageLockMode.WriteOnly, pixelFormat);
var sourceData=source.LockBits(rect, ImageLockMode.ReadOnly, source.PixelFormat);
var destinationSize=destinationData.Stride*destinationData.Height;
var destinationBuffer=new byte[destinationSize];
var sourceSize=sourceData.Stride*sourceData.Height;
var sourceBuffer=new byte[sourceSize];
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, sourceSize);
source.UnlockBits(sourceData);
destination.Palette=destination.Palette.ToGrayScale();
var list=destination.Palette.Entries.ToList();
for(var y=destination.Height; y-->0; ) {
for(var x=destination.Width; x-->0; ) {
var pixelIndex=y*destination.Width+x;
var sourceIndex=4*pixelIndex;
var color=
Color.FromArgb(
sourceBuffer[0+sourceIndex],
sourceBuffer[1+sourceIndex],
sourceBuffer[2+sourceIndex],
sourceBuffer[3+sourceIndex]
).ToGrayScale();
destinationBuffer[pixelIndex]=(byte)list.IndexOf(color);
}
}
Marshal.Copy(destinationBuffer, 0, destinationData.Scan0, destinationSize);
destination.UnlockBits(destinationData);
}
return destination;
}
}
Call bmpOut=bmpOut.To8bppIndexed(); before you it save to file.
Using C# how can I resize a jpeg image? A code sample would be great.
I'm using this:
public static void ResizeJpg(string path, int nWidth, int nHeight)
{
using (var result = new Bitmap(nWidth, nHeight))
{
using (var input = new Bitmap(path))
{
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
{
g.DrawImage(input, 0, 0, nWidth, nHeight);
}
}
var ici = ImageCodecInfo.GetImageEncoders().FirstOrDefault(ie => ie.MimeType == "image/jpeg");
var eps = new EncoderParameters(1);
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
result.Save(path, ici, eps);
}
}
Good free resize filter and example code.
http://code.google.com/p/zrlabs-yael/
private void MakeResizedImage(string fromFile, string toFile, int maxWidth, int maxHeight)
{
int width;
int height;
using (System.Drawing.Image image = System.Drawing.Image.FromFile(fromFile))
{
DetermineResizeRatio(maxWidth, maxHeight, image.Width, image.Height, out width, out height);
using (System.Drawing.Image thumbnailImage = image.GetThumbnailImage(width, height, new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback), IntPtr.Zero))
{
if (image.Width < thumbnailImage.Width && image.Height < thumbnailImage.Height)
File.Copy(fromFile, toFile);
else
{
ImageCodecInfo ec = GetCodecInfo();
EncoderParameters parms = new EncoderParameters(1);
parms.Param[0] = new EncoderParameter(Encoder.Compression, 40);
ZRLabs.Yael.BasicFilters.ResizeFilter rf = new ZRLabs.Yael.BasicFilters.ResizeFilter();
//rf.KeepAspectRatio = true;
rf.Height = height;
rf.Width = width;
System.Drawing.Image img = rf.ExecuteFilter(System.Drawing.Image.FromFile(fromFile));
img.Save(toFile, ec, parms);
}
}
}
}
C# (or rather: the .NET framework) itself doesn't offer such capability, but it does offer you Bitmap from System.Drawing to easily access the raw pixel data of various picture formats. For the rest, see http://en.wikipedia.org/wiki/Image_scaling
Nice example.
public static Image ResizeImage(Image sourceImage, int maxWidth, int maxHeight)
{
// Determine which ratio is greater, the width or height, and use
// this to calculate the new width and height. Effectually constrains
// the proportions of the resized image to the proportions of the original.
double xRatio = (double)sourceImage.Width / maxWidth;
double yRatio = (double)sourceImage.Height / maxHeight;
double ratioToResizeImage = Math.Max(xRatio, yRatio);
int newWidth = (int)Math.Floor(sourceImage.Width / ratioToResizeImage);
int newHeight = (int)Math.Floor(sourceImage.Height / ratioToResizeImage);
// Create new image canvas -- use maxWidth and maxHeight in this function call if you wish
// to set the exact dimensions of the output image.
Bitmap newImage = new Bitmap(newWidth, newHeight, PixelFormat.Format32bppArgb);
// Render the new image, using a graphic object
using (Graphics newGraphic = Graphics.FromImage(newImage))
{
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
newGraphic.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
// Set the background color to be transparent (can change this to any color)
newGraphic.Clear(Color.Transparent);
// Set the method of scaling to use -- HighQualityBicubic is said to have the best quality
newGraphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Apply the transformation onto the new graphic
Rectangle sourceDimensions = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
Rectangle destinationDimensions = new Rectangle(0, 0, newWidth, newHeight);
newGraphic.DrawImage(sourceImage, destinationDimensions, sourceDimensions, GraphicsUnit.Pixel);
}
// Image has been modified by all the references to it's related graphic above. Return changes.
return newImage;
}
Source : http://mattmeisinger.com/resize-image-c-sharp
ImageMagick should be the best way. Easy and reliable.
using (var image = new MagickImage(imgfilebuf))
{
image.Resize(len, len);
image.Strip();
using MemoryStream ms = new MemoryStream();
image.Write(ms);
return ms.ToArray();
}