Open Visual Studio and create new Cross Platform App with C# code as shown below,

Xamarin

Xamarin

Now you can see in solution explorer several projects for different platforms are created. One is Portal project that is common for all the platforms, another one is Android, this is specifically for Android OS, another one is iOS specifically for iOS OS.

Here I am going to explain how to create a drawing app for Android, so let's start.

To draw and create files in specific platforms, we have to do code in specific platforms and to access this from Portable project we need to use dependency service.

So here we will write those functions in Android project.

Then lets add some default color, styles to this drawing pad as shown below inside constructor,

  1. public ClassDrawing(Context context): base(context)
  2. {
  3. LineColor = Color.Black;
  4. PenWidth = 5.0f;
  5. DrawPath = new Path();
  6. DrawPaint = new Paint
  7. {
  8. Color = LineColor,
  9. AntiAlias = true,
  10. StrokeWidth = PenWidth
  11. };
  12. DrawPaint.SetStyle(Paint.Style.Stroke);
  13. DrawPaint.StrokeJoin = Paint.Join.Round;
  14. DrawPaint.StrokeCap = Paint.Cap.Round;
  15. CanvasPaint = new Paint
  16. {
  17. Dither = true
  18. };
  19. }

Next override some functions as shown below,

  1. protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
  2. {
  3. base.OnSizeChanged(w, h, oldw, oldh);
  4. if (w > 0 && h > 0)
  5. {
  6. try
  7. {
  8. CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
  9. DrawCanvas = new Canvas(CanvasBitmap);
  10. this.w = w;
  11. this.h = h;
  12. }
  13. catch (Exception ex)
  14. {
  15. Console.WriteLine(ex.Message);
  16. }
  17. }
  18. }
  19. protected override void OnDraw(Canvas canvas)
  20. {
  21. base.OnDraw(canvas);
  22. DrawPaint.Color = LineColor;
  23. DrawPaint.StrokeWidth = PenWidth;
  24. canvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
  25. canvas.DrawPath(DrawPath, DrawPaint);
  26. }
  27. public override bool OnTouchEvent(MotionEvent e)
  28. {
  29. var touchX = e.GetX();
  30. var touchY = e.GetY();
  31. switch (e.Action)
  32. {
  33. case MotionEventActions.Down:
  34. DrawPath.MoveTo(touchX, touchY);
  35. break;
  36. case MotionEventActions.Move:
  37. DrawPath.LineTo(touchX, touchY);
  38. break;
  39. case MotionEventActions.Up:
  40. DrawCanvas.DrawPath(DrawPath, DrawPaint);
  41. DrawPath.Reset();
  42. break;
  43. default:
  44. return false;
  45. }
  46. Invalidate();
  47. return true;
  48. }

Then let’s add function for clearing the drawing pad as shown below,

  1. public void Clear()
  2. {
  3. try
  4. {
  5. DrawPath = new Path();
  6. CanvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
  7. DrawCanvas = new Canvas(CanvasBitmap);
  8. DrawCanvas.DrawColor(Color.White, PorterDuff.Mode.Multiply);
  9. CanvasBitmap.EraseColor(Color.Transparent);
  10. DrawPaint = new Paint
  11. {
  12. Color = LineColor,
  13. AntiAlias = true,
  14. StrokeWidth = PenWidth
  15. };
  16. DrawPaint.SetStyle(Paint.Style.Stroke);
  17. DrawPaint.StrokeJoin = Paint.Join.Round;
  18. DrawPaint.StrokeCap = Paint.Cap.Round;
  19. }
  20. catch (Exception e)
  21. {
  22. Console.WriteLine(e.Message);
  23. }
  24. Invalidate();
  25. }

Next we have to return this drawn image so that we can get the file so we have to create a function as below to return a drawn image,

  1. public Bitmap GetImageFromView()
  2. {
  3. Bitmap tempBitmap = null;
  4. try
  5. {
  6. tempBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
  7. DrawCanvas = new Canvas(tempBitmap);
  8. if (_image != null)
  9. {
  10. DrawPaint.SetStyle(Paint.Style.Fill);
  11. DrawPaint.Color = Color.White; //Color.ParseColor("#ffd2d2");
  12. DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
  13. float scaleX = (float)_image.Width / w;
  14. float scaleY = (float)_image.Height / h;
  15. Rect outRect = new Rect();
  16. int outWidth, outHeight;
  17. if (scaleX > scaleY)
  18. {
  19. outWidth = w;
  20. outHeight = (int)(_image.Height / scaleX);
  21. }
  22. else
  23. {
  24. outWidth = (int)(_image.Width / scaleY);
  25. outHeight = h;
  26. }
  27. outRect.Left = w / 2 - outWidth / 2;
  28. outRect.Top = h / 2 - outHeight / 2;
  29. outRect.Right = w / 2 + outWidth / 2;
  30. outRect.Bottom = h / 2 + outHeight / 2;
  31. DrawCanvas.DrawBitmap(_image, new Rect(0, 0, _image.Width, _image.Height), outRect, DrawPaint);
  32. }
  33. else
  34. {
  35. DrawPaint.SetStyle(Paint.Style.Fill);
  36. DrawPaint.Color = Color.White;//Color.ParseColor("#ffd2d2");
  37. DrawCanvas.DrawRect(new Rect(0, 0, w, h), DrawPaint);
  38. }
  39. DrawPaint.Color =LineColor;
  40. DrawCanvas.DrawBitmap(CanvasBitmap, 0, 0, CanvasPaint);
  41. DrawCanvas.DrawPath(DrawPath, DrawPaint);
  42. }
  43. catch (Exception ex)
  44. {
  45. Console.WriteLine(ex.Message);
  46. }
  47. return tempBitmap;
  48. }

Next create a function to load file which is created, file path we will get it from variable ImageFilePath. Function as shown below,

  1. public void LoadImageFromFile()
  2. {
  3. if (ImageFilePath != null && ImageFilePath != "")
  4. {
  5. _image = BitmapFactory.DecodeFile(ImageFilePath);
  6. }
  7. }

Note

Here are the 3 variable I have mentioned as public to access from outside that are LineColor, ImageFilePath, PenWidth

Let's create another class to render this drawing into portable project, I have given name for this class as ClassImageRenderer, before writing functionality into this class let's create a class in portable project and add some properties to get and set from ClassImageRenderer from android project. I have given a name as ImageEditor and inherit this class from Image class, the class as shown below,

  1. public class ImageEditor : Image
  2. {
  3. public static readonly BindableProperty LineColorProperty =
  4. BindableProperty.Create((ImageEditor w) => w.LineColor, Color.Default);
  5. public static readonly BindableProperty LineWidthProperty =
  6. BindableProperty.Create((ImageEditor w) => w.LineWidth, 1);
  7. public static readonly BindableProperty ImageProperty =
  8. BindableProperty.Create((ImageEditor w) => w.ImagePath, "");
  9. public static readonly BindableProperty ClearImagePathProperty =
  10. BindableProperty.Create((ImageEditor w) => w.ClearPath, false);
  11. public static readonly BindableProperty SavedImagePathProperty =
  12. BindableProperty.Create((ImageEditor w) => w.SavedImagePath, "");
  13. public static readonly BindableProperty SavedImage64byteProperty =
  14. BindableProperty.Create((ImageEditor w) => w.SavedImage64byte, "");
  15. public Color LineColor
  16. {
  17. get
  18. {
  19. return (Color)GetValue(LineColorProperty);
  20. }
  21. set
  22. {
  23. SetValue(LineColorProperty, value);
  24. }
  25. }
  26. public int LineWidth
  27. {
  28. get
  29. {
  30. return (int)GetValue(LineWidthProperty);
  31. }
  32. set
  33. {
  34. SetValue(LineWidthProperty, value);
  35. }
  36. }
  37. public string ImagePath
  38. {
  39. get
  40. {
  41. return (string)GetValue(ImageProperty);
  42. }
  43. set
  44. {
  45. SetValue(ImageProperty, value);
  46. }
  47. }
  48. public bool ClearPath
  49. {
  50. get
  51. {
  52. return (bool)GetValue(ClearImagePathProperty);
  53. }
  54. set
  55. {
  56. SetValue(ClearImagePathProperty, value);
  57. }
  58. }
  59. public string SavedImagePath
  60. {
  61. get
  62. {
  63. return (string)GetValue(SavedImagePathProperty);
  64. }
  65. set
  66. {
  67. SetValue(SavedImagePathProperty, value);
  68. }
  69. }
  70. public string SavedImage64byte
  71. {
  72. get
  73. {
  74. return (string)GetValue(SavedImage64byteProperty);
  75. }
  76. set
  77. {
  78. SetValue(SavedImage64byteProperty, value);
  79. }
  80. }
  81. }

Next let’s add functionality to the class ClassImageRenderer which I created on Android project, first export those properties which we have added in previous class ImageEditor for that write the below code before namespace DrawingPad.Droid

[assembly: ExportRenderer(typeof(ImageEditor), typeof(ClassImageRenderer))]

Then inherit ClassImageRenderer from ViewRenderer and add name spaces as shown below,

  1. using Java.IO;
  2. using DrawingPad;
  3. using DrawingPad.Droid;
  4. using Xamarin.Forms;
  5. using System.ComponentModel;
  6. [assembly: ExportRenderer(typeof(ImageEditor), typeof(ClassImageRenderer))]
  7. namespace DrawingPad.Droid
  8. {
  9. public class ClassImageRenderer : ViewRenderer<ImageEditor, ClassDrawing>
  10. {
  11. }
  12. }

Then write the functions inside the class as shown below,

  1. protected override void OnElementChanged(ElementChangedEventArgs<ImageEditor> e)
  2. {
  3. base.OnElementChanged(e);
  4. if (e.OldElement == null)
  5. {
  6. SetNativeControl(new ClassDrawing(Context));
  7. }
  8. }
  9. public static byte[] BitmapToBytes(Android.Graphics.Bitmap bitmap)
  10. {
  11. byte[] data = new byte[0];
  12. using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
  13. {
  14. bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Jpeg, 90, stream);
  15. stream.Close();
  16. data = stream.ToArray();
  17. }
  18. return data;
  19. }
  20. rotected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
  21. {
  22. base.OnElementPropertyChanged(sender, e);
  23. if (e.PropertyName == ImageEditor.ClearImagePathProperty.PropertyName)
  24. {
  25. Control.Clear();
  26. }
  27. else if (e.PropertyName == ImageEditor.SavedImagePathProperty.PropertyName)
  28. {
  29. Bitmap curDrawingImage = Control.GetImageFromView();
  30. Byte[] imgBytes = BitmapToBytes(curDrawingImage);
  31. Element.SavedImage64byte = Convert.ToBase64String(imgBytes);
  32. Java.IO.File f = new Java.IO.File(Element.SavedImagePath);
  33. f.CreateNewFile();
  34. FileOutputStream fo = new FileOutputStream(f);
  35. fo.Write(imgBytes);
  36. fo.Close();
  37. }
  38. else
  39. {
  40. UpdateControl(true);
  41. }
  42. }
  43. private void UpdateControl(bool bDisplayFlag)
  44. {
  45. Control.LineColor = Element.LineColor.ToAndroid();
  46. Control.PenWidth = Element.LineWidth * 3;
  47. Control.ImageFilePath = Element.ImagePath;
  48. if (bDisplayFlag)
  49. {
  50. Control.LoadImageFromFile();
  51. Control.Invalidate();
  52. }
  53. }
Lets create an interface and dependency service to create a image save path, for this in portable class create one interface as shown below,
  1. public interface ISaveImage
  2. {
  3. string SaveImage();
  4. }

And create one class in Android project to create this class, and inside class let's assign image path to Android device as shown below,

  1. using System;
  2. using Xamarin.Forms;
  3. using DrawingPad.Droid;
  4. [assembly: Dependency(typeof(ClassSaveImage))]
  5. namespace DrawingPad.Droid
  6. {
  7. public class ClassSaveImage : ISaveImage
  8. {
  9. public string SaveImage()
  10. {
  11. string savedFileName = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "/temp_" + DateTime.Now.ToString("yyyy_mm_dd_hh_mm_ss") + ".png";
  12. return savedFileName;
  13. }
  14. }
  15. }
That’s it, now control is ready to use lets create a content page the content page I have added xaml code as shown below,

In xaml namespace add below line to add control which we have created to access ImageEditor class

xmlns:local="clr-namespace:DrawingPad;assembly:DrawingPad"

Then add below line inside stacklayout

  1. <local:ImageEditor x:Name="imgeditor" Grid.Row="0" HeightRequest="400" BackgroundColor="White" LineColor="Black"/>

And add color buttons and two buttons for clear and save, the full code is as shown below,

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  4. xmlns:local="clr-namespace:DrawingPad;assembly:DrawingPad"
  5. x:Class="DrawingPad.MainPage">
  6. <StackLayout x:Name="MainStack" VerticalOptions="Center" HorizontalOptions="Center" BackgroundColor="White">
  7. <ScrollView Orientation="Horizontal" >
  8. <StackLayout x:Name="redStripStack" Padding="0,0,0,0" HeightRequest="40" >
  9. <StackLayout Orientation="Horizontal" VerticalOptions="Center" HorizontalOptions="Center" Padding="5,5,5,5">
  10. <Button x:Name="btnGreen" BackgroundColor="Green" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnGreen_Clicked"/>
  11. <Button x:Name="btnGray" BackgroundColor="Gray" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnGray_Clicked" />
  12. <Button x:Name="btnDarkGray" BackgroundColor="DarkGray" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkGray_Clicked"/>
  13. <Button x:Name="btnRed" BackgroundColor="Red" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnRed_Clicked" />
  14. <Button x:Name="btnDarkRed" BackgroundColor="DarkRed" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkRed_Clicked"/>
  15. <Button x:Name="btnBlack" BackgroundColor="Black" HeightRequest="30" BorderColor="White" BorderWidth="0" TextColor="White" Clicked="btnBlack_Clicked" />
  16. <Button x:Name="btnBlue" BackgroundColor="Blue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnBlue_Clicked"/>
  17. <Button x:Name="btnDarkBlue" BackgroundColor="DarkBlue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkBlue_Clicked"/>
  18. <Button x:Name="btnLightBlue" BackgroundColor="LightBlue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnLightBlue_Clicked" />
  19. <Button x:Name="btnSkyBlue" BackgroundColor="SkyBlue" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnSkyBlue_Clicked"/>
  20. <Button x:Name="btnYellow" BackgroundColor="Yellow" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnYellow_Clicked" />
  21. <Button x:Name="btnOrange" BackgroundColor="Orange" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnOrange_Clicked"/>
  22. <Button x:Name="btnDarkOrange" BackgroundColor="DarkOrange" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnDarkOrange_Clicked"/>
  23. <Button x:Name="btnPurple" BackgroundColor="Purple" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnPurple_Clicked"/>
  24. <Button x:Name="btnPink" BackgroundColor="Pink" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnPink_Clicked"/>
  25. <Button x:Name="btnCyan" BackgroundColor="Cyan" HeightRequest="30" BorderColor="White" BorderWidth="3" TextColor="White" Clicked="btnCyan_Clicked"/>
  26. </StackLayout>
  27. </StackLayout>
  28. </ScrollView>
  29. <StackLayout>
  30. <Grid HorizontalOptions="Center" VerticalOptions="Center" Padding="5,5,5,5">
  31. <Grid.RowDefinitions>
  32. <RowDefinition Height="*"/>
  33. <RowDefinition Height="40"/>
  34. </Grid.RowDefinitions>
  35. <local:ImageEditor x:Name="imgeditor" Grid.Row="0" HeightRequest="400" BackgroundColor="White" LineColor="Black"/>
  36. <Grid Grid.Row="1" VerticalOptions="EndAndExpand">
  37. <Grid.ColumnDefinitions>
  38. <ColumnDefinition Width="60*"/>
  39. <ColumnDefinition Width="40*"/>
  40. </Grid.ColumnDefinitions>
  41. <Button x:Name="btnSave" Text="Save" Grid.Column="0" TextColor="White" BackgroundColor="Green" Clicked="btnSaveImage_Click"/>
  42. <Button x:Name="btnClear" Text="Clear" Grid.Column="1" TextColor="White" BackgroundColor="Gray" Clicked="btnClear_Click"/>
  43. </Grid>
  44. </Grid>
  45. </StackLayout>
  46. </StackLayout>
  47. </ContentPage>

Now let’s write functionality for these buttons, first write the code for save button as shown for this, first get the file path from Android device using dependency service and assign this path to this image editor property SavedImagePath and get 64 bye string from the control as shown below.

  1. private void btnSaveImage_Click(object sender, EventArgs e)
  2. {
  3. var imgPath = DependencyService.Get<ISaveImage>().SaveImage();
  4. imgeditor.SavedImagePath = imgPath;
  5. string _64byte = imgeditor.SavedImage64byte;
  6. DisplayAlert("64byteData", _64byte, "Ok");
  7. }

Next for clear button as shown below,

  1. private void btnClear_Click(object sender, EventArgs e)
  2. {
  3. imgeditor.ClearPath = !imgeditor.ClearPath;

For color buttons as shown below,

  1. private void setBorder()
  2. {
  3. btnGreen.BorderWidth = 3;
  4. btnRed.BorderWidth = 3;
  5. btnGray.BorderWidth = 3;
  6. btnDarkGray.BorderWidth = 3;
  7. btnDarkRed.BorderWidth = 3;
  8. btnBlack.BorderWidth = 3;
  9. btnBlue.BorderWidth = 3;
  10. btnDarkBlue.BorderWidth = 3;
  11. btnLightBlue.BorderWidth = 3;
  12. btnSkyBlue.BorderWidth = 3;
  13. btnYellow.BorderWidth = 3;
  14. btnOrange.BorderWidth = 3;
  15. btnDarkOrange.BorderWidth = 3;
  16. btnPurple.BorderWidth = 3;
  17. btnPink.BorderWidth = 3;
  18. btnCyan.BorderWidth = 3;
  19. }
  20. private void btnGreen_Clicked(object sender, EventArgs args)
  21. {
  22. setBorder();
  23. btnGreen.BorderWidth = 0;
  24. imgeditor.LineColor = Color.Green;
  25. }
  26. private void btnRed_Clicked(object sender, EventArgs args)
  27. {
  28. setBorder();
  29. btnRed.BorderWidth = 0;
  30. imgeditor.LineColor = Color.Red;
  31. }
  32. private void btnGray_Clicked(object sender, EventArgs args)
  33. {
  34. setBorder();
  35. btnGray.BorderWidth = 0;
  36. imgeditor.LineColor = Color.Gray;
  37. }
  38. private void btnDarkGray_Clicked(object sender, EventArgs args)
  39. {
  40. setBorder();
  41. btnDarkGray.BorderWidth = 0;
  42. imgeditor.LineColor = Color.DarkGray;
  43. }
  44. private void btnDarkRed_Clicked(object sender, EventArgs args)
  45. {
  46. setBorder();
  47. btnDarkRed.BorderWidth = 0;
  48. imgeditor.LineColor = Color.DarkRed;
  49. }
  50. private void btnBlack_Clicked(object sender, EventArgs args)
  51. {
  52. setBorder();
  53. btnBlack.BorderWidth = 0;
  54. imgeditor.LineColor = Color.Black;
  55. }
  56. private void btnBlue_Clicked(object sender, EventArgs args)
  57. {
  58. setBorder();
  59. btnBlue.BorderWidth = 0;
  60. imgeditor.LineColor = Color.Blue;
  61. }
  62. private void btnDarkBlue_Clicked(object sender, EventArgs args)
  63. {
  64. setBorder();
  65. btnDarkBlue.BorderWidth = 0;
  66. imgeditor.LineColor = Color.DarkBlue;
  67. }
  68. private void btnLightBlue_Clicked(object sender, EventArgs args)
  69. {
  70. setBorder();
  71. btnLightBlue.BorderWidth = 0;
  72. imgeditor.LineColor = Color.LightBlue;
  73. }
  74. private void btnSkyBlue_Clicked(object sender, EventArgs args)
  75. {
  76. setBorder();
  77. btnSkyBlue.BorderWidth = 0;
  78. imgeditor.LineColor = Color.SkyBlue;
  79. }
  80. private void btnYellow_Clicked(object sender, EventArgs args)
  81. {
  82. setBorder();
  83. btnYellow.BorderWidth = 0;
  84. imgeditor.LineColor = Color.Yellow;
  85. }
  86. private void btnOrange_Clicked(object sender, EventArgs args)
  87. {
  88. setBorder();
  89. btnOrange.BorderWidth = 0;
  90. imgeditor.LineColor = Color.Orange;
  91. }
  92. private void btnDarkOrange_Clicked(object sender, EventArgs args)
  93. {
  94. setBorder();
  95. btnDarkOrange.BorderWidth = 0;
  96. imgeditor.LineColor = Color.DarkOrange;
  97. }
  98. private void btnPurple_Clicked(object sender, EventArgs args)
  99. {
  100. setBorder();
  101. btnPurple.BorderWidth = 0;
  102. imgeditor.LineColor = Color.Purple;
  103. }

And last override the sizeallocated function for this content page as shown below,

  1. protected override void OnSizeAllocated(double width, double height)
  2. {
  3. base.OnSizeAllocated(width, height);
  4. if (this.Width > 0)
  5. {
  6. MainStack.WidthRequest = (this.Width * 90) / 100;
  7. imgeditor.WidthRequest = (this.Width * 90) / 100;
  8. // redStripStack.WidthRequest = this.Width;
  9. }
  10. }
Now Drawing pad ready, just compile and see the output as shown below,

Xamarin