Browse Source

Merge pull request #8 from CraigOates/0.7

0.7.1
master
Craig Oates 5 years ago committed by GitHub
parent
commit
ab0d9f1427
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      DeathSocket/ColourServices.fs
  2. 10
      DeathSocket/DeathSocket.fsproj
  3. 99
      DeathSocket/Domain.fs
  4. 256
      DeathSocket/GridPainter.fs
  5. 70
      DeathSocket/ImagePrep.fs
  6. 135
      DeathSocket/ImageServices.fs
  7. 27
      DeathSocket/ScratchPad.fsx
  8. 20
      DeathSocket/Validation.fs
  9. 31
      DeathSocket/ValidationServices.fs
  10. 4
      TestCentre/AssemblyInfo.fs
  11. 229
      TestCentre/LibraryTests.fs
  12. BIN
      TestCentre/LoadingTestArea/RequiredInfo/ImageTest1.png
  13. BIN
      TestCentre/LoadingTestArea/RequiredInfo/ImageTest2.png
  14. BIN
      TestCentre/LoadingTestArea/RequiredInfo/ImageTest3.png
  15. BIN
      TestCentre/LoadingTestArea/RequiredInfo/Skia-599x336.png
  16. BIN
      TestCentre/LoadingTestArea/RequiredInfo/SystemDrawing-338x307.png

2
DeathSocket/ColourServices.fs

@ -2,8 +2,6 @@
open System.Drawing
open DeathSocket.Domain
open System
open SkiaSharp
let makeBrushFromRGBASpec (spec: RGBASpec) =
let a = int spec.alpha

10
DeathSocket/DeathSocket.fsproj

@ -4,7 +4,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<Version>0.6-alpha</Version>
<Version>0.7.1-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>
@ -15,15 +15,17 @@
<RepositoryUrl>https://github.com/CraigOates/Death-Socket/tree/master</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<PackageTags>deathsocket craig oates image overlay f#</PackageTags>
<PackageReleaseNotes>This release deprecated the "applyGrid" functions and refactored them into one single function.</PackageReleaseNotes>
<AssemblyVersion>0.6.0.0</AssemblyVersion>
<PackageReleaseNotes>This release fixes the pen thickness scaling bugs in 0.7.</PackageReleaseNotes>
<AssemblyVersion>0.7.1.0</AssemblyVersion>
<PackageIconUrl>https://github.com/CraigOates/Death-Socket/blob/master/.github/Images/death-socket-logo.png</PackageIconUrl>
<FileVersion>0.7.1.0</FileVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="Domain.fs" />
<Compile Include="Validation.fs" />
<Compile Include="ValidationServices.fs" />
<Compile Include="ColourServices.fs" />
<Compile Include="ImagePrep.fs" />
<Compile Include="ImageServices.fs" />
<Compile Include="GridPainter.fs" />
<None Include="ScratchPad.fsx" />

99
DeathSocket/Domain.fs

@ -6,10 +6,11 @@
open System.Drawing
open SkiaSharp
open System.Threading
/// 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
/// This specification is 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.
@ -26,11 +27,11 @@
///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
/// This specification is 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.
/// 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
@ -52,10 +53,10 @@
///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
/// This specification is 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.
/// the SkiaSharp library.
type SkiaSpec =
{ /// The original path of the image which the grid is being added to.
originalPath: string
@ -70,11 +71,53 @@
///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
/// This specification is used to create a gridded image as an SKData
/// buffer. This spec. allows you to use one of the pre-formed
/// SKColors. This makes it quicker and easier to develop your code
/// when compared to SkiaRGBStream. That requires setting individual
/// RGB values. You this spec. if you are using the SkiaSharp library.
type SkiaBufferSpec =
{ /// The location of the image the grid will be applied to
filePath: 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 }
/// This specification is used to create a gridded image as a SKData
/// buffer. This spec. allows you to specify the colour of the grid
/// using individual RGB values. This gives you greater control of your
/// colour choice than a SkiaStreamSpec, which requires a pre-formed
/// SKColor value. you this spec. if you are using the SkiaSharp
/// library.
type SkiaRGBBufferSpec =
{ /// The location of the image the grid will be applied to
filePath: 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 }
/// This specification is 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.
/// if you are using the SkiaSharp library.
type SkiaRGBSpec =
{ /// The original path of the image which the grid is being added to.
originalPath: string
@ -96,10 +139,36 @@
///The number of columns the grid will have.
columns: int }
/// Discriminated Union representing the various specification types
/// A Discriminated Union representing the various specification types
/// Death Socket can use to apply a grid to an image.
type ImageSpec =
/// Spec. which uses System.Drawing and its built-in (colour) Brush.
| Brush of BrushSpec
/// Spec. which uses System.Drawing and individual RGBA values.
| RGBA of RGBASpec
/// Spec. which uses SkiaSharp and its built-in SKColors.
| Skia of SkiaSpec
| SkiaRGB of SkiaRGBSpec
/// Spec. which uses SkiaSharp and individual RGB values.
| SkiaRGB of SkiaRGBSpec
/// Spec. which uses SkiaSharp and its built-in SKColors.
| SkiaBuffer of SkiaBufferSpec
/// Spec. which uses SkiaSharp and individual RGB values.
| SkiaRGBBuffer of SkiaRGBBufferSpec
/// A Discriminated Union denoting a type of image. The type refers
/// to the graphics library used to read the image file.
/// The graphics library determines how the image is read and
/// manipulated in memory and the functions you can perform on it.
/// When creating an ImageType, pass in the file path of the image
/// as a string. If you are on Windows or using Mono, SystemDrawing is
/// the recommended choice. If you using Xamarin, SkiaSharp is the
/// recommended choice.
type ImageType =
/// Denotes an image created using SkiaSharp
| SkiaSharp of string
/// Denotes an image created using System.Drawing
| SystemDrawing of string
type internal longestDimension =
| Width
| Height

256
DeathSocket/GridPainter.fs

@ -13,24 +13,23 @@ namespace DeathSocket
/// functions.
module GridPainter =
open Validation
open ValidationServices
open ImagePrep
open ImageServices
open System
open SkiaSharp
/// <summary>
/// Uses the information included in spec to create a gridded image.
/// It then asynchronously saves it. Uses .jpg or .png formats only.
/// Please note: Any buffer-based spec's (E.G. SkiaBufferSpec) will be
/// ignored by this function.
/// </summary>
/// <param name="spec">
/// The specification used to generate the new gridded image. The
/// ImageSpec is a discriminated union, consisting of a Brush, RGBA,
/// Skia or SkiaRGB spec.
/// </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
/// <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.
@ -51,13 +50,70 @@ namespace DeathSocket
| SkiaRGB sR ->
validateIO sR.originalPath sR.savePath |> ignore
drawSkiaRGBGrid sR
| _ -> printfn "[DEATH SOCKET INFO] Inappropriate ImageSpec used here. Please use none buffer-based spec's when using this function."
with
| :? FileNotFoundException as ex ->
printfn "File could not be found at %s" ex.Message
}
}
// System.Drawing Functions
// ========================================================================
// NOT TESTED.
/// <summary>
/// Determines the current scale an image is viewed at (E.G. scaled
/// preview in image viewer). The (pen) line thickness is then updated
/// to match this preview scale and can be used when drawing a grid
/// line on the scaled preview of the image.
/// </summary>
/// <param name="previewDimension">
/// The width or height of the image when previewed (I.E. in a GUI).
/// </param>
/// <param name="actualDimension">
/// The width or height of the actual image.
/// </param>
/// <param name="lineThickness">
/// The thickness of the pen used to draw the grid line.
/// </param>
/// <remarks>
/// You should find you only need this function when dealing with
/// previewing an image in a GUI. Another thing to note is the
/// SkiaSharp based functions already scale the grid lines for you. So,
/// you should not need to use this function when using them.
/// </remarks>
let scaleLineThickness (previewDimensions: int * int) (actualDimensions: int * int) (lineThickness: double) =
validateDimensions previewDimensions |> ignore
validateDimensions actualDimensions |> ignore
validateLineThickness |> ignore
adjustLineThickness previewDimensions actualDimensions lineThickness
/// <summary>
/// Reads an (jpg or png) image and return its width and height as a
/// tuple, (width * hight).
/// </summary>
/// <param name="imageType">
/// The name of the graphics library used to read and determine the
/// images dimensions.
/// </param>
/// <remarks>
/// The image type is determined by the graphics library used. It is
/// called "ImageType" and not "GraphicsLibUsed" because the image/file
/// information is the thing this function cares about most of all. How
/// it is read and what library it uses it secondary.
///</remarks>
let determineImageDimensions (imgType: ImageType) =
try
match imgType with
| SkiaSharp s ->
validateFilePath s |> ignore
determineSkiaDimensions s
| SystemDrawing d ->
validateFilePath d |> ignore
determineSystemDrawingDimensions d
with
| :? FileNotFoundException as ex ->
printfn "%s" ex.Message
reraise ()
(* System.Drawing Functions
==================================================================== *)
/// <summary>
/// Determines the (Pen) points needed to draw the appropriate number
@ -68,7 +124,9 @@ namespace DeathSocket
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="rows">The number of rows the grid should have.</param>
/// <remarks>You will probably only need these when dealing with GUI's.</remarks>
/// <remarks>
/// You will probably only need these when dealing with GUI's.
/// </remarks>
let determineHorizontalLines (width: int) (height: int) (rows: int) =
createHorizontalLines width height rows
@ -80,68 +138,14 @@ namespace DeathSocket
/// </summary>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="columns">The number of columns the grid should have.</param>
/// <remarks>You will probably only need these when dealing with GUI's.</remarks>
let determineVerticalLines (width: int) (height: int) (columns: int) =
createVerticalLines width height columns
/// <summary>
/// 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 .Net/Mono).
/// </summary>
/// <param name="spec">
/// The specification used to generate the new 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>
[<Obsolete("Method is deprecated, use applyGridToImage instead.")>]
let applyBrushSpecGridAsync (spec: BrushSpec) =
async {
try
validateFilePath spec.originalPath |> ignore
validateSaveFileType spec.savePath |> ignore
drawBrushSpecGrid spec |> ignore
with
| :? FileNotFoundException as ex ->
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. Uses .jpg or .png formats only.
/// Use this function when targeting .Net/Mono).
/// </summary>
/// <param name="spec">
/// The specification used to generate the gridded image.
/// <param name="columns">
/// The number of columns the grid should have.
/// </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>
/// You will probably only need these when dealing with GUI's.
/// </remarks>
[<Obsolete("Method is deprecated, use applyGridToImage instead.")>]
let applyRGBAGridAsync (spec: RGBASpec) =
async {
try
validateFilePath spec.originalPath |> ignore
validateSaveFileType spec.savePath |> ignore
drawRGBAGrid spec
with
| :? FileNotFoundException as ex ->
printfn "File could not be found at %s" ex.Message
}
let determineVerticalLines (width: int) (height: int) (columns: int) =
createVerticalLines width height columns
/// <summary>
/// Creates a Sytsem.Drawing SolidBrush from the individual RGBA values.
@ -163,8 +167,8 @@ namespace DeathSocket
/// Use this function when targeting .Net/Mono).
/// </summary>
/// <param name="spec">
///The specification which the brush is made from.
///</param>
/// The specification which the brush is made from.
/// </param>
/// <remarks>
/// Death Socket uses System.Drawing and not System.Media for colours
/// and brushes.
@ -172,8 +176,44 @@ namespace DeathSocket
let makeSolidBrushFromRGBASpec (spec: RGBASpec) =
makeBrushFromRGBASpec spec
// SkiaSharp Functions
// ========================================================================
(* SkiaSharp Functions
==================================================================== *)
/// <summary>
/// Uses the information included in spec to create a SKData buffer.
/// The buffer will contain a gridded image which you can then use in
/// any way you see fit. Accepted image formats are limited to .jpg
/// and .png. Please note: Any non buffer-based spec's (E.G. SkiaSpec)
/// will be ignored by this function.
/// </summary>
/// <param name="spec">
/// The specification used to generate the new SKData buffer. The
/// ImageSpec is a discriminated union, consisting of a SkiaBuffer and
/// a SkiaRGBBuffer spec.
/// </param>
/// <remarks>
/// Make sure the image, which is used to create the SKData buffer,
/// is not in use or needed by another program/process.
/// This is because it is locked whilst in this function.
/// </remarks>
let createSKDataAsync (spec: ImageSpec) =
async {
try
match spec with
| SkiaBuffer s ->
validateFilePath s.filePath |> ignore
return createSkiaBuffer s
| SkiaRGBBuffer sR ->
validateFilePath sR.filePath |> ignore
return createSkiaRGBBuffer sR
| _ ->
printfn "[DEATH SOCKET INFO] Inappropriate ImageSpec used here. Please use buffer-based spec's when using this function. Returning an empty SKData object."
return SKData.Empty
with
| :? FileNotFoundException as ex ->
printfn "%s" ex.Message |> ignore
return SKData.Empty
}
/// <summary>
/// Determines the (SKPoints) points needed to draw the appropriate
@ -184,8 +224,10 @@ namespace DeathSocket
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="rows">The number of rows the grid should have.</param>
/// <remarks>This function is part of the SkiaSharp functions provided
/// by Death Socket.</remarks>
/// <remarks>
/// This function is part of the SkiaSharp functions provided
/// by Death Socket.
/// </remarks>
let determineSKHorizontalLines (width: int) (height: int) (rows: int) =
createSKHorizontalLines width height rows
@ -199,61 +241,9 @@ namespace DeathSocket
/// <param name="height">The height of the image.</param>
/// <param name="columns">The number of columns the grid should have.
/// </param>
/// <remarks>This function is part of the SkiaSharp functions provided
/// by Death Socket.</remarks>
let determineSKVerticalLines (width: int) (height: int) (columns: int) =
createSKVerticalLines width height columns
/// <summary>
/// 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.
/// </summary>
/// <param name="spec">
/// 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).
/// </param>
/// <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>
[<Obsolete("Method is deprecated, use applyGridToImage instead.")>]
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
}
/// <summary>
/// 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.
/// </summary>
/// <param name="spec">
/// 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).
/// </param>
/// <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.
/// This function is part of the SkiaSharp functions provided by Death
/// Socket.
/// </remarks>
[<Obsolete("Method is deprecated, use applyGridToImage instead.")>]
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
}
let determineSKVerticalLines (width: int) (height: int) (columns: int) =
createSKVerticalLines width height columns

70
DeathSocket/ImagePrep.fs

@ -0,0 +1,70 @@
module internal ImagePrep
open System.IO
open DeathSocket
open SkiaSharp
open System.Drawing
let determineLongestDimension pDims aDims =
let width = (fst pDims) - (fst aDims)
let height = (snd pDims) - (snd aDims)
match width > height with
| true -> Width
| false -> Height
let determineImageScale pDimension aDimension =
(double aDimension) / (double pDimension)
let determineLineScale (lineWidth: double) (scale: double) =
lineWidth / scale
let adjustLineThickness pDimensions aDimensions lineWidth =
match (determineLongestDimension pDimensions aDimensions) with
| Width ->
let thickness =
determineImageScale (fst pDimensions) (fst aDimensions)
|> determineLineScale lineWidth
thickness
| Height ->
let thickness =
determineImageScale (snd pDimensions) (snd aDimensions)
|> determineLineScale lineWidth
thickness
(* 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 determineSkiaDimensions filePath =
use fileStream = File.Open (filePath, FileMode.Open)
use skStream = new SKManagedStream (fileStream)
use bitmap = SKBitmap.Decode (skStream)
(bitmap.Width, bitmap.Height)
(* System.Drawing Functions
======================================================================== *)
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 determineSystemDrawingDimensions filePath =
use bitmap = Bitmap.FromFile filePath
(bitmap.Width, bitmap.Height)

135
DeathSocket/ImageServices.fs

@ -5,23 +5,25 @@
open System.Drawing.Imaging
open DeathSocket
open ColourServices
open ImagePrep
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))|]|]
(* Note on the Use of Repeated Code
===========================================================================
In this file, you will find code which seems duplicated. The areas in
question are mostly the graphics/drawing parts of the file. Before you
consider refactoring this code, please note the memory footprint of this
library. By using "use", the objects created are diposed of properly. This,
also, means it is difficult to pass/return them from one function to the
next. Therefore, the decision made was to accept the duplicated code in
exchange for a smaller memory footprint. Several (large) undisposed images
lurking in an end users RAM can grind their computer to a halt. On top of
that, they have no means to make alterations to the code. *)
(* SkiaSharp Functions
======================================================================== *)
// Does not work when using SkiaSharp NuGet v. 1.68
let drawSkiaGrid (spec: SkiaSpec) =
use fileStream = File.Open (spec.originalPath, FileMode.Open)
use skStream = new SKManagedStream (fileStream)
@ -60,6 +62,7 @@
use saveStream = File.OpenWrite (spec.savePath)
data.SaveTo (saveStream)
// Does not work when using SkiaSharp NuGet v. 1.68
let drawSkiaRGBGrid spec =
use fileStream = File.Open (spec.originalPath, FileMode.Open)
use skStream = new SKManagedStream (fileStream)
@ -99,31 +102,100 @@
use saveStream = File.OpenWrite (spec.savePath)
data.SaveTo (saveStream)
// System.Drawing Functions
// ========================================================================
// Does not work when using SkiaSharp NuGet v. 1.68
let createSkiaBuffer (spec: SkiaBufferSpec) =
use fileStream = File.Open (spec.filePath, 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 ()
let data =
match Path.GetExtension (spec.filePath) with
| ".jpg" | ".JPG" -> snapshot.Encode (SKEncodedImageFormat.Jpeg, 100)
| ".png"| ".PNG" -> snapshot.Encode (SKEncodedImageFormat.Png, 100)
| _ -> invalidArg "filePath" "The file type must be a .jpg or .png file."
// (data) Buffer returned -- does not dispose automatically.
data
// Does not work when using SkiaSharp NuGet v. 1.68
let createSkiaRGBBuffer (spec: SkiaRGBBufferSpec) =
use fileStream = File.Open (spec.filePath, 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)
let createHorizontalLines width height rows =
let interval = height / rows
[| for point in 1 .. (rows - 1) ->
[|Point (0, (interval * point))
Point (width, (interval * point) )|]|]
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 ()
let data =
match Path.GetExtension (spec.filePath) with
| ".jpg" | ".JPG" -> snapshot.Encode (SKEncodedImageFormat.Jpeg, 100)
| ".png"| ".PNG" -> snapshot.Encode (SKEncodedImageFormat.Png, 100)
| _ -> invalidArg "filePath" "The file type must be a .jpg or .png file."
// (data) Buffer returned -- does not dispose automatically.
data
let createVerticalLines width height columns =
let interval = width / columns
[| for point in 1 .. (columns - 1) ->
[| Point ((interval * point), 0)
Point ((interval * point), height)|]|]
(* System.Drawing Functions
======================================================================== *)
(* 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 listing them all, just assume any function
with a "*Spec" type as a parameter will use this "temp" file. *)
with indexed pixels. Instead of listing them all (future additions), 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)
use clone =
temp.Clone(new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb)
use graphics = Graphics.FromImage(clone)
use pen = new Pen (spec.colour, width = spec.penWidth)
graphics.DrawImage(original,new Rectangle(0, 0, clone.Width, clone.Height))
@ -138,7 +210,8 @@
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 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))

27
DeathSocket/ScratchPad.fsx

@ -1,26 +1,29 @@
// These two paths need adjusting to match your computer.
// These paths need adjusting to match your computer.
#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"
// Currently not working...
//#r @"C:/Users/craig/.nuget/packages/skiasharp/1.60.3/runtimes/win-x64/native/libSkiaSharp.dll"
#load "Domain.fs"
#load "Validation.fs"
#load "ValidationServices.fs"
#load "ColourServices.fs"
#load "ImagePrep.fs"
#load "ImageServices.fs"
#load "GridPainter.fs"
open System.Drawing
open System
open DeathSocket
open Validation
open ImageServices
open System.Drawing
open SkiaSharp
open ValidationServices
open ImagePrep
open ImageServices
open DeathSocket
(* 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. *)
a new image. *)
let desktop = Environment.GetFolderPath (Environment.SpecialFolder.Desktop)
@ -40,6 +43,14 @@ let skVerticalLines = createSKVerticalLines 120 450 22
(* You will need to provide the image and specify its load/save location.
Death Socket assumes either JPEG or PNG files.*)
let dimensions =
//SkiaSharp (desktop + "/test.jpg")
SystemDrawing (desktop + "/test.jpg")
|> GridPainter.determineImageDimensions
// Change the line thickness (the last parameter) to whatever you want.
let scaledPen = GridPainter.scaleLineThickness (100, 100) dimensions 8.0
// Brush Specification (uses System.Drawing)
Brush ({ originalPath = desktop + "/test.jpg"
savePath = desktop + "/grid.png"

20
DeathSocket/Validation.fs

@ -1,20 +0,0 @@
module internal Validation
open System.IO
let validateFilePath path =
match File.Exists path with
| true -> ()
| false -> raise (new FileNotFoundException (path + " could not be found."))
let validateSaveFileType file =
match Path.GetExtension file with
| ".jpg" -> ()
| ".JPG" -> ()
| ".png" -> ()
| ".PNG" -> ()
| _ -> invalidArg "savePath" "The file type must be a .jpg or .png file."
let validateIO iPath oPath =
validateFilePath iPath
validateSaveFileType oPath

31
DeathSocket/ValidationServices.fs

@ -0,0 +1,31 @@
module internal ValidationServices
open System.IO
let validateFilePath path =
match File.Exists path with
| true -> ()
| false -> raise (new FileNotFoundException ("No file found at " + path))
let validateSaveFileType file =
match Path.GetExtension file with
| ".jpg" -> ()
| ".JPG" -> ()
| ".png" -> ()
| ".PNG" -> ()
| _ -> invalidArg "savePath" "The file type must be a .jpg or .png file."
let validateIO iPath oPath =
validateFilePath iPath
validateSaveFileType oPath
let validateDimensions dimensions =
match dimensions with
| (0, _) -> invalidArg "Width" "Width must be greater than 0."
| (_, 0) -> invalidArg "Height" "Height must be greater than 0."
| (_, _) -> ()
let validateLineThickness thickness =
match thickness with
| thickness when thickness <= 0.0 -> invalidArg "LineThickness" "LineThickness must be greater than 0."
| _ -> ()

4
TestCentre/AssemblyInfo.fs

@ -34,8 +34,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:
// [<assembly: AssemblyVersion("1.0.*")>]
[<assembly: AssemblyVersion("1.0.0.0")>]
[<assembly: AssemblyFileVersion("1.0.0.0")>]
[<assembly: AssemblyVersion("0.7.1.0")>]
[<assembly: AssemblyFileVersion("0.7.1.0")>]
do
()

229
TestCentre/LibraryTests.fs

@ -34,6 +34,18 @@
let loadLocation = __SOURCE_DIRECTORY__ + "/LoadingTestArea"
let saveLocation = __SOURCE_DIRECTORY__ + "/SavingTestArea"
let validSkiaImagePath =
(loadLocation + "/RequiredInfo/Skia-599x336.png")
let validSystemDrawingImagePath =
(loadLocation + "/RequiredInfo/SystemDrawing-338x307.png")
let validImagePath =
(loadLocation + "/RequiredInfo/ImageTest1.png")
let validImagePath2 =
(loadLocation + "/RequiredInfo/ImageTest2.png")
let validImagePath3 =
(loadLocation + "/RequiredInfo/ImageTest3.png")
let invalidImagePath = "invalid.png"
let allColours =
let properties =
typeof<Brushes>.GetProperties
@ -99,12 +111,12 @@
module PropertyTests =
open FsCheck.Xunit
open DeathSocket
open System.IO
open System.Drawing
open DeathSocket
open DeathSocket.GridPainter
open TestingHelpers
open System.IO
open FsCheck.Xunit
(* With regards to the "saving images" tests, you should end up with
one image left over in the SavingTestArea folder. Comment out the
@ -175,6 +187,7 @@
let result = determineVerticalLines (newNum()) (newNum()) (newNum())
result.Length > 0
// This test fails when using SkiaSharp 1.68
[<Property>]
let ``Can apply grid to image and save it using SkiaSpec`` () =
resetSavingTestArea()
@ -190,6 +203,7 @@
|> Async.RunSynchronously
(File.Exists sPath) = true
// This test fails when using SkiaSharp 1.68
[<Property>]
let ``Can apply grid to image and save it using SkiaRGBSpec`` () =
resetSavingTestArea()
@ -217,17 +231,188 @@
let result = determineSKVerticalLines (newNum()) (newNum()) (newNum())
result.Length > 0
[<Property>]
let ``Can create a populated SKData buffer when calling createSKDataAsync with SkiaBuffer`` () =
let result () =
SkiaBuffer ({ filePath = validImagePath
skColour = randomSKColour ()
penWidth = float32 (newPenWidth())
rows = newNum ()
columns = newNum () })
|> createSKDataAsync
|> Async.RunSynchronously
(result ()).IsEmpty = false
[<Property>]
let ``Can create a populated SKData buffer when calling createSKDataAsync with SkiaRGBBuffer`` () =
let result () =
SkiaRGBBuffer ({ filePath = validImagePath3
red = float32 (newNum())
green = float32 (newNum())
blue = float32 (newNum())
penWidth = float32 (newPenWidth())
rows = newNum ()
columns = newNum () })
|> createSKDataAsync
|> Async.RunSynchronously
(result ()).IsEmpty = false
module UnitTests =
open TestingHelpers
open Xunit
open DeathSocket
open System
open System.IO
open System.Drawing
open Xunit
open DeathSocket
open SkiaSharp
open TestingHelpers
[<Fact>]
let ``An empty SKData object is returned when createSKDataAsync cannot find file when using SkiaBuffer`` () =
let result =
SkiaBuffer ({ filePath = invalidImagePath
penWidth = float32 1
skColour = SKColors.Empty
rows = 1
columns = 1 })
|> GridPainter.createSKDataAsync
|> Async.RunSynchronously
Assert.Equal(SKData.Empty, result)
[<Fact>]
let ``An empty SKData object is returned when createSKDataAsync cannot find file when using SkiaRGBBuffer`` () =
let result =
SkiaRGBBuffer ({ filePath = invalidImagePath
penWidth = float32 1
red = float32 1
green = float32 1
blue = float32 1
rows = 1
columns = 1 })
|> GridPainter.createSKDataAsync
|> Async.RunSynchronously
Assert.Equal(SKData.Empty, result)
[<Fact>]
let ``An empty SKData object is returned when calling createSKDataAsync with SkiaRGB`` () =
let result =
SkiaRGB ({ originalPath = validImagePath2
savePath= validImagePath
red = float32 1
green = float32 1
blue = float32 1
penWidth = float32 1
rows = 1
columns = 1})
|> GridPainter.createSKDataAsync
|> Async.RunSynchronously
Assert.Equal(SKData.Empty, result)
[<Fact>]
let ``An empty SKData object is returned when calling createSKDataAsync with Skia`` () =
let result =
Skia ({ originalPath = validImagePath3
savePath= validImagePath
skColour = SKColors.Empty
penWidth = float32 1
rows = 1
columns = 1 })
|> GridPainter.createSKDataAsync
|> Async.RunSynchronously
Assert.Equal(SKData.Empty, result)
[<Fact>]
let ``An empty SKData object is returned when calling createSKDataAsync with Brush`` () =
let result =
Brush ({ originalPath = validImagePath
savePath= validImagePath
colour = Brushes.AliceBlue
penWidth = float32 1
rows = 1
columns = 1 })
|> GridPainter.createSKDataAsync
|> Async.RunSynchronously
Assert.Equal(SKData.Empty, result)
[<Fact>]
let ``An empty SKData object is returned when calling createSKDataAsync with RGBA`` () =
let result =
RGBA ({ originalPath = validImagePath
savePath= validImagePath
red = float 1
green = float 1
blue = float 1
alpha = float 1
penWidth = float32 1
rows = 1
columns = 1 })
|> GridPainter.createSKDataAsync
|> Async.RunSynchronously
Assert.Equal(SKData.Empty, result)
[<Fact>]
let ``Argument Exception is thrown when actualDimensions are set to 0 when calling scaleLineThickness`` () =
let result () =
GridPainter.scaleLineThickness (1, 0) (0, 0) 1.0
Assert.Throws<ArgumentException>(fun () -> result () |> ignore)
[<Fact>]
let ``Argument Exception is thrown when lineThickness is set to 0 when calling scaleLineThickness`` () =
let result () =
GridPainter.scaleLineThickness (1, 0)(1, 0) 0.0
Assert.Throws<ArgumentException>(fun () -> result () |> ignore)
[<Fact>]
let ``Argument Exception is thrown when previewDimensions are set to 0 when calling scaleLineThickness`` () =
let result () =
GridPainter.scaleLineThickness (0, 0) (1, 0) 1.0
Assert.Throws<ArgumentException>(fun () -> result () |> ignore)
[<Fact>]
let ``Can determine image width using SkiaSharp`` () =
let dimensions =
SkiaSharp (validSkiaImagePath)
|> GridPainter.determineImageDimensions
let result () = (fst dimensions) = 599
Assert.True (result ())
[<Fact>]
let ``Can determine image height using SkiaSharp`` () =
let dimensions =
SkiaSharp (validSkiaImagePath)
|> GridPainter.determineImageDimensions
let result () = (snd dimensions) = 336
Assert.True (result ())
[<Fact>]
let ``Can determine image width using SystemDrawing`` () =
let dimensions =
SystemDrawing (validSystemDrawingImagePath)
|> GridPainter.determineImageDimensions
let result () = (fst dimensions) = 338
Assert.True (result ())
[<Fact>]
let ``Can determine image height using SystemDrawing`` () =
let dimensions =
SystemDrawing (validSystemDrawingImagePath)
|> GridPainter.determineImageDimensions
let result () = (snd dimensions) = 307
Assert.True (result ())
[<Fact>]
let ``Divide By Zero Exception is thrown when 0 rows is used when determining horizontal lines`` () =
let result () = GridPainter.determineHorizontalLines 100 100 0
Assert.Throws<DivideByZeroException>(fun () -> result () |> ignore)
[<Fact>]
let ``Divide By Zero Exception is thrown when 0 columns is used when determining vertical lines`` () =
let result () = GridPainter.determineVerticalLines 100 100 0
Assert.Throws<DivideByZeroException>(fun () -> result () |> ignore)
(* 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.
If the property tests fails, here is a good place to start.
See script.fs (in Test Centre) for information on populating the
LoadingTestArea folder. *)
[<Fact>]
@ -236,6 +421,24 @@
let imagesAreThere = if length < 100 then false else true
Assert.True imagesAreThere
[<Fact>]
let ``Pen thickness scales down to match preview image dimensions`` () =
let result () =
GridPainter.scaleLineThickness (100, 100) (200, 200) 10.0
Assert.Equal (5.0, (result ()))
[<Fact>]
let ``Pen thickness scales up to match preview image dimensions`` () =
let result () =
GridPainter.scaleLineThickness (200, 200) (100, 100) 10.0
Assert.Equal (20.0, (result ()))
[<Fact>]
let ``Pen thickness remains the same when preview image matches actual dimensions`` () =
let result () =
GridPainter.scaleLineThickness (100, 100) (100, 100) 10.0
Assert.Equal (10.0, (result ()))
(* 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
@ -244,14 +447,4 @@
for extra context. *)
[<Fact>]
let ``SavingTestArea folder can be found`` () =
Assert.True (Directory.Exists saveLocation)
[<Fact>]
let ``Divide By Zero Exception is thrown when 0 rows is used when determining horizontal lines`` () =
let result () = GridPainter.determineHorizontalLines 100 100 0
Assert.Throws<DivideByZeroException>(fun () -> result () |> ignore)
[<Fact>]
let ``Divide By Zero Exception is thrown when 0 columns is used when determining vertical lines`` () =
let result () = GridPainter.determineVerticalLines 100 100 0
Assert.Throws<DivideByZeroException>(fun () -> result () |> ignore)
Assert.True (Directory.Exists saveLocation)

BIN
TestCentre/LoadingTestArea/RequiredInfo/ImageTest1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
TestCentre/LoadingTestArea/RequiredInfo/ImageTest2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
TestCentre/LoadingTestArea/RequiredInfo/ImageTest3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
TestCentre/LoadingTestArea/RequiredInfo/Skia-599x336.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
TestCentre/LoadingTestArea/RequiredInfo/SystemDrawing-338x307.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Loading…
Cancel
Save