In my WP8 app, the user could take pictures and store in my sqlce database. The problem is the size of the pictures that needs to be send by internet and the length of the size is at least 4mb by picture!
I need to reduce the size of the pictures after they are captured in my app.
Here is my code:
private void camera_Completed(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK && e.Error == null)
{
image = new System.Windows.Media.Imaging.BitmapImage();
image.SetSource(e.ChosenPhoto);
Imagem.Source = image;
}
}
To get the image to save the image to my sqlce database, I'm using this code:
byte[] ConvertImage()
{
if (image == null) return null;
MemoryStream ms = new MemoryStream();
WriteableBitmap wb = new WriteableBitmap(image);
wb.SaveJpeg(ms, image.PixelWidth, image.PixelHeight, 0, 100);
return ms.ToArray();
}
Even save to Jpeg format, the size in bytes are still too big in my opinion.
You have two ways to adjust the size of the image. Resize the image resolution to the smaller one:
WritableBitmapEx sample code:
// Resizes the WriteableBitmap to 200px wide and 300px high using a bilinear interpolation method
var resized = writeableBmp.Resize(200, 300, WriteableBitmapExtensions.Interpolation.Bilinear)
and/or adjust(decrease) the quality (affect image size):
public static void SaveJpeg(
this WriteableBitmap bitmap,
Stream targetStream,
int targetWidth,
int targetHeight,
int orientation,
int quality
)
You can use both methods.
try this to your code
byte[] ConvertImage()
{
if (image == null) return null;
MemoryStream ms = new MemoryStream();
WriteableBitmap wb = new WriteableBitmap(image);
wb.SaveJpeg(ms, image.PixelWidth/4, image.PixelHeight/4, 0, 100);
return ms.ToArray();
}
you can also do /2 or whatever you required
Related
I have a collection of Bitmaps that are stored in a List<byte[]> object. I want to combine all those Bitmaps into one image (kinda like a paneramic) without converting them back to bitmaps. I've tried using a MemoryStream (see below) but it doesn't seem to work. I always run out of memory or the image comes out corrupted. I would appreciate any thoughts/advice on the matter.
List<byte[]> FrameList = new List<byte[]>();
using (MemoryStream ms = new MemoryStream())
{
for (int i = 0; i < FrameList.Count; i++)
{
//Does not work
ms.Write(FrameList[i], i * FrameList[i].Length, FrameList[i].Length);
//Does not work
ms.Write(FrameList[i], 0, FrameList[i].Length);
}
Bitmap img = (Bitmap)Bitmap.FromStream(ms);
img.Save("D:\\testing.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
The steps:
calculate the final image size as sum of size of the individual
images
create a Bitmap with the final size
get the Graphics object
draw all images using the Graphics object
save the final Bitmap
var final = new Bitmap(width, height); // calculated final size
using (var graphics = Graphics.FromImage(final))
{
graphics.DrawImage(...); // draw all partial images onto the final bitmap
}
I m trying to compress tiff file with CCITT4. Original size 101 KB, after run size reduce a little 98 KB. My limit size 50 KB, how can I make the size under my limit? code is below
My limits;
Size <50KB
Image Width: 1766 – 1890 pixels
Image Length: 915 – 1040 pixels
Thanks everyone.
Image Sample
public static Byte[] CompressBitmap(Bitmap img)
{
Bitmap bm = img;
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo ici = null;
foreach (ImageCodecInfo codec in codecs)
{
if (codec.MimeType == "image/tiff")
ici = codec;
}
EncoderParameters ep = new EncoderParameters(1);
ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag,
(long)EncoderValue.Flush);
ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression,
(long)EncoderValue.CompressionCCITT4);
using (var stream = new MemoryStream())
{
bm.Save(stream, ici, ep);
return stream.ToArray();
}
}
Since the source Image seems to be a Color Image (that just happens to look like Black&White) and its dimensions need to be adjusted to a maximum value, you need to:
Redefine proportionally the Size of the source Image, setting the maximum Width and Height limits that the converted image needs to respect
Convert the Image to a 1bpp Indexed format: the Bitmap.Clone() method provides means to both define the source Image new Size and set a specific PixelFormat (PixelFormat.Format1bppIndexed, in this case). The internal conversion method is quite good, the resulting Image quality is usually a good compromise.
Define the quality of the Image: since the Image will be converted to B&W, its quality can be set initially to the maximum (100). The process can be repeated if the final Image bytes count doesn't fit the requirements (50KB)
Compress the Image using the CCTII Group 4 Compression scheme
Encode the Image, using the TIFF format Encoder, saving it to a MemoryStream for further processing
The size in bytes of the sample Image provided in the question, when saved to a file, is reduced from the initial 94,600 bytes to 6,786 bytes with maximum quality.
File.WriteAllBytes("[Image Path].tiff", imageStream.ToArray());
The ConvertToBWTiffImageStream() method can be called as:
var maximumWidthAndHeight = new Size(1024, 768);
int quality = 100;
var compression = EncoderValue.CompressionCCITT4;
var imageStream = ConvertToBWTiffImageStream(sourceImage, maximumWidthAndHeight, quality, compression);
Note that maximumWidthAndHeight specify the maximum Width and the maximum Height, not a new Size of the Image (which would be otherwise distorted). The Image Size is scaled proportionally according to these values.
► Remember to add this: using ImageCodec = System.Drawing.Imaging.Encoder;
(the GetImageMaxSize() method is half-dumb, I'll fix it later)
using System.Drawing;
using System.Drawing.Imaging;
using ImageCodec = System.Drawing.Imaging.Encoder;
private MemoryStream ConvertToBWTiffImageStream(Bitmap image, Size maxWidthHeight, long compressionLevel, EncoderValue compressionScheme)
{
var imageSize = GetImageMaxSize(image, maxWidthHeight);
var bwImage = image.Clone(new Rectangle(Point.Empty, image.Size), PixelFormat.Format1bppIndexed);
compressionLevel = Math.Max(0, Math.Min(100, compressionLevel));
var codecParms = new EncoderParameters(2);
try {
var imageCodec = ImageCodecInfo.GetImageEncoders().FirstOrDefault(enc => enc.FormatID == ImageFormat.Tiff.Guid);
var qualityParam = new EncoderParameter(ImageCodec.Quality, compressionLevel);
var compressionParam = new EncoderParameter(ImageCodec.Compression, (long)compressionScheme);
var ms = new MemoryStream();
codecParms.Param[0] = qualityParam;
codecParms.Param[1] = compressionParam;
image.Save(ms, imageCodec, codecParms);
return ms;
}
finally {
codecParms.Dispose();
bwImage.Dispose();
}
}
private Size GetImageMaxSize(Image image, Size maxSize)
{
float scale = 1.0f;
if (image.Width > maxSize.Width && image.Width >= image.Height) {
scale = (float)maxSize.Width / image.Width;
}
else if (image.Height > maxSize.Height) {
scale = (float)maxSize.Height / image.Height;
}
return new Size((int)(image.Width * scale), (int)(image.Height * scale));
}
I want to draw Image in windows phone 8 using pixel byte. so I get some pixel byte via c++ library(c++/CLI). But pixel data not include bitmap Header. It is just pixel byte array.
Is this possible to convert pixel data array to bitmap Image without bitmap header in windows phone?
public void updateImage(byte[] byteArray, UInt32 bufferSize, int cvtWidth, int cvtHeight)
{
// I saw this source a lot of search. But It's not work. It makes some exeption.
BitmapImage bi = new BitmapImage();
MemoryStream memoryStream = new MemoryStream(byteArray);
bi.SetSource(memoryStream);
ImageScreen.Source = bi;
}
You need to use a WriteableBitmap : http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.imaging.writeablebitmap
Then you can access it's PixelBuffer using AsStream and save that array to the stream.
//srcWidth and srcHeight are original image size, needed
//srcData is pixel data array
WriteableBitmap wb = new WriteableBitmap(srcWidth, srcHeight);
using (Stream stream = wb.PixelBuffer.AsStream())
{
await stream.WriteAsync(srcData, 0, srcData.Length);
}
EDIT
As Proglamour stated in WIndows Phone there is no PixelBuffer, but a property named Pixels which is an array exists.
It cannot be replaced but it's content can so this should work:
WriteableBitmap wb = new WriteableBitmap(srcWidth, srcHeight);
Array.Copy(srcData, wb.Pixels, srcData.Length);
I solved this problem. Thanks for your help Filip.
public void updateImage(byte[] byteArray, UInt32 bufferSize, int cvtWidth, int cvtHeight)
{
Dispatcher.BeginInvoke(() =>
{
WriteableBitmap wb = new WriteableBitmap(cvtWidth, cvtHeight);
System.Buffer.BlockCopy(byteArray, 0, wb.Pixels, 0, byteArray.Length);
//wb.Invalidate();
ImageScreen.Source = wb;
});
}
I want to make image size smaller then its original size.I am using following code for compress the size images but it increased the image size from 1MB to 1.5MB
Any Other solution for compress large size images without change image original height,width.
public static byte[] CompressImage(Image img) {
int originalwidth = img.Width, originalheight = img.Height;
Bitmap bmpimage = new Bitmap(originalwidth, originalheight);
Graphics gf = Graphics.FromImage(bmpimage);
gf.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
gf.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.AssumeLinear;
gf.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
Rectangle rect = new Rectangle(0, 0, originalwidth, originalheight);
gf.DrawImage(img, rect, 0, 0, originalwidth, originalheight, GraphicsUnit.Pixel);
byte[] imagearray;
using (MemoryStream ms = new MemoryStream())
{
bmpimage.Save(ms, ImageFormat.Jpeg);
imagearray= ms.ToArray();
}
return imagearray;
}
You can set the quality level when you save the file as JPEG, which mostly also directly will correlate with file size - the less quality the smaller your output file will be.
Also see How to: Set JPEG Compression Level , for an example see this SO answer.
As said by #BrokenGlass you can specify the compression level within the EncoderParameter. Here's a snippet if you want to give a try changing quality:
public static void SaveJpeg(string path, Image image, int quality)
{
//ensure the quality is within the correct range
if ((quality < 0) || (quality > 100))
{
//create the error message
string error = string.Format("Jpeg image quality must be between 0 and 100, with 100 being the highest quality. A value of {0} was specified.", quality);
//throw a helpful exception
throw new ArgumentOutOfRangeException(error);
}
//create an encoder parameter for the image quality
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
//get the jpeg codec
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
//create a collection of all parameters that we will pass to the encoder
EncoderParameters encoderParams = new EncoderParameters(1);
//set the quality parameter for the codec
encoderParams.Param[0] = qualityParam;
//save the image using the codec and the parameters
image.Save(path, jpegCodec, encoderParams);
}
I've got to following function which is called to change the resolution of an image. I want to do this so uploaded image with for example 300dpi will be modified to 72dpi (for web). This question is related to another question here on SO where i'm working on.
I'm creation an extension method for this to be able to use this function on more places in my application, instead of only when uploading new files. (See above mentioned question)
public static byte[] SetDpiTo72(this byte[] imageToFit, string mimeType, Size newSize)
{
using (MemoryStream memoryStream = new MemoryStream(), newMemoryStream = new MemoryStream())
{
memoryStream.Write(imageToFit, 0, imageToFit.Length);
var originalImage = new Bitmap(memoryStream);
using (var canvas = Graphics.FromImage(originalImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage((Image)originalImage,0,0, newSize.Width, newSize.Height);
newBitmap.SetResolution(72, 72);
newBitmap.Save(newMemoryStream, ImageFunctions.GetEncoderInfo(mimeType), null);
}
return newMemoryStream.ToArray();
}
}
The mentioned extension methode is being called in a function similar to the situation below;
if (newSize.Width > originalImage.Width && newSize.Height > originalImage.Height)
{
newSize.Width = originalImage.Width;
newSize.Height = originalImage.Height;
uploadedFileBuffer = uploadedFileBuffer.SetDpiTo72(uploadedFile.ContentType, newSize);
return CreateFile(newSize, uploadedFile, uploadedFileBuffer);
}
The bytearray coming in is the file as an bytearray. It already has the correct size, but I want to change the resolution to 72dpi. However after exectution and saving the image the resolution is still the originale entered resolution, which is 300dpi. How can I do this?
UPDATE AFTER SEVERAL ANSWERS:
public static byte[] SetDpiTo72(this byte[] imageToFit, string mimeType, Size newSize)
{
using (MemoryStream memoryStream = new MemoryStream(), newMemoryStream = new MemoryStream())
{
memoryStream.Write(imageToFit, 0, imageToFit.Length);
var originalImage = new Bitmap(memoryStream);
using (var canvas = Graphics.FromImage(originalImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage((Image)originalImage,0,0, newSize.Width, newSize.Height);
originalImage.SetResolution(72, 72);
var epQuality = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75);
var epParameters = new EncoderParameters(1);
epParameters.Param[0] = epQuality;
Image newimg = Image.FromStream(memoryStream);
//Getting an GDI+ exception after the execution of this line.
newimg.Save("C:\\test1234.jpg", ImageFunctions.GetEncoderInfo(mimeType), epParameters);
originalImage.Save("test.jpg", ImageFormat.Jpeg);
//This line give me an Argumentexception - Parameter is not valid.
//originalImage.Save(newMemoryStream, ImageFunctions.GetEncoderInfo(mimeType), epParameters);
//newMemoryStream.Close();
}
return newMemoryStream.ToArray();
}
}
The stackstrace which comes with the exception is telling me the following;
at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
at Extensions.ByteArrayExtensions.SetDpiTo72(Byte[] imageToFit, String mimeType, Size newSize) in C:\Website\Project\Extensions\ByteArrayExtensions.cs:line 356
at CMS.Presentation.FileFunctions.CreateFullsizeImage(HttpPostedFileBase uploadedFile, Size newSize, Byte[] uploadedFileBuffer) in C:\Website\Project\CMS.Presentation\FileFunctions.cs:line 197
at CMS.Presentation.FileFunctions.CreateFile(HttpPostedFileBase uploadedFile, INodeService nodeservice, Guid userId, Node parentNode) in C:\Website\Project\CMS.Presentation\FileFunctions.cs:line 53
In the mean time I've also developed another function (see below) resizing just a bitmap. And this seem to work correctly. I can't use this function with my current implementation though because it returns just an Bitmap. Or should i change everything to work with bitmaps?
private static Bitmap ResizeImage(Image image, int width, int height)
{
var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
Bitmap resizedImage;
if (frameCount > 1)
{
//we have a animated GIF
resizedImage = ResizeAnimatedGifImage(image, width, height);
}
else
{
resizedImage = (Bitmap)image.GetThumbnailImage(newDimensions.Width, newDimensions.Height, null, IntPtr.Zero);
}
resizedImage.SetResolution(72,72);
return resizedImage;
}
Ok, I tried it only on files on harddrive, but it should work with streams too.
Bitmap bitmap = new Bitmap(loadFrom);
Bitmap newBitmap = new Bitmap(bitmap);
newBitmap.SetResolution(72, 72);
newBitmap.Save(saveTo);
Took me a while, but I finally found the problem!
The problem lied in the ResizeImage function I used. In the 'GetThumbnailImage' to be specific. I ran into another problem with blurry images, which was explainable because GetThumbnailImage would stretch up the created ThumbNail to the desired size. And the resolution off the thumbnail never changes.
private static Bitmap ResizeImage(Image image, int width, int height)
{
var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
Bitmap resizedImage;
if (frameCount > 1)
{
//we have a animated GIF
resizedImage = ResizeAnimatedGifImage(image, width, height);
}
else
{
resizedImage = (Bitmap)image.GetThumbnailImage(newDimensions.Width, newDimensions.Height, null, IntPtr.Zero);
}
resizedImage.SetResolution(72,72);
return resizedImage;
}
By modifying the function above to the function below I was able to solve the problem using Graphics.DrawImage to redraw the new image before rendering it. Also the GenerateImageDimensions was slightly modified. This taken together the problem was solved.
private static Bitmap ResizeImage(Image image, int width, int height)
{
var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
var resizedImage = new Bitmap(newDimensions.Width, newDimensions.Height);
if (frameCount > 1)
{
//we have a animated GIF
resizedImage = ResizeAnimatedGifImage(image, width, height);
}
else
{
//we have a normal image
using (var gfx = Graphics.FromImage(resizedImage))
{
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;
var targRectangle = new Rectangle(0, 0, newDimensions.Width, newDimensions.Height);
var srcRectangle = new Rectangle(0, 0, image.Width, image.Height);
gfx.DrawImage(image, targRectangle, srcRectangle, GraphicsUnit.Pixel);
}
}
return resizedImage;
}
By "changing the resolution", do you actually mean you want to reduce the number of pixels in the image by 72/300? I.e. change a 4000x3000 image to 960x720?
If so, I can't see where your code actually does that. The overload of DrawImage() you're using does this:
Draws the specified image, using its original physical size, at the location specified by a coordinate pair.
Which is exactly what is happening.
Try one of the other overloads such as this one:
Draws the specified Image at the specified location and with the specified size.
for example:
// Create image.
Image newImage = Image.FromFile("SampImag.jpg");
// Create coordinates for upper-left corner of image and for size of image.
int x = 0;
int y = 0;
int width = 450;
int height = 150;
// Draw image to screen.
e.Graphics.DrawImage(newImage, x, y, width, height);
EDIT: per the comments, I understand the OP wants to reduce file size without reducing pixel count. Therefore the files must be recompressed.
I've borrowed some sample code from here:
ImageCodecInfo iciJpegCodec = null;
// This will specify the image quality to the encoder. Change the value of 75 from 0 to 100, where 100 is best quality, but highest file size.
EncoderParameter epQuality = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75);
// Get all image codecs that are available
ImageCodecInfo[] iciCodecs = ImageCodecInfo.GetImageEncoders();
// Store the quality parameter in the list of encoder parameters
EncoderParameters epParameters = new EncoderParameters(1);
epParameters.Param[0] = epQuality;
// Loop through all the image codecs
for (int i = 0; i < iciCodecs.Length; i++)
{
// Until the one that we are interested in is found, which is image/jpeg
if (iciCodecs[i].MimeType == "image/jpeg")
{
iciJpegCodec = iciCodecs[i];
break;
}
}
// Create a new Image object from the current file
Image newImage = Image.FromFile(strFile);
// Get the file information again, this time we want to find out the extension
FileInfo fiPicture = new FileInfo(strFile);
// Save the new file at the selected path with the specified encoder parameters, and reuse the same file name
newImage.Save(outputPath + "\\" + fiPicture.Name, iciJpegCodec, epParameters);
Rob, I believe that issue with your code is at saving the image - the actual digital image data would be certain number of dots/pixels i.e. (m x n) and setting resolution at bitmap wouldn't/shouldn't change the number dots (and hence physical byte size of image). The resolution information will be stored in the image header (to be used by programs while printing/editing images) - what happens if you store the new bitmap to file instead of mem stream
newBitmap.Save("c:\test.png", ImageFormat.Png);
Check dpi for above file from file -> properties -> summary (advanced). It should be 72 dpi.