Skip to content
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
1 change: 1 addition & 0 deletions .JuliaFormatter.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
style = "blue"
short_to_long_function_def = false
trailing_comma = "nothing"
always_use_return = false
import_to_using = false
align_struct_field = true
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,5 @@ jobs:
with:
version: ${{ matrix.version }}
- uses: julia-actions/cache@v1
- name: Test XTermColors
run: |
julia --project -e 'using Pkg; Pkg.develop(url="https://github.com/JuliaImages/XTermColors.jl")' # FIXME: remove when registered
julia --project -e 'using Pkg; Pkg.test("XTermColors")'
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
66 changes: 38 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
[![][pkgeval-img]][pkgeval-url]
[![][codecov-img]][codecov-url]

ImageInTerminal.jl is a drop-in package that once imported
changes a how a single `Colorant` and whole `Colorant` arrays (i.e.
Images) are displayed in the interactive REPL.
`ImageInTerminal` is a drop-in package that once imported changes
how a single `Colorant` and whole `Colorant` arrays (regular images)
are displayed in the interactive REPL.
The displayed images will be downscaled to fit into the size of
your active terminal session.

To activate this package simply import it into your Julia session.

### Without this package
### Without ImageInTerminal

```julia
julia> using Images, TestImages
Expand Down Expand Up @@ -45,7 +45,7 @@ julia> colorview(RGB, rand(3, 10, 10))
RGB{Float64}(0.422224,0.914328,0.773111) RGB{Float64}(0.448258,0.955572,0.0445449)
```

### With this package
### Using ImageInTerminal

```julia
julia> using Images, TestImages, ImageInTerminal
Expand All @@ -55,53 +55,63 @@ julia> testimage("cameraman")
julia> colorview(RGB, rand(3, 10, 10))
```

<img src="https://cloud.githubusercontent.com/assets/10854026/22923639/92e3b164-f2a2-11e6-85ea-b92bdc4a63e0.png" alt="ImageInTerminal" width="500">
<img src="https://github.com/JuliaImages/ImageInTerminal.jl/raw/imgs/example.png" alt="Example" width="500">

### Sixel encoder (Julia 1.6+)

If [`Sixel`](https://github.com/johnnychen94/Sixel.jl) (requires Julia 1.6+) is loaded, this package will try to encode
the content using `Sixel` encoder for large images, and thus bring much better image visualization experience in terminal:
If [`Sixel`](https://github.com/johnnychen94/Sixel.jl) is supported by the terminal, this package will encode
the content using a `Sixel` encoder for large images, and thus bring much better image visualization experience in terminal:

<img src="https://user-images.githubusercontent.com/8684355/118361462-20954800-b5be-11eb-8505-9455a0b00ec0.png" alt="Sixel" width="500">
<img src="https://github.com/JuliaImages/ImageInTerminal.jl/raw/imgs/sixel.png" alt="Sixel" width="500">

However, do notice that not all terminals support sixel format.
See [Terminals that support sixel](https://github.com/johnnychen94/Sixel.jl#terminals-that-support-sixel) for more information.

### Display equations

`ImageInTerminal` can be used to display latex equations from [Latexify.jl](https://github.com/korsbo/Latexify.jl), here on `mlterm`:

```julia
using ImageInTerminal, Latexify

render(latexify(:(iħ * (∂Ψ(𝐫, t) / ∂t) = -ħ^2 / 2m * ΔΨ(𝐫, t) + V * Ψ(𝐫, t))), MIME("image/png"))
```

<img src="https://github.com/JuliaImages/ImageInTerminal.jl/raw/imgs/latexify.png" alt="Latexify" width="500">

### 8-bit (256) colors and 24-bit colors

By default this packages will detect if your running terminal supports 24 bit colors, i.e., true color.
If it does, then the image will be displayed in 24-bit colors,
otherwise it will use 256 colors (8-bit) as a fallback option.
To manually switch between 24-bit colors and 256 colors, you can use the internal helpers:
By default this packages will detect if your running terminal supports 24-bit colors (true colors).
If it does, the image will be displayed in 24-bit colors, otherwise it fallbacks to 8-bit (256 colors).
To manually switch between 24-bit and 8-bit colors, you can use the internal helpers:

```julia
using XTermColors
XTermColors.set_colormode(8)
XTermColors.set_colormode(24)
using ImageInTerminal
ImageInTerminal.set_colormode(8)
ImageInTerminal.set_colormode(24)
```

Note that 24 bits format only works as expected if your terminal supports it,
otherwise you are likely to get some random outputs. To check if your terminal
supports 24 bits color, you can check if the environment variable `COLORTERM` is
`24bit` (or `truecolor`).
otherwise you are likely to get some random outputs.
To check if your terminal supports 24 bits color, you can check if
the environment variable `COLORTERM` is set to `24bit` (or `truecolor`).

Here's how images are displayed in 24-bit colors:

<img src="https://user-images.githubusercontent.com/8684355/76688541-a17a4c00-6668-11ea-9fb9-41669fbec07e.png" alt="24bit color" width="500">
<img src="https://github.com/JuliaImages/ImageInTerminal.jl/raw/imgs/cameraman.png" alt="Cameraman" width="500">

### Enable and disable

If you want to temporarily disable this package, you can call `ImageInTerminal.disable_encoding()`. To
restore the encoding functionality with `ImageInTerminal.enable_encoding()`.
If you want to temporarily disable this package, you can call `ImageInTerminal.disable_encoding()`.
To restore the encoding functionality use `ImageInTerminal.enable_encoding()`.

## Troubleshooting

If you see out of place horizontal lines in your Image it means
that your font displays the utilized unicode block-characters
in an unfortunate way. Try changing font or reducing your
terminal's line-spacing. If your font is Source Code Pro, update to
the latest version.

If you see out of place horizontal lines in your Image it means that
your font displays the unicode block-characters in an unfortunate way.
Try changing font or reducing your terminal's line-spacing.
If your font is Source Code Pro, update to the latest version.
It is recommended to use the [JuliaMono](https://juliamono.netlify.app) font.

<!-- URLS -->

Expand Down
92 changes: 66 additions & 26 deletions src/ImageInTerminal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ using ColorTypes
using Crayons
using FileIO

import XTermColors: TermColorDepth, TermColor8bit, TermColor24bit
import ImageBase: restrict
import Sixel

# -------------------------------------------------------------------
# overload default show in the REPL for colorant (arrays)

const encoder_backend = Ref(:ImageInTerminal)
const should_render_image = Ref(true)
const small_imgs_sixel = Ref(false)
const COLORMODE = Ref{TermColorDepth}(TermColor8bit())
const ENCODER_BACKEND = Ref(:XTermColors)
const SHOULD_RENDER_IMAGE = Ref(true)
const SMALL_IMGS_SIXEL = Ref(false)
const RESET = Crayon(; reset=true)
const SUMMARY = Ref(false)

"""
disable_encoding()
Expand All @@ -23,31 +27,31 @@ Disable the image encoding feature and show images as if they are normal arrays.

This can be restored by calling `ImageInTerminal.enable_encoding()`.
"""
disable_encoding() = (should_render_image[] = false)
disable_encoding() = SHOULD_RENDER_IMAGE[] = false

"""
enable_encoding()

Enable the image encoding feature and show images in terminal.

This can be disabled by calling `ImageInTerminal.disable_encoding()`. To choose between
different encoding method, call `XTermColors.set_colormode(8)` or `XTermColors.set_colormode(24)`.
different encoding method, call `ImageInTerminal.set_colormode(8)` or `ImageInTerminal.set_colormode(24)`.
"""
enable_encoding() = (should_render_image[] = true)
enable_encoding() = SHOULD_RENDER_IMAGE[] = true

"""
choose_sixel(img::AbstractArray)

Choose to encode the image using sixels based on the size of the encoded image.
"""
function choose_sixel(img::AbstractArray)
encoder_backend[] == :Sixel || return false
ENCODER_BACKEND[] === :Sixel || return false

# Sixel requires at least 6 pixels in row direction and thus doesn't perform very well for vectors.
# ImageInTerminal encoder is good enough for vector case.
ndims(img) == 1 && return false

if small_imgs_sixel[]
if SMALL_IMGS_SIXEL[]
return true
else
# Small images really do not need sixel encoding.
Expand All @@ -61,8 +65,8 @@ end

# colorant arrays
function Base.show(io::IO, mime::MIME"text/plain", img::AbstractArray{<:Colorant})
if should_render_image[]
println(io, summary(img), ":")
if SHOULD_RENDER_IMAGE[]
SUMMARY[] && println(io, summary(img), ":")
imshow(io, img)
else
invoke(Base.show, Tuple{typeof(io),typeof(mime),AbstractArray}, io, mime, img)
Expand All @@ -71,12 +75,19 @@ end

# colorant
function Base.show(io::IO, mime::MIME"text/plain", color::Colorant)
if should_render_image[]
fgcol = XTermColors._colorant2ansi(color, XTermColors.colormode[])
if SHOULD_RENDER_IMAGE[]
fgcol = XTermColors._colorant2ansi(color, COLORMODE[])
chr = XTermColors._charof(alpha(color))
print(io, Crayon(; foreground=fgcol), chr, chr, " ")
print(io, Crayon(; foreground=:white), color)
print(io, Crayon(; reset=true))
XTermColors._printc(
io,
Crayon(; foreground=fgcol),
chr,
chr,
' ',
Crayon(; foreground=:white),
color,
RESET
)
else
invoke(Base.show, Tuple{typeof(io),typeof(mime),Any}, io, mime, color)
end
Expand All @@ -87,30 +98,36 @@ include("display.jl")
"""
imshow([stream], img, [maxsize])

Displays the given image `img` using unicode characters and
terminal colors (defaults to 256 colors).
Displays the given image `img` using unicode characters and terminal colors (defaults to 256 colors).
`img` has to be an array of `Colorant`.

If working in the REPL, the function tries to choose the encoding
based on the current display size. The image will also be
downsampled to fit into the display.
If working in the REPL, the function tries to choose the encoding based on the current display size.
The image will also be downsampled to fit into the display.

Supported encoding:
- sixel (`Sixel` backend)
- ascii (`XTermColors` backend)
"""

function imshow(io::IO, img::AbstractArray{<:Colorant}, maxsize::Tuple=displaysize(io))
buf = PipeBuffer()
io_color = get(io, :color, false)
iobuf = IOContext(buf, :color => io_color)
if choose_sixel(img)
sixel_encode(io, img)
sixel_encode(iobuf, img)
else
colormode = XTermColors.colormode[]
if ndims(img) > 2
Base.show_nd(io, img, (io, x) -> ascii_display(io, x, colormode, maxsize), true)
Base.show_nd(
iobuf,
img,
(iobuf, x) -> ascii_display(iobuf, x, COLORMODE[], maxsize),
true
)
else
ascii_display(io, img, colormode, maxsize)
ascii_display(iobuf, img, COLORMODE[], maxsize)
end
end
write(io, read(iobuf, String))
end

imshow(img::AbstractArray{<:Colorant}, args...) = imshow(stdout, img, args...)
Expand All @@ -119,12 +136,35 @@ imshow(img, args...) =

sixel_encode(args...; kwargs...) = Sixel.sixel_encode(args...; kwargs...)

"""
set_colormode(bit::Int)

Sets the terminal color depth to the given argument.
"""
function set_colormode(bit::Int)
if bit == 8
COLORMODE[] = TermColor8bit()
elseif bit == 24
COLORMODE[] = TermColor24bit()
else
error("Setting color depth to $bit-bit is not supported, valid modes are:
- 8bit (256 colors)
- 24bit")
end
COLORMODE[]
end

is_24bit_supported() = lowercase(get(ENV, "COLORTERM", "")) in ("24bit", "truecolor")

function __init__()
enable_encoding()

Sixel.is_sixel_supported() && (encoder_backend[] = :Sixel)
# use 24bit if the terminal supports it
is_24bit_supported() && set_colormode(24)

Sixel.is_sixel_supported() && (ENCODER_BACKEND[] = :Sixel)

pushdisplay(TerminalGraphicDisplay(stdout, devnull))
pushdisplay(TerminalGraphicDisplay(stdout))
end

end
11 changes: 5 additions & 6 deletions src/display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ TerminalGraphicDisplay(io::IO) = TerminalGraphicDisplay(io, io)
Base.displayable(::TerminalGraphicDisplay, ::MIME"image/png") = true

function Base.display(d::TerminalGraphicDisplay, ::MIME"image/png", bytes::Vector{UInt8})
# In this case, assume it to be png byte sequences, use FileIO
# to find a decoder for it.
img = FileIO.load(FileIO.Stream{format"PNG"}(IOBuffer(bytes)))
# In this case, assume it to be png byte sequences, use FileIO to find a decoder for it.
img = FileIO.load(FileIO.Stream{format"PNG"}(PipeBuffer(bytes)))
display(d, MIME("image/png"), img)
end

function Base.display(
d::TerminalGraphicDisplay, ::MIME"image/png", img::AbstractArray{<:Colorant}
)
println(d.summary_stream, summary(img), ":")
ImageInTerminal.imshow(d.content_stream, img, colormode[1])
return nothing
SUMMARY[] && println(d.summary_stream, summary(img), ":")
ImageInTerminal.imshow(d.content_stream, img)
nothing
end
Binary file added test/latex_fraction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading