I've read about creating a HttpHandler to call each time I want to show a thumbnail where it will perfom the resizing for me.
I've also heard about some other solutions, but I was wondering which solution would be the best for a social networking website where thumbnails are shown all over the place on each page and everywhere.
Would it be good to resize and save the image on the disk after the origianl file has been uploaded? What's the best way to reference these images?
Does anyone have any advice for me?
Thank you.
Would it be good to resize and save the image on the disk after the
origianl file has been uploaded? What's the best way to reference
these images?
Definitely, that's what Twitter does, for example, and most websites when thumbnails need to be displayed. This is time consuming. You don't want your user to sit idle while you do this on every image every time.
Store the thumbnails on disk and keep a reference to the thumbnails on the database. Or store them on the db itself. I don't want to get into that debate about disk vs DB but just don't resize them every time. It should be done ONCE.
Here is some resize Code if you need it, the first you set a max height and width and it created a thumbnail with the same aspect retio as the original that do now violate either max.
The second, if you know the actual size of the final image and don't need to worry about aspect ratios is fatr simpler.
public Image Thumbnail(Image FullsizeImage, int MaxHeight, int MaxWidth)
{
try
{
// This has to be here or for some reason this resize code will
// resize an internal Thumbnail and wil stretch it instead of shrinking
// the fullsized image and give horrible results
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
System.Drawing.Image NewImage;
if (!((MaxWidth < FullsizeImage.Width) || (MaxHeight < FullsizeImage.Height)))
NewImage = FullsizeImage;
else
{
float HeightRatio = 1;
float WidthRatio = 1;
HeightRatio = (float)FullsizeImage.Width / FullsizeImage.Height;
WidthRatio = (float)FullsizeImage.Height / FullsizeImage.Width;
float DrawHeight = (float)FullsizeImage.Height;
float DrawWidth = (float)FullsizeImage.Width;
if (MaxHeight < FullsizeImage.Height)
{
DrawHeight = (float)MaxHeight;
DrawWidth = MaxHeight * HeightRatio;
}
if (MaxWidth < DrawWidth)
{
DrawWidth = MaxWidth;
DrawHeight = DrawWidth * WidthRatio;
}
NewImage = FullsizeImage.GetThumbnailImage((int)(DrawWidth),
(int)(DrawHeight), null, IntPtr.Zero);
}
return NewImage;
// To return a byte array for saving in a db
//ms = new MemoryStream();
//NewImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
//NewImage.Dispose();
//FullsizeImage.Dispose();
//return ms.ToArray();
}
catch
{
return null;
}
finally
{
}
}
public Image Resize(Image OrigImage, int NewHeight, int NewWidth)
{
if (OrigImage != null)
{
Bitmap bmp = new Bitmap(OrigImage, new Size(NewWidth, NewHeight));
bmp.SetResolution(this.ImageResolution, this.ImageResolution);
Graphics g = Graphics.FromImage(bmp);
return bmp;
}
else
{
return null;
}
}
Related
I have created an app where the user can select or take a picture. This picture is then submitted to a web service, and can be a maximum size of 4MB. Some pictures are larger than this so I need to resize them before submitting to get under the 4MB limit, but doing this as little as possible to keep as much detail as possible. I couldn't find a built in way of image resizing down to a certain filesize, so to do this I have so far created a DependencyService with a specific image resizing implementation in Android and iOS.
The code that I have come up with for Android (and iOS is similar) is as follows, where I repeatedly scale the bitmap by 0.9 until it gets under the limit. Is there a better way of doing this?:
public byte[] ResizeImage(byte[] imageData)
{
var originalImage = BitmapFactory.DecodeByteArray(imageData, 0, imageData.Length);
var resizedImage = originalImage;
double newWidth = originalImage.Width;
double newHeight = originalImage.Height;
while (resizedImage.ByteCount > 4194304)
{
newWidth *= 0.9;
newHeight *= 0.9;
resizedImage = Bitmap.CreateScaledBitmap(originalImage, (int) newWidth,
(int) newHeight, false);
}
using (var ms = new MemoryStream())
{
resizedImage.Compress(Bitmap.CompressFormat.Jpeg, 100, ms);
return ms.ToArray();
}
}
I see that you use JPG without compression (in your code sample the quality is set to '100'). We also know that RGB bit depth is 24 bit/pixel. Given this we have:
ImageWidthPx * ImageHeightPx * 24/8 = TotalImageSizeInBytes
What you are looking for is:
ScaleFactor = TotalImageSizeInBytes / (ImageWidthPx * ImageHeightPx * 24/8)
And in your case:
var scaleFactor = 4194304 / (originalImage.Width * originalImage.Height * 3)
I am creating a card game, for this i have created a custom surface view, in which images are getting load. Since images are downloaded from internet, they are of different sizes and looks visually bad on screen. I want to achieve two things here.
Load images of fixed size or resize the images dynamically.
Draw images from bottom of screen in upward direction.
For 1st point i used CreateBitmap method but getting below exception.
java.lang.OutOfMemoryError: Failed to allocate a 1915060280 byte allocation with 4194304 free bytes and 123MB until OOM error
To fixed the issue i thought of using Glide/Picasso based on this question and this, but i found out that Glide/Picasso load images only on imageview, but i don't have any imageview, i only got a custom surfaceview inside a linearlayout.
For 2nd point i used rotation of image. Following is the code of that.
public void Render(Canvas paramCanvas)
{
try
{
// paramCanvas.DrawColor(Android.Graphics.Color.Blue);
int i = 0;
Down_Card_Gap = 0;
foreach (Cards localcard in FaceDownDeck.ToList())
{
Bitmap localimage = BitmapFactory.DecodeResource(Resources, localcard.GetImageId(context));
Bitmap rotatedimage = RotateBitmap(localimage, 180);
paramCanvas.DrawBitmap(rotatedimage, (Screen_Center_X - Card_Width / 2)+Down_Card_Gap, (Screen_Height - Card_Height), null);
// paramCanvas.DrawBitmap(localimage, (Screen_Center_X - Card_Width / 2), (Screen_Center_Y - Card_Height), null);
if (i++ == 7)
{ break; }
if (Down_Card_Gap > 0)
{
Down_Card_Gap += Card_Width / 2;
}
else
{
Down_Card_Gap -= Card_Width / 2;
}
Down_Card_Gap *= -1;
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
private Bitmap RotateBitmap(Bitmap localimage, float angle)
{
Matrix matrix = new Matrix();
matrix.PostRotate(angle);
matrix.PostScale(Card_Width, Card_Height);
Bitmap resized= Bitmap.CreateBitmap(localimage, 0, 0, localimage.Width, localimage.Height, matrix, true);
localimage.Recycle();
return resized;
}
I want to know if it is a right approach, or is there any better method achieve the functionality.
Load images of fixed size or resize the images dynamically.
About the fixed size and resize, you can refer to this, find decodeFile method:
protected Bitmap decodeFile(File f) {
try {
//decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
//Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE = 150;
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 1;
while (true) {
if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE)
break;
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
//decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {
}
return null;
}
You can see, it uses BitmapFactory.Options.inJustDecodeBounds= true to preload the bitmap, and scale the bitmap. Also you can refer to official document. Read this to compress bitmap's quality.
Except from this, you also need consider the picture cache.This talks about how to build an efficient memory cache for bitmaps.
For uploading image I am using plupload on client side. Then in my controlled I have next logic:
public ActionResult UploadFile()
{
try
{
var file = Request.Files.Count > 0 ? Request.Files[0] : null;
using (var fileStream = new MemoryStream())
{
using (var oldImage = new Bitmap(file.InputStream))
{
var format = oldImage.RawFormat;
using (var newImage = ImageUtility.ResizeImage(oldImage, 800, 2000))
{
newImage.Save(fileStream, format);
}
byte[] bits = fileStream.ToArray();
}
}
{
catch (Exception ex)
{
}
}
ImageUtility.ResizeImage Method:
public static class ImageUtility
{
public static Bitmap ResizeImage(Bitmap image, int width, int height)
{
if (image.Width <= width && image.Height <= height)
{
return image;
}
int newWidth;
int newHeight;
if (image.Width > image.Height)
{
newWidth = width;
newHeight = (int)(image.Height * ((float)width / image.Width));
}
else
{
newHeight = height;
newWidth = (int)(image.Width * ((float)height / image.Height));
}
var newImage = new Bitmap(newWidth, newHeight);
using (var graphics = Graphics.FromImage(newImage))
{
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphics.FillRectangle(Brushes.Transparent, 0, 0, newWidth, newHeight);
graphics.DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
}
}
The issue which i have here that Image size is increased.
I uploaded image of 1.62MB and after this controller is called and it creates instance if Bitmap and then save Bitmap to filestream and read bits with "fileStream.ToArray();" I am getting 2.35MB in "bits".
Can anyone tell me what's the reason of increasing the image size after I save it as bitmap. I need Bitmap because I need to check with and height of uploaded image and resize it if I need.
The answer is simple, the bitmap takes up more memory the whatever format the image was in previously because it's uncompressed it stays in that uncompressed format after saving it.
jpeg, png, gif, etc. are compressed and therefore use less bytes tha a bitmap which is uncompressed.
If you just want to save the original image, just save file.InputStream.
If you need to resize, you can use a library to apply jpg/png/etc compression and then save the result.
What is the goal here? Are you merely trying to upload an image? Does it need to be validated as an image? Or are you just trying to upload the file?
If upload is the goal, without any regard to validation, just move the bits and save them with the name of the file. As soon as you do this ...
using (var oldImage = new Bitmap(file.InputStream))
... you are converting to a bitmap. Here is where you are telling the bitmap what format to use (raw).
var format = oldImage.RawFormat;
If you merely want to move the file (upload), you can run the memory stream to a filestream object and you save the bits.
If you want a few checks on whether the image is empty, etc, you can try this page (http://www.codeproject.com/Articles/1956/NET-Image-Uploading), but realize it is still putting it in an image, which is not your desire if you simply want to save "as is".
I have the following code placed at a button click. Button got clicked after user selects an image to upload.
using (SD.Image OriginalImage = SD.Image.FromStream(AsyncUpload.FileContent))
{
decimal dRatio = (Decimal)1024 / OriginalImage.Width;
int iScaledW = 1024;
int iScaledH = (int)(OriginalImage.Height * dRatio);
using (SD.Bitmap bmp = new SD.Bitmap(iScaledW, iScaledH))
{
bmp.SetResolution(OriginalImage.HorizontalResolution, OriginalImage.VerticalResolution);
using (SD.Graphics Graphic = SD.Graphics.FromImage(bmp))
{
Graphic.SmoothingMode =SD2.SmoothingMode.AntiAlias;
Graphic.InterpolationMode = SD2.InterpolationMode.HighQualityBicubic;
Graphic.PixelOffsetMode = SD2.PixelOffsetMode.HighQuality;
Graphic.FillRectangle(SD.Brushes.White, 0, 0, iScaledW, iScaledH);
Graphic.DrawImage(OriginalImage, 0, 0, iScaledW, iScaledH);
bmp.Save(Path.Combine(Server.MapPath(upPath), AsyncUpload.FileName));
}
}
}
Here AsyncUpload is the name of FileUpload control. Original image is 3.96MB and by re-sizing the image with the above function, creates a re-sized image of 1.92MB.
However, If I select that same 3.96MB file as click a button that executes the following code that simply uploads whatever provided.
objfile.SaveAs(Path.Combine(Server.MapPath(upPath), AsyncUpload.FileName));
And then click a button that do the same processing of re-sizing the image as above mentioned, it creates an image of 200KB only. Below is the code of that button click.
static byte[] Crop(string Img, int Width, int Height, int X, int Y)
{
int w = Width;
int h = Height;
int xx = X;
int yy = Y;
try
{
using (SD.Image OriginalImage = SD.Image.FromFile(Img))
{
if (w == 0 || h == 0)
{
w = OriginalImage.Width;
h = OriginalImage.Height;
}
int iScaleX = 0, iScaleY = 0;
GetImageResolution(w, h, out iScaleX, out iScaleY);
using (SD.Bitmap bmp = new SD.Bitmap(iScaleX, iScaleY))
{
bmp.SetResolution(OriginalImage.HorizontalResolution, OriginalImage.VerticalResolution);
using (SD.Graphics Graphic = SD.Graphics.FromImage(bmp))
{
Graphic.SmoothingMode = SmoothingMode.AntiAlias;
Graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
Graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
Graphic.FillRectangle(SD.Brushes.White, 0, 0, iScaleX, iScaleY);
Graphic.DrawImage(OriginalImage, 0, 0, iScaleX, iScaleY);
bmp.Save(<Path>);
}
}
}
}
catch (Exception Ex)
{
return null;
// throw (Ex);
}
}
Both the code are same but the output images are of different sizes and that too, a big difference in size. Please help me understand why is this behavior. I want to avoid uploading big size image by reading them as stream and re-sizing them before even uploading them to disk. But the size after re-sizing is too big to accept.
Also, if anybody can share their thoughts of how to compress and image.
Thanks in advance
When both the image dimensions and contents are the same, the most likely cause for a file size difference of this magnitude (1.92MB to 200KB) is the compression type.
Most likely one of the 2 functions is saving lossless PNG, probably at the default 32 bits per pixel, and this produces large file.
The other likely saves 24-bit lossy JPEG, which gives much smaller size with almost all images.
To test this, force the format to be JPEG using this code:
bmp.Save(Path.Combine(Server.MapPath(upPath), AsyncUpload.FileName), SD.Imaging.ImageFormat.Jpeg);
Does this produce smaller files?
This is a follow up question on Save image to file keeping aspect ration in a WPF app
I know howto scale the image, but how do I expand the canvas size, to ensure the image still has the requested width and height. In this example its 250x250 but its dynamic.
I have created this illustration to show what I'm trying to accomplice.
I can't find any way of expanding the canvas of an BitmapImage, nor a way to create an in memory image in the correct size, with a transparent background, and then merging the two images together.
CroppedBitmap doesn't seem to support adding space around an image so instead you can create a transparent image the correct size using WriteableBitmap. If the input is smaller than the target size this method will enlarge it, but that is easy to alter.
public static BitmapSource FitImage(BitmapSource input, int width, int height)
{
if (input.PixelWidth == width && input.PixelHeight == height)
return input;
if(input.Format != PixelFormats.Bgra32 || input.Format != PixelFormats.Pbgra32)
input = new FormatConvertedBitmap(input, PixelFormats.Bgra32, null, 0);
//Use the same scale for x and y to keep aspect ratio.
double scale = Math.Min((double)width / input.PixelWidth, height / (double)input.PixelHeight);
int x = (int)Math.Round((width - (input.PixelWidth * scale))/2);
int y = (int)Math.Round((height - (input.PixelHeight * scale))/2);
var scaled = new TransformedBitmap(input, new ScaleTransform(scale, scale));
var stride = scaled.PixelWidth * (scaled.Format.BitsPerPixel / 8);
var result = new WriteableBitmap(width, height, input.DpiX, input.DpiY, input.Format,null);
var data = new byte[scaled.PixelHeight * stride];
scaled.CopyPixels(data, stride, 0);
result.WritePixels(new Int32Rect(0,0,scaled.PixelWidth,scaled.PixelHeight), data, stride,x,y);
return result;
}
If you are already rendering content using RenderTargetBitmap you could wrap it in a ViewBox to do the scaling but if you're just working with normal images I'd use the above method.
You should be able to set the Stretch property to Uniform.
Just some code in case others are trying to postprocess files from a form upload.
if (file.PostedFile != null)
{
//write the new file to disk
string cachePath = String.Format("{0}temp\\", Request.PhysicalApplicationPath);
string photoPath = String.Format("{0}temp.png", cachePath);
if (!Directory.Exists(cachePath))
{
Directory.CreateDirectory(cachePath);
}
file.PostedFile.SaveAs(photoPath);
//resize the new file and save it to disk
BitmapSource banana = FitImage(ReadBitmapFrame(file.PostedFile.InputStream), 640, 480);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(banana));
FileStream pngStream = new FileStream(cachePath + "test.png", FileMode.Create);
encoder.Save(pngStream);
pngStream.Close();
//set a couple images on page to the newly uploaded and newly processed files
image.Src = "temp/temp.png";
image.Visible = true;
image2.Src = "temp/test.png";
image2.Visible = true;
}