Browse Source

add SkiaSharp versions of the System.Drawing functionality.

None of the SkiaSharp code has tests in Test Centre.
master
Craig Oates 6 years ago
parent
commit
0e381f824c
  1. 8
      DeathSocket/ColourServices.fs
  2. 60
      DeathSocket/Domain.fs
  3. 151
      DeathSocket/GridPainter.fs
  4. 147
      DeathSocket/ImageServices.fs
  5. 8
      DeathSocket/Validation.fs
  6. 33
      DeathSocketCLI/Commands.fs
  7. 79
      DeathSocketCLI/Validation.fs

8
DeathSocket/ColourServices.fs

@ -15,10 +15,4 @@
let makeBrushFromRGBA r g b a =
let colour = Color.FromArgb (a, r, g, b)
new SolidBrush (colour)
let makeSkiaColour r g b =
let red = BitConverter.GetBytes (int r)
let green = BitConverter.GetBytes (int g)
let blue = BitConverter.GetBytes (int b)
new SKColor (red.[0], green.[0], blue.[0])
new SolidBrush (colour)

60
DeathSocket/Domain.fs

@ -5,14 +5,19 @@
module Domain =
open System.Drawing
open SkiaSharp
/// The specification which uses System.Drawing brush to draw a grid.
/// The specification used to note the orignial file's location and the
/// changes the user would like to make to it. (including the save
/// location of the modified version). Use this spec if you are using
/// System.Drawing.
type BrushSpec =
{ /// The original path of the image which the grid is being added to.
originalPath: string
/// The location of the new gridded image.
savePath: string
/// The (System.Drawing) brush used to draw the grid. This determines the colour.
/// The (System.Drawing) brush used to draw the grid.
/// This determines the colour of the grid.
colour: Brush
/// The thickness of the line on the grid.
penWidth: float32
@ -21,8 +26,11 @@
///The number of columns the grid will have.
columns: int }
/// The specification which uses includes individual RGBA values to
/// draw a grid.
/// The specification used to note the orignial file's location and the
/// changes the user would like to make to it (including the save
/// location of the modified version). This specification includes
/// individual RGBA values to describe the grid's colour. Use this spec.
/// if you are using System.Drawing.
type RGBASpec =
{ /// The original path of the image which the grid is being added to.
originalPath: string
@ -42,4 +50,48 @@
/// The number of rows the grid will have.
rows: int
///The number of columns the grid will have.
columns: int }
/// The specification used to note the orignial file's location and the
/// changes the user would like to make to it (including the save
/// location of the modified version). Use this spec. if you are using
/// the Skia-Sharp library.
type SkiaSpec =
{ /// The original path of the image which the grid is being added to.
originalPath: string
/// The location of the new gridded image.
savePath: string
/// The (SkiaSharp) colour used to draw the grid.
skColour: SKColor
/// The thickness of the line on the grid.
penWidth: float32
/// The number of rows the grid will have.
rows: int
///The number of columns the grid will have.
columns: int }
/// The specification used to note the orignial file's location and the
/// changes the user would like to make to it (including the save
/// location of the modified version). This specification includes
/// individual RGB values to describe the grid's colour. Use this spec.
/// if you are using the Skia-Sharp library.
type SkiaRGBSpec =
{ /// The original path of the image which the grid is being added to.
originalPath: string
/// The location of the new gridded image.
savePath: string
/// The amount of red (RGB) the grid's line will have.
/// (0 = no red, 255 = red turned up to eleven).
red: float32
/// The amount of green (RGB) the grid's line will have.
/// (0 = no green, 255 = green turned up to eleven).
green: float32
/// The amount of blue (RGB) the grid's line will have.
/// (0 = no blue, 255 = blue turned up to eleven).
blue: float32
/// The thickness of the line on the grid.
penWidth: float32
/// The number of rows the grid will have.
rows: int
///The number of columns the grid will have.
columns: int }

151
DeathSocket/GridPainter.fs

@ -4,18 +4,51 @@ namespace DeathSocket
open ColourServices
/// Provides functions which help draw gridded overlays onto images.
/// Grid Painter, and all of Death Socket, uses the System.Drawing brushes/colours.
/// If you are using System.Media brushes and colour, you will need to convert them.
/// Death Socket, uses System.Drawing's brushes and colours.
/// If you are using System.Media brushes and colour, you will need to
/// convert them. Death Socket, also, uses SkiaSharp for cross platform
/// functionality. If you are using Death Socket on Windows Desktop, use
/// functions which do not include "Skia" in their name. If you are using
/// Death Socket in a Xamarin/UWP project you should use the SkiaSharp
/// functions.
module GridPainter =
open Validation
open ImageServices
// System.Drawing Functions
// ========================================================================
/// <summary>
/// Determines the (Pen) points needed to draw the appropriate number
/// of horizontal lines (I.E. rows). Each item in the array includes a
/// start and end co-ordinate (point) for each line.
/// Use this function when targeting .Net/Mono).
/// </summary>
/// <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>
let determineHorizontalLines (width: int) (height: int) (rows: int) =
createHorizontalLines width height rows
/// <summary>
/// Determines the (Pen) points needed to draw the appropriate number
/// of vertical lines (I.E. columns). Each item in the array includes a
/// start and end co-ordinate (point) for each line.
/// Use this function when targeting Windows Desktop (or Mono).
/// </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.
/// Please stick to .bmp, .jpg or .png files.
/// The other (image) file types have not been tested.
/// It then asynchronously saves it. Uses .jpg or .png formats only.
/// Use this function when targeting .Net/Mono).
/// </summary>
/// <param name="spec">
/// The specification used to generate the new gridded image
@ -33,7 +66,7 @@ namespace DeathSocket
async {
try
validateFilePath spec.originalPath |> ignore
validatFileType spec.savePath |> ignore
validateSaveFileType spec.savePath |> ignore
drawBrushSpecGrid spec |> ignore
with
| :? FileNotFoundException as ex ->
@ -42,8 +75,8 @@ namespace DeathSocket
/// <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.
/// 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.
@ -61,7 +94,7 @@ namespace DeathSocket
async {
try
validateFilePath spec.originalPath |> ignore
validatFileType spec.savePath |> ignore
validateSaveFileType spec.savePath |> ignore
drawRGBAGrid spec
with
| :? FileNotFoundException as ex ->
@ -70,6 +103,7 @@ namespace DeathSocket
/// <summary>
/// Creates a Sytsem.Drawing SolidBrush from the individual RGBA values.
/// Use this function when targeting .Net/Mono).
/// </summary>
/// <param name="r">The red value.</param>
/// <param name="g">The green value.</param>
@ -79,10 +113,12 @@ namespace DeathSocket
/// 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
let makeSolidBrushFromRGBA (r: int) (g: int) (b:int) (a: int) =
makeBrushFromRGBA r g b a
/// <summary>
/// Creates a System.Drawing SolidBrush from a RGBASpec.
/// Use this function when targeting .Net/Mono).
/// </summary>
/// <param name="spec">
///The specification which the brush is made from.
@ -91,28 +127,97 @@ namespace DeathSocket
/// Death Socket uses System.Drawing and not System.Media for colours
/// and brushes.
/// </remarks>
let makeSolidBrushFromRGBASpec spec = makeBrushFromRGBASpec spec
let makeSolidBrushFromRGBASpec (spec: RGBASpec) =
makeBrushFromRGBASpec spec
// SkiaSharp Functions
// ========================================================================
// NOT TESTED
/// <summary>
/// Determines the (Pen) points needed to draw the appropriate number of horizontal lines (I.E. rows).
/// Each item in the array includes a start and end co-ordinate (point) for each line.
/// Determines the (SKPoints) points needed to draw the appropriate
/// number of horizontal lines (I.E. rows). Each item in the array
/// included a start and end co-ordinate (SKPoint) for each line.
/// Use this function when targeting Xamarin/UWP/.Net Core (SkiaSharp).
/// </summary>
/// <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>
let determineHorizontalLines width height rows =
createHorizontalLines width height rows
/// <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
// NOT TESTED
/// <summary>
/// Determines the (Pen) points needed to draw the appropriate number of vertical lines (I.E. columns).
/// Each item in the array includes a start and end co-ordinate (point) for each line.
/// Determines the (SKPoints) points needed to draw the appropriate
/// number of vertical lines (I.E. columns). Each item in the array
/// included a start and end co-ordinate (SKPoint) for each line.
/// Use this function when targeting Xamarin/UWP/.Net Core (SkiaSharp).
/// </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 height columns =
createVerticalLines width height columns
/// <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
// NOT TESTED
/// <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>
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
}
// NOT TESTED
let drawskia () = drawSkiaGrid ()
/// <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>
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
}

147
DeathSocket/ImageServices.fs

@ -7,6 +7,101 @@
open ColourServices
open SkiaSharp
// SkiaSharp Functions
// ========================================================================
let createSKHorizontalLines width height rows =
let interval = float32 (height / rows)
[|for point in 1 .. (rows - 1) ->
[| SKPoint ((float32 0), (interval * (float32 point)))
SKPoint ((float32 width), (interval * (float32 point)))|]|]
let createSKVerticalLines width height columns =
let interval = float32 (width / columns)
[| for point in 1 .. (columns - 1) ->
[| SKPoint ((interval * (float32 point)), (float32 0))
SKPoint ((interval * (float32 point)), (float32 height))|]|]
let drawSkiaGrid (spec: SkiaSpec) =
use fileStream = File.Open (spec.originalPath, FileMode.Open)
use skStream = new SKManagedStream (fileStream)
use bitmap = SKBitmap.Decode (skStream)
use shader =
SKShader.CreateBitmap (bitmap, SKShaderTileMode.Mirror, SKShaderTileMode.Mirror)
let imageInfo = new SKImageInfo (bitmap.Width, bitmap.Height)
use surface = SKSurface.Create (imageInfo)
use canvas = surface.Canvas
canvas.Clear (SKColors.White)
use imageFill = new SKPaint ()
imageFill.Style <- SKPaintStyle.Fill
imageFill.Shader <- shader
canvas.DrawPaint (imageFill)
use stroke = new SKPaint ()
stroke.Style <- SKPaintStyle.Stroke
stroke.StrokeWidth <- spec.penWidth
stroke.Color <- spec.skColour
let horizontalLines =
createSKHorizontalLines (bitmap.Width) (bitmap.Height) (spec.rows)
let verticalLines =
createSKVerticalLines (bitmap.Width) (bitmap.Height) (spec.columns)
for hLine in horizontalLines do
canvas.DrawLine (hLine.[0], hLine.[1], stroke)
for vLine in verticalLines do
canvas.DrawLine (vLine.[0], vLine.[1], stroke)
use snapshot = surface.Snapshot ()
use data =
match Path.GetExtension (spec.savePath) with
| ".jpg" | ".JPG" -> snapshot.Encode (SKEncodedImageFormat.Jpeg, 100)
| ".png"| ".PNG" -> snapshot.Encode (SKEncodedImageFormat.Png, 100)
| _ -> invalidArg "savePath" "The file type must be a .jpg or .png file."
use saveStream = File.OpenWrite (spec.savePath)
data.SaveTo (saveStream)
let drawSkiaRGBGrid spec =
use fileStream = File.Open (spec.originalPath, FileMode.Open)
use skStream = new SKManagedStream (fileStream)
use bitmap = SKBitmap.Decode (skStream)
use shader =
SKShader.CreateBitmap (bitmap, SKShaderTileMode.Mirror, SKShaderTileMode.Mirror)
let imageInfo = new SKImageInfo (bitmap.Width, bitmap.Height)
use surface = SKSurface.Create (imageInfo)
use canvas = surface.Canvas
canvas.Clear (SKColors.White)
use imageFill = new SKPaint ()
imageFill.Style <- SKPaintStyle.Fill
imageFill.Shader <- shader
canvas.DrawPaint (imageFill)
use stroke = new SKPaint ()
stroke.Style <- SKPaintStyle.Stroke
stroke.StrokeWidth <- spec.penWidth
stroke.Color <-
new SKColor ((byte spec.red), (byte spec.green), (byte spec.blue))
let horizontalLines =
createSKHorizontalLines (bitmap.Width) (bitmap.Height) (spec.rows)
let verticalLines =
createSKVerticalLines (bitmap.Width) (bitmap.Height) (spec.columns)
for hLine in horizontalLines do
canvas.DrawLine (hLine.[0], hLine.[1], stroke)
for vLine in verticalLines do
canvas.DrawLine (vLine.[0], vLine.[1], stroke)
use snapshot = surface.Snapshot ()
use data =
match Path.GetExtension (spec.savePath) with
| ".jpg" | ".JPG" -> snapshot.Encode (SKEncodedImageFormat.Jpeg, 100)
| ".png"| ".PNG" -> snapshot.Encode (SKEncodedImageFormat.Png, 100)
| _ -> invalidArg "savePath" "The file type must be a .jpg or .png file."
use saveStream = File.OpenWrite (spec.savePath)
data.SaveTo (saveStream)
// System.Drawing Functions
// ========================================================================
let createHorizontalLines width height rows =
let interval = height / rows
[| for point in 1 .. (rows - 1) ->
@ -19,7 +114,7 @@
[| Point ((interval * point), 0)
Point ((interval * point), height)|]|]
(* Note on Use of Temp File in Functions which Add A Grid Overlay
(* Note on Use of Temp. File in Functions which Add A Grid Overlay
===========================================================================
The temp. file is used in the functions below are there to convert images
with indexed pixels. Instead of listing them all, just assume any function
@ -53,52 +148,4 @@
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)
// You are up to here. Need to draw lines and add image.
let drawSkiaGrid () =
// Set Canvas/Surface
let imageInfo = new SKImageInfo (2592, 1456)
use surface = SKSurface.Create(imageInfo)
let canvas = surface.Canvas
canvas.Clear (SKColors.White)
// Set SKPaint Objects
use stroke = new SKPaint ()
stroke.Style <- SKPaintStyle.Stroke
stroke.StrokeWidth <- float32 3
//stroke.Color <- SKColors.BlueViolet
stroke.Color <- makeSkiaColour 123 123 123
// Note about generating colours in Skia Sharp.
//stroke.Color <- new SKColor (byte 1000, byte 5111, byte 4225, byte 255)
use imageFill = new SKPaint ()
imageFill.Style <- SKPaintStyle.Fill
// Add Bitmap
let fileStream = File.Open (@"C:\Users\craig\Desktop\test.jpg", FileMode.Open)
use skStream = new SKManagedStream (fileStream)
use bitmap = SKBitmap.Decode (skStream)
use shader = SKShader.CreateBitmap (bitmap, SKShaderTileMode.Mirror, SKShaderTileMode.Mirror)
imageFill.Shader <- shader
canvas.DrawPaint (imageFill)
// Draw Lines
let horizontalLines = createHorizontalLines 2592 1456 6
let verticalLines = createVerticalLines 2592 1456 9
for hLine in horizontalLines do
let x1 = float32 hLine.[0].X
let y1 = float32 hLine.[0].Y
let x2 = float32 hLine.[1].X
let y2 = float32 hLine.[1].Y
canvas.DrawLine (x1, y1, x2, y2, stroke)
for vLine in verticalLines do
let x1 = float32 vLine.[0].X
let y1 = float32 vLine.[0].Y
let x2 = float32 vLine.[1].X
let y2 = float32 vLine.[1].Y
canvas.DrawLine (x1, y1, x2, y2, stroke)
use snapshot = surface.Snapshot ()
use data = snapshot.Encode (SKEncodedImageFormat.Png, 100)
use saveStream = File.OpenWrite (@"C:\Users\craig\Desktop\test-complete.jpg")
data.SaveTo (saveStream)
printfn "Skia Sharp image saved."
clone.Save (spec.savePath)

8
DeathSocket/Validation.fs

@ -7,14 +7,10 @@
| true -> ()
| false -> raise (new FileNotFoundException (path + " could not be found."))
let validatFileType file =
let validateSaveFileType file =
match Path.GetExtension file with
| ".bmp" -> ()
| ".BMP" -> ()
| ".jpg" -> ()
| ".JPG" -> ()
| ".png" -> ()
| ".PNG" -> ()
| ".tif" -> ()
| ".TIF" -> ()
| _ -> invalidArg "savePath" "The file type must be a .bmp, .jpg, .png or .tif file."
| _ -> invalidArg "savePath" "The file type must be a .jpg or .png file."

33
DeathSocketCLI/Commands.fs

@ -1,6 +1,7 @@
namespace Commands
module ConsoleCommands =
open System
open DeathSocket.GridPainter
open DeathSocketCLI.Validation
@ -92,6 +93,34 @@
let lc () =``list-colours`` ()
let``skia-test`` () =
drawskia ()
(* SkiaSharp Command-Methods -- NOT FOR MASS CONSUMPTION
=======================================================================
These command-methods will not show up in the help section. These
command-methods are here for testing purposes. They mimic the command-
methods above, apart from using SkiaSharp. So, including these command-
methods will create a sense of duplicated functionality. Seeing as you
are reading this, it is assumed you can understand it. If so, please
feel free to use them -- just keep their exposure to a minimum.*)
// NOT TESTED
let ``list-skia-colours`` () =
printfn "[INFO.] Listing available SkiaSharp colours..."
for item in skiaColourList do
printfn "%s" item.Key
showEndOfCommandMessage
// NOT TESTED
let``add-skia-grid`` imgPath numRows numColumns pWidth colour newPath =
printfn "[INFO.] Adding SkiaSharp grid to image..."
buildSkiaSpec imgPath numRows numColumns pWidth colour newPath
|> applySkiaGridAsync
|> Async.Start
showEndOfCommandMessage
// NOT TESTED
let``add-skia-rgb-grid`` imgPath numRows numColumns pWidth r g b newPath =
printfn "[INFO.] Adding SkiaSharp grid to image..."
buildSkiaRGBSpec imgPath numRows numColumns pWidth r g b newPath
|> applySkiaRGBGridAsync
|> Async.Start
showEndOfCommandMessage

79
DeathSocketCLI/Validation.fs

@ -4,6 +4,19 @@
open System.Drawing
open System
open System.IO
open SkiaSharp
(* 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
if (width >= height) then
height * (float32 0.002)
else width * (float32 0.002)
// System.Drawing Functions
// ========================================================================
let colourList =
[ "blue", Brushes.AliceBlue
@ -32,16 +45,7 @@
(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
if (width >= height) then
height * (float32 0.002)
else width * (float32 0.002)
let buildSpec imgPath numRows numColumns pWidth colour newPath =
let buildSpec (imgPath) (numRows) (numColumns) (pWidth) (colour) (newPath) : BrushSpec =
{ originalPath = imgPath
savePath = newPath
colour = parseColour colour
@ -52,13 +56,60 @@
let buildDefaultSpec imgPath newPath =
use stream = new FileStream (imgPath, FileMode.Open)
use image = Image.FromStream (stream, false, false)
let spec =
let spec : BrushSpec =
{ originalPath = imgPath
savePath = newPath
colour = Brushes.White
penWidth = setPenWidth (image.Width) (image.Height)
rows = 10
columns = 10 }
stream.Dispose ()
image.Dispose ()
spec
spec
// SkiaSharp Functions
// ========================================================================
let skiaColourList =
[ "blue", SKColors.AliceBlue
"brown", SKColors.Brown
"black", SKColors.Black
"gray", SKColors.Gray
"grey", SKColors.Gray
"green", SKColors.Green
"purple", SKColors.Purple
"red", SKColors.Red
"white", SKColors.White
"yellow", SKColors.Yellow ]
|> Map.ofList
let isSkiaColourValid (colour: string) =
skiaColourList
|> Map.containsKey (colour.ToLower())
let parseSkiaColour colour =
match (isSkiaColourValid colour) with
| true ->
skiaColourList
|> Map.find (colour.ToLower())
| false ->
invalidArg "Colour"
(String.Concat("The colour specifed is invalid.\n",
"Please use the 'list-skia-colours' command to see what you can use."))
let buildSkiaSpec imgPath numRows numColumns pWidth colour newPath =
let colour = parseSkiaColour colour
{ originalPath = imgPath
savePath = newPath
skColour = colour
penWidth = pWidth
rows = numRows
columns = numColumns }
let buildSkiaRGBSpec imgPath numRows numColumns pWidth r g b newPath =
{ originalPath = imgPath
savePath = newPath
red = (float32 r)
green = (float32 g)
blue = (float32 b)
penWidth = pWidth
rows = numRows
columns = numColumns }

Loading…
Cancel
Save