diff --git a/.github/Images/death-socket-logo.png b/.github/Images/death-socket-logo.png new file mode 100644 index 0000000..85c6c6b Binary files /dev/null and b/.github/Images/death-socket-logo.png differ diff --git a/DeathSocket/ColourServices.fs b/DeathSocket/ColourServices.fs index fb7d29b..4fba086 100644 --- a/DeathSocket/ColourServices.fs +++ b/DeathSocket/ColourServices.fs @@ -2,6 +2,8 @@ open System.Drawing open DeathSocket.Domain + open System + open SkiaSharp let makeBrushFromRGBASpec (spec: RGBASpec) = let a = int spec.alpha @@ -13,5 +15,4 @@ let makeBrushFromRGBA r g b a = let colour = Color.FromArgb (a, r, g, b) - new SolidBrush (colour) - + new SolidBrush (colour) \ No newline at end of file diff --git a/DeathSocket/DeathSocket.fsproj b/DeathSocket/DeathSocket.fsproj index 80d6bb6..69d8b67 100644 --- a/DeathSocket/DeathSocket.fsproj +++ b/DeathSocket/DeathSocket.fsproj @@ -4,7 +4,7 @@ netstandard2.0 true true - 0.4-alpha + 0.5-alpha Craig Oates Death Socket A .Net Standard library which you can plug into to project and draw gridded overlays onto you images. Please note, Death Socket uses System.Drawing brushes and not System.Media brushes. For futher information, visit the projects repository on GitHub. To get a sense of what Death Socket can do, there is a console program you can download seperately (on GitHub). @@ -15,8 +15,9 @@ https://github.com/CraigOates/Death-Socket/tree/master Git deathsocket craig oates image overlay f# - This release has renamed the ImageSpec to BrushSpec and added a new RGBASpec type. The RGBASpec allows the user to pass in individual RGBA values instead of creating a SolidBrush before adding it to the BrushSpec. -New functions have been added so you can now create a SolidBrush using individual RGBA values or an RGBASpec -- you do not need to do it yourself. Another "apply grid" function has been added , as well, which uses the new RGBASpec. + With this release comes the ability to draw grids with SkiaSharp, alongside System.Drawing. + 0.5.0.0 + https://github.com/CraigOates/Death-Socket/blob/master/.github/Images/death-socket-logo.png @@ -29,7 +30,12 @@ New functions have been added so you can now create a SolidBrush using individua - + + + + + + diff --git a/DeathSocket/Domain.fs b/DeathSocket/Domain.fs index ae4a174..0fd06ba 100644 --- a/DeathSocket/Domain.fs +++ b/DeathSocket/Domain.fs @@ -5,14 +5,19 @@ module Domain = open System.Drawing + open SkiaSharp - /// The specification which uses System.Drawing brush to draw a grid. + /// The specification used to note the orignial file's location and the + /// changes the user would like to make to it. (including the save + /// location of the modified version). Use this spec if you are using + /// System.Drawing. type BrushSpec = { /// The original path of the image which the grid is being added to. originalPath: string /// The location of the new gridded image. savePath: string - /// The (System.Drawing) brush used to draw the grid. This determines the colour. + /// The (System.Drawing) brush used to draw the grid. + /// This determines the colour of the grid. colour: Brush /// The thickness of the line on the grid. penWidth: float32 @@ -21,8 +26,11 @@ ///The number of columns the grid will have. columns: int } - /// The specification which uses includes individual RGBA values to - /// draw a grid. + /// The specification used to note the orignial file's location and the + /// changes the user would like to make to it (including the save + /// location of the modified version). This specification includes + /// individual RGBA values to describe the grid's colour. Use this spec. + /// if you are using System.Drawing. type RGBASpec = { /// The original path of the image which the grid is being added to. originalPath: string @@ -42,4 +50,48 @@ /// The number of rows the grid will have. rows: int ///The number of columns the grid will have. + columns: int } + + /// The specification used to note the orignial file's location and the + /// changes the user would like to make to it (including the save + /// location of the modified version). Use this spec. if you are using + /// the Skia-Sharp library. + type SkiaSpec = + { /// The original path of the image which the grid is being added to. + originalPath: string + /// The location of the new gridded image. + savePath: string + /// The (SkiaSharp) colour used to draw the grid. + skColour: SKColor + /// The thickness of the line on the grid. + penWidth: float32 + /// The number of rows the grid will have. + rows: int + ///The number of columns the grid will have. + columns: int } + + /// The specification used to note the orignial file's location and the + /// changes the user would like to make to it (including the save + /// location of the modified version). This specification includes + /// individual RGB values to describe the grid's colour. Use this spec. + /// if you are using the Skia-Sharp library. + type SkiaRGBSpec = + { /// The original path of the image which the grid is being added to. + originalPath: string + /// The location of the new gridded image. + savePath: string + /// The amount of red (RGB) the grid's line will have. + /// (0 = no red, 255 = red turned up to eleven). + red: float32 + /// The amount of green (RGB) the grid's line will have. + /// (0 = no green, 255 = green turned up to eleven). + green: float32 + /// The amount of blue (RGB) the grid's line will have. + /// (0 = no blue, 255 = blue turned up to eleven). + blue: float32 + /// The thickness of the line on the grid. + penWidth: float32 + /// The number of rows the grid will have. + rows: int + ///The number of columns the grid will have. columns: int } \ No newline at end of file diff --git a/DeathSocket/GridPainter.fs b/DeathSocket/GridPainter.fs index 60dfa1a..e12bd8b 100644 --- a/DeathSocket/GridPainter.fs +++ b/DeathSocket/GridPainter.fs @@ -4,18 +4,51 @@ namespace DeathSocket open ColourServices /// Provides functions which help draw gridded overlays onto images. - /// Grid Painter, and all of Death Socket, uses the System.Drawing brushes/colours. - /// If you are using System.Media brushes and colour, you will need to convert them. + /// Death Socket, uses System.Drawing's brushes and colours. + /// If you are using System.Media brushes and colour, you will need to + /// convert them. Death Socket, also, uses SkiaSharp for cross platform + /// functionality. If you are using Death Socket on Windows Desktop, use + /// functions which do not include "Skia" in their name. If you are using + /// Death Socket in a Xamarin/UWP project you should use the SkiaSharp + /// functions. module GridPainter = open Validation open ImageServices + // System.Drawing Functions + // ======================================================================== + + /// + /// Determines the (Pen) points needed to draw the appropriate number + /// of horizontal lines (I.E. rows). Each item in the array includes a + /// start and end co-ordinate (point) for each line. + /// Use this function when targeting .Net/Mono). + /// + /// The width of the image. + /// The height of the image. + /// The number of rows the grid should have. + /// You will probably only need these when dealing with GUI's. + let determineHorizontalLines (width: int) (height: int) (rows: int) = + createHorizontalLines width height rows + + /// + /// Determines the (Pen) points needed to draw the appropriate number + /// of vertical lines (I.E. columns). Each item in the array includes a + /// start and end co-ordinate (point) for each line. + /// Use this function when targeting Windows Desktop (or Mono). + /// + /// The width of the image. + /// The height of the image. + /// The number of columns the grid should have. + /// You will probably only need these when dealing with GUI's. + let determineVerticalLines (width: int) (height: int) (columns: int) = + createVerticalLines width height columns + /// /// Uses the information included in spec to create a gridded image. - /// It then asynchronously saves it. - /// Please stick to .bmp, .jpg or .png files. - /// The other (image) file types have not been tested. + /// It then asynchronously saves it. Uses .jpg or .png formats only. + /// Use this function when targeting .Net/Mono). /// /// /// The specification used to generate the new gridded image @@ -33,7 +66,7 @@ namespace DeathSocket async { try validateFilePath spec.originalPath |> ignore - validatFileType spec.savePath |> ignore + validateSaveFileType spec.savePath |> ignore drawBrushSpecGrid spec |> ignore with | :? FileNotFoundException as ex -> @@ -42,8 +75,8 @@ namespace DeathSocket /// /// Uses the information included in spec to create a gridded image. It - /// then asynchronously saves it. Please stick to .bmp, .jpg or .png - /// files. The other (image) file types have not been tested. + /// then asynchronously saves it. Uses .jpg or .png formats only. + /// Use this function when targeting .Net/Mono). /// /// /// The specification used to generate the gridded image. @@ -61,7 +94,7 @@ namespace DeathSocket async { try validateFilePath spec.originalPath |> ignore - validatFileType spec.savePath |> ignore + validateSaveFileType spec.savePath |> ignore drawRGBAGrid spec with | :? FileNotFoundException as ex -> @@ -70,6 +103,7 @@ namespace DeathSocket /// /// Creates a Sytsem.Drawing SolidBrush from the individual RGBA values. + /// Use this function when targeting .Net/Mono). /// /// The red value. /// The green value. @@ -79,10 +113,12 @@ namespace DeathSocket /// Death Socket uses System.Drawing and not System.Media for colours /// and brushes. /// - let makeSolidBrushFromRGBA r g b a = makeBrushFromRGBA r g b a + let makeSolidBrushFromRGBA (r: int) (g: int) (b:int) (a: int) = + makeBrushFromRGBA r g b a /// /// Creates a System.Drawing SolidBrush from a RGBASpec. + /// Use this function when targeting .Net/Mono). /// /// ///The specification which the brush is made from. @@ -91,26 +127,90 @@ namespace DeathSocket /// Death Socket uses System.Drawing and not System.Media for colours /// and brushes. /// - let makeSolidBrushFromRGBASpec spec = makeBrushFromRGBASpec spec + let makeSolidBrushFromRGBASpec (spec: RGBASpec) = + makeBrushFromRGBASpec spec + + // SkiaSharp Functions + // ======================================================================== /// - /// Determines the (Pen) points needed to draw the appropriate number of horizontal lines (I.E. rows). - /// Each item in the array includes a start and end co-ordinate (point) for each line. + /// Determines the (SKPoints) points needed to draw the appropriate + /// number of horizontal lines (I.E. rows). Each item in the array + /// included a start and end co-ordinate (SKPoint) for each line. + /// Use this function when targeting Xamarin/UWP/.Net Core (SkiaSharp). /// /// The width of the image. /// The height of the image. /// The number of rows the grid should have. - /// You will probably only need these when dealing with GUI's. - let determineHorizontalLines width height rows = - createHorizontalLines width height rows + /// This function is part of the SkiaSharp functions provided + /// by Death Socket. + let determineSKHorizontalLines (width: int) (height: int) (rows: int) = + createSKHorizontalLines width height rows /// - /// Determines the (Pen) points needed to draw the appropriate number of vertical lines (I.E. columns). - /// Each item in the array includes a start and end co-ordinate (point) for each line. + /// Determines the (SKPoints) points needed to draw the appropriate + /// number of vertical lines (I.E. columns). Each item in the array + /// included a start and end co-ordinate (SKPoint) for each line. + /// Use this function when targeting Xamarin/UWP/.Net Core (SkiaSharp). /// /// The width of the image. /// The height of the image. - /// The number of columns the grid should have. - /// You will probably only need these when dealing with GUI's. - let determineVerticalLines width height columns = - createVerticalLines width height columns \ No newline at end of file + /// The number of columns the grid should have. + /// + /// This function is part of the SkiaSharp functions provided + /// by Death Socket. + let determineSKVerticalLines (width: int) (height: int) (columns: int) = + createSKVerticalLines width height columns + + + /// + /// Uses the information included in spec to create a gridded image. + /// It then asynchronously saves it. Uses .jpg or .png formats only. + /// Use this function when targeting Xamarin/UWP/.Net Core. + /// + /// + /// Used to note the orignial file's location and the + /// changes the user would like to make to it (including the save + /// location of the modified version). + /// + /// + /// Make sure the image, which is having the overlay added to it, + /// is not in use or needed by another program/process. + /// This is because it is locked whilst in this function. + /// + let applySkiaGridAsync (spec: SkiaSpec) = + async { + try + validateFilePath spec.originalPath |> ignore + validateSaveFileType spec.savePath |> ignore + drawSkiaGrid spec + with + | :? FileNotFoundException as ex -> + printfn "File could not be found at %s" ex.Message + } + + /// + /// Uses the information included in spec to create a gridded image. + /// It then asynchronously saves it. Uses .jpg or .png formats only. + /// Use this function when targeting Xamarin/UWP/.Net Core. + /// + /// + /// Used to note the orignial file's location and the + /// changes the user would like to make to it (including the save + /// location of the modified version). + /// + /// + /// Make sure the image, which is having the overlay added to it, + /// is not in use or needed by another program/process. + /// This is because it is locked whilst in this function. + /// + let applySkiaRGBGridAsync (spec: SkiaRGBSpec) = + async { + try + validateFilePath spec.originalPath |> ignore + validateSaveFileType spec.savePath |> ignore + drawSkiaRGBGrid spec + with + | :? FileNotFoundException as ex -> + printfn "File could not be found at %s" ex.Message + } \ No newline at end of file diff --git a/DeathSocket/ImageServices.fs b/DeathSocket/ImageServices.fs index 8babd60..c68b950 100644 --- a/DeathSocket/ImageServices.fs +++ b/DeathSocket/ImageServices.fs @@ -1,9 +1,106 @@ module internal ImageServices + open System.IO open System.Drawing open System.Drawing.Imaging open DeathSocket open ColourServices + open SkiaSharp + + // SkiaSharp Functions + // ======================================================================== + + let createSKHorizontalLines width height rows = + let interval = float32 (height / rows) + [|for point in 1 .. (rows - 1) -> + [| SKPoint ((float32 0), (interval * (float32 point))) + SKPoint ((float32 width), (interval * (float32 point)))|]|] + + let createSKVerticalLines width height columns = + let interval = float32 (width / columns) + [| for point in 1 .. (columns - 1) -> + [| SKPoint ((interval * (float32 point)), (float32 0)) + SKPoint ((interval * (float32 point)), (float32 height))|]|] + + let drawSkiaGrid (spec: SkiaSpec) = + use fileStream = File.Open (spec.originalPath, FileMode.Open) + use skStream = new SKManagedStream (fileStream) + use bitmap = SKBitmap.Decode (skStream) + use shader = + SKShader.CreateBitmap (bitmap, SKShaderTileMode.Mirror, SKShaderTileMode.Mirror) + let imageInfo = new SKImageInfo (bitmap.Width, bitmap.Height) + use surface = SKSurface.Create (imageInfo) + use canvas = surface.Canvas + canvas.Clear (SKColors.White) + + use imageFill = new SKPaint () + imageFill.Style <- SKPaintStyle.Fill + imageFill.Shader <- shader + canvas.DrawPaint (imageFill) + + use stroke = new SKPaint () + stroke.Style <- SKPaintStyle.Stroke + stroke.StrokeWidth <- spec.penWidth + stroke.Color <- spec.skColour + let horizontalLines = + createSKHorizontalLines (bitmap.Width) (bitmap.Height) (spec.rows) + let verticalLines = + createSKVerticalLines (bitmap.Width) (bitmap.Height) (spec.columns) + for hLine in horizontalLines do + canvas.DrawLine (hLine.[0], hLine.[1], stroke) + for vLine in verticalLines do + canvas.DrawLine (vLine.[0], vLine.[1], stroke) + + use snapshot = surface.Snapshot () + use data = + match Path.GetExtension (spec.savePath) with + | ".jpg" | ".JPG" -> snapshot.Encode (SKEncodedImageFormat.Jpeg, 100) + | ".png"| ".PNG" -> snapshot.Encode (SKEncodedImageFormat.Png, 100) + | _ -> invalidArg "savePath" "The file type must be a .jpg or .png file." + use saveStream = File.OpenWrite (spec.savePath) + data.SaveTo (saveStream) + + let drawSkiaRGBGrid spec = + use fileStream = File.Open (spec.originalPath, FileMode.Open) + use skStream = new SKManagedStream (fileStream) + use bitmap = SKBitmap.Decode (skStream) + use shader = + SKShader.CreateBitmap (bitmap, SKShaderTileMode.Mirror, SKShaderTileMode.Mirror) + let imageInfo = new SKImageInfo (bitmap.Width, bitmap.Height) + use surface = SKSurface.Create (imageInfo) + use canvas = surface.Canvas + canvas.Clear (SKColors.White) + + use imageFill = new SKPaint () + imageFill.Style <- SKPaintStyle.Fill + imageFill.Shader <- shader + canvas.DrawPaint (imageFill) + + use stroke = new SKPaint () + stroke.Style <- SKPaintStyle.Stroke + stroke.StrokeWidth <- spec.penWidth + stroke.Color <- + new SKColor ((byte spec.red), (byte spec.green), (byte spec.blue)) + let horizontalLines = + createSKHorizontalLines (bitmap.Width) (bitmap.Height) (spec.rows) + let verticalLines = + createSKVerticalLines (bitmap.Width) (bitmap.Height) (spec.columns) + for hLine in horizontalLines do + canvas.DrawLine (hLine.[0], hLine.[1], stroke) + for vLine in verticalLines do + canvas.DrawLine (vLine.[0], vLine.[1], stroke) + + use snapshot = surface.Snapshot () + use data = + match Path.GetExtension (spec.savePath) with + | ".jpg" | ".JPG" -> snapshot.Encode (SKEncodedImageFormat.Jpeg, 100) + | ".png"| ".PNG" -> snapshot.Encode (SKEncodedImageFormat.Png, 100) + | _ -> invalidArg "savePath" "The file type must be a .jpg or .png file." + use saveStream = File.OpenWrite (spec.savePath) + data.SaveTo (saveStream) + + // System.Drawing Functions + // ======================================================================== let createHorizontalLines width height rows = let interval = height / rows @@ -17,10 +114,10 @@ [| Point ((interval * point), 0) Point ((interval * point), height)|]|] - (* Note on Use of Temp File in Functions which Add A Grid Overlay + (* Note on Use of Temp. File in Functions which Add A Grid Overlay =========================================================================== The temp. file is used in the functions below are there to convert images - with indexed pixels. Instead of listig them all, just assume any function + with indexed pixels. Instead of listing them all, just assume any function with a "*Spec" type as a parameter will use this "temp" file. *) let drawBrushSpecGrid (spec: BrushSpec) = @@ -40,15 +137,15 @@ let drawRGBAGrid (spec: RGBASpec) = use original = Bitmap.FromFile spec.originalPath - use temp = new Bitmap(original) - use clone = temp.Clone(new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb) + use temp = new Bitmap (original) + use clone = temp.Clone (new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb) use graphics = Graphics.FromImage(clone) use pen = new Pen ((makeBrushFromRGBASpec spec), width = spec.penWidth) - graphics.DrawImage(original,new Rectangle(0, 0, clone.Width, clone.Height)) + graphics.DrawImage (original,new Rectangle(0, 0, clone.Width, clone.Height)) let horizontalLines = createHorizontalLines (clone.Size.Width) (clone.Size.Height) (spec.rows) let verticalLines = createVerticalLines (clone.Size.Width) (clone.Size.Height) (spec.columns) for line in horizontalLines do graphics.DrawLines (pen, line) for line in verticalLines do graphics.DrawLines (pen, line) - clone.Save (spec.savePath) + clone.Save (spec.savePath) \ No newline at end of file diff --git a/DeathSocket/ScratchPad.fsx b/DeathSocket/ScratchPad.fsx index 13eaa89..4775c49 100644 --- a/DeathSocket/ScratchPad.fsx +++ b/DeathSocket/ScratchPad.fsx @@ -1,5 +1,9 @@ -#load "Domain.fs" +#r @"C:\Users\craig\.nuget\packages\skiasharp\1.60.3\lib\netstandard1.3\SkiaSharp.dll" +#r @"C:\Users\craig\.nuget\packages\skiasharp\1.60.3\lib\net45\SkiaSharp.dll" + +#load "Domain.fs" #load "Validation.fs" +#load "ColourServices.fs" #load "ImageServices.fs" #load "GridPainter.fs" @@ -30,7 +34,7 @@ let verticalLines = createVerticalLines 300 600 10 Death Socket assumes either JPEG or PNG files so use other files at your own risk. Cannot guarantee they will work. Also, either in this spec. can be changed to suit your needs. *) -let spec :ImageSpec = +let spec: BrushSpec = { originalPath = desktop + "/test.jpg" savePath = desktop + "/grid.png" colour = Brushes.Chartreuse @@ -39,4 +43,14 @@ let spec :ImageSpec = columns = 10 } // Run this when you have finished building the spec. -GridPainter.applyGridAsync spec |> Async.RunSynchronously +GridPainter.applyBrushSpecGridAsync spec |> Async.RunSynchronously + +//***************************************************************************** + +(* Skia Sharp Draft +=============================================================================== +The code below here will eventually be worked into the main codebase or thrown +away. It is here acting as a first draft for integrating the Skia Sharp NuGet +package into Death Socket. *) + +drawSkiaGrid() \ No newline at end of file diff --git a/DeathSocket/Validation.fs b/DeathSocket/Validation.fs index a192a9b..71128f3 100644 --- a/DeathSocket/Validation.fs +++ b/DeathSocket/Validation.fs @@ -7,14 +7,10 @@ | true -> () | false -> raise (new FileNotFoundException (path + " could not be found.")) - let validatFileType file = + let validateSaveFileType file = match Path.GetExtension file with - | ".bmp" -> () - | ".BMP" -> () | ".jpg" -> () | ".JPG" -> () | ".png" -> () | ".PNG" -> () - | ".tif" -> () - | ".TIF" -> () - | _ -> invalidArg "savePath" "The file type must be a .bmp, .jpg, .png or .tif file." \ No newline at end of file + | _ -> invalidArg "savePath" "The file type must be a .jpg or .png file." \ No newline at end of file diff --git a/DeathSocketCLI/AssemblyInfo.fs b/DeathSocketCLI/AssemblyInfo.fs index 00d540e..138c923 100644 --- a/DeathSocketCLI/AssemblyInfo.fs +++ b/DeathSocketCLI/AssemblyInfo.fs @@ -38,8 +38,8 @@ open System.Runtime.InteropServices // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [] -[] -[] +[] +[] do () \ No newline at end of file diff --git a/DeathSocketCLI/Commands.fs b/DeathSocketCLI/Commands.fs index f70ddad..d0fd0ed 100644 --- a/DeathSocketCLI/Commands.fs +++ b/DeathSocketCLI/Commands.fs @@ -1,6 +1,7 @@ namespace Commands module ConsoleCommands = + open System open DeathSocket.GridPainter open DeathSocketCLI.Validation @@ -90,4 +91,33 @@ let ag imgPath numRows numColumns pWidth colour newPath = ``add-grid`` imgPath numRows numColumns pWidth colour newPath - let lc () =``list-colours`` () \ No newline at end of file + let lc () =``list-colours`` () + + (* SkiaSharp Command-Methods -- NOT FOR MASS CONSUMPTION + ======================================================================= + These command-methods will not show up in the help section. These + command-methods are here for testing purposes. They mimic the command- + methods above, apart from using SkiaSharp. So, including these command- + methods will create a sense of duplicated functionality. Seeing as you + are reading this, it is assumed you can understand it. If so, please + feel free to use them -- just keep their exposure to a minimum.*) + + let ``list-skia-colours`` () = + printfn "[INFO.] Listing available SkiaSharp colours..." + for item in skiaColourList do + printfn "%s" item.Key + showEndOfCommandMessage + + let``add-skia-grid`` imgPath numRows numColumns pWidth colour newPath = + printfn "[INFO.] Adding SkiaSharp grid to image..." + buildSkiaSpec imgPath numRows numColumns pWidth colour newPath + |> applySkiaGridAsync + |> Async.Start + showEndOfCommandMessage + + let``add-skia-rgb-grid`` imgPath numRows numColumns pWidth r g b newPath = + printfn "[INFO.] Adding SkiaSharp grid to image..." + buildSkiaRGBSpec imgPath numRows numColumns pWidth r g b newPath + |> applySkiaRGBGridAsync + |> Async.Start + showEndOfCommandMessage \ No newline at end of file diff --git a/DeathSocketCLI/DeathSocketCLI.fsproj b/DeathSocketCLI/DeathSocketCLI.fsproj index e62a4d0..cf3d00a 100644 --- a/DeathSocketCLI/DeathSocketCLI.fsproj +++ b/DeathSocketCLI/DeathSocketCLI.fsproj @@ -14,6 +14,8 @@ True DeathSocketCLI ..\DeathSocketCLI\resources.res + + true @@ -60,14 +62,17 @@ ..\packages\Console.Waterworks.0.1.0.0-alpha1\lib\Console.Waterworks.dll - ..\packages\FSharp.Core.4.5.2\lib\net45\FSharp.Core.dll + ..\packages\FSharp.Core.4.5.4\lib\net45\FSharp.Core.dll + + ..\packages\SkiaSharp.1.60.3\lib\net45\SkiaSharp.dll + - ..\packages\System.Drawing.Common.4.5.0\lib\net461\System.Drawing.Common.dll + ..\packages\System.Drawing.Common.4.5.1\lib\net461\System.Drawing.Common.dll @@ -81,6 +86,13 @@ True + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + +