diff --git a/DeathSocket/ColourServices.fs b/DeathSocket/ColourServices.fs new file mode 100644 index 0000000..fb7d29b --- /dev/null +++ b/DeathSocket/ColourServices.fs @@ -0,0 +1,17 @@ +module internal ColourServices + + open System.Drawing + open DeathSocket.Domain + + let makeBrushFromRGBASpec (spec: RGBASpec) = + let a = int spec.alpha + let r = int spec.red + let g = int spec.green + let b = int spec.blue + let colour = Color.FromArgb (a, r, g, b) + new SolidBrush (colour) + + let makeBrushFromRGBA r g b a = + let colour = Color.FromArgb (a, r, g, b) + new SolidBrush (colour) + diff --git a/DeathSocket/DeathSocket.fsproj b/DeathSocket/DeathSocket.fsproj index 3f8cc01..80d6bb6 100644 --- a/DeathSocket/DeathSocket.fsproj +++ b/DeathSocket/DeathSocket.fsproj @@ -2,11 +2,27 @@ netstandard2.0 + true + true + 0.4-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). + https://github.com/CraigOates/Death-Socket/tree/master + https://github.com/CraigOates/Death-Socket/blob/master/LICENSE + + Craig Oates + 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. + diff --git a/DeathSocket/Domain.fs b/DeathSocket/Domain.fs index 34cc817..ae4a174 100644 --- a/DeathSocket/Domain.fs +++ b/DeathSocket/Domain.fs @@ -6,10 +6,8 @@ open System.Drawing - /// - /// The specification used by Death Socket when adding a grid to an image. - /// - type ImageSpec = + /// The specification which uses System.Drawing brush to draw a grid. + type BrushSpec = { /// The original path of the image which the grid is being added to. originalPath: string /// The location of the new gridded image. @@ -21,4 +19,27 @@ /// The number of rows the grid will have. rows: int ///The number of columns the grid will have. + columns: int } + + /// The specification which uses includes individual RGBA values to + /// draw a grid. + type RGBASpec = + { /// 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 opacity level of the grid. + /// 0 makes it completely see through and 1 makes it solid. + alpha: float + /// The amount of red the grid's line will have. + red: float + /// The amount of green the grid's line will have. + green: float + /// The amount of blue the grid's line will have. + blue: float + /// 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 fbb3bba..60dfa1a 100644 --- a/DeathSocket/GridPainter.fs +++ b/DeathSocket/GridPainter.fs @@ -1,6 +1,7 @@ namespace DeathSocket open System.IO + open ColourServices /// Provides functions which help draw gridded overlays onto images. /// Grid Painter, and all of Death Socket, uses the System.Drawing brushes/colours. @@ -11,10 +12,10 @@ namespace DeathSocket open ImageServices /// - /// Uses the information included in spec and creates a gridded image. + /// 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 others (image) file types have not been tested. + /// The other (image) file types have not been tested. /// /// /// The specification used to generate the new gridded image @@ -28,17 +29,70 @@ namespace DeathSocket /// is not in use or needed by another program/process. /// This is because it is locked whilst in this function. /// - let applyGridAsync spec = + let applyBrushSpecGridAsync (spec: BrushSpec) = async { try validateFilePath spec.originalPath |> ignore validatFileType spec.savePath |> ignore - drawGrid spec |> ignore + drawBrushSpecGrid spec |> ignore 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. Please stick to .bmp, .jpg or .png + /// files. The other (image) file types have not been tested. + /// + /// + /// The specification used to generate the gridded image. + /// + /// + /// If the file the grid is being applied to cannot be found, + /// a FileNotFoundException will be thrown. + /// + /// + let applyRGBAGridAsync (spec: RGBASpec) = + async { + try + validateFilePath spec.originalPath |> ignore + validatFileType spec.savePath |> ignore + drawRGBAGrid spec + with + | :? FileNotFoundException as ex -> + printfn "File could not be found at %s" ex.Message + } + + /// + /// Creates a Sytsem.Drawing SolidBrush from the individual RGBA values. + /// + /// The red value. + /// The green value. + /// The blue value. + /// The alpha value. + /// + /// Death Socket uses System.Drawing and not System.Media for colours + /// and brushes. + /// + let makeSolidBrushFromRGBA r g b a = makeBrushFromRGBA r g b a + + /// + /// Creates a System.Drawing SolidBrush from a RGBASpec. + /// + /// + ///The specification which the brush is made from. + /// + /// + /// Death Socket uses System.Drawing and not System.Media for colours + /// and brushes. + /// + let makeSolidBrushFromRGBASpec spec = makeBrushFromRGBASpec spec + /// /// 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. diff --git a/DeathSocket/ImageServices.fs b/DeathSocket/ImageServices.fs index b6219ed..8babd60 100644 --- a/DeathSocket/ImageServices.fs +++ b/DeathSocket/ImageServices.fs @@ -3,6 +3,7 @@ open System.Drawing open System.Drawing.Imaging open DeathSocket + open ColourServices let createHorizontalLines width height rows = let interval = height / rows @@ -16,8 +17,13 @@ [| Point ((interval * point), 0) Point ((interval * point), height)|]|] - let drawGrid spec = - // The temp. file is used as a way to convert images with indexed pixels. + (* 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 a "*Spec" type as a parameter will use this "temp" file. *) + + let drawBrushSpecGrid (spec: BrushSpec) = 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) @@ -30,4 +36,19 @@ 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) \ No newline at end of file + clone.Save (spec.savePath) + + 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 graphics = Graphics.FromImage(clone) + use pen = new Pen ((makeBrushFromRGBASpec spec), width = spec.penWidth) + 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) diff --git a/DeathSocket/ScratchPad.fsx b/DeathSocket/ScratchPad.fsx index 63444cb..13eaa89 100644 --- a/DeathSocket/ScratchPad.fsx +++ b/DeathSocket/ScratchPad.fsx @@ -30,7 +30,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 = +let spec :ImageSpec = { originalPath = desktop + "/test.jpg" savePath = desktop + "/grid.png" colour = Brushes.Chartreuse @@ -39,4 +39,4 @@ let spec = columns = 10 } // Run this when you have finished building the spec. -GridPainter.applyGrid spec |> Async.RunSynchronously +GridPainter.applyGridAsync spec |> Async.RunSynchronously diff --git a/DeathSocketCLI/AssemblyInfo.fs b/DeathSocketCLI/AssemblyInfo.fs index 2352b17..00d540e 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 53fbc9c..f70ddad 100644 --- a/DeathSocketCLI/Commands.fs +++ b/DeathSocketCLI/Commands.fs @@ -38,7 +38,7 @@ try printfn "[INFO.] Adding default grid to image..." buildDefaultSpec imgPath newPath - |> applyGridAsync + |> applyBrushSpecGridAsync |> Async.Start showEndOfCommandMessage with @@ -57,7 +57,7 @@ try printfn "[INFO.] Adding grid to image..." buildSpec imgPath numRows numColumns pWidth colour newPath - |> applyGridAsync + |> applyBrushSpecGridAsync |> Async.Start showEndOfCommandMessage with diff --git a/DeathSocketCLI/Validation.fs b/DeathSocketCLI/Validation.fs index 1225133..c0476a3 100644 --- a/DeathSocketCLI/Validation.fs +++ b/DeathSocketCLI/Validation.fs @@ -32,6 +32,8 @@ (String.Concat("The colour specifed is invalid.\n", "Please use the 'list-colours' command to see what you can use.")) + (* This function creates a consistent line width amoung different image + sizes. Use this when the user has no means to specify a pen width. *) let setPenWidth imgWidth imgHeight = let width = float32 imgWidth let height = float32 imgHeight diff --git a/TestCentre/ConsoleTests.fs b/TestCentre/ConsoleTests.fs index 056847c..32f4d0c 100644 --- a/TestCentre/ConsoleTests.fs +++ b/TestCentre/ConsoleTests.fs @@ -59,7 +59,7 @@ (setPenWidth (Random().Next()) (Random().Next())) > 0.0f [] - let ``Can build the intended default image specification`` () = + let ``Can build the intended default BrushSpec`` () = let defaultSpec = { originalPath = loadPath savePath = savePath @@ -71,7 +71,7 @@ defaultSpec = spec [] - let ``Can build an image specification as intended`` () = + let ``Can build a BrushSpec as intended`` () = let colourString = randomColourString () let brush = parseColour colourString let pWidth = float32 (Random().Next()) diff --git a/TestCentre/LibraryTests.fs b/TestCentre/LibraryTests.fs index 4d15747..6199435 100644 --- a/TestCentre/LibraryTests.fs +++ b/TestCentre/LibraryTests.fs @@ -20,13 +20,16 @@ open System.Drawing open System.Reflection open System.IO + open DeathSocket.Domain let rand = Random () - (* These are duplicates from ConsoleTests.fs (both of them). See point about helpers. Tests for checking these locations can be found in - ConsoleTests.fs. *) + ConsoleTests.fs. + Also, these folders should not show up in Visual Studios + Solution Explorer -- unless you are viewing the solution in its folder + format. *) let loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea" let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea" @@ -46,6 +49,10 @@ Intended for horizontal and vertical line tests. *) let newNum () = rand.Next(1, 1000) + let newPenWidth () = rand.Next (1, 10) + + let newRGBANum () = rand.Next (255) + let imagesInLoadingTestArea = Directory.GetFileSystemEntries (loadLocation, "*.png") @@ -67,6 +74,17 @@ files |> Array.iter (fun f -> File.Delete(f)) + let makeTestRGBASpec r g b a = + { originalPath = "test path" + savePath = "test path" + alpha = float a + red = float r + green = float g + blue = float b + penWidth = float32 10 + rows = newNum () + columns = newNum () } + module PropertyTests = open FsCheck.Xunit @@ -75,28 +93,68 @@ open DeathSocket.GridPainter open TestingHelpers open System.IO - open System + + (* With regards to the "saving images" tests, you should end up with + one image left over in the SavingTestArea folder. 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. *) [] - 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. *) + let ``Can apply grid to image and save it using BrushSpec`` () = resetSavingTestArea () let oPath = generateLoadPath () let sPath = generateSavePath oPath - let spec = + let (spec: BrushSpec) = { originalPath = oPath savePath = sPath colour = randomBrush () :?> Brush - penWidth = float32 1 + penWidth = float32 (newPenWidth()) + rows = 10 + columns = 10 } + applyBrushSpecGridAsync spec + |> Async.RunSynchronously + (File.Exists sPath) = true + + [] + let ``Can apply grid to image and save it using RGBASpec`` () = + resetSavingTestArea () + let oPath = generateLoadPath () + let sPath = generateSavePath oPath + let (spec: RGBASpec) = + { originalPath = oPath + savePath = sPath + alpha = float (newRGBANum ()) + red = float (newRGBANum ()) + green = float (newRGBANum ()) + blue = float (newRGBANum ()) + penWidth = float32 (newPenWidth()) rows = 10 columns = 10 } - applyGridAsync spec + applyRGBAGridAsync spec |> Async.RunSynchronously (File.Exists sPath) = true + [] + let ``SolidBrush colour matches the individual RGBA values`` () = + let a = newRGBANum () + let r = newRGBANum () + let g = newRGBANum () + let b = newRGBANum () + let referenceColour = Color.FromArgb (a, r, g, b) + let brush = makeSolidBrushFromRGBA r g b a + brush.Color = referenceColour + + [] + let ``SolidBrush colour matches the RGBASpec`` () = + let a = newRGBANum () + let r = newRGBANum () + let g = newRGBANum () + let b = newRGBANum () + let referenceColour = Color.FromArgb (a, r, g, b) + let referenceSpec = makeTestRGBASpec r g b a + let brush = makeSolidBrushFromRGBASpec referenceSpec + brush.Color = referenceColour + [] let ``Can return a collection of points which represent a grids horizontal lines`` () = let result = determineHorizontalLines (newNum()) (newNum()) (newNum()) @@ -113,8 +171,9 @@ open Xunit open DeathSocket open System + open System.IO - (* This test is a precaution (a test for the tests if you will...). + (* This test is a pre-test test (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 @@ -125,6 +184,16 @@ let imagesAreThere = if length < 100 then false else true Assert.True imagesAreThere + (* This test is a pre-test test. If the property tests fails, here is a + good place to start. The easiest way to get this test to pass is to + create a folder called "SavingTestArea" in this projects folder + (at the root). You can, also, see script.fs (in Test Centre) for + creating this folder via code. (See "LoadingTestArea contains..." note + for extra context. *) + [] + let ``SavingTestArea folder can be found`` () = + Assert.True (Directory.Exists saveLocation) + [] let ``Divide By Zero Exception is thrown when 0 rows is used when determining horizontal lines`` () = let result () = GridPainter.determineHorizontalLines 100 100 0 diff --git a/TestCentre/Script.fsx b/TestCentre/Script.fsx index e053c75..58283d6 100644 --- a/TestCentre/Script.fsx +++ b/TestCentre/Script.fsx @@ -14,7 +14,7 @@ let loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea" let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea" let random = Random() -(*Resetting the Testing Area Folders Scripts +(* 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 @@ -93,4 +93,22 @@ let populateLoadingTestArea () = // You should only need this once. // Make sure you have passed the above into F# Interactive. -populateLoadingTestArea () \ No newline at end of file +populateLoadingTestArea () + +(* Creating the Saving Test Area Script +============================================================================== +This bit of code is for creating the SavingTestArea folder which Test Centre +will use when it runs its tests. You will normally only need to run this code +when you have just cloned this repository or you accidently deleted said +fodler. In other words, you should only need to use it once. *) + +let createSavingTestArea () = + match Directory.Exists saveLocation with + | false -> + Directory.CreateDirectory saveLocation |> ignore + printfn "SavingTestArea created." + | _ -> printfn "SavingTestArea already exists." + +(* Before calling this function, make sure the test checking this folder exists +is failing first. *) +createSavingTestArea () \ No newline at end of file