Browse Source

added zeromq frame receiver

cc 4 years ago
parent
commit
5b00eae2ac
49 changed files with 10182 additions and 0 deletions
  1. 2 0
      matrix-zmq/.gitignore
  2. 3 0
      matrix-zmq/.gitmodules
  3. 18 0
      matrix-zmq/.travis.yml
  4. 32 0
      matrix-zmq/Dockerfile
  5. 674 0
      matrix-zmq/LICENSE
  6. 32 0
      matrix-zmq/Makefile
  7. 45 0
      matrix-zmq/README.md
  8. 34 0
      matrix-zmq/rpi-rgb-led-matrix/Makefile
  9. 52 0
      matrix-zmq/rpi-rgb-led-matrix/include/canvas.h
  10. 108 0
      matrix-zmq/rpi-rgb-led-matrix/include/content-streamer.h
  11. 127 0
      matrix-zmq/rpi-rgb-led-matrix/include/deprecated-transformer.h
  12. 122 0
      matrix-zmq/rpi-rgb-led-matrix/include/gpio.h
  13. 144 0
      matrix-zmq/rpi-rgb-led-matrix/include/graphics.h
  14. 394 0
      matrix-zmq/rpi-rgb-led-matrix/include/led-matrix-c.h
  15. 504 0
      matrix-zmq/rpi-rgb-led-matrix/include/led-matrix.h
  16. 110 0
      matrix-zmq/rpi-rgb-led-matrix/include/pixel-mapper.h
  17. 86 0
      matrix-zmq/rpi-rgb-led-matrix/include/thread.h
  18. 103 0
      matrix-zmq/rpi-rgb-led-matrix/include/threaded-canvas-manipulator.h
  19. 9 0
      matrix-zmq/rpi-rgb-led-matrix/include/transformer.h
  20. 3 0
      matrix-zmq/rpi-rgb-led-matrix/lib/.gitignore
  21. 196 0
      matrix-zmq/rpi-rgb-led-matrix/lib/Makefile
  22. 188 0
      matrix-zmq/rpi-rgb-led-matrix/lib/bdf-font.cc
  23. 203 0
      matrix-zmq/rpi-rgb-led-matrix/lib/content-streamer.cc
  24. 175 0
      matrix-zmq/rpi-rgb-led-matrix/lib/framebuffer-internal.h
  25. 878 0
      matrix-zmq/rpi-rgb-led-matrix/lib/framebuffer.cc
  26. 28 0
      matrix-zmq/rpi-rgb-led-matrix/lib/gpio-bits.h
  27. 788 0
      matrix-zmq/rpi-rgb-led-matrix/lib/gpio.cc
  28. 153 0
      matrix-zmq/rpi-rgb-led-matrix/lib/gpio.h
  29. 172 0
      matrix-zmq/rpi-rgb-led-matrix/lib/graphics.cc
  30. 287 0
      matrix-zmq/rpi-rgb-led-matrix/lib/hardware-mapping.c
  31. 60 0
      matrix-zmq/rpi-rgb-led-matrix/lib/hardware-mapping.h
  32. 304 0
      matrix-zmq/rpi-rgb-led-matrix/lib/led-matrix-c.cc
  33. 763 0
      matrix-zmq/rpi-rgb-led-matrix/lib/led-matrix.cc
  34. 38 0
      matrix-zmq/rpi-rgb-led-matrix/lib/multiplex-mappers-internal.h
  35. 476 0
      matrix-zmq/rpi-rgb-led-matrix/lib/multiplex-mappers.cc
  36. 461 0
      matrix-zmq/rpi-rgb-led-matrix/lib/options-initialize.cc
  37. 338 0
      matrix-zmq/rpi-rgb-led-matrix/lib/pixel-mapper.cc
  38. 100 0
      matrix-zmq/rpi-rgb-led-matrix/lib/thread.cc
  39. 243 0
      matrix-zmq/rpi-rgb-led-matrix/lib/transformer.cc
  40. 57 0
      matrix-zmq/rpi-rgb-led-matrix/lib/utf8-internal.h
  41. 2 0
      matrix-zmq/rpi-rgb-led-matrix/readme.md
  42. 2 0
      matrix-zmq/rpi-rgb-led-matrix/utils/.gitignore
  43. 49 0
      matrix-zmq/rpi-rgb-led-matrix/utils/Makefile
  44. 306 0
      matrix-zmq/rpi-rgb-led-matrix/utils/README.md
  45. 502 0
      matrix-zmq/rpi-rgb-led-matrix/utils/led-image-viewer.cc
  46. 272 0
      matrix-zmq/rpi-rgb-led-matrix/utils/text-scroller.cc
  47. 426 0
      matrix-zmq/rpi-rgb-led-matrix/utils/video-viewer.cc
  48. 108 0
      matrix-zmq/src/main.cpp
  49. 5 0
      matrix-zmq/travis/push_to_docker.sh

+ 2 - 0
matrix-zmq/.gitignore

@@ -0,0 +1,2 @@
+.vscode
+bin

+ 3 - 0
matrix-zmq/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "rpi-rgb-led-matrix"]
+	path = rpi-rgb-led-matrix
+	url = https://github.com/hzeller/rpi-rgb-led-matrix.git

+ 18 - 0
matrix-zmq/.travis.yml

@@ -0,0 +1,18 @@
+os: linux
+dist: xenial
+language: minimal
+
+services:
+  - docker
+
+install:
+  - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
+
+script:
+  - docker build -t knifa/led-matrix-zmq-server .
+
+deploy:
+  provider: script
+  script: ./travis/push_to_docker.sh
+  on:
+    branch: master

+ 32 - 0
matrix-zmq/Dockerfile

@@ -0,0 +1,32 @@
+FROM arm32v7/debian:buster-slim as builder
+
+RUN apt-get update \
+  && apt-get -y install \
+    build-essential \
+    cmake \
+    libzmq3-dev \
+    libzmq5
+
+COPY ./rpi-rgb-led-matrix /root/rpi-rgb-led-matrix
+WORKDIR /root/rpi-rgb-led-matrix/lib
+RUN make
+
+WORKDIR /root
+COPY ./Makefile /root/Makefile
+COPY ./src /root/src
+RUN make
+
+
+FROM arm32v7/debian:buster-slim
+
+WORKDIR /root
+
+RUN apt-get update \
+  && apt-get -y install \
+    libzmq5
+
+COPY --from=builder /root/bin/led-matrix-zmq-server ./led-matrix-zmq-server
+RUN chmod +x ./led-matrix-zmq-server
+
+EXPOSE 42042/tcp
+ENTRYPOINT ["/root/led-matrix-zmq-server"]

+ 674 - 0
matrix-zmq/LICENSE

@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.

+ 32 - 0
matrix-zmq/Makefile

@@ -0,0 +1,32 @@
+BIN_DIR=bin
+
+BIN=$(BIN_DIR)/led-matrix-zmq-server
+SRC = $(wildcard src/*.cpp)
+OBJ = $(patsubst src/%.cpp, bin/%.o, $(SRC))
+
+
+RGB_INCDIR=rpi-rgb-led-matrix/include
+RGB_LIBDIR=rpi-rgb-led-matrix/lib
+RGB_LIBRARY_NAME=rgbmatrix
+RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
+
+CFLAGS+=-O3 -Wall
+LDFLAGS+=-L$(RGB_LIBDIR) -l$(RGB_LIBRARY_NAME) \
+	 -lpthread -lzmq
+
+all : $(BIN)
+
+$(BIN) : $(OBJ)
+	$(CXX) $(CFLAGS) $(OBJ) -o $@ $(LDFLAGS)
+
+$(BIN_DIR)/%.o : src/%.cpp | $(BIN_DIR)
+	$(CXX) $(CFLAGS) -I$(RGB_INCDIR) -c -o $@ $<
+
+$(BIN_DIR):
+	mkdir $@
+
+clean:
+	rm -f $(OBJ) $(BIN)
+
+FORCE:
+.PHONY: FORCE

+ 45 - 0
matrix-zmq/README.md

@@ -0,0 +1,45 @@
+# led-matrix-zmq-server
+
+[![Build Status](https://travis-ci.org/Knifa/led-matrix-zmq-server.svg?branch=master)](https://travis-ci.org/Knifa/led-matrix-zmq-server)
+
+A tool for streaming frames to [rpi-rgb-led-matrix](https://github.com/hzeller/rpi-rgb-led-matrix/) over ZeroMQ.
+
+Available as a Docker image at [knifa/led-matrix-zmq-server](https://hub.docker.com/r/knifa/led-matrix-zmq-server).
+
+## Building
+
+In addition to `rpi-rgb-led-matrix`, requires `czmq`.
+
+## Usage
+
+You can run the image with e.g., the following command. Mostly, you MUST pass `--privileged` as access to `/dev/mem` is required.
+
+```bash
+docker run \
+  --privileged \
+  --rm \
+  knifa/led-matrix-zmq-server \
+    --led-brightness=50 \
+    --led-cols=64 \
+    --led-slowdown-gpio=2 \
+    --led-pwm-lsb-nanoseconds=50 \
+    --led-rows=32 \
+    --zmq-endpoint=tcp://*:1337
+```
+
+The following server arguments are available:
+
+- `--zmq-endpoint`: The ZMQ endpoint to listen on. Default is `tcp://*:42024`.
+- `--bytes-per-pixel`: Number of *bytes* per pixel. Default is `3` (i.e., `24BPP`)
+
+The remaining matrix arguments are passed directly to `rpi-rgb-led-matrix`. [Check out their README for details](https://github.com/hzeller/rpi-rgb-led-matrix/blob/master/README.md).
+
+### Sending Frames
+
+The server is a simple ZMQ REQ-REP loop. All you need to do is send your frame as a big ol' byte chunk then wait for an empty message back.
+
+Each frame should be in a BGR24 (or however large you passed into `--bytes-per-pixel`) format, with exact size depending on your matrix setup (i.e., whatever `rpi-rgb-led-matrix` says your final canvas size is.)
+
+## License
+
+GNU GPL v3. See [LICENSE](LICENSE).

+ 34 - 0
matrix-zmq/rpi-rgb-led-matrix/Makefile

@@ -0,0 +1,34 @@
+# This toplevel Makefile compiles the library in the lib subdirectory.
+# If you want to see how to integrate the library in your own projects, check
+# out the sub-directories examples-api-use/ and utils/
+RGB_LIBDIR=./lib
+RGB_LIBRARY_NAME=rgbmatrix
+RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
+
+# Some language bindings.
+PYTHON_LIB_DIR=bindings/python
+CSHARP_LIB_DIR=bindings/c\#
+
+all : $(RGB_LIBRARY)
+
+$(RGB_LIBRARY): FORCE
+	$(MAKE) -C $(RGB_LIBDIR)
+	$(MAKE) -C examples-api-use
+
+clean:
+	$(MAKE) -C lib clean
+	$(MAKE) -C utils clean
+	$(MAKE) -C examples-api-use clean
+	$(MAKE) -C $(PYTHON_LIB_DIR) clean
+
+build-csharp:
+	$(MAKE) -C $(CSHARP_LIB_DIR) build
+
+build-python: $(RGB_LIBRARY)
+	$(MAKE) -C $(PYTHON_LIB_DIR) build
+
+install-python: build-python
+	$(MAKE) -C $(PYTHON_LIB_DIR) install
+
+FORCE:
+.PHONY: FORCE

+ 52 - 0
matrix-zmq/rpi-rgb-led-matrix/include/canvas.h

@@ -0,0 +1,52 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#ifndef RPI_CANVAS_H
+#define RPI_CANVAS_H
+#include <stdint.h>
+
+namespace rgb_matrix {
+// An interface for things a Canvas can do. The RGBMatrix implements this
+// interface, so you can use it directly wherever a canvas is needed.
+//
+// This abstraction also allows you to e.g. create delegating
+// implementations that do a particular transformation, e.g. re-map
+// pixels (as you might lay out the physical RGB matrix in a different way),
+// compose images (OR, XOR, transparecy), scale, rotate, anti-alias or
+// translate coordinates in a funky way.
+//
+// It is a good idea to have your applications use the concept of
+// a Canvas to write the content to instead of directly using the RGBMatrix.
+class Canvas {
+public:
+  virtual ~Canvas() {}
+  virtual int width() const = 0;  // Pixels available in x direction.
+  virtual int height() const = 0; // Pixels available in y direction.
+
+  // Set pixel at coordinate (x,y) with given color. Pixel (0,0) is the
+  // top left corner.
+  // Each color is 8 bit (24bpp), 0 black, 255 brightest.
+  virtual void SetPixel(int x, int y,
+                        uint8_t red, uint8_t green, uint8_t blue) = 0;
+
+  // Clear screen to be all black.
+  virtual void Clear() = 0;
+
+  // Fill screen with given 24bpp color.
+  virtual void Fill(uint8_t red, uint8_t green, uint8_t blue) = 0;
+};
+
+}  // namespace rgb_matrix
+#endif  // RPI_CANVAS_H

+ 108 - 0
matrix-zmq/rpi-rgb-led-matrix/include/content-streamer.h

@@ -0,0 +1,108 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+//
+// Abstractions to read and write FrameCanvas objects to streams. This allows
+// you to create canned streams of content with minimal overhead at runtime
+// to play with extreme pixel-throughput which also minimizes overheads in
+// the Pi to avoid stuttering or brightness glitches.
+//
+// The disadvantage is, that this represents the full expanded internal
+// representation of a frame, so is very large memory wise.
+//
+// These abstractions are used in util/led-image-viewer.cc to read and
+// write such animations to disk. It is also used in util/video-viewer.cc
+// to write a version to disk that then can be played with the led-image-viewer.
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include <string>
+
+namespace rgb_matrix {
+class FrameCanvas;
+
+// An abstraction of a data stream.
+class StreamIO {
+public:
+  virtual ~StreamIO() {}
+
+  // Rewind stream.
+  virtual void Rewind() = 0;
+
+  // Read bytes into buffer. Similar to Posix behavior that allows short reads.
+  virtual ssize_t Read(void *buf, size_t count) = 0;
+
+  // Write bytes from buffer. Similar to Posix behavior that allows short
+  // writes.
+  virtual ssize_t Append(const void *buf, size_t count) = 0;
+};
+
+class FileStreamIO : public StreamIO {
+public:
+  explicit FileStreamIO(int fd);
+  ~FileStreamIO();
+
+  virtual void Rewind();
+  virtual ssize_t Read(void *buf, size_t count);
+  virtual ssize_t Append(const void *buf, size_t count);
+
+private:
+  const int fd_;
+};
+
+class MemStreamIO : public StreamIO {
+public:
+  virtual void Rewind();
+  virtual ssize_t Read(void *buf, size_t count);
+  virtual ssize_t Append(const void *buf, size_t count);
+
+private:
+  std::string buffer_;  // super simplistic.
+  size_t pos_;
+};
+
+class StreamWriter {
+public:
+  // Does not take ownership of StreamIO
+  StreamWriter(StreamIO *io);
+
+  // Stream out given canvas at the given time. "hold_time_us" indicates
+  // for how long this frame is to be shown in microseconds.
+  bool Stream(const FrameCanvas &frame, uint32_t hold_time_us);
+
+private:
+  void WriteFileHeader(const FrameCanvas &frame, size_t len);
+
+  StreamIO *const io_;
+  bool header_written_;
+};
+
+class StreamReader {
+public:
+  // Does not take ownership of StreamIO
+  StreamReader(StreamIO *io);
+  ~StreamReader();
+
+  // Go back to the beginning.
+  void Rewind();
+
+  // Get next frame and its timestamp. Returns 'false' if there is an error
+  // or end of stream reached..
+  bool GetNext(FrameCanvas *frame, uint32_t* hold_time_us);
+
+private:
+  enum State {
+    STREAM_AT_BEGIN,
+    STREAM_READING,
+    STREAM_ERROR,
+  };
+  bool ReadFileHeader(const FrameCanvas &frame);
+
+  StreamIO *io_;
+  size_t frame_buf_size_;
+  State state_;
+
+  char *header_frame_buffer_;
+};
+}

+ 127 - 0
matrix-zmq/rpi-rgb-led-matrix/include/deprecated-transformer.h

@@ -0,0 +1,127 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
+// Copyright (C) 2015 Christoph Friedrich <christoph.friedrich@vonaffenfels.de>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+/*
+ * Deprecated.
+ *
+ * Use PixelMapper instead. See pixel-mapper.h
+ */
+#ifndef RPI_TRANSFORMER_H
+#define RPI_TRANSFORMER_H
+
+#ifndef REMOVE_DEPRECATED_TRANSFORMERS
+
+#include <vector>
+#include <cstddef>
+
+#include "canvas.h"
+
+namespace rgb_matrix {
+
+// Transformer for RotateCanvas
+class RotateTransformer : public CanvasTransformer {
+public:
+  RotateTransformer(int angle = 0);
+  virtual ~RotateTransformer();
+
+  void SetAngle(int angle);
+  inline int angle() { return angle_; }
+
+  virtual Canvas *Transform(Canvas *output);
+
+private:
+  // Transformer canvas to rotate the input canvas in 90° steps
+  class TransformCanvas;
+
+  int angle_;
+  TransformCanvas *const canvas_;
+};
+
+// Transformer for linked transformer objects
+// First transformer added will be considered last
+// (so it would the transformer that gets the original Canvas object)
+class LinkedTransformer : public CanvasTransformer {
+public:
+  typedef std::vector<CanvasTransformer*> List;
+
+  LinkedTransformer() {}
+  LinkedTransformer(List transformer_list) : list_(transformer_list) {}
+
+  // The ownership of the given transformers is _not_ taken over unless
+  // you explicitly call DeleteTransformers().
+  void AddTransformer(CanvasTransformer *transformer);
+  void AddTransformer(List transformer_list);
+  void SetTransformer(List transformer_list);
+
+  // Delete transformers that have been added or set.
+  void DeleteTransformers();
+
+  // -- CanvasTransformer interface
+  virtual Canvas *Transform(Canvas *output);
+
+private:
+  List list_;
+};
+
+// If we take a long chain of panels and arrange them in a U-shape, so
+// that after half the panels we bend around and continue below. This way
+// we have a panel that has double the height but only uses one chain.
+// A single chain display with four 32x32 panels can then be arranged in this
+// 64x64 display:
+//    [<][<][<][<] }- Raspbery Pi connector
+//
+// can be arranged in this U-shape
+//    [<][<] }----- Raspberry Pi connector
+//    [>][>]
+//
+// This works for more than one chain as well. Here an arrangement with
+// two chains with 8 panels each
+//   [<][<][<][<]  }-- Pi connector #1
+//   [>][>][>][>]
+//   [<][<][<][<]  }--- Pi connector #2
+//   [>][>][>][>]
+class UArrangementTransformer : public CanvasTransformer {
+public:
+  UArrangementTransformer(int parallel = 1);
+  ~UArrangementTransformer();
+
+  virtual Canvas *Transform(Canvas *output);
+
+private:
+  class TransformCanvas;
+
+  TransformCanvas *const canvas_;
+};
+
+// Something used before, but it had a confusing 180 degree turn and was not
+// ready for multiple parallel chains. So consider using the
+// U-ArrangementTransformer instead.
+class LargeSquare64x64Transformer : public CanvasTransformer {
+public:
+  LargeSquare64x64Transformer();
+  virtual Canvas *Transform(Canvas *output);
+
+private:
+  // This old transformer was a little off and rotated the whole result in
+  // the end. simulated that here.
+  UArrangementTransformer arrange_;
+  RotateTransformer rotated_;
+}  __attribute__((deprecated));
+
+} // namespace rgb_matrix
+
+#endif  // REMOVE_DEPRECATED_TRANSFORMERS
+#endif  // RPI_TRANSFORMER_H

+ 122 - 0
matrix-zmq/rpi-rgb-led-matrix/include/gpio.h

@@ -0,0 +1,122 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#ifndef RPI_GPIO_H
+#define RPI_GPIO_H
+
+#include <stdint.h>
+
+#include <vector>
+
+// Putting this in our namespace to not collide with other things called like
+// this.
+namespace rgb_matrix {
+// For now, everything is initialized as output.
+class GPIO {
+ public:
+  // Available bits that actually have pins.
+  static const uint32_t kValidBits;
+
+  GPIO();
+
+  // Initialize before use. Returns 'true' if successful, 'false' otherwise
+  // (e.g. due to a permission problem).
+  bool Init(int
+#if RGB_SLOWDOWN_GPIO
+            slowdown = RGB_SLOWDOWN_GPIO
+#else
+            slowdown = 1
+#endif
+            );
+
+  // Initialize outputs.
+  // Returns the bits that were available and could be set for output.
+  // (never use the optional adafruit_hack_needed parameter, it is used
+  // internally to this library).
+  uint32_t InitOutputs(uint32_t outputs, bool adafruit_hack_needed = false);
+
+  // Request given bitmap of GPIO inputs.
+  // Returns the bits that were available and could be reserved.
+  uint32_t RequestInputs(uint32_t inputs);
+
+  // Set the bits that are '1' in the output. Leave the rest untouched.
+  inline void SetBits(uint32_t value) {
+    if (!value) return;
+    *gpio_set_bits_ = value;
+    for (int i = 0; i < slowdown_; ++i) {
+      *gpio_set_bits_ = value;
+    }
+  }
+
+  // Clear the bits that are '1' in the output. Leave the rest untouched.
+  inline void ClearBits(uint32_t value) {
+    if (!value) return;
+    *gpio_clr_bits_ = value;
+    for (int i = 0; i < slowdown_; ++i) {
+      *gpio_clr_bits_ = value;
+    }
+  }
+
+  // Write all the bits of "value" mentioned in "mask". Leave the rest untouched.
+  inline void WriteMaskedBits(uint32_t value, uint32_t mask) {
+    // Writing a word is two operations. The IO is actually pretty slow, so
+    // this should probably  be unnoticable.
+    ClearBits(~value & mask);
+    SetBits(value & mask);
+  }
+
+  inline void Write(uint32_t value) { WriteMaskedBits(value, output_bits_); }
+  inline uint32_t Read() const { return *gpio_read_bits_ & input_bits_; }
+
+ private:
+  uint32_t output_bits_;
+  uint32_t input_bits_;
+  uint32_t reserved_bits_;
+  int slowdown_;
+  volatile uint32_t *gpio_set_bits_;
+  volatile uint32_t *gpio_clr_bits_;
+  volatile uint32_t *gpio_read_bits_;
+};
+
+// A PinPulser is a utility class that pulses a GPIO pin. There can be various
+// implementations.
+class PinPulser {
+public:
+  // Factory for a PinPulser. Chooses the right implementation depending
+  // on the context (CPU and which pins are affected).
+  // "gpio_mask" is the mask that should be output (since we only
+  //   need negative pulses, this is what it does)
+  // "nano_wait_spec" contains a list of time periods we'd like
+  //   invoke later. This can be used to pre-process timings if needed.
+  static PinPulser *Create(GPIO *io, uint32_t gpio_mask,
+                           bool allow_hardware_pulsing,
+                           const std::vector<int> &nano_wait_spec);
+
+  virtual ~PinPulser() {}
+
+  // Send a pulse with a given length (index into nano_wait_spec array).
+  virtual void SendPulse(int time_spec_number) = 0;
+
+  // If SendPulse() is asynchronously implemented, wait for pulse to finish.
+  virtual void WaitPulseFinished() {}
+};
+
+// Get rolling over microsecond counter. We get this from a hardware register
+// if possible and a terrible slow fallback otherwise.
+uint32_t GetMicrosecondCounter();
+
+}  // end namespace rgb_matrix
+
+#endif  // RPI_GPIO_H

+ 144 - 0
matrix-zmq/rpi-rgb-led-matrix/include/graphics.h

@@ -0,0 +1,144 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Very simple graphics library to do simple things.
+//
+// Might be useful to consider using Cairo instead and just have an interface
+// between that and the Canvas. Well, this is a quick set of things to get
+// started (and nicely self-contained).
+#ifndef RPI_GRAPHICS_H
+#define RPI_GRAPHICS_H
+
+#include "canvas.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <map>
+
+namespace rgb_matrix {
+struct Color {
+  Color() : r(0), g(0), b(0) {}
+  Color(uint8_t rr, uint8_t gg, uint8_t bb) : r(rr), g(gg), b(bb) {}
+  uint8_t r;
+  uint8_t g;
+  uint8_t b;
+};
+
+// Font loading bdf files. If this ever becomes more types, just make virtual
+// base class.
+class Font {
+public:
+  // Initialize font, but it is only usable after LoadFont() has been called.
+  Font();
+  ~Font();
+
+  bool LoadFont(const char *path);
+
+  // Return height of font in pixels. Returns -1 if font has not been loaded.
+  int height() const { return font_height_; }
+
+  // Return baseline. Pixels from the topline to the baseline.
+  int baseline() const { return base_line_; }
+
+  // Return width of given character, or -1 if font is not loaded or character
+  // does not exist.
+  int CharacterWidth(uint32_t unicode_codepoint) const;
+
+  // Draws the unicode character at position "x","y"
+  // with "color" on "background_color" (background_color can be NULL for
+  // transparency.
+  // The "y" position is the baseline of the font.
+  // If we don't have it in the font, draws the replacement character "�" if
+  // available.
+  // Returns how much we advance on the screen, which is the width of the
+  // character or 0 if we didn't draw any chracter.
+  int DrawGlyph(Canvas *c, int x, int y,
+                const Color &color, const Color *background_color,
+                uint32_t unicode_codepoint) const;
+
+  // Same without background. Deprecated, use the one above instead.
+  int DrawGlyph(Canvas *c, int x, int y, const Color &color,
+                uint32_t unicode_codepoint) const;
+
+  // Create a new font derived from this font, which represents an outline
+  // of the original font, essentially pixels tracing around the original
+  // letter.
+  // This can be used in situations in which it is desirable to frame a letter
+  // in a different color to increase contrast.
+  // The ownership of the returned pointer is passed to the caller.
+  Font *CreateOutlineFont() const;
+
+private:
+  Font(const Font& x);  // No copy constructor. Use references or pointer instead.
+
+  struct Glyph;
+  typedef std::map<uint32_t, Glyph*> CodepointGlyphMap;
+
+  const Glyph *FindGlyph(uint32_t codepoint) const;
+
+  int font_height_;
+  int base_line_;
+  CodepointGlyphMap glyphs_;
+};
+
+// -- Some utility functions.
+
+// Utility function: set an image from the given buffer containting pixels.
+//
+// Draw image of size "image_width" and "image_height" from pixel at
+// canvas-offset "canvas_offset_x", "canvas_offset_y". Image will be shown
+// cropped on the edges if needed.
+//
+// The canvas offset can be negative, i.e. the image start can be shifted
+// outside the image frame on the left/top edge.
+//
+// The buffer needs to be organized as rows with columns of three bytes
+// organized as rgb or bgr. Thus the size of the buffer needs to be exactly
+// (3 * image_width * image_height) bytes.
+//
+// The "image_buffer" parameters contains the data, "buffer_size_bytes" the
+// size in bytes.
+//
+// If "is_bgr" is true, the buffer is treated as BGR pixel arrangement instead
+// of RGB.
+// Returns 'true' if image was shown within canvas.
+bool SetImage(Canvas *c, int canvas_offset_x, int canvas_offset_y,
+              const uint8_t *image_buffer, size_t buffer_size_bytes,
+              int image_width, int image_height,
+              bool is_bgr);
+
+// Draw text, a standard NUL terminated C-string encoded in UTF-8,
+// with given "font" at "x","y" with "color".
+// "color" always needs to be set (hence it is a reference),
+// "background_color" is a pointer to optionally be NULL for transparency.
+// "kerning_offset" allows for additional spacing between characters (can be
+// negative)
+// Returns how many pixels we advanced on the screen.
+int DrawText(Canvas *c, const Font &font, int x, int y,
+             const Color &color, const Color *background_color,
+             const char *utf8_text, int kerning_offset = 0);
+
+// Same without background. Deprecated, use the one above instead.
+int DrawText(Canvas *c, const Font &font, int x, int y, const Color &color,
+             const char *utf8_text);
+
+// Draw text, a standard NUL terminated C-string encoded in UTF-8,
+// with given "font" at "x","y" with "color".
+// Draw text as above, but vertically (top down).
+// The text is a standard NUL terminated C-string encoded in UTF-8.
+// "font, "x", "y", "color" and "background_color" are same as DrawText().
+// "kerning_offset" allows for additional spacing between characters (can be
+// negative).
+// Returns font height to advance up on the screen.
+int VerticalDrawText(Canvas *c, const Font &font, int x, int y,
+                     const Color &color, const Color *background_color,
+                     const char *utf8_text, int kerning_offset = 0);
+
+// Draw a circle centered at "x", "y", with a radius of "radius" and with "color"
+void DrawCircle(Canvas *c, int x, int y, int radius, const Color &color);
+
+// Draw a line from "x0", "y0" to "x1", "y1" and with "color"
+void DrawLine(Canvas *c, int x0, int y0, int x1, int y1, const Color &color);
+
+}  // namespace rgb_matrix
+
+#endif  // RPI_GRAPHICS_H

+ 394 - 0
matrix-zmq/rpi-rgb-led-matrix/include/led-matrix-c.h

@@ -0,0 +1,394 @@
+/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+ * Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+ *
+ * Controlling 16x32 or 32x32 RGB matrixes via GPIO. It allows daisy chaining
+ * of a string of these, and also connecting a parallel string on newer
+ * Raspberry Pis with more GPIO pins available.
+ *
+ * This is a C-binding (for the C++ library) to allow easy binding and
+ * integration with other languages. The symbols are exported in librgbmatrix.a
+ * and librgbmatrix.so. You still need to call the final link with
+ *
+ * See examples-api-use/c-example.c for a usage example.
+ *
+ */
+#ifndef RPI_RGBMATRIX_C_H
+#define RPI_RGBMATRIX_C_H
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+struct RGBLedMatrix;
+struct LedCanvas;
+struct LedFont;
+
+/**
+ * Parameters to create a new matrix.
+ *
+ * To get the defaults, non-set values have to be initialized to zero, so you
+ * should zero out this struct before setting anything.
+ */
+struct RGBLedMatrixOptions {
+  /*
+   * Name of the hardware mapping used. If passed NULL here, the default
+   * is used.
+   */
+  const char *hardware_mapping;
+
+  /* The "rows" are the number of rows supported by the display, so 32 or 16.
+   * Default: 32.
+   * Corresponding flag: --led-rows
+   */
+  int rows;
+
+  /* The "cols" are the number of columns per panel. Typically something
+   * like 32, but also 64 is possible. Sometimes even 40.
+   * cols * chain_length is the total length of the display, so you can
+   * represent a 64 wide display as cols=32, chain=2 or cols=64, chain=1;
+   * same thing.
+   * Flag: --led-cols
+   */
+  int cols;
+
+  /* The chain_length is the number of displays daisy-chained together
+   * (output of one connected to input of next). Default: 1
+   * Corresponding flag: --led-chain
+   */
+  int chain_length;
+
+  /* The number of parallel chains connected to the Pi; in old Pis with 26
+   * GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can
+   * also be 2 or 3. The effective number of pixels in vertical direction is
+   * then thus rows * parallel. Default: 1
+   * Corresponding flag: --led-parallel
+   */
+  int parallel;
+
+  /* Set PWM bits used for output. Default is 11, but if you only deal with
+   * limited comic-colors, 1 might be sufficient. Lower require less CPU and
+   * increases refresh-rate.
+   * Corresponding flag: --led-pwm-bits
+   */
+  int pwm_bits;
+
+  /* Change the base time-unit for the on-time in the lowest
+   * significant bit in nanoseconds.
+   * Higher numbers provide better quality (more accurate color, less
+   * ghosting), but have a negative impact on the frame rate.
+   * Corresponding flag: --led-pwm-lsb-nanoseconds
+   */
+  int pwm_lsb_nanoseconds;
+
+  /* The lower bits can be time-dithered for higher refresh rate.
+   * Corresponding flag: --led-pwm-dither-bits
+   */
+  int pwm_dither_bits;
+
+  /* The initial brightness of the panel in percent. Valid range is 1..100
+   * Corresponding flag: --led-brightness
+   */
+  int brightness;
+
+  /* Scan mode: 0=progressive, 1=interlaced
+   * Corresponding flag: --led-scan-mode
+   */
+  int scan_mode;
+
+  /* Default row address type is 0, corresponding to direct setting of the
+   * row, while row address type 1 is used for panels that only have A/B,
+   * typically some 64x64 panels
+   */
+  int row_address_type;  /* Corresponding flag: --led-row-addr-type */
+
+  /*  Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker (typical 1:8)
+   */
+  int multiplexing;
+
+  /* In case the internal sequence of mapping is not "RGB", this contains the
+   * real mapping. Some panels mix up these colors.
+   */
+  const char *led_rgb_sequence;     /* Corresponding flag: --led-rgb-sequence */
+
+  /* A string describing a sequence of pixel mappers that should be applied
+   * to this matrix. A semicolon-separated list of pixel-mappers with optional
+   * parameter.
+   */
+  const char *pixel_mapper_config;  /* Corresponding flag: --led-pixel-mapper */
+
+  /*
+   * Panel type. Typically just NULL, but certain panels (FM6126) require
+   * an initialization sequence
+   */
+  const char *panel_type;  /* Corresponding flag: --led-panel-type */
+
+  /** The following are boolean flags, all off by default **/
+
+  /* Allow to use the hardware subsystem to create pulses. This won't do
+   * anything if output enable is not connected to GPIO 18.
+   * Corresponding flag: --led-hardware-pulse
+   */
+  char disable_hardware_pulsing;
+  char show_refresh_rate;     /* Corresponding flag: --led-show-refresh    */
+  char inverse_colors;        /* Corresponding flag: --led-inverse         */
+
+  /* Limit refresh rate of LED panel. This will help on a loaded system
+   * to keep a constant refresh rate. <= 0 for no limit.
+   */
+  int limit_refresh_rate_hz;     /* Corresponding flag: --led-limit-refresh */
+};
+
+/**
+ * Runtime options to simplify doing common things for many programs such as
+ * dropping privileges and becoming a daemon.
+ */
+struct RGBLedRuntimeOptions {
+  int gpio_slowdown;    // 0 = no slowdown.          Flag: --led-slowdown-gpio
+
+  // ----------
+  // If the following options are set to disabled with -1, they are not
+  // even offered via the command line flags.
+  // ----------
+
+  // Thre are three possible values here
+  //   -1 : don't leave choise of becoming daemon to the command line parsing.
+  //        If set to -1, the --led-daemon option is not offered.
+  //    0 : do not becoma a daemon, run in forgreound (default value)
+  //    1 : become a daemon, run in background.
+  //
+  // If daemon is disabled (= -1), the user has to call
+  // RGBMatrix::StartRefresh() manually once the matrix is created, to leave
+  // the decision to become a daemon
+  // after the call (which requires that no threads have been started yet).
+  // In the other cases (off or on), the choice is already made, so the thread
+  // is conveniently already started for you.
+  int daemon;           // -1 disabled. 0=off, 1=on. Flag: --led-daemon
+
+  // Drop privileges from 'root' to 'daemon' once the hardware is initialized.
+  // This is usually a good idea unless you need to stay on elevated privs.
+  int drop_privileges;  // -1 disabled. 0=off, 1=on. flag: --led-drop-privs
+
+  // By default, the gpio is initialized for you, but if you run on a platform
+  // not the Raspberry Pi, this will fail. If you don't need to access GPIO
+  // e.g. you want to just create a stream output (see content-streamer.h),
+  // set this to false.
+  bool do_gpio_init;
+};
+
+/**
+ * Universal way to create and initialize a matrix.
+ * The "options" struct (if not NULL) contains all default configuration values
+ * chosen by the programmer to create the matrix.
+ *
+ * If "argc" and "argv" are provided, this function also reads command line
+ * flags provided, that then can override any of the defaults given.
+ * The arguments that have been used from the command line are removed from
+ * the argv list (and argc is adjusted) - that way these don't mess with your
+ * own command line handling.
+ *
+ * The actual options used are filled back into the "options" struct if not
+ * NULL.
+ *
+ * Usage:
+ * ----------------
+ * int main(int argc, char **argv) {
+ *   struct RGBLedMatrixOptions options;
+ *   memset(&options, 0, sizeof(options));
+ *   options.rows = 32;            // You can set defaults if you want.
+ *   options.chain_length = 1;
+ *   struct RGBLedMatrix *matrix = led_matrix_create_from_options(&options,
+ *                                                                &argc, &argv);
+ *   if (matrix == NULL) {
+ *      led_matrix_print_flags(stderr);
+ *      return 1;
+ *   }
+ *   // do additional commandline handling; then use matrix...
+ * }
+ * ----------------
+ */
+struct RGBLedMatrix *led_matrix_create_from_options(
+             struct RGBLedMatrixOptions *options, int *argc, char ***argv);
+
+/* Same, but does not modify the argv array. */
+struct RGBLedMatrix *led_matrix_create_from_options_const_argv(
+             struct RGBLedMatrixOptions *options, int argc, char **argv);
+
+/**
+ * The way to completely initialize your matrix without using command line
+ * flags to initialize some things.
+ *
+ * The actual options used are filled back into the "options" and "rt_options"
+ * struct if not NULL. If they are null, the default value is used.
+ *
+ * Usage:
+ * ----------------
+ * int main(int argc, char **argv) {
+ *   struct RGBLedMatrixOptions options;
+ *   struct RGBLedRuntimeOptions rt_options;
+ *   memset(&options, 0, sizeof(options));
+ *   memset(&rt_options, 0, sizeof(rt_options));
+ *   options.rows = 32;            // You can set defaults if you want.
+ *   options.chain_length = 1;
+ *   rt_options.gpio_slowdown = 4;
+ *   struct RGBLedMatrix *matrix = led_matrix_create_from_options_and_rt_options(&options, &rt_options);
+ *   if (matrix == NULL) {
+ *      return 1;
+ *   }
+ *   // do additional commandline handling; then use matrix...
+ * }
+ * ----------------
+ */
+struct RGBLedMatrix *led_matrix_create_from_options_and_rt_options(
+  struct RGBLedMatrixOptions *opts, struct RGBLedRuntimeOptions * rt_opts);
+
+/**
+ * Print available LED matrix options.
+ */
+void led_matrix_print_flags(FILE *out);
+
+/**
+ * Simple form of led_matrix_create_from_options() with just the few
+ * main options. Returns NULL if that was not possible.
+ * The "rows" are the number of rows supported by the display, so 32, 16 or 8.
+ *
+ * Number of "chained_display"s tells many of these are daisy-chained together
+ * (output of one connected to input of next).
+ *
+ * The "parallel_display" number determines if there is one or two displays
+ * connected in parallel to the GPIO port - this only works with newer
+ * Raspberry Pi that have 40 interface pins.
+ *
+ * This creates a realtime thread and requires root access to access the GPIO
+ * pins.
+ * So if you run this in a daemon, this should be called after becoming a
+ * daemon (as fork/exec stops threads) and before dropping privileges.
+ */
+struct RGBLedMatrix *led_matrix_create(int rows, int chained, int parallel);
+
+
+/**
+ * Stop matrix and free memory.
+ * Always call before the end of the program to properly reset the hardware
+ */
+void led_matrix_delete(struct RGBLedMatrix *matrix);
+
+
+/**
+ * Get active canvas from LED matrix for you to draw on.
+ * Ownership of returned pointer stays with the matrix, don't free().
+ */
+struct LedCanvas *led_matrix_get_canvas(struct RGBLedMatrix *matrix);
+
+/** Return size of canvas. */
+void led_canvas_get_size(const struct LedCanvas *canvas,
+                         int *width, int *height);
+
+/** Set pixel at (x, y) with color (r,g,b). */
+void led_canvas_set_pixel(struct LedCanvas *canvas, int x, int y,
+                          uint8_t r, uint8_t g, uint8_t b);
+
+/** Clear screen (black). */
+void led_canvas_clear(struct LedCanvas *canvas);
+
+/** Fill matrix with given color. */
+void led_canvas_fill(struct LedCanvas *canvas, uint8_t r, uint8_t g, uint8_t b);
+
+/*** API to provide double-buffering. ***/
+
+/**
+ * Create a new canvas to be used with led_matrix_swap_on_vsync()
+ * Ownership of returned pointer stays with the matrix, don't free().
+ */
+struct LedCanvas *led_matrix_create_offscreen_canvas(struct RGBLedMatrix *matrix);
+
+/**
+ * Swap the given canvas (created with create_offscreen_canvas) with the
+ * currently active canvas on vsync (blocks until vsync is reached).
+ * Returns the previously active canvas. So with that, you can create double
+ * buffering:
+ *
+ *   struct LedCanvas *offscreen = led_matrix_create_offscreen_canvas(...);
+ *   led_canvas_set_pixel(offscreen, ...);   // not shown until swap-on-vsync
+ *   offscreen = led_matrix_swap_on_vsync(matrix, offscreen);
+ *   // The returned buffer, assigned to offscreen, is now the inactive buffer
+ *   // fill, then swap again.
+ */
+struct LedCanvas *led_matrix_swap_on_vsync(struct RGBLedMatrix *matrix,
+                                           struct LedCanvas *canvas);
+
+uint8_t led_matrix_get_brightness(struct RGBLedMatrix *matrix);
+void led_matrix_set_brightness(struct RGBLedMatrix *matrix, uint8_t brightness);
+
+// Utility function: set an image from the given buffer containting pixels.
+//
+// Draw image of size "image_width" and "image_height" from pixel at
+// canvas-offset "canvas_offset_x", "canvas_offset_y". Image will be shown
+// cropped on the edges if needed.
+//
+// The canvas offset can be negative, i.e. the image start can be shifted
+// outside the image frame on the left/top edge.
+//
+// The buffer needs to be organized as rows with columns of three bytes
+// organized as rgb or bgr. Thus the size of the buffer needs to be exactly
+// (3 * image_width * image_height) bytes.
+//
+// The "image_buffer" parameters contains the data, "buffer_size_bytes" the
+// size in bytes.
+//
+// If "is_bgr" is 1, the buffer is treated as BGR pixel arrangement instead
+// of RGB with is_bgr = 0.
+void set_image(struct LedCanvas *c, int canvas_offset_x, int canvas_offset_y,
+               const uint8_t *image_buffer, size_t buffer_size_bytes,
+               int image_width, int image_height,
+               char is_bgr);
+
+// Load a font given a path to a font file containing a bdf font.
+struct LedFont *load_font(const char *bdf_font_file);
+
+// Read the baseline of a font
+int baseline_font(struct LedFont *font);
+
+// Read the height of a font
+int height_font(struct LedFont *font);
+
+// Creates an outline font based on an existing font instance
+struct LedFont *create_outline_font(struct LedFont *font);
+
+// Delete a font originally created from load_font.
+void delete_font(struct LedFont *font);
+
+int draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
+              uint8_t r, uint8_t g, uint8_t b,
+              const char *utf8_text, int kerning_offset);
+
+int vertical_draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
+                       uint8_t r, uint8_t g, uint8_t b,
+                       const char *utf8_text, int kerning_offset);
+
+void draw_circle(struct LedCanvas *c, int x, int y, int radius,
+                 uint8_t r, uint8_t g, uint8_t b);
+
+void draw_line(struct LedCanvas *c, int x0, int y0, int x1, int y1,
+               uint8_t r, uint8_t g, uint8_t b);
+
+#ifdef  __cplusplus
+}  // extern C
+#endif
+
+#endif

+ 504 - 0
matrix-zmq/rpi-rgb-led-matrix/include/led-matrix.h

@@ -0,0 +1,504 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+// Controlling 16x32 or 32x32 RGB matrixes via GPIO. It allows daisy chaining
+// of a string of these, and also connecting a parallel string on newer
+// Raspberry Pis with more GPIO pins available.
+
+#ifndef RPI_RGBMATRIX_H
+#define RPI_RGBMATRIX_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include <string>
+#include <vector>
+
+#include "canvas.h"
+#include "thread.h"
+#include "pixel-mapper.h"
+
+namespace rgb_matrix {
+class RGBMatrix;
+class FrameCanvas;   // Canvas for Double- and Multibuffering
+struct RuntimeOptions;
+
+// The RGB matrix provides the framebuffer and the facilities to constantly
+// update the LED matrix.
+//
+// This implement the Canvas interface that represents the display with
+// (led_cols * chained_displays)x(rows * parallel_displays) pixels.
+//
+// If can do multi-buffering using the CreateFrameCanvas() and SwapOnVSync()
+// methods. This is useful for animations and to prevent tearing.
+//
+// If you arrange the panels in a different way in the physical space, write
+// a CanvasTransformer that does coordinate remapping and which should be added
+// to the transformers, like with UArrangementTransformer in demo-main.cc.
+class RGBMatrix : public Canvas {
+public:
+  // Options to initialize the RGBMatrix. Also see the main README.md for
+  // detailed descriptions of the command line flags.
+  struct Options {
+    Options();   // Creates a default option set.
+
+    // Validate the options and possibly output a message to string. If
+    // "err" is NULL, outputs validation problems to stderr.
+    // Returns 'true' if all options look good.
+    bool Validate(std::string *err) const;
+
+    // Name of the hardware mapping. Something like "regular" or "adafruit-hat"
+    const char *hardware_mapping;
+
+    // The "rows" are the number
+    // of rows supported by the display, so 32 or 16. Default: 32.
+    // Flag: --led-rows
+    int rows;
+
+    // The "cols" are the number of columns per panel. Typically something
+    // like 32, but also 64 is possible. Sometimes even 40.
+    // cols * chain_length is the total length of the display, so you can
+    // represent a 64 wide display as cols=32, chain=2 or cols=64, chain=1;
+    // same thing, but more convenient to think of.
+    // Flag: --led-cols
+    int cols;
+
+    // The chain_length is the number of displays daisy-chained together
+    // (output of one connected to input of next). Default: 1
+    // Flag: --led-chain
+    int chain_length;
+
+    // The number of parallel chains connected to the Pi; in old Pis with 26
+    // GPIO pins, that is 1, in newer Pis with 40 interfaces pins, that can
+    // also be 2 or 3. The effective number of pixels in vertical direction is
+    // then thus rows * parallel. Default: 1
+    // Flag: --led-parallel
+    int parallel;
+
+    // Set PWM bits used for output. Default is 11, but if you only deal with
+    // limited comic-colors, 1 might be sufficient. Lower require less CPU and
+    // increases refresh-rate.
+    // Flag: --led-pwm-bits
+    int pwm_bits;
+
+    // Change the base time-unit for the on-time in the lowest
+    // significant bit in nanoseconds.
+    // Higher numbers provide better quality (more accurate color, less
+    // ghosting), but have a negative impact on the frame rate.
+    // Flag: --led-pwm-lsb-nanoseconds
+    int pwm_lsb_nanoseconds;
+
+    // The lower bits can be time-dithered for higher refresh rate.
+    // Flag: --led-pwm-dither-bits
+    int pwm_dither_bits;
+
+    // The initial brightness of the panel in percent. Valid range is 1..100
+    // Default: 100
+    // Flag: --led-brightness
+    int brightness;
+
+    // Scan mode: 0=progressive, 1=interlaced.
+    // Flag: --led-scan-mode
+    int scan_mode;
+
+    // Default row address type is 0, corresponding to direct setting of the
+    // row, while row address type 1 is used for panels that only have A/B,
+    // typically some 64x64 panels
+    int row_address_type;  // Flag --led-row-addr-type
+
+    // Type of multiplexing. 0 = direct, 1 = stripe, 2 = checker,...
+    // Flag: --led-multiplexing
+    int multiplexing;
+
+    // Disable the PWM hardware subsystem to create pulses.
+    // Typically, you don't want to disable hardware pulsing, this is mostly
+    // for debugging and figuring out if there is interference with the
+    // sound system.
+    // This won't do anything if output enable is not connected to GPIO 18 in
+    // non-standard wirings.
+    bool disable_hardware_pulsing;     // Flag: --led-hardware-pulse
+
+    // Show refresh rate on the terminal for debugging and tweaking purposes.
+    bool show_refresh_rate;            // Flag: --led-show-refresh
+
+    // Some panels have inversed colors.
+    bool inverse_colors;                // Flag: --led-inverse
+
+    // In case the internal sequence of mapping is not "RGB", this contains the
+    // real mapping. Some panels mix up these colors. String of length three
+    // which has to contain all characters R, G and B.
+    const char *led_rgb_sequence;  // Flag: --led-rgb-sequence
+
+    // A string describing a sequence of pixel mappers that should be applied
+    // to this matrix. A semicolon-separated list of pixel-mappers with optional
+    // parameter.
+    const char *pixel_mapper_config;   // Flag: --led-pixel-mapper
+
+    // Panel type. Typically an empty string or NULL, but some panels need
+    // a particular initialization sequence, so this is used for that.
+    // This can be e.g. "FM6126A" for that particular panel type.
+    const char *panel_type;  // Flag: --led-panel-type
+
+    // Limit refresh rate of LED panel. This will help on a loaded system
+    // to keep a constant refresh rate. <= 0 for no limit.
+    int limit_refresh_rate_hz;   // Flag: --led-limit-refresh
+  };
+
+  // Factory to create a matrix. Additional functionality includes dropping
+  // privileges and becoming a daemon.
+  // Returns NULL, if there was a problem (a message then is written to stderr).
+  static RGBMatrix *CreateFromOptions(const Options &options,
+                                      const RuntimeOptions &runtime_options);
+
+  // A factory that parses your main() commandline flags to read options
+  // meant to configure the the matrix and returns a freshly allocated matrix.
+  //
+  // Optionally,  you can pass in option structs with a couple of defaults
+  // which are used unless overwritten on the command line.
+  // A matrix is created and returned; also the options structs are
+  // updated to reflect the values that were used and set on the command line.
+  //
+  // If you allow the user to start a daemon with --led-daemon, make sure to
+  // call this function before you have started any threads, so early on in
+  // main() (see RuntimeOptions documentation).
+  //
+  // Note, the permissions are dropped by default from 'root' to 'daemon', so
+  // if you are required to stay root after this, disable this option in
+  // the default RuntimeOptions (set drop_privileges = -1).
+  // Returns NULL, if there was a problem (a message then is written to stderr).
+  static RGBMatrix *CreateFromFlags(int *argc, char ***argv,
+                                    RGBMatrix::Options *default_options = NULL,
+                                    RuntimeOptions *default_runtime_opts = NULL,
+                                    bool remove_consumed_flags = true);
+
+  // Stop matrix, delete all resources.
+  virtual ~RGBMatrix();
+
+  // -- Canvas interface. These write to the active FrameCanvas
+  // (see documentation in canvas.h)
+  //
+  // Since this is updating the canvas that is currently displayed, this
+  // might result in tearing.
+  // Prefer using a FrameCanvas and do double-buffering, see section below.
+  virtual int width() const;
+  virtual int height() const;
+  virtual void SetPixel(int x, int y,
+                        uint8_t red, uint8_t green, uint8_t blue);
+  virtual void Clear();
+  virtual void Fill(uint8_t red, uint8_t green, uint8_t blue);
+
+  // -- Double- and Multibuffering.
+
+  // Create a new buffer to be used for multi-buffering. The returned new
+  // Buffer implements a Canvas with the same size of thie RGBMatrix.
+  // You can use it to draw off-screen on it, then swap it with the active
+  // buffer using SwapOnVSync(). That would be classic double-buffering.
+  //
+  // You can also create as many FrameCanvas as you like and for instance use
+  // them to pre-fill scenes of an animation for fast playback later.
+  //
+  // The ownership of the created Canvases remains with the RGBMatrix, so you
+  // don't have to worry about deleting them (but you also don't want to create
+  // more than needed as this will fill up your memory as they are only deleted
+  // when the RGBMatrix is deleted).
+  FrameCanvas *CreateFrameCanvas();
+
+  // This method waits to the next VSync and swaps the active buffer with the
+  // supplied buffer. The formerly active buffer is returned.
+  //
+  // If you pass in NULL, the active buffer is returned, but it won't be
+  // replaced with NULL. You can use the NULL-behavior to just wait on
+  // VSync or to retrieve the initial buffer when preparing a multi-buffer
+  // animation.
+  //
+  // The optional "framerate_fraction" parameter allows to choose which
+  // multiple of the global frame-count to use. So it slows down your animation
+  // to an exact integer fraction of the refresh rate.
+  // Default is 1, so immediately next available frame.
+  // (Say you have 140Hz refresh rate, then a value of 5 would give you an
+  // 28Hz animation, nicely locked to the refresh-rate).
+  // If you combine this with Options::limit_refresh_rate_hz you can create
+  // time-correct animations.
+  FrameCanvas *SwapOnVSync(FrameCanvas *other, unsigned framerate_fraction = 1);
+
+  // -- Setting shape and behavior of matrix.
+
+  // Apply a pixel mapper. This is used to re-map pixels according to some
+  // scheme implemented by the PixelMapper. Does _not_ take ownership of the
+  // mapper. Mapper can be NULL, in which case nothing happens.
+  // Returns a boolean indicating if this was successful.
+  bool ApplyPixelMapper(const PixelMapper *mapper);
+
+  // Note, there used to be ApplyStaticTransformer(), which has been deprecated
+  // since 2018 and changed to a compile-time option, then finally removed
+  // in 2020. Use PixelMapper instead, which is simpler and more intuitive.
+
+  // Set PWM bits used for output. Default is 11, but if you only deal with
+  // limited comic-colors, 1 might be sufficient. Lower require less CPU and
+  // increases refresh-rate.
+  //
+  // Returns boolean to signify if value was within range.
+  //
+  // This sets the PWM bits for the current active FrameCanvas and future
+  // ones that are created with CreateFrameCanvas().
+  bool SetPWMBits(uint8_t value);
+  uint8_t pwmbits();   // return the pwm-bits of the currently active buffer.
+
+  // Map brightness of output linearly to input with CIE1931 profile.
+  void set_luminance_correct(bool on);
+  bool luminance_correct() const;
+
+  // Set brightness in percent for all created FrameCanvas. 1%..100%.
+  // This will only affect newly set pixels.
+  void SetBrightness(uint8_t brightness);
+  uint8_t brightness();
+
+  //-- GPIO interaction.
+  // This library uses the GPIO pins to drive the matrix; this is a safe way
+  // to request the 'remaining' bits to be used for user purposes.
+
+  // Request user readable GPIO bits.
+  // This function allows you to request pins you'd like to read with
+  // AwaitInputChange().
+  // Only bits that are not already in use for reading or wrtiting
+  // by the matrix are allowed.
+  // Input is a bitmap of all the GPIO bits you're interested in; returns all
+  // the bits that are actually available.
+  uint64_t RequestInputs(uint64_t all_interested_bits);
+
+  // This function will return whenever the GPIO input pins
+  // change (pins that are not already in use for output, that is) or the
+  // timeout is reached. You need to have reserved the inputs with
+  // matrix->RequestInputs(...) first (e.g.
+  //   matrix->RequestInputs((1<<25)|(1<<24));
+  //
+  // A positive timeout waits the given amount of milliseconds for a change
+  // (e.g. a button-press) to occur; if there is no change, it will just
+  // return the last value.
+  // If you just want to know how the pins are right now, call with zero
+  // timeout.
+  // A negative number waits forever and will only return if there is a change.
+  //
+  // This function only samples between display refreshes so polling some
+  // input does not generate flicker and provide a convenient change interface.
+  //
+  // Returns the bitmap of all GPIO input pins.
+  uint64_t AwaitInputChange(int timeout_ms);
+
+  // Request user writable GPIO bits.
+  // This allows to request a bitmap of GPIO-bits to be used by the user for
+  // writing.
+  // Only bits that are not already in use for reading or wrtiting
+  // by the matrix are allowed.
+  // Returns the subset bits that are _actually_ available,
+  uint64_t RequestOutputs(uint64_t output_bits);
+
+  // Set the user-settable bits according to output bits.
+  void OutputGPIO(uint64_t output_bits);
+
+  // Legacy way to set gpio pins. We're not doing this anymore but need to
+  // be source-compatible with old calls of the form
+  // matrix->gpio()->RequestInputs(...)
+  //
+  // Don't use, use AwaitInputChange() directly.
+  RGBMatrix *gpio() __attribute__((deprecated)) { return this; }
+
+  //--  Rarely needed
+  // Start the refresh thread.
+  // This is only needed if you chose RuntimeOptions::daemon = -1 (see below),
+  // otherwise the refresh thread is already started.
+  bool StartRefresh();
+
+private:
+  class Impl;
+
+  RGBMatrix(Impl *impl) : impl_(impl) {}
+  Impl *const impl_;
+};
+
+namespace internal {
+class Framebuffer;
+}
+
+class FrameCanvas : public Canvas {
+public:
+  // Set PWM bits used for this Frame.
+  // Simple comic-colors, 1 might be sufficient (111 RGB, i.e. 8 colors).
+  // Lower require less CPU.
+  // Returns boolean to signify if value was within range.
+  bool SetPWMBits(uint8_t value);
+  uint8_t pwmbits();
+
+  // Map brightness of output linearly to input with CIE1931 profile.
+  void set_luminance_correct(bool on);
+  bool luminance_correct() const;
+
+  void SetBrightness(uint8_t brightness);
+  uint8_t brightness();
+
+  //-- Serialize()/Deserialize() are fast ways to store and re-create a canvas.
+
+  // Provides a pointer to a buffer of the internal representation to
+  // be copied out for later Deserialize().
+  //
+  // Returns a "data" pointer and the data "len" in the given out-paramters;
+  // the content can be copied from there by the caller.
+  //
+  // Note, the content is not simply RGB, it is the opaque and platform
+  // specific representation which allows to make deserialization very fast.
+  // It is also bigger than just RGB; if you want to store it somewhere,
+  // using compression is a good idea.
+  void Serialize(const char **data, size_t *len) const;
+
+  // Load data previously stored with Serialize(). Needs to be restored into
+  // a FrameCanvas with exactly the same settings (rows, chain, transformer,...)
+  // as serialized.
+  // Returns 'false' if size is unexpected.
+  // This method should only be called if FrameCanvas is off-screen.
+  bool Deserialize(const char *data, size_t len);
+
+  // Copy content from other FrameCanvas owned by the same RGBMatrix.
+  void CopyFrom(const FrameCanvas &other);
+
+  // -- Canvas interface.
+  virtual int width() const;
+  virtual int height() const;
+  virtual void SetPixel(int x, int y,
+                        uint8_t red, uint8_t green, uint8_t blue);
+  virtual void Clear();
+  virtual void Fill(uint8_t red, uint8_t green, uint8_t blue);
+
+private:
+  friend class RGBMatrix;
+
+  FrameCanvas(internal::Framebuffer *frame) : frame_(frame){}
+  virtual ~FrameCanvas();   // Any FrameCanvas is owned by RGBMatrix.
+  internal::Framebuffer *framebuffer() { return frame_; }
+
+  internal::Framebuffer *const frame_;
+};
+
+// Runtime options to simplify doing common things for many programs such as
+// dropping privileges and becoming a daemon.
+struct RuntimeOptions {
+  RuntimeOptions();
+
+  int gpio_slowdown;    // 0 = no slowdown.          Flag: --led-slowdown-gpio
+
+  // ----------
+  // If the following options are set to disabled with -1, they are not
+  // even offered via the command line flags.
+  // ----------
+
+  // Thre are three possible values here
+  //   -1 : don't leave choise of becoming daemon to the command line parsing.
+  //        If set to -1, the --led-daemon option is not offered.
+  //    0 : do not becoma a daemon, run in forgreound (default value)
+  //    1 : become a daemon, run in background.
+  //
+  // If daemon is disabled (= -1), the user has to call
+  // RGBMatrix::StartRefresh() manually once the matrix is created, to leave
+  // the decision to become a daemon
+  // after the call (which requires that no threads have been started yet).
+  // In the other cases (off or on), the choice is already made, so the thread
+  // is conveniently already started for you.
+  int daemon;           // -1 disabled. 0=off, 1=on. Flag: --led-daemon
+
+  // Drop privileges from 'root' to 'daemon' once the hardware is initialized.
+  // This is usually a good idea unless you need to stay on elevated privs.
+  int drop_privileges;  // -1 disabled. 0=off, 1=on. flag: --led-drop-privs
+
+  // By default, the gpio is initialized for you, but if you run on a platform
+  // not the Raspberry Pi, this will fail. If you don't need to access GPIO
+  // e.g. you want to just create a stream output (see content-streamer.h),
+  // set this to false.
+  bool do_gpio_init;
+};
+
+// Convenience utility functions to read standard rgb-matrix flags and create
+// a RGBMatrix. Commandline flags are something like --led-rows, --led-chain,
+// --led-parallel. See output of PrintMatrixFlags() for all available options
+// and detailed description in
+// https://github.com/hzeller/rpi-rgb-led-matrix#changing-parameters-via-command-line-flags
+//
+// Example use:
+/*
+using rgb_matrix::RGBMatrix;
+int main(int argc, char **argv) {
+  RGBMatrix::Options led_options;
+  rgb_matrix::RuntimeOptions runtime;
+
+  // Set defaults
+  led_options.chain_length = 3;
+  led_options.show_refresh_rate = true;
+  runtime.drop_privileges = 1;
+  if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv, &led_options, &runtime)) {
+    rgb_matrix::PrintMatrixFlags(stderr);
+    return 1;
+  }
+
+  // Do your own command line handling with the remaining flags.
+  while (getopt()) {...}
+
+  // Looks like we're ready to start
+  RGBMatrix *matrix = RGBMatrix::CreateFromOptions(led_options, runtime);
+  if (matrix == NULL) {
+    return 1;
+  }
+
+  //  .. now use matrix
+
+  delete matrix;   // Make sure to delete it in the end to switch off LEDs.
+  return 0;
+}
+*/
+// This parses the flags from argv and updates the structs with the parsed-out
+// values. Structs can be NULL if you are not interested in it.
+//
+// The recongized flags are removed from argv if "remove_consumed_flags" is
+// true; this simplifies your command line processing for the remaining options.
+//
+// Returns 'true' on success, 'false' if there was flag parsing problem.
+bool ParseOptionsFromFlags(int *argc, char ***argv,
+                           RGBMatrix::Options *default_options,
+                           RuntimeOptions *rt_options,
+                           bool remove_consumed_flags = true);
+
+// Show all the available options in a style that can be used in a --help
+// output on the command line.
+void PrintMatrixFlags(FILE *out,
+                      const RGBMatrix::Options &defaults = RGBMatrix::Options(),
+                      const RuntimeOptions &rt_opt = RuntimeOptions());
+
+// Legacy version of RGBMatrix::CreateFromOptions()
+inline RGBMatrix *CreateMatrixFromOptions(
+  const RGBMatrix::Options &options,
+  const RuntimeOptions &runtime_options) {
+  return RGBMatrix::CreateFromOptions(options, runtime_options);
+}
+
+// Legacy version of RGBMatrix::CreateFromFlags()
+inline RGBMatrix *CreateMatrixFromFlags(
+  int *argc, char ***argv,
+  RGBMatrix::Options *default_options = NULL,
+  RuntimeOptions *default_runtime_opts = NULL,
+  bool remove_consumed_flags = true) {
+  return RGBMatrix::CreateFromFlags(argc, argv,
+                                    default_options, default_runtime_opts,
+                                    remove_consumed_flags);
+}
+
+}  // end namespace rgb_matrix
+#endif  // RPI_RGBMATRIX_H

+ 110 - 0
matrix-zmq/rpi-rgb-led-matrix/include/pixel-mapper.h

@@ -0,0 +1,110 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2018 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+#ifndef RGBMATRIX_PIXEL_MAPPER
+#define RGBMATRIX_PIXEL_MAPPER
+
+#include <string>
+#include <vector>
+
+namespace rgb_matrix {
+
+// A pixel mapper is a way for you to map pixels of LED matrixes to a different
+// layout. If you have an implementation of a PixelMapper, you can give it
+// to the RGBMatrix::ApplyPixelMapper(), which then presents you a canvas
+// that has the new "visible_width", "visible_height".
+class PixelMapper {
+public:
+  virtual ~PixelMapper() {}
+
+  // Get the name of this PixelMapper. Each PixelMapper needs to have a name
+  // so that it can be referred to with command line flags.
+  virtual const char *GetName() const = 0;
+
+  // Pixel mappers receive the chain and parallel information and
+  // might receive optional user-parameters, e.g. from command line flags.
+  //
+  // This is a single string containing the parameters.
+  // You can be used from simple scalar parameters, such as the angle for
+  // the rotate transformer, or more complex parameters that describe a mapping
+  // of panels for instance.
+  // Keep it concise (as people will give parameters on the command line) and
+  // don't use semicolons in your string (as they are
+  // used to separate pixel mappers on the command line).
+  //
+  // For instance, the rotate transformer is invoked like this
+  //  --led-pixel-mapper=rotate:90
+  // And the parameter that is passed to SetParameter() is "90".
+  //
+  // Returns 'true' if parameter was parsed successfully.
+  virtual bool SetParameters(int chain, int parallel,
+                             const char *parameter_string) {
+    return true;
+  }
+
+  // Given a underlying matrix (width, height), returns the
+  // visible (width, height) after the mapping.
+  // E.g. a 90 degree rotation might map matrix=(64, 32) -> visible=(32, 64)
+  // Some multiplexing matrices will double the height and half the width.
+  //
+  // While not technically necessary, one would expect that the number of
+  // pixels stay the same, so
+  // matrix_width * matrix_height == (*visible_width) * (*visible_height);
+  //
+  // Returns boolean "true" if the mapping can be successfully done with this
+  // mapper.
+  virtual bool GetSizeMapping(int matrix_width, int matrix_height,
+                              int *visible_width, int *visible_height)
+    const = 0;
+
+  // Map where a visible pixel (x,y) is mapped to the underlying matrix (x,y).
+  //
+  // To be convienently stateless, the first parameters are the full
+  // matrix width and height.
+  //
+  // So for many multiplexing methods this means to map a panel to a double
+  // length and half height panel (32x16 -> 64x8).
+  // The logic_x, logic_y are output parameters and guaranteed not to be
+  // nullptr.
+  virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
+                                  int visible_x, int visible_y,
+                                  int *matrix_x, int *matrix_y) const = 0;
+};
+
+// This is a place to register PixelMappers globally. If you register your
+// PixelMapper before calling RGBMatrix::CreateFromFlags(), the named
+// PixelMapper is available in the --led-pixel-mapper options.
+//
+// Note, you don't _have_ to register your mapper, you can always call
+// RGBMatrix::ApplyPixelMapper() directly. Registering is for convenience and
+// commandline-flag support.
+//
+// There are a few standard mappers registered by default.
+void RegisterPixelMapper(PixelMapper *mapper);
+
+// Get a list of the names of available pixel mappers.
+std::vector<std::string> GetAvailablePixelMappers();
+
+// Given a name (e.g. "rotate") and a parameter (e.g. "90"), return the
+// parametrized PixelMapper with that name. Returns NULL if mapper
+// can not be found or parameter is invalid.
+// Ownership of the returned object is _NOT_ transferred to the caller.
+// Current available mappers are "U-mapper" and "Rotate". The "Rotate"
+// gets a parameter denoting the angle.
+const PixelMapper *FindPixelMapper(const char *name,
+                                   int chain, int parallel,
+                                   const char *parameter = NULL);
+}  // namespace rgb_matrix
+
+#endif  // RGBMATRIX_PIXEL_MAPPER

+ 86 - 0
matrix-zmq/rpi-rgb-led-matrix/include/thread.h

@@ -0,0 +1,86 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#ifndef RPI_THREAD_H
+#define RPI_THREAD_H
+
+#include <stdint.h>
+#include <pthread.h>
+
+namespace rgb_matrix {
+// Simple thread abstraction.
+class Thread {
+public:
+  Thread();
+
+  // The destructor waits for Run() to return so make sure it does.
+  virtual ~Thread();
+
+  // Wait for the Run() method to return.
+  void WaitStopped();
+
+  // Start thread. If realtime_priority is > 0, then this will be a
+  // thread with SCHED_FIFO and the given priority.
+  // If cpu_affinity is set !=, chooses the given bitmask of CPUs
+  // this thread should have an affinity to.
+  // On a Raspberry Pi 1, this doesn't matter, as there is only one core,
+  // Raspberry Pi 2 can has 4 cores, so any combination of (1<<0) .. (1<<3) is
+  // valid.
+  virtual void Start(int realtime_priority = 0, uint32_t cpu_affinity_mask = 0);
+
+  // Override this to do the work.
+  //
+  // This will be called in a thread once Start() has been called. You typically
+  // will have an endless loop doing stuff.
+  //
+  // It is a good idea to provide a way to communicate to the thread that
+  // it should stop (see ThreadedCanvasManipulator for an example)
+  virtual void Run() = 0;
+
+private:
+  static void *PthreadCallRun(void *tobject);
+  bool started_;
+  pthread_t thread_;
+};
+
+// Non-recursive Mutex.
+class Mutex {
+public:
+  Mutex() { pthread_mutex_init(&mutex_, NULL); }
+  ~Mutex() { pthread_mutex_destroy(&mutex_); }
+  void Lock() { pthread_mutex_lock(&mutex_); }
+  void Unlock() { pthread_mutex_unlock(&mutex_); }
+
+  // Wait on condition. If "timeout_ms" is < 0, it waits forever, otherwise
+  // until timeout is reached.
+  // Returns 'true' if condition is met, 'false', if wait timed out.
+  bool WaitOn(pthread_cond_t *cond, long timeout_ms = -1);
+
+private:
+  pthread_mutex_t mutex_;
+};
+
+// Useful RAII wrapper around mutex.
+class MutexLock {
+public:
+  MutexLock(Mutex *m) : mutex_(m) { mutex_->Lock(); }
+  ~MutexLock() { mutex_->Unlock(); }
+private:
+  Mutex *const mutex_;
+};
+
+}  // end namespace rgb_matrix
+
+#endif  // RPI_THREAD_H

+ 103 - 0
matrix-zmq/rpi-rgb-led-matrix/include/threaded-canvas-manipulator.h

@@ -0,0 +1,103 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+// Utility base class for continuously updating the canvas.
+
+// Note: considering removing this, as real applications likely have something
+// similar, but this might not be quite usable.
+// Since it is just a few lines of code, it is probably better
+// implemented in the application for readability.
+//
+// So for simplicity of the API, consider ThreadedCanvasManipulator deprecated.
+
+#ifndef RPI_THREADED_CANVAS_MANIPULATOR_H
+#define RPI_THREADED_CANVAS_MANIPULATOR_H
+
+#include "thread.h"
+#include "canvas.h"
+
+namespace rgb_matrix {
+//
+// Typically, your programs will crate a canvas and then updating the image
+// in a loop. If you want to do stuff in parallel, then this utility class
+// helps you doing that. Also a demo for how to use the Thread class.
+//
+// Extend it, then just implement Run(). Example:
+/*
+  class MyCrazyDemo : public ThreadedCanvasManipulator {
+  public:
+    MyCrazyDemo(Canvas *canvas) : ThreadedCanvasManipulator(canvas) {}
+    virtual void Run() {
+      unsigned char c;
+      while (running()) {
+        // Calculate the next frame.
+        c++;
+        for (int x = 0; x < canvas()->width(); ++x) {
+          for (int y = 0; y < canvas()->height(); ++y) {
+            canvas()->SetPixel(x, y, c, c, c);
+          }
+        }
+        usleep(15 * 1000);
+      }
+    }
+  };
+
+  // Later, in your main method.
+  RGBMatrix *matrix = RGBMatrix::CreateFromOptions(...);
+  MyCrazyDemo *demo = new MyCrazyDemo(matrix);
+  demo->Start();   // Start doing things.
+  // This now runs in the background, you can do other things here,
+  // e.g. aquiring new data or simply wait. But for waiting, you wouldn't
+  // need a thread in the first place.
+  demo->Stop();
+  delete demo;
+*/
+class ThreadedCanvasManipulator : public Thread {
+public:
+  ThreadedCanvasManipulator(Canvas *m) : running_(false), canvas_(m) {}
+  virtual ~ThreadedCanvasManipulator() {  Stop(); }
+
+  virtual void Start(int realtime_priority=0, uint32_t affinity_mask=0) {
+    {
+      MutexLock l(&mutex_);
+      running_ = true;
+    }
+    Thread::Start(realtime_priority, affinity_mask);
+  }
+
+  // Stop the thread at the next possible time Run() checks the running_ flag.
+  void Stop() {
+    MutexLock l(&mutex_);
+    running_ = false;
+  }
+
+  // Implement this and run while running() returns true.
+  virtual void Run() = 0;
+
+protected:
+  inline Canvas *canvas() { return canvas_; }
+  inline bool running() {
+    MutexLock l(&mutex_);
+    return running_;
+  }
+
+private:
+  Mutex mutex_;
+  bool running_;
+  Canvas *const canvas_;
+};
+}  // namespace rgb_matrix
+
+#endif  // RPI_THREADED_CANVAS_MANIPULATOR_H

+ 9 - 0
matrix-zmq/rpi-rgb-led-matrix/include/transformer.h

@@ -0,0 +1,9 @@
+/*
+ * NOTE:
+ *
+ * Transformers are deprecated. For the kind of mappings they were be
+ * used by they turned out to be too complicated.
+ *
+ * They have been superseeded by the simpler PixelMapper, see pixel-mapper.h
+ */
+#include "deprecated-transformer.h"

+ 3 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/.gitignore

@@ -0,0 +1,3 @@
+compiler-flags
+librgbmatrix.a
+librgbmatrix.so.1

+ 196 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/Makefile

@@ -0,0 +1,196 @@
+# Creating RGB matrix library
+# When you link this library with your binary, you need to add -lrt -lm -lpthread
+# So
+#   -lrgbmatrix
+##
+OBJECTS=gpio.o led-matrix.o options-initialize.o framebuffer.o \
+        thread.o bdf-font.o graphics.o led-matrix-c.o hardware-mapping.o \
+        pixel-mapper.o multiplex-mappers.o \
+	content-streamer.o
+
+TARGET=librgbmatrix
+
+###
+# After you change any of the following DEFINES, make sure to 'make' again.
+#
+# ###########      NOTE     ###########
+# all of these options can now can be set programmatically and
+# via command line flags as well. No real need to change them in the Makefile.
+# (So be prepared for these to be removed at some point)
+###
+
+# There are several different pinouts for various breakout boards that uses
+# this library. If you are using the described pinout in the toplevel README.md
+# or the standard active-3 breakout board, then 'regular' is the one you'd like
+# to use.
+#
+# Adafruit also made a breakout board, if you want to use that, choose
+# 'adafruit-hat'
+#
+# These are the choices
+#   regular            # Following this project wiring and using these PCBs
+#   adafruit-hat       # If you have a RGB matrix HAT from Adafruit
+#   adafruit-hat-pwm   # If you have an Adafruit HAT with PWM hardware mod.
+#   regular-pi1        # If you have an old Pi1 and regular didn't work.
+#   classic            # (deprecated) Classic Pi1/2/. Not used anymore.
+#   classic-pi1        # (deprecated) Classic pinout on Rasperry Pi 1
+HARDWARE_DESC?=regular
+
+# If you see that your display is inverse, you might have a matrix variant
+# has uses inverse logic for the RGB bits. In that case: uncomment this.
+# Flag: --led-inverse
+#DEFINES+=-DINVERSE_RGB_DISPLAY_COLORS
+
+# For curiosity reasons and while tweaking values for LSB_PWM_NANOSECONDS,
+# uncomment to see refresh rate in terminal.
+# Flag: --led-show-refresh
+#DEFINES+=-DSHOW_REFRESH_RATE
+
+# For low refresh rates below 100Hz (e.g. a lot of panels), the eye will notice
+# some flicker. With this option enabled, the refreshed lines are interleaved,
+# so it is less noticeable. But looks less pleasant with fast eye movements.
+# Flag: --led-scan-mode=1
+#DEFINES+=-DRGB_SCAN_INTERLACED=1
+
+# The signal can be too fast for some LED panels, in particular with newer
+# (faster) Raspberry Pi 2s - in that case, the LED matrix only shows garbage.
+# This allows to slow down the GPIO for these cases.
+#
+# Set to 1 for RPi2 or RPi3 (default below), because they are typically
+# faster than the panels can digest.
+#
+# Set to 0 (or comment out) for RPi1, that are slow enough.
+#
+# Sometimes, you even have to give RGB_SLOWDOWN_GPIO=2 or even 3 for
+# particularly slow panels or bad signal cable situations. If that happens, you
+# typically should double check cables and add TTL level converter if you
+# haven't.
+# Flag: --led-slowdown-gpio
+#DEFINES+=-DRGB_SLOWDOWN_GPIO=1
+
+# This allows to change the base time-unit for the on-time in the lowest
+# significant bit in nanoseconds.
+# Higher numbers provide better quality (more accurate color, less ghosting),
+# but have a negative impact on the frame rate.
+#
+# For the same frame-rate, displays with higher multiplexing (e.g. 1:16 or 1:32)
+# require lower values.
+#
+# Good values for full-color display (PWM=11) are somewhere between 100 and 300.
+#
+# If you you use reduced bit color (e.g. PWM=1 for 8 colors like for text),
+# then higher values might be good to minimize ghosting (and you can afford
+# that, because lower PWM values result in higher frame-rates).
+#
+# How to decide ? Just leave the default if things are fine. If you see
+# ghosting in high-contrast applications (e.g. text), increase the value.
+# If you want to tweak, watch the framerate (-DSHOW_FRAME_RATE) while playing
+# with this number and the PWM values.
+# Flag: --led-pwm-lsb-nanoseconds
+#DEFINES+=-DLSB_PWM_NANOSECONDS=130
+
+# This is to debug problems with the hardware pulse generation. The PWM hardware
+# module is also used by Raspberry Pi sound system, so there might be
+# interference. Note, you typically don't want the hardware pulses disabled, as
+# the image will have visible brightness glitches; but for debugging, this is
+# a good choice.
+# Flag: --led-no-hardware-pulses
+#DEFINES+=-DDISABLE_HARDWARE_PULSES
+
+# This allows to fix the refresh rate to a particular refresh time in
+# microseconds.
+#
+# This can be used to mitigate some situations in which you have a rare
+# faint flicker, which can happen due to hardware events (network access)
+# or other situations such as other IO or heavy memory access by other
+# processes (all of which seem to break the isolation we request from the
+# kernel. You did set isolcpus=3 right ?)
+# You trade a slightly slower refresh rate and display brightness for less
+# visible flicker situations.
+#
+# For this to calibrate, run your program for a while with --led-show-refresh
+# and watch the line that shows the refresh time and the maximum microseconds
+# for a frame observed. The maximum number is updated whenever the frame
+# refresh take a little bit longer. So wait a while until that value doesn't
+# change anymore (at least a minute, so that you catch tasks that happen once
+# a minute). Some value might read e.g.
+#   204.6Hz max: 5133usec
+# Now take this maximum value you see there (here: 5133) and put in
+# this define (don't forget to remove the # in front).
+#
+# The refresh rate will now be adapted to always have this amount of time
+# between frames, so faster refreshes will be slowed down, but the occasional
+# delayed frame will fit into the time-window as well, thus reducing visible
+# brightness fluctuations.
+#
+# You can play with value a little and reduce until you find a good balance
+# between refresh rate (which is reduce the higher this value is) and
+# flicker suppression (which is better with higher values).
+# Flag: --led-limit-refresh
+#DEFINES+=-DFIXED_FRAME_MICROSECONDS=5000
+
+# Enable wide 64 bit GPIO offered with the compute module.
+# This will use more memory to internally represent the frame buffer, so
+# caches can't be utilized as much.
+# So only switch this on if you really use the compute module and use more
+# than 3 parallel chains.
+# (this is untested right now, waiting for hardware to arrive for testing)
+#DEFINES+=-DENABLE_WIDE_GPIO_COMPUTE_MODULE
+
+# ---- Pinout options for hardware variants; usually no change needed here ----
+
+# Uncomment if you want to use the Adafruit HAT with stable PWM timings.
+# The newer version of this library allows for much more stable (less flicker)
+# output, but it does not work with the Adafruit HAT unless you do a
+# simple hardware hack on them:
+# connect GPIO 4 (old OE) with 18 (the new OE); there are
+# convenient solder holes labeled 4 and 18 on the Adafruit HAT, pretty
+# close together.
+# Then you can set the flag --led-gpio-mapping=adafruit-hat-pwm
+# .. or uncomment the following line.
+#HARDWARE_DESC=adafruit-hat-pwm
+
+# Typically, a Hub75 panel is split in two half displays, so that a 1:16
+# multiplexing actually multiplexes over two half displays and gives 32 lines.
+# There are some other displays out there that you might experiment with
+# that are internally wired to only have one sub-panel. In that case you might
+# want to try this define to get a more reasonable canvas mapping.
+# This option is typically _not_ needed, only use when you attempt to connect
+# some oddball old (typically one-colored) display, such as Hub12.
+#DEFINES+=-DONLY_SINGLE_SUB_PANEL
+
+# If someone gives additional values on the make commandline e.g.
+# make USER_DEFINES="-DSHOW_REFRESH_RATE"
+DEFINES+=$(USER_DEFINES)
+
+DEFINES+=-DDEFAULT_HARDWARE='"$(HARDWARE_DESC)"'
+INCDIR=../include
+CFLAGS=-W -Wall -Wextra -Wno-unused-parameter -O3 -g -fPIC $(DEFINES)
+CXXFLAGS=$(CFLAGS) -fno-exceptions -std=c++11
+
+all : $(TARGET).a $(TARGET).so.1
+
+$(TARGET).a : $(OBJECTS)
+	$(AR) rcs $@ $^
+
+$(TARGET).so.1 : $(OBJECTS)
+	$(CXX) -shared -Wl,-soname,$@ -o $@ $^ -lpthread  -lrt -lm -lpthread
+
+led-matrix.o: led-matrix.cc $(INCDIR)/led-matrix.h
+thread.o : thread.cc $(INCDIR)/thread.h
+framebuffer.o: framebuffer.cc framebuffer-internal.h
+graphics.o: graphics.cc utf8-internal.h
+
+%.o : %.cc compiler-flags
+	$(CXX) -I$(INCDIR) $(CXXFLAGS) -c -o $@ $<
+
+%.o : %.c compiler-flags
+	$(CC)  -I$(INCDIR) $(CFLAGS) -c -o $@ $<
+
+clean:
+	rm -f $(OBJECTS) $(TARGET).a $(TARGET).so.1
+
+compiler-flags: FORCE
+	@echo '$(CXX) $(CXXFLAGS)' | cmp -s - $@ || echo '$(CXX) $(CXXFLAGS)' > $@
+
+.PHONY: FORCE

+ 188 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/bdf-font.cc

@@ -0,0 +1,188 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+// Some old g++ installations need this macro to be defined for PRIx64.
+#ifndef __STDC_FORMAT_MACROS
+#  define __STDC_FORMAT_MACROS
+#endif
+#include <inttypes.h>
+
+#include "graphics.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+// The little question-mark box "�" for unknown code.
+static const uint32_t kUnicodeReplacementCodepoint = 0xFFFD;
+
+// Bitmap for one row. This limits the number of available columns.
+// Make wider if running into trouble.
+typedef uint64_t rowbitmap_t;
+
+namespace rgb_matrix {
+struct Font::Glyph {
+  int device_width, device_height;
+  int width, height;
+  int x_offset, y_offset;
+  rowbitmap_t bitmap[0];  // contains 'height' elements.
+};
+
+Font::Font() : font_height_(-1), base_line_(0) {}
+Font::~Font() {
+  for (CodepointGlyphMap::iterator it = glyphs_.begin();
+       it != glyphs_.end(); ++it) {
+    free(it->second);
+  }
+}
+
+// TODO: that might not be working for all input files yet.
+bool Font::LoadFont(const char *path) {
+  if (!path || !*path) return false;
+  FILE *f = fopen(path, "r");
+  if (f == NULL)
+    return false;
+  uint32_t codepoint;
+  char buffer[1024];
+  int dummy;
+  Glyph tmp;
+  Glyph *current_glyph = NULL;
+  int row = 0;
+
+  int bitmap_shift = 0;
+  while (fgets(buffer, sizeof(buffer), f)) {
+    if (sscanf(buffer, "FONTBOUNDINGBOX %d %d %d %d",
+               &dummy, &font_height_, &dummy, &base_line_) == 4) {
+      base_line_ += font_height_;
+    }
+    else if (sscanf(buffer, "ENCODING %ud", &codepoint) == 1) {
+      // parsed.
+    }
+    else if (sscanf(buffer, "DWIDTH %d %d", &tmp.device_width, &tmp.device_height
+                    ) == 2) {
+      // parsed.
+    }
+    else if (sscanf(buffer, "BBX %d %d %d %d", &tmp.width, &tmp.height,
+                    &tmp.x_offset, &tmp.y_offset) == 4) {
+      current_glyph = (Glyph*) malloc(sizeof(Glyph)
+                                      + tmp.height * sizeof(rowbitmap_t));
+      *current_glyph = tmp;
+      // We only get number of bytes large enough holding our width. We want
+      // it always left-aligned.
+      bitmap_shift =
+        8 * (sizeof(rowbitmap_t) - ((current_glyph->width + 7) / 8))
+        - current_glyph->x_offset;
+      row = -1;  // let's not start yet, wait for BITMAP
+    }
+    else if (strncmp(buffer, "BITMAP", strlen("BITMAP")) == 0) {
+      row = 0;
+    }
+    else if (current_glyph && row >= 0 && row < current_glyph->height
+             && (sscanf(buffer, "%" PRIx64, &current_glyph->bitmap[row]) == 1)) {
+      current_glyph->bitmap[row] <<= bitmap_shift;
+      row++;
+    }
+    else if (strncmp(buffer, "ENDCHAR", strlen("ENDCHAR")) == 0) {
+      if (current_glyph && row == current_glyph->height) {
+        free(glyphs_[codepoint]);  // just in case there was one.
+        glyphs_[codepoint] = current_glyph;
+        current_glyph = NULL;
+      }
+    }
+  }
+  fclose(f);
+  return true;
+}
+
+Font *Font::CreateOutlineFont() const {
+  Font *r = new Font();
+  const int kBorder = 1;
+  r->font_height_ = font_height_ + 2*kBorder;
+  r->base_line_ = base_line_ + kBorder;
+  for (CodepointGlyphMap::const_iterator it = glyphs_.begin();
+       it != glyphs_.end(); ++it) {
+    const Glyph *orig = it->second;
+    const int height = orig->height + 2 * kBorder;
+    const size_t alloc_size = sizeof(Glyph) + height * sizeof(rowbitmap_t);
+    Glyph *const tmp_glyph = (Glyph*) calloc(1, alloc_size);
+    tmp_glyph->width  = orig->width  + 2*kBorder;
+    tmp_glyph->height = height;
+    tmp_glyph->device_width  = orig->device_width + 2*kBorder;
+    tmp_glyph->device_height = height;
+    tmp_glyph->y_offset = orig->y_offset - kBorder;
+    // TODO: we don't really need bounding box, right ?
+    const rowbitmap_t fill_pattern = 0b111;
+    const rowbitmap_t start_mask   = 0b010;
+    // Fill the border
+    for (int h = 0; h < orig->height; ++h) {
+      rowbitmap_t fill = fill_pattern;
+      rowbitmap_t orig_bitmap = orig->bitmap[h] >> kBorder;
+      for (rowbitmap_t m = start_mask; m; m <<= 1, fill <<= 1) {
+        if (orig_bitmap & m) {
+          tmp_glyph->bitmap[h+kBorder-1] |= fill;
+          tmp_glyph->bitmap[h+kBorder+0] |= fill;
+          tmp_glyph->bitmap[h+kBorder+1] |= fill;
+        }
+      }
+    }
+    // Remove original font again.
+    for (int h = 0; h < orig->height; ++h) {
+      rowbitmap_t orig_bitmap = orig->bitmap[h] >> kBorder;
+      tmp_glyph->bitmap[h+kBorder] &= ~orig_bitmap;
+    }
+    r->glyphs_[it->first] = tmp_glyph;
+  }
+  return r;
+}
+
+const Font::Glyph *Font::FindGlyph(uint32_t unicode_codepoint) const {
+  CodepointGlyphMap::const_iterator found = glyphs_.find(unicode_codepoint);
+  if (found == glyphs_.end())
+    return NULL;
+  return found->second;
+}
+
+int Font::CharacterWidth(uint32_t unicode_codepoint) const {
+  const Glyph *g = FindGlyph(unicode_codepoint);
+  return g ? g->device_width : -1;
+}
+
+int Font::DrawGlyph(Canvas *c, int x_pos, int y_pos,
+                    const Color &color, const Color *bgcolor,
+                    uint32_t unicode_codepoint) const {
+  const Glyph *g = FindGlyph(unicode_codepoint);
+  if (g == NULL) g = FindGlyph(kUnicodeReplacementCodepoint);
+  if (g == NULL) return 0;
+  y_pos = y_pos - g->height - g->y_offset;
+  for (int y = 0; y < g->height; ++y) {
+    const rowbitmap_t row = g->bitmap[y];
+    rowbitmap_t x_mask = (1LL<<63);
+    for (int x = 0; x < g->device_width; ++x, x_mask >>= 1) {
+      if (row & x_mask) {
+        c->SetPixel(x_pos + x, y_pos + y, color.r, color.g, color.b);
+      } else if (bgcolor) {
+        c->SetPixel(x_pos + x, y_pos + y, bgcolor->r, bgcolor->g, bgcolor->b);
+      }
+    }
+  }
+  return g->device_width;
+}
+
+int Font::DrawGlyph(Canvas *c, int x_pos, int y_pos, const Color &color,
+                    uint32_t unicode_codepoint) const {
+  return DrawGlyph(c, x_pos, y_pos, color, NULL, unicode_codepoint);
+}
+
+}  // namespace rgb_matrix

+ 203 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/content-streamer.cc

@@ -0,0 +1,203 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+
+#include "content-streamer.h"
+#include "led-matrix.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "gpio-bits.h"
+
+namespace rgb_matrix {
+
+// Pre-c++11 helper
+#define STATIC_ASSERT(msg, c) typedef int static_assert_##msg[(c) ? 1 : -1]
+
+namespace {
+// We write magic values as integers to automatically detect endian issues.
+// Streams are stored in little-endian. This is the ARM default (running
+// the Raspberry Pi, but also x86; so it is possible to create streams easily
+// on a different x86 Linux PC.
+static const uint32_t kFileMagicValue = 0xED0C5A48;
+struct FileHeader {
+  uint32_t magic;  // kFileMagicValue
+  uint32_t buf_size;
+  uint32_t width;
+  uint32_t height;
+  uint64_t future_use1;
+  uint64_t is_wide_gpio : 1;
+  uint64_t flags_future_use : 63;
+};
+STATIC_ASSERT(file_header_size_changed, sizeof(FileHeader) == 32);
+
+static const uint32_t kFrameMagicValue = 0x12345678;
+struct FrameHeader {
+  uint32_t magic;  // kFrameMagic
+  uint32_t size;
+  uint32_t hold_time_us;  // How long this frame lasts in usec.
+  uint32_t future_use1;
+  uint64_t future_use2;
+  uint64_t future_use3;
+};
+STATIC_ASSERT(file_header_size_changed, sizeof(FrameHeader) == 32);
+}
+
+FileStreamIO::FileStreamIO(int fd) : fd_(fd) {
+  posix_fadvise(fd_, 0, 0, POSIX_FADV_SEQUENTIAL);
+}
+FileStreamIO::~FileStreamIO() { close(fd_); }
+
+void FileStreamIO::Rewind() { lseek(fd_, 0, SEEK_SET); }
+
+ssize_t FileStreamIO::Read(void *buf, const size_t count) {
+  return read(fd_, buf, count);
+}
+
+ssize_t FileStreamIO::Append(const void *buf, const size_t count) {
+  return write(fd_, buf, count);
+}
+
+void MemStreamIO::Rewind() { pos_ = 0; }
+ssize_t MemStreamIO::Read(void *buf, size_t count) {
+  const size_t amount = std::min(count, buffer_.size() - pos_);
+  memcpy(buf, buffer_.data() + pos_, amount);
+  pos_ += amount;
+  return amount;
+}
+ssize_t MemStreamIO::Append(const void *buf, size_t count) {
+  buffer_.append((const char*)buf, count);
+  return count;
+}
+
+// Read exactly count bytes including retries. Returns success.
+static bool FullRead(StreamIO *io, void *buf, const size_t count) {
+  int remaining = count;
+  char *char_buffer = (char*)buf;
+  while (remaining > 0) {
+    int r = io->Read(char_buffer, remaining);
+    if (r < 0) return false;
+    if (r == 0) break;  // EOF.
+    char_buffer += r; remaining -= r;
+  }
+  return remaining == 0;
+}
+
+// Write exactly count bytes including retries. Returns success.
+static bool FullAppend(StreamIO *io, const void *buf, const size_t count) {
+  int remaining = count;
+  const char *char_buffer = (const char*) buf;
+  while (remaining > 0) {
+    int w = io->Append(char_buffer, remaining);
+    if (w < 0) return false;
+    char_buffer += w; remaining -= w;
+  }
+  return remaining == 0;
+}
+
+StreamWriter::StreamWriter(StreamIO *io) : io_(io), header_written_(false) {}
+bool StreamWriter::Stream(const FrameCanvas &frame, uint32_t hold_time_us) {
+  const char *data;
+  size_t len;
+  frame.Serialize(&data, &len);
+
+  if (!header_written_) {
+    WriteFileHeader(frame, len);
+  }
+  FrameHeader h = {};
+  h.magic = kFrameMagicValue;
+  h.size = len;
+  h.hold_time_us = hold_time_us;
+  FullAppend(io_, &h, sizeof(h));
+  return FullAppend(io_, data, len) == (ssize_t)len;
+}
+
+void StreamWriter::WriteFileHeader(const FrameCanvas &frame, size_t len) {
+  FileHeader header = {};
+  header.magic = kFileMagicValue;
+  header.width = frame.width();
+  header.height = frame.height();
+  header.buf_size = len;
+  header.is_wide_gpio = (sizeof(gpio_bits_t) > 4);
+  FullAppend(io_, &header, sizeof(header));
+  header_written_ = true;
+}
+
+StreamReader::StreamReader(StreamIO *io)
+  : io_(io), state_(STREAM_AT_BEGIN), header_frame_buffer_(NULL) {
+  io_->Rewind();
+}
+StreamReader::~StreamReader() { delete [] header_frame_buffer_; }
+
+void StreamReader::Rewind() {
+  io_->Rewind();
+  state_ = STREAM_AT_BEGIN;
+}
+
+bool StreamReader::GetNext(FrameCanvas *frame, uint32_t* hold_time_us) {
+  if (state_ == STREAM_AT_BEGIN && !ReadFileHeader(*frame)) return false;
+  if (state_ != STREAM_READING) return false;
+
+  // Read header and expected buffer size.
+  if (!FullRead(io_, header_frame_buffer_,
+                sizeof(FrameHeader) + frame_buf_size_)) {
+    return false;
+  }
+
+  const FrameHeader &h = *reinterpret_cast<FrameHeader*>(header_frame_buffer_);
+
+  // TODO: we might allow for this to be a kFileMagicValue, to allow people
+  // to just concatenate streams. In that case, we just would need to read
+  // ahead past this header (both headers are designed to be same size)
+  if (h.magic != kFrameMagicValue) {
+    state_ = STREAM_ERROR;
+    return false;
+  }
+
+  // In the future, we might allow larger buffers (audio?), but never smaller.
+  // For now, we need to make sure to exactly match the size, as our assumption
+  // above is that we can read the full header + frame in one FullRead().
+  if (h.size != frame_buf_size_)
+    return false;
+
+  if (hold_time_us) *hold_time_us = h.hold_time_us;
+  return frame->Deserialize(header_frame_buffer_ + sizeof(FrameHeader),
+                            frame_buf_size_);
+}
+
+bool StreamReader::ReadFileHeader(const FrameCanvas &frame) {
+  FileHeader header;
+  FullRead(io_, &header, sizeof(header));
+  if (header.magic != kFileMagicValue) {
+    state_ = STREAM_ERROR;
+    return false;
+  }
+  if ((int)header.width != frame.width()
+      || (int)header.height != frame.height()) {
+    fprintf(stderr, "This stream is for %dx%d, can't play on %dx%d. "
+            "Please use the same settings for record/replay\n",
+            header.width, header.height, frame.width(), frame.height());
+    state_ = STREAM_ERROR;
+    return false;
+  }
+  if (header.is_wide_gpio != (sizeof(gpio_bits_t) == 8)) {
+    fprintf(stderr, "This stream was written with %s GPIO width support but "
+            "this library is compiled with %d bit GPIO width (see "
+            "ENABLE_WIDE_GPIO_COMPUTE_MODULE setting in lib/Makefile)\n",
+            header.is_wide_gpio ? "wide (64-bit)" : "narrow (32-bit)",
+            int(sizeof(gpio_bits_t) * 8));
+    state_ = STREAM_ERROR;
+    return false;
+  }
+  state_ = STREAM_READING;
+  frame_buf_size_ = header.buf_size;
+  if (!header_frame_buffer_)
+    header_frame_buffer_ = new char [ sizeof(FrameHeader) + header.buf_size ];
+  return true;
+}
+}  // namespace rgb_matrix

+ 175 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/framebuffer-internal.h

@@ -0,0 +1,175 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+#ifndef RPI_RGBMATRIX_FRAMEBUFFER_INTERNAL_H
+#define RPI_RGBMATRIX_FRAMEBUFFER_INTERNAL_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "hardware-mapping.h"
+
+namespace rgb_matrix {
+class GPIO;
+class PinPulser;
+namespace internal {
+class RowAddressSetter;
+
+// An opaque type used within the framebuffer that can be used
+// to copy between PixelMappers.
+struct PixelDesignator {
+  PixelDesignator() : gpio_word(-1), r_bit(0), g_bit(0), b_bit(0), mask(~0u){}
+  long gpio_word;
+  gpio_bits_t r_bit;
+  gpio_bits_t g_bit;
+  gpio_bits_t b_bit;
+  gpio_bits_t mask;
+};
+
+class PixelDesignatorMap {
+public:
+  PixelDesignatorMap(int width, int height, const PixelDesignator &fill_bits);
+  ~PixelDesignatorMap();
+
+  // Get a writable version of the PixelDesignator. Outside Framebuffer used
+  // by the RGBMatrix to re-assign mappings to new PixelDesignatorMappers.
+  PixelDesignator *get(int x, int y);
+
+  inline int width() const { return width_; }
+  inline int height() const { return height_; }
+
+  // All bits that set red/green/blue pixels; used for Fill().
+  const PixelDesignator &GetFillColorBits() { return fill_bits_; }
+
+private:
+  const int width_;
+  const int height_;
+  const PixelDesignator fill_bits_;  // Precalculated for fill.
+  PixelDesignator *const buffer_;
+};
+
+// Internal representation of the frame-buffer that as well can
+// write itself to GPIO.
+// Our internal memory layout mimicks as much as possible what needs to be
+// written out.
+class Framebuffer {
+public:
+  // Maximum usable bitplanes.
+  //
+  // 11 bits seems to be a sweet spot in which we still get somewhat useful
+  // refresh rate and have good color richness. This is the default setting
+  // However, in low-light situations, we want to be able to scale down
+  // brightness more, having more bits at the bottom.
+  // TODO(hzeller): make the default 15 bit or so, but slide the use of
+  //  timing to lower bits if fewer bits requested to not affect the overall
+  //  refresh in that case.
+  //  This needs to be balanced to not create too agressive timing however.
+  //  To be explored in a separete commit.
+  //
+  // For now, if someone needs very low level of light, change this to
+  // say 13 and recompile. Run with --led-pwm-bits=13. Also, consider
+  // --led-pwm-dither-bits=2 to have the refresh rate not suffer too much.
+  static constexpr int kBitPlanes = 11;
+  static constexpr int kDefaultBitPlanes = 11;
+
+  Framebuffer(int rows, int columns, int parallel,
+              int scan_mode,
+              const char* led_sequence, bool inverse_color,
+              PixelDesignatorMap **mapper);
+  ~Framebuffer();
+
+  // Initialize GPIO bits for output. Only call once.
+  static void InitHardwareMapping(const char *named_hardware);
+  static void InitGPIO(GPIO *io, int rows, int parallel,
+                       bool allow_hardware_pulsing,
+                       int pwm_lsb_nanoseconds,
+                       int dither_bits,
+                       int row_address_type);
+  static void InitializePanels(GPIO *io, const char *panel_type, int columns);
+
+  // Set PWM bits used for output. Default is 11, but if you only deal with
+  // simple comic-colors, 1 might be sufficient. Lower require less CPU.
+  // Returns boolean to signify if value was within range.
+  bool SetPWMBits(uint8_t value);
+  uint8_t pwmbits() { return pwm_bits_; }
+
+  // Map brightness of output linearly to input with CIE1931 profile.
+  void set_luminance_correct(bool on) { do_luminance_correct_ = on; }
+  bool luminance_correct() const { return do_luminance_correct_; }
+
+  // Set brightness in percent; range=1..100
+  // This will only affect newly set pixels.
+  void SetBrightness(uint8_t b) {
+    brightness_ = (b <= 100 ? (b != 0 ? b : 1) : 100);
+  }
+  uint8_t brightness() { return brightness_; }
+
+  void DumpToMatrix(GPIO *io, int pwm_bits_to_show);
+
+  void Serialize(const char **data, size_t *len) const;
+  bool Deserialize(const char *data, size_t len);
+  void CopyFrom(const Framebuffer *other);
+
+  // Canvas-inspired methods, but we're not implementing this interface to not
+  // have an unnecessary vtable.
+  int width() const;
+  int height() const;
+  void SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue);
+  void Clear();
+  void Fill(uint8_t red, uint8_t green, uint8_t blue);
+
+private:
+  static const struct HardwareMapping *hardware_mapping_;
+  static RowAddressSetter *row_setter_;
+
+  // This returns the gpio-bit for given color (one of 'R', 'G', 'B'). This is
+  // returning the right value in case "led_sequence" is _not_ "RGB"
+  static gpio_bits_t GetGpioFromLedSequence(char col, const char *led_sequence,
+                                            gpio_bits_t default_r,
+                                            gpio_bits_t default_g,
+                                            gpio_bits_t default_b);
+
+  void InitDefaultDesignator(int x, int y, const char *led_sequence,
+                             PixelDesignator *designator);
+  inline void  MapColors(uint8_t r, uint8_t g, uint8_t b,
+                         uint16_t *red, uint16_t *green, uint16_t *blue);
+  const int rows_;     // Number of rows. 16 or 32.
+  const int parallel_; // Parallel rows of chains. 1 or 2.
+  const int height_;   // rows * parallel
+  const int columns_;  // Number of columns. Number of chained boards * 32.
+
+  const int scan_mode_;
+  const bool inverse_color_;
+
+  uint8_t pwm_bits_;   // PWM bits to display.
+  bool do_luminance_correct_;
+  uint8_t brightness_;
+
+  const int double_rows_;
+  const size_t buffer_size_;
+
+  // The frame-buffer is organized in bitplanes.
+  // Highest level (slowest to cycle through) are double rows.
+  // For each double-row, we store pwm-bits columns of a bitplane.
+  // Each bitplane-column is pre-filled IoBits, of which the colors are set.
+  // Of course, that means that we store unrelated bits in the frame-buffer,
+  // but it allows easy access in the critical section.
+  gpio_bits_t *bitplane_buffer_;
+  inline gpio_bits_t *ValueAt(int double_row, int column, int bit);
+
+  PixelDesignatorMap **shared_mapper_;  // Storage in RGBMatrix.
+};
+}  // namespace internal
+}  // namespace rgb_matrix
+#endif // RPI_RGBMATRIX_FRAMEBUFFER_INTERNAL_H

+ 878 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/framebuffer.cc

@@ -0,0 +1,878 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+// The framebuffer is the workhorse: it represents the frame in some internal
+// format that is friendly to be dumped to the matrix quickly. Provides methods
+// to manipulate the content.
+
+#include "framebuffer-internal.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "gpio.h"
+
+namespace rgb_matrix {
+namespace internal {
+// We need one global instance of a timing correct pulser. There are different
+// implementations depending on the context.
+static PinPulser *sOutputEnablePulser = NULL;
+
+#ifdef ONLY_SINGLE_SUB_PANEL
+#  define SUB_PANELS_ 1
+#else
+#  define SUB_PANELS_ 2
+#endif
+
+PixelDesignator *PixelDesignatorMap::get(int x, int y) {
+  if (x < 0 || y < 0 || x >= width_ || y >= height_)
+    return NULL;
+  return buffer_ + (y*width_) + x;
+}
+
+PixelDesignatorMap::PixelDesignatorMap(int width, int height,
+                                       const PixelDesignator &fill_bits)
+  : width_(width), height_(height), fill_bits_(fill_bits),
+    buffer_(new PixelDesignator[width * height]) {
+}
+
+PixelDesignatorMap::~PixelDesignatorMap() {
+  delete [] buffer_;
+}
+
+// Different panel types use different techniques to set the row address.
+// We abstract that away with different implementations of RowAddressSetter
+class RowAddressSetter {
+public:
+  virtual ~RowAddressSetter() {}
+  virtual gpio_bits_t need_bits() const = 0;
+  virtual void SetRowAddress(GPIO *io, int row) = 0;
+};
+
+namespace {
+
+// The default DirectRowAddressSetter just sets the address in parallel
+// output lines ABCDE with A the LSB and E the MSB.
+class DirectRowAddressSetter : public RowAddressSetter {
+public:
+  DirectRowAddressSetter(int double_rows, const HardwareMapping &h)
+    : row_mask_(0), last_row_(-1) {
+    assert(double_rows <= 32);  // need to resize row_lookup_
+    if (double_rows > 16) row_mask_ |= h.e;
+    if (double_rows > 8)  row_mask_ |= h.d;
+    if (double_rows > 4)  row_mask_ |= h.c;
+    if (double_rows > 2)  row_mask_ |= h.b;
+    row_mask_ |= h.a;
+    for (int i = 0; i < double_rows; ++i) {
+      // To avoid the bit-fiddle in the critical path, utilize
+      // a lookup-table for all possible rows.
+      gpio_bits_t row_address = (i & 0x01) ? h.a : 0;
+      row_address |= (i & 0x02) ? h.b : 0;
+      row_address |= (i & 0x04) ? h.c : 0;
+      row_address |= (i & 0x08) ? h.d : 0;
+      row_address |= (i & 0x10) ? h.e : 0;
+      row_lookup_[i] = row_address;
+    }
+  }
+
+  virtual gpio_bits_t need_bits() const { return row_mask_; }
+
+  virtual void SetRowAddress(GPIO *io, int row) {
+    if (row == last_row_) return;
+    io->WriteMaskedBits(row_lookup_[row], row_mask_);
+    last_row_ = row;
+  }
+
+private:
+  gpio_bits_t row_mask_;
+  gpio_bits_t row_lookup_[32];
+  int last_row_;
+};
+
+// The SM5266RowAddressSetter (ABC Shifter + DE direct) sets bits ABC using
+// a 8 bit shifter and DE directly. The panel this works with has 8 SM5266
+// shifters (4 for the top 32 rows and 4 for the bottom 32 rows).
+// DE is used to select the active shifter
+// (rows 1-8/33-40, 9-16/41-48, 17-24/49-56, 25-32/57-64).
+// Rows are enabled by shifting in 8 bits (high bit first) with a high bit
+// enabling that row. This allows up to 8 rows per group to be active at the
+// same time (if they have the same content), but that isn't implemented here.
+// BK, DIN and DCK are the designations on the SM5266P datasheet.
+// BK = Enable Input, DIN = Serial In, DCK = Clock
+class SM5266RowAddressSetter : public RowAddressSetter {
+public:
+  SM5266RowAddressSetter(int double_rows, const HardwareMapping &h)
+    : row_mask_(h.a | h.b | h.c),
+      last_row_(-1),
+      bk_(h.c),
+      din_(h.b),
+      dck_(h.a) {
+    assert(double_rows <= 32); // designed for up to 1/32 panel
+    if (double_rows > 8)  row_mask_ |= h.d;
+    if (double_rows > 16) row_mask_ |= h.e;
+    for (int i = 0; i < double_rows; ++i) {
+      gpio_bits_t row_address = 0;
+      row_address |= (i & 0x08) ? h.d : 0;
+      row_address |= (i & 0x10) ? h.e : 0;
+      row_lookup_[i] = row_address;
+    }
+  }
+
+  virtual gpio_bits_t need_bits() const { return row_mask_; }
+
+  virtual void SetRowAddress(GPIO *io, int row) {
+    if (row == last_row_) return;
+    io->SetBits(bk_);  // Enable serial input for the shifter
+    for (int r = 7; r >= 0; r--) {
+      if (row % 8 == r) {
+        io->SetBits(din_);
+      } else {
+        io->ClearBits(din_);
+      }
+      io->SetBits(dck_);
+      io->SetBits(dck_);  // Longer clock time; tested with Pi3
+      io->ClearBits(dck_);
+    }
+    io->ClearBits(bk_);  // Disable serial input to keep unwanted bits out of the shifters
+    last_row_ = row;
+    // Set bits D and E to enable the proper shifter to display the selected
+    // row.
+    io->WriteMaskedBits(row_lookup_[row], row_mask_);
+  }
+
+private:
+  gpio_bits_t row_mask_;
+  int last_row_;
+  const gpio_bits_t bk_;
+  const gpio_bits_t din_;
+  const gpio_bits_t dck_;
+  gpio_bits_t row_lookup_[32];
+};
+
+class ShiftRegisterRowAddressSetter : public RowAddressSetter {
+public:
+  ShiftRegisterRowAddressSetter(int double_rows, const HardwareMapping &h)
+    : double_rows_(double_rows),
+      row_mask_(h.a | h.b), clock_(h.a), data_(h.b),
+      last_row_(-1) {
+  }
+  virtual gpio_bits_t need_bits() const { return row_mask_; }
+
+  virtual void SetRowAddress(GPIO *io, int row) {
+    if (row == last_row_) return;
+    for (int activate = 0; activate < double_rows_; ++activate) {
+      io->ClearBits(clock_);
+      if (activate == double_rows_ - 1 - row) {
+        io->ClearBits(data_);
+      } else {
+        io->SetBits(data_);
+      }
+      io->SetBits(clock_);
+    }
+    io->ClearBits(clock_);
+    io->SetBits(clock_);
+    last_row_ = row;
+  }
+
+private:
+  const int double_rows_;
+  const gpio_bits_t row_mask_;
+  const gpio_bits_t clock_;
+  const gpio_bits_t data_;
+  int last_row_;
+};
+
+// Issue #823
+// An shift register row address setter that does not use B but C for the
+// data. Clock is inverted.
+class ABCShiftRegisterRowAddressSetter : public RowAddressSetter {
+public:
+  ABCShiftRegisterRowAddressSetter(int double_rows, const HardwareMapping &h)
+    : double_rows_(double_rows),
+      row_mask_(h.a | h.c),
+      clock_(h.a),
+      data_(h.c),
+      last_row_(-1) {
+  }
+  virtual gpio_bits_t need_bits() const { return row_mask_; }
+
+  virtual void SetRowAddress(GPIO *io, int row) {
+    for (int activate = 0; activate < double_rows_; ++activate) {
+      io->ClearBits(clock_);
+      if (activate == double_rows_ - 1 - row) {
+        io->SetBits(data_);
+      } else {
+        io->ClearBits(data_);
+      }
+      io->SetBits(clock_);
+    }
+    io->SetBits(clock_);
+    io->ClearBits(clock_);
+    last_row_ = row;
+  }
+
+private:
+  const int double_rows_;
+  const gpio_bits_t row_mask_;
+  const gpio_bits_t clock_;
+  const gpio_bits_t data_;
+  int last_row_;
+};
+
+// The DirectABCDRowAddressSetter sets the address by one of
+// row pin ABCD for 32х16 matrix 1:4 multiplexing. The matrix has
+// 4 addressable rows. Row is selected by a low level on the
+// corresponding row address pin. Other row address pins must be in high level.
+//
+// Row addr| 0 | 1 | 2 | 3
+// --------+---+---+---+---
+// Line A  | 0 | 1 | 1 | 1
+// Line B  | 1 | 0 | 1 | 1
+// Line C  | 1 | 1 | 0 | 1
+// Line D  | 1 | 1 | 1 | 0
+class DirectABCDLineRowAddressSetter : public RowAddressSetter {
+public:
+  DirectABCDLineRowAddressSetter(int double_rows, const HardwareMapping &h)
+    : last_row_(-1) {
+	row_mask_ = h.a | h.b | h.c | h.d;
+
+	row_lines_[0] = /*h.a |*/ h.b | h.c | h.d;
+	row_lines_[1] = h.a /*| h.b*/ | h.c | h.d;
+	row_lines_[2] = h.a | h.b /*| h.c */| h.d;
+	row_lines_[3] = h.a | h.b | h.c /*| h.d*/;
+  }
+
+  virtual gpio_bits_t need_bits() const { return row_mask_; }
+
+  virtual void SetRowAddress(GPIO *io, int row) {
+    if (row == last_row_) return;
+
+    gpio_bits_t row_address = row_lines_[row % 4];
+
+    io->WriteMaskedBits(row_address, row_mask_);
+    last_row_ = row;
+  }
+
+private:
+  gpio_bits_t row_lines_[4];
+  gpio_bits_t row_mask_;
+  int last_row_;
+};
+
+}
+
+const struct HardwareMapping *Framebuffer::hardware_mapping_ = NULL;
+RowAddressSetter *Framebuffer::row_setter_ = NULL;
+
+Framebuffer::Framebuffer(int rows, int columns, int parallel,
+                         int scan_mode,
+                         const char *led_sequence, bool inverse_color,
+                         PixelDesignatorMap **mapper)
+  : rows_(rows),
+    parallel_(parallel),
+    height_(rows * parallel),
+    columns_(columns),
+    scan_mode_(scan_mode),
+    inverse_color_(inverse_color),
+    pwm_bits_(kBitPlanes), do_luminance_correct_(true), brightness_(100),
+    double_rows_(rows / SUB_PANELS_),
+    buffer_size_(double_rows_ * columns_ * kBitPlanes * sizeof(gpio_bits_t)),
+    shared_mapper_(mapper) {
+  assert(hardware_mapping_ != NULL);   // Called InitHardwareMapping() ?
+  assert(shared_mapper_ != NULL);  // Storage should be provided by RGBMatrix.
+  assert(rows_ >=4 && rows_ <= 64 && rows_ % 2 == 0);
+  if (parallel > hardware_mapping_->max_parallel_chains) {
+    fprintf(stderr, "The %s GPIO mapping only supports %d parallel chain%s, "
+            "but %d was requested.\n", hardware_mapping_->name,
+            hardware_mapping_->max_parallel_chains,
+            hardware_mapping_->max_parallel_chains > 1 ? "s" : "", parallel);
+    abort();
+  }
+  assert(parallel >= 1 && parallel <= 6);
+
+  bitplane_buffer_ = new gpio_bits_t[double_rows_ * columns_ * kBitPlanes];
+
+  // If we're the first Framebuffer created, the shared PixelMapper is
+  // still NULL, so create one.
+  // The first PixelMapper represents the physical layout of a standard matrix
+  // with the specific knowledge of the framebuffer, setting up PixelDesignators
+  // in a way that they are useful for this Framebuffer.
+  //
+  // Newly created PixelMappers then can just re-arrange PixelDesignators
+  // from the parent PixelMapper opaquely without having to know the details.
+  if (*shared_mapper_ == NULL) {
+    // Gather all the bits for given color for fast Fill()s and use the right
+    // bits according to the led sequence
+    const struct HardwareMapping &h = *hardware_mapping_;
+    gpio_bits_t r = h.p0_r1 | h.p0_r2 | h.p1_r1 | h.p1_r2 | h.p2_r1 | h.p2_r2 | h.p3_r1 | h.p3_r2 | h.p4_r1 | h.p4_r2 | h.p5_r1 | h.p5_r2;
+    gpio_bits_t g = h.p0_g1 | h.p0_g2 | h.p1_g1 | h.p1_g2 | h.p2_g1 | h.p2_g2 | h.p3_g1 | h.p3_g2 | h.p4_g1 | h.p4_g2 | h.p5_g1 | h.p5_g2;
+    gpio_bits_t b = h.p0_b1 | h.p0_b2 | h.p1_b1 | h.p1_b2 | h.p2_b1 | h.p2_b2 | h.p3_b1 | h.p3_b2 | h.p4_b1 | h.p4_b2 | h.p5_b1 | h.p5_b2;
+    PixelDesignator fill_bits;
+    fill_bits.r_bit = GetGpioFromLedSequence('R', led_sequence, r, g, b);
+    fill_bits.g_bit = GetGpioFromLedSequence('G', led_sequence, r, g, b);
+    fill_bits.b_bit = GetGpioFromLedSequence('B', led_sequence, r, g, b);
+
+    *shared_mapper_ = new PixelDesignatorMap(columns_, height_, fill_bits);
+    for (int y = 0; y < height_; ++y) {
+      for (int x = 0; x < columns_; ++x) {
+        InitDefaultDesignator(x, y, led_sequence, (*shared_mapper_)->get(x, y));
+      }
+    }
+  }
+
+  Clear();
+}
+
+Framebuffer::~Framebuffer() {
+  delete [] bitplane_buffer_;
+}
+
+// TODO: this should also be parsed from some special formatted string, e.g.
+// {addr={22,23,24,25,15},oe=18,clk=17,strobe=4, p0={11,27,7,8,9,10},...}
+/* static */ void Framebuffer::InitHardwareMapping(const char *named_hardware) {
+  if (named_hardware == NULL || *named_hardware == '\0') {
+    named_hardware = "regular";
+  }
+
+  struct HardwareMapping *mapping = NULL;
+  for (HardwareMapping *it = matrix_hardware_mappings; it->name; ++it) {
+    if (strcasecmp(it->name, named_hardware) == 0) {
+      mapping = it;
+      break;
+    }
+  }
+
+  if (!mapping) {
+    fprintf(stderr, "There is no hardware mapping named '%s'.\nAvailable: ",
+            named_hardware);
+    for (HardwareMapping *it = matrix_hardware_mappings; it->name; ++it) {
+      if (it != matrix_hardware_mappings) fprintf(stderr, ", ");
+      fprintf(stderr, "'%s'", it->name);
+    }
+    fprintf(stderr, "\n");
+    abort();
+  }
+
+  if (mapping->max_parallel_chains == 0) {
+    // Auto determine.
+    struct HardwareMapping *h = mapping;
+    if ((h->p0_r1 | h->p0_g1 | h->p0_g1 | h->p0_r2 | h->p0_g2 | h->p0_g2) > 0)
+      ++mapping->max_parallel_chains;
+    if ((h->p1_r1 | h->p1_g1 | h->p1_g1 | h->p1_r2 | h->p1_g2 | h->p1_g2) > 0)
+      ++mapping->max_parallel_chains;
+    if ((h->p2_r1 | h->p2_g1 | h->p2_g1 | h->p2_r2 | h->p2_g2 | h->p2_g2) > 0)
+      ++mapping->max_parallel_chains;
+    if ((h->p3_r1 | h->p3_g1 | h->p3_g1 | h->p3_r2 | h->p3_g2 | h->p3_g2) > 0)
+      ++mapping->max_parallel_chains;
+    if ((h->p4_r1 | h->p4_g1 | h->p4_g1 | h->p4_r2 | h->p4_g2 | h->p4_g2) > 0)
+      ++mapping->max_parallel_chains;
+    if ((h->p5_r1 | h->p5_g1 | h->p5_g1 | h->p5_r2 | h->p5_g2 | h->p5_g2) > 0)
+      ++mapping->max_parallel_chains;
+  }
+  hardware_mapping_ = mapping;
+}
+
+/* static */ void Framebuffer::InitGPIO(GPIO *io, int rows, int parallel,
+                                        bool allow_hardware_pulsing,
+                                        int pwm_lsb_nanoseconds,
+                                        int dither_bits,
+                                        int row_address_type) {
+  if (sOutputEnablePulser != NULL)
+    return;  // already initialized.
+
+  const struct HardwareMapping &h = *hardware_mapping_;
+  // Tell GPIO about all bits we intend to use.
+  gpio_bits_t all_used_bits = 0;
+
+  all_used_bits |= h.output_enable | h.clock | h.strobe;
+
+  all_used_bits |= h.p0_r1 | h.p0_g1 | h.p0_b1 | h.p0_r2 | h.p0_g2 | h.p0_b2;
+  if (parallel >= 2) {
+    all_used_bits |= h.p1_r1 | h.p1_g1 | h.p1_b1 | h.p1_r2 | h.p1_g2 | h.p1_b2;
+  }
+  if (parallel >= 3) {
+    all_used_bits |= h.p2_r1 | h.p2_g1 | h.p2_b1 | h.p2_r2 | h.p2_g2 | h.p2_b2;
+  }
+  if (parallel >= 4) {
+    all_used_bits |= h.p3_r1 | h.p3_g1 | h.p3_b1 | h.p3_r2 | h.p3_g2 | h.p3_b2;
+  }
+  if (parallel >= 5) {
+    all_used_bits |= h.p4_r1 | h.p4_g1 | h.p4_b1 | h.p4_r2 | h.p4_g2 | h.p4_b2;
+  }
+  if (parallel >= 6) {
+    all_used_bits |= h.p5_r1 | h.p5_g1 | h.p5_b1 | h.p5_r2 | h.p5_g2 | h.p5_b2;
+  }
+
+  const int double_rows = rows / SUB_PANELS_;
+  switch (row_address_type) {
+  case 0:
+    row_setter_ = new DirectRowAddressSetter(double_rows, h);
+    break;
+  case 1:
+    row_setter_ = new ShiftRegisterRowAddressSetter(double_rows, h);
+    break;
+  case 2:
+    row_setter_ = new DirectABCDLineRowAddressSetter(double_rows, h);
+    break;
+  case 3:
+    row_setter_ = new ABCShiftRegisterRowAddressSetter(double_rows, h);
+    break;
+  case 4:
+    row_setter_ = new SM5266RowAddressSetter(double_rows, h);
+    break;
+  default:
+    assert(0);  // unexpected type.
+  }
+
+  all_used_bits |= row_setter_->need_bits();
+
+  // Adafruit HAT identified by the same prefix.
+  const bool is_some_adafruit_hat = (0 == strncmp(h.name, "adafruit-hat",
+                                                  strlen("adafruit-hat")));
+  // Initialize outputs, make sure that all of these are supported bits.
+  const gpio_bits_t result = io->InitOutputs(all_used_bits,
+                                             is_some_adafruit_hat);
+  assert(result == all_used_bits);  // Impl: all bits declared in gpio.cc ?
+
+  std::vector<int> bitplane_timings;
+  uint32_t timing_ns = pwm_lsb_nanoseconds;
+  for (int b = 0; b < kBitPlanes; ++b) {
+    bitplane_timings.push_back(timing_ns);
+    if (b >= dither_bits) timing_ns *= 2;
+  }
+  sOutputEnablePulser = PinPulser::Create(io, h.output_enable,
+                                          allow_hardware_pulsing,
+                                          bitplane_timings);
+}
+
+// NOTE: first version for panel initialization sequence, need to refine
+// until it is more clear how different panel types are initialized to be
+// able to abstract this more.
+
+static void InitFM6126(GPIO *io, const struct HardwareMapping &h, int columns) {
+  const gpio_bits_t bits_on
+    = h.p0_r1 | h.p0_g1 | h.p0_b1 | h.p0_r2 | h.p0_g2 | h.p0_b2
+    | h.p1_r1 | h.p1_g1 | h.p1_b1 | h.p1_r2 | h.p1_g2 | h.p1_b2
+    | h.p2_r1 | h.p2_g1 | h.p2_b1 | h.p2_r2 | h.p2_g2 | h.p2_b2
+    | h.p3_r1 | h.p3_g1 | h.p3_b1 | h.p3_r2 | h.p3_g2 | h.p3_b2
+    | h.p4_r1 | h.p4_g1 | h.p4_b1 | h.p4_r2 | h.p4_g2 | h.p4_b2
+    | h.p5_r1 | h.p5_g1 | h.p5_b1 | h.p5_r2 | h.p5_g2 | h.p5_b2
+    | h.a;  // Address bit 'A' is always on.
+  const gpio_bits_t bits_off = h.a;
+  const gpio_bits_t mask = bits_on | h.strobe;
+
+  // Init bits. TODO: customize, as we can do things such as brightness here,
+  // which would allow more higher quality output.
+  static const char* init_b12 = "0111111111111111";  // full bright
+  static const char* init_b13 = "0000000001000000";  // panel on.
+
+  io->ClearBits(h.clock | h.strobe);
+
+  for (int i = 0; i < columns; ++i) {
+    gpio_bits_t value = init_b12[i % 16] == '0' ? bits_off : bits_on;
+    if (i > columns - 12) value |= h.strobe;
+    io->WriteMaskedBits(value, mask);
+    io->SetBits(h.clock);
+    io->ClearBits(h.clock);
+  }
+  io->ClearBits(h.strobe);
+
+  for (int i = 0; i < columns; ++i) {
+    gpio_bits_t value = init_b13[i % 16] == '0' ? bits_off : bits_on;
+    if (i > columns - 13) value |= h.strobe;
+    io->WriteMaskedBits(value, mask);
+    io->SetBits(h.clock);
+    io->ClearBits(h.clock);
+  }
+  io->ClearBits(h.strobe);
+}
+
+// The FM6217 is very similar to the FM6216.
+// FM6217 adds Register 3 to allow for automatic bad pixel supression.
+static void InitFM6127(GPIO *io, const struct HardwareMapping &h, int columns) {
+  const gpio_bits_t bits_r_on= h.p0_r1 | h.p0_r2;
+  const gpio_bits_t bits_g_on= h.p0_g1 | h.p0_g2;
+  const gpio_bits_t bits_b_on= h.p0_b1 | h.p0_b2;
+  const gpio_bits_t bits_on= bits_r_on | bits_g_on | bits_b_on;
+  const gpio_bits_t bits_off = 0;
+
+  const gpio_bits_t mask = bits_on | h.strobe;
+
+  static const char* init_b12 = "1111111111001110";  // register 1
+  static const char* init_b13 = "1110000001100010";  // register 2.
+  static const char* init_b11 = "0101111100000000";  // register 3.
+  io->ClearBits(h.clock | h.strobe);
+  for (int i = 0; i < columns; ++i) {
+    gpio_bits_t value = init_b12[i % 16] == '0' ? bits_off : bits_on;
+    if (i > columns - 12) value |= h.strobe;
+    io->WriteMaskedBits(value, mask);
+    io->SetBits(h.clock);
+    io->ClearBits(h.clock);
+  }
+  io->ClearBits(h.strobe);
+
+  for (int i = 0; i < columns; ++i) {
+    gpio_bits_t value = init_b13[i % 16] == '0' ? bits_off : bits_on;
+    if (i > columns - 13) value |= h.strobe;
+    io->WriteMaskedBits(value, mask);
+    io->SetBits(h.clock);
+    io->ClearBits(h.clock);
+  }
+  io->ClearBits(h.strobe);
+
+  for (int i = 0; i < columns; ++i) {
+    gpio_bits_t value = init_b11[i % 16] == '0' ? bits_off : bits_on;
+    if (i > columns - 11) value |= h.strobe;
+    io->WriteMaskedBits(value, mask);
+    io->SetBits(h.clock);
+    io->ClearBits(h.clock);
+  }
+  io->ClearBits(h.strobe);
+}
+
+/*static*/ void Framebuffer::InitializePanels(GPIO *io,
+                                              const char *panel_type,
+                                              int columns) {
+  if (!panel_type || panel_type[0] == '\0') return;
+  if (strncasecmp(panel_type, "fm6126", 6) == 0) {
+    InitFM6126(io, *hardware_mapping_, columns);
+  }
+  else if (strncasecmp(panel_type, "fm6127", 6) == 0) {
+    InitFM6127(io, *hardware_mapping_, columns);
+  }
+  // else if (strncasecmp(...))  // more init types
+  else {
+    fprintf(stderr, "Unknown panel type '%s'; typo ?\n", panel_type);
+  }
+}
+
+bool Framebuffer::SetPWMBits(uint8_t value) {
+  if (value < 1 || value > kBitPlanes)
+    return false;
+  pwm_bits_ = value;
+  return true;
+}
+
+inline gpio_bits_t *Framebuffer::ValueAt(int double_row, int column, int bit) {
+  return &bitplane_buffer_[ double_row * (columns_ * kBitPlanes)
+                            + bit * columns_
+                            + column ];
+}
+
+void Framebuffer::Clear() {
+  if (inverse_color_) {
+    Fill(0, 0, 0);
+  } else  {
+    // Cheaper.
+    memset(bitplane_buffer_, 0,
+           sizeof(*bitplane_buffer_) * double_rows_ * columns_ * kBitPlanes);
+  }
+}
+
+// Do CIE1931 luminance correction and scale to output bitplanes
+static uint16_t luminance_cie1931(uint8_t c, uint8_t brightness) {
+  float out_factor = ((1 << internal::Framebuffer::kBitPlanes) - 1);
+  float v = (float) c * brightness / 255.0;
+  return roundf(out_factor * ((v <= 8) ? v / 902.3 : pow((v + 16) / 116.0, 3)));
+}
+
+struct ColorLookup {
+  uint16_t color[256];
+};
+static ColorLookup *CreateLuminanceCIE1931LookupTable() {
+  ColorLookup *for_brightness = new ColorLookup[100];
+  for (int c = 0; c < 256; ++c)
+    for (int b = 0; b < 100; ++b)
+      for_brightness[b].color[c] = luminance_cie1931(c, b + 1);
+
+  return for_brightness;
+}
+
+static inline uint16_t CIEMapColor(uint8_t brightness, uint8_t c) {
+  static ColorLookup *luminance_lookup = CreateLuminanceCIE1931LookupTable();
+  return luminance_lookup[brightness - 1].color[c];
+}
+
+// Non luminance correction. TODO: consider getting rid of this.
+static inline uint16_t DirectMapColor(uint8_t brightness, uint8_t c) {
+  // simple scale down the color value
+  c = c * brightness / 100;
+
+  // shift to be left aligned with top-most bits.
+  constexpr int shift = internal::Framebuffer::kBitPlanes - 8;
+  return (shift > 0) ? (c << shift) : (c >> -shift);
+}
+
+inline void Framebuffer::MapColors(
+  uint8_t r, uint8_t g, uint8_t b,
+  uint16_t *red, uint16_t *green, uint16_t *blue) {
+
+  if (do_luminance_correct_) {
+    *red   = CIEMapColor(brightness_, r);
+    *green = CIEMapColor(brightness_, g);
+    *blue  = CIEMapColor(brightness_, b);
+  } else {
+    *red   = DirectMapColor(brightness_, r);
+    *green = DirectMapColor(brightness_, g);
+    *blue  = DirectMapColor(brightness_, b);
+  }
+
+  if (inverse_color_) {
+    *red = ~(*red);
+    *green = ~(*green);
+    *blue = ~(*blue);
+  }
+}
+
+void Framebuffer::Fill(uint8_t r, uint8_t g, uint8_t b) {
+  uint16_t red, green, blue;
+  MapColors(r, g, b, &red, &green, &blue);
+  const PixelDesignator &fill = (*shared_mapper_)->GetFillColorBits();
+
+  for (int b = kBitPlanes - pwm_bits_; b < kBitPlanes; ++b) {
+    uint16_t mask = 1 << b;
+    gpio_bits_t plane_bits = 0;
+    plane_bits |= ((red & mask) == mask)   ? fill.r_bit : 0;
+    plane_bits |= ((green & mask) == mask) ? fill.g_bit : 0;
+    plane_bits |= ((blue & mask) == mask)  ? fill.b_bit : 0;
+
+    for (int row = 0; row < double_rows_; ++row) {
+      gpio_bits_t *row_data = ValueAt(row, 0, b);
+      for (int col = 0; col < columns_; ++col) {
+        *row_data++ = plane_bits;
+      }
+    }
+  }
+}
+
+int Framebuffer::width() const { return (*shared_mapper_)->width(); }
+int Framebuffer::height() const { return (*shared_mapper_)->height(); }
+
+void Framebuffer::SetPixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
+  const PixelDesignator *designator = (*shared_mapper_)->get(x, y);
+  if (designator == NULL) return;
+  const long pos = designator->gpio_word;
+  if (pos < 0) return;  // non-used pixel marker.
+
+  uint16_t red, green, blue;
+  MapColors(r, g, b, &red, &green, &blue);
+
+  gpio_bits_t *bits = bitplane_buffer_ + pos;
+  const int min_bit_plane = kBitPlanes - pwm_bits_;
+  bits += (columns_ * min_bit_plane);
+  const gpio_bits_t r_bits = designator->r_bit;
+  const gpio_bits_t g_bits = designator->g_bit;
+  const gpio_bits_t b_bits = designator->b_bit;
+  const gpio_bits_t designator_mask = designator->mask;
+  for (uint16_t mask = 1<<min_bit_plane; mask != 1<<kBitPlanes; mask <<=1 ) {
+    gpio_bits_t color_bits = 0;
+    if (red & mask)   color_bits |= r_bits;
+    if (green & mask) color_bits |= g_bits;
+    if (blue & mask)  color_bits |= b_bits;
+    *bits = (*bits & designator_mask) | color_bits;
+    bits += columns_;
+  }
+}
+
+// Strange LED-mappings such as RBG or so are handled here.
+gpio_bits_t Framebuffer::GetGpioFromLedSequence(char col,
+                                                const char *led_sequence,
+                                                gpio_bits_t default_r,
+                                                gpio_bits_t default_g,
+                                                gpio_bits_t default_b) {
+  const char *pos = strchr(led_sequence, col);
+  if (pos == NULL) pos = strchr(led_sequence, tolower(col));
+  if (pos == NULL) {
+    fprintf(stderr, "LED sequence '%s' does not contain any '%c'.\n",
+            led_sequence, col);
+    abort();
+  }
+  switch (pos - led_sequence) {
+  case 0: return default_r;
+  case 1: return default_g;
+  case 2: return default_b;
+  }
+  return default_r;  // String too long, should've been caught earlier.
+}
+
+void Framebuffer::InitDefaultDesignator(int x, int y, const char *seq,
+                                        PixelDesignator *d) {
+  const struct HardwareMapping &h = *hardware_mapping_;
+  gpio_bits_t *bits = ValueAt(y % double_rows_, x, 0);
+  d->gpio_word = bits - bitplane_buffer_;
+  d->r_bit = d->g_bit = d->b_bit = 0;
+  if (y < rows_) {
+    if (y < double_rows_) {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p0_r1, h.p0_g1, h.p0_b1);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p0_r1, h.p0_g1, h.p0_b1);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p0_r1, h.p0_g1, h.p0_b1);
+    } else {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p0_r2, h.p0_g2, h.p0_b2);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p0_r2, h.p0_g2, h.p0_b2);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p0_r2, h.p0_g2, h.p0_b2);
+    }
+  }
+  else if (y >= rows_ && y < 2 * rows_) {
+    if (y - rows_ < double_rows_) {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p1_r1, h.p1_g1, h.p1_b1);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p1_r1, h.p1_g1, h.p1_b1);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p1_r1, h.p1_g1, h.p1_b1);
+    } else {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p1_r2, h.p1_g2, h.p1_b2);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p1_r2, h.p1_g2, h.p1_b2);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p1_r2, h.p1_g2, h.p1_b2);
+    }
+  }
+  else if (y >= 2*rows_ && y < 3 * rows_) {
+    if (y - 2*rows_ < double_rows_) {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p2_r1, h.p2_g1, h.p2_b1);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p2_r1, h.p2_g1, h.p2_b1);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p2_r1, h.p2_g1, h.p2_b1);
+    } else {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p2_r2, h.p2_g2, h.p2_b2);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p2_r2, h.p2_g2, h.p2_b2);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p2_r2, h.p2_g2, h.p2_b2);
+    }
+  }
+  else if (y >= 3*rows_ && y < 4 * rows_) {
+    if (y - 3*rows_ < double_rows_) {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p3_r1, h.p3_g1, h.p3_b1);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p3_r1, h.p3_g1, h.p3_b1);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p3_r1, h.p3_g1, h.p3_b1);
+    } else {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p3_r2, h.p3_g2, h.p3_b2);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p3_r2, h.p3_g2, h.p3_b2);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p3_r2, h.p3_g2, h.p3_b2);
+    }
+  }
+  else if (y >= 4*rows_ && y < 5 * rows_){
+    if (y - 4*rows_ < double_rows_) {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p4_r1, h.p4_g1, h.p4_b1);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p4_r1, h.p4_g1, h.p4_b1);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p4_r1, h.p4_g1, h.p4_b1);
+    } else {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p4_r2, h.p4_g2, h.p4_b2);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p4_r2, h.p4_g2, h.p4_b2);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p4_r2, h.p4_g2, h.p4_b2);
+    }
+
+  }
+  else {
+    if (y - 5*rows_ < double_rows_) {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p5_r1, h.p5_g1, h.p5_b1);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p5_r1, h.p5_g1, h.p5_b1);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p5_r1, h.p5_g1, h.p5_b1);
+    } else {
+      d->r_bit = GetGpioFromLedSequence('R', seq, h.p5_r2, h.p5_g2, h.p5_b2);
+      d->g_bit = GetGpioFromLedSequence('G', seq, h.p5_r2, h.p5_g2, h.p5_b2);
+      d->b_bit = GetGpioFromLedSequence('B', seq, h.p5_r2, h.p5_g2, h.p5_b2);
+    }
+  }
+
+  d->mask = ~(d->r_bit | d->g_bit | d->b_bit);
+}
+
+void Framebuffer::Serialize(const char **data, size_t *len) const {
+  *data = reinterpret_cast<const char*>(bitplane_buffer_);
+  *len = buffer_size_;
+}
+
+bool Framebuffer::Deserialize(const char *data, size_t len) {
+  if (len != buffer_size_) return false;
+  memcpy(bitplane_buffer_, data, len);
+  return true;
+}
+
+void Framebuffer::CopyFrom(const Framebuffer *other) {
+  if (other == this) return;
+  memcpy(bitplane_buffer_, other->bitplane_buffer_, buffer_size_);
+}
+
+void Framebuffer::DumpToMatrix(GPIO *io, int pwm_low_bit) {
+  const struct HardwareMapping &h = *hardware_mapping_;
+  gpio_bits_t color_clk_mask = 0;  // Mask of bits while clocking in.
+  color_clk_mask |= h.p0_r1 | h.p0_g1 | h.p0_b1 | h.p0_r2 | h.p0_g2 | h.p0_b2;
+  if (parallel_ >= 2) {
+    color_clk_mask |= h.p1_r1 | h.p1_g1 | h.p1_b1 | h.p1_r2 | h.p1_g2 | h.p1_b2;
+  }
+  if (parallel_ >= 3) {
+    color_clk_mask |= h.p2_r1 | h.p2_g1 | h.p2_b1 | h.p2_r2 | h.p2_g2 | h.p2_b2;
+  }
+  if (parallel_ >= 4) {
+    color_clk_mask |= h.p3_r1 | h.p3_g1 | h.p3_b1 | h.p3_r2 | h.p3_g2 | h.p3_b2;
+  }
+  if (parallel_ >= 5) {
+    color_clk_mask |= h.p4_r1 | h.p4_g1 | h.p4_b1 | h.p4_r2 | h.p4_g2 | h.p4_b2;
+  }
+  if (parallel_ >= 6) {
+    color_clk_mask |= h.p5_r1 | h.p5_g1 | h.p5_b1 | h.p5_r2 | h.p5_g2 | h.p5_b2;
+  }
+
+  color_clk_mask |= h.clock;
+
+  // Depending if we do dithering, we might not always show the lowest bits.
+  const int start_bit = std::max(pwm_low_bit, kBitPlanes - pwm_bits_);
+
+  const uint8_t half_double = double_rows_/2;
+  for (uint8_t row_loop = 0; row_loop < double_rows_; ++row_loop) {
+    uint8_t d_row;
+    switch (scan_mode_) {
+    case 0:  // progressive
+    default:
+      d_row = row_loop;
+      break;
+
+    case 1:  // interlaced
+      d_row = ((row_loop < half_double)
+               ? (row_loop << 1)
+               : ((row_loop - half_double) << 1) + 1);
+    }
+
+    // Rows can't be switched very quickly without ghosting, so we do the
+    // full PWM of one row before switching rows.
+    for (int b = start_bit; b < kBitPlanes; ++b) {
+      gpio_bits_t *row_data = ValueAt(d_row, 0, b);
+      // While the output enable is still on, we can already clock in the next
+      // data.
+      for (int col = 0; col < columns_; ++col) {
+        const gpio_bits_t &out = *row_data++;
+        io->WriteMaskedBits(out, color_clk_mask);  // col + reset clock
+        io->SetBits(h.clock);               // Rising edge: clock color in.
+      }
+      io->ClearBits(color_clk_mask);    // clock back to normal.
+
+      // OE of the previous row-data must be finished before strobe.
+      sOutputEnablePulser->WaitPulseFinished();
+
+      // Setting address and strobing needs to happen in dark time.
+      row_setter_->SetRowAddress(io, d_row);
+
+      io->SetBits(h.strobe);   // Strobe in the previously clocked in row.
+      io->ClearBits(h.strobe);
+
+      // Now switch on for the sleep time necessary for that bit-plane.
+      sOutputEnablePulser->SendPulse(b);
+    }
+  }
+}
+}  // namespace internal
+}  // namespace rgb_matrix

+ 28 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/gpio-bits.h

@@ -0,0 +1,28 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+// This file needs to compile in C and C++ context, so deliberately broken out.
+
+#ifndef RPI_GPIOBITS_H
+#define RPI_GPIOBITS_H
+
+#include <stdint.h>
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+typedef uint64_t gpio_bits_t;
+#else
+typedef uint32_t gpio_bits_t;
+#endif
+
+#endif

+ 788 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/gpio.cc

@@ -0,0 +1,788 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+
+#include "gpio.h"
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+
+/*
+ * nanosleep() takes longer than requested because of OS jitter.
+ * In about 99.9% of the cases, this is <= 25 microcseconds on
+ * the Raspberry Pi (empirically determined with a Raspbian kernel), so
+ * we substract this value whenever we do nanosleep(); the remaining time
+ * we then busy wait to get a good accurate result.
+ *
+ * You can measure the overhead using DEBUG_SLEEP_JITTER below.
+ *
+ * Note: A higher value here will result in more CPU use because of more busy
+ * waiting inching towards the real value (for all the cases that nanosleep()
+ * actually was better than this overhead).
+ *
+ * This might be interesting to tweak in particular if you have a realtime
+ * kernel with different characteristics.
+ */
+#define EMPIRICAL_NANOSLEEP_OVERHEAD_US 12
+
+/*
+ * In case of non-hardware pulse generation, use nanosleep if we want to wait
+ * longer than these given microseconds beyond the general overhead.
+ * Below that, just use busy wait.
+ */
+#define MINIMUM_NANOSLEEP_TIME_US 5
+
+/* In order to determine useful values for above, set this to 1 and use the
+ * hardware pin-pulser.
+ * It will output a histogram atexit() of how much how often we were over
+ * the requested time.
+ * (The full histogram will be shifted by the EMPIRICAL_NANOSLEEP_OVERHEAD_US
+ *  value above. To get a full histogram of OS overhead, set it to 0 first).
+ */
+#define DEBUG_SLEEP_JITTER 0
+
+// Raspberry 1 and 2 have different base addresses for the periphery
+#define BCM2708_PERI_BASE        0x20000000
+#define BCM2709_PERI_BASE        0x3F000000
+#define BCM2711_PERI_BASE        0xFE000000
+
+#define GPIO_REGISTER_OFFSET         0x200000
+#define COUNTER_1Mhz_REGISTER_OFFSET   0x3000
+
+#define GPIO_PWM_BASE_OFFSET	(GPIO_REGISTER_OFFSET + 0xC000)
+#define GPIO_CLK_BASE_OFFSET	0x101000
+
+#define REGISTER_BLOCK_SIZE (4*1024)
+
+#define PWM_CTL      (0x00 / 4)
+#define PWM_STA      (0x04 / 4)
+#define PWM_RNG1     (0x10 / 4)
+#define PWM_FIFO     (0x18 / 4)
+
+#define PWM_CTL_CLRF1 (1<<6)	// CH1 Clear Fifo (1 Clears FIFO 0 has no effect)
+#define PWM_CTL_USEF1 (1<<5)	// CH1 Use Fifo (0=data reg transmit 1=Fifo used for transmission)
+#define PWM_CTL_POLA1 (1<<4)	// CH1 Polarity (0=(0=low 1=high) 1=(1=low 0=high)
+#define PWM_CTL_SBIT1 (1<<3)	// CH1 Silence Bit (state of output when 0 transmission takes place)
+#define PWM_CTL_MODE1 (1<<1)	// CH1 Mode (0=pwm 1=serialiser mode)
+#define PWM_CTL_PWEN1 (1<<0)	// CH1 Enable (0=disable 1=enable)
+
+#define PWM_STA_EMPT1 (1<<1)
+#define PWM_STA_FULL1 (1<<0)
+
+#define CLK_PASSWD  (0x5A<<24)
+
+#define CLK_CTL_MASH(x)((x)<<9)
+#define CLK_CTL_BUSY    (1 <<7)
+#define CLK_CTL_KILL    (1 <<5)
+#define CLK_CTL_ENAB    (1 <<4)
+#define CLK_CTL_SRC(x) ((x)<<0)
+
+#define CLK_CTL_SRC_PLLD 6  /* 500.0 MHz */
+
+#define CLK_DIV_DIVI(x) ((x)<<12)
+#define CLK_DIV_DIVF(x) ((x)<< 0)
+
+#define CLK_PWMCTL 40
+#define CLK_PWMDIV 41
+
+// We want to have the last word in the fifo free
+#define MAX_PWM_BIT_USE 224
+#define PWM_BASE_TIME_NS 2
+
+// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x).
+#define INP_GPIO(g) *(s_GPIO_registers+((g)/10)) &= ~(7ull<<(((g)%10)*3))
+#define OUT_GPIO(g) *(s_GPIO_registers+((g)/10)) |=  (1ull<<(((g)%10)*3))
+
+#define GPIO_SET *(gpio+7)  // sets   bits which are 1 ignores bits which are 0
+#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0
+
+// We're pre-mapping all the registers on first call of GPIO::Init(),
+// so that it is possible to drop privileges afterwards and still have these
+// usable.
+static volatile uint32_t *s_GPIO_registers = NULL;
+static volatile uint32_t *s_Timer1Mhz = NULL;
+static volatile uint32_t *s_PWM_registers = NULL;
+static volatile uint32_t *s_CLK_registers = NULL;
+
+namespace rgb_matrix {
+#define GPIO_BIT(x) (1ull << x)
+
+GPIO::GPIO() : output_bits_(0), input_bits_(0), reserved_bits_(0),
+               slowdown_(1)
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+             , uses_64_bit_(false)
+#endif
+{
+}
+
+gpio_bits_t GPIO::InitOutputs(gpio_bits_t outputs,
+                              bool adafruit_pwm_transition_hack_needed) {
+  if (s_GPIO_registers == NULL) {
+    fprintf(stderr, "Attempt to init outputs but not yet Init()-ialized.\n");
+    return 0;
+  }
+
+  // Hack: for the PWM mod, the user soldered together GPIO 18 (new OE)
+  // with GPIO 4 (old OE).
+  // Since they are connected inside the HAT, want to make extra sure that,
+  // whatever the outside system set as pinmux, the old OE is _not_ also
+  // set as output so that these GPIO outputs don't fight each other.
+  //
+  // So explicitly set both of these pins as input initially, so the user
+  // can switch between the two modes "adafruit-hat" and "adafruit-hat-pwm"
+  // without trouble.
+  if (adafruit_pwm_transition_hack_needed) {
+    INP_GPIO(4);
+    INP_GPIO(18);
+    // Even with PWM enabled, GPIO4 still can not be used, because it is
+    // now connected to the GPIO18 and thus must stay an input.
+    // So reserve this bit if it is not set in outputs.
+    reserved_bits_ = GPIO_BIT(4) & ~outputs;
+  }
+
+  outputs &= ~(output_bits_ | input_bits_ | reserved_bits_);
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+  const int kMaxAvailableBit = 45;
+  uses_64_bit_ |= (outputs >> 32) != 0;
+#else
+  const int kMaxAvailableBit = 31;
+#endif
+  for (int b = 0; b <= kMaxAvailableBit; ++b) {
+    if (outputs & GPIO_BIT(b)) {
+      INP_GPIO(b);   // for writing, we first need to set as input.
+      OUT_GPIO(b);
+    }
+  }
+  output_bits_ |= outputs;
+  return outputs;
+}
+
+gpio_bits_t GPIO::RequestInputs(gpio_bits_t inputs) {
+  if (s_GPIO_registers == NULL) {
+    fprintf(stderr, "Attempt to init inputs but not yet Init()-ialized.\n");
+    return 0;
+  }
+
+  inputs &= ~(output_bits_ | input_bits_ | reserved_bits_);
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+  const int kMaxAvailableBit = 45;
+  uses_64_bit_ |= (inputs >> 32) != 0;
+#else
+  const int kMaxAvailableBit = 31;
+#endif
+  for (int b = 0; b <= kMaxAvailableBit; ++b) {
+    if (inputs & GPIO_BIT(b)) {
+      INP_GPIO(b);
+    }
+  }
+  input_bits_ |= inputs;
+  return inputs;
+}
+
+// We are not interested in the _exact_ model, just good enough to determine
+// What to do.
+enum RaspberryPiModel {
+  PI_MODEL_1,
+  PI_MODEL_2,
+  PI_MODEL_3,
+  PI_MODEL_4
+};
+
+static int ReadFileToBuffer(char *buffer, size_t size, const char *filename) {
+  const int fd = open(filename, O_RDONLY);
+  if (fd < 0) return -1;
+  ssize_t r = read(fd, buffer, size - 1); // assume one read enough
+  buffer[r >= 0 ? r : 0] = '\0';
+  close(fd);
+  return r;
+}
+
+static RaspberryPiModel DetermineRaspberryModel() {
+  char buffer[4096];
+  if (ReadFileToBuffer(buffer, sizeof(buffer), "/proc/cpuinfo") < 0) {
+    fprintf(stderr, "Reading cpuinfo: Could not determine Pi model\n");
+    return PI_MODEL_3;  // safe guess fallback.
+  }
+  static const char RevisionTag[] = "Revision";
+  const char *revision_key;
+  if ((revision_key = strstr(buffer, RevisionTag)) == NULL) {
+    fprintf(stderr, "non-existent Revision: Could not determine Pi model\n");
+    return PI_MODEL_3;
+  }
+  unsigned int pi_revision;
+  if (sscanf(index(revision_key, ':') + 1, "%x", &pi_revision) != 1) {
+    fprintf(stderr, "Unknown Revision: Could not determine Pi model\n");
+    return PI_MODEL_3;
+  }
+
+  // https://www.raspberrypi.org/documentation/hardware/raspberrypi/revision-codes/README.md
+  const unsigned pi_type = (pi_revision >> 4) & 0xff;
+  switch (pi_type) {
+  case 0x00: /* A */
+  case 0x01: /* B, Compute Module 1 */
+  case 0x02: /* A+ */
+  case 0x03: /* B+ */
+  case 0x05: /* Alpha ?*/
+  case 0x06: /* Compute Module1 */
+  case 0x09: /* Zero */
+  case 0x0c: /* Zero W */
+    return PI_MODEL_1;
+
+  case 0x04:  /* Pi 2 */
+    return PI_MODEL_2;
+
+  case 0x11: /* Pi 4 */
+  case 0x14: /* CM4 */
+    return PI_MODEL_4;
+
+  default:  /* a bunch of versions representing Pi 3 */
+    return PI_MODEL_3;
+  }
+}
+
+static RaspberryPiModel GetPiModel() {
+  static RaspberryPiModel pi_model = DetermineRaspberryModel();
+  return pi_model;
+}
+
+static int GetNumCores() {
+  return GetPiModel() == PI_MODEL_1 ? 1 : 4;
+}
+
+static uint32_t *mmap_bcm_register(off_t register_offset) {
+  off_t base = BCM2709_PERI_BASE;  // safe fallback guess.
+  switch (GetPiModel()) {
+  case PI_MODEL_1: base = BCM2708_PERI_BASE; break;
+  case PI_MODEL_2: base = BCM2709_PERI_BASE; break;
+  case PI_MODEL_3: base = BCM2709_PERI_BASE; break;
+  case PI_MODEL_4: base = BCM2711_PERI_BASE; break;
+  }
+
+  int mem_fd;
+  if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
+    // Try to fall back to /dev/gpiomem. Unfortunately, that device
+    // is implemented in a way that it _only_ supports GPIO, not the
+    // other registers we need, such as PWM or COUNTER_1Mhz, which means
+    // we only can operate with degraded performance.
+    //
+    // But, instead of failing, mmap() then silently succeeds with the
+    // unsupported offset. So bail out here.
+    if (register_offset != GPIO_REGISTER_OFFSET)
+      return NULL;
+
+    mem_fd = open("/dev/gpiomem", O_RDWR|O_SYNC);
+    if (mem_fd < 0) return NULL;
+  }
+
+  uint32_t *result =
+    (uint32_t*) mmap(NULL,                  // Any adddress in our space will do
+                     REGISTER_BLOCK_SIZE,   // Map length
+                     PROT_READ|PROT_WRITE,  // Enable r/w on GPIO registers.
+                     MAP_SHARED,
+                     mem_fd,                // File to map
+                     base + register_offset // Offset to bcm register
+                     );
+  close(mem_fd);
+
+  if (result == MAP_FAILED) {
+    perror("mmap error: ");
+    fprintf(stderr, "MMapping from base 0x%lx, offset 0x%lx\n",
+            base, register_offset);
+    return NULL;
+  }
+  return result;
+}
+
+static bool mmap_all_bcm_registers_once() {
+  if (s_GPIO_registers != NULL) return true;  // alrady done.
+
+  // The common GPIO registers.
+  s_GPIO_registers = mmap_bcm_register(GPIO_REGISTER_OFFSET);
+  if (s_GPIO_registers == NULL) {
+    return false;
+  }
+
+  // Time measurement. Might fail when run as non-root.
+  uint32_t *timereg = mmap_bcm_register(COUNTER_1Mhz_REGISTER_OFFSET);
+  if (timereg != NULL) {
+    s_Timer1Mhz = timereg + 1;
+  }
+
+  // Hardware pin-pulser. Might fail when run as non-root.
+  s_PWM_registers  = mmap_bcm_register(GPIO_PWM_BASE_OFFSET);
+  s_CLK_registers  = mmap_bcm_register(GPIO_CLK_BASE_OFFSET);
+
+  return true;
+}
+
+bool GPIO::Init(int slowdown) {
+  slowdown_ = slowdown;
+
+  // Pre-mmap all bcm registers we need now and possibly in the future, as to
+  // allow  dropping privileges after GPIO::Init() even as some of these
+  // registers might be needed later.
+  if (!mmap_all_bcm_registers_once())
+    return false;
+
+  gpio_set_bits_low_ = s_GPIO_registers + (0x1C / sizeof(uint32_t));
+  gpio_clr_bits_low_ = s_GPIO_registers + (0x28 / sizeof(uint32_t));
+  gpio_read_bits_low_ = s_GPIO_registers + (0x34 / sizeof(uint32_t));
+
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+  gpio_set_bits_high_ = s_GPIO_registers + (0x20 / sizeof(uint32_t));
+  gpio_clr_bits_high_ = s_GPIO_registers + (0x2C / sizeof(uint32_t));
+  gpio_read_bits_high_ = s_GPIO_registers + (0x38 / sizeof(uint32_t));
+#endif
+
+  return true;
+}
+
+/*
+ * We support also other pinouts that don't have the OE- on the hardware
+ * PWM output pin, so we need to provide (impefect) 'manual' timing as well.
+ * Hence all various busy_wait_nano() implementations depending on the hardware.
+ */
+
+// --- PinPulser. Private implementation parts.
+namespace {
+// Manual timers.
+class Timers {
+public:
+  static bool Init();
+  static void sleep_nanos(long t);
+};
+
+// Simplest of PinPulsers. Uses somewhat jittery and manual timers
+// to get the timing, but not optimal.
+class TimerBasedPinPulser : public PinPulser {
+public:
+  TimerBasedPinPulser(GPIO *io, gpio_bits_t bits,
+                      const std::vector<int> &nano_specs)
+    : io_(io), bits_(bits), nano_specs_(nano_specs) {
+    if (!s_Timer1Mhz) {
+      fprintf(stderr, "FYI: not running as root which means we can't properly "
+              "control timing unless this is a real-time kernel. Expect color "
+              "degradation. Consider running as root with sudo.\n");
+    }
+  }
+
+  virtual void SendPulse(int time_spec_number) {
+    io_->ClearBits(bits_);
+    Timers::sleep_nanos(nano_specs_[time_spec_number]);
+    io_->SetBits(bits_);
+  }
+
+private:
+  GPIO *const io_;
+  const gpio_bits_t bits_;
+  const std::vector<int> nano_specs_;
+};
+
+static bool LinuxHasModuleLoaded(const char *name) {
+  FILE *f = fopen("/proc/modules", "r");
+  if (f == NULL) return false; // don't care.
+  char buf[256];
+  const size_t namelen = strlen(name);
+  bool found = false;
+  while (fgets(buf, sizeof(buf), f) != NULL) {
+    if (strncmp(buf, name, namelen) == 0) {
+      found = true;
+      break;
+    }
+  }
+  fclose(f);
+  return found;
+}
+
+static void busy_wait_nanos_rpi_1(long nanos);
+static void busy_wait_nanos_rpi_2(long nanos);
+static void busy_wait_nanos_rpi_3(long nanos);
+static void busy_wait_nanos_rpi_4(long nanos);
+static void (*busy_wait_impl)(long) = busy_wait_nanos_rpi_3;
+
+// Best effort write to file. Used to set kernel parameters.
+static void WriteTo(const char *filename, const char *str) {
+  const int fd = open(filename, O_WRONLY);
+  if (fd < 0) return;
+  (void) write(fd, str, strlen(str));  // Best effort. Ignore return value.
+  close(fd);
+}
+
+// By default, the kernel applies some throtteling for realtime
+// threads to prevent starvation of non-RT threads. But we
+// really want all we can get iff the machine has more cores and
+// our RT-thread is locked onto one of these.
+// So let's tell it not to do that.
+static void DisableRealtimeThrottling() {
+  if (GetNumCores() == 1) return;   // Not safe if we don't have > 1 core.
+  // We need to leave the kernel a little bit of time, as it does not like
+  // us to hog the kernel solidly. The default of 950000 leaves 50ms that
+  // can generate visible flicker, so we reduce that to 1ms.
+  WriteTo("/proc/sys/kernel/sched_rt_runtime_us", "999000");
+}
+
+bool Timers::Init() {
+  if (!mmap_all_bcm_registers_once())
+    return false;
+
+  // Choose the busy-wait loop that fits our Pi.
+  switch (GetPiModel()) {
+  case PI_MODEL_1: busy_wait_impl = busy_wait_nanos_rpi_1; break;
+  case PI_MODEL_2: busy_wait_impl = busy_wait_nanos_rpi_2; break;
+  case PI_MODEL_3: busy_wait_impl = busy_wait_nanos_rpi_3; break;
+  case PI_MODEL_4: busy_wait_impl = busy_wait_nanos_rpi_4; break;
+  }
+
+  DisableRealtimeThrottling();
+  // If we have it, we run the update thread on core3. No perf-compromises:
+  WriteTo("/sys/devices/system/cpu/cpu3/cpufreq/scaling_governor",
+          "performance");
+  return true;
+}
+
+static uint32_t JitterAllowanceMicroseconds() {
+  // If this is a Raspberry Pi with more than one core, we add a bit of
+  // additional overhead measured up to the 99.999%-ile: we can allow to burn
+  // a bit more busy-wait CPU cycles to get the timing accurate as we have
+  // more CPU to spare.
+  switch (GetPiModel()) {
+  case PI_MODEL_1:
+    return EMPIRICAL_NANOSLEEP_OVERHEAD_US;  // 99.9%-ile
+  case PI_MODEL_2: case PI_MODEL_3:
+    return EMPIRICAL_NANOSLEEP_OVERHEAD_US + 35;  // 99.999%-ile
+  case PI_MODEL_4:
+    return EMPIRICAL_NANOSLEEP_OVERHEAD_US + 10;  // this one is fast.
+  }
+  return EMPIRICAL_NANOSLEEP_OVERHEAD_US;
+}
+
+void Timers::sleep_nanos(long nanos) {
+  // For smaller durations, we go straight to busy wait.
+
+  // For larger duration, we use nanosleep() to give the operating system
+  // a chance to do something else.
+
+  // However, these timings have a lot of jitter, so if we have the 1Mhz timer
+  // available, we use that to accurately mesure time spent and do the
+  // remaining time with busy wait. If we don't have the timer available
+  // (not running as root), we just use nanosleep() for larger values.
+
+  if (s_Timer1Mhz) {
+    static long kJitterAllowanceNanos = JitterAllowanceMicroseconds() * 1000;
+    if (nanos > kJitterAllowanceNanos + MINIMUM_NANOSLEEP_TIME_US*1000) {
+      const uint32_t before = *s_Timer1Mhz;
+      struct timespec sleep_time = { 0, nanos - kJitterAllowanceNanos };
+      nanosleep(&sleep_time, NULL);
+      const uint32_t after = *s_Timer1Mhz;
+      const long nanoseconds_passed = 1000 * (uint32_t)(after - before);
+      if (nanoseconds_passed > nanos) {
+        return;  // darn, missed it.
+      } else {
+        nanos -= nanoseconds_passed; // remaining time with busy-loop
+      }
+    }
+  } else {
+    // Not running as root, not having access to 1Mhz timer. Approximate large
+    // durations with nanosleep(); small durations are done with busy wait.
+    if (nanos > (EMPIRICAL_NANOSLEEP_OVERHEAD_US + MINIMUM_NANOSLEEP_TIME_US)*1000) {
+      struct timespec sleep_time
+        = { 0, nanos - EMPIRICAL_NANOSLEEP_OVERHEAD_US*1000 };
+      nanosleep(&sleep_time, NULL);
+      return;
+    }
+  }
+
+  busy_wait_impl(nanos);  // Use model-specific busy-loop for remaining time.
+}
+
+static void busy_wait_nanos_rpi_1(long nanos) {
+  if (nanos < 70) return;
+  // The following loop is determined empirically on a 700Mhz RPi
+  for (uint32_t i = (nanos - 70) >> 2; i != 0; --i) {
+    asm("nop");
+  }
+}
+
+static void busy_wait_nanos_rpi_2(long nanos) {
+  if (nanos < 20) return;
+  // The following loop is determined empirically on a 900Mhz RPi 2
+  for (uint32_t i = (nanos - 20) * 100 / 110; i != 0; --i) {
+    asm("");
+  }
+}
+
+static void busy_wait_nanos_rpi_3(long nanos) {
+  if (nanos < 20) return;
+  for (uint32_t i = (nanos - 15) * 100 / 73; i != 0; --i) {
+    asm("");
+  }
+}
+
+static void busy_wait_nanos_rpi_4(long nanos) {
+  if (nanos < 20) return;
+  // Interesting, the Pi4 is _slower_ than the Pi3 ? At least for this busy loop
+  for (uint32_t i = (nanos - 5) * 100 / 132; i != 0; --i) {
+    asm("");
+  }
+}
+
+#if DEBUG_SLEEP_JITTER
+static int overshoot_histogram_us[256] = {0};
+static void print_overshoot_histogram() {
+  fprintf(stderr, "Overshoot histogram >= empirical overhead of %dus\n"
+          "%6s | %7s | %7s\n",
+          JitterAllowanceMicroseconds(), "usec", "count", "accum");
+  int total_count = 0;
+  for (int i = 0; i < 256; ++i) total_count += overshoot_histogram_us[i];
+  int running_count = 0;
+  for (int us = 0; us < 256; ++us) {
+    const int count = overshoot_histogram_us[us];
+    if (count > 0) {
+      running_count += count;
+      fprintf(stderr, "%s%3dus: %8d %7.3f%%\n", (us == 0) ? "<=" : " +",
+              us, count, 100.0 * running_count / total_count);
+    }
+  }
+}
+#endif
+
+// A PinPulser that uses the PWM hardware to create accurate pulses.
+// It only works on GPIO-12 or 18 though.
+class HardwarePinPulser : public PinPulser {
+public:
+  static bool CanHandle(gpio_bits_t gpio_mask) {
+#ifdef DISABLE_HARDWARE_PULSES
+    return false;
+#else
+    const bool can_handle = gpio_mask==GPIO_BIT(18) || gpio_mask==GPIO_BIT(12);
+    if (can_handle && (s_PWM_registers == NULL || s_CLK_registers == NULL)) {
+      // Instead of silently not using the hardware pin pulser and falling back
+      // to timing based loops, complain loudly and request the user to make
+      // a choice before continuing.
+      fprintf(stderr, "Need root. You are configured to use the hardware pulse "
+              "generator "
+              "for\n\tsmooth color rendering, however the necessary hardware\n"
+              "\tregisters can't be accessed because you probably don't run\n"
+              "\twith root permissions or privileges have been dropped.\n"
+              "\tSo you either have to run as root (e.g. using sudo) or\n"
+              "\tsupply the --led-no-hardware-pulse command-line flag.\n\n"
+              "\tExiting; run as root or with --led-no-hardware-pulse\n\n");
+      exit(1);
+    }
+    return can_handle;
+#endif
+  }
+
+  HardwarePinPulser(gpio_bits_t pins, const std::vector<int> &specs)
+    : triggered_(false) {
+    assert(CanHandle(pins));
+    assert(s_CLK_registers && s_PWM_registers && s_Timer1Mhz);
+
+#if DEBUG_SLEEP_JITTER
+    atexit(print_overshoot_histogram);
+#endif
+
+    if (LinuxHasModuleLoaded("snd_bcm2835")) {
+      fprintf(stderr,
+              "\n%s=== snd_bcm2835: found that the Pi sound module is loaded. ===%s\n"
+              "Don't use the built-in sound of the Pi together with this lib; it is known to be\n"
+	      "incompatible and cause trouble and hangs (you can still use external USB sound adapters).\n\n"
+              "See Troubleshooting section in README how to disable the sound module.\n"
+	      "You can also run with --led-no-hardware-pulse to avoid the incompatibility,\n"
+	      "but you will have more flicker.\n"
+              "Exiting; fix the above first or use --led-no-hardware-pulse\n\n",
+              "\033[1;31m", "\033[0m");
+      exit(1);
+    }
+
+    for (size_t i = 0; i < specs.size(); ++i) {
+      // Hints how long to nanosleep, already corrected for system overhead.
+      sleep_hints_us_.push_back(specs[i]/1000 - JitterAllowanceMicroseconds());
+    }
+
+    const int base = specs[0];
+    // Get relevant registers
+    fifo_ = s_PWM_registers + PWM_FIFO;
+
+    if (pins == GPIO_BIT(18)) {
+      // set GPIO 18 to PWM0 mode (Alternative 5)
+      SetGPIOMode(s_GPIO_registers, 18, 2);
+    } else if (pins == GPIO_BIT(12)) {
+      // set GPIO 12 to PWM0 mode (Alternative 0)
+      SetGPIOMode(s_GPIO_registers, 12, 4);
+    } else {
+      assert(false); // should've been caught by CanHandle()
+    }
+    InitPWMDivider((base/2) / PWM_BASE_TIME_NS);
+    for (size_t i = 0; i < specs.size(); ++i) {
+      pwm_range_.push_back(2 * specs[i] / base);
+    }
+  }
+
+  virtual void SendPulse(int c) {
+    if (pwm_range_[c] < 16) {
+      s_PWM_registers[PWM_RNG1] = pwm_range_[c];
+
+      *fifo_ = pwm_range_[c];
+    } else {
+      // Keep the actual range as short as possible, as we have to
+      // wait for one full period of these in the zero phase.
+      // The hardware can't deal with values < 2, so only do this when
+      // have enough of these.
+      s_PWM_registers[PWM_RNG1] = pwm_range_[c] / 8;
+
+      *fifo_ = pwm_range_[c] / 8;
+      *fifo_ = pwm_range_[c] / 8;
+      *fifo_ = pwm_range_[c] / 8;
+      *fifo_ = pwm_range_[c] / 8;
+      *fifo_ = pwm_range_[c] / 8;
+      *fifo_ = pwm_range_[c] / 8;
+      *fifo_ = pwm_range_[c] / 8;
+      *fifo_ = pwm_range_[c] / 8;
+    }
+
+    /*
+     * We need one value at the end to have it go back to
+     * default state (otherwise it just repeats the last
+     * value, so will be constantly 'on').
+     */
+    *fifo_ = 0;   // sentinel.
+
+    /*
+     * For some reason, we need a second empty sentinel in the
+     * fifo, otherwise our way to detect the end of the pulse,
+     * which relies on 'is the queue empty' does not work. It is
+     * not entirely clear why that is from the datasheet,
+     * but probably there is some buffering register in which data
+     * elements are kept after the fifo is emptied.
+     */
+    *fifo_ = 0;
+
+    sleep_hint_us_ = sleep_hints_us_[c];
+    start_time_ = *s_Timer1Mhz;
+    triggered_ = true;
+    s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_PWEN1 | PWM_CTL_POLA1;
+  }
+
+  virtual void WaitPulseFinished() {
+    if (!triggered_) return;
+    // Determine how long we already spent and sleep to get close to the
+    // actual end-time of our sleep period.
+    //
+    // TODO(hzeller): find if it is possible to get some sort of interrupt from
+    //   the hardware once it is done with the pulse. Sounds silly that there is
+    //   not (so far, only tested GPIO interrupt with a feedback line, but that
+    //   is super-slow with 20μs overhead).
+    if (sleep_hint_us_ > 0) {
+      const uint32_t already_elapsed_usec = *s_Timer1Mhz - start_time_;
+      const int to_sleep_us = sleep_hint_us_ - already_elapsed_usec;
+      if (to_sleep_us > 0) {
+        struct timespec sleep_time = { 0, 1000 * to_sleep_us };
+        nanosleep(&sleep_time, NULL);
+
+#if DEBUG_SLEEP_JITTER
+        {
+          // Record histogram of realtime jitter how much longer we actually
+          // took.
+          const int total_us = *s_Timer1Mhz - start_time_;
+          const int nanoslept_us = total_us - already_elapsed_usec;
+          int overshoot = nanoslept_us - (to_sleep_us + JitterAllowanceMicroseconds());
+          if (overshoot < 0) overshoot = 0;
+          if (overshoot > 255) overshoot = 255;
+          overshoot_histogram_us[overshoot]++;
+        }
+#endif
+      }
+    }
+
+    while ((s_PWM_registers[PWM_STA] & PWM_STA_EMPT1) == 0) {
+      // busy wait until done.
+    }
+    s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_POLA1 | PWM_CTL_CLRF1;
+    triggered_ = false;
+  }
+
+private:
+  void SetGPIOMode(volatile uint32_t *gpioReg, unsigned gpio, unsigned mode) {
+    const int reg = gpio / 10;
+    const int mode_pos = (gpio % 10) * 3;
+    gpioReg[reg] = (gpioReg[reg] & ~(7 << mode_pos)) | (mode << mode_pos);
+  }
+
+  void InitPWMDivider(uint32_t divider) {
+    assert(divider < (1<<12));  // we only have 12 bits.
+
+    s_PWM_registers[PWM_CTL] = PWM_CTL_USEF1 | PWM_CTL_POLA1 | PWM_CTL_CLRF1;
+
+    // reset PWM clock
+    s_CLK_registers[CLK_PWMCTL] = CLK_PASSWD | CLK_CTL_KILL;
+
+    // set PWM clock source as 500 MHz PLLD
+    s_CLK_registers[CLK_PWMCTL] = CLK_PASSWD | CLK_CTL_SRC(CLK_CTL_SRC_PLLD);
+
+    // set PWM clock divider
+    s_CLK_registers[CLK_PWMDIV]
+      = CLK_PASSWD | CLK_DIV_DIVI(divider) | CLK_DIV_DIVF(0);
+
+    // enable PWM clock
+    s_CLK_registers[CLK_PWMCTL]
+      = CLK_PASSWD | CLK_CTL_ENAB | CLK_CTL_SRC(CLK_CTL_SRC_PLLD);
+  }
+
+private:
+  std::vector<uint32_t> pwm_range_;
+  std::vector<int> sleep_hints_us_;
+  volatile uint32_t *fifo_;
+  uint32_t start_time_;
+  int sleep_hint_us_;
+  bool triggered_;
+};
+
+} // end anonymous namespace
+
+// Public PinPulser factory
+PinPulser *PinPulser::Create(GPIO *io, gpio_bits_t gpio_mask,
+                             bool allow_hardware_pulsing,
+                             const std::vector<int> &nano_wait_spec) {
+  if (!Timers::Init()) return NULL;
+  if (allow_hardware_pulsing && HardwarePinPulser::CanHandle(gpio_mask)) {
+    return new HardwarePinPulser(gpio_mask, nano_wait_spec);
+  } else {
+    return new TimerBasedPinPulser(io, gpio_mask, nano_wait_spec);
+  }
+}
+
+// For external use, e.g. in the matrix for extra time.
+uint32_t GetMicrosecondCounter() {
+  if (s_Timer1Mhz) return *s_Timer1Mhz;
+
+  // When run as non-root, we can't read the timer. Fall back to slow
+  // operating-system ways.
+  struct timespec ts;
+  clock_gettime(CLOCK_MONOTONIC, &ts);
+  const uint64_t micros = ts.tv_nsec / 1000;
+  const uint64_t epoch_usec = (uint64_t)ts.tv_sec * 1000000 + micros;
+  return epoch_usec & 0xFFFFFFFF;
+}
+
+} // namespace rgb_matrix

+ 153 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/gpio.h

@@ -0,0 +1,153 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#ifndef RPI_GPIO_INTERNAL_H
+#define RPI_GPIO_INTERNAL_H
+
+#include "gpio-bits.h"
+
+#include <vector>
+
+// Putting this in our namespace to not collide with other things called like
+// this.
+namespace rgb_matrix {
+// For now, everything is initialized as output.
+class GPIO {
+public:
+  GPIO();
+
+  // Initialize before use. Returns 'true' if successful, 'false' otherwise
+  // (e.g. due to a permission problem).
+  bool Init(int
+#if RGB_SLOWDOWN_GPIO
+            slowdown = RGB_SLOWDOWN_GPIO
+#else
+            slowdown = 1
+#endif
+      );
+
+
+  // Initialize outputs.
+  // Returns the bits that were available and could be set for output.
+  // (never use the optional adafruit_hack_needed parameter, it is used
+  // internally to this library).
+  gpio_bits_t InitOutputs(gpio_bits_t outputs,
+                          bool adafruit_hack_needed = false);
+
+  // Request given bitmap of GPIO inputs.
+  // Returns the bits that were available and could be reserved.
+  gpio_bits_t RequestInputs(gpio_bits_t inputs);
+
+  // Set the bits that are '1' in the output. Leave the rest untouched.
+  inline void SetBits(gpio_bits_t value) {
+    if (!value) return;
+    WriteSetBits(value);
+    for (int i = 0; i < slowdown_; ++i) {
+      WriteSetBits(value);
+    }
+  }
+
+  // Clear the bits that are '1' in the output. Leave the rest untouched.
+  inline void ClearBits(gpio_bits_t value) {
+    if (!value) return;
+    WriteClrBits(value);
+    for (int i = 0; i < slowdown_; ++i) {
+      WriteClrBits(value);
+    }
+  }
+
+  // Write all the bits of "value" mentioned in "mask". Leave the rest untouched.
+  inline void WriteMaskedBits(gpio_bits_t value, gpio_bits_t mask) {
+    // Writing a word is two operations. The IO is actually pretty slow, so
+    // this should probably  be unnoticable.
+    ClearBits(~value & mask);
+    SetBits(value & mask);
+  }
+
+  inline gpio_bits_t Read() const { return ReadRegisters() & input_bits_; }
+
+private:
+  inline gpio_bits_t ReadRegisters() const {
+    return (static_cast<gpio_bits_t>(*gpio_read_bits_low_)
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+            | (static_cast<gpio_bits_t>(*gpio_read_bits_low_) << 32)
+#endif
+            );
+  }
+
+  inline void WriteSetBits(gpio_bits_t value) {
+    *gpio_set_bits_low_ = static_cast<uint32_t>(value & 0xFFFFFFFF);
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+    if (uses_64_bit_)
+      *gpio_set_bits_high_ = static_cast<uint32_t>(value >> 32);
+#endif
+  }
+
+  inline void WriteClrBits(gpio_bits_t value) {
+    *gpio_clr_bits_low_ = static_cast<uint32_t>(value & 0xFFFFFFFF);
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+    if (uses_64_bit_)
+      *gpio_clr_bits_high_ = static_cast<uint32_t>(value >> 32);
+#endif
+  }
+
+private:
+  gpio_bits_t output_bits_;
+  gpio_bits_t input_bits_;
+  gpio_bits_t reserved_bits_;
+  int slowdown_;
+
+  volatile uint32_t *gpio_set_bits_low_;
+  volatile uint32_t *gpio_clr_bits_low_;
+  volatile uint32_t *gpio_read_bits_low_;
+
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+  bool uses_64_bit_;
+  volatile uint32_t *gpio_set_bits_high_;
+  volatile uint32_t *gpio_clr_bits_high_;
+  volatile uint32_t *gpio_read_bits_high_;
+#endif
+};
+
+// A PinPulser is a utility class that pulses a GPIO pin. There can be various
+// implementations.
+class PinPulser {
+public:
+  // Factory for a PinPulser. Chooses the right implementation depending
+  // on the context (CPU and which pins are affected).
+  // "gpio_mask" is the mask that should be output (since we only
+  //   need negative pulses, this is what it does)
+  // "nano_wait_spec" contains a list of time periods we'd like
+  //   invoke later. This can be used to pre-process timings if needed.
+  static PinPulser *Create(GPIO *io, gpio_bits_t gpio_mask,
+                           bool allow_hardware_pulsing,
+                           const std::vector<int> &nano_wait_spec);
+
+  virtual ~PinPulser() {}
+
+  // Send a pulse with a given length (index into nano_wait_spec array).
+  virtual void SendPulse(int time_spec_number) = 0;
+
+  // If SendPulse() is asynchronously implemented, wait for pulse to finish.
+  virtual void WaitPulseFinished() {}
+};
+
+// Get rolling over microsecond counter. We get this from a hardware register
+// if possible and a terrible slow fallback otherwise.
+uint32_t GetMicrosecondCounter();
+
+}  // end namespace rgb_matrix
+
+#endif  // RPI_GPIO_INGERNALH

+ 172 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/graphics.cc

@@ -0,0 +1,172 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include "graphics.h"
+#include "utf8-internal.h"
+
+#include <stdlib.h>
+#include <functional>
+#include <algorithm>
+
+namespace rgb_matrix {
+bool SetImage(Canvas *c, int canvas_offset_x, int canvas_offset_y,
+              const uint8_t *buffer, size_t size,
+              const int width, const int height,
+              bool is_bgr) {
+  if (3 * width * height != (int)size)   // Sanity check
+    return false;
+
+  int image_display_w = width;
+  int image_display_h = height;
+
+  size_t skip_start_row = 0;   // Bytes to skip before each row
+  if (canvas_offset_x < 0) {
+    skip_start_row = -canvas_offset_x * 3;
+    image_display_w += canvas_offset_x;
+    if (image_display_w <= 0) return false;  // Done. outside canvas.
+    canvas_offset_x = 0;
+  }
+  if (canvas_offset_y < 0) {
+    // Skip buffer to the first row we'll be showing
+    buffer += 3 * width * -canvas_offset_y;
+    image_display_h += canvas_offset_y;
+    if (image_display_h <= 0) return false;  // Done. outside canvas.
+    canvas_offset_y = 0;
+  }
+  const int w = std::min(c->width(), canvas_offset_x + image_display_w);
+  const int h = std::min(c->height(), canvas_offset_y + image_display_h);
+
+  // Bytes to skip for wider than canvas image at the end of a row
+  const size_t skip_end_row = (canvas_offset_x + image_display_w > w)
+    ? (canvas_offset_x + image_display_w - w) * 3
+    : 0;
+
+  // Let's make this a combined skip per row and ajust where we start.
+  const size_t next_row_skip = skip_start_row + skip_end_row;
+  buffer += skip_start_row;
+
+  if (is_bgr) {
+    for (int y = canvas_offset_y; y < h; ++y) {
+      for (int x = canvas_offset_x; x < w; ++x) {
+        c->SetPixel(x, y, buffer[2], buffer[1], buffer[0]);
+        buffer += 3;
+      }
+      buffer += next_row_skip;
+    }
+  } else {
+    for (int y = canvas_offset_y; y < h; ++y) {
+      for (int x = canvas_offset_x; x < w; ++x) {
+        c->SetPixel(x, y, buffer[0], buffer[1], buffer[2]);
+        buffer += 3;
+      }
+      buffer += next_row_skip;
+    }
+  }
+  return true;
+}
+
+int DrawText(Canvas *c, const Font &font,
+             int x, int y, const Color &color,
+             const char *utf8_text) {
+  return DrawText(c, font, x, y, color, NULL, utf8_text);
+}
+
+int DrawText(Canvas *c, const Font &font,
+             int x, int y, const Color &color, const Color *background_color,
+             const char *utf8_text, int extra_spacing) {
+  const int start_x = x;
+  while (*utf8_text) {
+    const uint32_t cp = utf8_next_codepoint(utf8_text);
+    x += font.DrawGlyph(c, x, y, color, background_color, cp);
+    x += extra_spacing;
+  }
+  return x - start_x;
+}
+
+// There used to be a symbol without the optional extra_spacing parameter. Let's
+// define this here so that people linking against an old library will still
+// have their code usable. Now: 2017-06-04; can probably be removed in a couple
+// of months.
+int DrawText(Canvas *c, const Font &font,
+             int x, int y, const Color &color, const Color *background_color,
+             const char *utf8_text) {
+  return DrawText(c, font, x, y, color, background_color, utf8_text, 0);
+}
+
+int VerticalDrawText(Canvas *c, const Font &font, int x, int y,
+                     const Color &color, const Color *background_color,
+                     const char *utf8_text, int extra_spacing) {
+  const int start_y = y;
+  while (*utf8_text) {
+    const uint32_t cp = utf8_next_codepoint(utf8_text);
+    font.DrawGlyph(c, x, y, color, background_color, cp);
+    y += font.height() + extra_spacing;
+  }
+  return y - start_y;
+}
+
+void DrawCircle(Canvas *c, int x0, int y0, int radius, const Color &color) {
+  int x = radius, y = 0;
+  int radiusError = 1 - x;
+
+  while (y <= x) {
+    c->SetPixel(x + x0, y + y0, color.r, color.g, color.b);
+    c->SetPixel(y + x0, x + y0, color.r, color.g, color.b);
+    c->SetPixel(-x + x0, y + y0, color.r, color.g, color.b);
+    c->SetPixel(-y + x0, x + y0, color.r, color.g, color.b);
+    c->SetPixel(-x + x0, -y + y0, color.r, color.g, color.b);
+    c->SetPixel(-y + x0, -x + y0, color.r, color.g, color.b);
+    c->SetPixel(x + x0, -y + y0, color.r, color.g, color.b);
+    c->SetPixel(y + x0, -x + y0, color.r, color.g, color.b);
+    y++;
+    if (radiusError<0){
+      radiusError += 2 * y + 1;
+    } else {
+      x--;
+      radiusError+= 2 * (y - x + 1);
+    }
+  }
+}
+
+void DrawLine(Canvas *c, int x0, int y0, int x1, int y1, const Color &color) {
+  int dy = y1 - y0, dx = x1 - x0, gradient, x, y, shift = 0x10;
+
+  if (abs(dx) > abs(dy)) {
+    // x variation is bigger than y variation
+    if (x1 < x0) {
+      std::swap(x0, x1);
+      std::swap(y0, y1);
+    }
+    gradient = (dy << shift) / dx ;
+
+    for (x = x0 , y = 0x8000 + (y0 << shift); x <= x1; ++x, y += gradient) {
+      c->SetPixel(x, y >> shift, color.r, color.g, color.b);
+    }
+  } else if (dy != 0) {
+    // y variation is bigger than x variation
+    if (y1 < y0) {
+      std::swap(x0, x1);
+      std::swap(y0, y1);
+    }
+    gradient = (dx << shift) / dy;
+    for (y = y0 , x = 0x8000 + (x0 << shift); y <= y1; ++y, x += gradient) {
+      c->SetPixel(x >> shift, y, color.r, color.g, color.b);
+    }
+  } else {
+    c->SetPixel(x0, y0, color.r, color.g, color.b);
+  }
+}
+
+}//namespace

+ 287 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/hardware-mapping.c

@@ -0,0 +1,287 @@
+/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+ * Copyright (C) 2013, 2016 Henner Zeller <h.zeller@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http: *gnu.org/licenses/gpl-2.0.txt>
+ */
+
+/*
+ * We do this in plain C so that we can use designated initializers.
+ */
+#include "hardware-mapping.h"
+
+#define GPIO_BIT(b) ((uint64_t)1<<(b))
+
+struct HardwareMapping matrix_hardware_mappings[] = {
+  /*
+   * The regular hardware mapping described in the wiring.md and used
+   * by the adapter PCBs.
+   */
+  {
+    .name          = "regular",
+
+    .output_enable = GPIO_BIT(18),
+    .clock         = GPIO_BIT(17),
+    .strobe        = GPIO_BIT(4),
+
+    /* Address lines */
+    .a             = GPIO_BIT(22),
+    .b             = GPIO_BIT(23),
+    .c             = GPIO_BIT(24),
+    .d             = GPIO_BIT(25),
+    .e             = GPIO_BIT(15),  /* RxD kept free unless 1:64 */
+
+    /* Parallel chain 0, RGB for both sub-panels */
+    .p0_r1         = GPIO_BIT(11),  /* masks: SPI0_SCKL  */
+    .p0_g1         = GPIO_BIT(27),  /* Not on RPi1, Rev1; use "regular-pi1" instead */
+    .p0_b1         = GPIO_BIT(7),   /* masks: SPI0_CE1   */
+    .p0_r2         = GPIO_BIT(8),   /* masks: SPI0_CE0   */
+    .p0_g2         = GPIO_BIT(9),   /* masks: SPI0_MISO  */
+    .p0_b2         = GPIO_BIT(10),  /* masks: SPI0_MOSI  */
+
+    /* All the following are only available with 40 GPIP pins, on A+/B+/Pi2,3 */
+    /* Chain 1 */
+    .p1_r1         = GPIO_BIT(12),
+    .p1_g1         = GPIO_BIT(5),
+    .p1_b1         = GPIO_BIT(6),
+    .p1_r2         = GPIO_BIT(19),
+    .p1_g2         = GPIO_BIT(13),
+    .p1_b2         = GPIO_BIT(20),
+
+    /* Chain 2 */
+    .p2_r1         = GPIO_BIT(14), /* masks TxD when parallel=3 */
+    .p2_g1         = GPIO_BIT(2),  /* masks SCL when parallel=3 */
+    .p2_b1         = GPIO_BIT(3),  /* masks SDA when parallel=3 */
+    .p2_r2         = GPIO_BIT(26),
+    .p2_g2         = GPIO_BIT(16),
+    .p2_b2         = GPIO_BIT(21),
+  },
+
+  /*
+   * This is used if you have an Adafruit HAT in the default configuration
+   */
+  {
+    .name          = "adafruit-hat",
+
+    .output_enable = GPIO_BIT(4),
+    .clock         = GPIO_BIT(17),
+    .strobe        = GPIO_BIT(21),
+
+    .a             = GPIO_BIT(22),
+    .b             = GPIO_BIT(26),
+    .c             = GPIO_BIT(27),
+    .d             = GPIO_BIT(20),
+    .e             = GPIO_BIT(24),  /* Needs manual wiring, see README.md */
+
+    .p0_r1         = GPIO_BIT(5),
+    .p0_g1         = GPIO_BIT(13),
+    .p0_b1         = GPIO_BIT(6),
+    .p0_r2         = GPIO_BIT(12),
+    .p0_g2         = GPIO_BIT(16),
+    .p0_b2         = GPIO_BIT(23),
+  },
+
+  /*
+   * An Adafruit HAT with the PWM modification
+   */
+  {
+    .name          = "adafruit-hat-pwm",
+
+    .output_enable = GPIO_BIT(18),  /* The only change compared to above */
+    .clock         = GPIO_BIT(17),
+    .strobe        = GPIO_BIT(21),
+
+    .a             = GPIO_BIT(22),
+    .b             = GPIO_BIT(26),
+    .c             = GPIO_BIT(27),
+    .d             = GPIO_BIT(20),
+    .e             = GPIO_BIT(24),
+
+    .p0_r1         = GPIO_BIT(5),
+    .p0_g1         = GPIO_BIT(13),
+    .p0_b1         = GPIO_BIT(6),
+    .p0_r2         = GPIO_BIT(12),
+    .p0_g2         = GPIO_BIT(16),
+    .p0_b2         = GPIO_BIT(23),
+  },
+
+  /*
+   * The regular pin-out, but for Raspberry Pi1. The very first Pi1 Rev1 uses
+   * the same pin for GPIO-21 as later Pis use GPIO-27. Make it work for both.
+   */
+  {
+    .name          = "regular-pi1",
+
+    .output_enable = GPIO_BIT(18),
+    .clock         = GPIO_BIT(17),
+    .strobe        = GPIO_BIT(4),
+
+    /* Address lines */
+    .a             = GPIO_BIT(22),
+    .b             = GPIO_BIT(23),
+    .c             = GPIO_BIT(24),
+    .d             = GPIO_BIT(25),
+    .e             = GPIO_BIT(15),  /* RxD kept free unless 1:64 */
+
+    /* Parallel chain 0, RGB for both sub-panels */
+    .p0_r1         = GPIO_BIT(11),  /* masks: SPI0_SCKL  */
+    /* On Pi1 Rev1, the pin other Pis have GPIO27, these have GPIO21. So make
+     * this work for both Rev1 and Rev2.
+     */
+    .p0_g1         = GPIO_BIT(21) | GPIO_BIT(27),
+    .p0_b1         = GPIO_BIT(7),   /* masks: SPI0_CE1   */
+    .p0_r2         = GPIO_BIT(8),   /* masks: SPI0_CE0   */
+    .p0_g2         = GPIO_BIT(9),   /* masks: SPI0_MISO  */
+    .p0_b2         = GPIO_BIT(10),  /* masks: SPI0_MOSI  */
+
+    /* No more chains - there are not enough GPIO */
+  },
+
+  /*
+   * Classic: Early forms of this library had this as default mapping, mostly
+   * derived from the 26 GPIO-header version so that it also can work
+   * on 40 Pin GPIO headers with more parallel chains.
+   * Not used anymore.
+   */
+  {
+    .name          = "classic",
+
+    .output_enable = GPIO_BIT(27),  /* Not available on RPi1, Rev 1 */
+    .clock         = GPIO_BIT(11),
+    .strobe        = GPIO_BIT(4),
+
+    .a             = GPIO_BIT(7),
+    .b             = GPIO_BIT(8),
+    .c             = GPIO_BIT(9),
+    .d             = GPIO_BIT(10),
+
+    .p0_r1         = GPIO_BIT(17),
+    .p0_g1         = GPIO_BIT(18),
+    .p0_b1         = GPIO_BIT(22),
+    .p0_r2         = GPIO_BIT(23),
+    .p0_g2         = GPIO_BIT(24),
+    .p0_b2         = GPIO_BIT(25),
+
+    .p1_r1         = GPIO_BIT(12),
+    .p1_g1         = GPIO_BIT(5),
+    .p1_b1         = GPIO_BIT(6),
+    .p1_r2         = GPIO_BIT(19),
+    .p1_g2         = GPIO_BIT(13),
+    .p1_b2         = GPIO_BIT(20),
+
+    .p2_r1         = GPIO_BIT(14),   /* masks TxD if parallel = 3 */
+    .p2_g1         = GPIO_BIT(2),    /* masks SDA if parallel = 3 */
+    .p2_b1         = GPIO_BIT(3),    /* masks SCL if parallel = 3 */
+    .p2_r2         = GPIO_BIT(15),
+    .p2_g2         = GPIO_BIT(26),
+    .p2_b2         = GPIO_BIT(21),
+  },
+
+  /*
+   * Classic pin-out for Rev-A Raspberry Pi.
+   */
+  {
+    .name          = "classic-pi1",
+
+    /* The Revision-1 and Revision-2 boards have different GPIO mappings
+     * on the P1-3 and P1-5. So we use both interpretations.
+     * To keep the I2C pins free, we avoid these in later mappings.
+     */
+    .output_enable = GPIO_BIT(0) | GPIO_BIT(2),
+    .clock         = GPIO_BIT(1) | GPIO_BIT(3),
+    .strobe        = GPIO_BIT(4),
+
+    .a             = GPIO_BIT(7),
+    .b             = GPIO_BIT(8),
+    .c             = GPIO_BIT(9),
+    .d             = GPIO_BIT(10),
+
+    .p0_r1         = GPIO_BIT(17),
+    .p0_g1         = GPIO_BIT(18),
+    .p0_b1         = GPIO_BIT(22),
+    .p0_r2         = GPIO_BIT(23),
+    .p0_g2         = GPIO_BIT(24),
+    .p0_b2         = GPIO_BIT(25),
+  },
+
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+  /*
+   * Custom pin-out for compute-module
+   */
+  {
+    .name          = "compute-module",
+
+    /* This GPIO mapping is made for the official I/O development
+     * board. No pin is left free when using 6 parallel chains.
+     */
+    .output_enable = GPIO_BIT(18),
+    .clock         = GPIO_BIT(16),
+    .strobe        = GPIO_BIT(17),
+
+    .a             = GPIO_BIT(2),
+    .b             = GPIO_BIT(3),
+    .c             = GPIO_BIT(4),
+    .d             = GPIO_BIT(5),
+    .e             = GPIO_BIT(6),  /* RxD kept free unless 1:64 */
+
+    /* Chain 0 */
+    .p0_r1         = GPIO_BIT(7),
+    .p0_g1         = GPIO_BIT(8),
+    .p0_b1         = GPIO_BIT(9),
+    .p0_r2         = GPIO_BIT(10),
+    .p0_g2         = GPIO_BIT(11),
+    .p0_b2         = GPIO_BIT(12),
+
+    /* Chain 1 */
+    .p1_r1         = GPIO_BIT(13),
+    .p1_g1         = GPIO_BIT(14),
+    .p1_b1         = GPIO_BIT(15),
+    .p1_r2         = GPIO_BIT(19),
+    .p1_g2         = GPIO_BIT(20),
+    .p1_b2         = GPIO_BIT(21),
+
+    /* Chain 2 */
+    .p2_r1         = GPIO_BIT(22),
+    .p2_g1         = GPIO_BIT(23),
+    .p2_b1         = GPIO_BIT(24),
+    .p2_r2         = GPIO_BIT(25),
+    .p2_g2         = GPIO_BIT(26),
+    .p2_b2         = GPIO_BIT(27),
+
+    /* Chain 3 */
+    .p3_r1         = GPIO_BIT(28),
+    .p3_g1         = GPIO_BIT(29),
+    .p3_b1         = GPIO_BIT(30),
+    .p3_r2         = GPIO_BIT(31),
+    .p3_g2         = GPIO_BIT(32),
+    .p3_b2         = GPIO_BIT(33),
+
+    /* Chain 4 */
+    .p4_r1         = GPIO_BIT(34),
+    .p4_g1         = GPIO_BIT(35),
+    .p4_b1         = GPIO_BIT(36),
+    .p4_r2         = GPIO_BIT(37),
+    .p4_g2         = GPIO_BIT(38),
+    .p4_b2         = GPIO_BIT(39),
+
+    /* Chain 5 */
+    .p5_r1         = GPIO_BIT(40),
+    .p5_g1         = GPIO_BIT(41),
+    .p5_b1         = GPIO_BIT(42),
+    .p5_r2         = GPIO_BIT(43),
+    .p5_g2         = GPIO_BIT(44),
+    .p5_b2         = GPIO_BIT(45),
+  },
+#endif
+
+  {0}
+};

+ 60 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/hardware-mapping.h

@@ -0,0 +1,60 @@
+/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+ * Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http: *gnu.org/licenses/gpl-2.0.txt>
+ */
+#ifndef RPI_HARDWARE_MAPPING_H
+#define RPI_HARDWARE_MAPPING_H
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#include "gpio-bits.h"
+
+struct HardwareMapping {
+  const char *name;
+  int max_parallel_chains;
+
+  gpio_bits_t output_enable;
+  gpio_bits_t clock;
+  gpio_bits_t strobe;
+
+  gpio_bits_t a, b, c, d, e;
+
+  gpio_bits_t p0_r1, p0_g1, p0_b1;
+  gpio_bits_t p0_r2, p0_g2, p0_b2;
+
+  gpio_bits_t p1_r1, p1_g1, p1_b1;
+  gpio_bits_t p1_r2, p1_g2, p1_b2;
+
+  gpio_bits_t p2_r1, p2_g1, p2_b1;
+  gpio_bits_t p2_r2, p2_g2, p2_b2;
+
+  gpio_bits_t p3_r1, p3_g1, p3_b1;
+  gpio_bits_t p3_r2, p3_g2, p3_b2;
+
+  gpio_bits_t p4_r1, p4_g1, p4_b1;
+  gpio_bits_t p4_r2, p4_g2, p4_b2;
+
+  gpio_bits_t p5_r1, p5_g1, p5_b1;
+  gpio_bits_t p5_r2, p5_g2, p5_b2;
+};
+
+extern struct HardwareMapping matrix_hardware_mappings[];
+
+#ifdef  __cplusplus
+}  // extern C
+#endif
+
+#endif

+ 304 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/led-matrix-c.cc

@@ -0,0 +1,304 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+//
+// C-bridge for led matrix.
+#include "led-matrix-c.h"
+
+#include <string.h>
+#include <stdio.h>
+
+#include "led-matrix.h"
+#include "graphics.h"
+
+// Make sure C++ is in sync with C
+static_assert(sizeof(rgb_matrix::RGBMatrix::Options) == sizeof(RGBLedMatrixOptions), "C and C++ out of sync");
+static_assert(sizeof(rgb_matrix::RuntimeOptions) == sizeof(RGBLedRuntimeOptions), "C and C++ out of sync");
+
+// Our opaque dummy structs to communicate with the c-world
+struct RGBLedMatrix {};
+struct LedCanvas {};
+struct LedFont {};
+
+
+static rgb_matrix::RGBMatrix *to_matrix(struct RGBLedMatrix *matrix) {
+  return reinterpret_cast<rgb_matrix::RGBMatrix*>(matrix);
+}
+static struct RGBLedMatrix *from_matrix(rgb_matrix::RGBMatrix *matrix) {
+  return reinterpret_cast<struct RGBLedMatrix*>(matrix);
+}
+
+static rgb_matrix::FrameCanvas *to_canvas(struct LedCanvas *canvas) {
+  return reinterpret_cast<rgb_matrix::FrameCanvas*>(canvas);
+}
+static struct LedCanvas *from_canvas(rgb_matrix::FrameCanvas *canvas) {
+  return reinterpret_cast<struct LedCanvas*>(canvas);
+}
+
+static rgb_matrix::Font *to_font(struct LedFont *font) {
+  return reinterpret_cast<rgb_matrix::Font*>(font);
+}
+static struct LedFont *from_font(rgb_matrix::Font *font) {
+  return reinterpret_cast<struct LedFont*>(font);
+}
+
+
+static struct RGBLedMatrix *led_matrix_create_from_options_optional_edit(
+  struct RGBLedMatrixOptions *opts, struct RGBLedRuntimeOptions *rt_opts,
+  int *argc, char ***argv, bool remove_consumed_flags) {
+  rgb_matrix::RuntimeOptions default_rt;
+  rgb_matrix::RGBMatrix::Options default_opts;
+
+  if (opts) {
+    // Copy between C struct and C++ struct. The C++ struct already has a
+    // default constructor that sets some values. These we override with the
+    // C-struct values if available.
+    // We assume everything non-zero has an explicit value.
+#define OPT_COPY_IF_SET(o) if (opts->o) default_opts.o = opts->o
+    OPT_COPY_IF_SET(hardware_mapping);
+    OPT_COPY_IF_SET(rows);
+    OPT_COPY_IF_SET(cols);
+    OPT_COPY_IF_SET(chain_length);
+    OPT_COPY_IF_SET(parallel);
+    OPT_COPY_IF_SET(pwm_bits);
+    OPT_COPY_IF_SET(pwm_lsb_nanoseconds);
+    OPT_COPY_IF_SET(pwm_dither_bits);
+    OPT_COPY_IF_SET(brightness);
+    OPT_COPY_IF_SET(scan_mode);
+    OPT_COPY_IF_SET(row_address_type);
+    OPT_COPY_IF_SET(multiplexing);
+    OPT_COPY_IF_SET(disable_hardware_pulsing);
+    OPT_COPY_IF_SET(show_refresh_rate);
+    OPT_COPY_IF_SET(inverse_colors);
+    OPT_COPY_IF_SET(led_rgb_sequence);
+    OPT_COPY_IF_SET(pixel_mapper_config);
+    OPT_COPY_IF_SET(panel_type);
+    OPT_COPY_IF_SET(limit_refresh_rate_hz);
+#undef OPT_COPY_IF_SET
+  }
+
+  if (rt_opts) {
+    // Same story as RGBMatrix::Options
+#define RT_OPT_COPY_IF_SET(o) if (rt_opts->o) default_rt.o = rt_opts->o
+    RT_OPT_COPY_IF_SET(gpio_slowdown);
+    RT_OPT_COPY_IF_SET(daemon);
+    RT_OPT_COPY_IF_SET(drop_privileges);
+    RT_OPT_COPY_IF_SET(do_gpio_init);
+#undef RT_OPT_COPY_IF_SET
+  }
+
+  rgb_matrix::RGBMatrix::Options matrix_options = default_opts;
+  rgb_matrix::RuntimeOptions runtime_opt = default_rt;
+  if (argc != NULL && argv != NULL) {
+    if (!ParseOptionsFromFlags(argc, argv, &matrix_options, &runtime_opt,
+                               remove_consumed_flags)) {
+      rgb_matrix::PrintMatrixFlags(stderr, default_opts, default_rt);
+      return NULL;
+    }
+  }
+
+  if (opts) {
+#define ACTUAL_VALUE_BACK_TO_OPT(o) opts->o = matrix_options.o
+    ACTUAL_VALUE_BACK_TO_OPT(hardware_mapping);
+    ACTUAL_VALUE_BACK_TO_OPT(rows);
+    ACTUAL_VALUE_BACK_TO_OPT(cols);
+    ACTUAL_VALUE_BACK_TO_OPT(chain_length);
+    ACTUAL_VALUE_BACK_TO_OPT(parallel);
+    ACTUAL_VALUE_BACK_TO_OPT(pwm_bits);
+    ACTUAL_VALUE_BACK_TO_OPT(pwm_lsb_nanoseconds);
+    ACTUAL_VALUE_BACK_TO_OPT(pwm_dither_bits);
+    ACTUAL_VALUE_BACK_TO_OPT(brightness);
+    ACTUAL_VALUE_BACK_TO_OPT(scan_mode);
+    ACTUAL_VALUE_BACK_TO_OPT(row_address_type);
+    ACTUAL_VALUE_BACK_TO_OPT(multiplexing);
+    ACTUAL_VALUE_BACK_TO_OPT(disable_hardware_pulsing);
+    ACTUAL_VALUE_BACK_TO_OPT(show_refresh_rate);
+    ACTUAL_VALUE_BACK_TO_OPT(inverse_colors);
+    ACTUAL_VALUE_BACK_TO_OPT(led_rgb_sequence);
+    ACTUAL_VALUE_BACK_TO_OPT(pixel_mapper_config);
+    ACTUAL_VALUE_BACK_TO_OPT(panel_type);
+    ACTUAL_VALUE_BACK_TO_OPT(limit_refresh_rate_hz);
+#undef ACTUAL_VALUE_BACK_TO_OPT
+  }
+
+  if (rt_opts) {
+#define ACTUAL_VALUE_BACK_TO_RT_OPT(o) rt_opts->o = runtime_opt.o
+    ACTUAL_VALUE_BACK_TO_RT_OPT(gpio_slowdown);
+    ACTUAL_VALUE_BACK_TO_RT_OPT(daemon);
+    ACTUAL_VALUE_BACK_TO_RT_OPT(drop_privileges);
+    ACTUAL_VALUE_BACK_TO_RT_OPT(do_gpio_init);
+#undef ACTUAL_VALUE_BACK_TO_RT_OPT
+  }
+
+  rgb_matrix::RGBMatrix *matrix
+    = rgb_matrix::RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
+  return from_matrix(matrix);
+}
+
+struct RGBLedMatrix *led_matrix_create_from_options(
+  struct RGBLedMatrixOptions *opts, int *argc, char ***argv) {
+  return led_matrix_create_from_options_optional_edit(opts, NULL, argc, argv,
+                                                      true);
+}
+
+struct RGBLedMatrix *led_matrix_create_from_options_const_argv(
+  struct RGBLedMatrixOptions *opts, int argc, char **argv) {
+  return led_matrix_create_from_options_optional_edit(opts, NULL, &argc, &argv,
+                                                      false);
+}
+
+struct RGBLedMatrix *led_matrix_create_from_options_and_rt_options(
+  struct RGBLedMatrixOptions *opts, struct RGBLedRuntimeOptions * rt_opts) {
+  return led_matrix_create_from_options_optional_edit(opts, rt_opts, NULL, NULL,
+                                                      false);
+}
+
+struct RGBLedMatrix *led_matrix_create(int rows, int chained, int parallel) {
+  struct RGBLedMatrixOptions opts;
+  memset(&opts, 0, sizeof(opts));
+  opts.rows = rows;
+  opts.chain_length = chained;
+  opts.parallel = parallel;
+  return led_matrix_create_from_options(&opts, NULL, NULL);
+}
+
+void led_matrix_print_flags(FILE *out) {
+  rgb_matrix::RGBMatrix::Options defaults;
+  rgb_matrix::RuntimeOptions rt_opt;
+  rt_opt.daemon = -1;
+  rt_opt.drop_privileges = -1;
+  rgb_matrix::PrintMatrixFlags(out, defaults, rt_opt);
+}
+
+void led_matrix_delete(struct RGBLedMatrix *matrix) {
+  delete to_matrix(matrix);
+}
+
+struct LedCanvas *led_matrix_get_canvas(struct RGBLedMatrix *matrix) {
+  return from_canvas(to_matrix(matrix)->SwapOnVSync(NULL));
+}
+
+struct LedCanvas *led_matrix_create_offscreen_canvas(struct RGBLedMatrix *m) {
+  return from_canvas(to_matrix(m)->CreateFrameCanvas());
+}
+
+struct LedCanvas *led_matrix_swap_on_vsync(struct RGBLedMatrix *matrix,
+                                           struct LedCanvas *canvas) {
+  return from_canvas(to_matrix(matrix)->SwapOnVSync(to_canvas(canvas)));
+}
+
+void led_matrix_set_brightness(struct RGBLedMatrix *matrix,
+                               uint8_t brightness) {
+  to_matrix(matrix)->SetBrightness(brightness);
+}
+
+uint8_t led_matrix_get_brightness(struct RGBLedMatrix *matrix) {
+  return to_matrix(matrix)->brightness();
+}
+
+void led_canvas_get_size(const struct LedCanvas *canvas,
+                         int *width, int *height) {
+  rgb_matrix::FrameCanvas *c = to_canvas((struct LedCanvas*)canvas);
+  if (c == NULL ) return;
+  if (width != NULL) *width = c->width();
+  if (height != NULL) *height = c->height();
+}
+
+void led_canvas_set_pixel(struct LedCanvas *canvas, int x, int y,
+			  uint8_t r, uint8_t g, uint8_t b) {
+  to_canvas(canvas)->SetPixel(x, y, r, g, b);
+}
+
+void led_canvas_clear(struct LedCanvas *canvas) {
+  to_canvas(canvas)->Clear();
+}
+
+void led_canvas_fill(struct LedCanvas *canvas, uint8_t r, uint8_t g, uint8_t b) {
+  to_canvas(canvas)->Fill(r, g, b);
+}
+
+struct LedFont *load_font(const char *bdf_font_file) {
+  rgb_matrix::Font* font = new rgb_matrix::Font();
+  font->LoadFont(bdf_font_file);
+  return from_font(font);
+}
+
+int baseline_font(struct LedFont * font) {
+  return to_font(font)->baseline();
+}
+
+int height_font(struct LedFont * font) {
+  return to_font(font)->height();
+}
+
+struct LedFont *create_outline_font(struct LedFont * font) {
+  rgb_matrix::Font* outlineFont = to_font(font)->CreateOutlineFont();
+  return from_font(outlineFont);
+}
+
+void delete_font(struct LedFont *font) {
+  delete to_font(font);
+}
+
+// -- Some utility functions.
+
+void set_image(struct LedCanvas *c, int canvas_offset_x, int canvas_offset_y,
+	       const uint8_t *image_buffer, size_t buffer_size_bytes,
+	       int image_width, int image_height,
+	       char is_bgr) {
+  SetImage(to_canvas(c), canvas_offset_x, canvas_offset_y,
+           image_buffer, buffer_size_bytes,
+           image_width, image_height,
+           is_bgr);
+}
+
+// Draw text, a standard NUL terminated C-string encoded in UTF-8,
+// with given "font" at "x","y" with "color".
+// "color" always needs to be set (hence it is a reference),
+// "background_color" is a pointer to optionally be NULL for transparency.
+// "kerning_offset" allows for additional spacing between characters (can be
+// negative)
+// Returns how many pixels we advanced on the screen.
+int draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
+              uint8_t r, uint8_t g, uint8_t b, const char *utf8_text, int kerning_offset) {
+  const rgb_matrix::Color col = rgb_matrix::Color(r, g, b);
+  return DrawText(to_canvas(c), *to_font(font), x, y, col, NULL, utf8_text, kerning_offset);
+}
+
+// Draw text, a standard NUL terminated C-string encoded in UTF-8,
+// with given "font" at "x","y" with "color".
+// Draw text as above, but vertically (top down).
+// The text is a standard NUL terminated C-string encoded in UTF-8.
+// "font, "x", "y", "color" and "background_color" are same as DrawText().
+// "kerning_offset" allows for additional spacing between characters (can be
+// negative).
+// Returns font height to advance up on the screen.
+int vertical_draw_text(struct LedCanvas *c, struct LedFont *font, int x, int y,
+                       uint8_t r, uint8_t g, uint8_t b,
+                       const char *utf8_text, int kerning_offset = 0) {
+  const rgb_matrix::Color col = rgb_matrix::Color(r, g, b);
+  return VerticalDrawText(to_canvas(c), *to_font(font), x, y, col, NULL, utf8_text, kerning_offset);
+}
+
+// Draw a circle centered at "x", "y", with a radius of "radius" and with "color"
+void draw_circle(struct LedCanvas *c, int xx, int y, int radius, uint8_t r, uint8_t g, uint8_t b) {
+  const rgb_matrix::Color col = rgb_matrix::Color( r,g,b );
+  DrawCircle(to_canvas(c), xx, y, radius, col);
+}
+
+// Draw a line from "x0", "y0" to "x1", "y1" and with "color"
+void draw_line(struct LedCanvas *c, int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) {
+  const rgb_matrix::Color col = rgb_matrix::Color(r, g, b);
+  DrawLine(to_canvas(c), x0, y0, x1, y1, col);
+}

+ 763 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/led-matrix.cc

@@ -0,0 +1,763 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include "led-matrix.h"
+
+#include <assert.h>
+#include <grp.h>
+#include <pwd.h>
+#include <math.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "gpio.h"
+#include "thread.h"
+#include "framebuffer-internal.h"
+#include "multiplex-mappers-internal.h"
+
+// Leave this in here for a while. Setting things from old defines.
+#if defined(ADAFRUIT_RGBMATRIX_HAT)
+#  error "ADAFRUIT_RGBMATRIX_HAT has long been deprecated. Please use the Options struct or --led-gpio-mapping=adafruit-hat commandline flag"
+#endif
+
+#if defined(ADAFRUIT_RGBMATRIX_HAT_PWM)
+#  error "ADAFRUIT_RGBMATRIX_HAT_PWM has long been deprecated. Please use the Options struct or --led-gpio-mapping=adafruit-hat-pwm commandline flag"
+#endif
+
+namespace rgb_matrix {
+// Implementation details of RGBmatrix.
+class RGBMatrix::Impl {
+  class UpdateThread;
+  friend class UpdateThread;
+
+public:
+  // Create an RGBMatrix.
+  //
+  // Needs an initialized GPIO object and configuration options from the
+  // RGBMatrix::Options struct.
+  //
+  // If you pass an GPIO object (which has to be Init()ialized), it will start  // the internal thread to start the screen immediately.
+  //
+  // If you need finer control over when the refresh thread starts (which you
+  // might when you become a daemon), pass NULL here and see SetGPIO() method.
+  //
+  // The resulting canvas is (options.rows * options.parallel) high and
+  // (32 * options.chain_length) wide.
+  Impl(GPIO *io, const Options &options);
+
+  ~Impl();
+
+  // Used to be there to help user delay initialization of thread starting,
+  // these days only used internally.
+  void SetGPIO(GPIO *io, bool start_thread = true);
+
+  bool StartRefresh();
+
+  FrameCanvas *CreateFrameCanvas();
+  FrameCanvas *SwapOnVSync(FrameCanvas *other, unsigned framerate_fraction);
+  bool ApplyPixelMapper(const PixelMapper *mapper);
+
+  bool SetPWMBits(uint8_t value);
+  uint8_t pwmbits();   // return the pwm-bits of the currently active buffer.
+
+  void set_luminance_correct(bool on);
+  bool luminance_correct() const;
+
+  // Set brightness in percent for all created FrameCanvas. 1%..100%.
+  // This will only affect newly set pixels.
+  void SetBrightness(uint8_t brightness);
+  uint8_t brightness();
+
+  uint64_t RequestInputs(uint64_t);
+  uint64_t AwaitInputChange(int timeout_ms);
+
+  uint64_t RequestOutputs(uint64_t output_bits);
+  void OutputGPIO(uint64_t output_bits);
+
+  void Clear();
+private:
+  friend class RGBMatrix;
+
+  // Apply pixel mappers that have been passed down via a configuration
+  // string.
+  void ApplyNamedPixelMappers(const char *pixel_mapper_config,
+                              int chain, int parallel);
+
+  Options params_;
+  bool do_luminance_correct_;
+
+  FrameCanvas *active_;
+
+  GPIO *io_;
+  Mutex active_frame_sync_;
+  UpdateThread *updater_;
+  std::vector<FrameCanvas*> created_frames_;
+  internal::PixelDesignatorMap *shared_pixel_mapper_;
+  uint64_t user_output_bits_;
+};
+
+using namespace internal;
+
+// Pump pixels to screen. Needs to be high priority real-time because jitter
+class RGBMatrix::Impl::UpdateThread : public Thread {
+public:
+  UpdateThread(GPIO *io, FrameCanvas *initial_frame,
+               int pwm_dither_bits, bool show_refresh,
+               int limit_refresh_hz)
+    : io_(io), show_refresh_(show_refresh),
+      target_frame_usec_(limit_refresh_hz < 1 ? 0 : 1e6/limit_refresh_hz),
+      running_(true),
+      current_frame_(initial_frame), next_frame_(NULL),
+      requested_frame_multiple_(1) {
+    pthread_cond_init(&frame_done_, NULL);
+    pthread_cond_init(&input_change_, NULL);
+    switch (pwm_dither_bits) {
+    case 0:
+      start_bit_[0] = 0; start_bit_[1] = 0;
+      start_bit_[2] = 0; start_bit_[3] = 0;
+      break;
+    case 1:
+      start_bit_[0] = 0; start_bit_[1] = 1;
+      start_bit_[2] = 0; start_bit_[3] = 1;
+      break;
+    case 2:
+      start_bit_[0] = 0; start_bit_[1] = 1;
+      start_bit_[2] = 2; start_bit_[3] = 2;
+      break;
+    }
+  }
+
+  void Stop() {
+    MutexLock l(&running_mutex_);
+    running_ = false;
+  }
+
+  virtual void Run() {
+    unsigned frame_count = 0;
+    unsigned low_bit_sequence = 0;
+    uint32_t largest_time = 0;
+    gpio_bits_t last_gpio_bits = 0;
+
+    // Let's start measure max time only after a we were running for a few
+    // seconds to not pick up start-up glitches.
+    static const int kHoldffTimeUs = 2000 * 1000;
+    uint32_t initial_holdoff_start = GetMicrosecondCounter();
+    bool max_measure_enabled = false;
+
+    while (running()) {
+      const uint32_t start_time_us = GetMicrosecondCounter();
+
+      current_frame_->framebuffer()
+        ->DumpToMatrix(io_, start_bit_[low_bit_sequence % 4]);
+
+      // SwapOnVSync() exchange.
+      {
+        MutexLock l(&frame_sync_);
+        // Do fast equality test first (likely due to frame_count reset).
+        if (frame_count == requested_frame_multiple_
+            || frame_count % requested_frame_multiple_ == 0) {
+          // We reset to avoid frame hick-up every couple of weeks
+          // run-time iff requested_frame_multiple_ is not a factor of 2^32.
+          frame_count = 0;
+          if (next_frame_ != NULL) {
+            current_frame_ = next_frame_;
+            next_frame_ = NULL;
+          }
+          pthread_cond_signal(&frame_done_);
+        }
+      }
+
+      // Read input bits.
+      const gpio_bits_t inputs = io_->Read();
+      if (inputs != last_gpio_bits) {
+        last_gpio_bits = inputs;
+        MutexLock l(&input_sync_);
+        gpio_inputs_ = inputs;
+        pthread_cond_signal(&input_change_);
+      }
+
+      ++frame_count;
+      ++low_bit_sequence;
+
+      if (target_frame_usec_) {
+        while ((GetMicrosecondCounter() - start_time_us) < target_frame_usec_) {
+          // busy wait. We have our dedicated core, so ok to burn cycles.
+        }
+      }
+
+      const uint32_t end_time_us = GetMicrosecondCounter();
+      if (show_refresh_) {
+        uint32_t usec = end_time_us - start_time_us;
+        printf("\b\b\b\b\b\b\b\b%6.1fHz", 1e6 / usec);
+        if (usec > largest_time && max_measure_enabled) {
+          largest_time = usec;
+          const float lowest_hz = 1e6 / largest_time;
+          printf(" (lowest: %.1fHz)"
+                 "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b", lowest_hz);
+        } else {
+          // Don't measure at startup, as times will be janky.
+          max_measure_enabled = (end_time_us - initial_holdoff_start) > kHoldffTimeUs;
+        }
+      }
+    }
+  }
+
+  FrameCanvas *SwapOnVSync(FrameCanvas *other, unsigned frame_fraction) {
+    MutexLock l(&frame_sync_);
+    FrameCanvas *previous = current_frame_;
+    next_frame_ = other;
+    requested_frame_multiple_ = frame_fraction;
+    frame_sync_.WaitOn(&frame_done_);
+    return previous;
+  }
+
+  gpio_bits_t AwaitInputChange(int timeout_ms) {
+    MutexLock l(&input_sync_);
+    input_sync_.WaitOn(&input_change_, timeout_ms);
+    return gpio_inputs_;
+  }
+
+private:
+  inline bool running() {
+    MutexLock l(&running_mutex_);
+    return running_;
+  }
+
+  GPIO *const io_;
+  const bool show_refresh_;
+  const uint32_t target_frame_usec_;
+  uint32_t start_bit_[4];
+
+  Mutex running_mutex_;
+  bool running_;
+
+  Mutex input_sync_;
+  pthread_cond_t input_change_;
+  gpio_bits_t gpio_inputs_;
+
+  Mutex frame_sync_;
+  pthread_cond_t frame_done_;
+  FrameCanvas *current_frame_;
+  FrameCanvas *next_frame_;
+  unsigned requested_frame_multiple_;
+};
+
+// Some defaults. See options-initialize.cc for the command line parsing.
+RGBMatrix::Options::Options() :
+  // Historically, we provided these options only as #defines. Make sure that
+  // things still behave as before if someone has set these.
+  // At some point: remove them from the Makefile. Later: remove them here.
+#ifdef DEFAULT_HARDWARE
+  hardware_mapping(DEFAULT_HARDWARE),
+#else
+  hardware_mapping("regular"),
+#endif
+
+  rows(32), cols(32), chain_length(1), parallel(1),
+  pwm_bits(internal::Framebuffer::kDefaultBitPlanes),
+
+#ifdef LSB_PWM_NANOSECONDS
+    pwm_lsb_nanoseconds(LSB_PWM_NANOSECONDS),
+#else
+    pwm_lsb_nanoseconds(130),
+#endif
+
+  pwm_dither_bits(0),
+  brightness(100),
+
+#ifdef RGB_SCAN_INTERLACED
+    scan_mode(1),
+#else
+    scan_mode(0),
+#endif
+
+  row_address_type(0),
+  multiplexing(0),
+
+#ifdef DISABLE_HARDWARE_PULSES
+    disable_hardware_pulsing(true),
+#else
+    disable_hardware_pulsing(false),
+#endif
+
+#ifdef SHOW_REFRESH_RATE
+    show_refresh_rate(true),
+#else
+    show_refresh_rate(false),
+#endif
+
+#ifdef INVERSE_RGB_DISPLAY_COLORS
+    inverse_colors(true),
+#else
+    inverse_colors(false),
+#endif
+  led_rgb_sequence("RGB"),
+  pixel_mapper_config(NULL),
+  panel_type(NULL),
+#ifdef FIXED_FRAME_MICROSECONDS
+  limit_refresh_rate_hz(1e6 / FIXED_FRAME_MICROSECONDS)
+#else
+  limit_refresh_rate_hz(0)
+#endif
+{
+  // Nothing to see here.
+}
+
+#define DEBUG_MATRIX_OPTIONS 0
+
+#if DEBUG_MATRIX_OPTIONS
+static void PrintOptions(const RGBMatrix::Options &o) {
+#define P_INT(val) fprintf(stderr, "%s : %d\n", #val, o.val)
+#define P_STR(val) fprintf(stderr, "%s : %s\n", #val, o.val)
+#define P_BOOL(val) fprintf(stderr, "%s : %s\n", #val, o.val ? "true":"false")
+  P_STR(hardware_mapping);
+  P_INT(rows);
+  P_INT(cols);
+  P_INT(chain_length);
+  P_INT(parallel);
+  P_INT(pwm_bits);
+  P_INT(pwm_lsb_nanoseconds);
+  P_INT(pwm_dither_bits);
+  P_INT(brightness);
+  P_INT(scan_mode);
+  P_INT(row_address_type);
+  P_INT(multiplexing);
+  P_BOOL(disable_hardware_pulsing);
+  P_BOOL(show_refresh_rate);
+  P_BOOL(inverse_colors);
+  P_STR(led_rgb_sequence);
+  P_STR(pixel_mapper_config);
+  P_STR(panel_type);
+  P_INT(limit_refresh_rate_hz);
+#undef P_INT
+#undef P_STR
+#undef P_BOOL
+}
+#endif  // DEBUG_MATRIX_OPTIONS
+
+RGBMatrix::Impl::Impl(GPIO *io, const Options &options)
+  : params_(options), io_(NULL), updater_(NULL), shared_pixel_mapper_(NULL),
+    user_output_bits_(0) {
+  assert(params_.Validate(NULL));
+#if DEBUG_MATRIX_OPTIONS
+  PrintOptions(params_);
+#endif
+  const MultiplexMapper *multiplex_mapper = NULL;
+  if (params_.multiplexing > 0) {
+    const MuxMapperList &multiplexers = GetRegisteredMultiplexMappers();
+    if (params_.multiplexing <= (int) multiplexers.size()) {
+      // TODO: we could also do a find-by-name here, but not sure if worthwhile
+      multiplex_mapper = multiplexers[params_.multiplexing - 1];
+    }
+  }
+
+  if (multiplex_mapper) {
+    // The multiplexers might choose to have a different physical layout.
+    // We need to configure that first before setting up the hardware.
+    multiplex_mapper->EditColsRows(&params_.cols, &params_.rows);
+  }
+
+  Framebuffer::InitHardwareMapping(params_.hardware_mapping);
+
+  active_ = CreateFrameCanvas();
+  active_->Clear();
+  SetGPIO(io, true);
+
+  // We need to apply the mapping for the panels first.
+  ApplyPixelMapper(multiplex_mapper);
+
+  // .. followed by higher level mappers that might arrange panels.
+  ApplyNamedPixelMappers(options.pixel_mapper_config,
+                         params_.chain_length, params_.parallel);
+}
+
+RGBMatrix::Impl::~Impl() {
+  if (updater_) {
+    updater_->Stop();
+    updater_->WaitStopped();
+  }
+  delete updater_;
+
+  // Make sure LEDs are off.
+  active_->Clear();
+  if (io_) active_->framebuffer()->DumpToMatrix(io_, 0);
+
+  for (size_t i = 0; i < created_frames_.size(); ++i) {
+    delete created_frames_[i];
+  }
+  delete shared_pixel_mapper_;
+}
+
+RGBMatrix::~RGBMatrix() {
+  delete impl_;
+}
+
+uint64_t RGBMatrix::Impl::RequestInputs(uint64_t bits) {
+  return io_->RequestInputs(bits);
+}
+
+uint64_t RGBMatrix::Impl::RequestOutputs(uint64_t output_bits) {
+  uint64_t success_bits = io_->InitOutputs(output_bits);
+  user_output_bits_ |= success_bits;
+  return success_bits;
+}
+
+void RGBMatrix::Impl::OutputGPIO(uint64_t output_bits) {
+  io_->WriteMaskedBits(output_bits, user_output_bits_);
+}
+
+void RGBMatrix::Impl::ApplyNamedPixelMappers(const char *pixel_mapper_config,
+                                             int chain, int parallel) {
+  if (pixel_mapper_config == NULL || strlen(pixel_mapper_config) == 0)
+    return;
+  char *const writeable_copy = strdup(pixel_mapper_config);
+  const char *const end = writeable_copy + strlen(writeable_copy);
+  char *s = writeable_copy;
+  while (s < end) {
+    char *const semicolon = strchrnul(s, ';');
+    *semicolon = '\0';
+    char *optional_param_start = strchr(s, ':');
+    if (optional_param_start) {
+      *optional_param_start++ = '\0';
+    }
+    if (*s == '\0' && optional_param_start && *optional_param_start != '\0') {
+      fprintf(stderr, "Stray parameter ':%s' without mapper name ?\n", optional_param_start);
+    }
+    if (*s) {
+      ApplyPixelMapper(FindPixelMapper(s, chain, parallel, optional_param_start));
+    }
+    s = semicolon + 1;
+  }
+  free(writeable_copy);
+}
+
+void RGBMatrix::Impl::SetGPIO(GPIO *io, bool start_thread) {
+  if (io != NULL && io_ == NULL) {
+    io_ = io;
+    Framebuffer::InitGPIO(io_, params_.rows, params_.parallel,
+                          !params_.disable_hardware_pulsing,
+                          params_.pwm_lsb_nanoseconds, params_.pwm_dither_bits,
+                          params_.row_address_type);
+    Framebuffer::InitializePanels(io_, params_.panel_type,
+                                  params_.cols * params_.chain_length);
+  }
+  if (start_thread) {
+    StartRefresh();
+  }
+}
+
+bool RGBMatrix::Impl::StartRefresh() {
+  if (updater_ == NULL && io_ != NULL) {
+    updater_ = new UpdateThread(io_, active_, params_.pwm_dither_bits,
+                                params_.show_refresh_rate,
+                                params_.limit_refresh_rate_hz);
+    // If we have multiple processors, the kernel
+    // jumps around between these, creating some global flicker.
+    // So let's tie it to the last CPU available.
+    // The Raspberry Pi2 has 4 cores, our attempt to bind it to
+    //   core #3 will succeed.
+    // The Raspberry Pi1 only has one core, so this affinity
+    //   call will simply fail and we keep using the only core.
+    updater_->Start(99, (1<<3));  // Prio: high. Also: put on last CPU.
+  }
+  return updater_ != NULL;
+}
+
+FrameCanvas *RGBMatrix::Impl::CreateFrameCanvas() {
+  FrameCanvas *result =
+    new FrameCanvas(new Framebuffer(params_.rows,
+                                    params_.cols * params_.chain_length,
+                                    params_.parallel,
+                                    params_.scan_mode,
+                                    params_.led_rgb_sequence,
+                                    params_.inverse_colors,
+                                    &shared_pixel_mapper_));
+  if (created_frames_.empty()) {
+    // First time. Get defaults from initial Framebuffer.
+    do_luminance_correct_ = result->framebuffer()->luminance_correct();
+  }
+
+  result->framebuffer()->SetPWMBits(params_.pwm_bits);
+  result->framebuffer()->set_luminance_correct(do_luminance_correct_);
+  result->framebuffer()->SetBrightness(params_.brightness);
+
+  created_frames_.push_back(result);
+  return result;
+}
+
+FrameCanvas *RGBMatrix::Impl::SwapOnVSync(FrameCanvas *other,
+                                          unsigned frame_fraction) {
+  if (frame_fraction == 0) frame_fraction = 1; // correct user error.
+  if (!updater_) return NULL;
+  FrameCanvas *const previous = updater_->SwapOnVSync(other, frame_fraction);
+  if (other) active_ = other;
+  return previous;
+}
+
+uint64_t RGBMatrix::Impl::AwaitInputChange(int timeout_ms) {
+  if (!updater_) return 0;
+  return updater_->AwaitInputChange(timeout_ms);
+}
+
+bool RGBMatrix::Impl::SetPWMBits(uint8_t value) {
+  const bool success = active_->framebuffer()->SetPWMBits(value);
+  if (success) {
+    params_.pwm_bits = value;
+  }
+  return success;
+}
+uint8_t RGBMatrix::Impl::pwmbits() { return params_.pwm_bits; }
+
+// Map brightness of output linearly to input with CIE1931 profile.
+void RGBMatrix::Impl::set_luminance_correct(bool on) {
+  active_->framebuffer()->set_luminance_correct(on);
+  do_luminance_correct_ = on;
+}
+bool RGBMatrix::Impl::luminance_correct() const {
+  return do_luminance_correct_;
+}
+
+void RGBMatrix::Impl::SetBrightness(uint8_t brightness) {
+  for (size_t i = 0; i < created_frames_.size(); ++i) {
+    created_frames_[i]->framebuffer()->SetBrightness(brightness);
+  }
+  params_.brightness = brightness;
+}
+
+uint8_t RGBMatrix::Impl::brightness() {
+  return params_.brightness;
+}
+
+bool RGBMatrix::Impl::ApplyPixelMapper(const PixelMapper *mapper) {
+  if (mapper == NULL) return true;
+  using internal::PixelDesignatorMap;
+  const int old_width = shared_pixel_mapper_->width();
+  const int old_height = shared_pixel_mapper_->height();
+  int new_width, new_height;
+  if (!mapper->GetSizeMapping(old_width, old_height, &new_width, &new_height)) {
+    return false;
+  }
+  PixelDesignatorMap *new_mapper = new PixelDesignatorMap(
+    new_width, new_height, shared_pixel_mapper_->GetFillColorBits());
+  for (int y = 0; y < new_height; ++y) {
+    for (int x = 0; x < new_width; ++x) {
+      int orig_x = -1, orig_y = -1;
+      mapper->MapVisibleToMatrix(old_width, old_height,
+                                 x, y, &orig_x, &orig_y);
+      if (orig_x < 0 || orig_y < 0 ||
+          orig_x >= old_width || orig_y >= old_height) {
+        fprintf(stderr, "Error in PixelMapper: (%d, %d) -> (%d, %d) [range: "
+                "%dx%d]\n", x, y, orig_x, orig_y, old_width, old_height);
+        continue;
+      }
+      const internal::PixelDesignator *orig_designator;
+      orig_designator = shared_pixel_mapper_->get(orig_x, orig_y);
+      *new_mapper->get(x, y) = *orig_designator;
+    }
+  }
+  delete shared_pixel_mapper_;
+  shared_pixel_mapper_ = new_mapper;
+  return true;
+}
+
+// -- Public interface of RGBMatrix. Delegate everything to impl_
+
+static bool drop_privs(const char *priv_user, const char *priv_group) {
+  uid_t ruid, euid, suid;
+  if (getresuid(&ruid, &euid, &suid) >= 0) {
+    if (euid != 0)   // not root anyway. No priv dropping.
+      return true;
+  }
+
+  struct group *g = getgrnam(priv_group);
+  if (g == NULL) {
+    perror("group lookup.");
+    return false;
+  }
+  if (setresgid(g->gr_gid, g->gr_gid, g->gr_gid) != 0) {
+    perror("setresgid()");
+    return false;
+  }
+  struct passwd *p = getpwnam(priv_user);
+  if (p == NULL) {
+    perror("user lookup.");
+    return false;
+  }
+  if (setresuid(p->pw_uid, p->pw_uid, p->pw_uid) != 0) {
+    perror("setresuid()");
+    return false;
+  }
+  return true;
+}
+
+RGBMatrix *RGBMatrix::CreateFromOptions(const RGBMatrix::Options &options,
+                                        const RuntimeOptions &runtime_options) {
+  std::string error;
+  if (!options.Validate(&error)) {
+    fprintf(stderr, "%s\n", error.c_str());
+    return NULL;
+  }
+
+  // For the Pi4, we might need 2, maybe up to 4. Let's open up to 5.
+  if (runtime_options.gpio_slowdown < 0 || runtime_options.gpio_slowdown > 5) {
+    fprintf(stderr, "--led-slowdown-gpio=%d is outside usable range\n",
+            runtime_options.gpio_slowdown);
+    return NULL;
+  }
+
+  static GPIO io;  // This static var is a little bit icky.
+  if (runtime_options.do_gpio_init
+      && !io.Init(runtime_options.gpio_slowdown)) {
+    fprintf(stderr, "Must run as root to be able to access /dev/mem\n"
+            "Prepend 'sudo' to the command\n");
+    return NULL;
+  }
+
+  if (runtime_options.daemon > 0 && daemon(1, 0) != 0) {
+    perror("Failed to become daemon");
+  }
+
+  RGBMatrix::Impl *result = new RGBMatrix::Impl(NULL, options);
+  // Allowing daemon also means we are allowed to start the thread now.
+  const bool allow_daemon = !(runtime_options.daemon < 0);
+  if (runtime_options.do_gpio_init)
+    result->SetGPIO(&io, allow_daemon);
+
+  // TODO(hzeller): if we disallow daemon, then we might also disallow
+  // drop privileges: we can't drop privileges until we have created the
+  // realtime thread that usually requires root to be established.
+  // Double check and document.
+  if (runtime_options.drop_privileges > 0) {
+    drop_privs("daemon", "daemon");
+  }
+
+  return new RGBMatrix(result);
+}
+
+// Public interface.
+RGBMatrix *RGBMatrix::CreateFromFlags(int *argc, char ***argv,
+                                      RGBMatrix::Options *m_opt_in,
+                                      RuntimeOptions *rt_opt_in,
+                                      bool remove_consumed_options) {
+  RGBMatrix::Options scratch_matrix;
+  RGBMatrix::Options *mopt = (m_opt_in != NULL) ? m_opt_in : &scratch_matrix;
+
+  RuntimeOptions scratch_rt;
+  RuntimeOptions *ropt = (rt_opt_in != NULL) ? rt_opt_in : &scratch_rt;
+
+  if (!ParseOptionsFromFlags(argc, argv, mopt, ropt, remove_consumed_options))
+    return NULL;
+  return CreateFromOptions(*mopt, *ropt);
+}
+
+FrameCanvas *RGBMatrix::CreateFrameCanvas() {
+  return impl_->CreateFrameCanvas();
+}
+FrameCanvas *RGBMatrix::SwapOnVSync(FrameCanvas *other,
+                                    unsigned framerate_fraction) {
+  return impl_->SwapOnVSync(other, framerate_fraction);
+}
+bool RGBMatrix::ApplyPixelMapper(const PixelMapper *mapper) {
+  return impl_->ApplyPixelMapper(mapper);
+}
+bool RGBMatrix::SetPWMBits(uint8_t value) { return impl_->SetPWMBits(value); }
+uint8_t RGBMatrix::pwmbits() { return impl_->pwmbits(); }
+
+void RGBMatrix::set_luminance_correct(bool on) {
+  return impl_->set_luminance_correct(on);
+}
+bool RGBMatrix::luminance_correct() const { return impl_->luminance_correct(); }
+
+void RGBMatrix::SetBrightness(uint8_t brightness) {
+  impl_->SetBrightness(brightness);
+}
+uint8_t RGBMatrix::brightness() { return impl_->brightness(); }
+
+uint64_t RGBMatrix::RequestInputs(uint64_t all_interested_bits) {
+  return impl_->RequestInputs(all_interested_bits);
+}
+uint64_t RGBMatrix::AwaitInputChange(int timeout_ms) {
+  return impl_->AwaitInputChange(timeout_ms);
+}
+
+uint64_t RGBMatrix::RequestOutputs(uint64_t all_interested_bits) {
+  return impl_->RequestOutputs(all_interested_bits);
+}
+void RGBMatrix::OutputGPIO(uint64_t output_bits) {
+  impl_->OutputGPIO(output_bits);
+}
+
+bool RGBMatrix::StartRefresh() { return impl_->StartRefresh(); }
+
+// -- Implementation of RGBMatrix Canvas: delegation to ContentBuffer
+int RGBMatrix::width() const {
+  return impl_->active_->width();
+}
+
+int RGBMatrix::height() const {
+  return impl_->active_->height();
+}
+
+void RGBMatrix::SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue) {
+  impl_->active_->SetPixel(x, y, red, green, blue);
+}
+
+void RGBMatrix::Clear() {
+  impl_->active_->Clear();
+}
+
+void RGBMatrix::Fill(uint8_t red, uint8_t green, uint8_t blue) {
+  impl_->active_->Fill(red, green, blue);
+}
+
+// FrameCanvas implementation of Canvas
+FrameCanvas::~FrameCanvas() { delete frame_; }
+int FrameCanvas::width() const { return frame_->width(); }
+int FrameCanvas::height() const { return frame_->height(); }
+void FrameCanvas::SetPixel(int x, int y,
+                         uint8_t red, uint8_t green, uint8_t blue) {
+  frame_->SetPixel(x, y, red, green, blue);
+}
+void FrameCanvas::Clear() { return frame_->Clear(); }
+void FrameCanvas::Fill(uint8_t red, uint8_t green, uint8_t blue) {
+  frame_->Fill(red, green, blue);
+}
+bool FrameCanvas::SetPWMBits(uint8_t value) { return frame_->SetPWMBits(value); }
+uint8_t FrameCanvas::pwmbits() { return frame_->pwmbits(); }
+
+// Map brightness of output linearly to input with CIE1931 profile.
+void FrameCanvas::set_luminance_correct(bool on) { frame_->set_luminance_correct(on); }
+bool FrameCanvas::luminance_correct() const { return frame_->luminance_correct(); }
+
+void FrameCanvas::SetBrightness(uint8_t brightness) { frame_->SetBrightness(brightness); }
+uint8_t FrameCanvas::brightness() { return frame_->brightness(); }
+
+void FrameCanvas::Serialize(const char **data, size_t *len) const {
+  frame_->Serialize(data, len);
+}
+bool FrameCanvas::Deserialize(const char *data, size_t len) {
+  return frame_->Deserialize(data, len);
+}
+void FrameCanvas::CopyFrom(const FrameCanvas &other) {
+  frame_->CopyFrom(other.frame_);
+}
+}  // end namespace rgb_matrix

+ 38 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/multiplex-mappers-internal.h

@@ -0,0 +1,38 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2017 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include <vector>
+
+#include "pixel-mapper.h"
+
+namespace rgb_matrix {
+namespace internal {
+
+class MultiplexMapper : public PixelMapper {
+public:
+  // Function that edits the original rows and columns of the panels
+  // provided by the user to the actual underlying mapping. This is called
+  // before we do the actual set-up of the GPIO mapping as this influences
+  // the hardware interface.
+  // This is so that the user can provide the rows/columns they see.
+  virtual void EditColsRows(int *cols, int *rows) const = 0;
+};
+
+// Returns a vector of the registered Multiplex mappers.
+typedef std::vector<const MultiplexMapper*> MuxMapperList;
+const MuxMapperList &GetRegisteredMultiplexMappers();
+
+}  // namespace internal
+}  // namespace rgb_matrix

+ 476 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/multiplex-mappers.cc

@@ -0,0 +1,476 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2017 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include "multiplex-mappers-internal.h"
+
+namespace rgb_matrix {
+namespace internal {
+// A Pixel Mapper maps physical pixels locations to the internal logical
+// mapping in a panel or panel-assembly, which depends on the wiring.
+class MultiplexMapperBase : public MultiplexMapper {
+public:
+  MultiplexMapperBase(const char *name, int stretch_factor)
+    : name_(name), panel_stretch_factor_(stretch_factor) {}
+
+  // This method is const, but we sneakily remember the original size
+  // of the panels so that we can more easily quantize things.
+  // So technically, we're stateful, but let's pretend we're not changing
+  // state. In the context this is used, it is never accessed in multiple
+  // threads.
+  virtual void EditColsRows(int *cols, int *rows) const {
+    panel_rows_ = *rows;
+    panel_cols_ = *cols;
+
+    *rows /= panel_stretch_factor_;
+    *cols *= panel_stretch_factor_;
+  }
+
+  virtual bool GetSizeMapping(int matrix_width, int matrix_height,
+                              int *visible_width, int *visible_height) const {
+    // Matrix width has been altered. Alter it back.
+    *visible_width = matrix_width / panel_stretch_factor_;
+    *visible_height = matrix_height * panel_stretch_factor_;
+    return true;
+  }
+
+  virtual const char *GetName() const { return name_; }
+
+  // The MapVisibleToMatrix() as required by PanelMatrix here breaks it
+  // down to the individual panel, so that derived classes only need to
+  // implement MapSinglePanel().
+  virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
+                                  int visible_x, int visible_y,
+                                  int *matrix_x, int *matrix_y) const {
+    const int chained_panel  = visible_x / panel_cols_;
+    const int parallel_panel = visible_y / panel_rows_;
+
+    const int within_panel_x = visible_x % panel_cols_;
+    const int within_panel_y = visible_y % panel_rows_;
+
+    int new_x, new_y;
+    MapSinglePanel(within_panel_x, within_panel_y, &new_x, &new_y);
+    *matrix_x = chained_panel  * panel_stretch_factor_*panel_cols_ + new_x;
+    *matrix_y = parallel_panel * panel_rows_/panel_stretch_factor_ + new_y;
+  }
+
+  // Map the coordinates for a single panel. This is to be overridden in
+  // derived classes.
+  // Input parameter is the visible position on the matrix, and this method
+  // should return the internal multiplexed position.
+  virtual void MapSinglePanel(int visible_x, int visible_y,
+                              int *matrix_x, int *matrix_y) const = 0;
+
+protected:
+  const char *const name_;
+  const int panel_stretch_factor_;
+
+  mutable int panel_cols_;
+  mutable int panel_rows_;
+};
+
+
+/* ========================================================================
+ * Multiplexer implementations.
+ *
+ * Extend MultiplexMapperBase and implement MapSinglePanel. You only have
+ * to worry about the mapping within a single panel, the overall panel
+ * construction with chains and parallel is already taken care of.
+ *
+ * Don't forget to register the new multiplexer sin CreateMultiplexMapperList()
+ * below. After that, the new mapper is available in the --led-multiplexing
+ * option.
+ */
+class StripeMultiplexMapper : public MultiplexMapperBase {
+public:
+  StripeMultiplexMapper() : MultiplexMapperBase("Stripe", 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const bool is_top_stripe = (y % (panel_rows_/2)) < panel_rows_/4;
+    *matrix_x = is_top_stripe ? x + panel_cols_ : x;
+    *matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+                 + y % (panel_rows_/4));
+  }
+};
+
+class FlippedStripeMultiplexMapper : public MultiplexMapperBase {
+public:
+  FlippedStripeMultiplexMapper() : MultiplexMapperBase("FlippedStripe", 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const bool is_top_stripe = (y % (panel_rows_/2)) >= panel_rows_/4;
+    *matrix_x = is_top_stripe ? x + panel_cols_ : x;
+    *matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+                 + y % (panel_rows_/4));
+  }
+};
+
+class CheckeredMultiplexMapper : public MultiplexMapperBase {
+public:
+  CheckeredMultiplexMapper() : MultiplexMapperBase("Checkered", 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const bool is_top_check = (y % (panel_rows_/2)) < panel_rows_/4;
+    const bool is_left_check = (x < panel_cols_/2);
+    if (is_top_check) {
+      *matrix_x = is_left_check ? x+panel_cols_/2 : x+panel_cols_;
+    } else {
+      *matrix_x = is_left_check ? x : x + panel_cols_/2;
+    }
+    *matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+                 + y % (panel_rows_/4));
+  }
+};
+
+class SpiralMultiplexMapper : public MultiplexMapperBase {
+public:
+  SpiralMultiplexMapper() : MultiplexMapperBase("Spiral", 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const bool is_top_stripe = (y % (panel_rows_/2)) < panel_rows_/4;
+    const int panel_quarter = panel_cols_/4;
+    const int quarter = x / panel_quarter;
+    const int offset = x % panel_quarter;
+    *matrix_x = ((2*quarter*panel_quarter)
+                 + (is_top_stripe
+                    ? panel_quarter - 1 - offset
+                    : panel_quarter + offset));
+    *matrix_y = ((y / (panel_rows_/2)) * (panel_rows_/4)
+                 + y % (panel_rows_/4));
+  }
+};
+
+class ZStripeMultiplexMapper : public MultiplexMapperBase {
+public:
+  ZStripeMultiplexMapper(const char *name, int even_vblock_offset, int odd_vblock_offset)
+  : MultiplexMapperBase(name, 2),
+    even_vblock_offset_(even_vblock_offset),
+    odd_vblock_offset_(odd_vblock_offset) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    static const int tile_width = 8;
+    static const int tile_height = 4;
+
+    const int vert_block_is_odd = ((y / tile_height) % 2);
+
+    const int even_vblock_shift = (1 - vert_block_is_odd) * even_vblock_offset_;
+    const int odd_vblock_shitf = vert_block_is_odd * odd_vblock_offset_;
+
+    *matrix_x = x + ((x + even_vblock_shift) / tile_width) * tile_width + odd_vblock_shitf;
+    *matrix_y = (y % tile_height) + tile_height * (y / (tile_height * 2));
+  }
+
+private:
+  const int even_vblock_offset_;
+  const int odd_vblock_offset_;
+};
+
+class CoremanMapper : public MultiplexMapperBase {
+public:
+  CoremanMapper() : MultiplexMapperBase("coreman", 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const bool is_left_check = (x < panel_cols_/2);
+
+    if ((y <= 7) || ((y >= 16) && (y <= 23))){
+      *matrix_x = ((x / (panel_cols_/2)) * panel_cols_) + (x % (panel_cols_/2));
+      if ((y & (panel_rows_/4)) == 0) {
+        *matrix_y = (y / (panel_rows_/2)) * (panel_rows_/4) + (y % (panel_rows_/4));
+      }
+    } else {
+      *matrix_x = is_left_check ? x + panel_cols_/2 : x + panel_cols_;
+      *matrix_y = (y / (panel_rows_/2)) * (panel_rows_/4) + y % (panel_rows_/4);
+    }
+  }
+};
+
+class Kaler2ScanMapper : public MultiplexMapperBase {
+public:
+  Kaler2ScanMapper() : MultiplexMapperBase("Kaler2Scan", 4) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    // Now we have a 128x4 matrix
+    int offset = ((y%4)/2) == 0 ? -1 : 1;// Add o substract
+    int deltaOffset = offset < 0 ? 7:8;
+    int deltaColumn = ((y%8)/4)== 0 ? 64 : 0;
+
+    *matrix_y = (y%2+(y/8)*2);
+    *matrix_x = deltaColumn + (16 * (x/8)) + deltaOffset + ((x%8) * offset);
+
+  }
+};
+
+class P10MapperZ : public MultiplexMapperBase {
+public:
+  P10MapperZ() : MultiplexMapperBase("P10-128x4-Z", 4) {}
+  // supports this panel: https://www.aliexpress.com/item/2017-Special-Offer-P10-Outdoor-Smd-Full-Color-Led-Display-Module-320x160mm-1-2-Scan-Outdoor/32809267439.html?spm=a2g0s.9042311.0.0.Ob0jEw
+  // with --led-row-addr-type=2 flag
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    int yComp = 0;
+    if (y == 0 || y == 1 || y == 8 || y == 9) {
+      yComp = 127;
+    }
+    else if (y == 2 || y == 3 || y == 10 || y == 11) {
+      yComp = 112;
+    }
+    else if (y == 4 || y == 5 || y == 12 || y == 13) {
+      yComp = 111;
+    }
+    else if (y == 6 || y == 7 || y == 14 || y == 15) {
+      yComp = 96;
+    }
+
+    if (y == 0 || y == 1 || y == 4 || y == 5 ||
+        y == 8 || y == 9 || y == 12 || y == 13) {
+      *matrix_x = yComp - x;
+      *matrix_x -= (24 * ((int)(x / 8)));
+    }
+    else {
+      *matrix_x = yComp + x;
+      *matrix_x -= (40 * ((int)(x / 8)));
+    }
+
+    if (y == 0 || y == 2 || y == 4 || y == 6) {
+      *matrix_y = 3;
+    }
+    else if (y == 1 || y == 3 || y == 5 || y == 7) {
+      *matrix_y = 2;
+    }
+    else if (y == 8 || y == 10 || y == 12 || y == 14) {
+      *matrix_y = 1;
+    }
+    else if (y == 9 || y == 11 || y == 13 || y == 15) {
+      *matrix_y = 0;
+    }
+  }
+};
+
+class QiangLiQ8 : public MultiplexMapperBase {
+public:
+  QiangLiQ8() : MultiplexMapperBase("QiangLiQ8", 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const int column = x + (4+ 4*(x/4));
+    *matrix_x = column;
+    if ((y >= 15 && y <=19) || (y >= 5 && y <= 9)) {
+      const int reverseColumn = x + (4*(x/4));
+      *matrix_x = reverseColumn;
+    }
+    *matrix_y = y % 5 + (y/10) *5;
+  }
+};
+
+class InversedZStripe : public MultiplexMapperBase {
+public:
+  InversedZStripe() : MultiplexMapperBase("InversedZStripe", 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    static const int tile_width = 8;
+    static const int tile_height = 4;
+
+    const int vert_block_is_odd = ((y / tile_height) % 2);
+    const int evenOffset[8] = {7, 5, 3, 1, -1, -3, -5, -7};
+
+    if (vert_block_is_odd) {
+      *matrix_x = x + (x / tile_width) * tile_width;
+    } else {
+      *matrix_x = x + (x / tile_width) * tile_width + 8 + evenOffset[x % 8];
+    }
+    *matrix_y = (y % tile_height) + tile_height * (y / (tile_height * 2));
+  }
+};
+
+
+/*
+ * Vairous P10 1R1G1B Outdoor implementations for 16x16 modules with separate
+ * RGB LEDs, e.g.:
+ * https://www.ledcontrollercard.com/english/p10-outdoor-rgb-led-module-160x160mm-dip.html
+ *
+ */
+class P10Outdoor1R1G1BMultiplexBase : public MultiplexMapperBase {
+public:
+  P10Outdoor1R1G1BMultiplexBase(const char *name)
+    : MultiplexMapperBase(name, 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const int vblock_is_odd = (y / tile_height_) % 2;
+    const int vblock_is_even = 1 - vblock_is_odd;
+    const int even_vblock_shift = vblock_is_even * even_vblock_offset_;
+    const int odd_vblock_shift = vblock_is_odd * odd_vblock_offset_;
+
+    MapPanel(x, y, matrix_x, matrix_y,
+             vblock_is_even, vblock_is_odd,
+             even_vblock_shift, odd_vblock_shift);
+  }
+
+protected:
+  virtual void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
+                        int vblock_is_even, int vblock_is_odd,
+                        int even_vblock_shift, int odd_vblock_shift) const = 0;
+
+  static const int tile_width_ = 8;
+  static const int tile_height_ = 4;
+  static const int even_vblock_offset_ = 0;
+  static const int odd_vblock_offset_ = 8;
+};
+
+class P10Outdoor1R1G1BMultiplexMapper1 : public P10Outdoor1R1G1BMultiplexBase {
+public:
+  P10Outdoor1R1G1BMultiplexMapper1()
+    : P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-1") {}
+
+protected:
+  void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
+                const int vblock_is_even, const int vblock_is_odd,
+                const int even_vblock_shift, const int odd_vblock_shift) const {
+    *matrix_x = tile_width_ * (1 + vblock_is_even + 2 * (x / tile_width_))
+      - (x % tile_width_) - 1;
+    *matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2));
+  }
+};
+
+class P10Outdoor1R1G1BMultiplexMapper2 : public P10Outdoor1R1G1BMultiplexBase {
+public:
+  P10Outdoor1R1G1BMultiplexMapper2()
+    : P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-2") {}
+
+protected:
+  void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
+                const int vblock_is_even, const int vblock_is_odd,
+                const int even_vblock_shift, const int odd_vblock_shift) const {
+    *matrix_x = vblock_is_even
+      ? tile_width_ * (1 + 2 * (x / tile_width_)) - (x % tile_width_) - 1
+      : x + ((x + even_vblock_shift) / tile_width_) * tile_width_ + odd_vblock_shift;
+    *matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2));
+  }
+};
+
+class P10Outdoor1R1G1BMultiplexMapper3 : public P10Outdoor1R1G1BMultiplexBase {
+public:
+  P10Outdoor1R1G1BMultiplexMapper3()
+    : P10Outdoor1R1G1BMultiplexBase("P10Outdoor1R1G1-3") {}
+
+protected:
+  void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
+                const int vblock_is_even, const int vblock_is_odd,
+                const int even_vblock_shift, const int odd_vblock_shift) const {
+    *matrix_x = vblock_is_odd
+      ? tile_width_ * (2 + 2 * (x / tile_width_)) - (x % tile_width_) - 1
+      : x + ((x + even_vblock_shift) / tile_width_) * tile_width_ + odd_vblock_shift;
+    *matrix_y = (y % tile_height_) + tile_height_ * (y / (tile_height_ * 2));
+  }
+};
+
+class P10CoremanMapper : public MultiplexMapperBase {
+public:
+  P10CoremanMapper() : MultiplexMapperBase("P10CoremanMapper", 4) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    //Row offset 8,8,8,8,0,0,0,0,8,8,8,8,0,0,0,0
+    int mulY = (y & 4) > 0 ? 0 : 8;
+
+    //Row offset 9,9,8,8,1,1,0,0,9,9,8,8,1,1,0,0
+    mulY += (y & 2) > 0 ? 0 : 1;
+    mulY += (x >> 2) & ~1; //Drop lsb
+
+    *matrix_x = (mulY << 3) + x % 8;
+    *matrix_y = (y & 1) + ((y >> 2) & ~1);
+  }
+};
+
+/*
+ * P8 1R1G1B Outdoor P8-5S-V3.2-HX 20x40
+ */
+class P8Outdoor1R1G1BMultiplexBase : public MultiplexMapperBase {
+public:
+  P8Outdoor1R1G1BMultiplexBase(const char *name)
+    : MultiplexMapperBase(name, 2) {}
+
+  void MapSinglePanel(int x, int y, int *matrix_x, int *matrix_y) const {
+    const int vblock_is_odd = (y / tile_height_) % 2;
+    const int vblock_is_even = 1 - vblock_is_odd;
+    const int even_vblock_shift = vblock_is_even * even_vblock_offset_;
+    const int odd_vblock_shift = vblock_is_odd * odd_vblock_offset_;
+
+    MapPanel(x, y, matrix_x, matrix_y,
+             vblock_is_even, vblock_is_odd,
+             even_vblock_shift, odd_vblock_shift);
+  }
+
+protected:
+  virtual void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
+                        int vblock_is_even, int vblock_is_odd,
+                        int even_vblock_shift, int odd_vblock_shift) const = 0;
+
+  static const int tile_width_ = 8;
+  static const int tile_height_ = 5;
+  static const int even_vblock_offset_ = 0;
+  static const int odd_vblock_offset_ = 8;
+};
+
+class P8Outdoor1R1G1BMultiplexMapper : public P8Outdoor1R1G1BMultiplexBase {
+public:
+  P8Outdoor1R1G1BMultiplexMapper()
+    : P8Outdoor1R1G1BMultiplexBase("P8Outdoor1R1G1") {}
+
+protected:
+  void MapPanel(int x, int y, int *matrix_x, int *matrix_y,
+                const int vblock_is_even, const int vblock_is_odd,
+                const int even_vblock_shift, const int odd_vblock_shift) const {
+
+
+    *matrix_x = vblock_is_even
+      ? tile_width_ * (1 + tile_width_ - 2 * (x / tile_width_)) + tile_width_ - (x % tile_width_) - 1
+      : tile_width_ * (1 + tile_width_ - 2 * (x / tile_width_)) - tile_width_ + (x % tile_width_);
+
+    *matrix_y = (tile_height_ - y % tile_height_) + tile_height_ * (1 - y / (tile_height_ * 2)) -1;
+
+  }
+};
+
+/*
+ * Here is where the registration happens.
+ * If you add an instance of the mapper here, it will automatically be
+ * made available in the --led-multiplexing commandline option.
+ */
+static MuxMapperList *CreateMultiplexMapperList() {
+  MuxMapperList *result = new MuxMapperList();
+
+  // Here, register all multiplex mappers from above.
+  result->push_back(new StripeMultiplexMapper());
+  result->push_back(new CheckeredMultiplexMapper());
+  result->push_back(new SpiralMultiplexMapper());
+  result->push_back(new ZStripeMultiplexMapper("ZStripe", 0, 8));
+  result->push_back(new ZStripeMultiplexMapper("ZnMirrorZStripe", 4, 4));
+  result->push_back(new CoremanMapper());
+  result->push_back(new Kaler2ScanMapper());
+  result->push_back(new ZStripeMultiplexMapper("ZStripeUneven", 8, 0));
+  result->push_back(new P10MapperZ());
+  result->push_back(new QiangLiQ8());
+  result->push_back(new InversedZStripe());
+  result->push_back(new P10Outdoor1R1G1BMultiplexMapper1());
+  result->push_back(new P10Outdoor1R1G1BMultiplexMapper2());
+  result->push_back(new P10Outdoor1R1G1BMultiplexMapper3());
+  result->push_back(new P10CoremanMapper());
+  result->push_back(new P8Outdoor1R1G1BMultiplexMapper());
+  result->push_back(new FlippedStripeMultiplexMapper());
+  return result;
+}
+
+const MuxMapperList &GetRegisteredMultiplexMappers() {
+  static const MuxMapperList *all_mappers = CreateMultiplexMapperList();
+  return *all_mappers;
+}
+}  // namespace internal
+}  // namespace rgb_matrix

+ 461 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/options-initialize.cc

@@ -0,0 +1,461 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013, 2016 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include "led-matrix.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <grp.h>
+#include <pwd.h>
+
+#include <vector>
+
+#include "multiplex-mappers-internal.h"
+#include "framebuffer-internal.h"
+
+#include "gpio.h"
+
+namespace rgb_matrix {
+RuntimeOptions::RuntimeOptions() :
+#ifdef RGB_SLOWDOWN_GPIO
+  gpio_slowdown(RGB_SLOWDOWN_GPIO),
+#else
+  gpio_slowdown(1),
+#endif
+  daemon(0),            // Don't become a daemon by default.
+  drop_privileges(1),   // Encourage good practice: drop privileges by default.
+  do_gpio_init(true)
+{
+  // Nothing to see here.
+}
+
+namespace {
+typedef char** argv_iterator;
+
+#define OPTION_PREFIX     "--led-"
+#define OPTION_PREFIX_LEN strlen(OPTION_PREFIX)
+
+static bool ConsumeBoolFlag(const char *flag_name, const argv_iterator &pos,
+                            bool *result_value) {
+  const char *option = *pos;
+  if (strncmp(option, OPTION_PREFIX, OPTION_PREFIX_LEN) != 0)
+    return false;
+  option += OPTION_PREFIX_LEN;
+  bool value_to_set = true;
+  if (strncmp(option, "no-", 3) == 0) {
+    value_to_set = false;
+    option += 3;
+  }
+  if (strcmp(option, flag_name) != 0)
+    return false;  // not consumed.
+  *result_value = value_to_set;
+  return true;
+}
+
+static bool ConsumeIntFlag(const char *flag_name,
+                           argv_iterator &pos, const argv_iterator end,
+                           int *result_value, int *error) {
+  const char *option = *pos;
+  if (strncmp(option, OPTION_PREFIX, OPTION_PREFIX_LEN) != 0)
+    return false;
+  option += OPTION_PREFIX_LEN;
+  const size_t flag_len = strlen(flag_name);
+  if (strncmp(option, flag_name, flag_len) != 0)
+    return false;  // not consumed.
+  const char *value;
+  if (option[flag_len] == '=')  // --option=42  # value in same arg
+    value = option + flag_len + 1;
+  else if (pos + 1 < end) {     // --option 42  # value in next arg
+    value = *(++pos);
+  } else {
+    fprintf(stderr, "Parameter expected after %s%s\n",
+            OPTION_PREFIX, flag_name);
+    ++*error;
+    return true;  // consumed, but error.
+  }
+  char *end_value = NULL;
+  int val = strtol(value, &end_value, 10);
+  if (!*value || *end_value) {
+    fprintf(stderr, "Couldn't parse parameter %s%s=%s "
+            "(Expected decimal number but '%s' looks funny)\n",
+            OPTION_PREFIX, flag_name, value, end_value);
+    ++*error;
+    return true;  // consumed, but error
+  }
+  *result_value = val;
+  return true;  // consumed.
+}
+
+// The resulting value is allocated.
+static bool ConsumeStringFlag(const char *flag_name,
+                              argv_iterator &pos, const argv_iterator end,
+                              const char **result_value, int *error) {
+  const char *option = *pos;
+  if (strncmp(option, OPTION_PREFIX, OPTION_PREFIX_LEN) != 0)
+    return false;
+  option += OPTION_PREFIX_LEN;
+  const size_t flag_len = strlen(flag_name);
+  if (strncmp(option, flag_name, flag_len) != 0)
+    return false;  // not consumed.
+  const char *value;
+  if (option[flag_len] == '=')  // --option=hello  # value in same arg
+    value = option + flag_len + 1;
+  else if (pos + 1 < end) {     // --option hello  # value in next arg
+    value = *(++pos);
+  } else {
+    fprintf(stderr, "Parameter expected after %s%s\n",
+            OPTION_PREFIX, flag_name);
+    ++*error;
+    *result_value = NULL;
+    return true;  // consumed, but error.
+  }
+  *result_value = strdup(value);  // This will leak, but no big deal.
+  return true;
+}
+
+static bool FlagInit(int &argc, char **&argv,
+                     RGBMatrix::Options *mopts,
+                     RuntimeOptions *ropts,
+                     bool remove_consumed_options) {
+  argv_iterator it = &argv[0];
+  argv_iterator end = it + argc;
+
+  std::vector<char*> unused_options;
+  unused_options.push_back(*it++);  // Not interested in program name
+
+  bool bool_scratch;
+  int err = 0;
+  bool posix_end_option_seen = false;  // end of options '--'
+  for (/**/; it < end; ++it) {
+    posix_end_option_seen |= (strcmp(*it, "--") == 0);
+    if (!posix_end_option_seen) {
+      if (ConsumeStringFlag("gpio-mapping", it, end,
+                            &mopts->hardware_mapping, &err))
+        continue;
+      if (ConsumeStringFlag("rgb-sequence", it, end,
+                            &mopts->led_rgb_sequence, &err))
+        continue;
+      if (ConsumeStringFlag("pixel-mapper", it, end,
+                            &mopts->pixel_mapper_config, &err))
+        continue;
+      if (ConsumeStringFlag("panel-type", it, end,
+                            &mopts->panel_type, &err))
+        continue;
+      if (ConsumeIntFlag("rows", it, end, &mopts->rows, &err))
+        continue;
+      if (ConsumeIntFlag("cols", it, end, &mopts->cols, &err))
+        continue;
+      if (ConsumeIntFlag("chain", it, end, &mopts->chain_length, &err))
+        continue;
+      if (ConsumeIntFlag("parallel", it, end, &mopts->parallel, &err))
+        continue;
+      if (ConsumeIntFlag("multiplexing", it, end, &mopts->multiplexing, &err))
+        continue;
+      if (ConsumeIntFlag("brightness", it, end, &mopts->brightness, &err))
+        continue;
+      if (ConsumeIntFlag("scan-mode", it, end, &mopts->scan_mode, &err))
+        continue;
+      if (ConsumeIntFlag("pwm-bits", it, end, &mopts->pwm_bits, &err))
+        continue;
+      if (ConsumeIntFlag("pwm-lsb-nanoseconds", it, end,
+                         &mopts->pwm_lsb_nanoseconds, &err))
+        continue;
+      if (ConsumeIntFlag("pwm-dither-bits", it, end,
+                         &mopts->pwm_dither_bits, &err))
+        continue;
+      if (ConsumeIntFlag("row-addr-type", it, end,
+                         &mopts->row_address_type, &err))
+        continue;
+      if (ConsumeIntFlag("limit-refresh", it, end,
+                         &mopts->limit_refresh_rate_hz, &err))
+        continue;
+      if (ConsumeBoolFlag("show-refresh", it, &mopts->show_refresh_rate))
+        continue;
+      if (ConsumeBoolFlag("inverse", it, &mopts->inverse_colors))
+        continue;
+      // We don't have a swap_green_blue option anymore, but we simulate the
+      // flag for a while.
+      bool swap_green_blue;
+      if (ConsumeBoolFlag("swap-green-blue", it, &swap_green_blue)) {
+        if (strlen(mopts->led_rgb_sequence) == 3) {
+          char *new_sequence = strdup(mopts->led_rgb_sequence);
+          new_sequence[0] = mopts->led_rgb_sequence[0];
+          new_sequence[1] = mopts->led_rgb_sequence[2];
+          new_sequence[2] = mopts->led_rgb_sequence[1];
+          mopts->led_rgb_sequence = new_sequence;  // leaking. Ignore.
+        }
+        continue;
+      }
+      bool allow_hardware_pulsing = !mopts->disable_hardware_pulsing;
+      if (ConsumeBoolFlag("hardware-pulse", it, &allow_hardware_pulsing)) {
+        mopts->disable_hardware_pulsing = !allow_hardware_pulsing;
+        continue;
+      }
+
+      bool request_help = false;
+      if (ConsumeBoolFlag("help", it, &request_help) && request_help) {
+        // In that case, we pretend to have failure in parsing, which will
+        // trigger printing the usage(). Typically :)
+        return false;
+      }
+
+      //-- Runtime options.
+      if (ConsumeIntFlag("slowdown-gpio", it, end, &ropts->gpio_slowdown, &err))
+        continue;
+      if (ropts->daemon >= 0 && ConsumeBoolFlag("daemon", it, &bool_scratch)) {
+        ropts->daemon = bool_scratch ? 1 : 0;
+        continue;
+      }
+      if (ropts->drop_privileges >= 0 &&
+          ConsumeBoolFlag("drop-privs", it, &bool_scratch)) {
+        ropts->drop_privileges = bool_scratch ? 1 : 0;
+        continue;
+      }
+      if (strncmp(*it, OPTION_PREFIX, OPTION_PREFIX_LEN) == 0) {
+        fprintf(stderr, "Option %s starts with %s but it is unknown. Typo?\n",
+                *it, OPTION_PREFIX);
+      }
+    }
+    unused_options.push_back(*it);
+  }
+
+  if (err > 0) {
+    return false;
+  }
+
+  if (remove_consumed_options) {
+    // Success. Re-arrange flags to only include the ones not consumed.
+    argc = (int) unused_options.size();
+    for (int i = 0; i < argc; ++i) {
+      argv[i] = unused_options[i];
+    }
+  }
+  return true;
+}
+
+}  // anonymous namespace
+
+bool ParseOptionsFromFlags(int *argc, char ***argv,
+                           RGBMatrix::Options *m_opt_in,
+                           RuntimeOptions *rt_opt_in,
+                           bool remove_consumed_options) {
+  if (argc == NULL || argv == NULL) {
+    fprintf(stderr, "Called ParseOptionsFromFlags() without argc/argv\n");
+    return false;
+  }
+  // Replace NULL arguments with some scratch-space.
+  RGBMatrix::Options scratch_matrix;
+  RGBMatrix::Options *mopt = (m_opt_in != NULL) ? m_opt_in : &scratch_matrix;
+
+  RuntimeOptions scratch_rt;
+  RuntimeOptions *ropt = (rt_opt_in != NULL) ? rt_opt_in : &scratch_rt;
+
+  return FlagInit(*argc, *argv, mopt, ropt, remove_consumed_options);
+}
+
+static std::string CreateAvailableMultiplexString(
+  const internal::MuxMapperList &m) {
+  std::string result;
+  char buffer[256];
+  for (size_t i = 0; i < m.size(); ++i) {
+    if (i != 0) result.append("; ");
+    snprintf(buffer, sizeof(buffer), "%d=%s", (int) i+1, m[i]->GetName());
+    result.append(buffer);
+  }
+  return result;
+}
+
+void PrintMatrixFlags(FILE *out, const RGBMatrix::Options &d,
+                      const RuntimeOptions &r) {
+  const internal::MuxMapperList &muxers
+    = internal::GetRegisteredMultiplexMappers();
+
+  std::vector<std::string> mapper_names = GetAvailablePixelMappers();
+  std::string available_mappers;
+  for (size_t i = 0; i < mapper_names.size(); ++i) {
+    if (i != 0) available_mappers.append(", ");
+    available_mappers.append("\"").append(mapper_names[i]).append("\"");
+  }
+
+  fprintf(out,
+          "\t--led-gpio-mapping=<name> : Name of GPIO mapping used. Default \"%s\"\n"
+          "\t--led-rows=<rows>         : Panel rows. Typically 8, 16, 32 or 64."
+          " (Default: %d).\n"
+          "\t--led-cols=<cols>         : Panel columns. Typically 32 or 64. "
+          "(Default: %d).\n"
+          "\t--led-chain=<chained>     : Number of daisy-chained panels. "
+          "(Default: %d).\n"
+          "\t--led-parallel=<parallel> : Parallel chains. range=1..3 "
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+          "(6 for CM3) "
+#endif
+          "(Default: %d).\n"
+          "\t--led-multiplexing=<0..%d> : Mux type: 0=direct; %s (Default: 0)\n"
+          "\t--led-pixel-mapper        : Semicolon-separated list of pixel-mappers to arrange pixels.\n"
+          "\t                            Optional params after a colon e.g. \"U-mapper;Rotate:90\"\n"
+          "\t                            Available: %s. Default: \"\"\n"
+          "\t--led-pwm-bits=<1..%d>    : PWM bits (Default: %d).\n"
+          "\t--led-brightness=<percent>: Brightness in percent (Default: %d).\n"
+          "\t--led-scan-mode=<0..1>    : 0 = progressive; 1 = interlaced "
+          "(Default: %d).\n"
+          "\t--led-row-addr-type=<0..4>: 0 = default; 1 = AB-addressed panels; 2 = direct row select; 3 = ABC-addressed panels; 4 = ABC Shift + DE direct "
+          "(Default: 0).\n"
+          "\t--led-%sshow-refresh        : %show refresh rate.\n"
+          "\t--led-limit-refresh=<Hz>  : Limit refresh rate to this frequency in Hz. Useful to keep a\n"
+          "\t                            constant refresh rate on loaded system. 0=no limit. Default: %d\n"
+          "\t--led-%sinverse             "
+          ": Switch if your matrix has inverse colors %s.\n"
+          "\t--led-rgb-sequence        : Switch if your matrix has led colors "
+          "swapped (Default: \"RGB\")\n"
+          "\t--led-pwm-lsb-nanoseconds : PWM Nanoseconds for LSB "
+          "(Default: %d)\n"
+          "\t--led-pwm-dither-bits=<0..2> : Time dithering of lower bits "
+          "(Default: 0)\n"
+          "\t--led-%shardware-pulse   : %sse hardware pin-pulse generation.\n"
+          "\t--led-panel-type=<name>   : Needed to initialize special panels. Supported: 'FM6126A', 'FM6127'\n",
+          d.hardware_mapping,
+          d.rows, d.cols, d.chain_length, d.parallel,
+          (int) muxers.size(), CreateAvailableMultiplexString(muxers).c_str(),
+          available_mappers.c_str(),
+          internal::Framebuffer::kBitPlanes, d.pwm_bits,
+          d.brightness, d.scan_mode,
+          d.show_refresh_rate ? "no-" : "", d.show_refresh_rate ? "Don't s" : "S",
+          d.limit_refresh_rate_hz,
+          d.inverse_colors ? "no-" : "",    d.inverse_colors ? "off" : "on",
+          d.pwm_lsb_nanoseconds,
+          !d.disable_hardware_pulsing ? "no-" : "",
+          !d.disable_hardware_pulsing ? "Don't u" : "U");
+
+  fprintf(out, "\t--led-slowdown-gpio=<0..4>: "
+          "Slowdown GPIO. Needed for faster Pis/slower panels "
+          "(Default: %d).\n", r.gpio_slowdown);
+  if (r.daemon >= 0) {
+    const bool on = (r.daemon > 0);
+    fprintf(out,
+            "\t--led-%sdaemon              : "
+            "%sake the process run in the background as daemon.\n",
+            on ? "no-" : "", on ? "Don't m" : "M");
+  }
+  if (r.drop_privileges >= 0) {
+    const bool on = (r.drop_privileges > 0);
+    fprintf(out,
+            "\t--led-%sdrop-privs       : %srop privileges from 'root' "
+            "after initializing the hardware.\n",
+            on ? "no-" : "", on ? "Don't d" : "D");
+  }
+}
+
+bool RGBMatrix::Options::Validate(std::string *err_in) const {
+  std::string scratch;
+  std::string *err = err_in ? err_in : &scratch;
+  bool success = true;
+  if (rows < 8 || rows > 64 || rows % 2 != 0) {
+    err->append("Invalid number or rows per panel (--led-rows). "
+                "Should be in range of [8..64] and divisible by 2.\n");
+    success = false;
+  }
+
+  if (cols < 16) {
+    err->append("Invlid number of columns for panel (--led-cols). "
+                "Typically that is something like 32 or 64\n");
+    success = false;
+  }
+
+  if (chain_length < 1) {
+    err->append("Chain-length outside usable range.\n");
+    success = false;
+  }
+
+  const internal::MuxMapperList &muxers
+    = internal::GetRegisteredMultiplexMappers();
+  if (multiplexing < 0 || multiplexing > (int)muxers.size()) {
+    err->append("Multiplexing can only be one of 0=normal; ")
+      .append(CreateAvailableMultiplexString(muxers));
+    success = false;
+  }
+
+  if (row_address_type < 0 || row_address_type > 4) {
+    err->append("Row address type values can be 0 (default), 1 (AB addressing), 2 (direct row select), 3 (ABC address), 4 (ABC Shift + DE direct).\n");
+    success = false;
+  }
+
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+  const bool is_cm = (strcmp(hardware_mapping, "compute-module") == 0);
+#else
+  const bool is_cm = false;
+#endif
+  if (parallel < 1 || parallel > (is_cm ? 6 : 3)) {
+    err->append("Parallel outside usable range (1..3 allowed"
+#ifdef ENABLE_WIDE_GPIO_COMPUTE_MODULE
+                ", up to 6 only for CM3"
+#endif
+                ").\n");
+    success = false;
+  }
+
+  if (brightness < 1 || brightness > 100) {
+    err->append("Brightness outside usable range (Percent 1..100 allowed).\n");
+    success = false;
+  }
+
+  if (pwm_bits <= 0 || pwm_bits > internal::Framebuffer::kBitPlanes) {
+    char buffer[256];
+    snprintf(buffer, sizeof(buffer),
+             "Invalid range of pwm-bits (1..%d allowed).\n",
+             internal::Framebuffer::kBitPlanes);
+    err->append(buffer);
+    success = false;
+  }
+
+  if (scan_mode < 0 || scan_mode > 1) {
+    err->append("Invalid scan mode (0 or 1 allowed).\n");
+    success = false;
+  }
+
+  if (pwm_lsb_nanoseconds < 50 || pwm_lsb_nanoseconds > 3000) {
+    err->append("Invalid range of pwm-lsb-nanoseconds (50..3000 allowed).\n");
+    success = false;
+  }
+
+  if (pwm_dither_bits < 0 || pwm_dither_bits > 2) {
+    err->append("Inavlid range of pwm-dither-bits (0..2 allowed).\n");
+    success = false;
+  }
+
+  if (led_rgb_sequence == NULL || strlen(led_rgb_sequence) != 3) {
+    err->append("led-sequence needs to be three characters long.\n");
+    success = false;
+  } else {
+    if ((!strchr(led_rgb_sequence, 'R') && !strchr(led_rgb_sequence, 'r'))
+        || (!strchr(led_rgb_sequence, 'G') && !strchr(led_rgb_sequence, 'g'))
+        || (!strchr(led_rgb_sequence, 'B') && !strchr(led_rgb_sequence, 'b'))) {
+      err->append("led-sequence needs to contain all of letters 'R', 'G' "
+                  "and 'B'\n");
+      success = false;
+    }
+  }
+
+  if (!success && !err_in) {
+    // If we didn't get a string to write to, we write things to stderr.
+    fprintf(stderr, "%s", err->c_str());
+  }
+
+  return success;
+}
+
+}  // namespace rgb_matrix

+ 338 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/pixel-mapper.cc

@@ -0,0 +1,338 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2018 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include "pixel-mapper.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <map>
+
+namespace rgb_matrix {
+namespace {
+
+class RotatePixelMapper : public PixelMapper {
+public:
+  RotatePixelMapper() : angle_(0) {}
+
+  virtual const char *GetName() const { return "Rotate"; }
+
+  virtual bool SetParameters(int chain, int parallel, const char *param) {
+    if (param == NULL || strlen(param) == 0) {
+      angle_ = 0;
+      return true;
+    }
+    char *errpos;
+    const int angle = strtol(param, &errpos, 10);
+    if (*errpos != '\0') {
+      fprintf(stderr, "Invalid rotate parameter '%s'\n", param);
+      return false;
+    }
+    if (angle % 90 != 0) {
+      fprintf(stderr, "Rotation needs to be multiple of 90 degrees\n");
+      return false;
+    }
+    angle_ = (angle + 360) % 360;
+    return true;
+  }
+
+  virtual bool GetSizeMapping(int matrix_width, int matrix_height,
+                              int *visible_width, int *visible_height)
+    const {
+    if (angle_ % 180 == 0) {
+      *visible_width = matrix_width;
+      *visible_height = matrix_height;
+    } else {
+      *visible_width = matrix_height;
+      *visible_height = matrix_width;
+    }
+    return true;
+  }
+
+  virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
+                                  int x, int y,
+                                  int *matrix_x, int *matrix_y) const {
+    switch (angle_) {
+    case 0:
+      *matrix_x = x;
+      *matrix_y = y;
+      break;
+    case 90:
+      *matrix_x = matrix_width - y - 1;
+      *matrix_y = x;
+      break;
+    case 180:
+      *matrix_x = matrix_width - x - 1;
+      *matrix_y = matrix_height - y - 1;
+      break;
+    case 270:
+      *matrix_x = y;
+      *matrix_y = matrix_height - x - 1;
+      break;
+    }
+  }
+
+private:
+  int angle_;
+};
+
+class MirrorPixelMapper : public PixelMapper {
+public:
+  MirrorPixelMapper() : horizontal_(true) {}
+
+  virtual const char *GetName() const { return "Mirror"; }
+
+  virtual bool SetParameters(int chain, int parallel, const char *param) {
+    if (param == NULL || strlen(param) == 0) {
+      horizontal_ = true;
+      return true;
+    }
+    if (strlen(param) != 1) {
+      fprintf(stderr, "Mirror parameter should be a single "
+              "character:'V' or 'H'\n");
+    }
+    switch (*param) {
+    case 'V':
+    case 'v':
+      horizontal_ = false;
+      break;
+    case 'H':
+    case 'h':
+      horizontal_ = true;
+      break;
+    default:
+      fprintf(stderr, "Mirror parameter should be either 'V' or 'H'\n");
+      return false;
+    }
+    return true;
+  }
+
+  virtual bool GetSizeMapping(int matrix_width, int matrix_height,
+                              int *visible_width, int *visible_height)
+    const {
+    *visible_height = matrix_height;
+    *visible_width = matrix_width;
+    return true;
+  }
+
+  virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
+                                  int x, int y,
+                                  int *matrix_x, int *matrix_y) const {
+    if (horizontal_) {
+      *matrix_x = matrix_width - 1 - x;
+      *matrix_y = y;
+    } else {
+      *matrix_x = x;
+      *matrix_y = matrix_height - 1 - y;
+    }
+  }
+
+private:
+  bool horizontal_;
+};
+
+// If we take a long chain of panels and arrange them in a U-shape, so
+// that after half the panels we bend around and continue below. This way
+// we have a panel that has double the height but only uses one chain.
+// A single chain display with four 32x32 panels can then be arranged in this
+// 64x64 display:
+//    [<][<][<][<] }- Raspbery Pi connector
+//
+// can be arranged in this U-shape
+//    [<][<] }----- Raspberry Pi connector
+//    [>][>]
+//
+// This works for more than one chain as well. Here an arrangement with
+// two chains with 8 panels each
+//   [<][<][<][<]  }-- Pi connector #1
+//   [>][>][>][>]
+//   [<][<][<][<]  }--- Pi connector #2
+//   [>][>][>][>]
+class UArrangementMapper : public PixelMapper {
+public:
+  UArrangementMapper() : parallel_(1) {}
+
+  virtual const char *GetName() const { return "U-mapper"; }
+
+  virtual bool SetParameters(int chain, int parallel, const char *param) {
+    if (chain < 2) {  // technically, a chain of 2 would work, but somewhat pointless
+      fprintf(stderr, "U-mapper: need at least --led-chain=4 for useful folding\n");
+      return false;
+    }
+    if (chain % 2 != 0) {
+      fprintf(stderr, "U-mapper: Chain (--led-chain) needs to be divisible by two\n");
+      return false;
+    }
+    parallel_ = parallel;
+    return true;
+  }
+
+  virtual bool GetSizeMapping(int matrix_width, int matrix_height,
+                              int *visible_width, int *visible_height)
+    const {
+    *visible_width = (matrix_width / 64) * 32;   // Div at 32px boundary
+    *visible_height = 2 * matrix_height;
+    if (matrix_height % parallel_ != 0) {
+      fprintf(stderr, "%s For parallel=%d we would expect the height=%d "
+              "to be divisible by %d ??\n",
+              GetName(), parallel_, matrix_height, parallel_);
+      return false;
+    }
+    return true;
+  }
+
+  virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
+                                  int x, int y,
+                                  int *matrix_x, int *matrix_y) const {
+    const int panel_height = matrix_height / parallel_;
+    const int visible_width = (matrix_width / 64) * 32;
+    const int slab_height = 2 * panel_height;   // one folded u-shape
+    const int base_y = (y / slab_height) * panel_height;
+    y %= slab_height;
+    if (y < panel_height) {
+      x += matrix_width / 2;
+    } else {
+      x = visible_width - x - 1;
+      y = slab_height - y - 1;
+    }
+    *matrix_x = x;
+    *matrix_y = base_y + y;
+  }
+
+private:
+  int parallel_;
+};
+
+
+
+class VerticalMapper : public PixelMapper {
+public:
+  VerticalMapper() {}
+
+  virtual const char *GetName() const { return "V-mapper"; }
+
+  virtual bool SetParameters(int chain, int parallel, const char *param) {
+    chain_ = chain;
+    parallel_ = parallel;
+    // optional argument :Z allow for every other panel to be flipped
+    // upside down so that cabling can be shorter:
+    // [ O < I ]   without Z       [ O < I  ]
+    //   ,---^      <----                ^
+    // [ O < I ]                   [ I > O  ]
+    //   ,---^            with Z     ^
+    // [ O < I ]            --->   [ O < I  ]
+    z_ = (param && strcasecmp(param, "Z") == 0);
+    return true;
+  }
+
+  virtual bool GetSizeMapping(int matrix_width, int matrix_height,
+                              int *visible_width, int *visible_height)
+    const {
+    *visible_width = matrix_width * parallel_ / chain_;
+    *visible_height = matrix_height * chain_ / parallel_;
+#if 0
+     fprintf(stderr, "%s: C:%d P:%d. Turning W:%d H:%d Physical "
+	     "into W:%d H:%d Virtual\n",
+             GetName(), chain_, parallel_,
+	     *visible_width, *visible_height, matrix_width, matrix_height);
+#endif
+    return true;
+  }
+
+  virtual void MapVisibleToMatrix(int matrix_width, int matrix_height,
+                                  int x, int y,
+                                  int *matrix_x, int *matrix_y) const {
+    const int panel_width  = matrix_width  / chain_;
+    const int panel_height = matrix_height / parallel_;
+    const int x_panel_start = y / panel_height * panel_width;
+    const int y_panel_start = x / panel_width * panel_height;
+    const int x_within_panel = x % panel_width;
+    const int y_within_panel = y % panel_height;
+    const bool needs_flipping = z_ && (y / panel_height) % 2 == 1;
+    *matrix_x = x_panel_start + (needs_flipping
+                                 ? panel_width - 1 - x_within_panel
+                                 : x_within_panel);
+    *matrix_y = y_panel_start + (needs_flipping
+                                 ? panel_height - 1 - y_within_panel
+                                 : y_within_panel);
+  }
+
+private:
+  bool z_;
+  int chain_;
+  int parallel_;
+};
+
+
+typedef std::map<std::string, PixelMapper*> MapperByName;
+static void RegisterPixelMapperInternal(MapperByName *registry,
+                                        PixelMapper *mapper) {
+  assert(mapper != NULL);
+  std::string lower_name;
+  for (const char *n = mapper->GetName(); *n; n++)
+    lower_name.append(1, tolower(*n));
+  (*registry)[lower_name] = mapper;
+}
+
+static MapperByName *CreateMapperMap() {
+  MapperByName *result = new MapperByName();
+
+  // Register all the default PixelMappers here.
+  RegisterPixelMapperInternal(result, new RotatePixelMapper());
+  RegisterPixelMapperInternal(result, new UArrangementMapper());
+  RegisterPixelMapperInternal(result, new VerticalMapper());
+  RegisterPixelMapperInternal(result, new MirrorPixelMapper());
+  return result;
+}
+
+static MapperByName *GetMapperMap() {
+  static MapperByName *singleton_instance = CreateMapperMap();
+  return singleton_instance;
+}
+}  // anonymous namespace
+
+// Public API.
+void RegisterPixelMapper(PixelMapper *mapper) {
+  RegisterPixelMapperInternal(GetMapperMap(), mapper);
+}
+
+std::vector<std::string> GetAvailablePixelMappers() {
+  std::vector<std::string> result;
+  MapperByName *m = GetMapperMap();
+  for (MapperByName::const_iterator it = m->begin(); it != m->end(); ++it) {
+    result.push_back(it->second->GetName());
+  }
+  return result;
+}
+
+const PixelMapper *FindPixelMapper(const char *name,
+                                   int chain, int parallel,
+                                   const char *parameter) {
+  std::string lower_name;
+  for (const char *n = name; *n; n++) lower_name.append(1, tolower(*n));
+  MapperByName::const_iterator found = GetMapperMap()->find(lower_name);
+  if (found == GetMapperMap()->end()) {
+    fprintf(stderr, "%s: no such mapper\n", name);
+    return NULL;
+  }
+  PixelMapper *mapper = found->second;
+  if (mapper == NULL) return NULL;  // should not happen.
+  if (!mapper->SetParameters(chain, parallel, parameter))
+    return NULL;   // Got parameter, but couldn't deal with it.
+  return mapper;
+}
+}  // namespace rgb_matrix

+ 100 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/thread.cc

@@ -0,0 +1,100 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include "thread.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace rgb_matrix {
+void *Thread::PthreadCallRun(void *tobject) {
+  reinterpret_cast<Thread*>(tobject)->Run();
+  return NULL;
+}
+
+Thread::Thread() : started_(false) {}
+Thread::~Thread() {
+  WaitStopped();
+}
+
+void Thread::WaitStopped() {
+  if (!started_) return;
+  int result = pthread_join(thread_, NULL);
+  if (result != 0) {
+    perror("Issue joining thread");
+  }
+  started_ = false;
+}
+
+void Thread::Start(int priority, uint32_t affinity_mask) {
+  assert(!started_);  // Did you call WaitStopped() ?
+  pthread_create(&thread_, NULL, &PthreadCallRun, this);
+  int err;
+
+  if (priority > 0) {
+    struct sched_param p;
+    p.sched_priority = priority;
+    if ((err = pthread_setschedparam(thread_, SCHED_FIFO, &p))) {
+      char buffer[PATH_MAX];
+      const char *bin = realpath("/proc/self/exe", buffer);  // Linux specific.
+      fprintf(stderr, "Can't set realtime thread priority=%d: %s.\n"
+              "\tYou are probably not running as root ?\n"
+              "\tThis will seriously mess with color stability and flicker\n"
+              "\tof the matrix. Please run as `root` (e.g. by invoking this\n"
+              "\tprogram with `sudo`), or setting the capability on this\n"
+              "\tbinary by calling\n"
+              "\tsudo setcap 'cap_sys_nice=eip' %s\n",
+              p.sched_priority, strerror(err), bin ? bin : "<this binary>");
+    }
+  }
+
+  if (affinity_mask != 0) {
+    cpu_set_t cpu_mask;
+    CPU_ZERO(&cpu_mask);
+    for (int i = 0; i < 32; ++i) {
+      if ((affinity_mask & (1<<i)) != 0) {
+        CPU_SET(i, &cpu_mask);
+      }
+    }
+    if ((err=pthread_setaffinity_np(thread_, sizeof(cpu_mask), &cpu_mask))) {
+      // On a Pi1, this won't work as there is only one core. Don't worry in
+      // that case.
+    }
+  }
+
+  started_ = true;
+}
+
+bool Mutex::WaitOn(pthread_cond_t *cond, long timeout_ms) {
+  if (timeout_ms < 0) {
+    pthread_cond_wait(cond, &mutex_);
+    return true;
+  }
+  else {
+    struct timespec t;
+    clock_gettime(CLOCK_REALTIME, &t);
+    t.tv_sec += timeout_ms / 1000;
+    t.tv_nsec += (timeout_ms % 1000) * 1000000;
+    t.tv_sec += t.tv_nsec / 1000000000;
+    t.tv_nsec %= 1000000000;
+    // TODO(hzeller): It doesn't seem we return with EINTR on signal. We should.
+    return pthread_cond_timedwait(cond, &mutex_, &t) == 0;
+  }
+}
+}  // namespace rgb_matrix

+ 243 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/transformer.cc

@@ -0,0 +1,243 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2014 Henner Zeller <h.zeller@acm.org>
+// Copyright (C) 2015 Christoph Friedrich <christoph.friedrich@vonaffenfels.de>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+/*
+ * NOTE:
+ *
+ * Transformers are deprecated. For the kind of mappings they were be
+ * used by they turned out to be too complicated.
+ *
+ * They have been superseeded by the simpler PixelMapper, see pixel-mapper.h
+ */
+
+#ifndef REMOVE_DEPRECATED_TRANSFORMERS
+#include <assert.h>
+#include <stdio.h>
+
+#include "transformer.h"
+
+namespace rgb_matrix {
+
+/*****************************/
+/* Rotate Transformer Canvas */
+/*****************************/
+class RotateTransformer::TransformCanvas : public Canvas {
+public:
+  TransformCanvas(int angle);
+
+  void SetDelegatee(Canvas* delegatee);
+  void SetAngle(int angle);
+
+  virtual int width() const;
+  virtual int height() const;
+  virtual void SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue);
+  virtual void Clear();
+  virtual void Fill(uint8_t red, uint8_t green, uint8_t blue);
+
+private:
+  Canvas *delegatee_;
+  int angle_;
+};
+
+RotateTransformer::TransformCanvas::TransformCanvas(int angle)
+  : delegatee_(NULL) {
+  SetAngle(angle);
+}
+
+void RotateTransformer::TransformCanvas::SetDelegatee(Canvas* delegatee) {
+  delegatee_ = delegatee;
+}
+
+void RotateTransformer::TransformCanvas::SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue) {
+  switch (angle_) {
+  case 0:
+    delegatee_->SetPixel(x, y, red, green, blue);
+    break;
+  case 90:
+    delegatee_->SetPixel(delegatee_->width() - y - 1, x,
+                         red, green, blue);
+    break;
+  case 180:
+    delegatee_->SetPixel(delegatee_->width() - x - 1,
+                         delegatee_->height() - y - 1,
+                         red, green, blue);
+    break;
+  case 270:
+    delegatee_->SetPixel(y, delegatee_->height() - x - 1, red, green, blue);
+    break;
+  }
+}
+
+int RotateTransformer::TransformCanvas::width() const {
+  return (angle_ % 180 == 0) ? delegatee_->width() : delegatee_->height();
+}
+
+int RotateTransformer::TransformCanvas::height() const {
+  return (angle_ % 180 == 0) ? delegatee_->height() : delegatee_->width();
+}
+
+void RotateTransformer::TransformCanvas::Clear() {
+  delegatee_->Clear();
+}
+
+void RotateTransformer::TransformCanvas::Fill(uint8_t red, uint8_t green, uint8_t blue) {
+  delegatee_->Fill(red, green, blue);
+}
+
+void RotateTransformer::TransformCanvas::SetAngle(int angle) {
+  assert(angle % 90 == 0);  // We currenlty enforce that for more pretty output
+  angle_ = (angle + 360) % 360;
+}
+
+/**********************/
+/* Rotate Transformer */
+/**********************/
+RotateTransformer::RotateTransformer(int angle)
+  : angle_(angle), canvas_(new TransformCanvas(angle)) {
+}
+
+RotateTransformer::~RotateTransformer() {
+  delete canvas_;
+}
+
+Canvas *RotateTransformer::Transform(Canvas *output) {
+  assert(output != NULL);
+
+  canvas_->SetDelegatee(output);
+  return canvas_;
+}
+
+void RotateTransformer::SetAngle(int angle) {
+  canvas_->SetAngle(angle);
+  angle_ = angle;
+}
+
+/**********************/
+/* Linked Transformer */
+/**********************/
+void LinkedTransformer::AddTransformer(CanvasTransformer *transformer) {
+  list_.push_back(transformer);
+}
+
+void LinkedTransformer::AddTransformer(List transformer_list) {
+  list_.insert(list_.end(), transformer_list.begin(), transformer_list.end());
+}
+void LinkedTransformer::SetTransformer(List transformer_list) {
+  list_ = transformer_list;
+}
+
+Canvas *LinkedTransformer::Transform(Canvas *output) {
+  for (size_t i = 0; i < list_.size(); ++i) {
+    output = list_[i]->Transform(output);
+  }
+
+  return output;
+}
+
+void LinkedTransformer::DeleteTransformers() {
+  for (size_t i = 0; i < list_.size(); ++i) {
+    delete list_[i];
+  }
+  list_.clear();
+}
+
+// U-Arrangement Transformer.
+class UArrangementTransformer::TransformCanvas : public Canvas {
+public:
+  TransformCanvas(int parallel) : parallel_(parallel), delegatee_(NULL) {}
+
+  void SetDelegatee(Canvas* delegatee);
+
+  virtual void Clear();
+  virtual void Fill(uint8_t red, uint8_t green, uint8_t blue);
+  virtual int width() const { return width_; }
+  virtual int height() const { return height_; }
+  virtual void SetPixel(int x, int y, uint8_t red, uint8_t green, uint8_t blue);
+
+private:
+  const int parallel_;
+  int width_;
+  int height_;
+  int panel_height_;
+  Canvas *delegatee_;
+};
+
+void UArrangementTransformer::TransformCanvas::SetDelegatee(Canvas* delegatee) {
+  delegatee_ = delegatee;
+  width_ = (delegatee->width() / 64) * 32;   // Div in middle at 32px boundary
+  height_ = 2 * delegatee->height();
+  if (delegatee->width() % 64 != 0) {
+    fprintf(stderr, "An U-arrangement would need an even number of panels "
+            "unless you can fold one in the middle...\n");
+  }
+  if (delegatee->height() % parallel_ != 0) {
+    fprintf(stderr, "For parallel=%d we would expect the height=%d to be "
+            "divisible by %d ??\n", parallel_, delegatee->height(), parallel_);
+    assert(false);
+  }
+  panel_height_ = delegatee->height() / parallel_;
+}
+
+void UArrangementTransformer::TransformCanvas::Clear() {
+  delegatee_->Clear();
+}
+
+void UArrangementTransformer::TransformCanvas::Fill(
+  uint8_t red, uint8_t green, uint8_t blue) {
+  delegatee_->Fill(red, green, blue);
+}
+
+void UArrangementTransformer::TransformCanvas::SetPixel(
+  int x, int y, uint8_t red, uint8_t green, uint8_t blue) {
+  if (x < 0 || x >= width_ || y < 0 || y >= height_) return;
+  const int slab_height = 2*panel_height_;   // one folded u-shape
+  const int base_y = (y / slab_height) * panel_height_;
+  y %= slab_height;
+  if (y < panel_height_) {
+    x += delegatee_->width() / 2;
+  } else {
+    x = width_ - x - 1;
+    y = slab_height - y - 1;
+  }
+  delegatee_->SetPixel(x, base_y + y, red, green, blue);
+}
+
+UArrangementTransformer::UArrangementTransformer(int parallel)
+  : canvas_(new TransformCanvas(parallel)) {
+  assert(parallel > 0);
+}
+
+UArrangementTransformer::~UArrangementTransformer() {
+  delete canvas_;
+}
+
+Canvas *UArrangementTransformer::Transform(Canvas *output) {
+  assert(output != NULL);
+
+  canvas_->SetDelegatee(output);
+  return canvas_;
+}
+
+// Legacly LargeSquare64x64Transformer: uses the UArrangementTransformer, but
+// does things so that it looks the same as before.
+LargeSquare64x64Transformer::LargeSquare64x64Transformer()
+  : arrange_(1), rotated_(180) { }
+Canvas *LargeSquare64x64Transformer::Transform(Canvas *output) {
+  return rotated_.Transform(arrange_.Transform(output));
+}
+} // namespace rgb_matrix
+
+#endif // REMOVE_DEPRECATED_TRANSFORMERS

+ 57 - 0
matrix-zmq/rpi-rgb-led-matrix/lib/utf8-internal.h

@@ -0,0 +1,57 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2013 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+#ifndef RPI_GRAPHICS_UTF8_H
+#define RPI_GRAPHICS_UTF8_H
+
+#include <stdint.h>
+
+// Utility function that reads UTF-8 encoded codepoints from byte iterator.
+// No error checking, we assume string is UTF-8 clean.
+template <typename byte_iterator>
+uint32_t utf8_next_codepoint(byte_iterator &it) {
+  uint32_t cp = *it++;
+  if (cp < 0x80) {
+    return cp;   // iterator already incremented.
+  }
+  else if ((cp & 0xE0) == 0xC0) {
+    cp = ((cp & 0x1F) << 6) + (*it & 0x3F);
+  }
+  else if ((cp & 0xF0) == 0xE0) {
+    cp = ((cp & 0x0F) << 12) + ((*it & 0x3F) << 6);
+    cp += (*++it & 0x3F);
+  }
+  else if ((cp & 0xF8) == 0xF0) {
+    cp = ((cp & 0x07) << 18) + ((*it & 0x3F) << 12);
+    cp += (*++it & 0x3F) << 6;
+    cp += (*++it & 0x3F);
+  }
+  else if ((cp & 0xFC) == 0xF8) {
+    cp = ((cp & 0x03) << 24) + ((*it & 0x3F) << 18);
+    cp += (*++it & 0x3F) << 12;
+    cp += (*++it & 0x3F) << 6;
+    cp += (*++it & 0x3F);
+  }
+  else if ((cp & 0xFE) == 0xFC) {
+    cp = ((cp & 0x01) << 30) + ((*it & 0x3F) << 24);
+    cp += (*++it & 0x3F) << 18;
+    cp += (*++it & 0x3F) << 12;
+    cp += (*++it & 0x3F) << 6;
+    cp += (*++it & 0x3F);
+  }
+  ++it;
+  return cp;
+}
+
+#endif  // RPI_GRAPHICS_UTF8_H

+ 2 - 0
matrix-zmq/rpi-rgb-led-matrix/readme.md

@@ -0,0 +1,2 @@
+this is
+https://github.com/hzeller/rpi-rgb-led-matrix/tree/21410d2b0bac006b4a1661594926af347b3ce334

+ 2 - 0
matrix-zmq/rpi-rgb-led-matrix/utils/.gitignore

@@ -0,0 +1,2 @@
+led-image-viewer
+video-viewer

+ 49 - 0
matrix-zmq/rpi-rgb-led-matrix/utils/Makefile

@@ -0,0 +1,49 @@
+CXXFLAGS=-O3 -W -Wall -Wextra -Wno-unused-parameter -D_FILE_OFFSET_BITS=64
+OBJECTS=led-image-viewer.o text-scroller.o
+BINARIES=led-image-viewer text-scroller
+
+OPTIONAL_OBJECTS=video-viewer.o
+OPTIONAL_BINARIES=video-viewer
+
+# Where our library resides. You mostly only need to change the
+# RGB_LIB_DISTRIBUTION, this is where the library is checked out.
+RGB_LIB_DISTRIBUTION=..
+RGB_INCDIR=$(RGB_LIB_DISTRIBUTION)/include
+RGB_LIBDIR=$(RGB_LIB_DISTRIBUTION)/lib
+RGB_LIBRARY_NAME=rgbmatrix
+RGB_LIBRARY=$(RGB_LIBDIR)/lib$(RGB_LIBRARY_NAME).a
+LDFLAGS+=-L$(RGB_LIBDIR) -l$(RGB_LIBRARY_NAME) -lrt -lm -lpthread
+
+# Imagemagic flags, only needed if actually compiled.
+MAGICK_CXXFLAGS?=$(shell GraphicsMagick++-config --cppflags --cxxflags)
+MAGICK_LDFLAGS?=$(shell GraphicsMagick++-config --ldflags --libs)
+AV_CXXFLAGS=$(shell pkg-config --cflags  libavcodec libavformat libswscale libavutil)
+AV_LDFLAGS=$(shell pkg-config --cflags --libs  libavcodec libavformat libswscale libavutil)
+
+simple: $(BINARIES)
+
+all : $(BINARIES) $(OPTIONAL_BINARIES)
+
+$(RGB_LIBRARY): FORCE
+	$(MAKE) -C $(RGB_LIBDIR)
+
+text-scroller: text-scroller.o $(RGB_LIBRARY)
+	$(CXX) $(CXXFLAGS) text-scroller.o -o $@ $(LDFLAGS)
+
+led-image-viewer: led-image-viewer.o $(RGB_LIBRARY)
+	$(CXX) $(CXXFLAGS) led-image-viewer.o -o $@ $(LDFLAGS) $(MAGICK_LDFLAGS)
+
+video-viewer: video-viewer.o $(RGB_LIBRARY)
+	$(CXX) $(CXXFLAGS) video-viewer.o -o $@ $(LDFLAGS) $(AV_LDFLAGS)
+
+%.o : %.cc
+	$(CXX) -I$(RGB_INCDIR) $(CXXFLAGS) -c -o $@ $<
+
+led-image-viewer.o : led-image-viewer.cc
+	$(CXX) -I$(RGB_INCDIR) $(CXXFLAGS) $(MAGICK_CXXFLAGS) -c -o $@ $<
+
+clean:
+	rm -f $(OBJECTS) $(BINARIES) $(OPTIONAL_OBJECTS) $(OPTIONAL_BINARIES)
+
+FORCE:
+.PHONY: FORCE

+ 306 - 0
matrix-zmq/rpi-rgb-led-matrix/utils/README.md

@@ -0,0 +1,306 @@
+Utilities
+=========
+
+This contains useful utilities that might be directly useful without having
+to write any code.
+
+Below, the description of the utilities contains a list of commandline flags
+they support. Next to the specific flags for their use, they all
+have a set of standard options that always come with the LED matrix,
+such as choosing the `--led-rows` or `--led-chain`.
+
+For brevity, we don't repeat them below in the synopsis prints of each of
+these utilities. You find a description of the standard options in
+the [toplevel readme](../README.md#changing-parameters-via-command-line-flags)
+
+<details><summary>Unfold: Standard LED-matrix options present in all utilities</summary>
+
+```
+ --led-gpio-mapping=<name> : Name of GPIO mapping used. Default "regular"
+ --led-rows=<rows>         : Panel rows. Typically 8, 16, 32 or 64. (Default: 32).
+ --led-cols=<cols>         : Panel columns. Typically 32 or 64. (Default: 32).
+ --led-chain=<chained>     : Number of daisy-chained panels. (Default: 1).
+ --led-parallel=<parallel> : Parallel chains. range=1..3 (Default: 1).
+ --led-multiplexing=<0..11> : Mux type: 0=direct; 1=Stripe; 2=Checkered; 3=Spiral; 4=ZStripe; 5=ZnMirrorZStripe; 6=coreman; 7=Kaler2Scan; 8=ZStripeUneven; 9=P10-128x4-Z; 10=QiangLiQ8; 11=InversedZStripe (Default: 0)
+ --led-pixel-mapper        : Semicolon-separated list of pixel-mappers to arrange pixels.
+                                    Optional params after a colon e.g. "U-mapper;Rotate:90"
+                                    Available: "Mirror", "Rotate", "U-mapper". Default: ""
+ --led-pwm-bits=<1..11>    : PWM bits (Default: 11).
+ --led-brightness=<percent>: Brightness in percent (Default: 100).
+ --led-scan-mode=<0..1>    : 0 = progressive; 1 = interlaced (Default: 0).
+ --led-row-addr-type=<0..4>: 0 = default; 1 = AB-addressed panels; 2 = direct row select; 3 = ABC-addressed panels; 4 = ABC Shift + DE direct (Default: 0).
+ --led-show-refresh        : Show refresh rate.
+ --led-inverse             : Switch if your matrix has inverse colors on.
+ --led-rgb-sequence        : Switch if your matrix has led colors swapped (Default: "RGB")
+ --led-pwm-lsb-nanoseconds : PWM Nanoseconds for LSB (Default: 130)
+ --led-pwm-dither-bits=<0..2> : Time dithering of lower bits (Default: 0)
+ --led-no-hardware-pulse   : Don't use hardware pin-pulse generation.
+ --led-panel-type=<name>   : Needed to initialize special panels. Supported: 'FM6126A'
+ --led-slowdown-gpio=<0..4>: Slowdown GPIO. Needed for faster Pis/slower panels (Default: 1).
+ --led-daemon              : Make the process run in the background as daemon.
+ --led-no-drop-privs       : Don't drop privileges from 'root' after initializing the hardware.
+```
+</details>
+
+### Image Viewer ###
+
+The image viewer reads all kinds of image formats, including animated gifs.
+
+To speed up lengthy loading of image files or animations, you also can also
+pre-process images or animations and write them to a 'stream' file that then
+later can be loaded very quickly by this viewer (at the expense of disk-space
+as these are not compressed). This is in particular useful for large panels
+and animations with many frames: less loading time and less RAM used.
+See `-O` example below in the example section.
+
+##### Building
+
+The `led-image-viewer` requires the GraphicsMagick dependency first, then
+it can be built with `make led-image-viewer`.
+
+```
+sudo apt-get update
+sudo apt-get install libgraphicsmagick++-dev libwebp-dev -y
+make led-image-viewer
+```
+
+##### Usage
+
+The resulting binary has a couple of flags.
+```
+usage: ./led-image-viewer [options] <image> [option] [<image> ...]
+Options:
+        -O<streamfile>            : Output to stream-file instead of matrix (Don't need to be root).
+        -C                        : Center images.
+
+These options affect images FOLLOWING them on the command line,
+so it is possible to have different options for each image
+        -w<seconds>               : Regular image: Wait time in seconds before next image is shown (default: 1.5).
+        -t<seconds>               : For animations: stop after this time.
+        -l<loop-count>            : For animations: number of loops through a full cycle.
+        -D<animation-delay-ms>    : For animations: override the delay between frames given in the
+                                    gif/stream animation with this value. Use -1 to use default value.
+        -V<vsync-multiple>        : For animation (expert): Only do frame vsync-swaps on multiples of refresh (default: 1)
+                                    (Tip: use --led-limit-refresh for stable rate)
+
+Options affecting display of multiple images:
+        -f                        : Forever cycle through the list of files on the command line.
+        -s                        : If multiple images are given: shuffle.
+
+General LED matrix options:
+        <... all the --led- options>
+
+Switch time between files: -w for static images; -t/-l for animations
+Animated gifs: If both -l and -t are given, whatever finishes first determines duration.
+
+The -w, -t and -l options apply to the following images until a new instance of one of these options is seen.
+So you can choose different durations for different images.
+```
+
+Then, you can run it with any common image format, including animated gifs:
+
+##### Examples
+
+```bash
+sudo ./led-image-viewer some-image.jpg       # Display an image.
+sudo ./led-image-viewer animated.gif         # Show an animated gif
+sudo ./led-image-viewer -t5 animated.gif     # Show an animated gif for 5 seconds
+sudo ./led-image-viewer -l2 animated.gif     # Show an animated gif for 2 loops
+sudo ./led-image-viewer -D16 animated.gif    # Play animated gif, use 16ms frame delay
+
+# If you want to have an even frame rate, that is depending on your
+# refresh rate, use the following. Note, your refresh rate is dependent on
+# factors such as chain length and rows; use --led-show-refresh to get an idea.
+# Then fix it with --led-limit-refresh
+sudo ./led-image-viewer --led-limit-refresh=200 -D0 -V10 animated.gif # Frame rate = 1/12 refresh rate
+
+sudo ./led-image-viewer    -w3 foo.jpg bar.png  # show two images, wait 3 seconds between. Stop.
+sudo ./led-image-viewer    -w3 foo.jpg -w2 bar.png baz.png  # show images, wait 3 seconds after the first, 2 seconds after the second and third. Stop.
+sudo ./led-image-viewer -f -w3 foo.jpg bar.png  # show images, wait 3sec between, go back and loop forever
+
+sudo ./led-image-viewer -f -w3 *.png *.jpg   # Loop forever through a list of images
+
+sudo ./led-image-viewer -f -s *.png  # Loop forever but randomize (shuffle) each round.
+
+# Show image.png and animated.gif in a loop. Show the static image for 3 seconds
+# while the animation is shown for 5 seconds (-t takes precedence for animated
+# images over -w)
+sudo ./led-image-viewer -f -w3 -t5 image.png animated.gif
+
+# Create a fast animation from a bunch of *.png files
+# with 16.6ms frame time (=60Hz) and write to a raw animation stream
+# animation-out.stream (beware, uncompressed, uses lots of disk).
+# Note:
+#  o We have to supply all the options (rows, chain, parallel, hardware-mapping,
+#    rotation etc), that we would supply to the real viewer later.
+#  o We don't need to be root, as we don't write to the matrix
+./led-image-viewer --led-rows=32 --led-chain=4 --led-parallel=3 -w0.016667 *.png -Oanimation-out.stream
+
+# Now, play back this animation.
+sudo ./led-image-viewer --led-rows=32 --led-chain=4 --led-parallel=3 animation-out.stream
+```
+
+### Text Scroller ###
+
+The text scroller allows to show some scrolling text.
+
+##### Building
+```
+make text-scroller
+```
+
+##### Usage
+
+```
+usage: ./text-scroller [options] <text>
+Takes text and scrolls it with speed -s
+Options:
+        -f <font-file>    : Path to *.bdf-font to be used.
+        -s <speed>        : Approximate letters per second.
+                            Positive: scroll right to left; Negative: scroll left to right
+                            (Zero for no scrolling)
+        -l <loop-count>   : Number of loops through the text. -1 for endless (default)
+        -b <on-time>,<off-time>  : Blink while scrolling. Keep on and off for these amount of scrolled pixels.
+        -x <x-origin>     : Shift X-Origin of displaying text (Default: 0)
+        -y <y-origin>     : Shift Y-Origin of displaying text (Default: 0)
+        -t <track-spacing>: Spacing pixels between letters (Default: 0)
+
+        -C <r,g,b>        : Text Color. Default 255,255,255 (white)
+        -B <r,g,b>        : Background-Color. Default 0,0,0
+        -O <r,g,b>        : Outline-Color, e.g. to increase contrast.
+
+General LED matrix options:
+        <... all the --led- options>
+```
+
+You need to specify a font for the tool to use. We are using BDF-fonts, which are bitmap fonts
+nicely suited for low-resolution displays such as ours. A few fonts you find in the
+[../fonts](../fonts) directory. The [README.md](../fonts/README.md) there also describes
+how to make your own.
+
+##### Examples
+
+```bash
+# (use your --led-rows, --led-chain and --led-parallel suited for your setup)
+
+# Red (-C) text on a display with 4 chained displays. Notice you can use UTF-8 characters
+# if they are supported by the font.
+sudo ./text-scroller -f ../fonts/9x18.bdf -C255,0,0 --led-chain=4 "Hello World ♥"
+
+# .. faster speed; roughly 20 characters per second with option -s.
+sudo ./text-scroller -f ../fonts/9x18.bdf -C255,0,0 --led-chain=4 -s20 "The quick brown fox jumps over the lazy dog"
+
+# A speed of zero does just shows the text, no scrolling.
+sudo ./text-scroller -f ../fonts/9x18.bdf -C255,0,0 --led-chain=4 -s0 "No Scroll"
+
+# A text might need to be arranged a bit. Let's move it 15 pixels to the right and 5 down:
+sudo ./text-scroller -f ../fonts/9x18.bdf -C255,0,0 --led-chain=4 -s0 -x15 -y5 "Shifted"
+
+# Now text in red color on a blue background (-B). We choose an outline (-O)
+# of a slightly darker blue for better contrast
+sudo ./text-scroller -f ../fonts/9x18.bdf -B0,0,255 -O0,0,100 -C255,0,0 --led-chain=4 "Contrast outline"
+
+# A larger font. This one needs a bit of an y-adjustment
+# (move up 11 pixels: a negative y shift) to fit nicely on a panel.
+sudo ./text-scroller -f ../fonts/texgyre-27.bdf --led-chain=4 -y-11 "Large Font"
+```
+
+### Video Viewer ###
+
+The video viewer allows to play common video formats on the RGB matrix (just
+the picture, no sound).
+
+This is currently doing a software decode; if you are familiar with the
+av libraries, a pull request that adds hardware decoding is welcome.
+
+Right now, this is CPU intensive and decoding can result in an output that
+is not smooth or presents flicker. If you observe that, it is suggested to
+do one of these:
+
+  - Transcode the video first to the width and height of the final output size.
+  - If you use tools such as [youtube-dl] to acquire the video, tell it
+    to choose a low resolution version (e.g. for that program use option
+    `-f"[height<480]"`).
+  - Synchronize output as integer fraction of matrix refresh rate (example
+    below).
+  - Prepare an animation stream that you then later watch with led-image-viewer
+    (see example below).
+  - Another route to watch videos is to run a [flaschen-taschen]
+    server on your Pi, that provides a network interface to your LED-Matrix.
+    Now, you can use [vlc] from some other computer on your network and
+    stream the output to your Pi.
+    You have to provide the IP address and size of the panel:
+    ```
+      vlc --vout flaschen --flaschen-display=<IP-address-of-your-pi> \
+           --flaschen-width=128 --flaschen-height=64 \
+           <video-filename-or-YouTube-URL>
+    ```
+
+##### Building
+
+The video-viewer requires some dependencies first, then it can be
+built with `make video-viewer`.
+
+```
+sudo apt-get update
+sudo apt-get install pkg-config libavcodec-dev libavformat-dev libswscale-dev
+make video-viewer
+```
+
+##### Usage
+
+```
+Show one or a sequence of video files on the RGB-Matrix
+usage: ./video-viewer [options] <video> [<video>...]
+Options:
+        -F                 : Full screen without black bars; aspect ratio might suffer
+        -O<streamfile>     : Output to stream-file instead of matrix (don't need to be root).
+        -s <count>         : Skip these number of frames in the beginning.
+        -c <count>         : Only show this number of frames (excluding skipped frames).
+        -V<vsync-multiple> : Instead of native video framerate, playback framerate
+                             is a fraction of matrix refresh. In particular with a stable refresh,
+                             this can result in more smooth playback. Choose multiple for desired framerate.
+                             (Tip: use --led-limit-refresh for stable rate)
+        -v                 : verbose; prints video metadata and other info.
+        -f                 : Loop forever.
+
+General LED matrix options:
+        <... all the --led- options>
+```
+
+##### Examples
+
+```bash
+# Play video. If you observe that the Pi has trouble to keep up (extensive
+# flickering), transcode the video first to the exact size of your display.
+sudo ./video-viewer --led-chain=4 --led-parallel=3 myvideo.webm
+
+# If you observe flicker you can try to synchronize video output with
+# the refresh rate of the panel. For that, first figure out with
+# --led-show-refresh what the 'natural' refresh rate is of your LED panel.
+# Then choose one that is lower and a multiple of the frame-rate of the
+# video. Let's say we have a video with a framerate of 25fps and find that
+# our panel can refresh with more than 200Hz (after the usual refresh
+# tweakings such as with --led-pwm-dither-bits).
+# Let's fix the refresh rate to 200 and sync a new frame with every
+# 8th refresh to get the desired video fps (200/8 = 25)
+sudo ./video-viewer --led-chain=4 --led-parallel=3 --led-limit-refresh=200 -V8 myvideo.webm
+
+# Another way to avoid flicker playback with best possible results even with
+# very high framerate: create a preprocessed stream first, then replay it with
+# led-image-viewer. This results in best quality (no CPU use at play-time), but
+# comes with a caveat: It can use _A LOT_ of disk, as it is not compressed.
+# Note:
+#  o We have to supply all the options (rows, chain, parallel, hardware-mapping,
+#    rotation etc), that we would supply to the real viewer later.
+#  o We don't need to be root, as we don't write to the matrix
+./video-viewer --led-chain=5 --led-parallel=3 myvideo.webm -O/tmp/vid.stream
+
+#.. now play it with led-image-viewer. Also try using -D or -V to replay with
+# different frame rate.
+sudo ./led-image-viewer --led-chain=5 --led-parallel=3 /tmp/vid.stream
+```
+
+[youtube-dl]: https://youtube-dl.org/
+[flaschen-taschen]: https://github.com/hzeller/flaschen-taschen/tree/master/server#rgb-matrix-panel-display
+[vlc]: https://www.videolan.org/vlc

+ 502 - 0
matrix-zmq/rpi-rgb-led-matrix/utils/led-image-viewer.cc

@@ -0,0 +1,502 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2015 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+// To use this image viewer, first get image-magick development files
+// $ sudo apt-get install libgraphicsmagick++-dev libwebp-dev
+//
+// Then compile with
+// $ make led-image-viewer
+
+#include "led-matrix.h"
+#include "pixel-mapper.h"
+#include "content-streamer.h"
+
+#include <fcntl.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <Magick++.h>
+#include <magick/image.h>
+
+using rgb_matrix::Canvas;
+using rgb_matrix::FrameCanvas;
+using rgb_matrix::RGBMatrix;
+using rgb_matrix::StreamReader;
+
+typedef int64_t tmillis_t;
+static const tmillis_t distant_future = (1LL<<40); // that is a while.
+
+struct ImageParams {
+  ImageParams() : anim_duration_ms(distant_future), wait_ms(1500),
+                  anim_delay_ms(-1), loops(-1), vsync_multiple(1) {}
+  tmillis_t anim_duration_ms;  // If this is an animation, duration to show.
+  tmillis_t wait_ms;           // Regular image: duration to show.
+  tmillis_t anim_delay_ms;     // Animation delay override.
+  int loops;
+  int vsync_multiple;
+};
+
+struct FileInfo {
+  ImageParams params;      // Each file might have specific timing settings
+  bool is_multi_frame;
+  rgb_matrix::StreamIO *content_stream;
+};
+
+volatile bool interrupt_received = false;
+static void InterruptHandler(int signo) {
+  interrupt_received = true;
+}
+
+static tmillis_t GetTimeInMillis() {
+  struct timeval tp;
+  gettimeofday(&tp, NULL);
+  return tp.tv_sec * 1000 + tp.tv_usec / 1000;
+}
+
+static void SleepMillis(tmillis_t milli_seconds) {
+  if (milli_seconds <= 0) return;
+  struct timespec ts;
+  ts.tv_sec = milli_seconds / 1000;
+  ts.tv_nsec = (milli_seconds % 1000) * 1000000;
+  nanosleep(&ts, NULL);
+}
+
+static void StoreInStream(const Magick::Image &img, int delay_time_us,
+                          bool do_center,
+                          rgb_matrix::FrameCanvas *scratch,
+                          rgb_matrix::StreamWriter *output) {
+  scratch->Clear();
+  const int x_offset = do_center ? (scratch->width() - img.columns()) / 2 : 0;
+  const int y_offset = do_center ? (scratch->height() - img.rows()) / 2 : 0;
+  for (size_t y = 0; y < img.rows(); ++y) {
+    for (size_t x = 0; x < img.columns(); ++x) {
+      const Magick::Color &c = img.pixelColor(x, y);
+      if (c.alphaQuantum() < 256) {
+        scratch->SetPixel(x + x_offset, y + y_offset,
+                          ScaleQuantumToChar(c.redQuantum()),
+                          ScaleQuantumToChar(c.greenQuantum()),
+                          ScaleQuantumToChar(c.blueQuantum()));
+      }
+    }
+  }
+  output->Stream(*scratch, delay_time_us);
+}
+
+static void CopyStream(rgb_matrix::StreamReader *r,
+                       rgb_matrix::StreamWriter *w,
+                       rgb_matrix::FrameCanvas *scratch) {
+  uint32_t delay_us;
+  while (r->GetNext(scratch, &delay_us)) {
+    w->Stream(*scratch, delay_us);
+  }
+}
+
+// Load still image or animation.
+// Scale, so that it fits in "width" and "height" and store in "result".
+static bool LoadImageAndScale(const char *filename,
+                              int target_width, int target_height,
+                              bool fill_width, bool fill_height,
+                              std::vector<Magick::Image> *result,
+                              std::string *err_msg) {
+  std::vector<Magick::Image> frames;
+  try {
+    readImages(&frames, filename);
+  } catch (std::exception& e) {
+    if (e.what()) *err_msg = e.what();
+    return false;
+  }
+  if (frames.size() == 0) {
+    fprintf(stderr, "No image found.");
+    return false;
+  }
+
+  // Put together the animation from single frames. GIFs can have nasty
+  // disposal modes, but they are handled nicely by coalesceImages()
+  if (frames.size() > 1) {
+    Magick::coalesceImages(result, frames.begin(), frames.end());
+  } else {
+    result->push_back(frames[0]);   // just a single still image.
+  }
+
+  const int img_width = (*result)[0].columns();
+  const int img_height = (*result)[0].rows();
+  const float width_fraction = (float)target_width / img_width;
+  const float height_fraction = (float)target_height / img_height;
+  if (fill_width && fill_height) {
+    // Scrolling diagonally. Fill as much as we can get in available space.
+    // Largest scale fraction determines that.
+    const float larger_fraction = (width_fraction > height_fraction)
+      ? width_fraction
+      : height_fraction;
+    target_width = (int) roundf(larger_fraction * img_width);
+    target_height = (int) roundf(larger_fraction * img_height);
+  }
+  else if (fill_height) {
+    // Horizontal scrolling: Make things fit in vertical space.
+    // While the height constraint stays the same, we can expand to full
+    // width as we scroll along that axis.
+    target_width = (int) roundf(height_fraction * img_width);
+  }
+  else if (fill_width) {
+    // dito, vertical. Make things fit in horizontal space.
+    target_height = (int) roundf(width_fraction * img_height);
+  }
+
+  for (size_t i = 0; i < result->size(); ++i) {
+    (*result)[i].scale(Magick::Geometry(target_width, target_height));
+  }
+
+  return true;
+}
+
+void DisplayAnimation(const FileInfo *file,
+                      RGBMatrix *matrix, FrameCanvas *offscreen_canvas) {
+  const tmillis_t duration_ms = (file->is_multi_frame
+                                 ? file->params.anim_duration_ms
+                                 : file->params.wait_ms);
+  rgb_matrix::StreamReader reader(file->content_stream);
+  int loops = file->params.loops;
+  const tmillis_t end_time_ms = GetTimeInMillis() + duration_ms;
+  const tmillis_t override_anim_delay = file->params.anim_delay_ms;
+  for (int k = 0;
+       (loops < 0 || k < loops)
+         && !interrupt_received
+         && GetTimeInMillis() < end_time_ms;
+       ++k) {
+    uint32_t delay_us = 0;
+    while (!interrupt_received && GetTimeInMillis() <= end_time_ms
+           && reader.GetNext(offscreen_canvas, &delay_us)) {
+      const tmillis_t anim_delay_ms =
+        override_anim_delay >= 0 ? override_anim_delay : delay_us / 1000;
+      const tmillis_t start_wait_ms = GetTimeInMillis();
+      offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas,
+                                             file->params.vsync_multiple);
+      const tmillis_t time_already_spent = GetTimeInMillis() - start_wait_ms;
+      SleepMillis(anim_delay_ms - time_already_spent);
+    }
+    reader.Rewind();
+  }
+}
+
+static int usage(const char *progname) {
+  fprintf(stderr, "usage: %s [options] <image> [option] [<image> ...]\n",
+          progname);
+
+  fprintf(stderr, "Options:\n"
+          "\t-O<streamfile>            : Output to stream-file instead of matrix (Don't need to be root).\n"
+          "\t-C                        : Center images.\n"
+
+          "\nThese options affect images FOLLOWING them on the command line,\n"
+          "so it is possible to have different options for each image\n"
+          "\t-w<seconds>               : Regular image: "
+          "Wait time in seconds before next image is shown (default: 1.5).\n"
+          "\t-t<seconds>               : "
+          "For animations: stop after this time.\n"
+          "\t-l<loop-count>            : "
+          "For animations: number of loops through a full cycle.\n"
+          "\t-D<animation-delay-ms>    : "
+          "For animations: override the delay between frames given in the\n"
+          "\t                            gif/stream animation with this value. Use -1 to use default value.\n"
+          "\t-V<vsync-multiple>        : For animation (expert): Only do frame vsync-swaps on multiples of refresh (default: 1)\n"
+          "\t                            (Tip: use --led-limit-refresh for stable rate)\n"
+
+          "\nOptions affecting display of multiple images:\n"
+          "\t-f                        : "
+          "Forever cycle through the list of files on the command line.\n"
+          "\t-s                        : If multiple images are given: shuffle.\n"
+          );
+
+  fprintf(stderr, "\nGeneral LED matrix options:\n");
+  rgb_matrix::PrintMatrixFlags(stderr);
+
+  fprintf(stderr,
+          "\nSwitch time between files: "
+          "-w for static images; -t/-l for animations\n"
+          "Animated gifs: If both -l and -t are given, "
+          "whatever finishes first determines duration.\n");
+
+  fprintf(stderr, "\nThe -w, -t and -l options apply to the following images "
+          "until a new instance of one of these options is seen.\n"
+          "So you can choose different durations for different images.\n");
+
+  return 1;
+}
+
+int main(int argc, char *argv[]) {
+  Magick::InitializeMagick(*argv);
+
+  RGBMatrix::Options matrix_options;
+  rgb_matrix::RuntimeOptions runtime_opt;
+  if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
+                                         &matrix_options, &runtime_opt)) {
+    return usage(argv[0]);
+  }
+
+  bool do_forever = false;
+  bool do_center = false;
+  bool do_shuffle = false;
+
+  // We remember ImageParams for each image, which will change whenever
+  // there is a flag modifying them. This map keeps track of filenames
+  // and their image params (also for unrelated elements of argv[], but doesn't
+  // matter).
+  // We map the pointer instad of the string of the argv parameter so that
+  // we can have two times the same image on the commandline list with different
+  // parameters.
+  std::map<const void *, struct ImageParams> filename_params;
+
+  // Set defaults.
+  ImageParams img_param;
+  for (int i = 0; i < argc; ++i) {
+    filename_params[argv[i]] = img_param;
+  }
+
+  const char *stream_output = NULL;
+
+  int opt;
+  while ((opt = getopt(argc, argv, "w:t:l:fr:c:P:LhCR:sO:V:D:")) != -1) {
+    switch (opt) {
+    case 'w':
+      img_param.wait_ms = roundf(atof(optarg) * 1000.0f);
+      break;
+    case 't':
+      img_param.anim_duration_ms = roundf(atof(optarg) * 1000.0f);
+      break;
+    case 'l':
+      img_param.loops = atoi(optarg);
+      break;
+    case 'D':
+      img_param.anim_delay_ms = atoi(optarg);
+      break;
+    case 'f':
+      do_forever = true;
+      break;
+    case 'C':
+      do_center = true;
+      break;
+    case 's':
+      do_shuffle = true;
+      break;
+    case 'r':
+      fprintf(stderr, "Instead of deprecated -r, use --led-rows=%s instead.\n",
+              optarg);
+      matrix_options.rows = atoi(optarg);
+      break;
+    case 'c':
+      fprintf(stderr, "Instead of deprecated -c, use --led-chain=%s instead.\n",
+              optarg);
+      matrix_options.chain_length = atoi(optarg);
+      break;
+    case 'P':
+      matrix_options.parallel = atoi(optarg);
+      break;
+    case 'L':
+      fprintf(stderr, "-L is deprecated. Use\n\t--led-pixel-mapper=\"U-mapper\" --led-chain=4\ninstead.\n");
+      return 1;
+      break;
+    case 'R':
+      fprintf(stderr, "-R is deprecated. "
+              "Use --led-pixel-mapper=\"Rotate:%s\" instead.\n", optarg);
+      return 1;
+      break;
+    case 'O':
+      stream_output = strdup(optarg);
+      break;
+    case 'V':
+      img_param.vsync_multiple = atoi(optarg);
+      if (img_param.vsync_multiple < 1) img_param.vsync_multiple = 1;
+      break;
+    case 'h':
+    default:
+      return usage(argv[0]);
+    }
+
+    // Starting from the current file, set all the remaining files to
+    // the latest change.
+    for (int i = optind; i < argc; ++i) {
+      filename_params[argv[i]] = img_param;
+    }
+  }
+
+  const int filename_count = argc - optind;
+  if (filename_count == 0) {
+    fprintf(stderr, "Expected image filename.\n");
+    return usage(argv[0]);
+  }
+
+  // Prepare matrix
+  runtime_opt.do_gpio_init = (stream_output == NULL);
+  RGBMatrix *matrix = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
+  if (matrix == NULL)
+    return 1;
+
+  FrameCanvas *offscreen_canvas = matrix->CreateFrameCanvas();
+
+  printf("Size: %dx%d. Hardware gpio mapping: %s\n",
+         matrix->width(), matrix->height(), matrix_options.hardware_mapping);
+
+  // These parameters are needed once we do scrolling.
+  const bool fill_width = false;
+  const bool fill_height = false;
+
+  // In case the output to stream is requested, set up the stream object.
+  rgb_matrix::StreamIO *stream_io = NULL;
+  rgb_matrix::StreamWriter *global_stream_writer = NULL;
+  if (stream_output) {
+    int fd = open(stream_output, O_CREAT|O_WRONLY, 0644);
+    if (fd < 0) {
+      perror("Couldn't open output stream");
+      return 1;
+    }
+    stream_io = new rgb_matrix::FileStreamIO(fd);
+    global_stream_writer = new rgb_matrix::StreamWriter(stream_io);
+  }
+
+  const tmillis_t start_load = GetTimeInMillis();
+  fprintf(stderr, "Loading %d files...\n", argc - optind);
+  // Preparing all the images beforehand as the Pi might be too slow to
+  // be quickly switching between these. So preprocess.
+  std::vector<FileInfo*> file_imgs;
+  for (int imgarg = optind; imgarg < argc; ++imgarg) {
+    const char *filename = argv[imgarg];
+    FileInfo *file_info = NULL;
+
+    std::string err_msg;
+    std::vector<Magick::Image> image_sequence;
+    if (LoadImageAndScale(filename, matrix->width(), matrix->height(),
+                          fill_width, fill_height, &image_sequence, &err_msg)) {
+      file_info = new FileInfo();
+      file_info->params = filename_params[filename];
+      file_info->content_stream = new rgb_matrix::MemStreamIO();
+      file_info->is_multi_frame = image_sequence.size() > 1;
+      rgb_matrix::StreamWriter out(file_info->content_stream);
+      for (size_t i = 0; i < image_sequence.size(); ++i) {
+        const Magick::Image &img = image_sequence[i];
+        int64_t delay_time_us;
+        if (file_info->is_multi_frame) {
+          delay_time_us = img.animationDelay() * 10000; // unit in 1/100s
+        } else {
+          delay_time_us = file_info->params.wait_ms * 1000;  // single image.
+        }
+        if (delay_time_us <= 0) delay_time_us = 100 * 1000;  // 1/10sec
+        StoreInStream(img, delay_time_us, do_center, offscreen_canvas,
+                      global_stream_writer ? global_stream_writer : &out);
+      }
+    } else {
+      // Ok, not an image. Let's see if it is one of our streams.
+      int fd = open(filename, O_RDONLY);
+      if (fd >= 0) {
+        file_info = new FileInfo();
+        file_info->params = filename_params[filename];
+        file_info->content_stream = new rgb_matrix::FileStreamIO(fd);
+        StreamReader reader(file_info->content_stream);
+        if (reader.GetNext(offscreen_canvas, NULL)) {  // header+size ok
+          file_info->is_multi_frame = reader.GetNext(offscreen_canvas, NULL);
+          reader.Rewind();
+          if (global_stream_writer) {
+            CopyStream(&reader, global_stream_writer, offscreen_canvas);
+          }
+        } else {
+          err_msg = "Can't read as image or compatible stream";
+          delete file_info->content_stream;
+          delete file_info;
+          file_info = NULL;
+        }
+      }
+      else {
+        perror("Opening file");
+      }
+    }
+
+    if (file_info) {
+      file_imgs.push_back(file_info);
+    } else {
+      fprintf(stderr, "%s skipped: Unable to open (%s)\n",
+              filename, err_msg.c_str());
+    }
+  }
+
+  if (stream_output) {
+    delete global_stream_writer;
+    delete stream_io;
+    if (file_imgs.size()) {
+      fprintf(stderr, "Done: Output to stream %s; "
+              "this can now be opened with led-image-viewer with the exact same panel configuration settings such as rows, chain, parallel and hardware-mapping\n", stream_output);
+    }
+    if (do_shuffle)
+      fprintf(stderr, "Note: -s (shuffle) does not have an effect when generating streams.\n");
+    if (do_forever)
+      fprintf(stderr, "Note: -f (forever) does not have an effect when generating streams.\n");
+    // Done, no actual output to matrix.
+    return 0;
+  }
+
+  // Some parameter sanity adjustments.
+  if (file_imgs.empty()) {
+    // e.g. if all files could not be interpreted as image.
+    fprintf(stderr, "No image could be loaded.\n");
+    return 1;
+  } else if (file_imgs.size() == 1) {
+    // Single image: show forever.
+    file_imgs[0]->params.wait_ms = distant_future;
+  } else {
+    for (size_t i = 0; i < file_imgs.size(); ++i) {
+      ImageParams &params = file_imgs[i]->params;
+      // Forever animation ? Set to loop only once, otherwise that animation
+      // would just run forever, stopping all the images after it.
+      if (params.loops < 0 && params.anim_duration_ms == distant_future) {
+        params.loops = 1;
+      }
+    }
+  }
+
+  fprintf(stderr, "Loading took %.3fs; now: Display.\n",
+          (GetTimeInMillis() - start_load) / 1000.0);
+
+  signal(SIGTERM, InterruptHandler);
+  signal(SIGINT, InterruptHandler);
+
+  do {
+    if (do_shuffle) {
+      std::random_shuffle(file_imgs.begin(), file_imgs.end());
+    }
+    for (size_t i = 0; i < file_imgs.size() && !interrupt_received; ++i) {
+      DisplayAnimation(file_imgs[i], matrix, offscreen_canvas);
+    }
+  } while (do_forever && !interrupt_received);
+
+  if (interrupt_received) {
+    fprintf(stderr, "Caught signal. Exiting.\n");
+  }
+
+  // Animation finished. Shut down the RGB matrix.
+  matrix->Clear();
+  delete matrix;
+
+  // Leaking the FileInfos, but don't care at program end.
+  return 0;
+}

+ 272 - 0
matrix-zmq/rpi-rgb-led-matrix/utils/text-scroller.cc

@@ -0,0 +1,272 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+// Copyright (C) 2015 Henner Zeller <h.zeller@acm.org>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation version 2.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
+
+#include "led-matrix.h"
+#include "graphics.h"
+
+#include <string>
+
+#include <getopt.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+using namespace rgb_matrix;
+
+volatile bool interrupt_received = false;
+static void InterruptHandler(int signo) {
+  interrupt_received = true;
+}
+
+static int usage(const char *progname) {
+  fprintf(stderr, "usage: %s [options] <text>\n", progname);
+  fprintf(stderr, "Takes text and scrolls it with speed -s\n");
+  fprintf(stderr, "Options:\n");
+  fprintf(stderr,
+          "\t-f <font-file>    : Path to *.bdf-font to be used.\n"
+          "\t-s <speed>        : Approximate letters per second. \n"
+          "\t                    Positive: scroll right to left; Negative: scroll left to right\n"
+          "\t                    (Zero for no scrolling)\n"
+          "\t-l <loop-count>   : Number of loops through the text. "
+          "-1 for endless (default)\n"
+          "\t-b <on-time>,<off-time>  : Blink while scrolling. Keep "
+          "on and off for these amount of scrolled pixels.\n"
+          "\t-x <x-origin>     : Shift X-Origin of displaying text (Default: 0)\n"
+          "\t-y <y-origin>     : Shift Y-Origin of displaying text (Default: 0)\n"
+          "\t-t <track-spacing>: Spacing pixels between letters (Default: 0)\n"
+          "\n"
+          "\t-C <r,g,b>        : Text Color. Default 255,255,255 (white)\n"
+          "\t-B <r,g,b>        : Background-Color. Default 0,0,0\n"
+          "\t-O <r,g,b>        : Outline-Color, e.g. to increase contrast.\n"
+          );
+  fprintf(stderr, "\nGeneral LED matrix options:\n");
+  rgb_matrix::PrintMatrixFlags(stderr);
+  return 1;
+}
+
+static bool parseColor(Color *c, const char *str) {
+  return sscanf(str, "%hhu,%hhu,%hhu", &c->r, &c->g, &c->b) == 3;
+}
+
+static bool FullSaturation(const Color &c) {
+  return (c.r == 0 || c.r == 255)
+    && (c.g == 0 || c.g == 255)
+    && (c.b == 0 || c.b == 255);
+}
+
+static void add_micros(struct timespec *accumulator, long micros) {
+  const long billion = 1000000000;
+  const int64_t nanos = (int64_t) micros * 1000;
+  accumulator->tv_sec += nanos / billion;
+  accumulator->tv_nsec += nanos % billion;
+  while (accumulator->tv_nsec > billion) {
+    accumulator->tv_nsec -= billion;
+    accumulator->tv_sec += 1;
+  }
+}
+
+int main(int argc, char *argv[]) {
+  RGBMatrix::Options matrix_options;
+  rgb_matrix::RuntimeOptions runtime_opt;
+  if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
+                                         &matrix_options, &runtime_opt)) {
+    return usage(argv[0]);
+  }
+
+  Color color(255, 255, 255);
+  Color bg_color(0, 0, 0);
+  Color outline_color(0,0,0);
+  bool with_outline = false;
+
+  const char *bdf_font_file = NULL;
+  std::string line;
+  bool xorigin_configured = false;
+  int x_orig = 0;
+  int y_orig = 0;
+  int letter_spacing = 0;
+  float speed = 7.0f;
+  int loops = -1;
+  int blink_on = 0;
+  int blink_off = 0;
+
+  int opt;
+  while ((opt = getopt(argc, argv, "x:y:f:C:B:O:t:s:l:b:")) != -1) {
+    switch (opt) {
+    case 's': speed = atof(optarg); break;
+    case 'b':
+      if (sscanf(optarg, "%d,%d", &blink_on, &blink_off) == 1) {
+        blink_off = blink_on;
+      }
+      fprintf(stderr, "hz: on=%d off=%d\n", blink_on, blink_off);
+      break;
+    case 'l': loops = atoi(optarg); break;
+    case 'x': x_orig = atoi(optarg); xorigin_configured = true; break;
+    case 'y': y_orig = atoi(optarg); break;
+    case 'f': bdf_font_file = strdup(optarg); break;
+    case 't': letter_spacing = atoi(optarg); break;
+    case 'C':
+      if (!parseColor(&color, optarg)) {
+        fprintf(stderr, "Invalid color spec: %s\n", optarg);
+        return usage(argv[0]);
+      }
+      break;
+    case 'B':
+      if (!parseColor(&bg_color, optarg)) {
+        fprintf(stderr, "Invalid background color spec: %s\n", optarg);
+        return usage(argv[0]);
+      }
+      break;
+    case 'O':
+      if (!parseColor(&outline_color, optarg)) {
+        fprintf(stderr, "Invalid outline color spec: %s\n", optarg);
+        return usage(argv[0]);
+      }
+      with_outline = true;
+      break;
+    default:
+      return usage(argv[0]);
+    }
+  }
+
+  for (int i = optind; i < argc; ++i) {
+    line.append(argv[i]).append(" ");
+  }
+
+  if (line.empty()) {
+    fprintf(stderr, "Add the text you want to print on the command-line.\n");
+    return usage(argv[0]);
+  }
+
+  if (bdf_font_file == NULL) {
+    fprintf(stderr, "Need to specify BDF font-file with -f\n");
+    return usage(argv[0]);
+  }
+
+  /*
+   * Load font. This needs to be a filename with a bdf bitmap font.
+   */
+  rgb_matrix::Font font;
+  if (!font.LoadFont(bdf_font_file)) {
+    fprintf(stderr, "Couldn't load font '%s'\n", bdf_font_file);
+    return 1;
+  }
+
+  /*
+   * If we want an outline around the font, we create a new font with
+   * the original font as a template that is just an outline font.
+   */
+  rgb_matrix::Font *outline_font = NULL;
+  if (with_outline) {
+    outline_font = font.CreateOutlineFont();
+  }
+
+  RGBMatrix *canvas = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
+  if (canvas == NULL)
+    return 1;
+
+  const bool all_extreme_colors = (matrix_options.brightness == 100)
+    && FullSaturation(color)
+    && FullSaturation(bg_color)
+    && FullSaturation(outline_color);
+  if (all_extreme_colors)
+    canvas->SetPWMBits(1);
+
+  signal(SIGTERM, InterruptHandler);
+  signal(SIGINT, InterruptHandler);
+
+  printf("CTRL-C for exit.\n");
+
+  // Create a new canvas to be used with led_matrix_swap_on_vsync
+  FrameCanvas *offscreen_canvas = canvas->CreateFrameCanvas();
+
+  const int scroll_direction = (speed >= 0) ? -1 : 1;
+  speed = fabs(speed);
+  int delay_speed_usec = 1000000;
+  if (speed > 0) {
+    delay_speed_usec = 1000000 / speed / font.CharacterWidth('W');
+  }
+
+  if (!xorigin_configured) {
+    if (speed == 0) {
+      // There would be no scrolling, so text would never appear. Move to front.
+      x_orig = with_outline ? 1 : 0;
+    } else {
+      x_orig = scroll_direction < 0 ? canvas->width() : 0;
+    }
+  }
+
+  int x = x_orig;
+  int y = y_orig;
+  int length = 0;
+
+  struct timespec next_frame = {0, 0};
+
+  uint frame_counter = 0;
+  while (!interrupt_received && loops != 0) {
+    ++frame_counter;
+    offscreen_canvas->Fill(bg_color.r, bg_color.g, bg_color.b);
+    const bool draw_on_frame = (blink_on <= 0)
+      || (frame_counter % (blink_on + blink_off) < (uint)blink_on);
+
+    if (draw_on_frame) {
+      if (outline_font) {
+        // The outline font, we need to write with a negative (-2) text-spacing,
+        // as we want to have the same letter pitch as the regular text that
+        // we then write on top.
+        rgb_matrix::DrawText(offscreen_canvas, *outline_font,
+                             x - 1, y + font.baseline(),
+                             outline_color, NULL,
+                             line.c_str(), letter_spacing - 2);
+      }
+
+      // length = holds how many pixels our text takes up
+      length = rgb_matrix::DrawText(offscreen_canvas, font,
+                                    x, y + font.baseline(),
+                                    color, NULL,
+                                    line.c_str(), letter_spacing);
+    }
+
+    x += scroll_direction;
+    if ((scroll_direction < 0 && x + length < 0) ||
+        (scroll_direction > 0 && x > canvas->width())) {
+      x = x_orig + ((scroll_direction > 0) ? -length : 0);
+      if (loops > 0) --loops;
+    }
+
+    // Make sure render-time delays are not influencing scroll-time
+    if (speed > 0) {
+      if (next_frame.tv_sec == 0) {
+        // First time. Start timer, but don't wait.
+        clock_gettime(CLOCK_MONOTONIC, &next_frame);
+      } else {
+        add_micros(&next_frame, delay_speed_usec);
+        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame, NULL);
+      }
+    }
+    // Swap the offscreen_canvas with canvas on vsync, avoids flickering
+    offscreen_canvas = canvas->SwapOnVSync(offscreen_canvas);
+    if (speed <= 0) pause();  // Nothing to scroll.
+  }
+
+  // Finished. Shut down the RGB matrix.
+  canvas->Clear();
+  delete canvas;
+
+  return 0;
+}

+ 426 - 0
matrix-zmq/rpi-rgb-led-matrix/utils/video-viewer.cc

@@ -0,0 +1,426 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+//
+// Quick hack based on ffmpeg
+// tutorial http://dranger.com/ffmpeg/tutorial01.html
+// in turn based on a tutorial by
+// Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
+//
+// HELP NEEDED
+// Note, this is known to not be optimal, causing flicker etc. It is at this
+// point merely a demonstration of what is possible. It also serves as a
+// converter to a 'stream' (-O option) which then can be played quickly with
+// the led-image-viewer.
+//
+// Pull requests are welcome to address
+//    * Use hardware acceleration if possible. The Pi does have some
+//      acceleration features IIRC, so if we could use these, that would be
+//      great.
+//    * Other improvements that could reduce the flicker on a Raspberry Pi.
+//      Currently it seems to create flicker in particular when decoding larger
+//      videos due to memory bandwidth overload (?). Might already be fixed
+//      with using hardware acceleration.
+//    * Add sound ? Right now, we don't decode the sound. It is usually
+//      not very useful as the builtin-sound is disabled when running the
+//      LED matrix, but if there is an external USB sound adapter, it might
+//      be nice.
+
+
+// Ancient AV versions forgot to set this.
+#define __STDC_CONSTANT_MACROS
+
+// libav: "U NO extern C in header ?"
+extern "C" {
+#  include <libavcodec/avcodec.h>
+#  include <libavformat/avformat.h>
+#  include <libavutil/imgutils.h>
+#  include <libswscale/swscale.h>
+}
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "led-matrix.h"
+#include "content-streamer.h"
+
+using rgb_matrix::FrameCanvas;
+using rgb_matrix::RGBMatrix;
+using rgb_matrix::StreamWriter;
+using rgb_matrix::StreamIO;
+
+volatile bool interrupt_received = false;
+static void InterruptHandler(int) {
+  interrupt_received = true;
+}
+
+struct LedPixel {
+  uint8_t r, g, b;
+};
+void CopyFrame(AVFrame *pFrame, FrameCanvas *canvas,
+               int offset_x, int offset_y,
+               int width, int height) {
+  for (int y = 0; y < height; ++y) {
+    LedPixel *pix = (LedPixel*) (pFrame->data[0] + y*pFrame->linesize[0]);
+    for (int x = 0; x < width; ++x, ++pix) {
+      canvas->SetPixel(x + offset_x, y + offset_y, pix->r, pix->g, pix->b);
+    }
+  }
+}
+
+// Scale "width" and "height" to fit within target rectangle of given size.
+void ScaleToFitKeepAscpet(int fit_in_width, int fit_in_height,
+                          int *width, int *height) {
+  if (*height < fit_in_height && *width < fit_in_width) return; // Done.
+  const float height_ratio = 1.0 * (*height) / fit_in_height;
+  const float width_ratio  = 1.0 * (*width) / fit_in_width;
+  const float ratio = (height_ratio > width_ratio) ? height_ratio : width_ratio;
+  *width = roundf(*width / ratio);
+  *height = roundf(*height / ratio);
+}
+
+static int usage(const char *progname, const char *msg = NULL) {
+  if (msg) {
+    fprintf(stderr, "%s\n", msg);
+  }
+  fprintf(stderr, "Show one or a sequence of video files on the RGB-Matrix\n");
+  fprintf(stderr, "usage: %s [options] <video> [<video>...]\n", progname);
+  fprintf(stderr, "Options:\n"
+          "\t-F                 : Full screen without black bars; aspect ratio might suffer\n"
+          "\t-O<streamfile>     : Output to stream-file instead of matrix (don't need to be root).\n"
+          "\t-s <count>         : Skip these number of frames in the beginning.\n"
+          "\t-c <count>         : Only show this number of frames (excluding skipped frames).\n"
+          "\t-V<vsync-multiple> : Instead of native video framerate, playback framerate\n"
+          "\t                     is a fraction of matrix refresh. In particular with a stable refresh,\n"
+          "\t                     this can result in more smooth playback. Choose multiple for desired framerate.\n"
+          "\t                     (Tip: use --led-limit-refresh for stable rate)\n"
+          "\t-v                 : verbose; prints video metadata and other info.\n"
+          "\t-f                 : Loop forever.\n");
+
+  fprintf(stderr, "\nGeneral LED matrix options:\n");
+  rgb_matrix::PrintMatrixFlags(stderr);
+  return 1;
+}
+
+static void add_nanos(struct timespec *accumulator, long nanoseconds) {
+  accumulator->tv_nsec += nanoseconds;
+  while (accumulator->tv_nsec > 1000000000) {
+    accumulator->tv_nsec -= 1000000000;
+    accumulator->tv_sec += 1;
+  }
+}
+
+// Convert deprecated color formats to new and manually set the color range.
+// YUV has funny ranges (16-235), while the YUVJ are 0-255. SWS prefers to
+// deal with the YUV range, but then requires to set the output range.
+// https://libav.org/documentation/doxygen/master/pixfmt_8h.html#a9a8e335cf3be472042bc9f0cf80cd4c5
+SwsContext *CreateSWSContext(const AVCodecContext *codec_ctx,
+                             int display_width, int display_height) {
+  AVPixelFormat pix_fmt;
+  bool src_range_extended_yuvj = true;
+  // Remap deprecated to new pixel format.
+  switch (codec_ctx->pix_fmt) {
+  case AV_PIX_FMT_YUVJ420P: pix_fmt = AV_PIX_FMT_YUV420P; break;
+  case AV_PIX_FMT_YUVJ422P: pix_fmt = AV_PIX_FMT_YUV422P; break;
+  case AV_PIX_FMT_YUVJ444P: pix_fmt = AV_PIX_FMT_YUV444P; break;
+  case AV_PIX_FMT_YUVJ440P: pix_fmt = AV_PIX_FMT_YUV440P; break;
+  default:
+    src_range_extended_yuvj = false;
+    pix_fmt = codec_ctx->pix_fmt;
+  }
+  SwsContext *swsCtx = sws_getContext(codec_ctx->width, codec_ctx->height,
+                                      pix_fmt,
+                                      display_width, display_height,
+                                      AV_PIX_FMT_RGB24, SWS_BILINEAR,
+                                      NULL, NULL, NULL);
+  if (src_range_extended_yuvj) {
+    // Manually set the source range to be extended. Read modify write.
+    int dontcare[4];
+    int src_range, dst_range;
+    int brightness, contrast, saturation;
+    sws_getColorspaceDetails(swsCtx, (int**)&dontcare, &src_range,
+                             (int**)&dontcare, &dst_range, &brightness,
+                             &contrast, &saturation);
+    const int* coefs = sws_getCoefficients(SWS_CS_DEFAULT);
+    src_range = 1;  // New src range.
+    sws_setColorspaceDetails(swsCtx, coefs, src_range, coefs, dst_range,
+                             brightness, contrast, saturation);
+  }
+  return swsCtx;
+}
+
+int main(int argc, char *argv[]) {
+  RGBMatrix::Options matrix_options;
+  rgb_matrix::RuntimeOptions runtime_opt;
+  if (!rgb_matrix::ParseOptionsFromFlags(&argc, &argv,
+                                         &matrix_options, &runtime_opt)) {
+    return usage(argv[0]);
+  }
+
+  int vsync_multiple = 1;
+  bool use_vsync_for_frame_timing = false;
+  bool maintain_aspect_ratio = true;
+  bool verbose = false;
+  bool forever = false;
+  int stream_output_fd = -1;
+  unsigned int frame_skip = 0;
+  unsigned int framecount_limit = UINT_MAX;  // even at 60fps, that is > 2yrs
+
+  int opt;
+  while ((opt = getopt(argc, argv, "vO:R:Lfc:s:FV:")) != -1) {
+    switch (opt) {
+    case 'v':
+      verbose = true;
+      break;
+    case 'f':
+      forever = true;
+      break;
+    case 'O':
+      stream_output_fd = open(optarg, O_CREAT|O_TRUNC|O_WRONLY, 0644);
+      if (stream_output_fd < 0) {
+        perror("Couldn't open output stream");
+        return 1;
+      }
+      break;
+    case 'L':
+      fprintf(stderr, "-L is deprecated. Use\n\t--led-pixel-mapper=\"U-mapper\" --led-chain=4\ninstead.\n");
+      return 1;
+      break;
+    case 'R':
+      fprintf(stderr, "-R is deprecated. "
+              "Use --led-pixel-mapper=\"Rotate:%s\" instead.\n", optarg);
+      return 1;
+      break;
+    case 'c':
+      framecount_limit = atoi(optarg);
+      break;
+    case 's':
+      frame_skip = atoi(optarg);
+      break;
+    case 'F':
+      maintain_aspect_ratio = false;
+      break;
+    case 'V':
+      vsync_multiple = atoi(optarg);
+      if (vsync_multiple <= 0)
+        return usage(argv[0],
+                     "-V: VSync-multiple needs to be a positive integer");
+      use_vsync_for_frame_timing = true;
+      break;
+    default:
+      return usage(argv[0]);
+    }
+  }
+
+  if (optind >= argc) {
+    fprintf(stderr, "Expected video filename.\n");
+    return usage(argv[0]);
+  }
+
+  const bool multiple_videos = (argc > optind + 1);
+
+  // We want to have the matrix start unless we actually write to a stream.
+  runtime_opt.do_gpio_init = (stream_output_fd < 0);
+  RGBMatrix *matrix = RGBMatrix::CreateFromOptions(matrix_options, runtime_opt);
+  if (matrix == NULL) {
+    return 1;
+  }
+  FrameCanvas *offscreen_canvas = matrix->CreateFrameCanvas();
+
+  long frame_count = 0;
+  StreamIO *stream_io = NULL;
+  StreamWriter *stream_writer = NULL;
+  if (stream_output_fd >= 0) {
+    stream_io = new rgb_matrix::FileStreamIO(stream_output_fd);
+    stream_writer = new StreamWriter(stream_io);
+    if (forever) {
+      fprintf(stderr, "-f (forever) doesn't make sense with -O; disabling\n");
+      forever = false;
+    }
+  }
+
+  // If we only have to loop a single video, we can avoid doing the
+  // expensive video stream set-up and just repeat in an inner loop.
+  const bool one_video_forever = forever && !multiple_videos;
+  const bool multiple_video_forever = forever && multiple_videos;
+
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
+  av_register_all();
+#endif
+  avformat_network_init();
+
+  signal(SIGTERM, InterruptHandler);
+  signal(SIGINT, InterruptHandler);
+
+  do {
+    for (int m = optind; m < argc && !interrupt_received; ++m) {
+      const char *movie_file = argv[m];
+      if (strcmp(movie_file, "-") == 0) {
+        movie_file = "/dev/stdin";
+      }
+
+      AVFormatContext *format_context = avformat_alloc_context();
+      if (avformat_open_input(&format_context, movie_file, NULL, NULL) != 0) {
+        perror("Issue opening file: ");
+        return -1;
+      }
+
+      if (avformat_find_stream_info(format_context, NULL) < 0) {
+        fprintf(stderr, "Couldn't find stream information\n");
+        return -1;
+      }
+
+      if (verbose) av_dump_format(format_context, 0, movie_file, 0);
+
+      // Find the first video stream
+      int videoStream = -1;
+      AVCodecParameters *codec_parameters = NULL;
+      AVCodec *av_codec = NULL;
+      for (int i = 0; i < (int)format_context->nb_streams; ++i) {
+        codec_parameters = format_context->streams[i]->codecpar;
+        av_codec = avcodec_find_decoder(codec_parameters->codec_id);
+        if (!av_codec) continue;
+        if (codec_parameters->codec_type == AVMEDIA_TYPE_VIDEO) {
+          videoStream = i;
+          break;
+        }
+      }
+      if (videoStream == -1)
+        return false;
+
+      // Frames per second; calculate wait time between frames.
+      AVStream *const stream = format_context->streams[videoStream];
+      AVRational rate = av_guess_frame_rate(format_context, stream, NULL);
+      const long frame_wait_nanos = 1e9 * rate.den / rate.num;
+      if (verbose) fprintf(stderr, "FPS: %f\n", 1.0*rate.num / rate.den);
+
+      AVCodecContext *codec_context = avcodec_alloc_context3(av_codec);
+      if (avcodec_parameters_to_context(codec_context, codec_parameters) < 0)
+        return -1;
+      if (avcodec_open2(codec_context, av_codec, NULL) < 0)
+        return -1;
+
+      /*
+       * Prepare frame to hold the scaled target frame to be send to matrix.
+       */
+      int display_width = codec_context->width;
+      int display_height = codec_context->height;
+      if (maintain_aspect_ratio) {
+        display_width = codec_context->width;
+        display_height = codec_context->height;
+        // Make display fit within canvas.
+        ScaleToFitKeepAscpet(matrix->width(), matrix->height(),
+                             &display_width, &display_height);
+      } else {
+        display_width = matrix->width();
+        display_height = matrix->height();
+      }
+      // Letterbox or pillarbox black bars.
+      const int display_offset_x = (matrix->width() - display_width)/2;
+      const int display_offset_y = (matrix->height() - display_height)/2;
+
+      // The output_frame_ will receive the scaled result.
+      AVFrame *output_frame = av_frame_alloc();
+      if (av_image_alloc(output_frame->data, output_frame->linesize,
+                         display_width, display_height, AV_PIX_FMT_RGB24,
+                         64) < 0) {
+        return -1;
+      }
+
+      if (verbose) {
+        fprintf(stderr, "Scaling %dx%d -> %dx%d; black border x:%d y:%d\n",
+                codec_context->width, codec_context->height,
+                display_width, display_height,
+                display_offset_x, display_offset_y);
+      }
+
+      // initialize SWS context for software scaling
+      SwsContext *const sws_ctx = CreateSWSContext(
+        codec_context, display_width, display_height);
+      if (!sws_ctx) {
+        fprintf(stderr, "Trouble doing scaling to %dx%d :(\n",
+                matrix->width(), matrix->height());
+        return 1;
+      }
+
+
+      struct timespec next_frame;
+
+      AVPacket *packet = av_packet_alloc();
+      AVFrame *decode_frame = av_frame_alloc();  // Decode video into this
+      do {
+        unsigned int frames_left = framecount_limit;
+        unsigned int frames_to_skip = frame_skip;
+        if (one_video_forever) {
+          av_seek_frame(format_context, videoStream, 0, AVSEEK_FLAG_ANY);
+          avcodec_flush_buffers(codec_context);
+        }
+        clock_gettime(CLOCK_MONOTONIC, &next_frame);
+        while (!interrupt_received && av_read_frame(format_context, packet) >= 0
+               && frames_left > 0) {
+          // Is this a packet from the video stream?
+          if (packet->stream_index == videoStream) {
+            // Determine absolute end of this frame now so that we don't include
+            // decoding overhead. TODO: skip frames if getting too slow ?
+            add_nanos(&next_frame, frame_wait_nanos);
+
+            // Decode video frame
+            if (avcodec_send_packet(codec_context, packet) < 0)
+              continue;
+
+            if (avcodec_receive_frame(codec_context, decode_frame) < 0)
+              continue;
+
+            if (frames_to_skip) { frames_to_skip--; continue; }
+
+            // Convert the image from its native format to RGB
+            sws_scale(sws_ctx, (uint8_t const * const *)decode_frame->data,
+                      decode_frame->linesize, 0, codec_context->height,
+                      output_frame->data, output_frame->linesize);
+            CopyFrame(output_frame, offscreen_canvas,
+                      display_offset_x, display_offset_y,
+                      display_width, display_height);
+            frame_count++;
+            frames_left--;
+            if (stream_writer) {
+              if (verbose) fprintf(stderr, "%6ld", frame_count);
+              stream_writer->Stream(*offscreen_canvas, frame_wait_nanos/1000);
+            } else {
+              offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas,
+                                                     vsync_multiple);
+            }
+          }
+          if (!stream_writer && !use_vsync_for_frame_timing) {
+            clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame, NULL);
+          }
+          av_packet_unref(packet);
+        }
+      } while (one_video_forever && !interrupt_received);
+
+      av_packet_free(&packet);
+
+      av_frame_free(&output_frame);
+      av_frame_free(&decode_frame);
+      avcodec_close(codec_context);
+      avformat_close_input(&format_context);
+    }
+  } while (multiple_video_forever && !interrupt_received);
+
+  if (interrupt_received) {
+    // Feedback for Ctrl-C, but most importantly, force a newline
+    // at the output, so that commandline-shell editing is not messed up.
+    fprintf(stderr, "Got interrupt. Exiting\n");
+  }
+
+  delete matrix;
+  delete stream_writer;
+  delete stream_io;
+  fprintf(stderr, "Total of %ld frames decoded\n", frame_count);
+
+  return 0;
+}

+ 108 - 0
matrix-zmq/src/main.cpp

@@ -0,0 +1,108 @@
+#include <csignal>
+#include <cstdint>
+#include <cstdlib>
+#include <getopt.h>
+#include <iostream>
+#include <led-matrix.h>
+#include <string>
+#include <unistd.h>
+#include <zmq.hpp>
+
+class ServerOptions {
+public:
+  enum class Args { endpoint = 1, bytes_per_pixel };
+
+  std::string endpoint = "tcp://*:42024";
+  int bytes_per_pixel = 3;
+
+  static ServerOptions from_args(int argc, char *argv[]) {
+    ServerOptions server_options;
+
+    static struct option long_opts[]{
+        {"zmq-endpoint", required_argument, nullptr,
+         static_cast<int>(ServerOptions::Args::endpoint)},
+        {"bytes-per-pixel", required_argument, nullptr,
+         static_cast<int>(ServerOptions::Args::bytes_per_pixel)},
+        {nullptr, 0, nullptr, 0}};
+
+    int opt_code;
+    int opt_index;
+    while ((opt_code = getopt_long(argc, argv, "", long_opts, &opt_index)) !=
+           -1) {
+      switch (opt_code) {
+      case static_cast<int>(ServerOptions::Args::endpoint):
+        server_options.endpoint = std::string(optarg);
+        break;
+
+      case static_cast<int>(ServerOptions::Args::bytes_per_pixel):
+        server_options.bytes_per_pixel = std::stoi(optarg);
+        break;
+      }
+    }
+
+    return server_options;
+  }
+};
+
+volatile bool running = true;
+
+void handle_interrupt(const int signo) { running = false; }
+
+int main(int argc, char *argv[]) {
+  std::signal(SIGINT, handle_interrupt);
+  std::signal(SIGTERM, handle_interrupt);
+
+  rgb_matrix::RGBMatrix::Options matrix_opts;
+  rgb_matrix::RuntimeOptions runtime_opts;
+  ParseOptionsFromFlags(&argc, &argv, &matrix_opts, &runtime_opts);
+  auto server_options = ServerOptions::from_args(argc, argv);
+
+  auto matrix = CreateMatrixFromOptions(matrix_opts, runtime_opts);
+  if (matrix == nullptr) {
+    return 1;
+  }
+
+  matrix->set_luminance_correct(true);
+
+  zmq::context_t zmq_ctx;
+  zmq::socket_t zmq_sock(zmq_ctx, zmq::socket_type::rep);
+  zmq_sock.bind(server_options.endpoint.c_str());
+  std::cout << "Listening on " << server_options.endpoint
+            << " @ " << (server_options.bytes_per_pixel) * 8 << "BPP" << std::endl;
+
+  size_t expected_frame_size =
+      matrix->width() * matrix->height() * server_options.bytes_per_pixel;
+  uint8_t frame[expected_frame_size];
+  std::cout << "Frame dimensions: " << matrix->width() << "x" << matrix->height() << std::endl;
+  std::cout << "Expected frame size: " << expected_frame_size << " bytes" << std::endl;
+
+  while (running) {
+    size_t frame_size = zmq_sock.recv(&frame, expected_frame_size);
+    zmq_sock.send(nullptr, 0);
+
+    if (frame_size != expected_frame_size) {
+      std::cout << "Frame size mismatch! Expected " << expected_frame_size
+                << " but got " << frame_size << std::endl;
+    } else {
+      for (auto y = 0; y < matrix->height(); y++) {
+        for (auto x = 0; x < matrix->width(); x++) {
+          uint8_t r =
+              frame[y * matrix->width() * server_options.bytes_per_pixel +
+                    x * server_options.bytes_per_pixel + 0];
+          uint8_t g =
+              frame[y * matrix->width() * server_options.bytes_per_pixel +
+                    x * server_options.bytes_per_pixel + 1];
+          uint8_t b =
+              frame[y * matrix->width() * server_options.bytes_per_pixel +
+                    x * server_options.bytes_per_pixel + 2];
+
+          matrix->SetPixel(x, y, r, g, b);
+        }
+      }
+    }
+  }
+
+  delete matrix;
+
+  return 0;
+}

+ 5 - 0
matrix-zmq/travis/push_to_docker.sh

@@ -0,0 +1,5 @@
+#!/bin/bash
+echo "$DOCKER_PASSWORD" \
+  | docker login --username "$DOCKER_USERNAME" --password-stdin
+
+docker push knifa/led-matrix-zmq-server