-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.fs
More file actions
205 lines (173 loc) · 6.24 KB
/
Copy pathProgram.fs
File metadata and controls
205 lines (173 loc) · 6.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
open System
open System.IO
open System.Text.RegularExpressions
open SixLabors.ImageSharp
open SixLabors.ImageSharp.Processing
open SixLabors.ImageSharp.Advanced
open SixLabors.ImageSharp.Memory
open System.Collections.Generic
open System.Runtime.InteropServices
let reverseEndian = false
let ushortBytes (x: IConvertible) =
let mutable b = BitConverter.GetBytes (x.ToUInt16(null))
if reverseEndian then Array.Reverse b
b
let uintBytes (x: IConvertible) =
let mutable b = BitConverter.GetBytes (x.ToUInt32(null))
if reverseEndian then Array.Reverse b
b
let writeIco (images: 'T Image list) outname =
let outfile = File.OpenWrite(outname)
let imageBuffers = [
for image in images ->
let ms = new MemoryStream()
image.SaveAsPng(ms)
ms.ToArray()
]
let data = Array.concat [
// HEADER (6 bytes)
yield! [
0; 1 // constants for ICO
images.Length
] |> List.map ushortBytes
// ENTRIES
for i, image in List.indexed images do
let offset =
6 + 0x10 * images.Length + List.sumBy Array.length imageBuffers.[0..i-1]
yield! [ // (16 bytes)
[|
byte(image.Width)
byte(image.Height)
0uy // PNG Color Palette Count
0uy // Reserved bytes in entry
|]
ushortBytes 1 // PNG Color Planes
ushortBytes image.PixelType.BitsPerPixel // 32, usually
uintBytes imageBuffers.[i].Length
uintBytes offset
]
// IMAGE DATA
yield! imageBuffers
]
outfile.Write(ReadOnlySpan(data))
let ICON_SIZES = [
16
32
48
256
]
let resized (image: Image<'T>) size =
if image.Height = image.Width && image.Width = size then
image
else
let maxdim = max image.Height image.Width
printfn $"{image.Height} x {image.Width} -> {size}x{size}"
let x, y =
if image.Height > image.Width then
(image.Height-image.Width) /2, 0
else
0, (image.Width-image.Height) /2
let outputImage = new Image<'T>(maxdim, maxdim)
outputImage.Mutate (
fun i ->
i.DrawImage(image, new Point(x, y), 1.0f)
.Resize(size, size)
|> ignore
)
outputImage
// FROM: http://www.fssnip.net/29/title/Regular-expression-active-pattern
// Daniel Robinson
let (|Regex|_|) pattern input =
let m = Regex.Match(input, pattern)
if m.Success then Some([ for g in m.Groups -> g.Value ])
else None
let println = printfn "%s"
type ImageType = Image<PixelFormats.Argb32>
type ArgExpr =
| Default
| Output
| Specific of size: int
let rec parseArgs (tokens: string list) =
match tokens with
| [] -> []
// specified params
| "-o"::path::rest
| "--output"::path::rest -> (Output, path)::(parseArgs rest)
| "-d"::path::rest
| "--default"::path::rest -> (Default, path)::(parseArgs rest)
// procedural params
| (Regex @"\-[\d,]+" [givenSizes])::path::rest ->
let givenSizes' = givenSizes.Substring 1
let splitSizes =
if givenSizes.Contains ',' then
(givenSizes').Split(',')
else
[|givenSizes'|]
let mutable dones = []
let parsedSizes =
[
for size in splitSizes ->
let size'' = size
let isInt, size' = Int32.TryParse(size'')
if (List.contains size' dones) then
raise (System.ArgumentException $"Size override '{size'}' was given more than once.")
else if isInt && (List.contains size' ICON_SIZES) then
dones <- size'::dones
(Specific size', path)
else
raise (System.ArgumentException $"Given size override '{size'}' is not a supported icon size.")
]
List.concat [parsedSizes; parseArgs rest]
| path::rest -> (Default, path)::(parseArgs rest)
let HELP =
"Convert an image or set of images to a Windows ICO image.
Usage:
iconify (-d [default image])? (-[sizes,] [size-specific override])* (-o [output file])?
Examples:
iconify example.png
iconify example.png -16 example16px.bmp -o ../out.ico
Options:
--default, -d : Explicitly specify a default image to use for sizes.
--output, -o : Explicitly specify an output path. If omitted, inferred from default image.
-[size] : Specify an image to use instead of the default at a particular size."
[<EntryPoint>]
let main (argv) =
if argv = [||] then
println HELP
exit(-1)
try
let options = argv |> List.ofArray |> parseArgs |> Map.ofList
let outputFile = Option.defaultValue options.[Default] (options.TryFind Output)
for option in options do
match option.Key with
| Default ->
printfn "Default: %s" option.Value
| Output ->
printfn "Output: %s" option.Value
| Specific size ->
printfn "Size %d: %s" size option.Value
let newName = outputFile.Substring(0, outputFile.LastIndexOf '.')+".ico"
let imagesForIcon = [
for size in ICON_SIZES ->
let path = Option.defaultValue options.[Default] (options.TryFind (Specific size))
if not(File.Exists path) then
raise(System.IO.FileNotFoundException $"Input image '{path}' not found!")
let img: ImageType = ImageType.Load(path)
resized img size
]
writeIco imagesForIcon newName
0
with
| :? System.IO.FileNotFoundException
| :? System.ArgumentException as e ->
println e.Message
-1
| :? System.Collections.Generic.KeyNotFoundException as e->
println "An output file name or default image file must be specified."
-1
| :? UnknownImageFormatException ->
println "Only JPEG, PNG, BMP, GIF, and TARGA image formats are supported."
-1
| :? InvalidImageContentException as e ->
println $"An error occurred reading an image:\n{e.Message}"
-1