I found that my problem lies in a method I used to resize the picture:
Here's the code:
private WriteableBitmap ResizeImage(BitmapImage original, double destWidth, double destHeight)
{
Image image = new Image()
{
Source = original,
Stretch = Stretch.UniformToFill
};
image.UpdateLayout();
int origWidth = original.PixelWidth;
int origHeight = original.PixelHeight;
ScaleTransform st = new ScaleTransform();
st.ScaleX = destWidth / (double)origWidth;
st.ScaleY = destHeight / (double) origHeight;
WriteableBitmap result = new WriteableBitmap((int)destWidth, (int)destHeight);
result.Render(image, st);
result.Invalidate();
return result;
}
I tested my code under 2 circumstances:
pass picture from "Camera Roll"
pass picture from other albums
My code will work on "Camera Roll" pictures, but it will return me a whole black result for the pictures loaded from other albums.
In either of these circumstances, despite the bitmap is whole black or not, this method will return me the bitmaps correct width and height.
For these two scenario, I'm using the same method to load pictures, but why only pictures from camera roll can be displayed but those who come from other albums cannot?
I know the WriteableBitmapEx library has the method to do resize perfectly. But I just curious about why my method won't work? Could any one help me with that?
Try the following:
private static BitmapSource ResizeImage(BitmapImage original, int destWidth, int destHeight)
{
if (original == null) return null;
if (destWidth == original.PixelWidth && destHeight == original.PixelHeight) return original;
return new TransformedBitmap(original, new ScaleTransform((double)destWidth / original.PixelWidth, (double)destHeight / original.PixelHeight));
}
I'm not sure if TransformedBitmap is available on Windows Phone...
By the way, you're performing an integer division:
st.ScaleX = destWidth / origWidth;
st.ScaleY = destHeight / origHeight;
For instance, if destWidth=150 and origWidth=300 then st.ScaleX will be 0.0 (not 0.5).
Related
I'm working on a screen sharing project ,and i recieve a small blocks of image from a Socket constantly and need to update them on a certain initial dekstop bitmap i have.
Basically i constantly read data from socket(data which is stored as jpeg image) ,using Image.FromStream() to retrieve the image and copying the recieved block pixels to the full primary bitmap(at a specific position X and Y which i also get from the socket)- that's how the initial image gets updated. But then comes the part where i need to display it on a Picturebox
I handle the Paint event and redrawing it all again-the entire inital image,which is pretty big(1920X1080 in my case).
This is my code:
private void MainScreenThread()
{
ReadData();//reading data from socket.
initial = bufferToJpeg();//first intial full screen image.
pictureBox1.Paint += pictureBox1_Paint;//activating the paint event.
while (true)
{
int pos = ReadData();
x = BlockX();//where to draw :X
y = BlockY();//where to draw :Y
Bitmap block = bufferToJpeg();//constantly reciving blocks.
Draw(block, new Point(x, y));//applying the changes-drawing the block on the big initial image.using native memcpy.
this.Invoke(new Action(() =>
{
pictureBox1.Refresh();//updaing the picturebox for seeing results.
// this.Text = ((pos / 1000).ToString() + "KB");
}));
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
lock (initial)
{
e.Graphics.DrawImage(initial, pictureBox1.ClientRectangle); //draws at picturebox's bounds
}
}
Because i'm aiming at high speed performance(it's kind of a real-time project) , i would like to know if there isn't any method to draw current recieved the block itself on the picturebox instead of drawing the whole initial bitmap again-which seems very inefficient to me...
This is my drawing method(works extremly fast, copying block with memcpy):
private unsafe void Draw(Bitmap bmp2, Point point)
{
lock (initial)
{
BitmapData bmData = initial.LockBits(new Rectangle(0, 0, initial.Width, initial.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, initial.PixelFormat);
BitmapData bmData2 = bmp2.LockBits(new Rectangle(0, 0, bmp2.Width, bmp2.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
IntPtr scan0 = bmData.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride = bmData.Stride;
int stride2 = bmData2.Stride;
int Width = bmp2.Width;
int Height = bmp2.Height;
int X = point.X;
int Y = point.Y;
scan0 = IntPtr.Add(scan0, stride * Y + X * 3);//setting the pointer to the requested line
for (int y = 0; y < Height; y++)
{
memcpy(scan0, scan02 ,(UIntPtr)(Width * 3));//copy one line
scan02 = IntPtr.Add(scan02, stride2);//advance pointers
scan0 = IntPtr.Add(scan0, stride);//advance pointers//
}
initial.UnlockBits(bmData);
bmp2.UnlockBits(bmData2);
}
}
Here are some examples of a full primary bitmap,and other small blocks i'm getting and need to draw over the full one.
Full bitmap:
small block:
small block:
small block:
I'm getting large amount of small blocks per second(30~40) somtimes their bounds are really small(rectangle of 100X80 pixels for example) so redrawing the entire bitmap again is not necessary...Rapidly Refreshing a full screen image would kill the performance...
I hope my explaination was clear.
Looking forward for an answer.
Thanks.
It would be shame to leave that question without some answer. The following is about 10 times faster in my tests when updating small portions of the picture box. What it does basically is smart invalidating (invalidates just the updated portion of the bitmap, considering the scaling) and smart painting (draws only the invalidated portion of the picture box, taken from e.ClipRectangle and considering the scaling):
private Rectangle GetViewRect() { return pictureBox1.ClientRectangle; }
private void MainScreenThread()
{
ReadData();//reading data from socket.
initial = bufferToJpeg();//first intial full screen image.
pictureBox1.Paint += pictureBox1_Paint;//activating the paint event.
// The update action
Action<Rectangle> updateAction = imageRect =>
{
var viewRect = GetViewRect();
var scaleX = (float)viewRect.Width / initial.Width;
var scaleY = (float)viewRect.Height / initial.Height;
// Make sure the target rectangle includes the new block
var targetRect = Rectangle.FromLTRB(
(int)Math.Truncate(imageRect.X * scaleX),
(int)Math.Truncate(imageRect.Y * scaleY),
(int)Math.Ceiling(imageRect.Right * scaleX),
(int)Math.Ceiling(imageRect.Bottom * scaleY));
pictureBox1.Invalidate(targetRect);
pictureBox1.Update();
};
while (true)
{
int pos = ReadData();
x = BlockX();//where to draw :X
y = BlockY();//where to draw :Y
Bitmap block = bufferToJpeg();//constantly reciving blocks.
Draw(block, new Point(x, y));//applying the changes-drawing the block on the big initial image.using native memcpy.
// Invoke the update action, passing the updated block rectangle
this.Invoke(updateAction, new Rectangle(x, y, block.Width, block.Height));
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
lock (initial)
{
var viewRect = GetViewRect();
var scaleX = (float)initial.Width / viewRect.Width;
var scaleY = (float)initial.Height / viewRect.Height;
var targetRect = e.ClipRectangle;
var imageRect = new RectangleF(targetRect.X * scaleX, targetRect.Y * scaleY, targetRect.Width * scaleX, targetRect.Height * scaleY);
e.Graphics.DrawImage(initial, targetRect, imageRect, GraphicsUnit.Pixel);
}
}
The only kind of tricky part is determining the scaled rectangles, especially the one for invalidating, due to floating point to int conversions required, so we make sure it's eventually a little bigger than needed, but not less.
If you just need to draw on top of the canvas, you can draw the initial image just once and then use CreateGraphics() and DrawImage to update the content:
ReadData();
initial = bufferToJpeg();
pictureBox1.Image = initial;
var graphics = pictureBox1.CreateGraphics();
while (true)
{
int pos = ReadData();
Bitmap block = bufferToJpeg();
graphics.DrawImage(block, BlockX(), BlockY());
}
I'll update the answer with a performance comparison as I'm not convinced this will give any major benefit; it will, at least, avoid a double DrawImage though.
For my current WPF appliaction I have to down scale some System.Drawing.Image ( objects which I load from PNG files (some of them with transparent background). I've tried multiple approaches for the resizing of the images and all of them worked fine in terms of having a smaller image afterwards. But unfortunately they all make the images loose their transparency.
My last try was to use ImageResizer an external library to get the job done as I expected it to handle that problem easily, but I still have the same issue: Original image is displayed with transparent background; Resized image displayed is displayed with black background.
Here's my code for the usage of the ImageResizer library:
ImageResizer.Instructions inst = new ImageResizer.Instructions("width=" + newWidth.ToString() + ";height=" + newHeight.ToString() + ";format=png;mode=max");
ImageResizer.ImageJob job = new ImageResizer.ImageJob(originalImage, typeof(System.Drawing.Bitmap), inst);
job.Build();
return job.Result as System.Drawing.Image;
These are my other two approaches, which also basically deliver the same result (Image resized: yet; Transparency preserved: Nope):
return originalImage.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero); // Transparency gets lost
return new System.Drawing.Bitmap(originalImage, new System.Drawing.Size(newWidth, newHeight)); // Transparency gets lost
Any ideas on what I have to do in order preserve the transparency while resizing?
Regards
Ralf
ImageResizer always preserves transparency.
You're losing transparency during the encoding (or display) of the image, which happens after you've taken control away from ImageResizer. Instead of passing in typeof(System.Drawing.Bitmap), pass in an output path or output stream.
var i = new Instructions(){ Width = newWidth,Height = newHeight, OutputFormat= OutputFormat.Png, Mode= FitMode.Max};
new ImageJob(originalImage, "output.png", i).Build();
ImageResizer can't control how an image is encoded if you take a raw Bitmap from it instead.
You didn't specific how you're using the results, which is very important. If you are not writing them to disk or a stream, but are instead displaying them, then you're looking for the problem in the wrong place. It's likely that the code responsible for compositing the results onto the display surface is failing to treat the image as a 32-bit image, and is instead ignoring the alpha channel.
Even though you're using WPF, you're working with System.Drawing.Image objects, so you can do this:
public static Bitmap ResizeImage(Image imgToResize, int newHeight)
{
int sourceWidth = imgToResize.Width;
int sourceHeight = imgToResize.Height;
float nPercentH = ((float)newHeight / (float)sourceHeight);
int destWidth = Math.Max((int)Math.Round(sourceWidth * nPercentH), 1); // Just in case;
int destHeight = newHeight;
Bitmap b = new Bitmap(destWidth, destHeight);
using (Graphics g = Graphics.FromImage((Image)b))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
}
return b;
}
Afterwards, be sure to save it with the PNG encoder:
public static System.Drawing.Imaging.ImageCodecInfo GetEncoder(System.Drawing.Imaging.ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
and then
codec = GetEncoder(ImageFormat.Png);
newBitmap.Save(newFile, codec, null);
(Note I'm using the standard .Net class libraries rather than a 3rd party library; hope that's OK.)
Update
Incidentally, since you are working in WPF, why not use WPF's image manipulation?
public static class BitmapHelper
{
public static void SaveToPng(this BitmapSource bitmap, string fileName)
{
var encoder = new PngBitmapEncoder();
SaveUsingEncoder(bitmap, fileName, encoder);
}
public static void SaveUsingEncoder(this BitmapSource bitmap, string fileName, BitmapEncoder encoder)
{
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (var stream = File.Create(fileName))
{
encoder.Save(stream);
}
}
public static void ImageLoadResizeAndSave(string inFile, string outFile, int newPixelHeight)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(inFile);
image.EndInit();
var newImage = BitmapHelper.ResizeImageToHeight(image, newPixelHeight);
BitmapHelper.SaveToPng(newImage, outFile);
}
/// <summary>
/// Resize the image to have the selected height, keeping the width proportionate.
/// </summary>
/// <param name="imgToResize"></param>
/// <param name="newHeight"></param>
/// <returns></returns>
public static BitmapSource ResizeImageToHeight(BitmapSource imgToResize, int newPixelHeight)
{
double sourceWidth = imgToResize.PixelWidth;
double sourceHeight = imgToResize.PixelHeight;
var nPercentH = ((double)newPixelHeight / sourceHeight);
double destWidth = Math.Max((int)Math.Round(sourceWidth * nPercentH), 1); // Just in case;
double destHeight = newPixelHeight;
var bitmap = new TransformedBitmap(imgToResize, new ScaleTransform(destWidth / imgToResize.PixelWidth, destHeight / imgToResize.PixelHeight));
return bitmap;
}
}
Maybe you're losing transparencies converting images in the older format to WPF format?
I want to develop a function with the following signature:
CopyImage(ImageSource inputImage, Point inTopLeft, Point InBottomRight, ImageSource outputImage, Point outTopLeft);
This function copy part of input image (ROI defined by inTopLeft and inBottomRight) and copy it to outputImage at outTopLeft.
I can do this in WPF by manipulating pixels, but I am looking for a solution that can do it much faster.
What is the fastest way to do this in WPF?
Your method could look like this:
private BitmapSource CopyRegion(
BitmapSource sourceBitmap, Int32Rect sourceRect,
BitmapSource targetBitmap, int targetX, int targetY)
{
if (sourceBitmap.Format != targetBitmap.Format)
{
throw new ArgumentException(
"Source and target bitmap must have the same PixelFormat.");
}
var bytesPerPixel = (sourceBitmap.Format.BitsPerPixel + 7) / 8;
var stride = bytesPerPixel * sourceRect.Width;
var pixelBuffer = new byte[stride * sourceRect.Height];
sourceBitmap.CopyPixels(sourceRect, pixelBuffer, stride, 0);
var outputBitmap = new WriteableBitmap(targetBitmap);
sourceRect.X = targetX;
sourceRect.Y = targetY;
outputBitmap.WritePixels(sourceRect, pixelBuffer, stride, 0);
return outputBitmap;
}
I'm trying to get an array of PictureBox to display a list of pictures (in png file format).
I tried to use the .NET ImageList control but it insists in re-sizing my pictures. It also does not support transparent background of those png files.
I also tried to use the Assembly to retrieve my files like this:
_imageStream = _assembly.GetManifestResourceStream("MyNamespace.MyImage.png");
but the code does not return me any resource files nor does it throw any run-time error.
My question is, is there any other ways to do this? Or better yet, can I somehow make the ImageList control to NOT alter my picture? Thanks.
You can try something like this although I am not sure that this is the best one or not:-
Assembly ambly = Assembly.LoadFile(pathToDll);
or
BitMap bitMap;
// where "ns" is the default namespace of the resource project
using (Stream resourceStream = ambly.GetManifestResourceSream("ns.image.jpg"))
{
bitMap = BitMap.FromStream(resourceStream);
}
An example:-
interface IThemeResourceProvider
{
Stream LoadBigLogo();
Stream LoadSmallLogo();
}
Then implement that interface in your resource library
public class ThemeResourceProvider : IThemeResourceProvider
{
public Stream LoadBigLogo()
{
Assembly ambly = Assembly.GetExecutingAssembly();
return ambly.GetManifestResourceStream("namespace.image.jpg");
}
(...)
}
Finally, instead of loading the resource directly in your main application, you instantiate the IThemeResourceProvider found in the resource library
Assembly assembly = Assembly.LoadFile(pathToDll);
var results = from type in assembly.GetTypes()
where typeof(IThemeResourceProvider).IsAssignableFrom(type)
select type;
Now you have an IEnumerable in that list. Typically, you'd only have one, but using this approach you could also host multiple sets of resources, and implement multiple IThemeResourceProviders in the same resource dll. You could e.g. identify each IThemeResourceProvider with a name, either as a property, or using a custom [Attribute] decoration on your various implementations. I'll leave the rest up to you to figure out.
But here's how to instantiate the IThemeResourceProviders in your list
foreach (var providerType in results)
{
var constructorInfo = providerType.GetConstructor(Type.EmptyTypes);
IThemeResourceProvider provider = constructorInfo.Invoke(null);
}
And finally, using one of these providers to get a bitmap:
BitMap bitMap;
using (Stream resourceStream = provider.LoadBigLogo())
{
bitMap = BitMap.FromStream(resourceStream);
}
This is the code that I got from someone and it's worked well for me!
private void SetImage(PictureBox pb) {
try {
Image img = pb.Image;
Size imgSize = GenerateImageDimensions( img.Width, img.Height, pb.Width, pb.Height );
Bitmap finalImg = new Bitmap( img, imgSize.Width, imgSize.Height );
Graphics gfx = Graphics.FromImage( img );
gfx.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
pb.Image = null;
pb.SizeMode = PictureBoxSizeMode.AutoSize;
pb.Image = finalImg;
} catch(Exception ex) {
}
}
public Size GenerateImageDimensions(int currW, int currH, int destW, int destH) {
//double to hold the final multiplier to use when scaling the image
double multiplier = 0;
//string for holding layout
string layout;
//determine if it's Portrait or Landscape
if(currH > currW) layout = "portrait";
else layout = "landscape";
switch(layout.ToLower()) {
case "portrait":
//calculate multiplier on heights
if(destH > destW) {
multiplier = (double) destW / (double) currW;
} else {
multiplier = (double) destH / (double) currH;
}
break;
case "landscape":
//calculate multiplier on widths
if(destH > destW) {
multiplier = (double) destW / (double) currW;
} else {
multiplier = (double) destH / (double) currH;
}
break;
}
//return the new image dimensions
return new Size( (int) (currW * multiplier), (int) (currH * multiplier) );
}
EDIT: Full disclosure all my images are jpg so I have no clue how this will hand transparent backgrounds.
EDIT TWO: Also you will need to adjust the pb.SizeMode to fit your needs. The way I did it was to set a max size for the PictureBox and it's worked well.
I'm trying to thumb an image as fast as possible regardless of the usage of resources to be used in my ImageList and listview and this is currently how i'm doing it but it seems to be slow:
public Image toThumbs(string file, int width, int height)
{
image = null;
aspectRatio = 1;
fullSizeImg = null;
try
{
fullSizeImg = Image.FromFile(file);
float w = fullSizeImg.Width;
float h = fullSizeImg.Height;
aspectRatio = w / h;
int xp = width;
int yp = height;
if (fullSizeImg.Width > width && fullSizeImg.Height > height)
{
if ((float)xp / yp > aspectRatio)
{
xp = (int)(yp * aspectRatio);
}
else
{
yp = (int)(xp / aspectRatio);
}
}
else if (fullSizeImg.Width != 0 && fullSizeImg.Height != 0)
{
xp = fullSizeImg.Width;
yp = fullSizeImg.Height;
}
image = new Bitmap(width, height);
graphics = Graphics.FromImage(image);
graphics.FillRectangle(Brushes.White, ((width - xp) / 2), (height - yp), xp, yp);
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.DrawImage(fullSizeImg, new Rectangle(((width - xp) / 2), (height - yp), xp, yp));
graphics.Dispose();
fullSizeImg.Dispose();
}
catch (Exception)
{
image = null;
}
return image;
}
I'm not sure if the computation is the one that is slowing down the thumbnailing or maybe the classes itself that are being used are slow, if that is the case then what other alternatives can be use maybe a different computation or i need to import other classes or is there a third party libraries that can be used or i need to do a dll import or something? Please help me.
Edit: Just found a solution here
http://www.vbforums.com/showthread.php?t=342386
it extracts a thumbnail from a file
without reading the whole file. I was able to reduce the time about 40% when i used this.
Your calculations happen in fractions of a second. The call to DrawImage is most likely the slowest part of this (as that one is doing the scaling).
If you're needing this thumbnail image exactly once then I don't see much room for improvement here. If you're calling that method on the same image more than once, you should cache the thumbnails.
I use this mechanism which seems to be very fast.
BitmapFrame bi = BitmapFrame.Create(new Uri(value.ToString()), BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnDemand);
// If this is a photo there should be a thumbnail image, this is VERY fast
if (bi.Thumbnail != null)
{
return bi.Thumbnail;
}
else
{
// No thumbnail so make our own (Not so fast)
BitmapImage bi2 = new BitmapImage();
bi2.BeginInit();
bi2.DecodePixelWidth = 100;
bi2.CacheOption = BitmapCacheOption.OnLoad;
bi2.UriSource = new Uri(value.ToString());
bi2.EndInit();
return bi2;
}
Hope this helps.
Out of curiosity, have you tried the GetThumbnailImage method on System.Drawing.Bitmap? It might at least be worth comparing to your current implementation.
This may seem like to much of an obvious answer but have you tried just using Image.GetThumbnailImage()?
You don't get as much control over the quality of the result but if speed is your main concern?
Your thumbnail extraction that gets you the big speed up relies on the image having a thumbnail already embedded in it.
To speed up the original you might find that changing:-
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
to
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
Might help.