From 0e381f824c6ff2f883c99a403edd93c1c25ebbe7 Mon Sep 17 00:00:00 2001 From: "OPTIMUS-PRIME\\craig" Date: Mon, 3 Dec 2018 03:36:49 +0000 Subject: [PATCH] add SkiaSharp versions of the System.Drawing functionality. None of the SkiaSharp code has tests in Test Centre. --- DeathSocket/ColourServices.fs | 8 +- DeathSocket/Domain.fs | 60 +++++++++++++- DeathSocket/GridPainter.fs | 151 ++++++++++++++++++++++++++++------ DeathSocket/ImageServices.fs | 147 ++++++++++++++++++++++----------- DeathSocket/Validation.fs | 8 +- DeathSocketCLI/Commands.fs | 33 +++++++- DeathSocketCLI/Validation.fs | 79 ++++++++++++++---- 7 files changed, 380 insertions(+), 106 deletions(-) diff --git a/DeathSocket/ColourServices.fs b/DeathSocket/ColourServices.fs index 1fec9e9..4fba086 100644 --- a/DeathSocket/ColourServices.fs +++ b/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]) \ No newline at end of file + new SolidBrush (colour) \ No newline at end of file diff --git a/DeathSocket/Domain.fs b/DeathSocket/Domain.fs index ae4a174..0fd06ba 100644 --- a/DeathSocket/Domain.fs +++ b/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 } \ No newline at end of file diff --git a/DeathSocket/GridPainter.fs b/DeathSocket/GridPainter.fs index 0ed5ecd..0954f71 100644 --- a/DeathSocket/GridPainter.fs +++ b/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 + // ======================================================================== + + /// + /// 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). + /// + /// The width of the image. + /// The height of the image. + /// The number of rows the grid should have. + /// You will probably only need these when dealing with GUI's. + let determineHorizontalLines (width: int) (height: int) (rows: int) = + createHorizontalLines width height rows + + /// + /// 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). + /// + /// The width of the image. + /// The height of the image. + /// The number of columns the grid should have. + /// You will probably only need these when dealing with GUI's. + let determineVerticalLines (width: int) (height: int) (columns: int) = + createVerticalLines width height columns + /// /// 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). /// /// /// 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 /// /// 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). /// /// /// 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 /// /// Creates a Sytsem.Drawing SolidBrush from the individual RGBA values. + /// Use this function when targeting .Net/Mono). /// /// The red value. /// The green value. @@ -79,10 +113,12 @@ namespace DeathSocket /// Death Socket uses System.Drawing and not System.Media for colours /// and brushes. /// - 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 /// /// Creates a System.Drawing SolidBrush from a RGBASpec. + /// Use this function when targeting .Net/Mono). /// /// ///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. /// - let makeSolidBrushFromRGBASpec spec = makeBrushFromRGBASpec spec + let makeSolidBrushFromRGBASpec (spec: RGBASpec) = + makeBrushFromRGBASpec spec + + // SkiaSharp Functions + // ======================================================================== + + // NOT TESTED /// - /// 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). /// /// The width of the image. /// The height of the image. /// The number of rows the grid should have. - /// You will probably only need these when dealing with GUI's. - let determineHorizontalLines width height rows = - createHorizontalLines width height rows + /// This function is part of the SkiaSharp functions provided + /// by Death Socket. + let determineSKHorizontalLines (width: int) (height: int) (rows: int) = + createSKHorizontalLines width height rows + + // NOT TESTED /// - /// 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). /// /// The width of the image. /// The height of the image. - /// The number of columns the grid should have. - /// You will probably only need these when dealing with GUI's. - let determineVerticalLines width height columns = - createVerticalLines width height columns + /// The number of columns the grid should have. + /// + /// This function is part of the SkiaSharp functions provided + /// by Death Socket. + let determineSKVerticalLines (width: int) (height: int) (columns: int) = + createSKVerticalLines width height columns + + // NOT TESTED + + /// + /// 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. + /// + /// + /// 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). + /// + /// + /// 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. + /// + 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 () \ No newline at end of file + /// + /// 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. + /// + /// + /// 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). + /// + /// + /// 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. + /// + 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 + } \ No newline at end of file diff --git a/DeathSocket/ImageServices.fs b/DeathSocket/ImageServices.fs index 190b796..c68b950 100644 --- a/DeathSocket/ImageServices.fs +++ b/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." \ No newline at end of file + clone.Save (spec.savePath) \ No newline at end of file diff --git a/DeathSocket/Validation.fs b/DeathSocket/Validation.fs index a192a9b..71128f3 100644 --- a/DeathSocket/Validation.fs +++ b/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." \ No newline at end of file + | _ -> invalidArg "savePath" "The file type must be a .jpg or .png file." \ No newline at end of file diff --git a/DeathSocketCLI/Commands.fs b/DeathSocketCLI/Commands.fs index 3e8796d..969b23c 100644 --- a/DeathSocketCLI/Commands.fs +++ b/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 \ No newline at end of file diff --git a/DeathSocketCLI/Validation.fs b/DeathSocketCLI/Validation.fs index c0476a3..7b42a6a 100644 --- a/DeathSocketCLI/Validation.fs +++ b/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 \ No newline at end of file + 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 }