-
Notifications
You must be signed in to change notification settings - Fork 0
/
OutConsolePicture.psm1
275 lines (230 loc) · 9.04 KB
/
OutConsolePicture.psm1
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
Add-Type -Assembly 'System.Drawing'
function Out-ConsolePicture {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ParameterSetName = "FromPath", Position = 0)]
[ValidateNotNullOrEmpty()][string[]]
$Path,
[Parameter(Mandatory = $true, ParameterSetName = "FromWeb")]
[System.Uri[]]$Url,
[Parameter(Mandatory = $true, ParameterSetName = "FromPipeline", ValueFromPipeline = $true)]
[System.Drawing.Bitmap[]]$InputObject,
[Parameter()]
[int]$Width,
[Parameter()]
[switch]$DoNotResize,
[Parameter()]
[int]$AlphaThreshold = 255,
[Parameter()]
[ValidateSet("Left","Center","Right")]
[string]$Align = "Left"
)
begin {
if ($PSCmdlet.ParameterSetName -eq "FromPath") {
foreach ($file in $Path) {
try {
$image = New-Object System.Drawing.Bitmap -ArgumentList "$(Resolve-Path $file)"
$InputObject += $image
}
catch {
Write-Error "An error occurred while loading image. Supported formats are BMP, GIF, EXIF, JPG, PNG and TIFF."
}
}
}
if ($PSCmdlet.ParameterSetName -eq "FromWeb") {
foreach ($uri in $Url) {
try {
$data = (Invoke-WebRequest $uri).RawContentStream
}
catch [Microsoft.PowerShell.Commands.HttpResponseException] {
if ($_.Exception.Response.statuscode.value__ -eq 302) {
$actual_location = $_.Exception.Response.Headers.Location.AbsoluteUri
$data = (Invoke-WebRequest $actual_location).RawContentStream
}
else {
throw $_
}
}
try {
$image = New-Object System.Drawing.Bitmap -ArgumentList $data
$InputObject += $image
}
catch {
Write-Error "An error occurred while loading image. Supported formats are BMP, GIF, EXIF, JPG, PNG and TIFF."
}
}
}
if ($Host.Name -eq "Windows PowerShell ISE Host") {
# ISE neither supports ANSI, nor reports back a width for resizing.
Write-Warning "ISE does not support ANSI colors."
Break
}
# Ignore Align if Width is not set
if(-not $PSBoundParameters.ContainsKey('Width') -and ($Align -ne "Left")){
$Align = "Left"
}
}
process {
# Character used to cause a line break
$line_break_char = "`n"
# For each image
$InputObject | ForEach-Object {
# If it's a recognized bitmap
if ($_ -is [System.Drawing.Bitmap]) {
# Resize image unless explicitly told not to
if (-not $DoNotResize) {
# If we're not given a width, or it's too large set to full width
if(-not $width -or ($width -gt $Host.UI.RawUI.BufferSize.Width)){
$width = $Host.UI.RawUI.BufferSize.Width;
}
# Perform ratio-safe resize
$new_height = $_.Height / ($_.Width / $width)
$resized_image = New-Object System.Drawing.Bitmap -ArgumentList $_, $width, $new_height
$_.Dispose()
$_ = $resized_image
}
else {
# If we can't resize, at least clip the image at the buffer width so we don't overflow it
$width = $Host.UI.RawUI.BufferSize.Width;
}
$all_pixel_pairs = New-Object System.Text.StringBuilder
# For each row of pixels in image
for ($y = 0; $y -lt $_.Height; $y++) {
if ($y % 2) {
# Skip over even rows because we process them in pairs of odds only
continue
}
else {
if($y -gt 0) {
# Add linebreaks after every row, if we're not on the first row
[void]$all_pixel_pairs.append($line_break_char)
}
}
# For each pixel (and its corresponding pixel below)
for ($x = 0; $x -lt [math]::Min($_.Width, $width); $x++) {
# Reset variables
$fg_transparent, $bg_transparent = $false, $false
$color_bg, $color_fg = $null, $null
$pixel_pair = ""
# Determine foreground color and transparency state
$color_fg = $_.GetPixel($x, $y)
if($color_fg.A -lt $AlphaThreshold){
$fg_transparent = $true
}
# Check if there's even a pixel below to work with
if (($y + 2) -gt $_.Height) {
# We are on the last row. There's not.
# There is no pixel below, and so treat the background as transparent
$bg_transparent = $true
}
else{
# There is a pixel below
# Determine background color and transparency state
$color_bg = $_.GetPixel($x, $y + 1)
if($color_bg.A -lt $AlphaThreshold){
$bg_transparent = $true
}
}
# If both top/bottom pixels are transparent, just use an empty space as a fully "transparent" pixel pair
if($fg_transparent -and $bg_transparent){
$pixel_pair = " "
}
# Otherwse determine which to render and which not to render
else{
# The two types of characters to use
$top_half_char = [char]9600 # In which the foreground is on top
$bottom_half_char = [char]9604 # In which the foreground is on the bottom
# Use the top character as the foreground by default
$character_to_use = $top_half_char
# If our top character is transparent but bottom isnt, we can't render the foreground as transparent and also have a background.
if($fg_transparent -and -not $bg_transparent){
# We need to invert the logic,
# So use the bottom-half char to render instead
$character_to_use = $bottom_half_char
# Invert the colors
$color_fg = $color_bg
# Invert the known transparent states
$fg_transparent = $false
$bg_transparent = $true
}
# If the fg (top pixel) is not transparent, give it a character with color
if(-not $fg_transparent){
# Draw a foreground
$pixel_pair += "$([char]27)[38;2;{0};{1};{2}m" -f $color_fg.r, $color_fg.g, $color_fg.b
}
# If the bg (bottom pixel) is not transparent, give it a character with color
if(-not $bg_transparent){
# Draw a background
$pixel_pair += "$([char]27)[48;2;{0};{1};{2}m" -f $color_bg.r, $color_bg.g, $color_bg.b
}
# Add the actual character to render
$pixel_pair += $character_to_use
# Reset the style to prepare for the next pixel
$pixel_pair += "$([char]27)[0m"
}
# Add the pixel-pair to the string builder
[void]$all_pixel_pairs.Append($pixel_pair)
}
}
# Write the colors to the console based on alignment
if($Align -eq "Left"){
# Left is the default
$all_pixel_pairs.ToString()
}
else{
# Right and Center require padding be added to each line
$screen_width = $Host.UI.RawUI.BufferSize.Width;
if($Align -eq "Right"){
# Add spaces each line to push to right of buffer
$padding = $screen_width - $width;
}
if($Align -eq "Center"){
# Add spaces each line to push to center of buffer
$padding = [math]::ceiling($screen_width / 2) - [math]::ceiling($width / 2);
}
# Print each line with required padding
$all_pixel_pairs.ToString().Split($line_break_char) |% {
Write-Host (" "*$padding+$_)
}
}
$_.Dispose()
}
}
}
end {
}
<#
.SYNOPSIS
Renders an image to the console
.DESCRIPTION
Out-ConsolePicture will take an image file and convert it to a text string. Colors will be "encoded" using ANSI escape strings. The final result will be output in the shell. By default images will be reformatted to the size of the current shell, though this behaviour can be suppressed with the -DoNotResize switch. ISE users, take note: ISE does not report a window width, and scaling fails as a result. I don't think there is anything I can do about that, so either use the -DoNotResize switch, or don't use ISE.
.PARAMETER Path
One or more paths to the image(s) to be rendered to the console.
.PARAMETER Url
One or more Urls for the image(s) to be rendered to the console.
.PARAMETER InputObject
A Bitmap object that will be rendered to the console.
.PARAMETER DoNotResize
By default, images will be resized to have their width match the current console width. Setting this switch disables that behaviour.
.PARAMETER Width
Renders the image at this specific width. Use of the width parameter overrides DoNotResize.
.PARAMETER AlphaThreshold
Default 255; Pixels with an alpha (opacity) value less than this are rendered as fully transparent. Fully opaque = 255. Lowering the value will require a pixel to be more transparent to vanish, and will therefor include more pixels.
.PARAMETER Align
Default 'Left'; Align image to the Left, Right, or Center of the terminal. Must be used in conjuction with the Width parameter.
.EXAMPLE
Out-ConsolePicture ".\someimage.png"
Renders the image to console
.EXAMPLE
Out-ConsolePicture -Url "http://somewhere.com/image.png"
Renders the image to console
.EXAMPLE
$image = New-Object System.Drawing.Bitmap -ArgumentList "C:\myimages\image.png"
$image | Out-ConsolePicture
Creates a new Bitmap object from a file on disk renders it to the console
.INPUTS
One or more System.Drawing.Bitmap objects
.OUTPUTS
The image rendered as console output
#>
}