Read and Write GPS coordinates with RedCorners.ExifLibrary

I recently published RedCorners.ExifLibrary, a NuGet that lets you read and write EXIF metadata in image files. This is basically a fork of the ExifLibrary project by oozcitak, with four minor changes:

  • It removes dependencies to System.Drawing, making the library work across all .net Standard 2.0 compatible runtimes, including Xamarin.iOS and Xamarin.Android.
  • It adds a conversion helper method for going back and forth between floating point or Degrees/Minutes/Seconds representations for coordinates.
  • It adds helper methods to quickly read and write GPS coordinates in the image files.
  • Everything is moved to the RedCorners.ExifLibrary namespace, in order to make it possible to use both the original library and my version of it simultaneously.

It has been tested on Xamarin.iOS, Xamarin.Android, .NET Core and .NET Framework on JPEG images.

You can get the NuGet from here: https://www.nuget.org/packages/RedCorners.ExifLibrary

Some quick examples:


using RedCorners.ExifLibrary;

// Read GPS Coordinates
var file = ImageFile.FromFile(Path);
var coords = file.GetGPSCoords();
if (coords.HasValue)
    (Latitude, Longitude) = coords.Value;

// Write GPS Coordinates
var file = ImageFile.FromFile(Path);
file.SetGPSCoords(Latitude, Longitude);
file.Save(Path);

More information on GitHub.

RestSharp returns 0 for custom ASP.NET Core middleware response

A normal behavior of RestSharp is to return StatusCode = 0 for responses that won’t go through (e.g. unreachable server). However, a tricky behavior of RestSharp is to also return 0 if the response has content, but the Content-Length is undefined.

If you are confused why your RestSharp is returning 0 while Postman and Chrome work fine, make sure you are setting the response Content-Length to the actual content length.


public async Task Invoke(HttpContext context)
{
//instead of await next(context);
var result = JsonConvert.SerializeObject(new
{
Message = "Hello, World!"
});
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.StatusCode = 200;
context.Response.ContentLength = result.Length + 2;
await context.Response.WriteAsync("\r\n" + result);
await context.Response.Body.FlushAsync();
}

Change Image Format on Xamarin / C#

SkiaSharp is a Xamarin-friendly NuGet that lets you do powerful image processing and rendering tasks. You can use it to quickly resize, compress or re-encode your images (i.e. change their format). This example shows how to change a JPEG file to PNG with SkiaSharp:

using (var istream = File.OpenRead(originalPath))
    using (var skBitmap = SKBitmap.Decode(istream))
        using (var image = SKImage.FromBitmap(skBitmap))
            using (var ostream = File.OpenWrite(newPath))
                image.Encode(SKEncodedImageFormat.Png, 100)
                    .SaveTo(ostream);

Preventing multiple clicks on Xamarin.Forms buttons

I encountered this issue many times where a button was supposed to open a modal page, and tapping on that button repeatedly would open the modal page several times.

To prevent this, you can create a simple custom renderer for the Button class, and put a timer which disables the button for a short period of time after each successful tap:

#if __ANDROID__
    using Xamarin.Forms.Platform.Android;
    using Android.Views;
#elif __IOS__
    using Xamarin.Forms.Platform.iOS;
    using UIKit;
#endif

using Xamarin.Forms;
using System.Threading.Tasks;
using Neat.Renderers;

[assembly: ExportRenderer(typeof(Button), typeof(BlockingButtonRenderer))]
namespace Neat.Renderers
{
    public class BlockingButtonRenderer : ButtonRenderer
    {
        bool _isPressed = false;
        
#if __ANDROID__
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
        {
            base.OnElementChanged(e);
            
            var control = Control as Android.Widget.Button;
            control?.SetOnClickListener(ButtonClickListener.Instance.Value);
        }
        
        public async void SendBlockingClicked()
        {
            if (_isPressed) return;
            _isPressed = true;
            ((IButtonController)Element).SendClicked();
            await Task.Delay(500);
            _isPressed = false;
        }
        
        class ButtonClickListener : Java.Lang.Object, IOnClickListener
        {
            public static readonly Lazy<ButtonClickListener> Instance =
                new Lazy<ButtonClickListener>(() =>
                    new ButtonClickListener());
                    
            public void OnClick(Android.Views.View view)
            {
                var renderer = view.Tag as ButtonRenderer;
                (renderer as BlockingButtonRenderer)?.SendBlockingClicked();
            }
        }
#elif __IOS__
        protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Button> e)
        {
            base.OnElementChanged(e);
            
            var control = Control as UIButton;
            if (control != null)
            {
                control.TouchUpInside += async (sender, ev) =>
                {
                    if (_isPressed) return;
                    _isPressed = true;
                    var oldInteractionFlag = control.UserInteractionEnabled;
                    control.UserInteractionEnabled = false;
                    await Task.Delay(500);
                    control.UserInteractionEnabled = oldInteractionFlag;
                    _isPressed = false;
                };
            }
        }
#endif
    }
}

Xamarin.Android Rounded Corners Masked Layout

If you want to create a FrameLayout that has real rounded corners and clips its children beyond its corners, you can use the following class, which is a port of an answer on StackOverflow. You should replace Constants.PackageName with your Android package name, e.g. com.mycompany.myapp.

using Android.Content;
using Android.Graphics;
using Android.Runtime;
using Android.Util;
using Android.Widget;

namespace UIWidgets.Android
{
[Register(Constants.PackageName + ".UIWidgets.Android.RoundedCornerLayout")]
public class RoundedCornerLayout : FrameLayout
{
const float CornerRadius = 20.0f;

Bitmap maskBitmap;
Paint paint, maskPaint;
float cornerRadius;

public RoundedCornerLayout(Context context) : base(context)
{
Init(context);
}

public RoundedCornerLayout(Context context, IAttributeSet attrs) : base(context, attrs)
{
Init(context, attrs);
}

public RoundedCornerLayout(Context context, IAttributeSet attrs, int defStyle) : base(context, attrs, defStyle)
{
Init(context, attrs, defStyle);
}

void Init(Context context, IAttributeSet attrs = null, int defStyle = 0)
{
DisplayMetrics metrics = Context.Resources.DisplayMetrics;
cornerRadius = TypedValue.ApplyDimension(ComplexUnitType.Dip, CornerRadius, metrics);
paint = new Paint(PaintFlags.AntiAlias);
maskPaint = new Paint(PaintFlags.AntiAlias | PaintFlags.FilterBitmap);
maskPaint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear));
SetWillNotDraw(false);
}

public override void Draw(Canvas canvas)
{
Bitmap offscreenBitmap = Bitmap.CreateBitmap(canvas.Width, canvas.Height, Bitmap.Config.Argb8888);
Canvas offscreenCanvas = new Canvas(offscreenBitmap);

base.Draw(offscreenCanvas);

maskBitmap = maskBitmap ?? CreateMask(canvas.Width, canvas.Height);
offscreenCanvas.DrawBitmap(maskBitmap, 0.0f, 0.0f, maskPaint);
canvas.DrawBitmap(offscreenBitmap, 0.0f, 0.0f, paint);
}

Bitmap CreateMask(int width, int height)
{
Bitmap mask = Bitmap.CreateBitmap(width, height, Bitmap.Config.Alpha8);
Canvas canvas = new Canvas(mask);

Paint paint = new Paint(PaintFlags.AntiAlias);
paint.Color = Color.White;

canvas.DrawRect(0, 0, width, height, paint);

paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear));
canvas.DrawRoundRect(new RectF(0, 0, width, height), cornerRadius, cornerRadius, paint);

return mask;
}
}
}

And to use it in your AXML:

 <UIWidgets.Android.RoundedCornerLayout
 android:layout_width="150dp"
 android:layout_height="150dp">

 <RelativeLayout
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:background="#FFDDAA"
 />
 </UIWidgets.Android.RoundedCornerLayout>