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

support transparency #18

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
18 changes: 4 additions & 14 deletions src/backend/libsixel/encoder.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function (enc::LibSixelEncoder)(io::T, bytes::Matrix) where {T<:IO}
depth = 3 # unused

dither = SixelDither(bytes, height, width, pixelformat, quality_mode; allocator=enc.allocator)
C.sixel_dither_set_transparent(dither.ptr, eltype(bytes) <: TransparentColor ? 0 : -1)

fn_write_cb = @cfunction(sixel_write_callback_function, Cint, (Ptr{Cchar}, Cint, Ref{T}))
output = SixelOutput(fn_write_cb, Ref{T}(io); allocator=enc.allocator)
Expand Down Expand Up @@ -62,9 +63,10 @@ default_pixelformat(::Type{T}) where T<:Union{N0f8, UInt8} = C.SIXEL_PIXELFORMA
default_quality_mode(img)
default_quality_mode(CT)

Infer the default quality mode that used to encode pixel.
Infer the default quality mode that is used to encode pixel.
"""
default_quality_mode(::AbstractArray{CT}) where CT<:Union{Colorant, Real} = default_quality_mode(CT)
default_quality_mode(::Type{CT}) where CT<:TransparentRGB = C.SIXEL_QUALITY_AUTO
default_quality_mode(::Type{CT}) where CT<:AbstractRGB = C.SIXEL_QUALITY_AUTO
# CHECK: highcolor is needed for iTerm2 on macOS, check if this works on other terminal
default_quality_mode(::Type{CT}) where CT<:AbstractGray = C.SIXEL_QUALITY_HIGHCOLOR
Expand All @@ -73,18 +75,6 @@ default_quality_mode(::Type{T}) where T<:Union{N0f8, UInt8} = C.SIXEL_QUALITY_HI

canonical_sixel_eltype(::LibSixelEncoder, ::Type{CT}) where CT<:Colorant = n0f8(CT)
canonical_sixel_eltype(::LibSixelEncoder, ::Type{CT}) where CT<:Color3 = RGB{N0f8}
canonical_sixel_eltype(::LibSixelEncoder, ::Type{CT}) where CT<:Transparent3 = RGBA{N0f8}
canonical_sixel_eltype(::LibSixelEncoder, ::Type{T}) where T<:Real = N0f8
canonical_sixel_eltype(::LibSixelEncoder, ::Type{T}) where T<:Integer = UInt8
# strip the alpha channel before sending into libsixel
canonical_sixel_eltype(lib::LibSixelEncoder, ::Type{CT}) where CT<:Union{ColorAlpha, AlphaColor} =
canonical_sixel_eltype(lib, base_color_type(CT))
# TODO: these special types might have native libsixel support, but I haven't
# figured it out yet.
# canonical_sixel_eltype(::LibSixelEncoder, ::Type{Bool}) = Gray{N0f8}
# canonical_sixel_eltype(::LibSixelEncoder, ::Type{Gray{Bool}}) = Gray{N0f8}
# canonical_sixel_eltype(::LibSixelEncoder, ::Gray24) = Gray{N0f8}
# canonical_sixel_eltype(::LibSixelEncoder, ::RGB24) = RGB{N0f8}
# canonical_sixel_eltype(::LibSixelEncoder, ::ARGB32) = ARGB{N0f8}
# TODO: For unknown reasons, AGray and GrayA encoded by libsixel is not correctly displayed
# in iTerm. Thus here we convert it to `ARGB` types.
# canonical_sixel_eltype(::LibSixelEncoder, ::Union{AGray, GrayA}) = ARGB{N0f8}
74 changes: 41 additions & 33 deletions test/backend/libsixel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
@testset "1d vector" begin
for img in (
repeat(Gray.(0:0.1:0.9), inner=10),
repeat(distinguishable_colors(10), inner=(10, ))
repeat(distinguishable_colors(10), inner=(10,)),
repeat(alphacolor.(distinguishable_colors(10), 0.5), inner=(10,))
)
@info "1d vector: $(eltype(img))"
@test size(img) == (100, )
Expand All @@ -28,9 +29,9 @@
println()
end

io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, img, enc)
bufferdata = String(take!(io))
bufferdata = read(io, String)
h, w = size(img, 1), 1
@test startswith(bufferdata, "\ePq\"1;1;$w;$h") && endswith(bufferdata, "\e\\")
test_sixel_display() do
Expand Down Expand Up @@ -59,7 +60,8 @@
@testset "2d matrix" begin
for img in (
repeat(Gray.(0:0.1:0.9), inner=(10, 50)),
repeat(distinguishable_colors(10), inner=(10, 50))
repeat(distinguishable_colors(10), inner=(10, 50)),
repeat(coloralpha.(distinguishable_colors(10), 0.5), inner=(10, 50)),
)
@info "2d matrix: $(eltype(img))"
h, w = size(img)
Expand All @@ -71,9 +73,9 @@
sixel_encode(img, enc)
println()
end
io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, img, enc)
bufferdata = String(take!(io))
bufferdata = read(io, String)
@test startswith(bufferdata, "\ePq\"1;1;$w;$h") && endswith(bufferdata, "\e\\")
test_sixel_display() do
println(bufferdata)
Expand Down Expand Up @@ -101,7 +103,8 @@
@testset "3d array" begin
for img in (
repeat(Gray.(0:0.1:0.9), inner=(10, 50, 3)),
repeat(distinguishable_colors(5), inner=(20, 50, 3))
repeat(distinguishable_colors(5), inner=(20, 50, 3)),
repeat(alphacolor.(distinguishable_colors(5), 0.5), inner=(20, 50, 3)),
)
@info "3d array: $(eltype(img))"
@test size(img) == (100, 50, 3)
Expand All @@ -112,9 +115,9 @@
sixel_encode(img, enc)
println()
end
io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, img, enc)
bufferdata = String(take!(io))
bufferdata = read(io, String)
h, w, c = size(img)
w = w * c
@test startswith(bufferdata, "\ePq\"1;1;$w;$h") && endswith(bufferdata, "\e\\")
Expand All @@ -141,9 +144,9 @@
sixel_encode(img, enc; transpose=true)
end

io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, img, enc; transpose=true)
bufferdata = String(take!(io))
bufferdata = read(io, String)
h, w, c = size(img)
h = h * c
h, w = w, h # transpose
Expand Down Expand Up @@ -180,9 +183,9 @@
# lazy array that does not occupy full memory
img = Diagonal(repeat(distinguishable_colors(5), inner=20))
w, h = size(img)
io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, img)
bufferdata = String(take!(io))
bufferdata = read(io, String)
@test startswith(bufferdata, "\ePq\"1;1;$w;$h") && endswith(bufferdata, "\e\\")
@info "Test Diagonal array"
test_sixel_display() do
Expand All @@ -196,40 +199,40 @@
@test stride(img, 1) == 50
@test stride(ori_img, 1) == 1

io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, ori_img)
bufferdata = String(take!(io))
bufferdata = read(io, String)
h, w = size(ori_img)
@test startswith(bufferdata, "\ePq\"1;1;$w;$h") && endswith(bufferdata, "\e\\")
io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, img)
@test bufferdata == String(take!(io))
@test bufferdata == read(io, String)

io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, ori_img, transpose=false)
bufferdata = String(take!(io))
bufferdata = read(io, String)
h, w = size(ori_img)
@test startswith(bufferdata, "\ePq\"1;1;$w;$h") && endswith(bufferdata, "\e\\")
io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, img, transpose=false)
@test bufferdata == String(take!(io))
@test bufferdata == read(io, String)
end

@testset "OffsetArray" begin
enc = Sixel.LibSixelEncoder()
for img in [
for img in (
repeat(distinguishable_colors(10), inner=(10, )),
repeat(distinguishable_colors(10), inner=(10, 50)),
repeat(distinguishable_colors(5), inner=(20, 50, 3))
]
io = IOBuffer()
)
io = PipeBuffer()
sixel_encode(io, img, enc)
ref = String(take!(io))
ref = read(io, String)

imgo = OffsetArray(img, OffsetArrays.Origin(0))
io = IOBuffer()
io = PipeBuffer()
sixel_encode(io, imgo, enc)
actual = String(take!(io))
actual = read(io, String)
@test ref == actual
end
end
Expand Down Expand Up @@ -292,25 +295,30 @@ end
dec = Sixel.LibSixelDecoder()
tmp_file = tempname()

alpha_channel = range(0, 1, length=10)
for img in (
repeat(Gray.(0:0.1:0.9), inner=10),
repeat(distinguishable_colors(10), inner=(10, )),
repeat(distinguishable_colors(10), inner=(10,)),
repeat(alphacolor.(distinguishable_colors(10), alpha_channel), inner=(10,)), # add linear alpha channel
repeat(Gray.(0:0.1:0.9), inner=(10, 50)),
repeat(distinguishable_colors(10), inner=(10, 50)),
repeat(Gray.(0:0.1:0.9), inner=(10, 50, 3)),
repeat(distinguishable_colors(5), inner=(20, 50, 3))
repeat(distinguishable_colors(5), inner=(20, 50, 3)),
)
open(tmp_file, "w") do io
sixel_encode(io, img, enc)
end

img_readback = open(tmp_file, "r") do io
sixel_decode(eltype(img), io, dec)
end
end |> x -> reshape(x, size(img))

img_readback = reshape(img_readback, size(img))
# 30 is actually pretty good given that sixel encode always do quantization
@test assess_psnr(img, img_readback) > 30
if eltype(img) <: TransparentColor
@test isapprox.(alpha.(img), repeat(alpha_channel, inner=(10,)), atol=1e-2) |> all
else
# 30 is actually pretty good given that sixel encode always do quantization
@test assess_psnr(img, img_readback) > 30 # FIXME: `TransparentColor`s unsupported
end
end
end
end
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ using LinearAlgebra
using FileIO, TestImages

sixel_output = Sixel.is_sixel_supported()
sixel_output || @info "Current terminal does not support sixel format sequence. Display tests to stdout will be marked as broken."
sixel_output || @info "Current terminal does not support sixel format sequence (or querying failed as is the case with non-interactive mode). Display tests to stdout will be marked as broken."
function test_sixel_display(f)
if sixel_output
@test_nowarn f()
Expand Down