diff --git a/.gitignore b/.gitignore index 3e759b7..7d62a42 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,10 @@ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +# Project-specific Files +/TestCentre/LoadingTestArea/*.png +/TestCentre/SavingTestArea/*.png + # User-specific files *.suo *.user @@ -328,3 +332,4 @@ ASALocalRun/ # MFractors (Xamarin productivity tool) working folder .mfractor/ + diff --git a/DeathSocket/DeathSocket.fsproj b/DeathSocket/DeathSocket.fsproj index df45432..3f8cc01 100644 --- a/DeathSocket/DeathSocket.fsproj +++ b/DeathSocket/DeathSocket.fsproj @@ -5,7 +5,15 @@ - + + + + + + + + + diff --git a/DeathSocket/Domain.fs b/DeathSocket/Domain.fs new file mode 100644 index 0000000..04be768 --- /dev/null +++ b/DeathSocket/Domain.fs @@ -0,0 +1,23 @@ +namespace DeathSocket + + [] + module Domain = + + open System.Drawing + open System.Drawing.Imaging + + type ImageSpec = + { originalPath: string; + savePath: string; + colour: Brush; + penWidth: float32 + rows: int; + columns: int } + + type StreamSpec = + { imagePath: string; + format: ImageFormat; + colour: Brush; + penWidth: float32 + rows: int; + columns: int } \ No newline at end of file diff --git a/DeathSocket/GridPainter.fs b/DeathSocket/GridPainter.fs new file mode 100644 index 0000000..088c846 --- /dev/null +++ b/DeathSocket/GridPainter.fs @@ -0,0 +1,17 @@ +namespace DeathSocket + + open System.IO + + module GridPainter = + + open Validation + open ImageServices + + let applyGrid spec = + async { + try + validateFilePath |> ignore + drawGrid spec |> ignore + 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 new file mode 100644 index 0000000..ca5830b --- /dev/null +++ b/DeathSocket/ImageServices.fs @@ -0,0 +1,31 @@ +module internal ImageServices + + open System.Drawing + open System.Drawing.Imaging + open DeathSocket + + let createHorizontalLines width height rows = + let interval = height / rows + [| for point in 1 .. (rows - 1) -> + [|Point (0, (interval * point)) + Point (width, (interval * point) )|]|] + + let createVerticalLines width height columns = + let interval = width / columns + [| for point in 1 .. (columns - 1) -> + [| Point ((interval * point), 0) + Point ((interval * point), height)|]|] + + let drawGrid spec = + let img = Bitmap.FromFile spec.originalPath + let graphics = Graphics.FromImage img + let pen = new Pen (spec.colour, width = spec.penWidth) + let horizontalLines = + createHorizontalLines (img.Size.Width) (img.Size.Height) (spec.columns) + let verticalLines = createVerticalLines (img.Size.Width) (img.Size.Height) (spec.columns) + for line in horizontalLines do graphics.DrawLines (pen, line) + for line in verticalLines do graphics.DrawLines (pen, line) + img.Save (spec.savePath, ImageFormat.Png) + img.Dispose () + graphics.Dispose () + pen.Dispose () \ No newline at end of file diff --git a/DeathSocket/Library.fs b/DeathSocket/Library.fs deleted file mode 100644 index 5e59df0..0000000 --- a/DeathSocket/Library.fs +++ /dev/null @@ -1,5 +0,0 @@ -namespace DeathSocket - -module Say = - let hello name = - printfn "Hello %s" name diff --git a/DeathSocket/ScratchPad.fsx b/DeathSocket/ScratchPad.fsx new file mode 100644 index 0000000..63444cb --- /dev/null +++ b/DeathSocket/ScratchPad.fsx @@ -0,0 +1,42 @@ +#load "Domain.fs" +#load "Validation.fs" +#load "ImageServices.fs" +#load "GridPainter.fs" + +open System.Drawing +open System +open DeathSocket +open Validation +open ImageServices + +(* Death Socket Scripts +=============================================================================== +The code in here can be use to create a gridded image without running the CLI. +You can, also, run checks to see what Death Socket is doing when creating +a nerw image. *) + +let desktop = Environment.GetFolderPath (Environment.SpecialFolder.Desktop) + +(* You will need to provide the image at the location specified. It will throw +an exception if the file cannnot be found.*) +let validationTest = validateFilePath (desktop + "/test.jpg") + +(* These are not needed to create an image. There here for you to check the +start and end points of each pen stroke/path. *) +let horizontalLines = createHorizontalLines 1000 500 10 +let verticalLines = createVerticalLines 300 600 10 + +(* You will need to provide the image and specify its load/save location. +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 = + { originalPath = desktop + "/test.jpg" + savePath = desktop + "/grid.png" + colour = Brushes.Chartreuse + penWidth = (float32 1) + rows = 10 + columns = 10 } + +// Run this when you have finished building the spec. +GridPainter.applyGrid spec |> Async.RunSynchronously diff --git a/DeathSocket/Validation.fs b/DeathSocket/Validation.fs new file mode 100644 index 0000000..1931f22 --- /dev/null +++ b/DeathSocket/Validation.fs @@ -0,0 +1,9 @@ +module internal Validation + +open System.IO + + let validateFilePath path = + match File.Exists path with + | true -> () + | false -> raise (new FileNotFoundException (path + " could not be found.")) + diff --git a/DeathSocketCLI/AssemblyInfo.fs b/DeathSocketCLI/AssemblyInfo.fs index 8b8de4f..0ae66af 100644 --- a/DeathSocketCLI/AssemblyInfo.fs +++ b/DeathSocketCLI/AssemblyInfo.fs @@ -1,18 +1,22 @@ namespace DeathSocketCLI.AssemblyInfo open System.Reflection -open System.Runtime.CompilerServices open System.Runtime.InteropServices // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[] -[] +[] +[] + [] -[] -[] -[] +[] +[] +[] [] [] @@ -34,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 new file mode 100644 index 0000000..aad33ee --- /dev/null +++ b/DeathSocketCLI/Commands.fs @@ -0,0 +1,93 @@ +namespace Commands + + module ConsoleCommands = + open System + open DeathSocket.GridPainter + open DeathSocketCLI.Validation + open System.IO + open Console.Waterworks + open Console.Waterworks.Attributes + + let showEndOfCommandMessage = "[INFO.] Task completed." + + [] + [] + [] + [] + let test () = "[SUCCESS] Death Socket is working." + + [] + [] + [] + [] + let help () = CW_Liaison().RequestHelpDocumentation("Commands") + + [] + [] + [] + [] + let exit () = Environment.Exit (Environment.ExitCode) + + [] + [] + [] + [] + let ``add-default`` imgPath newPath = + try + printfn "[INFO.] Adding default grid to image..." + buildDefaultSpec imgPath newPath + |> applyGrid + |> Async.RunSynchronously + showEndOfCommandMessage + with + | :? FileNotFoundException as ex -> "[ERROR] No file was found at " + ex.FileName + | :? ArgumentException as ex -> "[ERROR] Invalid argument: " + ex.Message + | _ as ex -> ex.Message + + [] + [] + [] + [] + let ``add-grid`` imgPath numRows numColumns pWidth colour newPath = + try + printfn "[INFO.] Adding grid to image..." + buildSpec imgPath numRows numColumns pWidth colour newPath + |> applyGrid + |> Async.RunSynchronously + showEndOfCommandMessage + with + | :? FileNotFoundException as ex -> "[ERROR] No file was found at " + ex.FileName + | :? ArgumentException as ex -> "[ERROR] Invalid argument: " + ex.Message + | _ as ex -> ex.Message + + [] + [] + [] + [] + let ``list-colours`` () = + printfn "[INFO.] Listing available colours..." + for item in colourList do + printfn "%s" item.Key + showEndOfCommandMessage + + (* ALIASES + ======================================================================= + These command-methods will not show up in the help section. Before + adding extra aliases, make sure the main version is as clear and + helpful to someone new to the program as possible. Aliases should be + treated as commands for experienced users and documented on the wiki, + hosted alongside the repository on GitHub. + URL: https://github.com/CraigOates/Death-Socket/wiki *) + + let ad imgPath newPath = ``add-default`` imgPath newPath + + 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 diff --git a/DeathSocketCLI/DeathSocketCLI.fsproj b/DeathSocketCLI/DeathSocketCLI.fsproj index fc5caea..e62a4d0 100644 --- a/DeathSocketCLI/DeathSocketCLI.fsproj +++ b/DeathSocketCLI/DeathSocketCLI.fsproj @@ -11,8 +11,9 @@ DeathSocketCLI v4.7.1 true - true + True DeathSocketCLI + ..\DeathSocketCLI\resources.res true @@ -46,17 +47,28 @@ + + + + + + ..\packages\Console.Waterworks.0.1.0.0-alpha1\lib\Console.Waterworks.dll + ..\packages\FSharp.Core.4.5.2\lib\net45\FSharp.Core.dll + + + ..\packages\System.Drawing.Common.4.5.0\lib\net461\System.Drawing.Common.dll + True diff --git a/DeathSocketCLI/Program.fs b/DeathSocketCLI/Program.fs index 2bcf7f9..8e51171 100644 --- a/DeathSocketCLI/Program.fs +++ b/DeathSocketCLI/Program.fs @@ -1,7 +1,7 @@ -// Learn more about F# at http://fsharp.org -// See the 'F# Tutorial' project for more help. +open Console.Waterworks [] let main argv = - printfn "%A" argv - 0 // return an integer exit code + let liaison = CW_Liaison () + liaison.Run ("Commands", true) + 0 diff --git a/DeathSocketCLI/Validation.fs b/DeathSocketCLI/Validation.fs new file mode 100644 index 0000000..c798351 --- /dev/null +++ b/DeathSocketCLI/Validation.fs @@ -0,0 +1,62 @@ +module DeathSocketCLI.Validation + + open DeathSocket + open System.Drawing + open System + open System.IO + + let colourList = + [ "blue", Brushes.AliceBlue + "brown", Brushes.Brown + "black", Brushes.Black + "gray", Brushes.Gray + "grey", Brushes.Gray + "green", Brushes.Green + "purple", Brushes.Purple + "red", Brushes.Red + "white", Brushes.White + "yellow", Brushes.Yellow ] + |> Map.ofList + + let isColourValid (colour: string) = + colourList + |> Map.containsKey (colour.ToLower()) + + let parseColour colour = + match (isColourValid colour) with + | true -> + colourList + |> Map.find (colour.ToLower()) + | false -> + invalidArg "Colour" + (String.Concat("The colour specifed is invalid.\n", + "Please use the 'list-colours' command to see what you can use.")) + + let setPenWidth imgWidth imgHeight = + let width = float32 imgWidth + let height = float32 imgHeight + if (width >= height) then + height * (float32 0.002) + else width * (float32 0.002) + + let buildSpec imgPath numRows numColumns pWidth colour newPath = + { originalPath = imgPath + savePath = newPath + colour = parseColour colour + penWidth = pWidth + rows = numRows + columns = numColumns } + + let buildDefaultSpec imgPath newPath = + let stream = new FileStream (imgPath, FileMode.Open) + let image = Image.FromStream (stream, false, false) + let spec = + { originalPath = imgPath + savePath = newPath + colour = Brushes.White + penWidth = setPenWidth (image.Width) (image.Height) + rows = 10 + columns = 10 } + stream.Dispose () + image.Dispose () + spec \ No newline at end of file diff --git a/DeathSocketCLI/logo.ico b/DeathSocketCLI/logo.ico new file mode 100644 index 0000000..623edf6 Binary files /dev/null and b/DeathSocketCLI/logo.ico differ diff --git a/DeathSocketCLI/packages.config b/DeathSocketCLI/packages.config index 4c12917..45c38e7 100644 --- a/DeathSocketCLI/packages.config +++ b/DeathSocketCLI/packages.config @@ -1,5 +1,7 @@  + - + + \ No newline at end of file diff --git a/DeathSocketCLI/resources.res b/DeathSocketCLI/resources.res new file mode 100644 index 0000000..e22b546 Binary files /dev/null and b/DeathSocketCLI/resources.res differ diff --git a/TestCentre/ConsoleTests.fs b/TestCentre/ConsoleTests.fs new file mode 100644 index 0000000..056847c --- /dev/null +++ b/TestCentre/ConsoleTests.fs @@ -0,0 +1,113 @@ +namespace ConsoleTests + + (* Initial Setup -- Populating the Test Folders. + =========================================================================== + If you have just cloned this repository or have not run any of the tests + before, please head over to script.fs (in Test Centre) and populate the + LoadingTestArea and SavingTestArea folders. More information will be + provided there about what the scripts do. It is worth pointing out here the + .gitignore file ignores any .png files in them. This is why you must + populate them before running any tests. *) + + module TestingHelpers = + + (* When you are writing tests, please keep all helper functions in this + module. If this module grows to a point where it hinders the actual + testing modules, consider moving it then. If you do decide to move them + out, pay attention to LibraryTests.fs and its helper functions. *) + + open System + open DeathSocketCLI.Validation + + // Directory Path + let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea" + let loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea" + + // File Path + let loadPath = loadLocation + "/RequiredInfo/LoadTest.png" + let savePath = saveLocation + "/SaveTest.png" + + (* Brushes converted to string []. + CLI deals with strings before brushes. *) + let testingColourArray = + colourList + |> Map.toSeq + |> Seq.map fst + |> Seq.toArray + + let randomColourString () = + testingColourArray.[Random().Next(testingColourArray.Length - 1)] + + let randomBrush () = + colourList + |> Map.toSeq + |> Seq.map snd + |> Seq.toArray + |> Array.item (Random().Next(colourList.Count)) + + module PropertyTests = + + open System + open FsCheck.Xunit + open DeathSocketCLI.Validation + open System.Drawing + open TestingHelpers + open DeathSocket.Domain + + [] + let ``Pen width is set greater than 0`` () = + (setPenWidth (Random().Next()) (Random().Next())) > 0.0f + + [] + let ``Can build the intended default image specification`` () = + let defaultSpec = + { originalPath = loadPath + savePath = savePath + colour = Brushes.White + penWidth = setPenWidth 1000 1000 + rows = 10 + columns = 10 } + let spec = buildDefaultSpec loadPath savePath + defaultSpec = spec + + [] + let ``Can build an image specification as intended`` () = + let colourString = randomColourString () + let brush = parseColour colourString + let pWidth = float32 (Random().Next()) + let randRows = Random().Next() + let randCols = Random().Next() + let intendedSpec = + { originalPath = loadPath + savePath = savePath + colour = brush + penWidth = pWidth + rows =randRows + columns = randCols } + let spec = + buildSpec loadPath randRows randCols pWidth colourString savePath + intendedSpec = spec + + module UnitTests = + + open System.IO + open DeathSocketCLI.Validation + open Xunit + open TestingHelpers + + [] + let ``Saving Test Area can be located`` () = + Assert.True (Directory.Exists saveLocation) + + [] + let ``Loading Test Area can be located`` () = + Assert.True (Directory.Exists loadLocation) + + [] + let ``Colour list is not empty`` () = + Assert.False (colourList.IsEmpty) + + [] + let ``Exception thrown when invalid colour is used`` () = + Assert.Throws + (fun () -> parseColour "not a valid colour" |> ignore) \ No newline at end of file diff --git a/TestCentre/Library1.fs b/TestCentre/Library1.fs deleted file mode 100644 index 7809afa..0000000 --- a/TestCentre/Library1.fs +++ /dev/null @@ -1,4 +0,0 @@ -namespace TestCentre - -type Class1() = - member this.X = "F#" diff --git a/TestCentre/LibraryTests.fs b/TestCentre/LibraryTests.fs new file mode 100644 index 0000000..deaf25a --- /dev/null +++ b/TestCentre/LibraryTests.fs @@ -0,0 +1,106 @@ +namespace LibraryTests + + (* Initial Setup -- Populating the Test Folders. + =========================================================================== + If you have just cloned this repository or have not run any of the tests + before, please head over to script.fs (in Test Centre) and populate the + LoadingTestArea and SavingTestArea folders. More information will be + provided there about what the scripts do. It is worth pointing out here the + .gitignore file ignores any .png files in them. This is why you must + populate them before running any tests. *) + + module TestingHelpers = + + (* When you are writing tests, please keep all helper functions in this + module. If this module grows to a point where it hinders the actual + testing modules, consider moving it then. If you do decide to move them + out, pay attention to ConsoleTests.fs and its helper functions. *) + + open System + open System.Drawing + open System.Reflection + open System.IO + + (* These are duplicates from ConsoleTests.fs (both of them). See point + about helpers. Tests for checking these locations can be found in + ConsoleTests.fs.*) + let loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea" + let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea" + + let allColours = + let properties = + typeof.GetProperties + (BindingFlags.Public ||| BindingFlags.Static) + seq { for prop in properties -> prop} + |> Seq.toArray + + let randomBrush () = + let item = allColours.[Random().Next(allColours.Length)] + item.GetValue(null, null) + + let imagesInLoadingTestArea = + Directory.GetFileSystemEntries (loadLocation, "*.png") + + let generateLoadPath () = + let rand = Random () + let files = imagesInLoadingTestArea + files.[rand.Next(files.Length)] + + let generateSavePath originalFilePath = + let fileName = Path.GetFileName originalFilePath + saveLocation + "/" + fileName + + (* To "manually" clear out the SavingTestArea folder, use this function + in script.fsx. More information can be found there, also.*) + let resetSavingTestArea () = + let files = Directory.GetFileSystemEntries(saveLocation, "*.png") + match files.Length with + | 0 -> () + | _ -> + files + |> Array.iter (fun f -> File.Delete(f)) + + module PropertyTests = + + open FsCheck.Xunit + open DeathSocket + open System.Drawing + open DeathSocket.GridPainter + open TestingHelpers + open System.IO + + [] + let ``Can apply grid to image and save it`` () = + (* You should end up with one image left over in SavingTestArea. + Comment out the "reset" function to see all the images produced, + by this test. This will mean you will need to manually delete the + images yourself if you do.*) + resetSavingTestArea () + let oPath = generateLoadPath () + let sPath = generateSavePath oPath + let spec = + { originalPath = oPath + savePath = sPath + colour = randomBrush () :?> Brush + penWidth = float32 1 + rows = 10 + columns = 10 } + applyGrid spec + |> Async.RunSynchronously + (File.Exists sPath) = true + + module UnitTests = + + open TestingHelpers + open Xunit + + (* This test is a precaution (a test for the tests if you will...). + It is here to make sure the property test has what it needs to run. + If the property test fails, here is a good place to start. + See script.fs (in Test Centre) for information on populating the + LoadingTestArea folder. *) + [] + let ``LoadingTestArea contains at least 100 test images`` () = + let length = imagesInLoadingTestArea.Length + let imagesAreThere = if length < 100 then false else true + Assert.True imagesAreThere \ No newline at end of file diff --git a/TestCentre/LoadingTestArea/RequiredInfo/LoadTest.png b/TestCentre/LoadingTestArea/RequiredInfo/LoadTest.png new file mode 100644 index 0000000..44f413c Binary files /dev/null and b/TestCentre/LoadingTestArea/RequiredInfo/LoadTest.png differ diff --git a/TestCentre/Script.fsx b/TestCentre/Script.fsx index 1845605..7c24da5 100644 --- a/TestCentre/Script.fsx +++ b/TestCentre/Script.fsx @@ -1,8 +1,96 @@ -// Learn more about F# at http://fsharp.org -// See the 'F# Tutorial' project for more help. +// These DLL's must be built before you can use them in this script. +#r "bin/Debug/System.Drawing.Common.dll" +#r "bin/Debug/SmoulderingBeachBall.dll" -#load "Library1.fs" -open TestCentre +open System +open System.Drawing +open System.Reflection +open SmoulderingBeachBall.Domain +open SmoulderingBeachBall.Services +open System.Threading +open System.IO -// Define your library scripting code here +let loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea" +let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea" +let random = Random() +(*Resetting the Testing Area Folders Scripts +=============================================================================== +The following scripts are for when you need to "manually" clear out the +LoadingTestArea and SavingTestArea folders. If you do not want to open up the +folder in Explorer and delete the files that way, just run the scripts in this +section. If you reset the LoadingTestArea, you will need to repopulate it using +the "Populating LoadingTestArea Folder Scripts" below. *) + +let resetSavingTestArea () = + let files = Directory.GetFileSystemEntries(saveLocation, "*.png") + match files.Length with + | 0 -> () + | _ -> + files + |> Array.iter (fun f -> File.Delete(f)) + +let resetLoadingTestArea () = + let files = Directory.GetFileSystemEntries(loadLocation, "*.png") + match files.Length with + | 0 -> () + | _ -> + files + |> Array.iter (fun f -> File.Delete(f)) + +// Run these when the above functions have been added to F# interactive. +resetSavingTestArea () +resetLoadingTestArea () + +(* Populating LoadingTestArea Folder Scripts +=============================================================================== +The following scripts are to help you populate a test folder of images. +You can then use these images in LibraryTests.fs -- Property Tests. +The tests consists of loading images from LoadingTestArea, transforming them +and saving them in SavingTestArea. *) + +let allColours = + let properties = + typeof.GetProperties(BindingFlags.Public ||| BindingFlags.Static) + seq { for prop in properties -> prop} + |> Seq.toArray + +let randomBrush () = + let item = allColours.[random.Next(allColours.Length)] + item.GetValue(null, null) + +let generateDimensionSizes total = + List.init total (fun _ -> random.Next(3000)) + +let randomSize (sizes: int list) = + // (Sleep) Helps to reduce chance of picking the same item twice in quick succession. + Thread.Sleep 100 + sizes.Item (random.Next(sizes.Length)) + +let populateSpec sizes = + { width = randomSize (sizes) + height = randomSize (sizes) + colour = (randomBrush ()) :?> Brush + filePath = loadLocation + overlay = None } + +let generateSpecs amount = + let imageSizes = generateDimensionSizes amount + [for i in 0 .. amount -> (populateSpec (imageSizes)) ] + +let generateImage spec = + printfn "[INFO.] Creating image [Width: %i] [Height: %i] ..." (spec.width) (spec.height) + makeImage spec + +let populateLoadingTestArea () = + printfn "[INFO.] Populating LoadingTestArea..." + generateSpecs 100 + |> List.map generateImage + |> Async.Parallel + |> Async.RunSynchronously + |> ignore + printfn "[INFO.] Finishing populating /LoadingTestArea." + +// You should only need this once. +// Make sure you have passed the above into F# Interactive. +populateLoadingTestArea () \ No newline at end of file diff --git a/TestCentre/TestCentre.fsproj b/TestCentre/TestCentre.fsproj index ba01899..095f0ae 100644 --- a/TestCentre/TestCentre.fsproj +++ b/TestCentre/TestCentre.fsproj @@ -1,5 +1,7 @@ + + Debug @@ -13,6 +15,8 @@ v4.7.1 true TestCentre + + true @@ -42,21 +46,47 @@ - + + + + ..\packages\FsCheck.2.11.0\lib\net452\FsCheck.dll + + + ..\packages\FsCheck.Xunit.2.11.0\lib\net452\FsCheck.Xunit.dll + ..\packages\FSharp.Core.4.5.2\lib\net45\FSharp.Core.dll + + ..\packages\SmoulderingBeachBall.0.4.0-alpha1\lib\netstandard2.0\SmoulderingBeachBall.dll + + + + ..\packages\System.Drawing.Common.4.5.0\lib\net461\System.Drawing.Common.dll + True + + ..\packages\xunit.abstractions.2.0.3\lib\net35\xunit.abstractions.dll + + + ..\packages\xunit.assert.2.4.0\lib\netstandard2.0\xunit.assert.dll + + + ..\packages\xunit.extensibility.core.2.4.0\lib\net452\xunit.core.dll + + + ..\packages\xunit.extensibility.execution.2.4.0\lib\net452\xunit.execution.desktop.dll + @@ -70,6 +100,15 @@ 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}. + + + + + +