7

Someone helped me get this code for taking a picture using xamarin forms labs camera:

picker = DependencyService.Get<IMediaPicker> ();  
                task = picker.TakePhotoAsync (new CameraMediaStorageOptions {
                    DefaultCamera = CameraDevice.Rear, 
                    MaxPixelDimension = 800,

                });

                img.BackgroundColor = Color.Gray;

                Device.StartTimer (TimeSpan.FromMilliseconds (250), () => {
                    if (task != null) {
                        if (task.Status == TaskStatus.RanToCompletion) {
                            Device.BeginInvokeOnMainThread (async () => {
                                //img.Source = ImageSource.FromStream (() => task.Result.Source);
                                var fileAccess = Resolver.Resolve<IFileAccess> ();
                                string imageName = "img_user_" + User.CurrentUser().id + "_" + DateTime.Now.ToString ("yy_MM_dd_HH_mm_ss") + ".jpg";
                                fileName = imageName;

                                fileAccess.WriteStream (imageName, task.Result.Source);
                                fileLocation = fileAccess.FullPath(imageName);

                                FileStream fileStream = new FileStream(fileAccess.FullPath(imageName), FileMode.Open, System.IO.FileAccess.Read);
                                imageUrl = (string)test[0]["url"];
                                img.Source = imageUrl;
                            }); 
                        }

                            return  task.Status != TaskStatus.Canceled
                            && task.Status != TaskStatus.Faulted
                            && task.Status != TaskStatus.RanToCompletion;
                    }
                    return true;
                });

It saves the image, but the actual size of the phone picture taken is huge, is there a way to resize it.

Nestor Ledon
  • 1,925
  • 2
  • 23
  • 36
user3841879
  • 659
  • 3
  • 12
  • 21

4 Answers4

15

UPDATE: The original answer is not useful, see below for updated answer. The issue was the PCL library was very slow and consumed too much memory.

ORIGINAL ANSWER (do not use):

I found an image I/O library, ImageTools-PCL, which I forked on github and trimmed down what wouldn't compile in Xamarin, keeping the modifications to minimum and the result seems to work.

To use it download the linked repository, compile it with Xamarin and add the DLLs from Build folder to your Forms project.

To resize an image you can do this (should fit the context of your question)

var decoder = new   ImageTools.IO.Jpeg.JpegDecoder ();
ImageTools.ExtendedImage inImage = new ImageTools.ExtendedImage ();

decoder.Decode (inImage, task.Result.Source); 

var outImage = ImageTools.ExtendedImage.Resize (inImage, 1024, new ImageTools.Filtering.BilinearResizer ());

var encoder = new ImageTools.IO.Jpeg.JpegEncoder ();
encoder.Encode (outImage, fileAccess.CreateStream (imageName));


ImageSource imgSource = ImageSource.FromFile (fileAccess.FullPath (imageName));

UPDATED ANSWER:

Get Xamarin.XLabs from nuget, learn about using Resolver, create an IImageService interface with Resize method.

Implementation for iOS:

public class ImageServiceIOS: IImageService{
   public void ResizeImage(string sourceFile, string targetFile, float maxWidth, float maxHeight)
    {  
        if (File.Exists(sourceFile) && !File.Exists(targetFile))
        {
            using (UIImage sourceImage = UIImage.FromFile(sourceFile))
            {  
                var sourceSize = sourceImage.Size;
                var maxResizeFactor = Math.Min(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);

                if (!Directory.Exists(Path.GetDirectoryName(targetFile)))
                    Directory.CreateDirectory(Path.GetDirectoryName(targetFile));

                if (maxResizeFactor > 0.9)
                {
                    File.Copy(sourceFile, targetFile);
                }
                else
                { 
                    var width = maxResizeFactor * sourceSize.Width;
                    var height = maxResizeFactor * sourceSize.Height;

                    UIGraphics.BeginImageContextWithOptions(new CGSize((float)width, (float)height), true, 1.0f);  
                    //  UIGraphics.GetCurrentContext().RotateCTM(90 / Math.PI);
                    sourceImage.Draw(new CGRect(0, 0, (float)width, (float)height)); 

                    var resultImage = UIGraphics.GetImageFromCurrentImageContext();
                    UIGraphics.EndImageContext();


                    if (targetFile.ToLower().EndsWith("png"))
                        resultImage.AsPNG().Save(targetFile, true);
                    else
                        resultImage.AsJPEG().Save(targetFile, true);
                }
            }
        }
    }
}

Implementation of the service for Android:

public class ImageServiceDroid: IImageService{
public void ResizeImage(string sourceFile, string targetFile, float maxWidth, float maxHeight)
{ 
    if (!File.Exists(targetFile) && File.Exists(sourceFile))
    {   
        // First decode with inJustDecodeBounds=true to check dimensions
        var options = new BitmapFactory.Options()
        {
            InJustDecodeBounds = false,
            InPurgeable = true,
        };

        using (var image = BitmapFactory.DecodeFile(sourceFile, options))
        {  
            if (image != null)
            {
                var sourceSize = new Size((int)image.GetBitmapInfo().Height, (int)image.GetBitmapInfo().Width);

                var maxResizeFactor = Math.Min(maxWidth / sourceSize.Width, maxHeight / sourceSize.Height);

                string targetDir = System.IO.Path.GetDirectoryName(targetFile);
                if (!Directory.Exists(targetDir))
                    Directory.CreateDirectory(targetDir);

                if (maxResizeFactor > 0.9)
                { 
                    File.Copy(sourceFile, targetFile);
                }
                else
                { 
                    var width = (int)(maxResizeFactor * sourceSize.Width);
                    var height = (int)(maxResizeFactor * sourceSize.Height);

                    using (var bitmapScaled = Bitmap.CreateScaledBitmap(image, height, width, true))
                    {
                        using (Stream outStream = File.Create(targetFile))
                        {
                            if (targetFile.ToLower().EndsWith("png"))
                                bitmapScaled.Compress(Bitmap.CompressFormat.Png, 100, outStream);
                            else
                                bitmapScaled.Compress(Bitmap.CompressFormat.Jpeg, 95, outStream);
                        }
                        bitmapScaled.Recycle();
                    }
                }

                image.Recycle();
            }
            else
                Log.E("Image scaling failed: " + sourceFile);
        }
    }
}
}
Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
  • Many thanks. I was able to get this working cross-platform relatively easily, thanks to your fork of ImageTools-PCL. – Timothy Lee Russell Jan 12 '15 at 05:04
  • Looks very promising! Just wonder, why not publish separate NuGet for Xamarin? :) – rudyryk Feb 20 '15 at 09:10
  • 1
    @rudyryk see updated answer, that's why I didn't publish it on Git or even less nuget – Sten Petrov Feb 20 '15 at 15:48
  • @StenPetrov I see. Finally, I've used solution similar to suggested by you in updated answer, but ImageTools still are looking interesting at some point. I think we can use native methods for encode/decode and ImageTools manipulation methods like filtering etc. What do you think? – rudyryk Feb 20 '15 at 17:13
  • ImageTools-PCL was *really* slow and bloated, I'd stick to native methods. I'm using the same approach for cropping and rotating images, iOS also has filtering (not sure about Android) – Sten Petrov Feb 20 '15 at 18:31
  • Can someone explain what parameters to use for ResizeImage? Is sourceFile the path to where the photo is on the disk? I imagine that I should call this method when I just have taken an photo? Please let me know! – user2915962 Mar 25 '15 at 17:42
  • This algorithm makes only a rough resize. You can see it especially on images with text where you see the pixels. It would be nice if there was an algorithm out there, which has a smoother scaling ... – testing Jun 25 '20 at 12:41
5

@Sten's answer might encounter out-of-memory problem on some android devices. Here's my solution to implement the ResizeImage function , which is according to google's "Loading Large Bitmaps Efficiently" document:

public void ResizeImage (string sourceFile, string targetFile, int reqWidth, int reqHeight)
{ 
    if (!File.Exists (targetFile) && File.Exists (sourceFile)) {   
        var downImg = decodeSampledBitmapFromFile (sourceFile, reqWidth, reqHeight);
        using (var outStream = File.Create (targetFile)) {
            if (targetFile.ToLower ().EndsWith ("png"))
                downImg.Compress (Bitmap.CompressFormat.Png, 100, outStream);
            else
                downImg.Compress (Bitmap.CompressFormat.Jpeg, 95, outStream);
        }
        downImg.Recycle();
    }
}

public static Bitmap decodeSampledBitmapFromFile (string path, int reqWidth, int reqHeight)
{
    // First decode with inJustDecodeBounds=true to check dimensions
    var options = new BitmapFactory.Options ();
    options.InJustDecodeBounds = true;
    BitmapFactory.DecodeFile (path, options);

    // Calculate inSampleSize
    options.InSampleSize = calculateInSampleSize (options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.InJustDecodeBounds = false;
    return BitmapFactory.DecodeFile (path, options);
}

public static int calculateInSampleSize (BitmapFactory.Options options, int reqWidth, int reqHeight)
{
    // Raw height and width of image
    int height = options.OutHeight;
    int width = options.OutWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {
        int halfHeight = height / 2;
        int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
               && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}
Community
  • 1
  • 1
Jon
  • 1,211
  • 13
  • 29
  • I see a problem with this solution. Because of the `inSampleSize` you can't exactly specify a certain image size you would like to have. It is a power of two and only comes close, but not to the specified sizes. It may be more memory efficient, but this doesn't help me with my goal. – testing Jun 19 '20 at 11:07
3

You can do this natively for each platform and use an interface. Heres an example for IOS

In your PCL project you need to add an interface

public interface IImageResizer
{
    byte[] ResizeImage (byte[] imageData, double width, double height);
}

Then to resize an image in your code, you can load the IOS implementation of that interface using the DependencyService and run the ResizeImage method

var resizer = DependencyService.Get<IImageResizer>();
var resizedBytes = resizer.ResizeImage (originalImageByteArray, 400, 400);
Stream stream = new MemoryStream(resizedBytes);
image.Source = ImageSource.FromStream(stream);

IOS Implementation, add this class to your IOS project.

[assembly: Xamarin.Forms.Dependency (typeof (ImageResizer_iOS))]
namespace YourNamespace
{
    public class ImageResizer_iOS : IImageResizer
    {

        public byte[] ResizeImage (byte[] imageData, double maxWidth, double maxHeight)
        {
            UIImage originalImage = ImageFromByteArray (imageData);


            double width = 300, height = 300;

            double maxAspect = (double)maxWidth / (double)maxHeight;
            double aspect = (double)originalImage.Size.Width/(double)originalImage.Size.Height;

            if (maxAspect > aspect && originalImage.Size.Width > maxWidth) {
                //Width is the bigger dimension relative to max bounds
                width = maxWidth;
                height = maxWidth / aspect;
            }else if (maxAspect <= aspect && originalImage.Size.Height > maxHeight){
                //Height is the bigger dimension
                height = maxHeight;
                width = maxHeight * aspect;
            }

            return originalImage.Scale(new SizeF((float)width,(float)height)).AsJPEG ().ToArray ();
        }


        public static MonoTouch.UIKit.UIImage ImageFromByteArray(byte[] data)
        {
            if (data == null) {
                return null;
            }

            MonoTouch.UIKit.UIImage image;
            try {
                image = new MonoTouch.UIKit.UIImage(MonoTouch.Foundation.NSData.FromArray(data));
            } catch (Exception e) {
                Console.WriteLine ("Image load failed: " + e.Message);
                return null;
            }
            return image;
        }
    }
}
Steve
  • 313
  • 4
  • 15
2

An update from the Xamarin Media Plugin allows you to resize the image https://github.com/jamesmontemagno/MediaPlugin ... barring that, and you need a more generic resize option (say the image comes from a web call, and not the device, then have a look at: https://github.com/InquisitorJax/Wibci.Xamarin.Images

InquisitorJax
  • 1,062
  • 11
  • 20