Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

add multimedia I/O mechanism (display, mm_write, and friends)

  • Loading branch information...
commit ebe0912eb084033e034acb56e4b107589b14e9a0 1 parent 227a312
@stevengj stevengj authored
View
4 base/base64.jl
@@ -107,10 +107,6 @@ function write(b::Base64Pipe, x::Uint8)
end
function close(b::Base64Pipe)
- try
- flush(b.io)
- catch
- end
if b.nb > 0
# write leftover bytes + padding
if b.nb == 1
View
16 base/exports.jl
@@ -1121,6 +1121,22 @@ export
wait,
workers,
+# multimedia I/O
+ Display,
+ display,
+ displayable,
+ TextDisplay,
+ istext,
+ MIME,
+ @MIME,
+ reprmime,
+ stringmime,
+ writemime,
+ mimewritable,
+ popdisplay,
+ pushdisplay,
+ redisplay,
+
# distributed arrays
dfill,
distribute,
View
214 base/multimedia.jl
@@ -0,0 +1,214 @@
+module Multimedia
+
+export Display, display, pushdisplay, popdisplay, displayable, redisplay,
+ MIME, @MIME, writemime, reprmime, stringmime, istext,
+ mimewritable, TextDisplay, reinit_displays
+
+###########################################################################
+# We define a singleton type MIME{mime symbol} for each MIME type, so
+# that Julia's dispatch and overloading mechanisms can be used to
+# dispatch writemime and to add conversions for new types.
+
+immutable MIME{mime} end
+
+import Base: show, string, convert
+MIME(s) = MIME{symbol(s)}()
+show{mime}(io::IO, ::MIME{mime}) = print(io, "MIME type ", string(mime))
+string{mime}(::MIME{mime}) = string(mime)
+
+# needs to be a macro so that we can use ::@mime(s) in type declarations
+macro MIME(s)
+ quote
+ MIME{symbol($s)}
+ end
+end
+
+###########################################################################
+# For any type T one can define writemime(io, ::@MIME(mime), x::T) = ...
+# in order to provide a way to export T as a given mime type.
+
+# We provide a fallback text/plain representation of any type:
+writemime(io, ::@MIME("text/plain"), x) = repl_show(io, x)
+
+mimewritable{mime}(::MIME{mime}, T::Type) =
+ method_exists(writemime, (IO, MIME{mime}, T))
+
+# it is convenient to accept strings instead of ::MIME
+writemime(io, m::String, x) = writemime(io, MIME(m), x)
+mimewritable(m::String, T::Type) = mimewritable(MIME(m), T)
+
+###########################################################################
+# MIME types are assumed to be binary data except for a set of types known
+# to be text data (possibly Unicode). istext(m) returns whether
+# m::MIME is text data, and reprmime(m, x) returns x written to either
+# a string (for text m::MIME) or a Vector{Uint8} (for binary m::MIME),
+# assuming the corresponding write_mime method exists. stringmime
+# is like reprmime except that it always returns a string, which in the
+# case of binary data is Base64-encoded.
+#
+# Also, if reprmime is passed a String for a text type or Vector{Uint8} for
+# a binary type, the argument is assumed to already be in the corresponding
+# format and is returned unmodified. This is useful so that raw data can be
+# passed to display(m::MIME, x).
+
+for mime in ["text/cmd", "text/css", "text/csv", "text/html", "text/javascript", "text/plain", "text/vcard", "text/xml", "application/atom+xml", "application/ecmascript", "application/json", "application/rdf+xml", "application/rss+xml", "application/xml-dtd", "application/postscript", "image/svg+xml", "application/x-latex", "application/xhtml+xml", "application/javascript", "application/xml", "model/x3d+xml", "model/x3d+vrml", "model/vrml"]
+ @eval begin
+ istext(::@MIME($mime)) = true
+ reprmime(m::@MIME($mime), x::String) = x
+ reprmime(m::@MIME($mime), x) = sprint(writemime, m, x)
+ stringmime(m::@MIME($mime), x) = reprmime(m, x)
+ # avoid method ambiguities with definitions below:
+ # (Q: should we treat Vector{Uint8} as a bytestring?)
+ reprmime(m::@MIME($mime), x::Vector{Uint8}) = sprint(writemime, m, x)
+ stringmime(m::@MIME($mime), x::Vector{Uint8}) = reprmime(m, x)
+ end
+end
+
+istext(::MIME) = false
+function reprmime(m::MIME, x)
+ s = IOBuffer()
+ writemime(s, m, x)
+ takebuf_array(s)
+end
+reprmime(m::MIME, x::Vector{Uint8}) = x
+stringmime(m::MIME, x) = base64(writemime, m, x)
+stringmime(m::MIME, x::Vector{Uint8}) = base64(write, x)
+
+# it is convenient to accept strings instead of ::MIME
+istext(m::String) = istext(MIME(m))
+reprmime(m::String, x) = reprmime(MIME(m), x)
+stringmime(m::String, x) = stringmime(MIME(m), x)
+
+###########################################################################
+# We have an abstract Display class that can be subclassed in order to
+# define new rich-display output devices. A typical subclass should
+# overload display(d::Display, m::MIME, x) for supported MIME types m,
+# (typically using reprmime or stringmime to get the MIME
+# representation of x) and should also overload display(d::Display, x)
+# to display x in whatever MIME type is preferred by the Display and
+# is writable by x. display(..., x) should throw a MethodError if x
+# cannot be displayed. The return value of display(...) is up to the
+# Display type.
+
+abstract Display
+
+# it is convenient to accept strings instead of ::MIME
+display(d::Display, mime::String, x) = display(d, MIME(mime), x)
+display(mime::String, x) = display(MIME(mime), x)
+displayable(d::Display, mime::String) = displayable(d, MIME(mime))
+displayable(mime::String) = displayable(MIME(mime))
+
+# simplest display, which only knows how to display text/plain
+immutable TextDisplay <: Display
+ io::IO
+end
+display(d::TextDisplay, ::@MIME("text/plain"), x) =
+ writemime(d.io, MIME("text/plain"), x)
+display(d::TextDisplay, x) = display(d, MIME("text/plain"), x)
+
+import Base: close, flush
+flush(d::TextDisplay) = flush(d.io)
+close(d::TextDisplay) = close(d.io)
+
+###########################################################################
+# We keep a stack of Displays, and calling display(x) uses the topmost
+# Display that is capable of displaying x (doesn't throw an error)
+
+const displays = Display[]
+function pushdisplay(d::Display)
+ global displays
+ push!(displays, d)
+end
+popdisplay() = pop!(displays)
+function popdisplay(d::Display)
+ for i = length(displays):-1:1
+ if d == displays[i]
+ return splice!(displays, i)
+ end
+ end
+ throw(KeyError(d))
+end
+function reinit_displays()
+ empty!(displays)
+ pushdisplay(TextDisplay(STDOUT))
+end
+
+function display(x)
+ for i = length(displays):-1:1
+ try
+ return display(displays[i], x)
+ catch e
+ if !isa(e, MethodError)
+ rethrow()
+ end
+ end
+ end
+ throw(MethodError(display, (x,)))
+end
+
+function display(m::MIME, x)
+ for i = length(displays):-1:1
+ try
+ return display(displays[i], m, x)
+ catch e
+ if !isa(e, MethodError)
+ rethrow()
+ end
+ end
+ end
+ throw(MethodError(display, (m, x)))
+end
+
+displayable{D<:Display,mime}(d::D, ::MIME{mime}) =
+ method_exists(display, (D, MIME{mime}, Any))
+
+function displayable(m::MIME)
+ for d in displays
+ if displayable(d, m)
+ return true
+ end
+ end
+ return false
+end
+
+###########################################################################
+# The redisplay method can be overridden by a Display in order to
+# update an existing display (instead of, for example, opening a new
+# window), and is used by the IJulia interface to defer display
+# until the next interactive prompt. This is especially useful
+# for Matlab/Pylab-like stateful plotting interfaces, where
+# a plot is created and then modified many times (xlabel, title, etc.).
+
+function redisplay(x)
+ for i = length(displays):-1:1
+ try
+ return redisplay(displays[i], x)
+ catch e
+ if !isa(e, MethodError)
+ rethrow()
+ end
+ end
+ end
+ throw(MethodError(redisplay, (x,)))
+end
+
+function redisplay(m::Union(MIME,String), x)
+ for i = length(displays):-1:1
+ try
+ return redisplay(displays[i], m, x)
+ catch e
+ if !isa(e, MethodError)
+ rethrow()
+ end
+ end
+ end
+ throw(MethodError(redisplay, (m, x)))
+end
+
+# default redisplay is simply to call display
+redisplay(d::Display, x) = display(d, x)
+redisplay(d::Display, m::Union(MIME,String), x) = display(d, m, x)
+
+###########################################################################
+
+end # module
View
1  base/stream.jl
@@ -232,6 +232,7 @@ function reinit_stdio()
global STDIN = init_stdio(ccall(:jl_stdin_stream ,Ptr{Void},()),0)
global STDOUT = init_stdio(ccall(:jl_stdout_stream,Ptr{Void},()),1)
global STDERR = init_stdio(ccall(:jl_stderr_stream,Ptr{Void},()),2)
+ reinit_displays() # since Multimedia.displays uses STDOUT as fallback
end
flush(::TTY) = nothing
View
2  base/sysimg.jl
@@ -82,6 +82,8 @@ include("stat.jl")
include("fs.jl")
importall .FS
include("process.jl")
+include("multimedia.jl")
+importall .Multimedia
reinit_stdio()
ccall(:jl_get_uv_hooks, Void, ())
include("grisu.jl")
View
162 doc/stdlib/base.rst
@@ -1193,7 +1193,8 @@ Text I/O
``close`` on the ``Base64Pipe`` stream is necessary to complete the
encoding (but does not close ``ostream``).
-.. function:: base64(writefunc, args...), base64(args...)
+.. function:: base64(writefunc, args...)
+ base64(args...)
Given a ``write``-like function ``writefunc``, which takes an I/O
stream as its first argument, ``base64(writefunc, args...)``
@@ -1203,6 +1204,165 @@ Text I/O
using the standard ``write`` functions and returns the base64-encoded
string.
+Multimedia I/O
+--------------
+
+Just as text output is performed by ``print`` and user-defined types
+can indicate their textual representation by overloading ``show``,
+Julia provides a standardized mechanism for rich multimedia output
+(such as images, formatted text, or even audio and video), consisting
+of three parts:
+
+* A function ``display(x)`` to request the richest available multimedia
+ display of a Julia object ``x`` (with a plain-text fallback).
+* Overloading ``writemime`` allows one to indicate arbitrary multimedia
+ representations (keyed by standard MIME types) of user-defined types.
+* Multimedia-capable display backends may be registered by subclassing
+ a generic ``Display`` type and pushing them onto a stack of display
+ backends via ``pushdisplay``.
+
+The base Julia runtime provides only plain-text display, but richer
+displays may be enabled by loading external modules or by using graphical
+Julia environments (such as the IPython-based IJulia notebook).
+
+.. function:: display(x)
+ display(d::Display, x)
+ display(mime, x)
+ display(d::Display, mime, x)
+
+ Display ``x`` using the topmost applicable display in the display stack,
+ typically using the richest supported multimedia output for ``x``, with
+ plain-text ``STDOUT`` output as a fallback. The ``display(d, x)`` variant
+ attempts to display ``x`` on the given display ``d`` only, throwing
+ a ``MethodError`` if ``d`` cannot display objects of this type.
+
+ There are also two variants with a ``mime`` argument (a MIME type
+ string, such as ``"image/png"``) attempt to display ``x`` using the
+ requesed MIME type *only*, throwing a ``MethodError`` if this type
+ is not supported by either the display(s) or by ``x``. With these
+ variants, one can also supply the "raw" data in the requested MIME
+ type by passing ``x::String`` (for MIME types with text-based storage,
+ such as text/html or application/postscript) or ``x::Vector{Uint8}``
+ (for binary MIME types).
+
+.. function:: redisplay(x)
+ redisplay(d::Display, x)
+ redisplay(mime, x)
+ redisplay(d::Display, mime, x)
+
+ By default, the `redisplay` functions simply call ``display``. However,
+ some display backends may override ``redisplay`` to modify an existing
+ display of ``x`` (if any). Using ``redisplay`` is also a hint to the
+ backend that ``x`` may be redisplayed several times, and the backend
+ may choose to defer the display until (for example) the next interactive
+ prompt.
+
+.. function:: displayable(mime)
+ displayable(d::Display, mime)
+
+ Returns a boolean value indicating whether the given ``mime`` type (string)
+ is displayable by any of the displays in the current display stack, or
+ specifically by the display ``d`` in the second variant.
+
+.. function:: writemime(stream, mime, x)
+
+ The ``display`` functions ultimately call ``writemime`` in order to
+ write an object ``x`` as a given ``mime`` type to a given I/O
+ ``stream`` (usually a memory buffer), if possible. In order to
+ provide a rich multimedia representation of a user-defined type
+ ``T``, it is only necessary to define a new ``writemime`` method for
+ ``T``, via: ``writemime(stream, ::@MIME(mime), x::T) = ...``, where
+ ``mime`` is a MIME-type string and the function body calls
+ ``write`` (or similar) to write that representation of ``x`` to
+ ``stream``.
+
+ For example, if you define a ``MyImage`` type and know how to write
+ it to a PNG file, you could define a function ``writemime(stream,
+ ::@MIME("image/png"), x::MyImage) = ...``` to allow your images to
+ be displayed on any PNG-capable ``Display`` (such as IJulia).
+ As usual, be sure to ``import Base.writemime`` in order to add
+ new methods to the built-in Julia function ``writemime``.
+
+ Technically, the ``@MIME(mime)`` macro defines a singleton type for
+ the given ``mime`` string, which allows us to exploit Julia's
+ dispatch mechanisms in determining how to display objects of any
+ given type.
+
+.. function:: mimewritable(mime, T::Type)
+
+ Returns a boolean value indicating whether or not objects of type
+ ``T`` can be written as the given ``mime`` type. (By default, this
+ is determined automatically by the existence of the corresponding
+ ``writemime`` function.)
+
+.. function:: reprmime(mime, x)
+
+ Returns a ``String`` or ``Vector{Uint8}`` containing the
+ representation of ``x`` in the requested ``mime`` type, as written
+ by ``writemime`` (throwing a ``MethodError`` if no appropriate
+ ``writemime`` is available). A ``String`` is returned for MIME
+ types with textual representations (such as ``"text/html"`` or
+ ``"application/postscript"``), whereas binary data is returned as
+ ``Vector{Uint8}``. (The function ``istext(mime)`` returns whether
+ or not Julia treats a given ``mime`` type as text.)
+
+ As a special case, if ``x`` is a ``String`` (for textual MIME types)
+ or a ``Vector{Uint8}`` (for binary MIME types), the ``reprmime`` function
+ assumes that ``x`` is already in the requested ``mime`` format and
+ simply returns ``x``.
+
+.. function:: stringmime(mime, x)
+
+ Returns a ``String`` containing the representation of ``x`` in the
+ requested ``mime`` type. This is similar to ``reprmime`` except
+ that binary data is base64-encoded as an ASCII string.
+
+As mentioned above, one can also define new display backends. For
+example, a module that can display PNG images in a window can register
+this capability with Julia, so that calling `display(x)` on types
+with PNG representations will automatically display the image using
+the module's window.
+
+In order to define a new display backend, one should first create a
+subtype ``D`` of the abstract class ``Display``. Then, for each MIME
+type (``mime`` string) that can be displayed on ``D``, one should
+define a function ``display(d::D, ::@MIME(mime), x) = ...`` that
+displays ``x`` as that MIME type, usually by calling ``reprmime(mime,
+x)``. A ``MethodError`` should be thrown if ``x`` cannot be displayed
+as that MIME type; this is automatic if one calls ``reprmime``.
+Finally, one should define a function ``display(d::D, x)`` that
+queries ``mimewritable(mime, x)`` for the ``mime`` types supported by
+``D`` and displays the "best" one; a ``MethodError`` should be thrown
+if no supported MIME types are found for ``x``. Similarly, some
+subtypes may wish to override ``redisplay(d::D, ...)``. (Again, one
+should ``import Base.display`` to add new methods to ``display``.)
+The return values of these functions are up to the implementation
+(since in some cases it may be useful to return a display "handle" of
+some type). The display functions for ``D`` can then be called
+directly, but they can also be invoked automatically from
+``display(x)`` simply by pushing a new display onto the display-backend
+stack with:
+
+.. function:: pushdisplay(d::Display)
+
+ Pushes a new display ``d`` on top of the global display-backend
+ stack. Calling ``display(x)`` or ``display(mime, x)`` will display
+ ``x`` on the topmost compatible backend in the stack (i.e., the
+ topmost backend that does not throw a ``MethodError``).
+
+.. function:: popdisplay()
+ popdisplay(d::Display)
+
+ Pop the topmost backend off of the display-backend stack, or the
+ topmost copy of ``d`` in the second variant.
+
+.. function:: TextDisplay(stream)
+
+ Returns a ``TextDisplay <: Display``, which can display any object
+ as the text/plain MIME type (only), writing the text representation
+ to the given I/O stream. (The text representation is the same
+ as the way an object is printed in the Julia REPL.)
+
Memory-mapped I/O
-----------------
Please sign in to comment.
Something went wrong with that request. Please try again.