diff --git a/src/IJulia.jl b/src/IJulia.jl index 4d5a4469..82299a33 100644 --- a/src/IJulia.jl +++ b/src/IJulia.jl @@ -38,7 +38,7 @@ import Base.invokelatest import Dates using Dates: now import Random -using Base64: Base64EncodePipe +using Base64: Base64EncodePipe, base64decode import REPL # InteractiveUtils is not used inside IJulia, but loaded in src/kernel.jl diff --git a/src/inline.jl b/src/inline.jl index 9beca6b7..eeaab098 100644 --- a/src/inline.jl +++ b/src/inline.jl @@ -88,14 +88,38 @@ function display(d::InlineDisplay, M::MIME, x) "data" => d))) end + + +# extract width and height from a PNG file header inside a base64 string +function png_wh(img::String) + # PNG header is 8 bytes, 4 byte chunk size, 4 byte IHDR string, 8 bytes for w, h + decoded = base64decode(img[1:32]) # Base64 encodes 6 bits per character + if any(decoded[13:16] .!= b"IHDR") # check if the header looks reasonable + return 0, 0 # unreasonable header, return zeroes. we check for this in display() + end + w, h = ntoh.(reinterpret(Int32, decoded[17:24])) # get the 8 bytes after + return w, h +end + +const retina = Ref(false) # flag for setting retina-type images + # override display to send IPython a dictionary of all supported # output types, so that IPython can choose what to display. function display(d::InlineDisplay, x) undisplay(x) # dequeue previous redisplay(x) + + meta = metadata(x) + data = display_dict(x) + if retina[] && "image/png" in keys(data) # if retina, apply metadata to halve sizes + w, h = png_wh(data["image/png"]) + if w != 0 && h != 0 # avoid setting any metadata if w or h is zero + meta["image/png"] = Dict("width" => w/2, "height" => h/2) + end + end send_ipython(publish[], msg_pub(execute_msg, "display_data", - Dict("metadata" => metadata(x), # optional - "data" => display_dict(x)))) + Dict("metadata" => meta, # optional + "data" => data))) end # we overload redisplay(d, x) to add x to a queue of objects to display, diff --git a/test/inline.jl b/test/inline.jl index 1caceeb5..9d2e0878 100644 --- a/test/inline.jl +++ b/test/inline.jl @@ -1,5 +1,5 @@ using Test -import IJulia: InlineIOContext +import IJulia: InlineIOContext, png_wh @testset "Custom Jupyter inline display" begin @eval struct TestDataType @@ -22,4 +22,12 @@ import IJulia: InlineIOContext Base.show(InlineIOContext(buf), MIME("text/plain"), data) @test String(take!(buf)) == "TestDataType: Jupyter: \"foo\"" + + # test that we can extract a PNG header + @test png_wh( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAMCAYAAACqYHctAAAAE0lEQVR42mNk+P" + * "+/ngENMI4MQQCgfR3py/xS9AAAAABJRU5ErkJggg==") == (5,12) + @test png_wh( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x" + * "8AAwMCAO+ip1sAAAAASUVORK5CYII=") == (1,1) end