Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add imageplot (support sixel) #196

Merged
merged 5 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## [3.1] - 2022-09-02
### Added
- `imageplot`.
- sixel support in `ImageGraphics`.

## [3.0] - 2022-06-24
### Added
- Support multiple series (`Matrix` columns) on `lineplot` and `scatterplot` (and mutating versions).
Expand Down
10 changes: 7 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,38 @@ LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
MarchingCubes = "299715c1-40a9-479a-aaf9-4a633d36f717"
NaNMath = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[compat]
ColorSchemes = "3.19"
ColorSchemes = "^3.19"
ColorTypes = "0.11"
Contour = "0.5 - 0.6"
Crayons = "4.1"
Crayons = "^4.1"
FileIO = "1"
FreeTypeAbstraction = "0.9 - 0.10"
LazyModules = "0.3"
MarchingCubes = "0.1"
NaNMath = "0.3, 1"
Requires = "1"
StaticArrays = "1"
StatsBase = "0.33"
Unitful = "1"
julia = "1.6"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254"
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReferenceTests = "324d217c-45ce-50fc-942e-d289b448e8cf"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"

[targets]
test = ["Aqua", "ImageMagick", "Random", "ReferenceTests", "StableRNGs", "Test", "TimerOutputs"]
test = ["Aqua", "ImageInTerminal", "ImageMagick", "Random", "ReferenceTests", "StableRNGs", "Test", "TestImages", "TimerOutputs"]
4 changes: 3 additions & 1 deletion src/UnicodePlots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ using ColorSchemes
using StaticArrays
using LazyModules
using ColorTypes
using Requires
using Crayons
using Printf
using Dates
Expand Down Expand Up @@ -74,14 +75,15 @@ export scatterplot!,
stairs

# methods without mutating variants
export horizontal_histogram, vertical_histogram, histogram, heatmap, spy
export horizontal_histogram, vertical_histogram, histogram, heatmap, spy, imageplot

include("common.jl")
include("lut.jl")

include("graphics.jl")
include("graphics/bargraphics.jl")
include("graphics/boxgraphics.jl")
include("graphics/imagegraphics.jl")

include("canvas.jl")
include("canvas/lookupcanvas.jl")
Expand Down
2 changes: 1 addition & 1 deletion src/canvas/densitycanvas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function pixel!(c::DensityCanvas, pixel_x::Integer, pixel_y::Integer, color::Use
c
end

function preprocess!(c::DensityCanvas)
function preprocess!(::IO, c::DensityCanvas)
c.max_density[] = max(eps(), maximum(c.dscale, c.grid))
c -> c.max_density[] = -Inf
end
Expand Down
7 changes: 7 additions & 0 deletions src/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ terminal_24bit() = lowercase(get(ENV, "COLORTERM", "")) ∈ ("24bit", "truecolor
forced_24bit() = lowercase(get(ENV, "UP_COLORMODE", "")) ∈ ("24", "24bit", "truecolor")
forced_8bit() = lowercase(get(ENV, "UP_COLORMODE", "")) ∈ ("8", "8bit")

imageplot(args...; kwargs...) =
error("not implemented, did you forget 'using ImageInTerminal' ?")

function __init__()
if (terminal_24bit() || forced_24bit()) && !forced_8bit()
truecolors!()
Expand All @@ -246,6 +249,10 @@ function __init__()
colors256!()
faintcolors!()
end
@require ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" begin
imageplot(img::AbstractArray{<:Colorant}; kwargs...) =
Plot(ImageGraphics(img); kwargs...)
end
nothing
end

Expand Down
6 changes: 3 additions & 3 deletions src/graphics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ suitable_color(c::GraphicsArea, color::Union{UserColorType,AbstractVector}) = an
Base.print(io::IO, c::GraphicsArea) = _print(io, print, print_color, c)

function _print(io::IO, print_nocol, print_color, c::GraphicsArea)
postprocess! = preprocess!(c)
postprocess! = preprocess!(io, c)
for row ∈ 1:nrows(c)
print_row(io, print_nocol, print_color, c, row)
row < nrows(c) && print_nocol(io, '\n')
Expand All @@ -26,7 +26,7 @@ function _show(io::IO, print_nocol, print_color, c::GraphicsArea)
bc = BORDER_COLOR[]
border_length = ncols(c)
print_border(io, print_nocol, print_color, :t, border_length, nothing, '\n', b, bc)
postprocess! = preprocess!(c)
postprocess! = preprocess!(io, c)
for row ∈ 1:nrows(c)
print_color(io, bc, b[:l])
print_row(io, print_nocol, print_color, c, row)
Expand All @@ -46,4 +46,4 @@ print_row(io::IO, c::GraphicsArea, row::Integer) = print_row(io, print, print_co
Optional step: pre-process canvas before printing rows (e.g. for costly computations).
Returns a cleanup callback for optional cleanup after printing.
"""
preprocess!(c::GraphicsArea) = c -> nothing
preprocess!(::IO, c::GraphicsArea) = c -> nothing
2 changes: 1 addition & 1 deletion src/graphics/bargraphics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function addrow!(
c
end

function preprocess!(c::BarplotGraphics)
function preprocess!(::IO, c::BarplotGraphics)
max_val, i = findmax(c.xscale.(c.bars))
c.max_val[] = max(max_val, c.maximum)
c.max_len[] = length(string(c.bars[i]))
Expand Down
49 changes: 49 additions & 0 deletions src/graphics/imagegraphics.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
struct ImageGraphics{C<:Colorant} <: GraphicsArea
img::Matrix{C}
sixel::RefValue{Bool}
visible::Bool
encoded_size::Vector{Int}
ren::Vector{String}
end

ImageGraphics(img::AbstractArray{<:Colorant}) =
ImageGraphics(img, Ref(false), true, [0, 0], String[])

@inline nrows(c::ImageGraphics) = c.encoded_size[1]
@inline ncols(c::ImageGraphics) = c.encoded_size[2]

function preprocess!(io::IO, c::ImageGraphics)
ctx = IOContext(PipeBuffer(), :displaysize => displaysize(io))
c.sixel[] = false
char_h = char_w = nothing # determine the terminal caret size, in pixels
if ImageInTerminal.choose_sixel(c.img)
ans = ImageInTerminal.Sixel.TerminalTools.query_terminal("\e[16t", stdout)
if ans isa String && (m = match(r"\e\[6;(\d+);(\d+)t", ans)) ≢ nothing
char_h, char_w = tryparse.(Int, m.captures)
c.sixel[] = char_h ≢ nothing && char_w ≢ nothing
end
end
if c.sixel[]
h, w = size(c.img)
lines = String[]
for r ∈ 1:char_h:h
ImageInTerminal.sixel_encode(ctx, c.img[r:min(r + char_h - 1, h), :])
push!(lines, read(ctx, String))
end
nc = ceil(Int, w / char_w)
else
ImageInTerminal.imshow(ctx, c.img)
lines = readlines(ctx)
nc = length(no_ansi_escape(first(lines)))
end
c.encoded_size .= length(lines), nc
resize!(c.ren, nrows(c))
copyto!(c.ren, lines)
c -> nothing
end

function print_row(io::IO, print_nocol, _, c::ImageGraphics, row::Integer)
0 < row ≤ nrows(c) || throw(ArgumentError("`row` out of bounds: $row"))
print_nocol(io, c.ren[row])
nothing
end
44 changes: 25 additions & 19 deletions src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,22 @@ Base.show(io::IO, p::Plot) = _show(io, print, print_color, p)
function _show(end_io::IO, print_nocol, print_color, p::Plot)
buf = PipeBuffer() # buffering, for performance
io_color = get(end_io, :color, false)
io = IOContext(buf, :color => io_color)
io = IOContext(buf, :color => io_color, :displaysize => displaysize(end_io))

c = p.graphics
🗷 = Char(BLANK) # blank outside canvas
🗹 = blank(c) # blank inside canvas
g = p.graphics
🗷 = Char(BLANK) # blank outside graphics
🗹 = blank(g) # blank inside graphics
############################################################
# 🗷 = 'x' # debug
# 🗹 = Char(typeof(c) <: BrailleCanvas ? '⠿' : 'o') # debug
# 🗹 = Char(typeof(g) <: BrailleCanvas ? '⠿' : 'o') # debug
############################################################
nr = nrows(c)
nc = ncols(c)
p_width = nc + 2 # left corner + border length (number of canvas cols) + right corner
postprocess! = preprocess!(io, g)

bmap = BORDERMAP[p.border ≡ :none && c isa BrailleCanvas ? :bnone : p.border]
nr = nrows(g)
nc = ncols(g)
p_width = nc + 2 # left corner + border length (number of graphics cols) + right corner

bmap = BORDERMAP[p.border ≡ :none && g isa BrailleCanvas ? :bnone : p.border]
bc = BORDER_COLOR[]

# get length of largest strings to the left and right
Expand All @@ -166,7 +168,7 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
max_len_l += length(ylabel(p)) + 1
end

# offset where the plot (incl border) begins
# offset where the plot (including border) begins
plot_offset = max_len_l + p.margin + p.padding

# padding-string from left to border
Expand Down Expand Up @@ -210,7 +212,7 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
🗹 * border_right_pad * '\n',
🗹,
)
c.visible && print_border(
g.visible && print_border(
io,
print_nocol,
print_color,
Expand All @@ -224,8 +226,6 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
# compute position of ylabel
y_lab_row = round(nr / 2, RoundNearestTiesUp)

postprocess! = preprocess!(c)

# plot all rows
for row ∈ 1:nr
# print left annotations
Expand Down Expand Up @@ -255,12 +255,18 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
# print the left annotation
print_color(io, left_col, left_str)
end
if c.visible
if g.visible
# print left border
print_nocol(io, plot_padding)
print_color(io, bc, bmap[:l])
# print canvas row
print_row(io, print_nocol, print_color, c, row)
print_row(io, print_nocol, print_color, g, row)
if g isa ImageGraphics && g.sixel[]
offset = plot_offset + nc + 1
# 1F: move cursor to the beginning of the previous line, 1 line up
# $(offset)C: move cursor to the right by an amount of $offset columns
write(io, "\e[1F\e[$(offset)C")
end
# print right label and padding
print_color(io, bc, bmap[:r])
end
Expand All @@ -276,7 +282,7 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
io,
print_nocol,
print_color,
c,
g,
row,
p.cmap,
min_max_z_str,
Expand All @@ -286,13 +292,13 @@ function _show(end_io::IO, print_nocol, print_color, p::Plot)
🗷,
)
end
row < nrows(c) && print_nocol(io, '\n')
row < nrows(g) && print_nocol(io, '\n')
end

postprocess!(c)
postprocess!(g)

# draw bottom border
c.visible && print_border(
g.visible && print_border(
io,
print_nocol,
print_color,
Expand Down
Loading