create Using DeathSocket in Proj. page.

master
Craig Oates 5 years ago
parent
commit
864fbcdb2b
  1. 166
      Using-Death-Socket-in-Your-Project.md

166
Using-Death-Socket-in-Your-Project.md

@ -0,0 +1,166 @@
Although there are slight variations, you will use Death Socket (D.S.) as part of a three step process; Which are;
1. Construct a specification for how you want the overlay to look.
2. Pass the specification to D.S. in preparation of adding the grid to the image.
3. Tell D.S. to add the grid to the image, synchronously or asynchronously.
Before we get into what the specifications look like, here is an example of the above steps in code,
```f#
(* For now, view the first line as the "construct a specification" part
of the above three steps. Info. on how to build an image specification
is a topic I will cover a little later. *)
Brush (buildSpec imgPath numRows numColumns pWidth colour newPath)
|> applyGridToImageAsync
|> Async.Start
```
For a more real-world example (with extra cruft), please read the `add-grid` function in "Commands.fs". This file is part of the DeathSocketCLI project and the link for it is as follows;
- [ Add-Grid example in Commands.fs](https://gitlab.com/craig.oates/Death-Socket/blob/master/DeathSocketCLI/Commands.fs)
**Note:** Most of the "add-grid" functions will (by default) save the gridded image for you (using the save location part of the image specification). If you prefer, D.S. can create a gridded image and not save it. When that happens, it will return it as an in-memory buffer. If you are not sure on which function does what, use Visual Studio's intellisense. It will help you identify those which saves the image for you and those which return an in-memory buffer. Having said that, you are free to read the source code for these functions with the following link;
- [GridPainter.fs](https://gitlab.com/craig.oates/Death-Socket/blob/master/DeathSocket/GridPainter.fs)
## A Note about the Graphics Libraries Used by Death Socket (I.E. The System's Flow)
If you look at the source code for D.S., you will see I have included two graphics libraries. They are;
- [System.Drawing (Windows specific graphic library)](https://docs.microsoft.com/en-us/dotnet/api/system.drawing?view=netframework-4.8)
- [SkiaSharp (Cross-platform graphics library)](https://github.com/mono/SkiaSharp)
I have included these two libraries because D.S. is a .Net Standard 2.0 library. This means the code in D.S. can run on the traditional .Net framework and .Net Core. If you are building a traditional .Net application (E.G. a W.P.F. app.), I recommend you use *System.Drawing*. If you are creating a cross-platform application (E.G. a Xamarin app.), you will need to use the *SkiaSharp* parts of D.S. This means the general flow of the system looks like this,
IMAGE OF SYSTEM FLOW.
 If you would like to know more about these graphics libraries, please use the following links;
- [Graphics Class](https://docs.microsoft.com/en-us/dotnet/api/system.drawing.graphics?view=netframework-4.8#applies-to) (this is probably a good place to start with *System.Drawing*)
- [SkiaSharp Graphics in Xamarin Forms](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/) (provides an introductory overview to SkiaSharp)
This next bit is an aside; So, feel free to skip over this bit and jump straight into the next section... I admit I should have dropped the *System.Drawing* part and focused on *SkiaSharp*, at least in the long-term. The reason I did not is because using *SkiaSharp* with a Xamarin project annoyed me. Long story short... I developed D.S. as the foundation for Paint Grid which is a G.U.I. version of D.S. Paint Grid exists as a W.P.F. and Xamarin (MacOS) application. (The Mac version is not published at the time of writing.) I built the W.P.F. version first with *System.Drawing* and it was fine. I discovered Xamarin does not work with *System.Drawing* and it recommended *SkiaSharp*. This was not as easy to make as the W.P.F. version. So, by the time I finished writing the application, I was annoyed and want to throw my toys out of the pram. I decided to keep the *System.Drawing* code in D.S. so I would not need to use the *SkiaSharp* bits if I needed it for another project. Do not let this put you off using the *SkiaSharp* part of D.S., though. If you are building a cross-platform application, I recommend you use the *SkiaSharp* stuff. I would even go as far as saying I would probably write D.S. with just *SkiaSharp* if I had to start from scratch -- having calmed down.
## The Pieces You Will Work With
As a consumer of D.S., you will not have access to every part of it. Instead, you work with two modules within a single name-space, called `DeathSocket`. The module's names are `Domain` and `GridPainter`.
IMAGE OF THE MODULES AND THEIR RELATIONSHIPS.
The above image shows the flow of a typical code-base using D.S. Looking at this, you would think the `GridPainter` module is where you should focus most of your time. This is not immediately true, though. If anything, forming good image-specifications is the more time consuming task.
To help you get started, I recommend you keep the following two files open when writing your code;
- [GridPainter.fs](https://gitlab.com/craig.oates/Death-Socket/blob/master/DeathSocket/GridPainter.fs)
- [Domain.fs link](https://gitlab.com/craig.oates/Death-Socket/blob/master/DeathSocket/Domain.fs)
### Domain Module
Within `Domain`, you will find the following types:
* `BrushSpec`
* `RGBASpec`
* `SkiaSpec`
* `SkiaBufferSpec`
* `SkiaRGBBufferSpec`
* `SkiaRGBSpec`
* `Imagespec`
* `ImageType`
You can split these types up into the following groups;
|System.Drawing|SkiaSharp |Discriminated Unions|
|:------------:|:-----------------:|:------------------:|
|`BrushSpec` |`SkiaSpec` |`ImageSpec` |
|`RGBASpec` |`SkiaBufferSpec` |`ImageType` |
| |`SkiaRGBBufferSpec`| |
| |`SkiaRGBSpec` | |
As you can see, the types fall into one of three groups. The name of the groups refer to the graphics libraries used by the functions which use those types -- excluding "Discriminated Unions" (D.U.). For example, any function which uses `BrushSpec` also uses the *System.Drawing* library. If you use a function which takes a `SkiaSpec`, that function will also use the *SkiaSharp* library. The types in the D.U. group mostly act as wrappers for the types in the other two groups. The main thing they (D.U's) offer is a way to generalise an input parameter for a function. For example, instead of writing separate functions for each type in the other two groups, I wrote one. Within that function, I specified it needed an `ImageSpec`. From here, I can deduce the type within the function and make the appropriate function calls. Here is a code example to help explain,
```f#
// Instead of separate functions for each type, I now have one.
let applyGridToImageAsync (spec: ImageSpec) =
           async {
               try
                   match spec with
                   | Brush b ->
                       validateIO b.originalPath b.savePath |> ignore
                       drawBrushSpecGrid b
                   | RGBA r ->
                       validateIO r.originalPath r.savePath |> ignore
                       drawRGBAGrid r
                   | Skia s ->
                       validateIO s.originalPath s.savePath |> ignore
                       drawSkiaGrid s
                   | SkiaRGB sR ->
                       validateIO sR.originalPath sR.savePath |> ignore
                       drawSkiaRGBGrid sR
                   | _ -> printfn "Inappropriate ImageSpec used here."
               with
               | :? FileNotFoundException as ex ->
                   printfn "File could not be found at %s" ex.Message
           }
```
The name of the other D.U. is `ImageType`. I view this as a way to refer to a type within the other two (non D.U.) groups without creating them. I do not foresee you using this D.U. much but it is there if you need it. The only place I have use it in DeathSocket is in the following function (found in "GridPainter.fs"),
```f#
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 ()
```
The purpose of this function is to determine the dimensions of the image at the location specified in the `ImageType`. Having said that, DeathSocket uses *System.Drawing* and *SkiaSharp*. So, it needs to provide a way to use both libraries. I could have written two separate functions, one for each graphics library. But, that felt like a waste of effort. So, I used the `ImageType` and condensed it into one. To be clear, all this function needs is the path of the image. What the `ImageType` provides is extra context.
Instead of repeating all the code in "Domain.fs", I recommend you take a look at the file yourself. The link for that is as follows;
- [Domain.fs](https://gitlab.com/craig.oates/Death-Socket/blob/master/DeathSocket/Domain.fs)
I have documented the code within the file using XML comments. So, you should get intellisense overlays when you use Visual Studio or Visual Studio Code. I have, also, provided templates/examples in the "Snippets" section. Feel free to use them as starting points for creating your own specifications. The link for the snippets section is as follows;
- [Creating Domain Types Examples Snippet](https://gitlab.com/craig.oates/Death-Socket/snippets/1868660)
### GridPainter Module
Within `GridPainter`, you will find ten functions, which are as follows;
1. `applyImageToGridAsync`
2. `scaleLineThickness`
3. `determineImageDimensions`
4. `determineHorizontalLines`
5. `determineVerticalLines`
6. `makeSolidBrushFromRGBA`
7. `makeSolidBrushFromRGBASpec` 
8. `createSKDataAsync`
9. `determineSKHorizontalLines`
10. `determineSKVerticalLines`
Instead of repeating the code from that file here, I recommend you open up "GridPainter.fs" in a separate tab. Every function has XML comments explaining what they do. So, you should have intellisense in Visual Studio and Visual Studio Code. If you read "GridPainter.fs", you will notice I have marked out areas called "SkiaSharp Functions" and "System.Drawing Functions". This should help you denote which function use which graphics library. If you are unsure which function uses which library, you can refer back to the XML comments/intellisense. As a general rule, if a function has "SK" in its name, it uses *SkiaSharp*. If it does not, it uses *System.Drawing*. The link for GridPainter.fs is as follows;
- [GridPainter.fs](https://gitlab.com/craig.oates/Death-Socket/blob/master/DeathSocket/GridPainter.fs)
In an attempt to get you up and running, I have provided the following table. It shows what graphics library is used by each function and should help alongside the Intellisense in Visual Studio.
|System.Drawing |SkiaSharp |Both |
|:--------------------------:|:--------------------------:|:--------------------------:|
|`determineHorizontalLines` |`createSKDataAsync` |`applyImageToGridAsync` |
|`determineVerticalLines` |`determineSKHorizontalLines`|`scaleLineThickness` |
|`makeSolidBrushFromRGBA` |`determineSKVerticalLines` |`determineImageDimensions` |
|`makeSolidBrushFromRGBASpec`| | |
To help you get a sense of how to use these functions, I have provided some examples in the "Snippets" section. To view them, use the following link;
- [GridPainter Usage Examples Snippet](https://gitlab.com/craig.oates/Death-Socket/snippets/1868663)
Loading…
Cancel
Save