Browse Source

Merge pull request #5 from CraigOates/0.4

0.4
master
Craig Oates 6 years ago committed by GitHub
parent
commit
6d49856502
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      DeathSocket/ColourServices.fs
  2. 16
      DeathSocket/DeathSocket.fsproj
  3. 29
      DeathSocket/Domain.fs
  4. 62
      DeathSocket/GridPainter.fs
  5. 27
      DeathSocket/ImageServices.fs
  6. 4
      DeathSocket/ScratchPad.fsx
  7. 4
      DeathSocketCLI/AssemblyInfo.fs
  8. 4
      DeathSocketCLI/Commands.fs
  9. 2
      DeathSocketCLI/Validation.fs
  10. 4
      TestCentre/ConsoleTests.fs
  11. 93
      TestCentre/LibraryTests.fs
  12. 22
      TestCentre/Script.fsx

17
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)

16
DeathSocket/DeathSocket.fsproj

@ -2,11 +2,27 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Version>0.4-alpha</Version>
<Authors>Craig Oates</Authors>
<Product>Death Socket</Product>
<Description>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).</Description>
<PackageProjectUrl>https://github.com/CraigOates/Death-Socket/tree/master</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/CraigOates/Death-Socket/blob/master/LICENSE</PackageLicenseUrl>
<Company />
<Copyright>Craig Oates</Copyright>
<RepositoryUrl>https://github.com/CraigOates/Death-Socket/tree/master</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<PackageTags>deathsocket craig oates image overlay f#</PackageTags>
<PackageReleaseNotes>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.</PackageReleaseNotes>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Domain.fs" /> <Compile Include="Domain.fs" />
<Compile Include="Validation.fs" /> <Compile Include="Validation.fs" />
<Compile Include="ColourServices.fs" />
<Compile Include="ImageServices.fs" /> <Compile Include="ImageServices.fs" />
<Compile Include="GridPainter.fs" /> <Compile Include="GridPainter.fs" />
<None Include="ScratchPad.fsx" /> <None Include="ScratchPad.fsx" />

29
DeathSocket/Domain.fs

@ -6,10 +6,8 @@
open System.Drawing open System.Drawing
/// <summary> /// The specification which uses System.Drawing brush to draw a grid.
/// The specification used by Death Socket when adding a grid to an image. type BrushSpec =
/// </summary>
type ImageSpec =
{ /// The original path of the image which the grid is being added to. { /// The original path of the image which the grid is being added to.
originalPath: string originalPath: string
/// The location of the new gridded image. /// The location of the new gridded image.
@ -21,4 +19,27 @@
/// The number of rows the grid will have. /// The number of rows the grid will have.
rows: int rows: int
///The number of columns the grid will have. ///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 } columns: int }

62
DeathSocket/GridPainter.fs

@ -1,6 +1,7 @@
namespace DeathSocket namespace DeathSocket
open System.IO open System.IO
open ColourServices
/// Provides functions which help draw gridded overlays onto images. /// Provides functions which help draw gridded overlays onto images.
/// Grid Painter, and all of Death Socket, uses the System.Drawing brushes/colours. /// Grid Painter, and all of Death Socket, uses the System.Drawing brushes/colours.
@ -11,10 +12,10 @@ namespace DeathSocket
open ImageServices open ImageServices
/// <summary> /// <summary>
/// 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. /// It then asynchronously saves it.
/// Please stick to .bmp, .jpg or .png files. /// 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.
/// </summary> /// </summary>
/// <param name="spec"> /// <param name="spec">
/// The specification used to generate the new gridded image /// 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. /// is not in use or needed by another program/process.
/// This is because it is locked whilst in this function. /// This is because it is locked whilst in this function.
/// </remarks> /// </remarks>
let applyGridAsync spec = let applyBrushSpecGridAsync (spec: BrushSpec) =
async { async {
try try
validateFilePath spec.originalPath |> ignore validateFilePath spec.originalPath |> ignore
validatFileType spec.savePath |> ignore validatFileType spec.savePath |> ignore
drawGrid spec |> ignore drawBrushSpecGrid spec |> ignore
with with
| :? FileNotFoundException as ex -> | :? FileNotFoundException as ex ->
printfn "File could not be found at %s" ex.Message printfn "File could not be found at %s" ex.Message
} }
/// <summary>
/// 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.
/// </summary>
/// <param name="spec">
/// The specification used to generate the gridded image.
/// </param>
/// <exeption cref="System.IO.FileNotFoundException">
/// If the file the grid is being applied to cannot be found,
/// a FileNotFoundException will be thrown.
/// </exception>
/// <remarks
/// 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.
/// </remarks>
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
}
/// <summary>
/// Creates a Sytsem.Drawing SolidBrush from the individual RGBA values.
/// </summary>
/// <param name="r">The red value.</param>
/// <param name="g">The green value.</param>
/// <param name="b">The blue value.</param>
/// <param name="a">The alpha value.</param>
/// <remarks>
/// Death Socket uses System.Drawing and not System.Media for colours
/// and brushes.
/// </remarks>
let makeSolidBrushFromRGBA r g b a = makeBrushFromRGBA r g b a
/// <summary>
/// Creates a System.Drawing SolidBrush from a RGBASpec.
/// </summary>
/// <param name="spec">
///The specification which the brush is made from.
///</param>
/// <remarks>
/// Death Socket uses System.Drawing and not System.Media for colours
/// and brushes.
/// </remarks>
let makeSolidBrushFromRGBASpec spec = makeBrushFromRGBASpec spec
/// <summary> /// <summary>
/// Determines the (Pen) points needed to draw the appropriate number of horizontal lines (I.E. rows). /// 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. /// Each item in the array includes a start and end co-ordinate (point) for each line.

27
DeathSocket/ImageServices.fs

@ -3,6 +3,7 @@
open System.Drawing open System.Drawing
open System.Drawing.Imaging open System.Drawing.Imaging
open DeathSocket open DeathSocket
open ColourServices
let createHorizontalLines width height rows = let createHorizontalLines width height rows =
let interval = height / rows let interval = height / rows
@ -16,8 +17,13 @@
[| Point ((interval * point), 0) [| Point ((interval * point), 0)
Point ((interval * point), height)|]|] Point ((interval * point), height)|]|]
let drawGrid spec = (* Note on Use of Temp File in Functions which Add A Grid Overlay
// The temp. file is used as a way to convert images with indexed pixels. ===========================================================================
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 original = Bitmap.FromFile spec.originalPath
use temp = new Bitmap(original) use temp = new Bitmap(original)
use clone = temp.Clone(new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb) 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) createVerticalLines (clone.Size.Width) (clone.Size.Height) (spec.columns)
for line in horizontalLines do graphics.DrawLines (pen, line) for line in horizontalLines do graphics.DrawLines (pen, line)
for line in verticalLines do graphics.DrawLines (pen, line) for line in verticalLines do graphics.DrawLines (pen, line)
clone.Save (spec.savePath) 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)

4
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 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 risk. Cannot guarantee they will work. Also, either in this spec. can be
changed to suit your needs. *) changed to suit your needs. *)
let spec = let spec :ImageSpec =
{ originalPath = desktop + "/test.jpg" { originalPath = desktop + "/test.jpg"
savePath = desktop + "/grid.png" savePath = desktop + "/grid.png"
colour = Brushes.Chartreuse colour = Brushes.Chartreuse
@ -39,4 +39,4 @@ let spec =
columns = 10 } columns = 10 }
// Run this when you have finished building the spec. // Run this when you have finished building the spec.
GridPainter.applyGrid spec |> Async.RunSynchronously GridPainter.applyGridAsync spec |> Async.RunSynchronously

4
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 // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [<assembly: AssemblyVersion("1.0.*")>] // [<assembly: AssemblyVersion("1.0.*")>]
[<assembly: AssemblyVersion("0.3.0.0")>] [<assembly: AssemblyVersion("0.4.0.0")>]
[<assembly: AssemblyFileVersion("0.3.0.0")>] [<assembly: AssemblyFileVersion("0.4.0.0")>]
do do
() ()

4
DeathSocketCLI/Commands.fs

@ -38,7 +38,7 @@
try try
printfn "[INFO.] Adding default grid to image..." printfn "[INFO.] Adding default grid to image..."
buildDefaultSpec imgPath newPath buildDefaultSpec imgPath newPath
|> applyGridAsync |> applyBrushSpecGridAsync
|> Async.Start |> Async.Start
showEndOfCommandMessage showEndOfCommandMessage
with with
@ -57,7 +57,7 @@
try try
printfn "[INFO.] Adding grid to image..." printfn "[INFO.] Adding grid to image..."
buildSpec imgPath numRows numColumns pWidth colour newPath buildSpec imgPath numRows numColumns pWidth colour newPath
|> applyGridAsync |> applyBrushSpecGridAsync
|> Async.Start |> Async.Start
showEndOfCommandMessage showEndOfCommandMessage
with with

2
DeathSocketCLI/Validation.fs

@ -32,6 +32,8 @@
(String.Concat("The colour specifed is invalid.\n", (String.Concat("The colour specifed is invalid.\n",
"Please use the 'list-colours' command to see what you can use.")) "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 setPenWidth imgWidth imgHeight =
let width = float32 imgWidth let width = float32 imgWidth
let height = float32 imgHeight let height = float32 imgHeight

4
TestCentre/ConsoleTests.fs

@ -59,7 +59,7 @@
(setPenWidth (Random().Next()) (Random().Next())) > 0.0f (setPenWidth (Random().Next()) (Random().Next())) > 0.0f
[<Property>] [<Property>]
let ``Can build the intended default image specification`` () = let ``Can build the intended default BrushSpec`` () =
let defaultSpec = let defaultSpec =
{ originalPath = loadPath { originalPath = loadPath
savePath = savePath savePath = savePath
@ -71,7 +71,7 @@
defaultSpec = spec defaultSpec = spec
[<Property>] [<Property>]
let ``Can build an image specification as intended`` () = let ``Can build a BrushSpec as intended`` () =
let colourString = randomColourString () let colourString = randomColourString ()
let brush = parseColour colourString let brush = parseColour colourString
let pWidth = float32 (Random().Next()) let pWidth = float32 (Random().Next())

93
TestCentre/LibraryTests.fs

@ -20,13 +20,16 @@
open System.Drawing open System.Drawing
open System.Reflection open System.Reflection
open System.IO open System.IO
open DeathSocket.Domain
let rand = Random () let rand = Random ()
(* These are duplicates from ConsoleTests.fs (both of them). See point (* These are duplicates from ConsoleTests.fs (both of them). See point
about helpers. Tests for checking these locations can be found in 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 loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea"
let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea" let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea"
@ -46,6 +49,10 @@
Intended for horizontal and vertical line tests. *) Intended for horizontal and vertical line tests. *)
let newNum () = rand.Next(1, 1000) let newNum () = rand.Next(1, 1000)
let newPenWidth () = rand.Next (1, 10)
let newRGBANum () = rand.Next (255)
let imagesInLoadingTestArea = let imagesInLoadingTestArea =
Directory.GetFileSystemEntries (loadLocation, "*.png") Directory.GetFileSystemEntries (loadLocation, "*.png")
@ -67,6 +74,17 @@
files files
|> Array.iter (fun f -> File.Delete(f)) |> 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 = module PropertyTests =
open FsCheck.Xunit open FsCheck.Xunit
@ -75,28 +93,68 @@
open DeathSocket.GridPainter open DeathSocket.GridPainter
open TestingHelpers open TestingHelpers
open System.IO 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. *)
[<Property>] [<Property>]
let ``Can apply grid to image and save it`` () = let ``Can apply grid to image and save it using BrushSpec`` () =
(* 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 () resetSavingTestArea ()
let oPath = generateLoadPath () let oPath = generateLoadPath ()
let sPath = generateSavePath oPath let sPath = generateSavePath oPath
let spec = let (spec: BrushSpec) =
{ originalPath = oPath { originalPath = oPath
savePath = sPath savePath = sPath
colour = randomBrush () :?> Brush colour = randomBrush () :?> Brush
penWidth = float32 1 penWidth = float32 (newPenWidth())
rows = 10
columns = 10 }
applyBrushSpecGridAsync spec
|> Async.RunSynchronously
(File.Exists sPath) = true
[<Property>]
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 rows = 10
columns = 10 } columns = 10 }
applyGridAsync spec applyRGBAGridAsync spec
|> Async.RunSynchronously |> Async.RunSynchronously
(File.Exists sPath) = true (File.Exists sPath) = true
[<Property>]
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
[<Property>]
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
[<Property>] [<Property>]
let ``Can return a collection of points which represent a grids horizontal lines`` () = let ``Can return a collection of points which represent a grids horizontal lines`` () =
let result = determineHorizontalLines (newNum()) (newNum()) (newNum()) let result = determineHorizontalLines (newNum()) (newNum()) (newNum())
@ -113,8 +171,9 @@
open Xunit open Xunit
open DeathSocket open DeathSocket
open System 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. 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. If the property test fails, here is a good place to start.
See script.fs (in Test Centre) for information on populating the See script.fs (in Test Centre) for information on populating the
@ -125,6 +184,16 @@
let imagesAreThere = if length < 100 then false else true let imagesAreThere = if length < 100 then false else true
Assert.True imagesAreThere 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. *)
[<Fact>]
let ``SavingTestArea folder can be found`` () =
Assert.True (Directory.Exists saveLocation)
[<Fact>] [<Fact>]
let ``Divide By Zero Exception is thrown when 0 rows is used when determining horizontal lines`` () = let ``Divide By Zero Exception is thrown when 0 rows is used when determining horizontal lines`` () =
let result () = GridPainter.determineHorizontalLines 100 100 0 let result () = GridPainter.determineHorizontalLines 100 100 0

22
TestCentre/Script.fsx

@ -14,7 +14,7 @@ let loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea"
let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea" let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea"
let random = Random() 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 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 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. // You should only need this once.
// Make sure you have passed the above into F# Interactive. // Make sure you have passed the above into F# Interactive.
populateLoadingTestArea () 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 ()
Loading…
Cancel
Save