Browse Source

update comments and add 'skia stream' functionality

master
Craig Oates 5 years ago
parent
commit
a3d9da4457
  1. 80
      DeathSocket/Domain.fs
  2. 46
      DeathSocket/GridPainter.fs
  3. 110
      DeathSocket/ImageServices.fs

80
DeathSocket/Domain.fs

@ -8,9 +8,9 @@
open SkiaSharp open SkiaSharp
open System.Threading open System.Threading
/// The specification used to note the orignial file's location and the /// This specification is used to note the orignial file's location and
/// changes the user would like to make to it. (including the save /// 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 /// location of the modified version). Use this spec. if you are using
/// System.Drawing. /// System.Drawing.
type BrushSpec = type BrushSpec =
{ /// The original path of the image which the grid is being added to. { /// The original path of the image which the grid is being added to.
@ -27,11 +27,11 @@
///The number of columns the grid will have. ///The number of columns the grid will have.
columns: int } columns: int }
/// The specification used to note the orignial file's location and the /// This specification is used to note the orignial file's location and
/// changes the user would like to make to it (including the save /// the changes the user would like to make to it (including the save
/// location of the modified version). This specification includes /// location of the modified version). This specification includes
/// individual RGBA values to describe the grid's colour. Use this spec. /// individual RGBA values to describe the grid's colour. Use this
/// if you are using System.Drawing. /// spec. if you are using System.Drawing.
type RGBASpec = type RGBASpec =
{ /// The original path of the image which the grid is being added to. { /// The original path of the image which the grid is being added to.
originalPath: string originalPath: string
@ -53,10 +53,10 @@
///The number of columns the grid will have. ///The number of columns the grid will have.
columns: int } columns: int }
/// The specification used to note the orignial file's location and the /// This specification is used to note the orignial file's location and
/// changes the user would like to make to it (including the save /// 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 /// location of the modified version). Use this spec. if you are using
/// the Skia-Sharp library. /// the SkiaSharp library.
type SkiaSpec = type SkiaSpec =
{ /// The original path of the image which the grid is being added to. { /// The original path of the image which the grid is being added to.
originalPath: string originalPath: string
@ -71,11 +71,53 @@
///The number of columns the grid will have. ///The number of columns the grid will have.
columns: int } columns: int }
/// The specification used to note the orignial file's location and the /// This specification is used to create a gridded image as an SKData
/// changes the user would like to make to it (including the save /// 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 /// location of the modified version). This specification includes
/// individual RGB values to describe the grid's colour. Use this spec. /// 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 = type SkiaRGBSpec =
{ /// The original path of the image which the grid is being added to. { /// The original path of the image which the grid is being added to.
originalPath: string originalPath: string
@ -100,12 +142,20 @@
/// A 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. /// Death Socket can use to apply a grid to an image.
type ImageSpec = type ImageSpec =
/// Spec. which uses System.Drawing and its built-in (colour) Brush.
| Brush of BrushSpec | Brush of BrushSpec
/// Spec. which uses System.Drawing and individual RGBA values.
| RGBA of RGBASpec | RGBA of RGBASpec
/// Spec. which uses SkiaSharp and its built-in SKColors.
| Skia of SkiaSpec | Skia of SkiaSpec
/// Spec. which uses SkiaSharp and individual RGB values.
| SkiaRGB of SkiaRGBSpec | 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 representing a type of image. The type refers /// A Discriminated Union denoting a type of image. The type refers
/// to the graphics library used to read the image file. /// to the graphics library used to read the image file.
/// The graphics library determines how the image is read and /// The graphics library determines how the image is read and
/// manipulated in memory and the functions you can perform on it. /// manipulated in memory and the functions you can perform on it.
@ -114,5 +164,7 @@
/// the recommended choice. If you using Xamarin, SkiaSharp is the /// the recommended choice. If you using Xamarin, SkiaSharp is the
/// recommended choice. /// recommended choice.
type ImageType = type ImageType =
/// Denotes an image created using SkiaSharp
| SkiaSharp of string | SkiaSharp of string
/// Denotes an image created using System.Drawing
| SystemDrawing of string | SystemDrawing of string

46
DeathSocket/GridPainter.fs

@ -15,10 +15,13 @@ namespace DeathSocket
open Validation open Validation
open ImageServices open ImageServices
open SkiaSharp
/// <summary> /// <summary>
/// Uses the information included in spec to create a gridded image. /// Uses the information included in spec to create a gridded image.
/// It then asynchronously saves it. Uses .jpg or .png formats only. /// 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> /// </summary>
/// <param name="spec"> /// <param name="spec">
/// The specification used to generate the new gridded image. The /// The specification used to generate the new gridded image. The
@ -46,6 +49,7 @@ namespace DeathSocket
| SkiaRGB sR -> | SkiaRGB sR ->
validateIO sR.originalPath sR.savePath |> ignore validateIO sR.originalPath sR.savePath |> ignore
drawSkiaRGBGrid sR drawSkiaRGBGrid sR
| _ -> printfn "[DEATH SOCKET INFO] Inappropriate ImageSpec used here. Please use none buffer-based spec's when using this function."
with with
| :? FileNotFoundException as ex -> | :? FileNotFoundException as ex ->
printfn "File could not be found at %s" ex.Message printfn "File could not be found at %s" ex.Message
@ -104,7 +108,7 @@ namespace DeathSocket
reraise () reraise ()
// System.Drawing Functions // System.Drawing Functions
// ======================================================================== // ====================================================================
/// <summary> /// <summary>
/// Determines the (Pen) points needed to draw the appropriate number /// Determines the (Pen) points needed to draw the appropriate number
@ -162,7 +166,45 @@ namespace DeathSocket
makeBrushFromRGBASpec spec makeBrushFromRGBASpec spec
// SkiaSharp Functions // SkiaSharp Functions
// ======================================================================== // ====================================================================
// NOT TESTED
/// <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> /// <summary>
/// Determines the (SKPoints) points needed to draw the appropriate /// Determines the (SKPoints) points needed to draw the appropriate

110
DeathSocket/ImageServices.fs

@ -8,13 +8,25 @@
open SkiaSharp open SkiaSharp
open System open System
(* Why 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 grid their computer to a halt. On top of
that, they have no means to make alterations to the code. *)
let setLineThickness pDimension aDimension lineWidth = let setLineThickness pDimension aDimension lineWidth =
if (pDimension <= 0.0 || aDimension <= 0.0 || lineWidth <= 0.0) then if (pDimension <= 0.0 || aDimension <= 0.0 || lineWidth <= 0.0) then
raise (new DivideByZeroException ("[ERROR] The images height and width must be greater than 0.")) raise (new DivideByZeroException ("[ERROR] The images height and width must be greater than 0."))
else lineWidth / (pDimension / aDimension) else lineWidth / (pDimension / aDimension)
// SkiaSharp Functions (* SkiaSharp Functions
// ======================================================================== ======================================================================== *)
let createSKHorizontalLines width height rows = let createSKHorizontalLines width height rows =
let interval = float32 (height / rows) let interval = float32 (height / rows)
@ -113,8 +125,87 @@
use saveStream = File.OpenWrite (spec.savePath) use saveStream = File.OpenWrite (spec.savePath)
data.SaveTo (saveStream) 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)
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
(* System.Drawing Functions
======================================================================== *)
let createHorizontalLines width height rows = let createHorizontalLines width height rows =
let interval = height / rows let interval = height / rows
@ -135,13 +226,15 @@
(* 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 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 indexed pixels. Instead of listing them all (future additions), just
with a "*Spec" type as a parameter will use this "temp" file. *) assume any function with a "*Spec" type as a parameter will use this "temp"
file. *)
let drawBrushSpecGrid (spec: BrushSpec) = let drawBrushSpecGrid (spec: BrushSpec) =
use original = Bitmap.FromFile spec.originalPath use original = Bitmap.FromFile spec.originalPath
use temp = new Bitmap(original) use temp = new Bitmap(original)
use clone = temp.Clone(new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb) use clone =
temp.Clone(new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb)
use graphics = Graphics.FromImage(clone) use graphics = Graphics.FromImage(clone)
use pen = new Pen (spec.colour, width = spec.penWidth) use pen = new Pen (spec.colour, width = spec.penWidth)
graphics.DrawImage(original,new Rectangle(0, 0, clone.Width, clone.Height)) graphics.DrawImage(original,new Rectangle(0, 0, clone.Width, clone.Height))
@ -156,7 +249,8 @@
let drawRGBAGrid (spec: RGBASpec) = let drawRGBAGrid (spec: RGBASpec) =
use original = Bitmap.FromFile spec.originalPath use original = Bitmap.FromFile spec.originalPath
use temp = new Bitmap (original) use temp = new Bitmap (original)
use clone = temp.Clone (new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb) use clone =
temp.Clone (new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format32bppArgb)
use graphics = Graphics.FromImage(clone) use graphics = Graphics.FromImage(clone)
use pen = new Pen ((makeBrushFromRGBASpec spec), width = spec.penWidth) use pen = new Pen ((makeBrushFromRGBASpec spec), width = spec.penWidth)
graphics.DrawImage (original,new Rectangle(0, 0, clone.Width, clone.Height)) graphics.DrawImage (original,new Rectangle(0, 0, clone.Width, clone.Height))

Loading…
Cancel
Save