--- /dev/null
+WebDVD is licenced under the GNU GPL, version 2, with the following
+additions:
+
+A. You may combine and distribute WebDVD and derivative works with
+ libraries licenced under the GNU LGPL without exercising the option to
+ treat them as licenced under the GPL.
+
+B. When distributing WebDVD or derivative works without source code
+ included you must ensure that the documentation retains the notice that
+ "this software is based in part on the work of the Independent JPEG
+ Group", or else remove the code contained in jquant2.c to which that
+ notice applies.
+
+--
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) 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
+this service 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 make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. 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.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+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
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the 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 a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE 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.
+
+ END OF TERMS AND CONDITIONS
+\f
+ 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
+convey 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision 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, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This 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 Library General
+Public License instead of this License.
--- /dev/null
+Building WebDVD
+===============
+
+WebDVD is written in C++ and requires a recent C++ compiler e.g. g++
+3.3.
+
+It requires headers and libraries for gtkmm and Mozilla. I have
+developed and tested it with gtkmm 2.2.12 and Mozilla 1.7.8 but it may
+well work with earlier or later versions of these.
+
+I use Debian Linux and have not yet attempted to build it on other
+systems, but it should work on most modern Unix-like systems.
+
+If the above requirements are satisfied, building it should be as
+simple as running "make" and then "sudo make install".
+
+- Ben Hutchings <ben@decadentplace.org.uk>
--- /dev/null
+prefix := /usr/local
+
+moz_include_dir := \
+ $(shell pkg-config --variable=prefix mozilla-gtkmozembed)/include/mozilla
+moz_lib_dir := \
+ $(shell pkg-config --variable=prefix mozilla-gtkmozembed)/lib/mozilla
+
+CFLAGS := -Wall
+CPPFLAGS := -D_REENTRANT
+CXXFLAGS := -Wall
+LDFLAGS := -lpthread $(shell pkg-config --libs gtkmm-2.0 mozilla-gtkmozembed) \
+ -Wl,-rpath -Wl,$(moz_lib_dir)
+
+ifdef NDEBUG
+ CPPFLAGS += -DNDEBUG
+else
+ CFLAGS += -g
+ CXXFLAGS += -g
+ LDFLAGS += -g
+endif
+
+cxxsources := \
+ auto_proc.cpp browserwidget.cpp childiterator.cpp framebuffer.cpp \
+ linkiterator.cpp pixbufs.cpp stylesheets.cpp webdvd.cpp xpcom_support.cpp
+csources := jquant2.c
+
+webdvd : $(cxxsources:.cpp=.o) $(csources:.c=.o)
+ $(CXX) $(LDFLAGS) -o $@ $^
+
+clean :
+ rm -f webdvd *.d *.o *~ \#* *.orig *.rej
+
+install :
+ mkdir -p -m 755 $(prefix)/bin $(prefix)/lib/webdvd
+ install -m 755 -s webdvd $(prefix)/bin
+ install -m 644 webdvd.css $(prefix)/lib/webdvd
+
+.PHONY : clean install
+
+browserwidget.% : CPPFLAGS += -DMOZ_LIB_DIR='"$(moz_lib_dir)"'
+
+webdvd.% \
+ : CPPFLAGS += -DWEBDVD_LIB_DIR='"$(prefix)/lib/webdvd"'
+
+browserwidget.% pixbufs.% webdvd.% \
+ : CPPFLAGS += $(shell pkg-config --cflags gtkmm-2.0)
+
+browserwidget.% childiterator.o linkiterator.% stylesheets.% webdvd.% \
+xpcom_support.% \
+ : CPPFLAGS += $(shell pkg-config --cflags mozilla-gtkmozembed)
+
+# These dig a bit deeper into Mozilla
+linkiterator.% stylesheets.% webdvd.% \
+ : CPPFLAGS += $(addprefix -I$(moz_include_dir)/, \
+ content docshell dom gfx layout necko webshell widget)
+
+%.d : %.cpp
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM -MF $@ $<
+
+%.d : %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) -MM -MF $@ $<
+
+ifneq ($(MAKECMDGOALS),clean)
+ include $(cxxsources:.cpp=.d) $(csources:.c=.d)
+endif
+
+# Temporary rule for testing output files.
+
+%.vob : %_back.png %.spumux %_links.png
+ pngtopnm $*_back.png \
+ | ppmtoy4m -v0 -n 1 -F 25:1 -A 59:54 -I p -S 420_mpeg2 \
+ | mpeg2enc -v0 -f 8 -a 2 -o /dev/stdout \
+ | mplex -v0 -f 8 -o /dev/stdout /dev/stdin \
+ | spumux -v0 -m dvd $*.spumux > $@
--- /dev/null
+WebDVD
+======
+
+WebDVD is intended to provide a simple way of producing DVDs with
+attractive and usable menus. It converts HTML pages into DVD menus by
+rendering them in Mozilla and reproducing their link structure. This
+allows you to design DVDs using familiar HTML editing tools or your
+favourite text editor. This very early version produces a set of
+files suitable for passing to the "dvdauthor" and "spumux" programs,
+but in future it is intended to run the necessary external programs
+automatically.
+
+Requirements
+------------
+
+WebDVD depends on the following software:
+
+- Gtkmm 2.0
+- Mozilla 1.7.x (later versions may work but are untested)
+- Xvfb (from XFree86 or X.org)
+
+To build a complete DVD image you will also need:
+
+- dvdauthor
+- mjpegtools
+- mkisofs
+- netpbm
+
+You will also need a program such as ffmpeg or mencoder for producing
+DVD-suitable MPEG-1 or MPEG-2 video files.
+
+Usage
+-----
+
+Run "webdvd URL" where URL is the URL for the page that is to be the
+top menu of the DVD. It will automatically follow links to other
+pages and to video files, rendering each page. You must be careful
+not to link to pages that you do not want to appear on the disc, such
+as normal web sites.
+
+By default, webdvd uses a frame size of 720x576, which is suitable for
+PAL DVDs. If you wish to produce NTSC DVDs you must override this
+by adding the option "-geometry 720x480".
+
+This will create the following files (with NNNNNN replaced with each
+successive page number):
+
+- webdvd.dvdauthor: This is an XML file to be passed to dvdauthor.
+- page_NNNNNN.spumux: These are XML files to be passed to spumux.
+- page_NNNNNN_back.png: This is a static image of the page, which
+ becomes the menu background.
+- page_NNNNNN_links.png: This is an image of the change in appearance
+ of each link when the pointer is over it.
+
+Currently, you must run commands along the following lines to produce
+a complete DVD image:
+
+for spumux in page_??????.spumux; do
+ page=$(basename $spumux .spumux)
+ pngtopnm ${page}_back.png \
+ | ppmtoy4m -v0 -n 1 -F 25:1 -A 59:54 -I p -S 420_mpeg2 \
+ | mpeg2enc -v0 -f 8 -a 2 -o /dev/stdout \
+ | mplex -v0 -f 8 -o /dev/stdout /dev/stdin \
+ | spumux -v0 -m dvd $spumux > ${page}.vob
+done
+rm -rf dvd-temp
+dvdauthor -o dvd-temp -x webdvd.dvdauthor
+mkisofs -dvd-video dvd-temp >dvd.iso
+rm -rf dvd-temp
+
+Adjust the name of the temporary directory (here "dvd-temp") and the
+output file ("dvd.iso") as you please.
+
+If you are using NTSC video, you will need to change the ppmtoy4m
+parameters. Use "-F 30000:1001 -A 10:11" instead of "-F 25:1 -A 59:54".
+
+Limitations
+-----------
+
+Each page must fit within the frame - DVD players do not support
+scrolling menus and WebDVD currently is not able to split them into
+multiple menus. Note also that the video frame is somewhat larger
+than the visible area of a normal TV. For this reason WebDVD applies
+a stylesheet to all pages that adds 50-60 pixels of padding on all
+sides of the body.
+
+WebDVD sends a "mouseover" event for each link and sets it into its
+"hover" state, then records how this changes its appearance. This
+change is then shown when the corresponding button on the DVD menu is
+highlighted. WebDVD applies a stylesheet which changes the colour of
+text links in the "hover" state, but this has no effect on image
+links. You must ensure that image links are highlighted in an obvious
+way when the mouse pointer is over them.
+
+The DVD specifications limit each menu to having no more than 36
+buttons. In any case, it is poor design to have very large numbers of
+buttons on a single menu. WebDVD will warn you if you use more than
+this number of a links on a page, and will ignore any additional ones.
+
+The DVD specification also limits the overlays that are used for
+highlighting of buttons to using no more than 4 colours. WebDVD will
+reduce link highlighting to 1 transparent and 3 opaque colours using
+Floyd-Steinberg dithering, which is certainly good enough for
+anti-alised text but may not be so good for complex highlighting.
+
+Author and copyright
+--------------------
+
+WebDVD was written by Ben Hutchings <ben@decadentplace.org.uk>.
+Copyright 2005 Ben Hutchings.
+
+This software is based in part on the work of the Independent JPEG Group.
+Copyright 1991-1998 Thomas G. Lane. (This applies to the file jquant2.c.)
--- /dev/null
+Call spumux.
+Disable scrollbars (not sure how how to do this).
+Call dvdauthor.
+May need to override default menu navigation actions because dvdauthor
+seems to cause rather undesirable behaviour when buttons are not
+arranged in a grid.
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_AUTO_ARRAY_HPP
+#define INC_AUTO_ARRAY_HPP
+
+#include <cstddef>
+
+// Like auto_ptr, but for arrays
+
+template<typename element_type>
+class auto_array_ref;
+
+template<typename element_type>
+class auto_array
+{
+ typedef auto_array_ref<element_type> ref_type;
+public:
+ auto_array()
+ : ptr_(0)
+ {}
+ explicit auto_array(element_type * ptr)
+ : ptr_(ptr)
+ {}
+ auto_array(ref_type other)
+ : ptr_(other.release())
+ {}
+ auto_array & operator=(auto_array & other)
+ {
+ reset(other.release());
+ }
+ ~auto_array()
+ {
+ reset();
+ }
+ element_type * get() const
+ {
+ return ptr_;
+ }
+ element_type * release()
+ {
+ element_type * ptr(ptr_);
+ ptr_ = 0;
+ return ptr;
+ }
+ void reset(element_type * ptr = 0)
+ {
+ delete[] ptr_;
+ ptr_ = ptr;
+ }
+ element_type & operator[](std::ptrdiff_t index)
+ {
+ return ptr_[index];
+ }
+ operator ref_type()
+ {
+ return ref_type(*this);
+ }
+private:
+ element_type * ptr_;
+};
+
+template<typename element_type>
+class auto_array_ref
+{
+ typedef auto_array<element_type> target_type;
+public:
+ explicit auto_array_ref(target_type & target)
+ : target_(target)
+ {}
+ element_type * release()
+ {
+ return target_.release();
+ }
+private:
+ target_type & target_;
+};
+
+#endif // !INC_AUTO_ARRAY_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_AUTO_FD_HPP
+#define INC_AUTO_FD_HPP
+
+#include "auto_handle.hpp"
+
+#include <cassert>
+
+#include <unistd.h>
+
+struct auto_fd_closer
+{
+ void operator()(int fd) const
+ {
+ if (fd >= 0)
+ {
+ int result = close(fd);
+ assert(result == 0);
+ }
+ }
+};
+struct auto_fd_factory
+{
+ int operator()() const { return -1; }
+};
+typedef auto_handle<int, auto_fd_closer, auto_fd_factory> auto_fd;
+
+#endif // !INC_AUTO_FD_HPP
--- /dev/null
+#ifndef INC_AUTO_HANDLE_H
+#define INC_AUTO_HANDLE_H
+
+// Like auto_ptr, but for arbitrary "handle" types.
+// The parameters are:
+// - handle_type: the type of the raw handle to be wrapped
+// - closer_type: a function object type whose operator() takes a raw handle
+// and closes it (or does nothing if it is a null handle)
+// - factory_type: a function object type whose operator() returns a null
+// handle
+
+template<typename handle_type, typename closer_type, typename factory_type>
+class auto_handle_ref;
+
+template<typename handle_type, typename closer_type, typename factory_type>
+class auto_handle
+ // Use inheritance so we can benefit from the empty base optimisation
+ : private closer_type, private factory_type
+{
+ typedef auto_handle_ref<handle_type, closer_type, factory_type> ref_type;
+public:
+ auto_handle()
+ : handle_(factory_type::operator()())
+ {}
+ explicit auto_handle(handle_type handle)
+ : handle_(handle)
+ {}
+ auto_handle(ref_type other)
+ : handle_(other.release())
+ {}
+ auto_handle & operator=(auto_handle & other)
+ {
+ reset(other.release());
+ }
+ ~auto_handle()
+ {
+ reset();
+ }
+ handle_type get() const
+ {
+ return handle_;
+ }
+ handle_type release()
+ {
+ handle_type handle(handle_);
+ handle_ = factory_type::operator()();
+ return handle;
+ }
+ void reset()
+ {
+ closer_type::operator()(handle_);
+ handle_ = factory_type::operator()();
+ }
+ void reset(handle_type handle)
+ {
+ closer_type::operator()(handle_);
+ handle_ = handle;
+ }
+ operator ref_type()
+ {
+ return ref_type(*this);
+ }
+private:
+ handle_type handle_;
+};
+
+template<typename handle_type, typename closer_type, typename factory_type>
+class auto_handle_ref
+{
+ typedef auto_handle<handle_type, closer_type, factory_type> target_type;
+public:
+ explicit auto_handle_ref(target_type & target)
+ : target_(target)
+ {}
+ handle_type release()
+ {
+ return target_.release();
+ }
+private:
+ target_type & target_;
+};
+
+#endif // !INC_AUTO_HANDLE_H
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include <cassert>
+
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <wait.h>
+
+#include "auto_proc.hpp"
+
+void auto_kill_proc_closer::operator()(pid_t pid) const
+{
+ assert(pid >= -1);
+
+ if (pid > 0 && waitpid(pid, NULL, WNOHANG) == 0)
+ kill(pid, SIGTERM);
+}
+
+void auto_wait_proc_closer::operator()(pid_t pid) const
+{
+ assert(pid >= -1);
+
+ if (pid > 0)
+ while (waitpid(pid, NULL, 0) == -1)
+ if (errno != EINTR)
+ {
+ assert(!"invalid pid in auto_wait_proc_closer");
+ break;
+ }
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_AUTO_PROC_HPP
+#define INC_AUTO_PROC_HPP
+
+#include <sys/types.h>
+
+#include "auto_handle.hpp"
+
+struct auto_proc_factory
+{
+ pid_t operator()() const { return -1; }
+};
+
+struct auto_kill_proc_closer
+{
+ void operator()(pid_t pid) const;
+};
+typedef auto_handle<pid_t, auto_kill_proc_closer, auto_proc_factory>
+ auto_kill_proc;
+
+struct auto_wait_proc_closer
+{
+ void operator()(pid_t pid) const;
+};
+typedef auto_handle<pid_t, auto_wait_proc_closer, auto_proc_factory>
+ auto_wait_proc;
+
+#endif // !INC_AUTO_PROC_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_AUTO_TEMP_FILE_HPP
+#define INC_AUTO_TEMP_FILE_HPP
+
+#include "auto_handle.hpp"
+
+#include <string>
+
+#include <unistd.h>
+
+struct auto_temp_file_closer
+{
+ void operator()(const std::string & name) const
+ {
+ if (!name.empty())
+ unlink(name.c_str());
+ }
+};
+struct auto_temp_file_factory
+{
+ std::string operator()() const { return std::string(); }
+};
+typedef auto_handle<std::string, auto_temp_file_closer, auto_temp_file_factory>
+ auto_temp_file;
+
+#endif // !INC_AUTO_TEMP_FILE_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include "browserwidget.hpp"
+
+#include <cassert>
+
+#include <gtkmozembed_internal.h>
+
+BrowserWidget::BrowserWidget()
+ : Gtk::Bin(GTK_BIN(gtk_moz_embed_new()))
+{
+}
+BrowserWidget::~BrowserWidget()
+{
+}
+
+GtkMozEmbed * BrowserWidget::gobj()
+{
+ return GTK_MOZ_EMBED(gobject_);
+}
+const GtkMozEmbed * BrowserWidget::gobj() const
+{
+ return GTK_MOZ_EMBED(gobject_);
+}
+
+void BrowserWidget::load_uri(const char * uri)
+{
+ gtk_moz_embed_load_url(gobj(), uri);
+}
+void BrowserWidget::load_uri(const std::string & uri)
+{
+ return load_uri(uri.c_str());
+}
+void BrowserWidget::stop_load()
+{
+ gtk_moz_embed_stop_load(gobj());
+}
+void BrowserWidget::go_back()
+{
+ gtk_moz_embed_go_back(gobj());
+}
+void BrowserWidget::go_forward()
+{
+ gtk_moz_embed_go_forward(gobj());
+}
+void BrowserWidget::reload(gint32 flags)
+{
+ gtk_moz_embed_reload(gobj(), flags);
+}
+
+bool BrowserWidget::can_go_back() const
+{
+ return gtk_moz_embed_can_go_back(const_cast<GtkMozEmbed *>(gobj()));
+}
+bool BrowserWidget::can_go_forward() const
+{
+ return gtk_moz_embed_can_go_forward(const_cast<GtkMozEmbed *>(gobj()));
+}
+
+namespace
+{
+ template<typename T>
+ class c_scoped_ptr
+ {
+ public:
+ explicit c_scoped_ptr(T * p = 0) : p_(p) {}
+ ~c_scoped_ptr() { free(p_); }
+ T * get() const { return p_; }
+ T * release()
+ {
+ T * p = p_;
+ p_ = NULL;
+ return p;
+ }
+ void reset(T * p = 0)
+ {
+ free(p_);
+ p_ = p;
+ }
+ private:
+ T * p_;
+ };
+}
+
+std::string BrowserWidget::get_link_message() const
+{
+ c_scoped_ptr<char> str(
+ gtk_moz_embed_get_link_message(const_cast<GtkMozEmbed *>(gobj())));
+ return std::string(str.get());
+}
+std::string BrowserWidget::get_js_status() const
+{
+ c_scoped_ptr<char> str(
+ gtk_moz_embed_get_js_status(const_cast<GtkMozEmbed *>(gobj())));
+ return std::string(str.get());
+}
+std::string BrowserWidget::get_title() const
+{
+ c_scoped_ptr<char> str(
+ gtk_moz_embed_get_title(const_cast<GtkMozEmbed *>(gobj())));
+ return std::string(str.get());
+}
+std::string BrowserWidget::get_location() const
+{
+ c_scoped_ptr<char> str(
+ gtk_moz_embed_get_location(const_cast<GtkMozEmbed *>(gobj())));
+ return std::string(str.get());
+}
+already_AddRefed<nsIWebBrowser> BrowserWidget::get_browser()
+{
+ nsIWebBrowser * result = 0;
+ gtk_moz_embed_get_nsIWebBrowser(gobj(), &result);
+ assert(result);
+ return dont_AddRef(result);
+}
+
+namespace
+{
+ void BrowserWidget_signal_link_message_callback(GtkMozEmbed * self, void * data)
+ {
+ typedef SigC::Slot0<void> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_link_message_info =
+ {
+ "link_message",
+ (GCallback) &BrowserWidget_signal_link_message_callback,
+ (GCallback) &BrowserWidget_signal_link_message_callback
+ };
+
+ void BrowserWidget_signal_js_status_callback(GtkMozEmbed * self, void * data)
+ {
+ typedef SigC::Slot0<void> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_js_status_info =
+ {
+ "js_status",
+ (GCallback) &BrowserWidget_signal_js_status_callback,
+ (GCallback) &BrowserWidget_signal_js_status_callback
+ };
+
+ void BrowserWidget_signal_location_callback(GtkMozEmbed * self, void * data)
+ {
+ typedef SigC::Slot0<void> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_location_info =
+ {
+ "location",
+ (GCallback) &BrowserWidget_signal_location_callback,
+ (GCallback) &BrowserWidget_signal_location_callback
+ };
+
+ void BrowserWidget_signal_title_callback(GtkMozEmbed * self, void * data)
+ {
+ typedef SigC::Slot0<void> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_title_info =
+ {
+ "title",
+ (GCallback) &BrowserWidget_signal_title_callback,
+ (GCallback) &BrowserWidget_signal_title_callback
+ };
+
+ void BrowserWidget_signal_progress_callback(
+ GtkMozEmbed * self, gint p0, gint p1, void * data)
+ {
+ typedef SigC::Slot2<void, gint, gint> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(p0, p1, slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_progress_info =
+ {
+ "progress",
+ (GCallback) &BrowserWidget_signal_progress_callback,
+ (GCallback) &BrowserWidget_signal_progress_callback
+ };
+
+ void BrowserWidget_signal_net_state_callback(
+ GtkMozEmbed * self, const char * p0, gint p1, guint p2, void * data)
+ {
+ typedef SigC::Slot3<void, const char *, gint, guint> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(p0, p1, p2, slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_net_state_info =
+ {
+ "net_state_all",
+ (GCallback) &BrowserWidget_signal_net_state_callback,
+ (GCallback) &BrowserWidget_signal_net_state_callback
+ };
+
+ void BrowserWidget_signal_net_start_callback(GtkMozEmbed * self, void * data)
+ {
+ typedef SigC::Slot0<void> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_net_start_info =
+ {
+ "net_start",
+ (GCallback) &BrowserWidget_signal_net_start_callback,
+ (GCallback) &BrowserWidget_signal_net_start_callback
+ };
+
+ void BrowserWidget_signal_net_stop_callback(GtkMozEmbed * self, void * data)
+ {
+ typedef SigC::Slot0<void> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_net_stop_info =
+ {
+ "net_stop",
+ (GCallback) &BrowserWidget_signal_net_stop_callback,
+ (GCallback) &BrowserWidget_signal_net_stop_callback
+ };
+
+ void BrowserWidget_signal_new_window_callback(
+ GtkMozEmbed * self, GtkMozEmbed ** p0, guint p1, void * data)
+ {
+ typedef SigC::Slot1<BrowserWidget *, guint> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ {
+ if (BrowserWidget * result =
+ (*(SlotType::Proxy)(slot->proxy_))(p1, slot))
+ {
+ *p0 = result->gobj();
+ return;
+ }
+ }
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+
+ *p0 = NULL;
+ return;
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_new_window_info =
+ {
+ "new_window",
+ (GCallback) &BrowserWidget_signal_new_window_callback,
+ (GCallback) &BrowserWidget_signal_new_window_callback
+ };
+
+ void BrowserWidget_signal_visibility_callback(
+ GtkMozEmbed * self, gboolean p0, void * data)
+ {
+ typedef SigC::Slot1<void, bool> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(p0, slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_visibility_info =
+ {
+ "visibility",
+ (GCallback) &BrowserWidget_signal_visibility_callback,
+ (GCallback) &BrowserWidget_signal_visibility_callback
+ };
+
+ void BrowserWidget_signal_destroy_browser_callback(
+ GtkMozEmbed * self, void * data)
+ {
+ typedef SigC::Slot0<void> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ (*(SlotType::Proxy)(slot->proxy_))(slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_destroy_info =
+ {
+ "destroy_browser",
+ (GCallback) &BrowserWidget_signal_destroy_browser_callback,
+ (GCallback) &BrowserWidget_signal_destroy_browser_callback
+ };
+
+ gint BrowserWidget_signal_open_uri_callback(
+ GtkMozEmbed * self, const char * p0, void * data)
+ {
+ typedef SigC::Slot1<bool, const char *> SlotType;
+
+ if (Glib::ObjectBase::_get_current_wrapper((GObject *)self))
+ {
+ try
+ {
+ if (SigC::SlotNode * const slot =
+ Glib::SignalProxyNormal::data_to_slot(data))
+ return (*(SlotType::Proxy)(slot->proxy_))(p0, slot);
+ }
+ catch(...)
+ {
+ Glib::exception_handlers_invoke();
+ }
+ }
+
+ return 0;
+ }
+
+ const Glib::SignalProxyInfo BrowserWidget_signal_open_uri_info =
+ {
+ "open_uri",
+ (GCallback) &BrowserWidget_signal_open_uri_callback,
+ (GCallback) &BrowserWidget_signal_open_uri_callback
+ };
+
+} // namespace
+
+Glib::SignalProxy0<void> BrowserWidget::signal_link_message()
+{
+ return Glib::SignalProxy0<void>(this, &BrowserWidget_signal_link_message_info);
+}
+Glib::SignalProxy0<void> BrowserWidget::signal_js_status()
+{
+ return Glib::SignalProxy0<void>(this, &BrowserWidget_signal_js_status_info);
+}
+Glib::SignalProxy0<void> BrowserWidget::signal_location()
+{
+ return Glib::SignalProxy0<void>(this, &BrowserWidget_signal_location_info);
+}
+Glib::SignalProxy0<void> BrowserWidget::signal_title()
+{
+ return Glib::SignalProxy0<void>(this, &BrowserWidget_signal_title_info);
+}
+Glib::SignalProxy2<void, gint /*cur*/, gint /*max*/> BrowserWidget::signal_progress()
+{
+ return Glib::SignalProxy2<void, gint, gint>(
+ this, &BrowserWidget_signal_progress_info);
+}
+Glib::SignalProxy3<void, const char *, gint /*flags*/, guint /*status*/>
+BrowserWidget::signal_net_state()
+{
+ return Glib::SignalProxy3<void, const char *, gint, guint>(
+ this, &BrowserWidget_signal_net_state_info);
+}
+Glib::SignalProxy0<void> BrowserWidget::signal_net_start()
+{
+ return Glib::SignalProxy0<void>(this, &BrowserWidget_signal_net_start_info);
+}
+Glib::SignalProxy0<void> BrowserWidget::signal_net_stop()
+{
+ return Glib::SignalProxy0<void>(this, &BrowserWidget_signal_net_stop_info);
+}
+Glib::SignalProxy1<BrowserWidget *, guint /*chromemask*/> BrowserWidget::signal_new_window()
+{
+ return Glib::SignalProxy1<BrowserWidget *, guint>(
+ this, &BrowserWidget_signal_new_window_info);
+}
+Glib::SignalProxy1<void, bool /*visibility*/> BrowserWidget::signal_visibility()
+{
+ return Glib::SignalProxy1<void, bool>(
+ this, &BrowserWidget_signal_visibility_info);
+}
+Glib::SignalProxy0<void> BrowserWidget::signal_destroy()
+{
+ return Glib::SignalProxy0<void>(this, &BrowserWidget_signal_destroy_info);
+}
+Glib::SignalProxy1<bool, const char * /*uri*/> BrowserWidget::signal_open_uri()
+{
+ return Glib::SignalProxy1<bool, const char *>(
+ this, &BrowserWidget_signal_open_uri_info);
+}
+
+BrowserWidget::BrowserWidget(GObject * gobject, bool take_copy)
+{
+ assert(GTK_MOZ_EMBED(gobject));
+ gobject_ = gobject;
+ if (take_copy)
+ reference();
+}
+Glib::ObjectBase * BrowserWidget::wrap_new(GObject * gobject)
+{
+ return new BrowserWidget(gobject, false);
+}
+
+void BrowserWidget::init()
+{
+ gtk_moz_embed_set_comp_path(MOZ_LIB_DIR);
+ wrap_register(gtk_moz_embed_get_type(), wrap_new);
+}
+
+namespace Glib
+{
+ BrowserWidget * wrap(GtkMozEmbed * object, bool take_copy)
+ {
+ return dynamic_cast<BrowserWidget *>(
+ Glib::wrap_auto((GObject*)(object), take_copy));
+ }
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_BROWSERWIDGET_HPP
+#define INC_BROWSERWIDGET_HPP
+
+#include <glibmm/signalproxy.h>
+#include <gtkmm/bin.h>
+
+#include <gtkmozembed.h>
+#include <nsCOMPtr.h>
+
+class BrowserWidget;
+class nsIWebBrowser;
+
+namespace Glib
+{
+ BrowserWidget * wrap(GtkMozEmbed * object, bool take_copy = false);
+}
+
+class BrowserWidget : public Gtk::Bin
+{
+public:
+ BrowserWidget();
+ virtual ~BrowserWidget();
+ GtkMozEmbed * gobj();
+ const GtkMozEmbed * gobj() const;
+
+ void load_uri(const char * uri);
+ void load_uri(const std::string & uri);
+ void stop_load();
+ void go_back();
+ void go_forward();
+ void reload(gint32 flags = GTK_MOZ_EMBED_FLAG_RELOADNORMAL);
+
+ bool can_go_back() const;
+ bool can_go_forward() const;
+
+ std::string get_link_message() const;
+ std::string get_js_status() const;
+ std::string get_title() const;
+ std::string get_location() const;
+ already_AddRefed<nsIWebBrowser> get_browser();
+
+ Glib::SignalProxy0<void> signal_link_message();
+ Glib::SignalProxy0<void> signal_js_status();
+ Glib::SignalProxy0<void> signal_location();
+ Glib::SignalProxy0<void> signal_title();
+ Glib::SignalProxy2<void, gint /*cur*/, gint /*max*/> signal_progress();
+ Glib::SignalProxy3<void, const char *, gint /*flags*/, guint /*status*/>
+ signal_net_state();
+ Glib::SignalProxy0<void> signal_net_start();
+ Glib::SignalProxy0<void> signal_net_stop();
+ Glib::SignalProxy1<BrowserWidget *, guint /*chromemask*/> signal_new_window();
+ Glib::SignalProxy1<void, bool /*visibility*/> signal_visibility();
+ Glib::SignalProxy0<void> signal_destroy();
+ Glib::SignalProxy1<bool, const char * /*uri*/> signal_open_uri();
+
+ // This must be called after Gtk initialisation and before instantiation
+ // of BrowserWidget.
+ static void init();
+
+private:
+ BrowserWidget(GObject * gobject, bool take_copy);
+ static Glib::ObjectBase * wrap_new(GObject * gobject);
+ friend BrowserWidget * Glib::wrap(GtkMozEmbed * object, bool take_copy);
+};
+
+#endif // !INC_BROWSERWIDGET_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include "childiterator.hpp"
+
+#include <cassert>
+
+#include "xpcom_support.hpp"
+
+using xpcom_support::check;
+
+ChildIterator::ChildIterator()
+ : node_(0)
+{}
+
+ChildIterator::ChildIterator(nsIDOMNode * node)
+{
+ check(node->GetFirstChild(&node_));
+}
+
+ChildIterator::~ChildIterator()
+{
+ if (node_)
+ node_->Release();
+}
+
+already_AddRefed<nsIDOMNode> ChildIterator::operator*() const
+{
+ assert(node_);
+ node_->AddRef();
+ return node_;
+}
+
+ChildIterator & ChildIterator::operator++()
+{
+ nsIDOMNode * next;
+ check(node_->GetNextSibling(&next));
+ node_->Release();
+ node_ = next;
+ return *this;
+}
+
+bool ChildIterator::operator==(const ChildIterator & other) const
+{
+ return node_ == other.node_;
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_CHILDITERATOR_HPP
+#define INC_CHILDITERATOR_HPP
+
+#include <iterator>
+
+#include <nsCOMPtr.h>
+#include <nsIDOMNode.h>
+
+class ChildIterator
+ : public std::iterator<std::input_iterator_tag, nsCOMPtr<nsIDOMNode>,
+ void, void, void>
+{
+public:
+ ChildIterator();
+ explicit ChildIterator(nsIDOMNode * node);
+ ~ChildIterator();
+
+ already_AddRefed<nsIDOMNode> operator*() const;
+ ChildIterator & operator++();
+ bool operator==(const ChildIterator &) const;
+ bool operator!=(const ChildIterator & other) const
+ {
+ return !(*this == other);
+ }
+
+private:
+ nsIDOMNode * node_;
+};
+
+#endif // !INC_CHILDITERATOR_HPP
--- /dev/null
+#ifndef INC_DVD_HPP
+#define INC_DVD_HPP
+
+namespace dvd
+{
+ // Maximum number of buttons in a menu.
+ // TODO: Check whether this is a dvdauthor limitation or part of the
+ // DVD Video spec.
+ const int menu_buttons_max = 36;
+
+ // Number of colours allowed in each button.
+ // Buttons can change colour when they are selected.
+ const int button_n_colours = 4;
+
+ // DVD virtual machine register size.
+ const int reg_bits = 16;
+
+ // Number by which button numbers must be multiplied when stored in
+ // system register 8.
+ const int reg_s8_button_mult = 0x400;
+}
+
+#endif // !INC_DVD_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <stdexcept>
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <wait.h>
+
+#include "framebuffer.hpp"
+#include "auto_fd.hpp"
+
+namespace
+{
+ int select_display_num()
+ {
+ // Minimum and maximum display numbers to use. Xvnc and ssh's
+ // proxies start at 10, so we'll follow that convention. We
+ // have to put a limit on iteration somewhere, and 100
+ // displays seems rather excessive so we'll stop there.
+ const int min_display_num = 10;
+ const int max_display_num = 99;
+
+ // Note that we have to leave it to the X server to create the
+ // lock file for the selected display number, which leaves a
+ // race condition. We could perhaps read its error stream to
+ // detect the case where another server grabs the display
+ // number before it.
+ char lock_file_name[20];
+ for (int display_num = min_display_num;
+ display_num <= max_display_num;
+ ++display_num)
+ {
+ // Check whether a lock file exists for this display. Really we
+ // should also check for stale locks, but this will probably do.
+ std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
+ if (access(lock_file_name, 0) == -1 && errno == ENOENT)
+ return display_num;
+ }
+
+ throw std::runtime_error("did not find a free X display");
+ }
+
+ void get_random_bytes(unsigned char * buf, std::size_t len)
+ {
+ auto_fd random_fd(open("/dev/urandom", O_RDONLY));
+ if (random_fd.get() == -1 || read(random_fd.get(), buf, len) != len)
+ throw std::runtime_error(std::strerror(errno));
+ }
+
+ auto_temp_file create_temp_auth_file(int display_num)
+ {
+ char auth_file_name[] = "/tmp/Xvfb-auth-XXXXXX";
+ auto_fd auth_file_fd(mkstemp(auth_file_name));
+ if (auth_file_fd.get() == -1)
+ throw std::runtime_error(std::strerror(errno));
+ auto_temp_file auth_file(auth_file_name);
+
+ // mkstemp may use lax permissions, so fix that before writing
+ // the auth data to it.
+ fchmod(auth_file_fd.get(), S_IREAD|S_IWRITE);
+ ftruncate(auth_file_fd.get(), 0);
+
+ // An xauth entry consists of the following fields. All u16 fields
+ // are big-endian and unaligned. Character arrays are not null-
+ // terminated.
+ // u16 address family (= 256 for local socket)
+ // u16 length of address
+ // char[] address (= hostname)
+ // u16 length of display number
+ // char[] display number
+ // u16 auth type name length
+ // char[] auth type name (= "MIT-MAGIC-COOKIE-1")
+ // u16 length of auth data (= 16)
+ // char[] auth data (= random bytes)
+ uint16_t family = htons(0x100);
+ write(auth_file_fd.get(), &family, sizeof(family));
+ utsname my_uname;
+ uname(&my_uname);
+ uint16_t len = htons(strlen(my_uname.nodename));
+ write(auth_file_fd.get(), &len, sizeof(len));
+ write(auth_file_fd.get(),
+ my_uname.nodename, strlen(my_uname.nodename));
+ char display[15];
+ std::sprintf(display, "%d", display_num);
+ len = htons(strlen(display));
+ write(auth_file_fd.get(), &len, sizeof(len));
+ write(auth_file_fd.get(), display, strlen(display));
+ static const char auth_type[] = "MIT-MAGIC-COOKIE-1";
+ len = htons(sizeof(auth_type) - 1);
+ write(auth_file_fd.get(), &len, sizeof(len));
+ write(auth_file_fd.get(), auth_type, sizeof(auth_type) - 1);
+ unsigned char auth_key[16];
+ get_random_bytes(auth_key, sizeof(auth_key));
+ len = htons(sizeof(auth_key));
+ write(auth_file_fd.get(), &len, sizeof(len));
+ write(auth_file_fd.get(), auth_key, sizeof(auth_key));
+
+ return auth_file;
+ }
+
+ // Run the X server with the specified auth file, dimensions and
+ // assigned display number.
+ auto_kill_proc spawn_x_server(int display_num,
+ const std::string & auth_file_name,
+ int width, int height, int depth)
+ {
+ char display[15];
+ std::sprintf(display, ":%d", display_num);
+ const char * auth_file_c_str = auth_file_name.c_str();
+ std::fflush(NULL);
+ auto_kill_proc server_proc(fork());
+ if (server_proc.get() == -1)
+ throw std::runtime_error(std::strerror(errno));
+
+ if (server_proc.get() == 0)
+ {
+ char dimensions[40];
+ std::sprintf(dimensions, "%dx%dx%d", width, height, depth);
+ execlp("Xvfb",
+ "Xvfb",
+ "-auth", auth_file_c_str,
+ "-screen", "0", dimensions,
+ display,
+ NULL);
+ _exit(128 + errno);
+ }
+
+ // Wait for the lock file to appear or the server to exit. We can't
+ // really wait on both of these, so poll at 1-second intervals.
+ char lock_file_name[20];
+ std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
+ for (;;)
+ {
+ if (access(lock_file_name, 0) == 0)
+ break;
+ if (errno != ENOENT) // huh?
+ throw std::runtime_error(std::strerror(errno));
+ if (waitpid(server_proc.get(), NULL, WNOHANG) == server_proc.get())
+ {
+ server_proc.release(); // pid is now invalid
+ // TODO: Get the exit status and decode it properly.
+ throw std::runtime_error("X server failed to create display");
+ }
+ sleep(1);
+ }
+
+ return server_proc;
+ }
+}
+
+FrameBuffer::FrameBuffer(int width, int height, int depth)
+ : display_num_(select_display_num()),
+ auth_file_(create_temp_auth_file(display_num_)),
+ server_proc_(spawn_x_server(display_num_,
+ get_x_authority(),
+ width, height, depth))
+{}
+
+std::string FrameBuffer::get_x_authority() const
+{
+ return auth_file_.get();
+}
+
+std::string FrameBuffer::get_x_display() const
+{
+ char display[15];
+ std::sprintf(display, ":%d", display_num_);
+ return display;
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_FRAMEBUFFER_HPP
+#define INC_FRAMEBUFFER_HPP
+
+#include <string>
+
+#include "auto_proc.hpp"
+#include "auto_temp_file.hpp"
+
+// Run Xvfb with a frame buffer of the given dimensions.
+class FrameBuffer
+{
+public:
+ FrameBuffer(int width, int height, int depth);
+ std::string get_x_authority() const;
+ std::string get_x_display() const;
+
+private:
+ int display_num_;
+ auto_temp_file auth_file_;
+ auto_kill_proc server_proc_;
+};
+
+#endif // !INC_FRAMEBUFFER_HPP
--- /dev/null
+/*
+ * Copyright 2005 Ben Hutchings.
+ *
+ * This is derived from jquant2.c and parts of jdmaster.c, jmorecfg.h,
+ * jpegint.h and jpeglib.h, from the Independent JPEG Group's software.
+ * Copyright (C) 1991-1998, Thomas G. Lane.
+ *
+ * This file contains 2-pass color quantization (color mapping) routines.
+ * These routines provide selection of a custom color map for an image,
+ * followed by mapping of the image to that color map, with optional
+ * Floyd-Steinberg dithering.
+ * It is also possible to use just the second pass to map to an arbitrary
+ * externally-given color map.
+ *
+ * Note: ordered dithering is not supported, since there isn't any fast
+ * way to compute intercolor distances; it's unclear that ordered dither's
+ * fundamental assumptions even hold with an irregularly spaced color map.
+ */
+
+/*
+ * These definitions cover what would normally be defind in jconfig.h,
+ * jmorecfg.h, jpegint.h and jpeglib.h.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "jquant2.h"
+
+/*
+ * This controls whether an additional alpha channel is expected in the
+ * input buffer. If so, output color 0 is reserved for fully transparent
+ * pixels and the alpha channel is otherwise ignored. That is, only
+ * binary transparency is really supported.
+ */
+#define HAS_ALPHA 1
+
+typedef uint16_t UINT16;
+typedef int16_t INT16;
+typedef int32_t INT32;
+typedef int boolean;
+
+#define BITS_IN_JSAMPLE 8
+#define MAXJSAMPLE 255
+#define CENTERJSAMPLE 128
+#define GETJSAMPLE(value) ((int) (value))
+
+typedef int JDIMENSION;
+
+#define RGB_RED 0
+#define RGB_GREEN 1
+#define RGB_BLUE 2
+
+#define FAR
+#define SIZEOF sizeof
+
+#define TRUE 1
+#define FALSE 0
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#define JMETHOD(return_type, name, param_list) return_type (* name) param_list
+#define METHODDEF(return_type) static return_type
+#define LOCAL(return_type) static return_type
+#define GLOBAL(return_type) return_type
+
+#define TRACEMS1(cinfo, b, message_num, param_1)
+/* FIXME */
+#define ERREXIT(cinfo, message_num) abort()
+#define ERREXIT1(cinfo, message_num, param_1) abort()
+
+#define SHIFT_TEMPS
+#define RIGHT_SHIFT(x,shft) ((x) >> (shft))
+
+#define jzero_far(base, length) memset(base, 0, length)
+#define MEMZERO(base, length) memset(base, 0, length)
+#define MEMCOPY(dest, source, length) memcpy(dest, source, length)
+
+struct jpeg_decompress_struct {
+
+ J_DITHER_MODE dither_mode; /* type of color dithering to use */
+
+ /* Description of actual output image that will be returned to application.
+ * These fields are computed by jpeg_start_decompress().
+ * You can also use jpeg_calc_output_dimensions() to determine these values
+ * in advance of calling jpeg_start_decompress().
+ */
+ JDIMENSION output_width; /* scaled image width */
+ JDIMENSION output_height; /* scaled image height */
+
+ /* When quantizing colors, the output colormap is described by these fields.
+ * The application can supply a colormap by setting colormap non-NULL before
+ * calling jpeg_start_decompress; otherwise a colormap is created during
+ * jpeg_start_decompress or jpeg_start_output.
+ * The map has 3 rows and actual_number_of_colors columns.
+ */
+ int actual_number_of_colors; /* number of entries in use */
+ JSAMPARRAY colormap; /* The color map as a 2-D pixel array */
+ JSAMPLE * sample_range_limit; /* table for fast range-limiting */
+
+ struct jpeg_color_quantizer * cquantize;
+};
+
+typedef struct jpeg_decompress_struct * j_decompress_ptr;
+typedef j_decompress_ptr j_common_ptr;
+
+struct jpeg_color_quantizer {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, boolean is_pre_scan));
+ JMETHOD(void, color_quantize, (j_decompress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPARRAY output_buf,
+ int num_rows));
+ JMETHOD(void, finish_pass, (j_decompress_ptr cinfo));
+};
+
+/*
+ * This module implements the well-known Heckbert paradigm for color
+ * quantization. Most of the ideas used here can be traced back to
+ * Heckbert's seminal paper
+ * Heckbert, Paul. "Color Image Quantization for Frame Buffer Display",
+ * Proc. SIGGRAPH '82, Computer Graphics v.16 #3 (July 1982), pp 297-304.
+ *
+ * In the first pass over the image, we accumulate a histogram showing the
+ * usage count of each possible color. To keep the histogram to a reasonable
+ * size, we reduce the precision of the input; typical practice is to retain
+ * 5 or 6 bits per color, so that 8 or 4 different input values are counted
+ * in the same histogram cell.
+ *
+ * Next, the color-selection step begins with a box representing the whole
+ * color space, and repeatedly splits the "largest" remaining box until we
+ * have as many boxes as desired colors. Then the mean color in each
+ * remaining box becomes one of the possible output colors.
+ *
+ * The second pass over the image maps each input pixel to the closest output
+ * color (optionally after applying a Floyd-Steinberg dithering correction).
+ * This mapping is logically trivial, but making it go fast enough requires
+ * considerable care.
+ *
+ * Heckbert-style quantizers vary a good deal in their policies for choosing
+ * the "largest" box and deciding where to cut it. The particular policies
+ * used here have proved out well in experimental comparisons, but better ones
+ * may yet be found.
+ *
+ * In earlier versions of the IJG code, this module quantized in YCbCr color
+ * space, processing the raw upsampled data without a color conversion step.
+ * This allowed the color conversion math to be done only once per colormap
+ * entry, not once per pixel. However, that optimization precluded other
+ * useful optimizations (such as merging color conversion with upsampling)
+ * and it also interfered with desired capabilities such as quantizing to an
+ * externally-supplied colormap. We have therefore abandoned that approach.
+ * The present code works in the post-conversion color space, typically RGB.
+ *
+ * To improve the visual quality of the results, we actually work in scaled
+ * RGB space, giving G distances more weight than R, and R in turn more than
+ * B. To do everything in integer math, we must use integer scale factors.
+ * The 2/3/1 scale factors used here correspond loosely to the relative
+ * weights of the colors in the NTSC grayscale equation.
+ * If you want to use this code to quantize a non-RGB color space, you'll
+ * probably need to change these scale factors.
+ */
+
+#define R_SCALE 2 /* scale R distances by this much */
+#define G_SCALE 3 /* scale G distances by this much */
+#define B_SCALE 1 /* and B by this much */
+
+/* Relabel R/G/B as components 0/1/2, respecting the RGB ordering defined
+ * in jmorecfg.h. As the code stands, it will do the right thing for R,G,B
+ * and B,G,R orders. If you define some other weird order in jmorecfg.h,
+ * you'll get compile errors until you extend this logic. In that case
+ * you'll probably want to tweak the histogram sizes too.
+ */
+
+#if RGB_RED == 0
+#define C0_SCALE R_SCALE
+#endif
+#if RGB_BLUE == 0
+#define C0_SCALE B_SCALE
+#endif
+#if RGB_GREEN == 1
+#define C1_SCALE G_SCALE
+#endif
+#if RGB_RED == 2
+#define C2_SCALE R_SCALE
+#endif
+#if RGB_BLUE == 2
+#define C2_SCALE B_SCALE
+#endif
+
+
+/*
+ * First we have the histogram data structure and routines for creating it.
+ *
+ * The number of bits of precision can be adjusted by changing these symbols.
+ * We recommend keeping 6 bits for G and 5 each for R and B.
+ * If you have plenty of memory and cycles, 6 bits all around gives marginally
+ * better results; if you are short of memory, 5 bits all around will save
+ * some space but degrade the results.
+ * To maintain a fully accurate histogram, we'd need to allocate a "long"
+ * (preferably unsigned long) for each cell. In practice this is overkill;
+ * we can get by with 16 bits per cell. Few of the cell counts will overflow,
+ * and clamping those that do overflow to the maximum value will give close-
+ * enough results. This reduces the recommended histogram size from 256Kb
+ * to 128Kb, which is a useful savings on PC-class machines.
+ * (In the second pass the histogram space is re-used for pixel mapping data;
+ * in that capacity, each cell must be able to store zero to the number of
+ * desired colors. 16 bits/cell is plenty for that too.)
+ * Since the JPEG code is intended to run in small memory model on 80x86
+ * machines, we can't just allocate the histogram in one chunk. Instead
+ * of a true 3-D array, we use a row of pointers to 2-D arrays. Each
+ * pointer corresponds to a C0 value (typically 2^5 = 32 pointers) and
+ * each 2-D array has 2^6*2^5 = 2048 or 2^6*2^6 = 4096 entries. Note that
+ * on 80x86 machines, the pointer row is in near memory but the actual
+ * arrays are in far memory (same arrangement as we use for image arrays).
+ */
+
+#define MAXNUMCOLORS (MAXJSAMPLE+1) /* maximum size of colormap */
+
+/* These will do the right thing for either R,G,B or B,G,R color order,
+ * but you may not like the results for other color orders.
+ */
+#define HIST_C0_BITS 5 /* bits of precision in R/B histogram */
+#define HIST_C1_BITS 6 /* bits of precision in G histogram */
+#define HIST_C2_BITS 5 /* bits of precision in B/R histogram */
+
+/* Number of elements along histogram axes. */
+#define HIST_C0_ELEMS (1<<HIST_C0_BITS)
+#define HIST_C1_ELEMS (1<<HIST_C1_BITS)
+#define HIST_C2_ELEMS (1<<HIST_C2_BITS)
+
+/* These are the amounts to shift an input value to get a histogram index. */
+#define C0_SHIFT (BITS_IN_JSAMPLE-HIST_C0_BITS)
+#define C1_SHIFT (BITS_IN_JSAMPLE-HIST_C1_BITS)
+#define C2_SHIFT (BITS_IN_JSAMPLE-HIST_C2_BITS)
+
+
+typedef UINT16 histcell; /* histogram cell; prefer an unsigned type */
+
+typedef histcell FAR * histptr; /* for pointers to histogram cells */
+
+typedef histcell hist1d[HIST_C2_ELEMS]; /* typedefs for the array */
+typedef hist1d FAR * hist2d; /* type for the 2nd-level pointers */
+typedef hist2d * hist3d; /* type for top-level pointer */
+
+
+/* Declarations for Floyd-Steinberg dithering.
+ *
+ * Errors are accumulated into the array fserrors[], at a resolution of
+ * 1/16th of a pixel count. The error at a given pixel is propagated
+ * to its not-yet-processed neighbors using the standard F-S fractions,
+ * ... (here) 7/16
+ * 3/16 5/16 1/16
+ * We work left-to-right on even rows, right-to-left on odd rows.
+ *
+ * We can get away with a single array (holding one row's worth of errors)
+ * by using it to store the current row's errors at pixel columns not yet
+ * processed, but the next row's errors at columns already processed. We
+ * need only a few extra variables to hold the errors immediately around the
+ * current column. (If we are lucky, those variables are in registers, but
+ * even if not, they're probably cheaper to access than array elements are.)
+ *
+ * The fserrors[] array has (#columns + 2) entries; the extra entry at
+ * each end saves us from special-casing the first and last pixels.
+ * Each entry is three values long, one value for each color component.
+ *
+ * Note: on a wide image, we might not have enough room in a PC's near data
+ * segment to hold the error array; so it is allocated with alloc_large.
+ */
+
+#if BITS_IN_JSAMPLE == 8
+typedef INT16 FSERROR; /* 16 bits should be enough */
+typedef int LOCFSERROR; /* use 'int' for calculation temps */
+#else
+typedef INT32 FSERROR; /* may need more than 16 bits */
+typedef INT32 LOCFSERROR; /* be sure calculation temps are big enough */
+#endif
+
+typedef FSERROR FAR *FSERRPTR; /* pointer to error array (in FAR storage!) */
+
+
+/* Private subobject */
+
+typedef struct {
+ struct jpeg_color_quantizer pub; /* public fields */
+
+ /* Space for the eventually created colormap is stashed here */
+ JSAMPROW sv_colormap[3]; /* colormap allocated at init time */
+ int desired; /* desired # of colors = size of colormap */
+
+ /* Variables for accumulating image statistics */
+ hist3d histogram; /* pointer to the histogram */
+
+ boolean needs_zeroed; /* TRUE if next pass must zero histogram */
+
+ /* Variables for Floyd-Steinberg dithering */
+ FSERRPTR fserrors; /* accumulated errors */
+ boolean on_odd_row; /* flag to remember which row we are on */
+ int * error_limiter; /* table for clamping the applied error */
+} my_cquantizer;
+
+typedef my_cquantizer * my_cquantize_ptr;
+
+
+/*
+ * Prescan some rows of pixels.
+ * In this module the prescan simply updates the histogram, which has been
+ * initialized to zeroes by start_pass.
+ * An output_buf parameter is required by the method signature, but no data
+ * is actually output (in fact the buffer controller is probably passing a
+ * NULL pointer).
+ */
+
+METHODDEF(void)
+prescan_quantize (j_decompress_ptr cinfo, JSAMPARRAY input_buf,
+ JSAMPARRAY output_buf, int num_rows)
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ register JSAMPROW ptr;
+ register histptr histp;
+ register hist3d histogram = cquantize->histogram;
+ int row;
+ JDIMENSION col;
+ JDIMENSION width = cinfo->output_width;
+
+ for (row = 0; row < num_rows; row++) {
+ ptr = input_buf[row];
+ for (col = width; col > 0; col--) {
+ /* ignore transparent pixels */
+ if (!HAS_ALPHA || GETJSAMPLE(ptr[3]) != 0) {
+ /* get pixel value and index into the histogram */
+ histp = & histogram[GETJSAMPLE(ptr[0]) >> C0_SHIFT]
+ [GETJSAMPLE(ptr[1]) >> C1_SHIFT]
+ [GETJSAMPLE(ptr[2]) >> C2_SHIFT];
+ /* increment, check for overflow and undo increment if so. */
+ if (++(*histp) <= 0)
+ (*histp)--;
+ }
+ ptr += 3+HAS_ALPHA;
+ }
+ }
+}
+
+
+/*
+ * Next we have the really interesting routines: selection of a colormap
+ * given the completed histogram.
+ * These routines work with a list of "boxes", each representing a rectangular
+ * subset of the input color space (to histogram precision).
+ */
+
+typedef struct {
+ /* The bounds of the box (inclusive); expressed as histogram indexes */
+ int c0min, c0max;
+ int c1min, c1max;
+ int c2min, c2max;
+ /* The volume (actually 2-norm) of the box */
+ INT32 volume;
+ /* The number of nonzero histogram cells within this box */
+ long colorcount;
+} box;
+
+typedef box * boxptr;
+
+
+LOCAL(boxptr)
+find_biggest_color_pop (boxptr boxlist, int numboxes)
+/* Find the splittable box with the largest color population */
+/* Returns NULL if no splittable boxes remain */
+{
+ register boxptr boxp;
+ register int i;
+ register long maxc = 0;
+ boxptr which = NULL;
+
+ for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) {
+ if (boxp->colorcount > maxc && boxp->volume > 0) {
+ which = boxp;
+ maxc = boxp->colorcount;
+ }
+ }
+ return which;
+}
+
+
+LOCAL(boxptr)
+find_biggest_volume (boxptr boxlist, int numboxes)
+/* Find the splittable box with the largest (scaled) volume */
+/* Returns NULL if no splittable boxes remain */
+{
+ register boxptr boxp;
+ register int i;
+ register INT32 maxv = 0;
+ boxptr which = NULL;
+
+ for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) {
+ if (boxp->volume > maxv) {
+ which = boxp;
+ maxv = boxp->volume;
+ }
+ }
+ return which;
+}
+
+
+LOCAL(void)
+update_box (j_decompress_ptr cinfo, boxptr boxp)
+/* Shrink the min/max bounds of a box to enclose only nonzero elements, */
+/* and recompute its volume and population */
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ hist3d histogram = cquantize->histogram;
+ histptr histp;
+ int c0,c1,c2;
+ int c0min,c0max,c1min,c1max,c2min,c2max;
+ INT32 dist0,dist1,dist2;
+ long ccount;
+
+ c0min = boxp->c0min; c0max = boxp->c0max;
+ c1min = boxp->c1min; c1max = boxp->c1max;
+ c2min = boxp->c2min; c2max = boxp->c2max;
+
+ if (c0max > c0min)
+ for (c0 = c0min; c0 <= c0max; c0++)
+ for (c1 = c1min; c1 <= c1max; c1++) {
+ histp = & histogram[c0][c1][c2min];
+ for (c2 = c2min; c2 <= c2max; c2++)
+ if (*histp++ != 0) {
+ boxp->c0min = c0min = c0;
+ goto have_c0min;
+ }
+ }
+ have_c0min:
+ if (c0max > c0min)
+ for (c0 = c0max; c0 >= c0min; c0--)
+ for (c1 = c1min; c1 <= c1max; c1++) {
+ histp = & histogram[c0][c1][c2min];
+ for (c2 = c2min; c2 <= c2max; c2++)
+ if (*histp++ != 0) {
+ boxp->c0max = c0max = c0;
+ goto have_c0max;
+ }
+ }
+ have_c0max:
+ if (c1max > c1min)
+ for (c1 = c1min; c1 <= c1max; c1++)
+ for (c0 = c0min; c0 <= c0max; c0++) {
+ histp = & histogram[c0][c1][c2min];
+ for (c2 = c2min; c2 <= c2max; c2++)
+ if (*histp++ != 0) {
+ boxp->c1min = c1min = c1;
+ goto have_c1min;
+ }
+ }
+ have_c1min:
+ if (c1max > c1min)
+ for (c1 = c1max; c1 >= c1min; c1--)
+ for (c0 = c0min; c0 <= c0max; c0++) {
+ histp = & histogram[c0][c1][c2min];
+ for (c2 = c2min; c2 <= c2max; c2++)
+ if (*histp++ != 0) {
+ boxp->c1max = c1max = c1;
+ goto have_c1max;
+ }
+ }
+ have_c1max:
+ if (c2max > c2min)
+ for (c2 = c2min; c2 <= c2max; c2++)
+ for (c0 = c0min; c0 <= c0max; c0++) {
+ histp = & histogram[c0][c1min][c2];
+ for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS)
+ if (*histp != 0) {
+ boxp->c2min = c2min = c2;
+ goto have_c2min;
+ }
+ }
+ have_c2min:
+ if (c2max > c2min)
+ for (c2 = c2max; c2 >= c2min; c2--)
+ for (c0 = c0min; c0 <= c0max; c0++) {
+ histp = & histogram[c0][c1min][c2];
+ for (c1 = c1min; c1 <= c1max; c1++, histp += HIST_C2_ELEMS)
+ if (*histp != 0) {
+ boxp->c2max = c2max = c2;
+ goto have_c2max;
+ }
+ }
+ have_c2max:
+
+ /* Update box volume.
+ * We use 2-norm rather than real volume here; this biases the method
+ * against making long narrow boxes, and it has the side benefit that
+ * a box is splittable iff norm > 0.
+ * Since the differences are expressed in histogram-cell units,
+ * we have to shift back to JSAMPLE units to get consistent distances;
+ * after which, we scale according to the selected distance scale factors.
+ */
+ dist0 = ((c0max - c0min) << C0_SHIFT) * C0_SCALE;
+ dist1 = ((c1max - c1min) << C1_SHIFT) * C1_SCALE;
+ dist2 = ((c2max - c2min) << C2_SHIFT) * C2_SCALE;
+ boxp->volume = dist0*dist0 + dist1*dist1 + dist2*dist2;
+
+ /* Now scan remaining volume of box and compute population */
+ ccount = 0;
+ for (c0 = c0min; c0 <= c0max; c0++)
+ for (c1 = c1min; c1 <= c1max; c1++) {
+ histp = & histogram[c0][c1][c2min];
+ for (c2 = c2min; c2 <= c2max; c2++, histp++)
+ if (*histp != 0) {
+ ccount++;
+ }
+ }
+ boxp->colorcount = ccount;
+}
+
+
+LOCAL(int)
+median_cut (j_decompress_ptr cinfo, boxptr boxlist, int numboxes,
+ int desired_colors)
+/* Repeatedly select and split the largest box until we have enough boxes */
+{
+ int n,lb;
+ int c0,c1,c2,cmax;
+ register boxptr b1,b2;
+
+ while (numboxes < desired_colors) {
+ /* Select box to split.
+ * Current algorithm: by population for first half, then by volume.
+ */
+ if (numboxes*2 <= desired_colors) {
+ b1 = find_biggest_color_pop(boxlist, numboxes);
+ } else {
+ b1 = find_biggest_volume(boxlist, numboxes);
+ }
+ if (b1 == NULL) /* no splittable boxes left! */
+ break;
+ b2 = &boxlist[numboxes]; /* where new box will go */
+ /* Copy the color bounds to the new box. */
+ b2->c0max = b1->c0max; b2->c1max = b1->c1max; b2->c2max = b1->c2max;
+ b2->c0min = b1->c0min; b2->c1min = b1->c1min; b2->c2min = b1->c2min;
+ /* Choose which axis to split the box on.
+ * Current algorithm: longest scaled axis.
+ * See notes in update_box about scaling distances.
+ */
+ c0 = ((b1->c0max - b1->c0min) << C0_SHIFT) * C0_SCALE;
+ c1 = ((b1->c1max - b1->c1min) << C1_SHIFT) * C1_SCALE;
+ c2 = ((b1->c2max - b1->c2min) << C2_SHIFT) * C2_SCALE;
+ /* We want to break any ties in favor of green, then red, blue last.
+ * This code does the right thing for R,G,B or B,G,R color orders only.
+ */
+#if RGB_RED == 0
+ cmax = c1; n = 1;
+ if (c0 > cmax) { cmax = c0; n = 0; }
+ if (c2 > cmax) { n = 2; }
+#else
+ cmax = c1; n = 1;
+ if (c2 > cmax) { cmax = c2; n = 2; }
+ if (c0 > cmax) { n = 0; }
+#endif
+ /* Choose split point along selected axis, and update box bounds.
+ * Current algorithm: split at halfway point.
+ * (Since the box has been shrunk to minimum volume,
+ * any split will produce two nonempty subboxes.)
+ * Note that lb value is max for lower box, so must be < old max.
+ */
+ switch (n) {
+ case 0:
+ lb = (b1->c0max + b1->c0min) / 2;
+ b1->c0max = lb;
+ b2->c0min = lb+1;
+ break;
+ case 1:
+ lb = (b1->c1max + b1->c1min) / 2;
+ b1->c1max = lb;
+ b2->c1min = lb+1;
+ break;
+ case 2:
+ lb = (b1->c2max + b1->c2min) / 2;
+ b1->c2max = lb;
+ b2->c2min = lb+1;
+ break;
+ }
+ /* Update stats for boxes */
+ update_box(cinfo, b1);
+ update_box(cinfo, b2);
+ numboxes++;
+ }
+ return numboxes;
+}
+
+
+LOCAL(void)
+compute_color (j_decompress_ptr cinfo, boxptr boxp, int icolor)
+/* Compute representative color for a box, put it in colormap[icolor] */
+{
+ /* Current algorithm: mean weighted by pixels (not colors) */
+ /* Note it is important to get the rounding correct! */
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ hist3d histogram = cquantize->histogram;
+ histptr histp;
+ int c0,c1,c2;
+ int c0min,c0max,c1min,c1max,c2min,c2max;
+ long count;
+ long total = 0;
+ long c0total = 0;
+ long c1total = 0;
+ long c2total = 0;
+
+ c0min = boxp->c0min; c0max = boxp->c0max;
+ c1min = boxp->c1min; c1max = boxp->c1max;
+ c2min = boxp->c2min; c2max = boxp->c2max;
+
+ for (c0 = c0min; c0 <= c0max; c0++)
+ for (c1 = c1min; c1 <= c1max; c1++) {
+ histp = & histogram[c0][c1][c2min];
+ for (c2 = c2min; c2 <= c2max; c2++) {
+ if ((count = *histp++) != 0) {
+ total += count;
+ c0total += ((c0 << C0_SHIFT) + ((1<<C0_SHIFT)>>1)) * count;
+ c1total += ((c1 << C1_SHIFT) + ((1<<C1_SHIFT)>>1)) * count;
+ c2total += ((c2 << C2_SHIFT) + ((1<<C2_SHIFT)>>1)) * count;
+ }
+ }
+ }
+
+ if (total) {
+ cinfo->colormap[0][icolor] = (JSAMPLE) ((c0total + (total>>1)) / total);
+ cinfo->colormap[1][icolor] = (JSAMPLE) ((c1total + (total>>1)) / total);
+ cinfo->colormap[2][icolor] = (JSAMPLE) ((c2total + (total>>1)) / total);
+ } else {
+ cinfo->colormap[0][icolor] = 0;
+ cinfo->colormap[1][icolor] = 0;
+ cinfo->colormap[2][icolor] = 0;
+ }
+}
+
+
+LOCAL(void)
+select_colors (j_decompress_ptr cinfo, int desired_colors)
+/* Master routine for color selection */
+{
+ boxptr boxlist;
+ int numboxes;
+ int i;
+
+ /* Allocate workspace for box list */
+ boxlist = (boxptr) malloc(desired_colors * SIZEOF(box));
+ /* Initialize one box containing whole space */
+ numboxes = 1;
+ boxlist[0].c0min = 0;
+ boxlist[0].c0max = MAXJSAMPLE >> C0_SHIFT;
+ boxlist[0].c1min = 0;
+ boxlist[0].c1max = MAXJSAMPLE >> C1_SHIFT;
+ boxlist[0].c2min = 0;
+ boxlist[0].c2max = MAXJSAMPLE >> C2_SHIFT;
+ /* Shrink it to actually-used volume and set its statistics */
+ update_box(cinfo, & boxlist[0]);
+ /* Perform median-cut to produce final box list */
+ numboxes = median_cut(cinfo, boxlist, numboxes, desired_colors);
+ /* Compute the representative color for each box, fill colormap */
+ for (i = 0; i < numboxes; i++)
+ compute_color(cinfo, & boxlist[i], i);
+ cinfo->actual_number_of_colors = numboxes;
+ TRACEMS1(cinfo, 1, JTRC_QUANT_SELECTED, numboxes);
+}
+
+
+/*
+ * These routines are concerned with the time-critical task of mapping input
+ * colors to the nearest color in the selected colormap.
+ *
+ * We re-use the histogram space as an "inverse color map", essentially a
+ * cache for the results of nearest-color searches. All colors within a
+ * histogram cell will be mapped to the same colormap entry, namely the one
+ * closest to the cell's center. This may not be quite the closest entry to
+ * the actual input color, but it's almost as good. A zero in the cache
+ * indicates we haven't found the nearest color for that cell yet; the array
+ * is cleared to zeroes before starting the mapping pass. When we find the
+ * nearest color for a cell, its colormap index plus one is recorded in the
+ * cache for future use. The pass2 scanning routines call fill_inverse_cmap
+ * when they need to use an unfilled entry in the cache.
+ *
+ * Our method of efficiently finding nearest colors is based on the "locally
+ * sorted search" idea described by Heckbert and on the incremental distance
+ * calculation described by Spencer W. Thomas in chapter III.1 of Graphics
+ * Gems II (James Arvo, ed. Academic Press, 1991). Thomas points out that
+ * the distances from a given colormap entry to each cell of the histogram can
+ * be computed quickly using an incremental method: the differences between
+ * distances to adjacent cells themselves differ by a constant. This allows a
+ * fairly fast implementation of the "brute force" approach of computing the
+ * distance from every colormap entry to every histogram cell. Unfortunately,
+ * it needs a work array to hold the best-distance-so-far for each histogram
+ * cell (because the inner loop has to be over cells, not colormap entries).
+ * The work array elements have to be INT32s, so the work array would need
+ * 256Kb at our recommended precision. This is not feasible in DOS machines.
+ *
+ * To get around these problems, we apply Thomas' method to compute the
+ * nearest colors for only the cells within a small subbox of the histogram.
+ * The work array need be only as big as the subbox, so the memory usage
+ * problem is solved. Furthermore, we need not fill subboxes that are never
+ * referenced in pass2; many images use only part of the color gamut, so a
+ * fair amount of work is saved. An additional advantage of this
+ * approach is that we can apply Heckbert's locality criterion to quickly
+ * eliminate colormap entries that are far away from the subbox; typically
+ * three-fourths of the colormap entries are rejected by Heckbert's criterion,
+ * and we need not compute their distances to individual cells in the subbox.
+ * The speed of this approach is heavily influenced by the subbox size: too
+ * small means too much overhead, too big loses because Heckbert's criterion
+ * can't eliminate as many colormap entries. Empirically the best subbox
+ * size seems to be about 1/512th of the histogram (1/8th in each direction).
+ *
+ * Thomas' article also describes a refined method which is asymptotically
+ * faster than the brute-force method, but it is also far more complex and
+ * cannot efficiently be applied to small subboxes. It is therefore not
+ * useful for programs intended to be portable to DOS machines. On machines
+ * with plenty of memory, filling the whole histogram in one shot with Thomas'
+ * refined method might be faster than the present code --- but then again,
+ * it might not be any faster, and it's certainly more complicated.
+ */
+
+
+/* log2(histogram cells in update box) for each axis; this can be adjusted */
+#define BOX_C0_LOG (HIST_C0_BITS-3)
+#define BOX_C1_LOG (HIST_C1_BITS-3)
+#define BOX_C2_LOG (HIST_C2_BITS-3)
+
+#define BOX_C0_ELEMS (1<<BOX_C0_LOG) /* # of hist cells in update box */
+#define BOX_C1_ELEMS (1<<BOX_C1_LOG)
+#define BOX_C2_ELEMS (1<<BOX_C2_LOG)
+
+#define BOX_C0_SHIFT (C0_SHIFT + BOX_C0_LOG)
+#define BOX_C1_SHIFT (C1_SHIFT + BOX_C1_LOG)
+#define BOX_C2_SHIFT (C2_SHIFT + BOX_C2_LOG)
+
+
+/*
+ * The next three routines implement inverse colormap filling. They could
+ * all be folded into one big routine, but splitting them up this way saves
+ * some stack space (the mindist[] and bestdist[] arrays need not coexist)
+ * and may allow some compilers to produce better code by registerizing more
+ * inner-loop variables.
+ */
+
+LOCAL(int)
+find_nearby_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2,
+ JSAMPLE colorlist[])
+/* Locate the colormap entries close enough to an update box to be candidates
+ * for the nearest entry to some cell(s) in the update box. The update box
+ * is specified by the center coordinates of its first cell. The number of
+ * candidate colormap entries is returned, and their colormap indexes are
+ * placed in colorlist[].
+ * This routine uses Heckbert's "locally sorted search" criterion to select
+ * the colors that need further consideration.
+ */
+{
+ int numcolors = cinfo->actual_number_of_colors;
+ int maxc0, maxc1, maxc2;
+ int centerc0, centerc1, centerc2;
+ int i, x, ncolors;
+ INT32 minmaxdist, min_dist, max_dist, tdist;
+ INT32 mindist[MAXNUMCOLORS]; /* min distance to colormap entry i */
+
+ /* Compute true coordinates of update box's upper corner and center.
+ * Actually we compute the coordinates of the center of the upper-corner
+ * histogram cell, which are the upper bounds of the volume we care about.
+ * Note that since ">>" rounds down, the "center" values may be closer to
+ * min than to max; hence comparisons to them must be "<=", not "<".
+ */
+ maxc0 = minc0 + ((1 << BOX_C0_SHIFT) - (1 << C0_SHIFT));
+ centerc0 = (minc0 + maxc0) >> 1;
+ maxc1 = minc1 + ((1 << BOX_C1_SHIFT) - (1 << C1_SHIFT));
+ centerc1 = (minc1 + maxc1) >> 1;
+ maxc2 = minc2 + ((1 << BOX_C2_SHIFT) - (1 << C2_SHIFT));
+ centerc2 = (minc2 + maxc2) >> 1;
+
+ /* For each color in colormap, find:
+ * 1. its minimum squared-distance to any point in the update box
+ * (zero if color is within update box);
+ * 2. its maximum squared-distance to any point in the update box.
+ * Both of these can be found by considering only the corners of the box.
+ * We save the minimum distance for each color in mindist[];
+ * only the smallest maximum distance is of interest.
+ */
+ minmaxdist = 0x7FFFFFFFL;
+
+ for (i = 0; i < numcolors; i++) {
+ /* We compute the squared-c0-distance term, then add in the other two. */
+ x = GETJSAMPLE(cinfo->colormap[0][i]);
+ if (x < minc0) {
+ tdist = (x - minc0) * C0_SCALE;
+ min_dist = tdist*tdist;
+ tdist = (x - maxc0) * C0_SCALE;
+ max_dist = tdist*tdist;
+ } else if (x > maxc0) {
+ tdist = (x - maxc0) * C0_SCALE;
+ min_dist = tdist*tdist;
+ tdist = (x - minc0) * C0_SCALE;
+ max_dist = tdist*tdist;
+ } else {
+ /* within cell range so no contribution to min_dist */
+ min_dist = 0;
+ if (x <= centerc0) {
+ tdist = (x - maxc0) * C0_SCALE;
+ max_dist = tdist*tdist;
+ } else {
+ tdist = (x - minc0) * C0_SCALE;
+ max_dist = tdist*tdist;
+ }
+ }
+
+ x = GETJSAMPLE(cinfo->colormap[1][i]);
+ if (x < minc1) {
+ tdist = (x - minc1) * C1_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - maxc1) * C1_SCALE;
+ max_dist += tdist*tdist;
+ } else if (x > maxc1) {
+ tdist = (x - maxc1) * C1_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - minc1) * C1_SCALE;
+ max_dist += tdist*tdist;
+ } else {
+ /* within cell range so no contribution to min_dist */
+ if (x <= centerc1) {
+ tdist = (x - maxc1) * C1_SCALE;
+ max_dist += tdist*tdist;
+ } else {
+ tdist = (x - minc1) * C1_SCALE;
+ max_dist += tdist*tdist;
+ }
+ }
+
+ x = GETJSAMPLE(cinfo->colormap[2][i]);
+ if (x < minc2) {
+ tdist = (x - minc2) * C2_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - maxc2) * C2_SCALE;
+ max_dist += tdist*tdist;
+ } else if (x > maxc2) {
+ tdist = (x - maxc2) * C2_SCALE;
+ min_dist += tdist*tdist;
+ tdist = (x - minc2) * C2_SCALE;
+ max_dist += tdist*tdist;
+ } else {
+ /* within cell range so no contribution to min_dist */
+ if (x <= centerc2) {
+ tdist = (x - maxc2) * C2_SCALE;
+ max_dist += tdist*tdist;
+ } else {
+ tdist = (x - minc2) * C2_SCALE;
+ max_dist += tdist*tdist;
+ }
+ }
+
+ mindist[i] = min_dist; /* save away the results */
+ if (max_dist < minmaxdist)
+ minmaxdist = max_dist;
+ }
+
+ /* Now we know that no cell in the update box is more than minmaxdist
+ * away from some colormap entry. Therefore, only colors that are
+ * within minmaxdist of some part of the box need be considered.
+ */
+ ncolors = 0;
+ for (i = 0; i < numcolors; i++) {
+ if (mindist[i] <= minmaxdist)
+ colorlist[ncolors++] = (JSAMPLE) i;
+ }
+ return ncolors;
+}
+
+
+LOCAL(void)
+find_best_colors (j_decompress_ptr cinfo, int minc0, int minc1, int minc2,
+ int numcolors, JSAMPLE colorlist[], JSAMPLE bestcolor[])
+/* Find the closest colormap entry for each cell in the update box,
+ * given the list of candidate colors prepared by find_nearby_colors.
+ * Return the indexes of the closest entries in the bestcolor[] array.
+ * This routine uses Thomas' incremental distance calculation method to
+ * find the distance from a colormap entry to successive cells in the box.
+ */
+{
+ int ic0, ic1, ic2;
+ int i, icolor;
+ register INT32 * bptr; /* pointer into bestdist[] array */
+ JSAMPLE * cptr; /* pointer into bestcolor[] array */
+ INT32 dist0, dist1; /* initial distance values */
+ register INT32 dist2; /* current distance in inner loop */
+ INT32 xx0, xx1; /* distance increments */
+ register INT32 xx2;
+ INT32 inc0, inc1, inc2; /* initial values for increments */
+ /* This array holds the distance to the nearest-so-far color for each cell */
+ INT32 bestdist[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS];
+
+ /* Initialize best-distance for each cell of the update box */
+ bptr = bestdist;
+ for (i = BOX_C0_ELEMS*BOX_C1_ELEMS*BOX_C2_ELEMS-1; i >= 0; i--)
+ *bptr++ = 0x7FFFFFFFL;
+
+ /* For each color selected by find_nearby_colors,
+ * compute its distance to the center of each cell in the box.
+ * If that's less than best-so-far, update best distance and color number.
+ */
+
+ /* Nominal steps between cell centers ("x" in Thomas article) */
+#define STEP_C0 ((1 << C0_SHIFT) * C0_SCALE)
+#define STEP_C1 ((1 << C1_SHIFT) * C1_SCALE)
+#define STEP_C2 ((1 << C2_SHIFT) * C2_SCALE)
+
+ for (i = 0; i < numcolors; i++) {
+ icolor = GETJSAMPLE(colorlist[i]);
+ /* Compute (square of) distance from minc0/c1/c2 to this color */
+ inc0 = (minc0 - GETJSAMPLE(cinfo->colormap[0][icolor])) * C0_SCALE;
+ dist0 = inc0*inc0;
+ inc1 = (minc1 - GETJSAMPLE(cinfo->colormap[1][icolor])) * C1_SCALE;
+ dist0 += inc1*inc1;
+ inc2 = (minc2 - GETJSAMPLE(cinfo->colormap[2][icolor])) * C2_SCALE;
+ dist0 += inc2*inc2;
+ /* Form the initial difference increments */
+ inc0 = inc0 * (2 * STEP_C0) + STEP_C0 * STEP_C0;
+ inc1 = inc1 * (2 * STEP_C1) + STEP_C1 * STEP_C1;
+ inc2 = inc2 * (2 * STEP_C2) + STEP_C2 * STEP_C2;
+ /* Now loop over all cells in box, updating distance per Thomas method */
+ bptr = bestdist;
+ cptr = bestcolor;
+ xx0 = inc0;
+ for (ic0 = BOX_C0_ELEMS-1; ic0 >= 0; ic0--) {
+ dist1 = dist0;
+ xx1 = inc1;
+ for (ic1 = BOX_C1_ELEMS-1; ic1 >= 0; ic1--) {
+ dist2 = dist1;
+ xx2 = inc2;
+ for (ic2 = BOX_C2_ELEMS-1; ic2 >= 0; ic2--) {
+ if (dist2 < *bptr) {
+ *bptr = dist2;
+ *cptr = (JSAMPLE) icolor;
+ }
+ dist2 += xx2;
+ xx2 += 2 * STEP_C2 * STEP_C2;
+ bptr++;
+ cptr++;
+ }
+ dist1 += xx1;
+ xx1 += 2 * STEP_C1 * STEP_C1;
+ }
+ dist0 += xx0;
+ xx0 += 2 * STEP_C0 * STEP_C0;
+ }
+ }
+}
+
+
+LOCAL(void)
+fill_inverse_cmap (j_decompress_ptr cinfo, int c0, int c1, int c2)
+/* Fill the inverse-colormap entries in the update box that contains */
+/* histogram cell c0/c1/c2. (Only that one cell MUST be filled, but */
+/* we can fill as many others as we wish.) */
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ hist3d histogram = cquantize->histogram;
+ int minc0, minc1, minc2; /* lower left corner of update box */
+ int ic0, ic1, ic2;
+ register JSAMPLE * cptr; /* pointer into bestcolor[] array */
+ register histptr cachep; /* pointer into main cache array */
+ /* This array lists the candidate colormap indexes. */
+ JSAMPLE colorlist[MAXNUMCOLORS];
+ int numcolors; /* number of candidate colors */
+ /* This array holds the actually closest colormap index for each cell. */
+ JSAMPLE bestcolor[BOX_C0_ELEMS * BOX_C1_ELEMS * BOX_C2_ELEMS];
+
+ /* Convert cell coordinates to update box ID */
+ c0 >>= BOX_C0_LOG;
+ c1 >>= BOX_C1_LOG;
+ c2 >>= BOX_C2_LOG;
+
+ /* Compute true coordinates of update box's origin corner.
+ * Actually we compute the coordinates of the center of the corner
+ * histogram cell, which are the lower bounds of the volume we care about.
+ */
+ minc0 = (c0 << BOX_C0_SHIFT) + ((1 << C0_SHIFT) >> 1);
+ minc1 = (c1 << BOX_C1_SHIFT) + ((1 << C1_SHIFT) >> 1);
+ minc2 = (c2 << BOX_C2_SHIFT) + ((1 << C2_SHIFT) >> 1);
+
+ /* Determine which colormap entries are close enough to be candidates
+ * for the nearest entry to some cell in the update box.
+ */
+ numcolors = find_nearby_colors(cinfo, minc0, minc1, minc2, colorlist);
+
+ /* Determine the actually nearest colors. */
+ find_best_colors(cinfo, minc0, minc1, minc2, numcolors, colorlist,
+ bestcolor);
+
+ /* Save the best color numbers (plus 1) in the main cache array */
+ c0 <<= BOX_C0_LOG; /* convert ID back to base cell indexes */
+ c1 <<= BOX_C1_LOG;
+ c2 <<= BOX_C2_LOG;
+ cptr = bestcolor;
+ for (ic0 = 0; ic0 < BOX_C0_ELEMS; ic0++) {
+ for (ic1 = 0; ic1 < BOX_C1_ELEMS; ic1++) {
+ cachep = & histogram[c0+ic0][c1+ic1][c2];
+ for (ic2 = 0; ic2 < BOX_C2_ELEMS; ic2++) {
+ *cachep++ = (histcell) (GETJSAMPLE(*cptr++) + 1);
+ }
+ }
+ }
+}
+
+
+/*
+ * Map some rows of pixels to the output colormapped representation.
+ */
+
+METHODDEF(void)
+pass2_no_dither (j_decompress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows)
+/* This version performs no dithering */
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ hist3d histogram = cquantize->histogram;
+ register JSAMPROW inptr, outptr;
+ register histptr cachep;
+ register int c0, c1, c2;
+ int row;
+ JDIMENSION col;
+ JDIMENSION width = cinfo->output_width;
+
+ for (row = 0; row < num_rows; row++) {
+ inptr = input_buf[row];
+ outptr = output_buf[row];
+ for (col = width; col > 0; col--) {
+ /* get pixel value and index into the cache */
+ c0 = GETJSAMPLE(*inptr++) >> C0_SHIFT;
+ c1 = GETJSAMPLE(*inptr++) >> C1_SHIFT;
+ c2 = GETJSAMPLE(*inptr++) >> C2_SHIFT;
+ if (HAS_ALPHA && *inptr++ == 0) {
+ *outptr++ = 0;
+ } else {
+ cachep = & histogram[c0][c1][c2];
+ /* If we have not seen this color before, find nearest colormap entry */
+ /* and update the cache */
+ if (*cachep == 0)
+ fill_inverse_cmap(cinfo, c0,c1,c2);
+ /* Now emit the colormap index for this cell */
+ *outptr++ = (JSAMPLE) (*cachep - 1 + HAS_ALPHA);
+ }
+ }
+ }
+}
+
+
+METHODDEF(void)
+pass2_fs_dither (j_decompress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPARRAY output_buf, int num_rows)
+/* This version performs Floyd-Steinberg dithering */
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ hist3d histogram = cquantize->histogram;
+ register LOCFSERROR cur0, cur1, cur2; /* current error or pixel value */
+ LOCFSERROR belowerr0, belowerr1, belowerr2; /* error for pixel below cur */
+ LOCFSERROR bpreverr0, bpreverr1, bpreverr2; /* error for below/prev col */
+ register FSERRPTR errorptr; /* => fserrors[] at column before current */
+ JSAMPROW inptr; /* => current input pixel */
+ JSAMPROW outptr; /* => current output pixel */
+ histptr cachep;
+ int dir; /* +1 or -1 depending on direction */
+ int dir_comp; /* 3*dir or 4*dir, for advancing inptr */
+ int dir3; /* 3*dir, for advancing errorptr */
+ int row;
+ JDIMENSION col;
+ JDIMENSION width = cinfo->output_width;
+ JSAMPLE *range_limit = cinfo->sample_range_limit;
+ int *error_limit = cquantize->error_limiter;
+ JSAMPROW colormap0 = cinfo->colormap[0];
+ JSAMPROW colormap1 = cinfo->colormap[1];
+ JSAMPROW colormap2 = cinfo->colormap[2];
+ SHIFT_TEMPS
+
+ for (row = 0; row < num_rows; row++) {
+ inptr = input_buf[row];
+ outptr = output_buf[row];
+ if (cquantize->on_odd_row) {
+ /* work right to left in this row */
+ inptr += (width-1) * (3+HAS_ALPHA); /* so point to rightmost pixel */
+ outptr += width-1;
+ dir = -1;
+ dir3 = -3;
+ dir_comp = -3-HAS_ALPHA;
+ errorptr = cquantize->fserrors + (width+1)*3; /* => entry after last column */
+ cquantize->on_odd_row = FALSE; /* flip for next time */
+ } else {
+ /* work left to right in this row */
+ dir = 1;
+ dir3 = 3;
+ dir_comp = 3+HAS_ALPHA;
+ errorptr = cquantize->fserrors; /* => entry before first real column */
+ cquantize->on_odd_row = TRUE; /* flip for next time */
+ }
+ /* Preset error values: no error propagated to first pixel from left */
+ cur0 = cur1 = cur2 = 0;
+ /* and no error propagated to row below yet */
+ belowerr0 = belowerr1 = belowerr2 = 0;
+ bpreverr0 = bpreverr1 = bpreverr2 = 0;
+
+ for (col = width; col > 0; col--) {
+ if (HAS_ALPHA && inptr[3] == 0) {
+ /* Output transparent pixel and reset error values. */
+ *outptr = 0;
+ cur0 = cur1 = cur2 = 0;
+ } else {
+ /* curN holds the error propagated from the previous pixel on the
+ * current line. Add the error propagated from the previous line
+ * to form the complete error correction term for this pixel, and
+ * round the error term (which is expressed * 16) to an integer.
+ * RIGHT_SHIFT rounds towards minus infinity, so adding 8 is correct
+ * for either sign of the error value.
+ * Note: errorptr points to *previous* column's array entry.
+ */
+ cur0 = RIGHT_SHIFT(cur0 + errorptr[dir_comp+0] + 8, 4);
+ cur1 = RIGHT_SHIFT(cur1 + errorptr[dir_comp+1] + 8, 4);
+ cur2 = RIGHT_SHIFT(cur2 + errorptr[dir_comp+2] + 8, 4);
+ /* Limit the error using transfer function set by init_error_limit.
+ * See comments with init_error_limit for rationale.
+ */
+ cur0 = error_limit[cur0];
+ cur1 = error_limit[cur1];
+ cur2 = error_limit[cur2];
+ /* Form pixel value + error, and range-limit to 0..MAXJSAMPLE.
+ * The maximum error is +- MAXJSAMPLE (or less with error limiting);
+ * this sets the required size of the range_limit array.
+ */
+ cur0 += GETJSAMPLE(inptr[0]);
+ cur1 += GETJSAMPLE(inptr[1]);
+ cur2 += GETJSAMPLE(inptr[2]);
+ cur0 = GETJSAMPLE(range_limit[cur0]);
+ cur1 = GETJSAMPLE(range_limit[cur1]);
+ cur2 = GETJSAMPLE(range_limit[cur2]);
+ /* Index into the cache with adjusted pixel value */
+ cachep = & histogram[cur0>>C0_SHIFT][cur1>>C1_SHIFT][cur2>>C2_SHIFT];
+ /* If we have not seen this color before, find nearest colormap */
+ /* entry and update the cache */
+ if (*cachep == 0)
+ fill_inverse_cmap(cinfo, cur0>>C0_SHIFT,cur1>>C1_SHIFT,cur2>>C2_SHIFT);
+ /* Now emit the colormap index for this cell */
+ { register int pixcode = *cachep - 1;
+ *outptr = (JSAMPLE) pixcode + HAS_ALPHA;
+ /* Compute representation error for this pixel */
+ cur0 -= GETJSAMPLE(colormap0[pixcode]);
+ cur1 -= GETJSAMPLE(colormap1[pixcode]);
+ cur2 -= GETJSAMPLE(colormap2[pixcode]);
+ }
+ /* Compute error fractions to be propagated to adjacent pixels.
+ * Add these into the running sums, and simultaneously shift the
+ * next-line error sums left by 1 column.
+ */
+ { register LOCFSERROR bnexterr, delta;
+
+ bnexterr = cur0; /* Process component 0 */
+ delta = cur0 * 2;
+ cur0 += delta; /* form error * 3 */
+ errorptr[0] = (FSERROR) (bpreverr0 + cur0);
+ cur0 += delta; /* form error * 5 */
+ bpreverr0 = belowerr0 + cur0;
+ belowerr0 = bnexterr;
+ cur0 += delta; /* form error * 7 */
+ bnexterr = cur1; /* Process component 1 */
+ delta = cur1 * 2;
+ cur1 += delta; /* form error * 3 */
+ errorptr[1] = (FSERROR) (bpreverr1 + cur1);
+ cur1 += delta; /* form error * 5 */
+ bpreverr1 = belowerr1 + cur1;
+ belowerr1 = bnexterr;
+ cur1 += delta; /* form error * 7 */
+ bnexterr = cur2; /* Process component 2 */
+ delta = cur2 * 2;
+ cur2 += delta; /* form error * 3 */
+ errorptr[2] = (FSERROR) (bpreverr2 + cur2);
+ cur2 += delta; /* form error * 5 */
+ bpreverr2 = belowerr2 + cur2;
+ belowerr2 = bnexterr;
+ cur2 += delta; /* form error * 7 */
+ }
+ /* At this point curN contains the 7/16 error value to be propagated
+ * to the next pixel on the current line, and all the errors for the
+ * next line have been shifted over. We are therefore ready to move on.
+ */
+ }
+ inptr += dir_comp; /* Advance pixel pointers to next column */
+ outptr += dir;
+ errorptr += dir3; /* advance errorptr to current column */
+ }
+ /* Post-loop cleanup: we must unload the final error values into the
+ * final fserrors[] entry. Note we need not unload belowerrN because
+ * it is for the dummy column before or after the actual array.
+ */
+ errorptr[0] = (FSERROR) bpreverr0; /* unload prev errs into array */
+ errorptr[1] = (FSERROR) bpreverr1;
+ errorptr[2] = (FSERROR) bpreverr2;
+ }
+}
+
+
+/*
+ * Initialize the error-limiting transfer function (lookup table).
+ * The raw F-S error computation can potentially compute error values of up to
+ * +- MAXJSAMPLE. But we want the maximum correction applied to a pixel to be
+ * much less, otherwise obviously wrong pixels will be created. (Typical
+ * effects include weird fringes at color-area boundaries, isolated bright
+ * pixels in a dark area, etc.) The standard advice for avoiding this problem
+ * is to ensure that the "corners" of the color cube are allocated as output
+ * colors; then repeated errors in the same direction cannot cause cascading
+ * error buildup. However, that only prevents the error from getting
+ * completely out of hand; Aaron Giles reports that error limiting improves
+ * the results even with corner colors allocated.
+ * A simple clamping of the error values to about +- MAXJSAMPLE/8 works pretty
+ * well, but the smoother transfer function used below is even better. Thanks
+ * to Aaron Giles for this idea.
+ */
+
+LOCAL(void)
+init_error_limit (j_decompress_ptr cinfo)
+/* Allocate and fill in the error_limiter table */
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ int * table;
+ int in, out;
+
+ table = (int *) malloc((MAXJSAMPLE*2+1) * SIZEOF(int));
+ table += MAXJSAMPLE; /* so can index -MAXJSAMPLE .. +MAXJSAMPLE */
+ cquantize->error_limiter = table;
+
+#define STEPSIZE ((MAXJSAMPLE+1)/16)
+ /* Map errors 1:1 up to +- MAXJSAMPLE/16 */
+ out = 0;
+ for (in = 0; in < STEPSIZE; in++, out++) {
+ table[in] = out; table[-in] = -out;
+ }
+ /* Map errors 1:2 up to +- 3*MAXJSAMPLE/16 */
+ for (; in < STEPSIZE*3; in++, out += (in&1) ? 0 : 1) {
+ table[in] = out; table[-in] = -out;
+ }
+ /* Clamp the rest to final out value (which is (MAXJSAMPLE+1)/8) */
+ for (; in <= MAXJSAMPLE; in++) {
+ table[in] = out; table[-in] = -out;
+ }
+#undef STEPSIZE
+}
+
+
+/*
+ * Finish up at the end of each pass.
+ */
+
+METHODDEF(void)
+finish_pass1 (j_decompress_ptr cinfo)
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+
+ /* Select the representative colors and fill in cinfo->colormap */
+ cinfo->colormap = cquantize->sv_colormap;
+ select_colors(cinfo, cquantize->desired);
+ /* Force next pass to zero the color index table */
+ cquantize->needs_zeroed = TRUE;
+}
+
+
+METHODDEF(void)
+finish_pass2 (j_decompress_ptr cinfo)
+{
+ /* no work */
+}
+
+
+/*
+ * Initialize for each processing pass.
+ */
+
+METHODDEF(void)
+start_pass_2_quant (j_decompress_ptr cinfo, boolean is_pre_scan)
+{
+ my_cquantize_ptr cquantize = (my_cquantize_ptr) cinfo->cquantize;
+ hist3d histogram = cquantize->histogram;
+ int i;
+
+ if (is_pre_scan) {
+ /* Set up method pointers */
+ cquantize->pub.color_quantize = prescan_quantize;
+ cquantize->pub.finish_pass = finish_pass1;
+ cquantize->needs_zeroed = TRUE; /* Always zero histogram */
+ } else {
+ /* Set up method pointers */
+ if (cinfo->dither_mode == JDITHER_FS)
+ cquantize->pub.color_quantize = pass2_fs_dither;
+ else
+ cquantize->pub.color_quantize = pass2_no_dither;
+ cquantize->pub.finish_pass = finish_pass2;
+
+ /* Make sure color count is acceptable */
+ i = cinfo->actual_number_of_colors;
+ if (i < 1)
+ ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 1);
+ if (i > MAXNUMCOLORS)
+ ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS);
+
+ if (cinfo->dither_mode == JDITHER_FS) {
+ size_t arraysize = (size_t) ((cinfo->output_width + 2) *
+ (3 * SIZEOF(FSERROR)));
+ /* Allocate Floyd-Steinberg workspace if we didn't already. */
+ if (cquantize->fserrors == NULL)
+ cquantize->fserrors = (FSERRPTR) malloc(arraysize);
+ /* Initialize the propagated errors to zero. */
+ jzero_far((void FAR *) cquantize->fserrors, arraysize);
+ /* Make the error-limit table if we didn't already. */
+ if (cquantize->error_limiter == NULL)
+ init_error_limit(cinfo);
+ cquantize->on_odd_row = FALSE;
+ }
+
+ }
+ /* Zero the histogram or inverse color map, if necessary */
+ if (cquantize->needs_zeroed) {
+ for (i = 0; i < HIST_C0_ELEMS; i++) {
+ jzero_far((void FAR *) histogram[i],
+ HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell));
+ }
+ cquantize->needs_zeroed = FALSE;
+ }
+}
+
+
+/*
+ * Several decompression processes need to range-limit values to the range
+ * 0..MAXJSAMPLE; the input value may fall somewhat outside this range
+ * due to noise introduced by quantization, roundoff error, etc. These
+ * processes are inner loops and need to be as fast as possible. On most
+ * machines, particularly CPUs with pipelines or instruction prefetch,
+ * a (subscript-check-less) C table lookup
+ * x = sample_range_limit[x];
+ * is faster than explicit tests
+ * if (x < 0) x = 0;
+ * else if (x > MAXJSAMPLE) x = MAXJSAMPLE;
+ * These processes all use a common table prepared by the routine below.
+ *
+ * For most steps we can mathematically guarantee that the initial value
+ * of x is within MAXJSAMPLE+1 of the legal range, so a table running from
+ * -(MAXJSAMPLE+1) to 2*MAXJSAMPLE+1 is sufficient. But for the initial
+ * limiting step (just after the IDCT), a wildly out-of-range value is
+ * possible if the input data is corrupt. To avoid any chance of indexing
+ * off the end of memory and getting a bad-pointer trap, we perform the
+ * post-IDCT limiting thus:
+ * x = range_limit[x & MASK];
+ * where MASK is 2 bits wider than legal sample data, ie 10 bits for 8-bit
+ * samples. Under normal circumstances this is more than enough range and
+ * a correct output will be generated; with bogus input data the mask will
+ * cause wraparound, and we will safely generate a bogus-but-in-range output.
+ * For the post-IDCT step, we want to convert the data from signed to unsigned
+ * representation by adding CENTERJSAMPLE at the same time that we limit it.
+ * So the post-IDCT limiting table ends up looking like this:
+ * CENTERJSAMPLE,CENTERJSAMPLE+1,...,MAXJSAMPLE,
+ * MAXJSAMPLE (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times),
+ * 0 (repeat 2*(MAXJSAMPLE+1)-CENTERJSAMPLE times),
+ * 0,1,...,CENTERJSAMPLE-1
+ * Negative inputs select values from the upper half of the table after
+ * masking.
+ *
+ * We can save some space by overlapping the start of the post-IDCT table
+ * with the simpler range limiting table. The post-IDCT table begins at
+ * sample_range_limit + CENTERJSAMPLE.
+ *
+ * Note that the table is allocated in near data space on PCs; it's small
+ * enough and used often enough to justify this.
+ */
+
+LOCAL(void)
+prepare_range_limit_table (j_decompress_ptr cinfo)
+/* Allocate and fill in the sample_range_limit table */
+{
+ JSAMPLE * table;
+ int i;
+
+ table = (JSAMPLE *) malloc(
+ (5 * (MAXJSAMPLE+1) + CENTERJSAMPLE) * SIZEOF(JSAMPLE));
+ table += (MAXJSAMPLE+1); /* allow negative subscripts of simple table */
+ cinfo->sample_range_limit = table;
+ /* First segment of "simple" table: limit[x] = 0 for x < 0 */
+ MEMZERO(table - (MAXJSAMPLE+1), (MAXJSAMPLE+1) * SIZEOF(JSAMPLE));
+ /* Main part of "simple" table: limit[x] = x */
+ for (i = 0; i <= MAXJSAMPLE; i++)
+ table[i] = (JSAMPLE) i;
+ table += CENTERJSAMPLE; /* Point to where post-IDCT table starts */
+ /* End of simple table, rest of first half of post-IDCT table */
+ for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++)
+ table[i] = MAXJSAMPLE;
+ /* Second half of post-IDCT table */
+ MEMZERO(table + (2 * (MAXJSAMPLE+1)),
+ (2 * (MAXJSAMPLE+1) - CENTERJSAMPLE) * SIZEOF(JSAMPLE));
+ MEMCOPY(table + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE),
+ cinfo->sample_range_limit, CENTERJSAMPLE * SIZEOF(JSAMPLE));
+}
+
+
+void quantize (JSAMPARRAY input_buf,
+ JSAMPARRAY output_buf,
+ int width, int height,
+ J_DITHER_MODE dither_mode,
+ int desired /*number_of_colors*/,
+ unsigned int * output_colors)
+{
+ struct jpeg_decompress_struct cinfo_buf = {}, * cinfo;
+ my_cquantizer cquantize_buf = {}, * cquantize;
+ int i;
+ int pass_flag;
+
+ cinfo = &cinfo_buf;
+ cinfo->dither_mode = dither_mode;
+ cinfo->output_width = width;
+ cinfo->output_height = height;
+ prepare_range_limit_table(cinfo);
+
+ cquantize = &cquantize_buf;
+ cinfo->cquantize = (struct jpeg_color_quantizer *) cquantize;
+ cquantize->pub.start_pass = start_pass_2_quant;
+ cquantize->fserrors = NULL; /* flag optional arrays not allocated */
+ cquantize->error_limiter = NULL;
+
+ /* Allocate the histogram/inverse colormap storage */
+ cquantize->histogram = (hist3d) malloc(HIST_C0_ELEMS * SIZEOF(hist2d));
+ for (i = 0; i < HIST_C0_ELEMS; i++) {
+ cquantize->histogram[i] = (hist2d) malloc(
+ HIST_C1_ELEMS*HIST_C2_ELEMS * SIZEOF(histcell));
+ }
+
+ /* Allocate storage for the completed colormap, if required.
+ */
+ if (desired < 1+HAS_ALPHA)
+ ERREXIT1(cinfo, JERR_QUANT_FEW_COLORS, 1+HAS_ALPHA);
+ /* Make sure colormap indexes can be represented by JSAMPLEs */
+ if (desired > MAXNUMCOLORS)
+ ERREXIT1(cinfo, JERR_QUANT_MANY_COLORS, MAXNUMCOLORS);
+ desired -= HAS_ALPHA;
+ cquantize->sv_colormap[0] = malloc((JDIMENSION) desired * (JDIMENSION) 3);
+ cquantize->sv_colormap[1] = cquantize->sv_colormap[0] + (JDIMENSION) desired;
+ cquantize->sv_colormap[2] = cquantize->sv_colormap[1] + (JDIMENSION) desired;
+ cquantize->desired = desired;
+
+ for (pass_flag = 1; pass_flag >= 0; --pass_flag) {
+ start_pass_2_quant(cinfo, pass_flag);
+ cquantize->pub.color_quantize(cinfo, input_buf, output_buf, height);
+ cquantize->pub.finish_pass(cinfo);
+ }
+
+ if (HAS_ALPHA)
+ output_colors[0] = 0;
+ for (i = 0; i != desired; ++i) {
+ output_colors[HAS_ALPHA+i] = (0xFF000000
+ + (cquantize->sv_colormap[2][i] << 16)
+ + (cquantize->sv_colormap[1][i] << 8)
+ + cquantize->sv_colormap[0][i]);
+ }
+
+ if (cinfo->sample_range_limit)
+ free(cinfo->sample_range_limit - (MAXJSAMPLE+1));
+ free(cquantize->sv_colormap[0]);
+ if (cquantize->histogram) {
+ for (i = 0; i < HIST_C0_ELEMS; i++)
+ free(cquantize->histogram[i]);
+ free(cquantize->histogram);
+ }
+ free(cquantize->fserrors);
+ if (cquantize->error_limiter)
+ free(cquantize->error_limiter - MAXJSAMPLE);
+}
--- /dev/null
+#ifndef INC_JQUANT2_H
+#define INC_JQUANT2_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ JDITHER_NONE, /* no dithering */
+ JDITHER_FS /* Floyd-Steinberg error diffusion dither */
+} J_DITHER_MODE;
+
+typedef unsigned char JSAMPLE;
+typedef JSAMPLE * JSAMPROW;
+typedef JSAMPROW * JSAMPARRAY;
+
+void quantize (JSAMPARRAY input_buf,
+ JSAMPARRAY output_buf,
+ int width, int height,
+ J_DITHER_MODE dither_mode,
+ int desired_number_of_colors,
+ unsigned int * output_colors);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* !INC_JQUANT2_H */
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include "linkiterator.hpp"
+
+#include <cassert>
+
+#include <nsIDOMHTMLCollection.h>
+#include <nsIDOMHTMLDocument.h>
+
+LinkIterator::LinkIterator()
+{}
+
+LinkIterator::LinkIterator(nsIDOMDocument * document)
+{
+ nsCOMPtr<nsIDOMHTMLDocument> htmlDoc(do_QueryInterface(document));
+ if (!htmlDoc)
+ return;
+
+ htmlDoc->GetLinks(getter_AddRefs(collection_));
+ assert(collection_);
+
+ index_ = 0;
+ length_ = 0;
+ collection_->GetLength(&length_);
+ if (length_ == 0)
+ collection_ = 0;
+}
+
+already_AddRefed<nsIDOMNode> LinkIterator::operator*() const
+{
+ assert(collection_);
+ nsIDOMNode * result = 0;
+ collection_->Item(index_, &result);
+ assert(result);
+ return dont_AddRef(result);
+}
+
+LinkIterator & LinkIterator::operator++()
+{
+ assert(collection_);
+ ++index_;
+ if (index_ == length_)
+ collection_ = 0;
+ return *this;
+}
+
+bool LinkIterator::operator==(const LinkIterator & other) const
+{
+ return (collection_ == other.collection_
+ && (!collection_ || index_ == other.index_));
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_LINKITERATOR_HPP
+#define INC_LINKITERATOR_HPP
+
+#include <iterator>
+
+#include <nsCOMPtr.h>
+#include <nsIDOMHTMLCollection.h>
+#include <nsIDOMNode.h>
+
+class nsIDOMDocument;
+
+class LinkIterator
+ : public std::iterator<std::input_iterator_tag, nsCOMPtr<nsIDOMNode>,
+ void, void, void>
+{
+public:
+ LinkIterator();
+ explicit LinkIterator(nsIDOMDocument * document);
+
+ already_AddRefed<nsIDOMNode> operator*() const;
+ LinkIterator & operator++();
+ bool operator==(const LinkIterator &) const;
+ bool operator!=(const LinkIterator & other) const
+ {
+ return !(*this == other);
+ }
+
+private:
+ nsCOMPtr<nsIDOMHTMLCollection> collection_;
+ unsigned int index_, length_;
+};
+
+#endif // !INC_LINKITERATOR_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include "pixbufs.hpp"
+
+#include <cassert>
+
+#include <gdkmm/pixbuf.h>
+
+#include "auto_array.hpp"
+#include "jquant2.h"
+
+// Find pixel differences between an "old" and "new" RGB Pixbuf
+// (or RGBA, but the alpha component will be ignored) and copy the
+// differing pixels from the new one to a third RGBA Pixbuf at the
+// specified offset with full opacity.
+// The width and height of the old and new Pixbufs must be equal
+// and match the specified dimensions. The width and height of
+// the third Pixbuf must be large enough to store a rectangle of
+// those dimensions at the specified offset.
+void diff_rgb_pixbufs(Glib::RefPtr<Gdk::Pixbuf> old_buf,
+ Glib::RefPtr<Gdk::Pixbuf> new_buf,
+ Glib::RefPtr<Gdk::Pixbuf> diff_buf,
+ int offset_x, int offset_y,
+ int width, int height)
+{
+ assert(old_buf->get_colorspace() == Gdk::COLORSPACE_RGB);
+ assert(new_buf->get_colorspace() == Gdk::COLORSPACE_RGB);
+ assert(diff_buf->get_colorspace() == Gdk::COLORSPACE_RGB
+ && diff_buf->get_has_alpha());
+ int old_bpr = old_buf->get_rowstride();
+ int old_bpp = old_buf->get_n_channels();
+ assert(old_bpp >= 3);
+ assert(old_buf->get_width() == width);
+ assert(old_buf->get_height() == height);
+ int new_bpr = new_buf->get_rowstride();
+ int new_bpp = new_buf->get_n_channels();
+ assert(new_bpp >= 3);
+ assert(new_buf->get_width() == width);
+ assert(new_buf->get_height() == height);
+ int diff_bpr = diff_buf->get_rowstride();
+ int diff_bpp = diff_buf->get_n_channels();
+ assert(diff_bpp == 4);
+ assert(diff_buf->get_width() >= offset_x + width);
+ assert(diff_buf->get_height() >= offset_y + height);
+
+ const guint8 * old_p = old_buf->get_pixels();
+ const guint8 * new_p = new_buf->get_pixels();
+ guint8 * diff_p = (diff_buf->get_pixels()
+ + diff_bpr * offset_y
+ + diff_bpp * offset_x);
+
+ for (int y = 0; y != height; ++y)
+ {
+ for (int x = 0; x != width; ++x)
+ {
+ if (old_p[0] != new_p[0]
+ || old_p[1] != new_p[1]
+ || old_p[2] != new_p[2])
+ {
+ diff_p[0] = new_p[0];
+ diff_p[1] = new_p[1];
+ diff_p[2] = new_p[2];
+ diff_p[3] = 0xFF; // fully opaque
+ }
+ old_p += old_bpp;
+ new_p += new_bpp;
+ diff_p += diff_bpp;
+ }
+ old_p += old_bpr - old_bpp * width;
+ new_p += new_bpr - new_bpp * width;
+ diff_p += diff_bpr - diff_bpp * width;
+ }
+}
+
+// Quantise an RGBA Pixbuf to the specified number of colours, including
+// one transparent colour. Currently uses Floyd-Steinberg dithering.
+void quantise_rgba_pixbuf(Glib::RefPtr<Gdk::Pixbuf> buf, int n_colours)
+{
+ assert(buf->get_colorspace() == Gdk::COLORSPACE_RGB
+ && buf->get_has_alpha());
+ int bpr = buf->get_rowstride();
+ assert(buf->get_n_channels() == 4);
+ int width = buf->get_width();
+ int height = buf->get_height();
+
+ unsigned char * buf_p = buf->get_pixels();
+ auto_array<unsigned char *> rows(new unsigned char *[height]);
+ for (int y = 0; y != height; ++y)
+ rows[y] = &buf_p[y * bpr];
+ auto_array<unsigned char> quant_buf_p(
+ new unsigned char[width * height]);
+ auto_array<unsigned char *> quant_rows(
+ new unsigned char *[height]);
+ for (int y = 0; y != height; ++y)
+ quant_rows[y] = &quant_buf_p[y * width];
+ auto_array<unsigned int> colours(new unsigned int[n_colours]);
+
+ quantize(rows.get(), quant_rows.get(), width, height,
+ JDITHER_FS, n_colours, colours.get());
+
+ // Pixbuf doesn't support quantised images, so convert back to RGBA.
+ for (int y = 0; y != height; ++y)
+ for (int x = 0; x != width; ++x)
+ {
+ unsigned int colour = colours[quant_rows[y][x]];
+ rows[y][4*x] = colour;
+ rows[y][4*x+1] = colour >> 8;
+ rows[y][4*x+2] = colour >> 16;
+ rows[y][4*x+3] = colour >> 24;
+ }
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_PIXBUFS_HPP
+#define INC_PIXBUFS_HPP
+
+#include <glibmm/refptr.h>
+
+namespace Gdk
+{
+ class Pixbuf;
+}
+
+// Find pixel differences between an "old" and "new" RGB Pixbuf
+// (or RGBA, but the alpha component will be ignored) and copy the
+// differing pixels from the new one to a third RGBA Pixbuf at the
+// specified offset with full opacity.
+// The width and height of the old and new Pixbufs must be equal
+// and match the specified dimensions. The width and height of
+// the third Pixbuf must be large enough to store a rectangle of
+// those dimensions at the specified offset.
+void diff_rgb_pixbufs(Glib::RefPtr<Gdk::Pixbuf> old_buf,
+ Glib::RefPtr<Gdk::Pixbuf> new_buf,
+ Glib::RefPtr<Gdk::Pixbuf> diff_buf,
+ int offset_x, int offset_y,
+ int width, int height);
+
+// Quantise an RGBA Pixbuf to the specified number of colours, including
+// one transparent colour. Currently uses Floyd-Steinberg dithering.
+void quantise_rgba_pixbuf(Glib::RefPtr<Gdk::Pixbuf> buf, int n_colours);
+
+#endif // !INC_PIXBUFS_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include "stylesheets.hpp"
+
+#include <nsContentCID.h>
+#include <nsICSSLoader.h>
+#include <nsICSSStyleSheet.h>
+#include <nsIPresShell.h>
+#include <nsIServiceManagerUtils.h>
+#include <nsIURI.h>
+#include <nsNetUtil.h>
+
+#include "xpcom_support.hpp"
+
+using xpcom_support::check;
+
+// Load a CSS from an (absolute) URI.
+// TODO: Support loading from an absolute, or better, relative filename.
+already_AddRefed<nsIStyleSheet> load_css(const char * uri)
+{
+ nsCOMPtr<nsICSSLoader> css_loader;
+ static const nsCID css_loader_cid = NS_CSS_LOADER_CID;
+ check(CallGetService<nsICSSLoader>(css_loader_cid,
+ getter_AddRefs(css_loader)));
+
+ nsCOMPtr<nsIURI> style_sheet_uri;
+ check(NS_NewURI(getter_AddRefs(style_sheet_uri), nsCString(uri)));
+
+ nsICSSStyleSheet * style_sheet;
+ check(css_loader->LoadAgentSheet(style_sheet_uri, &style_sheet));
+ return style_sheet;
+}
+
+// Apply a style-sheet to a given presentation shell as the top-priority
+// agent style-sheet and disable the preferences-derived style rules.
+void apply_style_sheet(nsIStyleSheet * style_sheet, nsIPresShell * pres_shell)
+{
+ nsCOMArray<nsIStyleSheet> style_sheets;
+ check(pres_shell->GetAgentStyleSheets(style_sheets));
+ check(style_sheets.InsertObjectAt(style_sheet, 0));
+ check(pres_shell->SetAgentStyleSheets(style_sheets));
+
+ check(pres_shell->EnablePrefStyleRules(false));
+
+ // Update the display
+ check(pres_shell->ReconstructStyleData());
+ check(pres_shell->FlushPendingNotifications(true));
+}
+
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_STYLESHEETS_HPP
+#define INC_STYLESHEETS_HPP
+
+#include <nsCOMPtr.h>
+#include <nsIStyleSheet.h>
+
+class nsIPresShell;
+
+// Load a CSS from an (absolute) URI.
+// TODO: Support loading from an absolute, or better, relative filename.
+already_AddRefed<nsIStyleSheet> load_css(const char * uri);
+
+// Apply a style-sheet to a given presentation shell as the top-priority
+// agent style-sheet and disable the preferences-derived style rules.
+void apply_style_sheet(nsIStyleSheet *, nsIPresShell *);
+
+#endif // !INC_STYLESHEETS_HPP
--- /dev/null
+#ifndef INC_VIDEO_HPP
+#define INC_VIDEO_HPP
+
+namespace video
+{
+ const int pal_oscan_width = 720;
+ const int pal_oscan_height = 576;
+ const int ntsc_oscan_width = 720;
+ const int ntsc_oscan_height = 480;
+}
+
+#endif // !INC_VIDEO_HPP
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include <cassert>
+#include <exception>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <queue>
+#include <set>
+#include <string>
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <gdkmm/pixbuf.h>
+#include <gtkmm/main.h>
+#include <gtkmm/window.h>
+
+#include <nsGUIEvent.h>
+#include <nsIBoxObject.h>
+#include <nsIContent.h>
+#include <nsIDocShell.h>
+#include <nsIDOMAbstractView.h>
+#include <nsIDOMDocumentEvent.h>
+#include <nsIDOMDocumentView.h>
+#include <nsIDOMElement.h>
+#include <nsIDOMEventTarget.h>
+#include <nsIDOMHTMLDocument.h>
+#include <nsIDOMMouseEvent.h>
+#include <nsIDOMNSDocument.h>
+#include <nsIDOMWindow.h>
+#include <nsIEventStateManager.h>
+#include <nsIInterfaceRequestorUtils.h>
+#include <nsIURI.h> // required before nsILink.h
+#include <nsILink.h>
+#include <nsIPresContext.h>
+#include <nsIPresShell.h>
+#include <nsIWebBrowser.h>
+#include <nsString.h>
+
+#include "browserwidget.hpp"
+#include "childiterator.hpp"
+#include "dvd.hpp"
+#include "framebuffer.hpp"
+#include "linkiterator.hpp"
+#include "pixbufs.hpp"
+#include "stylesheets.hpp"
+#include "video.hpp"
+#include "xpcom_support.hpp"
+
+using xpcom_support::check;
+
+namespace
+{
+ struct rectangle
+ {
+ int left, top; // inclusive
+ int right, bottom; // exclusive
+
+ rectangle operator|=(const rectangle & other)
+ {
+ if (other.empty())
+ {
+ // use current extents unchanged
+ }
+ else if (empty())
+ {
+ // use other extents
+ *this = other;
+ }
+ else
+ {
+ // find rectangle enclosing both extents
+ left = std::min(left, other.left);
+ top = std::min(top, other.top);
+ right = std::max(right, other.right);
+ bottom = std::max(bottom, other.bottom);
+ }
+
+ return *this;
+ }
+
+ rectangle operator&=(const rectangle & other)
+ {
+ // find rectangle enclosed in both extents
+ left = std::max(left, other.left);
+ top = std::max(top, other.top);
+ right = std::max(left, std::min(right, other.right));
+ bottom = std::max(top, std::min(bottom, other.bottom));
+ return *this;
+ }
+
+ bool empty() const
+ {
+ return left == right || bottom == top;
+ }
+ };
+
+ rectangle get_elem_rect(nsIDOMNSDocument * ns_doc,
+ nsIDOMElement * elem)
+ {
+ rectangle result;
+
+ nsCOMPtr<nsIBoxObject> box;
+ check(ns_doc->GetBoxObjectFor(elem, getter_AddRefs(box)));
+ int width, height;
+ check(box->GetScreenX(&result.left));
+ check(box->GetScreenY(&result.top));
+ check(box->GetWidth(&width));
+ check(box->GetHeight(&height));
+ result.right = result.left + width;
+ result.bottom = result.top + height;
+
+ for (ChildIterator it = ChildIterator(elem), end; it != end; ++it)
+ {
+ nsCOMPtr<nsIDOMNode> child_node(*it);
+ PRUint16 child_type;
+ if (check(child_node->GetNodeType(&child_type)),
+ child_type == nsIDOMNode::ELEMENT_NODE)
+ {
+ nsCOMPtr<nsIDOMElement> child_elem(
+ do_QueryInterface(child_node));
+ result |= get_elem_rect(ns_doc, child_elem);
+ }
+ }
+
+ return result;
+ }
+
+ class WebDvdWindow : public Gtk::Window
+ {
+ public:
+ WebDvdWindow(int width, int height);
+ void add_page(const std::string & uri);
+
+ private:
+ void add_video(const std::string & uri);
+ void load_next_page();
+ void on_net_state_change(const char * uri, gint flags, guint status);
+ void save_screenshot();
+ void process_links(nsIPresShell * pres_shell,
+ nsIPresContext * pres_context,
+ nsIDOMWindow * dom_window);
+ void generate_dvdauthor_file();
+
+ enum ResourceType { page_resource, video_resource };
+ typedef std::pair<ResourceType, int> ResourceEntry;
+ int width_, height_;
+ BrowserWidget browser_widget_;
+ nsCOMPtr<nsIStyleSheet> stylesheet_;
+ std::queue<std::string> page_queue_;
+ std::map<std::string, ResourceEntry> resource_map_;
+ std::vector<std::vector<std::string> > page_links_;
+ std::vector<std::string> video_paths_;
+ bool loading_;
+ int pending_req_count_;
+ struct link_state;
+ std::auto_ptr<link_state> link_state_;
+ };
+
+ WebDvdWindow::WebDvdWindow(int width, int height)
+ : width_(width), height_(height),
+ stylesheet_(load_css("file://" WEBDVD_LIB_DIR "/webdvd.css")),
+ loading_(false),
+ pending_req_count_(0)
+ {
+ set_default_size(width, height);
+ add(browser_widget_);
+ browser_widget_.show();
+ browser_widget_.signal_net_state().connect(
+ SigC::slot(*this, &WebDvdWindow::on_net_state_change));
+ }
+
+ void WebDvdWindow::add_page(const std::string & uri)
+ {
+ if (resource_map_.insert(
+ std::make_pair(uri, ResourceEntry(page_resource, 0)))
+ .second)
+ {
+ page_queue_.push(uri);
+ if (!loading_)
+ load_next_page();
+ }
+ }
+
+ void WebDvdWindow::add_video(const std::string & uri)
+ {
+ if (resource_map_.insert(
+ std::make_pair(uri, ResourceEntry(video_resource,
+ video_paths_.size() + 1)))
+ .second)
+ {
+ // FIXME: Should accept some slightly different URI prefixes
+ // (e.g. file://localhost/) and decode any URI-escaped
+ // characters in the path.
+ assert(uri.compare(0, 8, "file:///") == 0);
+ video_paths_.push_back(uri.substr(7));
+ }
+ }
+
+ void WebDvdWindow::load_next_page()
+ {
+ loading_ = true;
+
+ assert(!page_queue_.empty());
+ const std::string & uri = page_queue_.front();
+ std::cout << "loading " << uri << std::endl;
+
+ std::size_t page_count = page_links_.size();
+ resource_map_[uri].second = ++page_count;
+ page_links_.resize(page_count);
+ browser_widget_.load_uri(uri);
+ }
+
+ void WebDvdWindow::on_net_state_change(const char * uri,
+ gint flags, guint status)
+ {
+ enum {
+ process_nothing,
+ process_new_page,
+ process_current_link
+ } action = process_nothing;
+
+ if (flags & GTK_MOZ_EMBED_FLAG_IS_REQUEST)
+ {
+ if (flags & GTK_MOZ_EMBED_FLAG_START)
+ ++pending_req_count_;
+ if (flags & GTK_MOZ_EMBED_FLAG_STOP)
+ {
+ assert(pending_req_count_ != 0);
+ --pending_req_count_;
+ }
+ if (pending_req_count_ == 0 && link_state_.get())
+ action = process_current_link;
+ }
+
+ if (flags & GTK_MOZ_EMBED_FLAG_STOP
+ && flags & GTK_MOZ_EMBED_FLAG_IS_WINDOW)
+ action = process_new_page;
+
+ if (action != process_nothing)
+ {
+ assert(loading_ && !page_queue_.empty());
+ assert(pending_req_count_ == 0);
+
+ try
+ {
+ check(status);
+
+ nsCOMPtr<nsIWebBrowser> browser(
+ browser_widget_.get_browser());
+ nsCOMPtr<nsIDocShell> doc_shell(do_GetInterface(browser));
+ assert(doc_shell);
+ nsCOMPtr<nsIPresShell> pres_shell;
+ check(doc_shell->GetPresShell(getter_AddRefs(pres_shell)));
+ nsCOMPtr<nsIPresContext> pres_context;
+ check(doc_shell->GetPresContext(
+ getter_AddRefs(pres_context)));
+ nsCOMPtr<nsIDOMWindow> dom_window;
+ check(browser->GetContentDOMWindow(
+ getter_AddRefs(dom_window)));
+
+ if (action == process_new_page)
+ {
+ apply_style_sheet(stylesheet_, pres_shell);
+ save_screenshot();
+ }
+ process_links(pres_shell, pres_context, dom_window);
+ if (!link_state_.get())
+ {
+ page_queue_.pop();
+ if (page_queue_.empty())
+ {
+ generate_dvdauthor_file();
+ Gtk::Main::quit();
+ }
+ else
+ load_next_page();
+ }
+ }
+ catch (std::exception & e)
+ {
+ std::cerr << "Fatal error";
+ if (!page_queue_.empty())
+ std::cerr << " while processing <" << page_queue_.front()
+ << ">";
+ std::cerr << ": " << e.what() << "\n";
+ Gtk::Main::quit();
+ }
+ }
+ }
+
+ void WebDvdWindow::save_screenshot()
+ {
+ char filename[20];
+ std::sprintf(filename, "page_%06d_back.png", page_links_.size());
+ Glib::RefPtr<Gdk::Window> window(get_window());
+ assert(window);
+ window->process_updates(true);
+ std::cout << "saving " << filename << std::endl;
+ Gdk::Pixbuf::create(Glib::RefPtr<Gdk::Drawable>(window),
+ window->get_colormap(),
+ 0, 0, 0, 0, width_, height_)
+ ->save(filename, "png");
+ }
+
+ struct WebDvdWindow::link_state
+ {
+ Glib::RefPtr<Gdk::Pixbuf> diff_pixbuf;
+
+ std::ofstream spumux_file;
+
+ int link_num;
+ LinkIterator links_it, links_end;
+
+ rectangle link_rect;
+ bool link_changing;
+ Glib::RefPtr<Gdk::Pixbuf> norm_pixbuf;
+ };
+
+ void WebDvdWindow::process_links(nsIPresShell * pres_shell,
+ nsIPresContext * pres_context,
+ nsIDOMWindow * dom_window)
+ {
+ Glib::RefPtr<Gdk::Window> window(get_window());
+ assert(window);
+
+ nsCOMPtr<nsIDOMDocument> basic_doc;
+ check(dom_window->GetDocument(getter_AddRefs(basic_doc)));
+ nsCOMPtr<nsIDOMNSDocument> ns_doc(do_QueryInterface(basic_doc));
+ assert(ns_doc);
+ nsCOMPtr<nsIEventStateManager> event_state_man(
+ pres_context->EventStateManager()); // does not AddRef
+ assert(event_state_man);
+ nsCOMPtr<nsIDOMDocumentEvent> event_factory(
+ do_QueryInterface(basic_doc));
+ assert(event_factory);
+ nsCOMPtr<nsIDOMDocumentView> doc_view(do_QueryInterface(basic_doc));
+ assert(doc_view);
+ nsCOMPtr<nsIDOMAbstractView> view;
+ check(doc_view->GetDefaultView(getter_AddRefs(view)));
+
+ // Set up or recover our iteration state.
+ std::auto_ptr<link_state> state(link_state_);
+ if (!state.get())
+ {
+ state.reset(new link_state);
+
+ state->diff_pixbuf = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB,
+ true, // has_alpha
+ 8, // bits_per_sample
+ width_, height_);
+
+ char spumux_filename[20];
+ std::sprintf(spumux_filename,
+ "page_%06d.spumux", page_links_.size());
+ state->spumux_file.open(spumux_filename);
+ state->spumux_file <<
+ "<subpictures>\n"
+ " <stream>\n"
+ " <spu force='yes' start='00:00:00.00'\n"
+ " highlight='page_" << std::setfill('0')
+ << std::setw(6) << page_links_.size()
+ << "_links.png'\n"
+ " select='page_" << std::setfill('0')
+ << std::setw(6) << page_links_.size()
+ << "_links.png'>\n";
+
+ state->link_num = 0;
+ state->links_it = LinkIterator(basic_doc);
+ state->link_changing = false;
+ }
+
+ rectangle window_rect = { 0, 0, width_, height_ };
+
+ for (/* no initialisation */;
+ state->links_it != state->links_end;
+ ++state->links_it)
+ {
+ nsCOMPtr<nsIDOMNode> node(*state->links_it);
+
+ // Find the link URI.
+ nsCOMPtr<nsILink> link(do_QueryInterface(node));
+ assert(link);
+ nsCOMPtr<nsIURI> uri;
+ check(link->GetHrefURI(getter_AddRefs(uri)));
+ std::string uri_string;
+ {
+ nsCString uri_ns_string;
+ check(uri->GetSpec(uri_ns_string));
+ uri_string.assign(uri_ns_string.BeginReading(),
+ uri_ns_string.EndReading());
+ }
+ std::string uri_sans_fragment(uri_string, 0, uri_string.find('#'));
+
+ // Is this a new link?
+ if (!state->link_changing)
+ {
+ // Find a rectangle enclosing the link and clip it to the
+ // window.
+ nsCOMPtr<nsIDOMElement> elem(do_QueryInterface(node));
+ assert(elem);
+ state->link_rect = get_elem_rect(ns_doc, elem);
+ state->link_rect &= window_rect;
+
+ if (state->link_rect.empty())
+ {
+ std::cerr << "Ignoring invisible link to "
+ << uri_string << "\n";
+ continue;
+ }
+
+ ++state->link_num;
+
+ if (state->link_num >= dvd::menu_buttons_max)
+ {
+ if (state->link_num == dvd::menu_buttons_max)
+ std::cerr << "No more than " << dvd::menu_buttons_max
+ << " buttons can be placed on a page\n";
+ std::cerr << "Ignoring link to " << uri_string << "\n";
+ continue;
+ }
+
+ // Check whether this is a link to a video or a page then
+ // add it to the known resources if not already seen.
+ nsCString path;
+ check(uri->GetPath(path));
+ // FIXME: This is a bit of a hack. Perhaps we could decide
+ // later based on the MIME type determined by Mozilla?
+ if (path.Length() > 4
+ && std::strcmp(path.EndReading() - 4, ".vob") == 0)
+ {
+ PRBool is_file;
+ check(uri->SchemeIs("file", &is_file));
+ if (!is_file)
+ {
+ std::cerr << "Links to video must use the file:"
+ << " scheme\n";
+ continue;
+ }
+ add_video(uri_sans_fragment);
+ }
+ else
+ {
+ add_page(uri_sans_fragment);
+ }
+
+ nsCOMPtr<nsIContent> content(do_QueryInterface(node));
+ assert(content);
+ nsCOMPtr<nsIDOMEventTarget> event_target(
+ do_QueryInterface(node));
+ assert(event_target);
+
+ state->norm_pixbuf = Gdk::Pixbuf::create(
+ Glib::RefPtr<Gdk::Drawable>(window),
+ window->get_colormap(),
+ state->link_rect.left,
+ state->link_rect.top,
+ 0,
+ 0,
+ state->link_rect.right - state->link_rect.left,
+ state->link_rect.bottom - state->link_rect.top);
+
+ nsCOMPtr<nsIDOMEvent> event;
+ check(event_factory->CreateEvent(
+ NS_ConvertASCIItoUTF16("MouseEvents"),
+ getter_AddRefs(event)));
+ nsCOMPtr<nsIDOMMouseEvent> mouse_event(
+ do_QueryInterface(event));
+ assert(mouse_event);
+ check(mouse_event->InitMouseEvent(
+ NS_ConvertASCIItoUTF16("mouseover"),
+ true, // can bubble
+ true, // cancelable
+ view,
+ 0, // detail: mouse click count
+ state->link_rect.left, // screenX
+ state->link_rect.top, // screenY
+ state->link_rect.left, // clientX
+ state->link_rect.top, // clientY
+ false, false, false, false, // qualifiers
+ 0, // button: left (or primary)
+ 0)); // related target
+ PRBool dummy;
+ check(event_target->DispatchEvent(mouse_event,
+ &dummy));
+ check(event_state_man->SetContentState(content,
+ NS_EVENT_STATE_HOVER));
+
+ pres_shell->FlushPendingNotifications(true);
+
+ // We may have to exit and wait for image loading
+ // to complete, at which point we will be called
+ // again.
+ if (pending_req_count_ > 0)
+ {
+ state->link_changing = true;
+ link_state_ = state;
+ return;
+ }
+ }
+
+ window->process_updates(true);
+
+ Glib::RefPtr<Gdk::Pixbuf> changed_pixbuf(
+ Gdk::Pixbuf::create(
+ Glib::RefPtr<Gdk::Drawable>(window),
+ window->get_colormap(),
+ state->link_rect.left,
+ state->link_rect.top,
+ 0,
+ 0,
+ state->link_rect.right - state->link_rect.left,
+ state->link_rect.bottom - state->link_rect.top));
+ diff_rgb_pixbufs(
+ state->norm_pixbuf,
+ changed_pixbuf,
+ state->diff_pixbuf,
+ state->link_rect.left,
+ state->link_rect.top,
+ state->link_rect.right - state->link_rect.left,
+ state->link_rect.bottom - state->link_rect.top);
+
+ state->spumux_file <<
+ " <button x0='" << state->link_rect.left << "'"
+ " y0='" << state->link_rect.top << "'"
+ " x1='" << state->link_rect.right - 1 << "'"
+ " y1='" << state->link_rect.bottom - 1 << "'/>\n";
+
+ // Add to the page's links, ignoring any fragment (for now).
+ page_links_.back().push_back(uri_sans_fragment);
+ }
+
+ quantise_rgba_pixbuf(state->diff_pixbuf, dvd::button_n_colours);
+
+ char filename[25];
+ std::sprintf(filename, "page_%06d_links.png", page_links_.size());
+ std::cout << "saving " << filename << std::endl;
+ state->diff_pixbuf->save(filename, "png");
+
+ state->spumux_file <<
+ " </spu>\n"
+ " </stream>\n"
+ "</subpictures>\n";
+ }
+
+ void generate_page_dispatch(std::ostream &, int indent,
+ int first_page, int last_page);
+
+ void WebDvdWindow::generate_dvdauthor_file()
+ {
+ std::ofstream file("webdvd.dvdauthor");
+
+ // We generate code that uses registers in the following way:
+ //
+ // g0: link destination (when jumping to menu 1), then scratch
+ // g1: current location
+ // g2-g11: location history (g2 = most recent)
+ // g12: location that last linked to a video
+ //
+ // All locations are divided into two bitfields: the least
+ // significant 10 bits are a page/menu number and the most
+ // significant 6 bits are a link/button number. This is
+ // chosen for compatibility with the encoding of the s8
+ // (button) register.
+ //
+ static const int link_mult = dvd::reg_s8_button_mult;
+ static const int page_mask = link_mult - 1;
+ static const int link_mask = (1 << dvd::reg_bits) - link_mult;
+
+ file <<
+ "<dvdauthor>\n"
+ " <vmgm>\n"
+ " <menus>\n";
+
+ for (std::size_t page_num = 1;
+ page_num <= page_links_.size();
+ ++page_num)
+ {
+ std::vector<std::string> & page_links =
+ page_links_[page_num - 1];
+
+ if (page_num == 1)
+ {
+ // This is the first page (root menu) which needs to
+ // include initialisation and dispatch code.
+
+ file <<
+ " <pgc entry='title'>\n"
+ " <pre>\n"
+ // Has the location been set yet?
+ " if (g1 eq 0)\n"
+ " {\n"
+ // Initialise the current location to first link on
+ // this page.
+ " g1 = " << 1 * link_mult + 1 << ";\n"
+ " }\n"
+ " else\n"
+ " {\n"
+ // Has the user selected a link?
+ " if (g0 ne 0)\n"
+ " {\n"
+ // First update the history.
+ // Does link go to the last page in the history?
+ " if (((g0 ^ g2) & " << page_mask
+ << ") == 0)\n"
+ // It does; we treat this as going back and pop the old
+ // location off the history stack into the current
+ // location. Clear the free stack slot.
+ " {\n"
+ " g1 = g2; g2 = g3; g3 = g4; g4 = g5;\n"
+ " g5 = g6; g6 = g7; g7 = g8; g8 = g9;\n"
+ " g9 = g10; g10 = g11; g11 = 0;\n"
+ " }\n"
+ " else\n"
+ // Link goes to some other page, so push current
+ // location onto the history stack and set the current
+ // location to be exactly the target location.
+ " {\n"
+ " g11 = g10; g10 = g9; g9 = g8; g8 = g7;\n"
+ " g7 = g6; g6 = g5; g5 = g4; g4 = g3;\n"
+ " g3 = g2; g2 = g1; g1 = g0;\n"
+ " }\n"
+ " }\n"
+ // Find the target page number.
+ " g0 = g1 & " << page_mask << ";\n";
+ // There seems to be no way to perform a computed jump,
+ // so we generate all possible jumps and a binary search
+ // to select the correct one.
+ generate_page_dispatch(file, 12, 1, page_links_.size());
+ file <<
+ " }\n";
+ }
+ else // page_num != 1
+ {
+ file <<
+ " <pgc>\n"
+ " <pre>\n";
+ }
+
+ file <<
+ // Clear link indicator and highlight the
+ // appropriate link/button.
+ " g0 = 0; s8 = g1 & " << link_mask << ";\n"
+ " </pre>\n"
+ " <vob file='page_"
+ << std::setfill('0') << std::setw(6) << page_num
+ << ".vob'/>\n";
+
+ for (std::size_t link_num = 1;
+ link_num <= page_links.size();
+ ++link_num)
+ {
+ file <<
+ " <button>"
+ // Update current location.
+ " g1 = " << link_num * link_mult + page_num << ";";
+
+ // Jump to appropriate resource.
+ const ResourceEntry & resource_loc =
+ resource_map_[page_links[link_num - 1]];
+ if (resource_loc.first == page_resource)
+ file <<
+ " g0 = " << 1 * link_mult + resource_loc.second << ";"
+ " jump menu 1;";
+ else if (resource_loc.first == video_resource)
+ file << " jump title " << resource_loc.second << ";";
+
+ file << " </button>\n";
+ }
+
+ file << " </pgc>\n";
+ }
+
+ file <<
+ " </menus>\n"
+ " </vmgm>\n";
+
+ // Generate a titleset for each video. This appears to make
+ // jumping to titles a whole lot simpler.
+ for (std::size_t video_num = 1;
+ video_num <= video_paths_.size();
+ ++video_num)
+ {
+ file <<
+ " <titleset>\n"
+ // Generate a dummy menu so that the menu button on the
+ // remote control will work.
+ " <menus>\n"
+ " <pgc entry='root'>\n"
+ " <pre> jump vmgm menu; </pre>\n"
+ " </pgc>\n"
+ " </menus>\n"
+ " <titles>\n"
+ " <pgc>\n"
+ // Record calling page/menu.
+ " <pre> g12 = g1; </pre>\n"
+ // FIXME: Should XML-escape the path
+ " <vob file='" << video_paths_[video_num - 1]
+ << "'/>\n"
+ // If page/menu location has not been changed during the
+ // video, change the location to be the following
+ // link/button when returning to it. In any case,
+ // return to a page/menu.
+ " <post> if (g1 eq g12) g1 = g1 + " << link_mult
+ << "; call menu; </post>\n"
+ " </pgc>\n"
+ " </titles>\n"
+ " </titleset>\n";
+ }
+
+ file <<
+ "</dvdauthor>\n";
+ }
+
+ void generate_page_dispatch(std::ostream & file, int indent,
+ int first_page, int last_page)
+ {
+ if (first_page == 1 && last_page == 1)
+ {
+ // The dispatch code is *on* page 1 so we must not dispatch to
+ // page 1 since that would cause an infinite loop. This case
+ // should be unreachable if there is more than one page due
+ // to the following case.
+ }
+ else if (first_page == 1 && last_page == 2)
+ {
+ // dvdauthor doesn't allow empty blocks or null statements so
+ // when selecting between pages 1 and 2 we don't use an "else"
+ // part. We must use braces so that a following "else" will
+ // match the right "if".
+ file << std::setw(indent) << "" << "{\n"
+ << std::setw(indent) << "" << "if (g0 eq 2)\n"
+ << std::setw(indent + 2) << "" << "jump menu 2;\n"
+ << std::setw(indent) << "" << "}\n";
+ }
+ else if (first_page == last_page)
+ {
+ file << std::setw(indent) << ""
+ << "jump menu " << first_page << ";\n";
+ }
+ else
+ {
+ int middle = (first_page + last_page) / 2;
+ file << std::setw(indent) << "" << "if (g0 le " << middle << ")\n";
+ generate_page_dispatch(file, indent + 2, first_page, middle);
+ file << std::setw(indent) << "" << "else\n";
+ generate_page_dispatch(file, indent + 2, middle + 1, last_page);
+ }
+ }
+
+} // namespace
+
+int main(int argc, char ** argv)
+{
+ // Get dimensions
+ int width = video::pal_oscan_width, height = video::pal_oscan_height;
+ for (int i = 1; i < argc - 1; ++i)
+ if (std::strcmp(argv[i], "-geometry") == 0)
+ {
+ std::sscanf(argv[i + 1], "%dx%d", &width, &height);
+ break;
+ }
+ // A depth of 24 results in 8 bits each for RGB components, which
+ // translates into "enough" bits for YUV components.
+ const int depth = 24;
+
+ try
+ {
+ // Spawn Xvfb and set env variables so that Xlib will use it
+ FrameBuffer fb(width, height, depth);
+ setenv("XAUTHORITY", fb.get_x_authority().c_str(), true);
+ setenv("DISPLAY", fb.get_x_display().c_str(), true);
+
+ // Initialise Gtk and Mozilla
+ Gtk::Main kit(argc, argv);
+ BrowserWidget::init();
+
+ WebDvdWindow window(width, height);
+ for (int argi = 1; argi < argc; ++argi)
+ window.add_page(argv[argi]);
+ if (argc < 2)
+ window.add_page("about:");
+ Gtk::Main::run(window);
+ }
+ catch (std::exception & e)
+ {
+ std::cerr << "Fatal error: " << e.what() << "\n";
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
--- /dev/null
+body {
+ /* Sans-serif fonts will be much more readable than serif on a TV. */
+ font-family: sans-serif;
+ /* Let the background overscan, but not the content. */
+ padding: 60px 50px 60px 50px;
+}
+a:link, a:visited {
+ color: blue;
+ /* Don't underline links because underlining tends to flicker on TVs. */
+ text-decoration: none;
+ /* Buttons have to be rectangular (AFAIK). */
+ white-space: nowrap;
+}
+/* The hover state must be made obvious since DVD players have no pointer. */
+a:hover {
+ color: red;
+}
+/* The active state should provide visual feedback, but is not so critical. */
+a:active {
+ color: purple;
+}
+/* Don't show focus rectangles. */
+*|*:-moz-any-link:focus {
+ -moz-outline: none !important;
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include <cassert>
+#include <memory>
+#include <stdexcept>
+
+#include "xpcom_support.hpp"
+
+namespace xpcom_support
+{
+ void throw_exception(nsresult error)
+ {
+ assert(NS_ERROR_GET_SEVERITY(error) == NS_ERROR_SEVERITY_ERROR);
+
+ // TODO: look up error message
+ char message[30];
+ std::sprintf(message, "XPCOM error %08x", error);
+
+ switch (error)
+ {
+ case NS_ERROR_OUT_OF_MEMORY:
+ throw std::bad_alloc();
+
+ case NS_ERROR_NOT_INITIALIZED:
+ case NS_ERROR_ALREADY_INITIALIZED:
+ case NS_ERROR_INVALID_POINTER:
+ case NS_ERROR_ILLEGAL_VALUE:
+ case NS_BASE_STREAM_CLOSED:
+ case NS_BASE_STREAM_ILLEGAL_ARGS:
+ assert(!"internal error detected by XPCOM function");
+ throw std::logic_error(message);
+
+ default:
+ throw std::runtime_error(message);
+ }
+ }
+}
--- /dev/null
+// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_XPCOM_SUPPORT_HPP
+#define INC_XPCOM_SUPPORT_HPP
+
+#include <stdexcept>
+
+#include <nsError.h>
+
+namespace xpcom_support
+{
+ void throw_exception(nsresult error);
+
+ inline nsresult check(nsresult result)
+ {
+ if (NS_FAILED(result))
+ throw_exception(result);
+ return result;
+ }
+}
+
+#endif // !INC_XPCOM_SUPPORT_HPP