aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkraxel <kraxel>2004-03-28 11:31:57 +0000
committerkraxel <kraxel>2004-03-28 11:31:57 +0000
commite9e9684117719204929821028ba9dbb7915ea119 (patch)
tree6dd2d71940b33a9f960945335e9124ef4fea9fe7
downloadfbida-e9e9684117719204929821028ba9dbb7915ea119.tar.gz
Initial revision
-rw-r--r--COPYING339
-rw-r--r--Changes180
-rw-r--r--GNUmakefile215
-rw-r--r--INSTALL59
-rw-r--r--Ida.ad655
-rw-r--r--README47
-rw-r--r--RegEdit.c1795
-rw-r--r--RegEdit.h65
-rw-r--r--RegEditI.h368
-rw-r--r--TODO22
-rw-r--r--VERSION1
-rw-r--r--backup/Ida-de173
-rw-r--r--backup/Ida-default178
-rw-r--r--backup/Ida-fixed439
-rw-r--r--browser.c571
-rw-r--r--browser.h3
-rw-r--r--color.c513
-rw-r--r--color.h1
-rw-r--r--config.h0
-rw-r--r--curl.c348
-rw-r--r--curl.h5
-rw-r--r--desktop.c276
-rw-r--r--desktop.h7
-rw-r--r--desktop/ida.desktop9
-rw-r--r--dither.c193
-rw-r--r--dither.h6
-rw-r--r--exiftran.c262
-rw-r--r--exiftran.man106
-rwxr-xr-xfallback.pl26
-rw-r--r--fb-gui.c189
-rw-r--r--fb-gui.h11
-rwxr-xr-xfbgs69
-rw-r--r--fbgs.man20
-rw-r--r--fbi.c1552
-rw-r--r--fbi.man160
-rw-r--r--fbtools.c523
-rw-r--r--fbtools.h23
-rw-r--r--filebutton.c934
-rw-r--r--filebutton.h71
-rw-r--r--filelist.c619
-rw-r--r--filelist.h3
-rw-r--r--fileops.c100
-rw-r--r--fileops.h1
-rw-r--r--filter.c495
-rw-r--r--filter.h27
-rw-r--r--fs.c502
-rw-r--r--fs.h72
-rw-r--r--genthumbnail.c219
-rw-r--r--genthumbnail.h1
-rw-r--r--hex.c141
-rw-r--r--hex.h1
-rw-r--r--icons.c130
-rw-r--r--icons.h3
-rw-r--r--ida.c1909
-rw-r--r--ida.h26
-rw-r--r--ida.man102
-rw-r--r--idaconfig.c51
-rw-r--r--idaconfig.h24
-rw-r--r--jpeg/README2
-rw-r--r--jpeg/jinclude.h91
-rw-r--r--jpeg/jpegint.h392
-rw-r--r--jpeg/jpeglib.h1096
-rw-r--r--jpeg/transupp.c928
-rw-r--r--jpeg/transupp.h135
-rw-r--r--jpegtools.c621
-rw-r--r--jpegtools.h28
-rw-r--r--lirc.c60
-rw-r--r--lirc.h2
-rw-r--r--list.h168
-rw-r--r--logo.jpgbin0 -> 16990 bytes
-rw-r--r--lut.c97
-rw-r--r--lut.h15
-rw-r--r--man.c119
-rw-r--r--man.h4
-rw-r--r--misc.h9
-rw-r--r--mk/Autoconf.mk138
-rw-r--r--mk/Compile.mk88
-rw-r--r--mk/Maintainer.mk12
-rw-r--r--mk/Variables.mk46
-rw-r--r--mk/utf8.tmp25
-rw-r--r--op.c289
-rw-r--r--op.h7
-rw-r--r--parseconfig.c857
-rw-r--r--parseconfig.h68
-rw-r--r--rd/Makefile2
-rw-r--r--rd/magick.c85
-rw-r--r--rd/read-bmp.c216
-rw-r--r--rd/read-gif.c220
-rw-r--r--rd/read-jpeg.c187
-rw-r--r--rd/read-pcd.c78
-rw-r--r--rd/read-png.c164
-rw-r--r--rd/read-ppm.c110
-rw-r--r--rd/read-tiff.c195
-rw-r--r--rd/read-xpm.c287
-rw-r--r--rd/read-xwd.c357
-rw-r--r--readers.c133
-rw-r--r--readers.h104
-rw-r--r--sane.c324
-rw-r--r--sane.h1
-rw-r--r--selections.c623
-rw-r--r--selections.h18
-rw-r--r--thumbnail.cgi.c125
-rw-r--r--viewer.c962
-rw-r--r--viewer.h96
-rw-r--r--wr/Makefile2
-rw-r--r--wr/write-jpeg.c103
-rw-r--r--wr/write-png.c76
-rw-r--r--wr/write-ppm.c34
-rw-r--r--wr/write-ps.c475
-rw-r--r--wr/write-tiff.c60
-rw-r--r--writers.c15
-rw-r--r--writers.h14
-rw-r--r--x11.c456
-rw-r--r--x11.h42
-rw-r--r--xdnd.c321
-rw-r--r--xdnd.h5
-rw-r--r--xpm/ccw.xpm22
-rw-r--r--xpm/cw.xpm22
-rw-r--r--xpm/dir.xpm41
-rw-r--r--xpm/exit.xpm22
-rw-r--r--xpm/file.xpm39
-rw-r--r--xpm/fliph.xpm22
-rw-r--r--xpm/flipv.xpm22
-rw-r--r--xpm/next.xpm22
-rw-r--r--xpm/prev.xpm22
-rw-r--r--xpm/question.xpm38
-rw-r--r--xpm/unknown.xpm39
-rw-r--r--xpm/zoomin.xpm22
-rw-r--r--xpm/zoomout.xpm22
-rw-r--r--xwd.h1
130 files changed, 26383 insertions, 0 deletions
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..a43ea21
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, 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.
+
+ 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.)
+
+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.
+
+ 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.
+
+ 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
+
+ Appendix: 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) 19yy <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., 675 Mass Ave, Cambridge, MA 02139, 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) 19yy 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.
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..2a17a93
--- /dev/null
+++ b/Changes
@@ -0,0 +1,180 @@
+
+0.21
+====
+
+ * add support to edit jpeg comments.
+ * various minor fixes/tweaks.
+
+
+0.20
+====
+
+ * file browser code is largely rewritten.
+ * added support to maintain file lists.
+ * lots of minor tweaks.
+
+
+0.19
+====
+
+ * url support for most image formats.
+
+
+0.16 => 0.17
+============
+
+ * fixed some signed/unsigned bugs.
+ * fixed tiff loader bug.
+ * utf-8 locale crash workaround.
+
+
+0.15 => 0.16
+============
+
+ * ditched autotools crap into the waste basket.
+ * fixed plenty of gcc 3.3 warnings.
+ * file browser improvements.
+ * tweaked font sets.
+ * multiplage support (Andrey Kiselev).
+
+
+0.14 => 0.15
+============
+
+ * more tiff loader fixes (based on patches by
+ Andrey Kiselev <dron@ak4719.spb.edu>).
+ * png loader fixes.
+
+
+0.13 => 0.14
+============
+
+ * tiff loader fixes (for b/w images).
+ * Made pixmap selection transfer less strict.
+
+
+0.12 => 0.13
+============
+
+ * fixed selections / clipboard / cut+paste handling.
+ * fixed some warnings.
+ * dropped extra "make depend" pass from build proccess.
+
+
+0.11 => 0.12
+============
+
+ * added tooltips (needs OpenMotif 2.2 to work).
+ * made the scale image dialog take care about image resolution.
+ * some fixes in sane code.
+ * man page updates.
+ * unbundled libpcd.
+
+
+0.10 => 0.11
+============
+
+ * renamed "iv" to "ida".
+ * hex viewer fixes.
+ * [ fixme: weißpunkt ]
+ * sane support.
+ * keep track of the image resolution
+ * The image size for printing / PostScript export defaults to the
+ original size (calculated from image resolution if available).
+
+
+0.9 => 0.10
+===========
+
+ * do scaling with floats to avoid rounding errors with large
+ scale factors.
+ * fixed segfault with huge zoom factors / huge images.
+ * need a new name for the utility, "iv" has way to much name
+ clashes. ideas are welcome ...
+
+
+0.8 => 0.9
+==========
+
+ * fixed memory leak in file browser.
+
+
+0.7 => 0.8
+==========
+
+ * added loader for (uncompressed) windows bitmap files.
+ * some minor changes for image save code - can write tmp files
+ cleanly now. DnD for uses jpeg for tmp files, so you can drop them
+ into netscape.
+ * added loader for xbm files.
+ * Some minor DnD fixes.
+ * better "busy cursor" handling.
+ * added wildcards filter to the file browser
+ * file browser DnD fixes.
+ * made PhotoCD resolution switchable at runtime.
+
+
+0.6 => 0.7
+==========
+
+ * added some handlers for selection data transfer, for
+ clipboard + drag'n'drop support.
+
+
+0.5 => 0.6
+==========
+
+ * added simple file browser.
+ * fixed bugs in png image loader.
+ * fixed a bug in the gif loader.
+ * figured out that libungif 4.1 is broken (if iv segfaults when loading
+ gif images, try downgrading to libungif 3.0 + rebuild)
+ * added autocrop.
+ * added print dialog.
+ * added blur,sharpe and emboss filters.
+
+
+0.4 => 0.5
+==========
+
+ * add options for image saving (jpeg, PostScript).
+ * added PNG write support.
+
+
+0.3 => 0.4
+==========
+
+ * Added resize.
+ * Added free rotation.
+ * Added edge detect filter.
+ * Some fine tuning here and there.
+ * Added i18n + german translation.
+
+
+0.2 => 0.3
+==========
+
+ * added PhotoCD support.
+ * fixed some minor nitpicks.
+ * added toolbar.
+ * added support for writing tiff images.
+ * most image operations look at the selected area now.
+ * added support for writing Postscript. Needs more work, not
+ configurable yet (does: A4, portrait, image centered and scaled to
+ max size with same aspect ratio).
+
+
+0.1 => 0.2
+==========
+
+ * implemented saving (ppm, jpeg).
+ * redirect stderr to a message box.
+ * color editor works.
+ * fixed a bug in the gif loader.
+ * fixed bugs in the tiff loader.
+ * added -debug + -help command line switches.
+ * added x window dump loader.
+ * added support for reading images from stdin (you can now do
+ screenshots using "xwd | iv -" for example).
+ * added one-level undo.
+ * added crop
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644
index 0000000..f7f86d9
--- /dev/null
+++ b/GNUmakefile
@@ -0,0 +1,215 @@
+# config
+srcdir = .
+VPATH = $(srcdir)
+-include Make.config
+include $(srcdir)/mk/Variables.mk
+
+resdir = $(DESTDIR)$(RESDIR)
+
+# fixup flags
+CFLAGS += -DVERSION='"$(VERSION)"' -I$(srcdir)
+
+# default target
+all: build
+
+# what to build
+TARGETS := exiftran thumbnail.cgi
+ifeq ($(HAVE_LINUX_FB_H),yes)
+ TARGETS += fbi
+endif
+ifeq ($(HAVE_MOTIF),yes)
+ TARGETS += ida
+endif
+
+
+#################################################################
+# poor man's autoconf ;-)
+
+include $(srcdir)/mk/Autoconf.mk
+
+define make-config
+LIB := $(LIB)
+RESDIR := $(call ac_resdir)
+HAVE_ENDIAN_H := $(call ac_header,endian.h)
+HAVE_LINUX_FB_H := $(call ac_header,linux/fb.h)
+HAVE_GLIBC := $(call ac_func,fopencookie)
+HAVE_STRCASESTR := $(call ac_func,strcasestr)
+HAVE_LIBPCD := $(call ac_lib,pcd_open,pcd)
+HAVE_LIBUNGIF := $(call ac_lib,DGifOpenFileName,ungif)
+HAVE_LIBPNG := $(call ac_lib,png_read_info,png,-lz)
+HAVE_LIBTIFF := $(call ac_lib,TIFFOpen,tiff)
+#HAVE_LIBMAGICK := $(call ac_binary,Magick-config)
+HAVE_LIBSANE := $(call ac_lib,sane_init,sane)
+HAVE_LIBCURL := $(call ac_lib,curl_easy_init,curl)
+HAVE_LIBLIRC := $(call ac_lib,lirc_init,lirc_client)
+HAVE_MOTIF := $(call ac_lib,XmStringGenerate,Xm,-L/usr/X11R6/$(LIB) -lXpm -lXt -lXext -lX11)
+endef
+
+# transparent http/ftp access using curl depends on fopencookie (glibc)
+ifneq ($(HAVE_GLIBC),yes)
+ HAVE_LIBCURL := no
+endif
+
+# catch fopen calls for transparent ftp/http access
+ifeq ($(HAVE_LIBCURL),yes)
+ ida fbi : CFLAGS += -D_GNU_SOURCE
+ ida fbi : LDFLAGS += -Wl,--wrap=fopen
+endif
+
+
+########################################################################
+# conditional stuff
+
+includes = ENDIAN_H STRCASESTR
+libraries = PCD UNGIF PNG TIFF CURL SANE LIRC
+ida_libs = PCD UNGIF PNG TIFF CURL SANE
+fbi_libs = PCD UNGIF PNG TIFF CURL LIRC
+
+#MAGICK_CFLAGS = $(shell Magick-config --cflags)
+#MAGICK_LDFLAGS = $(shell Magick-config --ldflags)
+#MAGICK_LDLIBS = $(shell Magick-config --libs)
+#MAGICK_OBJS := rd/magick.o
+
+PNG_LDLIBS := -lpng -lz
+TIFF_LDLIBS := -ltiff
+PCD_LDLIBS := -lpcd
+UNGIF_LDLIBS := -lungif
+SANE_LDLIBS := -lsane
+CURL_LDLIBS := -lcurl
+LIRC_LDLIBS := -llirc_client
+
+PNG_OBJS := rd/read-png.o wr/write-png.o
+TIFF_OBJS := rd/read-tiff.o wr/write-tiff.o
+PCD_OBJS := rd/read-pcd.o
+UNGIF_OBJS := rd/read-gif.o
+SANE_OBJS := sane.o
+CURL_OBJS := curl.o
+LIRC_OBJS := lirc.o
+
+# common objs
+OBJS_READER := readers.o rd/read-ppm.o rd/read-bmp.o rd/read-jpeg.o
+OBJS_WRITER := writers.o wr/write-ppm.o wr/write-ps.o wr/write-jpeg.o
+
+# update various flags depending on HAVE_*
+CFLAGS += $(call ac_inc_cflags,$(includes))
+CFLAGS += $(call ac_lib_cflags,$(libraries))
+CFLAGS += $(call ac_lib_mkvar,$(libraries),CFLAGS)
+LDFLAGS += $(call ac_lib_mkvar,$(libraries),LDFLAGS)
+
+# link which conditional libs
+ida : LDLIBS += $(call ac_lib_mkvar,$(ida_libs),LDLIBS)
+fbi : LDLIBS += $(call ac_lib_mkvar,$(fbi_libs),LDLIBS)
+
+
+########################################################################
+# rules for the small tools
+
+# jpeg/exif libs
+exiftran : LDLIBS += -ljpeg -lexif -lm
+genthumbnail : LDLIBS += -ljpeg -lexif -lm
+thumbnail.cgi : LDLIBS += -lexif -lm
+
+exiftran: exiftran.o genthumbnail.o jpegtools.o jpeg/transupp.o \
+ filter.o op.o readers.o rd/read-jpeg.o
+thumbnail.cgi: thumbnail.cgi.o
+
+
+########################################################################
+# rules for ida
+
+# object files
+OBJS_IDA := \
+ ida.o man.o hex.o x11.o viewer.o dither.o icons.o \
+ parseconfig.o idaconfig.o fileops.o desktop.o \
+ RegEdit.o selections.o xdnd.o jpeg/transupp.o \
+ filebutton.o filelist.o browser.o jpegtools.o \
+ op.o filter.o lut.o color.o \
+ rd/read-xwd.o rd/read-xpm.o
+
+OBJS_IDA += $(call ac_lib_mkvar,$(ida_libs),OBJS)
+
+# for X11 + Motif
+ida : CFLAGS += -I/usr/X11R6/include
+ida : LDFLAGS += -L/usr/X11R6/$(LIB)
+ida : LDLIBS += -lXm -lXpm -lXt -lXext -lX11
+
+# jpeg/exif libs
+ida : LDLIBS += -ljpeg -lexif -lm
+
+# RegEdit.c is good old K&R ...
+RegEdit.o : CFLAGS += -Wno-missing-prototypes -Wno-strict-prototypes
+
+ida: $(OBJS_IDA) $(OBJS_READER) $(OBJS_WRITER)
+
+Ida.ad.h: Ida.ad $(srcdir)/fallback.pl
+ perl $(srcdir)/fallback.pl < $< > $@
+
+logo.h: logo.jpg
+ hexdump -v -e '1/1 "0x%02x,"' < $< > $@
+ echo >> $@ # make gcc 3.x happy
+
+ida.o: Ida.ad.h logo.h
+
+
+########################################################################
+# rules for fbi
+
+# object files
+OBJS_FBI := \
+ fbi.o fbtools.o fs.o fb-gui.o desktop.o \
+ jpegtools.o jpeg/transupp.o \
+ dither.o filter.o op.o
+
+OBJS_FBI += $(filter-out wr/%,$(call ac_lib_mkvar,$(fbi_libs),OBJS))
+
+# jpeg/exif libs
+fbi : LDLIBS += -ljpeg -lexif -lm
+fs.o fb-gui.o : CFLAGS += -DX_DISPLAY_MISSING=1
+
+fbi: $(OBJS_FBI) $(OBJS_READER)
+
+
+########################################################################
+# general rules
+
+.PHONY: build install clean distclean realclean
+build: $(TARGETS)
+
+install: build
+ $(INSTALL_DIR) $(bindir)
+ $(INSTALL_DIR) $(mandir)/man1
+ $(INSTALL_BINARY) exiftran $(bindir)
+ $(INSTALL_DATA) $(srcdir)/exiftran.man $(mandir)/man1/exiftran.1
+ifeq ($(HAVE_LINUX_FB_H),yes)
+ $(INSTALL_BINARY) fbi fbgs $(bindir)
+ $(INSTALL_DATA) $(srcdir)/fbi.man $(mandir)/man1/fbi.1
+ $(INSTALL_DATA) $(srcdir)/fbgs.man $(mandir)/man1/fbgs.1
+endif
+ifeq ($(HAVE_MOTIF),yes)
+ $(INSTALL_BINARY) ida $(bindir)
+ $(INSTALL_DATA) $(srcdir)/ida.man $(mandir)/man1/ida.1
+ $(INSTALL_DIR) $(resdir)/app-defaults
+ $(INSTALL_DATA) $(srcdir)/Ida.ad $(resdir)/app-defaults/Ida
+endif
+
+clean:
+ -rm -f *.o jpeg/*.o rd/*.o wr/*.o $(depfiles) core core.*
+
+realclean distclean: clean
+ -rm -f Make.config
+ -rm -f $(TARGETS) *~ rd/*~ wr/*~ xpm/*~ Ida.ad.h logo.h
+
+
+include $(srcdir)/mk/Compile.mk
+-include $(depfiles)
+
+
+########################################################################
+# maintainer stuff
+
+include $(srcdir)/mk/Maintainer.mk
+
+sync::
+ cp $(srcdir)/../xawtv/common/parseconfig.[ch] $(srcdir)
+ cp $(srcdir)/../xawtv/console/fbtools.[ch] $(srcdir)
+ cp $(srcdir)/../xawtv/console/fs.[ch] $(srcdir)
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..0d1ba36
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,59 @@
+
+howto compile and install this package
+======================================
+
+
+really short install instructions
+---------------------------------
+
+ $ make
+ $ su -c "make install"
+
+
+
+the more detailed version
+-------------------------
+
+Make sure you use GNU make. The file name "GNUmakefile" isn't a joke,
+this package really requires GNU make.
+
+As first step make will do some config checks on your system and write
+the results to Make.config. If you want to have a look at Make.config
+before the actual build starts you can run this step separately using
+"make config".
+
+The Makefiles use the usual GNU-ish Makefile conventions for variable
+names and default values, i.e. prefix=/usr/local, ...
+
+The values for some frequently adapted variables are initialized from
+the enviroment. Thus you can change the defaults simply by setting
+environment variables:
+
+ $ prefix="/usr"
+ $ CFLAGS="-O3 -mcpu=i686"
+ $ export prefix CFLAGS
+
+Almost any variable can be overridden on the make command line. It is
+often used this way to install into some buildroot for packaging ...
+
+ $ su -c "make DESTDIR=/tmp/buildroot install"
+
+... but it works for most other variables equally well. There are
+some exceptions through, it usually does _not_ work for CFLAGS for
+example.
+
+Try "make verbose=yes" if you want to see the complete command lines
+executed by make instead of the short messages (for trouble shooting,
+because you like this way, for whatever reason ...). This also makes
+the config checks performed by "make config" more verbose.
+
+If you don't trust my Makefiles you can run "make -n install" to see
+what "make install" would do on your system. It will produce
+human-readable output (unlike automake ...).
+
+Have fun,
+
+ Gerd
+
+--
+Gerd Knorr <kraxel@bytesex.org>
diff --git a/Ida.ad b/Ida.ad
new file mode 100644
index 0000000..87bac13
--- /dev/null
+++ b/Ida.ad
@@ -0,0 +1,655 @@
+! ----------------------------------------------------------------------------
+! fonts
+
+*renderTable: small
+*renderTable.fontType: FONT_IS_FONTSET
+*renderTable.fontName: \
+ -*-bitstream vera sans-medium-r-normal-*-*-140-*-*-p-*-iso8859-1, \
+ -*-bitstream vera sans-medium-r-normal-*-*-140-*-*-p-*-iso8859-15, \
+ -microsoft-tahoma-medium-r-normal-*-*-140-*-*-p-*-iso8859-*, \
+ -adobe-helvetica-medium-r-normal-*-*-140-*-*-p-*-iso8859-*, \
+ -cronyx-helvetica-medium-r-normal-*-*-140-*-*-p-*-koi8-r, \
+ -*-lucida-medium-r-normal-*-*-140-*-*-p-*-iso8859-*, \
+ -gnu-unifont-medium-r-normal-*-*-160-*-*-*-*-*-*, \
+ -*-*-medium-r-normal-*-*-140-*-*-p-*-*-*, \
+ -*-*-medium-r-normal-*-*-160-*-*-p-*-*-*, \
+ -*-*-*-*-*-*-*-140-*-*-*-*-*-*, \
+ -*-*-*-*-*-*-*-160-*-*-*-*-*-*, *
+*renderTable.small.fontType: FONT_IS_FONTSET
+*renderTable.small.fontName: \
+ -*-bitstream vera sans-medium-r-normal-*-*-100-*-*-p-*-iso8859-1, \
+ -*-bitstream vera sans-medium-r-normal-*-*-100-*-*-p-*-iso8859-15, \
+ -microsoft-tahoma-medium-r-normal-*-*-100-*-*-p-*-iso8859-*, \
+ -adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-*, \
+ -cronyx-helvetica-medium-r-normal-*-*-100-*-*-p-*-koi8-r, \
+ -*-lucida-medium-r-normal-*-*-100-*-*-p-*-iso8859-*, \
+ -*-*-medium-r-normal-*-*-100-*-*-p-*-*-*, \
+ -*-*-medium-r-normal-*-*-120-*-*-p-*-*-*, \
+ -*-*-*-*-*-*-*-100-*-*-*-*-*-*, \
+ -*-*-*-*-*-*-*-120-*-*-*-*-*-*, *
+
+*XmTextField.renderTable:
+*XmTextField.renderTable.fontType: FONT_IS_FONTSET
+*XmTextField.renderTable.fontName: \
+ -*-bitstream vera sans mono-medium-r-normal-*-*-140-*-*-*-*-iso8859-1, \
+ -*-bitstream vera sans mono-medium-r-normal-*-*-140-*-*-*-*-iso8859-15, \
+ -monotype-andale mono-medium-r-normal-*-*-140-*-*-*-*-iso8859-*, \
+ -adobe-courier-medium-r-normal-*-*-140-*-*-m-*-iso8859-*, \
+ -cronyx-courier-medium-r-normal-*-*-140-*-*-m-*-koi8-r, \
+ -*-lucidatypewriter-medium-r-normal-*-*-140-*-*-m-*-iso8859-*, \
+ -*-*-medium-r-normal-*-*-140-*-*-m-*-*-*, \
+ -*-*-medium-r-normal-*-*-160-*-*-m-*-*-*, \
+ -*-*-*-*-*-*-*-140-*-*-*-*-*-*, \
+ -*-*-*-*-*-*-*-160-*-*-*-*-*-*, *
+
+
+*background: gray77
+!*shadowThickness: 2
+!*highlightThickness: 1
+
+
+! ----------------------------------------------------------------------------
+! image window
+
+Ida.geometry: 75x50
+!Ida.winGravity: static
+Ida.view*translations: #override \
+ <Key>space: Next() \n\
+ <Key>osfDelete: Prev() \n\
+ <Key>osfBackSpace: Prev() \n\
+ <Key>Page_Down: NextPage() \n\
+ <Key>Page_Up: PrevPage() \n\
+ <Key>N: NextPage() \n\
+ ~Ctrl<Key>P: PrevPage() \n\
+ <Key>KP_Add: Zoom(inc) \n\
+ <Key>KP_Subtract: Zoom(dec) \n\
+ <Btn2Down>: Ipc(drag) \n\
+ <Btn3Up>: Popup(control) \n\
+ \
+ <Key>G: Gamma() \n\
+ <Key>F: Browser() \n\
+ <Key>L: Filelist() \n\
+ Ctrl<Key>V: Ipc(paste) \n\
+ Alt<Key>V: Ipc(paste) \n\
+ Ctrl<Key>C: Ipc(copy) \n\
+ Alt<Key>C: Ipc(copy) \n\
+ \
+ Ctrl<Key>P: Print() \n\
+ Ctrl<Key>L: Load() \n\
+ Ctrl<Key>S: Save() \n\
+ Alt<Key>S: Sharpe() \n\
+ ~Alt ~Ctrl<Key>S: Resize() \n\
+ <Key>plus: Zoom(inc) \n\
+ <Key>minus: Zoom(dec) \n\
+ <Key>U: Undo() \n\
+ ~Alt ~Ctrl<Key>C: Filter(crop) \n\
+ ~Alt ~Ctrl<Key>V: Filter(flip-vert) \n\
+ <Key>H: Filter(flip-horz) \n\
+ ~Alt ~Shift<Key>T: Filter(rotate-cw) \n\
+ Shift<Key>T: Filter(rotate-ccw) \n\
+ Alt<Key>T: Rotate() \n\
+ <Key>I: Filter(invert) \n\
+ ~Alt<Key>E: Color() \n\
+ Alt<Key>E: F3x3(-1,-1,-1,-1,8,-1,-1,-1,-1) \n\
+ Alt<Key>B: F3x3(1,1,1,1,1,1,1,1,1, 1,9) \n\
+ Alt<Key>M: F3x3(1,0,0,0,0,0,0,0,-1, 0,0,128) \n\
+ <Key>osfHelp: Man(ida) \n\
+ <Key>Q: Exit()
+
+Ida.view.VertScrollBar.accelerators: #override \
+ <Btn4Up>: IncrementUpOrLeft(Up) \n\
+ <Btn5Up>: IncrementDownOrRight(Down)\n\
+ ~Ctrl<Key>osfUp: IncrementUpOrLeft(Up) \n\
+ ~Ctrl<Key>osfDown: IncrementDownOrRight(Down)\n\
+ Ctrl<Key>osfUp: PageUpOrLeft(Up) \n\
+ Ctrl<Key>osfDown: PageDownOrRight(Down)
+Ida.view.HorScrollBar.accelerators: #override \
+ ~Ctrl<Key>osfLeft: IncrementUpOrLeft(Left) \n\
+ ~Ctrl<Key>osfRight: IncrementDownOrRight(Right)\n\
+ Ctrl<Key>osfLeft: PageUpOrLeft(Left) \n\
+ Ctrl<Key>osfRight: PageDownOrRight(Right)
+
+
+Ida.view.shadowThickness: 1
+Ida.view.scrollingPolicy: AUTOMATIC
+Ida.view.scrollBarPlacement: BOTTOM_RIGHT
+Ida.view.scrolledWindowChildType: SCROLL_VERT
+
+Ida.view*image.backgroundPixmap: none
+Ida.view*image.borderWidth: 0
+Ida.view*image.highlightThickness: 1
+Ida.view*image.highlightColor: red
+
+Ida.aboutbox_popup.deleteResponse: DESTROY
+Ida.aboutbox_popup.title: About ida
+Ida*aboutbox_popup*messageString: \
+ ida - image viewer & editor \n\
+ \n\
+ (c) 2001,02 Gerd Knorr <kraxel@bytesex.org>
+
+Ida.sorrybox_popup.deleteResponse: DESTROY
+
+Ida.noundobox_popup.deleteResponse: DESTROY
+Ida.noundobox_popup.title: No undo
+Ida*noundobox_popup*messageString: \
+ No undo info available, sorry. \n\
+ You can undo the last step only.
+
+
+! ----------------------------------------------------------------------------
+! dialog boxes
+
+Ida.XmDialogShell.deleteResponse: DESTROY
+Ida.XmDialogShell*scale.orientation: HORIZONTAL
+Ida.XmDialogShell*scale.showValue: True
+
+Ida.errbox_popup.deleteResponse: UNMAP
+Ida.errbox_popup.title: Errors
+
+Ida.load_popup.deleteResponse: UNMAP
+Ida.load_popup.title: Load File
+
+Ida.save_popup*deleteResponse: UNMAP
+Ida.save_popup*dialogStyle: DIALOG_PRIMARY_APPLICATION_MODAL
+Ida.save_popup.title: Save File
+Ida.save_popup*format.labelString: Image format:
+
+Ida.print_popup*deleteResponse: UNMAP
+Ida.print_popup*dialogStyle: DIALOG_PRIMARY_APPLICATION_MODAL
+Ida.print_popup.title: Print File
+Ida.print_popup*selectionLabelString: Print command
+Ida.print_popup*textString: lpr
+
+Ida*ps_popup*rc1.orientation: HORIZONTAL
+Ida*ps_popup*draw.borderWidth: 1
+Ida*ps_popup*draw.background: white
+Ida*ps_popup*draw.resizePolicy: RESIZE_NONE
+Ida*ps_popup*scale.titleString: Scaling
+Ida*ps_popup*scale.minimum: 10
+Ida*ps_popup*scale.maximum: 1000
+Ida*ps_popup*scale.decimalPoints: 1
+Ida*ps_popup.title: PostScript Options
+Ida*ps_popup*paper.labelString: Paper size:
+Ida*ps_popup*ori.labelString: Orientation:
+
+Ida*jpeg_popup.title: JPEG Options
+Ida*jpeg_popup*selectionLabelString: Image quality (0 ... 100)
+
+Ida.gamma_popup*scale.minimum: 20
+Ida.gamma_popup*scale.maximum: 500
+Ida.gamma_popup*scale.decimalPoints: 2
+Ida.gamma_popup.title: Gamma correction
+Ida.gamma_popup*selectionLabelString: Gamma value
+
+Ida.bright_popup*scale.minimum: -256
+Ida.bright_popup*scale.maximum: 256
+Ida.bright_popup.title: Adjust bright
+Ida.bright_popup*selectionLabelString: Bright
+
+Ida.contrast_popup*scale.minimum: -128
+Ida.contrast_popup*scale.maximum: 512
+Ida.contrast_popup.title: Adjust contrast
+Ida.contrast_popup*selectionLabelString: Contrast
+
+Ida.rotate_popup*scale.minimum: -180
+Ida.rotate_popup*scale.maximum: 180
+Ida.rotate_popup.title: Rotate image
+Ida.rotate_popup*selectionLabelString: angle
+
+Ida.sharpe_popup*scale.minimum: 0
+Ida.sharpe_popup*scale.maximum: 100
+Ida.sharpe_popup.title: Sharpe image
+Ida.sharpe_popup*selectionLabelString: value
+
+Ida.resize_popup.deleteResponse: DESTROY
+Ida.resize_popup*rc.adjustMargin: false
+Ida.resize_popup*rc.rc.orientation: HORIZONTAL
+Ida.resize_popup*rc.rc.?.indicatorType: ONE_OF_MANY
+Ida.resize_popup.title: Scale image
+Ida.resize_popup*lx.labelString: Width (pixels)
+Ida.resize_popup*ly.labelString: Height (pixels)
+Ida.resize_popup*lr.labelString: Resolution (dpi)
+Ida.resize_popup*lock.labelString: Keep aspect ratio
+Ida.resize_popup*size.labelString: Change size
+Ida.resize_popup*res.labelString: Change resolution
+Ida.resize_popup*phys.labelString: Image size
+
+
+! ----------------------------------------------------------------------------
+! edit colors dialog
+
+Ida.color_popup.deleteResponse: DESTROY
+
+Ida.color_popup.title: Edit colors
+Ida.color_popup*hist.labelString: Histograms
+Ida.color_popup*map.labelString: Maps
+Ida.color_popup*lock.labelString: Same values for all channels
+Ida.color_popup*vals.labelString: Show values for channel:
+Ida.color_popup*valsM.red.labelString: red
+Ida.color_popup*valsM.green.labelString: green
+Ida.color_popup*valsM.blue.labelString: blue
+Ida.color_popup*in.label.labelString: Input range:
+Ida.color_popup*out.label.labelString: Output range:
+Ida.color_popup*gamma.label.labelString: Gamma:
+!Ida.color_popup*white.labelString: FIXME
+
+Ida.color_popup*XmForm*leftOffset: 10
+Ida.color_popup*XmForm*rightOffset: 10
+Ida.color_popup*XmForm*topOffset: 10
+Ida.color_popup*XmForm*bottomOffset: 10
+Ida.color_popup*XmForm*leftAttachment: ATTACH_WIDGET
+Ida.color_popup*XmForm*topAttachment: ATTACH_WIDGET
+Ida.color_popup*XmForm.sep.rightAttachment: ATTACH_FORM
+Ida.color_popup*XmForm.XmRowColumn.rightAttachment: ATTACH_FORM
+Ida.color_popup*XmForm.XmRowColumn.orientation: HORIZONTAL
+Ida.color_popup*XmText.columns: 5
+
+Ida.color_popup*XmDrawingArea.background: white
+Ida.color_popup*XmDrawingArea.borderWidth: 1
+Ida.color_popup*XmDrawingArea.borderColor: black
+
+Ida.color_popup*hred.topWidget: hist
+Ida.color_popup*hgreen.topWidget: hred
+Ida.color_popup*hblue.topWidget: hgreen
+
+Ida.color_popup*map.leftWidget: hred
+Ida.color_popup*mred.topWidget: hist
+Ida.color_popup*mred.leftWidget: hred
+Ida.color_popup*mred.rightAttachment: ATTACH_FORM
+Ida.color_popup*mgreen.topWidget: mred
+Ida.color_popup*mgreen.leftWidget: hgreen
+Ida.color_popup*mgreen.rightAttachment: ATTACH_FORM
+Ida.color_popup*mblue.topWidget: mgreen
+Ida.color_popup*mblue.leftWidget: hblue
+Ida.color_popup*mblue.rightAttachment: ATTACH_FORM
+
+Ida.color_popup*lock.topWidget: hblue
+Ida.color_popup*vals.topWidget: lock
+Ida.color_popup*in.topWidget: vals
+Ida.color_popup*out.topWidget: in
+Ida.color_popup*gamma.topWidget: out
+Ida.color_popup*pick.topWidget: gamma
+
+
+! ----------------------------------------------------------------------------
+! control
+
+ctrl.title: ida controls
+ctrl.form.status.labelString: fixme
+ctrl*XmMenuShell.XmRowColumn.tearOffModel: TEAR_OFF_ENABLED
+
+ctrl*tool.orientation: HORIZONTAL
+ctrl*tool.XmPushButton.shadowThickness: 1
+
+ctrl.toolTipEnable: 1
+ctrl.toolTipPostDelay: 2000
+ctrl.toolTipPostDuration: 5000
+ctrl*TipLabel.foreground: black
+ctrl*TipLabel.background: lightyellow
+ctrl*TipShell.borderWidth: 1
+ctrl*TipShell.borderColor: black
+ctrl*tool.XmSeparator.orientation: VERTICAL
+ctrl*tool.XmSeparator.width: 12
+ctrl*tool.XmSeparator.margin: 3
+ctrl*tool.XmPushButton.labelType: PIXMAP
+ctrl*tool.prev.toolTipString: previous file
+ctrl*tool.prev.labelPixmap: prev
+ctrl*tool.next.toolTipString: next file
+ctrl*tool.next.labelPixmap: next
+ctrl*tool.zoomin.toolTipString: zoom in
+ctrl*tool.zoomin.labelPixmap: zoomin
+ctrl*tool.zoomout.toolTipString: zoom out
+ctrl*tool.zoomout.labelPixmap: zoomout
+ctrl*tool.flipv.toolTipString: flip vertical
+ctrl*tool.flipv.labelPixmap: flipv
+ctrl*tool.fliph.toolTipString: flip horizontal
+ctrl*tool.fliph.labelPixmap: fliph
+ctrl*tool.rotccw.toolTipString: turn counter clockwise
+ctrl*tool.rotccw.labelPixmap: rotccw
+ctrl*tool.rotcw.toolTipString: turn clockwise
+ctrl*tool.rotcw.labelPixmap: rotcw
+ctrl*tool.exit.toolTipString: quit
+ctrl*tool.exit.labelPixmap: exit
+
+ctrl.form*list.visibleItemCount: 12
+ctrl.form*list.translations: #override \
+ <Key>space: Next() \n\
+ <Key>osfDelete: Prev() \n\
+ <Key>osfBackSpace: Prev() \n\
+ <Key>KP_Add: Zoom(inc) \n\
+ <Key>KP_Subtract: Zoom(dec)
+
+! file menu
+ctrl*bar.file.labelString: File
+ctrl*bar.file.mnemonic: F
+ctrl*bar*load.labelString: Load image ...
+ctrl*bar*load.mnemonic: L
+ctrl*bar*load.acceleratorText: Ctrl+L
+ctrl*bar*load.accelerator: Ctrl<Key>L
+ctrl*bar*save.labelString: Save image ...
+ctrl*bar*save.mnemonic: S
+ctrl*bar*save.acceleratorText: Ctrl+S
+ctrl*bar*save.accelerator: Ctrl<Key>S
+ctrl*bar*browse.labelString: File browser ...
+ctrl*bar*browse.acceleratorText: F
+ctrl*bar*browse.accelerator: <Key>F
+ctrl*bar*filelist.labelString: File list ...
+ctrl*bar*filelist.acceleratorText: L
+ctrl*bar*filelist.accelerator: <Key>L
+ctrl*bar*scan.labelString: Scan
+ctrl*bar*print.labelString: Print ...
+ctrl*bar*print.mnemonic: P
+ctrl*bar*print.acceleratorText: Ctrl+P
+ctrl*bar*print.accelerator: Ctrl<Key>P
+ctrl*bar*quit.labelString: Quit
+ctrl*bar*quit.mnemonic: Q
+ctrl*bar*quit.acceleratorText: Q
+ctrl*bar*quit.accelerator: <Key>Q
+
+! edit menu
+ctrl*bar.edit.labelString: Edit
+ctrl*bar.edit.mnemonic: E
+ctrl*bar*undo.labelString: Undo last operation
+ctrl*bar*undo.mnemonic: U
+ctrl*bar*undo.acceleratorText: U
+ctrl*bar*undo.accelerator: <Key>U
+ctrl*bar*copy.labelString: Copy
+ctrl*bar*copy.acceleratorText: Ctrl+C
+ctrl*bar*copy.accelerator: Ctrl<Key>C
+ctrl*bar*paste.labelString: Paste
+ctrl*bar*paste.acceleratorText: Ctrl+V
+ctrl*bar*paste.accelerator: Ctrl<Key>V
+ctrl*bar*flipv.labelString: Flip vertical
+ctrl*bar*flipv.mnemonic: v
+ctrl*bar*flipv.acceleratorText: V
+ctrl*bar*flipv.accelerator: <Key>V
+ctrl*bar*fliph.labelString: Flip horizontal
+ctrl*bar*fliph.mnemonic: h
+ctrl*bar*fliph.acceleratorText: H
+ctrl*bar*fliph.accelerator: <Key>H
+ctrl*bar*rotcw.labelString: Turn clockwise
+ctrl*bar*rotcw.mnemonic: T
+ctrl*bar*rotcw.acceleratorText: T
+ctrl*bar*rotcw.accelerator: ~Meta ~Shift<Key>T
+ctrl*bar*rotccw.labelString: Turn counter clockwise
+ctrl*bar*rotccw.acceleratorText: Shift+T
+ctrl*bar*rotccw.accelerator: Shift<Key>T
+ctrl*bar*invert.labelString: Invert
+ctrl*bar*invert.mnemonic: I
+ctrl*bar*invert.acceleratorText: I
+ctrl*bar*invert.accelerator: <Key>I
+ctrl*bar*crop.labelString: Crop
+ctrl*bar*crop.mnemonic: C
+ctrl*bar*crop.acceleratorText: C
+ctrl*bar*crop.accelerator: <Key>C
+ctrl*bar*acrop.labelString: Autocrop
+ctrl*bar*acrop.mnemonic: A
+ctrl*bar*scale.labelString: Scale ...
+ctrl*bar*scale.mnemonic: S
+ctrl*bar*scale.acceleratorText: S
+ctrl*bar*scale.accelerator: ~Ctrl<Key>S
+ctrl*bar*rotany.labelString: Rotate ...
+ctrl*bar*rotany.acceleratorText: Alt+T
+ctrl*bar*rotany.accelerator: Alt<Key>T
+
+! filter menu
+ctrl*bar.op.labelString: Filters
+ctrl*bar.op.mnemonic: F
+ctrl*bar*gamma.labelString: Gamma ...
+ctrl*bar*gamma.mnemonic: G
+ctrl*bar*gamma.acceleratorText: G
+ctrl*bar*gamma.accelerator: <Key>G
+ctrl*bar*bright.labelString: Bright ...
+ctrl*bar*bright.mnemonic: B
+ctrl*bar*contr.labelString: Contrast ...
+ctrl*bar*contr.mnemonic: C
+ctrl*bar*color.labelString: Edit colors ...
+ctrl*bar*color.mnemonic: E
+ctrl*bar*color.acceleratorText: E
+ctrl*bar*color.accelerator: ~Alt<Key>E
+ctrl*bar*gray.labelString: Grayscale
+ctrl*bar*blur.labelString: Blur
+ctrl*bar*blur.acceleratorText: Alt+B
+ctrl*bar*blur.accelerator: Alt<Key>B
+ctrl*bar*sharpe.labelString: Sharpe ...
+ctrl*bar*sharpe.acceleratorText: Alt+S
+ctrl*bar*sharpe.accelerator: Alt<Key>S
+ctrl*bar*edge.labelString: Edge detect
+ctrl*bar*edge.acceleratorText: Alt+E
+ctrl*bar*edge.accelerator: Alt<Key>E
+ctrl*bar*emboss.labelString: Emboss
+ctrl*bar*emboss.acceleratorText: Alt+M
+ctrl*bar*emboss.accelerator: Alt<Key>m
+
+! view menu
+ctrl*bar.view.labelString: View
+ctrl*bar.view.mnemonic: V
+ctrl*bar*prev.labelString: Previous file
+ctrl*bar*prev.acceleratorText: backspace
+!ctrl*bar*prev.accelerator: <Key>Backspace
+ctrl*bar*next.labelString: Next file
+ctrl*bar*next.acceleratorText: space
+!ctrl*bar*next.accelerator: <Key>space
+ctrl*bar*prevpage.labelString: Previous page
+ctrl*bar*prevpage.acceleratorText: PageUp
+!ctrl*bar*prevpage.accelerator: <Key>osfPageUp
+ctrl*bar*nextpage.labelString: Next page
+ctrl*bar*nextpage.acceleratorText: PageDown
+!ctrl*bar*nextpage.accelerator: <Key>osfPageDown
+ctrl*bar*zoomin.labelString: Zoom in
+ctrl*bar*zoomin.acceleratorText: plus
+ctrl*bar*zoomin.accelerator: <Key>plus
+ctrl*bar*zoomout.labelString: Zoom out
+ctrl*bar*zoomout.acceleratorText: minus
+ctrl*bar*zoomout.accelerator: <Key>minus
+
+! options menu
+ctrl*bar.opt.labelString: Options
+ctrl*bar.opt.mnemonic: O
+ctrl*bar*pcd.labelString: PhotoCD resolution
+ctrl*bar*autozoom.labelString: Autozoom
+ctrl*bar*cfgsave.labelString: Save Options
+
+! options/photocd menu
+ctrl*bar*pcdM.1.labelString: 192 x 128
+ctrl*bar*pcdM.2.labelString: 384 x 256
+ctrl*bar*pcdM.3.labelString: 768 x 512
+ctrl*bar*pcdM.4.labelString: 1536 x 1024
+ctrl*bar*pcdM.5.labelString: 3072 x 2048
+
+! help menu
+ctrl*bar.help.labelString: Help
+ctrl*bar.help.mnemonic: H
+ctrl*bar*man.labelString: Manual page ...
+ctrl*bar*man.mnemonic: M
+ctrl*bar*man.acceleratorText: F1
+ctrl*bar*man.accelerator: <Key>F1
+ctrl*bar*about.labelString: About ...
+ctrl*bar*about.mnemonic: A
+
+ctrl.form.*.leftAttachment: ATTACH_FORM
+ctrl.form.*.rightAttachment: ATTACH_FORM
+ctrl.form.tool.topAttachment: ATTACH_WIDGET
+ctrl.form.tool.topWidget: bar
+ctrl.form.listSW.topAttachment: ATTACH_WIDGET
+ctrl.form.listSW.topWidget: tool
+ctrl.form.listSW.bottomAttachment: ATTACH_WIDGET
+ctrl.form.listSW.bottomWidget: status
+ctrl.form.listSW.width: 320
+ctrl.form.listSW.height: 240
+ctrl.form.status.bottomAttachment: ATTACH_FORM
+ctrl.form.status.alignment: ALIGNMENT_BEGINNING
+
+
+! ----------------------------------------------------------------------------
+! man page renderer
+
+Ida.man_popup.deleteResponse: DESTROY
+Ida.man_popup*view.width: 500
+Ida.man_popup*view.height: 600
+Ida.man_popup*view.scrollingPolicy: AUTOMATIC
+Ida.man_popup*view.scrollBarPlacement: BOTTOM_RIGHT
+
+Ida.man_popup.title: Manual page
+Ida.man_popup*okLabelString: close window
+Ida.man_popup*label.labelString: please wait ...
+
+Ida.man_popup*label.alignment: ALIGNMENT_BEGINNING
+Ida.man_popup*label.marginWidth: 5
+Ida.man_popup*label.marginHeight: 5
+Ida.man_popup*label.renderTable: bold,underline
+Ida.man_popup*label.renderTable.fontType: FONT_IS_FONTSET
+Ida.man_popup*label.renderTable.fontName: \
+ -*-fixed-medium-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-medium-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.man_popup*label.renderTable.bold.fontType: FONT_IS_FONTSET
+Ida.man_popup*label.renderTable.bold.fontName: \
+ -*-fixed-bold-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-bold-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.man_popup*label.renderTable.underline.underlineType: SINGLE_LINE
+
+
+! ----------------------------------------------------------------------------
+! hex viewer
+
+Ida.hex_popup.deleteResponse: DESTROY
+Ida.hex_popup*view.width: 600
+Ida.hex_popup*view.height: 600
+Ida.hex_popup*view.scrollingPolicy: AUTOMATIC
+Ida.hex_popup*view.scrollBarPlacement: BOTTOM_RIGHT
+
+Ida.hex_popup*label.alignment: ALIGNMENT_BEGINNING
+Ida.hex_popup*label.marginWidth: 5
+Ida.hex_popup*label.marginHeight: 5
+Ida.hex_popup*label.renderTable: bold,underline
+Ida.hex_popup*label.renderTable.fontType: FONT_IS_FONTSET
+Ida.hex_popup*label.renderTable.fontName: \
+ -*-fixed-medium-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-medium-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.hex_popup*label.renderTable.bold.fontType: FONT_IS_FONTSET
+Ida.hex_popup*label.renderTable.bold.fontName: \
+ -*-fixed-bold-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-bold-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.hex_popup*label.renderTable.underline.underlineType: SINGLE_LINE
+
+
+! ----------------------------------------------------------------------------
+! file browser
+
+browser.geometry: 600x450
+browser.form.?.leftAttachment: ATTACH_FORM
+browser.form.?.rightAttachment: ATTACH_FORM
+browser.form.scroll.topAttachment: ATTACH_WIDGET
+browser.form.scroll.topWidget: cbar
+browser.form.scroll.bottomAttachment: ATTACH_WIDGET
+browser.form.scroll.bottomWidget: status
+browser.form.status.bottomAttachment: ATTACH_FORM
+browser.form.status.alignment: ALIGNMENT_BEGINNING
+
+browser.form.scroll.scrollingPolicy: AUTOMATIC
+browser.form.scroll.scrollBarPlacement: BOTTOM_RIGHT
+browser.form.scroll.XmScrollBar.highlightThickness: 1
+
+browser.filter_popup.title: Filter
+browser.filter_popup*selectionLabelString: pattern?
+
+browser*comment_popup.title: JPEG Comment
+browser*comment_popup*selectionLabelString: Please enter/edit the comment here.
+
+
+! ----------------------------------------------------------------------------
+! file lists
+
+filelist.geometry: 300x400
+filelist.form.?.leftAttachment: ATTACH_FORM
+filelist.form.?.rightAttachment: ATTACH_FORM
+filelist.form.scroll.topAttachment: ATTACH_WIDGET
+filelist.form.scroll.topWidget: cbar
+filelist.form.scroll.bottomAttachment: ATTACH_WIDGET
+filelist.form.scroll.bottomWidget: status
+filelist.form.status.bottomAttachment: ATTACH_FORM
+filelist.form.status.alignment: ALIGNMENT_BEGINNING
+
+filelist.form.scroll.scrollingPolicy: AUTOMATIC
+filelist.form.scroll.scrollBarPlacement: BOTTOM_RIGHT
+filelist.form.scroll.XmScrollBar.highlightThickness: 1
+
+
+! ----------------------------------------------------------------------------
+! browser + file list common stuff
+
+*container.outlineButtonPolicy: OUTLINE_BUTTON_ABSENT
+*container.spatialStyle: CELLS
+*container.spatialResizeModel: GROW_MINOR
+*container.spatialSnapModel: CENTER
+*container.detailTabList: 3cm
+!*container.spatialIncludeModel: APPEND
+!*container.layoutDirection: LEFT_TO_RIGHT_TOP_TO_BOTTOM
+!*container.background: gray85
+*container.primaryOwnership: XmOWN_NEVER
+*container.XmIconGadget.highlightColor: darkred
+*container.XmIconGadget.shadowThickness: 1
+
+! file menu
+*cbar.file.labelString: File
+*cbar.file.mnemonic: F
+*cbar*new.labelString: New list
+*cbar*new.mnemonic: N
+*cbar*load.labelString: Load list ...
+*cbar*load.mnemonic: L
+*cbar*save.labelString: Save list
+*cbar*save.mnemonic: S
+*cbar*saveas.labelString: Save list as ...
+*cbar*saveas.mnemonic: a
+*cbar*close.labelString: Close window
+*cbar*close.acceleratorText: Q
+*cbar*close.accelerator: <Key>Q
+
+! edit menu
+*cbar.edit.labelString: Edit
+*cbar.edit.mnemonic: E
+*cbar*copy.labelString: Copy
+*cbar*copy.acceleratorText: Ctrl+C
+*cbar*copy.accelerator: Ctrl<Key>C
+*cbar*paste.labelString: Paste
+*cbar*paste.acceleratorText: Ctrl+V
+*cbar*paste.accelerator: Ctrl<Key>V
+*cbar*del.labelString: Delete
+
+! view menu
+*cbar.view.labelString: View
+*cbar.view.mnemonic: V
+*cbar*spatial.labelString: Large Icons
+*cbar*details.labelString: Details
+*cbar*filter.labelString: Filter ...
+*cbar*filter.acceleratorText: F
+*cbar*filter.accelerator: <Key>F
+*cbar*freset.labelString: Reset filter
+
+! jpeg ops menu
+*cbar.ops.labelString: JPEG
+*cbar.ops.mnemonic: J
+*cbar*rotexif.labelString: Auto Rotate (by EXIT Tag)
+*cbar*rotexif.mnemonic: A
+*cbar*rotcw.labelString: Rotate Clockwise
+*cbar*rotccw.labelString: Rotate Counterclockwise
+*cbar*comment.labelString: Edit Comment ...
+*cbar*comment.mnemonic: E
+*cbar*comment.acceleratorText: E
+*cbar*comment.accelerator: <Key>E
+
+! bookmarks
+*cbar.dirs.labelString: Bookmarks
+*cbar.dirs.mnemonic: B
+
+! lists
+*cbar.lists.labelString: Lists
+*cbar.lists.mnemonic: L
diff --git a/README b/README
new file mode 100644
index 0000000..c6b0016
--- /dev/null
+++ b/README
@@ -0,0 +1,47 @@
+
+what is it?
+===========
+
+Ida is a small and fast image viewer, motif-based. For people who
+don't want the KDE/GNOME overhead. Some basic editing functions are
+available too.
+
+
+build
+=====
+
+Check the INSTALL file for detailed build instructions.
+
+ida uses Motif 2.x features (utm, render tables). This means you need
+openmotif, lesstif does *not* cut it.
+
+It also uses the usual graphics libraries (libtiff, libpng, libjpeg,
+...), you should have them installed to get support for these image
+formats.
+
+
+usage
+=====
+
+There is a manual page, check it out.
+
+
+what "ida" stands for?
+======================
+
+It is just a name. The utility used to be named "iv" for "Image
+Viewer", but that gave lots of name clashes. Its very likely that
+people name such a tool "iv", it is also used as shortcut for
+InterViews (remember Sun's Open Windows?). So i decided to rename it.
+
+I looked for a short name starting with 'i' in a list for children
+first names. I like "ida", so I picked this one. It is a old, german
+name.
+
+
+Have fun,
+
+ Gerd
+
+--
+Gerd Knorr <kraxel@bytesex.org>
diff --git a/RegEdit.c b/RegEdit.c
new file mode 100644
index 0000000..fa01b88
--- /dev/null
+++ b/RegEdit.c
@@ -0,0 +1,1795 @@
+/* $XConsortium: RegEdit.c /main/5 1995/07/15 20:44:04 drk $ */
+/*
+ * @OPENGROUP_COPYRIGHT@
+ * COPYRIGHT NOTICE
+ * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc.
+ * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group
+ * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for
+ * the full copyright text.
+ *
+ * This software is subject to an open license. It may only be
+ * used on, with or for operating systems which are themselves open
+ * source systems. You must contact The Open Group for a license
+ * allowing distribution and sublicensing of this software on, with,
+ * or for operating systems which are not Open Source programs.
+ *
+ * See http://www.opengroup.org/openmotif/license for full
+ * details of the license agreement. Any use, reproduction, or
+ * distribution of the program constitutes recipient's acceptance of
+ * this agreement.
+ *
+ * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+ * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+ * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+ * OR FITNESS FOR A PARTICULAR PURPOSE
+ *
+ * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+ * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+ * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ */
+/*
+ * HISTORY
+ */
+#include <stdio.h>
+#include <Xm/XmP.h>
+#include <X11/ShellP.h>
+#include "RegEditI.h"
+
+
+/* static forward. move from global in the original Editres code */
+static void _XEditResCheckMessages();
+static void _XEditResPutString8();
+static void _XEditResPut8();
+static void _XEditResPut16();
+static void _XEditResPut32();
+static void _XEditResPutWidgetInfo();
+static void _XEditResResetStream();
+static Boolean _XEditResGet8();
+static Boolean _XEditResGet16();
+static Boolean _XEditResGetSigned16();
+static Boolean _XEditResGet32();
+static Boolean _XEditResGetString8();
+static Boolean _XEditResGetWidgetInfo();
+
+/* the only entry point here */
+void
+XmdRegisterEditres(Widget toplevel)
+{
+ XtAddEventHandler(toplevel, (EventMask) 0, TRUE,
+ _XEditResCheckMessages, NULL);
+}
+
+
+/************************************************************
+ *
+ * Dump the content of the R5 lib/Xmu/EditresCom.c module.
+ * just move global as static.
+ *
+ ************************************************************/
+
+#define _XEditResPutBool _XEditResPut8
+#define _XEditResPutResourceType _XEditResPut8
+
+/************************************************************
+ *
+ * Local structure definitions.
+ *
+ ************************************************************/
+
+typedef enum { BlockNone, BlockSetValues, BlockAll } EditresBlock;
+
+typedef struct _SetValuesEvent {
+ EditresCommand type; /* first field must be type. */
+ WidgetInfo * widgets;
+ unsigned short num_entries; /* number of set values requests. */
+ char * name;
+ char * res_type;
+ XtPointer value;
+ unsigned short value_len;
+} SetValuesEvent;
+
+typedef struct _SVErrorInfo {
+ SetValuesEvent * event;
+ ProtocolStream * stream;
+ unsigned short * count;
+ WidgetInfo * entry;
+} SVErrorInfo;
+
+typedef struct _FindChildEvent {
+ EditresCommand type; /* first field must be type. */
+ WidgetInfo * widgets;
+ short x, y;
+} FindChildEvent;
+
+typedef struct _GenericGetEvent {
+ EditresCommand type; /* first field must be type. */
+ WidgetInfo * widgets;
+ unsigned short num_entries; /* number of set values requests. */
+} GenericGetEvent, GetResEvent, GetGeomEvent;
+
+/*
+ * Things that are common to all events.
+ */
+
+typedef struct _AnyEvent {
+ EditresCommand type; /* first field must be type. */
+ WidgetInfo * widgets;
+} AnyEvent;
+
+/*
+ * The event union.
+ */
+
+typedef union _EditresEvent {
+ AnyEvent any_event;
+ SetValuesEvent set_values_event;
+ GetResEvent get_resources_event;
+ GetGeomEvent get_geometry_event;
+ FindChildEvent find_child_event;
+} EditresEvent;
+
+typedef struct _Globals {
+ EditresBlock block;
+ SVErrorInfo error_info;
+ ProtocolStream stream;
+ ProtocolStream * command_stream; /* command stream. */
+} Globals;
+
+#define CURRENT_PROTOCOL_VERSION 4L
+
+#define streq(a,b) (strcmp( (a), (b) ) == 0)
+
+static Atom res_editor_command, res_editor_protocol, client_value;
+
+static Globals globals;
+
+static void SendFailure(), SendCommand(), InsertWidget(), ExecuteCommand();
+static void FreeEvent(), ExecuteSetValues(), ExecuteGetGeometry();
+static void ExecuteGetResources();
+
+static void GetCommand();
+static void LoadResources();
+static Boolean IsChild();
+static void DumpChildren();
+static char *DumpWidgets(), *DoSetValues(), *DoFindChild();
+static char *DoGetGeometry(), *DoGetResources();
+
+/************************************************************
+ *
+ * Resource Editor Communication Code
+ *
+ ************************************************************/
+
+/* Function Name: _XEditResCheckMessages
+ * Description: This callback routine is set on all shell widgets,
+ * and checks to see if a client message event
+ * has come from the resource editor.
+ * Arguments: w - the shell widget.
+ * data - *** UNUSED ***
+ * event - The X Event that triggered this handler.
+ * cont - *** UNUSED ***.
+ * Returns: none.
+ */
+
+/* ARGSUSED */
+static void
+_XEditResCheckMessages(w, data, event, cont)
+Widget w;
+XtPointer data;
+XEvent *event;
+Boolean *cont;
+{
+ Time time;
+ ResIdent ident;
+ static Boolean first_time = FALSE;
+ static Atom res_editor, res_comm;
+ Display * dpy;
+
+ if (event->type == ClientMessage) {
+ XClientMessageEvent * c_event = (XClientMessageEvent *) event;
+ dpy = XtDisplay(w);
+
+ if (!first_time) {
+ first_time = TRUE;
+ res_editor = XInternAtom(dpy, EDITRES_NAME, False);
+ res_editor_command = XInternAtom(dpy, EDITRES_COMMAND_ATOM, False);
+ res_editor_protocol = XInternAtom(dpy, EDITRES_PROTOCOL_ATOM,
+ False);
+
+ /* Used in later procedures. */
+ client_value = XInternAtom(dpy, EDITRES_CLIENT_VALUE, False);
+ LoadResources(w);
+ }
+
+ if ((c_event->message_type != res_editor) ||
+ (c_event->format != EDITRES_SEND_EVENT_FORMAT))
+ return;
+
+ time = c_event->data.l[0];
+ res_comm = c_event->data.l[1];
+ ident = (ResIdent) c_event->data.l[2];
+ if (c_event->data.l[3] != CURRENT_PROTOCOL_VERSION) {
+ _XEditResResetStream(&globals.stream);
+ _XEditResPut8(&globals.stream, CURRENT_PROTOCOL_VERSION);
+ SendCommand(w, res_comm, ident, ProtocolMismatch, &globals.stream);
+ return;
+ }
+
+ XtGetSelectionValue(w, res_comm, res_editor_command,
+ GetCommand, (XtPointer) (long) ident, time);
+ }
+}
+
+/* Function Name: BuildEvent
+ * Description: Takes the info out the protocol stream an constructs
+ * the proper event structure.
+ * Arguments: w - widget to own selection, in case of error.
+ * sel - selection to send error message beck in.
+ * data - the data for the request.
+ * ident - the id number we are looking for.
+ * length - length of request.
+ * Returns: the event, or NULL.
+ */
+
+#define ERROR_MESSAGE ("Client: Improperly formatted protocol request")
+
+static EditresEvent *
+BuildEvent(w, sel, data, ident, length)
+Widget w;
+Atom sel;
+XtPointer data;
+ResIdent ident;
+unsigned long length;
+{
+ EditresEvent * event;
+ ProtocolStream alloc_stream, *stream;
+ unsigned char temp;
+ register unsigned int i;
+
+ stream = &alloc_stream; /* easier to think of it this way... */
+
+ stream->current = stream->top = (unsigned char *) data;
+ stream->size = HEADER_SIZE; /* size of header. */
+
+ /*
+ * Retrieve the Header.
+ */
+
+ if (length < HEADER_SIZE) {
+ SendFailure(w, sel, ident, Failure, ERROR_MESSAGE);
+ return(NULL);
+ }
+
+ (void) _XEditResGet8(stream, &temp);
+ if (temp != ident) /* Id's don't match, ignore request. */
+ return(NULL);
+
+ event = (EditresEvent *) XtCalloc(sizeof(EditresEvent), 1);
+
+ (void) _XEditResGet8(stream, &temp);
+ event->any_event.type = (EditresCommand) temp;
+ (void) _XEditResGet32(stream, &(stream->size));
+ stream->top = stream->current; /* reset stream to top of value.*/
+
+ /*
+ * Now retrieve the data segment.
+ */
+
+ switch(event->any_event.type) {
+ case SendWidgetTree:
+ break; /* no additional info */
+ case SetValues:
+ {
+ SetValuesEvent * sv_event = (SetValuesEvent *) event;
+
+ if ( !(_XEditResGetString8(stream, &(sv_event->name)) &&
+ _XEditResGetString8(stream, &(sv_event->res_type))))
+ {
+ goto done;
+ }
+
+ /*
+ * Since we need the value length, we have to pull the
+ * value out by hand.
+ */
+
+ if (!_XEditResGet16(stream, &(sv_event->value_len)))
+ goto done;
+
+ sv_event->value = XtMalloc(sizeof(char) *
+ (sv_event->value_len + 1));
+
+ for (i = 0; i < sv_event->value_len; i++) {
+ if (!_XEditResGet8(stream,
+ (unsigned char *) sv_event->value + i))
+ {
+ goto done;
+ }
+ }
+ ((char*)sv_event->value)[i] = '\0'; /* NULL terminate that sucker. */
+
+ if (!_XEditResGet16(stream, &(sv_event->num_entries)))
+ goto done;
+
+ sv_event->widgets = (WidgetInfo *)
+ XtCalloc(sizeof(WidgetInfo), sv_event->num_entries);
+
+ for (i = 0; i < sv_event->num_entries; i++) {
+ if (!_XEditResGetWidgetInfo(stream, sv_event->widgets + i))
+ goto done;
+ }
+ }
+ break;
+ case FindChild:
+ {
+ FindChildEvent * find_event = (FindChildEvent *) event;
+
+ find_event->widgets = (WidgetInfo *)
+ XtCalloc(sizeof(WidgetInfo), 1);
+
+ if (!(_XEditResGetWidgetInfo(stream, find_event->widgets) &&
+ _XEditResGetSigned16(stream, &(find_event->x)) &&
+ _XEditResGetSigned16(stream, &(find_event->y))))
+ {
+ goto done;
+ }
+
+ }
+ break;
+ case GetGeometry:
+ case GetResources:
+ {
+ GenericGetEvent * get_event = (GenericGetEvent *) event;
+
+ if (!_XEditResGet16(stream, &(get_event->num_entries)))
+ goto done;
+
+ get_event->widgets = (WidgetInfo *)
+ XtCalloc(sizeof(WidgetInfo), get_event->num_entries);
+ for (i = 0; i < get_event->num_entries; i++) {
+ if (!_XEditResGetWidgetInfo(stream, get_event->widgets + i))
+ goto done;
+ }
+ }
+ break;
+ default:
+ {
+ char buf[BUFSIZ];
+
+ sprintf(buf, "Unknown Protocol request %d.",event->any_event.type);
+ SendFailure(w, sel, ident, buf);
+ return(NULL);
+ }
+ }
+ return(event);
+
+ done:
+
+ SendFailure(w, sel, ident, ERROR_MESSAGE);
+ FreeEvent(event);
+ return(NULL);
+}
+
+/* Function Name: FreeEvent
+ * Description: Frees the event structure and any other pieces
+ * in it that need freeing.
+ * Arguments: event - the event to free.
+ * Returns: none.
+ */
+
+static void
+FreeEvent(event)
+EditresEvent * event;
+{
+ if (event->any_event.widgets != NULL) {
+ XtFree((char *)event->any_event.widgets->ids);
+ XtFree((char *)event->any_event.widgets);
+ }
+
+ if (event->any_event.type == SetValues) {
+ XtFree(event->set_values_event.name); /* XtFree does not free if */
+ XtFree(event->set_values_event.res_type); /* value is NULL. */
+ }
+
+ XtFree((char *)event);
+}
+
+/* Function Name: GetCommand
+ * Description: Gets the Command out of the selection asserted by the
+ * resource manager.
+ * Arguments: (See Xt XtConvertSelectionProc)
+ * data - contains the ident number for the command.
+ * Returns: none.
+ */
+
+/* ARGSUSED */
+static void
+GetCommand(w, data, selection, type, value, length, format)
+Widget w;
+XtPointer data, value;
+Atom *selection, *type;
+unsigned long *length;
+int * format;
+{
+ ResIdent ident = (ResIdent) (long) data;
+ EditresEvent * event;
+
+ if ( (*type != res_editor_protocol) || (*format != EDITRES_FORMAT) )
+ return;
+
+ if ((event = BuildEvent(w, *selection, value, ident, *length)) != NULL) {
+ ExecuteCommand(w, *selection, ident, event);
+ FreeEvent(event);
+ }
+}
+
+/* Function Name: ExecuteCommand
+ * Description: Executes a command string received from the
+ * resource editor.
+ * Arguments: w - a widget.
+ * command - the command to execute.
+ * value - the associated with the command.
+ * Returns: none.
+ *
+ * NOTES: munges str
+ */
+
+/* ARGSUSED */
+static void
+ExecuteCommand(w, sel, ident, event)
+Widget w;
+Atom sel;
+ResIdent ident;
+EditresEvent * event;
+{
+ char * (*func)();
+ char * str;
+
+ if (globals.block == BlockAll) {
+ SendFailure(w, sel, ident,
+ "This client has blocked all Editres commands.");
+ return;
+ }
+ else if ((globals.block == BlockSetValues) &&
+ (event->any_event.type == SetValues)) {
+ SendFailure(w, sel, ident,
+ "This client has blocked all SetValues requests.");
+ return;
+ }
+
+ switch(event->any_event.type) {
+ case SendWidgetTree:
+ func = DumpWidgets;
+ break;
+ case SetValues:
+ func = DoSetValues;
+ break;
+ case FindChild:
+ func = DoFindChild;
+ break;
+ case GetGeometry:
+ func = DoGetGeometry;
+ break;
+ case GetResources:
+ func = DoGetResources;
+ break;
+ default:
+ {
+ char buf[BUFSIZ];
+ sprintf(buf,"Unknown Protocol request %d.",event->any_event.type);
+ SendFailure(w, sel, ident, buf);
+ return;
+ }
+ }
+
+ _XEditResResetStream(&globals.stream);
+ if ((str = (*func)(w, event, &globals.stream)) == NULL)
+ SendCommand(w, sel, ident, PartialSuccess, &globals.stream);
+ else {
+ SendFailure(w, sel, ident, str);
+ XtFree(str);
+ }
+}
+
+/* Function Name: ConvertReturnCommand
+ * Description: Converts a selection.
+ * Arguments: w - the widget that owns the selection.
+ * selection - selection to convert.
+ * target - target type for this selection.
+ * type_ret - type of the selection.
+ * value_ret - selection value;
+ * length_ret - lenght of this selection.
+ * format_ret - the format the selection is in.
+ * Returns: True if conversion was sucessful.
+ */
+
+/* ARGSUSED */
+static Boolean
+ConvertReturnCommand(w, selection, target,
+ type_ret, value_ret, length_ret, format_ret)
+Widget w;
+Atom * selection, * target, * type_ret;
+XtPointer *value_ret;
+unsigned long * length_ret;
+int * format_ret;
+{
+ /*
+ * I assume the intrinsics give me the correct selection back.
+ */
+
+ if ((*target != client_value))
+ return(FALSE);
+
+ *type_ret = res_editor_protocol;
+ *value_ret = (XtPointer) globals.command_stream->real_top;
+ *length_ret = globals.command_stream->size + HEADER_SIZE;
+ *format_ret = EDITRES_FORMAT;
+
+ return(TRUE);
+}
+
+/* Function Name: CommandDone
+ * Description: done with the selection.
+ * Arguments: *** UNUSED ***
+ * Returns: none.
+ */
+
+/* ARGSUSED */
+static void
+CommandDone(widget, selection, target)
+Widget widget;
+Atom *selection;
+Atom *target;
+{
+ /* Keep the toolkit from automaticaly freeing the selection value */
+}
+
+/* Function Name: SendFailure
+ * Description: Sends a failure message.
+ * Arguments: w - the widget to own the selection.
+ * sel - the selection to assert.
+ * ident - the identifier.
+ * str - the error message.
+ * Returns: none.
+ */
+
+static void
+SendFailure(w, sel, ident, str)
+Widget w;
+Atom sel;
+ResIdent ident;
+char * str;
+{
+ _XEditResResetStream(&globals.stream);
+ _XEditResPutString8(&globals.stream, str);
+ SendCommand(w, sel, ident, Failure, &globals.stream);
+}
+
+/* Function Name: BuildReturnPacket
+ * Description: Builds a return packet, given the data to send.
+ * Arguments: ident - the identifier.
+ * command - the command code.
+ * stream - the protocol stream.
+ * Returns: packet - the packet to send.
+ */
+
+static XtPointer
+BuildReturnPacket(ident, command, stream)
+ResIdent ident;
+EditresCommand command;
+ProtocolStream * stream;
+{
+ long old_alloc, old_size;
+ unsigned char * old_current;
+
+ /*
+ * We have cleverly keep enough space at the top of the header
+ * for the return protocol stream, so all we have to do is
+ * fill in the space.
+ */
+
+ /*
+ * Fool the insert routines into putting the header in the right
+ * place while being damn sure not to realloc (that would be very bad.
+ */
+
+ old_current = stream->current;
+ old_alloc = stream->alloc;
+ old_size = stream->size;
+
+ stream->current = stream->real_top;
+ stream->alloc = stream->size + (2 * HEADER_SIZE);
+
+ _XEditResPut8(stream, ident);
+ _XEditResPut8(stream, (unsigned char) command);
+ _XEditResPut32(stream, old_size);
+
+ stream->alloc = old_alloc;
+ stream->current = old_current;
+ stream->size = old_size;
+
+ return((XtPointer) stream->real_top);
+}
+
+/* Function Name: SendCommand
+ * Description: Builds a return command line.
+ * Arguments: w - the widget to own the selection.
+ * sel - the selection to assert.
+ * ident - the identifier.
+ * command - the command code.
+ * stream - the protocol stream.
+ * Returns: none.
+ */
+
+static void
+SendCommand(w, sel, ident, command, stream)
+Widget w;
+Atom sel;
+ResIdent ident;
+EditresCommand command;
+ProtocolStream * stream;
+{
+ BuildReturnPacket(ident, command, stream);
+ globals.command_stream = stream;
+
+/*
+ * I REALLY want to own the selection. Since this was not triggered
+ * by a user action, and I am the only one using this atom it is safe to
+ * use CurrentTime.
+ */
+
+ XtOwnSelection(w, sel, CurrentTime,
+ ConvertReturnCommand, NULL, CommandDone);
+}
+
+/************************************************************
+ *
+ * Generic Utility Functions.
+ *
+ ************************************************************/
+
+/* Function Name: FindChildren
+ * Description: Retuns all children (popup, normal and otherwise)
+ * of this widget
+ * Arguments: parent - the parent widget.
+ * children - the list of children.
+ * normal - return normal children.
+ * popup - return popup children.
+ * Returns: the number of children.
+ */
+
+static int
+FindChildren(parent, children, normal, popup)
+Widget parent, **children;
+Boolean normal, popup;
+{
+ CompositeWidget cw = (CompositeWidget) parent;
+ int i, num_children, current = 0;
+
+ num_children = 0;
+
+ if (XtIsWidget(parent) && popup)
+ num_children += parent->core.num_popups;
+
+ if (XtIsComposite(parent) && normal)
+ num_children += cw->composite.num_children;
+
+ if (num_children == 0) {
+ *children = NULL;
+ return(0);
+ }
+
+ *children =(Widget*) XtMalloc((Cardinal) sizeof(Widget) * num_children);
+
+ if (XtIsComposite(parent) && normal)
+ for (i = 0; i < cw->composite.num_children; i++,current++)
+ (*children)[current] = cw->composite.children[i];
+
+ if (XtIsWidget(parent) && popup)
+ for ( i = 0; i < parent->core.num_popups; i++, current++)
+ (*children)[current] = parent->core.popup_list[i];
+
+ return(num_children);
+}
+
+/* Function Name: IsChild
+ * Description: check to see of child is a child of parent.
+ * Arguments: top - the top of the tree.
+ * parent - the parent widget.
+ * child - the child.
+ * Returns: none.
+ */
+
+static Boolean
+IsChild(top, parent, child)
+Widget top, parent, child;
+{
+ int i, num_children;
+ Widget * children;
+
+ if (parent == NULL)
+ return(top == child);
+
+ num_children = FindChildren(parent, &children, TRUE, TRUE);
+
+ for (i = 0; i < num_children; i++) {
+ if (children[i] == child) {
+ XtFree((char *)children);
+ return(TRUE);
+ }
+ }
+
+ XtFree((char *)children);
+ return(FALSE);
+}
+
+/* Function Name: VerifyWidget
+ * Description: Makes sure all the widgets still exist.
+ * Arguments: w - any widget in the tree.
+ * info - the info about the widget to verify.
+ * Returns: an error message or NULL.
+ */
+
+static char *
+VerifyWidget(w, info)
+Widget w;
+WidgetInfo *info;
+{
+ Widget top;
+
+ register int count;
+ register Widget parent;
+ register unsigned long * child;
+
+ for (top = w; XtParent(top) != NULL; top = XtParent(top)) {}
+
+ parent = NULL;
+ child = info->ids;
+ count = 0;
+
+ while (TRUE) {
+ if (!IsChild(top, parent, (Widget) *child))
+ return(XtNewString("This widget no longer exists in the client."));
+
+ if (++count == info->num_widgets)
+ break;
+
+ parent = (Widget) *child++;
+ }
+
+ info->real_widget = (Widget) *child;
+ return(NULL);
+}
+
+/************************************************************
+ *
+ * Code to Perform SetValues operations.
+ *
+ ************************************************************/
+
+
+/* Function Name: DoSetValues
+ * Description: performs the setvalues requested.
+ * Arguments: w - a widget in the tree.
+ * event - the event that caused this action.
+ * stream - the protocol stream to add.
+ * Returns: NULL.
+ */
+
+static char *
+DoSetValues(w, event, stream)
+Widget w;
+EditresEvent * event;
+ProtocolStream * stream;
+{
+ char * str;
+ register unsigned i;
+ unsigned short count = 0;
+ SetValuesEvent * sv_event = (SetValuesEvent *) event;
+
+ _XEditResPut16(stream, count); /* insert 0, will be overwritten later. */
+
+ for (i = 0 ; i < sv_event->num_entries; i++) {
+ if ((str = VerifyWidget(w, &(sv_event->widgets[i]))) != NULL) {
+ _XEditResPutWidgetInfo(stream, &(sv_event->widgets[i]));
+ _XEditResPutString8(stream, str);
+ XtFree(str);
+ count++;
+ }
+ else
+ ExecuteSetValues(sv_event->widgets[i].real_widget,
+ sv_event, sv_event->widgets + i, stream, &count);
+ }
+
+ /*
+ * Overwrite the first 2 bytes with the real count.
+ */
+
+ *(stream->top) = count >> XER_NBBY;
+ *(stream->top + 1) = count;
+ return(NULL);
+}
+
+/* Function Name: HandleToolkitErrors
+ * Description: Handles X Toolkit Errors.
+ * Arguments: name - name of the error.
+ * type - type of the error.
+ * class - class of the error.
+ * msg - the default message.
+ * params, num_params - the extra parameters for this message.
+ * Returns: none.
+ */
+
+/* ARGSUSED */
+static void
+HandleToolkitErrors(name, type, class, msg, params, num_params)
+String name, type, class, msg, *params;
+Cardinal * num_params;
+{
+ SVErrorInfo * info = &globals.error_info;
+ char buf[BUFSIZ];
+
+ if ( streq(name, "unknownType") )
+ sprintf(buf, "The `%s' resource is not used by this widget.",
+ info->event->name);
+ else if ( streq(name, "noColormap") )
+ sprintf(buf, msg, params[0]);
+ else if (streq(name, "conversionFailed") || streq(name, "conversionError"))
+ {
+ if (streq(info->event->value, XtRString))
+ sprintf(buf,
+ "Could not convert the string '%s' for the `%s' resource.",
+ (char*)info->event->value, info->event->name);
+ else
+ sprintf(buf, "Could not convert the `%s' resource.",
+ info->event->name);
+ }
+ else
+ sprintf(buf, "Name: %s, Type: %s, Class: %s, Msg: %s",
+ name, type, class, msg);
+
+ /*
+ * Insert this info into the protocol stream, and update the count.
+ */
+
+ (*(info->count))++;
+ _XEditResPutWidgetInfo(info->stream, info->entry);
+ _XEditResPutString8(info->stream, buf);
+}
+
+/* Function Name: ExecuteSetValues
+ * Description: Performs a setvalues for a given command.
+ * Arguments: w - the widget to perform the set_values on.
+ * sv_event - the set values event.
+ * sv_info - the set_value info.
+ * Returns: none.
+ */
+
+static void
+ExecuteSetValues(w, sv_event, entry, stream, count)
+Widget w;
+SetValuesEvent * sv_event;
+WidgetInfo * entry;
+ProtocolStream * stream;
+unsigned short * count;
+{
+ XtErrorMsgHandler old;
+
+ SVErrorInfo * info = &globals.error_info;
+ info->event = sv_event; /* No data can be passed to */
+ info->stream = stream; /* an error handler, so we */
+ info->count = count; /* have to use a global, YUCK... */
+ info->entry = entry;
+
+ old = XtAppSetWarningMsgHandler(XtWidgetToApplicationContext(w),
+ HandleToolkitErrors);
+
+ XtVaSetValues(w, XtVaTypedArg,
+ sv_event->name, sv_event->res_type,
+ sv_event->value, sv_event->value_len,
+ NULL);
+
+ (void)XtAppSetWarningMsgHandler(XtWidgetToApplicationContext(w), old);
+}
+
+
+/************************************************************
+ *
+ * Code for Creating and dumping widget tree.
+ *
+ ************************************************************/
+
+/* Function Name: DumpWidgets
+ * Description: Given a widget it builds a protocol packet
+ * containing the entire widget heirarchy.
+ * Arguments: w - a widget in the tree.
+ * event - the event that caused this action.
+ * stream - the protocol stream to add.
+ * Returns: NULL
+ */
+
+/* ARGSUSED */
+static char *
+DumpWidgets(w, event, stream)
+Widget w;
+EditresEvent * event; /* UNUSED */
+ProtocolStream * stream;
+{
+ unsigned short count = 0;
+
+ /* Find Tree's root. */
+ for ( ; XtParent(w) != NULL; w = XtParent(w)) {}
+
+ /*
+ * hold space for count, overwritten later.
+ */
+
+ _XEditResPut16(stream, (unsigned int) 0);
+
+ DumpChildren(w, stream, &count);
+
+ /*
+ * Overwrite the first 2 bytes with the real count.
+ */
+
+ *(stream->top) = count >> XER_NBBY;
+ *(stream->top + 1) = count;
+ return(NULL);
+}
+
+/* Function Name: DumpChildren
+ * Description: Adds a child's name to the list.
+ * Arguments: w - the widget to dump.
+ * stream - the stream to dump to.
+ * count - number of dumps we have performed.
+ * Returns: none.
+ */
+
+/* This is a trick/kludge. To make shared libraries happier (linking
+ * against Xmu but not linking against Xt, and apparently even work
+ * as we desire on SVR4, we need to avoid an explicit data reference
+ * to applicationShellWidgetClass. XtIsTopLevelShell is known
+ * (implementation dependent assumption!) to use a bit flag. So we
+ * go that far. Then, we test whether it is an applicationShellWidget
+ * class by looking for an explicit class name. Seems pretty safe.
+ */
+static Bool isApplicationShell(w)
+ Widget w;
+{
+ register WidgetClass c;
+
+ if (!XtIsTopLevelShell(w))
+ return False;
+ for (c = XtClass(w); c; c = c->core_class.superclass) {
+ if (!strcmp(c->core_class.class_name, "ApplicationShell"))
+ return True;
+ }
+ return False;
+}
+
+static void
+DumpChildren(w, stream, count)
+Widget w;
+ProtocolStream * stream;
+unsigned short *count;
+{
+ int i, num_children;
+ Widget *children;
+ unsigned long window;
+ char * class;
+
+ (*count)++;
+
+ InsertWidget(stream, w); /* Insert the widget into the stream. */
+
+ _XEditResPutString8(stream, XtName(w)); /* Insert name */
+
+ if (isApplicationShell(w))
+ class = ((ApplicationShellWidget) w)->application.class;
+ else
+ class = XtClass(w)->core_class.class_name;
+
+ _XEditResPutString8(stream, class); /* Insert class */
+
+ if (XtIsWidget(w))
+ if (XtIsRealized(w))
+ window = XtWindow(w);
+ else
+ window = EDITRES_IS_UNREALIZED;
+ else
+ window = EDITRES_IS_OBJECT;
+
+ _XEditResPut32(stream, window); /* Insert window id. */
+
+ /*
+ * Find children and recurse.
+ */
+
+ num_children = FindChildren(w, &children, TRUE, TRUE);
+ for (i = 0; i < num_children; i++)
+ DumpChildren(children[i], stream, count);
+
+ XtFree((char *)children);
+}
+
+/************************************************************
+ *
+ * Code for getting the geometry of widgets.
+ *
+ ************************************************************/
+
+/* Function Name: DoGetGeometry
+ * Description: retrieves the Geometry of each specified widget.
+ * Arguments: w - a widget in the tree.
+ * event - the event that caused this action.
+ * stream - the protocol stream to add.
+ * Returns: NULL
+ */
+
+static char *
+DoGetGeometry(w, event, stream)
+Widget w;
+EditresEvent * event;
+ProtocolStream * stream;
+{
+ unsigned i;
+ char * str;
+ GetGeomEvent * geom_event = (GetGeomEvent *) event;
+
+ _XEditResPut16(stream, geom_event->num_entries);
+
+ for (i = 0 ; i < geom_event->num_entries; i++) {
+
+ /*
+ * Send out the widget id.
+ */
+
+ _XEditResPutWidgetInfo(stream, &(geom_event->widgets[i]));
+ if ((str = VerifyWidget(w, &(geom_event->widgets[i]))) != NULL) {
+ _XEditResPutBool(stream, True); /* an error occured. */
+ _XEditResPutString8(stream, str); /* set message. */
+ XtFree(str);
+ }
+ else
+ ExecuteGetGeometry(geom_event->widgets[i].real_widget, stream);
+ }
+ return(NULL);
+}
+
+/* Function Name: ExecuteGetGeometry
+ * Description: Gets the geometry for each widget specified.
+ * Arguments: w - the widget to get geom on.
+ * stream - stream to append to.
+ * Returns: True if no error occured.
+ */
+
+static void
+ExecuteGetGeometry(w, stream)
+Widget w;
+ProtocolStream * stream;
+{
+ int i;
+ Boolean mapped_when_man;
+ Dimension width, height, border_width;
+ Arg args[8];
+ Cardinal num_args = 0;
+ Position x, y;
+
+ if ( !XtIsRectObj(w) || (XtIsWidget(w) && !XtIsRealized(w)) ) {
+ _XEditResPutBool(stream, False); /* no error. */
+ _XEditResPutBool(stream, False); /* not visable. */
+ for (i = 0; i < 5; i++) /* fill in extra space with 0's. */
+ _XEditResPut16(stream, 0);
+ return;
+ }
+
+ XtSetArg(args[num_args], XtNwidth, &width); num_args++;
+ XtSetArg(args[num_args], XtNheight, &height); num_args++;
+ XtSetArg(args[num_args], XtNborderWidth, &border_width); num_args++;
+ XtSetArg(args[num_args], XtNmappedWhenManaged, &mapped_when_man);
+ num_args++;
+ XtGetValues(w, args, num_args);
+
+ if (!(XtIsManaged(w) && mapped_when_man) && XtIsWidget(w)) {
+ XWindowAttributes attrs;
+
+ /*
+ * The toolkit does not maintain mapping state, we have
+ * to go to the server.
+ */
+
+ if (XGetWindowAttributes(XtDisplay(w), XtWindow(w), &attrs) != 0) {
+ if (attrs.map_state != IsViewable) {
+ _XEditResPutBool(stream, False); /* no error. */
+ _XEditResPutBool(stream, False); /* not visable. */
+ for (i = 0; i < 5; i++) /* fill in extra space with 0's. */
+ _XEditResPut16(stream, 0);
+ return;
+ }
+ }
+ else {
+ _XEditResPut8(stream, True); /* Error occured. */
+ _XEditResPutString8(stream, "XGetWindowAttributes failed.");
+ return;
+ }
+ }
+
+ XtTranslateCoords(w, -((int) border_width), -((int) border_width), &x, &y);
+
+ _XEditResPutBool(stream, False); /* no error. */
+ _XEditResPutBool(stream, True); /* Visable. */
+ _XEditResPut16(stream, x);
+ _XEditResPut16(stream, y);
+ _XEditResPut16(stream, width);
+ _XEditResPut16(stream, height);
+ _XEditResPut16(stream, border_width);
+}
+
+/************************************************************
+ *
+ * Code for executing FindChild.
+ *
+ ************************************************************/
+
+/* Function Name: PositionInChild
+ * Description: returns true if this location is in the child.
+ * Arguments: child - the child widget to check.
+ * x, y - location of point to check in the parent's
+ * coord space.
+ * Returns: TRUE if the position is in this child.
+ */
+
+static Boolean
+PositionInChild(child, x, y)
+Widget child;
+int x, y;
+{
+ Arg args[6];
+ Cardinal num;
+ Dimension width, height, border_width;
+ Position child_x, child_y;
+ Boolean mapped_when_managed;
+
+ if (!XtIsRectObj(child)) /* we must at least be a rect obj. */
+ return(FALSE);
+
+ num = 0;
+ XtSetArg(args[num], XtNmappedWhenManaged, &mapped_when_managed); num++;
+ XtSetArg(args[num], XtNwidth, &width); num++;
+ XtSetArg(args[num], XtNheight, &height); num++;
+ XtSetArg(args[num], XtNx, &child_x); num++;
+ XtSetArg(args[num], XtNy, &child_y); num++;
+ XtSetArg(args[num], XtNborderWidth, &border_width); num++;
+ XtGetValues(child, args, num);
+
+ /*
+ * The only way we will know of the widget is mapped is to see if
+ * mapped when managed is True and this is a managed child. Otherwise
+ * we will have to ask the server if this window is mapped.
+ */
+
+ if (XtIsWidget(child) && !(mapped_when_managed && XtIsManaged(child)) ) {
+ XWindowAttributes attrs;
+
+ if (XGetWindowAttributes(XtDisplay(child),
+ XtWindow(child), &attrs) != 0) {
+ /* oops */
+ }
+ else if (attrs.map_state != IsViewable)
+ return(FALSE);
+ }
+
+ return (x >= child_x) &&
+ (x <= (child_x + (Position)width + 2 * (Position)border_width)) &&
+ (y >= child_y) &&
+ (y <= (child_y + (Position)height + 2 * (Position)border_width));
+}
+
+/* Function Name: _FindChild
+ * Description: Finds the child that actually contatians the point shown.
+ * Arguments: parent - a widget that is known to contain the point
+ * specified.
+ * x, y - The point in coordinates relative to the
+ * widget specified.
+ * Returns: none.
+ */
+
+static Widget
+_FindChild(parent, x, y)
+Widget parent;
+int x, y;
+{
+ Widget * children;
+ int i = FindChildren(parent, &children, TRUE, FALSE);
+
+ while (i > 0) {
+ i--;
+
+ if (PositionInChild(children[i], x, y)) {
+ Widget child = children[i];
+
+ XtFree((char *)children);
+ return(_FindChild(child, x - child->core.x, y - child->core.y));
+ }
+ }
+
+ XtFree((char *)children);
+ return(parent);
+}
+
+/* Function Name: DoFindChild
+ * Description: finds the child that contains the location specified.
+ * Arguments: w - a widget in the tree.
+ * event - the event that caused this action.
+ * stream - the protocol stream to add.
+ * Returns: an allocated error message if something went horribly
+ * wrong and no set values were performed, else NULL.
+ */
+
+static char *
+DoFindChild(w, event, stream)
+Widget w;
+EditresEvent * event;
+ProtocolStream * stream;
+{
+ char * str;
+ Widget parent, child;
+ Position parent_x, parent_y;
+ FindChildEvent * find_event = (FindChildEvent *) event;
+
+ if ((str = VerifyWidget(w, find_event->widgets)) != NULL)
+ return(str);
+
+ parent = find_event->widgets->real_widget;
+
+ XtTranslateCoords(parent, (Position) 0, (Position) 0,
+ &parent_x, &parent_y);
+
+ child = _FindChild(parent, find_event->x - (int) parent_x,
+ find_event->y - (int) parent_y);
+
+ InsertWidget(stream, child);
+ return(NULL);
+}
+
+/************************************************************
+ *
+ * Procedures for performing GetResources.
+ *
+ ************************************************************/
+
+/* Function Name: DoGetResources
+ * Description: Gets the Resources associated with the widgets passed.
+ * Arguments: w - a widget in the tree.
+ * event - the event that caused this action.
+ * stream - the protocol stream to add.
+ * Returns: NULL
+ */
+
+static char *
+DoGetResources(w, event, stream)
+Widget w;
+EditresEvent * event;
+ProtocolStream * stream;
+{
+ unsigned int i;
+ char * str;
+ GetResEvent * res_event = (GetResEvent *) event;
+
+ _XEditResPut16(stream, res_event->num_entries); /* number of replys */
+
+ for (i = 0 ; i < res_event->num_entries; i++) {
+ /*
+ * Send out the widget id.
+ */
+ _XEditResPutWidgetInfo(stream, &(res_event->widgets[i]));
+ if ((str = VerifyWidget(w, &(res_event->widgets[i]))) != NULL) {
+ _XEditResPutBool(stream, True); /* an error occured. */
+ _XEditResPutString8(stream, str); /* set message. */
+ XtFree(str);
+ }
+ else {
+ _XEditResPutBool(stream, False); /* no error occured. */
+ ExecuteGetResources(res_event->widgets[i].real_widget,
+ stream);
+ }
+ }
+ return(NULL);
+}
+
+/* Function Name: ExecuteGetResources.
+ * Description: Gets the resources for any individual widget.
+ * Arguments: w - the widget to get resources on.
+ * stream - the protocol stream.
+ * Returns: none.
+ */
+
+static void
+ExecuteGetResources(w, stream)
+Widget w;
+ProtocolStream * stream;
+{
+ XtResourceList norm_list, cons_list;
+ Cardinal num_norm, num_cons;
+ register int i;
+
+ /*
+ * Get Normal Resources.
+ */
+
+ XtGetResourceList(XtClass(w), &norm_list, &num_norm);
+
+ if (XtParent(w) != NULL)
+ XtGetConstraintResourceList(XtClass(XtParent(w)),&cons_list,&num_cons);
+ else
+ num_cons = 0;
+
+ _XEditResPut16(stream, num_norm + num_cons); /* how many resources. */
+
+ /*
+ * Insert all the normal resources.
+ */
+
+ for ( i = 0; i < (int) num_norm; i++) {
+ _XEditResPutResourceType(stream, NormalResource);
+ _XEditResPutString8(stream, norm_list[i].resource_name);
+ _XEditResPutString8(stream, norm_list[i].resource_class);
+ _XEditResPutString8(stream, norm_list[i].resource_type);
+ }
+ XtFree((char *) norm_list);
+
+ /*
+ * Insert all the constraint resources.
+ */
+
+ if (num_cons > 0) {
+ for ( i = 0; i < (int) num_cons; i++) {
+ _XEditResPutResourceType(stream, ConstraintResource);
+ _XEditResPutString8(stream, cons_list[i].resource_name);
+ _XEditResPutString8(stream, cons_list[i].resource_class);
+ _XEditResPutString8(stream, cons_list[i].resource_type);
+ }
+ XtFree((char *) cons_list);
+ }
+}
+
+/************************************************************
+ *
+ * Code for inserting values into the protocol stream.
+ *
+ ************************************************************/
+
+/* Function Name: InsertWidget
+ * Description: Inserts the full parent heirarchy of this
+ * widget into the protocol stream as a widget list.
+ * Arguments: stream - the protocol stream.
+ * w - the widget to insert.
+ * Returns: none
+ */
+
+static void
+InsertWidget(stream, w)
+ProtocolStream * stream;
+Widget w;
+{
+ Widget temp;
+ unsigned long * widget_list;
+ register int i, num_widgets;
+
+ for (temp = w, i = 0; temp != 0; temp = XtParent(temp), i++) {}
+
+ num_widgets = i;
+ widget_list = (unsigned long *)
+ XtMalloc(sizeof(unsigned long) * num_widgets);
+
+ /*
+ * Put the widgets into the list.
+ * make sure that they are inserted in the list from parent -> child.
+ */
+
+ for (i--, temp = w; temp != NULL; temp = XtParent(temp), i--)
+ widget_list[i] = (unsigned long) temp;
+
+ _XEditResPut16(stream, num_widgets); /* insert number of widgets. */
+ for (i = 0; i < num_widgets; i++) /* insert Widgets themselves. */
+ _XEditResPut32(stream, widget_list[i]);
+
+ XtFree((char *)widget_list);
+}
+
+/************************************************************
+ *
+ * All of the following routines are public.
+ *
+ ************************************************************/
+
+/* Function Name: _XEditResPutString8
+ * Description: Inserts a string into the protocol stream.
+ * Arguments: stream - stream to insert string into.
+ * str - string to insert.
+ * Returns: none.
+ */
+
+static void
+_XEditResPutString8(stream, str)
+ProtocolStream * stream;
+char * str;
+{
+ int i, len = strlen(str);
+
+ _XEditResPut16(stream, len);
+ for (i = 0 ; i < len ; i++, str++)
+ _XEditResPut8(stream, *str);
+}
+
+/* Function Name: _XEditResPut8
+ * Description: Inserts an 8 bit integer into the protocol stream.
+ * Arguments: stream - stream to insert string into.
+ * value - value to insert.
+ * Returns: none
+ */
+
+static void
+_XEditResPut8(stream, value)
+ProtocolStream * stream;
+unsigned int value;
+{
+ unsigned char temp;
+
+ if (stream->size >= stream->alloc) {
+ stream->alloc += 100;
+ stream->real_top = (unsigned char *) XtRealloc(
+ (char *)stream->real_top,
+ stream->alloc + HEADER_SIZE);
+ stream->top = stream->real_top + HEADER_SIZE;
+ stream->current = stream->top + stream->size;
+ }
+
+ temp = (unsigned char) (value & BYTE_MASK);
+ *((stream->current)++) = temp;
+ (stream->size)++;
+}
+
+/* Function Name: _XEditResPut16
+ * Description: Inserts a 16 bit integer into the protocol stream.
+ * Arguments: stream - stream to insert string into.
+ * value - value to insert.
+ * Returns: void
+ */
+
+static void
+_XEditResPut16(stream, value)
+ProtocolStream * stream;
+unsigned int value;
+{
+ _XEditResPut8(stream, (value >> XER_NBBY) & BYTE_MASK);
+ _XEditResPut8(stream, value & BYTE_MASK);
+}
+
+/* Function Name: _XEditResPut32
+ * Description: Inserts a 32 bit integer into the protocol stream.
+ * Arguments: stream - stream to insert string into.
+ * value - value to insert.
+ * Returns: void
+ */
+
+static void
+_XEditResPut32(stream, value)
+ProtocolStream * stream;
+unsigned long value;
+{
+ int i;
+
+ for (i = 3; i >= 0; i--)
+ _XEditResPut8(stream, (value >> (XER_NBBY*i)) & BYTE_MASK);
+}
+
+/* Function Name: _XEditResPutWidgetInfo
+ * Description: Inserts the widget info into the protocol stream.
+ * Arguments: stream - stream to insert widget info into.
+ * info - info to insert.
+ * Returns: none
+ */
+
+static void
+_XEditResPutWidgetInfo(stream, info)
+ProtocolStream * stream;
+WidgetInfo * info;
+{
+ unsigned int i;
+
+ _XEditResPut16(stream, info->num_widgets);
+ for (i = 0; i < info->num_widgets; i++)
+ _XEditResPut32(stream, info->ids[i]);
+}
+
+/************************************************************
+ *
+ * Code for retrieving values from the protocol stream.
+ *
+ ************************************************************/
+
+/* Function Name: _XEditResResetStream
+ * Description: resets the protocol stream
+ * Arguments: stream - the stream to reset.
+ * Returns: none.
+ */
+
+static void
+_XEditResResetStream(stream)
+ProtocolStream * stream;
+{
+ stream->current = stream->top;
+ stream->size = 0;
+ if (stream->real_top == NULL) {
+ stream->real_top = (unsigned char *) XtRealloc(
+ (char *)stream->real_top,
+ stream->alloc + HEADER_SIZE);
+ stream->top = stream->real_top + HEADER_SIZE;
+ stream->current = stream->top + stream->size;
+ }
+}
+
+/*
+ * NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
+ *
+ * The only modified field if the "current" field.
+ *
+ * The only fields that must be set correctly are the "current", "top"
+ * and "size" fields.
+ */
+
+/* Function Name: _XEditResGetg8
+ * Description: Retrieves an unsigned 8 bit value
+ * from the protocol stream.
+ * Arguments: stream.
+ * val - a pointer to value to return.
+ * Returns: TRUE if sucessful.
+ */
+
+static Boolean
+_XEditResGet8(stream, val)
+ProtocolStream * stream;
+unsigned char * val;
+{
+ if (stream->size < (stream->current - stream->top))
+ return(FALSE);
+
+ *val = *((stream->current)++);
+ return(TRUE);
+}
+
+/* Function Name: _XEditResGet16
+ * Description: Retrieves an unsigned 16 bit value
+ * from the protocol stream.
+ * Arguments: stream.
+ * val - a pointer to value to return.
+ * Returns: TRUE if sucessful.
+ */
+
+static Boolean
+_XEditResGet16(stream, val)
+ProtocolStream * stream;
+unsigned short * val;
+{
+ unsigned char temp1, temp2;
+
+ if ( !(_XEditResGet8(stream, &temp1) && _XEditResGet8(stream, &temp2)) )
+ return(FALSE);
+
+ *val = (((unsigned short) temp1 << XER_NBBY) + ((unsigned short) temp2));
+ return(TRUE);
+}
+
+/* Function Name: _XEditResGetSigned16
+ * Description: Retrieves an signed 16 bit value from the protocol stream.
+ * Arguments: stream.
+ * val - a pointer to value to return.
+ * Returns: TRUE if sucessful.
+ */
+
+static Boolean
+_XEditResGetSigned16(stream, val)
+ProtocolStream * stream;
+short * val;
+{
+ unsigned char temp1, temp2;
+
+ if ( !(_XEditResGet8(stream, &temp1) && _XEditResGet8(stream, &temp2)) )
+ return(FALSE);
+
+ if (temp1 & (1 << (XER_NBBY - 1))) { /* If the sign bit is active. */
+ *val = -1; /* store all 1's */
+ *val &= (temp1 << XER_NBBY); /* Now and in the MSB */
+ *val &= temp2; /* and LSB */
+ }
+ else
+ *val = (((unsigned short) temp1 << XER_NBBY) + ((unsigned short) temp2));
+
+ return(TRUE);
+}
+
+/* Function Name: _XEditResGet32
+ * Description: Retrieves an unsigned 32 bit value
+ * from the protocol stream.
+ * Arguments: stream.
+ * val - a pointer to value to return.
+ * Returns: TRUE if sucessful.
+ */
+
+static Boolean
+_XEditResGet32(stream, val)
+ProtocolStream * stream;
+unsigned long * val;
+{
+ unsigned short temp1, temp2;
+
+ if ( !(_XEditResGet16(stream, &temp1) && _XEditResGet16(stream, &temp2)) )
+ return(FALSE);
+
+ *val = (((unsigned short) temp1 << (XER_NBBY * 2)) +
+ ((unsigned short) temp2));
+ return(TRUE);
+}
+
+/* Function Name: _XEditResGetString8
+ * Description: Retrieves an 8 bit string value from the protocol stream.
+ * Arguments: stream - the protocol stream
+ * str - the string to retrieve.
+ * Returns: True if retrieval was successful.
+ */
+
+static Boolean
+_XEditResGetString8(stream, str)
+ProtocolStream * stream;
+char ** str;
+{
+ unsigned short len;
+ register unsigned i;
+
+ if (!_XEditResGet16(stream, &len)) {
+ return(FALSE);
+ }
+
+ *str = XtMalloc(sizeof(char) * (len + 1));
+
+ for (i = 0; i < len; i++) {
+ if (!_XEditResGet8(stream, (unsigned char *) *str + i)) {
+ XtFree(*str);
+ *str = NULL;
+ return(FALSE);
+ }
+ }
+ (*str)[i] = '\0'; /* NULL terminate that sucker. */
+ return(TRUE);
+}
+
+/* Function Name: _XEditResGetWidgetInfo
+ * Description: Retrieves the list of widgets that follow and stores
+ * them in the widget info structure provided.
+ * Arguments: stream - the protocol stream
+ * info - the widget info struct to store into.
+ * Returns: True if retrieval was successful.
+ */
+
+static Boolean
+_XEditResGetWidgetInfo(stream, info)
+ProtocolStream * stream;
+WidgetInfo * info;
+{
+ unsigned int i;
+
+ if (!_XEditResGet16(stream, &(info->num_widgets)))
+ return(FALSE);
+
+ info->ids = (unsigned long *) XtMalloc(sizeof(long) * (info->num_widgets));
+
+ for (i = 0; i < info->num_widgets; i++) {
+ if (!_XEditResGet32(stream, info->ids + i)) {
+ XtFree((char *)info->ids);
+ info->ids = NULL;
+ return(FALSE);
+ }
+ }
+ return(TRUE);
+}
+
+/************************************************************
+ *
+ * Code for Loading the EditresBlock resource.
+ *
+ ************************************************************/
+
+/* Function Name: CvStringToBlock
+ * Description: Converts a string to an editres block value.
+ * Arguments: dpy - the display.
+ * args, num_args - **UNUSED **
+ * from_val, to_val - value to convert, and where to put result
+ * converter_data - ** UNUSED **
+ * Returns: TRUE if conversion was sucessful.
+ */
+
+/* ARGSUSED */
+static Boolean
+CvtStringToBlock(dpy, args, num_args, from_val, to_val, converter_data)
+Display * dpy;
+XrmValue * args;
+Cardinal * num_args;
+XrmValue * from_val, * to_val;
+XtPointer * converter_data;
+{
+ char ptr[BUFSIZ];
+ static EditresBlock block;
+
+/* XmuCopyISOLatin1Lowered(ptr, from_val->addr);*/
+
+
+ if (streq(ptr, "none"))
+ block = BlockNone;
+ else if (streq(ptr, "setvalues"))
+ block = BlockSetValues;
+ else if (streq(ptr, "all"))
+ block = BlockAll;
+ else {
+ Cardinal num_params = 1;
+ String params[1];
+
+ params[0] = from_val->addr;
+ XtAppWarningMsg(XtDisplayToApplicationContext(dpy),
+ "CvtStringToBlock", "unknownValue", "EditresError",
+ "Could not convert string \"%s\" to EditresBlock.",
+ params, &num_params);
+ return(FALSE);
+ }
+
+ if (to_val->addr != NULL) {
+ if (to_val->size < sizeof(EditresBlock)) {
+ to_val->size = sizeof(EditresBlock);
+ return(FALSE);
+ }
+ *(EditresBlock *)(to_val->addr) = block;
+ }
+ else
+ to_val->addr = (XtPointer) block;
+
+ to_val->size = sizeof(EditresBlock);
+ return(TRUE);
+}
+
+#define XtREditresBlock ("EditresBlock")
+
+/* Function Name: LoadResources
+ * Description: Loads a global resource the determines of this
+ * application should allow Editres requests.
+ * Arguments: w - any widget in the tree.
+ * Returns: none.
+ */
+
+static void
+LoadResources(w)
+Widget w;
+{
+ static XtResource resources[] = {
+ {"editresBlock", "EditresBlock", XtREditresBlock, sizeof(EditresBlock),
+ XtOffsetOf(Globals, block), XtRImmediate, (XtPointer) BlockNone}
+ };
+
+ for (; XtParent(w) != NULL; w = XtParent(w)) {}
+
+ XtAppSetTypeConverter(XtWidgetToApplicationContext(w),
+ XtRString, XtREditresBlock, CvtStringToBlock,
+ NULL, (Cardinal) 0, XtCacheAll, NULL);
+
+ XtGetApplicationResources( w, (caddr_t) &globals, resources,
+ XtNumber(resources), NULL, (Cardinal) 0);
+}
+
+
diff --git a/RegEdit.h b/RegEdit.h
new file mode 100644
index 0000000..5274151
--- /dev/null
+++ b/RegEdit.h
@@ -0,0 +1,65 @@
+/* $XConsortium: RegEdit.h /main/5 1995/07/15 20:44:09 drk $ */
+/*
+ * @OPENGROUP_COPYRIGHT@
+ * COPYRIGHT NOTICE
+ * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc.
+ * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group
+ * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for
+ * the full copyright text.
+ *
+ * This software is subject to an open license. It may only be
+ * used on, with or for operating systems which are themselves open
+ * source systems. You must contact The Open Group for a license
+ * allowing distribution and sublicensing of this software on, with,
+ * or for operating systems which are not Open Source programs.
+ *
+ * See http://www.opengroup.org/openmotif/license for full
+ * details of the license agreement. Any use, reproduction, or
+ * distribution of the program constitutes recipient's acceptance of
+ * this agreement.
+ *
+ * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+ * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+ * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+ * OR FITNESS FOR A PARTICULAR PURPOSE
+ *
+ * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+ * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+ * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ */
+/*
+ * HISTORY
+ */
+
+ /* Ensure that the file be included only once. */
+#ifndef _XmdRegEdit_h
+#define _XmdRegEdit_h
+
+#include <Xm/Xm.h>
+
+/* Allow for C++ compilation. */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+extern void XmdRegisterEditres(Widget toplevel);
+
+/* Allow for C++ compilation. */
+#ifdef __cplusplus
+} /* Close scope of 'extern "C"' declaration which encloses file. */
+#endif
+
+
+/* Ensure that the file be included only once. */
+#endif /* _XmdRegEdit_h */
+/* DON'T ADD ANYTHING AFTER THIS #endif */
+
diff --git a/RegEditI.h b/RegEditI.h
new file mode 100644
index 0000000..4608593
--- /dev/null
+++ b/RegEditI.h
@@ -0,0 +1,368 @@
+/*
+ * @OPENGROUP_COPYRIGHT@
+ * COPYRIGHT NOTICE
+ * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc.
+ * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group
+ * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for
+ * the full copyright text.
+ *
+ * This software is subject to an open license. It may only be
+ * used on, with or for operating systems which are themselves open
+ * source systems. You must contact The Open Group for a license
+ * allowing distribution and sublicensing of this software on, with,
+ * or for operating systems which are not Open Source programs.
+ *
+ * See http://www.opengroup.org/openmotif/license for full
+ * details of the license agreement. Any use, reproduction, or
+ * distribution of the program constitutes recipient's acceptance of
+ * this agreement.
+ *
+ * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
+ * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
+ * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
+ * OR FITNESS FOR A PARTICULAR PURPOSE
+ *
+ * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
+ * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+ * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ */
+/*
+ * HISTORY
+ */
+
+
+/* $XConsortium: RegEditI.h /main/4 1995/07/14 10:05:01 drk $ */
+
+/*
+
+Copyright (c) 1989 X Consortium
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of the X Consortium shall not be
+used in advertising or otherwise to promote the sale, use or other dealings
+in this Software without prior written authorization from the X Consortium.
+
+*/
+
+/*
+ * Author: Chris D. Peterson, MIT X Consortium
+ */
+
+/************************************************************
+
+ The Editres Protocol
+
+
+ The Client message sent to the application is:
+
+ ATOM = "ResEditor" --- RES_EDITOR_NAME
+
+ FORMAT = 32 --- RES_EDIT_SEND_EVENT_FORMAT
+
+ l[0] = timestamp
+ l[1] = command atom name
+ l[2] = ident of command.
+ l[3] = protocol version number to use.
+
+
+
+ The binary protocol has the following format:
+
+ Card8: 8-bit unsingned integer
+ Card16: 16-bit unsingned integer
+ Card32: 32-bit unsingned integer
+ Int16: 16-bit signed integer
+ Window: 32-bit value
+ Widget: 32-bit value
+ String8: ListOfCard8
+
+ [a][b][c] represent an exclusive list of choices.
+
+ All widgets are passed as a list of widgets, containing the
+ full instance heirarch of this widget. The hierarchy is ordered
+ from parent to child. Thus the first element of each list is
+ the root of the widget tree (this makes verifying that the widget
+ still exists, MUCH faster).
+
+ ListOfFoo comprises a list of things in the following format:
+
+ number: Card16
+ <number> things: ????
+
+ This is a synchronous protocol, every request MUST be followed by a
+ reply.
+
+ Request:
+
+ Serial Number: Card8
+ Op Code: Card8 - { SendWidgetTree = 0,
+ SetValues = 1,
+ GetResources = 2,
+ GetGeometry = 3,
+ FindChild = 4,
+ GetValues = 5 }
+ Length: Card32
+ Data:
+
+ Reply:
+
+ Serial Number: Card8
+ Type: Card8 - { Formatted = 0,
+ Unformatted = 1,
+ ProtocolMismatch = 2
+ }
+ Length: Card32
+
+
+ Byte Order:
+
+ All Fields are MSB -> LSB
+
+ Data:
+
+ Formatted:
+
+ The data contains the reply information for the request as
+ specified below if the reply type is "Formatted". The return
+ values for the other reply types are shown below.
+
+ Unformatted:
+
+ Message: String8
+
+ ProtocolMismatch:
+
+ RequestedVersion: Card8
+
+------------------------------------------------------------
+
+ SendWidgetTree:
+
+ --->
+
+ Number of Entries: Card16
+ Entry:
+ widget: ListOfWidgets
+ name: String8
+ class: String8
+ window: Card32
+ toolkit: String8
+
+ Send Widget Tree returns the toolkit type, and a fuly specified list
+ of widgets for each widget in the tree. This is enough information
+ to completely reconstruct the entire widget heirarchy.
+
+ The window return value contains the Xid of the window currently
+ used by this widget. If the widget is unrealized then 0 is returned,
+ and if widget is a non-windowed object a value of 2 is returned.
+
+ SetValues:
+
+ name: String8
+ type: String8
+ value: String8
+ Number of Entries: Card16
+ Entry:
+ widget: ListOfWidgets
+
+ --->
+
+ Number of Entries: Card16
+ Entry:
+ widget: ListOfWidgets
+ message: String8
+
+ SetValues will allow the same resource to be set on a number of
+ widgets. This function will return an error message if the SetValues
+ request caused an Xt error.
+
+ GetValues:
+
+ names: ListOfString8
+ widget: Widget
+
+ --->
+ novalues: ListOfCard16
+ values: ListOfString8
+
+ GetValues will allow a number of resource values to be read
+ on a particular widget. The request specifies the names of
+ the resources wanted and the widget id these resources are
+ from. The reply returns a list of indices from the requests
+ name list of resources for which a value can not be returned.
+ It also returns a list of returned values, in the order of the
+ requests names list, skipping those indices present in novalues.
+
+ GetResources:
+
+ Number of Entries: Card16
+ Entry
+ widget: ListOfWidgets:
+
+ ---->
+
+ Number of Entries: Card16
+ Entry
+ Widget: ListOfWidgets:
+ Error: Bool
+
+ [ Message: String 8 ]
+ [ Number of Resources: Card16
+ Resource:
+ Kind: {normal, constraint}
+ Name: String8
+ Class: String8
+ Type: String8 ]
+
+ GetResource retrieves the kind, name, class and type for every
+ widget passed to it. If an error occured with the resource fetch
+ Error will be set to True for the given widget and a message
+ is returned rather than the resource info.
+
+ GetGeometry:
+
+ Number of Entries: Card16
+ Entry
+ Widget: ListOfWidgets:
+
+ ---->
+
+ Number of Entries: Card16
+ Entry
+ Widget: ListOfWidgets:
+ Error: Bool
+
+ [ message: String 8 ]
+ [ mapped: Boolean
+ X: Int16
+ Y: Int16
+ Width: Card16
+ Height: Card16
+ BorderWidth: Card16 ]
+
+ GetGeometry retreives the mapping state, x, y, width, height
+ and border width for each widget specified. If an error occured
+ with the geometry fetch "Error" will be set to True for the given
+ widget and a message is returned rather than the geometry info.
+ X an Y corrospond to the root coordinates of the upper left corner
+ of the widget (outside the window border).
+
+ FindChild:
+
+ Widget: ListOfWidgets
+ X: Int16
+ Y: Int16
+
+ --->
+
+ Widget: ListOfWidgets
+
+ Find Child returns a descendent of the widget specified that
+ is at the root coordinates specified.
+
+ NOTE:
+
+ The returned widget is undefined if the point is contained in
+ two or more mapped widgets, or in two overlapping Rect objs.
+
+ GetValues:
+
+ names: ListOfString8
+ widget: Widget
+
+ --->
+
+ values: ListOfString8
+
+ GetValues will allow a number of resource values to be read
+ on a particular widget. Currently only InterViews 3.0.1 Styles
+ and their attributes are supported. In addition, the current
+ user interface only supports the return of 1 resource. The ability
+ to specify and return multiple resources is defined for future editres
+ interfaces where some or all of a widgets resource values are returned
+ and displayed at once.
+
+
+************************************************************/
+
+#include <X11/Intrinsic.h>
+#include <X11/Xfuncproto.h>
+
+#define XER_NBBY 8 /* number of bits in a byte */
+#define BYTE_MASK 255
+
+#define HEADER_SIZE 6
+
+#define EDITRES_IS_OBJECT 2
+#define EDITRES_IS_UNREALIZED 0
+
+/*
+ * Format for atoms.
+ */
+
+#define EDITRES_FORMAT 8
+#define EDITRES_SEND_EVENT_FORMAT 32
+
+/*
+ * Atoms
+ */
+
+#define EDITRES_NAME "Editres"
+#define EDITRES_COMMAND_ATOM "EditresCommand"
+#define EDITRES_COMM_ATOM "EditresComm"
+#define EDITRES_CLIENT_VALUE "EditresClientVal"
+#define EDITRES_PROTOCOL_ATOM "EditresProtocol"
+
+typedef enum { SendWidgetTree = 0,
+ SetValues = 1,
+ GetResources = 2,
+ GetGeometry = 3,
+ FindChild = 4,
+ GetValues = 5
+ } EditresCommand;
+
+typedef enum {NormalResource = 0, ConstraintResource = 1} ResourceType;
+
+/*
+ * The type of a resource identifier.
+ */
+
+typedef unsigned char ResIdent;
+
+typedef enum {PartialSuccess= 0, Failure= 1, ProtocolMismatch= 2} EditResError;
+
+typedef struct _WidgetInfo {
+ unsigned short num_widgets;
+ unsigned long * ids;
+ Widget real_widget;
+} WidgetInfo;
+
+typedef struct _ProtocolStream {
+ unsigned long size, alloc;
+ unsigned char *real_top, *top, *current;
+} ProtocolStream;
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..050b577
--- /dev/null
+++ b/TODO
@@ -0,0 +1,22 @@
+
+- some effect filters
+- improve cut+paste [does complete images only right now ... ]
+- make the file browser save the thumbnails somewhere (optionally),
+ so it can be much faster the second time ...
+- screenshots
+- put images on root window / some batch processing.
+
+
+List is ordered according to my personal priorities. More ideas
+welcome, but it doesn't automatically mean they will be added to the
+list. iv is a interactive, small + fast[1] utility and will only
+support commonly needed features (focus on photos), compareable to xv.
+
+If you need a full featured image processing tool with all bells and
+whistles, better use the gimp. To to stuff batched in scripts, use
+ImageMagick (convert utility / perl interface).
+
+
+[1] It has finished loading the image while the gimp still shows the
+ splash screen :-)
+
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..cd5ac03
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+2.0
diff --git a/backup/Ida-de b/backup/Ida-de
new file mode 100644
index 0000000..31d5b40
--- /dev/null
+++ b/backup/Ida-de
@@ -0,0 +1,173 @@
+
+! ----------------------------------------------------------------------------
+! some standard motif stuff [i18]
+
+*.cancelLabelString: Abbrechen
+*.XmFileSelectionBox.dirListLabelString: Verzeichnisse
+*.XmFileSelectionBox.fileListLabelString: Dateien
+*.XmFileSelectionBox.selectionLabelString: Auswahl
+
+
+! ----------------------------------------------------------------------------
+! strings [i18]
+
+Ida.aboutbox_popup.title: Über ida
+Ida*aboutbox_popup*messageString: \
+ ida - image viewer & editor \n\
+ \n\
+ (c) 2001,02 Gerd Knorr <kraxel@bytesex.org>
+
+Ida.noundobox_popup.title: Nicht verfügbar
+Ida*noundobox_popup*messageString: \
+ Es kann nur der letzte Arbeitsschritt\n\
+ rückgänig gemacht werden.
+
+Ida.errbox_popup.title: Fehler
+Ida.load_popup.title: Datei öffnen
+Ida.save_popup.title: Datei speichern
+Ida.save_popup*format.labelString: Dateiformat:
+Ida.print_popup.title: Datei drucken
+Ida.print_popup*selectionLabelString: Kommando
+Ida.print_popup*textString: lpr
+
+Ida*jpeg_popup.title: JPEG Einstellungen
+Ida*jpeg_popup*selectionLabelString: Bildqualität (0 ... 100)
+Ida*ps_popup.title: PostScript Einstellungen
+Ida*ps_popup*paper.labelString: Papier:
+Ida*ps_popup*ori.labelString: Format:
+Ida*ps_popup*oriM.portrait.labelString: Hochformat
+Ida*ps_popup*oriM.landscape.labelString: Querformat
+
+Ida.gamma_popup.title: Gammakorrektur
+Ida.gamma_popup*selectionLabelString: Gamma Wert
+Ida.bright_popup.title: Helligkeit ändern
+Ida.bright_popup*selectionLabelString: Helligkeit
+Ida.contrast_popup.title: Kontrast ändern
+Ida.contrast_popup*selectionLabelString: Kontrast
+Ida.rotate_popup.title: Bild drehen
+Ida.rotate_popup*selectionLabelString: Drehwinkel
+Ida.sharpe_popup.title: Bild schärfen
+Ida.sharpe_popup*selectionLabelString: Wert
+
+Ida.resize_popup.title: Bild skalieren
+Ida.resize_popup*lx.labelString: Breite (Pixel)
+Ida.resize_popup*ly.labelString: Höhe (Pixel)
+Ida.resize_popup*lr.labelString: Auflösung (dpi)
+Ida.resize_popup*lock.labelString: Seitenverhältnis beibehalten
+Ida.resize_popup*size.labelString: Größe ändern
+Ida.resize_popup*res.labelString: Auflösung ändern
+Ida.resize_popup*phys.labelString: Bildgröße
+
+Ida.color_popup.title: Edit colors
+Ida.color_popup*hist.labelString: Histograms
+Ida.color_popup*map.labelString: Maps
+Ida.color_popup*lock.labelString: Same values for all channels
+Ida.color_popup*vals.labelString: Show values for channel:
+Ida.color_popup*valsM.red.labelString: red
+Ida.color_popup*valsM.green.labelString: green
+Ida.color_popup*valsM.blue.labelString: blue
+Ida.color_popup*in.label.labelString: Input range:
+Ida.color_popup*out.label.labelString: Output range:
+Ida.color_popup*gamma.label.labelString: Gamma:
+Ida.color_popup*white.labelString: Weißpunkt setzen
+
+ctrl.title: ida controls
+ctrl.form.status.labelString: fixme
+
+ctrl*bar.file.labelString: Datei
+ctrl*bar.file.mnemonic: D
+ctrl*bar*load.labelString: Öffnen ...
+ctrl*bar*load.mnemonic: Odiaeresis
+ctrl*bar*save.labelString: Speichern ...
+ctrl*bar*save.mnemonic: S
+ctrl*bar*browse.labelString: Datei Browser ...
+ctrl*bar*scan.labelString: Scannen
+ctrl*bar*print.labelString: Drucken ...
+ctrl*bar*quit.labelString: Beenden
+ctrl*bar*quit.mnemonic: B
+
+ctrl*bar.edit.labelString: Bearbeiten
+ctrl*bar.edit.mnemonic: B
+ctrl*bar*undo.labelString: Rückgänig
+ctrl*bar*undo.mnemonic: udiaeresis
+ctrl*bar*copy.labelString: Kopieren
+ctrl*bar*paste.labelString: Einfügen
+ctrl*bar*flipv.labelString: Vertikal spiegeln
+ctrl*bar*flipv.mnemonic: V
+ctrl*bar*fliph.labelString: Horizontal spiegeln
+ctrl*bar*fliph.mnemonic: H
+ctrl*bar*rotcw.labelString: Drehen im Uhrzeigersinn
+ctrl*bar*rotccw.labelString: Drehen gegen den Uhrzeigersinn
+ctrl*bar*invert.labelString: Invertieren
+ctrl*bar*invert.mnemonic: I
+ctrl*bar*crop.labelString: Crop
+ctrl*bar*crop.mnemonic: C
+ctrl*bar*acrop.labelString: Autocrop
+ctrl*bar*acrop.mnemonic: A
+ctrl*bar*scale.labelString: Skalieren ...
+ctrl*bar*scale.mnemonic: S
+ctrl*bar*rotany.labelString: Drehen ...
+
+ctrl*bar.op.labelString: Filter
+ctrl*bar.op.mnemonic: F
+ctrl*bar*gamma.labelString: Gamma ...
+ctrl*bar*gamma.mnemonic: G
+ctrl*bar*bright.labelString: Helligheit ...
+ctrl*bar*contr.labelString: Kontrast ...
+ctrl*bar*color.labelString: Farben bearbeiten
+ctrl*bar*color.mnemonic: e
+ctrl*bar*gray.labelString: In Graustufen wandeln
+ctrl*bar*blur.labelString: Weich zeichnen
+ctrl*bar*sharpe.labelString: Schärfen ...
+ctrl*bar*edge.labelString: Kanten finden
+ctrl*bar*emboss.labelString: Schattenriß
+
+ctrl*bar.view.labelString: Ansicht
+ctrl*bar.view.mnemonic: A
+ctrl*bar*prev.labelString: Vorherige Datei
+ctrl*bar*next.labelString: Nächste Datei
+ctrl*bar*prevpage.labelString: Vorherige Seite
+ctrl*bar*nextpage.labelString: Nächste Seite
+ctrl*bar*zoomin.labelString: Vergrößern
+ctrl*bar*zoomout.labelString: Verkleinern
+
+ctrl*bar.opt.labelString: Einstellungen
+ctrl*bar.view.mnemonic: E
+ctrl*bar*pcd.labelString: PhotoCD Auflösung
+
+ctrl*bar.help.labelString: Hilfe
+ctrl*bar.help.mnemonic: H
+ctrl*bar*man.labelString: Manual page ...
+ctrl*bar*man.mnemonic: M
+ctrl*bar*about.labelString: Über ...
+ctrl*bar*about.mnemonic: Udiaeresis
+
+ctrl*tool.prev.toolTipString: vorherige Datei
+ctrl*tool.next.toolTipString: nächste Datei
+ctrl*tool.zoomin.toolTipString: Vergrößern
+ctrl*tool.zoomout.toolTipString: Verkleinern
+ctrl*tool.flipv.toolTipString: Vertikal spiegeln
+ctrl*tool.fliph.toolTipString: Horizontal spiegeln
+ctrl*tool.rotccw.toolTipString: Drehen gegen Uhrzeigersinn
+ctrl*tool.rotcw.toolTipString: Drehen im Uhrzeigersinn
+ctrl*tool.exit.toolTipString: Beenden
+
+browser*bar.file.labelString: Datei
+browser*bar.file.mnemonic: D
+browser*bar*close.labelString: Fenster schließen
+
+browser*bar.view.labelString: Ansicht
+browser*bar.view.mnemonic: A
+browser*bar*filter.labelString: Filtern ...
+browser*bar*freset.labelString: Filter aufheben
+
+browser.filter_popup.title: Filter
+browser.filter_popup*selectionLabelString: pattern?
+
+browser*menu.copy.labelString: Kopieren (ins Clipboard)
+browser*menu.rotcw.labelString: Drehen im Uhrzeigersinn
+browser*menu.rotccw.labelString: Drehen gegen den Uhrzeigersinn
+
+Ida.man_popup.title: Manual page
+Ida.man_popup*okLabelString: Fenster schließen
+Ida.man_popup*label.labelString: Einen Moment bitte ...
diff --git a/backup/Ida-default b/backup/Ida-default
new file mode 100644
index 0000000..3d50d99
--- /dev/null
+++ b/backup/Ida-default
@@ -0,0 +1,178 @@
+! ----------------------------------------------------------------------------
+! strings [i18]
+
+Ida.aboutbox_popup.title: About ida
+Ida*aboutbox_popup*messageString: \
+ ida - image viewer & editor \n\
+ \n\
+ (c) 2001,02 Gerd Knorr <kraxel@bytesex.org>
+
+Ida.noundobox_popup.title: No undo
+Ida*noundobox_popup*messageString: \
+ No undo info available, sorry. \n\
+ You can undo the last step only.
+
+Ida.errbox_popup.title: Errors
+Ida.load_popup.title: Load File
+Ida.save_popup.title: Save File
+Ida.save_popup*format.labelString: Image format:
+Ida.print_popup.title: Print File
+Ida.print_popup*selectionLabelString: Print command
+Ida.print_popup*textString: lpr
+
+Ida*jpeg_popup.title: JPEG Options
+Ida*jpeg_popup*selectionLabelString: Image quality (0 ... 100)
+Ida*ps_popup.title: PostScript Options
+Ida*ps_popup*paper.labelString: Paper size:
+Ida*ps_popup*ori.labelString: Orientation:
+
+Ida.gamma_popup.title: Gamma correction
+Ida.gamma_popup*selectionLabelString: Gamma value
+Ida.bright_popup.title: Adjust bright
+Ida.bright_popup*selectionLabelString: Bright
+Ida.contrast_popup.title: Adjust contrast
+Ida.contrast_popup*selectionLabelString: Contrast
+Ida.rotate_popup.title: Rotate image
+Ida.rotate_popup*selectionLabelString: angle
+Ida.sharpe_popup.title: Sharpe image
+Ida.sharpe_popup*selectionLabelString: value
+
+Ida.resize_popup.title: Scale image
+Ida.resize_popup*lx.labelString: Width (pixels)
+Ida.resize_popup*ly.labelString: Height (pixels)
+Ida.resize_popup*lr.labelString: Resolution (dpi)
+Ida.resize_popup*lock.labelString: Keep aspect ratio
+Ida.resize_popup*size.labelString: Change size
+Ida.resize_popup*res.labelString: Change resolution
+Ida.resize_popup*phys.labelString: Image size
+
+Ida.color_popup.title: Edit colors
+Ida.color_popup*hist.labelString: Histograms
+Ida.color_popup*map.labelString: Maps
+Ida.color_popup*lock.labelString: Same values for all channels
+Ida.color_popup*vals.labelString: Show values for channel:
+Ida.color_popup*valsM.red.labelString: red
+Ida.color_popup*valsM.green.labelString: green
+Ida.color_popup*valsM.blue.labelString: blue
+Ida.color_popup*in.label.labelString: Input range:
+Ida.color_popup*out.label.labelString: Output range:
+Ida.color_popup*gamma.label.labelString: Gamma:
+!Ida.color_popup*white.labelString: FIXME
+
+ctrl.title: ida controls
+ctrl.form.status.labelString: fixme
+
+ctrl*bar.file.labelString: File
+ctrl*bar.file.mnemonic: F
+ctrl*bar*load.labelString: Load image ...
+ctrl*bar*load.mnemonic: L
+ctrl*bar*save.labelString: Save image ...
+ctrl*bar*save.mnemonic: S
+ctrl*bar*browse.labelString: File browser ...
+ctrl*bar*filelist.labelString: File list ...
+ctrl*bar*scan.labelString: Scan
+ctrl*bar*print.labelString: Print ...
+ctrl*bar*print.mnemonic: P
+ctrl*bar*quit.labelString: Quit
+ctrl*bar*quit.mnemonic: Q
+
+ctrl*bar.edit.labelString: Edit
+ctrl*bar.edit.mnemonic: E
+ctrl*bar*undo.labelString: Undo last operation
+ctrl*bar*undo.mnemonic: U
+ctrl*bar*copy.labelString: Copy
+ctrl*bar*paste.labelString: Paste
+ctrl*bar*flipv.labelString: Flip vertical
+ctrl*bar*flipv.mnemonic: v
+ctrl*bar*fliph.labelString: Flip horizontal
+ctrl*bar*fliph.mnemonic: h
+ctrl*bar*rotcw.labelString: Turn clockwise
+ctrl*bar*rotcw.mnemonic: T
+ctrl*bar*rotccw.labelString: Turn counter clockwise
+ctrl*bar*invert.labelString: Invert
+ctrl*bar*invert.mnemonic: I
+ctrl*bar*crop.labelString: Crop
+ctrl*bar*crop.mnemonic: C
+ctrl*bar*acrop.labelString: Autocrop
+ctrl*bar*acrop.mnemonic: A
+ctrl*bar*scale.labelString: Scale ...
+ctrl*bar*scale.mnemonic: S
+ctrl*bar*rotany.labelString: Rotate ...
+
+ctrl*bar.op.labelString: Filters
+ctrl*bar.op.mnemonic: F
+ctrl*bar*gamma.labelString: Gamma ...
+ctrl*bar*gamma.mnemonic: G
+ctrl*bar*bright.labelString: Bright ...
+ctrl*bar*bright.mnemonic: B
+ctrl*bar*contr.labelString: Contrast ...
+ctrl*bar*contr.mnemonic: C
+ctrl*bar*color.labelString: Edit colors ...
+ctrl*bar*color.mnemonic: E
+ctrl*bar*gray.labelString: Grayscale
+ctrl*bar*blur.labelString: Blur
+ctrl*bar*sharpe.labelString: Sharpe ...
+ctrl*bar*edge.labelString: Edge detect
+ctrl*bar*emboss.labelString: Emboss
+
+ctrl*bar.view.labelString: View
+ctrl*bar.view.mnemonic: V
+ctrl*bar*prev.labelString: previous file
+ctrl*bar*next.labelString: next file
+ctrl*bar*prevpage.labelString: previous page
+ctrl*bar*nextpage.labelString: next page
+ctrl*bar*zoomin.labelString: Zoom in
+ctrl*bar*zoomout.labelString: Zoom out
+
+ctrl*bar.opt.labelString: Options
+ctrl*bar.view.mnemonic: O
+ctrl*bar*pcd.labelString: PhotoCD resolution
+
+ctrl*bar.help.labelString: Help
+ctrl*bar.help.mnemonic: H
+ctrl*bar*man.labelString: Manual page ...
+ctrl*bar*man.mnemonic: M
+ctrl*bar*about.labelString: About ...
+ctrl*bar*about.mnemonic: A
+
+ctrl*tool.prev.toolTipString: previous file
+ctrl*tool.next.toolTipString: next file
+ctrl*tool.zoomin.toolTipString: zoom in
+ctrl*tool.zoomout.toolTipString: zoom out
+ctrl*tool.flipv.toolTipString: flip vertical
+ctrl*tool.fliph.toolTipString: flip horizontal
+ctrl*tool.rotccw.toolTipString: turn counter clockwise
+ctrl*tool.rotcw.toolTipString: turn clockwise
+ctrl*tool.exit.toolTipString: quit
+
+browser.filter_popup.title: Filter
+browser.filter_popup*selectionLabelString: pattern?
+
+Ida.man_popup.title: Manual page
+Ida.man_popup*okLabelString: close window
+Ida.man_popup*label.labelString: please wait ...
+
+*cbar.file.labelString: File
+*cbar.file.mnemonic: F
+*cbar*new.labelString: New list
+*cbar*new.mnemonic: N
+*cbar*load.labelString: Load list ...
+*cbar*load.mnemonic: L
+*cbar*save.labelString: Save list
+*cbar*save.mnemonic: S
+*cbar*saveas.labelString: Save list as ...
+*cbar*saveas.mnemonic: a
+*cbar*close.labelString: Close window
+
+*cbar.edit.labelString: Edit
+*cbar.edit.mnemonic: E
+*cbar*copy.labelString: Copy
+*cbar*paste.labelString: Paste
+*cbar*del.labelString: Delete
+
+*cbar.view.labelString: View
+*cbar.view.mnemonic: V
+*cbar*spatial.labelString: Large Icons
+*cbar*details.labelString: Details
+*cbar*filter.labelString: Filter ...
+*cbar*freset.labelString: Reset filter
diff --git a/backup/Ida-fixed b/backup/Ida-fixed
new file mode 100644
index 0000000..2c6baf6
--- /dev/null
+++ b/backup/Ida-fixed
@@ -0,0 +1,439 @@
+
+! ----------------------------------------------------------------------------
+! fonts
+
+*renderTable: small
+*renderTable.fontType: FONT_IS_FONTSET
+*renderTable.fontName: \
+ -*-bitstream vera sans-medium-r-normal-*-*-140-*-*-p-*-iso8859-1, \
+ -*-bitstream vera sans-medium-r-normal-*-*-140-*-*-p-*-iso8859-15, \
+ -microsoft-tahoma-medium-r-normal-*-*-140-*-*-p-*-iso8859-*, \
+ -adobe-helvetica-medium-r-normal-*-*-140-*-*-p-*-iso8859-*, \
+ -cronyx-helvetica-medium-r-normal-*-*-140-*-*-p-*-koi8-r, \
+ -*-lucida-medium-r-normal-*-*-140-*-*-p-*-iso8859-*, \
+ -gnu-unifont-medium-r-normal-*-*-160-*-*-*-*-*-*, \
+ -*-*-medium-r-normal-*-*-140-*-*-p-*-*-*, \
+ -*-*-medium-r-normal-*-*-160-*-*-p-*-*-*, \
+ -*-*-*-*-*-*-*-140-*-*-*-*-*-*, \
+ -*-*-*-*-*-*-*-160-*-*-*-*-*-*, *
+*renderTable.small.fontType: FONT_IS_FONTSET
+*renderTable.small.fontName: \
+ -*-bitstream vera sans-medium-r-normal-*-*-100-*-*-p-*-iso8859-1, \
+ -*-bitstream vera sans-medium-r-normal-*-*-100-*-*-p-*-iso8859-15, \
+ -microsoft-tahoma-medium-r-normal-*-*-100-*-*-p-*-iso8859-*, \
+ -adobe-helvetica-medium-r-normal-*-*-100-*-*-p-*-iso8859-*, \
+ -cronyx-helvetica-medium-r-normal-*-*-100-*-*-p-*-koi8-r, \
+ -*-lucida-medium-r-normal-*-*-100-*-*-p-*-iso8859-*, \
+ -*-*-medium-r-normal-*-*-100-*-*-p-*-*-*, \
+ -*-*-medium-r-normal-*-*-120-*-*-p-*-*-*, \
+ -*-*-*-*-*-*-*-100-*-*-*-*-*-*, \
+ -*-*-*-*-*-*-*-120-*-*-*-*-*-*, *
+
+*XmTextField.renderTable:
+*XmTextField.renderTable.fontType: FONT_IS_FONTSET
+*XmTextField.renderTable.fontName: \
+ -*-bitstream vera sans mono-medium-r-normal-*-*-140-*-*-*-*-iso8859-1, \
+ -*-bitstream vera sans mono-medium-r-normal-*-*-140-*-*-*-*-iso8859-15, \
+ -monotype-andale mono-medium-r-normal-*-*-140-*-*-*-*-iso8859-*, \
+ -adobe-courier-medium-r-normal-*-*-140-*-*-m-*-iso8859-*, \
+ -cronyx-courier-medium-r-normal-*-*-140-*-*-m-*-koi8-r, \
+ -*-lucidatypewriter-medium-r-normal-*-*-140-*-*-m-*-iso8859-*, \
+ -*-*-medium-r-normal-*-*-140-*-*-m-*-*-*, \
+ -*-*-medium-r-normal-*-*-160-*-*-m-*-*-*, \
+ -*-*-*-*-*-*-*-140-*-*-*-*-*-*, \
+ -*-*-*-*-*-*-*-160-*-*-*-*-*-*, *
+
+!*.shadowThickness: 2
+!*.highlightThickness: 1
+
+
+! ----------------------------------------------------------------------------
+! image window
+
+Ida.geometry: 75x50
+!Ida.winGravity: static
+Ida.view*translations: #override \
+ <Key>space: Next() \n\
+ <Key>osfDelete: Prev() \n\
+ <Key>osfBackSpace: Prev() \n\
+ <Key>Page_Down: NextPage() \n\
+ <Key>Page_Up: PrevPage() \n\
+ <Key>KP_Add: Zoom(inc) \n\
+ <Key>KP_Subtract: Zoom(dec) \n\
+ <Btn2Down>: Ipc(drag) \n\
+ <Btn3Up>: Popup(control) \n\
+ \
+ <Key>G: Gamma() \n\
+ <Key>F: Browser() \n\
+ <Key>L: Filelist() \n\
+ Ctrl<Key>V: Ipc(paste) \n\
+ Alt<Key>V: Ipc(paste) \n\
+ Ctrl<Key>C: Ipc(copy) \n\
+ Alt<Key>C: Ipc(copy) \n\
+ \
+ Ctrl<Key>P: Print() \n\
+ Ctrl<Key>L: Load() \n\
+ Ctrl<Key>S: Save() \n\
+ Alt<Key>S: Sharpe() \n\
+ ~Alt ~Ctrl<Key>S: Resize() \n\
+ <Key>plus: Zoom(inc) \n\
+ <Key>minus: Zoom(dec) \n\
+ <Key>U: Undo() \n\
+ ~Alt ~Ctrl<Key>C: Filter(crop) \n\
+ ~Alt ~Ctrl<Key>V: Filter(flip-vert) \n\
+ <Key>H: Filter(flip-horz) \n\
+ ~Alt ~Shift<Key>T: Filter(rotate-cw) \n\
+ Shift<Key>T: Filter(rotate-ccw) \n\
+ Alt<Key>T: Rotate() \n\
+ <Key>I: Filter(invert) \n\
+ ~Alt<Key>E: Color() \n\
+ Alt<Key>E: F3x3(-1,-1,-1,-1,8,-1,-1,-1,-1) \n\
+ Alt<Key>B: F3x3(1,1,1,1,1,1,1,1,1, 1,9) \n\
+ Alt<Key>M: F3x3(1,0,0,0,0,0,0,0,-1, 0,0,128) \n\
+ <Key>osfHelp: Man(ida) \n\
+ <Key>Q: Exit()
+
+Ida.view.VertScrollBar.accelerators: #override \
+ <Btn4Up>: IncrementUpOrLeft(Up) \n\
+ <Btn5Up>: IncrementDownOrRight(Down)\n\
+ ~Ctrl<Key>osfUp: IncrementUpOrLeft(Up) \n\
+ ~Ctrl<Key>osfDown: IncrementDownOrRight(Down)\n\
+ Ctrl<Key>osfUp: PageUpOrLeft(Up) \n\
+ Ctrl<Key>osfDown: PageDownOrRight(Down)
+Ida.view.HorScrollBar.accelerators: #override \
+ ~Ctrl<Key>osfLeft: IncrementUpOrLeft(Left) \n\
+ ~Ctrl<Key>osfRight: IncrementDownOrRight(Right)\n\
+ Ctrl<Key>osfLeft: PageUpOrLeft(Left) \n\
+ Ctrl<Key>osfRight: PageDownOrRight(Right)
+
+
+Ida.view.shadowThickness: 1
+Ida.view.scrollingPolicy: AUTOMATIC
+Ida.view.scrollBarPlacement: BOTTOM_RIGHT
+Ida.view.scrolledWindowChildType: SCROLL_VERT
+
+Ida.view*image.backgroundPixmap: none
+Ida.view*image.borderWidth: 0
+Ida.view*image.highlightThickness: 1
+Ida.view*image.highlightColor: red
+
+Ida.aboutbox_popup.deleteResponse: DESTROY
+Ida.sorrybox_popup.deleteResponse: DESTROY
+Ida.noundobox_popup.deleteResponse: DESTROY
+
+
+! ----------------------------------------------------------------------------
+! dialog boxes
+
+Ida.XmDialogShell.deleteResponse: DESTROY
+Ida.XmDialogShell*scale.orientation: HORIZONTAL
+Ida.XmDialogShell*scale.showValue: True
+
+Ida.errbox_popup.deleteResponse: UNMAP
+Ida.load_popup.deleteResponse: UNMAP
+Ida.save_popup*deleteResponse: UNMAP
+Ida.save_popup*dialogStyle: DIALOG_PRIMARY_APPLICATION_MODAL
+Ida.print_popup*deleteResponse: UNMAP
+Ida.print_popup*dialogStyle: DIALOG_PRIMARY_APPLICATION_MODAL
+
+Ida*ps_popup*rc1.orientation: HORIZONTAL
+Ida*ps_popup*draw.borderWidth: 1
+Ida*ps_popup*draw.background: white
+Ida*ps_popup*draw.resizePolicy: RESIZE_NONE
+Ida*ps_popup*scale.titleString: Scaling
+Ida*ps_popup*scale.minimum: 10
+Ida*ps_popup*scale.maximum: 1000
+Ida*ps_popup*scale.decimalPoints: 1
+
+Ida.gamma_popup*scale.minimum: 20
+Ida.gamma_popup*scale.maximum: 500
+Ida.gamma_popup*scale.decimalPoints: 2
+Ida.bright_popup*scale.minimum: -256
+Ida.bright_popup*scale.maximum: 256
+Ida.contrast_popup*scale.minimum: -128
+Ida.contrast_popup*scale.maximum: 512
+Ida.rotate_popup*scale.minimum: -180
+Ida.rotate_popup*scale.maximum: 180
+Ida.sharpe_popup*scale.minimum: 0
+Ida.sharpe_popup*scale.maximum: 100
+
+Ida.resize_popup.deleteResponse: DESTROY
+Ida.resize_popup*rc.adjustMargin: false
+Ida.resize_popup*rc.rc.orientation: HORIZONTAL
+Ida.resize_popup*rc.rc.?.indicatorType: ONE_OF_MANY
+
+
+! ----------------------------------------------------------------------------
+! edit colors dialog
+
+Ida.color_popup.deleteResponse: DESTROY
+
+Ida.color_popup*XmForm*leftOffset: 10
+Ida.color_popup*XmForm*rightOffset: 10
+Ida.color_popup*XmForm*topOffset: 10
+Ida.color_popup*XmForm*bottomOffset: 10
+Ida.color_popup*XmForm*leftAttachment: ATTACH_WIDGET
+Ida.color_popup*XmForm*topAttachment: ATTACH_WIDGET
+Ida.color_popup*XmForm.sep.rightAttachment: ATTACH_FORM
+Ida.color_popup*XmForm.XmRowColumn.rightAttachment: ATTACH_FORM
+Ida.color_popup*XmForm.XmRowColumn.orientation: HORIZONTAL
+Ida.color_popup*XmText.columns: 5
+
+Ida.color_popup*XmDrawingArea.background: white
+Ida.color_popup*XmDrawingArea.borderWidth: 1
+Ida.color_popup*XmDrawingArea.borderColor: black
+
+Ida.color_popup*hred.topWidget: hist
+Ida.color_popup*hgreen.topWidget: hred
+Ida.color_popup*hblue.topWidget: hgreen
+
+Ida.color_popup*map.leftWidget: hred
+Ida.color_popup*mred.topWidget: hist
+Ida.color_popup*mred.leftWidget: hred
+Ida.color_popup*mred.rightAttachment: ATTACH_FORM
+Ida.color_popup*mgreen.topWidget: mred
+Ida.color_popup*mgreen.leftWidget: hgreen
+Ida.color_popup*mgreen.rightAttachment: ATTACH_FORM
+Ida.color_popup*mblue.topWidget: mgreen
+Ida.color_popup*mblue.leftWidget: hblue
+Ida.color_popup*mblue.rightAttachment: ATTACH_FORM
+
+Ida.color_popup*lock.topWidget: hblue
+Ida.color_popup*vals.topWidget: lock
+Ida.color_popup*in.topWidget: vals
+Ida.color_popup*out.topWidget: in
+Ida.color_popup*gamma.topWidget: out
+Ida.color_popup*pick.topWidget: gamma
+
+
+! ----------------------------------------------------------------------------
+! control
+
+ctrl*XmMenuShell.XmRowColumn.tearOffModel: TEAR_OFF_ENABLED
+
+ctrl*tool.orientation: HORIZONTAL
+ctrl*tool.XmPushButton.shadowThickness: 1
+
+ctrl.toolTipEnable: 1
+ctrl.toolTipPostDelay: 2000
+ctrl.toolTipPostDuration: 5000
+ctrl*TipLabel.foreground: black
+ctrl*TipLabel.background: lightyellow
+ctrl*TipShell.borderWidth: 1
+ctrl*TipShell.borderColor: black
+ctrl*tool.XmSeparator.orientation: VERTICAL
+ctrl*tool.XmSeparator.width: 12
+ctrl*tool.XmSeparator.margin: 3
+ctrl*tool.XmPushButton.labelType: PIXMAP
+ctrl*tool.prev.labelPixmap: prev
+ctrl*tool.next.labelPixmap: next
+ctrl*tool.zoomin.labelPixmap: zoomin
+ctrl*tool.zoomout.labelPixmap: zoomout
+ctrl*tool.flipv.labelPixmap: flipv
+ctrl*tool.fliph.labelPixmap: fliph
+ctrl*tool.rotccw.labelPixmap: rotccw
+ctrl*tool.rotcw.labelPixmap: rotcw
+ctrl*tool.exit.labelPixmap: exit
+
+ctrl.form*list.visibleItemCount: 12
+ctrl.form*list.translations: #override \
+ <Key>space: Next() \n\
+ <Key>osfDelete: Prev() \n\
+ <Key>osfBackSpace: Prev() \n\
+ <Key>KP_Add: Zoom(inc) \n\
+ <Key>KP_Subtract: Zoom(dec)
+
+ctrl*bar*load.acceleratorText: Ctrl+L
+ctrl*bar*load.accelerator: Ctrl<Key>L
+ctrl*bar*save.acceleratorText: Ctrl+S
+ctrl*bar*save.accelerator: Ctrl<Key>S
+ctrl*bar*browse.acceleratorText: F
+ctrl*bar*browse.accelerator: <Key>F
+ctrl*bar*filelist.acceleratorText: L
+ctrl*bar*filelist.accelerator: <Key>L
+ctrl*bar*print.acceleratorText: Ctrl+P
+ctrl*bar*print.accelerator: Ctrl<Key>P
+ctrl*bar*quit.acceleratorText: Q
+ctrl*bar*quit.accelerator: <Key>Q
+
+ctrl*bar*undo.acceleratorText: U
+ctrl*bar*undo.accelerator: <Key>U
+ctrl*bar*copy.acceleratorText: Ctrl+C
+ctrl*bar*copy.accelerator: Ctrl<Key>C
+ctrl*bar*paste.acceleratorText: Ctrl+V
+ctrl*bar*paste.accelerator: Ctrl<Key>V
+ctrl*bar*flipv.acceleratorText: V
+ctrl*bar*flipv.accelerator: <Key>V
+ctrl*bar*fliph.acceleratorText: H
+ctrl*bar*fliph.accelerator: <Key>H
+ctrl*bar*rotcw.acceleratorText: T
+ctrl*bar*rotcw.accelerator: ~Meta ~Shift<Key>T
+ctrl*bar*rotccw.acceleratorText: Shift+T
+ctrl*bar*rotccw.accelerator: Shift<Key>T
+ctrl*bar*invert.acceleratorText: I
+ctrl*bar*invert.accelerator: <Key>I
+ctrl*bar*crop.acceleratorText: C
+ctrl*bar*crop.accelerator: <Key>C
+ctrl*bar*scale.acceleratorText: S
+ctrl*bar*scale.accelerator: ~Ctrl<Key>S
+ctrl*bar*rotany.acceleratorText: Alt+T
+ctrl*bar*rotany.accelerator: Alt<Key>T
+
+ctrl*bar*gamma.acceleratorText: G
+ctrl*bar*gamma.accelerator: <Key>G
+ctrl*bar*color.acceleratorText: E
+ctrl*bar*color.accelerator: ~Alt<Key>E
+ctrl*bar*blur.acceleratorText: Alt+B
+ctrl*bar*blur.accelerator: Alt<Key>B
+ctrl*bar*sharpe.acceleratorText: Alt+S
+ctrl*bar*sharpe.accelerator: Alt<Key>S
+ctrl*bar*edge.acceleratorText: Alt+E
+ctrl*bar*edge.accelerator: Alt<Key>E
+ctrl*bar*emboss.acceleratorText: Alt+M
+ctrl*bar*emboss.accelerator: Alt<Key>m
+
+ctrl*bar*prev.acceleratorText: backspace
+!ctrl*bar*prev.accelerator: <Key>Backspace
+ctrl*bar*next.acceleratorText: space
+!ctrl*bar*next.accelerator: <Key>space
+ctrl*bar*prevpage.acceleratorText: PageUp
+!ctrl*bar*prevpage.accelerator: <Key>osfPageUp
+ctrl*bar*nextpage.acceleratorText: PageDown
+!ctrl*bar*nextpage.accelerator: <Key>osfPageDown
+ctrl*bar*zoomin.acceleratorText: plus
+ctrl*bar*zoomin.accelerator: <Key>plus
+ctrl*bar*zoomout.acceleratorText: minus
+ctrl*bar*zoomout.accelerator: <Key>minus
+
+ctrl*bar*pcdM.1.labelString: 192 x 128
+ctrl*bar*pcdM.2.labelString: 384 x 256
+ctrl*bar*pcdM.3.labelString: 768 x 512
+ctrl*bar*pcdM.4.labelString: 1536 x 1024
+ctrl*bar*pcdM.5.labelString: 3072 x 2048
+
+ctrl*bar*man.acceleratorText: F1
+ctrl*bar*man.accelerator: <Key>F1
+
+ctrl.form.*.leftAttachment: ATTACH_FORM
+ctrl.form.*.rightAttachment: ATTACH_FORM
+ctrl.form.tool.topAttachment: ATTACH_WIDGET
+ctrl.form.tool.topWidget: bar
+ctrl.form.listSW.topAttachment: ATTACH_WIDGET
+ctrl.form.listSW.topWidget: tool
+ctrl.form.listSW.bottomAttachment: ATTACH_WIDGET
+ctrl.form.listSW.bottomWidget: status
+ctrl.form.listSW.width: 320
+ctrl.form.listSW.height: 240
+ctrl.form.status.bottomAttachment: ATTACH_FORM
+ctrl.form.status.alignment: ALIGNMENT_BEGINNING
+
+
+! ----------------------------------------------------------------------------
+! man page renderer
+
+Ida.man_popup.deleteResponse: DESTROY
+Ida.man_popup*view.width: 500
+Ida.man_popup*view.height: 600
+Ida.man_popup*view.scrollingPolicy: AUTOMATIC
+Ida.man_popup*view.scrollBarPlacement: BOTTOM_RIGHT
+
+Ida.man_popup*label.alignment: ALIGNMENT_BEGINNING
+Ida.man_popup*label.marginWidth: 5
+Ida.man_popup*label.marginHeight: 5
+Ida.man_popup*label.renderTable: bold,underline
+Ida.man_popup*label.renderTable.fontType: FONT_IS_FONTSET
+Ida.man_popup*label.renderTable.fontName: \
+ -*-fixed-medium-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-medium-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.man_popup*label.renderTable.bold.fontType: FONT_IS_FONTSET
+Ida.man_popup*label.renderTable.bold.fontName: \
+ -*-fixed-bold-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-bold-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.man_popup*label.renderTable.underline.underlineType: SINGLE_LINE
+
+
+! ----------------------------------------------------------------------------
+! hex viewer
+
+Ida.hex_popup.deleteResponse: DESTROY
+Ida.hex_popup*view.width: 600
+Ida.hex_popup*view.height: 600
+Ida.hex_popup*view.scrollingPolicy: AUTOMATIC
+Ida.hex_popup*view.scrollBarPlacement: BOTTOM_RIGHT
+
+Ida.hex_popup*label.alignment: ALIGNMENT_BEGINNING
+Ida.hex_popup*label.marginWidth: 5
+Ida.hex_popup*label.marginHeight: 5
+Ida.hex_popup*label.renderTable: bold,underline
+Ida.hex_popup*label.renderTable.fontType: FONT_IS_FONTSET
+Ida.hex_popup*label.renderTable.fontName: \
+ -*-fixed-medium-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-medium-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.hex_popup*label.renderTable.bold.fontType: FONT_IS_FONTSET
+Ida.hex_popup*label.renderTable.bold.fontName: \
+ -*-fixed-bold-r-normal--13-*-*-*-*-*-iso8859-*, \
+ -*-fixed-medium-r-normal-ja-13-*-*-*-*-*-iso10646-1, \
+ -gnu-unifont-bold-r-normal--16-*-*-*-*-*-*-*,*
+
+Ida.hex_popup*label.renderTable.underline.underlineType: SINGLE_LINE
+
+
+! ----------------------------------------------------------------------------
+! file browser
+
+browser.geometry: 600x450
+browser.form.?.leftAttachment: ATTACH_FORM
+browser.form.?.rightAttachment: ATTACH_FORM
+browser.form.scroll.topAttachment: ATTACH_WIDGET
+browser.form.scroll.topWidget: cbar
+browser.form.scroll.bottomAttachment: ATTACH_WIDGET
+browser.form.scroll.bottomWidget: status
+browser.form.status.bottomAttachment: ATTACH_FORM
+browser.form.status.alignment: ALIGNMENT_BEGINNING
+
+browser.form.scroll.scrollingPolicy: AUTOMATIC
+browser.form.scroll.scrollBarPlacement: BOTTOM_RIGHT
+browser.form.scroll.XmScrollBar.highlightThickness: 1
+
+*container.outlineButtonPolicy: OUTLINE_BUTTON_ABSENT
+*container.spatialStyle: CELLS
+*container.spatialResizeModel: GROW_MINOR
+*container.spatialSnapModel: CENTER
+!*container.spatialIncludeModel: APPEND
+!*container.layoutDirection: LEFT_TO_RIGHT_TOP_TO_BOTTOM
+*container.background: gray85
+*container.XmIconGadget.highlightColor: darkred
+*container.XmIconGadget.shadowThickness: 1
+
+*cbar*close.acceleratorText: Q
+*cbar*close.accelerator: <Key>Q
+*cbar*copy.acceleratorText: Ctrl+C
+*cbar*copy.accelerator: Ctrl<Key>C
+*cbar*paste.acceleratorText: Ctrl+V
+*cbar*paste.accelerator: Ctrl<Key>V
+*cbar*filter.acceleratorText: F
+*cbar*filter.accelerator: <Key>F
+
+
+! ----------------------------------------------------------------------------
+! file lists
+
+filelist.geometry: 300x400
+filelist.form.?.leftAttachment: ATTACH_FORM
+filelist.form.?.rightAttachment: ATTACH_FORM
+filelist.form.scroll.topAttachment: ATTACH_WIDGET
+filelist.form.scroll.topWidget: cbar
+filelist.form.scroll.bottomAttachment: ATTACH_WIDGET
+filelist.form.scroll.bottomWidget: status
+filelist.form.status.bottomAttachment: ATTACH_FORM
+filelist.form.status.alignment: ALIGNMENT_BEGINNING
+
+filelist.form.scroll.scrollingPolicy: AUTOMATIC
+filelist.form.scroll.scrollBarPlacement: BOTTOM_RIGHT
+filelist.form.scroll.XmScrollBar.highlightThickness: 1
diff --git a/browser.c b/browser.c
new file mode 100644
index 0000000..3313d0c
--- /dev/null
+++ b/browser.c
@@ -0,0 +1,571 @@
+/*
+ * simple file browser
+ * (c) 2001-03 Gerd Knorr <kraxel@bytesex.org>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <fnmatch.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/extensions/XShm.h>
+#include <Xm/Xm.h>
+#include <Xm/Form.h>
+#include <Xm/Label.h>
+#include <Xm/RowColumn.h>
+#include <Xm/PushB.h>
+#include <Xm/CascadeB.h>
+#include <Xm/ScrolledW.h>
+#include <Xm/SelectioB.h>
+#include <Xm/Transfer.h>
+#include <Xm/TransferP.h>
+#include <Xm/Container.h>
+#include <Xm/Separator.h>
+
+#include "RegEdit.h"
+#include "ida.h"
+#include "readers.h"
+#include "viewer.h"
+#include "browser.h"
+#include "filter.h"
+#include "x11.h"
+#include "dither.h"
+#include "selections.h"
+#include "filebutton.h"
+#include "misc.h"
+#include "idaconfig.h"
+#include "desktop.h"
+
+/*----------------------------------------------------------------------*/
+
+struct browser_handle;
+
+struct browser_handle {
+ char *dirname;
+ char *lastdir;
+ char *filter;
+ Widget shell;
+ Widget scroll;
+ Widget container;
+ Widget status;
+ XmString details[DETAIL_COUNT+1];
+
+ struct list_head files;
+ struct list_head *item;
+ unsigned int dirs,sfiles,afiles;
+
+ XtWorkProcId wproc;
+};
+
+/*----------------------------------------------------------------------*/
+
+static void dir_info(struct file_button *file)
+{
+ char path[256];
+ char comment[256];
+ int len;
+
+ snprintf(path,sizeof(path),"%s/.directory",file->filename);
+ len = desktop_read_entry(path, "Comment=", comment, sizeof(comment));
+
+ if (len) {
+ XmStringFree(file->details[DETAIL_COMMENT]);
+ file->details[DETAIL_COMMENT] =
+ XmStringGenerate(comment, NULL, XmMULTIBYTE_TEXT,NULL);
+ XtVaSetValues(file->widget,
+ XmNdetail, file->details,
+ XmNdetailCount, DETAIL_COUNT,
+ NULL);
+ }
+}
+
+static Boolean
+browser_statfiles(XtPointer clientdata)
+{
+ struct browser_handle *h = clientdata;
+ struct file_button *file;
+ struct list_head *item;
+ char line[80];
+ XmString str;
+ Pixmap pix;
+ char *type;
+
+ if (h->item == &h->files) {
+ /* done => read thumbnails now */
+ h->wproc = 0;
+ list_for_each(item,&h->files) {
+ file = list_entry(item, struct file_button, window);
+ if ((file->st.st_mode & S_IFMT) != S_IFREG)
+ continue;
+ fileinfo_queue(file);
+ }
+
+ list_for_each(item,&h->files) {
+ file = list_entry(item, struct file_button, window);
+ if ((file->st.st_mode & S_IFMT) != S_IFDIR)
+ continue;
+ dir_info(file);
+ }
+
+ /* update status line */
+ if (h->filter) {
+ snprintf(line, sizeof(line), "%d dirs, %d/%d files [%s]",
+ h->dirs,h->sfiles,h->afiles,h->filter);
+ } else {
+ snprintf(line, sizeof(line), "%d dirs, %d files",
+ h->dirs,h->afiles);
+ }
+ str = XmStringGenerate(line, NULL, XmMULTIBYTE_TEXT, NULL);
+ XtVaSetValues(h->status,XmNlabelString,str,NULL);
+ XmStringFree(str);
+ h->item = NULL;
+ return TRUE;
+ }
+
+ /* handle file */
+ file = list_entry(h->item, struct file_button, window);
+ switch (file->st.st_mode & S_IFMT) {
+ case S_IFDIR:
+ type = "dir";
+ break;
+ case S_IFREG:
+ type = "file";
+ break;
+ default:
+ type = NULL;
+ }
+ if (type) {
+ pix = XmGetPixmap(XtScreen(h->container),type,0,0);
+ file_set_icon(file,pix,pix);
+ }
+
+ h->item = h->item->next;
+ return FALSE;
+}
+
+static void list_add_sorted(struct browser_handle *h, struct file_button *add)
+{
+ struct file_button *file;
+ struct list_head *item;
+
+ list_for_each(item,&h->files) {
+ file = list_entry(item, struct file_button, window);
+ if (file_cmp_alpha(add,file) <= 0) {
+ list_add_tail(&add->window,&file->window);
+ return;
+ }
+ }
+ list_add_tail(&add->window,&h->files);
+}
+
+static Boolean browser_readdir(XtPointer clientdata)
+{
+ struct browser_handle *h = clientdata;
+ struct file_button *file;
+ struct list_head *item;
+ struct dirent *dirent;
+ WidgetList children;
+ struct file_button *lastdir = NULL;
+ Cardinal nchildren;
+ XmString str,elem;
+ DIR *dir;
+ unsigned int len;
+
+ /* status line */
+ str = XmStringGenerate("scanning ", NULL, XmMULTIBYTE_TEXT, NULL);
+ elem = XmStringGenerate(h->dirname, NULL, XmMULTIBYTE_TEXT, NULL);
+ str = XmStringConcatAndFree(str,elem);
+ elem = XmStringGenerate(" ...", NULL, XmMULTIBYTE_TEXT, NULL);
+ str = XmStringConcatAndFree(str,elem);
+ XtVaSetValues(h->status,XmNlabelString,str,NULL);
+ XmStringFree(str);
+ ptr_busy();
+
+ /* read + sort dir */
+ dir = opendir(h->dirname);
+ if (NULL == dir) {
+ fprintf(stderr,"opendir %s: %s\n",h->dirname,strerror(errno));
+ return -1;
+ }
+ h->dirs = 0;
+ h->sfiles = 0;
+ h->afiles = 0;
+ while (NULL != (dirent = readdir(dir))) {
+ /* skip dotfiles */
+ if (dirent->d_name[0] == '.' && 0 != strcmp(dirent->d_name,".."))
+ continue;
+
+ /* get memory */
+ file = malloc(sizeof(*file));
+ memset(file,0,sizeof(*file));
+
+ /* get file type */
+ file->basename = strdup(dirent->d_name);
+ file->d_type = dirent->d_type;
+ if (0 == strcmp(dirent->d_name, "..")) {
+ char *slash;
+ file->filename = strdup(h->dirname);
+ slash = strrchr(file->filename,'/');
+ if (slash == file->filename)
+ slash++;
+ *slash = 0;
+ } else {
+ len = strlen(h->dirname)+strlen(file->basename)+4;
+ file->filename = malloc(len);
+ if (0 == strcmp(h->dirname,"/")) {
+ sprintf(file->filename,"/%s",file->basename);
+ } else {
+ sprintf(file->filename,"%s/%s",h->dirname,
+ file->basename);
+ }
+ }
+ if (file->d_type != DT_UNKNOWN) {
+ file->st.st_mode = DTTOIF(file->d_type);
+ } else {
+ if (-1 == stat(file->filename, &file->st)) {
+ fprintf(stderr,"stat %s: %s\n",
+ file->filename,strerror(errno));
+ }
+ }
+
+ /* user-specified filter */
+ if (S_ISDIR(file->st.st_mode)) {
+ h->dirs++;
+ if (h->lastdir && 0 == strcmp(h->lastdir,file->filename)) {
+ lastdir = file;
+ }
+ } else {
+ h->afiles++;
+ if (h->filter && 0 != fnmatch(h->filter,dirent->d_name,0)) {
+ free(file);
+ continue;
+ } else
+ h->sfiles++;
+ }
+
+ list_add_sorted(h,file);
+ }
+ closedir(dir);
+
+ /* create & manage widgets */
+ list_for_each(item,&h->files) {
+ file = list_entry(item, struct file_button, window);
+ file_createwidgets(h->container, file);
+ }
+ nchildren = XmContainerGetItemChildren(h->container,NULL,&children);
+ XtManageChildren(children,nchildren);
+ if (nchildren)
+ XtFree((XtPointer)children);
+ container_relayout(h->container);
+ if (h->lastdir) {
+ if (lastdir) {
+ if (debug)
+ fprintf(stderr,"lastdir: %s\n",h->lastdir);
+ XtVaSetValues(h->container,
+ XmNinitialFocus, lastdir->widget,
+ NULL);
+// XmScrollVisible(h->scroll, lastdir->widget, 25, 25);
+// XtSetKeyboardFocus(h->shell,h->container);
+ }
+ free(h->lastdir);
+ h->lastdir = NULL;
+ }
+ XtVaSetValues(h->shell,XtNtitle,h->dirname,NULL);
+ ptr_idle();
+
+ /* start bg processing */
+ h->item = h->files.next;
+ h->wproc = XtAppAddWorkProc(app_context,browser_statfiles,h);
+ return TRUE;
+}
+
+static void browser_bgcancel(struct browser_handle *h)
+{
+ if (h->wproc)
+ XtRemoveWorkProc(h->wproc);
+ h->wproc = 0;
+}
+
+/*----------------------------------------------------------------------*/
+
+static void
+browser_cd(struct browser_handle *h, char *dir)
+{
+ /* build new dir path */
+ if (h->lastdir)
+ free(h->lastdir);
+ h->lastdir = h->dirname;
+ h->dirname = strdup(dir);
+
+ /* cleanup old stuff + read dir */
+ browser_bgcancel(h);
+ container_delwidgets(h->container);
+ h->wproc = XtAppAddWorkProc(app_context,browser_readdir,h);
+}
+
+static void
+browser_filter_done(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct browser_handle *h = clientdata;
+ XmSelectionBoxCallbackStruct *cb = call_data;
+ char *filter;
+
+ if (cb->reason == XmCR_OK) {
+ filter = XmStringUnparse(cb->value,NULL,
+ XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
+ NULL,0,0);
+ if (h->filter)
+ free(h->filter);
+ h->filter = NULL;
+ if (strlen(filter) > 0)
+ h->filter = strdup(filter);
+ XtFree(filter);
+
+ if (debug)
+ fprintf(stderr,"filter: %s\n", h->filter ? h->filter : "[none]");
+ browser_bgcancel(h);
+ container_delwidgets(h->container);
+ h->wproc = XtAppAddWorkProc(app_context,browser_readdir,h);
+ }
+ XtDestroyWidget(widget);
+}
+
+static void
+browser_filter(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct browser_handle *h = clientdata;
+ Widget shell;
+
+ shell = XmCreatePromptDialog(h->shell,"filter",NULL,0);
+ XtUnmanageChild(XmSelectionBoxGetChild(shell,XmDIALOG_HELP_BUTTON));
+ XtAddCallback(shell,XmNokCallback,browser_filter_done,h);
+ XtAddCallback(shell,XmNcancelCallback,browser_filter_done,h);
+ XtManageChild(shell);
+}
+
+static void
+browser_nofilter(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct browser_handle *h = clientdata;
+
+ if (!h->filter)
+ return;
+ if (debug)
+ fprintf(stderr,"filter: reset\n");
+ free(h->filter);
+ h->filter = NULL;
+
+ browser_bgcancel(h);
+ container_delwidgets(h->container);
+ h->wproc = XtAppAddWorkProc(app_context,browser_readdir,h);
+}
+
+/*----------------------------------------------------------------------*/
+
+static void
+browser_action_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmContainerSelectCallbackStruct *cd = call_data;
+ struct browser_handle *h = clientdata;
+ struct stat st;
+ char *file;
+
+ if (XmCR_DEFAULT_ACTION == cd->reason && 1 == cd->selected_item_count) {
+ file = XtName(cd->selected_items[0]);
+ if (debug)
+ fprintf(stderr,"browser: action %s\n", file);
+ if (-1 == stat(file,&st)) {
+ fprintf(stderr,"stat %s: %s\n",file,strerror(errno));
+ return;
+ }
+ if (S_ISDIR(st.st_mode))
+ browser_cd(h,file);
+ if (S_ISREG(st.st_mode))
+ new_file(file,1);
+ }
+}
+
+static void
+browser_bookmark_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct browser_handle *h = clientdata;
+ browser_cd(h, cfg_get_str(O_BOOKMARKS, XtName(widget)));
+}
+
+/*----------------------------------------------------------------------*/
+
+static void
+browser_destroy(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct browser_handle *h = clientdata;
+
+ if (debug)
+ fprintf(stderr,"browser: destroy\n");
+ browser_bgcancel(h);
+ ptr_unregister(h->shell);
+ free(h);
+}
+
+static char*
+browser_fixpath(char *dir)
+{
+ char path[1024];
+ char *s,*d;
+
+ memset(path,0,sizeof(path));
+ if (dir[0] == '/') {
+ /* absolute */
+ strncpy(path,dir,sizeof(path)-1);
+ } else {
+ /* relative */
+ getcwd(path,sizeof(path)-1);
+ if (strlen(path)+strlen(dir)+4 < sizeof(path)) {
+ strcat(path,"/");
+ strcat(path,dir);
+ }
+ }
+
+ for (s = d = path; *s != 0;) {
+ if (0 == strncmp(s,"//",2)) {
+ s++;
+ continue;
+ }
+ if (0 == strncmp(s,"/./",3)) {
+ s+=2;
+ continue;
+ }
+ if (0 == strcmp(s,"/"))
+ s++;
+ if (0 == strcmp(s,"/."))
+ s+=2;
+ *d = *s;
+ s++, d++;
+ }
+ return strdup(path);
+}
+
+void
+browser_window(char *dirname)
+{
+ Widget form,menubar,menu,push,clip;
+ struct browser_handle *h;
+ Arg args[8];
+ char *list;
+ int n = 0;
+
+ h = malloc(sizeof(*h));
+ if (NULL == h) {
+ fprintf(stderr,"out of memory");
+ return;
+ }
+ memset(h,0,sizeof(*h));
+ INIT_LIST_HEAD(&h->files);
+ h->dirname = browser_fixpath(dirname);
+
+ h->shell = XtVaAppCreateShell("browser","Ida",
+ topLevelShellWidgetClass,
+ dpy,
+ XtNclientLeader,app_shell,
+ XmNdeleteResponse,XmDESTROY,
+ NULL);
+ XmdRegisterEditres(h->shell);
+ XtAddCallback(h->shell,XtNdestroyCallback,browser_destroy,h);
+
+ /* widgets */
+ form = XtVaCreateManagedWidget("form", xmFormWidgetClass, h->shell,
+ NULL);
+ menubar = XmCreateMenuBar(form,"cbar",NULL,0);
+ XtManageChild(menubar);
+ h->status = XtVaCreateManagedWidget("status",xmLabelWidgetClass, form,
+ NULL);
+
+ /* scrolled container */
+ h->details[0] = XmStringGenerate("Image", NULL, XmMULTIBYTE_TEXT,NULL);
+ h->details[DETAIL_SIZE+1] =
+ XmStringGenerate("Size", NULL, XmMULTIBYTE_TEXT,NULL);
+ h->details[DETAIL_COMMENT+1] =
+ XmStringGenerate("Comment", NULL, XmMULTIBYTE_TEXT,NULL);
+ XtSetArg(args[n], XmNdetailColumnHeading, h->details); n++;
+ XtSetArg(args[n], XmNdetailColumnHeadingCount, DETAIL_COUNT+1); n++;
+
+ h->scroll = XmCreateScrolledWindow(form, "scroll", NULL, 0);
+ XtManageChild(h->scroll);
+ h->container = XmCreateContainer(h->scroll,"container",
+ args,n);
+ XtManageChild(h->container);
+ XtAddCallback(h->container,XmNdefaultActionCallback,
+ browser_action_cb,h);
+
+ XtAddCallback(h->scroll, XmNtraverseObscuredCallback,
+ container_traverse_cb, NULL);
+ XtAddCallback(h->container,XmNconvertCallback,
+ container_convert_cb,h);
+
+ XtVaGetValues(h->scroll,XmNclipWindow,&clip,NULL);
+ XtAddEventHandler(clip,StructureNotifyMask,True,container_resize_eh,NULL);
+
+ /* menu - file */
+ menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0);
+ XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ push = XtVaCreateManagedWidget("close",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,destroy_cb,h->shell);
+
+ /* menu - edit */
+ menu = XmCreatePulldownMenu(menubar,"editM",NULL,0);
+ XtVaCreateManagedWidget("edit",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ container_menu_edit(menu,h->container, 0,1,0,0);
+
+ /* menu - view */
+ menu = XmCreatePulldownMenu(menubar,"viewM",NULL,0);
+ XtVaCreateManagedWidget("view",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ container_menu_view(menu,h->container);
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("filter",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,browser_filter,h);
+ push = XtVaCreateManagedWidget("freset",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,browser_nofilter,h);
+
+ /* menu - ops */
+ menu = XmCreatePulldownMenu(menubar,"opsM",NULL,0);
+ XtVaCreateManagedWidget("ops",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ container_menu_ops(menu,h->container);
+
+ /* menu - dirs (bookmarks) */
+ menu = XmCreatePulldownMenu(menubar,"dirsM",NULL,0);
+ XtVaCreateManagedWidget("dirs",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ for (list = cfg_entries_first(O_BOOKMARKS);
+ list != NULL;
+ list = cfg_entries_next(O_BOOKMARKS,list)) {
+ push = XtVaCreateManagedWidget(list,xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,browser_bookmark_cb,h);
+ }
+
+ /* read dir and show window */
+ container_spatial_cb(NULL,h->container,NULL);
+ browser_readdir(h);
+ XtPopup(h->shell,XtGrabNone);
+ ptr_register(h->shell);
+}
+
+void
+browser_ac(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ if (*num_params > 0)
+ browser_window(params[0]);
+ else
+ browser_window(".");
+}
diff --git a/browser.h b/browser.h
new file mode 100644
index 0000000..62979cf
--- /dev/null
+++ b/browser.h
@@ -0,0 +1,3 @@
+void browser_window(char *dirname);
+void browser_ac(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params);
diff --git a/color.c b/color.c
new file mode 100644
index 0000000..51d32ea
--- /dev/null
+++ b/color.c
@@ -0,0 +1,513 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <math.h>
+
+#include <X11/X.h>
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/Form.h>
+#include <Xm/Label.h>
+#include <Xm/DrawingA.h>
+#include <Xm/RowColumn.h>
+#include <Xm/PushB.h>
+#include <Xm/ToggleB.h>
+#include <Xm/Scale.h>
+#include <Xm/Separator.h>
+#include <Xm/Text.h>
+#include <Xm/SelectioB.h>
+
+#include "RegEdit.h"
+#include "ida.h"
+#include "x11.h"
+#include "readers.h"
+#include "viewer.h"
+#include "color.h"
+#include "lut.h"
+
+/* ---------------------------------------------------------------------- */
+
+#define HIST_SIZE 60
+
+struct ida_coledit;
+
+struct ida_hist {
+ /* x11 */
+ GC gc;
+ unsigned long color;
+
+ /* histogram */
+ Widget hist;
+ unsigned int max;
+ unsigned int data[256];
+
+ /* mapping */
+ Widget map;
+ struct op_map_parm_ch parm;
+
+ struct ida_coledit *up;
+};
+
+struct ida_coledit {
+ /* misc */
+ Widget dlg,form,vals,toggle;
+ Widget l,r,t,b,g;
+ int lock,apply;
+
+ /* histogram data */
+ struct ida_hist red;
+ struct ida_hist green;
+ struct ida_hist blue;
+ struct ida_hist *cur;
+};
+
+/* ---------------------------------------------------------------------- */
+
+static void
+color_calchist(struct ida_image *img, struct ida_coledit *me)
+{
+ unsigned char *pix;
+ unsigned int i,x,y,max;
+
+ pix = img->data;
+ for (y = 0; y < img->i.height; y++) {
+ for (x = 0; x < img->i.width; x++) {
+ me->red.data[pix[0]]++;
+ me->green.data[pix[1]]++;
+ me->blue.data[pix[2]]++;
+ pix += 3;
+ }
+ }
+ max = 0;
+ for (i = 0; i < 256; i++) {
+ if (max < me->red.data[i])
+ max = me->red.data[i];
+ if (max < me->green.data[i])
+ max = me->green.data[i];
+ if (max < me->blue.data[i])
+ max = me->blue.data[i];
+ }
+ me->red.max = max;
+ me->green.max = max;
+ me->blue.max = max;
+}
+
+static void
+color_update(struct ida_coledit *me, struct ida_hist *h, int text)
+{
+ struct op_map_parm param;
+ char tmp[32];
+
+ if (me->lock) {
+ if (&me->red != h)
+ me->red.parm = h->parm;
+ if (&me->green != h)
+ me->green.parm = h->parm;
+ if (&me->blue != h)
+ me->blue.parm = h->parm;
+ XClearArea(XtDisplay(me->red.hist), XtWindow(me->red.hist),
+ 0,0,0,0, True);
+ XClearArea(XtDisplay(me->red.map), XtWindow(me->red.map),
+ 0,0,0,0, True);
+ XClearArea(XtDisplay(me->green.hist), XtWindow(me->green.hist),
+ 0,0,0,0, True);
+ XClearArea(XtDisplay(me->green.map), XtWindow(me->green.map),
+ 0,0,0,0, True);
+ XClearArea(XtDisplay(me->blue.hist), XtWindow(me->blue.hist),
+ 0,0,0,0, True);
+ XClearArea(XtDisplay(me->blue.map), XtWindow(me->blue.map),
+ 0,0,0,0, True);
+ } else {
+ XClearArea(XtDisplay(h->hist), XtWindow(h->hist),
+ 0,0,0,0, True);
+ XClearArea(XtDisplay(h->map), XtWindow(h->map),
+ 0,0,0,0, True);
+ }
+ if ((me->lock || h == me->cur) && text >= 1) {
+ /* mouse-click updateable values */
+ sprintf(tmp,"%d",h->parm.left);
+ XmTextSetString(me->l,tmp);
+ sprintf(tmp,"%d",h->parm.right);
+ XmTextSetString(me->r,tmp);
+ }
+ if ((me->lock || h == me->cur) && text >= 2) {
+ /* others */
+ sprintf(tmp,"%d",h->parm.bottom);
+ XmTextSetString(me->b,tmp);
+ sprintf(tmp,"%d",h->parm.top);
+ XmTextSetString(me->t,tmp);
+ sprintf(tmp,"%.2f",h->parm.gamma);
+ XmTextSetString(me->g,tmp);
+ }
+
+ param.red = me->red.parm;
+ param.green = me->green.parm;
+ param.blue = me->blue.parm;
+ viewer_start_preview(ida,&desc_map,&param);
+}
+
+static void
+color_drawmap(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_hist *me = client_data;
+ XmDrawingAreaCallbackStruct *cb = calldata;
+ XGCValues values;
+ int left,right,top,bottom,i,val,x1,y1,x2,y2;
+ float p;
+
+ if (cb->reason == XmCR_EXPOSE) {
+ /* window needs redraw */
+ XExposeEvent *e = (XExposeEvent*)cb->event;
+ if (e->count)
+ return;
+ values.foreground = x11_gray;
+ XChangeGC(dpy,me->gc,GCForeground,&values);
+ left = me->parm.left * HIST_SIZE / 255;
+ right = me->parm.right * HIST_SIZE / 255;
+ bottom = me->parm.bottom * HIST_SIZE / 255;
+ top = me->parm.top * HIST_SIZE / 255;
+ if (me->parm.left > 0)
+ XFillRectangle(dpy,XtWindow(me->map),me->gc,
+ 0,0,left,HIST_SIZE);
+ if (me->parm.right < 255)
+ XFillRectangle(dpy,XtWindow(me->map),me->gc,
+ right,0,HIST_SIZE-right,HIST_SIZE);
+ values.foreground = me->color;
+ XChangeGC(dpy,me->gc,GCForeground,&values);
+ if (me->parm.left > 0)
+ XDrawLine(dpy,XtWindow(me->map),me->gc,
+ 0,HIST_SIZE-bottom,left,HIST_SIZE-bottom);
+ if (me->parm.right < 255)
+ XDrawLine(dpy,XtWindow(me->map),me->gc,
+ right,HIST_SIZE-top,HIST_SIZE,HIST_SIZE-top);
+ p = 1/me->parm.gamma;
+ x2 = y2 = 0;
+ for (i = left; i <= right; i++) {
+ val = pow((float)(i-left)/(right-left),p) * (top-bottom) + 0.5;
+ val += bottom;
+ if (val < 0) val = 0;
+ if (val > HIST_SIZE) val = HIST_SIZE;
+ x1 = x2;
+ y1 = y2;
+ x2 = i;
+ y2 = HIST_SIZE-val;
+ if (i > left)
+ XDrawLine(dpy,XtWindow(me->map),me->gc,
+ x1,y1,x2,y2);
+ }
+ }
+}
+
+static void
+color_drawhist(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_hist *me = client_data;
+ XmDrawingAreaCallbackStruct *cb = calldata;
+ XGCValues values;
+ int i,val;
+
+ if (cb->reason == XmCR_EXPOSE) {
+ /* window needs redraw */
+ XExposeEvent *e = (XExposeEvent*)cb->event;
+ if (e->count)
+ return;
+ values.foreground = x11_gray;
+ XChangeGC(dpy,me->gc,GCForeground,&values);
+ if (me->parm.left > 0)
+ XFillRectangle(dpy,XtWindow(me->hist),me->gc,
+ 0,0,me->parm.left,HIST_SIZE);
+ if (me->parm.right < 255)
+ XFillRectangle(dpy,XtWindow(me->hist),me->gc,
+ me->parm.right,0,256-me->parm.right,HIST_SIZE);
+ values.foreground = me->color;
+ XChangeGC(dpy,me->gc,GCForeground,&values);
+ for (i = 0; i < 256; i++) {
+ val = log(me->data[i])*HIST_SIZE/log(me->max);
+ XDrawLine(dpy,XtWindow(me->hist),me->gc,
+ i,HIST_SIZE,i,HIST_SIZE-val);
+ }
+ }
+}
+
+static void
+color_mouse(Widget widget, XtPointer client_data,
+ XEvent *ev, Boolean *cont)
+{
+ struct ida_hist *me = client_data;
+ int x,y;
+
+ switch (ev->type) {
+ case ButtonPress:
+ case ButtonRelease:
+ {
+ XButtonEvent *e = (XButtonEvent*)ev;
+
+ x = e->x;
+ y = e->y;
+ break;
+ }
+ case MotionNotify:
+ {
+ XMotionEvent *e = (XMotionEvent*)ev;
+
+ x = e->x;
+ y = e->y;
+ break;
+ default:
+ return;
+ }
+ }
+ if (x > (me->parm.right + me->parm.left)/2) {
+ me->parm.right = x;
+ if (me->parm.right > 255)
+ me->parm.right = 255;
+ if (me->parm.right < me->parm.left)
+ me->parm.right = me->parm.left;
+ } else {
+ me->parm.left = x;
+ if (me->parm.left < 0)
+ me->parm.left = 0;
+ if (me->parm.left > me->parm.right)
+ me->parm.left = me->parm.right;
+ }
+ color_update(me->up,me,1);
+}
+
+static void
+color_lock(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_coledit *me = client_data;
+ XmToggleButtonCallbackStruct *cb = calldata;
+ Widget label,button;
+
+ label = XmOptionLabelGadget(me->vals);
+ button = XmOptionButtonGadget(me->vals);
+ me->lock = cb->set;
+ XtVaSetValues(label,XtNsensitive,!me->lock,NULL);
+ XtVaSetValues(button,XtNsensitive,!me->lock,NULL);
+}
+
+static void
+color_vals(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_hist *cur = client_data;
+ struct ida_coledit *me = cur->up;
+
+ me->cur = cur;
+ color_update(me,cur,2);
+}
+
+static void
+color_text(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_coledit *me = client_data;
+ int left,right,bottom,top;
+ float gamma;
+
+ if (widget == me->l &&
+ 1 == sscanf(XmTextGetString(me->l),"%d",&left) &&
+ left >= 0 && left <= me->cur->parm.right) {
+ me->cur->parm.left = left;
+ }
+ if (widget == me->r &&
+ 1 == sscanf(XmTextGetString(me->r),"%d",&right) &&
+ me->cur->parm.left <= right && right <= 255) {
+ me->cur->parm.right = right;
+ }
+ if (widget == me->b &&
+ 1 == sscanf(XmTextGetString(me->b),"%d",&bottom) &&
+ bottom <= me->cur->parm.top) {
+ me->cur->parm.bottom = bottom;
+ }
+ if (widget == me->t &&
+ 1 == sscanf(XmTextGetString(me->t),"%d",&top) &&
+ me->cur->parm.bottom <= top) {
+ me->cur->parm.top = top;
+ }
+ if (widget == me->g &&
+ 1 == sscanf(XmTextGetString(me->g),"%f",&gamma)) {
+ me->cur->parm.gamma = gamma;
+ }
+ color_update(me,me->cur,0);
+}
+
+static void
+color_pick_ok(int x, int y, unsigned char *pix, XtPointer data)
+{
+ struct ida_coledit *me = data;
+ int max;
+
+ if (debug)
+ fprintf(stderr,"color_pick_ok: +%d+%d %d/%d/%d\n",
+ x,y, pix[0],pix[1],pix[2]);
+
+ max = 0;
+ if (max < pix[0])
+ max = pix[0];
+ if (max < pix[1])
+ max = pix[1];
+ if (max < pix[2])
+ max = pix[2];
+
+ XmToggleButtonSetState(me->toggle,False,True);
+ me->red.parm.right = (int)255 * pix[0] / max;
+ color_update(me,&me->red,1);
+ me->green.parm.right = (int)255 * pix[1] / max;
+ color_update(me,&me->green,1);
+ me->blue.parm.right = (int)255 * pix[2] / max;
+ color_update(me,&me->blue,1);
+
+ if (debug)
+ fprintf(stderr,"color_pick_ok: %d/%d/%d max=%d\n",
+ me->red.parm.right,
+ me->green.parm.right,
+ me->blue.parm.right,
+ max);
+}
+
+static void
+color_pick(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_coledit *me = client_data;
+ viewer_pick(ida,color_pick_ok,me);
+}
+
+static void
+color_createhist(Widget parent, char *name, unsigned long color,
+ struct ida_hist *me)
+{
+ char tmp[32];
+
+ sprintf(tmp,"h%s",name);
+ me->hist = XtVaCreateManagedWidget(tmp,xmDrawingAreaWidgetClass,parent,
+ XtNwidth,256,
+ XtNheight,HIST_SIZE,
+ NULL);
+ sprintf(tmp,"m%s",name);
+ me->map = XtVaCreateManagedWidget(tmp,xmDrawingAreaWidgetClass,parent,
+ XtNwidth,HIST_SIZE,
+ XtNheight,HIST_SIZE,
+ NULL);
+ XtAddEventHandler(me->hist,
+ ButtonPressMask |
+ ButtonReleaseMask |
+ ButtonMotionMask,
+ False,color_mouse,me);
+ XtAddCallback(me->hist,XmNexposeCallback,color_drawhist,me);
+ XtAddCallback(me->map,XmNexposeCallback,color_drawmap,me);
+ me->gc = XCreateGC(dpy,XtWindow(app_shell),0,NULL);
+ me->color = color;
+ me->parm = op_map_nothing;
+}
+
+static void
+color_button_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_coledit *me = client_data;
+ XmSelectionBoxCallbackStruct *cb = calldata;
+
+ if (cb->reason == XmCR_OK)
+ me->apply = 1;
+ XtDestroyWidget(XtParent(me->form));
+}
+
+static void
+color_destroy(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_coledit *me = client_data;
+ struct op_map_parm param;
+
+ if (me->apply) {
+ param.red = me->red.parm;
+ param.green = me->green.parm;
+ param.blue = me->blue.parm;
+ viewer_start_op(ida,&desc_map,&param);
+ } else
+ viewer_cancel_preview(ida);
+ viewer_unpick(ida);
+ free(me);
+}
+
+void
+color_init(struct ida_image *img)
+{
+ Widget menu,push,rc;
+ struct ida_coledit *me;
+ Arg args[2];
+
+ me = malloc(sizeof(*me));
+ memset(me,0,sizeof(*me));
+ color_calchist(img,me);
+
+ /* dialog shell */
+ me->dlg = XmCreatePromptDialog(app_shell,"color",NULL,0);
+ XmdRegisterEditres(XtParent(me->dlg));
+ XtUnmanageChild(XmSelectionBoxGetChild(me->dlg,XmDIALOG_SELECTION_LABEL));
+ XtUnmanageChild(XmSelectionBoxGetChild(me->dlg,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmSelectionBoxGetChild(me->dlg,XmDIALOG_TEXT));
+ me->form = XtVaCreateManagedWidget("form",xmFormWidgetClass,
+ me->dlg,NULL);
+ XtAddCallback(XtParent(me->dlg),XmNdestroyCallback,color_destroy,me);
+ XtAddCallback(me->dlg,XmNokCallback,color_button_cb,me);
+ XtAddCallback(me->dlg,XmNcancelCallback,color_button_cb,me);
+
+ /* histograms */
+ XtVaCreateManagedWidget("hist",xmLabelWidgetClass,
+ me->form,NULL);
+ color_createhist(me->form,"red", x11_red, &me->red);
+ color_createhist(me->form,"green",x11_green,&me->green);
+ color_createhist(me->form,"blue", x11_blue, &me->blue);
+ me->red.up = me;
+ me->green.up = me;
+ me->blue.up = me;
+ XtVaCreateManagedWidget("map",xmLabelWidgetClass,
+ me->form,NULL);
+
+ /* control */
+ me->toggle = XtVaCreateManagedWidget("lock",xmToggleButtonWidgetClass,
+ me->form,NULL);
+ XtAddCallback(me->toggle,XmNvalueChangedCallback,color_lock,me);
+ menu = XmCreatePulldownMenu(me->form,"valsM",NULL,0);
+ XtSetArg(args[0],XmNsubMenuId,menu);
+ me->vals = XmCreateOptionMenu(me->form,"vals",args,1);
+ XtManageChild(me->vals);
+ push = XtVaCreateManagedWidget("red",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,color_vals,&me->red);
+ push = XtVaCreateManagedWidget("green",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,color_vals,&me->green);
+ push = XtVaCreateManagedWidget("blue",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,color_vals,&me->blue);
+
+ /* in range */
+ rc = XtVaCreateManagedWidget("in",xmRowColumnWidgetClass,me->form,NULL);
+ XtVaCreateManagedWidget("label",xmLabelWidgetClass,rc,NULL);
+ me->l = XtVaCreateManagedWidget("left",xmTextWidgetClass,rc,NULL);
+ XtAddCallback(me->l,XmNvalueChangedCallback,color_text,me);
+ me->r = XtVaCreateManagedWidget("right",xmTextWidgetClass,rc,NULL);
+ XtAddCallback(me->r,XmNvalueChangedCallback,color_text,me);
+
+ /* out range */
+ rc = XtVaCreateManagedWidget("out",xmRowColumnWidgetClass,me->form,NULL);
+ XtVaCreateManagedWidget("label",xmLabelWidgetClass,rc,NULL);
+ me->b = XtVaCreateManagedWidget("bottom",xmTextWidgetClass,rc,NULL);
+ XtAddCallback(me->b,XmNvalueChangedCallback,color_text,me);
+ me->t = XtVaCreateManagedWidget("top",xmTextWidgetClass,rc,NULL);
+ XtAddCallback(me->t,XmNvalueChangedCallback,color_text,me);
+
+ /* gamma */
+ rc = XtVaCreateManagedWidget("gamma",xmRowColumnWidgetClass,me->form,NULL);
+ XtVaCreateManagedWidget("label",xmLabelWidgetClass,rc,NULL);
+ me->g = XtVaCreateManagedWidget("gamma",xmTextWidgetClass,rc,NULL);
+ XtAddCallback(me->g,XmNvalueChangedCallback,color_text,me);
+
+ /* testing stuff */
+ rc = XtVaCreateManagedWidget("pick",xmRowColumnWidgetClass,me->form,NULL);
+ push = XtVaCreateManagedWidget("white",xmPushButtonWidgetClass,rc,NULL);
+ XtAddCallback(push,XmNactivateCallback,color_pick,me);
+
+ XtManageChild(me->dlg);
+
+ me->cur = &me->red;
+ color_update(me,me->cur,2);
+ XmToggleButtonSetState(me->toggle,True,True);
+}
diff --git a/color.h b/color.h
new file mode 100644
index 0000000..f8da6b4
--- /dev/null
+++ b/color.h
@@ -0,0 +1 @@
+void color_init(struct ida_image *img);
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config.h
diff --git a/curl.c b/curl.c
new file mode 100644
index 0000000..498b093
--- /dev/null
+++ b/curl.c
@@ -0,0 +1,348 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <sys/select.h>
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include "curl.h"
+
+/* curl globals */
+static CURLM *curlm;
+static fd_set rd, wr, ex;
+
+/* my globals */
+static int url_debug = 0;
+static int url_timeout = 30;
+
+/* my structs */
+struct iobuf {
+ off_t start;
+ size_t size;
+ char *data;
+};
+
+struct url_state {
+ char *path;
+ CURL *curl;
+ char errmsg[CURL_ERROR_SIZE];
+ off_t curl_pos;
+ off_t buf_pos;
+ struct iobuf buf;
+ int eof;
+};
+
+/* ---------------------------------------------------------------------- */
+/* curl stuff */
+
+static void __attribute__ ((constructor)) curl_init(void)
+{
+ curl_global_init(CURL_GLOBAL_ALL);
+ curlm = curl_multi_init();
+}
+
+static void __attribute__ ((destructor)) curl_fini(void)
+{
+ curl_multi_cleanup(curlm);
+ curl_global_cleanup();
+}
+
+static void curl_free_buffer(struct iobuf *buf)
+{
+ if (buf->data) {
+ free(buf->data);
+ memset(buf,0,sizeof(*buf));
+ }
+}
+
+/* CURLOPT_WRITEFUNCTION */
+static int curl_write(void *data, size_t size, size_t nmemb, void *handle)
+{
+ struct url_state *h = handle;
+
+ curl_free_buffer(&h->buf);
+ h->buf.start = h->curl_pos;
+ h->buf.size = size * nmemb;
+ h->buf.data = malloc(h->buf.size);
+ memcpy(h->buf.data, data, h->buf.size);
+ if (url_debug)
+ fprintf(stderr," put %5d @ %5d\n",
+ (int)h->buf.size, (int)h->buf.start);
+
+ h->curl_pos += h->buf.size;
+ return h->buf.size;
+}
+
+/* do transfers */
+static int curl_xfer(struct url_state *h)
+{
+ CURLMcode rc;
+ struct timeval tv;
+ int count, maxfd;
+
+ FD_ZERO(&rd);
+ FD_ZERO(&wr);
+ FD_ZERO(&ex);
+ maxfd = -1;
+ rc = curl_multi_fdset(curlm, &rd, &wr, &ex, &maxfd);
+ if (CURLM_OK != rc) {
+ fprintf(stderr,"curl_multi_fdset: %d %s\n",rc,h->errmsg);
+ return -1;
+ }
+ if (-1 == maxfd) {
+ /* wait 0.1 sec */
+ if (url_debug)
+ fprintf(stderr,"wait 0.01 sec\n");
+ tv.tv_sec = 0;
+ tv.tv_usec = 100000;
+ } else {
+ /* wait for data */
+ if (url_debug)
+ fprintf(stderr,"select for data [maxfd=%d]\n",maxfd);
+ tv.tv_sec = url_timeout;
+ tv.tv_usec = 0;
+ }
+ switch (select(maxfd+1, &rd, &wr, &ex, &tv)) {
+ case -1:
+ /* Huh? */
+ perror("select");
+ exit(1);
+ case 0:
+ /* timeout */
+ return -1;
+ }
+ for (;;) {
+ rc = curl_multi_perform(curlm,&count);
+ if (CURLM_CALL_MULTI_PERFORM == rc)
+ continue;
+ if (CURLM_OK != rc) {
+ fprintf(stderr,"curl_multi_perform: %d %s\n",rc,h->errmsg);
+ return -1;
+ }
+ if (0 == count)
+ h->eof = 1;
+ break;
+ }
+ return 0;
+}
+
+/* curl setup */
+static int curl_setup(struct url_state *h)
+{
+ if (h->curl) {
+ curl_multi_remove_handle(curlm,h->curl);
+ curl_easy_cleanup(h->curl);
+ }
+
+ h->curl = curl_easy_init();
+ curl_easy_setopt(h->curl, CURLOPT_URL, h->path);
+ curl_easy_setopt(h->curl, CURLOPT_ERRORBUFFER, h->errmsg);
+ curl_easy_setopt(h->curl, CURLOPT_WRITEFUNCTION, curl_write);
+ curl_easy_setopt(h->curl, CURLOPT_WRITEDATA, h);
+ curl_multi_add_handle(curlm, h->curl);
+
+ h->buf_pos = 0;
+ h->curl_pos = 0;
+ h->eof = 0;
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+/* GNU glibc custom stream interface */
+
+static ssize_t url_read(void *handle, char *buf, size_t size)
+{
+ struct url_state *h = handle;
+ size_t bytes, total;
+ off_t off;
+ int count;
+
+ if (url_debug)
+ fprintf(stderr,"url_read(size=%d)\n",(int)size);
+ for (total = 0; size > 0;) {
+ if (h->buf.start <= h->buf_pos &&
+ h->buf.start + h->buf.size > h->buf_pos) {
+ /* can satisfy from current buffer */
+ bytes = h->buf.start + h->buf.size - h->buf_pos;
+ off = h->buf_pos - h->buf.start;
+ if (bytes > size)
+ bytes = size;
+ memcpy(buf+total, h->buf.data + off, bytes);
+ if (url_debug)
+ fprintf(stderr," get %5d @ %5d [%5d]\n",
+ (int)bytes, (int)h->buf_pos, (int)off);
+ size -= bytes;
+ total += bytes;
+ h->buf_pos += bytes;
+ continue;
+ }
+ if (h->buf_pos < h->buf.start) {
+ /* seeking backwards -- restart transfer */
+ if (url_debug)
+ fprintf(stderr," rewind\n");
+ curl_free_buffer(&h->buf);
+ curl_setup(h);
+ while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
+ /* nothing */;
+ }
+ if (h->eof)
+ /* stop on eof */
+ break;
+ /* fetch more data */
+ if (-1 == curl_xfer(h)) {
+ if (0 == total)
+ return -1;
+ break;
+ }
+ }
+ return total;
+}
+
+#if 0
+static ssize_t url_write(void *handle, const char *buf, size_t size)
+{
+ //struct url_state *h = handle;
+
+ if (url_debug)
+ fprintf(stderr,"url_write(size=%d)\n",(int)size);
+ return -1;
+}
+#endif
+
+static int url_seek(void *handle, off64_t *pos, int whence)
+{
+ struct url_state *h = handle;
+ int rc = 0;
+
+ if (url_debug)
+ fprintf(stderr,"url_seek(pos=%d,whence=%d)\n", (int)(*pos), whence);
+ switch (whence) {
+ case SEEK_SET:
+ h->buf_pos = *pos;
+ break;
+ case SEEK_CUR:
+ h->buf_pos += *pos;
+ break;
+ case SEEK_END:
+ rc = -1;
+ }
+ *pos = h->buf_pos;
+ return rc;
+}
+
+static int url_close(void *handle)
+{
+ struct url_state *h = handle;
+
+ if (url_debug)
+ fprintf(stderr,"url_close()\n");
+ curl_multi_remove_handle(curlm,h->curl);
+ curl_easy_cleanup(h->curl);
+ if (h->buf.data)
+ free(h->buf.data);
+ free(h->path);
+ free(h);
+ return 0;
+}
+
+static cookie_io_functions_t url_hooks = {
+ .read = url_read,
+#if 0
+ .write = url_write,
+#endif
+ .seek = url_seek,
+ .close = url_close,
+};
+
+static FILE *url_open(const char *path, const char *mode)
+{
+ FILE *fp;
+ struct url_state *h;
+ int count;
+
+ if (url_debug)
+ fprintf(stderr,"url_open(%s,%s)\n",path,mode);
+
+ h = malloc(sizeof(*h));
+ if (NULL == h)
+ goto err;
+ memset(h,0,sizeof(*h));
+
+ h->path = strdup(path);
+ if (NULL == h->path)
+ goto err;
+
+ /* setup */
+ curl_setup(h);
+ fp = fopencookie(h, mode, url_hooks);
+ if (NULL == fp)
+ goto err;
+
+ /* connect + start fetching */
+ while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(curlm,&count))
+ /* nothing */;
+
+ /* check for errors */
+ if (0 == count && NULL == h->buf.data) {
+ errno = ENOENT;
+ goto fetch_err;
+ }
+
+ /* all done */
+ return fp;
+
+
+ fetch_err:
+ curl_multi_remove_handle(curlm,h->curl);
+ err:
+ if (h->curl)
+ curl_easy_cleanup(h->curl);
+ if (h->path)
+ free(h->path);
+ if (h)
+ free(h);
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------- */
+/* hook into fopen using GNU ld's --wrap */
+
+int curl_is_url(const char *url)
+{
+ static char *protocols[] = {
+ "ftp://",
+ "http://",
+ NULL,
+ };
+ int i;
+
+ for (i = 0; protocols[i] != NULL; i++)
+ if (0 == strncasecmp(url, protocols[i], strlen(protocols[i])))
+ return 1;
+ return 0;
+}
+
+FILE *__wrap_fopen(const char *path, const char *mode);
+FILE *__real_fopen(const char *path, const char *mode);
+
+FILE *__wrap_fopen(const char *path, const char *mode)
+{
+ if (url_debug)
+ fprintf(stderr,"fopen(%s,%s)\n",path,mode);
+
+ /* catch URLs */
+ if (curl_is_url(path)) {
+ if (strchr(mode,'w')) {
+ fprintf(stderr,"write access over ftp/http is not supported, sorry\n");
+ return NULL;
+ }
+ return url_open(path,mode);
+ }
+
+ /* files passed to the real fopen */
+ return __real_fopen(path,mode);
+}
diff --git a/curl.h b/curl.h
new file mode 100644
index 0000000..7507d78
--- /dev/null
+++ b/curl.h
@@ -0,0 +1,5 @@
+#ifdef HAVE_LIBCURL
+extern int curl_is_url(const char *url);
+#else
+static inline int curl_is_url(const char *url) { return 0; }
+#endif
diff --git a/desktop.c b/desktop.c
new file mode 100644
index 0000000..d30d3c7
--- /dev/null
+++ b/desktop.c
@@ -0,0 +1,276 @@
+/*
+ * some code to handle desktop files
+ * http://www.freedesktop.org/Standards/desktop-entry-spec
+ *
+ * This is really very simple and basic: next to no locale handling,
+ * no caching, no other clever tricks ...
+ * ida + fbi only use the Comment= entry of .directory files.
+ *
+ * (c) 2004 Gerd Knorr <kraxel@bytesex.org>
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <iconv.h>
+#include <langinfo.h>
+
+#include "list.h"
+#include "desktop.h"
+
+extern int debug;
+
+/* ------------------------------------------------------------------------- */
+/* desktop files are in utf-8 */
+
+static int iconv_string(char *to, char *from,
+ char *src, char *dst, size_t max)
+{
+ size_t ilen = strlen(src);
+ size_t olen = max-1;
+ iconv_t ic;
+
+ ic = iconv_open(to,from);
+ if (NULL == ic)
+ return 0;
+
+ while (ilen > 0) {
+ if (-1 == iconv(ic,&src,&ilen,&dst,&olen)) {
+ /* skip + quote broken byte unless we are out of space */
+ if (E2BIG == errno)
+ break;
+ if (olen < 4)
+ break;
+ sprintf(dst,"\\x%02x",(int)(unsigned char)src[0]);
+ src += 1;
+ dst += 4;
+ ilen -= 1;
+ olen -= 4;
+ }
+ }
+ dst[0] = 0;
+ iconv_close(ic);
+ return max-1 - olen;
+}
+
+int utf8_to_locale(char *src, char *dst, size_t max)
+{
+ char *codeset = nl_langinfo(CODESET);
+ return iconv_string(codeset, "UTF-8", src, dst, max);
+}
+
+int locale_to_utf8(char *src, char *dst, size_t max)
+{
+ char *codeset = nl_langinfo(CODESET);
+ return iconv_string("UTF-8", codeset, src, dst, max);
+}
+
+/* ------------------------------------------------------------------------- */
+/* read/write desktop files */
+
+struct desktop_line {
+ struct list_head next;
+ char line[1024];
+};
+
+static int read_file(char *filename, struct list_head *file)
+{
+ struct desktop_line *l;
+ int len,count = 0;
+ FILE *fp;
+
+ INIT_LIST_HEAD(file);
+ fp = fopen(filename,"r");
+ if (NULL == fp) {
+ if (debug)
+ fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
+ return 0;
+ }
+ for (;;) {
+ l = malloc(sizeof(*l));
+ memset(l,0,sizeof(*l));
+ if (NULL == fgets(l->line,sizeof(l->line),fp)) {
+ free(l);
+ break;
+ }
+ len = strlen(l->line);
+ if (l->line[len-1] == '\n')
+ l->line[len-1] = 0;
+ list_add_tail(&l->next,file);
+ count++;
+ }
+ fclose(fp);
+ return count;
+}
+
+static int write_file(char *filename, struct list_head *file)
+{
+ struct desktop_line *l;
+ struct list_head *item;
+ FILE *fp;
+
+ fp = fopen(filename,"w");
+ if (NULL == fp) {
+ fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
+ return 0;
+ }
+ list_for_each(item,file) {
+ l = list_entry(item, struct desktop_line, next);
+ fprintf(fp,"%s\n",l->line);
+ }
+ fclose(fp);
+ return 0;
+}
+
+static int dump_file(struct list_head *file)
+{
+ struct desktop_line *l;
+ struct list_head *item;
+
+ fprintf(stderr,"\n");
+ fprintf(stderr,"+--------------------\n");
+ list_for_each(item,file) {
+ l = list_entry(item, struct desktop_line, next);
+ fprintf(stderr,"| %s\n",l->line);
+ }
+ return 0;
+}
+
+static int free_file(struct list_head *file)
+{
+ struct desktop_line *l;
+
+ while (!list_empty(file)) {
+ l = list_entry(file->next, struct desktop_line, next);
+ list_del(&l->next);
+ free(l);
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static char* get_entry(struct list_head *file, char *entry)
+{
+ struct desktop_line *l;
+ struct list_head *item;
+ int in_desktop_entry = 0;
+ int len = strlen(entry);
+
+ list_for_each(item,file) {
+ l = list_entry(item, struct desktop_line, next);
+ if (0 == strcmp(l->line,"[Desktop Entry]")) {
+ in_desktop_entry = 1;
+ continue;
+ }
+ if (0 == strncmp(l->line,"[",1)) {
+ in_desktop_entry = 0;
+ continue;
+ }
+ if (!in_desktop_entry)
+ continue;
+ if (0 == strncmp(l->line,entry,len))
+ return l->line+len;
+ }
+ return NULL;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static int add_line(struct list_head *file, char *line)
+{
+ struct desktop_line *add;
+
+ add = malloc(sizeof(*add));
+ memset(add,0,sizeof(*add));
+ snprintf(add->line,sizeof(add->line),"%s",line);
+ list_add_tail(&add->next,file);
+ return 0;
+}
+
+static int add_entry(struct list_head *file, char *entry, char *value)
+{
+ struct desktop_line *l,*add;
+ struct list_head *item;
+
+ list_for_each(item,file) {
+ l = list_entry(item, struct desktop_line, next);
+ if (0 != strcmp(l->line,"[Desktop Entry]"))
+ continue;
+ add = malloc(sizeof(*add));
+ memset(add,0,sizeof(*add));
+ snprintf(add->line,sizeof(add->line),"%s%s",entry,value);
+ list_add(&add->next,item);
+ return 0;
+ }
+ return -1;
+}
+
+static int set_entry(struct list_head *file, char *type, char *entry, char *value)
+{
+ struct desktop_line *l;
+ struct list_head *item;
+ int in_desktop_entry = 0;
+ int len = strlen(entry);
+
+ list_for_each(item,file) {
+ l = list_entry(item, struct desktop_line, next);
+ if (0 == strcmp(l->line,"[Desktop Entry]")) {
+ in_desktop_entry = 1;
+ continue;
+ }
+ if (0 == strncmp(l->line,"[",1)) {
+ in_desktop_entry = 0;
+ continue;
+ }
+ if (!in_desktop_entry)
+ continue;
+ if (0 == strncmp(l->line,entry,len)) {
+ snprintf(l->line,sizeof(l->line),"%s%s",entry,value);
+ return 0;
+ }
+ }
+ if (0 != add_entry(file,entry,value)) {
+ add_line(file,"[Desktop Entry]");
+ add_entry(file,"Type=",type);
+ add_entry(file,entry,value);
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------------------- */
+/* public interface */
+
+int desktop_read_entry(char *filename, char *entry, char *dest, size_t max)
+{
+ struct list_head file;
+ char *value;
+ int rc = 0;
+
+ read_file(filename,&file);
+ if (debug)
+ dump_file(&file);
+ value = get_entry(&file,entry);
+ if (NULL != value) {
+ rc = utf8_to_locale(value,dest,max);
+ if (rc && debug)
+ fprintf(stderr,"# %s\n",dest);
+ };
+ free_file(&file);
+ return rc;
+}
+
+int desktop_write_entry(char *filename, char *type, char *entry, char *value)
+{
+ struct list_head file;
+ char utf8[1024];
+
+ read_file(filename,&file);
+ locale_to_utf8(value,utf8,sizeof(utf8));
+ set_entry(&file,"Directory",entry,utf8);
+ write_file(filename,&file);
+ free_file(&file);
+ return 0;
+}
diff --git a/desktop.h b/desktop.h
new file mode 100644
index 0000000..b0cc2c4
--- /dev/null
+++ b/desktop.h
@@ -0,0 +1,7 @@
+/* these are "for free", the desktop file stuff needs this anyway ... */
+int utf8_to_locale(char *src, char *dst, size_t max);
+int locale_to_utf8(char *src, char *dst, size_t max);
+
+/* handle desktop files */
+int desktop_read_entry(char *filename, char *entry, char *dest, size_t max);
+int desktop_write_entry(char *filename, char *type, char *entry, char *value);
diff --git a/desktop/ida.desktop b/desktop/ida.desktop
new file mode 100644
index 0000000..9cdd34c
--- /dev/null
+++ b/desktop/ida.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Type=Application
+Encoding=UTF-8
+Name=ida
+GenericName=Image Viewer
+Exec=ida %F
+Terminal=no
+Categories=Motif;Graphics;Viewer
+MimeType=image/jpeg;image/tiff;image/png
diff --git a/dither.c b/dither.c
new file mode 100644
index 0000000..2eaeb31
--- /dev/null
+++ b/dither.c
@@ -0,0 +1,193 @@
+/*
+ * ordered dither rotines
+ *
+ * stolen from The GIMP and trimmed for speed
+ *
+ */
+
+#include <stdlib.h>
+#include "dither.h"
+
+#define DITHER_LEVEL 8
+
+void (*dither_line)(unsigned char *, unsigned char *, int, int);
+
+static long red_mult, green_mult;
+static long red_dither[256];
+static long green_dither[256];
+static long blue_dither[256];
+static long gray_dither[256];
+
+typedef unsigned long vector[DITHER_LEVEL];
+typedef vector matrix[DITHER_LEVEL];
+
+#if DITHER_LEVEL == 8
+#define DITHER_MASK 7
+static matrix DM =
+{
+ {0, 32, 8, 40, 2, 34, 10, 42},
+ {48, 16, 56, 24, 50, 18, 58, 26},
+ {12, 44, 4, 36, 14, 46, 6, 38},
+ {60, 28, 52, 20, 62, 30, 54, 22},
+ {3, 35, 11, 43, 1, 33, 9, 41},
+ {51, 19, 59, 27, 49, 17, 57, 25},
+ {15, 47, 7, 39, 13, 45, 5, 37},
+ {63, 31, 55, 23, 61, 29, 53, 21}
+};
+
+#endif
+
+#if DITHER_LEVEL == 4
+#define DITHER_MASK 3
+static matrix DM =
+{
+ {0, 8, 2, 10},
+ {12, 4, 14, 6},
+ {3, 11, 1, 9},
+ {15, 7, 13, 5}
+};
+
+#endif
+
+void
+init_dither(int shades_r, int shades_g, int shades_b, int shades_gray)
+{
+ int i, j;
+ unsigned char low_shade, high_shade;
+ unsigned short index;
+ float red_colors_per_shade;
+ float green_colors_per_shade;
+ float blue_colors_per_shade;
+ float gray_colors_per_shade;
+
+ red_mult = shades_g * shades_b;
+ green_mult = shades_b;
+
+ red_colors_per_shade = 256.0 / (shades_r - 1);
+ green_colors_per_shade = 256.0 / (shades_g - 1);
+ blue_colors_per_shade = 256.0 / (shades_b - 1);
+ gray_colors_per_shade = 256.0 / (shades_gray - 1);
+
+ /* this avoids a shift when checking these values */
+ for (i = 0; i < DITHER_LEVEL; i++)
+ for (j = 0; j < DITHER_LEVEL; j++)
+ DM[i][j] *= 0x10000;
+
+ /* setup arrays containing three bytes of information for red, green, & blue */
+ /* the arrays contain :
+ * 1st byte: low end shade value
+ * 2nd byte: high end shade value
+ * 3rd & 4th bytes: ordered dither matrix index
+ */
+
+ for (i = 0; i < 256; i++) {
+
+ /* setup the red information */
+ {
+ low_shade = (unsigned char) (i / red_colors_per_shade);
+ high_shade = low_shade + 1;
+
+ index = (unsigned short)
+ (((i - low_shade * red_colors_per_shade) / red_colors_per_shade) *
+ (DITHER_LEVEL * DITHER_LEVEL + 1));
+
+ low_shade *= red_mult;
+ high_shade *= red_mult;
+
+ red_dither[i] = (index << 16) + (high_shade << 8) + (low_shade);
+ }
+
+ /* setup the green information */
+ {
+ low_shade = (unsigned char) (i / green_colors_per_shade);
+ high_shade = low_shade + 1;
+
+ index = (unsigned short)
+ (((i - low_shade * green_colors_per_shade) / green_colors_per_shade) *
+ (DITHER_LEVEL * DITHER_LEVEL + 1));
+
+ low_shade *= green_mult;
+ high_shade *= green_mult;
+
+ green_dither[i] = (index << 16) + (high_shade << 8) + (low_shade);
+ }
+
+ /* setup the blue information */
+ {
+ low_shade = (unsigned char) (i / blue_colors_per_shade);
+ high_shade = low_shade + 1;
+
+ index = (unsigned short)
+ (((i - low_shade * blue_colors_per_shade) / blue_colors_per_shade) *
+ (DITHER_LEVEL * DITHER_LEVEL + 1));
+
+ blue_dither[i] = (index << 16) + (high_shade << 8) + (low_shade);
+ }
+
+ /* setup the gray information */
+ {
+ low_shade = (unsigned char) (i / gray_colors_per_shade);
+ high_shade = low_shade + 1;
+
+ index = (unsigned short)
+ (((i - low_shade * gray_colors_per_shade) / gray_colors_per_shade) *
+ (DITHER_LEVEL * DITHER_LEVEL + 1));
+
+ gray_dither[i] = (index << 16) + (high_shade << 8) + (low_shade);
+ }
+ }
+}
+
+void
+dither_line_color(unsigned char *src, unsigned char *dest, int y, int width)
+{
+ register long a, b;
+ long *ymod, xmod;
+
+ ymod = DM[y & DITHER_MASK];
+
+ while (width--) {
+ xmod = width & DITHER_MASK;
+
+ b = red_dither[*(src++)];
+ if (ymod[xmod] < b)
+ b >>= 8;
+
+ a = green_dither[*(src++)];
+ if (ymod[xmod] < a)
+ a >>= 8;
+ b += a;
+
+ a = blue_dither[*(src++)];
+ if (ymod[xmod] < a)
+ a >>= 8;
+ b += a;
+
+ *(dest++) = b & 0xff;
+ }
+}
+
+void
+dither_line_gray(unsigned char *src, unsigned char *dest, int y, int width)
+{
+ long *ymod, xmod;
+ register long a,g;
+
+ ymod = DM[y & DITHER_MASK];
+
+ while (width--) {
+ xmod = width & DITHER_MASK;
+
+#if 1
+ g = (src[0]*3 + src[1]*6 + src[2]) / 10;
+ a = gray_dither[g];
+ src += 3;
+#else
+ a = gray_dither[*(src++)];
+#endif
+ if (ymod[xmod] < a)
+ a >>= 8;
+
+ *(dest++) = a & 0xff;
+ }
+}
diff --git a/dither.h b/dither.h
new file mode 100644
index 0000000..b209de6
--- /dev/null
+++ b/dither.h
@@ -0,0 +1,6 @@
+
+extern void (*dither_line)(unsigned char *, unsigned char *, int, int);
+
+void init_dither(int, int, int, int);
+void dither_line_color(unsigned char *, unsigned char *, int, int);
+void dither_line_gray(unsigned char *, unsigned char *, int, int);
diff --git a/exiftran.c b/exiftran.c
new file mode 100644
index 0000000..6db3fd2
--- /dev/null
+++ b/exiftran.c
@@ -0,0 +1,262 @@
+/*
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include <libexif/exif-data.h>
+
+#include <jpeglib.h>
+#include "jpeg/transupp.h" /* Support routines for jpegtran */
+#include "jpegtools.h"
+#include "genthumbnail.h"
+
+/* ---------------------------------------------------------------------- */
+
+static void dump_exif(FILE *out, ExifData *ed)
+{
+ const char *title, *value;
+ ExifEntry *ee;
+ int tag,i;
+
+ for (i = 0; i < EXIF_IFD_COUNT; i++) {
+ fprintf(out," ifd %s\n", exif_ifd_get_name (i));
+ for (tag = 0; tag < 0xffff; tag++) {
+ title = exif_tag_get_title(tag);
+ if (!title)
+ continue;
+ ee = exif_content_get_entry (ed->ifd[i], tag);
+ if (NULL == ee)
+ continue;
+ value = exif_entry_get_value(ee);
+ fprintf(out," 0x%04x %-30s %s\n", tag, title, value);
+ }
+ }
+ if (ed->data && ed->size)
+ fprintf(out," thumbnail\n %d bytes data\n", ed->size);
+}
+
+static int dump_file(FILE *out, char *filename)
+{
+ ExifData *ed;
+
+ ed = exif_data_new_from_file (filename);
+ if (NULL == ed) {
+ fprintf(stderr,"%s: no EXIF data\n",filename);
+ return -1;
+ }
+
+ fprintf(out,"%s\n",filename);
+ dump_exif(out,ed);
+ fprintf(out,"--\n");
+
+ exif_data_unref (ed);
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#define THUMB_MAX 65536
+
+static void
+usage(FILE *fp, char *name)
+{
+ char *h;
+
+ if (NULL != (h = strrchr(name, '/')))
+ name = h+1;
+ fprintf(fp,
+ "usage: %s [ options ] file\n"
+ "\n"
+ "transform options:\n"
+ " -a automatic (using exif orientation tag)\n"
+ " -9 rotate by 90 degrees\n"
+ " -1 rotate by 180 degrees\n"
+ " -2 rotate by 270 degrees\n"
+ " -f flip vertical\n"
+ " -F flip horizontal\n"
+ " -t transpose\n"
+ " -T transverse\n"
+ "\n"
+ " -nt don't rotate exif thumbnail\n"
+ " -ni don't rotate jpeg image\n"
+ " -no don't update the orientation tag\n"
+ "\n"
+ "other options:\n"
+ " -h print this text\n"
+ " -d dump exif data\n"
+ " -c <text> create/update comment\n"
+ " -g (re)generate thumbnail\n"
+ " -o <file> output file\n"
+ " -i change files inplace\n"
+ " -b create a backup file (with -i)\n"
+ " -p preserve timestamps (with -i)\n"
+ "\n"
+ "-- \n"
+ "Gerd Knorr <kraxel@bytesex.org> [SUSE Labs]\n",
+ name);
+}
+
+int main(int argc, char *argv[])
+{
+ JXFORM_CODE transform = JXFORM_NONE;
+ unsigned char *comment = NULL;
+ unsigned char *outfile = NULL;
+ unsigned char *thumbnail = NULL;
+ int tsize = 0;
+ int inplace = 0;
+ unsigned int flags =
+ JFLAG_TRANSFORM_IMAGE |
+ JFLAG_TRANSFORM_THUMBNAIL |
+ JFLAG_UPDATE_ORIENTATION;
+ int dump = 0;
+ int i, c, rc;
+
+ for (;;) {
+ c = getopt(argc, argv, "hbpid912fFtTagc:o:n:");
+ if (c == -1)
+ break;
+ switch (c) {
+ case '9':
+ transform = JXFORM_ROT_90;
+ break;
+ case '1':
+ transform = JXFORM_ROT_180;
+ break;
+ case '2':
+ transform = JXFORM_ROT_270;
+ break;
+ case 'f':
+ transform = JXFORM_FLIP_V;
+ break;
+ case 'F':
+ transform = JXFORM_FLIP_H;
+ break;
+ case 't':
+ transform = JXFORM_TRANSPOSE;
+ break;
+ case 'T':
+ transform = JXFORM_TRANSVERSE;
+ break;
+ case 'a':
+ transform = -1; /* automagic */
+ break;
+
+ case 'n':
+ /* don't ... */
+ switch (optarg[0]) {
+ case 't':
+ flags &= ~JFLAG_TRANSFORM_THUMBNAIL;
+ break;
+ case 'i':
+ flags &= ~JFLAG_TRANSFORM_IMAGE;
+ break;
+ case 'o':
+ flags &= ~JFLAG_UPDATE_ORIENTATION;
+ break;
+ default:
+ fprintf(stderr,"unknown option -n%c\n",optarg[0]);
+ exit(1);
+ }
+ break;
+
+ case 'c':
+ flags |= JFLAG_UPDATE_COMMENT;
+ comment = optarg;
+ break;
+ case 'g':
+ flags |= JFLAG_UPDATE_THUMBNAIL;
+ break;
+ case 'o':
+ outfile = optarg;
+ break;
+ case 'd':
+ dump = 1;
+ break;
+
+ case 'b':
+ flags |= JFLAG_FILE_BACKUP;
+ break;
+ case 'p':
+ flags |= JFLAG_FILE_KEEP_TIME;
+ break;
+ case 'i':
+ inplace = 1;
+ break;
+
+ case 'h':
+ usage(stdout,argv[0]);
+ exit(0);
+ default:
+ usage(stderr,argv[0]);
+ exit(1);
+ }
+ }
+
+ /* sanity checks on the arguments */
+ if (optind == argc) {
+ fprintf(stderr,
+ "no image file specified (try -h for more info)\n");
+ exit(1);
+ }
+
+ /* read-only stuff */
+ if (dump) {
+ rc = 0;
+ for (i = optind; i < argc; i++) {
+ if (0 != dump_file(stdout,argv[i]))
+ rc = 1;
+ }
+ return rc;
+ }
+
+ /* r/w sanity checks */
+ if (NULL != outfile && optind+1 > argc) {
+ fprintf(stderr,
+ "when specifying a output file you can process\n"
+ "one file at a time only (try -h for more info).\n");
+ exit(1);
+ }
+ if (NULL == outfile && 0 == inplace) {
+ fprintf(stderr,
+ "you have to either specify a output file (-o <file>)\n"
+ "or enable inplace editing (-i). Try -h for more info.\n");
+ exit(1);
+ }
+ if (JXFORM_NONE == transform &&
+ !(flags & JFLAG_UPDATE_COMMENT) &&
+ !(flags & JFLAG_UPDATE_THUMBNAIL)) {
+ fprintf(stderr,
+ "What do you want to do today? Neither a new comment nor a\n"
+ "tranformation operation was specified (try -h for more info).\n");
+ exit(1);
+ }
+
+ /* do actual update work */
+ if (outfile) {
+ if (flags & JFLAG_UPDATE_THUMBNAIL) {
+ thumbnail = malloc(THUMB_MAX);
+ tsize = create_thumbnail(argv[optind],thumbnail,THUMB_MAX);
+ }
+ return jpeg_transform_files(argv[optind], outfile, transform,
+ comment, thumbnail, tsize, flags);
+ } else {
+ rc = 0;
+ for (i = optind; i < argc; i++) {
+ fprintf(stderr,"processing %s\n",argv[i]);
+ if (flags & JFLAG_UPDATE_THUMBNAIL) {
+ thumbnail = malloc(THUMB_MAX);
+ tsize = create_thumbnail(argv[i],thumbnail,THUMB_MAX);
+ }
+ if (0 != jpeg_transform_inplace(argv[i], transform, comment,
+ thumbnail, tsize, flags))
+ rc = 1;
+ }
+ return rc;
+ }
+}
diff --git a/exiftran.man b/exiftran.man
new file mode 100644
index 0000000..4f3efde
--- /dev/null
+++ b/exiftran.man
@@ -0,0 +1,106 @@
+.TH exiftran 1 "(c) 2003,04 Gerd Knorr"
+.SH NAME
+exiftran - transform digital camera jpeg images
+.SH SYNOPSIS
+.B exiftran [ options ] file(s)
+.SH DESCRIPTION
+.B exiftran
+is a command line utility to transform digital image jpeg images. It
+can do lossless rotations like jpegtran, but unlike jpegtran it cares
+about the EXIF data: It can rotate images automatically by checking
+the exif orientation tag, it updates the exif informaton if needed
+(image dimension, orientation), it also rotates the exif thumbnail.
+It can process multiple images at once.
+.SH TRANSFORM OPTIONS
+.TP
+.B -a
+automatic (using exif orientation tag)
+.TP
+.B -9
+rotate by 90 degrees
+.TP
+.B -1
+rotate by 180 degrees
+.TP
+.B -2
+rotate by 270 degrees
+.TP
+.B -f
+flip vertical
+.TP
+.B -F
+flip horizontal
+.TP
+.B -t
+transpose
+.TP
+.B -T
+transverse
+.TP
+.B -nt
+Don't rotate exif thumbnail.
+.TP
+.B -ni
+Don't rotate jpeg image. You might need this or or the -nt option to
+fixup things in case you rotated the image with some utility which
+ignores the exif thumbnail. Just generating a new thumbnail with -g is
+another way to fix it.
+.TP
+.B -no
+Don't update the orientation tag. By default exiftran sets the
+orientation to "1" (no transformation needed) to avoid other
+exif-aware applications try to rotate the already-rotated image
+again.
+.SH OTHER OPTIONS
+.TP
+.B -h
+print a short help text
+.TP
+.B -d
+Dump exif data for the file(s).
+.TP
+.B -c <text>
+Set jpeg comment tag to <text>.
+.TP
+.B -g
+(re)generate EXIF thumbnail.
+.TP
+.B -o <file>
+Specify output file. Only one input file is allowed in this mode.
+.TP
+.B -i
+Enable inplace editing of the images. Exiftran allows multiple input
+files then. You must specify either this option or a output file with
+-o for all operations which modify the image (i.e. everything but -d
+right now).
+.TP
+.B -b
+Create a backup file when doing inplace editing.
+.TP
+.B -p
+Preserve timestamps (atime + mtime) when doing inplace editing.
+.SH EXAMPLES
+Autorotate all jpeg files in the current directory:
+.nf
+ exiftran -ai *.jpeg
+.fi
+.SH SEE ALSO
+jpegtran(1), exif(1)
+.SH AUTHOR
+Gerd Knorr <kraxel@bytesex.org>
+.SH COPYRIGHT
+Copyright (C) 2002 Gerd Knorr <kraxel@bytesex.org>
+.P
+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.
+.P
+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.
+.P
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
diff --git a/fallback.pl b/fallback.pl
new file mode 100755
index 0000000..03706c4
--- /dev/null
+++ b/fallback.pl
@@ -0,0 +1,26 @@
+#!/usr/bin/perl -w
+#
+# build header file from
+#
+use strict;
+
+while (my $line = <>) {
+ chomp $line;
+
+ # ignore comments
+ next if $line =~ /^!/;
+# next if $line =~ /^\s*$/;
+
+ # quote stuff
+ $line =~ s/\\/\\\\/g;
+ $line =~ s/\"/\\\"/g;
+
+ # continued line?
+ if ($line =~ s/\\\\$//) {
+ printf "\"%s\"\n",$line;
+ next;
+ }
+
+ # write out
+ printf "\"%s\",\n",$line;
+}
diff --git a/fb-gui.c b/fb-gui.c
new file mode 100644
index 0000000..3c6328c
--- /dev/null
+++ b/fb-gui.c
@@ -0,0 +1,189 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/fb.h>
+
+#include "fbtools.h"
+#include "fs.h"
+#include "fb-gui.h"
+
+/* public */
+int visible = 1;
+
+/* private */
+static struct fs_font *f;
+static char *x11_font = "10x20";
+
+static int ys = 3;
+static int xs = 10;
+
+/* ---------------------------------------------------------------------- */
+/* clear screen (areas) */
+
+void fb_clear_mem(void)
+{
+ if (visible)
+ fb_memset(fb_mem,0,fb_fix.smem_len);
+}
+
+void fb_clear_screen(void)
+{
+ if (visible)
+ fb_memset(fb_mem,0,fb_fix.line_length * fb_var.yres);
+}
+
+static void fb_clear_rect(int x1, int x2, int y1,int y2)
+{
+ unsigned char *ptr;
+ int y,h;
+
+ if (!visible)
+ return;
+
+ if (x2 < x1)
+ h = x2, x2 = x1, x1 = h;
+ if (y2 < y1)
+ h = y2, y2 = y1, y1 = h;
+ ptr = fb_mem;
+ ptr += y1 * fb_fix.line_length;
+ ptr += x1 * fs_bpp;
+
+ for (y = y1; y <= y2; y++) {
+ fb_memset(ptr, 0, (x2 - x1 + 1) * fs_bpp);
+ ptr += fb_fix.line_length;
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+/* draw lines */
+
+static void fb_setpixel(int x, int y, unsigned int color)
+{
+ unsigned char *ptr;
+
+ ptr = fb_mem;
+ ptr += y * fb_fix.line_length;
+ ptr += x * fs_bpp;
+ fs_setpixel(ptr, color);
+}
+
+static void fb_line(int x1, int x2, int y1,int y2)
+{
+ int x,y,h;
+ float inc;
+
+ if (x2 < x1)
+ h = x2, x2 = x1, x1 = h;
+ if (y2 < y1)
+ h = y2, y2 = y1, y1 = h;
+
+ if (x2 - x1 < y2 - y1) {
+ inc = (float)(x2-x1)/(float)(y2-y1);
+ for (y = y1; y <= y2; y++) {
+ x = x1 + inc * (y - y1);
+ fb_setpixel(x,y,fs_white);
+ }
+ } else {
+ inc = (float)(y2-y1)/(float)(x2-x1);
+ for (x = x1; x <= x2; x++) {
+ y = y1 + inc * (x - x1);
+ fb_setpixel(x,y,fs_white);
+ }
+ }
+}
+
+static void fb_rect(int x1, int x2, int y1,int y2)
+{
+ fb_line(x1, x2, y1, y1);
+ fb_line(x1, x2, y2, y2);
+ fb_line(x1, x1, y1, y2);
+ fb_line(x2, x2, y1, y2);
+}
+
+/* ---------------------------------------------------------------------- */
+/* text stuff */
+
+void fb_text_init1(char *font)
+{
+ char *fonts[2] = { font, NULL };
+
+ if (NULL == f)
+ f = fs_consolefont(font ? fonts : NULL);
+#ifndef X_DISPLAY_MISSING
+ if (NULL == f && 0 == fs_connect(NULL))
+ f = fs_open(font ? font : x11_font);
+#endif
+ if (NULL == f) {
+ fprintf(stderr,"no font available\n");
+ exit(1);
+ }
+}
+
+void fb_text_init2(void)
+{
+ fs_init_fb(255);
+}
+
+int fb_font_width(void)
+{
+ return f->width;
+}
+
+void fb_status_line(unsigned char *msg)
+{
+ int y;
+
+ if (!visible)
+ return;
+ y = fb_var.yres - f->height - ys;
+ fb_memset(fb_mem + fb_fix.line_length * y, 0,
+ fb_fix.line_length * (f->height+ys));
+ fb_line(0, fb_var.xres, y, y);
+ fs_puts(f, 0, y+ys, msg);
+}
+
+void fb_edit_line(unsigned char *str, int pos)
+{
+ int x,y;
+
+ if (!visible)
+ return;
+ y = fb_var.yres - f->height - ys;
+ x = pos * f->width;
+ fb_memset(fb_mem + fb_fix.line_length * y, 0,
+ fb_fix.line_length * (f->height+ys));
+ fb_line(0, fb_var.xres, y, y);
+ fs_puts(f, 0, y+ys, str);
+ fb_line(x, x + f->width, fb_var.yres-1, fb_var.yres-1);
+ fb_line(x, x + f->width, fb_var.yres-2, fb_var.yres-2);
+}
+
+void fb_text_box(int x, int y, char *lines[], unsigned int count)
+{
+ unsigned int i,len,max, x1, x2, y1, y2;
+
+ if (!visible)
+ return;
+
+ max = 0;
+ for (i = 0; i < count; i++) {
+ len = strlen(lines[i]);
+ if (max < len)
+ max = len;
+ }
+ x1 = x;
+ x2 = x + max * f->width;
+ y1 = y;
+ y2 = y + count * f->height;
+
+ x += xs; x2 += 2*xs;
+ y += ys; y2 += 2*ys;
+
+ fb_clear_rect(x1, x2, y1, y2);
+ fb_rect(x1, x2, y1, y2);
+ for (i = 0; i < count; i++) {
+ fs_puts(f,x,y,lines[i]);
+ y += f->height;
+ }
+}
+
diff --git a/fb-gui.h b/fb-gui.h
new file mode 100644
index 0000000..1e7d7b8
--- /dev/null
+++ b/fb-gui.h
@@ -0,0 +1,11 @@
+extern int visible;
+
+void fb_clear_mem(void);
+void fb_clear_screen(void);
+
+void fb_text_init1(char *font);
+void fb_text_init2(void);
+int fb_font_width(void);
+void fb_status_line(unsigned char *msg);
+void fb_edit_line(unsigned char *str, int pos);
+void fb_text_box(int x, int y, char *lines[], unsigned int count);
diff --git a/fbgs b/fbgs
new file mode 100755
index 0000000..cff56e4
--- /dev/null
+++ b/fbgs
@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# tmp dir
+DIR="${TMPDIR-/var/tmp}/fbps-$$"
+mkdir -p $DIR || exit 1
+trap "rm -rf $DIR" EXIT
+
+# parse options
+fbiopts=""
+gsopts=""
+passwd=""
+opt=1
+while test "$opt" = "1"; do
+ case "$1" in
+ -l) gsopts="$gsopts -r100x100"
+ shift
+ ;;
+ -xl) gsopts="$gsopts -r120x120"
+ shift
+ ;;
+ -xxl) gsopts="$gsopts -r150x150"
+ shift
+ ;;
+ -q | -a)
+ fbiopts="$fbiopts $1"
+ shift
+ ;;
+ -d | -m | -t | -g | -f)
+ fbiopts="$fbiopts $1 $2"
+ shift; shift
+ ;;
+ -p) password="$2"
+ shift; shift
+ ;;
+ -h) echo fixme: help text
+ exit 1
+ ;;
+ -*) echo "unknown option: $1"
+ exit 1
+ ;;
+ *) opt=0
+ ;;
+ esac
+done
+
+# run ghostscript
+echo
+echo "### rendering pages, please wait ... ###"
+echo
+gs -dSAVER -dNOPAUSE -dBATCH \
+ -sPDFPassword="$password" \
+ -sDEVICE=tiffpack -sOutputFile=$DIR/ps%03d.tiff \
+ $gsopts \
+ "$1"
+
+# tell the user we are done :-)
+echo -ne "\\007"
+
+# sanity check
+pages=`ls $DIR/ps*.tiff 2>/dev/null | wc -l`
+if test "$pages" -eq "0"; then
+ echo
+ echo "Oops: ghostscript wrote no pages?"
+ echo
+ exit 1
+fi
+
+# show pages
+fbi $fbiopts -P $DIR/ps*.tiff
diff --git a/fbgs.man b/fbgs.man
new file mode 100644
index 0000000..b53020a
--- /dev/null
+++ b/fbgs.man
@@ -0,0 +1,20 @@
+.TH fbps 1 "(c) 1999-2003 Gerd Knorr"
+.SH NAME
+fbgs - poor man's PostScript/pdf viewer for the linux
+framebuffer console
+.SH SYNOPSIS
+.B fbgs [ options ] file
+.SH DESCRIPTION
+.B fbgs
+is a simple wrapper script which takes a PostScript or pdf
+file as input, renders the pages using ghostscript into a
+temporarely directory and finally calls fbi to display them.
+.SH OPTIONS
+fbps understands all fbi options (they are passed throuth).
+Additionally you can specify -l, -xl or -xxl to get the pages
+rendered with 100, 120 or 150 dpi (default is 75). You can
+use option -p <password> if your PDF file requires password.
+.SH SEE ALSO
+fbi(1), gs(1)
+.SH AUTHOR
+Gerd Knorr <kraxel@bytesex.org>
diff --git a/fbi.c b/fbi.c
new file mode 100644
index 0000000..3ea278a
--- /dev/null
+++ b/fbi.c
@@ -0,0 +1,1552 @@
+/*
+ * image viewer, for framebuffer devices
+ *
+ * (c) 1998-2002 Gerd Knorr <kraxel@bytesex.org>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <termios.h>
+#include <getopt.h>
+#include <math.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <ctype.h>
+#include <locale.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <linux/fb.h>
+
+#include <jpeglib.h>
+
+#include <libexif/exif-data.h>
+
+#ifdef HAVE_LIBLIRC
+# include "lirc/lirc_client.h"
+# include "lirc.h"
+#endif
+
+#include "readers.h"
+#include "dither.h"
+#include "fbtools.h"
+#include "fb-gui.h"
+#include "filter.h"
+#include "desktop.h"
+#include "list.h"
+
+#include "jpeg/transupp.h" /* Support routines for jpegtran */
+#include "jpegtools.h"
+
+#define TRUE 1
+#define FALSE 0
+#define MAX(x,y) ((x)>(y)?(x):(y))
+#define MIN(x,y) ((x)<(y)?(x):(y))
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+#define KEY_EOF -1 /* ^D */
+#define KEY_ESC -2
+#define KEY_SPACE -3
+#define KEY_Q -4
+#define KEY_PGUP -5
+#define KEY_PGDN -6
+#define KEY_TIMEOUT -7
+#define KEY_TAGFILE -8
+#define KEY_PLUS -9
+#define KEY_MINUS -10
+#define KEY_VERBOSE -11
+#define KEY_ASCALE -12
+#define KEY_DESC -13
+
+/* with arg */
+#define KEY_GOTO -100
+#define KEY_SCALE -101
+
+/* edit */
+#define KEY_DELETE -200
+#define KEY_ROT_CW -201
+#define KEY_ROT_CCW -202
+
+#define DEFAULT_DEVICE "/dev/fb0"
+
+/* ---------------------------------------------------------------------- */
+
+/* lirc fd */
+int lirc = -1;
+
+/* variables for read_image */
+int32_t lut_red[256], lut_green[256], lut_blue[256];
+int dither = FALSE, pcd_res = 3, steps = 50;
+int textreading = 0, redraw = 0, statusline = 1;
+int new_image;
+int left, top;
+
+/* file list */
+struct flist {
+ int nr;
+ int tag;
+ char *name;
+ struct list_head list;
+};
+static LIST_HEAD(flist);
+static int fcount;
+static struct flist *fcurrent;
+
+/* framebuffer */
+char *fbdev = NULL;
+char *fbmode = NULL;
+int fd, switch_last, debug;
+
+unsigned short red[256], green[256], blue[256];
+struct fb_cmap cmap = { 0, 256, red, green, blue };
+
+static float fbgamma = 1;
+
+/* Command line options. */
+
+int autodown = 0;
+int autoup = 0;
+int comments = 0;
+
+struct option fbi_options[] = {
+ {"version", no_argument, NULL, 'V'}, /* version */
+ {"help", no_argument, NULL, 'h'}, /* help */
+ {"device", required_argument, NULL, 'd'}, /* device */
+ {"mode", required_argument, NULL, 'm'}, /* video mode */
+ {"gamma", required_argument, NULL, 'g'}, /* set gamma */
+ {"quiet", no_argument, NULL, 'q'}, /* quiet */
+ {"verbose", no_argument, NULL, 'v'}, /* verbose */
+ {"scroll", required_argument, NULL, 's'}, /* set scrool */
+ {"timeout", required_argument, NULL, 't'}, /* timeout value */
+ {"once", no_argument, NULL, '1'}, /* loop only once */
+ {"resolution", required_argument, NULL, 'r'}, /* select resolution */
+ {"random", no_argument, NULL, 'u'}, /* randomize images */
+ {"font", required_argument, NULL, 'f'}, /* font */
+ {"autozoom", no_argument, NULL, 'a'},
+ {"edit", no_argument, NULL, 'e'}, /* enable editing */
+ {"list", required_argument, NULL, 'l'},
+ {"vt", required_argument, NULL, 'T'},
+ {"backup", no_argument, NULL, 'b'},
+ {"preserve", no_argument, NULL, 'p'},
+
+ /* long-only options */
+ {"autoup", no_argument, &autoup, 1 },
+ {"autodown", no_argument, &autodown, 1 },
+ {"comments", no_argument, &comments, 1 },
+ {0,0,0,0}
+};
+
+/* font handling */
+static char *fontname = NULL;
+
+/* ---------------------------------------------------------------------- */
+
+static void
+version(void)
+{
+ fprintf(stderr, "fbi version " VERSION
+ " (c) 1999-2003 Gerd Knorr; compiled on %s.\n", __DATE__ );
+}
+
+static void
+usage(char *name)
+{
+ char *h;
+
+ if (NULL != (h = strrchr(name, '/')))
+ name = h+1;
+ fprintf(stderr,
+ "\n"
+ "This program displays images using the Linux framebuffer device.\n"
+ "Supported formats: PhotoCD, jpeg, ppm, gif, tiff, xwd, bmp, png.\n"
+ "It tries to use ImageMagick's convert for unknown file formats.\n"
+ "\n"
+ " Usage: %s [ options ] file1 file2 ... fileN\n"
+ "\n"
+ " --help [-h] Print this text\n"
+ " --version [-V] Show the fbi version number\n"
+ " --device [-d] dev Framebuffer device [%s]\n"
+ " --mode [-m] mode Video mode (must be listed in /etc/fb.modes)\n"
+ " - Default is current mode.\n"
+ " --gamma [-g] f Set gamma\n"
+ " --scroll [-s] n Set scroll steps in pixels (default: 50)\n"
+ " --quiet [-q] don't print anything at all\n"
+ " --verbose [-v] show print filenames all the time\n"
+ " --timeout [-t] n Load next image after N sec without any keypress\n"
+ " --once [-1] Don't loop (for use with -t).\n"
+ " --resolution [-r] n Select resolution [1..5] (PhotoCD)\n"
+ " --random [-u] Show file1 .. fileN in a random order\n"
+ " --font [-f] fn Use font fn (either console psf file or\n"
+ " X11 font spec if a font server is available\n"
+ " --autozoom [-a] Automagically pick useful zoom factor.\n"
+ " --autoup Like the above, but upscale only.\n"
+ " --autodown Like the above, but downscale only.\n"
+ " --edit [-e] enable editing commands (see man page).\n"
+ " --backup [-b] create backup files when editing.\n"
+ " --preserve [-p] preserve timestamps when editing.\n"
+ " --list [-l] file read list of images from file\n"
+ " --comments display image comments\n"
+ " --vt [-T] vt start on console #vt\n"
+ "\n"
+ "Large images can be scrolled using the cursor keys. Zoom in/out\n"
+ "works with '+' and '-'. Use ESC or 'q' to quit. Space and PgDn\n"
+ "show the next, PgUp shows the previous image. Jumping to a image\n"
+ "works with <number>g. Return acts like Space but additionally\n"
+ "prints the filename of the currently displayed image to stdout.\n"
+ "\n",
+ name, fbdev ? fbdev : "/dev/fb0");
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int flist_add(char *filename)
+{
+ struct flist *f;
+
+ f = malloc(sizeof(*f));
+ memset(f,0,sizeof(*f));
+ f->name = strdup(filename);
+ list_add_tail(&f->list,&flist);
+ return 0;
+}
+
+static int flist_add_list(char *listfile)
+{
+ char filename[256];
+ FILE *list;
+
+ list = fopen(listfile,"r");
+ if (NULL == list) {
+ fprintf(stderr,"open %s: %s\n",listfile,strerror(errno));
+ return -1;
+ }
+ while (NULL != fgets(filename,sizeof(filename)-1,list)) {
+ size_t off = strcspn(filename,"\r\n");
+ if (off)
+ filename[off] = 0;
+ flist_add(filename);
+ }
+ fclose(list);
+ return 0;
+}
+
+static int flist_del(struct flist *f)
+{
+ list_del(&f->list);
+ free(f->name);
+ free(f);
+ return 0;
+}
+
+static void flist_renumber(void)
+{
+ struct list_head *item;
+ struct flist *f;
+ int i = 0;
+
+ list_for_each(item,&flist) {
+ f = list_entry(item, struct flist, list);
+ f->nr = ++i;
+ }
+ fcount = i;
+}
+
+static int flist_islast(struct flist *f)
+{
+ return (&flist == f->list.next) ? 1 : 0;
+}
+
+static int flist_isfirst(struct flist *f)
+{
+ return (&flist == f->list.prev) ? 1 : 0;
+}
+
+static struct flist* flist_first(void)
+{
+ return list_entry(flist.next, struct flist, list);
+}
+
+static struct flist* flist_next(struct flist *f, int eof, int loop)
+{
+ if (flist_islast(f)) {
+ if (eof)
+ return NULL;
+ if (loop)
+ return flist_first();
+ return f;
+ }
+ return list_entry(f->list.next, struct flist, list);
+}
+
+static struct flist* flist_prev(struct flist *f)
+{
+ if (flist_isfirst(f))
+ return f;
+ return list_entry(f->list.prev, struct flist, list);
+}
+
+static struct flist* flist_goto(int dest)
+{
+ struct list_head *item;
+ struct flist *f;
+
+ list_for_each(item,&flist) {
+ f = list_entry(item, struct flist, list);
+ if (f->nr == dest)
+ return f;
+ }
+ return NULL;
+}
+
+static void flist_randomize(void)
+{
+ struct flist *f;
+ int count;
+
+ srand((unsigned)time(NULL));
+ flist_renumber();
+ for (count = fcount; count > 0; count--) {
+ f = flist_goto((rand() % count)+1);
+ list_del(&f->list);
+ list_add_tail(&f->list,&flist);
+ flist_renumber();
+ }
+}
+
+static void flist_print_tagged(FILE *fp)
+{
+ struct list_head *item;
+ struct flist *f;
+
+ list_for_each(item,&flist) {
+ f = list_entry(item, struct flist, list);
+ if (f->tag)
+ fprintf(fp,"%s\n",f->name);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void status(unsigned char *desc, char *info)
+{
+ int chars, ilen;
+ char *str;
+
+ if (!statusline)
+ return;
+ chars = fb_var.xres / fb_font_width();
+ str = malloc(chars+1);
+ if (info) {
+ ilen = strlen(info);
+ sprintf(str, "%-*.*s [ %s ] H - Help",
+ chars-14-ilen, chars-14-ilen, desc, info);
+ } else {
+ sprintf(str, "%-*.*s | H - Help", chars-11, chars-11, desc);
+ }
+ fb_status_line(str);
+ free(str);
+}
+
+static void show_error(unsigned char *msg)
+{
+ fb_status_line(msg);
+ sleep(2);
+}
+
+static void show_exif(struct flist *f)
+{
+ static unsigned int tags[] = {
+ 0x010f, // Manufacturer
+ 0x0110, // Model
+
+ 0x0112, // Orientation
+ 0x0132, // Date and Time
+
+ 0x01e3, // White Point
+ 0x829a, // Exposure Time
+ 0x829d, // FNumber
+ 0x9206, // Subject Distance
+ 0xa40c, // Subject Distance Range
+ 0xa405, // Focal Length In 35mm Film
+ 0x9209, // Flash
+ };
+ ExifData *ed;
+ ExifEntry *ee;
+ unsigned int tag,l1,l2,len,count,i;
+ const char *title[ARRAY_SIZE(tags)];
+ char *value[ARRAY_SIZE(tags)];
+ char *linebuffer[ARRAY_SIZE(tags)];
+
+ if (!visible)
+ return;
+
+ ed = exif_data_new_from_file(f->name);
+ if (NULL == ed) {
+ status("image has no EXIF data", NULL);
+ return;
+ }
+
+ /* pass one -- get data + calc size */
+ l1 = 0;
+ l2 = 0;
+ for (tag = 0; tag < ARRAY_SIZE(tags); tag++) {
+ ee = exif_content_get_entry (ed->ifd[EXIF_IFD_0], tags[tag]);
+ if (NULL == ee)
+ ee = exif_content_get_entry (ed->ifd[EXIF_IFD_EXIF], tags[tag]);
+ if (NULL == ee) {
+ title[tag] = NULL;
+ value[tag] = NULL;
+ continue;
+ }
+ title[tag] = exif_tag_get_title(tags[tag]);
+ value[tag] = strdup(exif_entry_get_value(ee));
+ len = strlen(title[tag]);
+ if (l1 < len)
+ l1 = len;
+ len = strlen(value[tag]);
+ if (l2 < len)
+ l2 = len;
+ }
+
+ /* pass two -- print stuff */
+ count = 0;
+ for (tag = 0; tag < ARRAY_SIZE(tags); tag++) {
+ if (NULL == title[tag])
+ continue;
+ linebuffer[count] = malloc(l1+l2+8);
+ sprintf(linebuffer[count],"%-*.*s : %-*.*s",
+ l1, l1, title[tag],
+ l2, l2, value[tag]);
+ count++;
+ }
+ fb_text_box(24,16,linebuffer,count);
+
+ /* pass three -- free data */
+ for (tag = 0; tag < ARRAY_SIZE(tags); tag++)
+ if (NULL != value[tag])
+ free(value[tag]);
+ exif_data_unref (ed);
+ for (i = 0; i < count; i++)
+ free(linebuffer[i]);
+}
+
+static void show_help(void)
+{
+ static char *help[] = {
+ "keyboard commands",
+ "~~~~~~~~~~~~~~~~~",
+ " ESC, Q - quit",
+ " pgdn, space - next image",
+ " pgup - previous image",
+ " +/- - zoom in/out",
+ " A - autozoom image",
+ " cursor keys - scroll image",
+ "",
+ " H - show this help text",
+ " I - show EXIF info",
+ " P - pause slideshow",
+ " V - toggle statusline",
+ "",
+ "available if started with --edit switch,",
+ "rotation works for jpeg images only:",
+ " shift+D - delete image",
+ " R - rotate clockwise",
+ " L - rotate counter-clockwise",
+ };
+
+ fb_text_box(24,16,help,ARRAY_SIZE(help));
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct termios saved_attributes;
+int saved_fl;
+
+static void
+tty_raw(void)
+{
+ struct termios tattr;
+
+ fcntl(0,F_GETFL,&saved_fl);
+ tcgetattr (0, &saved_attributes);
+
+ fcntl(0,F_SETFL,O_NONBLOCK);
+ memcpy(&tattr,&saved_attributes,sizeof(struct termios));
+ tattr.c_lflag &= ~(ICANON|ECHO);
+ tattr.c_cc[VMIN] = 1;
+ tattr.c_cc[VTIME] = 0;
+ tcsetattr (0, TCSAFLUSH, &tattr);
+}
+
+static void
+tty_restore(void)
+{
+ fcntl(0,F_SETFL,saved_fl);
+ tcsetattr (0, TCSANOW, &saved_attributes);
+}
+
+/* testing: find key codes */
+static void debug_key(char *key)
+{
+ char linebuffer[128];
+ int i,len;
+
+ len = sprintf(linebuffer,"key: ");
+ for (i = 0; key[i] != '\0'; i++)
+ len += sprintf(linebuffer+len, "%s%c",
+ key[i] < 0x20 ? "^" : "",
+ key[i] < 0x20 ? key[i] + 0x40 : key[i]);
+ status(linebuffer, NULL);
+}
+
+static void
+console_switch(int is_busy)
+{
+ switch (fb_switch_state) {
+ case FB_REL_REQ:
+ fb_switch_release();
+ case FB_INACTIVE:
+ visible = 0;
+ break;
+ case FB_ACQ_REQ:
+ fb_switch_acquire();
+ case FB_ACTIVE:
+ visible = 1;
+ redraw = 1;
+ ioctl(fd,FBIOPAN_DISPLAY,&fb_var);
+ fb_clear_screen();
+ if (is_busy)
+ status("busy, please wait ...", NULL);
+ break;
+ default:
+ break;
+ }
+ switch_last = fb_switch_state;
+ return;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void free_image(struct ida_image *img)
+{
+ if (img) {
+ if (img->data)
+ free(img->data);
+ free(img);
+ }
+}
+
+static struct ida_image*
+read_image(char *filename)
+{
+ char command[1024];
+ struct ida_loader *loader = NULL;
+ struct ida_image *img;
+ struct list_head *item;
+ char blk[512];
+ FILE *fp;
+ unsigned int y;
+ void *data;
+
+ new_image = 1;
+
+ /* open file */
+ if (NULL == (fp = fopen(filename, "r"))) {
+ fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
+ return NULL;
+ }
+ memset(blk,0,sizeof(blk));
+ fread(blk,1,sizeof(blk),fp);
+ rewind(fp);
+
+ /* pick loader */
+ list_for_each(item,&loaders) {
+ loader = list_entry(item, struct ida_loader, list);
+ if (NULL == loader->magic)
+ break;
+ if (0 == memcmp(blk+loader->moff,loader->magic,loader->mlen))
+ break;
+ loader = NULL;
+ }
+ if (NULL == loader) {
+ /* no loader found, try to use ImageMagick's convert */
+ sprintf(command,"convert -depth 8 \"%s\" ppm:-",filename);
+ if (NULL == (fp = popen(command,"r")))
+ return NULL;
+ loader = &ppm_loader;
+ }
+
+ /* load image */
+ img = malloc(sizeof(*img));
+ memset(img,0,sizeof(*img));
+ data = loader->init(fp,filename,0,&img->i,0);
+ if (NULL == data) {
+ fprintf(stderr,"loading %s [%s] FAILED\n",filename,loader->name);
+ free_image(img);
+ return NULL;
+ }
+ img->data = malloc(img->i.width * img->i.height * 3);
+ for (y = 0; y < img->i.height; y++) {
+ if (switch_last != fb_switch_state)
+ console_switch(1);
+ loader->read(img->data + img->i.width * 3 * y, y, data);
+ }
+ loader->done(data);
+ return img;
+}
+
+static struct ida_image*
+scale_image(struct ida_image *src, float scale)
+{
+ struct op_resize_parm p;
+ struct ida_rect rect;
+ struct ida_image *dest;
+ void *data;
+ unsigned int y;
+
+ dest = malloc(sizeof(*dest));
+ memset(dest,0,sizeof(*dest));
+ memset(&rect,0,sizeof(rect));
+ memset(&p,0,sizeof(p));
+
+ p.width = src->i.width * scale;
+ p.height = src->i.height * scale;
+ p.dpi = src->i.dpi;
+ if (0 == p.width)
+ p.width = 1;
+ if (0 == p.height)
+ p.height = 1;
+
+ data = desc_resize.init(src,&rect,&dest->i,&p);
+ dest->data = malloc(dest->i.width * dest->i.height * 3);
+ for (y = 0; y < dest->i.height; y++) {
+ if (switch_last != fb_switch_state)
+ console_switch(1);
+ desc_resize.work(src,&rect,
+ dest->data + 3 * dest->i.width * y,
+ y, data);
+ }
+ desc_resize.done(data);
+ return dest;
+}
+
+static float auto_scale(struct ida_image *img)
+{
+ float xs,ys,scale;
+
+ xs = (float)fb_var.xres / img->i.width;
+ ys = (float)fb_var.yres / img->i.height;
+ scale = (xs < ys) ? xs : ys;
+ return scale;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static unsigned char *
+convert_line(int bpp, int line, int owidth,
+ char unsigned *dest, char unsigned *buffer)
+{
+ unsigned char *ptr = (void*)dest;
+ unsigned short *ptr2 = (void*)dest;
+ unsigned long *ptr4 = (void*)dest;
+ int x;
+
+ switch (fb_var.bits_per_pixel) {
+ case 8:
+ dither_line(buffer, ptr, line, owidth);
+ ptr += owidth;
+ return ptr;
+ case 15:
+ case 16:
+ for (x = 0; x < owidth; x++) {
+ ptr2[x] = lut_red[buffer[x*3]] |
+ lut_green[buffer[x*3+1]] |
+ lut_blue[buffer[x*3+2]];
+ }
+ ptr2 += owidth;
+ return (char*)ptr2;
+ case 24:
+ for (x = 0; x < owidth; x++) {
+ ptr[3*x+2] = buffer[3*x+0];
+ ptr[3*x+1] = buffer[3*x+1];
+ ptr[3*x+0] = buffer[3*x+2];
+ }
+ ptr += owidth * 3;
+ return ptr;
+ case 32:
+ for (x = 0; x < owidth; x++) {
+ ptr4[x] = lut_red[buffer[x*3]] |
+ lut_green[buffer[x*3+1]] |
+ lut_blue[buffer[x*3+2]];
+ }
+ ptr4 += owidth;
+ return (char*)ptr4;
+ default:
+ /* keep compiler happy */
+ return NULL;
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void init_one(int32_t *lut, int bits, int shift)
+{
+ int i;
+
+ if (bits > 8)
+ for (i = 0; i < 256; i++)
+ lut[i] = (i << (bits + shift - 8));
+ else
+ for (i = 0; i < 256; i++)
+ lut[i] = (i >> (8 - bits)) << shift;
+}
+
+static void
+lut_init(int depth)
+{
+ if (fb_var.red.length &&
+ fb_var.green.length &&
+ fb_var.blue.length) {
+ /* fb_var.{red|green|blue} looks sane, use it */
+ init_one(lut_red, fb_var.red.length, fb_var.red.offset);
+ init_one(lut_green, fb_var.green.length, fb_var.green.offset);
+ init_one(lut_blue, fb_var.blue.length, fb_var.blue.offset);
+ } else {
+ /* fallback */
+ int i;
+ switch (depth) {
+ case 15:
+ for (i = 0; i < 256; i++) {
+ lut_red[i] = (i & 0xf8) << 7; /* bits -rrrrr-- -------- */
+ lut_green[i] = (i & 0xf8) << 2; /* bits ------gg ggg----- */
+ lut_blue[i] = (i & 0xf8) >> 3; /* bits -------- ---bbbbb */
+ }
+ break;
+ case 16:
+ for (i = 0; i < 256; i++) {
+ lut_red[i] = (i & 0xf8) << 8; /* bits rrrrr--- -------- */
+ lut_green[i] = (i & 0xfc) << 3; /* bits -----ggg ggg----- */
+ lut_blue[i] = (i & 0xf8) >> 3; /* bits -------- ---bbbbb */
+ }
+ break;
+ case 24:
+ for (i = 0; i < 256; i++) {
+ lut_red[i] = i << 16; /* byte -r-- */
+ lut_green[i] = i << 8; /* byte --g- */
+ lut_blue[i] = i; /* byte ---b */
+ }
+ break;
+ }
+ }
+}
+
+static unsigned short calc_gamma(int n, int max)
+{
+ int ret =65535.0 * pow((float)n/(max), 1 / fbgamma);
+ if (ret > 65535) ret = 65535;
+ if (ret < 0) ret = 0;
+ return ret;
+}
+
+static void
+linear_palette(int bit)
+{
+ int i, size = 256 >> (8 - bit);
+
+ for (i = 0; i < size; i++)
+ red[i] = green[i] = blue[i] = calc_gamma(i,size);
+}
+
+static void
+svga_dither_palette(int r, int g, int b)
+{
+ int rs, gs, bs, i;
+
+ rs = 256 / (r - 1);
+ gs = 256 / (g - 1);
+ bs = 256 / (b - 1);
+ for (i = 0; i < 256; i++) {
+ red[i] = calc_gamma(rs * ((i / (g * b)) % r), 255);
+ green[i] = calc_gamma(gs * ((i / b) % g), 255);
+ blue[i] = calc_gamma(bs * ((i) % b), 255);
+ }
+}
+
+static void
+svga_display_image(struct ida_image *img, int xoff, int yoff)
+{
+ unsigned int dwidth = MIN(img->i.width, fb_var.xres);
+ unsigned int dheight = MIN(img->i.height, fb_var.yres);
+ unsigned int data, video, bank, offset, bytes, y;
+
+ if (!visible)
+ return;
+ bytes = (fb_var.bits_per_pixel+7)/8;
+
+ /* offset for image data (image > screen, select visible area) */
+ offset = (yoff * img->i.width + xoff) * 3;
+
+ /* offset for video memory (image < screen, center image) */
+ video = 0, bank = 0;
+ if (img->i.width < fb_var.xres)
+ video += bytes * ((fb_var.xres - img->i.width) / 2);
+ if (img->i.height < fb_var.yres)
+ video += fb_fix.line_length * ((fb_var.yres - img->i.height) / 2);
+
+ /* go ! */
+ for (data = 0, y = 0;
+ data < img->i.width * img->i.height * 3
+ && data / img->i.width / 3 < dheight;
+ data += img->i.width * 3, video += fb_fix.line_length) {
+ convert_line(fb_var.bits_per_pixel, y++, dwidth,
+ fb_mem+video, img->data + data + offset);
+ }
+}
+
+static int
+svga_show(struct ida_image *img, int timeout, char *desc, char *info, int *nr)
+{
+ static int paused = 0;
+ int exif = 0, help = 0;
+ int rc;
+ char key[11];
+ fd_set set;
+ struct timeval limit;
+ char linebuffer[80];
+ int fdmax;
+
+ *nr = 0;
+ if (NULL == img)
+ return KEY_SPACE; /* skip */
+
+ if (new_image) {
+ /* start with centered image, if larger than screen */
+ if (img->i.width > fb_var.xres)
+ left = (img->i.width - fb_var.xres) / 2;
+ if (img->i.height > fb_var.yres && !textreading)
+ top = (img->i.height - fb_var.yres) / 2;
+ new_image = 0;
+ }
+
+ redraw = 1;
+ for (;;) {
+ if (redraw) {
+ redraw = 0;
+ if (img->i.height <= fb_var.yres) {
+ top = 0;
+ } else {
+ if (top < 0)
+ top = 0;
+ if (top + fb_var.yres > img->i.height)
+ top = img->i.height - fb_var.yres;
+ }
+ if (img->i.width <= fb_var.xres) {
+ left = 0;
+ } else {
+ if (left < 0)
+ left = 0;
+ if (left + fb_var.xres > img->i.width)
+ left = img->i.width - fb_var.xres;
+ }
+ svga_display_image(img, left, top);
+ status(desc, info);
+ }
+ if (switch_last != fb_switch_state) {
+ console_switch(0);
+ continue;
+ }
+ FD_SET(0, &set);
+ fdmax = 1;
+#ifdef HAVE_LIBLIRC
+ if (-1 != lirc) {
+ FD_SET(lirc,&set);
+ fdmax = lirc+1;
+ }
+#endif
+ limit.tv_sec = timeout;
+ limit.tv_usec = 0;
+ rc = select(fdmax, &set, NULL, NULL,
+ (-1 != timeout && !paused) ? &limit : NULL);
+ if (switch_last != fb_switch_state) {
+ console_switch(0);
+ continue;
+ }
+ if (0 == rc)
+ return KEY_TIMEOUT;
+
+ if (FD_ISSET(0,&set)) {
+ /* stdin, i.e. keyboard */
+ rc = read(0, key, sizeof(key)-1);
+ if (rc < 1) {
+ /* EOF */
+ return KEY_EOF;
+ }
+ key[rc] = 0;
+ }
+#ifdef HAVE_LIBLIRC
+ if (lirc != -1 && FD_ISSET(lirc,&set)) {
+ /* lirc input */
+ if (-1 == lirc_fbi_havedata(&rc,key)) {
+ fprintf(stderr,"lirc: connection lost\n");
+ close(lirc);
+ lirc = -1;
+ }
+ key[rc] = 0;
+ }
+#endif
+
+ if (rc == 1 && (*key == 'q' || *key == 'Q' ||
+ *key == 'e' || *key == 'E' ||
+ *key == '\x1b' || *key == '\n')) {
+ if (*key == '\n')
+ return KEY_TAGFILE;
+ if (*key == '\x1b')
+ return KEY_ESC;
+ return KEY_Q;
+
+ } else if (0 == strcmp(key, " ")) {
+ if (textreading && top < (int)(img->i.height - fb_var.yres)) {
+ redraw = 1;
+ top += (fb_var.yres-100);
+ } else {
+ return KEY_SPACE;
+ }
+
+ } else if (0 == strcmp(key, "\x1b[A") && img->i.height > fb_var.yres) {
+ redraw = 1;
+ top -= steps;
+ } else if (0 == strcmp(key, "\x1b[B") && img->i.height > fb_var.yres) {
+ redraw = 1;
+ top += steps;
+ } else if (0 == strcmp(key, "\x1b[1~") && img->i.height > fb_var.yres) {
+ redraw = 1;
+ top = 0;
+ } else if (0 == strcmp(key, "\x1b[4~")) {
+ redraw = 1;
+ top = img->i.height - fb_var.yres;
+ } else if (0 == strcmp(key, "\x1b[D") && img->i.width > fb_var.xres) {
+ redraw = 1;
+ left -= steps;
+ } else if (0 == strcmp(key, "\x1b[C") && img->i.width > fb_var.xres) {
+ redraw = 1;
+ left += steps;
+
+ } else if (0 == strcmp(key, "\x1b[5~")) {
+ return KEY_PGUP;
+ } else if (0 == strcmp(key, "\x1b[6~") ||
+ 0 == strcmp(key, "n") ||
+ 0 == strcmp(key, "N")) {
+ return KEY_PGDN;
+
+ } else if (0 == strcmp(key, "+")) {
+ return KEY_PLUS;
+ } else if (0 == strcmp(key, "-")) {
+ return KEY_MINUS;
+ } else if (0 == strcmp(key, "a") ||
+ 0 == strcmp(key, "A")) {
+ return KEY_ASCALE;
+
+ } else if (0 == strcmp(key, "p") ||
+ 0 == strcmp(key, "P")) {
+ if (-1 != timeout) {
+ paused = !paused;
+ status(paused ? "pause on " : "pause off", NULL);
+ }
+
+ } else if (0 == strcmp(key, "D")) {
+ return KEY_DELETE;
+ } else if (0 == strcmp(key, "r") ||
+ 0 == strcmp(key, "R")) {
+ return KEY_ROT_CW;
+ } else if (0 == strcmp(key, "l") ||
+ 0 == strcmp(key, "L")) {
+ return KEY_ROT_CCW;
+
+ } else if (0 == strcmp(key, "h") ||
+ 0 == strcmp(key, "H")) {
+ if (!help) {
+ show_help();
+ help = 1;
+ } else {
+ redraw = 1;
+ help = 0;
+ }
+ exif = 0;
+
+ } else if (0 == strcmp(key, "i") ||
+ 0 == strcmp(key, "I")) {
+ if (!exif) {
+ show_exif(fcurrent);
+ exif = 1;
+ } else {
+ redraw = 1;
+ exif = 0;
+ }
+ help = 0;
+
+ } else if (0 == strcmp(key, "v") ||
+ 0 == strcmp(key, "V")) {
+ return KEY_VERBOSE;
+
+ } else if (0 == strcmp(key, "t") ||
+ 0 == strcmp(key, "T")) {
+ return KEY_DESC;
+
+ } else if (rc == 1 && (*key == 'g' || *key == 'G')) {
+ return KEY_GOTO;
+ } else if (rc == 1 && (*key == 's' || *key == 'S')) {
+ return KEY_SCALE;
+ } else if (rc == 1 && *key >= '0' && *key <= '9') {
+ *nr = *nr * 10 + (*key - '0');
+ sprintf(linebuffer, "> %d",*nr);
+ status(linebuffer, NULL);
+ } else {
+
+ *nr = 0;
+#if 0
+ debug_key(key);
+#endif
+ }
+ }
+}
+
+static void scale_fix_top_left(float old, float new, struct ida_image *img)
+{
+ unsigned int width, height;
+ float cx,cy;
+
+ cx = (float)(left + fb_var.xres/2) / (img->i.width * old);
+ cy = (float)(top + fb_var.yres/2) / (img->i.height * old);
+
+ width = img->i.width * new;
+ height = img->i.height * new;
+ left = cx * width - fb_var.xres/2;
+ top = cy * height - fb_var.yres/2;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static char *my_basename(char *filename)
+{
+ char *h;
+
+ h = strrchr(filename,'/');
+ if (h)
+ return h+1;
+ return filename;
+}
+
+static char *file_desktop(char *filename)
+{
+ static char desc[128];
+ char *h;
+
+ strncpy(desc,filename,sizeof(desc)-1);
+ if (NULL != (h = strrchr(filename,'/'))) {
+ snprintf(desc,sizeof(desc),"%.*s/%s",
+ (int)(h - filename), filename,
+ ".directory");
+ } else {
+ strcpy(desc,".directory");
+ }
+ return desc;
+}
+
+static char *make_desc(struct ida_image_info *img, char *filename)
+{
+ static char linebuffer[128];
+ struct ida_extra *extra;
+ char *desc;
+ int len;
+
+ memset(linebuffer,0,sizeof(linebuffer));
+ strncpy(linebuffer,filename,sizeof(linebuffer)-1);
+
+ if (comments) {
+ extra = load_find_extra(img, EXTRA_COMMENT);
+ if (extra)
+ snprintf(linebuffer,sizeof(linebuffer),"%.*s",
+ extra->size,extra->data);
+ } else {
+ desc = file_desktop(filename);
+ len = desktop_read_entry(desc, "Comment=", linebuffer, sizeof(linebuffer));
+ if (0 != len)
+ snprintf(linebuffer+len,sizeof(linebuffer)-len,
+ " (%s)", my_basename(filename));
+ }
+
+ return linebuffer;
+}
+
+static char *make_info(struct ida_image *img, float scale)
+{
+ static char linebuffer[128];
+
+ snprintf(linebuffer, sizeof(linebuffer),
+ "%s%.0f%% %dx%d %d/%d",
+ fcurrent->tag ? "* " : "",
+ scale*100,
+ img->i.width, img->i.height,
+ fcurrent->nr, fcount);
+ return linebuffer;
+}
+
+static char edit_line(char *line, int max)
+{
+ int len = strlen(line);
+ int pos = len;
+ int rc;
+ char key[11];
+ fd_set set;
+
+ do {
+ fb_edit_line(line,pos);
+
+ FD_SET(0, &set);
+ rc = select(1, &set, NULL, NULL, NULL);
+ if (switch_last != fb_switch_state) {
+ console_switch(0);
+ continue;
+ }
+ rc = read(0, key, sizeof(key)-1);
+ if (rc < 1) {
+ /* EOF */
+ return KEY_EOF;
+ }
+ key[rc] = 0;
+
+ if (0 == strcmp(key,"\x0a")) {
+ /* Enter */
+ return 0;
+
+ } else if (0 == strcmp(key,"\x1b")) {
+ /* ESC */
+ return KEY_ESC;
+
+ } else if (0 == strcmp(key,"\x1b[C")) {
+ /* cursor right */
+ if (pos < len)
+ pos++;
+
+ } else if (0 == strcmp(key,"\x1b[D")) {
+ /* cursor left */
+ if (pos > 0)
+ pos--;
+
+ } else if (0 == strcmp(key,"\x1b[1~")) {
+ /* home */
+ pos = 0;
+
+ } else if (0 == strcmp(key,"\x1b[4~")) {
+ /* end */
+ pos = len;
+
+ } else if (0 == strcmp(key,"\x7f")) {
+ /* backspace */
+ if (pos > 0) {
+ memmove(line+pos-1,line+pos,len-pos+1);
+ pos--;
+ len--;
+ }
+
+ } else if (0 == strcmp(key,"\x1b[3~")) {
+ /* delete */
+ if (pos < len) {
+ memmove(line+pos,line+pos+1,len-pos);
+ len--;
+ }
+
+ } else if (1 == rc && isprint(key[0]) && len < max) {
+ /* new key */
+ if (pos < len)
+ memmove(line+pos+1,line+pos,len-pos+1);
+ line[pos] = key[0];
+ pos++;
+ len++;
+ line[len] = 0;
+
+ } else if (0 /* debug */) {
+ debug_key(key);
+ sleep(1);
+ }
+ } while (1);
+}
+
+static void edit_desc(char *filename)
+{
+ static char linebuffer[128];
+ char *desc;
+ int len, rc;
+
+ desc = file_desktop(filename);
+ len = desktop_read_entry(desc, "Comment=", linebuffer, sizeof(linebuffer));
+ if (0 == len) {
+ linebuffer[0] = 0;
+ len = 0;
+ }
+ rc = edit_line(linebuffer, sizeof(linebuffer)-1);
+ if (0 != rc)
+ return;
+ desktop_write_entry(desc, "Directory", "Comment=", linebuffer);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void cleanup_and_exit(int code)
+{
+ fb_clear_mem();
+ tty_restore();
+ fb_cleanup();
+ flist_print_tagged(stdout);
+ exit(code);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int timeout = -1;
+ int randomize = -1;
+ int opt_index = 0;
+ int vt = 0;
+ int backup = 0;
+ int preserve = 0;
+
+ struct ida_image *fimg = NULL;
+ struct ida_image *simg = NULL;
+ struct ida_image *img = NULL;
+ float scale = 1;
+ float newscale = 1;
+
+ int c, editable = 0, once = 0;
+ int need_read, need_refresh;
+ int i, arg, key;
+
+ char *line, *info, *desc;
+ char linebuffer[128];
+
+ if (NULL != (line = getenv("FRAMEBUFFER")))
+ fbdev = line;
+ if (NULL != (line = getenv("FBGAMMA")))
+ fbgamma = atof(line);
+ if (NULL != (line = getenv("FBFONT")))
+ fontname = line;
+
+#ifdef HAVE_LIBLIRC
+ lirc = lirc_fbi_init();
+#endif
+
+ setlocale(LC_ALL,"");
+ for (;;) {
+ c = getopt_long(argc, argv, "u1evahPqVbpr:t:m:d:g:s:f:l:T:",
+ fbi_options, &opt_index);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 0:
+ /* long option, nothing to do */
+ break;
+ case '1':
+ once = 1;
+ break;
+ case 'a':
+ autoup = 1;
+ autodown = 1;
+ break;
+ case 'q':
+ statusline = 0;
+ break;
+ case 'v':
+ statusline = 1;
+ break;
+ case 'P':
+ textreading = 1;
+ break;
+ case 'g':
+ fbgamma = atof(optarg);
+ break;
+ case 'r':
+ pcd_res = atoi(optarg);
+ break;
+ case 's':
+ steps = atoi(optarg);
+ break;
+ case 't':
+ timeout = atoi(optarg);
+ break;
+ case 'u':
+ randomize = 1;
+ break;
+ case 'd':
+ fbdev = optarg;
+ break;
+ case 'm':
+ fbmode = optarg;
+ break;
+ case 'f':
+ fontname = optarg;
+ break;
+ case 'e':
+ editable = 1;
+ break;
+ case 'b':
+ backup = 1;
+ break;
+ case 'p':
+ preserve = 1;
+ break;
+ case 'l':
+ flist_add_list(optarg);
+ break;
+ case 'T':
+ vt = atoi(optarg);
+ break;
+ case 'V':
+ version();
+ exit(0);
+ break;
+ default:
+ case 'h':
+ usage(argv[0]);
+ exit(1);
+ }
+ }
+
+ for (i = optind; i < argc; i++) {
+ flist_add(argv[i]);
+ }
+ flist_renumber();
+
+ if (0 == fcount) {
+ usage(argv[0]);
+ exit(1);
+ }
+
+ if (randomize != -1)
+ flist_randomize();
+ fcurrent = flist_first();
+
+ need_read = 1;
+ need_refresh = 1;
+
+ fb_text_init1(fontname);
+ fd = fb_init(fbdev, fbmode, vt);
+ fb_catch_exit_signals();
+ fb_switch_init();
+ signal(SIGTSTP,SIG_IGN);
+ fb_text_init2();
+
+ switch (fb_var.bits_per_pixel) {
+ case 8:
+ svga_dither_palette(8, 8, 4);
+ dither = TRUE;
+ init_dither(8, 8, 4, 2);
+ break;
+ case 15:
+ case 16:
+ if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR)
+ linear_palette(5);
+ if (fb_var.green.length == 5) {
+ lut_init(15);
+ } else {
+ lut_init(16);
+ }
+ break;
+ case 24:
+ if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR)
+ linear_palette(8);
+ break;
+ case 32:
+ if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR)
+ linear_palette(8);
+ lut_init(24);
+ break;
+ default:
+ fprintf(stderr, "Oops: %i bit/pixel ???\n",
+ fb_var.bits_per_pixel);
+ exit(1);
+ }
+ if (fb_fix.visual == FB_VISUAL_DIRECTCOLOR ||
+ fb_var.bits_per_pixel == 8) {
+ if (-1 == ioctl(fd,FBIOPUTCMAP,&cmap)) {
+ perror("ioctl FBIOPUTCMAP");
+ exit(1);
+ }
+ }
+
+ /* svga main loop */
+ tty_raw();
+ desc = NULL;
+ info = NULL;
+ for (;;) {
+ if (need_read) {
+ need_read = 0;
+ need_refresh = 1;
+ sprintf(linebuffer,"loading %s ...",fcurrent->name);
+ status(linebuffer, NULL);
+ free_image(fimg);
+ free_image(simg);
+ fimg = read_image(fcurrent->name);
+ simg = NULL;
+ img = NULL;
+ scale = 1;
+ if (fimg) {
+ if (autoup || autodown) {
+ scale = auto_scale(fimg);
+ if (scale < 1 && !autodown)
+ scale = 1;
+ if (scale > 1 && !autoup)
+ scale = 1;
+ }
+ if (scale != 1) {
+ sprintf(linebuffer,"scaling (%.0f%%) %s ...",
+ scale*100, fcurrent->name);
+ status(linebuffer, NULL);
+ simg = scale_image(fimg,scale);
+ img = simg;
+ } else {
+ img = fimg;
+ }
+ desc = make_desc(&fimg->i,fcurrent->name);
+ }
+ if (!img) {
+ sprintf(linebuffer,"%s: FAILED",fcurrent->name);
+ show_error(linebuffer);
+ }
+ }
+ if (img) {
+ if (need_refresh) {
+ need_refresh = 0;
+ if (img->i.width < fb_var.xres || img->i.height < fb_var.yres)
+ fb_clear_screen();
+ }
+ info = make_info(fimg,scale);
+ }
+ switch (key = svga_show(img, timeout, desc, info, &arg)) {
+ case KEY_DELETE:
+ if (editable) {
+ struct flist *fdel = fcurrent;
+ if (flist_islast(fcurrent))
+ fcurrent = flist_prev(fcurrent);
+ else
+ fcurrent = flist_next(fcurrent,0,0);
+ unlink(fdel->name);
+ flist_del(fdel);
+ flist_renumber();
+ need_read = 1;
+ if (list_empty(&flist)) {
+ /* deleted last one */
+ fb_clear_mem();
+ tty_restore();
+ fb_cleanup();
+ exit(0);
+ }
+ } else {
+ show_error("readonly mode, sorry [start with --edit?]");
+ }
+ break;
+ case KEY_ROT_CW:
+ case KEY_ROT_CCW:
+ {
+ if (editable) {
+ sprintf(linebuffer,"rotating %s ...",fcurrent->name);
+ status(linebuffer, NULL);
+ jpeg_transform_inplace
+ (fcurrent->name,
+ (key == KEY_ROT_CW) ? JXFORM_ROT_90 : JXFORM_ROT_270,
+ NULL,
+ NULL,0,
+ (backup ? JFLAG_FILE_BACKUP : 0) |
+ (preserve ? JFLAG_FILE_KEEP_TIME : 0) |
+ JFLAG_TRANSFORM_IMAGE |
+ JFLAG_TRANSFORM_THUMBNAIL |
+ JFLAG_UPDATE_ORIENTATION);
+ need_read = 1;
+ } else {
+ show_error("readonly mode, sorry [start with --edit?]");
+ }
+ break;
+ }
+ case KEY_TAGFILE:
+ fcurrent->tag = !fcurrent->tag;
+ /* fall throuth */
+ case KEY_SPACE:
+ need_read = 1;
+ fcurrent = flist_next(fcurrent,1,0);
+ if (NULL != fcurrent)
+ break;
+ /* else fall */
+ case KEY_ESC:
+ case KEY_Q:
+ case KEY_EOF:
+ cleanup_and_exit(0);
+ break;
+ case KEY_PGDN:
+ need_read = 1;
+ fcurrent = flist_next(fcurrent,0,0);
+ break;
+ case KEY_PGUP:
+ need_read = 1;
+ fcurrent = flist_prev(fcurrent);
+ break;
+ case KEY_TIMEOUT:
+ need_read = 1;
+ fcurrent = flist_next(fcurrent,once,1);
+ if (NULL == fcurrent) {
+ fb_clear_mem();
+ tty_restore();
+ fb_cleanup();
+ }
+ /* FIXME: wrap around */
+ break;
+ case KEY_PLUS:
+ case KEY_MINUS:
+ case KEY_ASCALE:
+ case KEY_SCALE:
+ if (key == KEY_PLUS) {
+ newscale = scale * 1.6;
+ } else if (key == KEY_MINUS) {
+ newscale = scale / 1.6;
+ } else if (key == KEY_ASCALE) {
+ newscale = auto_scale(fimg);
+ } else {
+ newscale = arg / 100;
+ }
+ if (newscale < 0.1)
+ newscale = 0.1;
+ if (newscale > 10)
+ newscale = 10;
+ scale_fix_top_left(scale, newscale, img);
+ scale = newscale;
+ sprintf(linebuffer,"scaling (%.0f%%) %s ...",
+ scale*100, fcurrent->name);
+ status(linebuffer, NULL);
+ free_image(simg);
+ simg = scale_image(fimg,scale);
+ img = simg;
+ need_refresh = 1;
+ break;
+ case KEY_GOTO:
+ if (arg > 0 && arg <= fcount) {
+ need_read = 1;
+ fcurrent = flist_goto(arg);
+ }
+ break;
+ case KEY_VERBOSE:
+ statusline = !statusline;
+ need_refresh = 1;
+ break;
+ case KEY_DESC:
+ if (!comments) {
+ edit_desc(fcurrent->name);
+ desc = make_desc(&fimg->i,fcurrent->name);
+ }
+ break;
+ }
+ }
+}
diff --git a/fbi.man b/fbi.man
new file mode 100644
index 0000000..35b8a7e
--- /dev/null
+++ b/fbi.man
@@ -0,0 +1,160 @@
+.TH fbi 1 "(c) 1999-2002 Gerd Knorr"
+.SH NAME
+fbi - linux \fBf\fPrame\fBb\fPuffer \fBi\fPmageviewer
+.SH SYNOPSIS
+.B fbi [ options ] file ...
+.SH DESCRIPTION
+.B fbi
+displays the specified file(s) on the linux console using the
+framebuffer device. PhotoCD, jpeg, ppm, gif, tiff, xwd, bmp and png
+are supported directly. For other formats fbi tries to use
+ImageMagick's convert.
+.SH OPTIONS
+.TP
+.B -h
+print usage info
+.TP
+.B -d device
+framebuffer device to use. Default is the one your vc is
+mapped to.
+.TP
+.B -m mode
+name of the video mode to use video mode (must be listed in
+/etc/fb.modes). Default is not to change the video mode.
+.TP
+.B -q
+be quiet: don't print anything.
+.TP
+.B -v
+be verbose: allways print filenames.
+.TP
+.B -P
+Enable textreading mode. This has the effect that fbi will display
+large images without vertical offset (default is to center the
+images). Space will first try to scroll down and go to the next image
+only if it is already on the bottom of the page. Useful if the images
+you are watching text pages, all you have to do to get the next piece
+of text is to press space...
+.TP
+.B -t sec
+timeout: load next image after >sec< seconds without any
+keypress
+.TP
+.B -g gamma
+gamma correction. Can also be put into the FBGAMMA environment
+variable. Default is 1.0. Requires Pseudocolor or Directcolor
+visual, doesn't work for Truecolor.
+.TP
+.B -r n
+select resolution. PhotoCD only, n = 1..5.
+.TP
+.B -s n
+set scroll steps in pixels (default is 50).
+.TP
+.B -f font
+Set font. This can be either a pcf console font file or a X11 font
+spec. Using X11 fonts requires a font server (The one specified in
+the environment variable FONTSERVER or on localhost). The FBFONT
+environment variable is used as default. If unset, fbi will
+fallback to 10x20 (X11) / lat1u-16.psf (console).
+.TP
+.B -a
+Enable autozoom. fbi will automagically pick a reasonable zoom factor
+when loading a new image.
+.TP
+.B --autoup
+Like autozoom, but scale up only.
+.TP
+.B --autodown
+Like autozoom, but scale down only.
+.TP
+.B -u
+Randomize the order of the filenames.
+.TP
+.B -e
+Enable editing commands.
+.TP
+.B -b
+create backup files (when editing images).
+.TP
+.B -p
+preserve timestamps (when editing images).
+.TP
+.B --comments
+Display comment tags (if present) instead of the filename. Probaby
+only useful if you added reasonable comments yourself (using wrjpgcom
+for example), otherwise you likely just find texts pointing to the
+software which created the image.
+.SH KEYS
+.nf
+cursor keys scroll large images
++, - zoom in/out
+ESQ, Q quit
+PgUp previous image
+PgDn, Space next image
+Return next image, write the filename of the current
+ image to stdout.
+P pause the slideshow (if started with -t, toggle)
+<number>g jump to image #<number>
+.fi
+.P
+The Return vs. Space key thing can be used to create a file list while
+reviewing the images and use the list for batch processing later on.
+.SH EDIT IMAGE
+fbi also provides some very basic image editing facilities. You have
+to start fbi with the -e switch to use them.
+.P
+.nf
+Shift+D delete image
+R rotate 90° clockwise
+L rotate 90° counter-clock wise
+.fi
+.P
+The delete function actually wants a capital letter 'D', thus you have
+to type Shift+D. This is done to avoid deleting images by mistake
+because there are no safety bells: If you ask fbi to delete the image,
+it will be deleted without questions asked.
+.P
+The rotate function actually works for JPEG images only because it
+calls the jpegtran command to perform a lossless rotation if the image.
+It is especially useful if you review the images of your digital
+camera.
+.SH COMMON PROBLEMS
+.B fbi
+needs rw access to the framebuffer devices (/dev/fbN), i.e you (our
+your admin) have to make sure fbi can open the devices in rw mode.
+The IMHO most elegant way is to use pam_console (see
+/etc/security/console.perms) to chown the devices to the user logged
+in on the console. Another way is to create some group, chown the
+special files to that group and put the users which are allowed to use
+the framebuffer device into the group. You can also make the special
+files world writable, but be aware of the security implications this
+has. On a private box it might be fine to handle it this way
+througth.
+.P
+.B fbi
+also needs access to the linux console (i.e. /dev/ttyN) for sane
+console switch handling. That is obviously no problem for console
+logins, but any kind of a pseudo tty (xterm, ssh, screen, ...) will
+.B not
+work.
+.SH SEE ALSO
+fbset(1), convert(1), jpegtran(1)
+.SH AUTHOR
+Gerd Knorr <kraxel@bytesex.org>
+.SH COPYRIGHT
+Copyright (C) 1999-2000 Gerd Knorr <kraxel@bytesex.org>
+.P
+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.
+.P
+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.
+.P
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
diff --git a/fbtools.c b/fbtools.c
new file mode 100644
index 0000000..a03f4d3
--- /dev/null
+++ b/fbtools.c
@@ -0,0 +1,523 @@
+/*
+ * some generic framebuffer device stuff
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <signal.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <linux/kd.h>
+#include <linux/vt.h>
+#include <linux/fb.h>
+
+#include <asm/page.h>
+
+#include "fbtools.h"
+
+/* -------------------------------------------------------------------- */
+/* exported stuff */
+
+struct fb_fix_screeninfo fb_fix;
+struct fb_var_screeninfo fb_var;
+unsigned char *fb_mem;
+int fb_mem_offset = 0;
+int fb_switch_state = FB_ACTIVE;
+
+/* -------------------------------------------------------------------- */
+/* internal variables */
+
+static int fb,tty;
+#if 0
+static int bpp,black,white;
+#endif
+
+static int orig_vt_no = 0;
+static struct vt_mode vt_mode;
+
+static int kd_mode;
+static struct vt_mode vt_omode;
+static struct termios term;
+static struct fb_var_screeninfo fb_ovar;
+static unsigned short ored[256], ogreen[256], oblue[256];
+static struct fb_cmap ocmap = { 0, 256, ored, ogreen, oblue };
+
+/* -------------------------------------------------------------------- */
+/* devices */
+
+struct DEVS {
+ char *fb0;
+ char *fbnr;
+ char *ttynr;
+};
+
+struct DEVS devs_default = {
+ fb0: "/dev/fb0",
+ fbnr: "/dev/fb%d",
+ ttynr: "/dev/tty%d",
+};
+struct DEVS devs_devfs = {
+ fb0: "/dev/fb/0",
+ fbnr: "/dev/fb/%d",
+ ttynr: "/dev/vc/%d",
+};
+struct DEVS *devices;
+
+static void dev_init(void)
+{
+ struct stat dummy;
+
+ if (NULL != devices)
+ return;
+ if (0 == stat("/dev/.devfsd",&dummy))
+ devices = &devs_devfs;
+ else
+ devices = &devs_default;
+}
+
+/* -------------------------------------------------------------------- */
+/* console switching */
+
+extern int debug;
+
+static void
+fb_switch_signal(int signal)
+{
+ if (signal == SIGUSR1) {
+ /* release */
+ fb_switch_state = FB_REL_REQ;
+ if (debug)
+ write(2,"vt: SIGUSR1\n",12);
+ }
+ if (signal == SIGUSR2) {
+ /* acquisition */
+ fb_switch_state = FB_ACQ_REQ;
+ if (debug)
+ write(2,"vt: SIGUSR2\n",12);
+ }
+}
+
+void
+fb_switch_release()
+{
+ ioctl(tty, VT_RELDISP, 1);
+ fb_switch_state = FB_INACTIVE;
+ if (debug)
+ write(2,"vt: release\n",12);
+}
+
+void
+fb_switch_acquire()
+{
+ ioctl(tty, VT_RELDISP, VT_ACKACQ);
+ fb_switch_state = FB_ACTIVE;
+ if (debug)
+ write(2,"vt: acquire\n",12);
+}
+
+int
+fb_switch_init()
+{
+ struct sigaction act,old;
+
+ memset(&act,0,sizeof(act));
+ act.sa_handler = fb_switch_signal;
+ sigemptyset(&act.sa_mask);
+ sigaction(SIGUSR1,&act,&old);
+ sigaction(SIGUSR2,&act,&old);
+
+ if (-1 == ioctl(tty,VT_GETMODE, &vt_mode)) {
+ perror("ioctl VT_GETMODE");
+ exit(1);
+ }
+ vt_mode.mode = VT_PROCESS;
+ vt_mode.waitv = 0;
+ vt_mode.relsig = SIGUSR1;
+ vt_mode.acqsig = SIGUSR2;
+
+ if (-1 == ioctl(tty,VT_SETMODE, &vt_mode)) {
+ perror("ioctl VT_SETMODE");
+ exit(1);
+ }
+ return 0;
+}
+
+/* -------------------------------------------------------------------- */
+/* initialisation & cleanup */
+
+void
+fb_memset (void *addr, int c, size_t len)
+{
+#if 1 /* defined(__powerpc__) */
+ unsigned int i, *p;
+
+ i = (c & 0xff) << 8;
+ i |= i << 16;
+ len >>= 2;
+ for (p = addr; len--; p++)
+ *p = i;
+#else
+ memset(addr, c, len);
+#endif
+}
+
+static int
+fb_setmode(char *name)
+{
+ FILE *fp;
+ char line[80],label[32],value[16];
+ int geometry=0, timings=0;
+
+ /* load current values */
+ if (-1 == ioctl(fb,FBIOGET_VSCREENINFO,&fb_var)) {
+ perror("ioctl FBIOGET_VSCREENINFO");
+ exit(1);
+ }
+
+ if (NULL == name)
+ return -1;
+ if (NULL == (fp = fopen("/etc/fb.modes","r")))
+ return -1;
+ while (NULL != fgets(line,79,fp)) {
+ if (1 == sscanf(line, "mode \"%31[^\"]\"",label) &&
+ 0 == strcmp(label,name)) {
+ /* fill in new values */
+ fb_var.sync = 0;
+ fb_var.vmode = 0;
+ while (NULL != fgets(line,79,fp) &&
+ NULL == strstr(line,"endmode")) {
+ if (5 == sscanf(line," geometry %d %d %d %d %d",
+ &fb_var.xres,&fb_var.yres,
+ &fb_var.xres_virtual,&fb_var.yres_virtual,
+ &fb_var.bits_per_pixel))
+ geometry = 1;
+ if (7 == sscanf(line," timings %d %d %d %d %d %d %d",
+ &fb_var.pixclock,
+ &fb_var.left_margin, &fb_var.right_margin,
+ &fb_var.upper_margin, &fb_var.lower_margin,
+ &fb_var.hsync_len, &fb_var.vsync_len))
+ timings = 1;
+ if (1 == sscanf(line, " hsync %15s",value) &&
+ 0 == strcasecmp(value,"high"))
+ fb_var.sync |= FB_SYNC_HOR_HIGH_ACT;
+ if (1 == sscanf(line, " vsync %15s",value) &&
+ 0 == strcasecmp(value,"high"))
+ fb_var.sync |= FB_SYNC_VERT_HIGH_ACT;
+ if (1 == sscanf(line, " csync %15s",value) &&
+ 0 == strcasecmp(value,"high"))
+ fb_var.sync |= FB_SYNC_COMP_HIGH_ACT;
+ if (1 == sscanf(line, " extsync %15s",value) &&
+ 0 == strcasecmp(value,"true"))
+ fb_var.sync |= FB_SYNC_EXT;
+ if (1 == sscanf(line, " laced %15s",value) &&
+ 0 == strcasecmp(value,"true"))
+ fb_var.vmode |= FB_VMODE_INTERLACED;
+ if (1 == sscanf(line, " double %15s",value) &&
+ 0 == strcasecmp(value,"true"))
+ fb_var.vmode |= FB_VMODE_DOUBLE;
+ }
+ /* ok ? */
+ if (!geometry || !timings)
+ return -1;
+ /* set */
+ fb_var.xoffset = 0;
+ fb_var.yoffset = 0;
+ if (-1 == ioctl(fb,FBIOPUT_VSCREENINFO,&fb_var))
+ perror("ioctl FBIOPUT_VSCREENINFO");
+ /* look what we have now ... */
+ if (-1 == ioctl(fb,FBIOGET_VSCREENINFO,&fb_var)) {
+ perror("ioctl FBIOGET_VSCREENINFO");
+ exit(1);
+ }
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void
+fb_setvt(int vtno)
+{
+ struct vt_stat vts;
+ char vtname[12];
+
+ if (vtno < 0) {
+ if (-1 == ioctl(tty,VT_OPENQRY, &vtno) || vtno == -1) {
+ perror("ioctl VT_OPENQRY");
+ exit(1);
+ }
+ }
+
+ vtno &= 0xff;
+ sprintf(vtname, devices->ttynr, vtno);
+ chown(vtname, getuid(), getgid());
+ if (-1 == access(vtname, R_OK | W_OK)) {
+ fprintf(stderr,"access %s: %s\n",vtname,strerror(errno));
+ exit(1);
+ }
+ switch (fork()) {
+ case 0:
+ break;
+ case -1:
+ perror("fork");
+ exit(1);
+ default:
+ exit(0);
+ }
+ close(tty);
+ close(0);
+ close(1);
+ close(2);
+ setsid();
+ open(vtname,O_RDWR);
+ dup(0);
+ dup(0);
+
+ if (-1 == ioctl(tty,VT_GETSTATE, &vts)) {
+ perror("ioctl VT_GETSTATE");
+ exit(1);
+ }
+ orig_vt_no = vts.v_active;
+ if (-1 == ioctl(tty,VT_ACTIVATE, vtno)) {
+ perror("ioctl VT_ACTIVATE");
+ exit(1);
+ }
+ if (-1 == ioctl(tty,VT_WAITACTIVE, vtno)) {
+ perror("ioctl VT_WAITACTIVE");
+ exit(1);
+ }
+}
+
+/* Hmm. radeonfb needs this. matroxfb doesn't. */
+static int fb_activate_current(int tty)
+{
+ struct vt_stat vts;
+
+ if (-1 == ioctl(tty,VT_GETSTATE, &vts)) {
+ perror("ioctl VT_GETSTATE");
+ return -1;
+ }
+ if (-1 == ioctl(tty,VT_ACTIVATE, vts.v_active)) {
+ perror("ioctl VT_ACTIVATE");
+ return -1;
+ }
+ if (-1 == ioctl(tty,VT_WAITACTIVE, vts.v_active)) {
+ perror("ioctl VT_WAITACTIVE");
+ return -1;
+ }
+ return 0;
+}
+
+int
+fb_init(char *device, char *mode, int vt)
+{
+ char fbdev[16];
+ struct vt_stat vts;
+
+ dev_init();
+ tty = 0;
+ if (vt != 0)
+ fb_setvt(vt);
+
+ if (-1 == ioctl(tty,VT_GETSTATE, &vts)) {
+ fprintf(stderr,"ioctl VT_GETSTATE: %s (not a linux console?)\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ if (NULL == device) {
+ device = getenv("FRAMEBUFFER");
+ if (NULL == device) {
+ struct fb_con2fbmap c2m;
+ if (-1 == (fb = open(devices->fb0,O_RDWR /* O_WRONLY */,0))) {
+ fprintf(stderr,"open %s: %s\n",devices->fb0,strerror(errno));
+ exit(1);
+ }
+ c2m.console = vts.v_active;
+ if (-1 == ioctl(fb, FBIOGET_CON2FBMAP, &c2m)) {
+ perror("ioctl FBIOGET_CON2FBMAP");
+ exit(1);
+ }
+ close(fb);
+ fprintf(stderr,"map: vt%02d => fb%d\n",
+ c2m.console,c2m.framebuffer);
+ sprintf(fbdev,devices->fbnr,c2m.framebuffer);
+ device = fbdev;
+ }
+ }
+
+ /* get current settings (which we have to restore) */
+ if (-1 == (fb = open(device,O_RDWR /* O_WRONLY */))) {
+ fprintf(stderr,"open %s: %s\n",device,strerror(errno));
+ exit(1);
+ }
+ if (-1 == ioctl(fb,FBIOGET_VSCREENINFO,&fb_ovar)) {
+ perror("ioctl FBIOGET_VSCREENINFO");
+ exit(1);
+ }
+ if (-1 == ioctl(fb,FBIOGET_FSCREENINFO,&fb_fix)) {
+ perror("ioctl FBIOGET_FSCREENINFO");
+ exit(1);
+ }
+ if (fb_ovar.bits_per_pixel == 8 ||
+ fb_fix.visual == FB_VISUAL_DIRECTCOLOR) {
+ if (-1 == ioctl(fb,FBIOGETCMAP,&ocmap)) {
+ perror("ioctl FBIOGETCMAP");
+ exit(1);
+ }
+ }
+ if (-1 == ioctl(tty,KDGETMODE, &kd_mode)) {
+ perror("ioctl KDGETMODE");
+ exit(1);
+ }
+ if (-1 == ioctl(tty,VT_GETMODE, &vt_omode)) {
+ perror("ioctl VT_GETMODE");
+ exit(1);
+ }
+ tcgetattr(tty, &term);
+
+ /* switch mode */
+ fb_setmode(mode);
+
+ /* checks & initialisation */
+ if (-1 == ioctl(fb,FBIOGET_FSCREENINFO,&fb_fix)) {
+ perror("ioctl FBIOGET_FSCREENINFO");
+ exit(1);
+ }
+ if (fb_fix.type != FB_TYPE_PACKED_PIXELS) {
+ fprintf(stderr,"can handle only packed pixel frame buffers\n");
+ goto err;
+ }
+#if 0
+ switch (fb_var.bits_per_pixel) {
+ case 8:
+ white = 255; black = 0; bpp = 1;
+ break;
+ case 15:
+ case 16:
+ if (fb_var.green.length == 6)
+ white = 0xffff;
+ else
+ white = 0x7fff;
+ black = 0; bpp = 2;
+ break;
+ case 24:
+ white = 0xffffff; black = 0; bpp = fb_var.bits_per_pixel/8;
+ break;
+ case 32:
+ white = 0xffffff; black = 0; bpp = fb_var.bits_per_pixel/8;
+ fb_setpixels = fb_setpixels4;
+ break;
+ default:
+ fprintf(stderr, "Oops: %i bit/pixel ???\n",
+ fb_var.bits_per_pixel);
+ goto err;
+ }
+#endif
+ fb_mem_offset = (unsigned long)(fb_fix.smem_start) & (~PAGE_MASK);
+ fb_mem = mmap(NULL,fb_fix.smem_len+fb_mem_offset,
+ PROT_READ|PROT_WRITE,MAP_SHARED,fb,0);
+ if (-1L == (long)fb_mem) {
+ perror("mmap");
+ goto err;
+ }
+ /* move viewport to upper left corner */
+ if (fb_var.xoffset != 0 || fb_var.yoffset != 0) {
+ fb_var.xoffset = 0;
+ fb_var.yoffset = 0;
+ if (-1 == ioctl(fb,FBIOPAN_DISPLAY,&fb_var)) {
+ perror("ioctl FBIOPAN_DISPLAY");
+ goto err;
+ }
+ }
+ if (-1 == ioctl(tty,KDSETMODE, KD_GRAPHICS)) {
+ perror("ioctl KDSETMODE");
+ goto err;
+ }
+ fb_activate_current(tty);
+
+ /* cls */
+ fb_memset(fb_mem+fb_mem_offset,0,fb_fix.smem_len);
+ return fb;
+
+ err:
+ fb_cleanup();
+ exit(1);
+}
+
+void
+fb_cleanup(void)
+{
+ /* restore console */
+ if (-1 == ioctl(fb,FBIOPUT_VSCREENINFO,&fb_ovar))
+ perror("ioctl FBIOPUT_VSCREENINFO");
+ if (-1 == ioctl(fb,FBIOGET_FSCREENINFO,&fb_fix))
+ perror("ioctl FBIOGET_FSCREENINFO");
+ if (fb_ovar.bits_per_pixel == 8 ||
+ fb_fix.visual == FB_VISUAL_DIRECTCOLOR) {
+ if (-1 == ioctl(fb,FBIOPUTCMAP,&ocmap))
+ perror("ioctl FBIOPUTCMAP");
+ }
+ close(fb);
+
+ if (-1 == ioctl(tty,KDSETMODE, kd_mode))
+ perror("ioctl KDSETMODE");
+ if (-1 == ioctl(tty,VT_SETMODE, &vt_omode))
+ perror("ioctl VT_SETMODE");
+ if (orig_vt_no && -1 == ioctl(tty, VT_ACTIVATE, orig_vt_no))
+ perror("ioctl VT_ACTIVATE");
+ if (orig_vt_no && -1 == ioctl(tty, VT_WAITACTIVE, orig_vt_no))
+ perror("ioctl VT_WAITACTIVE");
+ tcsetattr(tty, TCSANOW, &term);
+ close(tty);
+}
+
+/* -------------------------------------------------------------------- */
+/* handle fatal errors */
+
+static jmp_buf fb_fatal_cleanup;
+
+static void
+fb_catch_exit_signal(int signal)
+{
+ siglongjmp(fb_fatal_cleanup,signal);
+}
+
+void
+fb_catch_exit_signals(void)
+{
+ struct sigaction act,old;
+ int termsig;
+
+ memset(&act,0,sizeof(act));
+ act.sa_handler = fb_catch_exit_signal;
+ sigemptyset(&act.sa_mask);
+ sigaction(SIGINT, &act,&old);
+ sigaction(SIGQUIT,&act,&old);
+ sigaction(SIGTERM,&act,&old);
+
+ sigaction(SIGABRT,&act,&old);
+ sigaction(SIGTSTP,&act,&old);
+
+ sigaction(SIGBUS, &act,&old);
+ sigaction(SIGILL, &act,&old);
+ sigaction(SIGSEGV,&act,&old);
+
+ if (0 == (termsig = sigsetjmp(fb_fatal_cleanup,0)))
+ return;
+
+ /* cleanup */
+ fb_cleanup();
+ fprintf(stderr,"Oops: %s\n",sys_siglist[termsig]);
+ exit(42);
+}
diff --git a/fbtools.h b/fbtools.h
new file mode 100644
index 0000000..469675c
--- /dev/null
+++ b/fbtools.h
@@ -0,0 +1,23 @@
+#define FB_ACTIVE 0
+#define FB_REL_REQ 1
+#define FB_INACTIVE 2
+#define FB_ACQ_REQ 3
+
+/* info about videomode - yes I know, quick & dirty... */
+extern struct fb_fix_screeninfo fb_fix;
+extern struct fb_var_screeninfo fb_var;
+extern unsigned char *fb_mem;
+extern int fb_mem_offset;
+extern int fb_switch_state;
+
+/* init + cleanup */
+int fb_probe(void);
+int fb_init(char *device, char *mode, int vt);
+void fb_cleanup(void);
+void fb_catch_exit_signals(void);
+void fb_memset(void *addr, int c, size_t len);
+
+/* console switching */
+int fb_switch_init(void);
+void fb_switch_release(void);
+void fb_switch_acquire(void);
diff --git a/filebutton.c b/filebutton.c
new file mode 100644
index 0000000..3f922e2
--- /dev/null
+++ b/filebutton.c
@@ -0,0 +1,934 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/Label.h>
+#include <Xm/RowColumn.h>
+#include <Xm/PushB.h>
+#include <Xm/Transfer.h>
+#include <Xm/TransferP.h>
+#include <Xm/Container.h>
+#include <Xm/IconG.h>
+#include <Xm/ScrolledW.h>
+#include <Xm/SelectioB.h>
+#include <Xm/Text.h>
+
+#include "RegEdit.h"
+#include "list.h"
+#include "ida.h"
+#include "x11.h"
+#include "icons.h"
+#include "readers.h"
+#include "filter.h"
+#include "viewer.h"
+#include "selections.h"
+#include "filebutton.h"
+#include "fileops.h"
+#include "idaconfig.h"
+
+/*----------------------------------------------------------------------*/
+
+struct fileinfo {
+ struct list_head list;
+ char *path;
+ struct ida_image_info img;
+ Pixmap small;
+ Pixmap large;
+};
+
+static LIST_HEAD(pcache);
+static LIST_HEAD(pqueue);
+static LIST_HEAD(files);
+static XtWorkProcId pproc;
+
+/*----------------------------------------------------------------------*/
+
+static struct fileinfo*
+fileinfo_cache_add(char *path, struct ida_image_info *img,
+ Pixmap small, Pixmap large)
+{
+ struct fileinfo *item;
+
+ item = malloc(sizeof(*item));
+ memset(item,0,sizeof(*item));
+ item->path = strdup(path);
+ item->img = *img;
+ item->small = small;
+ item->large = large;
+ list_add_tail(&item->list,&pcache);
+ return item;
+}
+
+static void fileinfo_cache_del(char *path)
+{
+ struct list_head *item;
+ struct fileinfo *b;
+
+ list_for_each(item,&pcache) {
+ b = list_entry(item,struct fileinfo,list);
+ if (0 == strcmp(path,b->path)) {
+ list_del(&b->list);
+ free(b);
+ return;
+ }
+ }
+}
+
+static struct fileinfo* fileinfo_cache_get(char *path)
+{
+ struct list_head *item;
+ struct fileinfo *b;
+
+ list_for_each(item,&pcache) {
+ b = list_entry(item,struct fileinfo,list);
+ if (0 == strcmp(path,b->path))
+ return b;
+ }
+ return 0;
+}
+
+/*----------------------------------------------------------------------*/
+
+static void
+fileinfo_cleanup(struct file_button *file)
+{
+ switch (file->state) {
+ case 1:
+ file->loader->done(file->wdata);
+ break;
+ case 2:
+ desc_resize.done(file->wdata);
+ break;
+ }
+ file->state = 0;
+
+ if (file->wimg.data) {
+ free(file->wimg.data);
+ file->wimg.data = NULL;
+ }
+ if (file->simg.data) {
+ free(file->simg.data);
+ file->simg.data = NULL;
+ }
+ if (!list_empty(&file->queue)) {
+ list_del_init(&file->queue);
+ }
+}
+
+static void fileinfo_details(struct file_button *file)
+{
+ struct ida_image_info *img;
+ struct ida_extra *extra;
+ char buf[80];
+
+ img = &file->info->img;
+ snprintf(buf, sizeof(buf), "%dx%d",
+ img->thumbnail ? img->real_width : img->width,
+ img->thumbnail ? img->real_height : img->height);
+ XmStringFree(file->details[DETAIL_SIZE]);
+ file->details[DETAIL_SIZE] = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL);
+
+ extra = load_find_extra(img, EXTRA_COMMENT);
+ if (extra) {
+ XmStringFree(file->details[DETAIL_COMMENT]);
+ file->details[DETAIL_COMMENT] =
+ XmStringGenerate(extra->data, NULL, XmMULTIBYTE_TEXT,NULL);
+ }
+
+ XtVaSetValues(file->widget,
+ XmNdetail, file->details,
+ XmNdetailCount, DETAIL_COUNT,
+ NULL);
+}
+
+static Boolean
+fileinfo_loader(XtPointer clientdata)
+{
+ struct op_resize_parm resize;
+ struct ida_rect rect;
+ struct list_head *item;
+ struct file_button *file;
+ struct fileinfo *info;
+ Pixmap pix;
+ char blk[512];
+ FILE *fp;
+ float xs,ys,scale;
+ struct ida_image timg;
+ void *data;
+
+ if (list_empty(&pqueue)) {
+ /* nothing to do */
+ pproc = 0;
+ return TRUE;
+ }
+ file = list_entry(pqueue.next, struct file_button, queue);
+
+ switch (file->state) {
+ case 0:
+ /* ------------------- new file -------------------- */
+ info = fileinfo_cache_get(file->filename);
+ if (info) {
+ file_set_info(file,info);
+ goto next;
+ }
+
+ /* open file */
+ if (NULL == (fp = fopen(file->filename, "r"))) {
+ if (debug)
+ fprintf(stderr,"open %s: %s\n",file->filename,
+ strerror(errno));
+ goto unknown;
+ }
+ if (debug)
+ fprintf(stderr,"OPENED: %s\n",file->filename);
+ fstat(fileno(fp),&file->st);
+
+ /* pick loader */
+ memset(blk,0,sizeof(blk));
+ fread(blk,1,sizeof(blk),fp);
+ rewind(fp);
+ list_for_each(item,&loaders) {
+ file->loader = list_entry(item, struct ida_loader, list);
+ if (NULL == file->loader->magic)
+ continue;
+ if (0 == memcmp(blk+file->loader->moff,file->loader->magic,
+ file->loader->mlen))
+ break;
+ file->loader = NULL;
+ }
+ if (NULL == file->loader) {
+ if (debug)
+ fprintf(stderr,"%s: unknown format\n",file->filename);
+ fclose(fp);
+ goto unknown;
+ }
+
+ /* load image */
+ file->wdata = file->loader->init(fp, file->filename,
+ 0, &file->wimg.i, 1);
+ if (NULL == file->wdata) {
+ if (debug)
+ fprintf(stderr,"loading %s [%s] FAILED\n",
+ file->filename, file->loader->name);
+ goto unknown;
+ }
+
+ file->wimg.data = malloc(file->wimg.i.width * file->wimg.i.height * 3);
+ file->state = 1;
+ file->y = 0;
+ return FALSE;
+
+ case 1:
+ /* ------------------- loading file -------------------- */
+ if (file->y < file->wimg.i.height) {
+ file->loader->read(file->wimg.data
+ + 3 * file->y * file->wimg.i.width,
+ file->y, file->wdata);
+ file->y++;
+ return FALSE;
+ }
+ file->loader->done(file->wdata);
+ if (debug)
+ fprintf(stderr,"LOADED: %s [%dx%d]\n",
+ file->filename, file->wimg.i.width, file->wimg.i.height);
+
+ /* resize image */
+ xs = (float)GET_ICON_LARGE() / file->wimg.i.width;
+ ys = (float)GET_ICON_LARGE() / file->wimg.i.height;
+ scale = (xs < ys) ? xs : ys;
+ resize.width = file->wimg.i.width * scale;
+ resize.height = file->wimg.i.height * scale;
+ if (0 == resize.width)
+ resize.width = 1;
+ if (0 == resize.height)
+ resize.height = 1;
+
+ rect.x1 = 0;
+ rect.x2 = file->wimg.i.width;
+ rect.y1 = 0;
+ rect.y2 = file->wimg.i.height;
+ file->wdata = desc_resize.init(&file->wimg,&rect,&file->simg.i,&resize);
+ file->simg.data = malloc(file->simg.i.width * file->simg.i.height * 3);
+
+ file->state = 2;
+ file->y = 0;
+ return FALSE;
+
+ case 2:
+ /* ------------------- scaling file -------------------- */
+ if (file->y < file->simg.i.height) {
+ desc_resize.work(&file->wimg,&rect, file->simg.data
+ + 3 * file->simg.i.width * file->y,
+ file->y, file->wdata);
+ file->y++;
+ return FALSE;
+ }
+ desc_resize.done(file->wdata);
+ if (debug)
+ fprintf(stderr,"SCALED: %s [%dx%d]\n",
+ file->filename,file->simg.i.width,file->simg.i.height);
+
+ /* scale once more (small icon) */
+ xs = (float)GET_ICON_SMALL() / file->simg.i.width;
+ ys = (float)GET_ICON_SMALL() / file->simg.i.height;
+ scale = (xs < ys) ? xs : ys;
+ resize.width = file->simg.i.width * scale;
+ resize.height = file->simg.i.height * scale;
+ if (0 == resize.width)
+ resize.width = 1;
+ if (0 == resize.height)
+ resize.height = 1;
+
+ rect.x1 = 0;
+ rect.x2 = file->simg.i.width;
+ rect.y1 = 0;
+ rect.y2 = file->simg.i.height;
+ data = desc_resize.init(&file->simg,&rect,&timg.i,&resize);
+ timg.data = malloc(timg.i.width * timg.i.height * 3);
+
+ for (file->y = 0; file->y < timg.i.height; file->y++)
+ desc_resize.work(&file->simg,&rect,
+ timg.data + 3 * timg.i.width * file->y,
+ file->y, data);
+ desc_resize.done(data);
+
+ /* build, cache + install pixmap */
+ info = fileinfo_cache_add(file->filename,&file->wimg.i,
+ image_to_pixmap(&timg),
+ image_to_pixmap(&file->simg));
+ file_set_info(file,info);
+ free(timg.data);
+ file->state = 0;
+ goto next;
+
+ default:
+ /* shouldn't happen */
+ fprintf(stderr,"Oops: %s:%d\n",__FILE__,__LINE__);
+ exit(1);
+ }
+
+ unknown:
+ /* generic file icon */
+ pix = XmGetPixmap(file->screen,"unknown",0,0);
+ file_set_icon(file,pix,pix);
+
+ next:
+ fileinfo_cleanup(file);
+ return FALSE;
+}
+
+/*----------------------------------------------------------------------*/
+
+void fileinfo_queue(struct file_button *file)
+{
+ if (NULL == file->queue.next)
+ INIT_LIST_HEAD(&file->queue);
+
+ if (!list_empty(&file->queue)) {
+ /* already queued */
+ if (0 == file->state)
+ return;
+ fileinfo_cleanup(file);
+ }
+
+ file->state = 0;
+ memset(&file->wimg,0,sizeof(file->wimg));
+ memset(&file->simg,0,sizeof(file->simg));
+ list_add_tail(&file->queue,&pqueue);
+ if (0 == pproc)
+ pproc = XtAppAddWorkProc(app_context,fileinfo_loader,NULL);
+}
+
+void fileinfo_invalidate(char *filename)
+{
+ struct file_button *file;
+ struct list_head *item;
+ Pixmap pix;
+
+ if (debug)
+ fprintf(stderr,"fileinfo invalidate: %s\n",filename);
+ fileinfo_cache_del(filename);
+
+ list_for_each(item,&files) {
+ file = list_entry(item, struct file_button, global);
+ if (0 != strcmp(file->filename,filename))
+ continue;
+ if (debug)
+ fprintf(stderr," %p %s\n",file,filename);
+ file->info = NULL;
+ pix = XmGetPixmap(file->screen,"file",0,0);
+ file_set_icon(file,pix,pix);
+ fileinfo_queue(file);
+ }
+}
+
+/*----------------------------------------------------------------------*/
+
+static void
+container_ops_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget container = clientdata;
+ WidgetList children;
+ Cardinal nchildren,i;
+
+ XtVaGetValues(container,
+ XmNselectedObjects,&children,
+ XmNselectedObjectCount,&nchildren,
+ NULL);
+ for (i = 0; i < nchildren; i++) {
+ struct stat st;
+ if (-1 == stat(XtName(children[i]),&st))
+ continue;
+ if (!S_ISREG(st.st_mode))
+ continue;
+ job_submit(XtName(widget),XtName(children[i]), NULL);
+ }
+}
+
+static void
+comment_box_cb(Widget widget, XtPointer clientdata, XtPointer calldata)
+{
+ Widget container = clientdata;
+ XmSelectionBoxCallbackStruct *cd = calldata;
+ WidgetList children;
+ Cardinal nchildren,i;
+ Widget text;
+ char *comment;
+
+ if (XmCR_OK == cd->reason) {
+ /* TODO */
+ text = XmSelectionBoxGetChild(widget,XmDIALOG_TEXT);
+ comment = XmTextGetString(text);
+ XtVaGetValues(container,
+ XmNselectedObjects,&children,
+ XmNselectedObjectCount,&nchildren,
+ NULL);
+ for (i = 0; i < nchildren; i++) {
+ struct stat st;
+ if (-1 == stat(XtName(children[i]),&st))
+ continue;
+ if (!S_ISREG(st.st_mode))
+ continue;
+ job_submit("comment",XtName(children[i]), comment);
+ }
+ }
+ XtDestroyWidget(XtParent(widget));
+}
+
+static void
+container_comment_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget container = clientdata;
+ Widget box,text;
+ WidgetList children;
+ Cardinal nchildren;
+ static struct fileinfo *info;
+ struct ida_extra *extra;
+ char *comment = "";
+
+ XtVaGetValues(container,
+ XmNselectedObjects,&children,
+ XmNselectedObjectCount,&nchildren,
+ NULL);
+ switch (nchildren) {
+ case 0:
+ /* nothing to do */
+ return;
+ case 1:
+ /* get old comment */
+ info = fileinfo_cache_get(XtName(children[0]));
+ if (!info)
+ /* not a image */
+ return;
+ extra = load_find_extra(&info->img, EXTRA_COMMENT);
+ if (extra)
+ comment = extra->data;
+ break;
+ default:
+ /* start with a empty comment */
+ break;
+ }
+
+ /* dialog box */
+ box = XmCreatePromptDialog(container,"comment",NULL,0);
+ XtUnmanageChild(XmSelectionBoxGetChild(box,XmDIALOG_HELP_BUTTON));
+ XmdRegisterEditres(XtParent(box));
+ XtAddCallback(box,XmNokCallback,comment_box_cb,clientdata);
+ XtAddCallback(box,XmNcancelCallback,comment_box_cb,clientdata);
+ XtAddCallback(XtParent(box),XmNdestroyCallback,destroy_cb,XtParent(box));
+
+ text = XmSelectionBoxGetChild(box,XmDIALOG_TEXT);
+ XmTextSetString(text,comment);
+ XmTextSetInsertionPosition(text,strlen(comment));
+ XtManageChild(box);
+}
+
+void container_menu_ops(Widget menu, Widget container)
+{
+ Widget push;
+
+ push = XtVaCreateManagedWidget("rotexif",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,container_ops_cb,container);
+ push = XtVaCreateManagedWidget("rotcw",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,container_ops_cb,container);
+ push = XtVaCreateManagedWidget("rotccw",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,container_ops_cb,container);
+ push = XtVaCreateManagedWidget("comment",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,container_comment_cb,container);
+}
+
+/*----------------------------------------------------------------------*/
+
+void
+container_resize_eh(Widget widget, XtPointer clientdata, XEvent *event, Boolean *d)
+{
+ Widget clip,scroll,container;
+ Dimension width, height, ch;
+
+ clip = widget;
+ scroll = XtParent(widget);
+ XtVaGetValues(scroll,XmNworkWindow,&container,NULL);
+
+ XtVaGetValues(clip,
+ XtNwidth, &width,
+ XtNheight, &height,
+ NULL);
+ XtVaGetValues(container,
+ XtNheight, &ch,
+ NULL);
+ if (ch < height-5)
+ ch = height-5;
+ XtVaSetValues(container,
+ XtNwidth, width-5,
+ XtNheight,ch,
+ NULL);
+ container_relayout(container);
+}
+
+void
+container_spatial_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget container = clientdata;
+ WidgetList children;
+ Cardinal nchildren;
+
+ XtVaSetValues(container,
+ XmNlayoutType, XmSPATIAL,
+ XmNspatialStyle, XmNONE,
+ NULL);
+ nchildren = XmContainerGetItemChildren(container,NULL,&children);
+ if (nchildren) {
+ XtFree((XtPointer)children);
+ /* FIXME: Hmm, why ??? */
+ XtVaSetValues(container,
+ XmNentryViewType, XmLARGE_ICON,
+ NULL);
+ }
+ container_relayout(container);
+}
+
+void container_detail_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget container = clientdata;
+
+ XtVaSetValues(container,
+ XmNlayoutType, XmDETAIL,
+ XmNentryViewType, XmSMALL_ICON,
+ NULL);
+ container_relayout(container);
+}
+
+void
+container_traverse_cb(Widget scroll, XtPointer clientdata, XtPointer call_data)
+{
+ XmTraverseObscuredCallbackStruct *cd = call_data;
+
+ if (cd->reason == XmCR_OBSCURED_TRAVERSAL)
+ XmScrollVisible(scroll, cd->traversal_destination, 25, 25);
+}
+
+void
+container_convert_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmConvertCallbackStruct *ccs = call_data;
+ char *file = NULL;
+ Atom *targs;
+ int i,n,len;
+ WidgetList children;
+ Cardinal nchildren;
+
+ if (ccs->location_data) {
+ Widget item = ccs->location_data;
+ children = &item;
+ nchildren = 1;
+ } else {
+ XtVaGetValues(widget,
+ XmNselectedObjects,&children,
+ XmNselectedObjectCount,&nchildren,
+ NULL);
+ }
+
+ if (debug) {
+ char *t = !ccs->target ? NULL : XGetAtomName(dpy,ccs->target);
+ char *s = !ccs->selection ? NULL : XGetAtomName(dpy,ccs->selection);
+ fprintf(stderr,"drag: target=%s selection=%s [%d files,%p]\n",
+ t, s, nchildren, ccs->location_data);
+ if (t) XFree(t);
+ if (s) XFree(s);
+ }
+
+ if ((ccs->target == XA_TARGETS) ||
+ (ccs->target == _MOTIF_CLIPBOARD_TARGETS) ||
+ (ccs->target == _MOTIF_EXPORT_TARGETS)) {
+ targs = (Atom*)XtMalloc(sizeof(Atom)*8);
+ n = 0;
+ if (nchildren >= 1) {
+ targs[n++] = MIME_TEXT_URI_LIST;
+ }
+ if (1 == nchildren) {
+ targs[n++] = XA_FILE_NAME;
+ targs[n++] = XA_FILE;
+ targs[n++] = _NETSCAPE_URL;
+ targs[n++] = XA_STRING;
+ }
+ ccs->value = targs;
+ ccs->length = n;
+ ccs->type = XA_ATOM;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_MERGE;
+ return;
+ }
+
+ if (ccs->target == _MOTIF_DEFERRED_CLIPBOARD_TARGETS) {
+ targs = (Atom*)XtMalloc(sizeof(Atom)*8);
+ n = 0;
+ ccs->value = targs;
+ ccs->length = n;
+ ccs->type = XA_ATOM;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_DONE;
+ }
+
+ if ((ccs->target == _MOTIF_LOSE_SELECTION) ||
+ (ccs->target == XA_DONE)) {
+ /* free stuff */
+ ccs->value = NULL;
+ ccs->length = 0;
+ ccs->type = XA_INTEGER;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_DONE;
+ return;
+ }
+
+ if (ccs->target == XA_FILE_NAME ||
+ ccs->target == XA_FILE ||
+ ccs->target == XA_STRING) {
+ file = XtMalloc(strlen(XtName(children[0])+1));
+ strcpy(file,XtName(children[0]));
+ ccs->value = file;
+ ccs->length = strlen(file);
+ ccs->type = XA_STRING;
+ ccs->format = 8;
+ ccs->status = XmCONVERT_DONE;
+ return;
+ }
+
+ if (ccs->target == _NETSCAPE_URL ||
+ ccs->target == MIME_TEXT_URI_LIST) {
+ for (i = 0, len = 0; i < nchildren; i++)
+ len += strlen(XtName(children[i]));
+ file = XtMalloc(len + 8 * nchildren);
+ for (i = 0, len = 0; i < nchildren; i++)
+ len += sprintf(file+len,"file:%s\n",XtName(children[i]));
+ ccs->value = file;
+ ccs->length = len;
+ ccs->type = XA_STRING;
+ ccs->format = 8;
+ ccs->status = XmCONVERT_DONE;
+ return;
+ }
+
+ ccs->status = XmCONVERT_DEFAULT;
+}
+
+static void
+container_copy_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget container = clientdata;
+ XmeClipboardSource(container,XmCOPY,XtLastTimestampProcessed(dpy));
+}
+
+static void
+container_paste_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget container = clientdata;
+ XmeClipboardSink(container,XmCOPY,NULL);
+}
+
+static void
+container_del_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget container = clientdata;
+ WidgetList children,list;
+ Cardinal nchildren,i;
+
+ XtVaGetValues(container,
+ XmNselectedObjects,&children,
+ XmNselectedObjectCount,&nchildren,
+ NULL);
+ list = malloc(sizeof(Widget*)*nchildren);
+ memcpy(list,children,sizeof(Widget*)*nchildren);
+
+ XtVaSetValues(container,
+ XmNselectedObjectCount,0,
+ NULL);
+ XtUnmanageChildren(list,nchildren);
+ for (i = 0; i < nchildren; i++)
+ XtDestroyWidget(list[i]);
+ free(list);
+}
+
+void container_menu_edit(Widget menu, Widget container,
+ int cut, int copy, int paste, int del)
+{
+ Widget push;
+
+#if 0
+ if (cut) {
+ push = XtVaCreateManagedWidget("cut",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,
+ container_cut_cb,container);
+ }
+#endif
+ if (copy) {
+ push = XtVaCreateManagedWidget("copy",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,
+ container_copy_cb,container);
+ }
+ if (paste) {
+ push = XtVaCreateManagedWidget("paste",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,
+ container_paste_cb,container);
+ }
+ if (del) {
+ push = XtVaCreateManagedWidget("del",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,
+ container_del_cb,container);
+ }
+}
+
+void container_menu_view(Widget menu, Widget container)
+{
+ Widget push;
+
+ push = XtVaCreateManagedWidget("details",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,
+ container_detail_cb,container);
+ push = XtVaCreateManagedWidget("spatial",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,
+ container_spatial_cb,container);
+}
+
+void
+container_relayout(Widget container)
+{
+ Widget clip = XtParent(container);
+ WidgetList children;
+ Cardinal nchildren;
+ Dimension wwidth,wheight;
+ Dimension iwidth,iheight;
+ Position x,y;
+ unsigned char layout,style;
+ int i,margin = 10;
+
+ XtVaGetValues(container,
+ XmNlayoutType, &layout,
+ XmNspatialStyle, &style,
+ NULL);
+ if (XmSPATIAL != layout || XmNONE != style) {
+ XmContainerRelayout(container);
+ return;
+ }
+
+ nchildren = XmContainerGetItemChildren(container,NULL,&children);
+ XtVaGetValues(clip,
+ XtNwidth, &wwidth,
+ XtNheight, &wheight,
+ NULL);
+
+ wwidth -= 5;
+ x = margin; y = margin;
+ for (i = 0; i < nchildren; i++) {
+ if (!XtIsManaged(children[i]))
+ continue;
+ XtVaGetValues(children[i],
+ XtNwidth,&iwidth,
+ XtNheight,&iheight,
+ NULL);
+ if (x > 0 && x + iwidth + margin > wwidth) {
+ /* new row */
+ x = margin; y += iheight + margin;
+ }
+ XtVaSetValues(children[i],
+ XtNx,x, XtNy,y,
+ XmNpositionIndex,i,
+ NULL);
+ x += iwidth + margin;
+ }
+
+ if (wheight < y + iheight + margin)
+ wheight = y + iheight + margin;
+ XtVaSetValues(container,
+ XtNwidth, wwidth,
+ XtNheight, wheight,
+ NULL);
+ if (nchildren)
+ XtFree((XtPointer)children);
+}
+
+void
+container_delwidgets(Widget container)
+{
+ WidgetList children;
+ Cardinal nchildren;
+ unsigned int i;
+
+ /* delete widgets */
+ XtVaSetValues(container,
+ XmNselectedObjectCount,0,
+ NULL);
+ nchildren = XmContainerGetItemChildren(container,NULL,&children);
+ XtUnmanageChildren(children,nchildren);
+ for (i = 0; i < nchildren; i++)
+ XtDestroyWidget(children[i]);
+ if (nchildren)
+ XtFree((XtPointer)children);
+}
+
+/*----------------------------------------------------------------------*/
+
+void file_set_icon(struct file_button *file, Pixmap s, Pixmap l)
+{
+ Pixmap large, small;
+ Pixel background;
+
+ if (file->info)
+ return;
+
+ XtVaGetValues(file->widget, XmNbackground,&background, NULL);
+ small = x11_icon_fit(DisplayOfScreen(file->screen), s, background,
+ GET_ICON_SMALL(), GET_ICON_SMALL());
+ large = x11_icon_fit(DisplayOfScreen(file->screen), l, background,
+ GET_ICON_LARGE(), GET_ICON_LARGE());
+ XtVaSetValues(file->widget,
+ XmNsmallIconPixmap, small,
+ XmNlargeIconPixmap, large,
+ NULL);
+ if (file->small)
+ XFreePixmap(DisplayOfScreen(file->screen),file->small);
+ if (file->large)
+ XFreePixmap(DisplayOfScreen(file->screen),file->large);
+ file->small = small;
+ file->large = large;
+}
+
+void file_set_info(struct file_button *file, struct fileinfo *info)
+{
+ file->info = NULL;
+ file_set_icon(file,info->small,info->large);
+ file->info = info;
+ fileinfo_details(file);
+}
+
+#if 0
+static void
+file_copy_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct file_button *file = clientdata;
+
+ XmeClipboardSource(file->widget,XmCOPY,XtLastTimestampProcessed(dpy));
+}
+#endif
+
+/*----------------------------------------------------------------------*/
+
+int file_cmp_alpha(const struct file_button *aa,
+ const struct file_button *bb)
+{
+ if (S_ISDIR(aa->st.st_mode) != S_ISDIR(bb->st.st_mode))
+ return S_ISDIR(aa->st.st_mode) ? -1 : 1;
+ return strcmp(aa->basename,bb->basename);
+}
+
+static void
+file_destroy_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct file_button *file = clientdata;
+ int i;
+
+ if (debug)
+ fprintf(stderr,"file: del %p [%s]\n",file,file->filename);
+
+ if (NULL != file->queue.next)
+ fileinfo_cleanup(file);
+
+ if (file->basename)
+ free(file->basename);
+ if (file->filename)
+ free(file->filename);
+
+ XtVaSetValues(file->widget,
+ XmNsmallIconPixmap, XmUNSPECIFIED_PIXMAP,
+ XmNlargeIconPixmap, XmUNSPECIFIED_PIXMAP,
+ NULL);
+ if (file->small)
+ XFreePixmap(DisplayOfScreen(file->screen),file->small);
+ if (file->large)
+ XFreePixmap(DisplayOfScreen(file->screen),file->large);
+
+ if (file->label)
+ XmStringFree(file->label);
+ for (i = 0; i < DETAIL_COUNT; i++)
+ if (file->details[i])
+ XmStringFree(file->details[i]);
+
+ list_del(&file->global);
+ list_del(&file->window);
+ free(file);
+}
+
+int file_createwidgets(Widget parent, struct file_button *file)
+{
+ struct fileinfo *info;
+ Pixmap pix;
+ Arg args[8];
+ int i, n = 0;
+
+ if (debug)
+ fprintf(stderr,"file: new %p [%s]\n",file,file->filename);
+
+ file->screen = XtScreen(parent);
+ file->label = XmStringGenerate(file->basename, NULL, XmMULTIBYTE_TEXT,NULL);
+ for (i = 0; i < DETAIL_COUNT; i++)
+ file->details[i] = XmStringGenerate("-", NULL, XmMULTIBYTE_TEXT,NULL);
+
+ XtSetArg(args[n], XmNlabelString, file->label); n++;
+ XtSetArg(args[n], XmNdetail, file->details); n++;
+ XtSetArg(args[n], XmNdetailCount, DETAIL_COUNT); n++;
+ file->widget = XmCreateIconGadget(parent,file->filename,args,n);
+ XtAddCallback(file->widget,XtNdestroyCallback,file_destroy_cb,file);
+ list_add_tail(&file->global,&files);
+
+ info = fileinfo_cache_get(file->filename);
+ if (info) {
+ file_set_info(file,info);
+ } else {
+ pix = XmGetPixmap(file->screen,"question",0,0);
+ file_set_icon(file,pix,pix);
+ }
+ return 0;
+}
diff --git a/filebutton.h b/filebutton.h
new file mode 100644
index 0000000..4e5ebf1
--- /dev/null
+++ b/filebutton.h
@@ -0,0 +1,71 @@
+#include <sys/stat.h>
+#include <Xm/Xm.h>
+#include "list.h"
+
+#define DETAIL_SIZE 0
+#define DETAIL_COMMENT 1
+#define DETAIL_COUNT 2
+
+struct fileinfo_cache;
+
+struct file_button {
+ /* file info */
+ char *filename;
+ char *basename;
+ unsigned char d_type;
+ struct stat st;
+ struct fileinfo *info;
+
+ /* Widget + other X11 stuff */
+ Screen *screen;
+ Widget widget;
+ XmString label;
+ XmString details[DETAIL_COUNT];
+ Pixmap small,large;
+
+ /* lists */
+ struct list_head global;
+ struct list_head window;
+
+ /* private for file info + icon loader */
+ struct list_head queue;
+ int state,y;
+ struct ida_loader *loader;
+ void *wdata;
+ struct ida_image wimg;
+ struct ida_image simg;
+};
+
+void fileinfo_queue(struct file_button *file);
+void fileinfo_invalidate(char *filename);
+void file_set_icon(struct file_button *file, Pixmap s, Pixmap l);
+void file_set_info(struct file_button *file, struct fileinfo *info);
+
+/*----------------------------------------------------------------------*/
+
+void container_detail_cb(Widget widget, XtPointer clientdata,
+ XtPointer call_data);
+void container_spatial_cb(Widget widget, XtPointer clientdata,
+ XtPointer call_data);
+
+void container_resize_eh(Widget widget, XtPointer clientdata,
+ XEvent *event, Boolean *d);
+void container_convert_cb(Widget widget, XtPointer clientdata,
+ XtPointer call_data);
+void container_traverse_cb(Widget scroll, XtPointer clientdata,
+ XtPointer call_data);
+
+void container_menu_edit(Widget menu, Widget container,
+ int cut, int copy, int paste, int del);
+void container_menu_view(Widget menu, Widget container);
+void container_menu_ops(Widget menu, Widget container);
+
+void container_relayout(Widget container);
+void container_setsize(Widget container);
+void container_delwidgets(Widget container);
+
+/*----------------------------------------------------------------------*/
+
+int file_cmp_alpha(const struct file_button *aa,
+ const struct file_button *bb);
+int file_createwidgets(Widget parent, struct file_button *file);
diff --git a/filelist.c b/filelist.c
new file mode 100644
index 0000000..317e3b1
--- /dev/null
+++ b/filelist.c
@@ -0,0 +1,619 @@
+/*
+ * file list management ("virtual photo album").
+ * (c) 2003 Gerd Knorr <kraxel@bytesex.org>
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <string.h>
+#include <fnmatch.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/extensions/XShm.h>
+#include <Xm/Xm.h>
+#include <Xm/Form.h>
+#include <Xm/Label.h>
+#include <Xm/RowColumn.h>
+#include <Xm/PushB.h>
+#include <Xm/CascadeB.h>
+#include <Xm/ScrolledW.h>
+#include <Xm/SelectioB.h>
+#include <Xm/Transfer.h>
+#include <Xm/TransferP.h>
+#include <Xm/Container.h>
+#include <Xm/FileSB.h>
+#include <Xm/Separator.h>
+
+#include "RegEdit.h"
+#include "ida.h"
+#include "readers.h"
+#include "viewer.h"
+#include "browser.h"
+#include "filter.h"
+#include "x11.h"
+#include "dither.h"
+#include "selections.h"
+#include "filebutton.h"
+#include "filelist.h"
+#include "xdnd.h"
+#include "idaconfig.h"
+
+/*----------------------------------------------------------------------*/
+
+struct list_handle;
+
+struct list_handle {
+ char *filename;
+ struct list_head files;
+
+ Widget shell;
+ Widget scroll;
+ Widget container;
+ Widget status;
+ XmString details[DETAIL_COUNT+1];
+
+ Widget loadbox;
+ Widget savebox;
+
+ XtWorkProcId wproc;
+};
+
+/* ---------------------------------------------------------------------- */
+
+static void filelist_add(struct list_handle *h, char *filename)
+{
+ struct file_button *file;
+ char *tmp;
+
+ /* fixup filename */
+ if (0 == strncmp(filename,"file:",5))
+ filename += 5;
+ if (NULL != (tmp = strchr(filename,'\n')))
+ *tmp = 0;
+ if (NULL != (tmp = strchr(filename,'\r')))
+ *tmp = 0;
+ if (0 == strlen(filename))
+ return;
+
+ /* add file */
+ file = malloc(sizeof(*file));
+ memset(file,0,sizeof(*file));
+
+ tmp = strrchr(filename,'/');
+ if (!tmp)
+ goto oops;
+ file->basename = strdup(tmp+1);
+ file->filename = strdup(filename);
+
+ if (-1 == stat(file->filename,&file->st)) {
+ fprintf(stderr,"stat %s: %s\n",file->filename,strerror(errno));
+ goto oops;
+ }
+ if (!S_ISREG(file->st.st_mode)) {
+ fprintf(stderr,"%s: not a regular file\n",file->filename);
+ goto oops;
+ }
+
+ list_add_tail(&file->window,&h->files);
+ file_createwidgets(h->container, file);
+ XtManageChild(file->widget);
+ fileinfo_queue(file);
+ container_relayout(h->container);
+ return;
+
+ oops:
+ if (file->filename)
+ free(file->filename);
+ if (file->basename)
+ free(file->basename);
+ free(file);
+}
+
+static void filelist_file(struct list_handle *h, char *filename)
+{
+ if (h->filename == filename)
+ return;
+ if (h->filename)
+ free(h->filename);
+ h->filename = strdup(filename);
+ XtVaSetValues(h->shell,XtNtitle,h->filename,NULL);
+}
+
+static void filelist_read(struct list_handle *h, char *filename)
+{
+ FILE *fp;
+ char line[128];
+
+ fp = fopen(filename,"r");
+ if (NULL == fp) {
+ fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
+ return;
+ }
+ while (NULL != fgets(line, sizeof(line), fp)) {
+ filelist_add(h, line);
+ }
+ fclose(fp);
+ filelist_file(h,filename);
+ container_relayout(h->container);
+}
+
+static void filelist_write(struct list_handle *h, char *filename)
+{
+ struct file_button *file;
+ struct list_head *item;
+ FILE *fp;
+
+ fp = fopen(filename,"w");
+ if (NULL == fp) {
+ fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
+ return;
+ }
+ list_for_each(item, &h->files) {
+ file = list_entry(item, struct file_button, window);
+ fprintf(fp,"%s\n",file->filename);
+ }
+ fclose(fp);
+ filelist_file(h,filename);
+}
+
+static void filelist_delall(struct list_handle *h)
+{
+ struct file_button *file;
+ struct list_head *item;
+
+ list_for_each(item, &h->files) {
+ file = list_entry(item, struct file_button, window);
+ XtUnmanageChild(file->widget);
+ XtDestroyWidget(file->widget);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+/* receive data (drops, paste) */
+
+static Atom targets[16];
+static Cardinal ntargets;
+
+static void
+filelist_xfer(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct list_handle *h = clientdata;
+ XmSelectionCallbackStruct *scs = call_data;
+ unsigned char *cdata = scs->value;
+ unsigned long *ldata = scs->value;
+ Atom target = 0;
+ unsigned int i,j,pending;
+ char *file;
+
+ if (debug) {
+ char *y = !scs->type ? NULL : XGetAtomName(dpy,scs->type);
+ char *t = !scs->target ? NULL : XGetAtomName(dpy,scs->target);
+ char *s = !scs->selection ? NULL : XGetAtomName(dpy,scs->selection);
+ fprintf(stderr,"list: id=%p target=%s type=%s selection=%s\n",
+ scs->transfer_id,t,y,s);
+ if (y) XFree(y);
+ if (t) XFree(t);
+ if (s) XFree(s);
+ }
+
+ pending = scs->remaining;
+ if (scs->target == XA_TARGETS) {
+ /* look if we find a target we can deal with ... */
+ for (i = 0; !target && i < scs->length; i++) {
+ for (j = 0; j < ntargets; j++) {
+ if (ldata[i] == targets[j]) {
+ target = ldata[i];
+ break;
+ }
+ }
+ }
+ if (target) {
+ XmTransferValue(scs->transfer_id, target, filelist_xfer,
+ clientdata, XtLastTimestampProcessed(dpy));
+ pending++;
+ }
+ if (debug) {
+ fprintf(stderr,"list: available targets: ");
+ for (i = 0; i < scs->length; i++) {
+ char *name = !ldata[i] ? NULL : XGetAtomName(dpy,ldata[i]);
+ fprintf(stderr,"%s%s", i != 0 ? ", " : "", name);
+ XFree(name);
+ }
+ fprintf(stderr,"\n");
+ if (0 == scs->length)
+ fprintf(stderr,"list: Huh? no TARGETS available?\n");
+ }
+ }
+
+ if (scs->target == XA_FILE_NAME ||
+ scs->target == XA_FILE) {
+ /* load file */
+ if (debug)
+ fprintf(stderr,"list: => \"%s\"\n",cdata);
+ filelist_add(h,cdata);
+ }
+
+ if (scs->target == _NETSCAPE_URL) {
+ /* load file */
+ if (debug)
+ fprintf(stderr,"list: => \"%s\"\n",cdata);
+ filelist_add(h,cdata);
+ }
+
+ if (scs->target == MIME_TEXT_URI_LIST) {
+ /* load file(s) */
+ for (file = strtok(cdata,"\r\n");
+ NULL != file;
+ file = strtok(NULL,"\r\n")) {
+ if (debug)
+ fprintf(stderr,"list: => \"%s\"\n",file);
+ filelist_add(h,file);
+ }
+ }
+
+ XFree(scs->value);
+ if (1 == pending) {
+ /* all done -- clean up */
+ if (debug)
+ fprintf(stderr,"list: all done\n");
+ XmTransferDone(scs->transfer_id, XmTRANSFER_DONE_SUCCEED);
+ XdndDropFinished(widget,scs);
+ }
+}
+
+static void
+filelist_dest_cb(Widget w, XtPointer clientdata, XtPointer call_data)
+{
+ XmDestinationCallbackStruct *dcs = call_data;
+
+ if (debug)
+ fprintf(stderr,"list: xfer id=%p\n",dcs->transfer_id);
+ XmTransferValue(dcs->transfer_id, XA_TARGETS, filelist_xfer,
+ clientdata, XtLastTimestampProcessed(dpy));
+}
+
+/*----------------------------------------------------------------------*/
+
+static void
+filelist_new_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct list_handle *h = clientdata;
+
+ filelist_delall(h);
+ if (h->filename) {
+ free(h->filename);
+ h->filename = NULL;
+ }
+ XtVaSetValues(h->shell,XtNtitle,"new list",NULL);
+}
+
+static void
+init_file_box(Widget box, char *filename)
+{
+ char *dir,*file;
+ XmString s1;
+
+ if (NULL == filename) {
+ dir = strdup(ida_lists);
+ } else {
+ dir = strdup(filename);
+ file = strrchr(dir,'/');
+ if (NULL == file)
+ return;
+ *file = 0;
+ file++;
+ }
+
+ s1 = XmStringGenerate(dir, NULL, XmMULTIBYTE_TEXT, NULL);
+ XtVaSetValues(box,
+ XmNdirectory, s1,
+ XmNpattern, NULL,
+ NULL);
+ XmFileSelectionDoSearch(box,NULL);
+ XmStringFree(s1);
+ free(dir);
+}
+
+static void
+load_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmFileSelectionBoxCallbackStruct *cb = call_data;
+ struct list_handle *h = clientdata;
+ char *filename;
+
+ if (cb->reason == XmCR_OK) {
+ filename = XmStringUnparse(cb->value,NULL,
+ XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
+ NULL,0,0);
+ if (debug)
+ fprintf(stderr,"read list from %s\n",filename);
+ filelist_read(h, filename);
+ }
+ XtUnmanageChild(widget);
+}
+
+static void
+filelist_load_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct list_handle *h = clientdata;
+ Widget help;
+
+ if (NULL == h->loadbox) {
+ h->loadbox = XmCreateFileSelectionDialog(h->shell,"load",NULL,0);
+ help = XmFileSelectionBoxGetChild(h->loadbox,XmDIALOG_HELP_BUTTON);
+ XtUnmanageChild(help);
+ XtAddCallback(h->loadbox,XmNokCallback,load_done_cb,h);
+ XtAddCallback(h->loadbox,XmNcancelCallback,load_done_cb,h);
+ }
+ init_file_box(h->loadbox,h->filename);
+ XtManageChild(h->loadbox);
+}
+
+static void
+save_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmFileSelectionBoxCallbackStruct *cb = call_data;
+ struct list_handle *h = clientdata;
+ char *filename;
+
+ if (cb->reason == XmCR_OK) {
+ filename = XmStringUnparse(cb->value,NULL,
+ XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
+ NULL,0,0);
+ if (debug)
+ fprintf(stderr,"write list to %s\n",filename);
+ filelist_write(h, filename);
+ }
+ XtUnmanageChild(widget);
+}
+
+static void
+filelist_save_as_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct list_handle *h = clientdata;
+ Widget help;
+
+ if (NULL == h->savebox) {
+ h->savebox = XmCreateFileSelectionDialog(h->shell,"save",NULL,0);
+ help = XmFileSelectionBoxGetChild(h->savebox,XmDIALOG_HELP_BUTTON);
+ XtUnmanageChild(help);
+
+ XtAddCallback(h->savebox,XmNokCallback,save_done_cb,h);
+ XtAddCallback(h->savebox,XmNcancelCallback,save_done_cb,h);
+ }
+ init_file_box(h->savebox,h->filename);
+ XtManageChild(h->savebox);
+}
+
+static void
+filelist_save_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct list_handle *h = clientdata;
+
+ if (h->filename) {
+ filelist_write(h, h->filename);
+ } else {
+ filelist_save_as_cb(widget, h, call_data);
+ }
+}
+
+static void
+filelist_destroy(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct list_handle *h = clientdata;
+
+ if (h->filename)
+ free(h->filename);
+ ptr_unregister(h->shell);
+ free(h);
+}
+
+static void
+filelist_list_load(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct list_handle *h = clientdata;
+
+ filelist_delall(h);
+ filelist_read(h, XtName(widget));
+}
+
+static void filelist_builddir(Widget menu, char *path, XtPointer clientdata)
+{
+ Widget push,submenu;
+ XmString str;
+ char filename[1024];
+ struct dirent *ent;
+ struct stat st;
+ DIR *dir;
+
+ dir = opendir(path);
+ while (NULL != (ent = readdir(dir))) {
+ if (ent->d_name[0] == '.')
+ continue;
+ snprintf(filename,sizeof(filename),"%s/%s",
+ path,ent->d_name);
+ if (-1 == lstat(filename,&st))
+ continue;
+
+ str = XmStringGenerate(ent->d_name,NULL, XmMULTIBYTE_TEXT,NULL);
+ if (S_ISREG(st.st_mode)) {
+ push = XtVaCreateManagedWidget(filename,
+ xmPushButtonWidgetClass,menu,
+ XmNlabelString,str,
+ NULL);
+ XtAddCallback(push,XmNactivateCallback,filelist_list_load,clientdata);
+ }
+ if (S_ISDIR(st.st_mode)) {
+ submenu = XmCreatePulldownMenu(menu,"subdirM",NULL,0);
+ XtVaCreateManagedWidget("subdir",xmCascadeButtonWidgetClass,menu,
+ XmNlabelString,str,
+ XmNsubMenuId,submenu,
+ NULL);
+ filelist_builddir(submenu,filename,clientdata);
+ }
+ XmStringFree(str);
+ }
+ closedir(dir);
+}
+
+static void
+filelist_lists(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ WidgetList children,list;
+ Cardinal nchildren;
+ int i;
+
+ XtVaGetValues(widget,
+ XtNchildren,&children,
+ XtNnumChildren,&nchildren,
+ NULL);
+ list = malloc(sizeof(Widget*)*nchildren);
+ memcpy(list,children,sizeof(Widget*)*nchildren);
+ for (i = 0; i < nchildren; i++)
+ XtDestroyWidget(list[i]);
+ free(list);
+
+ filelist_builddir(widget,ida_lists,clientdata);
+}
+
+static void
+filelist_action_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmContainerSelectCallbackStruct *cd = call_data;
+ char *file;
+
+ if (XmCR_DEFAULT_ACTION == cd->reason && 1 == cd->selected_item_count) {
+ file = XtName(cd->selected_items[0]);
+ if (debug)
+ fprintf(stderr,"browser: action %s\n", file);
+ new_file(file,1);
+ }
+}
+
+/*----------------------------------------------------------------------*/
+
+void
+filelist_window(void)
+{
+ Widget form,clip,menubar,menu,push;
+ struct list_handle *h;
+ Arg args[8];
+ int n = 0;
+
+ if (0 == ntargets) {
+ /* first time init */
+ targets[ntargets++] = MIME_TEXT_URI_LIST;
+ targets[ntargets++] = XA_FILE_NAME;
+ targets[ntargets++] = XA_FILE;
+ targets[ntargets++] = _NETSCAPE_URL;
+ }
+
+ h = malloc(sizeof(*h));
+ if (NULL == h) {
+ fprintf(stderr,"out of memory");
+ return;
+ }
+ memset(h,0,sizeof(*h));
+ INIT_LIST_HEAD(&h->files);
+
+ h->shell = XtVaAppCreateShell("filelist","Ida",
+ topLevelShellWidgetClass,
+ dpy,
+ XtNclientLeader,app_shell,
+ XmNdeleteResponse,XmDESTROY,
+ XtNtitle,"new list",
+ NULL);
+ XmdRegisterEditres(h->shell);
+ XtAddCallback(h->shell,XtNdestroyCallback,filelist_destroy,h);
+
+ /* widgets */
+ form = XtVaCreateManagedWidget("form", xmFormWidgetClass, h->shell,
+ NULL);
+ menubar = XmCreateMenuBar(form,"cbar",NULL,0);
+ XtManageChild(menubar);
+ h->status = XtVaCreateManagedWidget("status",xmLabelWidgetClass, form,
+ NULL);
+
+ /* scrolled container */
+ h->details[0] = XmStringGenerate("Image", NULL, XmMULTIBYTE_TEXT,NULL);
+ h->details[DETAIL_SIZE+1] =
+ XmStringGenerate("Size", NULL, XmMULTIBYTE_TEXT,NULL);
+ h->details[DETAIL_COMMENT+1] =
+ XmStringGenerate("Comment", NULL, XmMULTIBYTE_TEXT,NULL);
+ XtSetArg(args[n], XmNdetailColumnHeading, h->details); n++;
+ XtSetArg(args[n], XmNdetailColumnHeadingCount, DETAIL_COUNT+1); n++;
+
+ h->scroll = XmCreateScrolledWindow(form, "scroll", NULL, 0);
+ XtManageChild(h->scroll);
+ h->container = XmCreateContainer(h->scroll,"container",
+ args,n);
+ XtManageChild(h->container);
+ XdndDropSink(h->container);
+
+ XtAddCallback(h->scroll, XmNtraverseObscuredCallback,
+ container_traverse_cb, NULL);
+ XtAddCallback(h->container,XmNdefaultActionCallback,
+ filelist_action_cb,h);
+ XtAddCallback(h->container,XmNconvertCallback,
+ container_convert_cb,h);
+ XtAddCallback(h->container,XmNdestinationCallback,
+ filelist_dest_cb,h);
+
+ XtVaGetValues(h->scroll,XmNclipWindow,&clip,NULL);
+ XtAddEventHandler(clip,StructureNotifyMask,True,container_resize_eh,NULL);
+
+ /* menu - file */
+ menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0);
+ XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ push = XtVaCreateManagedWidget("new",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,filelist_new_cb,h);
+ push = XtVaCreateManagedWidget("load",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,filelist_load_cb,h);
+ push = XtVaCreateManagedWidget("save",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,filelist_save_cb,h);
+ push = XtVaCreateManagedWidget("saveas",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,filelist_save_as_cb,h);
+
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("close",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,destroy_cb,h->shell);
+
+ /* menu - edit */
+ menu = XmCreatePulldownMenu(menubar,"editM",NULL,0);
+ XtVaCreateManagedWidget("edit",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ container_menu_edit(menu,h->container, 0,1,1,1);
+
+ /* menu - view */
+ menu = XmCreatePulldownMenu(menubar,"viewM",NULL,0);
+ XtVaCreateManagedWidget("view",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ container_menu_view(menu,h->container);
+
+ /* menu - lists */
+ menu = XmCreatePulldownMenu(menubar,"listsM",NULL,0);
+ XtVaCreateManagedWidget("lists",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ XtAddCallback(menu, XmNmapCallback, filelist_lists, h);
+
+ /* read dir and show window */
+ container_detail_cb(NULL,h->container,NULL);
+ XtPopup(h->shell,XtGrabNone);
+ ptr_register(h->shell);
+}
+
+void
+filelist_ac(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ filelist_window();
+}
diff --git a/filelist.h b/filelist.h
new file mode 100644
index 0000000..f7fc2ab
--- /dev/null
+++ b/filelist.h
@@ -0,0 +1,3 @@
+void filelist_window(void);
+void filelist_ac(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params);
diff --git a/fileops.c b/fileops.c
new file mode 100644
index 0000000..fb24930
--- /dev/null
+++ b/fileops.c
@@ -0,0 +1,100 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+
+#include "list.h"
+#include "ida.h"
+#include "readers.h"
+#include "filebutton.h"
+#include "fileops.h"
+
+#include <jpeglib.h>
+#include "jpeg/transupp.h" /* Support routines for jpegtran */
+#include "jpegtools.h"
+
+/*----------------------------------------------------------------------*/
+
+struct jobqueue {
+ struct list_head list;
+ char *op;
+ char *filename;
+ char *args;
+};
+static LIST_HEAD(jobs);
+static XtWorkProcId jobproc;
+
+/*----------------------------------------------------------------------*/
+
+static Boolean job_worker(XtPointer clientdata)
+{
+ struct jobqueue *job;
+ unsigned int flags =
+ JFLAG_TRANSFORM_IMAGE |
+ JFLAG_TRANSFORM_THUMBNAIL |
+ JFLAG_UPDATE_ORIENTATION;
+
+ if (list_empty(&jobs)) {
+ /* nothing to do */
+ jobproc = 0;
+ return TRUE;
+ }
+ job = list_entry(jobs.next, struct jobqueue, list);
+
+ /* process job */
+ if (debug)
+ fprintf(stderr,"job worker: %s %s\n",job->op,job->filename);
+ ptr_busy();
+ if (0 == strcmp(job->op,"rotexif")) {
+ jpeg_transform_inplace(job->filename, -1/*auto*/,
+ NULL, NULL, 0, flags);
+
+ } else if (0 == strcmp(job->op,"rotcw")) {
+ jpeg_transform_inplace(job->filename, JXFORM_ROT_90,
+ NULL, NULL, 0, flags);
+
+ } else if (0 == strcmp(job->op,"rotccw")) {
+ jpeg_transform_inplace(job->filename, JXFORM_ROT_270,
+ NULL, NULL, 0, flags);
+
+ } else if (0 == strcmp(job->op,"comment")) {
+ jpeg_transform_inplace(job->filename, JXFORM_NONE, job->args,
+ NULL, 0,
+ JFLAG_UPDATE_COMMENT);
+
+ } else {
+ fprintf(stderr,"job: \"%s\" is *unknown*\n",job->op);
+ }
+ ptr_idle();
+ fileinfo_invalidate(job->filename);
+
+ /* cleanup */
+ list_del(&job->list);
+ free(job->filename);
+ free(job->op);
+ if (job->args)
+ free(job->args);
+ free(job);
+ return FALSE;
+}
+
+/*----------------------------------------------------------------------*/
+
+void job_submit(char *op, char *filename, char *args)
+{
+ struct jobqueue *job;
+
+ job = malloc(sizeof(*job));
+ memset(job,0,sizeof(*job));
+ job->op = strdup(op);
+ job->filename = strdup(filename);
+ if (args)
+ job->args = strdup(args);
+ list_add_tail(&job->list,&jobs);
+ if (debug)
+ fprintf(stderr,"job submit: %s %s\n",job->op,job->filename);
+ if (0 == jobproc)
+ jobproc = XtAppAddWorkProc(app_context,job_worker,NULL);
+}
diff --git a/fileops.h b/fileops.h
new file mode 100644
index 0000000..d5ea2db
--- /dev/null
+++ b/fileops.h
@@ -0,0 +1 @@
+void job_submit(char *op, char *filename, char *args);
diff --git a/filter.c b/filter.c
new file mode 100644
index 0000000..283a1fa
--- /dev/null
+++ b/filter.c
@@ -0,0 +1,495 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+
+#include "readers.h"
+#include "filter.h"
+
+int debug = 0;
+
+/* ----------------------------------------------------------------------- */
+
+static void
+op_grayscale(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ unsigned char *scanline;
+ int i,g;
+
+ scanline = src->data + line * src->i.width * 3;
+ memcpy(dst,scanline,src->i.width * 3);
+ if (line < rect->y1 || line >= rect->y2)
+ return;
+ dst += 3*rect->x1;
+ scanline += 3*rect->x1;
+ for (i = rect->x1; i < rect->x2; i++) {
+ g = (scanline[0]*30 + scanline[1]*59+scanline[2]*11)/100;
+ dst[0] = g;
+ dst[1] = g;
+ dst[2] = g;
+ scanline += 3;
+ dst += 3;
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct op_3x3_handle {
+ struct op_3x3_parm filter;
+ int *linebuf;
+};
+
+static void*
+op_3x3_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm)
+{
+ struct op_3x3_parm *args = parm;
+ struct op_3x3_handle *h;
+
+ h = malloc(sizeof(*h));
+ memcpy(&h->filter,args,sizeof(*args));
+ h->linebuf = malloc(sizeof(int)*3*(src->i.width));
+
+ *i = src->i;
+ return h;
+}
+
+static int inline
+op_3x3_calc_pixel(struct op_3x3_parm *p, unsigned char *s1,
+ unsigned char *s2, unsigned char *s3)
+{
+ int val = 0;
+
+ val += p->f1[0] * s1[0];
+ val += p->f1[1] * s1[3];
+ val += p->f1[2] * s1[6];
+ val += p->f2[0] * s2[0];
+ val += p->f2[1] * s2[3];
+ val += p->f2[2] * s2[6];
+ val += p->f3[0] * s3[0];
+ val += p->f3[1] * s3[3];
+ val += p->f3[2] * s3[6];
+ if (p->mul && p->div)
+ val = val * p->mul / p->div;
+ val += p->add;
+ return val;
+}
+
+static void
+op_3x3_calc_line(struct ida_image *src, struct ida_rect *rect,
+ int *dst, unsigned int line, struct op_3x3_parm *p)
+{
+ unsigned char b1[9],b2[9],b3[9];
+ unsigned char *s1,*s2,*s3;
+ unsigned int i,left,right;
+
+ s1 = src->data + (line-1) * src->i.width * 3;
+ s2 = src->data + line * src->i.width * 3;
+ s3 = src->data + (line+1) * src->i.width * 3;
+ if (0 == line)
+ s1 = src->data + line * src->i.width * 3;
+ if (src->i.height-1 == line)
+ s3 = src->data + line * src->i.width * 3;
+
+ left = rect->x1;
+ right = rect->x2;
+ if (0 == left) {
+ /* left border special case: dup first col */
+ memcpy(b1,s1,3);
+ memcpy(b2,s2,3);
+ memcpy(b3,s3,3);
+ memcpy(b1+3,s1,6);
+ memcpy(b2+3,s2,6);
+ memcpy(b3+3,s3,6);
+ dst[0] = op_3x3_calc_pixel(p,b1,b2,b3);
+ dst[1] = op_3x3_calc_pixel(p,b1+1,b2+1,b3+1);
+ dst[2] = op_3x3_calc_pixel(p,b1+2,b2+2,b3+2);
+ left++;
+ }
+ if (src->i.width == right) {
+ /* right border */
+ memcpy(b1,s1+src->i.width*3-6,6);
+ memcpy(b2,s2+src->i.width*3-6,6);
+ memcpy(b3,s3+src->i.width*3-6,6);
+ memcpy(b1+3,s1+src->i.width*3-3,3);
+ memcpy(b2+3,s2+src->i.width*3-3,3);
+ memcpy(b3+3,s3+src->i.width*3-3,3);
+ dst[src->i.width*3-3] = op_3x3_calc_pixel(p,b1,b2,b3);
+ dst[src->i.width*3-2] = op_3x3_calc_pixel(p,b1+1,b2+1,b3+1);
+ dst[src->i.width*3-1] = op_3x3_calc_pixel(p,b1+2,b2+2,b3+2);
+ right--;
+ }
+
+ dst += 3*left;
+ s1 += 3*(left-1);
+ s2 += 3*(left-1);
+ s3 += 3*(left-1);
+ for (i = left; i < right; i++) {
+ dst[0] = op_3x3_calc_pixel(p,s1++,s2++,s3++);
+ dst[1] = op_3x3_calc_pixel(p,s1++,s2++,s3++);
+ dst[2] = op_3x3_calc_pixel(p,s1++,s2++,s3++);
+ dst += 3;
+ }
+}
+
+static void
+op_3x3_clip_line(unsigned char *dst, int *src, int left, int right)
+{
+ int i,val;
+
+ src += left*3;
+ dst += left*3;
+ for (i = left*3; i < right*3; i++) {
+ val = *(src++);
+ if (val < 0)
+ val = 0;
+ if (val > 255)
+ val = 255;
+ *(dst++) = val;
+ }
+}
+
+static void
+op_3x3_work(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ struct op_3x3_handle *h = data;
+ unsigned char *scanline;
+
+ scanline = src->data + line * src->i.width * 3;
+ memcpy(dst,scanline,src->i.width * 3);
+ if (line < rect->y1 || line >= rect->y2)
+ return;
+
+ op_3x3_calc_line(src,rect,h->linebuf,line,&h->filter);
+ op_3x3_clip_line(dst,h->linebuf,rect->x1,rect->x2);
+}
+
+static void
+op_3x3_free(void *data)
+{
+ struct op_3x3_handle *h = data;
+
+ free(h->linebuf);
+ free(h);
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct op_sharpe_handle {
+ int factor;
+ int *linebuf;
+};
+
+static void*
+op_sharpe_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm)
+{
+ struct op_sharpe_parm *args = parm;
+ struct op_sharpe_handle *h;
+
+ h = malloc(sizeof(*h));
+ h->factor = args->factor;
+ h->linebuf = malloc(sizeof(int)*3*(src->i.width));
+
+ *i = src->i;
+ return h;
+}
+
+static void
+op_sharpe_work(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ static struct op_3x3_parm laplace = {
+ f1: { 1, 1, 1 },
+ f2: { 1, -8, 1 },
+ f3: { 1, 1, 1 },
+ };
+ struct op_sharpe_handle *h = data;
+ unsigned char *scanline;
+ int i;
+
+ scanline = src->data + line * src->i.width * 3;
+ memcpy(dst,scanline,src->i.width * 3);
+ if (line < rect->y1 || line >= rect->y2)
+ return;
+
+ op_3x3_calc_line(src,rect,h->linebuf,line,&laplace);
+ for (i = rect->x1*3; i < rect->x2*3; i++)
+ h->linebuf[i] = scanline[i] - h->linebuf[i] * h->factor / 256;
+ op_3x3_clip_line(dst,h->linebuf,rect->x1,rect->x2);
+}
+
+static void
+op_sharpe_free(void *data)
+{
+ struct op_sharpe_handle *h = data;
+
+ free(h->linebuf);
+ free(h);
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct op_resize_state {
+ float xscale,yscale,inleft;
+ float *rowbuf;
+ unsigned int width,height,srcrow;
+};
+
+static void*
+op_resize_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm)
+{
+ struct op_resize_parm *args = parm;
+ struct op_resize_state *h;
+
+ h = malloc(sizeof(*h));
+ h->width = args->width;
+ h->height = args->height;
+ h->xscale = (float)args->width/src->i.width;
+ h->yscale = (float)args->height/src->i.height;
+ h->rowbuf = malloc(src->i.width * 3 * sizeof(float));
+ h->srcrow = 0;
+ h->inleft = 1;
+
+ *i = src->i;
+ i->width = args->width;
+ i->height = args->height;
+ i->dpi = args->dpi;
+ return h;
+}
+
+static void
+op_resize_work(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ struct op_resize_state *h = data;
+ float outleft,left,weight,d0,d1,d2;
+ unsigned char *csrcline;
+ float *fsrcline;
+ unsigned int i,sx,dx;
+
+ /* scale y */
+ memset(h->rowbuf, 0, src->i.width * 3 * sizeof(float));
+ outleft = 1/h->yscale;
+ while (outleft > 0 && h->srcrow < src->i.height) {
+ if (outleft < h->inleft) {
+ weight = outleft * h->yscale;
+ h->inleft -= outleft;
+ outleft = 0;
+ } else {
+ weight = h->inleft * h->yscale;
+ outleft -= h->inleft;
+ h->inleft = 0;
+ }
+#if 0
+ if (debug)
+ fprintf(stderr,"y: %6.2f%%: %d/%d => %d/%d\n",
+ weight*100,h->srcrow,src->height,line,h->height);
+#endif
+ csrcline = src->data + h->srcrow * src->i.width * 3;
+ for (i = 0; i < src->i.width * 3; i++)
+ h->rowbuf[i] += (float)csrcline[i] * weight;
+ if (0 == h->inleft) {
+ h->inleft = 1;
+ h->srcrow++;
+ }
+ }
+
+ /* scale x */
+ left = 1;
+ fsrcline = h->rowbuf;
+ for (sx = 0, dx = 0; dx < h->width; dx++) {
+ d0 = d1 = d2 = 0;
+ outleft = 1/h->xscale;
+ while (outleft > 0 && dx < h->width && sx < src->i.width) {
+ if (outleft < left) {
+ weight = outleft * h->xscale;
+ left -= outleft;
+ outleft = 0;
+ } else {
+ weight = left * h->xscale;
+ outleft -= left;
+ left = 0;
+ }
+#if 0
+ if (debug)
+ fprintf(stderr," x: %6.2f%%: %d/%d => %d/%d\n",
+ weight*100,sx,src->width,dx,h->width);
+#endif
+ d0 += fsrcline[3*sx+0] * weight;
+ d1 += fsrcline[3*sx+1] * weight;
+ d2 += fsrcline[3*sx+2] * weight;
+ if (0 == left) {
+ left = 1;
+ sx++;
+ }
+ }
+ dst[0] = d0;
+ dst[1] = d1;
+ dst[2] = d2;
+ dst += 3;
+ }
+}
+
+static void
+op_resize_done(void *data)
+{
+ struct op_resize_state *h = data;
+
+ free(h->rowbuf);
+ free(h);
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct op_rotate_state {
+ float angle,sina,cosa;
+ struct ida_rect calc;
+ int cx,cy;
+};
+
+static void*
+op_rotate_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm)
+{
+ struct op_rotate_parm *args = parm;
+ struct op_rotate_state *h;
+ float diag;
+
+ h = malloc(sizeof(*h));
+ h->angle = args->angle * 2 * M_PI / 360;
+ h->sina = sin(h->angle);
+ h->cosa = cos(h->angle);
+ h->cx = (rect->x2 - rect->x1) / 2 + rect->x1;
+ h->cy = (rect->y2 - rect->y1) / 2 + rect->y1;
+
+ /* the area we have to process (worst case: 45°) */
+ diag = sqrt((rect->x2 - rect->x1)*(rect->x2 - rect->x1) +
+ (rect->y2 - rect->y1)*(rect->y2 - rect->y1))/2;
+ h->calc.x1 = h->cx - diag;
+ h->calc.x2 = h->cx + diag;
+ h->calc.y1 = h->cy - diag;
+ h->calc.y2 = h->cy + diag;
+ if (h->calc.x1 < 0)
+ h->calc.x1 = 0;
+ if (h->calc.x2 > src->i.width)
+ h->calc.x2 = src->i.width;
+ if (h->calc.y1 < 0)
+ h->calc.y1 = 0;
+ if (h->calc.y2 > src->i.height)
+ h->calc.y2 = src->i.height;
+
+ *i = src->i;
+ return h;
+}
+
+static inline
+unsigned char* op_rotate_getpixel(struct ida_image *src, struct ida_rect *rect,
+ int sx, int sy, int dx, int dy)
+{
+ static unsigned char black[] = { 0, 0, 0};
+
+ if (sx < rect->x1 || sx >= rect->x2 ||
+ sy < rect->y1 || sy >= rect->y2) {
+ if (dx < rect->x1 || dx >= rect->x2 ||
+ dy < rect->y1 || dy >= rect->y2)
+ return src->data + dy * src->i.width * 3 + dx * 3;
+ return black;
+ }
+ return src->data + sy * src->i.width * 3 + sx * 3;
+}
+
+static void
+op_rotate_work(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int y, void *data)
+{
+ struct op_rotate_state *h = data;
+ unsigned char *pix;
+ float fx,fy,w;
+ int x,sx,sy;
+
+ pix = src->data + y * src->i.width * 3;
+ memcpy(dst,pix,src->i.width * 3);
+ if (y < h->calc.y1 || y >= h->calc.y2)
+ return;
+
+ dst += 3*h->calc.x1;
+ memset(dst, 0, (h->calc.x2-h->calc.x1) * 3);
+ for (x = h->calc.x1; x < h->calc.x2; x++, dst+=3) {
+ fx = h->cosa * (x - h->cx) - h->sina * (y - h->cy) + h->cx;
+ fy = h->sina * (x - h->cx) + h->cosa * (y - h->cy) + h->cy;
+ sx = (int)fx;
+ sy = (int)fy;
+ if (fx < 0)
+ sx--;
+ if (fy < 0)
+ sy--;
+ fx -= sx;
+ fy -= sy;
+
+ pix = op_rotate_getpixel(src,rect,sx,sy,x,y);
+ w = (1-fx) * (1-fy);
+ dst[0] += pix[0] * w;
+ dst[1] += pix[1] * w;
+ dst[2] += pix[2] * w;
+ pix = op_rotate_getpixel(src,rect,sx+1,sy,x,y);
+ w = fx * (1-fy);
+ dst[0] += pix[0] * w;
+ dst[1] += pix[1] * w;
+ dst[2] += pix[2] * w;
+ pix = op_rotate_getpixel(src,rect,sx,sy+1,x,y);
+ w = (1-fx) * fy;
+ dst[0] += pix[0] * w;
+ dst[1] += pix[1] * w;
+ dst[2] += pix[2] * w;
+ pix = op_rotate_getpixel(src,rect,sx+1,sy+1,x,y);
+ w = fx * fy;
+ dst[0] += pix[0] * w;
+ dst[1] += pix[1] * w;
+ dst[2] += pix[2] * w;
+ }
+}
+
+static void
+op_rotate_done(void *data)
+{
+ struct op_rotate_state *h = data;
+
+ free(h);
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct ida_op desc_grayscale = {
+ name: "grayscale",
+ init: op_none_init,
+ work: op_grayscale,
+ done: op_none_done,
+};
+struct ida_op desc_3x3 = {
+ name: "3x3",
+ init: op_3x3_init,
+ work: op_3x3_work,
+ done: op_3x3_free,
+};
+struct ida_op desc_sharpe = {
+ name: "sharpe",
+ init: op_sharpe_init,
+ work: op_sharpe_work,
+ done: op_sharpe_free,
+};
+struct ida_op desc_resize = {
+ name: "resize",
+ init: op_resize_init,
+ work: op_resize_work,
+ done: op_resize_done,
+};
+struct ida_op desc_rotate = {
+ name: "rotate",
+ init: op_rotate_init,
+ work: op_rotate_work,
+ done: op_rotate_done,
+};
diff --git a/filter.h b/filter.h
new file mode 100644
index 0000000..37d67ee
--- /dev/null
+++ b/filter.h
@@ -0,0 +1,27 @@
+
+struct op_3x3_parm {
+ int f1[3];
+ int f2[3];
+ int f3[3];
+ int mul,div,add;
+};
+
+struct op_sharpe_parm {
+ int factor;
+};
+
+struct op_resize_parm {
+ int width;
+ int height;
+ int dpi;
+};
+
+struct op_rotate_parm {
+ int angle;
+};
+
+extern struct ida_op desc_grayscale;
+extern struct ida_op desc_3x3;
+extern struct ida_op desc_sharpe;
+extern struct ida_op desc_resize;
+extern struct ida_op desc_rotate;
diff --git a/fs.c b/fs.c
new file mode 100644
index 0000000..47864fd
--- /dev/null
+++ b/fs.c
@@ -0,0 +1,502 @@
+/*
+ * text rendering for the framebuffer console
+ * pick fonts from X11 font server or
+ * use linux consolefont psf files.
+ * (c) 2001 Gerd Knorr <kraxel@bytesex.org>
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/fb.h>
+
+#include "fbtools.h"
+#include "fs.h"
+
+/* ------------------------------------------------------------------ */
+
+#define BIT_ORDER BitmapFormatBitOrderMSB
+#ifdef BYTE_ORDER
+#undef BYTE_ORDER
+#endif
+#define BYTE_ORDER BitmapFormatByteOrderMSB
+#define SCANLINE_UNIT BitmapFormatScanlineUnit8
+#define SCANLINE_PAD BitmapFormatScanlinePad8
+#define EXTENTS BitmapFormatImageRectMin
+
+#define SCANLINE_PAD_BYTES 1
+#define GLWIDTHBYTESPADDED(bits, nBytes) \
+ ((nBytes) == 1 ? (((bits) + 7) >> 3) /* pad to 1 byte */\
+ :(nBytes) == 2 ? ((((bits) + 15) >> 3) & ~1) /* pad to 2 bytes */\
+ :(nBytes) == 4 ? ((((bits) + 31) >> 3) & ~3) /* pad to 4 bytes */\
+ :(nBytes) == 8 ? ((((bits) + 63) >> 3) & ~7) /* pad to 8 bytes */\
+ : 0)
+
+static const unsigned fs_masktab[] = {
+ (1 << 7), (1 << 6), (1 << 5), (1 << 4),
+ (1 << 3), (1 << 2), (1 << 1), (1 << 0),
+};
+
+/* ------------------------------------------------------------------ */
+
+#ifndef X_DISPLAY_MISSING
+static FSServer *svr;
+#endif
+unsigned int fs_bpp, fs_black, fs_white;
+
+void (*fs_setpixel)(void *ptr, unsigned int color);
+
+static void setpixel1(void *ptr, unsigned int color)
+{
+ unsigned char *p = ptr;
+ *p = color;
+}
+static void setpixel2(void *ptr, unsigned int color)
+{
+ unsigned short *p = ptr;
+ *p = color;
+}
+static void setpixel3(void *ptr, unsigned int color)
+{
+ unsigned char *p = ptr;
+ *(p++) = (color >> 16) & 0xff;
+ *(p++) = (color >> 8) & 0xff;
+ *(p++) = color & 0xff;
+}
+static void setpixel4(void *ptr, unsigned int color)
+{
+ unsigned long *p = ptr;
+ *p = color;
+}
+
+int fs_init_fb(int white8)
+{
+ switch (fb_var.bits_per_pixel) {
+ case 8:
+ fs_white = white8; fs_black = 0; fs_bpp = 1;
+ fs_setpixel = setpixel1;
+ break;
+ case 15:
+ case 16:
+ if (fb_var.green.length == 6)
+ fs_white = 0xffff;
+ else
+ fs_white = 0x7fff;
+ fs_black = 0; fs_bpp = 2;
+ fs_setpixel = setpixel2;
+ break;
+ case 24:
+ fs_white = 0xffffff; fs_black = 0; fs_bpp = fb_var.bits_per_pixel/8;
+ fs_setpixel = setpixel3;
+ break;
+ case 32:
+ fs_white = 0xffffff; fs_black = 0; fs_bpp = fb_var.bits_per_pixel/8;
+ fs_setpixel = setpixel4;
+ break;
+ default:
+ fprintf(stderr, "Oops: %i bit/pixel ???\n",
+ fb_var.bits_per_pixel);
+ return -1;
+ }
+ return 0;
+}
+
+void fs_render_fb(unsigned char *ptr, int pitch,
+ FSXCharInfo *charInfo, unsigned char *data)
+{
+ int row,bit,bpr,x;
+
+ bpr = GLWIDTHBYTESPADDED((charInfo->right - charInfo->left),
+ SCANLINE_PAD_BYTES);
+ for (row = 0; row < (charInfo->ascent + charInfo->descent); row++) {
+ for (x = 0, bit = 0; bit < (charInfo->right - charInfo->left); bit++) {
+ if (data[bit>>3] & fs_masktab[bit&7])
+ fs_setpixel(ptr+x,fs_white);
+ x += fs_bpp;
+ }
+ data += bpr;
+ ptr += pitch;
+ }
+}
+
+int fs_puts(struct fs_font *f, unsigned int x, unsigned int y,
+ unsigned char *str)
+{
+ unsigned char *pos,*start;
+ int i,c,j,w;
+
+ pos = fb_mem+fb_mem_offset;
+ pos += fb_fix.line_length * y;
+ for (i = 0; str[i] != '\0'; i++) {
+ c = str[i];
+ if (NULL == f->eindex[c])
+ continue;
+ /* clear with bg color */
+ start = pos + x*fs_bpp + f->fontHeader.max_bounds.descent * fb_fix.line_length;
+ w = (f->eindex[c]->width+1)*fs_bpp;
+ for (j = 0; j < f->height; j++) {
+ memset(start,0,w);
+ start += fb_fix.line_length;
+ }
+ /* draw char */
+ start = pos + x*fs_bpp + fb_fix.line_length * (f->height-f->eindex[c]->ascent);
+ fs_render_fb(start,fb_fix.line_length,f->eindex[c],f->gindex[c]);
+ x += f->eindex[c]->width;
+ if (x > fb_var.xres - f->width)
+ return -1;
+ }
+ return x;
+}
+
+int fs_textwidth(struct fs_font *f, unsigned char *str)
+{
+ int width = 0;
+ int i,c;
+
+ for (i = 0; str[i] != '\0'; i++) {
+ c = str[i];
+ if (NULL == f->eindex[c])
+ continue;
+ width += f->eindex[c]->width;
+ }
+ return width;
+}
+
+void fs_render_tty(FSXCharInfo *charInfo, unsigned char *data)
+{
+ int bpr,row,bit,on;
+
+ bpr = GLWIDTHBYTESPADDED((charInfo->right - charInfo->left),
+ SCANLINE_PAD_BYTES);
+ for (row = 0; row < (charInfo->ascent + charInfo->descent); row++) {
+ fprintf(stdout,"|");
+ for (bit = 0; bit < (charInfo->right - charInfo->left); bit++) {
+ on = data[bit>>3] & fs_masktab[bit&7];
+ fprintf(stdout,"%s",on ? "##" : " ");
+ }
+ fprintf(stdout,"|\n");
+ data += bpr;
+ }
+ fprintf(stdout,"--\n");
+}
+
+/* ------------------------------------------------------------------ */
+
+#ifndef X_DISPLAY_MISSING
+/* connect to font server */
+int fs_connect(char *servername)
+{
+ if (NULL == servername)
+ servername = getenv("FONTSERVER");
+ if (NULL == servername)
+ servername = "unix/:7100";
+ svr = FSOpenServer(servername);
+ if (NULL == svr) {
+ if (NULL == FSServerName(servername)) {
+ fprintf(stderr, "no font server defined\n");
+ } else {
+ fprintf(stderr, "unable to open server \"%s\"\n",
+ FSServerName(servername));
+ }
+ return -1;
+ }
+ return 0;
+}
+
+/* load font from font server */
+struct fs_font* fs_open(char *pattern)
+{
+ int nnames = 1;
+ int available,high,low,encoding,bpr;
+ char **fonts;
+ unsigned char *glyph;
+ Font dummy;
+ FSBitmapFormat format;
+ FSXCharInfo *charInfo;
+ struct fs_font *f = NULL;
+
+ if (NULL == svr) {
+ fprintf(stderr,"fs: not connected\n");
+ return NULL;
+ }
+
+ fonts = FSListFonts(svr, pattern, nnames, &available);
+ if (0 == available) {
+ fprintf(stderr,"fs: font not available [%s]\n",pattern);
+ goto out;
+ }
+ fprintf(stderr,"using x11 font \"%s\"\n",fonts[0]);
+
+ f = malloc(sizeof(*f));
+ memset(f,0,sizeof(*f));
+ f->font = FSOpenBitmapFont(svr, 0, 0, fonts[0], &dummy);
+ FSFreeFontNames(fonts);
+ if (0 == f->font)
+ goto out;
+
+ FSQueryXInfo(svr,f->font,&f->fontHeader, &f->propInfo,
+ &f->propOffsets, &f->propData);
+ format = BYTE_ORDER | BIT_ORDER | SCANLINE_UNIT | SCANLINE_PAD | EXTENTS;
+ FSQueryXExtents16(svr, f->font, True, (FSChar2b *) 0, 0, &f->extents);
+ FSQueryXBitmaps16(svr, f->font, format, True, (FSChar2b *) 0, 0,
+ &f->offsets, &f->glyphs);
+
+ f->maxenc = (f->fontHeader.char_range.max_char.high+1) << 8;
+ f->width = f->fontHeader.max_bounds.right - f->fontHeader.min_bounds.left;
+ f->height = f->fontHeader.max_bounds.ascent + f->fontHeader.max_bounds.descent;
+ f->eindex = malloc(f->maxenc * sizeof(FSXCharInfo*));
+ f->gindex = malloc(f->maxenc * sizeof(unsigned char*));
+ memset(f->eindex,0,f->maxenc * sizeof(FSXCharInfo*));
+ memset(f->gindex,0,f->maxenc * sizeof(unsigned char*));
+
+ glyph = f->glyphs;
+ charInfo = f->extents;
+ for (high = f->fontHeader.char_range.min_char.high;
+ high <= f->fontHeader.char_range.max_char.high;
+ high++) {
+ for (low = f->fontHeader.char_range.min_char.low;
+ low <= f->fontHeader.char_range.max_char.low;
+ low++) {
+ bpr = GLWIDTHBYTESPADDED((charInfo->right - charInfo->left),
+ SCANLINE_PAD_BYTES);
+ encoding = (high<<8) + low;
+#ifdef TTY
+ fprintf(stdout,"e=0x%x | w=%d l=%d r=%d | a=%d d=%d\n",
+ encoding,charInfo->width,charInfo->left,
+ charInfo->right,charInfo->ascent,charInfo->descent);
+#endif
+ if ((charInfo->width != 0) || (charInfo->right != charInfo->left)) {
+ f->gindex[encoding] = glyph;
+ f->eindex[encoding] = charInfo;
+#ifdef TTY
+ fs_render_tty(f->eindex[encoding],
+ f->gindex[encoding]);
+#endif
+ }
+ glyph += (charInfo->descent + charInfo->ascent) * bpr;
+ charInfo++;
+ }
+ }
+ return f;
+
+ out:
+ if (f)
+ fs_free(f);
+ return NULL;
+}
+#endif
+
+void fs_free(struct fs_font *f)
+{
+ if (f->gindex)
+ free(f->gindex);
+#if 0
+ if (f->extents)
+ FSFree((char *) f->extents);
+ if (f->offsets)
+ FSFree((char *) f->offsets);
+ if (f->propOffsets)
+ FSFree((char *) (f->propOffsets));
+ if (f->propData)
+ FSFree((char *) (f->propData));
+#endif
+#if 0 /* FIXME */
+ if (f->glyphs)
+ FSFree((char *) f->glyphs);
+#endif
+ free(f);
+}
+
+/* ------------------------------------------------------------------ */
+/* load console font file */
+
+static char *default_font[] = {
+ /* why the heck every f*cking distribution picks another
+ location for these fonts ??? */
+ "/usr/share/consolefonts/lat1-16.psf",
+ "/usr/share/consolefonts/lat1-16.psf.gz",
+ "/usr/share/consolefonts/lat1-16.psfu.gz",
+ "/usr/share/kbd/consolefonts/lat1-16.psf",
+ "/usr/share/kbd/consolefonts/lat1-16.psf.gz",
+ "/usr/share/kbd/consolefonts/lat1-16.psfu.gz",
+ "/usr/lib/kbd/consolefonts/lat1-16.psf",
+ "/usr/lib/kbd/consolefonts/lat1-16.psf.gz",
+ "/usr/lib/kbd/consolefonts/lat1-16.psfu.gz",
+ "/lib/kbd/consolefonts/lat1-16.psf",
+ "/lib/kbd/consolefonts/lat1-16.psf.gz",
+ "/lib/kbd/consolefonts/lat1-16.psfu.gz",
+ NULL
+};
+
+struct fs_font* fs_consolefont(char **filename)
+{
+ int i;
+ char *h,command[256];
+ struct fs_font *f = NULL;
+ FILE *fp;
+
+ if (NULL == filename)
+ filename = default_font;
+
+ for(i = 0; filename[i] != NULL; i++) {
+ if (-1 == access(filename[i],R_OK))
+ continue;
+ break;
+ }
+ if (NULL == filename[i]) {
+ fprintf(stderr,"can't find console font file\n");
+ return NULL;
+ }
+
+ h = filename[i]+strlen(filename[i])-3;
+ if (0 == strcmp(h,".gz")) {
+ sprintf(command,"zcat %s",filename[i]);
+ fp = popen(command,"r");
+ } else {
+ fp = fopen(filename[i], "r");
+ }
+ if (NULL == fp) {
+ fprintf(stderr,"can't open %s: %s\n",filename[i],strerror(errno));
+ return NULL;
+ }
+
+ if (fgetc(fp) != 0x36 ||
+ fgetc(fp) != 0x04) {
+ fprintf(stderr,"can't use font %s\n",filename[i]);
+ return NULL;
+ }
+ fprintf(stderr,"using linux console font \"%s\"\n",filename[i]);
+
+ f = malloc(sizeof(*f));
+ memset(f,0,sizeof(*f));
+
+ fgetc(fp);
+ f->maxenc = 256;
+ f->width = 8;
+ f->height = fgetc(fp);
+ f->fontHeader.min_bounds.left = 0;
+ f->fontHeader.max_bounds.right = f->width;
+ f->fontHeader.max_bounds.descent = 0;
+ f->fontHeader.max_bounds.ascent = f->height;
+
+ f->glyphs = malloc(f->height * 256);
+ f->extents = malloc(sizeof(FSXCharInfo)*256);
+ fread(f->glyphs, 256, f->height, fp);
+ fclose(fp);
+
+ f->eindex = malloc(sizeof(FSXCharInfo*) * 256);
+ f->gindex = malloc(sizeof(unsigned char*) * 256);
+ for (i = 0; i < 256; i++) {
+ f->eindex[i] = f->extents +i;
+ f->gindex[i] = f->glyphs +i * f->height;
+ f->eindex[i]->left = 0;
+ f->eindex[i]->right = 7;
+ f->eindex[i]->width = 8;
+ f->eindex[i]->descent = 0;
+ f->eindex[i]->ascent = f->height;
+ }
+ return f;
+}
+
+
+#ifdef TESTING
+/* ------------------------------------------------------------------ */
+/* for testing */
+
+int debug;
+
+/* list fonts */
+int fs_ls(char *pattern)
+{
+ int nnames = 16;
+ int available,i;
+ char **fonts;
+
+ if (NULL == svr) {
+ fprintf(stderr,"fs: not connected\n");
+ return -1;
+ }
+
+ fonts = FSListFonts(svr, pattern, nnames, &available);
+ while (nnames <= available) {
+ nnames *= 2;
+ FSFreeFontNames(fonts);
+ fonts = FSListFonts(svr, pattern, nnames, &available);
+ }
+ for (i = 0; i < available; i++) {
+ fprintf(stderr,"%s\n",fonts[i]);
+ }
+ FSFreeFontNames(fonts);
+ return 0;
+}
+
+void dump_charset(struct fs_font *f)
+{
+ unsigned char *pos;
+ int c,x,y;
+
+ x = 0, y = 0;
+ for (c = 0; c < f->maxenc; c++) {
+ if (NULL == f->eindex[c])
+ continue;
+ pos = fb_mem+fb_mem_offset;
+ pos += fb_fix.line_length * (y+f->height-f->eindex[c]->ascent);
+ pos += x*bpp;
+ fs_render_fb(pos,fb_fix.line_length,f->eindex[c],f->gindex[c]);
+ x += f->eindex[c]->right-f->eindex[c]->left+1;
+ if (x > fb_var.xres - f->width) {
+ x = 0;
+ y += f->height+1;
+ }
+ if (y > fb_var.yres - f->height)
+ break;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct fs_font *f = NULL;
+ unsigned char dummy[42];
+ int fd;
+
+ if (argc < 2) {
+ fprintf(stderr,"missing arg\n");
+ exit(1);
+ }
+
+ /* try font server */
+ if (-1 != fs_connect(NULL)) {
+ fs_ls(argv[1]);
+ f = fs_open(argv[1]);
+ if (NULL == f)
+ fprintf(stderr,"no such font\n");
+ }
+
+ /* try console font */
+ if (NULL == f)
+ f = fs_consolefont(NULL);
+ if (NULL == f)
+ exit(1);
+
+#ifdef TTY
+ exit(1);
+#endif
+
+ fd = fb_init(NULL, NULL, 0);
+ fb_cleanup_fork();
+ fb_switch_init();
+ fs_init_fb();
+
+ if (argc < 3) {
+ dump_charset(f);
+ } else {
+ fs_puts(f,0,0,argv[2]);
+ }
+ fgets(dummy,42,stdin);
+
+ return 0;
+}
+#endif
diff --git a/fs.h b/fs.h
new file mode 100644
index 0000000..3b5fe59
--- /dev/null
+++ b/fs.h
@@ -0,0 +1,72 @@
+#ifndef X_DISPLAY_MISSING
+# include <FSlib.h>
+
+struct fs_font {
+ Font font;
+ FSXFontInfoHeader fontHeader;
+ FSPropInfo propInfo;
+ FSPropOffset *propOffsets;
+ unsigned char *propData;
+
+ FSXCharInfo *extents;
+ FSOffset *offsets;
+ unsigned char *glyphs;
+
+ int maxenc,width,height;
+ FSXCharInfo **eindex;
+ unsigned char **gindex;
+};
+
+#else
+
+typedef struct _FSXCharInfo {
+ short left;
+ short right;
+ short width;
+ short ascent;
+ short descent;
+ //unsigned short attributes;
+} FSXCharInfo;
+
+typedef struct _FSXFontInfoHeader {
+ //int flags;
+ //FSRange char_range;
+ //unsigned draw_direction;
+ //FSChar2b default_char;
+ FSXCharInfo min_bounds;
+ FSXCharInfo max_bounds;
+ short font_ascent;
+ short font_descent;
+} FSXFontInfoHeader;
+
+struct fs_font {
+ FSXFontInfoHeader fontHeader;
+ //unsigned char *propData;
+ FSXCharInfo *extents;
+ unsigned char *glyphs;
+ int maxenc,width,height;
+ FSXCharInfo **eindex;
+ unsigned char **gindex;
+};
+
+#endif
+
+/* ------------------------------------------------------------------ */
+
+extern unsigned int fs_bpp, fs_black, fs_white;
+void (*fs_setpixel)(void *ptr, unsigned int color);
+
+int fs_init_fb(int white8);
+void fs_render_fb(unsigned char *ptr, int pitch,
+ FSXCharInfo *charInfo, unsigned char *data);
+int fs_puts(struct fs_font *f, unsigned int x, unsigned int y,
+ unsigned char *str);
+int fs_textwidth(struct fs_font *f, unsigned char *str);
+void fs_render_tty(FSXCharInfo *charInfo, unsigned char *data);
+
+#ifndef X_DISPLAY_MISSING
+int fs_connect(char *servername);
+struct fs_font* fs_open(char *pattern);
+#endif
+struct fs_font* fs_consolefont(char **filename);
+void fs_free(struct fs_font *f);
diff --git a/genthumbnail.c b/genthumbnail.c
new file mode 100644
index 0000000..e3c0505
--- /dev/null
+++ b/genthumbnail.c
@@ -0,0 +1,219 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include <jpeglib.h>
+#include "jpeg/transupp.h" /* Support routines for jpegtran */
+#include "jpegtools.h"
+
+#include "misc.h"
+
+#include "readers.h"
+#include "filter.h"
+#include "genthumbnail.h"
+
+/* ---------------------------------------------------------------------- */
+
+static struct ida_image*
+read_jpeg(char *filename)
+{
+ struct ida_image *img;
+ FILE *fp;
+ unsigned int y;
+ void *data;
+
+ /* open file */
+ if (NULL == (fp = fopen(filename, "r"))) {
+ fprintf(stderr,"open %s: %s\n",filename,strerror(errno));
+ return NULL;
+ }
+
+ /* load image */
+ img = malloc(sizeof(*img));
+ memset(img,0,sizeof(*img));
+ data = jpeg_loader.init(fp,filename,0,&img->i,0);
+ if (NULL == data) {
+ fprintf(stderr,"loading %s [%s] FAILED\n",filename,jpeg_loader.name);
+ free(img);
+ return NULL;
+ }
+ img->data = malloc(img->i.width * img->i.height * 3);
+ for (y = 0; y < img->i.height; y++)
+ jpeg_loader.read(img->data + img->i.width * 3 * y, y, data);
+ jpeg_loader.done(data);
+ return img;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct ida_image*
+scale_thumbnail(struct ida_image *src, int max)
+{
+ struct op_resize_parm p;
+ struct ida_rect rect;
+ struct ida_image *dest;
+ void *data;
+ unsigned int y;
+ float xs,ys,scale;
+
+ xs = (float)max / src->i.width;
+ ys = (float)max / src->i.height;
+ scale = (xs < ys) ? xs : ys;
+
+ dest = malloc(sizeof(*dest));
+ memset(dest,0,sizeof(*dest));
+ memset(&rect,0,sizeof(rect));
+ memset(&p,0,sizeof(p));
+
+ p.width = src->i.width * scale;
+ p.height = src->i.height * scale;
+ p.dpi = src->i.dpi;
+ if (0 == p.width)
+ p.width = 1;
+ if (0 == p.height)
+ p.height = 1;
+
+ data = desc_resize.init(src,&rect,&dest->i,&p);
+ dest->data = malloc(dest->i.width * dest->i.height * 3);
+ for (y = 0; y < dest->i.height; y++)
+ desc_resize.work(src,&rect,
+ dest->data + 3 * dest->i.width * y,
+ y, data);
+ desc_resize.done(data);
+ return dest;
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct thc {
+ struct jpeg_compress_struct dst;
+ struct jpeg_error_mgr err;
+ unsigned char *out;
+ int osize;
+};
+
+static void thc_dest_init(struct jpeg_compress_struct *cinfo)
+{
+ struct thc *h = container_of(cinfo, struct thc, dst);
+ cinfo->dest->next_output_byte = h->out;
+ cinfo->dest->free_in_buffer = h->osize;
+}
+
+static boolean thc_dest_flush(struct jpeg_compress_struct *cinfo)
+{
+ fprintf(stderr,"jpeg: panic: output buffer full\n");
+ exit(1);
+}
+
+static void thc_dest_term(struct jpeg_compress_struct *cinfo)
+{
+ struct thc *h = container_of(cinfo, struct thc, dst);
+ h->osize -= cinfo->dest->free_in_buffer;
+}
+
+static struct jpeg_destination_mgr thumbnail_dst = {
+ .init_destination = thc_dest_init,
+ .empty_output_buffer = thc_dest_flush,
+ .term_destination = thc_dest_term,
+};
+
+static int
+compress_thumbnail(struct ida_image *img, char *dest, int max)
+{
+ struct thc thc;
+ unsigned char *line;
+ unsigned int i;
+
+ memset(&thc,0,sizeof(thc));
+ thc.dst.err = jpeg_std_error(&thc.err);
+ jpeg_create_compress(&thc.dst);
+ thc.dst.dest = &thumbnail_dst;
+ thc.out = dest;
+ thc.osize = max;
+
+ thc.dst.image_width = img->i.width;
+ thc.dst.image_height = img->i.height;
+ thc.dst.input_components = 3;
+ thc.dst.in_color_space = JCS_RGB;
+ jpeg_set_defaults(&thc.dst);
+ jpeg_start_compress(&thc.dst, TRUE);
+
+ for (i = 0, line = img->data; i < img->i.height; i++, line += img->i.width*3)
+ jpeg_write_scanlines(&thc.dst, &line, 1);
+
+ jpeg_finish_compress(&(thc.dst));
+ jpeg_destroy_compress(&(thc.dst));
+
+ return thc.osize;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int create_thumbnail(char *filename, char *dest, int max)
+{
+ struct ida_image *img,*thumb;
+ int size;
+
+ //fprintf(stderr,"%s: read ",filename);
+ img = read_jpeg(filename);
+ if (!img) {
+ fprintf(stderr,"FAILED\n");
+ return -1;
+ }
+
+ //fprintf(stderr,"scale ");
+ thumb = scale_thumbnail(img,160);
+ if (!thumb) {
+ free(img->data);
+ free(img);
+ fprintf(stderr,"FAILED\n");
+ return -1;
+ }
+
+ //fprintf(stderr,"compress ");
+ size = compress_thumbnail(thumb,dest,max);
+
+ /* cleanup */
+ free(img->data);
+ free(img);
+ free(thumb->data);
+ free(thumb);
+ return size;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#if 0
+
+#define THUMB_MAX 65536
+
+static int handle_image(char *filename)
+{
+ char *dest;
+ int size;
+
+ dest = malloc(THUMB_MAX);
+ size = create_thumbnail(filename,dest,THUMB_MAX);
+
+ fprintf(stderr,"transform ");
+ jpeg_transform_inplace(filename, JXFORM_NONE, NULL,
+ dest, size, JFLAG_UPDATE_THUMBNAIL);
+
+ fprintf(stderr,"done\n");
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ int i;
+
+ for (i = 1; i < argc; i++)
+ handle_image(argv[i]);
+ return 0;
+}
+
+#endif
+
diff --git a/genthumbnail.h b/genthumbnail.h
new file mode 100644
index 0000000..add3ff6
--- /dev/null
+++ b/genthumbnail.h
@@ -0,0 +1 @@
+int create_thumbnail(char *filename, char *dest, int max);
diff --git a/hex.c b/hex.c
new file mode 100644
index 0000000..b648233
--- /dev/null
+++ b/hex.c
@@ -0,0 +1,141 @@
+/*
+ * file viewer (hex dump)
+ * quick & dirty for now, handling xxl files doesn't work very well ...
+ *
+ * (c) 2001 Gerd Knorr <kraxel@bytesex.org>
+ *
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/Form.h>
+#include <Xm/Label.h>
+#include <Xm/RowColumn.h>
+#include <Xm/PushB.h>
+#include <Xm/ScrolledW.h>
+#include <Xm/SelectioB.h>
+
+#include "RegEdit.h"
+#include "hex.h"
+
+extern Display *dpy;
+extern Widget app_shell;
+
+/*----------------------------------------------------------------------*/
+
+static Widget hex_dlg, hex_label;
+
+static void
+hex_destroy(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XtDestroyWidget(hex_dlg);
+ hex_dlg = NULL;
+}
+
+static XmString
+hexify(int addr,unsigned char *data, int bytes)
+{
+ XmString xmpage,xmchunk;
+ char line[120];
+ int len,y,i;
+
+ xmpage = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT, NULL);
+ for (y = 0; y < bytes; y += 16) {
+ /* hexify line */
+ len = sprintf(line,"%04x ",addr+y);
+ for (i = 0; i < 16; i++) {
+ if (y+i < bytes)
+ len += sprintf(line+len,"%02x ",data[y+i]);
+ else
+ len += sprintf(line+len," ");
+ if (3 == i%4)
+ len += sprintf(line+len," ");
+ }
+ for (i = 0; i < 16; i++) {
+ if (y+i < bytes)
+ if (isprint(data[y+i]))
+ len += sprintf(line+len,"%c",data[y+i]);
+ else
+ len += sprintf(line+len,".");
+ else
+ len += sprintf(line+len," ");
+ }
+ len += sprintf(line+len,"\n");
+ /* add last chunk for line */
+ xmchunk = XmStringGenerate(line, NULL, XmMULTIBYTE_TEXT, NULL);
+ xmpage = XmStringConcatAndFree(xmpage, xmchunk);
+ }
+ return xmpage;
+}
+
+/* guess whenever we have ascii or binary data */
+static int
+is_binary(unsigned char *data, int bytes)
+{
+ int i, bin;
+
+ bin = 0;
+ for (i = 0; i < 64 && i < bytes; i++) {
+ if (!isprint(data[i]) &&
+ data[i] != '\t' &&
+ data[i] != '\n')
+ bin = 1;
+ }
+ return bin;
+}
+
+void
+hex_display(char *filename)
+{
+ Widget view;
+ XmString xmpage,chunk;
+ unsigned char data[32768+1];
+ int chars;
+ FILE *fp;
+
+ /* build dialog */
+ if (!hex_dlg) {
+ hex_dlg = XmCreatePromptDialog(app_shell,"hex",NULL,0);
+ XmdRegisterEditres(XtParent(hex_dlg));
+ XtUnmanageChild(XmSelectionBoxGetChild(hex_dlg,
+ XmDIALOG_SELECTION_LABEL));
+ XtUnmanageChild(XmSelectionBoxGetChild(hex_dlg,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmSelectionBoxGetChild(hex_dlg,
+ XmDIALOG_CANCEL_BUTTON));
+ XtUnmanageChild(XmSelectionBoxGetChild(hex_dlg,XmDIALOG_TEXT));
+ XtAddCallback(hex_dlg,XmNokCallback,hex_destroy,NULL);
+ view = XmCreateScrolledWindow(hex_dlg,"view",NULL,0);
+ XtManageChild(view);
+ hex_label = XtVaCreateManagedWidget("label", xmLabelWidgetClass,
+ view, NULL);
+ }
+ XtManageChild(hex_dlg);
+
+ /* read the file (32k max) */
+ fp = fopen(filename,"r");
+ memset(data,0,sizeof(data));
+ chars = fread(data, 1, sizeof(data)-1, fp);
+ if (is_binary(data,chars)) {
+ xmpage = hexify(0, data, chars);
+ if (sizeof(data)-1 == chars) {
+ chunk = XmStringGenerate("[ ... ]\n",NULL,
+ XmMULTIBYTE_TEXT, NULL);
+ xmpage = XmStringConcatAndFree(xmpage,chunk);
+ }
+ } else {
+ xmpage = XmStringGenerate(data, NULL, XmMULTIBYTE_TEXT, NULL);
+ if (sizeof(data)-1 == chars) {
+ chunk = XmStringGenerate("\n[ ... ]\n",NULL,
+ XmMULTIBYTE_TEXT, NULL);
+ xmpage = XmStringConcatAndFree(xmpage,chunk);
+ }
+ }
+ XtVaSetValues(hex_label,XmNlabelString,xmpage,NULL);
+ XmStringFree(xmpage);
+ XtVaSetValues(XtParent(hex_dlg),XmNtitle,filename,NULL);
+ fclose(fp);
+}
diff --git a/hex.h b/hex.h
new file mode 100644
index 0000000..efcce0f
--- /dev/null
+++ b/hex.h
@@ -0,0 +1 @@
+void hex_display(char *filename);
diff --git a/icons.c b/icons.c
new file mode 100644
index 0000000..0b4e6ce
--- /dev/null
+++ b/icons.c
@@ -0,0 +1,130 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/xpm.h>
+#include <Xm/Xm.h>
+
+#include "icons.h"
+#include "misc.h"
+
+#include "xpm/cw.xpm"
+#include "xpm/ccw.xpm"
+#include "xpm/fliph.xpm"
+#include "xpm/flipv.xpm"
+#include "xpm/prev.xpm"
+#include "xpm/next.xpm"
+#include "xpm/zoomin.xpm"
+#include "xpm/zoomout.xpm"
+#include "xpm/exit.xpm"
+
+#include "xpm/question.xpm"
+#include "xpm/dir.xpm"
+#include "xpm/file.xpm"
+#include "xpm/unknown.xpm"
+
+static void patch_bg(XImage *image, XImage *shape,
+ int width, int height, Pixel bg)
+{
+ unsigned int x,y;
+
+ for (y = 0; y < height; y++)
+ for (x = 0; x < width; x++)
+ if (!XGetPixel(shape, x, y))
+ XPutPixel(image, x, y, bg);
+}
+
+static void
+add_pixmap(Display *dpy, Pixel bg, char *name, char **data)
+{
+ XImage *image,*shape;
+ XpmAttributes attr;
+ char sname[32];
+
+ memset(&attr,0,sizeof(attr));
+ XpmCreateImageFromData(dpy,data,&image,&shape,&attr);
+
+ if (shape) {
+ patch_bg(image,shape,attr.width,attr.height,bg);
+ snprintf(sname,sizeof(sname),"%s_shape",name);
+ XmInstallImage(shape,sname);
+ }
+ XmInstallImage(image,name);
+}
+
+Pixmap x11_icon_fit(Display *dpy, Pixmap icon, unsigned long bg,
+ int width, int height)
+{
+ Pixmap pix;
+ GC gc;
+ XGCValues values;
+ Window root;
+ unsigned int w,h,b,d;
+ int x,y;
+
+ XGetGeometry(dpy,icon,&root,&x,&y,&w,&h,&b,&d);
+ pix = XCreatePixmap(dpy,icon,width,height,d);
+
+ /* fill background */
+ values.foreground = bg;
+ values.background = bg;
+ values.fill_style = FillSolid;
+ gc = XCreateGC(dpy, icon, GCForeground | GCBackground | GCFillStyle,
+ &values);
+ XFillRectangle(dpy,pix,gc,0,0,width,height);
+
+ /* blit */
+ if (w <= width && h <= height) {
+ /* just center ... */
+ x = (width - w) / 2;
+ y = (height - h) / 2;
+ XCopyArea(dpy,icon,pix,gc,0,0,w,h,x,y);
+ } else {
+ /* must scale down */
+#if 0
+ float xs,ys,scale;
+
+ xs = (float)width / w;
+ ys = (float)height / h;
+ scale = (xs < ys) ? xs : ys;
+ w = w * scale;
+ h = h * scale;
+ if (0 == w) w = 1;
+ if (0 == h) h = 1;
+
+ x = (width - w) / 2;
+ y = (height - h) / 2;
+ XCopyArea(dpy,icon,pix,gc,0,0,w,h,x,y);
+#endif
+ x = (width - w) / 2;
+ y = (height - h) / 2;
+ if (x < 0)
+ x = 0;
+ if (y < 0)
+ y = 0;
+ XCopyArea(dpy,icon,pix,gc,0,0,w,h,0,0);
+ }
+
+ XFreeGC(dpy, gc);
+ return pix;
+}
+
+void
+x11_icons_init(Display *dpy, unsigned long bg)
+{
+ add_pixmap(dpy, bg, "rotcw", cw_xpm);
+ add_pixmap(dpy, bg, "rotccw", ccw_xpm);
+ add_pixmap(dpy, bg, "fliph", fliph_xpm);
+ add_pixmap(dpy, bg, "flipv", flipv_xpm);
+ add_pixmap(dpy, bg, "prev", prev_xpm);
+ add_pixmap(dpy, bg, "next", next_xpm);
+ add_pixmap(dpy, bg, "zoomin", zoomin_xpm);
+ add_pixmap(dpy, bg, "zoomout", zoomout_xpm);
+ add_pixmap(dpy, bg, "exit", exit_xpm);
+
+ add_pixmap(dpy, bg, "question", question_xpm);
+ add_pixmap(dpy, bg, "dir", dir_xpm);
+ add_pixmap(dpy, bg, "file", file_xpm);
+ add_pixmap(dpy, bg, "unknown", unknown_xpm);
+}
diff --git a/icons.h b/icons.h
new file mode 100644
index 0000000..be599eb
--- /dev/null
+++ b/icons.h
@@ -0,0 +1,3 @@
+Pixmap x11_icon_fit(Display *dpy, Pixmap icon, unsigned long bg,
+ int width, int height);
+void x11_icons_init(Display *dpy, unsigned long bg);
diff --git a/ida.c b/ida.c
new file mode 100644
index 0000000..65e1d0a
--- /dev/null
+++ b/ida.c
@@ -0,0 +1,1909 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <locale.h>
+#include <langinfo.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <Xm/Xm.h>
+#include <Xm/Primitive.h>
+#include <Xm/Label.h>
+#include <Xm/CascadeB.h>
+#include <Xm/PushB.h>
+#include <Xm/Separator.h>
+#include <Xm/RowColumn.h>
+#include <Xm/ScrolledW.h>
+#include <Xm/Protocols.h>
+#include <Xm/List.h>
+#include <Xm/Form.h>
+#include <Xm/MessageB.h>
+#include <Xm/SelectioB.h>
+#include <Xm/Scale.h>
+#include <Xm/Text.h>
+#include <Xm/FileSB.h>
+#include <Xm/ToggleB.h>
+#include <Xm/DrawingA.h>
+#include <Xm/Transfer.h>
+#include <Xm/TransferP.h>
+
+#include "RegEdit.h"
+#include "ida.h"
+#include "x11.h"
+#include "man.h"
+#include "readers.h"
+#include "writers.h"
+#include "viewer.h"
+#include "op.h"
+#include "lut.h"
+#include "filter.h"
+#include "color.h"
+#include "icons.h"
+#include "browser.h"
+#include "filelist.h"
+#include "xdnd.h"
+#include "selections.h"
+#include "sane.h"
+#include "curl.h"
+#include "idaconfig.h"
+
+/* ---------------------------------------------------------------------- */
+
+static void popup_ac(Widget, XEvent*, String*, Cardinal*);
+static void exit_ac(Widget, XEvent*, String*, Cardinal*);
+static void next_ac(Widget, XEvent*, String*, Cardinal*);
+static void prev_ac(Widget, XEvent*, String*, Cardinal*);
+static void next_page_ac(Widget, XEvent*, String*, Cardinal*);
+static void prev_page_ac(Widget, XEvent*, String*, Cardinal*);
+static void zoom_ac(Widget, XEvent*, String*, Cardinal*);
+static void scroll_ac(Widget, XEvent*, String*, Cardinal*);
+static void debug_ac(Widget, XEvent*, String*, Cardinal*);
+static void load_ac(Widget, XEvent*, String*, Cardinal*);
+static void save_ac(Widget, XEvent*, String*, Cardinal*);
+static void scan_ac(Widget, XEvent*, String*, Cardinal*);
+static void print_ac(Widget, XEvent*, String*, Cardinal*);
+
+static void undo_ac(Widget, XEvent*, String*, Cardinal*);
+static void filter_ac(Widget, XEvent*, String*, Cardinal*);
+static void gamma_ac(Widget, XEvent*, String*, Cardinal*);
+static void bright_ac(Widget, XEvent*, String*, Cardinal*);
+static void contrast_ac(Widget, XEvent*, String*, Cardinal*);
+static void color_ac(Widget, XEvent*, String*, Cardinal*);
+static void f3x3_ac(Widget, XEvent*, String*, Cardinal*);
+static void resize_ac(Widget, XEvent*, String*, Cardinal*);
+static void rotate_ac(Widget, XEvent*, String*, Cardinal*);
+static void sharpe_ac(Widget, XEvent*, String*, Cardinal*);
+
+static XtActionsRec actionTable[] = {
+ { "Exit", exit_ac },
+ { "Next", next_ac },
+ { "Prev", prev_ac },
+ { "NextPage", next_page_ac },
+ { "PrevPage", prev_page_ac },
+ { "Zoom", zoom_ac },
+ { "Scroll", scroll_ac },
+ { "Debug", debug_ac },
+ { "Popup", popup_ac },
+ { "Man", man_action },
+ { "Load", load_ac },
+ { "Save", save_ac },
+ { "Scan", scan_ac },
+ { "Print", print_ac },
+ { "Browser", browser_ac },
+ { "Filelist", filelist_ac },
+
+ { "Undo", undo_ac },
+ { "Filter", filter_ac },
+ { "Gamma", gamma_ac },
+ { "Bright", bright_ac },
+ { "Contrast", contrast_ac },
+ { "Color", color_ac },
+ { "F3x3", f3x3_ac },
+ { "Resize", resize_ac },
+ { "Rotate", rotate_ac },
+ { "Sharpe", sharpe_ac },
+
+ { "Ipc", ipc_ac },
+ { "Xdnd", XdndAction },
+};
+
+/* ---------------------------------------------------------------------- */
+
+XtAppContext app_context;
+Display *dpy;
+Widget app_shell;
+int gray=0;
+char *binary;
+struct ida_viewer *ida;
+
+/* ---------------------------------------------------------------------- */
+
+struct ARGS args;
+unsigned int pcd_res;
+unsigned int sane_res;
+
+XtResource args_desc[] = {
+ {
+ "debug",
+ XtCBoolean, XtRBoolean, sizeof(Boolean),
+ XtOffset(struct ARGS*,debug),
+ XtRString, "false"
+ },{
+ "help",
+ XtCBoolean, XtRBoolean, sizeof(Boolean),
+ XtOffset(struct ARGS*,help),
+ XtRString, "false"
+ },{
+ "testload",
+ XtCBoolean, XtRBoolean, sizeof(Boolean),
+ XtOffset(struct ARGS*,testload),
+ XtRString, "false"
+ }
+};
+const int args_count = XtNumber(args_desc);
+
+XrmOptionDescRec opt_desc[] = {
+ { "-d", "debug", XrmoptionNoArg, "true" },
+ { "-debug", "debug", XrmoptionNoArg, "true" },
+ { "-testload", "testload", XrmoptionNoArg, "true" },
+ { "-h", "help", XrmoptionNoArg, "true" },
+ { "-help", "help", XrmoptionNoArg, "true" },
+ { "--help", "help", XrmoptionNoArg, "true" },
+};
+const int opt_count = (sizeof(opt_desc)/sizeof(XrmOptionDescRec));
+
+static String fallback_ressources[] = {
+#include "Ida.ad.h"
+ NULL
+};
+
+/* ---------------------------------------------------------------------- */
+
+static struct ida_writer *cwriter;
+static char *save_filename;
+static char *print_command;
+
+static Widget control_shell,status;
+static Atom wm_delete_window;
+
+static Widget view,loadbox,savebox,printbox;
+
+/* file list */
+static Widget wlist;
+static char **files = NULL;
+static int cfile = -1;
+static int nfiles = 0;
+static int cpage = 0;
+static int npages = 1;
+
+/* filter controls */
+static int gamma_val = 100;
+static int bright_val = 0;
+static int contrast_val = 0;
+static int rotate_val = 0;
+static int sharpe_val = 10;
+
+static struct MY_TOPLEVELS {
+ char *name;
+ Widget *shell;
+ int mapped;
+} my_toplevels [] = {
+ { "control", &control_shell },
+};
+#define TOPLEVELS (sizeof(my_toplevels)/sizeof(struct MY_TOPLEVELS))
+
+/* ---------------------------------------------------------------------- */
+
+static void
+popup_ac(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ unsigned int i;
+
+ /* which window we are talking about ? */
+ if (*num_params > 0) {
+ for (i = 0; i < TOPLEVELS; i++) {
+ if (0 == strcasecmp(my_toplevels[i].name,params[0]))
+ break;
+ }
+ if (i == TOPLEVELS) {
+ fprintf(stderr,"PopupAction: oops: shell not found (name=%s)\n",
+ params[0]);
+ return;
+ }
+ } else {
+ for (i = 0; i < TOPLEVELS; i++) {
+ if (*(my_toplevels[i].shell) == widget)
+ break;
+ }
+ if (i == TOPLEVELS) {
+ fprintf(stderr,"PopupAction: oops: shell not found (%p:%s)\n",
+ widget,XtName(widget));
+ return;
+ }
+ }
+
+ /* popup/down window */
+ if (!my_toplevels[i].mapped) {
+ XtPopup(*(my_toplevels[i].shell), XtGrabNone);
+ my_toplevels[i].mapped = 1;
+ } else {
+ XtPopdown(*(my_toplevels[i].shell));
+ my_toplevels[i].mapped = 0;
+ }
+}
+
+static void
+popupdown_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ int i = 0;
+ popup_ac(clientdata, NULL, NULL, &i);
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+destroy_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XtDestroyWidget(clientdata);
+}
+
+void
+action_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ char *calls, *action, *argv[16]; /* max: F3x3(9 args) */
+ int argc;
+
+ calls = strdup(clientdata);
+ action = strtok(calls,"(),");
+ for (argc = 0; NULL != (argv[argc] = strtok(NULL,"(),")); argc++)
+ /* nothing */;
+ XtCallActionProc(widget,action,NULL,argv,argc);
+ free(calls);
+}
+
+static void
+about_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget msgbox;
+
+ msgbox = XmCreateInformationDialog(app_shell,"aboutbox",NULL,0);
+ XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
+ XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
+ XtManageChild(msgbox);
+}
+
+#if 0
+static void
+sorry_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget msgbox;
+
+ msgbox = XmCreateErrorDialog(app_shell,"sorrybox",NULL,0);
+ XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
+ XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
+ XtManageChild(msgbox);
+}
+#endif
+
+void
+debug_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ unsigned int i;
+
+ fprintf(stderr,"Debug:");
+ for (i = 0; i < *num; i++)
+ fprintf(stderr," %s",params[i]);
+ fprintf(stderr,"\n");
+}
+
+static void
+display_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmDisplayCallbackStruct *arg = call_data;
+
+ switch (arg->reason) {
+ case XmCR_NO_RENDITION:
+ fprintf(stderr,"display_cb: no rendition: \"%s\"\n",arg->tag);
+ break;
+ case XmCR_NO_FONT:
+ fprintf(stderr,"display_cb: no font: \"%s\"\n",arg->font_name);
+ break;
+ default:
+ /* should not happen */
+ fprintf(stderr,"display_cb: unknown reason [%d]\n",arg->reason);
+ break;
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct ptr_list {
+ struct ptr_list *next;
+ Widget widget;
+};
+struct ptr_list *ptr_head;
+
+void ptr_register(Widget widget)
+{
+ struct ptr_list *item;
+
+ if (XtWindow(widget))
+ XDefineCursor(XtDisplay(widget), XtWindow(widget),
+ ptrs[POINTER_NORMAL]);
+ item = malloc(sizeof(*item));
+ memset(item,0,sizeof(*item));
+ item->widget = widget;
+ item->next = ptr_head;
+ ptr_head = item;
+}
+
+void ptr_unregister(Widget widget)
+{
+ struct ptr_list *item,*fitem;
+
+ if (ptr_head->widget == widget) {
+ fitem = ptr_head;
+ ptr_head = ptr_head->next;
+ free(fitem);
+ return;
+ }
+ for (item = ptr_head; NULL != item->next; item = item->next) {
+ if (item->next->widget == widget) {
+ fitem = item->next;
+ item->next = fitem->next;
+ free(fitem);
+ return;
+ }
+ }
+ /* shouldn't happen */
+ fprintf(stderr,"Oops: widget not found in list\n");
+}
+
+void ptr_busy(void)
+{
+ struct ptr_list *item;
+
+ for (item = ptr_head; NULL != item; item = item->next) {
+ if (!XtWindow(item->widget))
+ continue;
+ XDefineCursor(XtDisplay(item->widget), XtWindow(item->widget),
+ ptrs[POINTER_BUSY]);
+ }
+ XSync(dpy,False);
+}
+
+void ptr_idle(void)
+{
+ struct ptr_list *item;
+
+ for (item = ptr_head; NULL != item; item = item->next) {
+ if (!XtWindow(item->widget))
+ continue;
+ XDefineCursor(XtDisplay(item->widget), XtWindow(item->widget),
+ ptrs[POINTER_NORMAL]);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+static Boolean
+exit_wp(XtPointer client_data)
+{
+ exit(0);
+}
+
+static void
+exit_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ XtAppAddWorkProc(app_context,exit_wp, NULL);
+ XtDestroyWidget(app_shell);
+}
+
+void
+exit_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ exit_cb(widget,NULL,NULL);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void
+list_update(void)
+{
+ XmStringTable tab;
+ int i;
+
+ tab = malloc(nfiles * sizeof(XmString));
+ for (i = 0; i < nfiles; i++)
+ tab[i] = XmStringGenerate(files[i], NULL, XmMULTIBYTE_TEXT, NULL);
+ XtVaSetValues(wlist,
+ XmNitems, tab,
+ XmNitemCount, nfiles,
+ NULL);
+ for (i = 0; i < nfiles; i++)
+ XmStringFree(tab[i]);
+ free(tab);
+}
+
+static int
+list_append(char *filename)
+{
+ int i;
+
+ for (i = 0; i < nfiles; i++)
+ if (0 == strcmp(files[i],filename))
+ return i;
+ files = realloc(files,sizeof(char*)*(nfiles+1));
+ files[nfiles] = strdup(filename);
+ nfiles++;
+ return nfiles-1;
+}
+
+static void
+list_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ XmListCallbackStruct *list = calldata;
+
+ if (0 == list->selected_item_count)
+ return;
+ cfile = list->selected_item_positions[0]-1;
+ cpage = 0;
+ npages = viewer_loadimage(ida,files[cfile],cpage);
+ if (-1 == npages)
+ return;
+ resize_shell();
+}
+
+static void
+pcd_set(Widget widget)
+{
+ WidgetList items;
+ Cardinal nitems;
+ unsigned int i;
+ int value;
+
+ value = GET_PHOTOCD_RES();
+ XtVaGetValues(widget,XtNchildren,&items,
+ XtNnumChildren,&nitems,NULL);
+ for (i = 0; i < nitems; i++)
+ XmToggleButtonSetState(items[i],value == i+1,False);
+ pcd_res = value;
+}
+
+static void
+pcd_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ cfg_set_int(O_PHOTOCD_RES,(intptr_t)client_data);
+ pcd_set(XtParent(widget));
+}
+
+static void
+cfg_bool_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ char *option = XtName(widget);
+ Boolean value = XmToggleButtonGetState(widget);
+ cfg_set_bool(O_OPTIONS, option, value);
+}
+
+static void
+cfg_save_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ ida_write_config();
+}
+
+static void
+create_control(void)
+{
+ Widget form,menubar,tool,menu,smenu,push;
+
+ control_shell = XtVaAppCreateShell("ctrl","Iv",
+ topLevelShellWidgetClass,
+ dpy,
+ XtNclientLeader,app_shell,
+ XmNdeleteResponse,XmDO_NOTHING,
+ NULL);
+ XmdRegisterEditres(control_shell);
+ XmAddWMProtocolCallback(control_shell,wm_delete_window,
+ popupdown_cb,control_shell);
+
+ /* widgets */
+ form = XtVaCreateManagedWidget("form", xmFormWidgetClass, control_shell,
+ NULL);
+ menubar = XmCreateMenuBar(form,"bar",NULL,0);
+ XtManageChild(menubar);
+ tool = XtVaCreateManagedWidget("tool",xmRowColumnWidgetClass, form,
+ NULL);
+ status = XtVaCreateManagedWidget("status", xmLabelWidgetClass, form,
+ NULL);
+ wlist = XmCreateScrolledList(form,"list",NULL,0);
+ XtManageChild(wlist);
+ XtAddCallback(wlist,XmNdefaultActionCallback,list_cb,NULL);
+ XtAddCallback(wlist,XmNdestinationCallback,selection_dest,NULL);
+ dnd_add(wlist);
+
+ /* menu - file */
+ menu = XmCreatePulldownMenu(menubar,"fileM",NULL,0);
+ XtVaCreateManagedWidget("file",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ push = XtVaCreateManagedWidget("load",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Load()");
+ push = XtVaCreateManagedWidget("save",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Save()");
+ push = XtVaCreateManagedWidget("browse",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Browser()");
+ push = XtVaCreateManagedWidget("filelist",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filelist()");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+#ifdef HAVE_LIBSANE
+ sane_menu(menu);
+#endif
+ push = XtVaCreateManagedWidget("print",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Print()");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("quit",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,exit_cb,NULL);
+
+ /* menu - edit */
+ menu = XmCreatePulldownMenu(menubar,"editM",NULL,0);
+ XtVaCreateManagedWidget("edit",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ push = XtVaCreateManagedWidget("undo",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Undo()");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("copy",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Ipc(copy)");
+ push = XtVaCreateManagedWidget("paste",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Ipc(paste)");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("flipv",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-vert)");
+ push = XtVaCreateManagedWidget("fliph",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-horz)");
+ push = XtVaCreateManagedWidget("rotcw",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-cw)");
+ push = XtVaCreateManagedWidget("rotccw",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-ccw)");
+ push = XtVaCreateManagedWidget("invert",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(invert)");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("crop",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(crop)");
+ push = XtVaCreateManagedWidget("acrop",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(autocrop)");
+ push = XtVaCreateManagedWidget("scale",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Resize()");
+ push = XtVaCreateManagedWidget("rotany",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Rotate()");
+
+ /* menu - filters / operations */
+ menu = XmCreatePulldownMenu(menubar,"opM",NULL,0);
+ XtVaCreateManagedWidget("op",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ push = XtVaCreateManagedWidget("gamma",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Gamma()");
+ push = XtVaCreateManagedWidget("bright",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Bright()");
+ push = XtVaCreateManagedWidget("contr",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Contrast()");
+ push = XtVaCreateManagedWidget("color",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Color()");
+ push = XtVaCreateManagedWidget("gray",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(grayscale)");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("blur",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,
+ "F3x3(1,1,1, 1,1,1, 1,1,1, 1,9,0)");
+ push = XtVaCreateManagedWidget("sharpe",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Sharpe()");
+ push = XtVaCreateManagedWidget("edge",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,
+ "F3x3(-1,-1,-1, -1,8,-1, -1,-1,-1)");
+ push = XtVaCreateManagedWidget("emboss",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,
+ "F3x3(1,0,0, 0,0,0, 0,0,-1, 0,0,128)");
+
+ /* menu - view */
+ menu = XmCreatePulldownMenu(menubar,"viewM",NULL,0);
+ XtVaCreateManagedWidget("view",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ push = XtVaCreateManagedWidget("prev",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Prev()");
+ push = XtVaCreateManagedWidget("next",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Next()");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("prevpage",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"PrevPage()");
+ push = XtVaCreateManagedWidget("nextpage",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"NextPage()");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("zoomin",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(inc)");
+ push = XtVaCreateManagedWidget("zoomout",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(dec)");
+
+ /* menu - options */
+ menu = XmCreatePulldownMenu(menubar,"optM",NULL,0);
+ push = XtVaCreateManagedWidget("opt",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ smenu = XmCreatePulldownMenu(menu,"pcdM",NULL,0);
+ XtVaCreateManagedWidget("pcd",xmCascadeButtonWidgetClass,menu,
+ XmNsubMenuId,smenu,NULL);
+ push = XtVaCreateManagedWidget("1",xmToggleButtonWidgetClass,smenu,NULL);
+ XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)1);
+ push = XtVaCreateManagedWidget("2",xmToggleButtonWidgetClass,smenu,NULL);
+ XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)2);
+ push = XtVaCreateManagedWidget("3",xmToggleButtonWidgetClass,smenu,NULL);
+ XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)3);
+ push = XtVaCreateManagedWidget("4",xmToggleButtonWidgetClass,smenu,NULL);
+ XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)4);
+ push = XtVaCreateManagedWidget("5",xmToggleButtonWidgetClass,smenu,NULL);
+ XtAddCallback(push,XmNvalueChangedCallback,pcd_cb,(XtPointer)5);
+ pcd_set(smenu);
+
+ push = XtVaCreateManagedWidget("autozoom",xmToggleButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNvalueChangedCallback,cfg_bool_cb,NULL);
+ XmToggleButtonSetState(push,GET_AUTOZOOM(),False);
+
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("cfgsave",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,cfg_save_cb,NULL);
+
+ /* menu - help */
+ menu = XmCreatePulldownMenu(menubar,"helpM",NULL,0);
+ push = XtVaCreateManagedWidget("help",xmCascadeButtonWidgetClass,menubar,
+ XmNsubMenuId,menu,NULL);
+ XtVaSetValues(menubar,XmNmenuHelpWidget,push,NULL);
+ push = XtVaCreateManagedWidget("man",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Man(ida)");
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,menu,NULL);
+ push = XtVaCreateManagedWidget("about",xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,about_cb,NULL);
+
+ /* toolbar */
+ push = XtVaCreateManagedWidget("prev",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Prev()");
+ push = XtVaCreateManagedWidget("next",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Next()");
+ push = XtVaCreateManagedWidget("zoomin",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(inc)");
+ push = XtVaCreateManagedWidget("zoomout",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Zoom(dec)");
+
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,tool,NULL);
+ push = XtVaCreateManagedWidget("flipv",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-vert)");
+ push = XtVaCreateManagedWidget("fliph",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(flip-horz)");
+ push = XtVaCreateManagedWidget("rotccw",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-ccw)");
+ push = XtVaCreateManagedWidget("rotcw",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,action_cb,"Filter(rotate-cw)");
+
+ XtVaCreateManagedWidget("sep",xmSeparatorWidgetClass,tool,NULL);
+ push = XtVaCreateManagedWidget("exit",xmPushButtonWidgetClass,tool,NULL);
+ XtAddCallback(push,XmNactivateCallback,exit_cb,NULL);
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+resize_shell(void)
+{
+ char *title,*base;
+ Dimension x,y,w,h,sw,sh;
+ XmString str;
+ int len;
+
+ XtVaGetValues(app_shell, XtNx,&x, XtNy,&y, NULL);
+
+ /* resize shell + move shell
+ size: image size + 2*shadowThickness */
+ w = ida->scrwidth+2;
+ h = ida->scrheight+2;
+ sw = XtScreen(ida->widget)->width;
+ sh = XtScreen(ida->widget)->height;
+ if (w > sw)
+ w = sw;
+ if (h > sh)
+ h = sh;
+ if (x+w > sw)
+ x = sw-w;
+ if (y+h > sh)
+ y = sh-h;
+
+ base = strrchr(ida->file,'/');
+ if (base)
+ base++;
+ else
+ base = ida->file;
+ title = malloc(strlen(base)+128);
+ len = sprintf(title,"%s (%dx%d", base,
+ ida->img.i.width, ida->img.i.height);
+ if (ida->img.i.dpi)
+ len += sprintf(title+len," | %d dpi",
+ ida->img.i.dpi);
+ if (ida->img.i.npages > 1)
+ len += sprintf(title+len," | page %d/%d",
+ cpage+1, ida->img.i.npages);
+ len += sprintf(title+len," | %d%%)", viewer_i2s(ida->zoom,100));
+ XtVaSetValues(app_shell, XtNtitle,title,
+ /* XtNx,x, XtNy,y, */ XtNwidth,w, XtNheight,h,
+ NULL);
+ str = XmStringGenerate(title,NULL,XmMULTIBYTE_TEXT,NULL);
+ XtVaSetValues(status,XmNlabelString,str,NULL);
+ XmStringFree(str);
+ free(title);
+}
+
+static int
+load_file(int nr, int np)
+{
+ if(nr < 0 || nr >= nfiles)
+ return -1;
+ npages = viewer_loadimage(ida,files[nr],np);
+ if (-1 == npages)
+ return -1;
+ resize_shell();
+#if 0
+ XmListSelectPos(wlist,nr+1,False);
+ cfile = nr;
+#endif
+ return npages;
+}
+
+char*
+load_tmpfile(char *base)
+{
+ char *tmpdir;
+ char *filename;
+
+ tmpdir = getenv("TMPDIR");
+ if (NULL == tmpdir)
+ tmpdir="/tmp";
+ filename = malloc(strlen(tmpdir)+strlen(base)+16);
+ sprintf(filename,"%s/%s-XXXXXX",tmpdir,base);
+ return filename;
+}
+
+static void
+load_logo(void)
+{
+ static unsigned char logo[] = {
+#include "logo.h"
+ };
+ char *filename = load_tmpfile("ida-logo");
+ int fd;
+ fd = mkstemp(filename);
+ write(fd,logo,sizeof(logo));
+ close(fd);
+ cpage = 0;
+ npages = 1;
+ if (0 < viewer_loadimage(ida,filename,cpage)) {
+ ida->file = "ida " VERSION;
+ resize_shell();
+ }
+ unlink(filename);
+ free(filename);
+}
+
+static void
+load_stdin(void)
+{
+ char *filename = load_tmpfile("ida-stdin");
+ char buf[4096];
+ int rc,fd;
+ fd = mkstemp(filename);
+ for (;;) {
+ rc = read(0,buf,sizeof(buf));
+ if (rc <= 0)
+ break;
+ write(fd,buf,rc);
+ }
+ close(fd);
+ cpage = 0;
+ npages = 1;
+ if (0 < viewer_loadimage(ida,filename,cpage)) {
+ ida->file = "stdin";
+ resize_shell();
+ }
+ unlink(filename);
+ free(filename);
+}
+
+void
+next_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ for (;;) {
+ if (cfile >= nfiles-1)
+ return;
+ cfile++;
+ cpage = 0;
+ if (0 <= load_file(cfile,cpage))
+ break;
+ }
+}
+
+void
+prev_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ for (;;) {
+ if (cfile < 1)
+ return;
+ cfile--;
+ cpage = 0;
+ if (0 <= load_file(cfile,cpage))
+ break;
+ }
+}
+
+void
+next_page_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ for (;;) {
+ if (cpage >= npages-1)
+ return;
+ cpage++;
+ if (0 <= load_file(cfile,cpage))
+ break;
+ }
+}
+
+void
+prev_page_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ for (;;) {
+ if (cpage <= 0)
+ return;
+ cpage--;
+ if (0 <= load_file(cfile,cpage))
+ break;
+ }
+}
+
+void
+zoom_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ int zoom;
+
+ if (0 == *num)
+ return;
+
+ if (0 == strcasecmp(params[0],"auto")) {
+ viewer_autozoom(ida);
+ return;
+ }
+
+ if (0 == strcasecmp(params[0],"inc")) {
+ zoom = ida->zoom+1;
+ } else if (0 == strcasecmp(params[0],"dec")) {
+ zoom = ida->zoom-1;
+ } else {
+ zoom = atoi(params[0]);
+ }
+ viewer_setzoom(ida,zoom);
+ resize_shell();
+}
+
+void
+scroll_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ fprintf(stderr,"Scroll(): %s\n",XtName(widget));
+}
+
+/* ---------------------------------------------------------------------- */
+
+void new_file(char *name, int complain)
+{
+ struct stat st;
+ int n;
+
+ if (curl_is_url(name))
+ goto load;
+
+ if (0 == strncasecmp(name,"file:",5))
+ name += 5;
+ if (-1 == stat(name,&st)) {
+ if (complain)
+ fprintf(stderr,"stat %s: %s\n",name,strerror(errno));
+ return;
+ }
+ switch (st.st_mode & S_IFMT) {
+ case S_IFDIR:
+ browser_window(name);
+ break;
+ case S_IFREG:
+ goto load;
+ break;
+ }
+ return;
+
+ load:
+ n = list_append(name);
+ list_update();
+ cpage = 0;
+ load_file(n,cpage);
+}
+
+static void
+load_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmFileSelectionBoxCallbackStruct *cb = call_data;
+ char *line;
+
+ if (cb->reason == XmCR_OK) {
+ line = XmStringUnparse(cb->value,NULL,
+ XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
+ NULL,0,0);
+ new_file(line,1);
+ }
+ XtUnmanageChild(widget);
+}
+
+void
+load_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ Widget help;
+
+ if (NULL == loadbox) {
+ loadbox = XmCreateFileSelectionDialog(app_shell,"load",NULL,0);
+ help = XmFileSelectionBoxGetChild(loadbox,XmDIALOG_HELP_BUTTON);
+ XtUnmanageChild(help);
+ XtAddCallback(loadbox,XmNokCallback,load_done_cb,NULL);
+ XtAddCallback(loadbox,XmNcancelCallback,load_done_cb,NULL);
+ } else {
+ XmFileSelectionDoSearch(loadbox,NULL);
+ }
+ XtManageChild(loadbox);
+}
+
+void
+scan_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+#ifdef HAVE_LIBSANE
+ cpage = 0;
+ if (*num)
+ npages = viewer_loader_start(ida, &sane_loader, NULL, params[0], 0);
+ else
+ npages = viewer_loader_start(ida, &sane_loader, NULL, "", 0);
+ if (-1 == npages)
+ return;
+ ida->file = "scanned image";
+ resize_shell();
+#endif
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+do_save_print(void)
+{
+ FILE *fp;
+
+ if (save_filename) {
+ XtUnmanageChild(savebox);
+ ptr_busy();
+ if (NULL == (fp = fopen(save_filename,"wb"))) {
+ fprintf(stderr,"save: can't open %s: %s\n",
+ save_filename,strerror(errno));
+ } else if (-1 == cwriter->write(fp,&ida->img)) {
+ fclose(fp);
+ fprintf(stderr,"saving %s FAILED",save_filename);
+ } else {
+ fclose(fp);
+ list_append(save_filename);
+ list_update();
+ }
+ ptr_idle();
+ }
+ if (print_command) {
+ XtUnmanageChild(printbox);
+ ptr_busy();
+ if (NULL == (fp = popen(print_command,"w"))) {
+ fprintf(stderr,"print: can't exec %s: %s\n",
+ print_command,strerror(errno));
+ } else {
+ if (-1 == cwriter->write(fp,&ida->img))
+ fprintf(stderr,"printing FAILED");
+ fclose(fp);
+ }
+ ptr_idle();
+ }
+}
+
+static void
+save_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmFileSelectionBoxCallbackStruct *cb = call_data;
+
+ if (cb->reason == XmCR_OK) {
+ print_command = NULL;
+ save_filename = XmStringUnparse(cb->value,NULL,
+ XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
+ NULL,0,0);
+ if (cwriter->conf) {
+ cwriter->conf(widget,&ida->img);
+ } else {
+ do_save_print();
+ }
+ } else {
+ XtUnmanageChild(widget);
+ }
+}
+
+static void
+save_fmt_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ cwriter = clientdata;
+}
+
+static void
+save_ext_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ Widget option = clientdata;
+ Widget menu;
+ WidgetList children;
+ Cardinal nchildren;
+ struct ida_writer *wr = NULL;
+ struct list_head *item;
+ char *name,*ext;
+ int i,j,pick;
+
+ name = XmTextGetString(widget);
+ ext = strrchr(name,'.');
+ if (NULL == ext)
+ return;
+ if (strchr(ext,'/'))
+ return;
+ ext++;
+
+ i = 0; pick = -1;
+ list_for_each(item,&writers) {
+ wr = list_entry(item, struct ida_writer, list);
+ for (j = 0; NULL != wr->ext[j]; j++)
+ if (0 == strcasecmp(ext,wr->ext[j]))
+ pick = i;
+ if (-1 != pick)
+ break;
+ i++;
+ }
+ if (-1 == pick)
+ return;
+
+ XtVaGetValues(option,XmNsubMenuId,&menu,NULL);
+ XtVaGetValues(menu,XtNchildren,&children,
+ XtNnumChildren,&nchildren,NULL);
+ XtVaSetValues(option,XmNmenuHistory,children[pick],NULL);
+ cwriter = wr;
+}
+
+static void
+save_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ Widget help,menu,option,push,text;
+ Arg args[2];
+ struct ida_writer *wr = NULL;
+ struct list_head *item;
+
+ if (NULL == savebox) {
+ savebox = XmCreateFileSelectionDialog(app_shell,"save",NULL,0);
+ help = XmFileSelectionBoxGetChild(savebox,XmDIALOG_HELP_BUTTON);
+ text = XmFileSelectionBoxGetChild(savebox,XmDIALOG_TEXT);
+ XtUnmanageChild(help);
+
+ menu = XmCreatePulldownMenu(savebox,"formatM",NULL,0);
+ XtSetArg(args[0],XmNsubMenuId,menu);
+ option = XmCreateOptionMenu(savebox,"format",args,1);
+ XtManageChild(option);
+ list_for_each(item,&writers) {
+ wr = list_entry(item, struct ida_writer, list);
+ push = XtVaCreateManagedWidget(wr->label,
+ xmPushButtonWidgetClass,menu,
+ NULL);
+ XtAddCallback(push,XmNactivateCallback,save_fmt_cb,wr);
+ }
+ cwriter = list_entry(writers.next, struct ida_writer, list);
+
+ XtAddCallback(text,XmNvalueChangedCallback,save_ext_cb,option);
+ XtAddCallback(savebox,XmNokCallback,save_done_cb,NULL);
+ XtAddCallback(savebox,XmNcancelCallback,save_done_cb,NULL);
+ } else {
+ XmFileSelectionDoSearch(savebox,NULL);
+ }
+ XtManageChild(savebox);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void
+print_done_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmSelectionBoxCallbackStruct *cb = call_data;
+
+ if (cb->reason == XmCR_OK) {
+ save_filename = NULL;
+ print_command = XmStringUnparse(cb->value,NULL,
+ XmMULTIBYTE_TEXT,XmMULTIBYTE_TEXT,
+ NULL,0,0);
+ cwriter = &ps_writer;
+ cwriter->conf(widget,&ida->img);
+ } else {
+ XtUnmanageChild(widget);
+ }
+}
+
+static void
+print_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ if (NULL == printbox) {
+ printbox = XmCreatePromptDialog(app_shell,"print",NULL,0);
+ XtUnmanageChild(XmSelectionBoxGetChild(printbox,XmDIALOG_HELP_BUTTON));
+ XtAddCallback(printbox,XmNokCallback,print_done_cb,NULL);
+ XtAddCallback(printbox,XmNcancelCallback,print_done_cb,NULL);
+ }
+ XtManageChild(printbox);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct ida_op *ops[] = {
+ &desc_flip_vert,
+ &desc_flip_horz,
+ &desc_rotate_cw,
+ &desc_rotate_ccw,
+ &desc_invert,
+ &desc_crop,
+ &desc_autocrop,
+ &desc_grayscale,
+ NULL
+};
+
+void
+filter_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ struct ida_op *op = NULL;
+ int i;
+
+ if (*num < 1)
+ return;
+ for (i = 0; NULL != ops[i]; i++) {
+ op = ops[i];
+ if (0 == strcasecmp(op->name,params[0]))
+ break;
+ }
+ if (NULL == ops[i]) {
+ fprintf(stderr,"Oops: unknown filter: %s\n",params[0]);
+ return;
+ }
+
+ viewer_start_op(ida,op,NULL);
+ if (ida->op_src.i.width != ida->img.i.width ||
+ ida->op_src.i.height != ida->img.i.height)
+ resize_shell();
+}
+
+void
+f3x3_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ struct op_3x3_parm p;
+
+ if (*num < 9) {
+ fprintf(stderr,"F3x3: wrong number of args (%d, need 9)\n",*num);
+ return;
+ }
+ memset(&p,0,sizeof(p));
+ p.f1[0] = atoi(params[0]);
+ p.f1[1] = atoi(params[1]);
+ p.f1[2] = atoi(params[2]);
+ p.f2[0] = atoi(params[3]);
+ p.f2[1] = atoi(params[4]);
+ p.f2[2] = atoi(params[5]);
+ p.f3[0] = atoi(params[6]);
+ p.f3[1] = atoi(params[7]);
+ p.f3[2] = atoi(params[8]);
+ if (*num > 9) p.mul = atoi(params[ 9]);
+ if (*num > 10) p.div = atoi(params[10]);
+ if (*num > 11) p.add = atoi(params[11]);
+ if (debug) {
+ fprintf(stderr,"f3x3: -----------\n");
+ fprintf(stderr,"f3x3: %3d %3d %3d\n",p.f1[0],p.f1[1],p.f1[2]);
+ fprintf(stderr,"f3x3: %3d %3d %3d\n",p.f2[0],p.f2[1],p.f2[2]);
+ fprintf(stderr,"f3x3: %3d %3d %3d\n",p.f3[0],p.f3[1],p.f3[2]);
+ fprintf(stderr,"f3x3: *%d/%d+%d\n",p.mul,p.div,p.add);
+ }
+ viewer_start_op(ida,&desc_3x3,&p);
+}
+
+void
+undo_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ Widget msgbox;
+ int resize;
+
+ resize = (ida->undo.i.width != ida->img.i.width ||
+ ida->undo.i.height != ida->img.i.height);
+ if (-1 == viewer_undo(ida)) {
+ msgbox = XmCreateInformationDialog(app_shell,"noundobox",NULL,0);
+ XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmMessageBoxGetChild(msgbox,XmDIALOG_CANCEL_BUTTON));
+ XtAddCallback(msgbox,XmNokCallback,destroy_cb,msgbox);
+ XtManageChild(msgbox);
+ } else {
+ if (resize)
+ resize_shell();
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct ida_prompt {
+ Widget shell;
+ Widget box;
+ Widget scale;
+ Widget text;
+ int apply;
+ int value;
+ int decimal;
+ int factor; /* 10^decimal */
+ void (*notify)(int value, int preview);
+};
+
+static void
+prompt_setvalue(struct ida_prompt *me, int value, int scale, int text)
+{
+ char str[32];
+ int min,max;
+
+ if (me->value == value)
+ return;
+ XtVaGetValues(me->scale,XmNminimum,&min,XmNmaximum,&max,NULL);
+ if (value < min || value > max)
+ return;
+
+ me->value = value;
+ if (scale)
+ XmScaleSetValue(me->scale,value);
+ if (text) {
+ if (me->decimal) {
+ sprintf(str,"%*.*f",me->decimal+2,me->decimal,
+ (float)value/me->factor);
+ } else {
+ sprintf(str,"%d",value);
+ }
+ XmTextSetString(me->text,str);
+ }
+ if (me->notify)
+ me->notify(value,1);
+}
+
+static void
+prompt_scale_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_prompt *me = client_data;
+ XmScaleCallbackStruct *cd = calldata;
+
+ prompt_setvalue(me,cd->value,0,1);
+}
+
+static void
+prompt_text_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_prompt *me = client_data;
+ float fvalue;
+ int value;
+
+ if (me->decimal) {
+ fvalue = atof(XmTextGetString(me->text));
+ fvalue += 0.5/me->factor;
+ value = (int)(fvalue * me->factor);
+ } else {
+ value = atoi(XmTextGetString(me->text));
+ }
+ prompt_setvalue(me,value,1,0);
+}
+
+static void
+prompt_box_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_prompt *me = client_data;
+ XmSelectionBoxCallbackStruct *cd = calldata;
+
+ if (XmCR_OK == cd->reason)
+ me->apply = 1;
+ XtDestroyWidget(me->shell);
+}
+
+static void
+prompt_shell_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_prompt *me = client_data;
+
+ if (me->apply)
+ me->notify(me->value,0);
+ else
+ viewer_cancel_preview(ida);
+ free(me);
+}
+
+static void
+prompt_init(char *name, int decimal, int value,
+ void (*notify)(int value, int preview))
+{
+ struct ida_prompt *me;
+
+ me = malloc(sizeof(*me));
+ memset(me,0,sizeof(*me));
+ if (decimal) {
+ int i;
+ me->decimal = decimal;
+ me->factor = 1;
+ for (i = 0; i < decimal; i++)
+ me->factor *= 10;
+ }
+ me->notify = notify;
+
+ me->box = XmCreatePromptDialog(app_shell,name,NULL,0);
+ me->shell = XtParent(me->box);
+ me->text = XmSelectionBoxGetChild(me->box,XmDIALOG_TEXT);
+ XmdRegisterEditres(XtParent(me->box));
+ XtUnmanageChild(XmSelectionBoxGetChild(me->box,XmDIALOG_HELP_BUTTON));
+ me->scale = XtVaCreateManagedWidget("scale",xmScaleWidgetClass,
+ me->box,NULL);
+
+ XtAddCallback(me->scale,XmNdragCallback,prompt_scale_cb,me);
+ XtAddCallback(me->scale,XmNvalueChangedCallback,prompt_scale_cb,me);
+ XtAddCallback(me->text,XmNvalueChangedCallback,prompt_text_cb,me);
+ XtAddCallback(me->box,XmNokCallback,prompt_box_cb,me);
+ XtAddCallback(me->box,XmNcancelCallback,prompt_box_cb,me);
+ XtAddCallback(me->shell,XmNdestroyCallback,prompt_shell_cb,me);
+
+ XtManageChild(me->box);
+ prompt_setvalue(me,value,1,1);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void
+gamma_notify(int value, int preview)
+{
+ struct op_map_parm param;
+ float gamma = (float)value/100;
+
+ param.red = op_map_nothing;
+ param.red.gamma = gamma;
+ param.green = param.red;
+ param.blue = param.red;
+ if (preview) {
+ viewer_start_preview(ida,&desc_map,&param);
+ } else {
+ gamma_val = value;
+ viewer_start_op(ida,&desc_map,&param);
+ }
+}
+
+void
+gamma_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ prompt_init("gamma",2,gamma_val,gamma_notify);
+}
+
+static void
+bright_notify(int value, int preview)
+{
+ struct op_map_parm param;
+
+ param.red = op_map_nothing;
+ param.red.bottom += value;
+ param.red.top += value;
+ param.green = param.red;
+ param.blue = param.red;
+ if (preview) {
+ viewer_start_preview(ida,&desc_map,&param);
+ } else {
+ bright_val = value;
+ viewer_start_op(ida,&desc_map,&param);
+ }
+}
+
+void
+bright_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ prompt_init("bright",0,bright_val,bright_notify);
+}
+
+static void
+contrast_notify(int value, int preview)
+{
+ struct op_map_parm param;
+
+ param.red = op_map_nothing;
+ param.red.bottom -= value;
+ param.red.top += value;
+ param.green = param.red;
+ param.blue = param.red;
+ if (preview) {
+ viewer_start_preview(ida,&desc_map,&param);
+ } else {
+ contrast_val = value;
+ viewer_start_op(ida,&desc_map,&param);
+ }
+}
+
+void
+contrast_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ prompt_init("contrast",0,contrast_val,contrast_notify);
+}
+
+static void
+rotate_notify(int value, int preview)
+{
+ struct op_rotate_parm parm;
+
+ parm.angle = value;
+ if (preview) {
+ viewer_start_preview(ida,&desc_rotate,&parm);
+ } else {
+ rotate_val = value;
+ viewer_start_op(ida,&desc_rotate,&parm);
+ }
+}
+
+void
+rotate_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ prompt_init("rotate",0,rotate_val,rotate_notify);
+}
+
+static void
+sharpe_notify(int value, int preview)
+{
+ struct op_sharpe_parm parm;
+
+ parm.factor = value;
+ if (preview) {
+ viewer_start_preview(ida,&desc_sharpe,&parm);
+ } else {
+ sharpe_val = value;
+ viewer_start_op(ida,&desc_sharpe,&parm);
+ }
+}
+
+void
+sharpe_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ prompt_init("sharpe",0,sharpe_val,sharpe_notify);
+}
+
+void
+color_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ color_init(&ida->img);
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct ida_resize {
+ Widget dlg,tx,ty,tr,lock,size,res,label;
+ int yupdate,xupdate,rupdate;
+ int apply;
+};
+
+static void
+resize_phys_size(struct ida_resize *h)
+{
+ char buf[128];
+ XmString str;
+ int dpi;
+ float x,y;
+
+ dpi = atoi(XmTextGetString(h->tr));
+ if (dpi) {
+ x = (float)atoi(XmTextGetString(h->tx)) / dpi;
+ y = (float)atoi(XmTextGetString(h->ty)) / dpi;
+ sprintf(buf,"%.2f x %.2f inch\n%.2f x %.2f cm",
+ x,y, x*2.54, y*2.54);
+ } else {
+ strcpy(buf,"unknown");
+ }
+ str = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL);
+ XtVaSetValues(h->label,XmNlabelString,str,NULL);
+}
+
+static void
+resize_sync_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_resize *h = client_data;
+ char buf[32];
+ int i,lock,res;
+
+ lock = XmToggleButtonGetState(h->lock);
+ res = XmToggleButtonGetState(h->res);
+
+ /* update text fields */
+ if (h->tx == widget) {
+ if (h->xupdate) {
+ h->xupdate--;
+ return;
+ }
+ i = atoi(XmTextGetString(h->tx));
+ if (lock) {
+ sprintf(buf,"%d",i * ida->img.i.height / ida->img.i.width);
+ h->yupdate++;
+ XmTextSetString(h->ty,buf);
+ if (res) {
+ sprintf(buf,"%d", ida->img.i.dpi * i / ida->img.i.width);
+ h->rupdate++;
+ XmTextSetString(h->tr,buf);
+ }
+ } else {
+ if (res) {
+ h->rupdate++;
+ XmTextSetString(h->tr,"0");
+ }
+ }
+ resize_phys_size(h);
+ }
+ if (h->ty == widget) {
+ if (h->yupdate) {
+ h->yupdate--;
+ return;
+ }
+ i = atoi(XmTextGetString(h->ty));
+ if (lock) {
+ sprintf(buf,"%d",i * ida->img.i.width / ida->img.i.height);
+ h->xupdate++;
+ XmTextSetString(h->tx,buf);
+ if (res) {
+ sprintf(buf,"%d", ida->img.i.dpi * i / ida->img.i.height);
+ h->rupdate++;
+ XmTextSetString(h->tr,buf);
+ }
+ } else {
+ if (res) {
+ h->rupdate++;
+ XmTextSetString(h->tr,"0");
+ }
+ }
+ resize_phys_size(h);
+ }
+ if (h->tr == widget) {
+ if (h->rupdate) {
+ h->rupdate--;
+ return;
+ }
+ i = atoi(XmTextGetString(h->tr));
+ sprintf(buf,"%d", ida->img.i.width * i / ida->img.i.dpi);
+ h->xupdate++;
+ XmTextSetString(h->tx,buf);
+ sprintf(buf,"%d", ida->img.i.height * i / ida->img.i.dpi);
+ h->yupdate++;
+ XmTextSetString(h->ty,buf);
+ resize_phys_size(h);
+ }
+
+ /* radio buttons pressed */
+ if (h->size == widget && XmToggleButtonGetState(h->size)) {
+ XmToggleButtonSetState(h->res,0,False);
+ sprintf(buf,"%d", ida->img.i.dpi);
+ h->rupdate++;
+ XmTextSetString(h->tr,buf);
+ XtVaSetValues(h->tr,XmNsensitive,False,NULL);
+ resize_phys_size(h);
+ }
+ if (h->res == widget && XmToggleButtonGetState(h->res)) {
+ XmToggleButtonSetState(h->size,0,False);
+ XtVaSetValues(h->tr,XmNsensitive,True,NULL);
+ }
+}
+
+static void
+resize_button_cb(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_resize *h = client_data;
+ XmSelectionBoxCallbackStruct *cb = calldata;
+
+ if (cb->reason == XmCR_OK)
+ h->apply = 1;
+ XtDestroyWidget(XtParent(h->dlg));
+}
+
+static void
+resize_destroy(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ struct ida_resize *h = client_data;
+ struct op_resize_parm param;
+
+ if (!h->apply)
+ return;
+ param.width = atoi(XmTextGetString(h->tx));
+ param.height = atoi(XmTextGetString(h->ty));
+ param.dpi = atoi(XmTextGetString(h->tr));
+ if (0 == param.width ||
+ 0 == param.height) {
+ fprintf(stderr,"resize: invalid argument\n");
+ return;
+ }
+
+ viewer_start_op(ida,&desc_resize,&param);
+ resize_shell();
+ free(h);
+}
+
+static void
+resize_ac(Widget widget, XEvent *event, String *params, Cardinal *num)
+{
+ Widget rc,rc2;
+ char buf[32];
+ struct ida_resize *h;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ h->dlg = XmCreatePromptDialog(app_shell,"resize",NULL,0);
+ XmdRegisterEditres(XtParent(h->dlg));
+ XtUnmanageChild(XmSelectionBoxGetChild(h->dlg,XmDIALOG_SELECTION_LABEL));
+ XtUnmanageChild(XmSelectionBoxGetChild(h->dlg,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmSelectionBoxGetChild(h->dlg,XmDIALOG_TEXT));
+ rc = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass,h->dlg, NULL);
+ XtVaCreateManagedWidget("lx", xmLabelWidgetClass,rc, NULL);
+ h->tx = XtVaCreateManagedWidget("tx", xmTextWidgetClass,rc, NULL);
+ XtVaCreateManagedWidget("ly", xmLabelWidgetClass,rc, NULL);
+ h->ty = XtVaCreateManagedWidget("ty", xmTextWidgetClass,rc, NULL);
+ XtVaCreateManagedWidget("lr", xmLabelWidgetClass,rc, NULL);
+ h->tr = XtVaCreateManagedWidget("tr", xmTextWidgetClass,rc, NULL);
+ h->lock = XtVaCreateManagedWidget("lock", xmToggleButtonWidgetClass,
+ rc, NULL);
+ rc2 = XtVaCreateManagedWidget("rc", xmRowColumnWidgetClass,rc, NULL);
+ h->size = XtVaCreateManagedWidget("size", xmToggleButtonWidgetClass,
+ rc2, NULL);
+ h->res = XtVaCreateManagedWidget("res", xmToggleButtonWidgetClass,
+ rc2, NULL);
+ XtVaCreateManagedWidget("phys", xmLabelWidgetClass,rc,NULL);
+ h->label = XtVaCreateManagedWidget("label", xmLabelWidgetClass,
+ rc, NULL);
+
+ sprintf(buf,"%d",ida->img.i.width);
+ XmTextSetString(h->tx,buf);
+ sprintf(buf,"%d",ida->img.i.height);
+ XmTextSetString(h->ty,buf);
+ sprintf(buf,"%d",ida->img.i.dpi);
+ XmTextSetString(h->tr,buf);
+ XtVaSetValues(h->tr,XmNsensitive,False,NULL);
+ XmToggleButtonSetState(h->lock,1,False);
+ XmToggleButtonSetState(h->size,1,False);
+ XmToggleButtonSetState(h->res,0,False);
+ if (!ida->img.i.dpi) {
+ XtVaSetValues(h->size,XmNsensitive,False,NULL);
+ XtVaSetValues(h->res, XmNsensitive,False,NULL);
+ }
+ resize_phys_size(h);
+
+ XtAddCallback(XtParent(h->dlg),XmNdestroyCallback,resize_destroy,h);
+ XtAddCallback(h->dlg, XmNokCallback, resize_button_cb, h);
+ XtAddCallback(h->dlg, XmNcancelCallback, resize_button_cb, h);
+ XtAddCallback(h->tx, XmNvalueChangedCallback, resize_sync_cb, h);
+ XtAddCallback(h->ty, XmNvalueChangedCallback, resize_sync_cb, h);
+ XtAddCallback(h->tr, XmNvalueChangedCallback, resize_sync_cb, h);
+ XtAddCallback(h->size,XmNvalueChangedCallback, resize_sync_cb, h);
+ XtAddCallback(h->res, XmNvalueChangedCallback, resize_sync_cb, h);
+ XtManageChild(h->dlg);
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct stderr_handler {
+ Widget box;
+ XmString str;
+ int pipe,err;
+ XtInputId id;
+};
+
+static void
+stderr_input(XtPointer clientdata, int *src, XtInputId *id)
+{
+ struct stderr_handler *h = clientdata;
+ XmString item;
+ Widget label;
+ char buf[1024];
+ int rc;
+
+ rc = read(h->pipe,buf,sizeof(buf)-1);
+ if (rc <= 0) {
+ /* Oops */
+ XtRemoveInput(h->id);
+ close(h->pipe);
+ XtDestroyWidget(h->box);
+ free(h);
+ }
+ buf[rc] = 0;
+ write(h->err,buf,rc);
+ item = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT,NULL);
+ h->str = XmStringConcatAndFree(h->str,item);
+ label = XmMessageBoxGetChild(h->box,XmDIALOG_MESSAGE_LABEL);
+ XtVaSetValues(label,XmNlabelString,h->str,NULL);
+ XtManageChild(h->box);
+}
+
+static void
+stderr_ok_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct stderr_handler *h = clientdata;
+
+ XmStringFree(h->str);
+ h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
+ XtUnmanageChild(h->box);
+}
+
+static void
+stderr_close_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ struct stderr_handler *h = clientdata;
+
+ XmStringFree(h->str);
+ h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
+}
+
+static void
+stderr_init(void)
+{
+ struct stderr_handler *h;
+ int p[2];
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+ h->str = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT,NULL);
+ h->box = XmCreateErrorDialog(app_shell,"errbox",NULL,0);
+ XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmMessageBoxGetChild(h->box,XmDIALOG_CANCEL_BUTTON));
+ XtAddCallback(h->box,XmNokCallback,stderr_ok_cb,h);
+ XtAddCallback(XtParent(h->box),XmNpopdownCallback,stderr_close_cb,h);
+ XSync(XtDisplay(app_shell),False);
+ if (!debug) {
+ pipe(p);
+ h->err = dup(2);
+ dup2(p[1],2);
+ close(p[1]);
+ h->pipe = p[0];
+ h->id = XtAppAddInput(app_context,h->pipe,(XtPointer)XtInputReadMask,
+ stderr_input,h);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void
+create_mainwindow(void)
+{
+ Widget img;
+
+ XmdRegisterEditres(app_shell);
+ view = XmCreateScrolledWindow(app_shell,"view",NULL,0);
+ XtManageChild(view);
+ img = XtVaCreateManagedWidget("image", xmDrawingAreaWidgetClass,view,NULL);
+ XtAddCallback(img,XmNdestinationCallback,selection_dest,NULL);
+ XtAddCallback(img,XmNconvertCallback,selection_convert,NULL);
+ dnd_add(img);
+ ida = viewer_init(img);
+ XtInstallAllAccelerators(img,app_shell);
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+ "ida " VERSION " - image viewer & editor\n"
+ "usage: ida [ options ] [ files ]\n"
+ "options:\n"
+ " -h, -help this text\n"
+ " -pcd n pick PhotoCD size (n = 1 .. 5, default 3)\n"
+ " -d, -debug enable debug messages\n");
+ exit(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i, files, zero = 0;
+ struct stat st;
+ Pixel background;
+
+ setlocale(LC_ALL,"");
+ if (0 == strcasecmp("utf-8", nl_langinfo(CODESET))) {
+ /* ### FIXME ###
+ * for not-yet known reasons ida crashes somewhere deep in
+ * the Motif libraries when running in utf-8 locale ... */
+ setenv("LC_ALL", "POSIX", 1);
+ setlocale(LC_ALL,"");
+ }
+
+ binary = argv[0];
+ ida_init_config();
+ ida_read_config();
+
+ XtSetLanguageProc(NULL,NULL,NULL);
+ app_shell = XtAppInitialize(&app_context, "Ida",
+ opt_desc, opt_count,
+ &argc, argv,
+ fallback_ressources,
+ NULL, 0);
+ dpy = XtDisplay(app_shell);
+ XtGetApplicationResources(app_shell,&args,
+ args_desc,args_count,
+ NULL,0);
+ pcd_res = GET_PHOTOCD_RES();
+ sane_res = GET_SANE_RES();
+ if (args.help)
+ usage();
+ if (args.debug) {
+ debug=1;
+ xdnd_debug = 1;
+ XSynchronize(dpy,1);
+ }
+
+ XtAppAddActions(app_context, actionTable,
+ sizeof(actionTable) / sizeof(XtActionsRec));
+ if (0) {
+ XtAddCallback(XmGetXmDisplay(dpy),XmNnoFontCallback,
+ display_cb,NULL);
+ XtAddCallback(XmGetXmDisplay(dpy),XmNnoRenditionCallback,
+ display_cb,NULL);
+ }
+ XtVaGetValues(app_shell, XtNbackground,&background, NULL);
+ x11_color_init(app_shell,&gray);
+ x11_icons_init(dpy, background /* x11_gray */);
+ stderr_init();
+ ipc_init();
+
+ wm_delete_window = XInternAtom(dpy,"WM_DELETE_WINDOW",False);
+ create_mainwindow();
+ create_control();
+ XtRealizeWidget(app_shell);
+ ptr_register(ida->widget);
+ ptr_register(control_shell);
+
+ /* handle cmd line args */
+ if (2 == argc && 0 == strcmp(argv[1],"-")) {
+ load_stdin();
+ } else if (argc > 1) {
+ for (files = 0, i = 1; i < argc; i++) {
+ if (curl_is_url(argv[i])) {
+ list_append(argv[i]);
+ files++;
+ continue;
+ }
+ if (-1 == stat(argv[i],&st)) {
+ if (debug)
+ fprintf(stderr,"stat %s: %s\n",argv[i],strerror(errno));
+ continue;
+ }
+ switch (st.st_mode & S_IFMT) {
+ case S_IFDIR:
+ browser_window(argv[i]);
+ break;
+ case S_IFREG:
+ list_append(argv[i]);
+ files++;
+ break;
+ }
+ }
+ if (files) {
+ list_update();
+ next_ac(ida->widget,NULL,NULL,&zero);
+ }
+ }
+
+ if (NULL == ida->file)
+ load_logo();
+
+ XtAppMainLoop(app_context);
+ return 0; /* keep compiler happy */
+}
diff --git a/ida.h b/ida.h
new file mode 100644
index 0000000..7fa8b0a
--- /dev/null
+++ b/ida.h
@@ -0,0 +1,26 @@
+struct ARGS {
+ Boolean debug;
+ Boolean help;
+ Boolean testload;
+};
+
+extern struct ARGS args;
+extern unsigned int pcd_res;
+
+extern Widget app_shell;
+extern XtAppContext app_context;
+extern Display *dpy;
+extern struct ida_viewer *ida;
+
+void action_cb(Widget widget, XtPointer clientdata, XtPointer call_data);
+void destroy_cb(Widget widget, XtPointer clientdata, XtPointer call_data);
+
+void ptr_register(Widget widget);
+void ptr_unregister(Widget widget);
+void ptr_busy(void);
+void ptr_idle(void);
+
+void do_save_print(void);
+void resize_shell(void);
+char* load_tmpfile(char *base);
+void new_file(char *name, int complain);
diff --git a/ida.man b/ida.man
new file mode 100644
index 0000000..8ddc67c
--- /dev/null
+++ b/ida.man
@@ -0,0 +1,102 @@
+.TH ida 1 "(c) 2001,02 Gerd Knorr"
+.SH NAME
+ida - image viewing and editing program
+.SH SYNOPSIS
+.B ida [ options ] files
+.SH DESCRIPTION
+.B ida
+is a small and fast application for viewing images. Some basic
+editing functions are available too.
+.P
+You can specify any number of image files as arguments on the command
+line. Or you can read a single image from stdin by specifying "-" as
+only argument ("xwd | ida -" works nice for screenshots).
+.SH OPTIONS
+.B ida
+understands the usual toolkit options (-geometry + friends).
+Additional options are:
+.TP
+.B -help
+print a short help text
+.TP
+.B -pcd n
+Pick size for PhotoCD images (1 .. 5, default 3).
+.TP
+.B -debug
+enable debug messages. Also has the side effect that error messages
+are displayed on stderr only and \fInot\fP as message box.
+.SH GETTING STARTED
+.SS Mouse functions
+With the left mouse button you can creates and edit a selection
+rectangle. The middle mouse button is used to start drag'n'drop
+operations. The right mouse button brings up the control window with
+menus, toolbar and file list.
+.br
+.SS Keyboard Shortcuts
+Many keyboard shortcuts used by xv are available in ida too. If you
+are familiar with xv if should be easy for you to get started with
+ida.
+.P
+All available keyboard shortcuts are also listed in the menus of the
+control window. The most important ones are listed below:
+.P
+.nf
+space next file
+backspace previous file
+cursor keys scrolling (hold ctrl key for big steps).
+plus / minus zoom in/out
+Q quit
+.fi
+.SS Supported image formats
+.B read:
+PPM, xwd, PhotoCD, xpm, xbm, bmp (uncompressed), JPEG, TIFF, PNG, GIF.
+The last four are supported using the usual libraries, i.e. you need
+to have them installed at compile time.
+.br
+.B write:
+PPM, PostScript, JPEG, TIFF, PNG.
+.SS Using drag'n'drop
+.B ida
+is a motif application and thus supports the motif drag'n'drop
+protocol in both directions. The xdnd protocol is supported too, but
+only in one direction (receive drops).
+.P
+.B ida
+uses the middle mouse button to start a drag'n'drop operation (as the
+motif style guide suggests). This works for the main window and the
+file buttons within the file browser.
+.P
+.B motif applications
+should have absolutely no problems to deal with ida's drag'n'drop
+support. You can drop images into some netscape 4.x window -- it
+simply works. Mozilla accepts motif drops too.
+.P
+Interoperation with
+.B gnome / gtk
+is good. I can drag files from ida to eeyes and visa versa without
+problems. File drops from gmc into ida work just fine too.
+.P
+Interoperation with
+.B KDE
+is bad. cut+paste works most of the time, drag'n'drop often doesn't.
+The X11 selection handling of the Qt toolkit has a few design bugs and
+sucks. Basically the troll guys didn't understand what the TARGETS
+target is good for and violate the ICCCM specs by ignoring it.
+.SH AUTHOR
+Gerd Knorr <kraxel@bytesex.org>
+.SH COPYRIGHT
+Copyright (C) 2002 Gerd Knorr <kraxel@bytesex.org>
+.P
+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.
+.P
+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.
+.P
+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., 675 Mass Ave, Cambridge, MA 02139, USA.
diff --git a/idaconfig.c b/idaconfig.c
new file mode 100644
index 0000000..56b97d8
--- /dev/null
+++ b/idaconfig.c
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "idaconfig.h"
+
+char *ida_lists;
+static char *ida_config;
+
+void ida_init_config(void)
+{
+ char *home;
+ char *conf;
+ struct stat st;
+
+ home = getenv("HOME");
+ if (NULL == home)
+ return;
+
+ conf = malloc(strlen(home) + 16);
+ ida_lists = malloc(strlen(home) + 16);
+ ida_config = malloc(strlen(home) + 16);
+ sprintf(conf, "%s/.ida", home);
+ sprintf(ida_lists, "%s/.ida/lists", home);
+ sprintf(ida_config,"%s/.ida/config", home);
+
+ if (-1 == stat(ida_lists,&st)) {
+ if (-1 == stat(conf,&st))
+ mkdir(conf,0777);
+ mkdir(ida_lists,0777);
+ }
+ free(conf);
+}
+
+void ida_read_config(void)
+{
+ int rc;
+
+ rc = cfg_parse_file("config", ida_config);
+ if (-1 == rc) {
+ /* set some defaults */
+ cfg_set_str(O_BOOKMARKS, "Home", getenv("HOME"));
+ }
+}
+
+void ida_write_config(void)
+{
+ cfg_write_file("config", ida_config);
+}
diff --git a/idaconfig.h b/idaconfig.h
new file mode 100644
index 0000000..8d616ef
--- /dev/null
+++ b/idaconfig.h
@@ -0,0 +1,24 @@
+#include "parseconfig.h"
+
+#define O_OPTIONS "config", "options"
+#define O_BOOKMARKS "config", "bookmarks"
+
+#define O_AUTOZOOM O_OPTIONS, "autozoom"
+#define O_PHOTOCD_RES O_OPTIONS, "photocd_res"
+#define O_SANE_RES O_OPTIONS, "sane_res"
+#define O_ICON_SMALL O_OPTIONS, "icon_small"
+#define O_ICON_LARGE O_OPTIONS, "icon_large"
+
+#define GET_AUTOZOOM() cfg_get_bool(O_AUTOZOOM, 1)
+#define GET_PHOTOCD_RES() cfg_get_int(O_PHOTOCD_RES, 3)
+#define GET_SANE_RES() cfg_get_int(O_SANE_RES, 300)
+#define GET_ICON_SMALL() cfg_get_int(O_ICON_SMALL, 32)
+#define GET_ICON_LARGE() cfg_get_int(O_ICON_LARGE, 96)
+
+/* -------------------------------------------------------------------------- */
+
+char *ida_lists;
+
+void ida_init_config(void);
+void ida_read_config(void);
+void ida_write_config(void);
diff --git a/jpeg/README b/jpeg/README
new file mode 100644
index 0000000..76e7464
--- /dev/null
+++ b/jpeg/README
@@ -0,0 +1,2 @@
+some source files copyed over from
+The Independent JPEG Group's JPEG software
diff --git a/jpeg/jinclude.h b/jpeg/jinclude.h
new file mode 100644
index 0000000..0a4f151
--- /dev/null
+++ b/jpeg/jinclude.h
@@ -0,0 +1,91 @@
+/*
+ * jinclude.h
+ *
+ * Copyright (C) 1991-1994, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file exists to provide a single place to fix any problems with
+ * including the wrong system include files. (Common problems are taken
+ * care of by the standard jconfig symbols, but on really weird systems
+ * you may have to edit this file.)
+ *
+ * NOTE: this file is NOT intended to be included by applications using the
+ * JPEG library. Most applications need only include jpeglib.h.
+ */
+
+
+/* Include auto-config file to find out which system include files we need. */
+
+#include "jconfig.h" /* auto configuration options */
+#define JCONFIG_INCLUDED /* so that jpeglib.h doesn't do it again */
+
+/*
+ * We need the NULL macro and size_t typedef.
+ * On an ANSI-conforming system it is sufficient to include <stddef.h>.
+ * Otherwise, we get them from <stdlib.h> or <stdio.h>; we may have to
+ * pull in <sys/types.h> as well.
+ * Note that the core JPEG library does not require <stdio.h>;
+ * only the default error handler and data source/destination modules do.
+ * But we must pull it in because of the references to FILE in jpeglib.h.
+ * You can remove those references if you want to compile without <stdio.h>.
+ */
+
+#ifdef HAVE_STDDEF_H
+#include <stddef.h>
+#endif
+
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef NEED_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#include <stdio.h>
+
+/*
+ * We need memory copying and zeroing functions, plus strncpy().
+ * ANSI and System V implementations declare these in <string.h>.
+ * BSD doesn't have the mem() functions, but it does have bcopy()/bzero().
+ * Some systems may declare memset and memcpy in <memory.h>.
+ *
+ * NOTE: we assume the size parameters to these functions are of type size_t.
+ * Change the casts in these macros if not!
+ */
+
+#ifdef NEED_BSD_STRINGS
+
+#include <strings.h>
+#define MEMZERO(target,size) bzero((void *)(target), (size_t)(size))
+#define MEMCOPY(dest,src,size) bcopy((const void *)(src), (void *)(dest), (size_t)(size))
+
+#else /* not BSD, assume ANSI/SysV string lib */
+
+#include <string.h>
+#define MEMZERO(target,size) memset((void *)(target), 0, (size_t)(size))
+#define MEMCOPY(dest,src,size) memcpy((void *)(dest), (const void *)(src), (size_t)(size))
+
+#endif
+
+/*
+ * In ANSI C, and indeed any rational implementation, size_t is also the
+ * type returned by sizeof(). However, it seems there are some irrational
+ * implementations out there, in which sizeof() returns an int even though
+ * size_t is defined as long or unsigned long. To ensure consistent results
+ * we always use this SIZEOF() macro in place of using sizeof() directly.
+ */
+
+#define SIZEOF(object) ((size_t) sizeof(object))
+
+/*
+ * The modules that use fread() and fwrite() always invoke them through
+ * these macros. On some systems you may need to twiddle the argument casts.
+ * CAUTION: argument order is different from underlying functions!
+ */
+
+#define JFREAD(file,buf,sizeofbuf) \
+ ((size_t) fread((void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file)))
+#define JFWRITE(file,buf,sizeofbuf) \
+ ((size_t) fwrite((const void *) (buf), (size_t) 1, (size_t) (sizeofbuf), (file)))
diff --git a/jpeg/jpegint.h b/jpeg/jpegint.h
new file mode 100644
index 0000000..95b00d4
--- /dev/null
+++ b/jpeg/jpegint.h
@@ -0,0 +1,392 @@
+/*
+ * jpegint.h
+ *
+ * Copyright (C) 1991-1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file provides common declarations for the various JPEG modules.
+ * These declarations are considered internal to the JPEG library; most
+ * applications using the library shouldn't need to include this file.
+ */
+
+
+/* Declarations for both compression & decompression */
+
+typedef enum { /* Operating modes for buffer controllers */
+ JBUF_PASS_THRU, /* Plain stripwise operation */
+ /* Remaining modes require a full-image buffer to have been created */
+ JBUF_SAVE_SOURCE, /* Run source subobject only, save output */
+ JBUF_CRANK_DEST, /* Run dest subobject only, using saved data */
+ JBUF_SAVE_AND_PASS /* Run both subobjects, save output */
+} J_BUF_MODE;
+
+/* Values of global_state field (jdapi.c has some dependencies on ordering!) */
+#define CSTATE_START 100 /* after create_compress */
+#define CSTATE_SCANNING 101 /* start_compress done, write_scanlines OK */
+#define CSTATE_RAW_OK 102 /* start_compress done, write_raw_data OK */
+#define CSTATE_WRCOEFS 103 /* jpeg_write_coefficients done */
+#define DSTATE_START 200 /* after create_decompress */
+#define DSTATE_INHEADER 201 /* reading header markers, no SOS yet */
+#define DSTATE_READY 202 /* found SOS, ready for start_decompress */
+#define DSTATE_PRELOAD 203 /* reading multiscan file in start_decompress*/
+#define DSTATE_PRESCAN 204 /* performing dummy pass for 2-pass quant */
+#define DSTATE_SCANNING 205 /* start_decompress done, read_scanlines OK */
+#define DSTATE_RAW_OK 206 /* start_decompress done, read_raw_data OK */
+#define DSTATE_BUFIMAGE 207 /* expecting jpeg_start_output */
+#define DSTATE_BUFPOST 208 /* looking for SOS/EOI in jpeg_finish_output */
+#define DSTATE_RDCOEFS 209 /* reading file in jpeg_read_coefficients */
+#define DSTATE_STOPPING 210 /* looking for EOI in jpeg_finish_decompress */
+
+
+/* Declarations for compression modules */
+
+/* Master control module */
+struct jpeg_comp_master {
+ JMETHOD(void, prepare_for_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, pass_startup, (j_compress_ptr cinfo));
+ JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean call_pass_startup; /* True if pass_startup must be called */
+ boolean is_last_pass; /* True during last pass */
+};
+
+/* Main buffer control (downsampled-data buffer) */
+struct jpeg_c_main_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, process_data, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf, JDIMENSION *in_row_ctr,
+ JDIMENSION in_rows_avail));
+};
+
+/* Compression preprocessing (downsampling input buffer control) */
+struct jpeg_c_prep_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, pre_process_data, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf,
+ JDIMENSION *in_row_ctr,
+ JDIMENSION in_rows_avail,
+ JSAMPIMAGE output_buf,
+ JDIMENSION *out_row_group_ctr,
+ JDIMENSION out_row_groups_avail));
+};
+
+/* Coefficient buffer control */
+struct jpeg_c_coef_controller {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(boolean, compress_data, (j_compress_ptr cinfo,
+ JSAMPIMAGE input_buf));
+};
+
+/* Colorspace conversion */
+struct jpeg_color_converter {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, color_convert, (j_compress_ptr cinfo,
+ JSAMPARRAY input_buf, JSAMPIMAGE output_buf,
+ JDIMENSION output_row, int num_rows));
+};
+
+/* Downsampling */
+struct jpeg_downsampler {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ JMETHOD(void, downsample, (j_compress_ptr cinfo,
+ JSAMPIMAGE input_buf, JDIMENSION in_row_index,
+ JSAMPIMAGE output_buf,
+ JDIMENSION out_row_group_index));
+
+ boolean need_context_rows; /* TRUE if need rows above & below */
+};
+
+/* Forward DCT (also controls coefficient quantization) */
+struct jpeg_forward_dct {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo));
+ /* perhaps this should be an array??? */
+ JMETHOD(void, forward_DCT, (j_compress_ptr cinfo,
+ jpeg_component_info * compptr,
+ JSAMPARRAY sample_data, JBLOCKROW coef_blocks,
+ JDIMENSION start_row, JDIMENSION start_col,
+ JDIMENSION num_blocks));
+};
+
+/* Entropy encoding */
+struct jpeg_entropy_encoder {
+ JMETHOD(void, start_pass, (j_compress_ptr cinfo, boolean gather_statistics));
+ JMETHOD(boolean, encode_mcu, (j_compress_ptr cinfo, JBLOCKROW *MCU_data));
+ JMETHOD(void, finish_pass, (j_compress_ptr cinfo));
+};
+
+/* Marker writing */
+struct jpeg_marker_writer {
+ JMETHOD(void, write_file_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_frame_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_scan_header, (j_compress_ptr cinfo));
+ JMETHOD(void, write_file_trailer, (j_compress_ptr cinfo));
+ JMETHOD(void, write_tables_only, (j_compress_ptr cinfo));
+ /* These routines are exported to allow insertion of extra markers */
+ /* Probably only COM and APPn markers should be written this way */
+ JMETHOD(void, write_marker_header, (j_compress_ptr cinfo, int marker,
+ unsigned int datalen));
+ JMETHOD(void, write_marker_byte, (j_compress_ptr cinfo, int val));
+};
+
+
+/* Declarations for decompression modules */
+
+/* Master control module */
+struct jpeg_decomp_master {
+ JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean is_dummy_pass; /* True during 1st pass for 2-pass quant */
+};
+
+/* Input control module */
+struct jpeg_input_controller {
+ JMETHOD(int, consume_input, (j_decompress_ptr cinfo));
+ JMETHOD(void, reset_input_controller, (j_decompress_ptr cinfo));
+ JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, finish_input_pass, (j_decompress_ptr cinfo));
+
+ /* State variables made visible to other modules */
+ boolean has_multiple_scans; /* True if file has multiple scans */
+ boolean eoi_reached; /* True when EOI has been consumed */
+};
+
+/* Main buffer control (downsampled-data buffer) */
+struct jpeg_d_main_controller {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, process_data, (j_decompress_ptr cinfo,
+ JSAMPARRAY output_buf, JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+};
+
+/* Coefficient buffer control */
+struct jpeg_d_coef_controller {
+ JMETHOD(void, start_input_pass, (j_decompress_ptr cinfo));
+ JMETHOD(int, consume_data, (j_decompress_ptr cinfo));
+ JMETHOD(void, start_output_pass, (j_decompress_ptr cinfo));
+ JMETHOD(int, decompress_data, (j_decompress_ptr cinfo,
+ JSAMPIMAGE output_buf));
+ /* Pointer to array of coefficient virtual arrays, or NULL if none */
+ jvirt_barray_ptr *coef_arrays;
+};
+
+/* Decompression postprocessing (color quantization buffer control) */
+struct jpeg_d_post_controller {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+ JMETHOD(void, post_process_data, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf,
+ JDIMENSION *in_row_group_ctr,
+ JDIMENSION in_row_groups_avail,
+ JSAMPARRAY output_buf,
+ JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+};
+
+/* Marker reading & parsing */
+struct jpeg_marker_reader {
+ JMETHOD(void, reset_marker_reader, (j_decompress_ptr cinfo));
+ /* Read markers until SOS or EOI.
+ * Returns same codes as are defined for jpeg_consume_input:
+ * JPEG_SUSPENDED, JPEG_REACHED_SOS, or JPEG_REACHED_EOI.
+ */
+ JMETHOD(int, read_markers, (j_decompress_ptr cinfo));
+ /* Read a restart marker --- exported for use by entropy decoder only */
+ jpeg_marker_parser_method read_restart_marker;
+
+ /* State of marker reader --- nominally internal, but applications
+ * supplying COM or APPn handlers might like to know the state.
+ */
+ boolean saw_SOI; /* found SOI? */
+ boolean saw_SOF; /* found SOF? */
+ int next_restart_num; /* next restart number expected (0-7) */
+ unsigned int discarded_bytes; /* # of bytes skipped looking for a marker */
+};
+
+/* Entropy decoding */
+struct jpeg_entropy_decoder {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(boolean, decode_mcu, (j_decompress_ptr cinfo,
+ JBLOCKROW *MCU_data));
+
+ /* This is here to share code between baseline and progressive decoders; */
+ /* other modules probably should not use it */
+ boolean insufficient_data; /* set TRUE after emitting warning */
+};
+
+/* Inverse DCT (also performs dequantization) */
+typedef JMETHOD(void, inverse_DCT_method_ptr,
+ (j_decompress_ptr cinfo, jpeg_component_info * compptr,
+ JCOEFPTR coef_block,
+ JSAMPARRAY output_buf, JDIMENSION output_col));
+
+struct jpeg_inverse_dct {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ /* It is useful to allow each component to have a separate IDCT method. */
+ inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS];
+};
+
+/* Upsampling (note that upsampler must also call color converter) */
+struct jpeg_upsampler {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, upsample, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf,
+ JDIMENSION *in_row_group_ctr,
+ JDIMENSION in_row_groups_avail,
+ JSAMPARRAY output_buf,
+ JDIMENSION *out_row_ctr,
+ JDIMENSION out_rows_avail));
+
+ boolean need_context_rows; /* TRUE if need rows above & below */
+};
+
+/* Colorspace conversion */
+struct jpeg_color_deconverter {
+ JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+ JMETHOD(void, color_convert, (j_decompress_ptr cinfo,
+ JSAMPIMAGE input_buf, JDIMENSION input_row,
+ JSAMPARRAY output_buf, int num_rows));
+};
+
+/* Color quantization or color precision reduction */
+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));
+ JMETHOD(void, new_color_map, (j_decompress_ptr cinfo));
+};
+
+
+/* Miscellaneous useful macros */
+
+#undef MAX
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#undef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+
+/* We assume that right shift corresponds to signed division by 2 with
+ * rounding towards minus infinity. This is correct for typical "arithmetic
+ * shift" instructions that shift in copies of the sign bit. But some
+ * C compilers implement >> with an unsigned shift. For these machines you
+ * must define RIGHT_SHIFT_IS_UNSIGNED.
+ * RIGHT_SHIFT provides a proper signed right shift of an INT32 quantity.
+ * It is only applied with constant shift counts. SHIFT_TEMPS must be
+ * included in the variables of any routine using RIGHT_SHIFT.
+ */
+
+#ifdef RIGHT_SHIFT_IS_UNSIGNED
+#define SHIFT_TEMPS INT32 shift_temp;
+#define RIGHT_SHIFT(x,shft) \
+ ((shift_temp = (x)) < 0 ? \
+ (shift_temp >> (shft)) | ((~((INT32) 0)) << (32-(shft))) : \
+ (shift_temp >> (shft)))
+#else
+#define SHIFT_TEMPS
+#define RIGHT_SHIFT(x,shft) ((x) >> (shft))
+#endif
+
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jinit_compress_master jICompress
+#define jinit_c_master_control jICMaster
+#define jinit_c_main_controller jICMainC
+#define jinit_c_prep_controller jICPrepC
+#define jinit_c_coef_controller jICCoefC
+#define jinit_color_converter jICColor
+#define jinit_downsampler jIDownsampler
+#define jinit_forward_dct jIFDCT
+#define jinit_huff_encoder jIHEncoder
+#define jinit_phuff_encoder jIPHEncoder
+#define jinit_marker_writer jIMWriter
+#define jinit_master_decompress jIDMaster
+#define jinit_d_main_controller jIDMainC
+#define jinit_d_coef_controller jIDCoefC
+#define jinit_d_post_controller jIDPostC
+#define jinit_input_controller jIInCtlr
+#define jinit_marker_reader jIMReader
+#define jinit_huff_decoder jIHDecoder
+#define jinit_phuff_decoder jIPHDecoder
+#define jinit_inverse_dct jIIDCT
+#define jinit_upsampler jIUpsampler
+#define jinit_color_deconverter jIDColor
+#define jinit_1pass_quantizer jI1Quant
+#define jinit_2pass_quantizer jI2Quant
+#define jinit_merged_upsampler jIMUpsampler
+#define jinit_memory_mgr jIMemMgr
+#define jdiv_round_up jDivRound
+#define jround_up jRound
+#define jcopy_sample_rows jCopySamples
+#define jcopy_block_row jCopyBlocks
+#define jzero_far jZeroFar
+#define jpeg_zigzag_order jZIGTable
+#define jpeg_natural_order jZAGTable
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+
+/* Compression module initialization routines */
+EXTERN(void) jinit_compress_master JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_c_master_control JPP((j_compress_ptr cinfo,
+ boolean transcode_only));
+EXTERN(void) jinit_c_main_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_c_prep_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_c_coef_controller JPP((j_compress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_color_converter JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_downsampler JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_forward_dct JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_huff_encoder JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_phuff_encoder JPP((j_compress_ptr cinfo));
+EXTERN(void) jinit_marker_writer JPP((j_compress_ptr cinfo));
+/* Decompression module initialization routines */
+EXTERN(void) jinit_master_decompress JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_d_main_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_d_coef_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_d_post_controller JPP((j_decompress_ptr cinfo,
+ boolean need_full_buffer));
+EXTERN(void) jinit_input_controller JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_marker_reader JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_huff_decoder JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_phuff_decoder JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_inverse_dct JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_upsampler JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_color_deconverter JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_1pass_quantizer JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_2pass_quantizer JPP((j_decompress_ptr cinfo));
+EXTERN(void) jinit_merged_upsampler JPP((j_decompress_ptr cinfo));
+/* Memory manager initialization */
+EXTERN(void) jinit_memory_mgr JPP((j_common_ptr cinfo));
+
+/* Utility routines in jutils.c */
+EXTERN(long) jdiv_round_up JPP((long a, long b));
+EXTERN(long) jround_up JPP((long a, long b));
+EXTERN(void) jcopy_sample_rows JPP((JSAMPARRAY input_array, int source_row,
+ JSAMPARRAY output_array, int dest_row,
+ int num_rows, JDIMENSION num_cols));
+EXTERN(void) jcopy_block_row JPP((JBLOCKROW input_row, JBLOCKROW output_row,
+ JDIMENSION num_blocks));
+EXTERN(void) jzero_far JPP((void FAR * target, size_t bytestozero));
+/* Constant tables in jutils.c */
+#if 0 /* This table is not actually needed in v6a */
+extern const int jpeg_zigzag_order[]; /* natural coef order to zigzag order */
+#endif
+extern const int jpeg_natural_order[]; /* zigzag coef order to natural order */
+
+/* Suppress undefined-structure complaints if necessary. */
+
+#ifdef INCOMPLETE_TYPES_BROKEN
+#ifndef AM_MEMORY_MANAGER /* only jmemmgr.c defines these */
+struct jvirt_sarray_control { long dummy; };
+struct jvirt_barray_control { long dummy; };
+#endif
+#endif /* INCOMPLETE_TYPES_BROKEN */
diff --git a/jpeg/jpeglib.h b/jpeg/jpeglib.h
new file mode 100644
index 0000000..d1be8dd
--- /dev/null
+++ b/jpeg/jpeglib.h
@@ -0,0 +1,1096 @@
+/*
+ * jpeglib.h
+ *
+ * Copyright (C) 1991-1998, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file defines the application interface for the JPEG library.
+ * Most applications using the library need only include this file,
+ * and perhaps jerror.h if they want to know the exact error codes.
+ */
+
+#ifndef JPEGLIB_H
+#define JPEGLIB_H
+
+/*
+ * First we include the configuration files that record how this
+ * installation of the JPEG library is set up. jconfig.h can be
+ * generated automatically for many systems. jmorecfg.h contains
+ * manual configuration options that most people need not worry about.
+ */
+
+#ifndef JCONFIG_INCLUDED /* in case jinclude.h already did */
+#include "jconfig.h" /* widely used configuration options */
+#endif
+#include "jmorecfg.h" /* seldom changed options */
+
+
+/* Version ID for the JPEG library.
+ * Might be useful for tests like "#if JPEG_LIB_VERSION >= 60".
+ */
+
+#define JPEG_LIB_VERSION 62 /* Version 6b */
+
+
+/* Various constants determining the sizes of things.
+ * All of these are specified by the JPEG standard, so don't change them
+ * if you want to be compatible.
+ */
+
+#define DCTSIZE 8 /* The basic DCT block is 8x8 samples */
+#define DCTSIZE2 64 /* DCTSIZE squared; # of elements in a block */
+#define NUM_QUANT_TBLS 4 /* Quantization tables are numbered 0..3 */
+#define NUM_HUFF_TBLS 4 /* Huffman tables are numbered 0..3 */
+#define NUM_ARITH_TBLS 16 /* Arith-coding tables are numbered 0..15 */
+#define MAX_COMPS_IN_SCAN 4 /* JPEG limit on # of components in one scan */
+#define MAX_SAMP_FACTOR 4 /* JPEG limit on sampling factors */
+/* Unfortunately, some bozo at Adobe saw no reason to be bound by the standard;
+ * the PostScript DCT filter can emit files with many more than 10 blocks/MCU.
+ * If you happen to run across such a file, you can up D_MAX_BLOCKS_IN_MCU
+ * to handle it. We even let you do this from the jconfig.h file. However,
+ * we strongly discourage changing C_MAX_BLOCKS_IN_MCU; just because Adobe
+ * sometimes emits noncompliant files doesn't mean you should too.
+ */
+#define C_MAX_BLOCKS_IN_MCU 10 /* compressor's limit on blocks per MCU */
+#ifndef D_MAX_BLOCKS_IN_MCU
+#define D_MAX_BLOCKS_IN_MCU 10 /* decompressor's limit on blocks per MCU */
+#endif
+
+
+/* Data structures for images (arrays of samples and of DCT coefficients).
+ * On 80x86 machines, the image arrays are too big for near pointers,
+ * but the pointer arrays can fit in near memory.
+ */
+
+typedef JSAMPLE FAR *JSAMPROW; /* ptr to one image row of pixel samples. */
+typedef JSAMPROW *JSAMPARRAY; /* ptr to some rows (a 2-D sample array) */
+typedef JSAMPARRAY *JSAMPIMAGE; /* a 3-D sample array: top index is color */
+
+typedef JCOEF JBLOCK[DCTSIZE2]; /* one block of coefficients */
+typedef JBLOCK FAR *JBLOCKROW; /* pointer to one row of coefficient blocks */
+typedef JBLOCKROW *JBLOCKARRAY; /* a 2-D array of coefficient blocks */
+typedef JBLOCKARRAY *JBLOCKIMAGE; /* a 3-D array of coefficient blocks */
+
+typedef JCOEF FAR *JCOEFPTR; /* useful in a couple of places */
+
+
+/* Types for JPEG compression parameters and working tables. */
+
+
+/* DCT coefficient quantization tables. */
+
+typedef struct {
+ /* This array gives the coefficient quantizers in natural array order
+ * (not the zigzag order in which they are stored in a JPEG DQT marker).
+ * CAUTION: IJG versions prior to v6a kept this array in zigzag order.
+ */
+ UINT16 quantval[DCTSIZE2]; /* quantization step for each coefficient */
+ /* This field is used only during compression. It's initialized FALSE when
+ * the table is created, and set TRUE when it's been output to the file.
+ * You could suppress output of a table by setting this to TRUE.
+ * (See jpeg_suppress_tables for an example.)
+ */
+ boolean sent_table; /* TRUE when table has been output */
+} JQUANT_TBL;
+
+
+/* Huffman coding tables. */
+
+typedef struct {
+ /* These two fields directly represent the contents of a JPEG DHT marker */
+ UINT8 bits[17]; /* bits[k] = # of symbols with codes of */
+ /* length k bits; bits[0] is unused */
+ UINT8 huffval[256]; /* The symbols, in order of incr code length */
+ /* This field is used only during compression. It's initialized FALSE when
+ * the table is created, and set TRUE when it's been output to the file.
+ * You could suppress output of a table by setting this to TRUE.
+ * (See jpeg_suppress_tables for an example.)
+ */
+ boolean sent_table; /* TRUE when table has been output */
+} JHUFF_TBL;
+
+
+/* Basic info about one component (color channel). */
+
+typedef struct {
+ /* These values are fixed over the whole image. */
+ /* For compression, they must be supplied by parameter setup; */
+ /* for decompression, they are read from the SOF marker. */
+ int component_id; /* identifier for this component (0..255) */
+ int component_index; /* its index in SOF or cinfo->comp_info[] */
+ int h_samp_factor; /* horizontal sampling factor (1..4) */
+ int v_samp_factor; /* vertical sampling factor (1..4) */
+ int quant_tbl_no; /* quantization table selector (0..3) */
+ /* These values may vary between scans. */
+ /* For compression, they must be supplied by parameter setup; */
+ /* for decompression, they are read from the SOS marker. */
+ /* The decompressor output side may not use these variables. */
+ int dc_tbl_no; /* DC entropy table selector (0..3) */
+ int ac_tbl_no; /* AC entropy table selector (0..3) */
+
+ /* Remaining fields should be treated as private by applications. */
+
+ /* These values are computed during compression or decompression startup: */
+ /* Component's size in DCT blocks.
+ * Any dummy blocks added to complete an MCU are not counted; therefore
+ * these values do not depend on whether a scan is interleaved or not.
+ */
+ JDIMENSION width_in_blocks;
+ JDIMENSION height_in_blocks;
+ /* Size of a DCT block in samples. Always DCTSIZE for compression.
+ * For decompression this is the size of the output from one DCT block,
+ * reflecting any scaling we choose to apply during the IDCT step.
+ * Values of 1,2,4,8 are likely to be supported. Note that different
+ * components may receive different IDCT scalings.
+ */
+ int DCT_scaled_size;
+ /* The downsampled dimensions are the component's actual, unpadded number
+ * of samples at the main buffer (preprocessing/compression interface), thus
+ * downsampled_width = ceil(image_width * Hi/Hmax)
+ * and similarly for height. For decompression, IDCT scaling is included, so
+ * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE)
+ */
+ JDIMENSION downsampled_width; /* actual width in samples */
+ JDIMENSION downsampled_height; /* actual height in samples */
+ /* This flag is used only for decompression. In cases where some of the
+ * components will be ignored (eg grayscale output from YCbCr image),
+ * we can skip most computations for the unused components.
+ */
+ boolean component_needed; /* do we need the value of this component? */
+
+ /* These values are computed before starting a scan of the component. */
+ /* The decompressor output side may not use these variables. */
+ int MCU_width; /* number of blocks per MCU, horizontally */
+ int MCU_height; /* number of blocks per MCU, vertically */
+ int MCU_blocks; /* MCU_width * MCU_height */
+ int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */
+ int last_col_width; /* # of non-dummy blocks across in last MCU */
+ int last_row_height; /* # of non-dummy blocks down in last MCU */
+
+ /* Saved quantization table for component; NULL if none yet saved.
+ * See jdinput.c comments about the need for this information.
+ * This field is currently used only for decompression.
+ */
+ JQUANT_TBL * quant_table;
+
+ /* Private per-component storage for DCT or IDCT subsystem. */
+ void * dct_table;
+} jpeg_component_info;
+
+
+/* The script for encoding a multiple-scan file is an array of these: */
+
+typedef struct {
+ int comps_in_scan; /* number of components encoded in this scan */
+ int component_index[MAX_COMPS_IN_SCAN]; /* their SOF/comp_info[] indexes */
+ int Ss, Se; /* progressive JPEG spectral selection parms */
+ int Ah, Al; /* progressive JPEG successive approx. parms */
+} jpeg_scan_info;
+
+/* The decompressor can save APPn and COM markers in a list of these: */
+
+typedef struct jpeg_marker_struct FAR * jpeg_saved_marker_ptr;
+
+struct jpeg_marker_struct {
+ jpeg_saved_marker_ptr next; /* next in list, or NULL */
+ UINT8 marker; /* marker code: JPEG_COM, or JPEG_APP0+n */
+ unsigned int original_length; /* # bytes of data in the file */
+ unsigned int data_length; /* # bytes of data saved at data[] */
+ JOCTET FAR * data; /* the data contained in the marker */
+ /* the marker length word is not counted in data_length or original_length */
+};
+
+/* Known color spaces. */
+
+typedef enum {
+ JCS_UNKNOWN, /* error/unspecified */
+ JCS_GRAYSCALE, /* monochrome */
+ JCS_RGB, /* red/green/blue */
+ JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */
+ JCS_CMYK, /* C/M/Y/K */
+ JCS_YCCK /* Y/Cb/Cr/K */
+} J_COLOR_SPACE;
+
+/* DCT/IDCT algorithm options. */
+
+typedef enum {
+ JDCT_ISLOW, /* slow but accurate integer algorithm */
+ JDCT_IFAST, /* faster, less accurate integer method */
+ JDCT_FLOAT /* floating-point: accurate, fast on fast HW */
+} J_DCT_METHOD;
+
+#ifndef JDCT_DEFAULT /* may be overridden in jconfig.h */
+#define JDCT_DEFAULT JDCT_ISLOW
+#endif
+#ifndef JDCT_FASTEST /* may be overridden in jconfig.h */
+#define JDCT_FASTEST JDCT_IFAST
+#endif
+
+/* Dithering options for decompression. */
+
+typedef enum {
+ JDITHER_NONE, /* no dithering */
+ JDITHER_ORDERED, /* simple ordered dither */
+ JDITHER_FS /* Floyd-Steinberg error diffusion dither */
+} J_DITHER_MODE;
+
+
+/* Common fields between JPEG compression and decompression master structs. */
+
+#define jpeg_common_fields \
+ struct jpeg_error_mgr * err; /* Error handler module */\
+ struct jpeg_memory_mgr * mem; /* Memory manager module */\
+ struct jpeg_progress_mgr * progress; /* Progress monitor, or NULL if none */\
+ void * client_data; /* Available for use by application */\
+ boolean is_decompressor; /* So common code can tell which is which */\
+ int global_state /* For checking call sequence validity */
+
+/* Routines that are to be used by both halves of the library are declared
+ * to receive a pointer to this structure. There are no actual instances of
+ * jpeg_common_struct, only of jpeg_compress_struct and jpeg_decompress_struct.
+ */
+struct jpeg_common_struct {
+ jpeg_common_fields; /* Fields common to both master struct types */
+ /* Additional fields follow in an actual jpeg_compress_struct or
+ * jpeg_decompress_struct. All three structs must agree on these
+ * initial fields! (This would be a lot cleaner in C++.)
+ */
+};
+
+typedef struct jpeg_common_struct * j_common_ptr;
+typedef struct jpeg_compress_struct * j_compress_ptr;
+typedef struct jpeg_decompress_struct * j_decompress_ptr;
+
+
+/* Master record for a compression instance */
+
+struct jpeg_compress_struct {
+ jpeg_common_fields; /* Fields shared with jpeg_decompress_struct */
+
+ /* Destination for compressed data */
+ struct jpeg_destination_mgr * dest;
+
+ /* Description of source image --- these fields must be filled in by
+ * outer application before starting compression. in_color_space must
+ * be correct before you can even call jpeg_set_defaults().
+ */
+
+ JDIMENSION image_width; /* input image width */
+ JDIMENSION image_height; /* input image height */
+ int input_components; /* # of color components in input image */
+ J_COLOR_SPACE in_color_space; /* colorspace of input image */
+
+ double input_gamma; /* image gamma of input image */
+
+ /* Compression parameters --- these fields must be set before calling
+ * jpeg_start_compress(). We recommend calling jpeg_set_defaults() to
+ * initialize everything to reasonable defaults, then changing anything
+ * the application specifically wants to change. That way you won't get
+ * burnt when new parameters are added. Also note that there are several
+ * helper routines to simplify changing parameters.
+ */
+
+ int data_precision; /* bits of precision in image data */
+
+ int num_components; /* # of color components in JPEG image */
+ J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */
+
+ jpeg_component_info * comp_info;
+ /* comp_info[i] describes component that appears i'th in SOF */
+
+ JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS];
+ /* ptrs to coefficient quantization tables, or NULL if not defined */
+
+ JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS];
+ JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS];
+ /* ptrs to Huffman coding tables, or NULL if not defined */
+
+ UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */
+ UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */
+ UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */
+
+ int num_scans; /* # of entries in scan_info array */
+ const jpeg_scan_info * scan_info; /* script for multi-scan file, or NULL */
+ /* The default value of scan_info is NULL, which causes a single-scan
+ * sequential JPEG file to be emitted. To create a multi-scan file,
+ * set num_scans and scan_info to point to an array of scan definitions.
+ */
+
+ boolean raw_data_in; /* TRUE=caller supplies downsampled data */
+ boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */
+ boolean optimize_coding; /* TRUE=optimize entropy encoding parms */
+ boolean CCIR601_sampling; /* TRUE=first samples are cosited */
+ int smoothing_factor; /* 1..100, or 0 for no input smoothing */
+ J_DCT_METHOD dct_method; /* DCT algorithm selector */
+
+ /* The restart interval can be specified in absolute MCUs by setting
+ * restart_interval, or in MCU rows by setting restart_in_rows
+ * (in which case the correct restart_interval will be figured
+ * for each scan).
+ */
+ unsigned int restart_interval; /* MCUs per restart, or 0 for no restart */
+ int restart_in_rows; /* if > 0, MCU rows per restart interval */
+
+ /* Parameters controlling emission of special markers. */
+
+ boolean write_JFIF_header; /* should a JFIF marker be written? */
+ UINT8 JFIF_major_version; /* What to write for the JFIF version number */
+ UINT8 JFIF_minor_version;
+ /* These three values are not used by the JPEG code, merely copied */
+ /* into the JFIF APP0 marker. density_unit can be 0 for unknown, */
+ /* 1 for dots/inch, or 2 for dots/cm. Note that the pixel aspect */
+ /* ratio is defined by X_density/Y_density even when density_unit=0. */
+ UINT8 density_unit; /* JFIF code for pixel size units */
+ UINT16 X_density; /* Horizontal pixel density */
+ UINT16 Y_density; /* Vertical pixel density */
+ boolean write_Adobe_marker; /* should an Adobe marker be written? */
+
+ /* State variable: index of next scanline to be written to
+ * jpeg_write_scanlines(). Application may use this to control its
+ * processing loop, e.g., "while (next_scanline < image_height)".
+ */
+
+ JDIMENSION next_scanline; /* 0 .. image_height-1 */
+
+ /* Remaining fields are known throughout compressor, but generally
+ * should not be touched by a surrounding application.
+ */
+
+ /*
+ * These fields are computed during compression startup
+ */
+ boolean progressive_mode; /* TRUE if scan script uses progressive mode */
+ int max_h_samp_factor; /* largest h_samp_factor */
+ int max_v_samp_factor; /* largest v_samp_factor */
+
+ JDIMENSION total_iMCU_rows; /* # of iMCU rows to be input to coef ctlr */
+ /* The coefficient controller receives data in units of MCU rows as defined
+ * for fully interleaved scans (whether the JPEG file is interleaved or not).
+ * There are v_samp_factor * DCTSIZE sample rows of each component in an
+ * "iMCU" (interleaved MCU) row.
+ */
+
+ /*
+ * These fields are valid during any one scan.
+ * They describe the components and MCUs actually appearing in the scan.
+ */
+ int comps_in_scan; /* # of JPEG components in this scan */
+ jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN];
+ /* *cur_comp_info[i] describes component that appears i'th in SOS */
+
+ JDIMENSION MCUs_per_row; /* # of MCUs across the image */
+ JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */
+
+ int blocks_in_MCU; /* # of DCT blocks per MCU */
+ int MCU_membership[C_MAX_BLOCKS_IN_MCU];
+ /* MCU_membership[i] is index in cur_comp_info of component owning */
+ /* i'th block in an MCU */
+
+ int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */
+
+ /*
+ * Links to compression subobjects (methods and private variables of modules)
+ */
+ struct jpeg_comp_master * master;
+ struct jpeg_c_main_controller * main;
+ struct jpeg_c_prep_controller * prep;
+ struct jpeg_c_coef_controller * coef;
+ struct jpeg_marker_writer * marker;
+ struct jpeg_color_converter * cconvert;
+ struct jpeg_downsampler * downsample;
+ struct jpeg_forward_dct * fdct;
+ struct jpeg_entropy_encoder * entropy;
+ jpeg_scan_info * script_space; /* workspace for jpeg_simple_progression */
+ int script_space_size;
+};
+
+
+/* Master record for a decompression instance */
+
+struct jpeg_decompress_struct {
+ jpeg_common_fields; /* Fields shared with jpeg_compress_struct */
+
+ /* Source of compressed data */
+ struct jpeg_source_mgr * src;
+
+ /* Basic description of image --- filled in by jpeg_read_header(). */
+ /* Application may inspect these values to decide how to process image. */
+
+ JDIMENSION image_width; /* nominal image width (from SOF marker) */
+ JDIMENSION image_height; /* nominal image height */
+ int num_components; /* # of color components in JPEG image */
+ J_COLOR_SPACE jpeg_color_space; /* colorspace of JPEG image */
+
+ /* Decompression processing parameters --- these fields must be set before
+ * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes
+ * them to default values.
+ */
+
+ J_COLOR_SPACE out_color_space; /* colorspace for output */
+
+ unsigned int scale_num, scale_denom; /* fraction by which to scale image */
+
+ double output_gamma; /* image gamma wanted in output */
+
+ boolean buffered_image; /* TRUE=multiple output passes */
+ boolean raw_data_out; /* TRUE=downsampled data wanted */
+
+ J_DCT_METHOD dct_method; /* IDCT algorithm selector */
+ boolean do_fancy_upsampling; /* TRUE=apply fancy upsampling */
+ boolean do_block_smoothing; /* TRUE=apply interblock smoothing */
+
+ boolean quantize_colors; /* TRUE=colormapped output wanted */
+ /* the following are ignored if not quantize_colors: */
+ J_DITHER_MODE dither_mode; /* type of color dithering to use */
+ boolean two_pass_quantize; /* TRUE=use two-pass color quantization */
+ int desired_number_of_colors; /* max # colors to use in created colormap */
+ /* these are significant only in buffered-image mode: */
+ boolean enable_1pass_quant; /* enable future use of 1-pass quantizer */
+ boolean enable_external_quant;/* enable future use of external colormap */
+ boolean enable_2pass_quant; /* enable future use of 2-pass quantizer */
+
+ /* 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 */
+ int out_color_components; /* # of color components in out_color_space */
+ int output_components; /* # of color components returned */
+ /* output_components is 1 (a colormap index) when quantizing colors;
+ * otherwise it equals out_color_components.
+ */
+ int rec_outbuf_height; /* min recommended height of scanline buffer */
+ /* If the buffer passed to jpeg_read_scanlines() is less than this many rows
+ * high, space and time will be wasted due to unnecessary data copying.
+ * Usually rec_outbuf_height will be 1 or 2, at most 4.
+ */
+
+ /* 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 out_color_components 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 */
+
+ /* State variables: these variables indicate the progress of decompression.
+ * The application may examine these but must not modify them.
+ */
+
+ /* Row index of next scanline to be read from jpeg_read_scanlines().
+ * Application may use this to control its processing loop, e.g.,
+ * "while (output_scanline < output_height)".
+ */
+ JDIMENSION output_scanline; /* 0 .. output_height-1 */
+
+ /* Current input scan number and number of iMCU rows completed in scan.
+ * These indicate the progress of the decompressor input side.
+ */
+ int input_scan_number; /* Number of SOS markers seen so far */
+ JDIMENSION input_iMCU_row; /* Number of iMCU rows completed */
+
+ /* The "output scan number" is the notional scan being displayed by the
+ * output side. The decompressor will not allow output scan/row number
+ * to get ahead of input scan/row, but it can fall arbitrarily far behind.
+ */
+ int output_scan_number; /* Nominal scan number being displayed */
+ JDIMENSION output_iMCU_row; /* Number of iMCU rows read */
+
+ /* Current progression status. coef_bits[c][i] indicates the precision
+ * with which component c's DCT coefficient i (in zigzag order) is known.
+ * It is -1 when no data has yet been received, otherwise it is the point
+ * transform (shift) value for the most recent scan of the coefficient
+ * (thus, 0 at completion of the progression).
+ * This pointer is NULL when reading a non-progressive file.
+ */
+ int (*coef_bits)[DCTSIZE2]; /* -1 or current Al value for each coef */
+
+ /* Internal JPEG parameters --- the application usually need not look at
+ * these fields. Note that the decompressor output side may not use
+ * any parameters that can change between scans.
+ */
+
+ /* Quantization and Huffman tables are carried forward across input
+ * datastreams when processing abbreviated JPEG datastreams.
+ */
+
+ JQUANT_TBL * quant_tbl_ptrs[NUM_QUANT_TBLS];
+ /* ptrs to coefficient quantization tables, or NULL if not defined */
+
+ JHUFF_TBL * dc_huff_tbl_ptrs[NUM_HUFF_TBLS];
+ JHUFF_TBL * ac_huff_tbl_ptrs[NUM_HUFF_TBLS];
+ /* ptrs to Huffman coding tables, or NULL if not defined */
+
+ /* These parameters are never carried across datastreams, since they
+ * are given in SOF/SOS markers or defined to be reset by SOI.
+ */
+
+ int data_precision; /* bits of precision in image data */
+
+ jpeg_component_info * comp_info;
+ /* comp_info[i] describes component that appears i'th in SOF */
+
+ boolean progressive_mode; /* TRUE if SOFn specifies progressive mode */
+ boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */
+
+ UINT8 arith_dc_L[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */
+ UINT8 arith_dc_U[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */
+ UINT8 arith_ac_K[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */
+
+ unsigned int restart_interval; /* MCUs per restart interval, or 0 for no restart */
+
+ /* These fields record data obtained from optional markers recognized by
+ * the JPEG library.
+ */
+ boolean saw_JFIF_marker; /* TRUE iff a JFIF APP0 marker was found */
+ /* Data copied from JFIF marker; only valid if saw_JFIF_marker is TRUE: */
+ UINT8 JFIF_major_version; /* JFIF version number */
+ UINT8 JFIF_minor_version;
+ UINT8 density_unit; /* JFIF code for pixel size units */
+ UINT16 X_density; /* Horizontal pixel density */
+ UINT16 Y_density; /* Vertical pixel density */
+ boolean saw_Adobe_marker; /* TRUE iff an Adobe APP14 marker was found */
+ UINT8 Adobe_transform; /* Color transform code from Adobe marker */
+
+ boolean CCIR601_sampling; /* TRUE=first samples are cosited */
+
+ /* Aside from the specific data retained from APPn markers known to the
+ * library, the uninterpreted contents of any or all APPn and COM markers
+ * can be saved in a list for examination by the application.
+ */
+ jpeg_saved_marker_ptr marker_list; /* Head of list of saved markers */
+
+ /* Remaining fields are known throughout decompressor, but generally
+ * should not be touched by a surrounding application.
+ */
+
+ /*
+ * These fields are computed during decompression startup
+ */
+ int max_h_samp_factor; /* largest h_samp_factor */
+ int max_v_samp_factor; /* largest v_samp_factor */
+
+ int min_DCT_scaled_size; /* smallest DCT_scaled_size of any component */
+
+ JDIMENSION total_iMCU_rows; /* # of iMCU rows in image */
+ /* The coefficient controller's input and output progress is measured in
+ * units of "iMCU" (interleaved MCU) rows. These are the same as MCU rows
+ * in fully interleaved JPEG scans, but are used whether the scan is
+ * interleaved or not. We define an iMCU row as v_samp_factor DCT block
+ * rows of each component. Therefore, the IDCT output contains
+ * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row.
+ */
+
+ JSAMPLE * sample_range_limit; /* table for fast range-limiting */
+
+ /*
+ * These fields are valid during any one scan.
+ * They describe the components and MCUs actually appearing in the scan.
+ * Note that the decompressor output side must not use these fields.
+ */
+ int comps_in_scan; /* # of JPEG components in this scan */
+ jpeg_component_info * cur_comp_info[MAX_COMPS_IN_SCAN];
+ /* *cur_comp_info[i] describes component that appears i'th in SOS */
+
+ JDIMENSION MCUs_per_row; /* # of MCUs across the image */
+ JDIMENSION MCU_rows_in_scan; /* # of MCU rows in the image */
+
+ int blocks_in_MCU; /* # of DCT blocks per MCU */
+ int MCU_membership[D_MAX_BLOCKS_IN_MCU];
+ /* MCU_membership[i] is index in cur_comp_info of component owning */
+ /* i'th block in an MCU */
+
+ int Ss, Se, Ah, Al; /* progressive JPEG parameters for scan */
+
+ /* This field is shared between entropy decoder and marker parser.
+ * It is either zero or the code of a JPEG marker that has been
+ * read from the data source, but has not yet been processed.
+ */
+ int unread_marker;
+
+ /*
+ * Links to decompression subobjects (methods, private variables of modules)
+ */
+ struct jpeg_decomp_master * master;
+ struct jpeg_d_main_controller * main;
+ struct jpeg_d_coef_controller * coef;
+ struct jpeg_d_post_controller * post;
+ struct jpeg_input_controller * inputctl;
+ struct jpeg_marker_reader * marker;
+ struct jpeg_entropy_decoder * entropy;
+ struct jpeg_inverse_dct * idct;
+ struct jpeg_upsampler * upsample;
+ struct jpeg_color_deconverter * cconvert;
+ struct jpeg_color_quantizer * cquantize;
+};
+
+
+/* "Object" declarations for JPEG modules that may be supplied or called
+ * directly by the surrounding application.
+ * As with all objects in the JPEG library, these structs only define the
+ * publicly visible methods and state variables of a module. Additional
+ * private fields may exist after the public ones.
+ */
+
+
+/* Error handler object */
+
+struct jpeg_error_mgr {
+ /* Error exit handler: does not return to caller */
+ JMETHOD(void, error_exit, (j_common_ptr cinfo));
+ /* Conditionally emit a trace or warning message */
+ JMETHOD(void, emit_message, (j_common_ptr cinfo, int msg_level));
+ /* Routine that actually outputs a trace or error message */
+ JMETHOD(void, output_message, (j_common_ptr cinfo));
+ /* Format a message string for the most recent JPEG error or message */
+ JMETHOD(void, format_message, (j_common_ptr cinfo, char * buffer));
+#define JMSG_LENGTH_MAX 200 /* recommended size of format_message buffer */
+ /* Reset error state variables at start of a new image */
+ JMETHOD(void, reset_error_mgr, (j_common_ptr cinfo));
+
+ /* The message ID code and any parameters are saved here.
+ * A message can have one string parameter or up to 8 int parameters.
+ */
+ int msg_code;
+#define JMSG_STR_PARM_MAX 80
+ union {
+ int i[8];
+ char s[JMSG_STR_PARM_MAX];
+ } msg_parm;
+
+ /* Standard state variables for error facility */
+
+ int trace_level; /* max msg_level that will be displayed */
+
+ /* For recoverable corrupt-data errors, we emit a warning message,
+ * but keep going unless emit_message chooses to abort. emit_message
+ * should count warnings in num_warnings. The surrounding application
+ * can check for bad data by seeing if num_warnings is nonzero at the
+ * end of processing.
+ */
+ long num_warnings; /* number of corrupt-data warnings */
+
+ /* These fields point to the table(s) of error message strings.
+ * An application can change the table pointer to switch to a different
+ * message list (typically, to change the language in which errors are
+ * reported). Some applications may wish to add additional error codes
+ * that will be handled by the JPEG library error mechanism; the second
+ * table pointer is used for this purpose.
+ *
+ * First table includes all errors generated by JPEG library itself.
+ * Error code 0 is reserved for a "no such error string" message.
+ */
+ const char * const * jpeg_message_table; /* Library errors */
+ int last_jpeg_message; /* Table contains strings 0..last_jpeg_message */
+ /* Second table can be added by application (see cjpeg/djpeg for example).
+ * It contains strings numbered first_addon_message..last_addon_message.
+ */
+ const char * const * addon_message_table; /* Non-library errors */
+ int first_addon_message; /* code for first string in addon table */
+ int last_addon_message; /* code for last string in addon table */
+};
+
+
+/* Progress monitor object */
+
+struct jpeg_progress_mgr {
+ JMETHOD(void, progress_monitor, (j_common_ptr cinfo));
+
+ long pass_counter; /* work units completed in this pass */
+ long pass_limit; /* total number of work units in this pass */
+ int completed_passes; /* passes completed so far */
+ int total_passes; /* total number of passes expected */
+};
+
+
+/* Data destination object for compression */
+
+struct jpeg_destination_mgr {
+ JOCTET * next_output_byte; /* => next byte to write in buffer */
+ size_t free_in_buffer; /* # of byte spaces remaining in buffer */
+
+ JMETHOD(void, init_destination, (j_compress_ptr cinfo));
+ JMETHOD(boolean, empty_output_buffer, (j_compress_ptr cinfo));
+ JMETHOD(void, term_destination, (j_compress_ptr cinfo));
+};
+
+
+/* Data source object for decompression */
+
+struct jpeg_source_mgr {
+ const JOCTET * next_input_byte; /* => next byte to read from buffer */
+ size_t bytes_in_buffer; /* # of bytes remaining in buffer */
+
+ JMETHOD(void, init_source, (j_decompress_ptr cinfo));
+ JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo));
+ JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes));
+ JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired));
+ JMETHOD(void, term_source, (j_decompress_ptr cinfo));
+};
+
+
+/* Memory manager object.
+ * Allocates "small" objects (a few K total), "large" objects (tens of K),
+ * and "really big" objects (virtual arrays with backing store if needed).
+ * The memory manager does not allow individual objects to be freed; rather,
+ * each created object is assigned to a pool, and whole pools can be freed
+ * at once. This is faster and more convenient than remembering exactly what
+ * to free, especially where malloc()/free() are not too speedy.
+ * NB: alloc routines never return NULL. They exit to error_exit if not
+ * successful.
+ */
+
+#define JPOOL_PERMANENT 0 /* lasts until master record is destroyed */
+#define JPOOL_IMAGE 1 /* lasts until done with image/datastream */
+#define JPOOL_NUMPOOLS 2
+
+typedef struct jvirt_sarray_control * jvirt_sarray_ptr;
+typedef struct jvirt_barray_control * jvirt_barray_ptr;
+
+
+struct jpeg_memory_mgr {
+ /* Method pointers */
+ JMETHOD(void *, alloc_small, (j_common_ptr cinfo, int pool_id,
+ size_t sizeofobject));
+ JMETHOD(void FAR *, alloc_large, (j_common_ptr cinfo, int pool_id,
+ size_t sizeofobject));
+ JMETHOD(JSAMPARRAY, alloc_sarray, (j_common_ptr cinfo, int pool_id,
+ JDIMENSION samplesperrow,
+ JDIMENSION numrows));
+ JMETHOD(JBLOCKARRAY, alloc_barray, (j_common_ptr cinfo, int pool_id,
+ JDIMENSION blocksperrow,
+ JDIMENSION numrows));
+ JMETHOD(jvirt_sarray_ptr, request_virt_sarray, (j_common_ptr cinfo,
+ int pool_id,
+ boolean pre_zero,
+ JDIMENSION samplesperrow,
+ JDIMENSION numrows,
+ JDIMENSION maxaccess));
+ JMETHOD(jvirt_barray_ptr, request_virt_barray, (j_common_ptr cinfo,
+ int pool_id,
+ boolean pre_zero,
+ JDIMENSION blocksperrow,
+ JDIMENSION numrows,
+ JDIMENSION maxaccess));
+ JMETHOD(void, realize_virt_arrays, (j_common_ptr cinfo));
+ JMETHOD(JSAMPARRAY, access_virt_sarray, (j_common_ptr cinfo,
+ jvirt_sarray_ptr ptr,
+ JDIMENSION start_row,
+ JDIMENSION num_rows,
+ boolean writable));
+ JMETHOD(JBLOCKARRAY, access_virt_barray, (j_common_ptr cinfo,
+ jvirt_barray_ptr ptr,
+ JDIMENSION start_row,
+ JDIMENSION num_rows,
+ boolean writable));
+ JMETHOD(void, free_pool, (j_common_ptr cinfo, int pool_id));
+ JMETHOD(void, self_destruct, (j_common_ptr cinfo));
+
+ /* Limit on memory allocation for this JPEG object. (Note that this is
+ * merely advisory, not a guaranteed maximum; it only affects the space
+ * used for virtual-array buffers.) May be changed by outer application
+ * after creating the JPEG object.
+ */
+ long max_memory_to_use;
+
+ /* Maximum allocation request accepted by alloc_large. */
+ long max_alloc_chunk;
+};
+
+
+/* Routine signature for application-supplied marker processing methods.
+ * Need not pass marker code since it is stored in cinfo->unread_marker.
+ */
+typedef JMETHOD(boolean, jpeg_marker_parser_method, (j_decompress_ptr cinfo));
+
+
+/* Declarations for routines called by application.
+ * The JPP macro hides prototype parameters from compilers that can't cope.
+ * Note JPP requires double parentheses.
+ */
+
+#ifdef HAVE_PROTOTYPES
+#define JPP(arglist) arglist
+#else
+#define JPP(arglist) ()
+#endif
+
+
+/* Short forms of external names for systems with brain-damaged linkers.
+ * We shorten external names to be unique in the first six letters, which
+ * is good enough for all known systems.
+ * (If your compiler itself needs names to be unique in less than 15
+ * characters, you are out of luck. Get a better compiler.)
+ */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jpeg_std_error jStdError
+#define jpeg_CreateCompress jCreaCompress
+#define jpeg_CreateDecompress jCreaDecompress
+#define jpeg_destroy_compress jDestCompress
+#define jpeg_destroy_decompress jDestDecompress
+#define jpeg_stdio_dest jStdDest
+#define jpeg_stdio_src jStdSrc
+#define jpeg_set_defaults jSetDefaults
+#define jpeg_set_colorspace jSetColorspace
+#define jpeg_default_colorspace jDefColorspace
+#define jpeg_set_quality jSetQuality
+#define jpeg_set_linear_quality jSetLQuality
+#define jpeg_add_quant_table jAddQuantTable
+#define jpeg_quality_scaling jQualityScaling
+#define jpeg_simple_progression jSimProgress
+#define jpeg_suppress_tables jSuppressTables
+#define jpeg_alloc_quant_table jAlcQTable
+#define jpeg_alloc_huff_table jAlcHTable
+#define jpeg_start_compress jStrtCompress
+#define jpeg_write_scanlines jWrtScanlines
+#define jpeg_finish_compress jFinCompress
+#define jpeg_write_raw_data jWrtRawData
+#define jpeg_write_marker jWrtMarker
+#define jpeg_write_m_header jWrtMHeader
+#define jpeg_write_m_byte jWrtMByte
+#define jpeg_write_tables jWrtTables
+#define jpeg_read_header jReadHeader
+#define jpeg_start_decompress jStrtDecompress
+#define jpeg_read_scanlines jReadScanlines
+#define jpeg_finish_decompress jFinDecompress
+#define jpeg_read_raw_data jReadRawData
+#define jpeg_has_multiple_scans jHasMultScn
+#define jpeg_start_output jStrtOutput
+#define jpeg_finish_output jFinOutput
+#define jpeg_input_complete jInComplete
+#define jpeg_new_colormap jNewCMap
+#define jpeg_consume_input jConsumeInput
+#define jpeg_calc_output_dimensions jCalcDimensions
+#define jpeg_save_markers jSaveMarkers
+#define jpeg_set_marker_processor jSetMarker
+#define jpeg_read_coefficients jReadCoefs
+#define jpeg_write_coefficients jWrtCoefs
+#define jpeg_copy_critical_parameters jCopyCrit
+#define jpeg_abort_compress jAbrtCompress
+#define jpeg_abort_decompress jAbrtDecompress
+#define jpeg_abort jAbort
+#define jpeg_destroy jDestroy
+#define jpeg_resync_to_restart jResyncRestart
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+
+/* Default error-management setup */
+EXTERN(struct jpeg_error_mgr *) jpeg_std_error
+ JPP((struct jpeg_error_mgr * err));
+
+/* Initialization of JPEG compression objects.
+ * jpeg_create_compress() and jpeg_create_decompress() are the exported
+ * names that applications should call. These expand to calls on
+ * jpeg_CreateCompress and jpeg_CreateDecompress with additional information
+ * passed for version mismatch checking.
+ * NB: you must set up the error-manager BEFORE calling jpeg_create_xxx.
+ */
+#define jpeg_create_compress(cinfo) \
+ jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \
+ (size_t) sizeof(struct jpeg_compress_struct))
+#define jpeg_create_decompress(cinfo) \
+ jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, \
+ (size_t) sizeof(struct jpeg_decompress_struct))
+EXTERN(void) jpeg_CreateCompress JPP((j_compress_ptr cinfo,
+ int version, size_t structsize));
+EXTERN(void) jpeg_CreateDecompress JPP((j_decompress_ptr cinfo,
+ int version, size_t structsize));
+/* Destruction of JPEG compression objects */
+EXTERN(void) jpeg_destroy_compress JPP((j_compress_ptr cinfo));
+EXTERN(void) jpeg_destroy_decompress JPP((j_decompress_ptr cinfo));
+
+/* Standard data source and destination managers: stdio streams. */
+/* Caller is responsible for opening the file before and closing after. */
+EXTERN(void) jpeg_stdio_dest JPP((j_compress_ptr cinfo, FILE * outfile));
+EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile));
+
+/* Default parameter setup for compression */
+EXTERN(void) jpeg_set_defaults JPP((j_compress_ptr cinfo));
+/* Compression parameter setup aids */
+EXTERN(void) jpeg_set_colorspace JPP((j_compress_ptr cinfo,
+ J_COLOR_SPACE colorspace));
+EXTERN(void) jpeg_default_colorspace JPP((j_compress_ptr cinfo));
+EXTERN(void) jpeg_set_quality JPP((j_compress_ptr cinfo, int quality,
+ boolean force_baseline));
+EXTERN(void) jpeg_set_linear_quality JPP((j_compress_ptr cinfo,
+ int scale_factor,
+ boolean force_baseline));
+EXTERN(void) jpeg_add_quant_table JPP((j_compress_ptr cinfo, int which_tbl,
+ const unsigned int *basic_table,
+ int scale_factor,
+ boolean force_baseline));
+EXTERN(int) jpeg_quality_scaling JPP((int quality));
+EXTERN(void) jpeg_simple_progression JPP((j_compress_ptr cinfo));
+EXTERN(void) jpeg_suppress_tables JPP((j_compress_ptr cinfo,
+ boolean suppress));
+EXTERN(JQUANT_TBL *) jpeg_alloc_quant_table JPP((j_common_ptr cinfo));
+EXTERN(JHUFF_TBL *) jpeg_alloc_huff_table JPP((j_common_ptr cinfo));
+
+/* Main entry points for compression */
+EXTERN(void) jpeg_start_compress JPP((j_compress_ptr cinfo,
+ boolean write_all_tables));
+EXTERN(JDIMENSION) jpeg_write_scanlines JPP((j_compress_ptr cinfo,
+ JSAMPARRAY scanlines,
+ JDIMENSION num_lines));
+EXTERN(void) jpeg_finish_compress JPP((j_compress_ptr cinfo));
+
+/* Replaces jpeg_write_scanlines when writing raw downsampled data. */
+EXTERN(JDIMENSION) jpeg_write_raw_data JPP((j_compress_ptr cinfo,
+ JSAMPIMAGE data,
+ JDIMENSION num_lines));
+
+/* Write a special marker. See libjpeg.doc concerning safe usage. */
+EXTERN(void) jpeg_write_marker
+ JPP((j_compress_ptr cinfo, int marker,
+ const JOCTET * dataptr, unsigned int datalen));
+/* Same, but piecemeal. */
+EXTERN(void) jpeg_write_m_header
+ JPP((j_compress_ptr cinfo, int marker, unsigned int datalen));
+EXTERN(void) jpeg_write_m_byte
+ JPP((j_compress_ptr cinfo, int val));
+
+/* Alternate compression function: just write an abbreviated table file */
+EXTERN(void) jpeg_write_tables JPP((j_compress_ptr cinfo));
+
+/* Decompression startup: read start of JPEG datastream to see what's there */
+EXTERN(int) jpeg_read_header JPP((j_decompress_ptr cinfo,
+ boolean require_image));
+/* Return value is one of: */
+#define JPEG_SUSPENDED 0 /* Suspended due to lack of input data */
+#define JPEG_HEADER_OK 1 /* Found valid image datastream */
+#define JPEG_HEADER_TABLES_ONLY 2 /* Found valid table-specs-only datastream */
+/* If you pass require_image = TRUE (normal case), you need not check for
+ * a TABLES_ONLY return code; an abbreviated file will cause an error exit.
+ * JPEG_SUSPENDED is only possible if you use a data source module that can
+ * give a suspension return (the stdio source module doesn't).
+ */
+
+/* Main entry points for decompression */
+EXTERN(boolean) jpeg_start_decompress JPP((j_decompress_ptr cinfo));
+EXTERN(JDIMENSION) jpeg_read_scanlines JPP((j_decompress_ptr cinfo,
+ JSAMPARRAY scanlines,
+ JDIMENSION max_lines));
+EXTERN(boolean) jpeg_finish_decompress JPP((j_decompress_ptr cinfo));
+
+/* Replaces jpeg_read_scanlines when reading raw downsampled data. */
+EXTERN(JDIMENSION) jpeg_read_raw_data JPP((j_decompress_ptr cinfo,
+ JSAMPIMAGE data,
+ JDIMENSION max_lines));
+
+/* Additional entry points for buffered-image mode. */
+EXTERN(boolean) jpeg_has_multiple_scans JPP((j_decompress_ptr cinfo));
+EXTERN(boolean) jpeg_start_output JPP((j_decompress_ptr cinfo,
+ int scan_number));
+EXTERN(boolean) jpeg_finish_output JPP((j_decompress_ptr cinfo));
+EXTERN(boolean) jpeg_input_complete JPP((j_decompress_ptr cinfo));
+EXTERN(void) jpeg_new_colormap JPP((j_decompress_ptr cinfo));
+EXTERN(int) jpeg_consume_input JPP((j_decompress_ptr cinfo));
+/* Return value is one of: */
+/* #define JPEG_SUSPENDED 0 Suspended due to lack of input data */
+#define JPEG_REACHED_SOS 1 /* Reached start of new scan */
+#define JPEG_REACHED_EOI 2 /* Reached end of image */
+#define JPEG_ROW_COMPLETED 3 /* Completed one iMCU row */
+#define JPEG_SCAN_COMPLETED 4 /* Completed last iMCU row of a scan */
+
+/* Precalculate output dimensions for current decompression parameters. */
+EXTERN(void) jpeg_calc_output_dimensions JPP((j_decompress_ptr cinfo));
+
+/* Control saving of COM and APPn markers into marker_list. */
+EXTERN(void) jpeg_save_markers
+ JPP((j_decompress_ptr cinfo, int marker_code,
+ unsigned int length_limit));
+
+/* Install a special processing method for COM or APPn markers. */
+EXTERN(void) jpeg_set_marker_processor
+ JPP((j_decompress_ptr cinfo, int marker_code,
+ jpeg_marker_parser_method routine));
+
+/* Read or write raw DCT coefficients --- useful for lossless transcoding. */
+EXTERN(jvirt_barray_ptr *) jpeg_read_coefficients JPP((j_decompress_ptr cinfo));
+EXTERN(void) jpeg_write_coefficients JPP((j_compress_ptr cinfo,
+ jvirt_barray_ptr * coef_arrays));
+EXTERN(void) jpeg_copy_critical_parameters JPP((j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo));
+
+/* If you choose to abort compression or decompression before completing
+ * jpeg_finish_(de)compress, then you need to clean up to release memory,
+ * temporary files, etc. You can just call jpeg_destroy_(de)compress
+ * if you're done with the JPEG object, but if you want to clean it up and
+ * reuse it, call this:
+ */
+EXTERN(void) jpeg_abort_compress JPP((j_compress_ptr cinfo));
+EXTERN(void) jpeg_abort_decompress JPP((j_decompress_ptr cinfo));
+
+/* Generic versions of jpeg_abort and jpeg_destroy that work on either
+ * flavor of JPEG object. These may be more convenient in some places.
+ */
+EXTERN(void) jpeg_abort JPP((j_common_ptr cinfo));
+EXTERN(void) jpeg_destroy JPP((j_common_ptr cinfo));
+
+/* Default restart-marker-resync procedure for use by data source modules */
+EXTERN(boolean) jpeg_resync_to_restart JPP((j_decompress_ptr cinfo,
+ int desired));
+
+
+/* These marker codes are exported since applications and data source modules
+ * are likely to want to use them.
+ */
+
+#define JPEG_RST0 0xD0 /* RST0 marker code */
+#define JPEG_EOI 0xD9 /* EOI marker code */
+#define JPEG_APP0 0xE0 /* APP0 marker code */
+#define JPEG_COM 0xFE /* COM marker code */
+
+
+/* If we have a brain-damaged compiler that emits warnings (or worse, errors)
+ * for structure definitions that are never filled in, keep it quiet by
+ * supplying dummy definitions for the various substructures.
+ */
+
+#ifdef INCOMPLETE_TYPES_BROKEN
+#ifndef JPEG_INTERNALS /* will be defined in jpegint.h */
+struct jvirt_sarray_control { long dummy; };
+struct jvirt_barray_control { long dummy; };
+struct jpeg_comp_master { long dummy; };
+struct jpeg_c_main_controller { long dummy; };
+struct jpeg_c_prep_controller { long dummy; };
+struct jpeg_c_coef_controller { long dummy; };
+struct jpeg_marker_writer { long dummy; };
+struct jpeg_color_converter { long dummy; };
+struct jpeg_downsampler { long dummy; };
+struct jpeg_forward_dct { long dummy; };
+struct jpeg_entropy_encoder { long dummy; };
+struct jpeg_decomp_master { long dummy; };
+struct jpeg_d_main_controller { long dummy; };
+struct jpeg_d_coef_controller { long dummy; };
+struct jpeg_d_post_controller { long dummy; };
+struct jpeg_input_controller { long dummy; };
+struct jpeg_marker_reader { long dummy; };
+struct jpeg_entropy_decoder { long dummy; };
+struct jpeg_inverse_dct { long dummy; };
+struct jpeg_upsampler { long dummy; };
+struct jpeg_color_deconverter { long dummy; };
+struct jpeg_color_quantizer { long dummy; };
+#endif /* JPEG_INTERNALS */
+#endif /* INCOMPLETE_TYPES_BROKEN */
+
+
+/*
+ * The JPEG library modules define JPEG_INTERNALS before including this file.
+ * The internal structure declarations are read only when that is true.
+ * Applications using the library should not include jpegint.h, but may wish
+ * to include jerror.h.
+ */
+
+#ifdef JPEG_INTERNALS
+#include "jpegint.h" /* fetch private declarations */
+#include "jerror.h" /* fetch error codes too */
+#endif
+
+#endif /* JPEGLIB_H */
diff --git a/jpeg/transupp.c b/jpeg/transupp.c
new file mode 100644
index 0000000..e5ec564
--- /dev/null
+++ b/jpeg/transupp.c
@@ -0,0 +1,928 @@
+/*
+ * transupp.c
+ *
+ * Copyright (C) 1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains image transformation routines and other utility code
+ * used by the jpegtran sample application. These are NOT part of the core
+ * JPEG library. But we keep these routines separate from jpegtran.c to
+ * ease the task of maintaining jpegtran-like programs that have other user
+ * interfaces.
+ */
+
+/* Although this file really shouldn't have access to the library internals,
+ * it's helpful to let it call jround_up() and jcopy_block_row().
+ */
+#define JPEG_INTERNALS
+
+#include "jinclude.h"
+#include "jpeglib.h"
+#include "transupp.h" /* My own external interface */
+
+
+#if TRANSFORMS_SUPPORTED
+
+/*
+ * Lossless image transformation routines. These routines work on DCT
+ * coefficient arrays and thus do not require any lossy decompression
+ * or recompression of the image.
+ * Thanks to Guido Vollbeding for the initial design and code of this feature.
+ *
+ * Horizontal flipping is done in-place, using a single top-to-bottom
+ * pass through the virtual source array. It will thus be much the
+ * fastest option for images larger than main memory.
+ *
+ * The other routines require a set of destination virtual arrays, so they
+ * need twice as much memory as jpegtran normally does. The destination
+ * arrays are always written in normal scan order (top to bottom) because
+ * the virtual array manager expects this. The source arrays will be scanned
+ * in the corresponding order, which means multiple passes through the source
+ * arrays for most of the transforms. That could result in much thrashing
+ * if the image is larger than main memory.
+ *
+ * Some notes about the operating environment of the individual transform
+ * routines:
+ * 1. Both the source and destination virtual arrays are allocated from the
+ * source JPEG object, and therefore should be manipulated by calling the
+ * source's memory manager.
+ * 2. The destination's component count should be used. It may be smaller
+ * than the source's when forcing to grayscale.
+ * 3. Likewise the destination's sampling factors should be used. When
+ * forcing to grayscale the destination's sampling factors will be all 1,
+ * and we may as well take that as the effective iMCU size.
+ * 4. When "trim" is in effect, the destination's dimensions will be the
+ * trimmed values but the source's will be untrimmed.
+ * 5. All the routines assume that the source and destination buffers are
+ * padded out to a full iMCU boundary. This is true, although for the
+ * source buffer it is an undocumented property of jdcoefct.c.
+ * Notes 2,3,4 boil down to this: generally we should use the destination's
+ * dimensions and ignore the source's.
+ */
+
+
+LOCAL(void)
+do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays)
+/* Horizontal flip; done in-place, so no separate dest array is required */
+{
+ JDIMENSION MCU_cols, comp_width, blk_x, blk_y;
+ int ci, k, offset_y;
+ JBLOCKARRAY buffer;
+ JCOEFPTR ptr1, ptr2;
+ JCOEF temp1, temp2;
+ jpeg_component_info *compptr;
+
+ /* Horizontal mirroring of DCT blocks is accomplished by swapping
+ * pairs of blocks in-place. Within a DCT block, we perform horizontal
+ * mirroring by changing the signs of odd-numbered columns.
+ * Partial iMCUs at the right edge are left untouched.
+ */
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ for (blk_y = 0; blk_y < compptr->height_in_blocks;
+ blk_y += compptr->v_samp_factor) {
+ buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) {
+ ptr1 = buffer[offset_y][blk_x];
+ ptr2 = buffer[offset_y][comp_width - blk_x - 1];
+ /* this unrolled loop doesn't need to know which row it's on... */
+ for (k = 0; k < DCTSIZE2; k += 2) {
+ temp1 = *ptr1; /* swap even column */
+ temp2 = *ptr2;
+ *ptr1++ = temp2;
+ *ptr2++ = temp1;
+ temp1 = *ptr1; /* swap odd column with sign change */
+ temp2 = *ptr2;
+ *ptr1++ = -temp2;
+ *ptr2++ = -temp1;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Vertical flip */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* We output into a separate array because we can't touch different
+ * rows of the source virtual array simultaneously. Otherwise, this
+ * is a pretty straightforward analog of horizontal flip.
+ * Within a DCT block, vertical mirroring is done by changing the signs
+ * of odd-numbered rows.
+ * Partial iMCUs at the bottom edge are copied verbatim.
+ */
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge blocks will be copied verbatim. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ if (dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* copy even row */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ /* copy odd row with sign change */
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ } else {
+ /* Just copy row verbatim. */
+ jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y],
+ compptr->width_in_blocks);
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transpose source into destination */
+{
+ JDIMENSION dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Transposing pixels within a block just requires transposing the
+ * DCT coefficients.
+ * Partial iMCUs at the edges require no special treatment; we simply
+ * process all the available DCT blocks for every component.
+ */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 90 degree rotation is equivalent to
+ * 1. Transposing the image;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) right edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ if (dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ dst_ptr = dst_buffer[offset_y]
+ [comp_width - dst_blk_x - offset_x - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 270 degree rotation is equivalent to
+ * 1. Horizontal mirroring;
+ * 2. Transposing the image.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ /* Because of the horizontal mirror step, we can't process partial iMCUs
+ * at the (output) bottom edge properly. They just get transposed and
+ * not mirrored.
+ */
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ if (dst_blk_y < comp_height) {
+ /* Block is within the mirrorable area. */
+ src_ptr = src_buffer[offset_x]
+ [comp_height - dst_blk_y - offset_y - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Edge blocks are transposed but not mirrored. */
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* 180 degree rotation is equivalent to
+ * 1. Vertical mirroring;
+ * 2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JBLOCKROW src_row_ptr, dst_row_ptr;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ if (dst_blk_y < comp_height) {
+ /* Row is within the vertically mirrorable area. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+ comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ } else {
+ /* Bottom-edge rows are only mirrored horizontally. */
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, FALSE);
+ }
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ if (dst_blk_y < comp_height) {
+ /* Row is within the mirrorable area. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+ /* Process the blocks that can be mirrored both ways. */
+ for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ /* For even row, negate every odd column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ /* For odd row, negate every even column. */
+ for (j = 0; j < DCTSIZE; j += 2) {
+ *dst_ptr++ = - *src_ptr++;
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ }
+ /* Any remaining right-edge blocks are only mirrored vertically. */
+ for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE; i += 2) {
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = *src_ptr++;
+ for (j = 0; j < DCTSIZE; j++)
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ } else {
+ /* Remaining rows are just mirrored horizontally. */
+ dst_row_ptr = dst_buffer[offset_y];
+ src_row_ptr = src_buffer[offset_y];
+ /* Process the blocks that can be mirrored. */
+ for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
+ for (i = 0; i < DCTSIZE2; i += 2) {
+ *dst_ptr++ = *src_ptr++;
+ *dst_ptr++ = - *src_ptr++;
+ }
+ }
+ /* Any remaining right-edge blocks are only copied. */
+ for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+ dst_ptr = dst_row_ptr[dst_blk_x];
+ src_ptr = src_row_ptr[dst_blk_x];
+ for (i = 0; i < DCTSIZE2; i++)
+ *dst_ptr++ = *src_ptr++;
+ }
+ }
+ }
+ }
+ }
+}
+
+
+LOCAL(void)
+do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jvirt_barray_ptr *dst_coef_arrays)
+/* Transverse transpose is equivalent to
+ * 1. 180 degree rotation;
+ * 2. Transposition;
+ * or
+ * 1. Horizontal mirroring;
+ * 2. Transposition;
+ * 3. Horizontal mirroring.
+ * These steps are merged into a single processing routine.
+ */
+{
+ JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+ int ci, i, j, offset_x, offset_y;
+ JBLOCKARRAY src_buffer, dst_buffer;
+ JCOEFPTR src_ptr, dst_ptr;
+ jpeg_component_info *compptr;
+
+ MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+ MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ comp_width = MCU_cols * compptr->h_samp_factor;
+ comp_height = MCU_rows * compptr->v_samp_factor;
+ for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+ dst_blk_y += compptr->v_samp_factor) {
+ dst_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+ (JDIMENSION) compptr->v_samp_factor, TRUE);
+ for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+ for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+ dst_blk_x += compptr->h_samp_factor) {
+ src_buffer = (*srcinfo->mem->access_virt_barray)
+ ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+ (JDIMENSION) compptr->h_samp_factor, FALSE);
+ for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+ if (dst_blk_y < comp_height) {
+ src_ptr = src_buffer[offset_x]
+ [comp_height - dst_blk_y - offset_y - 1];
+ if (dst_blk_x < comp_width) {
+ /* Block is within the mirrorable area. */
+ dst_ptr = dst_buffer[offset_y]
+ [comp_width - dst_blk_x - offset_x - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ i++;
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ } else {
+ /* Right-edge blocks are mirrored in y only */
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++) {
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ j++;
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ } else {
+ src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+ if (dst_blk_x < comp_width) {
+ /* Bottom-edge blocks are mirrored in x only */
+ dst_ptr = dst_buffer[offset_y]
+ [comp_width - dst_blk_x - offset_x - 1];
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ i++;
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+ }
+ } else {
+ /* At lower right corner, just transpose, no mirroring */
+ dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+ for (i = 0; i < DCTSIZE; i++)
+ for (j = 0; j < DCTSIZE; j++)
+ dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/* Request any required workspace.
+ *
+ * We allocate the workspace virtual arrays from the source decompression
+ * object, so that all the arrays (both the original data and the workspace)
+ * will be taken into account while making memory management decisions.
+ * Hence, this routine must be called after jpeg_read_header (which reads
+ * the image dimensions) and before jpeg_read_coefficients (which realizes
+ * the source's virtual arrays).
+ */
+
+GLOBAL(void)
+jtransform_request_workspace (j_decompress_ptr srcinfo,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *coef_arrays = NULL;
+ jpeg_component_info *compptr;
+ int ci;
+
+ if (info->force_grayscale &&
+ srcinfo->jpeg_color_space == JCS_YCbCr &&
+ srcinfo->num_components == 3) {
+ /* We'll only process the first component */
+ info->num_components = 1;
+ } else {
+ /* Process all the components */
+ info->num_components = srcinfo->num_components;
+ }
+
+ switch (info->transform) {
+ case JXFORM_NONE:
+ case JXFORM_FLIP_H:
+ /* Don't need a workspace array */
+ break;
+ case JXFORM_FLIP_V:
+ case JXFORM_ROT_180:
+ /* Need workspace arrays having same dimensions as source image.
+ * Note that we allocate arrays padded out to the next iMCU boundary,
+ * so that transform routines need not worry about missing edge blocks.
+ */
+ coef_arrays = (jvirt_barray_ptr *)
+ (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+ SIZEOF(jvirt_barray_ptr) * info->num_components);
+ for (ci = 0; ci < info->num_components; ci++) {
+ compptr = srcinfo->comp_info + ci;
+ coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+ ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
+ (JDIMENSION) jround_up((long) compptr->width_in_blocks,
+ (long) compptr->h_samp_factor),
+ (JDIMENSION) jround_up((long) compptr->height_in_blocks,
+ (long) compptr->v_samp_factor),
+ (JDIMENSION) compptr->v_samp_factor);
+ }
+ break;
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+ /* Need workspace arrays having transposed dimensions.
+ * Note that we allocate arrays padded out to the next iMCU boundary,
+ * so that transform routines need not worry about missing edge blocks.
+ */
+ coef_arrays = (jvirt_barray_ptr *)
+ (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+ SIZEOF(jvirt_barray_ptr) * info->num_components);
+ for (ci = 0; ci < info->num_components; ci++) {
+ compptr = srcinfo->comp_info + ci;
+ coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+ ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
+ (JDIMENSION) jround_up((long) compptr->height_in_blocks,
+ (long) compptr->v_samp_factor),
+ (JDIMENSION) jround_up((long) compptr->width_in_blocks,
+ (long) compptr->h_samp_factor),
+ (JDIMENSION) compptr->h_samp_factor);
+ }
+ break;
+ }
+ info->workspace_coef_arrays = coef_arrays;
+}
+
+
+/* Transpose destination image parameters */
+
+LOCAL(void)
+transpose_critical_parameters (j_compress_ptr dstinfo)
+{
+ int tblno, i, j, ci, itemp;
+ jpeg_component_info *compptr;
+ JQUANT_TBL *qtblptr;
+ JDIMENSION dtemp;
+ UINT16 qtemp;
+
+ /* Transpose basic image dimensions */
+ dtemp = dstinfo->image_width;
+ dstinfo->image_width = dstinfo->image_height;
+ dstinfo->image_height = dtemp;
+
+ /* Transpose sampling factors */
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ compptr = dstinfo->comp_info + ci;
+ itemp = compptr->h_samp_factor;
+ compptr->h_samp_factor = compptr->v_samp_factor;
+ compptr->v_samp_factor = itemp;
+ }
+
+ /* Transpose quantization tables */
+ for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) {
+ qtblptr = dstinfo->quant_tbl_ptrs[tblno];
+ if (qtblptr != NULL) {
+ for (i = 0; i < DCTSIZE; i++) {
+ for (j = 0; j < i; j++) {
+ qtemp = qtblptr->quantval[i*DCTSIZE+j];
+ qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i];
+ qtblptr->quantval[j*DCTSIZE+i] = qtemp;
+ }
+ }
+ }
+ }
+}
+
+
+/* Trim off any partial iMCUs on the indicated destination edge */
+
+LOCAL(void)
+trim_right_edge (j_compress_ptr dstinfo)
+{
+ int ci, max_h_samp_factor;
+ JDIMENSION MCU_cols;
+
+ /* We have to compute max_h_samp_factor ourselves,
+ * because it hasn't been set yet in the destination
+ * (and we don't want to use the source's value).
+ */
+ max_h_samp_factor = 1;
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor;
+ max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor);
+ }
+ MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE);
+ if (MCU_cols > 0) /* can't trim to 0 pixels */
+ dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE);
+}
+
+LOCAL(void)
+trim_bottom_edge (j_compress_ptr dstinfo)
+{
+ int ci, max_v_samp_factor;
+ JDIMENSION MCU_rows;
+
+ /* We have to compute max_v_samp_factor ourselves,
+ * because it hasn't been set yet in the destination
+ * (and we don't want to use the source's value).
+ */
+ max_v_samp_factor = 1;
+ for (ci = 0; ci < dstinfo->num_components; ci++) {
+ int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor;
+ max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor);
+ }
+ MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE);
+ if (MCU_rows > 0) /* can't trim to 0 pixels */
+ dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE);
+}
+
+
+/* Adjust output image parameters as needed.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ *
+ * The return value is the set of virtual coefficient arrays to be written
+ * (either the ones allocated by jtransform_request_workspace, or the
+ * original source data arrays). The caller will need to pass this value
+ * to jpeg_write_coefficients().
+ */
+
+GLOBAL(jvirt_barray_ptr *)
+jtransform_adjust_parameters (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ /* If force-to-grayscale is requested, adjust destination parameters */
+ if (info->force_grayscale) {
+ /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
+ * properly. Among other things, the target h_samp_factor & v_samp_factor
+ * will get set to 1, which typically won't match the source.
+ * In fact we do this even if the source is already grayscale; that
+ * provides an easy way of coercing a grayscale JPEG with funny sampling
+ * factors to the customary 1,1. (Some decoders fail on other factors.)
+ */
+ if ((dstinfo->jpeg_color_space == JCS_YCbCr &&
+ dstinfo->num_components == 3) ||
+ (dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
+ dstinfo->num_components == 1)) {
+ /* We have to preserve the source's quantization table number. */
+ int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
+ jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
+ dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
+ } else {
+ /* Sorry, can't do it */
+ ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
+ }
+ }
+
+ /* Correct the destination's image dimensions etc if necessary */
+ switch (info->transform) {
+ case JXFORM_NONE:
+ /* Nothing to do */
+ break;
+ case JXFORM_FLIP_H:
+ if (info->trim)
+ trim_right_edge(dstinfo);
+ break;
+ case JXFORM_FLIP_V:
+ if (info->trim)
+ trim_bottom_edge(dstinfo);
+ break;
+ case JXFORM_TRANSPOSE:
+ transpose_critical_parameters(dstinfo);
+ /* transpose does NOT have to trim anything */
+ break;
+ case JXFORM_TRANSVERSE:
+ transpose_critical_parameters(dstinfo);
+ if (info->trim) {
+ trim_right_edge(dstinfo);
+ trim_bottom_edge(dstinfo);
+ }
+ break;
+ case JXFORM_ROT_90:
+ transpose_critical_parameters(dstinfo);
+ if (info->trim)
+ trim_right_edge(dstinfo);
+ break;
+ case JXFORM_ROT_180:
+ if (info->trim) {
+ trim_right_edge(dstinfo);
+ trim_bottom_edge(dstinfo);
+ }
+ break;
+ case JXFORM_ROT_270:
+ transpose_critical_parameters(dstinfo);
+ if (info->trim)
+ trim_bottom_edge(dstinfo);
+ break;
+ }
+
+ /* Return the appropriate output data set */
+ if (info->workspace_coef_arrays != NULL)
+ return info->workspace_coef_arrays;
+ return src_coef_arrays;
+}
+
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+GLOBAL(void)
+jtransform_execute_transformation (j_decompress_ptr srcinfo,
+ j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info)
+{
+ jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;
+
+ switch (info->transform) {
+ case JXFORM_NONE:
+ break;
+ case JXFORM_FLIP_H:
+ do_flip_h(srcinfo, dstinfo, src_coef_arrays);
+ break;
+ case JXFORM_FLIP_V:
+ do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSPOSE:
+ do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_TRANSVERSE:
+ do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_90:
+ do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_180:
+ do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ case JXFORM_ROT_270:
+ do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+ break;
+ }
+}
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/* Setup decompression object to save desired markers in memory.
+ * This must be called before jpeg_read_header() to have the desired effect.
+ */
+
+GLOBAL(void)
+jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
+{
+#ifdef SAVE_MARKERS_SUPPORTED
+ int m;
+
+ /* Save comments except under NONE option */
+ if (option != JCOPYOPT_NONE) {
+ jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
+ }
+ /* Save all types of APPn markers iff ALL option */
+ if (option == JCOPYOPT_ALL) {
+ for (m = 0; m < 16; m++)
+ jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
+ }
+#endif /* SAVE_MARKERS_SUPPORTED */
+}
+
+/* Copy markers saved in the given source object to the destination object.
+ * This should be called just after jpeg_start_compress() or
+ * jpeg_write_coefficients().
+ * Note that those routines will have written the SOI, and also the
+ * JFIF APP0 or Adobe APP14 markers if selected.
+ */
+
+GLOBAL(void)
+jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option)
+{
+ jpeg_saved_marker_ptr marker;
+
+ /* In the current implementation, we don't actually need to examine the
+ * option flag here; we just copy everything that got saved.
+ * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
+ * if the encoder library already wrote one.
+ */
+ for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
+ if (dstinfo->write_JFIF_header &&
+ marker->marker == JPEG_APP0 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x4A &&
+ GETJOCTET(marker->data[1]) == 0x46 &&
+ GETJOCTET(marker->data[2]) == 0x49 &&
+ GETJOCTET(marker->data[3]) == 0x46 &&
+ GETJOCTET(marker->data[4]) == 0)
+ continue; /* reject duplicate JFIF */
+ if (dstinfo->write_Adobe_marker &&
+ marker->marker == JPEG_APP0+14 &&
+ marker->data_length >= 5 &&
+ GETJOCTET(marker->data[0]) == 0x41 &&
+ GETJOCTET(marker->data[1]) == 0x64 &&
+ GETJOCTET(marker->data[2]) == 0x6F &&
+ GETJOCTET(marker->data[3]) == 0x62 &&
+ GETJOCTET(marker->data[4]) == 0x65)
+ continue; /* reject duplicate Adobe */
+#ifdef NEED_FAR_POINTERS
+ /* We could use jpeg_write_marker if the data weren't FAR... */
+ {
+ unsigned int i;
+ jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
+ for (i = 0; i < marker->data_length; i++)
+ jpeg_write_m_byte(dstinfo, marker->data[i]);
+ }
+#else
+ jpeg_write_marker(dstinfo, marker->marker,
+ marker->data, marker->data_length);
+#endif
+ }
+}
diff --git a/jpeg/transupp.h b/jpeg/transupp.h
new file mode 100644
index 0000000..5c2d32a
--- /dev/null
+++ b/jpeg/transupp.h
@@ -0,0 +1,135 @@
+/*
+ * transupp.h
+ *
+ * Copyright (C) 1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains declarations for image transformation routines and
+ * other utility code used by the jpegtran sample application. These are
+ * NOT part of the core JPEG library. But we keep these routines separate
+ * from jpegtran.c to ease the task of maintaining jpegtran-like programs
+ * that have other user interfaces.
+ *
+ * NOTE: all the routines declared here have very specific requirements
+ * about when they are to be executed during the reading and writing of the
+ * source and destination files. See the comments in transupp.c, or see
+ * jpegtran.c for an example of correct usage.
+ */
+
+/* If you happen not to want the image transform support, disable it here */
+#ifndef TRANSFORMS_SUPPORTED
+#define TRANSFORMS_SUPPORTED 1 /* 0 disables transform code */
+#endif
+
+/* Short forms of external names for systems with brain-damaged linkers. */
+
+#ifdef NEED_SHORT_EXTERNAL_NAMES
+#define jtransform_request_workspace jTrRequest
+#define jtransform_adjust_parameters jTrAdjust
+#define jtransform_execute_transformation jTrExec
+#define jcopy_markers_setup jCMrkSetup
+#define jcopy_markers_execute jCMrkExec
+#endif /* NEED_SHORT_EXTERNAL_NAMES */
+
+
+/*
+ * Codes for supported types of image transformations.
+ */
+
+typedef enum {
+ JXFORM_NONE, /* no transformation */
+ JXFORM_FLIP_H, /* horizontal flip */
+ JXFORM_FLIP_V, /* vertical flip */
+ JXFORM_TRANSPOSE, /* transpose across UL-to-LR axis */
+ JXFORM_TRANSVERSE, /* transpose across UR-to-LL axis */
+ JXFORM_ROT_90, /* 90-degree clockwise rotation */
+ JXFORM_ROT_180, /* 180-degree rotation */
+ JXFORM_ROT_270 /* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+/*
+ * Although rotating and flipping data expressed as DCT coefficients is not
+ * hard, there is an asymmetry in the JPEG format specification for images
+ * whose dimensions aren't multiples of the iMCU size. The right and bottom
+ * image edges are padded out to the next iMCU boundary with junk data; but
+ * no padding is possible at the top and left edges. If we were to flip
+ * the whole image including the pad data, then pad garbage would become
+ * visible at the top and/or left, and real pixels would disappear into the
+ * pad margins --- perhaps permanently, since encoders & decoders may not
+ * bother to preserve DCT blocks that appear to be completely outside the
+ * nominal image area. So, we have to exclude any partial iMCUs from the
+ * basic transformation.
+ *
+ * Transpose is the only transformation that can handle partial iMCUs at the
+ * right and bottom edges completely cleanly. flip_h can flip partial iMCUs
+ * at the bottom, but leaves any partial iMCUs at the right edge untouched.
+ * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched.
+ * The other transforms are defined as combinations of these basic transforms
+ * and process edge blocks in a way that preserves the equivalence.
+ *
+ * The "trim" option causes untransformable partial iMCUs to be dropped;
+ * this is not strictly lossless, but it usually gives the best-looking
+ * result for odd-size images. Note that when this option is active,
+ * the expected mathematical equivalences between the transforms may not hold.
+ * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim
+ * followed by -rot 180 -trim trims both edges.)
+ *
+ * We also offer a "force to grayscale" option, which simply discards the
+ * chrominance channels of a YCbCr image. This is lossless in the sense that
+ * the luminance channel is preserved exactly. It's not the same kind of
+ * thing as the rotate/flip transformations, but it's convenient to handle it
+ * as part of this package, mainly because the transformation routines have to
+ * be aware of the option to know how many components to work on.
+ */
+
+typedef struct {
+ /* Options: set by caller */
+ JXFORM_CODE transform; /* image transform operator */
+ boolean trim; /* if TRUE, trim partial MCUs as needed */
+ boolean force_grayscale; /* if TRUE, convert color image to grayscale */
+
+ /* Internal workspace: caller should not touch these */
+ int num_components; /* # of components in workspace */
+ jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
+} jpeg_transform_info;
+
+
+#if TRANSFORMS_SUPPORTED
+
+/* Request any required workspace */
+EXTERN(void) jtransform_request_workspace
+ JPP((j_decompress_ptr srcinfo, jpeg_transform_info *info));
+/* Adjust output image parameters */
+EXTERN(jvirt_barray_ptr *) jtransform_adjust_parameters
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info));
+/* Execute the actual transformation, if any */
+EXTERN(void) jtransform_execute_transformation
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ jvirt_barray_ptr *src_coef_arrays,
+ jpeg_transform_info *info));
+
+#endif /* TRANSFORMS_SUPPORTED */
+
+
+/*
+ * Support for copying optional markers from source to destination file.
+ */
+
+typedef enum {
+ JCOPYOPT_NONE, /* copy no optional markers */
+ JCOPYOPT_COMMENTS, /* copy only comment (COM) markers */
+ JCOPYOPT_ALL /* copy all optional markers */
+} JCOPY_OPTION;
+
+#define JCOPYOPT_DEFAULT JCOPYOPT_COMMENTS /* recommended default */
+
+/* Setup decompression object to save desired markers in memory */
+EXTERN(void) jcopy_markers_setup
+ JPP((j_decompress_ptr srcinfo, JCOPY_OPTION option));
+/* Copy markers saved in the given source object to the destination object */
+EXTERN(void) jcopy_markers_execute
+ JPP((j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+ JCOPY_OPTION option));
diff --git a/jpegtools.c b/jpegtools.c
new file mode 100644
index 0000000..90ac7dd
--- /dev/null
+++ b/jpegtools.c
@@ -0,0 +1,621 @@
+/*
+ * jpegtran.c
+ *
+ * Copyright (C) 1995-1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * plenty of changes by Gerd Knorr <kraxel@bytesex.org>, with focus on
+ * digital image processing and sane exif handling:
+ *
+ * - does transformations only (flip/rotate/transpose/transverse).
+ * - also transforms the exif thumbnail if present.
+ * - can automatically figure transformation from the
+ * exif orientation tag.
+ * - updates the exif orientation tag.
+ * - updates the exif pixel dimension tags.
+ *
+ * This file contains a command-line user interface for JPEG transcoding.
+ * It is very similar to cjpeg.c, but provides lossless transcoding between
+ * different JPEG file formats. It also provides some lossless and sort-of-
+ * lossless transformations of JPEG data.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <utime.h>
+#include <setjmp.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <jpeglib.h>
+#include "jpeg/transupp.h" /* Support routines for jpegtran */
+#include "jpegtools.h"
+
+#include "misc.h"
+
+#include <libexif/exif-data.h>
+#include <libexif/exif-utils.h>
+#include <libexif/exif-ifd.h>
+#include <libexif/exif-tag.h>
+
+static int do_transform(struct jpeg_decompress_struct *src,
+ struct jpeg_compress_struct *dst,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags);
+
+static JXFORM_CODE transmagic[] = {
+ [ 1 ] = JXFORM_NONE,
+ [ 2 ] = JXFORM_FLIP_H,
+ [ 3 ] = JXFORM_ROT_180,
+ [ 4 ] = JXFORM_FLIP_V,
+ [ 5 ] = JXFORM_TRANSPOSE,
+ [ 6 ] = JXFORM_ROT_90,
+ [ 7 ] = JXFORM_TRANSVERSE,
+ [ 8 ] = JXFORM_ROT_270,
+};
+
+#if 0
+static char *transname[] = {
+ [ JXFORM_NONE ] = "none",
+ [ JXFORM_FLIP_H ] = "flip h",
+ [ JXFORM_FLIP_V ] = "flip v",
+ [ JXFORM_TRANSPOSE ] = "transpose",
+ [ JXFORM_TRANSVERSE ] = "transverse",
+ [ JXFORM_ROT_90 ] = "rot 90",
+ [ JXFORM_ROT_180 ] = "rot 190",
+ [ JXFORM_ROT_270 ] = "rot 270",
+};
+#endif
+
+/* ---------------------------------------------------------------------- */
+
+/* libjpeg error handler -- exit via longjump */
+struct longjmp_error_mgr {
+ struct jpeg_error_mgr jpeg;
+ jmp_buf setjmp_buffer;
+};
+
+static void longjmp_error_exit(j_common_ptr cinfo)
+{
+ struct longjmp_error_mgr *h = (struct longjmp_error_mgr*)cinfo->err;
+ (*cinfo->err->output_message)(cinfo);
+ longjmp(h->setjmp_buffer, 1);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static long get_int(ExifData *ed, ExifEntry *ee)
+{
+ ExifByteOrder o = exif_data_get_byte_order(ed);
+ long value;
+
+ switch (ee->format) {
+ case EXIF_FORMAT_SHORT:
+ value = exif_get_short (ee->data, o);
+ break;
+ case EXIF_FORMAT_LONG:
+ value = exif_get_long (ee->data, o);
+ break;
+ case EXIF_FORMAT_SLONG:
+ value = exif_get_slong (ee->data, o);
+ break;
+ default:
+ fprintf(stderr,"get_int oops\n");
+ exit(1);
+ }
+ return value;
+}
+
+static void set_int(ExifData *ed, ExifEntry *ee, long value)
+{
+ ExifByteOrder o = exif_data_get_byte_order(ed);
+
+ switch (ee->format) {
+ case EXIF_FORMAT_SHORT:
+ exif_set_short (ee->data, o, value);
+ break;
+ case EXIF_FORMAT_LONG:
+ exif_set_long (ee->data, o, value);
+ break;
+ case EXIF_FORMAT_SLONG:
+ exif_set_slong (ee->data, o, value);
+ break;
+ default:
+ fprintf(stderr,"set_int oops\n");
+ exit(1);
+ }
+}
+
+static void update_orientation(ExifData *ed, int ifd, int orientation)
+{
+ ExifEntry *ee;
+
+ ee = exif_content_get_entry(ed->ifd[ifd], 0x0112);
+ if (NULL == ee)
+ return;
+ set_int(ed,ee,orientation);
+}
+
+static void update_dimension(ExifData *ed, JXFORM_CODE transform,
+ int src_x, int src_y)
+{
+ static struct {
+ int idf;
+ int tag;
+ int x;
+ } fields[] = {
+ {
+ .idf = EXIF_IFD_EXIF,
+ .tag = EXIF_TAG_PIXEL_X_DIMENSION,
+ .x = 1,
+ },{
+ .idf = EXIF_IFD_EXIF,
+ .tag = EXIF_TAG_PIXEL_Y_DIMENSION,
+ .x = 0,
+ },{
+ .idf = EXIF_IFD_INTEROPERABILITY,
+ .tag = EXIF_TAG_RELATED_IMAGE_WIDTH,
+ .x = 1,
+ },{
+ .idf = EXIF_IFD_INTEROPERABILITY,
+ .tag = EXIF_TAG_RELATED_IMAGE_LENGTH,
+ .x = 0,
+ }
+ };
+ ExifEntry *ee;
+ int i;
+
+ for (i = 0; i < sizeof(fields)/sizeof(fields[0]); i++) {
+ ee = exif_content_get_entry(ed->ifd[fields[i].idf], fields[i].tag);
+ if (!ee)
+ continue;
+ switch (transform) {
+ case JXFORM_ROT_90:
+ case JXFORM_ROT_270:
+ case JXFORM_TRANSPOSE:
+ case JXFORM_TRANSVERSE:
+ /* x/y reversed */
+ set_int(ed, ee, fields[i].x ? src_y : src_x);
+ break;
+ default:
+ /* normal */
+ set_int(ed, ee, fields[i].x ? src_x : src_y);
+ break;
+ }
+ }
+}
+
+static int get_orientation(ExifData *ed)
+{
+ ExifEntry *ee;
+
+ ee = exif_content_get_entry(ed->ifd[EXIF_IFD_0], 0x0112);
+ if (NULL == ee)
+ return 1; /* top - left */
+ return get_int(ed,ee);
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct th {
+ struct jpeg_decompress_struct src;
+ struct jpeg_compress_struct dst;
+ struct jpeg_error_mgr jsrcerr, jdsterr;
+ unsigned char *in;
+ unsigned char *out;
+ int isize, osize;
+};
+
+static void thumbnail_src_init(struct jpeg_decompress_struct *cinfo)
+{
+ struct th *h = container_of(cinfo, struct th, src);
+ cinfo->src->next_input_byte = h->in;
+ cinfo->src->bytes_in_buffer = h->isize;
+}
+
+static int thumbnail_src_fill(struct jpeg_decompress_struct *cinfo)
+{
+ fprintf(stderr,"jpeg: panic: no more thumbnail input data\n");
+ exit(1);
+}
+
+static void thumbnail_src_skip(struct jpeg_decompress_struct *cinfo,
+ long num_bytes)
+{
+ cinfo->src->next_input_byte += num_bytes;
+}
+
+static void thumbnail_src_term(struct jpeg_decompress_struct *cinfo)
+{
+ /* nothing */
+}
+
+static void thumbnail_dest_init(struct jpeg_compress_struct *cinfo)
+{
+ struct th *h = container_of(cinfo, struct th, dst);
+ h->osize = h->isize * 2;
+ h->out = malloc(h->osize);
+ cinfo->dest->next_output_byte = h->out;
+ cinfo->dest->free_in_buffer = h->osize;
+}
+
+static boolean thumbnail_dest_flush(struct jpeg_compress_struct *cinfo)
+{
+ fprintf(stderr,"jpeg: panic: output buffer full\n");
+ exit(1);
+}
+
+static void thumbnail_dest_term(struct jpeg_compress_struct *cinfo)
+{
+ struct th *h = container_of(cinfo, struct th, dst);
+ h->osize -= cinfo->dest->free_in_buffer;
+}
+
+static struct jpeg_source_mgr thumbnail_src = {
+ .init_source = thumbnail_src_init,
+ .fill_input_buffer = thumbnail_src_fill,
+ .skip_input_data = thumbnail_src_skip,
+ .resync_to_restart = jpeg_resync_to_restart,
+ .term_source = thumbnail_src_term,
+};
+
+static struct jpeg_destination_mgr thumbnail_dst = {
+ .init_destination = thumbnail_dest_init,
+ .empty_output_buffer = thumbnail_dest_flush,
+ .term_destination = thumbnail_dest_term,
+};
+
+static void do_thumbnail(ExifData *ed, JXFORM_CODE transform)
+{
+ struct th th;
+
+ if (JXFORM_NONE == transform)
+ return;
+
+ memset(&th,0,sizeof(th));
+ th.in = ed->data;
+ th.isize = ed->size;
+
+ /* setup src */
+ th.src.err = jpeg_std_error(&th.jsrcerr);
+ jpeg_create_decompress(&th.src);
+ th.src.src = &thumbnail_src;
+
+ /* setup dst */
+ th.dst.err = jpeg_std_error(&th.jdsterr);
+ jpeg_create_compress(&th.dst);
+ th.dst.dest = &thumbnail_dst;
+
+ /* transform image */
+ do_transform(&th.src,&th.dst,transform,NULL,NULL,0,JFLAG_TRANSFORM_IMAGE);
+
+ /* cleanup */
+ jpeg_destroy_decompress(&th.src);
+ jpeg_destroy_compress(&th.dst);
+
+ /* replace thumbnail */
+ free(ed->data);
+ ed->data = th.out;
+ ed->size = th.osize;
+}
+
+static void do_exif(struct jpeg_decompress_struct *src,
+ JXFORM_CODE *transform,
+ char *thumbnail, int tsize,
+ unsigned int flags)
+{
+ jpeg_saved_marker_ptr mark;
+ ExifData *ed = NULL;
+ unsigned char *data;
+ unsigned int size;
+
+ for (mark = src->marker_list; NULL != mark; mark = mark->next) {
+ if (mark->marker != JPEG_APP0 +1)
+ continue;
+ ed = exif_data_new_from_data(mark->data,mark->data_length);
+ break;
+ }
+ if (flags & JFLAG_UPDATE_THUMBNAIL) {
+ if (NULL == ed)
+ ed = exif_data_new();
+ if (NULL == mark) {
+ mark = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE,sizeof(*mark));
+ memset(mark,0,sizeof(*mark));
+ mark->marker = JPEG_APP0 +1;
+ mark->next = src->marker_list->next;
+ src->marker_list->next = mark;
+ }
+ if (ed->data)
+ free(ed->data);
+ ed->data = thumbnail;
+ ed->size = tsize;
+ }
+ if (NULL == ed)
+ return;
+
+ if (-1 == *transform) {
+ /* automagic image transformation */
+ int orientation = get_orientation(ed);
+ *transform = JXFORM_NONE;
+ if (orientation >= 1 && orientation <= 8)
+ *transform = transmagic[orientation];
+#if 0
+ if (debug)
+ fprintf(stderr,"autotrans: %s\n",transname[*transform]);
+#endif
+ }
+
+ /* update exif data */
+ if (flags & JFLAG_UPDATE_ORIENTATION) {
+ update_orientation(ed,EXIF_IFD_0,1);
+ update_orientation(ed,EXIF_IFD_1,1);
+ }
+ if (ed->data && ed->data[0] == 0xff && ed->data[1] == 0xd8 &&
+ (flags & JFLAG_TRANSFORM_THUMBNAIL))
+ do_thumbnail(ed,*transform);
+ update_dimension(ed, (flags & JFLAG_TRANSFORM_IMAGE) ? *transform : JXFORM_NONE,
+ src->image_width, src->image_height);
+
+ /* build new exif data block */
+ exif_data_save_data(ed,&data,&size);
+ exif_data_unref(ed);
+
+ /* update jpeg APP1 (EXIF) marker */
+ mark->data = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE,size);
+ mark->original_length = size;
+ mark->data_length = size;
+ memcpy(mark->data,data,size);
+ free(data);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static void do_comment(struct jpeg_decompress_struct *src,
+ unsigned char *comment)
+{
+ jpeg_saved_marker_ptr mark;
+ int size;
+
+ /* find or create comment marker */
+ for (mark = src->marker_list;; mark = mark->next) {
+ if (mark->marker == JPEG_COM)
+ break;
+ if (NULL == mark->next) {
+ mark->next = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE,
+ sizeof(*mark));
+ mark = mark->next;
+ memset(mark,0,sizeof(*mark));
+ mark->marker = JPEG_COM;
+ break;
+ }
+ }
+
+ /* update comment marker */
+ size = strlen(comment) +1;
+ mark->data = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE,size);
+ mark->original_length = size;
+ mark->data_length = size;
+ memcpy(mark->data,comment,size);
+}
+
+static int do_transform(struct jpeg_decompress_struct *src,
+ struct jpeg_compress_struct *dst,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags)
+{
+ jvirt_barray_ptr * src_coef_arrays;
+ jvirt_barray_ptr * dst_coef_arrays;
+ jpeg_transform_info transformoption;
+
+ jcopy_markers_setup(src, JCOPYOPT_ALL);
+ if (JPEG_HEADER_OK != jpeg_read_header(src, TRUE))
+ return -1;
+
+ do_exif(src,&transform,thumbnail,tsize,flags);
+ if (-1 == transform)
+ transform = JXFORM_NONE;
+ if (!(flags & JFLAG_TRANSFORM_IMAGE))
+ transform = JXFORM_NONE;
+ if ((flags & JFLAG_UPDATE_COMMENT) && NULL != comment)
+ do_comment(src,comment);
+
+ memset(&transformoption,0,sizeof(transformoption));
+ transformoption.transform = transform;
+ transformoption.trim = FALSE;
+ transformoption.force_grayscale = FALSE;
+
+ /* Any space needed by a transform option must be requested before
+ * jpeg_read_coefficients so that memory allocation will be done right.
+ */
+ jtransform_request_workspace(src, &transformoption);
+ src_coef_arrays = jpeg_read_coefficients(src);
+ jpeg_copy_critical_parameters(src, dst);
+ dst_coef_arrays = jtransform_adjust_parameters
+ (src, dst, src_coef_arrays, &transformoption);
+
+ /* Start compressor (note no image data is actually written here) */
+ jpeg_write_coefficients(dst, dst_coef_arrays);
+
+ /* Copy to the output file any extra markers that we want to preserve */
+ jcopy_markers_execute(src, dst, JCOPYOPT_ALL);
+
+ /* Execute image transformation, if any */
+ jtransform_execute_transformation(src, dst,
+ src_coef_arrays,
+ &transformoption);
+
+ /* Finish compression and release memory */
+ jpeg_finish_compress(dst);
+ jpeg_finish_decompress(src);
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------------- */
+
+int jpeg_transform_fp(FILE *in, FILE *out,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags)
+{
+ struct jpeg_decompress_struct src;
+ struct jpeg_compress_struct dst;
+ struct jpeg_error_mgr jdsterr;
+ struct longjmp_error_mgr jsrcerr;
+
+ /* setup src */
+ src.err = jpeg_std_error(&jsrcerr.jpeg);
+ jsrcerr.jpeg.error_exit = longjmp_error_exit;
+ if (setjmp(jsrcerr.setjmp_buffer))
+ /* something went wrong within the jpeg library ... */
+ goto oops;
+ jpeg_create_decompress(&src);
+ jpeg_stdio_src(&src, in);
+
+ /* setup dst */
+ dst.err = jpeg_std_error(&jdsterr);
+ jpeg_create_compress(&dst);
+ jpeg_stdio_dest(&dst, out);
+
+ /* transform image */
+ do_transform(&src,&dst,transform,comment,thumbnail,tsize,flags);
+
+ /* cleanup */
+ jpeg_destroy_decompress(&src);
+ jpeg_destroy_compress(&dst);
+ return 0;
+
+ oops:
+ jpeg_destroy_decompress(&src);
+ jpeg_destroy_compress(&dst);
+ return -1;
+}
+
+int jpeg_transform_files(char *infile, char *outfile,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags)
+{
+ int rc;
+ FILE *in;
+ FILE *out;
+
+ /* open infile */
+ in = fopen(infile,"r");
+ if (NULL == in) {
+ fprintf(stderr,"open %s: %s\n",infile,strerror(errno));
+ return -1;
+ }
+
+ /* open outfile */
+ out = fopen(outfile,"w");
+ if (NULL == out) {
+ fprintf(stderr,"open %s: %s\n",outfile,strerror(errno));
+ fclose(in);
+ return -1;
+ }
+
+ /* go! */
+ rc = jpeg_transform_fp(in,out,transform,comment,thumbnail,tsize,flags);
+ fclose(in);
+ fclose(out);
+
+ return rc;
+}
+
+int jpeg_transform_inplace(char *file,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags)
+{
+ char *tmpfile;
+ char *bakfile;
+ struct stat st;
+ int fd;
+ FILE *in = NULL;
+ FILE *out = NULL;
+
+ /* are we allowed to write to the file? */
+ if (0 != access(file,W_OK)) {
+ fprintf(stderr,"access %s: %s\n",file,strerror(errno));
+ return -1;
+ }
+
+ /* open infile */
+ in = fopen(file,"r");
+ if (NULL == in) {
+ fprintf(stderr,"open %s: %s\n",file,strerror(errno));
+ return -1;
+ }
+
+ /* open tmpfile */
+ tmpfile = malloc(strlen(file)+10);
+ sprintf(tmpfile,"%s.XXXXXX",file);
+ fd = mkstemp(tmpfile);
+ if (-1 == fd) {
+ fprintf(stderr,"mkstemp(%s): %s\n",tmpfile,strerror(errno));
+ goto oops;
+ }
+ out = fdopen(fd,"w");
+
+ /* copy owner and permissions */
+ if (-1 == fstat(fileno(in),&st)) {
+ fprintf(stderr,"fstat(%s): %s\n",file,strerror(errno));
+ goto oops;
+ }
+ if (-1 == fchown(fileno(out),st.st_uid,st.st_gid)) {
+ fprintf(stderr,"fchown(%s): %s\n",tmpfile,strerror(errno));
+ goto oops;
+ }
+ if (-1 == fchmod(fileno(out),st.st_mode)) {
+ fprintf(stderr,"fchmod(%s): %s\n",tmpfile,strerror(errno));
+ goto oops;
+ }
+
+ /* transform */
+ if (0 != jpeg_transform_fp(in,out,transform,comment,thumbnail,tsize,flags))
+ goto oops;
+
+ /* worked ok -- commit */
+ fclose(in);
+ fclose(out);
+ if (flags & JFLAG_FILE_BACKUP) {
+ bakfile = malloc(strlen(file)+2);
+ sprintf(bakfile,"%s~",file);
+ rename(file,bakfile);
+ free(bakfile);
+ }
+ rename(tmpfile,file);
+ if (flags & JFLAG_FILE_KEEP_TIME) {
+ struct utimbuf u;
+ u.actime = st.st_atime;
+ u.modtime = st.st_mtime;
+ utime(file,&u);
+ }
+
+ /* cleanup & return */
+ free(tmpfile);
+ return 0;
+
+ oops:
+ /* something went wrong -- rollback */
+ if (in)
+ fclose(in);
+ if (out) {
+ fclose(out);
+ unlink(tmpfile);
+ }
+ return -1;
+}
diff --git a/jpegtools.h b/jpegtools.h
new file mode 100644
index 0000000..78035b0
--- /dev/null
+++ b/jpegtools.h
@@ -0,0 +1,28 @@
+
+/* various flags */
+#define JFLAG_TRANSFORM_IMAGE 0x0001
+#define JFLAG_TRANSFORM_THUMBNAIL 0x0002
+
+#define JFLAG_UPDATE_COMMENT 0x0010
+#define JFLAG_UPDATE_ORIENTATION 0x0020
+#define JFLAG_UPDATE_THUMBNAIL 0x0040
+
+#define JFLAG_FILE_BACKUP 0x0100
+#define JFLAG_FILE_KEEP_TIME 0x0200
+
+/* functions */
+int jpeg_transform_fp(FILE *in, FILE *out,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags);
+int jpeg_transform_files(char *infile, char *outfile,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags);
+int jpeg_transform_inplace(char *file,
+ JXFORM_CODE transform,
+ unsigned char *comment,
+ char *thumbnail, int tsize,
+ unsigned int flags);
diff --git a/lirc.c b/lirc.c
new file mode 100644
index 0000000..1428001
--- /dev/null
+++ b/lirc.c
@@ -0,0 +1,60 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+
+#include <lirc/lirc_client.h>
+#include "lirc.h"
+
+/*-----------------------------------------------------------------------*/
+
+static int debug = 0;
+static struct lirc_config *config = NULL;
+
+int lirc_fbi_init()
+{
+ int fd;
+ if (-1 == (fd = lirc_init("fbi",debug))) {
+ if (debug)
+ fprintf(stderr,"lirc: no infrared remote support available\n");
+ return -1;
+ }
+ if (0 != lirc_readconfig(NULL,&config,NULL)) {
+ config = NULL;
+ }
+ if (debug)
+ fprintf(stderr, "lirc: ~/.lircrc file %sfound\n",
+ config ? "" : "not ");
+
+ fcntl(fd,F_SETFL,O_NONBLOCK);
+ fcntl(fd,F_SETFD,FD_CLOEXEC);
+ if (debug)
+ fprintf(stderr,"lirc: init ok\n");
+
+ return fd;
+}
+
+int lirc_fbi_havedata(int* rc, char key[11])
+{
+ char *code,*cmd;
+ int ret=-1;
+
+ while (lirc_nextcode(&code) == 0 && code != NULL) {
+ ret = 0;
+ if (config) {
+ /* use ~/.lircrc */
+ while (lirc_code2char(config,code,&cmd)==0 && cmd != NULL) {
+ memset(key,0,11);
+ strncpy(key,cmd,10);
+ *rc = strlen(cmd);
+ if (debug)
+ fprintf(stderr,"lirc: cmd \"%s\"\n", cmd);
+ }
+ }
+ free(code);
+ }
+ return ret;
+}
diff --git a/lirc.h b/lirc.h
new file mode 100644
index 0000000..19c60bb
--- /dev/null
+++ b/lirc.h
@@ -0,0 +1,2 @@
+int lirc_fbi_init(void);
+int lirc_fbi_havedata(int* rc, char key[11]);
diff --git a/list.h b/list.h
new file mode 100644
index 0000000..614d34b
--- /dev/null
+++ b/list.h
@@ -0,0 +1,168 @@
+#ifndef _LIST_H_
+#define _LIST_H_
+/*
+ * Simple doubly linked list implementation.
+ * -- shameless stolen from the linux kernel sources
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+ (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_add(struct list_head * new,
+ struct list_head * prev,
+ struct list_head * next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static __inline__ void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static __inline__ void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static __inline__ void __list_del(struct list_head * prev,
+ struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is in an undefined state.
+ */
+static __inline__ void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static __inline__ void list_del_init(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static __inline__ int list_empty(struct list_head *head)
+{
+ return head->next == head;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static __inline__ void list_splice(struct list_head *list, struct list_head *head)
+{
+ struct list_head *first = list->next;
+
+ if (first != list) {
+ struct list_head *last = list->prev;
+ struct list_head *at = head->next;
+
+ first->prev = head;
+ head->next = first;
+
+ last->next = at;
+ at->prev = last;
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list in reverse order
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+#endif /* _LIST_H_ */
diff --git a/logo.jpg b/logo.jpg
new file mode 100644
index 0000000..f9814b0
--- /dev/null
+++ b/logo.jpg
Binary files differ
diff --git a/lut.c b/lut.c
new file mode 100644
index 0000000..2901355
--- /dev/null
+++ b/lut.c
@@ -0,0 +1,97 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include "readers.h"
+#include "viewer.h"
+#include "lut.h"
+
+/* ----------------------------------------------------------------------- */
+
+struct op_map_parm_ch op_map_nothing = {
+ gamma: 1,
+ bottom: 0,
+ top: 255,
+ left: 0,
+ right: 255
+};
+
+struct op_map_lut {
+ unsigned char red[256];
+ unsigned char green[256];
+ unsigned char blue[256];
+};
+
+/* ----------------------------------------------------------------------- */
+/* functions */
+
+static void build_lut(struct op_map_parm_ch *arg, unsigned char *lut)
+{
+ int i,val;
+ int inrange,outrange;
+ float p;
+
+ inrange = arg->right - arg->left +1;
+ outrange = arg->top - arg->bottom +1;
+ p = 1/arg->gamma;
+
+ for (i = 0; i < arg->left; i++)
+ lut[i] = 0;
+ for (; i <= arg->right; i++) {
+ val = pow((float)(i-arg->left)/inrange,p) * outrange + 0.5;
+ val += arg->bottom;
+ if (val < 0) val = 0;
+ if (val > 255) val = 255;
+ lut[i] = val;
+ }
+ for (; i < 256; i++)
+ lut[i] = 255;
+}
+
+static void*
+op_map_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm)
+{
+ struct op_map_parm *args = parm;
+ struct op_map_lut *lut;
+
+ lut = malloc(sizeof(*lut));
+ build_lut(&args->red,lut->red);
+ build_lut(&args->green,lut->green);
+ build_lut(&args->blue,lut->blue);
+
+ *i = src->i;
+ return lut;
+}
+
+static void
+op_map_work(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ struct op_map_lut *lut = data;
+ unsigned char *scanline;
+ int i;
+
+ scanline = src->data + line * src->i.width * 3;
+ memcpy(dst,scanline,src->i.width * 3);
+ if (line < rect->y1 || line >= rect->y2)
+ return;
+ dst += 3*rect->x1;
+ scanline += 3*rect->x1;
+ for (i = rect->x1; i < rect->x2; i++) {
+ dst[0] = lut->red[scanline[0]];
+ dst[1] = lut->green[scanline[1]];
+ dst[2] = lut->blue[scanline[2]];
+ scanline += 3;
+ dst += 3;
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+struct ida_op desc_map = {
+ name: "map",
+ init: op_map_init,
+ work: op_map_work,
+ done: op_free_done,
+};
diff --git a/lut.h b/lut.h
new file mode 100644
index 0000000..5a1756c
--- /dev/null
+++ b/lut.h
@@ -0,0 +1,15 @@
+struct op_map_parm_ch {
+ float gamma;
+ int bottom;
+ int top;
+ int left;
+ int right;
+};
+struct op_map_parm {
+ struct op_map_parm_ch red;
+ struct op_map_parm_ch green;
+ struct op_map_parm_ch blue;
+};
+
+extern struct op_map_parm_ch op_map_nothing;
+extern struct ida_op desc_map;
diff --git a/man.c b/man.c
new file mode 100644
index 0000000..85b89a1
--- /dev/null
+++ b/man.c
@@ -0,0 +1,119 @@
+/*
+ * motif-based man page renderer
+ * (c) 2001 Gerd Knorr <kraxel@bytesex.org>
+ *
+ */
+
+#include <stdio.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/Form.h>
+#include <Xm/Label.h>
+#include <Xm/RowColumn.h>
+#include <Xm/PushB.h>
+#include <Xm/ScrolledW.h>
+#include <Xm/SelectioB.h>
+
+#include "RegEdit.h"
+#include "man.h"
+
+extern Display *dpy;
+extern Widget app_shell;
+
+/*----------------------------------------------------------------------*/
+
+#define MAN_UNDEF 0
+#define MAN_NORMAL 1
+#define MAN_BOLD 2
+#define MAN_UNDERLINE 3
+
+static XmStringTag man_tags[] = {
+ NULL, NULL, "bold", "underline"
+};
+
+static void
+man_destroy(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XtDestroyWidget(clientdata);
+}
+
+void
+man(char *page)
+{
+ Widget dlg,view,label;
+ XmString xmpage,xmchunk;
+ char line[1024],chunk[256];
+ int s,d,cur,last;
+ FILE *fp;
+
+ /* build dialog */
+ dlg = XmCreatePromptDialog(app_shell,"man",NULL,0);
+ XmdRegisterEditres(XtParent(dlg));
+ XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_SELECTION_LABEL));
+ XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_CANCEL_BUTTON));
+ XtUnmanageChild(XmSelectionBoxGetChild(dlg,XmDIALOG_TEXT));
+ XtAddCallback(dlg,XmNokCallback,man_destroy,dlg);
+ view = XmCreateScrolledWindow(dlg,"view",NULL,0);
+ XtManageChild(view);
+ label = XtVaCreateManagedWidget("label", xmLabelWidgetClass,view, NULL);
+ XtManageChild(dlg);
+
+ /* fetch page and render into XmString */
+ sprintf(line,"man %s 2>/dev/null",page);
+ fp = popen(line,"r");
+ xmpage = XmStringGenerate("", NULL, XmMULTIBYTE_TEXT, NULL);
+ while (NULL != fgets(line,sizeof(line)-1,fp)) {
+ last = MAN_UNDEF;
+ for (s = 0, d = 0; line[s] != '\0';) {
+ /* check current char */
+ cur = MAN_NORMAL;
+ if (line[s+1] == '\010' && line[s] == line[s+2])
+ cur = MAN_BOLD;
+ if (line[s] == '_' && line[s+1] == '\010')
+ cur = MAN_UNDERLINE;
+ /* add chunk if completed */
+ if (MAN_UNDEF != last && cur != last) {
+ xmchunk = XmStringGenerate(chunk,NULL,XmMULTIBYTE_TEXT,
+ man_tags[last]);
+ xmpage = XmStringConcatAndFree(xmpage,xmchunk);
+ d = 0;
+ }
+ /* add char to chunk */
+ switch (cur) {
+ case MAN_BOLD:
+ case MAN_UNDERLINE:
+ s += 2;
+ case MAN_NORMAL:
+ chunk[d++] = line[s++];
+ break;
+ }
+ chunk[d] = '\0';
+ last = cur;
+ }
+ /* add last chunk for line */
+ xmchunk = XmStringGenerate(chunk, NULL, XmMULTIBYTE_TEXT,
+ man_tags[last]);
+ xmpage = XmStringConcatAndFree(xmpage,xmchunk);
+ }
+ XtVaSetValues(label,XmNlabelString,xmpage,NULL);
+ XmStringFree(xmpage);
+ fclose(fp);
+}
+
+void
+man_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ char *page = clientdata;
+ man(page);
+}
+
+void
+man_action(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ if (*num_params > 0)
+ man(params[0]);
+}
diff --git a/man.h b/man.h
new file mode 100644
index 0000000..9943cfc
--- /dev/null
+++ b/man.h
@@ -0,0 +1,4 @@
+void man(char *page);
+void man_cb(Widget widget, XtPointer clientdata, XtPointer call_data);
+void man_action(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params);
diff --git a/misc.h b/misc.h
new file mode 100644
index 0000000..92a66bc
--- /dev/null
+++ b/misc.h
@@ -0,0 +1,9 @@
+/*
+ * misc useful #defines ...
+ */
+
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#define array_size(x) (sizeof(x)/sizeof(x[0]))
diff --git a/mk/Autoconf.mk b/mk/Autoconf.mk
new file mode 100644
index 0000000..4d25d21
--- /dev/null
+++ b/mk/Autoconf.mk
@@ -0,0 +1,138 @@
+#
+# simple autoconf system for GNU make
+#
+# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
+#
+# credits for creating this one go to the autotools people because
+# they managed it to annoy lots of developers and users (including
+# me) with version incompatibilities.
+#
+# This file is public domain. No warranty. If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose ?= no
+
+# some stuff used by the tests
+ifneq ($(verbose),no)
+ # verbose (for debug)
+ ac_init = echo "checking $(1) ... " >&2; rc=no
+ ac_b_cmd = echo "run: $(1)" >&2; $(1) >/dev/null && rc=yes
+ ac_s_cmd = echo "run: $(1)" >&2; rc=`$(1)`
+ ac_fini = echo "... result is $${rc}" >&2; echo >&2; echo "$${rc}"
+else
+ # normal
+ ac_init = echo -n "checking $(1) ... " >&2; rc=no
+ ac_b_cmd = $(1) >/dev/null 2>&1 && rc=yes
+ ac_s_cmd = rc=`$(1) 2>/dev/null`
+ ac_fini = echo "$${rc}" >&2; echo "$${rc}"
+endif
+
+# some helpers to build cflags and related variables
+ac_def_cflags_1 = $(if $(filter yes,$($(1))),-D$(1))
+ac_lib_cflags = $(foreach lib,$(1),$(call ac_def_cflags_1,HAVE_LIB$(lib)))
+ac_inc_cflags = $(foreach inc,$(1),$(call ac_def_cflags_1,HAVE_$(inc)))
+ac_lib_mkvar_1 = $(if $(filter yes,$(HAVE_LIB$(1))),$($(1)_$(2)))
+ac_lib_mkvar = $(foreach lib,$(1),$(call ac_lib_mkvar_1,$(lib),$(2)))
+
+
+########################################################################
+# the tests ...
+
+# get uname
+ac_uname = $(shell \
+ $(call ac_init,for system);\
+ $(call ac_s_cmd,uname -s | tr 'A-Z' 'a-z');\
+ $(call ac_fini))
+
+# check for some header file
+# args: header file
+ac_header = $(shell \
+ $(call ac_init,for $(1));\
+ $(call ac_b_cmd,echo '\#include <$(1)>' |\
+ $(CC) $(CFLAGS) -E -);\
+ $(call ac_fini))
+
+# check for some function
+# args: function [, additional libs ]
+ac_func = $(shell \
+ $(call ac_init,for $(1));\
+ echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+ > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+ __actest __actest.c $(2));\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check for some library
+# args: function, library [, additional libs ]
+ac_lib = $(shell \
+ $(call ac_init,for $(1) in $(2));\
+ echo 'void $(1)(void); int main(void) {$(1)();return 0;}' \
+ > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(LDFLAGS) -o \
+ __actest __actest.c -l$(2) $(3));\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check if some compiler flag works
+# args: compiler flag
+ac_cflag = $(shell \
+ $(call ac_init,if $(CC) supports $(1));\
+ echo 'int main() {return 0;}' > __actest.c;\
+ $(call ac_b_cmd,$(CC) $(CFLAGS) $(1) $(LDFLAGS) -o \
+ __actest __actest.c);\
+ rm -f __actest __actest.c;\
+ $(call ac_fini))
+
+# check for some binary
+# args: binary name
+ac_binary = $(shell \
+ $(call ac_init,for $(1));\
+ $(call ac_s_cmd,which $(1));\
+ bin="$$rc";rc="no";\
+ $(call ac_b_cmd,test -x "$$$$bin");\
+ $(call ac_fini))
+
+# check if lib64 is used
+ac_lib64 = $(shell \
+ $(call ac_init,for libdir name);\
+ $(call ac_s_cmd,$(CC) -print-search-dirs | grep -q lib64 &&\
+ echo "lib64" || echo "lib");\
+ $(call ac_fini))
+
+# check for x11 ressource dir prefix
+ac_resdir = $(shell \
+ $(call ac_init,for X11 app-defaults prefix);\
+ $(call ac_s_cmd, test -d /etc/X11/app-defaults &&\
+ echo "/etc/X11" || echo "/usr/X11R6/lib/X11");\
+ $(call ac_fini))
+
+
+########################################################################
+# build Make.config
+
+define newline
+
+
+endef
+make-config-q = $(subst $(newline),\n,$(make-config))
+
+ifeq ($(filter config,$(MAKECMDGOALS)),config)
+.PHONY: Make.config
+ LIB := $(call ac_lib64)
+else
+ LIB ?= $(call ac_lib64)
+ LIB := $(LIB)
+endif
+.PHONY: config
+config: Make.config
+ @true
+
+Make.config: $(srcdir)/GNUmakefile
+ @echo -e "$(make-config-q)" > $@
+ @echo
+ @echo "Make.config written, edit if needed"
+ @echo
diff --git a/mk/Compile.mk b/mk/Compile.mk
new file mode 100644
index 0000000..49dddbf
--- /dev/null
+++ b/mk/Compile.mk
@@ -0,0 +1,88 @@
+#
+# some rules to compile stuff ...
+#
+# (c) 2002-2004 Gerd Knorr <kraxel@bytesex.org>
+#
+# main features:
+# * autodependencies via "cpp -MD"
+# * fancy, non-verbose output
+#
+# This file is public domain. No warranty. If it breaks you keep
+# both pieces.
+#
+########################################################################
+
+# verbose yes/no
+verbose ?= no
+
+# dependency files
+tmpdep = mk/$(subst /,_,$*).tmp
+depfile = mk/$(subst /,_,$*).dep
+depfiles = mk/*.dep
+
+compile_c = $(CC) $(CFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+compile_cc = $(CXX) $(CXXFLAGS) -Wp,-MD,$(tmpdep) -c -o $@ $<
+fixup_deps = sed -e "s|.*\.o:|$@:|" < $(tmpdep) > $(depfile) && rm -f $(tmpdep)
+cc_makedirs = mkdir -p $(dir $@) $(dir $(depfile))
+
+link_app = $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+link_so = $(CC) $(LDFLAGS) -shared -Wl,-soname,$(@F) -o $@ $^ $(LDLIBS)
+ar_lib = rm -f $@ && ar -r $@ $^ && ranlib $@
+
+moc_h = $(MOC) $< -o $@
+msgfmt_po = msgfmt -o $@ $<
+
+# non-verbose output
+ifeq ($(verbose),no)
+ echo_compile_c = echo " CC " $@
+ echo_compile_cc = echo " CXX " $@
+ echo_link_app = echo " LD " $@
+ echo_link_so = echo " LD " $@
+ echo_ar_lib = echo " AR " $@
+ echo_moc_h = echo " MOC " $@
+ echo_msgfmt_po = echo " MSGFMT " $@
+else
+ echo_compile_c = echo $(compile_c)
+ echo_compile_cc = echo $(compile_cc)
+ echo_link_app = echo $(link_app)
+ echo_link_so = echo $(link_so)
+ echo_ar_lib = echo $(ar_lib)
+ echo_moc_h = echo $(moc_h)
+ echo_msgfmt_po = echo $(msgfmt_po)
+endif
+
+%.o: %.c
+ @$(cc_makedirs)
+ @$(echo_compile_c)
+ @$(compile_c)
+ @$(fixup_deps)
+
+%.o: %.cc
+ @$(cc_makedirs)
+ @$(echo_compile_cc)
+ @$(compile_cc)
+ @$(fixup_deps)
+
+%.o: %.cpp
+ @$(cc_makedirs)
+ @$(echo_compile_cc)
+ @$(compile_cc)
+ @$(fixup_deps)
+
+
+%.so: %.o
+ @$(echo_link_so)
+ @$(link_so)
+
+%: %.o
+ @$(echo_link_app)
+ @$(link_app)
+
+%.moc : %.h
+ @$(echo_moc_h)
+ @$(moc_h)
+
+%.mo : %.po
+ @$(echo_msgfmt_po)
+ @$(msgfmt_po)
+
diff --git a/mk/Maintainer.mk b/mk/Maintainer.mk
new file mode 100644
index 0000000..5bf9480
--- /dev/null
+++ b/mk/Maintainer.mk
@@ -0,0 +1,12 @@
+# just some maintainer stuff for me ...
+########################################################################
+
+make-sync-dir = $(HOME)/src/gnu-make
+
+.PHONY: sync
+sync:: distclean
+ test -d $(make-sync-dir)
+ rm -f $(srcdir)/INSTALL $(srcdir)/mk/*.mk
+ cp -v $(make-sync-dir)/INSTALL $(srcdir)/.
+ cp -v $(make-sync-dir)/*.mk $(srcdir)/mk
+ chmod 444 $(srcdir)/INSTALL $(srcdir)/mk/*.mk
diff --git a/mk/Variables.mk b/mk/Variables.mk
new file mode 100644
index 0000000..930f824
--- /dev/null
+++ b/mk/Variables.mk
@@ -0,0 +1,46 @@
+# common variables ...
+########################################################################
+
+# directories
+DESTDIR =
+srcdir ?= .
+prefix ?= /usr/local
+bindir = $(DESTDIR)$(prefix)/bin
+mandir = $(DESTDIR)$(prefix)/share/man
+locdir = $(DESTDIR)$(prefix)/share/locale
+
+# package + version
+empty :=
+space := $(empty) $(empty)
+ifneq ($(wildcard $(srcdir)/VERSION),)
+ VERSION := $(shell cat $(srcdir)/VERSION)
+else
+ VERSION := 42
+endif
+
+# programs
+CC ?= gcc
+CXX ?= g++
+MOC ?= $(if $(QTDIR),$(QTDIR)/bin/moc,moc)
+INSTALL ?= install
+INSTALL_BINARY := $(INSTALL) -s
+INSTALL_SCRIPT := $(INSTALL)
+INSTALL_DATA := $(INSTALL) -m 644
+INSTALL_DIR := $(INSTALL) -d
+
+# cflags
+CFLAGS ?= -g -O2
+CFLAGS += -Wall -Wmissing-prototypes -Wstrict-prototypes \
+ -Wpointer-arith -Wunused
+
+# add /usr/local to the search path if something is in there ...
+ifneq ($(wildcard /usr/local/include/*.h),)
+ CFLAGS += -I/usr/local/include
+ LDFLAGS += -L/usr/local/$(LIB)
+endif
+
+# fixup include path for $(srcdir) != "."
+ifneq ($(srcdir),.)
+ CFLAGS += -I. -I$(srcdir)
+endif
+
diff --git a/mk/utf8.tmp b/mk/utf8.tmp
new file mode 100644
index 0000000..ccd8e44
--- /dev/null
+++ b/mk/utf8.tmp
@@ -0,0 +1,25 @@
+utf8.o: utf8.c /usr/include/stdio.h /usr/include/features.h \
+ /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
+ /usr/lib64/gcc-lib/x86_64-suse-linux/3.3.3/include/stddef.h \
+ /usr/include/bits/types.h /usr/include/bits/wordsize.h \
+ /usr/include/bits/typesizes.h /usr/include/libio.h \
+ /usr/include/_G_config.h /usr/include/wchar.h /usr/include/bits/wchar.h \
+ /usr/include/gconv.h \
+ /usr/lib64/gcc-lib/x86_64-suse-linux/3.3.3/include/stdarg.h \
+ /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h \
+ /usr/include/bits/stdio.h /usr/include/stdlib.h \
+ /usr/include/bits/waitflags.h /usr/include/bits/waitstatus.h \
+ /usr/include/endian.h /usr/include/bits/endian.h /usr/include/xlocale.h \
+ /usr/include/sys/types.h /usr/include/time.h /usr/include/sys/select.h \
+ /usr/include/bits/select.h /usr/include/bits/sigset.h \
+ /usr/include/bits/time.h /usr/include/sys/sysmacros.h \
+ /usr/include/bits/pthreadtypes.h /usr/include/bits/sched.h \
+ /usr/include/alloca.h /usr/include/unistd.h \
+ /usr/include/bits/posix_opt.h /usr/include/bits/environments.h \
+ /usr/include/bits/confname.h /usr/include/getopt.h \
+ /usr/include/string.h /usr/include/bits/string.h \
+ /usr/include/bits/string2.h /usr/include/errno.h \
+ /usr/include/bits/errno.h /usr/include/linux/errno.h \
+ /usr/include/asm/errno.h /usr/include/asm-x86_64/errno.h \
+ /usr/include/asm-generic/errno.h /usr/include/asm-generic/errno-base.h \
+ /usr/include/iconv.h list.h
diff --git a/op.c b/op.c
new file mode 100644
index 0000000..b4e95fe
--- /dev/null
+++ b/op.c
@@ -0,0 +1,289 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "readers.h"
+#include "op.h"
+#include "filter.h"
+
+/* ----------------------------------------------------------------------- */
+/* functions */
+
+static char op_none_data;
+
+static void
+op_flip_vert(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ char *scanline;
+
+ scanline = src->data + (src->i.height - line - 1) * src->i.width * 3;
+ memcpy(dst,scanline,src->i.width*3);
+}
+
+static void
+op_flip_horz(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ char *scanline;
+ unsigned int i;
+
+ scanline = src->data + (line+1) * src->i.width * 3;
+ for (i = 0; i < src->i.width; i++) {
+ scanline -= 3;
+ dst[0] = scanline[0];
+ dst[1] = scanline[1];
+ dst[2] = scanline[2];
+ dst += 3;
+ }
+}
+
+static void*
+op_rotate_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm)
+{
+ *i = src->i;
+ i->height = src->i.width;
+ i->width = src->i.height;
+ i->dpi = src->i.dpi;
+ return &op_none_data;
+}
+
+static void
+op_rotate_cw(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ char *pix;
+ unsigned int i;
+
+ pix = src->data + src->i.width * src->i.height * 3 + line * 3;
+ for (i = 0; i < src->i.height; i++) {
+ pix -= src->i.width * 3;
+ dst[0] = pix[0];
+ dst[1] = pix[1];
+ dst[2] = pix[2];
+ dst += 3;
+ }
+}
+
+static void
+op_rotate_ccw(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ char *pix;
+ unsigned int i;
+
+ pix = src->data + (src->i.width-line-1) * 3;
+ for (i = 0; i < src->i.height; i++) {
+ dst[0] = pix[0];
+ dst[1] = pix[1];
+ dst[2] = pix[2];
+ pix += src->i.width * 3;
+ dst += 3;
+ }
+}
+
+static void
+op_invert(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ unsigned char *scanline;
+ int i;
+
+ scanline = src->data + line * src->i.width * 3;
+ memcpy(dst,scanline,src->i.width * 3);
+ if (line < rect->y1 || line >= rect->y2)
+ return;
+ dst += 3*rect->x1;
+ scanline += 3*rect->x1;
+ for (i = rect->x1; i < rect->x2; i++) {
+ dst[0] = 255-scanline[0];
+ dst[1] = 255-scanline[1];
+ dst[2] = 255-scanline[2];
+ scanline += 3;
+ dst += 3;
+ }
+}
+
+static void*
+op_crop_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm)
+{
+ if (rect->x2 - rect->x1 == src->i.width &&
+ rect->y2 - rect->y1 == src->i.height)
+ return NULL;
+ *i = src->i;
+ i->width = rect->x2 - rect->x1;
+ i->height = rect->y2 - rect->y1;
+ return &op_none_data;
+}
+
+static void
+op_crop_work(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line, void *data)
+{
+ unsigned char *scanline;
+ int i;
+
+ scanline = src->data + (line+rect->y1) * src->i.width * 3 + rect->x1 * 3;
+ for (i = rect->x1; i < rect->x2; i++) {
+ dst[0] = scanline[0];
+ dst[1] = scanline[1];
+ dst[2] = scanline[2];
+ scanline += 3;
+ dst += 3;
+ }
+}
+
+static void*
+op_autocrop_init(struct ida_image *src, struct ida_rect *unused,
+ struct ida_image_info *i, void *parm)
+{
+ static struct op_3x3_parm filter = {
+ f1: { -1, -1, -1 },
+ f2: { -1, 8, -1 },
+ f3: { -1, -1, -1 },
+ };
+ struct ida_rect rect;
+ struct ida_image img;
+ int x,y,limit;
+ unsigned char *line;
+ void *data;
+
+ /* detect edges */
+ rect.x1 = 0;
+ rect.x2 = src->i.width;
+ rect.y1 = 0;
+ rect.y2 = src->i.height;
+ data = desc_3x3.init(src, &rect, &img.i, &filter);
+
+ img.data = malloc(img.i.width * img.i.height * 3);
+ for (y = 0; y < (int)img.i.height; y++)
+ desc_3x3.work(src, &rect, img.data+3*img.i.width*y, y, data);
+ desc_3x3.done(data);
+ limit = 64;
+
+ /* y border */
+ for (y = 0; y < (int)img.i.height; y++) {
+ line = img.data + img.i.width*y*3;
+ for (x = 0; x < (int)img.i.width; x++)
+ if (line[3*x+0] > limit ||
+ line[3*x+1] > limit ||
+ line[3*x+2] > limit)
+ break;
+ if (x != (int)img.i.width)
+ break;
+ }
+ rect.y1 = y;
+ for (y = (int)img.i.height-1; y > rect.y1; y--) {
+ line = img.data + img.i.width*y*3;
+ for (x = 0; x < (int)img.i.width; x++)
+ if (line[3*x+0] > limit ||
+ line[3*x+1] > limit ||
+ line[3*x+2] > limit)
+ break;
+ if (x != (int)img.i.width)
+ break;
+ }
+ rect.y2 = y+1;
+
+ /* x border */
+ for (x = 0; x < (int)img.i.width; x++) {
+ for (y = 0; y < (int)img.i.height; y++) {
+ line = img.data + (img.i.width*y+x) * 3;
+ if (line[0] > limit ||
+ line[1] > limit ||
+ line[2] > limit)
+ break;
+ }
+ if (y != (int)img.i.height)
+ break;
+ }
+ rect.x1 = x;
+ for (x = (int)img.i.width-1; x > rect.x1; x--) {
+ for (y = 0; y < (int)img.i.height; y++) {
+ line = img.data + (img.i.width*y+x) * 3;
+ if (line[0] > limit ||
+ line[1] > limit ||
+ line[2] > limit)
+ break;
+ }
+ if (y != (int)img.i.height)
+ break;
+ }
+ rect.x2 = x+1;
+
+ free(img.data);
+ if (debug)
+ fprintf(stderr,"y: %d-%d/%d -- x: %d-%d/%d\n",
+ rect.y1, rect.y2, img.i.height,
+ rect.x1, rect.x2, img.i.width);
+
+ if (0 == rect.x2 - rect.x1 || 0 == rect.y2 - rect.y1)
+ return NULL;
+
+ *unused = rect;
+ *i = src->i;
+ i->width = rect.x2 - rect.x1;
+ i->height = rect.y2 - rect.y1;
+ return &op_none_data;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static char op_none_data;
+
+void* op_none_init(struct ida_image *src, struct ida_rect *sel,
+ struct ida_image_info *i, void *parm)
+{
+ *i = src->i;
+ return &op_none_data;
+}
+
+void op_none_done(void *data) {}
+void op_free_done(void *data) { free(data); }
+
+/* ----------------------------------------------------------------------- */
+
+struct ida_op desc_flip_vert = {
+ name: "flip-vert",
+ init: op_none_init,
+ work: op_flip_vert,
+ done: op_none_done,
+};
+struct ida_op desc_flip_horz = {
+ name: "flip-horz",
+ init: op_none_init,
+ work: op_flip_horz,
+ done: op_none_done,
+};
+struct ida_op desc_rotate_cw = {
+ name: "rotate-cw",
+ init: op_rotate_init,
+ work: op_rotate_cw,
+ done: op_none_done,
+};
+struct ida_op desc_rotate_ccw = {
+ name: "rotate-ccw",
+ init: op_rotate_init,
+ work: op_rotate_ccw,
+ done: op_none_done,
+};
+struct ida_op desc_invert = {
+ name: "invert",
+ init: op_none_init,
+ work: op_invert,
+ done: op_none_done,
+};
+struct ida_op desc_crop = {
+ name: "crop",
+ init: op_crop_init,
+ work: op_crop_work,
+ done: op_none_done,
+};
+struct ida_op desc_autocrop = {
+ name: "autocrop",
+ init: op_autocrop_init,
+ work: op_crop_work,
+ done: op_none_done,
+};
diff --git a/op.h b/op.h
new file mode 100644
index 0000000..4d882f5
--- /dev/null
+++ b/op.h
@@ -0,0 +1,7 @@
+extern struct ida_op desc_flip_vert;
+extern struct ida_op desc_flip_horz;
+extern struct ida_op desc_rotate_cw;
+extern struct ida_op desc_rotate_ccw;
+extern struct ida_op desc_invert;
+extern struct ida_op desc_crop;
+extern struct ida_op desc_autocrop;
diff --git a/parseconfig.c b/parseconfig.c
new file mode 100644
index 0000000..8c20169
--- /dev/null
+++ b/parseconfig.c
@@ -0,0 +1,857 @@
+/*
+ * config file parser
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "list.h"
+#include "parseconfig.h"
+
+struct cfg_entry {
+ struct list_head next;
+ char *name;
+ unsigned int flags;
+ char *value;
+};
+
+struct cfg_section {
+ struct list_head next;
+ char *name;
+ unsigned int flags;
+ struct list_head entries;
+};
+
+struct cfg_domain {
+ struct list_head next;
+ char *name;
+ struct list_head sections;
+};
+
+LIST_HEAD(domains);
+
+/* ------------------------------------------------------------------------ */
+/* prehistoric libc ;) */
+
+#ifndef HAVE_STRCASESTR
+char* strcasestr(char *haystack, char *needle)
+{
+ int hlen = strlen(haystack);
+ int nlen = strlen(needle);
+ int offset;
+
+ for (offset = 0; offset <= hlen - nlen; offset++)
+ if (0 == strncasecmp(haystack+offset,needle,nlen))
+ return haystack+offset;
+ return NULL;
+}
+#endif
+
+/* ------------------------------------------------------------------------ */
+/* internal stuff */
+
+static struct cfg_domain *d_last;
+static struct cfg_section *s_last;
+static struct cfg_entry *e_last;
+
+static struct cfg_domain*
+cfg_find_domain(char *dname)
+{
+ struct list_head *item;
+ struct cfg_domain *domain;
+
+ if (d_last && 0 == strcmp(d_last->name,dname))
+ return d_last;
+ d_last = NULL;
+ s_last = NULL;
+ e_last = NULL;
+
+ list_for_each(item,&domains) {
+ domain = list_entry(item, struct cfg_domain, next);
+ if (0 == strcasecmp(domain->name,dname)) {
+ d_last = domain;
+ return domain;
+ }
+ }
+ return NULL;
+}
+
+static struct cfg_domain*
+cfg_get_domain(char *dname)
+{
+ struct cfg_domain *domain;
+
+ domain = cfg_find_domain(dname);
+ if (NULL == domain) {
+ domain = malloc(sizeof(*domain));
+ memset(domain,0,sizeof(*domain));
+ domain->name = strdup(dname);
+ INIT_LIST_HEAD(&domain->sections);
+ list_add_tail(&domain->next,&domains);
+ }
+ d_last = domain;
+ return domain;
+}
+
+static struct cfg_section*
+cfg_find_section(struct cfg_domain *domain, char *sname)
+{
+ struct list_head *item;
+ struct cfg_section *section;
+
+ if (s_last && 0 == strcmp(s_last->name,sname))
+ return s_last;
+ s_last = NULL;
+ e_last = NULL;
+
+ list_for_each(item,&domain->sections) {
+ section = list_entry(item, struct cfg_section, next);
+ if (0 == strcasecmp(section->name,sname)) {
+ s_last = section;
+ return section;
+ }
+ }
+ return NULL;
+}
+
+static struct cfg_section*
+cfg_get_section(struct cfg_domain *domain, char *sname)
+{
+ struct cfg_section *section;
+
+ section = cfg_find_section(domain,sname);
+ if (NULL == section) {
+ section = malloc(sizeof(*section));
+ memset(section,0,sizeof(*section));
+ section->name = strdup(sname);
+ INIT_LIST_HEAD(&section->entries);
+ list_add_tail(&section->next,&domain->sections);
+ }
+ s_last = section;
+ return section;
+}
+
+static struct cfg_entry*
+cfg_find_entry(struct cfg_section *section, char *ename)
+{
+ struct list_head *item;
+ struct cfg_entry *entry;
+
+ if (e_last && 0 == strcmp(e_last->name,ename))
+ return e_last;
+ e_last = NULL;
+
+ list_for_each(item,&section->entries) {
+ entry = list_entry(item, struct cfg_entry, next);
+ if (0 == strcasecmp(entry->name,ename)) {
+ e_last = entry;
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+static struct cfg_entry*
+cfg_get_entry(struct cfg_section *section, char *ename)
+{
+ struct cfg_entry *entry;
+
+ entry = cfg_find_entry(section,ename);
+ if (NULL == entry) {
+ entry = malloc(sizeof(*entry));
+ memset(entry,0,sizeof(*entry));
+ entry->name = strdup(ename);
+ list_add_tail(&entry->next,&section->entries);
+ }
+ e_last = entry;
+ return entry;
+}
+
+static void
+cfg_set_entry(struct cfg_section *section, char *name, const char *value)
+{
+ struct cfg_entry *entry;
+
+ entry = cfg_get_entry(section,name);
+ if (entry->value)
+ free(entry->value);
+ entry->value = strdup(value);
+}
+
+static struct cfg_section*
+cfg_get_sec(char *dname, char *sname)
+{
+ struct cfg_domain *domain;
+
+ domain = cfg_find_domain(dname);
+ if (NULL == domain)
+ return NULL;
+ return cfg_find_section(domain,sname);
+}
+
+static struct cfg_entry*
+cfg_get_ent(char *dname, char *sname, char *ename)
+{
+ struct cfg_section *section;
+
+ section = cfg_get_sec(dname,sname);
+ if (NULL == section)
+ return NULL;
+ return cfg_find_entry(section,ename);
+}
+
+/* ------------------------------------------------------------------------ */
+/* import / add / del config data */
+
+int
+cfg_parse_file(char *dname, char *filename)
+{
+ struct cfg_domain *domain = NULL;
+ struct cfg_section *section = NULL;
+ char line[256],tag[64],value[192];
+ FILE *fp;
+ int nr;
+
+ if (NULL == (fp = fopen(filename,"r")))
+ return -1;
+
+ nr = 0;
+ domain = cfg_get_domain(dname);
+ while (NULL != fgets(line,255,fp)) {
+ nr++;
+ if (1 == sscanf(line,"# include \"%[^\"]\"",value)) {
+ /* includes */
+ char *h,*inc;
+ inc = malloc(strlen(filename)+strlen(value));
+ strcpy(inc,filename);
+ h = strrchr(inc,'/');
+ if (h)
+ h++;
+ else
+ h = inc;
+ strcpy(h,value);
+ cfg_parse_file(dname,inc);
+ free(inc);
+ continue;
+ }
+ if (line[0] == '\n' || line[0] == '#' || line[0] == '%')
+ continue;
+ if (1 == sscanf(line,"[%99[^]]]",value)) {
+ /* [section] */
+ section = cfg_get_section(domain,value);
+ } else if (2 == sscanf(line," %63[^= ] = %191[^\n]",tag,value)) {
+ /* foo = bar */
+ if (NULL == section) {
+ fprintf(stderr,"%s:%d: error: no section\n",filename,nr);
+ } else {
+ char *c = value + strlen(value)-1;
+ while (c > value && (*c == ' ' || *c == '\t'))
+ *(c--) = 0;
+ cfg_set_entry(section,tag,value);
+ }
+ } else {
+ /* Huh ? */
+ fprintf(stderr,"%s:%d: syntax error\n",filename,nr);
+ }
+ }
+ fclose(fp);
+ return 0;
+}
+
+void
+cfg_set_str(char *dname, char *sname, char *ename, const char *value)
+{
+ struct cfg_domain *domain = NULL;
+ struct cfg_section *section = NULL;
+
+ domain = cfg_get_domain(dname);
+ section = cfg_get_section(domain,sname);
+ cfg_set_entry(section,ename,value);
+}
+
+void
+cfg_set_int(char *dname, char *sname, char *ename, int value)
+{
+ char str[32];
+
+ snprintf(str,sizeof(str),"%d",value);
+ cfg_set_str(dname,sname,ename,str);
+}
+
+void
+cfg_set_bool(char *dname, char *sname, char *ename, int value)
+{
+ cfg_set_str(dname,sname,ename, value ? "true" : "false");
+}
+
+void
+cfg_del_section(char *dname, char *sname)
+{
+ struct cfg_section *section;
+ struct cfg_entry *entry;
+
+ section= cfg_get_sec(dname,sname);
+ if (!section)
+ return;
+ list_del(&section->next);
+ while (!list_empty(&section->entries)) {
+ entry = list_entry(section->entries.next, struct cfg_entry, next);
+ list_del(&entry->next);
+ free(entry->name);
+ free(entry->value);
+ free(entry);
+ }
+ s_last = NULL;
+ e_last = NULL;
+ free(section->name);
+ free(section);
+}
+
+void
+cfg_del_entry(char *dname, char *sname, char *ename)
+{
+ struct cfg_entry *entry;
+
+ entry = cfg_get_ent(dname,sname,ename);
+ if (!entry)
+ return;
+ e_last = NULL;
+ list_del(&entry->next);
+ free(entry->name);
+ free(entry->value);
+ free(entry);
+}
+
+void
+cfg_parse_cmdline(int *argc, char **argv, struct cfg_cmdline *opt)
+{
+ int i,j,o,shift,len;
+
+ for (i = 1; i < *argc;) {
+ if (argv[i][0] != '-') {
+ i++;
+ continue;
+ }
+
+ for (shift = 0, o = 0;
+ 0 == shift && opt[o].cmdline != NULL;
+ o++) {
+ len = strlen(opt[o].cmdline);
+ if (opt[o].yesno && 0 == strcmp(argv[i]+1,opt[o].cmdline)) {
+ /* yesno: -foo */
+ cfg_set_bool(opt[o].option.domain,
+ opt[o].option.section,
+ opt[o].option.entry,
+ 1);
+ shift = 1;
+
+ } else if (opt[o].yesno &&
+ 0 == strncmp(argv[i]+1,"no",2) &&
+ 0 == strcmp(argv[i]+3,opt[o].cmdline)) {
+ /* yesno: -nofoo */
+ cfg_set_bool(opt[o].option.domain,
+ opt[o].option.section,
+ opt[o].option.entry,
+ 0);
+ shift = 1;
+
+ } else if (opt[o].needsarg &&
+ 0 == strcmp(argv[i]+1,opt[o].cmdline) &&
+ i+1 < *argc) {
+ /* arg: -foo bar */
+ cfg_set_str(opt[o].option.domain,
+ opt[o].option.section,
+ opt[o].option.entry,
+ argv[i+1]);
+ shift = 2;
+
+ } else if (opt[o].needsarg &&
+ 0 == strncmp(argv[i]+1,opt[o].cmdline,len) &&
+ 0 == strncmp(argv[i]+1+len,"=",1)) {
+ /* arg: -foo=bar */
+ cfg_set_str(opt[o].option.domain,
+ opt[o].option.section,
+ opt[o].option.entry,
+ argv[i]+2+len);
+ shift = 1;
+
+ } else if (opt[o].value &&
+ 0 == strcmp(argv[i]+1,opt[o].cmdline)) {
+ /* -foo sets some fixed value */
+ cfg_set_str(opt[o].option.domain,
+ opt[o].option.section,
+ opt[o].option.entry,
+ opt[o].value);
+ shift = 1;
+ }
+ }
+
+ if (shift) {
+ /* remove processed args */
+ for (j = i; j < *argc+1-shift; j++)
+ argv[j] = argv[j+shift];
+ (*argc) -= shift;
+ } else
+ i++;
+ }
+}
+
+void
+cfg_help_cmdline(struct cfg_cmdline *opt, int w1, int w2, int w3)
+{
+ char *val;
+ int o,len;
+
+ for (o = 0; opt[o].cmdline != NULL; o++) {
+ fprintf(stderr,"%*s",w1,"");
+ if (opt[o].yesno) {
+ len = fprintf(stderr,"-(no)%s ",opt[o].cmdline);
+ } else if (opt[o].needsarg) {
+ len = fprintf(stderr,"-%s <arg> ",opt[o].cmdline);
+ } else {
+ len = fprintf(stderr,"-%s ",opt[o].cmdline);
+ }
+ if (len < w2)
+ fprintf(stderr,"%*s",w2-len,"");
+
+ len = fprintf(stderr,"%s ",opt[o].desc);
+ if (len < w3)
+ fprintf(stderr,"%*s",w3-len,"");
+
+ val = cfg_get_str(opt[o].option.domain,
+ opt[o].option.section,
+ opt[o].option.entry);
+ if (val)
+ fprintf(stderr,"[%s]",val);
+ fprintf(stderr,"\n");
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+/* export config data */
+
+static int cfg_mkdir(char *filename)
+{
+ char *h;
+ int rc;
+
+ h = strrchr(filename,'/');
+ if (!h || h == filename)
+ return -1;
+ *h = '\0';
+ rc = mkdir(filename,0777);
+ if (-1 == rc && ENOENT == errno) {
+ cfg_mkdir(filename);
+ rc = mkdir(filename,0777);
+ }
+ if (-1 == rc)
+ fprintf(stderr,"mkdir(%s): %s\n",filename,strerror(errno));
+ *h = '/';
+ return rc;
+}
+
+int
+cfg_write_file(char *dname, char *filename)
+{
+ struct list_head *item1,*item2;
+ struct cfg_domain *domain;
+ struct cfg_section *section;
+ struct cfg_entry *entry;
+ char *bfile, *tfile;
+ int len;
+ FILE *fp;
+
+ len = strlen(filename)+10;
+ bfile = malloc(len);
+ tfile = malloc(len);
+ sprintf(bfile,"%s~",filename);
+ sprintf(tfile,"%s.$$$",filename);
+
+ fp = fopen(tfile,"w");
+ if (NULL == fp && ENOENT == errno) {
+ cfg_mkdir(tfile);
+ fp = fopen(tfile,"w");
+ }
+ if (NULL == fp) {
+ fprintf(stderr,"open(%s): %s\n",tfile,strerror(errno));
+ return -1;
+ }
+
+ domain = cfg_find_domain(dname);
+ if (NULL != domain) {
+ list_for_each(item1,&domain->sections) {
+ section = list_entry(item1, struct cfg_section, next);
+ fprintf(fp,"[%s]\n",section->name);
+ list_for_each(item2,&section->entries) {
+ entry = list_entry(item2, struct cfg_entry, next);
+ fprintf(fp,"%s = %s\n",entry->name,entry->value);
+ }
+ fprintf(fp,"\n");
+ }
+ }
+ fclose(fp);
+
+ if (-1 == unlink(bfile) && ENOENT != errno) {
+ fprintf(stderr,"unlink(%s): %s\n",bfile,strerror(errno));
+ return -1;
+ }
+ if (-1 == rename(filename,bfile) && ENOENT != errno) {
+ fprintf(stderr,"rename(%s,%s): %s\n",filename,bfile,strerror(errno));
+ return -1;
+ }
+ if (-1 == rename(tfile,filename)) {
+ fprintf(stderr,"rename(%s,%s): %s\n",tfile,filename,strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+/* ------------------------------------------------------------------------ */
+/* list / search config data */
+
+char*
+cfg_sections_first(char *dname)
+{
+ struct list_head *item;
+ struct cfg_domain *domain;
+ struct cfg_section *section;
+
+ domain = cfg_find_domain(dname);
+ if (NULL == domain)
+ return NULL;
+
+ item = &domain->sections;
+ if (item->next == &domain->sections)
+ return NULL;
+ section = list_entry(item->next, struct cfg_section, next);
+ s_last = section;
+ e_last = NULL;
+ return section->name;
+}
+
+char*
+cfg_sections_next(char *dname, char *current)
+{
+ struct list_head *item;
+ struct cfg_domain *domain;
+ struct cfg_section *section;
+
+ domain = cfg_find_domain(dname);
+ if (NULL == domain)
+ return NULL;
+ section = cfg_find_section(domain,current);
+ if (NULL == section)
+ return NULL;
+ item = &section->next;
+
+ if (item->next == &domain->sections)
+ return NULL;
+ section = list_entry(item->next, struct cfg_section, next);
+ s_last = section;
+ e_last = NULL;
+ return section->name;
+}
+
+char*
+cfg_sections_prev(char *dname, char *current)
+{
+ struct list_head *item;
+ struct cfg_domain *domain;
+ struct cfg_section *section;
+
+ domain = cfg_find_domain(dname);
+ if (NULL == domain)
+ return NULL;
+ section = cfg_find_section(domain,current);
+ if (NULL == section)
+ return NULL;
+ item = &section->next;
+
+ if (item->prev == &domain->sections)
+ return NULL;
+ section = list_entry(item->prev, struct cfg_section, next);
+ s_last = section;
+ e_last = NULL;
+ return section->name;
+}
+
+int cfg_sections_count(char *dname)
+{
+ struct list_head *item;
+ struct cfg_domain *domain;
+ int count = 0;
+
+ domain = cfg_find_domain(dname);
+ if (NULL != domain)
+ list_for_each(item,&domain->sections)
+ count++;
+ return count;
+}
+
+char* cfg_sections_index(char *dname, int i)
+{
+ struct list_head *item;
+ struct cfg_domain *domain;
+ struct cfg_section *section;
+ int count = 0;
+
+ domain = cfg_find_domain(dname);
+ if (NULL == domain)
+ return NULL;
+
+ list_for_each(item,&domain->sections) {
+ if (i == count) {
+ section = list_entry(item, struct cfg_section, next);
+ s_last = section;
+ e_last = NULL;
+ return section->name;
+ }
+ count++;
+ }
+ return NULL;
+}
+
+char*
+cfg_entries_first(char *dname, char *sname)
+{
+ struct list_head *item;
+ struct cfg_section *section;
+ struct cfg_entry *entry;
+
+ section = cfg_get_sec(dname,sname);
+ if (NULL == section)
+ return NULL;
+
+ item = &section->entries;
+ if (item->next == &section->entries)
+ return NULL;
+ entry = list_entry(item->next, struct cfg_entry, next);
+ e_last = entry;
+ return entry->name;
+}
+
+char*
+cfg_entries_next(char *dname, char *sname, char *current)
+{
+ struct list_head *item;
+ struct cfg_section *section;
+ struct cfg_entry *entry;
+
+ section = cfg_get_sec(dname,sname);
+ if (NULL == section)
+ return NULL;
+ entry = cfg_find_entry(section,current);
+ if (NULL == entry)
+ return NULL;
+ item = &entry->next;
+
+ if (item->next == &section->entries)
+ return NULL;
+ entry = list_entry(item->next, struct cfg_entry, next);
+ e_last = entry;
+ return entry->name;
+}
+
+char*
+cfg_entries_prev(char *dname, char *sname, char *current)
+{
+ struct list_head *item;
+ struct cfg_section *section;
+ struct cfg_entry *entry;
+
+ section = cfg_get_sec(dname,sname);
+ if (NULL == section)
+ return NULL;
+ entry = cfg_find_entry(section,current);
+ if (NULL == entry)
+ return NULL;
+ item = &entry->next;
+
+ if (item->prev == &section->entries)
+ return NULL;
+ entry = list_entry(item->prev, struct cfg_entry, next);
+ e_last = entry;
+ return entry->name;
+}
+
+int cfg_entries_count(char *dname, char *sname)
+{
+ struct list_head *item;
+ struct cfg_section *section;
+ int count = 0;
+
+ section = cfg_get_sec(dname,sname);
+ if (NULL != section)
+ list_for_each(item,&section->entries)
+ count++;
+ return count;
+}
+
+char* cfg_entries_index(char *dname, char *sname, int i)
+{
+ struct list_head *item;
+ struct cfg_section *section;
+ struct cfg_entry *entry;
+ int count = 0;
+
+ section = cfg_get_sec(dname,sname);
+ if (NULL == section)
+ return NULL;
+
+ list_for_each(item,&section->entries) {
+ if (i == count) {
+ entry = list_entry(item, struct cfg_entry, next);
+ e_last = entry;
+ return entry->name;
+ }
+ count++;
+ }
+ return NULL;
+}
+
+char* cfg_search(char *dname, char *sname, char *ename, char *value)
+{
+ struct list_head *item1,*item2;
+ struct cfg_domain *domain;
+ struct cfg_section *section;
+ struct cfg_entry *entry;
+
+ domain = cfg_find_domain(dname);
+ if (NULL == domain)
+ return NULL;
+ list_for_each(item1,&domain->sections) {
+ section = list_entry(item1, struct cfg_section, next);
+ if (sname && 0 != strcasecmp(section->name,sname))
+ continue;
+ if (!ename)
+ return section->name;
+ list_for_each(item2,&section->entries) {
+ entry = list_entry(item2, struct cfg_entry, next);
+ if (0 != strcasecmp(entry->name,ename))
+ continue;
+ if (0 == strcasecmp(entry->value,value))
+ return section->name;
+ }
+ }
+ return NULL;
+}
+
+/* ------------------------------------------------------------------------ */
+/* get config data */
+
+char*
+cfg_get_str(char *dname, char *sname, char *ename)
+{
+ struct cfg_entry *entry;
+
+ entry = cfg_get_ent(dname, sname, ename);
+ if (NULL == entry)
+ return NULL;
+ return entry->value;
+}
+
+int
+cfg_get_int(char *dname, char *sname, char *ename, int def)
+{
+ char *val;
+
+ val = cfg_get_str(dname,sname,ename);
+ if (NULL == val)
+ return def;
+ return atoi(val);
+}
+
+int
+cfg_get_signed_int(char *dname, char *sname, char *ename, unsigned int def)
+{
+ char *val;
+
+ val = cfg_get_str(dname,sname,ename);
+ if (NULL == val)
+ return def;
+ return atoi(val);
+}
+
+float
+cfg_get_float(char *dname, char *sname, char *ename)
+{
+ char *val;
+
+ val = cfg_get_str(dname,sname,ename);
+ if (NULL == val)
+ return -1;
+ return atof(val);
+}
+
+int
+cfg_get_bool(char *dname, char *sname, char *ename, int def)
+{
+ static char *yes[] = { "true", "yes", "on", "1" };
+ char *val;
+ int i;
+ int retval = 0;
+
+ val = cfg_get_str(dname,sname,ename);
+ if (NULL == val)
+ return def;
+ for (i = 0; i < sizeof(yes)/sizeof(yes[0]); i++)
+ if (0 == strcasecmp(val,yes[i]))
+ retval = 1;
+ return retval;
+}
+
+/* ------------------------------------------------------------------------ */
+/* get/set flags */
+
+int cfg_get_sflags(char *dname, char *sname)
+{
+ struct cfg_section *section;
+
+ section = cfg_get_sec(dname, sname);
+ if (NULL == section)
+ return 0;
+ return section->flags;
+}
+
+int cfg_get_eflags(char *dname, char *sname, char *ename)
+{
+ struct cfg_entry *entry;
+
+ entry = cfg_get_ent(dname, sname, ename);
+ if (NULL == entry)
+ return 0;
+ return entry->flags;
+}
+
+int cfg_set_sflags(char *dname, char *sname,
+ unsigned int mask, unsigned int bits)
+{
+ struct cfg_section *section;
+
+ section = cfg_get_sec(dname, sname);
+ if (NULL == section)
+ return 0;
+ section->flags &= ~mask;
+ section->flags |= bits;
+ return section->flags;
+}
+
+int cfg_set_eflags(char *dname, char *sname, char *ename,
+ unsigned int mask, unsigned int bits)
+{
+ struct cfg_entry *entry;
+
+ entry = cfg_get_ent(dname, sname, ename);
+ if (NULL == entry)
+ return 0;
+ entry->flags &= ~mask;
+ entry->flags |= bits;
+ return entry->flags;
+}
diff --git a/parseconfig.h b/parseconfig.h
new file mode 100644
index 0000000..90abbfe
--- /dev/null
+++ b/parseconfig.h
@@ -0,0 +1,68 @@
+#ifndef HAVE_STRCASESTR
+extern char* strcasestr(char *haystack, char *needle);
+#endif
+
+/* config options */
+struct cfg_option {
+ char *domain;
+ char *section;
+ char *entry;
+};
+struct cfg_cmdline {
+ char *cmdline;
+ struct cfg_option option;
+ char *value;
+ char *desc;
+ int needsarg:1;
+ int yesno:1;
+};
+void cfg_parse_cmdline(int *argc, char **argv, struct cfg_cmdline *opt);
+void cfg_help_cmdline(struct cfg_cmdline *opt, int w1, int w2, int w3);
+
+/* file I/O */
+int cfg_parse_file(char *dname, char *filename);
+int cfg_write_file(char *dname, char *filename);
+
+/* update */
+void cfg_set_str(char *dname, char *sname, char *ename, const char *value);
+void cfg_set_int(char *dname, char *sname, char *ename, int value);
+void cfg_set_bool(char *dname, char *sname, char *ename, int value);
+
+void cfg_del_section(char *dname, char *sname);
+void cfg_del_entry(char *dname, char *sname, char *ename);
+
+/* search */
+char* cfg_sections_first(char *dname);
+char* cfg_sections_next(char *dname, char *current);
+char* cfg_sections_prev(char *dname, char *current);
+int cfg_sections_count(char *dname);
+char* cfg_sections_index(char *dname, int i);
+
+char* cfg_entries_first(char *dname, char *sname);
+char* cfg_entries_next(char *dname, char *sname, char *current);
+char* cfg_entries_prev(char *dname, char *sname, char *current);
+int cfg_entries_count(char *dname, char *sname);
+char* cfg_entries_index(char *dname, char *sname, int i);
+
+#define cfg_sections_for_each(dname, item) \
+ for (item = cfg_sections_first(dname); NULL != item; \
+ item = cfg_sections_next(dname,item))
+
+char* cfg_search(char *dname, char *sname, char *ename, char *value);
+
+/* read */
+char* cfg_get_str(char *dname, char *sname, char *ename);
+int cfg_get_int(char *dname, char *sname, char *ename,
+ int def);
+int cfg_get_signed_int(char *dname, char *sname, char *ename,
+ unsigned int def);
+float cfg_get_float(char *dname, char *sname, char *ename);
+int cfg_get_bool(char *dname, char *sname, char *ename,
+ int def);
+
+int cfg_get_sflags(char *dname, char *sname);
+int cfg_get_eflags(char *dname, char *sname, char *ename);
+int cfg_set_sflags(char *dname, char *sname,
+ unsigned int mask, unsigned int bits);
+int cfg_set_eflags(char *dname, char *sname, char *ename,
+ unsigned int mask, unsigned int bits);
diff --git a/rd/Makefile b/rd/Makefile
new file mode 100644
index 0000000..4e33e30
--- /dev/null
+++ b/rd/Makefile
@@ -0,0 +1,2 @@
+default:
+ cd ..; $(MAKE)
diff --git a/rd/magick.c b/rd/magick.c
new file mode 100644
index 0000000..f17e5e3
--- /dev/null
+++ b/rd/magick.c
@@ -0,0 +1,85 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <magick/api.h>
+
+#include "loader.h"
+#include "viewer.h"
+
+extern char *binary;
+
+static int first = 1;
+static ExceptionInfo exception;
+
+struct magick_state {
+ ImageInfo *image_info;
+ Image *image;
+};
+
+static void*
+magick_init(FILE *fp, char *filename, int *width, int *height)
+{
+ struct magick_state *h;
+
+ /* libmagick wants a filename */
+ fclose(fp);
+
+ if (first) {
+ /* init library first time */
+ MagickIncarnate(binary);
+ GetExceptionInfo(&exception);
+ first = 0;
+ }
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ h->image_info=CloneImageInfo(NULL);
+ strcpy(h->image_info->filename,filename);
+ h->image = ReadImage(h->image_info,&exception);
+ if (NULL == h->image) {
+ MagickError(exception.severity,exception.reason,exception.description);
+ goto oops;
+ }
+
+ *width = h->image->rows;
+ *height = h->image->columns;
+ return h;
+
+ oops:
+ if (h->image)
+ DestroyImage(h->image);
+ if (h->image_info)
+ DestroyImageInfo(h->image_info);
+ free(h);
+ return NULL;
+}
+
+static void
+magick_read(unsigned char *dst, int line, void *data)
+{
+ struct magick_state *h = data;
+ DispatchImage (h->image,0,line,h->image->columns, 1,
+ "RGB", 0, data);
+}
+
+static void
+magick_done(void *data)
+{
+ struct magick_state *h = data;
+
+ DestroyImageInfo(h->image_info);
+ DestroyImage(h->image);
+ free(h);
+}
+
+static struct ida_loader magick_loader = {
+ name: "libmagick",
+ init: magick_init,
+ read: magick_read,
+ done: magick_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&magick_loader);
+}
diff --git a/rd/read-bmp.c b/rd/read-bmp.c
new file mode 100644
index 0000000..30b5574
--- /dev/null
+++ b/rd/read-bmp.c
@@ -0,0 +1,216 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#ifdef HAVE_ENDIAN_H
+# include <endian.h>
+#endif
+
+#include "readers.h"
+
+/* ---------------------------------------------------------------------- */
+
+typedef unsigned int uint32;
+typedef unsigned short uint16;
+
+/* bitmap files are little endian */
+#if BYTE_ORDER == LITTLE_ENDIAN
+# define le16_to_cpu(x) (x)
+# define le32_to_cpu(x) (x)
+#elif BYTE_ORDER == BIG_ENDIAN
+# define le16_to_cpu(x) (((x>>8) & 0x00ff) |\
+ ((x<<8) & 0xff00))
+# define le32_to_cpu(x) (((x>>24) & 0x000000ff) |\
+ ((x>>8) & 0x0000ff00) |\
+ ((x<<8) & 0x00ff0000) |\
+ ((x<<24) & 0xff000000))
+#else
+# error "Oops: unknown byte order"
+#endif
+
+/* ---------------------------------------------------------------------- */
+/* load */
+
+struct bmp_hdr {
+ uint32 foobar;
+
+ uint32 size; /* == BitMapInfoHeader */
+ uint32 width;
+ uint32 height;
+ uint16 planes;
+ uint16 bit_cnt;
+ char compression[4];
+ uint32 image_size;
+ uint32 xpels_meter;
+ uint32 ypels_meter;
+ uint32 num_colors; /* used colors */
+ uint32 imp_colors; /* important colors */
+ /* may be more for some codecs */
+};
+
+struct bmp_cmap {
+ unsigned char blue;
+ unsigned char green;
+ unsigned char red;
+ unsigned char unused;
+};
+
+struct bmp_state {
+ struct bmp_hdr hdr;
+ struct bmp_cmap cmap[256];
+ FILE *fp;
+};
+
+static void*
+bmp_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail)
+{
+ struct bmp_state *h;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+ h->fp = fp;
+
+ fseek(fp,10,SEEK_SET);
+ fread(&h->hdr,sizeof(struct bmp_hdr),1,fp);
+
+#if BYTE_ORDER == BIG_ENDIAN
+ h->hdr.foobar = le32_to_cpu(h->hdr.foobar);
+ h->hdr.size = le32_to_cpu(h->hdr.size);
+ h->hdr.width = le32_to_cpu(h->hdr.width);
+ h->hdr.height = le32_to_cpu(h->hdr.height);
+
+ h->hdr.planes = le16_to_cpu(h->hdr.planes);
+ h->hdr.bit_cnt = le16_to_cpu(h->hdr.bit_cnt);
+
+ h->hdr.image_size = le32_to_cpu(h->hdr.image_size);
+ h->hdr.xpels_meter = le32_to_cpu(h->hdr.xpels_meter);
+ h->hdr.ypels_meter = le32_to_cpu(h->hdr.ypels_meter);
+ h->hdr.num_colors = le32_to_cpu(h->hdr.num_colors);
+ h->hdr.imp_colors = le32_to_cpu(h->hdr.imp_colors);
+#endif
+
+ if (debug)
+ fprintf(stderr,"bmp: hdr=%d size=%dx%d planes=%d"
+ " bits=%d size=%d res=%dx%d colors=%d/%d | %d\n",
+ h->hdr.size,h->hdr.width,h->hdr.height,
+ h->hdr.planes,h->hdr.bit_cnt,h->hdr.image_size,
+ h->hdr.xpels_meter,h->hdr.ypels_meter,
+ h->hdr.num_colors,h->hdr.imp_colors,h->hdr.foobar);
+ if (h->hdr.bit_cnt != 1 &&
+ h->hdr.bit_cnt != 4 &&
+ h->hdr.bit_cnt != 8 &&
+ h->hdr.bit_cnt != 24) {
+ fprintf(stderr,"bmp: can't handle depth [%d]\n",h->hdr.bit_cnt);
+ goto oops;
+ }
+ if (h->hdr.compression[0] || h->hdr.compression[1] ||
+ h->hdr.compression[2] || h->hdr.compression[3]) {
+ fprintf(stderr,"bmp: can't handle compressed bitmaps [%c%c%c%c]\n",
+ h->hdr.compression[0],
+ h->hdr.compression[1],
+ h->hdr.compression[2],
+ h->hdr.compression[3]);
+ goto oops;
+ }
+
+ if (0 == h->hdr.num_colors && h->hdr.bit_cnt <= 8)
+ h->hdr.num_colors = (1 << h->hdr.bit_cnt);
+ if (h->hdr.num_colors > 256)
+ h->hdr.num_colors = 256;
+ if (h->hdr.num_colors) {
+ fseek(fp,14+h->hdr.size,SEEK_SET);
+ fread(&h->cmap,sizeof(struct bmp_cmap),h->hdr.num_colors,fp);
+ }
+
+ i->width = h->hdr.width;
+ i->height = h->hdr.height;
+ if (h->hdr.xpels_meter)
+ i->dpi = res_m_to_inch(h->hdr.xpels_meter);
+ i->npages = 1;
+ return h;
+
+ oops:
+ free(h);
+ return NULL;
+}
+
+static void
+bmp_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct bmp_state *h = data;
+ unsigned int ll,y,x,pixel,byte = 0;
+
+ ll = (((h->hdr.width * h->hdr.bit_cnt + 31) & ~0x1f) >> 3);
+ y = h->hdr.height - line - 1;
+ fseek(h->fp,h->hdr.foobar + y * ll,SEEK_SET);
+
+ switch (h->hdr.bit_cnt) {
+ case 1:
+ for (x = 0; x < h->hdr.width; x++) {
+ if (0 == (x & 0x07))
+ byte = fgetc(h->fp);
+ pixel = byte & (0x80 >> (x & 0x07)) ? 1 : 0;
+ *(dst++) = h->cmap[pixel].red;
+ *(dst++) = h->cmap[pixel].green;
+ *(dst++) = h->cmap[pixel].blue;
+ }
+ break;
+ case 4:
+ for (x = 0; x < h->hdr.width; x++) {
+ if (x & 1) {
+ pixel = byte & 0xf;
+ } else {
+ byte = fgetc(h->fp);
+ pixel = byte >> 4;
+ }
+ *(dst++) = h->cmap[pixel].red;
+ *(dst++) = h->cmap[pixel].green;
+ *(dst++) = h->cmap[pixel].blue;
+ }
+ break;
+ case 8:
+ for (x = 0; x < h->hdr.width; x++) {
+ pixel = fgetc(h->fp);
+ *(dst++) = h->cmap[pixel].red;
+ *(dst++) = h->cmap[pixel].green;
+ *(dst++) = h->cmap[pixel].blue;
+ }
+ break;
+ case 24:
+ for (x = 0; x < h->hdr.width; x++) {
+ dst[2] = fgetc(h->fp);
+ dst[1] = fgetc(h->fp);
+ dst[0] = fgetc(h->fp);
+ dst += 3;
+ }
+ break;
+ default:
+ memset(dst,128,h->hdr.width*3);
+ break;
+ }
+}
+
+static void
+bmp_done(void *data)
+{
+ struct bmp_state *h = data;
+
+ fclose(h->fp);
+ free(h);
+}
+
+static struct ida_loader bmp_loader = {
+ magic: "BM",
+ moff: 0,
+ mlen: 2,
+ name: "bmp",
+ init: bmp_init,
+ read: bmp_read,
+ done: bmp_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&bmp_loader);
+}
diff --git a/rd/read-gif.c b/rd/read-gif.c
new file mode 100644
index 0000000..b841dc7
--- /dev/null
+++ b/rd/read-gif.c
@@ -0,0 +1,220 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gif_lib.h>
+
+#include "readers.h"
+
+struct gif_state {
+ FILE *infile;
+ GifFileType *gif;
+ GifPixelType *row;
+ GifPixelType *il;
+ int w,h;
+};
+
+static GifRecordType
+gif_fileread(struct gif_state *h)
+{
+ GifRecordType RecordType;
+ GifByteType *Extension;
+ int ExtCode, rc;
+ char *type;
+
+ for (;;) {
+ if (GIF_ERROR == DGifGetRecordType(h->gif,&RecordType)) {
+ if (debug)
+ fprintf(stderr,"gif: DGifGetRecordType failed\n");
+ PrintGifError();
+ return -1;
+ }
+ switch (RecordType) {
+ case IMAGE_DESC_RECORD_TYPE:
+ if (debug)
+ fprintf(stderr,"gif: IMAGE_DESC_RECORD_TYPE found\n");
+ return RecordType;
+ case EXTENSION_RECORD_TYPE:
+ if (debug)
+ fprintf(stderr,"gif: EXTENSION_RECORD_TYPE found\n");
+ for (rc = DGifGetExtension(h->gif,&ExtCode,&Extension);
+ NULL != Extension;
+ rc = DGifGetExtensionNext(h->gif,&Extension)) {
+ if (rc == GIF_ERROR) {
+ if (debug)
+ fprintf(stderr,"gif: DGifGetExtension failed\n");
+ PrintGifError();
+ return -1;
+ }
+ if (debug) {
+ switch (ExtCode) {
+ case COMMENT_EXT_FUNC_CODE: type="comment"; break;
+ case GRAPHICS_EXT_FUNC_CODE: type="graphics"; break;
+ case PLAINTEXT_EXT_FUNC_CODE: type="plaintext"; break;
+ case APPLICATION_EXT_FUNC_CODE: type="appl"; break;
+ default: type="???"; break;
+ }
+ fprintf(stderr,"gif: extcode=0x%x [%s]\n",ExtCode,type);
+ }
+ }
+ break;
+ case TERMINATE_RECORD_TYPE:
+ if (debug)
+ fprintf(stderr,"gif: TERMINATE_RECORD_TYPE found\n");
+ return RecordType;
+ default:
+ if (debug)
+ fprintf(stderr,"gif: unknown record type [%d]\n",RecordType);
+ return -1;
+ }
+ }
+}
+
+#if 0
+static void
+gif_skipimage(struct gif_state *h)
+{
+ unsigned char *line;
+ int i;
+
+ if (debug)
+ fprintf(stderr,"gif: skipping image record ...\n");
+ DGifGetImageDesc(h->gif);
+ line = malloc(h->gif->SWidth);
+ for (i = 0; i < h->gif->SHeight; i++)
+ DGifGetLine(h->gif, line, h->gif->SWidth);
+ free(line);
+}
+#endif
+
+static void*
+gif_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *info, int thumbnail)
+{
+ struct gif_state *h;
+ GifRecordType RecordType;
+ int i, image = 0;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ h->infile = fp;
+ h->gif = DGifOpenFileHandle(fileno(fp));
+ h->row = malloc(h->gif->SWidth * sizeof(GifPixelType));
+
+ while (0 == image) {
+ RecordType = gif_fileread(h);
+ switch (RecordType) {
+ case IMAGE_DESC_RECORD_TYPE:
+ if (GIF_ERROR == DGifGetImageDesc(h->gif)) {
+ if (debug)
+ fprintf(stderr,"gif: DGifGetImageDesc failed\n");
+ PrintGifError();
+ }
+ if (NULL == h->gif->SColorMap &&
+ NULL == h->gif->Image.ColorMap) {
+ if (debug)
+ fprintf(stderr,"gif: oops: no colormap found\n");
+ goto oops;
+ }
+#if 0
+ info->width = h->w = h->gif->SWidth;
+ info->height = h->h = h->gif->SHeight;
+#else
+ info->width = h->w = h->gif->Image.Width;
+ info->height = h->h = h->gif->Image.Height;
+#endif
+ info->npages = 1;
+ image = 1;
+ if (debug)
+ fprintf(stderr,"gif: reading image record ...\n");
+ if (h->gif->Image.Interlace) {
+ if (debug)
+ fprintf(stderr,"gif: interlaced\n");
+ h->il = malloc(h->w * h->h * sizeof(GifPixelType));
+ for (i = 0; i < h->h; i += 8)
+ DGifGetLine(h->gif, h->il + h->w*i,h->w);
+ for (i = 4; i < h->gif->SHeight; i += 8)
+ DGifGetLine(h->gif, h->il + h->w*i,h->w);
+ for (i = 2; i < h->gif->SHeight; i += 4)
+ DGifGetLine(h->gif, h->il + h->w*i,h->w);
+ }
+ break;
+ case TERMINATE_RECORD_TYPE:
+ default:
+ goto oops;
+ }
+ }
+ if (0 == info->width || 0 == info->height)
+ goto oops;
+
+ if (debug)
+ fprintf(stderr,"gif: s=%dx%d i=%dx%d\n",
+ h->gif->SWidth,h->gif->SHeight,
+ h->gif->Image.Width,h->gif->Image.Height);
+ return h;
+
+ oops:
+ if (debug)
+ fprintf(stderr,"gif: fatal error, aborting\n");
+ DGifCloseFile(h->gif);
+ fclose(h->infile);
+ free(h->row);
+ free(h);
+ return NULL;
+}
+
+static void
+gif_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct gif_state *h = data;
+ GifColorType *cmap;
+ int x;
+
+ if (h->gif->Image.Interlace) {
+ if (line % 2) {
+ DGifGetLine(h->gif, h->row, h->w);
+ } else {
+ memcpy(h->row, h->il + h->w * line, h->w);
+ }
+ } else {
+ DGifGetLine(h->gif, h->row, h->w);
+ }
+ cmap = h->gif->Image.ColorMap ?
+ h->gif->Image.ColorMap->Colors : h->gif->SColorMap->Colors;
+ for (x = 0; x < h->w; x++) {
+ dst[0] = cmap[h->row[x]].Red;
+ dst[1] = cmap[h->row[x]].Green;
+ dst[2] = cmap[h->row[x]].Blue;
+ dst += 3;
+ }
+}
+
+static void
+gif_done(void *data)
+{
+ struct gif_state *h = data;
+
+ if (debug)
+ fprintf(stderr,"gif: done, cleaning up\n");
+ DGifCloseFile(h->gif);
+ fclose(h->infile);
+ if (h->il)
+ free(h->il);
+ free(h->row);
+ free(h);
+}
+
+static struct ida_loader gif_loader = {
+ magic: "GIF",
+ moff: 0,
+ mlen: 3,
+ name: "libungif",
+ init: gif_init,
+ read: gif_read,
+ done: gif_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&gif_loader);
+}
diff --git a/rd/read-jpeg.c b/rd/read-jpeg.c
new file mode 100644
index 0000000..d12fb40
--- /dev/null
+++ b/rd/read-jpeg.c
@@ -0,0 +1,187 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <jpeglib.h>
+
+#include <libexif/exif-data.h>
+
+#include "readers.h"
+#include "misc.h"
+
+/* ---------------------------------------------------------------------- */
+/* load */
+
+struct jpeg_state {
+ FILE * infile; /* source file */
+
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ JSAMPARRAY buffer; /* Output row buffer */
+ int row_stride,linelength; /* physical row width in output buffer */
+ unsigned char *image,*ptr;
+
+ /* thumbnail */
+ unsigned char *thumbnail;
+ unsigned int tpos, tsize;
+};
+
+/* ---------------------------------------------------------------------- */
+/* data source manager for thumbnail images */
+
+static void thumbnail_src_init(struct jpeg_decompress_struct *cinfo)
+{
+ struct jpeg_state *h = container_of(cinfo, struct jpeg_state, cinfo);
+ cinfo->src->next_input_byte = h->thumbnail;
+ cinfo->src->bytes_in_buffer = h->tsize;
+}
+
+static int thumbnail_src_fill(struct jpeg_decompress_struct *cinfo)
+{
+ fprintf(stderr,"jpeg: panic: no more thumbnail input data\n");
+ exit(1);
+}
+
+static void thumbnail_src_skip(struct jpeg_decompress_struct *cinfo,
+ long num_bytes)
+{
+ cinfo->src->next_input_byte += num_bytes;
+}
+
+static void thumbnail_src_term(struct jpeg_decompress_struct *cinfo)
+{
+ /* nothing */
+}
+
+static struct jpeg_source_mgr thumbnail_mgr = {
+ .init_source = thumbnail_src_init,
+ .fill_input_buffer = thumbnail_src_fill,
+ .skip_input_data = thumbnail_src_skip,
+ .resync_to_restart = jpeg_resync_to_restart,
+ .term_source = thumbnail_src_term,
+};
+
+/* ---------------------------------------------------------------------- */
+/* jpeg loader */
+
+static void*
+jpeg_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail)
+{
+ struct jpeg_state *h;
+ jpeg_saved_marker_ptr mark;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+ h->infile = fp;
+
+ h->cinfo.err = jpeg_std_error(&h->jerr);
+ jpeg_create_decompress(&h->cinfo);
+ jpeg_save_markers(&h->cinfo, JPEG_COM, 0xffff); /* comment */
+ jpeg_save_markers(&h->cinfo, JPEG_APP0+1, 0xffff); /* EXIF */
+ jpeg_stdio_src(&h->cinfo, h->infile);
+ jpeg_read_header(&h->cinfo, TRUE);
+
+ for (mark = h->cinfo.marker_list; NULL != mark; mark = mark->next) {
+ switch (mark->marker) {
+ case JPEG_COM:
+ if (debug)
+ fprintf(stderr,"jpeg: comment found (COM marker) [%.*s]\n",
+ (int)mark->data_length, mark->data);
+ load_add_extra(i,EXTRA_COMMENT,mark->data,mark->data_length);
+ break;
+ case JPEG_APP0 +1:
+ if (debug)
+ fprintf(stderr,"jpeg: exif data found (APP1 marker)\n");
+ load_add_extra(i,EXTRA_COMMENT,mark->data,mark->data_length);
+
+ if (thumbnail) {
+ ExifData *ed;
+
+ ed = exif_data_new_from_data(mark->data,mark->data_length);
+ if (ed->data &&
+ ed->data[0] == 0xff &&
+ ed->data[1] == 0xd8) {
+ if (debug)
+ fprintf(stderr,"jpeg: exif thumbnail found\n");
+
+ /* save away thumbnail data */
+ h->thumbnail = malloc(ed->size);
+ h->tsize = ed->size;
+ memcpy(h->thumbnail,ed->data,ed->size);
+ }
+ exif_data_unref(ed);
+ }
+ break;
+ }
+ }
+
+ if (h->thumbnail) {
+ /* save image size */
+ i->thumbnail = 1;
+ i->real_width = h->cinfo.image_width;
+ i->real_height = h->cinfo.image_height;
+
+ /* re-setup jpeg */
+ jpeg_destroy_decompress(&h->cinfo);
+ fclose(h->infile);
+ h->infile = NULL;
+ jpeg_create_decompress(&h->cinfo);
+ h->cinfo.src = &thumbnail_mgr;
+ jpeg_read_header(&h->cinfo, TRUE);
+ }
+
+ h->cinfo.out_color_space = JCS_RGB;
+ jpeg_start_decompress(&h->cinfo);
+ i->width = h->cinfo.image_width;
+ i->height = h->cinfo.image_height;
+ i->npages = 1;
+ switch (h->cinfo.density_unit) {
+ case 0: /* unknown */
+ break;
+ case 1: /* dot per inch */
+ i->dpi = h->cinfo.X_density;
+ break;
+ case 2: /* dot per cm */
+ i->dpi = res_cm_to_inch(h->cinfo.X_density);
+ break;
+ }
+
+ return h;
+}
+
+static void
+jpeg_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct jpeg_state *h = data;
+ JSAMPROW row = dst;
+ jpeg_read_scanlines(&h->cinfo, &row, 1);
+}
+
+static void
+jpeg_done(void *data)
+{
+ struct jpeg_state *h = data;
+ jpeg_destroy_decompress(&h->cinfo);
+ if (h->infile)
+ fclose(h->infile);
+ if (h->thumbnail)
+ free(h->thumbnail);
+ free(h);
+}
+
+struct ida_loader jpeg_loader = {
+ magic: "\xff\xd8",
+ moff: 0,
+ mlen: 2,
+ name: "libjpeg",
+ init: jpeg_init,
+ read: jpeg_read,
+ done: jpeg_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&jpeg_loader);
+}
diff --git a/rd/read-pcd.c b/rd/read-pcd.c
new file mode 100644
index 0000000..40c43d5
--- /dev/null
+++ b/rd/read-pcd.c
@@ -0,0 +1,78 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "pcd.h"
+
+#include "readers.h"
+
+extern int pcd_res;
+
+/* ---------------------------------------------------------------------- */
+/* load */
+
+struct pcd_state {
+ struct PCD_IMAGE img;
+ int left,top,width,height;
+};
+
+static void*
+pcd_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail)
+{
+ struct pcd_state *h;
+
+ fclose(fp);
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ if (0 != pcd_open(&h->img, filename))
+ goto oops;
+ if (-1 == pcd_select(&h->img, thumbnail ? 1 : pcd_res,
+ 0,0,0, pcd_get_rot(&h->img, 0),
+ &h->left, &h->top, &h->width, &h->height))
+ goto oops;
+ if (-1 == pcd_decode(&h->img))
+ goto oops;
+
+ i->width = h->width;
+ i->height = h->height;
+ i->npages = 1;
+ return h;
+
+ oops:
+ free(h);
+ return NULL;
+}
+
+static void
+pcd_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct pcd_state *h = data;
+
+ pcd_get_image_line(&h->img, line, dst, PCD_TYPE_RGB, 0);
+}
+
+static void
+pcd_done(void *data)
+{
+ struct pcd_state *h = data;
+
+ pcd_close(&h->img);
+ free(h);
+}
+
+static struct ida_loader pcd_loader = {
+ magic: "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff",
+ moff: 0,
+ mlen: 16,
+ name: "libpcd",
+ init: pcd_init,
+ read: pcd_read,
+ done: pcd_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&pcd_loader);
+}
diff --git a/rd/read-png.c b/rd/read-png.c
new file mode 100644
index 0000000..c4f82d2
--- /dev/null
+++ b/rd/read-png.c
@@ -0,0 +1,164 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <png.h>
+
+#include "readers.h"
+
+static const char *ct[] = {
+ "gray", "X1", "rgb", "palette",
+ "graya", "X5", "rgba", "X7",
+};
+
+struct png_state {
+ FILE *infile;
+ png_structp png;
+ png_infop info;
+ png_bytep image;
+ png_uint_32 w,h;
+ int color_type;
+};
+
+static void*
+png_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail)
+{
+ struct png_state *h;
+ int bit_depth, interlace_type;
+ int pass, number_passes;
+ unsigned int y;
+ png_uint_32 resx, resy;
+ png_color_16 *file_bg, my_bg = {
+ .red = 192,
+ .green = 192,
+ .blue = 192,
+ .gray = 192,
+ };
+ int unit;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ h->infile = fp;
+
+ h->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ NULL, NULL, NULL);
+ if (NULL == h->png)
+ goto oops;
+ h->info = png_create_info_struct(h->png);
+ if (NULL == h->info)
+ goto oops;
+
+ png_init_io(h->png, h->infile);
+ png_read_info(h->png, h->info);
+ png_get_IHDR(h->png, h->info, &h->w, &h->h,
+ &bit_depth,&h->color_type,&interlace_type, NULL,NULL);
+ png_get_pHYs(h->png, h->info, &resx, &resy, &unit);
+ i->width = h->w;
+ i->height = h->h;
+ if (PNG_RESOLUTION_METER == unit)
+ i->dpi = res_m_to_inch(resx);
+ if (debug)
+ fprintf(stderr,"png: color_type=%s #1\n",ct[h->color_type]);
+ i->npages = 1;
+
+ png_set_packing(h->png);
+ if (bit_depth == 16)
+ png_set_strip_16(h->png);
+ if (h->color_type == PNG_COLOR_TYPE_PALETTE)
+ png_set_palette_to_rgb(h->png);
+ if (h->color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+ png_set_gray_1_2_4_to_8(h->png);
+
+ if (png_get_bKGD(h->png, h->info, &file_bg)) {
+ png_set_background(h->png,file_bg,PNG_BACKGROUND_GAMMA_FILE,1,1.0);
+ } else {
+ png_set_background(h->png,&my_bg,PNG_BACKGROUND_GAMMA_SCREEN,0,1.0);
+ }
+
+ number_passes = png_set_interlace_handling(h->png);
+ png_read_update_info(h->png, h->info);
+
+ h->color_type = png_get_color_type(h->png, h->info);
+ if (debug)
+ fprintf(stderr,"png: color_type=%s #2\n",ct[h->color_type]);
+
+ h->image = malloc(i->width * i->height * 4);
+
+ for (pass = 0; pass < number_passes-1; pass++) {
+ if (debug)
+ fprintf(stderr,"png: pass #%d\n",pass);
+ for (y = 0; y < i->height; y++) {
+ png_bytep row = h->image + y * i->width * 4;
+ png_read_rows(h->png, &row, NULL, 1);
+ }
+ }
+
+ return h;
+
+ oops:
+ if (h->image)
+ free(h->image);
+ if (h->png)
+ png_destroy_read_struct(&h->png, NULL, NULL);
+ fclose(h->infile);
+ free(h);
+ return NULL;
+}
+
+static void
+png_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct png_state *h = data;
+
+ png_bytep row = h->image + line * h->w * 4;
+ switch (h->color_type) {
+ case PNG_COLOR_TYPE_GRAY:
+ png_read_rows(h->png, &row, NULL, 1);
+ load_gray(dst,row,h->w);
+ break;
+ case PNG_COLOR_TYPE_RGB:
+ png_read_rows(h->png, &row, NULL, 1);
+ memcpy(dst,row,3*h->w);
+ break;
+ case PNG_COLOR_TYPE_RGB_ALPHA:
+ png_read_rows(h->png, &row, NULL, 1);
+ load_rgba(dst,row,h->w);
+ break;
+ case PNG_COLOR_TYPE_GRAY_ALPHA:
+ png_read_rows(h->png, &row, NULL, 1);
+ load_graya(dst,row,h->w);
+ break;
+ default:
+ /* shouldn't happen */
+ fprintf(stderr,"Oops: %s:%d\n",__FILE__,__LINE__);
+ exit(1);
+ }
+}
+
+static void
+png_done(void *data)
+{
+ struct png_state *h = data;
+
+ free(h->image);
+ png_destroy_read_struct(&h->png, &h->info, NULL);
+ fclose(h->infile);
+ free(h);
+}
+
+static struct ida_loader png_loader = {
+ magic: "\x89PNG",
+ moff: 0,
+ mlen: 4,
+ name: "libpng",
+ init: png_init,
+ read: png_read,
+ done: png_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&png_loader);
+}
diff --git a/rd/read-ppm.c b/rd/read-ppm.c
new file mode 100644
index 0000000..b7eb326
--- /dev/null
+++ b/rd/read-ppm.c
@@ -0,0 +1,110 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "readers.h"
+
+/* ---------------------------------------------------------------------- */
+/* load */
+
+struct ppm_state {
+ FILE *infile;
+ int width,height;
+ unsigned char *row;
+};
+
+static void*
+pnm_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail)
+{
+ struct ppm_state *h;
+ char line[1024];
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ h->infile = fp;
+ fgets(line,sizeof(line),fp); /* Px */
+ fgets(line,sizeof(line),fp); /* width height */
+ while ('#' == line[0])
+ fgets(line,sizeof(line),fp); /* skip comments */
+ sscanf(line,"%d %d",&h->width,&h->height);
+ fgets(line,sizeof(line),fp); /* ??? */
+ if (0 == h->width || 0 == h->height)
+ goto oops;
+ i->width = h->width;
+ i->height = h->height;
+ i->npages = 1;
+ h->row = malloc(h->width*3);
+
+ return h;
+
+ oops:
+ fclose(fp);
+ free(h);
+ return NULL;
+}
+
+static void
+ppm_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct ppm_state *h = data;
+
+ fread(dst,h->width,3,h->infile);
+}
+
+static void
+pgm_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct ppm_state *h = data;
+ unsigned char *src;
+ int x;
+
+ fread(h->row,h->width,1,h->infile);
+ src = h->row;
+ for (x = 0; x < h->width; x++) {
+ dst[0] = src[0];
+ dst[1] = src[0];
+ dst[2] = src[0];
+ dst += 3;
+ src += 1;
+ }
+}
+
+static void
+pnm_done(void *data)
+{
+ struct ppm_state *h = data;
+
+ fclose(h->infile);
+ free(h->row);
+ free(h);
+}
+
+struct ida_loader ppm_loader = {
+ magic: "P6",
+ moff: 0,
+ mlen: 2,
+ name: "ppm parser",
+ init: pnm_init,
+ read: ppm_read,
+ done: pnm_done,
+};
+
+static struct ida_loader pgm_loader = {
+ magic: "P5",
+ moff: 0,
+ mlen: 2,
+ name: "pgm parser",
+ init: pnm_init,
+ read: pgm_read,
+ done: pnm_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&ppm_loader);
+ load_register(&pgm_loader);
+}
+
diff --git a/rd/read-tiff.c b/rd/read-tiff.c
new file mode 100644
index 0000000..3f75b83
--- /dev/null
+++ b/rd/read-tiff.c
@@ -0,0 +1,195 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <tiffio.h>
+
+#include "readers.h"
+
+struct tiff_state {
+ TIFF* tif;
+ char emsg[1024];
+ tdir_t ndirs; /* Number of directories */
+ /* (could be interpreted as number of pages) */
+ uint32 width,height;
+ uint16 config,nsamples,depth,fillorder,photometric;
+ uint32* row;
+ uint32* image;
+ uint16 resunit;
+ float xres,yres;
+};
+
+static void*
+tiff_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail)
+{
+ struct tiff_state *h;
+
+ fclose(fp);
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ TIFFSetWarningHandler(NULL);
+ h->tif = TIFFOpen(filename,"r");
+ if (NULL == h->tif)
+ goto oops;
+ /* Determine number of directories */
+ h->ndirs = 1;
+ while (TIFFReadDirectory(h->tif))
+ h->ndirs++;
+ i->npages = h->ndirs;
+ /* Select requested directory (page) */
+ if (!TIFFSetDirectory(h->tif, (tdir_t)page))
+ goto oops;
+
+ TIFFGetField(h->tif, TIFFTAG_IMAGEWIDTH, &h->width);
+ TIFFGetField(h->tif, TIFFTAG_IMAGELENGTH, &h->height);
+ TIFFGetField(h->tif, TIFFTAG_PLANARCONFIG, &h->config);
+ TIFFGetField(h->tif, TIFFTAG_SAMPLESPERPIXEL, &h->nsamples);
+ TIFFGetField(h->tif, TIFFTAG_BITSPERSAMPLE, &h->depth);
+ TIFFGetField(h->tif, TIFFTAG_FILLORDER, &h->fillorder);
+ TIFFGetField(h->tif, TIFFTAG_PHOTOMETRIC, &h->photometric);
+ h->row = malloc(TIFFScanlineSize(h->tif));
+ if (debug)
+ fprintf(stderr,"tiff: %" PRId32 "x%" PRId32 ", planar=%d, "
+ "nsamples=%d, depth=%d fo=%d pm=%d scanline=%" PRId32 "\n",
+ h->width,h->height,h->config,h->nsamples,h->depth,
+ h->fillorder,h->photometric,
+ TIFFScanlineSize(h->tif));
+
+ if (PHOTOMETRIC_PALETTE == h->photometric ||
+ PHOTOMETRIC_YCBCR == h->photometric ||
+ PHOTOMETRIC_SEPARATED == h->photometric ||
+ TIFFIsTiled(h->tif) ||
+ (1 != h->depth && 8 != h->depth)) {
+ /* for the more difficuilt cases we let libtiff
+ * do all the hard work. Drawback is that we lose
+ * progressive loading and decode everything here */
+ if (debug)
+ fprintf(stderr,"tiff: reading whole image [TIFFReadRGBAImage]\n");
+ h->image=malloc(4*h->width*h->height);
+ TIFFReadRGBAImage(h->tif, h->width, h->height, h->image, 0);
+ } else {
+ if (debug)
+ fprintf(stderr,"tiff: reading scanline by scanline\n");
+ h->row = malloc(TIFFScanlineSize(h->tif));
+ }
+
+ i->width = h->width;
+ i->height = h->height;
+
+ if (TIFFGetField(h->tif, TIFFTAG_RESOLUTIONUNIT, &h->resunit) &&
+ TIFFGetField(h->tif, TIFFTAG_XRESOLUTION, &h->xres) &&
+ TIFFGetField(h->tif, TIFFTAG_YRESOLUTION, &h->yres)) {
+ switch (h->resunit) {
+ case RESUNIT_NONE:
+ break;
+ case RESUNIT_INCH:
+ i->dpi = h->xres;
+ break;
+ case RESUNIT_CENTIMETER:
+ i->dpi = res_cm_to_inch(h->xres);
+ break;
+ }
+ }
+
+ return h;
+
+ oops:
+ if (h->tif)
+ TIFFClose(h->tif);
+ free(h);
+ return NULL;
+}
+
+static void
+tiff_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct tiff_state *h = data;
+ int s,on,off;
+
+ if (h->image) {
+ /* loaded whole image using TIFFReadRGBAImage() */
+ uint32 *row = h->image + h->width * (h->height - line -1);
+ load_rgba(dst,(unsigned char*)row,h->width);
+ return;
+ }
+
+ if (h->config == PLANARCONFIG_CONTIG) {
+ TIFFReadScanline(h->tif, h->row, line, 0);
+ } else if (h->config == PLANARCONFIG_SEPARATE) {
+ for (s = 0; s < h->nsamples; s++)
+ TIFFReadScanline(h->tif, h->row, line, s);
+ }
+
+ switch (h->nsamples) {
+ case 1:
+ if (1 == h->depth) {
+ /* black/white */
+ on = 0, off = 0;
+ if (PHOTOMETRIC_MINISWHITE == h->photometric)
+ on = 0, off = 255;
+ if (PHOTOMETRIC_MINISBLACK == h->photometric)
+ on = 255, off = 0;
+#if 0
+ /* Huh? Does TIFFReadScanline handle this already ??? */
+ if (FILLORDER_MSB2LSB == h->fillorder)
+ load_bits_msb(dst,(unsigned char*)(h->row),h->width,on,off);
+ else
+ load_bits_lsb(dst,(unsigned char*)(h->row),h->width,on,off);
+#else
+ load_bits_msb(dst,(unsigned char*)(h->row),h->width,on,off);
+#endif
+ } else {
+ /* grayscaled */
+ load_gray(dst,(unsigned char*)(h->row),h->width);
+ }
+ break;
+ case 3:
+ /* rgb */
+ memcpy(dst,h->row,3*h->width);
+ break;
+ case 4:
+ /* rgb+alpha */
+ load_rgba(dst,(unsigned char*)(h->row),h->width);
+ break;
+ }
+}
+
+static void
+tiff_done(void *data)
+{
+ struct tiff_state *h = data;
+
+ TIFFClose(h->tif);
+ if (h->row)
+ free(h->row);
+ if (h->image)
+ free(h->image);
+ free(h);
+}
+
+static struct ida_loader tiff1_loader = {
+ magic: "MM\x00\x2a",
+ moff: 0,
+ mlen: 4,
+ name: "libtiff",
+ init: tiff_init,
+ read: tiff_read,
+ done: tiff_done,
+};
+static struct ida_loader tiff2_loader = {
+ magic: "II\x2a\x00",
+ moff: 0,
+ mlen: 4,
+ name: "libtiff",
+ init: tiff_init,
+ read: tiff_read,
+ done: tiff_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&tiff1_loader);
+ load_register(&tiff2_loader);
+}
diff --git a/rd/read-xpm.c b/rd/read-xpm.c
new file mode 100644
index 0000000..affdce6
--- /dev/null
+++ b/rd/read-xpm.c
@@ -0,0 +1,287 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+
+#include "ida.h"
+
+#include "readers.h"
+#include "viewer.h"
+
+/* ---------------------------------------------------------------------- */
+/* load */
+
+struct xpm_color {
+ char name[8];
+ XColor color;
+};
+
+struct xpm_state {
+ FILE *infile;
+ int width,height,colors,chars;
+ struct xpm_color *cmap;
+ char *charline;
+ char *rgbrow;
+};
+
+static void*
+xpm_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *info, int thumbnail)
+{
+ struct xpm_state *h;
+ char line[1024],cname[32],*tmp;
+ XColor dummy;
+ int i;
+ Colormap cmap = DefaultColormapOfScreen(XtScreen(app_shell));
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+ h->infile = fp;
+
+ fgets(line,sizeof(line)-1,fp); /* XPM */
+ fgets(line,sizeof(line)-1,fp); /* static char ... */
+ while (0 == strncmp(line,"/*",2)) {
+ while (NULL == strstr(line,"*/"))
+ fgets(line,sizeof(line)-1,fp);
+ fgets(line,sizeof(line)-1,fp);
+ }
+
+ /* size, colors */
+ fgets(line,sizeof(line)-1,fp);
+ if (0 == strncmp(line,"/*",2)) {
+ while (NULL == strstr(line,"*/"))
+ fgets(line,sizeof(line)-1,fp);
+ fgets(line,sizeof(line)-1,fp);
+ }
+ if (4 != sscanf(line,"\"%d %d %d %d",
+ &h->width,&h->height,&h->colors,&h->chars))
+ goto oops;
+ if (h->chars > 7)
+ goto oops;
+
+ /* read color table */
+ h->cmap = malloc(h->colors * sizeof(struct xpm_color));
+ memset(h->cmap,0,h->colors * sizeof(struct xpm_color));
+ for (i = 0; i < h->colors; i++) {
+ fgets(line,sizeof(line)-1,fp);
+ while (0 == strncmp(line,"/*",2)) {
+ while (NULL == strstr(line,"*/"))
+ fgets(line,sizeof(line)-1,fp);
+ fgets(line,sizeof(line)-1,fp);
+ }
+ memcpy(h->cmap[i].name,line+1,h->chars);
+
+ if (NULL != (tmp = strstr(line+1+h->chars,"c "))) {
+ /* color */
+ sscanf(tmp+2,"%32[^\" ]",cname);
+ } else if (NULL != (tmp = strstr(line+h->chars,"m "))) {
+ /* mono */
+ sscanf(tmp+2,"%32[^\" ]",cname);
+ } else if (NULL != (tmp = strstr(line+h->chars,"g "))) {
+ /* gray? */
+ sscanf(tmp+2,"%32[^\" ]",cname);
+ } else
+ goto oops;
+ if (0 == strcasecmp(cname,"none"))
+ /* transparent */
+ strcpy(cname,"lightgray");
+ if (debug)
+ fprintf(stderr,"xpm: cmap: \"%*.*s\" => %s\n",
+ h->chars,h->chars,h->cmap[i].name,cname);
+#if 0
+ if (1 != sscanf(line+1+h->chars," c %32[^\"]",cname))
+ goto oops;
+#endif
+ XLookupColor(dpy,cmap,cname,&h->cmap[i].color,&dummy);
+ }
+ h->charline = malloc(h->width * h->chars + 8);
+ h->rgbrow = malloc(h->width * 3);
+
+ info->width = h->width;
+ info->height = h->height;
+ info->npages = 1;
+ return h;
+
+ oops:
+ fclose(fp);
+ free(h);
+ return NULL;
+}
+
+static void
+xpm_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct xpm_state *h = data;
+ char *src;
+ int i,c;
+
+ fgets(h->charline,h->width * h->chars + 8,h->infile);
+ while (0 == strncmp(h->charline,"/*",2)) {
+ while (NULL == strstr(h->charline,"*/"))
+ fgets(h->charline,h->width * h->chars + 8,h->infile);
+ fgets(h->charline,h->width * h->chars + 8,h->infile);
+ }
+ src = h->charline+1;
+ for (i = 0; i < h->width; i++) {
+ for (c = 0; c < h->colors; c++) {
+ char *name = h->cmap[c].name;
+ if (src[0] != name[0])
+ continue;
+ if (1 == h->chars)
+ break;
+ if (src[1] != name[1])
+ continue;
+ if (2 == h->chars)
+ break;
+ if (0 == strncmp(src+2,name+2,h->chars-2))
+ break;
+ }
+ if (c == h->colors)
+ continue;
+ dst[0] = h->cmap[c].color.red >> 8;
+ dst[1] = h->cmap[c].color.green >> 8;
+ dst[2] = h->cmap[c].color.blue >> 8;
+ src += h->chars;
+ dst += 3;
+ }
+}
+
+static void
+xpm_done(void *data)
+{
+ struct xpm_state *h = data;
+
+ fclose(h->infile);
+ free(h->charline);
+ free(h->rgbrow);
+ free(h->cmap);
+ free(h);
+}
+
+/* ---------------------------------------------------------------------- */
+
+struct xbm_state {
+ FILE *infile;
+ int width,height;
+};
+
+static void*
+xbm_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *info, int thumbnail)
+{
+ struct xbm_state *h;
+ char line[256],dummy[128];
+ int i;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+ h->infile = fp;
+
+ for (i = 0; i < 128; i++) {
+ fgets(line,sizeof(line)-1,fp);
+ if (0 == strncmp(line,"#define",7))
+ break;
+ }
+ if (128 == i)
+ goto oops;
+
+ if (2 != sscanf(line,"#define %127s %d",dummy,&h->width))
+ goto oops;
+ fgets(line,sizeof(line)-1,fp);
+ if (2 != sscanf(line,"#define %127s %d",dummy,&h->height))
+ goto oops;
+ if (debug)
+ fprintf(stderr,"xbm: %dx%d\n",h->width,h->height);
+
+ for (i = 0; i < 4; i++) {
+ fgets(line,sizeof(line)-1,fp);
+ if (strstr(line,"[] = {"))
+ break;
+ }
+ if (4 == i)
+ goto oops;
+
+ info->width = h->width;
+ info->height = h->height;
+ info->npages = 1;
+ return h;
+
+ oops:
+ if (debug)
+ fprintf(stderr,"xbm: %s",line);
+ fclose(fp);
+ free(h);
+ return NULL;
+}
+
+static void
+xbm_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct xbm_state *h = data;
+ int x,val;
+
+ for (x = 0; x < h->width; x++) {
+ if (0 == (x % 8))
+ fscanf(h->infile," 0x%x,",&val);
+ if (val & (1 << (x % 8))) {
+ *(dst++) = 0;
+ *(dst++) = 0;
+ *(dst++) = 0;
+ } else {
+ *(dst++) = 255;
+ *(dst++) = 255;
+ *(dst++) = 255;
+ }
+ }
+}
+
+static void
+xbm_done(void *data)
+{
+ struct xpm_state *h = data;
+
+ fclose(h->infile);
+ free(h);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static struct ida_loader xpm_loader = {
+ magic: "/* XPM */",
+ moff: 0,
+ mlen: 9,
+ name: "xpm parser",
+ init: xpm_init,
+ read: xpm_read,
+ done: xpm_done,
+};
+
+static struct ida_loader xbm1_loader = {
+ magic: "#define",
+ moff: 0,
+ mlen: 7,
+ name: "xbm parser",
+ init: xbm_init,
+ read: xbm_read,
+ done: xbm_done,
+};
+
+static struct ida_loader xbm2_loader = {
+ magic: "/*",
+ moff: 0,
+ mlen: 2,
+ name: "xbm parser",
+ init: xbm_init,
+ read: xbm_read,
+ done: xbm_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&xpm_loader);
+ load_register(&xbm1_loader);
+ load_register(&xbm2_loader);
+}
diff --git a/rd/read-xwd.c b/rd/read-xwd.c
new file mode 100644
index 0000000..8d15cf3
--- /dev/null
+++ b/rd/read-xwd.c
@@ -0,0 +1,357 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/XWDFile.h>
+#ifdef HAVE_ENDIAN_H
+# include <endian.h>
+#endif
+
+#include "readers.h"
+#include "viewer.h"
+#include "xwd.h"
+#include "x11.h"
+#include "ida.h"
+
+/* xwd files are big endian */
+#if BYTE_ORDER == BIG_ENDIAN
+# define be16_to_cpu(x) (x)
+# define be32_to_cpu(x) (x)
+#elif BYTE_ORDER == LITTLE_ENDIAN
+# define be16_to_cpu(x) (((x>>8) & 0x00ff) |\
+ ((x<<8) & 0xff00))
+# define be32_to_cpu(x) (((x>>24) & 0x000000ff) |\
+ ((x>>8) & 0x0000ff00) |\
+ ((x<<8) & 0x00ff0000) |\
+ ((x<<24) & 0xff000000))
+#else
+# error "Oops: unknown byte order"
+#endif
+
+static char *vclass[] = {
+ "StaticGray",
+ "GrayScale",
+ "StaticColor",
+ "PseudoColor",
+ "TrueColor",
+ "DirectColor",
+};
+static char *order[] = {
+ "LSBFirst",
+ "MSBFirst",
+};
+static char *fmt[] = {
+ "XYBitmap",
+ "XYPixmap",
+ "ZPixmap",
+};
+
+/* ----------------------------------------------------------------------- */
+
+struct xwd_state {
+ FILE *infile;
+ XWDFileHeader header;
+ XWDColor cmap[256];
+ int width,bpp;
+ unsigned char *row;
+ unsigned long *pix;
+ unsigned long r_mask,g_mask,b_mask;
+ int r_shift,g_shift,b_shift;
+ int r_bits,g_bits,b_bits;
+};
+
+static void
+xwd_map(struct xwd_state *h)
+{
+ int i;
+ unsigned long mask;
+
+ h->r_mask = be32_to_cpu(h->header.red_mask);
+ h->g_mask = be32_to_cpu(h->header.green_mask);
+ h->b_mask = be32_to_cpu(h->header.blue_mask);
+ for (i = 0; i < 32; i++) {
+ mask = (1 << i);
+ if (h->r_mask & mask)
+ h->r_bits++;
+ else if (!h->r_bits)
+ h->r_shift++;
+ if (h->g_mask & mask)
+ h->g_bits++;
+ else if (!h->g_bits)
+ h->g_shift++;
+ if (h->b_mask & mask)
+ h->b_bits++;
+ else if (!h->b_bits)
+ h->b_shift++;
+ }
+ h->r_shift -= (8 - h->r_bits);
+ h->g_shift -= (8 - h->g_bits);
+ h->b_shift -= (8 - h->b_bits);
+#if 0
+ fprintf(stderr,"xwd: color: bits shift\n");
+ fprintf(stderr,"xwd: red : %04i %05i\n", h->r_bits, h->r_shift);
+ fprintf(stderr,"xwd: green: %04i %05i\n", h->g_bits, h->g_shift);
+ fprintf(stderr,"xwd: blue : %04i %05i\n", h->b_bits, h->b_shift);
+#endif
+}
+
+
+static void*
+xwd_init(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail)
+{
+ struct xwd_state *h;
+ char *buf;
+ int size;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+ h->infile = fp;
+
+ fread(&h->header,sizeof(h->header),1,fp);
+
+ if ((be32_to_cpu(h->header.pixmap_format) >sizeof(fmt)/sizeof(char*)) ||
+ (be32_to_cpu(h->header.byte_order) >sizeof(order)/sizeof(char*)) ||
+ (be32_to_cpu(h->header.visual_class) >sizeof(vclass)/sizeof(char*))) {
+ fprintf(stderr,"xwd: invalid file\n");
+ goto oops;
+ }
+
+ if (debug)
+ fprintf(stderr,
+ "xwd: fmt=%s depth=%" PRId32 " size=%" PRId32 "x%" PRId32
+ " bpp=%" PRId32 " bpl=%" PRId32 "\n"
+ "xwd: order=%s vclass=%s masks=%" PRIx32 "/%" PRIx32
+ "/%" PRIx32 " cmap=%" PRId32 "\n",
+ fmt[be32_to_cpu(h->header.pixmap_format)],
+ be32_to_cpu(h->header.pixmap_depth),
+ be32_to_cpu(h->header.pixmap_width),
+ be32_to_cpu(h->header.pixmap_height),
+ be32_to_cpu(h->header.bits_per_pixel),
+ be32_to_cpu(h->header.bytes_per_line),
+ order[be32_to_cpu(h->header.byte_order)],
+ vclass[be32_to_cpu(h->header.visual_class)],
+ be32_to_cpu(h->header.red_mask),
+ be32_to_cpu(h->header.green_mask),
+ be32_to_cpu(h->header.blue_mask),
+ be32_to_cpu(h->header.colormap_entries));
+
+ size = be32_to_cpu(h->header.header_size)-sizeof(h->header);
+ buf = malloc(size);
+ fread(buf,size,1,fp);
+ if (debug)
+ fprintf(stderr,"xwd: name=%s\n",buf);
+ free(buf);
+
+ /* check format */
+ if (8 != be32_to_cpu(h->header.bits_per_pixel) &&
+ 16 != be32_to_cpu(h->header.bits_per_pixel) &&
+ 24 != be32_to_cpu(h->header.bits_per_pixel) &&
+ 32 != be32_to_cpu(h->header.bits_per_pixel)) {
+ fprintf(stderr,"xwd: Oops: bpp != 8/16/24/32\n");
+ goto oops;
+ }
+ if (be32_to_cpu(h->header.pixmap_format) != ZPixmap) {
+ fprintf(stderr,"xwd: Oops: can read only ZPixmap format\n");
+ goto oops;
+ }
+
+ /* color map */
+ if (be32_to_cpu(h->header.colormap_entries) > 256) {
+ fprintf(stderr,"xwd: colormap too big (%" PRId32 " > 256)\n",
+ be32_to_cpu(h->header.colormap_entries));
+ goto oops;
+ }
+ fread(&h->cmap,sizeof(XWDColor),be32_to_cpu(h->header.colormap_entries),fp);
+#if 0
+ for (i = 0; i < be32_to_cpu(h->header.colormap_entries); i++)
+ fprintf(stderr, "xwd cmap: %d: "
+ "pix=%ld rgb=%d/%d/%d flags=%d pad=%d\n",i,
+ be32_to_cpu(h->cmap[i].pixel),
+ be16_to_cpu(h->cmap[i].red),
+ be16_to_cpu(h->cmap[i].green),
+ be16_to_cpu(h->cmap[i].blue),
+ h->cmap[i].flags,
+ h->cmap[i].pad);
+#endif
+
+ switch (be32_to_cpu(h->header.visual_class)) {
+ case StaticGray:
+ case PseudoColor:
+ /* nothing */
+ break;
+ case TrueColor:
+ case DirectColor:
+ xwd_map(h);
+ break;
+ default:
+ fprintf(stderr,"xwd: Oops: visual not implemented [%s]\n",
+ vclass[be32_to_cpu(h->header.visual_class)]);
+ goto oops;
+ }
+
+ h->bpp = be32_to_cpu(h->header.bits_per_pixel);
+ h->width = be32_to_cpu(h->header.pixmap_width);
+ h->pix = malloc(h->width*sizeof(unsigned long));
+ h->row = malloc(be32_to_cpu(h->header.bytes_per_line));
+ i->width = be32_to_cpu(h->header.pixmap_width);
+ i->height = be32_to_cpu(h->header.pixmap_height);
+ i->npages = 1;
+ return h;
+
+ oops:
+ fclose(h->infile);
+ free(h);
+ return NULL;
+}
+
+static void
+xwd_parse(unsigned char *dst, unsigned int line, void *data)
+{
+ struct xwd_state *h = data;
+ unsigned long r,g,b;
+ int x,i,bits;
+
+ /* data to 32bit values */
+ memset(h->pix,0,h->width * sizeof(unsigned long));
+ if (be32_to_cpu(h->header.byte_order) == LSBFirst) {
+ for (i = 0, x = 0; x < h->width; x++)
+ for (bits = 0; bits < h->bpp; bits += 8)
+ h->pix[x] |= h->row[i++] << bits;
+ } else {
+ for (i = 0, x = 0; x < h->width; x++)
+ for (bits = 0; bits < h->bpp; bits += 8)
+ h->pix[x] <<= 8, h->pix[x] |= h->row[i++];
+ }
+
+ /* transform to rgb */
+ switch (be32_to_cpu(h->header.visual_class)) {
+ case StaticGray:
+ for (x = 0; x < h->width; x++) {
+ dst[0] = h->pix[x];
+ dst[1] = h->pix[x];
+ dst[2] = h->pix[x];
+ dst += 3;
+ }
+ break;
+ case PseudoColor:
+ for (x = 0; x < h->width; x++) {
+ dst[0] = be16_to_cpu(h->cmap[h->pix[x]].red) >> 8;
+ dst[1] = be16_to_cpu(h->cmap[h->pix[x]].green) >> 8;
+ dst[2] = be16_to_cpu(h->cmap[h->pix[x]].blue) >> 8;
+ dst += 3;
+ }
+ break;
+ case TrueColor:
+ case DirectColor:
+ for (x = 0; x < h->width; x++) {
+ r = h->pix[x] & h->r_mask;
+ if (h->r_shift > 0)
+ r >>= h->r_shift;
+ if (h->r_shift < 0)
+ r <<= -h->r_shift;
+ g = h->pix[x] & h->g_mask;
+ if (h->g_shift > 0)
+ g >>= h->g_shift;
+ if (h->g_shift < 0)
+ g <<= -h->g_shift;
+ b = h->pix[x] & h->b_mask;
+ if (h->b_shift > 0)
+ b >>= h->b_shift;
+ if (h->b_shift < 0)
+ b <<= -h->b_shift;
+ dst[0] = r;
+ dst[1] = g;
+ dst[2] = b;
+ dst += 3;
+ }
+ break;
+ }
+}
+
+static void
+xwd_read(unsigned char *dst, unsigned int line, void *data)
+{
+ struct xwd_state *h = data;
+
+ fread(h->row,be32_to_cpu(h->header.bytes_per_line),1,h->infile);
+ xwd_parse(dst, line, data);
+}
+
+static void
+xwd_done(void *data)
+{
+ struct xwd_state *h = data;
+
+ fclose(h->infile);
+ free(h->pix);
+ free(h->row);
+ free(h);
+}
+
+static struct ida_loader xwd_loader = {
+ magic: "\0\0\0\7",
+ moff: 4,
+ mlen: 4,
+ name: "xwd",
+ init: xwd_init,
+ read: xwd_read,
+ done: xwd_done,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&xwd_loader);
+}
+
+/* ----------------------------------------------------------------------- */
+
+void
+parse_ximage(struct ida_image *dest, XImage *src)
+{
+ struct xwd_state h;
+ Colormap cmap;
+ XColor col;
+ int y,i;
+
+ memset(&h,0,sizeof(h));
+ h.width = src->width;
+ h.bpp = src->bits_per_pixel;
+ h.header.red_mask = be32_to_cpu(info->red_mask);
+ h.header.green_mask = be32_to_cpu(info->green_mask);
+ h.header.blue_mask = be32_to_cpu(info->blue_mask);
+ h.header.visual_class = be32_to_cpu(info->class);
+ h.header.byte_order = be32_to_cpu(ImageByteOrder(dpy));
+ h.pix = malloc(src->width * sizeof(unsigned long));
+
+ switch (be32_to_cpu(h.header.visual_class)) {
+ case PseudoColor:
+ cmap = DefaultColormapOfScreen(XtScreen(app_shell));
+ for (i = 0; i < 256; i++) {
+ col.pixel = i;
+ XQueryColor(dpy,cmap,&col);
+ h.cmap[i].red = be16_to_cpu(col.red);
+ h.cmap[i].green = be16_to_cpu(col.green);
+ h.cmap[i].blue = be16_to_cpu(col.blue);
+ }
+ break;
+ case TrueColor:
+ case DirectColor:
+ xwd_map(&h);
+ break;
+ }
+
+ memset(dest,0,sizeof(*dest));
+ dest->i.width = src->width;
+ dest->i.height = src->height;
+ dest->data = malloc(dest->i.width * dest->i.height * 3);
+ memset(dest->data,0,dest->i.width * dest->i.height * 3);
+
+ for (y = 0; y < src->height; y++) {
+ h.row = src->data + y*src->bytes_per_line;
+ xwd_parse(dest->data + 3*y*dest->i.width, y, &h);
+ }
+ free(h.pix);
+}
diff --git a/readers.c b/readers.c
new file mode 100644
index 0000000..065be06
--- /dev/null
+++ b/readers.c
@@ -0,0 +1,133 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "readers.h"
+
+/* ----------------------------------------------------------------------- */
+
+void load_bits_lsb(unsigned char *dst, unsigned char *src, int width,
+ int on, int off)
+{
+ int i,mask,bit;
+
+ for (i = 0; i < width; i++) {
+ mask = 1 << (i & 0x07);
+ bit = src[i>>3] & mask;
+ dst[0] = bit ? on : off;
+ dst[1] = bit ? on : off;
+ dst[2] = bit ? on : off;
+ dst += 3;
+ }
+}
+
+void load_bits_msb(unsigned char *dst, unsigned char *src, int width,
+ int on, int off)
+{
+ int i,mask,bit;
+
+ for (i = 0; i < width; i++) {
+ mask = 1 << (7 - (i & 0x07));
+ bit = src[i>>3] & mask;
+ dst[0] = bit ? on : off;
+ dst[1] = bit ? on : off;
+ dst[2] = bit ? on : off;
+ dst += 3;
+ }
+}
+
+void load_gray(unsigned char *dst, unsigned char *src, int width)
+{
+ int i;
+
+ for (i = 0; i < width; i++) {
+ dst[0] = src[0];
+ dst[1] = src[0];
+ dst[2] = src[0];
+ dst += 3;
+ src += 1;
+ }
+}
+
+void load_graya(unsigned char *dst, unsigned char *src, int width)
+{
+ int i;
+
+ for (i = 0; i < width; i++) {
+ dst[0] = src[0];
+ dst[1] = src[0];
+ dst[2] = src[0];
+ dst += 3;
+ src += 2;
+ }
+}
+
+void load_rgba(unsigned char *dst, unsigned char *src, int width)
+{
+ int i;
+
+ for (i = 0; i < width; i++) {
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst += 3;
+ src += 4;
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+int load_add_extra(struct ida_image_info *info, enum ida_extype type,
+ unsigned char *data, unsigned int size)
+{
+ struct ida_extra *extra;
+
+ extra = malloc(sizeof(*extra));
+ if (NULL == extra)
+ return -1;
+ memset(extra,0,sizeof(*extra));
+ extra->data = malloc(size);
+ if (NULL == extra->data) {
+ free(extra);
+ return -1;
+ }
+ extra->type = type;
+ extra->size = size;
+ memcpy(extra->data,data,size);
+ extra->next = info->extra;
+ info->extra = extra;
+ return 0;
+};
+
+struct ida_extra* load_find_extra(struct ida_image_info *info,
+ enum ida_extype type)
+{
+ struct ida_extra *extra;
+
+ for (extra = info->extra; NULL != extra; extra = extra->next)
+ if (type == extra->type)
+ return extra;
+ return NULL;
+}
+
+int load_free_extras(struct ida_image_info *info)
+{
+ struct ida_extra *next;
+
+ while (NULL != info->extra) {
+ next = info->extra->next;
+ free(info->extra->data);
+ free(info->extra);
+ info->extra = next;
+ }
+ return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+LIST_HEAD(loaders);
+
+void load_register(struct ida_loader *loader)
+{
+ list_add_tail(&loader->list, &loaders);
+}
diff --git a/readers.h b/readers.h
new file mode 100644
index 0000000..b54e5b6
--- /dev/null
+++ b/readers.h
@@ -0,0 +1,104 @@
+#include "list.h"
+
+enum ida_extype {
+ EXTRA_COMMENT = 1,
+ EXTRA_EXIF = 2,
+};
+
+struct ida_extra {
+ enum ida_extype type;
+ unsigned char *data;
+ unsigned int size;
+ struct ida_extra *next;
+};
+
+/* image data and metadata */
+struct ida_image_info {
+ unsigned int width;
+ unsigned int height;
+ unsigned int dpi;
+ unsigned int npages;
+ struct ida_extra *extra;
+
+ int thumbnail;
+ unsigned int real_width;
+ unsigned int real_height;
+};
+
+struct ida_image {
+ struct ida_image_info i;
+ unsigned char *data;
+};
+struct ida_rect {
+ int x1,y1,x2,y2;
+};
+
+/* load image files */
+struct ida_loader {
+ char *magic;
+ int moff;
+ int mlen;
+ char *name;
+ void* (*init)(FILE *fp, char *filename, unsigned int page,
+ struct ida_image_info *i, int thumbnail);
+ void (*read)(unsigned char *dst, unsigned int line, void *data);
+ void (*done)(void *data);
+ struct list_head list;
+};
+
+/* filter + operations */
+struct ida_op {
+ char *name;
+ void* (*init)(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm);
+ void (*work)(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line,
+ void *data);
+ void (*done)(void *data);
+};
+
+void* op_none_init(struct ida_image *src, struct ida_rect *rect,
+ struct ida_image_info *i, void *parm);
+void op_none_done(void *data);
+void op_free_done(void *data);
+
+/* ----------------------------------------------------------------------- */
+/* resolution */
+
+#define res_cm_to_inch(x) ((x * 2540 + 5) / 1000)
+#define res_m_to_inch(x) ((x * 2540 + 5) / 100000)
+#define res_inch_to_m(x) ((x * 100000 + 5) / 2540)
+
+/* ----------------------------------------------------------------------- */
+
+/* helpers */
+void load_bits_lsb(unsigned char *dst, unsigned char *src, int width,
+ int on, int off);
+void load_bits_msb(unsigned char *dst, unsigned char *src, int width,
+ int on, int off);
+void load_gray(unsigned char *dst, unsigned char *src, int width);
+void load_graya(unsigned char *dst, unsigned char *src, int width);
+void load_rgba(unsigned char *dst, unsigned char *src, int width);
+
+int load_add_extra(struct ida_image_info *info, enum ida_extype type,
+ unsigned char *data, unsigned int size);
+struct ida_extra* load_find_extra(struct ida_image_info *info,
+ enum ida_extype type);
+int load_free_extras(struct ida_image_info *info);
+
+/* ----------------------------------------------------------------------- */
+
+/* other */
+extern int debug;
+extern struct ida_loader ppm_loader;
+extern struct ida_loader jpeg_loader;
+extern struct ida_loader sane_loader;
+extern struct ida_writer ps_writer;
+extern struct ida_writer jpeg_writer;
+
+/* lists */
+#define __init __attribute__ ((constructor))
+#define __fini __attribute__ ((destructor))
+
+extern struct list_head loaders;
+void load_register(struct ida_loader *loader);
diff --git a/sane.c b/sane.c
new file mode 100644
index 0000000..585ac1c
--- /dev/null
+++ b/sane.c
@@ -0,0 +1,324 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sane/sane.h>
+#include <sane/saneopts.h>
+
+#include "readers.h"
+#include "viewer.h"
+#include "sane.h"
+#include "ida.h"
+
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/PushB.h>
+#include <Xm/CascadeB.h>
+#include <Xm/RowColumn.h>
+
+extern int sane_res;
+
+/* ---------------------------------------------------------------------- */
+
+static void
+build_menu(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ WidgetList children,wlist;
+ Cardinal nchildren;
+ const SANE_Device **list;
+ Widget push;
+ XmString str;
+ char action[256];
+ int rc,i;
+
+ /* del old */
+ XtVaGetValues(widget,
+ XtNchildren,&children,
+ XtNnumChildren,&nchildren,
+ NULL);
+ wlist = malloc(sizeof(Widget*)*nchildren);
+ memcpy(list,children,sizeof(Widget*)*nchildren);
+ for (i = 0; i < nchildren; i++)
+ XtDestroyWidget(wlist[i]);
+ free(wlist);
+
+ /* create new */
+ if (SANE_STATUS_GOOD != (rc = sane_init(NULL,NULL))) {
+ fprintf(stderr,"sane_init: %s\n",sane_strstatus(rc));
+ goto done;
+ }
+ sane_get_devices(&list,0);
+ if (NULL == list[0])
+ goto done;
+
+ for (i = 0; list[i] != NULL; i++) {
+ if (debug)
+ fprintf(stderr,"sane dev: %s | %s | %s | %s\n",
+ list[i]->name, list[i]->vendor,
+ list[i]->model, list[i]->type);
+ str = XmStringGenerate((char*)list[i]->model,
+ NULL, XmMULTIBYTE_TEXT, NULL);
+ push = XtVaCreateManagedWidget(list[i]->name,
+ xmPushButtonWidgetClass,widget,
+ XmNlabelString,str,
+ NULL);
+ XmStringFree(str);
+ sprintf(action,"Scan(%s)",list[i]->name);
+ XtAddCallback(push,XmNactivateCallback,action_cb,strdup(action));
+ }
+
+ done:
+ sane_exit();
+}
+
+void
+sane_menu(Widget menu)
+{
+ Widget submenu;
+ int rc;
+
+ if (SANE_STATUS_GOOD != (rc = sane_init(NULL,NULL))) {
+ fprintf(stderr,"sane_init: %s\n",sane_strstatus(rc));
+ goto done;
+ }
+ submenu = XmCreatePulldownMenu(menu,"scanM",NULL,0);
+ XtVaCreateManagedWidget("scan",xmCascadeButtonWidgetClass,menu,
+ XmNsubMenuId,submenu,NULL);
+ XtAddCallback(submenu, XmNmapCallback, build_menu, NULL);
+
+ done:
+ sane_exit();
+}
+
+/* ---------------------------------------------------------------------- */
+/* load */
+
+#define BUF_LINES 16 /* read bigger chunks to reduce overhead */
+
+struct sane_state {
+ SANE_Handle sane;
+ SANE_Parameters parm;
+ SANE_Byte *buf;
+ int started;
+};
+
+static void dump_desc(SANE_Handle h, int nr, const SANE_Option_Descriptor *opt)
+{
+ SANE_Bool b;
+ SANE_Int i,flags;
+
+ fprintf(stderr,"sane opt: name=%s title=%s type=%d unit=%d cap=%d\n",
+ opt->name, opt->title, opt->type, opt->unit, opt->cap);
+ switch (opt->type) {
+ case SANE_TYPE_BOOL:
+ sane_control_option(h, nr, SANE_ACTION_GET_VALUE,
+ &b, &flags);
+ fprintf(stderr," value=%s [bool]\n",b ? "true" : "false");
+ break;
+ case SANE_TYPE_INT:
+ sane_control_option(h, nr, SANE_ACTION_GET_VALUE,
+ &i, &flags);
+ fprintf(stderr," value=%d [int]\n",i);
+ break;
+ case SANE_TYPE_FIXED:
+ case SANE_TYPE_STRING:
+ case SANE_TYPE_BUTTON:
+ case SANE_TYPE_GROUP:
+ break;
+ }
+ switch (opt->constraint_type) {
+ case SANE_CONSTRAINT_NONE:
+ break;
+ case SANE_CONSTRAINT_RANGE:
+ fprintf(stderr," range=%d-%d\n",
+ opt->constraint.range->min,
+ opt->constraint.range->max);
+ break;
+ case SANE_CONSTRAINT_WORD_LIST:
+ fprintf(stderr," constraint word_list:");
+ for (i = 1; i <= opt->constraint.word_list[0]; i++)
+ fprintf(stderr," %d",opt->constraint.word_list[i]);
+ fprintf(stderr,"\n");
+ break;
+ case SANE_CONSTRAINT_STRING_LIST:
+ fprintf(stderr," constraint string_list:");
+ for (i = 0; opt->constraint.string_list[i] != NULL; i++)
+ fprintf(stderr," %s",opt->constraint.string_list[i]);
+ fprintf(stderr,"\n");
+ break;
+ };
+}
+
+static void*
+sane_idainit(FILE *fp, char *filename, unsigned int page, struct ida_image_info *info,
+ int thumbnail)
+{
+ const SANE_Option_Descriptor *opt;
+ SANE_Int flags, count;
+ struct sane_state *h;
+ int rc,i,value,dpi = 0;
+
+ h = malloc(sizeof(*h));
+ memset(h,0,sizeof(*h));
+
+ if (SANE_STATUS_GOOD != (rc = sane_init(NULL,NULL))) {
+ fprintf(stderr,"sane_init: %s\n",sane_strstatus(rc));
+ goto oops;
+ }
+ if (SANE_STATUS_GOOD != (rc = sane_open(filename,&h->sane))) {
+ fprintf(stderr,"sane_open: %s\n",sane_strstatus(rc));
+ goto oops;
+ }
+
+ /* set options */
+ opt = sane_get_option_descriptor(h->sane,0);
+ rc = sane_control_option(h->sane, 0, SANE_ACTION_GET_VALUE,
+ &count, &flags);
+ for (i = 1; i < count; i++) {
+ opt = sane_get_option_descriptor(h->sane,i);
+ if (opt->name && 0 == strcmp(opt->name,SANE_NAME_SCAN_TL_X)) {
+ value = opt->constraint.range->min;
+ sane_control_option(h->sane, i, SANE_ACTION_SET_VALUE,
+ &value, &flags);
+ } else if (opt->name && 0 == strcmp(opt->name,SANE_NAME_SCAN_TL_Y)) {
+ value = opt->constraint.range->min;
+ sane_control_option(h->sane, i, SANE_ACTION_SET_VALUE,
+ &value, &flags);
+ } else if (opt->name && 0 == strcmp(opt->name,SANE_NAME_SCAN_BR_X)) {
+ value = opt->constraint.range->max;
+ sane_control_option(h->sane, i, SANE_ACTION_SET_VALUE,
+ &value, &flags);
+ } else if (opt->name && 0 == strcmp(opt->name,SANE_NAME_SCAN_BR_Y)) {
+ value = opt->constraint.range->max;
+ sane_control_option(h->sane, i, SANE_ACTION_SET_VALUE,
+ &value, &flags);
+ } else if (opt->name && 0 == strcmp(opt->name,SANE_NAME_PREVIEW)) {
+ value = SANE_FALSE;
+ sane_control_option(h->sane, i, SANE_ACTION_SET_VALUE,
+ &value, &flags);
+ } else if (opt->cap & SANE_CAP_AUTOMATIC)
+ sane_control_option(h->sane, i, SANE_ACTION_SET_AUTO,
+ NULL, &flags);
+ if (opt->name && 0 == strcmp(opt->name,SANE_NAME_SCAN_RESOLUTION)) {
+ if (sane_res) {
+ dpi = sane_res;
+ sane_control_option(h->sane, i, SANE_ACTION_SET_VALUE,
+ &dpi, &flags);
+ }
+ sane_control_option(h->sane, i, SANE_ACTION_GET_VALUE,
+ &dpi, &flags);
+ }
+ if (debug)
+ dump_desc(h->sane,i,opt);
+ }
+
+ if (SANE_STATUS_GOOD != (rc = sane_start(h->sane))) {
+ fprintf(stderr,"sane_start: %s\n",sane_strstatus(rc));
+ goto oops;
+ }
+ h->started = 1;
+
+ if (SANE_STATUS_GOOD != (rc = sane_get_parameters(h->sane,&h->parm))) {
+ fprintf(stderr,"sane_get_parameters: %s\n",sane_strstatus(rc));
+ goto oops;
+ }
+
+ if (h->parm.format != SANE_FRAME_GRAY &&
+ h->parm.format != SANE_FRAME_RGB) {
+ fprintf(stderr,"sane: unsupported frame format (%d)\n",h->parm.format);
+ goto oops;
+ }
+ if (h->parm.depth != 8) {
+ fprintf(stderr,"sane: unsupported color depth (%d)\n",h->parm.depth);
+ goto oops;
+ }
+ if (-1 == h->parm.lines) {
+ fprintf(stderr,"sane: can't handle unknown image size\n");
+ goto oops;
+ }
+
+ info->width = h->parm.pixels_per_line;
+ info->height = h->parm.lines;
+ if (dpi)
+ info->dpi = dpi;
+ h->buf = malloc(h->parm.bytes_per_line * BUF_LINES);
+ if (debug)
+ fprintf(stderr,"sane: scanning %dx%d %s\n",info->width,info->height,
+ (h->parm.format == SANE_FRAME_GRAY) ? "gray" : "color");
+
+ return h;
+
+ oops:
+ if (h->buf)
+ free(h->buf);
+ if (h->started)
+ sane_cancel(h->sane);
+ if (h->sane)
+ sane_close(h->sane);
+ sane_exit();
+ free(h);
+ return NULL;
+}
+
+static void
+sane_idaread(unsigned char *dst, unsigned int line, void *data)
+{
+ struct sane_state *h = data;
+ unsigned int lines, total, offset, len;
+ int rc, i;
+ SANE_Byte *row;
+
+ if (0 == (line % BUF_LINES)) {
+ lines = BUF_LINES;
+ if (lines > h->parm.lines - line)
+ lines = h->parm.lines - line;
+ total = h->parm.bytes_per_line * lines;
+ offset = 0;
+ while (offset < total) {
+ rc = sane_read(h->sane, h->buf + offset, total - offset, &len);
+ if (rc != SANE_STATUS_GOOD)
+ return;
+ offset += len;
+ }
+ }
+ row = h->buf + (line % BUF_LINES) * h->parm.bytes_per_line;
+ switch (h->parm.format) {
+ case SANE_FRAME_GRAY:
+ for (i = 0; i < h->parm.pixels_per_line; i++) {
+ dst[3*i+0] = row[i];
+ dst[3*i+1] = row[i];
+ dst[3*i+2] = row[i];
+ }
+ break;
+ case SANE_FRAME_RGB:
+ memcpy(dst,row,h->parm.pixels_per_line*3);
+ break;
+ default:
+ fprintf(stderr,"sane: read: internal error\n");
+ exit(1);
+ }
+}
+
+static void
+sane_idadone(void *data)
+{
+ struct sane_state *h = data;
+
+ sane_cancel(h->sane);
+ sane_close(h->sane);
+ sane_exit();
+ free(h->buf);
+ free(h);
+}
+
+struct ida_loader sane_loader = {
+ name: "sane interface",
+ init: sane_idainit,
+ read: sane_idaread,
+ done: sane_idadone,
+};
+
+static void __init init_rd(void)
+{
+ load_register(&sane_loader);
+}
diff --git a/sane.h b/sane.h
new file mode 100644
index 0000000..2a4a385
--- /dev/null
+++ b/sane.h
@@ -0,0 +1 @@
+void sane_menu(Widget menu);
diff --git a/selections.c b/selections.c
new file mode 100644
index 0000000..262caa5
--- /dev/null
+++ b/selections.c
@@ -0,0 +1,623 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <Xm/Xm.h>
+#include <Xm/Transfer.h>
+#include <Xm/TransferP.h>
+#include <Xm/DragIcon.h>
+
+#include "ida.h"
+#include "readers.h"
+#include "writers.h"
+#include "viewer.h"
+#include "xwd.h"
+#include "xdnd.h"
+#include "selections.h"
+#include "list.h"
+
+Atom XA_TARGETS, XA_DONE;
+Atom XA_FILE_NAME, XA_FILE;
+Atom XA_BACKGROUND, XA_FOREGROUND, XA_PIXEL;
+Atom _MOTIF_EXPORT_TARGETS;
+Atom _MOTIF_CLIPBOARD_TARGETS;
+Atom _MOTIF_DEFERRED_CLIPBOARD_TARGETS;
+Atom _MOTIF_SNAPSHOT;
+Atom _MOTIF_DROP;
+Atom _MOTIF_LOSE_SELECTION;
+Atom _NETSCAPE_URL;
+Atom MIME_TEXT_URI_LIST;
+Atom MIME_IMAGE_PPM;
+Atom MIME_IMAGE_PGM;
+Atom MIME_IMAGE_XPM;
+Atom MIME_IMAGE_BMP;
+Atom MIME_IMAGE_JPEG;
+Atom MIME_IMAGE_GIF;
+Atom MIME_IMAGE_PNG;
+Atom MIME_IMAGE_TIFF;
+
+/* ---------------------------------------------------------------------- */
+/* send data (drags, copy) */
+
+struct sel_data {
+ struct list_head list;
+ Atom atom;
+ struct ida_image img;
+ Pixmap pixmap;
+ char *filename;
+ Pixmap icon_pixmap;
+ Widget icon_widget;
+};
+static struct list_head selections;
+
+static void
+iconify(Widget widget, struct sel_data *data)
+{
+ struct ida_image small;
+ unsigned int scale,x,y,depth;
+ char *src,*dst;
+ Arg args[4];
+ Cardinal n=0;
+
+ /* calc size */
+ memset(&small,0,sizeof(small));
+ for (scale = 1;; scale++) {
+ small.i.width = data->img.i.width / scale;
+ small.i.height = data->img.i.height / scale;
+ if (small.i.width < 128 && small.i.height < 128)
+ break;
+ }
+
+ /* scale down & create pixmap */
+ dst = small.data = malloc(small.i.width * small.i.height * 3);
+ for (y = 0; y < small.i.height; y++) {
+ src = data->img.data + 3 * y * scale * data->img.i.width;
+ for (x = 0; x < small.i.width; x++) {
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst += 3;
+ src += 3*scale;
+ }
+ }
+ data->icon_pixmap = image_to_pixmap(&small);
+
+ /* build DnD icon */
+ n = 0;
+ depth = DefaultDepthOfScreen(XtScreen(widget));
+ XtSetArg(args[n], XmNpixmap, data->icon_pixmap); n++;
+ XtSetArg(args[n], XmNwidth, small.i.width); n++;
+ XtSetArg(args[n], XmNheight, small.i.height); n++;
+ XtSetArg(args[n], XmNdepth, depth); n++;
+ data->icon_widget = XmCreateDragIcon(widget,"dragicon",args,n);
+
+ free(small.data);
+}
+
+static struct sel_data*
+sel_find(Atom selection)
+{
+ struct list_head *item;
+ struct sel_data *sel;
+
+ list_for_each(item,&selections) {
+ sel = list_entry(item, struct sel_data, list);
+ if (sel->atom == selection)
+ return sel;
+ }
+ return NULL;
+}
+
+static void
+sel_free(Atom selection)
+{
+ struct sel_data *sel;
+
+ sel = sel_find(selection);
+ if (NULL == sel)
+ return;
+ if (sel->filename) {
+ unlink(sel->filename);
+ free(sel->filename);
+ }
+ if (sel->icon_widget)
+ XtDestroyWidget(sel->icon_widget);
+ if (sel->icon_pixmap)
+ XFreePixmap(dpy,sel->icon_pixmap);
+ if (sel->pixmap)
+ XFreePixmap(dpy,sel->pixmap);
+ if (sel->img.data)
+ free(sel->img.data);
+
+ list_del(&sel->list);
+ free(sel);
+}
+
+static struct sel_data*
+sel_init(Atom selection)
+{
+ struct sel_data *sel;
+
+ sel_free(selection);
+ sel = malloc(sizeof(*sel));
+ memset(sel,0,sizeof(*sel));
+
+ sel->atom = selection;
+ sel->img = ida->img;
+ sel->img.data = malloc(ida->img.i.width * ida->img.i.height * 3);
+ memcpy(sel->img.data, ida->img.data,
+ ida->img.i.width * ida->img.i.height * 3);
+
+ list_add_tail(&sel->list,&selections);
+ return sel;
+}
+
+static void
+sel_tmpfile(struct sel_data *sel, struct ida_writer *wr)
+{
+ static char *base = "ida";
+ char *tmpdir;
+ FILE *fp;
+ int fd;
+
+ tmpdir = getenv("TMPDIR");
+ if (NULL == tmpdir)
+ tmpdir="/tmp";
+ sel->filename = malloc(strlen(tmpdir)+strlen(base)+16);
+ sprintf(sel->filename,"%s/%s-XXXXXX",tmpdir,base);
+ fd = mkstemp(sel->filename);
+ fp = fdopen(fd,"w");
+ wr->write(fp,&sel->img);
+ fclose(fp);
+}
+
+Atom sel_unique_atom(Widget widget)
+{
+ char id_name[32];
+ Atom id;
+ int i;
+
+ for (i = 0;; i++) {
+ sprintf(id_name,"_IDA_DATA_%lX_%d",XtWindow(widget),i);
+ id = XInternAtom(XtDisplay(widget),id_name,False);
+ if (NULL == sel_find(id))
+ break;
+ }
+ return id;
+}
+
+void
+selection_convert(Widget widget, XtPointer ignore, XtPointer call_data)
+{
+ XmConvertCallbackStruct *ccs = call_data;
+ unsigned long *ldata;
+ unsigned char *cdata;
+ struct sel_data *sel;
+ char *filename;
+ int n;
+
+ if (debug) {
+ char *t = !ccs->target ? NULL : XGetAtomName(dpy,ccs->target);
+ char *s = !ccs->selection ? NULL : XGetAtomName(dpy,ccs->selection);
+ fprintf(stderr,"conv: target=%s selection=%s\n",t,s);
+ if (t) XFree(t);
+ if (s) XFree(s);
+ }
+
+ /* tell which formats we can handle */
+ if ((ccs->target == XA_TARGETS) ||
+ (ccs->target == _MOTIF_CLIPBOARD_TARGETS) ||
+ (ccs->target == _MOTIF_DEFERRED_CLIPBOARD_TARGETS) ||
+ (ccs->target == _MOTIF_EXPORT_TARGETS)) {
+ n = 0;
+ ldata = (Atom*)XtMalloc(sizeof(Atom)*12);
+ if (ccs->target != _MOTIF_CLIPBOARD_TARGETS) {
+ ldata[n++] = XA_TARGETS;
+ ldata[n++] = MIME_IMAGE_PPM;
+ ldata[n++] = XA_PIXMAP;
+ ldata[n++] = XA_FOREGROUND;
+ ldata[n++] = XA_BACKGROUND;
+ ldata[n++] = XA_COLORMAP;
+ ldata[n++] = XA_FILE_NAME;
+ ldata[n++] = XA_FILE;
+ ldata[n++] = MIME_TEXT_URI_LIST;
+ ldata[n++] = _NETSCAPE_URL;
+ }
+ ccs->value = ldata;
+ ccs->length = n;
+ ccs->type = XA_ATOM;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_DONE;
+ return;
+
+ } else if (ccs->target == _MOTIF_SNAPSHOT) {
+ /* save away clipboard data */
+ n = 0;
+ ldata = (Atom*)XtMalloc(sizeof(Atom));
+ ldata[n++] = sel_unique_atom(widget);
+ sel_init(ldata[0]);
+ ccs->value = ldata;
+ ccs->length = n;
+ ccs->type = XA_ATOM;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_DONE;
+ return;
+ }
+
+ sel = sel_find(ccs->selection);
+ if (NULL == sel) {
+ /* should not happen */
+ fprintf(stderr,"Oops: selection not found\n");
+ ccs->status = XmCONVERT_REFUSE;
+ return;
+ }
+
+ if ((ccs->target == _MOTIF_LOSE_SELECTION) ||
+ (ccs->target == XA_DONE)) {
+ /* free stuff */
+ sel_free(ccs->selection);
+ ccs->value = NULL;
+ ccs->length = 0;
+ ccs->type = XA_INTEGER;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_DONE;
+ return;
+ }
+
+ /* convert data */
+ if (ccs->target == XA_BACKGROUND ||
+ ccs->target == XA_FOREGROUND ||
+ ccs->target == XA_COLORMAP) {
+ n = 0;
+ ldata = (Atom*)XtMalloc(sizeof(Atom)*8);
+ if (ccs->target == XA_BACKGROUND) {
+ ldata[n++] = WhitePixelOfScreen(XtScreen(widget));
+ ccs->type = XA_PIXEL;
+ }
+ if (ccs->target == XA_FOREGROUND) {
+ ldata[n++] = BlackPixelOfScreen(XtScreen(widget));
+ ccs->type = XA_PIXEL;
+ }
+ if (ccs->target == XA_COLORMAP) {
+ ldata[n++] = DefaultColormapOfScreen(XtScreen(widget));
+ ccs->type = XA_COLORMAP;
+ }
+ ccs->value = ldata;
+ ccs->length = n;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_DONE;
+
+ } else if (ccs->target == XA_PIXMAP) {
+ /* xfer pixmap id */
+ if (!sel->pixmap)
+ sel->pixmap = image_to_pixmap(&sel->img);
+ ldata = (Pixmap*)XtMalloc(sizeof(Pixmap));
+ ldata[0] = sel->pixmap;
+ if (debug)
+ fprintf(stderr,"conv: pixmap id is 0x%lx\n",ldata[0]);
+ ccs->value = ldata;
+ ccs->length = 1;
+ ccs->type = XA_DRAWABLE;
+ ccs->format = 32;
+ ccs->status = XmCONVERT_DONE;
+
+ } else if (ccs->target == MIME_IMAGE_PPM) {
+ /* xfer image data directly */
+ cdata = XtMalloc(sel->img.i.width * sel->img.i.height * 3 + 32);
+ n = sprintf(cdata,"P6\n%d %d\n255\n",
+ sel->img.i.width, sel->img.i.height);
+ memcpy(cdata+n, sel->img.data, sel->img.i.width*sel->img.i.height*3);
+ ccs->value = cdata;
+ ccs->length = n + sel->img.i.width * sel->img.i.height * 3;
+ ccs->type = MIME_IMAGE_PPM;
+ ccs->format = 8;
+ ccs->status = XmCONVERT_DONE;
+
+ } else if (ccs->target == XA_FILE_NAME ||
+ ccs->target == XA_FILE ||
+ ccs->target == XA_STRING ||
+ ccs->target == MIME_TEXT_URI_LIST ||
+ ccs->target == _NETSCAPE_URL) {
+ /* xfer image via tmp file */
+ if (NULL == sel->filename)
+ sel_tmpfile(sel,&jpeg_writer);
+ if (ccs->target == MIME_TEXT_URI_LIST ||
+ ccs->target == _NETSCAPE_URL) {
+ /* filename => url */
+ filename = XtMalloc(strlen(sel->filename)+8);
+ sprintf(filename,"file:%s\r\n",sel->filename);
+ ccs->type = ccs->target;
+ if (debug)
+ fprintf(stderr,"conv: tmp url is %s\n",filename);
+ } else {
+ filename = XtMalloc(strlen(sel->filename));
+ strcpy(filename,sel->filename);
+ ccs->type = XA_STRING;
+ if (debug)
+ fprintf(stderr,"conv: tmp file is %s\n",filename);
+ }
+ ccs->value = filename;
+ ccs->length = strlen(filename);
+ ccs->format = 8;
+ ccs->status = XmCONVERT_DONE;
+
+ } else {
+ /* shouldn't happen */
+ fprintf(stderr,"Huh? unknown target\n");
+ ccs->status = XmCONVERT_REFUSE;
+ }
+}
+
+static void
+dnd_done(Widget widget, XtPointer ignore, XtPointer call_data)
+{
+ if (debug)
+ fprintf(stderr,"conv: transfer finished\n");
+ sel_free(_MOTIF_DROP);
+}
+
+/* ---------------------------------------------------------------------- */
+/* receive data (drops, paste) */
+
+static Atom targets[16];
+static Cardinal ntargets;
+
+static void
+selection_xfer(Widget widget, XtPointer ignore, XtPointer call_data)
+{
+ XmSelectionCallbackStruct *scs = call_data;
+ unsigned char *cdata = scs->value;
+ unsigned long *ldata = scs->value;
+ Atom target = 0;
+ unsigned int i,j,pending;
+ char *file,*tmp;
+
+ if (debug) {
+ char *y = !scs->type ? NULL : XGetAtomName(dpy,scs->type);
+ char *t = !scs->target ? NULL : XGetAtomName(dpy,scs->target);
+ char *s = !scs->selection ? NULL : XGetAtomName(dpy,scs->selection);
+ fprintf(stderr,"xfer: id=%p target=%s type=%s selection=%s\n",
+ scs->transfer_id,t,y,s);
+ if (y) XFree(y);
+ if (t) XFree(t);
+ if (s) XFree(s);
+ }
+
+ pending = scs->remaining;
+ if (scs->target == XA_TARGETS) {
+ /* look if we find a target we can deal with ... */
+ for (i = 0; !target && i < scs->length; i++) {
+ for (j = 0; j < ntargets; j++) {
+ if (ldata[i] == targets[j]) {
+ target = ldata[i];
+ break;
+ }
+ }
+ }
+ if (target) {
+ XmTransferValue(scs->transfer_id, target, selection_xfer,
+ NULL, XtLastTimestampProcessed(dpy));
+ pending++;
+ }
+ if (debug) {
+ fprintf(stderr,"xfer: available targets: ");
+ for (i = 0; i < scs->length; i++) {
+ char *name = !ldata[i] ? NULL : XGetAtomName(dpy,ldata[i]);
+ fprintf(stderr,"%s%s", i != 0 ? ", " : "", name);
+ XFree(name);
+ }
+ fprintf(stderr,"\n");
+ if (0 == scs->length)
+ fprintf(stderr,"xfer: Huh? no TARGETS available?\n");
+ }
+ }
+
+ if (scs->target == XA_FILE_NAME ||
+ scs->target == XA_FILE) {
+ /* load file */
+ if (debug)
+ fprintf(stderr,"xfer: => \"%s\"\n",cdata);
+ new_file(cdata,1);
+ }
+
+ if (scs->target == _NETSCAPE_URL) {
+ /* load file */
+ if (NULL != (tmp = strchr(cdata,'\n')))
+ *tmp = 0;
+ if (NULL != (tmp = strchr(cdata,'\r')))
+ *tmp = 0;
+ if (debug)
+ fprintf(stderr,"xfer: => \"%s\"\n",cdata);
+ new_file(cdata,1);
+ }
+
+ if (scs->target == MIME_TEXT_URI_LIST) {
+ /* load file(s) */
+ for (file = strtok(cdata,"\r\n");
+ NULL != file;
+ file = strtok(NULL,"\r\n")) {
+ if (debug)
+ fprintf(stderr,"xfer: => \"%s\"\n",file);
+ new_file(file,1);
+ }
+ }
+
+ if (scs->target == XA_STRING) {
+ /* might be a file name too, but don't complain if not */
+ if (debug)
+ fprintf(stderr,"xfer: => \"%s\"\n",cdata);
+ new_file(cdata,0);
+ }
+
+ if (scs->target == MIME_IMAGE_PPM ||
+ scs->target == MIME_IMAGE_PGM ||
+ scs->target == MIME_IMAGE_JPEG ||
+ scs->target == MIME_IMAGE_GIF ||
+ scs->target == MIME_IMAGE_PNG ||
+ scs->target == MIME_IMAGE_TIFF ||
+ scs->target == MIME_IMAGE_XPM ||
+ scs->target == MIME_IMAGE_BMP) {
+ /* xfer image data directly */
+ char *filename = load_tmpfile("ida");
+ int fd;
+ fd = mkstemp(filename);
+ write(fd,scs->value,scs->length);
+ close(fd);
+ if (0 == viewer_loadimage(ida,filename,0)) {
+ ida->file = "selection";
+ resize_shell();
+ }
+ unlink(filename);
+ free(filename);
+ }
+
+ if (scs->target == XA_PIXMAP) {
+ /* beaming pixmaps between apps */
+ Screen *scr;
+ Window root;
+ Pixmap pix;
+ int x,y,w,h,bw,depth;
+ XImage *ximage;
+ struct ida_image img;
+
+ pix = ldata[0];
+ if (debug)
+ fprintf(stderr,"xfer: => id=0x%lx\n",pix);
+ scr = XtScreen(widget);
+ XGetGeometry(dpy,pix,&root,&x,&y,&w,&h,&bw,&depth);
+ ximage = XGetImage(dpy,pix,0,0,w,h,-1,ZPixmap);
+ parse_ximage(&img, ximage);
+ XDestroyImage(ximage);
+ viewer_setimage(ida,&img,"selection");
+ resize_shell();
+ }
+
+ XFree(scs->value);
+ if (1 == pending) {
+ /* all done -- clean up */
+ if (debug)
+ fprintf(stderr,"xfer: all done\n");
+ XmTransferDone(scs->transfer_id, XmTRANSFER_DONE_SUCCEED);
+ XdndDropFinished(widget,scs);
+ }
+}
+
+void
+selection_dest(Widget w, XtPointer ignore, XtPointer call_data)
+{
+ XmDestinationCallbackStruct *dcs = call_data;
+
+ if (NULL != sel_find(_MOTIF_DROP)) {
+ if (debug)
+ fprintf(stderr,"dest: ignore self drop\n");
+ XmTransferDone(dcs->transfer_id, XmTRANSFER_DONE_FAIL);
+ return;
+ }
+ if (debug)
+ fprintf(stderr,"dest: xfer id=%p\n",dcs->transfer_id);
+ XmTransferValue(dcs->transfer_id, XA_TARGETS, selection_xfer,
+ NULL, XtLastTimestampProcessed(dpy));
+}
+
+/* ---------------------------------------------------------------------- */
+
+void ipc_ac(Widget widget, XEvent *event, String *argv, Cardinal *argc)
+{
+ struct sel_data *sel;
+ Widget drag;
+ Arg args[4];
+ Cardinal n=0;
+
+ if (0 == *argc)
+ return;
+
+ if (debug)
+ fprintf(stderr,"ipc: %s\n",argv[0]);
+
+ if (0 == strcmp(argv[0],"paste")) {
+ XmeClipboardSink(ida->widget,XmCOPY,NULL);
+
+ } else if (0 == strcmp(argv[0],"copy")) {
+ XmeClipboardSource(ida->widget,XmCOPY,XtLastTimestampProcessed(dpy));
+
+ } else if (0 == strcmp(argv[0],"drag")) {
+ sel = sel_init(_MOTIF_DROP);
+ iconify(widget,sel);
+ XtSetArg(args[n], XmNdragOperations, XmDROP_COPY); n++;
+ XtSetArg(args[n], XmNsourcePixmapIcon, sel->icon_widget); n++;
+ drag = XmeDragSource(ida->widget, NULL, event, args, n);
+ XtAddCallback(drag, XmNdragDropFinishCallback, dnd_done, NULL);
+ }
+}
+
+void dnd_add(Widget widget)
+{
+ Arg args[4];
+ Cardinal n=0;
+
+ XtSetArg(args[n], XmNimportTargets, targets); n++;
+ XtSetArg(args[n], XmNnumImportTargets, ntargets); n++;
+ XmeDropSink(widget,args,n);
+ XdndDropSink(widget);
+}
+
+void ipc_init()
+{
+ _MOTIF_EXPORT_TARGETS =
+ XInternAtom(dpy, "_MOTIF_EXPORT_TARGETS", False);
+ _MOTIF_CLIPBOARD_TARGETS =
+ XInternAtom(dpy, "_MOTIF_CLIPBOARD_TARGETS", False);
+ _MOTIF_DEFERRED_CLIPBOARD_TARGETS =
+ XInternAtom(dpy, "_MOTIF_DEFERRED_CLIPBOARD_TARGETS", False);
+ _MOTIF_SNAPSHOT =
+ XInternAtom(dpy, "_MOTIF_SNAPSHOT", False);
+ _MOTIF_DROP =
+ XInternAtom(dpy, "_MOTIF_DROP", False);
+ _MOTIF_LOSE_SELECTION =
+ XInternAtom(dpy, "_MOTIF_LOSE_SELECTION", False);
+
+ XA_TARGETS = XInternAtom(dpy, "TARGETS", False);
+ XA_DONE = XInternAtom(dpy, "DONE", False);
+ XA_FILE_NAME = XInternAtom(dpy, "FILE_NAME", False);
+ XA_FILE = XInternAtom(dpy, "FILE", False);
+ XA_FOREGROUND = XInternAtom(dpy, "FOREGROUND", False);
+ XA_BACKGROUND = XInternAtom(dpy, "BACKGROUND", False);
+ XA_PIXEL = XInternAtom(dpy, "PIXEL", False);
+ _NETSCAPE_URL = XInternAtom(dpy, "_NETSCAPE_URL", False);
+
+ MIME_TEXT_URI_LIST = XInternAtom(dpy, "text/uri-list", False);
+ MIME_IMAGE_PPM = XInternAtom(dpy, "image/ppm", False);
+ MIME_IMAGE_PGM = XInternAtom(dpy, "image/pgm", False);
+ MIME_IMAGE_XPM = XInternAtom(dpy, "image/xpm", False);
+ MIME_IMAGE_BMP = XInternAtom(dpy, "image/bmp", False);
+ MIME_IMAGE_JPEG = XInternAtom(dpy, "image/jpeg", False);
+ MIME_IMAGE_GIF = XInternAtom(dpy, "image/gif", False);
+ MIME_IMAGE_PNG = XInternAtom(dpy, "image/png", False);
+ MIME_IMAGE_TIFF = XInternAtom(dpy, "image/tiff", False);
+
+ targets[ntargets++] = XA_FILE_NAME;
+ targets[ntargets++] = XA_FILE;
+ targets[ntargets++] = _NETSCAPE_URL;
+ targets[ntargets++] = MIME_TEXT_URI_LIST;
+ targets[ntargets++] = MIME_IMAGE_PPM;
+ targets[ntargets++] = MIME_IMAGE_PGM;
+ targets[ntargets++] = MIME_IMAGE_XPM;
+ targets[ntargets++] = MIME_IMAGE_BMP;
+ targets[ntargets++] = MIME_IMAGE_JPEG;
+#ifdef HAVE_LIBUNGIF
+ targets[ntargets++] = MIME_IMAGE_GIF;
+#endif
+#ifdef HAVE_LIBPNG
+ targets[ntargets++] = MIME_IMAGE_PNG;
+#endif
+#ifdef HAVE_LIBTIFF
+ targets[ntargets++] = MIME_IMAGE_TIFF;
+#endif
+ targets[ntargets++] = XA_PIXMAP;
+ targets[ntargets++] = XA_STRING;
+
+ INIT_LIST_HEAD(&selections);
+}
diff --git a/selections.h b/selections.h
new file mode 100644
index 0000000..784b9b7
--- /dev/null
+++ b/selections.h
@@ -0,0 +1,18 @@
+extern Atom XA_TARGETS, XA_DONE;
+extern Atom _MOTIF_EXPORT_TARGETS;
+extern Atom _MOTIF_CLIPBOARD_TARGETS;
+extern Atom _MOTIF_DEFERRED_CLIPBOARD_TARGETS;
+extern Atom _MOTIF_SNAPSHOT;
+extern Atom _MOTIF_LOSE_SELECTION;
+extern Atom XA_FILE_NAME, XA_FILE;
+extern Atom _NETSCAPE_URL;
+extern Atom MIME_TEXT_URI_LIST;
+
+Atom sel_unique_atom(Widget widget);
+
+void selection_dest(Widget w, XtPointer ignore, XtPointer call_data);
+void selection_convert(Widget widget, XtPointer ignore, XtPointer call_data);
+void ipc_ac(Widget widget, XEvent *event, String *argv, Cardinal *argc);
+void dnd_add(Widget widget);
+void ipc_init(void);
+
diff --git a/thumbnail.cgi.c b/thumbnail.cgi.c
new file mode 100644
index 0000000..a4ba3c8
--- /dev/null
+++ b/thumbnail.cgi.c
@@ -0,0 +1,125 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <libexif/exif-data.h>
+
+/* -------------------------------------------------------------------------- */
+
+static const char *shellhelp =
+ "\n"
+ "This is a cgi script. It doesn't do any useful (beside printing this text)\n"
+ "when started from the shell prompt, it is supposed to be started by your\n"
+ "web server\n";
+
+static const char *description =
+ "\n"
+ "The script deliveres the EXIF thumbnail of JPEG images to the web browser.\n"
+ "It will lookup the path passed via path info below your document root, i.e.\n"
+ "a request like this ...\n"
+ "\n"
+ " http://your.server/cgi-bin/thumbnail.cgi/path/file.jpg\n"
+ "\n"
+ "... will make the script send the thumbnail of the file ...\n"
+ "\n"
+ " %s/path/file.jpg\n"
+ "\n"
+ "to the client\n"
+ "\n"
+ "Security note: The script refuses paths containing \"..\" to avoid breaking\n"
+ "out of the document root. There are no other checks through, so it will\n"
+ "deliver thumbnails for any JPEG image below below your document root which\n"
+ "it is allowed to open by unix file permissions.\n"
+ "\n"
+ "(c) 2004 Gerd Knorr <kraxel@bytesex.org> [SUSE Labs]\n"
+ "\n";
+
+/* -------------------------------------------------------------------------- */
+
+static void panic(int code, char *message)
+{
+ printf("Status: %d %s\n"
+ "Content-Type: text/plain\n"
+ "\n"
+ "ERROR: %s\n",
+ code, message, message);
+ fflush(stdout);
+ exit(1);
+}
+
+static void dump_thumbnail(char *filename)
+{
+ char *cached;
+ char mtime[64];
+ struct stat st;
+ struct tm *tm;
+ ExifData *ed = NULL;
+
+ if (-1 == stat(filename,&st))
+ panic(404,"can't stat file");
+ tm = gmtime(&st.st_mtime);
+ strftime(mtime,sizeof(mtime),"%a, %d %b %Y %H:%M:%S GMT",tm);
+ cached = getenv("HTTP_IF_MODIFIED_SINCE");
+ if (NULL != cached && 0 == strcmp(cached,mtime)) {
+ /* shortcut -- browser has a up-to-date copy */
+ printf("Status: 304 Image not modified\n"
+ "\n");
+ fflush(stdout);
+ return;
+ }
+
+ ed = exif_data_new_from_file(filename);
+ if (!ed)
+ panic(500,"file has no exif data\n");
+ if (!ed->data)
+ panic(500,"no exif thumbnail present");
+ if (ed->data[0] != 0xff || ed->data[1] != 0xd8)
+ panic(500,"exif thumbnail has no jpeg magic");
+
+
+ printf("Status: 200 Thumbnail follows\n"
+ "Content-Type: image/jpeg\n"
+ "Content-Length: %d\n"
+ "Last-modified: %s\n"
+ "\n",
+ ed->size,mtime);
+ fwrite(ed->data,ed->size,1,stdout);
+ fflush(stdout);
+}
+
+/* -------------------------------------------------------------------------- */
+
+int main(int argc, char *argv[])
+{
+ char filename[1024];
+ char *document_root;
+ char *path_info;
+
+ if (NULL == getenv("GATEWAY_INTERFACE")) {
+ fprintf(stderr,shellhelp);
+ fprintf(stderr,description,"$DOCUMENT_ROOT");
+ exit(1);
+ }
+
+ document_root = getenv("DOCUMENT_ROOT");
+ if (NULL == document_root)
+ panic(500,"DOCUMENT_ROOT unset");
+
+ path_info = getenv("PATH_INFO");
+ if (NULL == path_info || 0 == strlen(path_info)) {
+ printf("Content-type: text/plain\n"
+ "\n");
+ printf(description,document_root);
+ fflush(stdout);
+ return 0;
+ }
+
+ if (NULL != strstr(path_info,".."))
+ panic(403,"\"..\" not allowed in path");
+ snprintf(filename,sizeof(filename),"%s/%s",document_root,path_info);
+ dump_thumbnail(filename);
+ return 0;
+}
diff --git a/viewer.c b/viewer.c
new file mode 100644
index 0000000..0bc81f5
--- /dev/null
+++ b/viewer.c
@@ -0,0 +1,962 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <X11/cursorfont.h>
+#include <X11/extensions/XShm.h>
+
+#include "ida.h"
+#include "x11.h"
+#include "dither.h"
+#include "readers.h"
+#include "viewer.h"
+#include "hex.h"
+#include "idaconfig.h"
+
+/* ----------------------------------------------------------------------- */
+
+#define POINTER_NORMAL 0
+#define POINTER_BUSY 1
+#define POINTER_PICK 2
+#define RUBBER_NEW 3
+#define RUBBER_MOVE 4
+#define RUBBER_X1 5
+#define RUBBER_Y1 6
+#define RUBBER_X2 7
+#define RUBBER_Y2 8
+
+#define RUBBER_RANGE 6
+#define RUBBER_INTERVAL 100
+
+#define PROCESS_LINES 16
+
+int debug;
+Cursor ptrs[POINTER_COUNT];
+
+/* ----------------------------------------------------------------------- */
+
+Pixmap image_to_pixmap(struct ida_image *img)
+{
+ unsigned char line[256],*src;
+ XImage *ximage;
+ void *shm;
+ Pixmap pix;
+ GC gc;
+ unsigned int x,y;
+
+ ximage = x11_create_ximage(app_shell, img->i.width, img->i.height, &shm);
+ for (y = 0; y < img->i.height; y++) {
+ src = img->data + 3*y*img->i.width;
+ if (display_type == PSEUDOCOLOR) {
+ dither_line(src, line, y, img->i.width);
+ for (x = 0; x < img->i.width; x++)
+ XPutPixel(ximage, x, y, x11_map[line[x]]);
+ } else {
+ for (x = 0; x < img->i.width; x++, src += 3) {
+ pix = x11_lut_red[src[0]] |
+ x11_lut_green[src[1]] |
+ x11_lut_blue[src[2]];
+ XPutPixel(ximage, x, y, pix);
+ }
+ }
+ }
+ pix = XCreatePixmap(dpy,XtWindow(app_shell),img->i.width, img->i.height,
+ DefaultDepthOfScreen(XtScreen(app_shell)));
+ gc = XCreateGC(dpy, pix, 0, NULL);
+ XPUTIMAGE(dpy, pix, gc, ximage, 0, 0, 0, 0, img->i.width, img->i.height);
+ XFreeGC(dpy, gc);
+ x11_destroy_ximage(app_shell, ximage, shm);
+ return pix;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int viewer_i2s(int zoom, int val)
+{
+ if (0 > zoom)
+ return val/(-zoom+1);
+ if (0 < zoom)
+ return val*(zoom+1);
+ return val;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void
+viewer_renderline(struct ida_viewer *ida, char *scanline)
+{
+ unsigned char *src,*dst,*rgb;
+ unsigned long pix;
+ unsigned int x,s,scrline;
+
+ src = scanline;
+
+ if (0 == ida->zoom) {
+ /* as-is */
+ if (display_type == PSEUDOCOLOR) {
+ dst = ida->dither_line;
+ dither_line(src, dst, ida->line, ida->scrwidth);
+ for (x = 0; x < ida->scrwidth; x++, dst++)
+ XPutPixel(ida->ximage, x, ida->line, x11_map[*dst]);
+ } else {
+ for (x = 0; x < ida->scrwidth; x++, src += 3) {
+ pix = x11_lut_red[src[0]] |
+ x11_lut_green[src[1]] |
+ x11_lut_blue[src[2]];
+ XPutPixel(ida->ximage, x, ida->line, pix);
+ }
+ }
+
+ } else if (ida->zoom < 0) {
+ /* zoom out */
+ s = -ida->zoom+1;
+ if (s-1 != (ida->line % s))
+ return;
+ scrline = ida->line/s;
+ if (display_type == PSEUDOCOLOR) {
+ rgb = ida->rgb_line;
+ for (x = 0; x < ida->scrwidth; x++, rgb += 3, src += 3*s) {
+ rgb[0] = src[0];
+ rgb[1] = src[1];
+ rgb[2] = src[2];
+ }
+ rgb = ida->rgb_line;
+ dst = ida->dither_line;
+ dither_line(rgb, dst, scrline, ida->scrwidth);
+ for (x = 0; x < ida->scrwidth; x++, dst++)
+ XPutPixel(ida->ximage, x, scrline, x11_map[*dst]);
+ } else {
+#if 0
+ /* just drop pixels */
+ for (x = 0; x < ida->scrwidth; x++, src += 3*s) {
+ pix = x11_lut_red[src[0]] |
+ x11_lut_green[src[1]] |
+ x11_lut_blue[src[2]];
+ XPutPixel(ida->ximage, x, scrline, pix);
+ }
+#else
+ /* horizontal interpolation (vertical is much harder ...) */
+ for (x = 0; x < ida->scrwidth; x++, src += 3*s) {
+ int red,green,blue,count,ix;
+ red = 0;
+ green = 0;
+ blue = 0;
+ count = 0;
+ for (ix = 0; ix < 3*s; ix += 3) {
+ red += src[ix+0];
+ green += src[ix+1];
+ blue += src[ix+2];
+ count += 1;
+ }
+ pix = x11_lut_red[red/count] |
+ x11_lut_green[green/count] |
+ x11_lut_blue[blue/count];
+ XPutPixel(ida->ximage, x, scrline, pix);
+ }
+#endif
+ }
+
+ } else {
+ /* zoom in */
+ s = ida->zoom+1;
+ if (display_type == PSEUDOCOLOR) {
+ rgb = ida->rgb_line;
+ for (x = 0; x < ida->scrwidth; rgb += 3) {
+ rgb[0] = src[0];
+ rgb[1] = src[1];
+ rgb[2] = src[2];
+ x++;
+ if (0 == (x%s))
+ src += 3;
+ }
+ for (scrline = ida->line*s; scrline < ida->line*s+s; scrline++) {
+ rgb = ida->rgb_line;
+ dst = ida->dither_line;
+ dither_line(rgb, dst, scrline, ida->scrwidth);
+ for (x = 0; x < ida->scrwidth; x++, dst++)
+ XPutPixel(ida->ximage, x, scrline, x11_map[*dst]);
+ }
+ } else {
+ for (scrline = ida->line*s; scrline < ida->line*s+s; scrline++) {
+ src = scanline;
+ for (x = 0; x < ida->scrwidth; src += 3) {
+ unsigned int i;
+ pix = x11_lut_red[src[0]] |
+ x11_lut_green[src[1]] |
+ x11_lut_blue[src[2]];
+ for (i = 0; i < s; i++, x++)
+ XPutPixel(ida->ximage, x, scrline, pix);
+ }
+ }
+ }
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void
+viewer_cleanup(struct ida_viewer *ida)
+{
+ if (ida->load_read) {
+ ida->load_done(ida->load_data);
+ ida->load_line = 0;
+ ida->load_read = NULL;
+ ida->load_done = NULL;
+ ida->load_data = NULL;
+ }
+ if (ida->op_work) {
+ ida->op_done(ida->op_data);
+ ida->op_line = 0;
+ ida->op_work = NULL;
+ ida->op_done = NULL;
+ ida->op_data = NULL;
+ if (ida->op_src.data) {
+ if (ida->undo.data) {
+ fprintf(stderr,"have undo buffer /* shouldn't happen */");
+ free(ida->undo.data);
+ }
+ ida->undo = ida->op_src;
+ memset(&ida->op_src,0,sizeof(ida->op_src));
+ }
+ }
+}
+
+static Boolean
+viewer_workproc(XtPointer client_data)
+{
+ struct ida_viewer *ida = client_data;
+ unsigned int start,end;
+ char *scanline;
+
+ start = ida->line;
+ end = ida->line + ida->steps;
+ if (end > ida->img.i.height)
+ end = ida->img.i.height;
+
+ /* image loading */
+ if (ida->load_read) {
+ for (ida->line = start; ida->line < end; ida->line++) {
+ if (ida->load_line > ida->line)
+ continue;
+ scanline = ida->img.data + ida->img.i.width * ida->load_line * 3;
+ ida->load_read(scanline,ida->load_line,ida->load_data);
+ ida->load_line++;
+ }
+ }
+
+ /* image processing */
+ if (ida->op_work && 0 == ida->op_preview) {
+ for (ida->line = start; ida->line < end; ida->line++) {
+ if (ida->op_line > ida->line)
+ continue;
+ scanline = ida->img.data + ida->img.i.width * ida->op_line * 3;
+ ida->op_work(&ida->op_src,&ida->op_rect,
+ scanline,ida->op_line,ida->op_data);
+ ida->op_line++;
+ }
+ }
+
+ /* image rendering */
+ if (ida->op_work && ida->op_preview) {
+ for (ida->line = start; ida->line < end; ida->line++) {
+ ida->op_line = ida->line;
+ ida->op_work(&ida->img,&ida->op_rect,
+ ida->preview_line,ida->line,ida->op_data);
+ viewer_renderline(ida,ida->preview_line);
+ }
+ } else {
+ for (ida->line = start; ida->line < end; ida->line++) {
+ scanline = ida->img.data + ida->img.i.width * ida->line * 3;
+ viewer_renderline(ida,scanline);
+ }
+ }
+
+ /* trigger redraw */
+ XClearArea(XtDisplay(ida->widget), XtWindow(ida->widget),
+ 0, viewer_i2s(ida->zoom,start),
+ ida->scrwidth, viewer_i2s(ida->zoom,ida->steps), True);
+
+ /* all done ? */
+ if (ida->line == ida->img.i.height) {
+ viewer_cleanup(ida);
+ ida->wproc = 0;
+#if 1
+ if (args.testload)
+ XtCallActionProc(ida->widget,"Next",NULL,NULL,0);
+#endif
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void viewer_workstart(struct ida_viewer *ida)
+{
+ /* (re-) start */
+ ida->line = 0;
+ if (!ida->wproc)
+ ida->wproc = XtAppAddWorkProc(app_context,viewer_workproc,ida);
+}
+
+static void viewer_workstop(struct ida_viewer *ida)
+{
+ if (!ida->wproc)
+ return;
+
+ viewer_cleanup(ida);
+ XtRemoveWorkProc(ida->wproc);
+ ida->wproc = 0;
+}
+
+static void viewer_workfinish(struct ida_viewer *ida)
+{
+ char *scanline;
+
+ if (ida->load_read) {
+ for (ida->line = ida->load_line; ida->line < ida->img.i.height;) {
+ scanline = ida->img.data + ida->img.i.width * ida->line * 3;
+ ida->load_read(scanline,ida->load_line,ida->load_data);
+ ida->line++;
+ ida->load_line++;
+ }
+ }
+ if (ida->op_work && 0 == ida->op_preview) {
+ for (ida->line = ida->op_line; ida->line < ida->img.i.height;) {
+ scanline = ida->img.data + ida->img.i.width * ida->line * 3;
+ ida->op_work(&ida->op_src,&ida->op_rect,
+ scanline,ida->op_line,ida->op_data);
+ ida->line++;
+ ida->op_line++;
+ }
+ }
+ viewer_workstop(ida);
+}
+
+/* ----------------------------------------------------------------------- */
+
+static void
+viewer_new_view(struct ida_viewer *ida)
+{
+ if (NULL != ida->ximage)
+ x11_destroy_ximage(ida->widget,ida->ximage,ida->ximage_shm);
+ if (NULL != ida->rgb_line)
+ free(ida->rgb_line);
+ if (NULL != ida->dither_line)
+ free(ida->dither_line);
+ if (NULL != ida->preview_line)
+ free(ida->preview_line);
+
+ ida->scrwidth = viewer_i2s(ida->zoom,ida->img.i.width);
+ ida->scrheight = viewer_i2s(ida->zoom,ida->img.i.height);
+ ida->steps = PROCESS_LINES;
+ if (ida->zoom < 0)
+ while ((ida->steps % (-ida->zoom+1)) != 0)
+ ida->steps++;
+
+ ida->rgb_line = malloc(ida->scrwidth*3);
+ ida->dither_line = malloc(ida->scrwidth);
+ ida->preview_line = malloc(ida->img.i.width*3);
+ ida->ximage = x11_create_ximage(ida->widget, ida->scrwidth, ida->scrheight,
+ &ida->ximage_shm);
+ if (NULL == ida->ximage) {
+ ida->zoom--;
+ return viewer_new_view(ida);
+ }
+ XtVaSetValues(ida->widget,
+ XtNwidth, ida->scrwidth,
+ XtNheight, ida->scrheight,
+ NULL);
+ viewer_workstart(ida);
+}
+
+static void
+viewer_timeout(XtPointer client_data, XtIntervalId *id);
+
+static int
+viewer_rubber_draw(struct ida_viewer *ida)
+{
+ XGCValues values;
+ struct ida_rect r = ida->current;
+ int x,y,w,h;
+
+ values.function = GXxor;
+ values.foreground = ida->mask;
+ XChangeGC(dpy,ida->wgc,GCFunction|GCForeground,&values);
+ if (r.x1 < r.x2) {
+ x = viewer_i2s(ida->zoom,r.x1);
+ w = viewer_i2s(ida->zoom,r.x2 - r.x1);
+ } else {
+ x = viewer_i2s(ida->zoom,r.x2);
+ w = viewer_i2s(ida->zoom,r.x1 - r.x2);
+ }
+ if (r.y1 < r.y2) {
+ y = viewer_i2s(ida->zoom,r.y1);
+ h = viewer_i2s(ida->zoom,r.y2 - r.y1);
+ } else {
+ y = viewer_i2s(ida->zoom,r.y2);
+ h = viewer_i2s(ida->zoom,r.y1 - r.y2);
+ }
+ if (0 == h && 0 == w)
+ return 0;
+ if (w)
+ w--;
+ if (h)
+ h--;
+ XDrawRectangle(dpy,XtWindow(ida->widget),ida->wgc,x,y,w,h);
+ return 1;
+}
+
+static void
+viewer_rubber_off(struct ida_viewer *ida)
+{
+ if (ida->marked)
+ viewer_rubber_draw(ida);
+ ida->marked = 0;
+ if (ida->timer)
+ XtRemoveTimeOut(ida->timer);
+ ida->timer = 0;
+}
+
+static void
+viewer_rubber_on(struct ida_viewer *ida)
+{
+ ida->marked = viewer_rubber_draw(ida);
+ if (ida->marked)
+ ida->timer = XtAppAddTimeOut(app_context,RUBBER_INTERVAL,
+ viewer_timeout,ida);
+}
+
+static void
+viewer_timeout(XtPointer client_data, XtIntervalId *id)
+{
+ struct ida_viewer *ida = client_data;
+
+ ida->timer = 0;
+ viewer_rubber_off(ida);
+ ida->mask <<= 1;
+ if ((ida->mask & 0x10) == 0x10)
+ ida->mask |= 0x01;
+ viewer_rubber_on(ida);
+}
+
+static void
+viewer_redraw(Widget widget, XtPointer client_data,
+ XEvent *ev, Boolean *cont)
+{
+ struct ida_viewer *ida = client_data;
+ XExposeEvent *event;
+ XGCValues values;
+
+ if (ev->type != Expose)
+ return;
+ event = (XExposeEvent*)ev;
+
+ if (NULL == ida->ximage)
+ return;
+ if (event->x + event->width > (int)ida->scrwidth)
+ return;
+ if (event->y + event->height > (int)ida->scrheight)
+ return;
+ if (NULL == ida->wgc)
+ ida->wgc = XCreateGC(XtDisplay(widget), XtWindow(widget), 0, NULL);
+
+ viewer_rubber_off(ida);
+ values.function = GXcopy;
+ XChangeGC(dpy,ida->wgc,GCFunction,&values);
+ XPUTIMAGE(XtDisplay(ida->widget), XtWindow(widget),
+ ida->wgc, ida->ximage,
+ event->x, event->y, event->x, event->y,
+ event->width, event->height);
+ viewer_rubber_on(ida);
+}
+
+static int
+viewer_pos2state(struct ida_viewer *ida, int x, int y)
+{
+ int x1,x2,y1,y2;
+
+ if (POINTER_PICK == ida->state)
+ return ida->state;
+
+ x1 = viewer_i2s(ida->zoom,ida->current.x1);
+ x2 = viewer_i2s(ida->zoom,ida->current.x2);
+ y1 = viewer_i2s(ida->zoom,ida->current.y1);
+ y2 = viewer_i2s(ida->zoom,ida->current.y2);
+ if ((x1 < x && x < x2) || (x2 < x && x < x1)) {
+ if (y1-RUBBER_RANGE < y && y < y1+RUBBER_RANGE)
+ return RUBBER_Y1;
+ if (y2-RUBBER_RANGE < y && y < y2+RUBBER_RANGE)
+ return RUBBER_Y2;
+ }
+ if ((y1 < y && y < y2) || (y2 < y && y < y1)) {
+ if (x1-RUBBER_RANGE < x && x < x1+RUBBER_RANGE)
+ return RUBBER_X1;
+ if (x2-RUBBER_RANGE < x && x < x2+RUBBER_RANGE)
+ return RUBBER_X2;
+ }
+ if (((x1 < x && x < x2) || (x2 < x && x < x1)) &&
+ ((y1 < y && y < y2) || (y2 < y && y < y1)))
+ return RUBBER_MOVE;
+ return RUBBER_NEW;
+}
+
+static void
+viewer_mouse(Widget widget, XtPointer client_data,
+ XEvent *ev, Boolean *cont)
+{
+ struct ida_viewer *ida = client_data;
+ int state = POINTER_NORMAL;
+ unsigned char *pix;
+ int x,y;
+
+ viewer_rubber_off(ida);
+
+ switch (ev->type) {
+ case ButtonPress:
+ {
+ XButtonEvent *eb = (XButtonEvent*)ev;
+
+ if (eb->button != Button1)
+ goto out;
+ ida->state = viewer_pos2state(ida,eb->x,eb->y);
+ switch (ida->state) {
+ case POINTER_PICK:
+ x = viewer_i2s(-ida->zoom,eb->x);
+ y = viewer_i2s(-ida->zoom,eb->y);
+ pix = ida->img.data + ida->img.i.width*y*3 + x*3;
+ ida->pick_cb(x,y,pix,ida->pick_data);
+ ida->pick_cb = NULL;
+ ida->pick_data = NULL;
+ ida->state = POINTER_NORMAL;
+ state = POINTER_NORMAL;
+ break;
+ case RUBBER_NEW:
+ ida->mask = 0x33333333;
+ ida->current.x1 = ida->current.x2 = viewer_i2s(-ida->zoom,eb->x);
+ ida->current.y1 = ida->current.y2 = viewer_i2s(-ida->zoom,eb->y);
+ break;
+ case RUBBER_MOVE:
+ ida->last_x = viewer_i2s(-ida->zoom,eb->x);
+ ida->last_y = viewer_i2s(-ida->zoom,eb->y);
+ break;
+ case RUBBER_X1:
+ ida->current.x1 = viewer_i2s(-ida->zoom,eb->x);
+ break;
+ case RUBBER_Y1:
+ ida->current.y1 = viewer_i2s(-ida->zoom,eb->y);
+ break;
+ case RUBBER_X2:
+ ida->current.x2 = viewer_i2s(-ida->zoom,eb->x);
+ break;
+ case RUBBER_Y2:
+ ida->current.y2 = viewer_i2s(-ida->zoom,eb->y);
+ break;
+ }
+ state = ida->state;
+ break;
+ }
+ case MotionNotify:
+ {
+ XMotionEvent *em = (XMotionEvent*)ev;
+
+ if (!(em->state & Button1Mask)) {
+ state = viewer_pos2state(ida,em->x,em->y);
+ goto out;
+ }
+ switch (ida->state) {
+ case RUBBER_NEW:
+ ida->current.x2 = viewer_i2s(-ida->zoom,em->x);
+ ida->current.y2 = viewer_i2s(-ida->zoom,em->y);
+ if (em->state & ShiftMask) {
+ /* square selection */
+ int xlen,ylen;
+ xlen = abs(ida->current.x1 - ida->current.x2);
+ ylen = abs(ida->current.y1 - ida->current.y2);
+ if (ylen > xlen) {
+ if (ida->current.x1 < ida->current.x2)
+ ida->current.x2 -= (xlen - ylen);
+ else
+ ida->current.x2 += (xlen - ylen);
+ } else {
+ if (ida->current.y1 < ida->current.y2)
+ ida->current.y2 -= (ylen - xlen);
+ else
+ ida->current.y2 += (ylen - xlen);
+ }
+ }
+ break;
+ case RUBBER_MOVE:
+ x = viewer_i2s(-ida->zoom,em->x);
+ y = viewer_i2s(-ida->zoom,em->y);
+ ida->current.x1 += (x - ida->last_x);
+ ida->current.x2 += (x - ida->last_x);
+ ida->current.y1 += (y - ida->last_y);
+ ida->current.y2 += (y - ida->last_y);
+ ida->last_x = x;
+ ida->last_y = y;
+ break;
+ case RUBBER_X1:
+ ida->current.x1 = viewer_i2s(-ida->zoom,em->x);
+ break;
+ case RUBBER_Y1:
+ ida->current.y1 = viewer_i2s(-ida->zoom,em->y);
+ break;
+ case RUBBER_X2:
+ ida->current.x2 = viewer_i2s(-ida->zoom,em->x);
+ break;
+ case RUBBER_Y2:
+ ida->current.y2 = viewer_i2s(-ida->zoom,em->y);
+ break;
+ }
+ state = ida->state;
+ break;
+ }
+ case ButtonRelease:
+ {
+ XButtonEvent *eb = (XButtonEvent*)ev;
+
+ if (eb->button != Button1)
+ goto out;
+ ida->state = POINTER_NORMAL;
+ state = ida->state;
+ break;
+ }
+ }
+
+ if (ida->current.x1 < 0)
+ ida->current.x1 = 0;
+ if (ida->current.x1 > ida->img.i.width)
+ ida->current.x1 = ida->img.i.width;
+ if (ida->current.x2 < 0)
+ ida->current.x2 = 0;
+ if (ida->current.x2 > ida->img.i.width)
+ ida->current.x2 = ida->img.i.width;
+ if (ida->current.y1 < 0)
+ ida->current.y1 = 0;
+ if (ida->current.y1 > ida->img.i.height)
+ ida->current.y1 = ida->img.i.height;
+ if (ida->current.y2 < 0)
+ ida->current.y2 = 0;
+ if (ida->current.y2 > ida->img.i.height)
+ ida->current.y2 = ida->img.i.height;
+
+ out:
+ XDefineCursor(dpy, XtWindow(widget), ptrs[state]);
+ viewer_rubber_on(ida);
+}
+
+/* ----------------------------------------------------------------------- */
+/* public stuff */
+
+void viewer_pick(struct ida_viewer *ida, viewer_pick_cb cb, XtPointer data)
+{
+ if (POINTER_NORMAL != ida->state)
+ return;
+ if (debug)
+ fprintf(stderr,"viewer_pick\n");
+ ida->state = POINTER_PICK;
+ ida->pick_cb = cb;
+ ida->pick_data = data;
+}
+
+void viewer_unpick(struct ida_viewer *ida)
+{
+ if (POINTER_PICK != ida->state)
+ return;
+ if (debug)
+ fprintf(stderr,"viewer_unpick\n");
+ ida->state = POINTER_NORMAL;
+ ida->pick_cb = NULL;
+ ida->pick_data = NULL;
+}
+
+void
+viewer_autozoom(struct ida_viewer *ida)
+{
+ if (GET_AUTOZOOM()) {
+ ida->zoom = 0;
+ while (XtScreen(ida->widget)->width < viewer_i2s(ida->zoom,ida->img.i.width) ||
+ XtScreen(ida->widget)->height < viewer_i2s(ida->zoom,ida->img.i.height))
+ ida->zoom--;
+ }
+ viewer_new_view(ida);
+}
+
+void
+viewer_setzoom(struct ida_viewer *ida, int zoom)
+{
+ ida->zoom = zoom;
+ viewer_new_view(ida);
+}
+
+static void
+viewer_op_rect(struct ida_viewer *ida)
+{
+ if (ida->current.x1 == ida->current.x2 &&
+ ida->current.y1 == ida->current.y2) {
+ /* full image */
+ ida->op_rect.x1 = 0;
+ ida->op_rect.x2 = ida->img.i.width;
+ ida->op_rect.y1 = 0;
+ ida->op_rect.y2 = ida->img.i.height;
+ return;
+ } else {
+ /* have selection */
+ if (ida->current.x1 < ida->current.x2) {
+ ida->op_rect.x1 = ida->current.x1;
+ ida->op_rect.x2 = ida->current.x2;
+ } else {
+ ida->op_rect.x1 = ida->current.x2;
+ ida->op_rect.x2 = ida->current.x1;
+ }
+ if (ida->current.y1 < ida->current.y2) {
+ ida->op_rect.y1 = ida->current.y1;
+ ida->op_rect.y2 = ida->current.y2;
+ } else {
+ ida->op_rect.y1 = ida->current.y2;
+ ida->op_rect.y2 = ida->current.y1;
+ }
+ }
+}
+
+int
+viewer_start_op(struct ida_viewer *ida, struct ida_op *op, void *parm)
+{
+ struct ida_image dst;
+
+ ptr_busy();
+ viewer_workfinish(ida);
+ viewer_rubber_off(ida);
+
+ /* try init */
+ viewer_op_rect(ida);
+ if (debug)
+ fprintf(stderr,"viewer_start_op: init %s(%p)\n",op->name,parm);
+ ida->op_data = op->init(&ida->img,&ida->op_rect,&dst.i,parm);
+ ptr_idle();
+ if (NULL == ida->op_data)
+ return -1;
+ dst.data = malloc(dst.i.width * dst.i.height * 3);
+
+ /* prepare background processing */
+ if (ida->undo.data) {
+ free(ida->undo.data);
+ memset(&ida->undo,0,sizeof(ida->undo));
+ }
+ if (ida->op_src.data) {
+ fprintf(stderr,"have op_src buffer /* shouldn't happen */");
+ free(ida->op_src.data);
+ }
+ ida->op_src = ida->img;
+ ida->img = dst;
+ ida->op_line = 0;
+ ida->op_work = op->work;
+ ida->op_done = op->done;
+ ida->op_preview = 0;
+
+ if (ida->op_src.i.width != ida->img.i.width ||
+ ida->op_src.i.height != ida->img.i.height) {
+ memset(&ida->current,0,sizeof(ida->current));
+ viewer_autozoom(ida);
+ } else
+ viewer_new_view(ida);
+ return 0;
+}
+
+int
+viewer_undo(struct ida_viewer *ida)
+{
+ int resize;
+
+ viewer_workfinish(ida);
+ if (NULL == ida->undo.data)
+ return -1;
+ viewer_rubber_off(ida);
+ memset(&ida->current,0,sizeof(ida->current));
+
+ resize = (ida->undo.i.width != ida->img.i.width ||
+ ida->undo.i.height != ida->img.i.height);
+ free(ida->img.data);
+ ida->img = ida->undo;
+ memset(&ida->undo,0,sizeof(ida->undo));
+
+ if (resize)
+ viewer_autozoom(ida);
+ else
+ viewer_new_view(ida);
+ return 0;
+}
+
+int
+viewer_start_preview(struct ida_viewer *ida, struct ida_op *op, void *parm)
+{
+ struct ida_image dst;
+
+ viewer_workfinish(ida);
+
+ /* try init */
+ viewer_op_rect(ida);
+ ida->op_data = op->init(&ida->img,&ida->op_rect,&dst.i,parm);
+ if (NULL == ida->op_data)
+ return -1;
+
+ /* prepare background preview */
+ ida->op_line = 0;
+ ida->op_work = op->work;
+ ida->op_done = op->done;
+ ida->op_preview = 1;
+
+ viewer_workstart(ida);
+ return 0;
+}
+
+int
+viewer_cancel_preview(struct ida_viewer *ida)
+{
+ viewer_workstop(ida);
+ viewer_workstart(ida);
+ return 0;
+}
+
+int
+viewer_loader_start(struct ida_viewer *ida, struct ida_loader *loader,
+ FILE *fp, char *filename, unsigned int page)
+{
+ struct ida_image_info info;
+ void *data;
+
+ /* init loader */
+ ptr_busy();
+ memset(&info,0,sizeof(info));
+ data = loader->init(fp,filename,page,&info,0);
+ ptr_idle();
+ if (NULL == data) {
+ fprintf(stderr,"loading %s [%s] FAILED\n",filename,loader->name);
+ if (fp)
+ hex_display(filename);
+ return -1;
+ }
+
+ /* ok, going to load new image */
+ viewer_workstop(ida);
+ viewer_rubber_off(ida);
+ memset(&ida->current,0,sizeof(ida->current));
+ if (ida->undo.data) {
+ free(ida->undo.data);
+ memset(&ida->undo,0,sizeof(ida->undo));
+ }
+ if (NULL != ida->img.data)
+ free(ida->img.data);
+ ida->file = filename;
+ ida->img.i = info;
+ ida->img.data = malloc(ida->img.i.width * ida->img.i.height * 3);
+
+ /* prepare background loading */
+ ida->load_line = 0;
+ ida->load_read = loader->read;
+ ida->load_done = loader->done;
+ ida->load_data = data;
+
+ viewer_autozoom(ida);
+ return info.npages;
+}
+
+int
+viewer_loadimage(struct ida_viewer *ida, char *filename, unsigned int page)
+{
+ struct list_head *item;
+ struct ida_loader *loader;
+ char blk[512];
+ FILE *fp;
+
+ if (NULL == (fp = fopen(filename, "r"))) {
+ fprintf(stderr,"fopen %s: %s\n",filename,strerror(errno));
+ return -1;
+ }
+ if (debug)
+ fprintf(stderr,"load: %s\n",filename);
+ memset(blk,0,sizeof(blk));
+ fread(blk,1,sizeof(blk),fp);
+ rewind(fp);
+
+ /* pick loader */
+ list_for_each(item,&loaders) {
+ loader = list_entry(item, struct ida_loader, list);
+#if 0
+ if (NULL == loader->magic)
+ break;
+#else
+ if (NULL == loader->magic)
+ continue;
+#endif
+ if (0 == memcmp(blk+loader->moff,loader->magic,loader->mlen))
+ return viewer_loader_start(ida,loader,fp,filename,page);
+ }
+ fprintf(stderr,"%s: unknown format\n",filename);
+ hex_display(filename);
+ fclose(fp);
+ return -1;
+}
+
+int
+viewer_setimage(struct ida_viewer *ida, struct ida_image *img, char *name)
+{
+ /* ok, going to load new image */
+ viewer_workstop(ida);
+ viewer_rubber_off(ida);
+ memset(&ida->current,0,sizeof(ida->current));
+ if (ida->undo.data) {
+ free(ida->undo.data);
+ memset(&ida->undo,0,sizeof(ida->undo));
+ }
+
+ if (NULL != ida->img.data)
+ free(ida->img.data);
+ ida->file = name;
+ ida->img = *img;
+
+ viewer_autozoom(ida);
+ return 0;
+}
+
+struct ida_viewer*
+viewer_init(Widget widget)
+{
+ Colormap cmap = DefaultColormapOfScreen(XtScreen(widget));
+ struct ida_viewer *ida;
+ XColor white,red,dummy;
+ unsigned int i;
+
+ ida = malloc(sizeof(*ida));
+ memset(ida,0,sizeof(*ida));
+ ida->widget = widget;
+ XtAddEventHandler(widget,ExposureMask,False,viewer_redraw,ida);
+ XtAddEventHandler(widget,
+ ButtonPressMask |
+ ButtonReleaseMask |
+ PointerMotionMask,
+ False,viewer_mouse,ida);
+
+ ptrs[POINTER_NORMAL] = XCreateFontCursor(dpy,XC_left_ptr);
+ ptrs[POINTER_BUSY] = XCreateFontCursor(dpy,XC_watch);
+ ptrs[POINTER_PICK] = XCreateFontCursor(dpy,XC_tcross);
+ ptrs[RUBBER_NEW] = XCreateFontCursor(dpy,XC_left_ptr);
+ ptrs[RUBBER_MOVE] = XCreateFontCursor(dpy,XC_fleur);
+ ptrs[RUBBER_X1] = XCreateFontCursor(dpy,XC_sb_h_double_arrow);
+ ptrs[RUBBER_X2] = XCreateFontCursor(dpy,XC_sb_h_double_arrow);
+ ptrs[RUBBER_Y1] = XCreateFontCursor(dpy,XC_sb_v_double_arrow);
+ ptrs[RUBBER_Y2] = XCreateFontCursor(dpy,XC_sb_v_double_arrow);
+ if (XAllocNamedColor(dpy,cmap,"white",&white,&dummy) &&
+ XAllocNamedColor(dpy,cmap,"red",&red,&dummy))
+ for (i = 0; i < sizeof(ptrs)/sizeof(Cursor); i++)
+ XRecolorCursor(dpy,ptrs[i],&red,&white);
+
+ return ida;
+}
diff --git a/viewer.h b/viewer.h
new file mode 100644
index 0000000..0f81c5b
--- /dev/null
+++ b/viewer.h
@@ -0,0 +1,96 @@
+#include <X11/Intrinsic.h>
+
+#define POINTER_NORMAL 0
+#define POINTER_BUSY 1
+#define POINTER_PICK 2
+#define RUBBER_NEW 3
+#define RUBBER_MOVE 4
+#define RUBBER_X1 5
+#define RUBBER_Y1 6
+#define RUBBER_X2 7
+#define RUBBER_Y2 8
+#define POINTER_COUNT 9
+
+extern Cursor ptrs[];
+
+/* ----------------------------------------------------------------------- */
+
+typedef void (*viewer_pick_cb)(int x, int y, unsigned char *pix,
+ XtPointer data);
+
+struct ida_viewer {
+ /* x11 stuff */
+ Widget widget;
+ GC wgc;
+ XtIntervalId timer;
+
+ /* image data */
+ struct ida_image img;
+ struct ida_image undo;
+ char *file;
+
+ /* view data */
+ int zoom;
+ unsigned int scrwidth, scrheight;
+ XImage *ximage;
+ void *ximage_shm;
+ unsigned char *rgb_line;
+ unsigned char *dither_line;
+ unsigned char *preview_line;
+
+ /* marked rectangle */
+ struct ida_rect current;
+ int marked,state;
+ int last_x,last_y;
+ unsigned long mask;
+
+ /* pixel picker */
+ viewer_pick_cb pick_cb;
+ XtPointer pick_data;
+
+ /* workproc state */
+ XtWorkProcId wproc;
+ unsigned int line;
+ unsigned int steps;
+
+ /* image loader */
+ unsigned int load_line;
+ void (*load_read)(unsigned char *dst, unsigned int line,
+ void *data);
+ void (*load_done)(void *data);
+ void *load_data;
+
+ /* image operation */
+ struct ida_image op_src;
+ struct ida_rect op_rect;
+ unsigned int op_line;
+ unsigned int op_preview;
+ void (*op_work)(struct ida_image *src, struct ida_rect *rect,
+ unsigned char *dst, int line,
+ void *data);
+ void (*op_done)(void *data);
+ void *op_data;
+};
+
+/* ----------------------------------------------------------------------- */
+
+Pixmap image_to_pixmap(struct ida_image *img);
+
+/* ----------------------------------------------------------------------- */
+
+struct ida_viewer* viewer_init(Widget widget);
+int viewer_loader_start(struct ida_viewer *ida, struct ida_loader *loader,
+ FILE *fp, char *filename, unsigned int page);
+int viewer_loadimage(struct ida_viewer *ida, char *filename, unsigned int page);
+int viewer_setimage(struct ida_viewer *ida, struct ida_image *img, char *name);
+void viewer_autozoom(struct ida_viewer *ida);
+void viewer_setzoom(struct ida_viewer *ida, int zoom);
+int viewer_i2s(int zoom, int val);
+int viewer_undo(struct ida_viewer *ida);
+int viewer_start_op(struct ida_viewer *ida, struct ida_op *op, void *parm);
+int viewer_start_preview(struct ida_viewer *ida, struct ida_op *op,
+ void *parm);
+int viewer_cancel_preview(struct ida_viewer *ida);
+
+void viewer_pick(struct ida_viewer *ida, viewer_pick_cb cb, XtPointer data);
+void viewer_unpick(struct ida_viewer *ida);
diff --git a/wr/Makefile b/wr/Makefile
new file mode 100644
index 0000000..4e33e30
--- /dev/null
+++ b/wr/Makefile
@@ -0,0 +1,2 @@
+default:
+ cd ..; $(MAKE)
diff --git a/wr/write-jpeg.c b/wr/write-jpeg.c
new file mode 100644
index 0000000..2e39c68
--- /dev/null
+++ b/wr/write-jpeg.c
@@ -0,0 +1,103 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <jpeglib.h>
+
+#include "readers.h"
+#include "writers.h"
+#include "misc.h"
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/Text.h>
+#include <Xm/SelectioB.h>
+#include "RegEdit.h"
+#include "ida.h"
+#include "viewer.h"
+
+/* ---------------------------------------------------------------------- */
+/* jpeg writer */
+
+static Widget jpeg_shell;
+static Widget jpeg_text;
+static int jpeg_quality = 75;
+
+static void
+jpeg_button_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmSelectionBoxCallbackStruct *cb = call_data;
+
+ if (XmCR_OK == cb->reason) {
+ jpeg_quality = atoi(XmTextGetString(jpeg_text));
+ do_save_print();
+ }
+ XtUnmanageChild(jpeg_shell);
+}
+
+static int
+jpeg_conf(Widget parent, struct ida_image *img)
+{
+ char tmp[32];
+
+ if (!jpeg_shell) {
+ /* build dialog */
+ jpeg_shell = XmCreatePromptDialog(parent,"jpeg",NULL,0);
+ XmdRegisterEditres(XtParent(jpeg_shell));
+ XtUnmanageChild(XmSelectionBoxGetChild(jpeg_shell,XmDIALOG_HELP_BUTTON));
+ jpeg_text = XmSelectionBoxGetChild(jpeg_shell,XmDIALOG_TEXT);
+ XtAddCallback(jpeg_shell,XmNokCallback,jpeg_button_cb,NULL);
+ XtAddCallback(jpeg_shell,XmNcancelCallback,jpeg_button_cb,NULL);
+ }
+ sprintf(tmp,"%d",jpeg_quality);
+ XmTextSetString(jpeg_text,tmp);
+ XtManageChild(jpeg_shell);
+ return 0;
+}
+
+static int
+jpeg_write(FILE *fp, struct ida_image *img)
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ unsigned char *line;
+ unsigned int i;
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ jpeg_stdio_dest(&cinfo, fp);
+ cinfo.image_width = img->i.width;
+ cinfo.image_height = img->i.height;
+ if (img->i.dpi) {
+ cinfo.density_unit = 1;
+ cinfo.X_density = img->i.dpi;
+ cinfo.Y_density = img->i.dpi;
+ }
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, jpeg_quality, TRUE);
+ jpeg_start_compress(&cinfo, TRUE);
+
+ for (i = 0, line = img->data; i < img->i.height; i++, line += img->i.width*3)
+ jpeg_write_scanlines(&cinfo, &line, 1);
+
+ jpeg_finish_compress(&(cinfo));
+ jpeg_destroy_compress(&(cinfo));
+ return 0;
+}
+
+struct ida_writer jpeg_writer = {
+ label: "JPEG",
+ ext: { "jpg", "jpeg", NULL},
+ write: jpeg_write,
+ conf: jpeg_conf,
+};
+
+static void __init init_wr(void)
+{
+ write_register(&jpeg_writer);
+}
+
diff --git a/wr/write-png.c b/wr/write-png.c
new file mode 100644
index 0000000..ee4fc09
--- /dev/null
+++ b/wr/write-png.c
@@ -0,0 +1,76 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <png.h>
+
+#include "readers.h"
+#include "writers.h"
+#include "viewer.h"
+
+/* ---------------------------------------------------------------------- */
+/* save */
+
+static int
+png_write(FILE *fp, struct ida_image *img)
+{
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ png_bytep row;
+ unsigned int y;
+
+ /* Create and initialize the png_struct with the desired error handler
+ * functions. If you want to use the default stderr and longjump method,
+ * you can supply NULL for the last three parameters. We also check that
+ * the library version is compatible with the one used at compile time,
+ * in case we are using dynamically linked libraries. REQUIRED.
+ */
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
+ if (png_ptr == NULL)
+ goto oops;
+
+ /* Allocate/initialize the image information data. REQUIRED */
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL)
+ goto oops;
+ if (setjmp(png_jmpbuf(png_ptr)))
+ goto oops;
+
+ png_init_io(png_ptr, fp);
+ png_set_IHDR(png_ptr, info_ptr, img->i.width, img->i.height, 8,
+ PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+ if (img->i.dpi) {
+ png_set_pHYs(png_ptr, info_ptr,
+ res_inch_to_m(img->i.dpi),
+ res_inch_to_m(img->i.dpi),
+ PNG_RESOLUTION_METER);
+ }
+ png_write_info(png_ptr, info_ptr);
+ png_set_packing(png_ptr);
+
+ for (y = 0; y < img->i.height; y++) {
+ row = img->data + y * 3 * img->i.width;
+ png_write_rows(png_ptr, &row, 1);
+ }
+ png_write_end(png_ptr, info_ptr);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return 0;
+
+ oops:
+ fprintf(stderr,"can't save image: libpng error\n");
+ if (png_ptr)
+ png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
+ return -1;
+}
+
+static struct ida_writer png_writer = {
+ label: "PNG",
+ ext: { "png", NULL},
+ write: png_write,
+};
+
+static void __init init_wr(void)
+{
+ write_register(&png_writer);
+}
diff --git a/wr/write-ppm.c b/wr/write-ppm.c
new file mode 100644
index 0000000..d16035b
--- /dev/null
+++ b/wr/write-ppm.c
@@ -0,0 +1,34 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "readers.h"
+#include "writers.h"
+#include "viewer.h"
+
+/* ---------------------------------------------------------------------- */
+/* save */
+
+static int
+ppm_write(FILE *fp, struct ida_image *img)
+{
+ fprintf(fp,"P6\n"
+ "# written by ida " VERSION "\n"
+ "# http://bytesex.org/ida/\n"
+ "%d %d\n255\n",
+ img->i.width,img->i.height);
+ fwrite(img->data, img->i.height, 3*img->i.width, fp);
+ return 0;
+}
+
+static struct ida_writer ppm_writer = {
+ label: "PPM",
+ ext: { "ppm", NULL},
+ write: ppm_write,
+};
+
+static void __init init_wr(void)
+{
+ write_register(&ppm_writer);
+}
diff --git a/wr/write-ps.c b/wr/write-ps.c
new file mode 100644
index 0000000..914a896
--- /dev/null
+++ b/wr/write-ps.c
@@ -0,0 +1,475 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <Xm/Xm.h>
+#include <Xm/Text.h>
+#include <Xm/SelectioB.h>
+#include <Xm/DrawingA.h>
+#include <Xm/RowColumn.h>
+#include <Xm/PushB.h>
+#include <Xm/Scale.h>
+#include <Xm/Label.h>
+#include "RegEdit.h"
+
+#include "ida.h"
+#include "readers.h"
+#include "writers.h"
+#include "viewer.h"
+
+struct PAPER {
+ char *name;
+ int width,height;
+};
+
+static struct PAPER formats[] = {
+ {
+ name: "A4",
+ width: 595,
+ height: 842,
+ },{
+ name: "Letter",
+ width: 612,
+ height: 792,
+ },{
+ /* EOF */
+ }
+};
+
+static const char *header =
+"%%!PS-Adobe-2.0 EPSF-2.0\n"
+"%%%%Creator: ida " VERSION " (http://bytesex.org/ida/)\n"
+"%%%%Pages: 1\n"
+"%%%%BoundingBox: %d %d %d %d\n"
+"%%%%DocumentFonts: \n"
+"%%%%EndComments\n"
+"%%%%EndProlog\n"
+"\n"
+"%%%%Page: 1 1"
+"\n"
+"/origstate save def\n"
+"20 dict begin\n";
+
+static const char *footer =
+"\n"
+"showpage\n"
+"end\n"
+"origstate restore\n"
+"%%%%Trailer\n";
+
+/* taken from xwd2ps, ftp://ftp.x.org/R5contrib/xwd2ps.tar.Z */
+static const char *ColorImage =
+"% define 'colorimage' if it isn't defined\n"
+"% ('colortogray' and 'mergeprocs' come from xwd2ps\n"
+"% via xgrab)\n"
+"/colorimage where % do we know about 'colorimage'?\n"
+" { pop } % yes: pop off the 'dict' returned\n"
+" { % no: define one\n"
+" /colortogray { % define an RGB->I function\n"
+" /rgbdata exch store % call input 'rgbdata'\n"
+" rgbdata length 3 idiv\n"
+" /npixls exch store\n"
+" /rgbindx 0 store\n"
+" 0 1 npixls 1 sub {\n"
+" grays exch\n"
+" rgbdata rgbindx get 20 mul % Red\n"
+" rgbdata rgbindx 1 add get 32 mul % Green\n"
+" rgbdata rgbindx 2 add get 12 mul % Blue\n"
+" add add 64 idiv % I = .5G + .31R + .18B\n"
+" put\n"
+" /rgbindx rgbindx 3 add store\n"
+" } for\n"
+" grays 0 npixls getinterval\n"
+" } bind def\n"
+"\n"
+" % Utility procedure for colorimage operator.\n"
+" % This procedure takes two procedures off the\n"
+" % stack and merges them into a single procedure.\n"
+"\n"
+" /mergeprocs { % def\n"
+" dup length\n"
+" 3 -1 roll\n"
+" dup\n"
+" length\n"
+" dup\n"
+" 5 1 roll\n"
+" 3 -1 roll\n"
+" add\n"
+" array cvx\n"
+" dup\n"
+" 3 -1 roll\n"
+" 0 exch\n"
+" putinterval\n"
+" dup\n"
+" 4 2 roll\n"
+" putinterval\n"
+" } bind def\n"
+"\n"
+" /colorimage { % def\n"
+" pop pop % remove 'false 3' operands\n"
+" {colortogray} mergeprocs\n"
+" image\n"
+" } bind def\n"
+" } ifelse % end of 'false' case\n"
+"\n";
+
+
+/* ---------------------------------------------------------------------- */
+/* save */
+
+#define PORTRAIT 0
+#define LANDSCAPE 1
+
+#define DRAW_SIZE 200
+#define DRAW_SCALE 6
+#define DSCALED(x) (((x)+DRAW_SCALE/2)/DRAW_SCALE)
+
+static struct ps_options {
+ Widget shell,draw,scale,geo;
+ int xscale,yscale;
+ GC gc;
+ int lastx,lasty;
+
+ int format;
+ int ori;
+ int scaling;
+
+ int iwidth,iheight,ires;
+ int pwidth,pheight;
+ int mwidth,mheight;
+ int width,height,xcenter,ycenter;
+} ps;
+
+static void
+ps_draw(Widget widget, XtPointer client_data, XtPointer calldata)
+{
+ XmDrawingAreaCallbackStruct *cb = calldata;
+ XExposeEvent *e;
+ XmString str;
+ char buf[128];
+ int x,y,w,h;
+
+ if (!(cb->reason == XmCR_EXPOSE))
+ return;
+ e = (XExposeEvent*)cb->event;
+ if (e->count)
+ return;
+
+ if (!ps.gc)
+ ps.gc = XCreateGC(dpy,XtWindow(app_shell),0,NULL);
+
+ w = DSCALED(ps.pwidth);
+ h = DSCALED(ps.pheight);
+ x = (DRAW_SIZE-w) / 2;
+ y = (DRAW_SIZE-h) / 2;
+ XDrawRectangle(dpy,XtWindow(widget),ps.gc, x,y,w,h);
+
+ w = DSCALED(ps.width);
+ h = DSCALED(ps.height);
+ x += DSCALED(ps.xcenter - ps.width/2);
+ y += DSCALED(ps.ycenter - ps.height/2);
+ XFillRectangle(dpy,XtWindow(widget),ps.gc, x,y,w,h);
+
+ sprintf(buf,"%d dpi",ps.iwidth * 72 / ps.width);
+ str = XmStringGenerate(buf, NULL, XmMULTIBYTE_TEXT, NULL);
+ XtVaSetValues(ps.geo,XmNlabelString,str,NULL);
+ XmStringFree(str);
+}
+
+static void
+ps_defaults(void)
+{
+ /* max size, keep aspect ratio */
+ if (ps.ori == PORTRAIT) {
+ ps.pwidth = formats[ps.format].width;
+ ps.pheight = formats[ps.format].height;
+ } else {
+ ps.pheight = formats[ps.format].width;
+ ps.pwidth = formats[ps.format].height;
+ }
+
+ if (ps.iwidth * ps.pheight > ps.iheight * ps.pwidth) {
+ ps.mwidth = ps.pwidth;
+ ps.mheight = ps.iheight * ps.mwidth / ps.iwidth;
+ } else {
+ ps.mheight = ps.pheight;
+ ps.mwidth = ps.iwidth * ps.mheight / ps.iheight;
+ }
+ ps.scaling = 0;
+ if (ps.ires) {
+ /* Use image resolution to calculate default scaling factor.
+ * The image will be printed in original size if it fits into
+ * one page */
+ ps.scaling = ps.iwidth * 72 * 1000 / ps.mwidth / ps.ires;
+ }
+ if (ps.scaling > 1000 || ps.scaling < 1) {
+ /* default: maxpect with some border */
+ ps.scaling = 1000;
+ while (ps.mwidth * ps.scaling / 1000 + 50 > ps.mwidth ||
+ ps.mheight * ps.scaling / 1000 + 50 > ps.mheight)
+ ps.scaling--;
+ }
+ XmScaleSetValue(ps.scale,ps.scaling);
+ ps.width = ps.mwidth * ps.scaling / 1000;
+ ps.height = ps.mheight * ps.scaling / 1000;
+ ps.xcenter = ps.pwidth/2;
+ ps.ycenter = ps.pheight/2;
+
+ if (XtWindow(ps.draw))
+ XClearArea(XtDisplay(ps.draw), XtWindow(ps.draw),
+ 0,0,0,0, True);
+}
+
+static void
+ps_ranges(void)
+{
+ if (ps.width == 0)
+ ps.width = 1;
+ if (ps.height == 0)
+ ps.height = 1;
+ if (ps.xcenter - ps.width/2 < 0)
+ ps.xcenter = ps.width/2;
+ if (ps.xcenter + ps.width/2 > ps.pwidth)
+ ps.xcenter = ps.pwidth - ps.width/2;
+ if (ps.ycenter - ps.height/2 < 0)
+ ps.ycenter = ps.height/2;
+ if (ps.ycenter + ps.height/2 > ps.pheight)
+ ps.ycenter = ps.pheight - ps.height/2;
+}
+
+static void
+ps_mouse(Widget widget, XtPointer client_data,
+ XEvent *ev, Boolean *cont)
+{
+ switch (ev->type) {
+ case ButtonPress:
+ {
+ XButtonEvent *e = (XButtonEvent*)ev;
+
+ ps.lastx = e->x;
+ ps.lasty = e->y;
+ break;
+ }
+ case MotionNotify:
+ {
+ XMotionEvent *e = (XMotionEvent*)ev;
+
+ if (e->state & Button1Mask) {
+ ps.xcenter += (e->x - ps.lastx) * DRAW_SCALE;
+ ps.ycenter += (e->y - ps.lasty) * DRAW_SCALE;
+ ps.lastx = e->x;
+ ps.lasty = e->y;
+ }
+ break;
+ default:
+ return;
+ }
+ }
+ ps_ranges();
+ if (XtWindow(ps.draw))
+ XClearArea(XtDisplay(ps.draw), XtWindow(ps.draw),
+ 0,0,0,0, True);
+}
+
+static void
+ps_paper_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ ps.format = (intptr_t)clientdata;
+ ps_defaults();
+}
+
+static void
+ps_ori_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ ps.ori = (intptr_t)clientdata;
+ ps_defaults();
+}
+
+static void
+ps_scaling_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmScaleCallbackStruct *cd = call_data;
+
+ ps.scaling = cd->value;
+ ps.width = ps.mwidth * ps.scaling / 1000;
+ ps.height = ps.mheight * ps.scaling / 1000;
+ ps_ranges();
+ if (XtWindow(ps.draw))
+ XClearArea(XtDisplay(ps.draw), XtWindow(ps.draw),
+ 0,0,0,0, True);
+}
+
+static void
+ps_button_cb(Widget widget, XtPointer clientdata, XtPointer call_data)
+{
+ XmSelectionBoxCallbackStruct *cb = call_data;
+
+ if (XmCR_OK == cb->reason) {
+ do_save_print();
+ }
+ XtUnmanageChild(ps.shell);
+}
+
+static int
+ps_conf(Widget parent, struct ida_image *img)
+{
+ Widget rc,menu,push,opt;
+ Arg args[2];
+ intptr_t i;
+
+ if (!ps.shell) {
+ /* build dialog */
+ ps.shell = XmCreatePromptDialog(parent,"ps",NULL,0);
+ XmdRegisterEditres(XtParent(ps.shell));
+ XtUnmanageChild(XmSelectionBoxGetChild(ps.shell,XmDIALOG_HELP_BUTTON));
+ XtUnmanageChild(XmSelectionBoxGetChild(ps.shell,XmDIALOG_SELECTION_LABEL));
+ XtUnmanageChild(XmSelectionBoxGetChild(ps.shell,XmDIALOG_TEXT));
+ XtAddCallback(ps.shell,XmNokCallback,ps_button_cb,NULL);
+ XtAddCallback(ps.shell,XmNcancelCallback,ps_button_cb,NULL);
+
+ rc = XtVaCreateManagedWidget("rc1",xmRowColumnWidgetClass,
+ ps.shell,NULL);
+ ps.draw = XtVaCreateManagedWidget("draw",xmDrawingAreaWidgetClass,rc,
+ XtNwidth,DRAW_SIZE,
+ XtNheight,DRAW_SIZE,
+ NULL);
+ XtAddCallback(ps.draw,XmNexposeCallback,ps_draw,NULL);
+ XtAddEventHandler(ps.draw,
+ ButtonPressMask |
+ ButtonReleaseMask |
+ ButtonMotionMask,
+ False,ps_mouse,NULL);
+ rc = XtVaCreateManagedWidget("rc2",xmRowColumnWidgetClass,
+ rc,NULL);
+
+ /* paper */
+ menu = XmCreatePulldownMenu(rc,"paperM",NULL,0);
+ XtSetArg(args[0],XmNsubMenuId,menu);
+ opt = XmCreateOptionMenu(rc,"paper",args,1);
+ XtManageChild(opt);
+ for (i = 0; formats[i].name != NULL; i++) {
+ push = XtVaCreateManagedWidget(formats[i].name,xmPushButtonWidgetClass,menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,ps_paper_cb,(XtPointer)i);
+ }
+
+ /* orientation */
+ menu = XmCreatePulldownMenu(rc,"oriM",NULL,0);
+ XtSetArg(args[0],XmNsubMenuId,menu);
+ opt = XmCreateOptionMenu(rc,"ori",args,1);
+ XtManageChild(opt);
+ push = XtVaCreateManagedWidget("portrait",xmPushButtonWidgetClass,
+ menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,ps_ori_cb,(XtPointer)PORTRAIT);
+ push = XtVaCreateManagedWidget("landscape",xmPushButtonWidgetClass,
+ menu,NULL);
+ XtAddCallback(push,XmNactivateCallback,ps_ori_cb,(XtPointer)LANDSCAPE);
+
+ ps.scale = XtVaCreateManagedWidget("scale",xmScaleWidgetClass,rc,NULL);
+ XtAddCallback(ps.scale,XmNdragCallback,ps_scaling_cb,NULL);
+ XtAddCallback(ps.scale,XmNvalueChangedCallback,ps_scaling_cb,NULL);
+
+ /* output */
+ ps.geo = XtVaCreateManagedWidget("geo",xmLabelWidgetClass,rc,NULL);
+ }
+
+ ps.iwidth = img->i.width;
+ ps.iheight = img->i.height;
+ ps.ires = 0;
+ if (ida->img.i.dpi)
+ ps.ires = ida->img.i.dpi;
+ ps_defaults();
+
+ XtManageChild(ps.shell);
+ return 0;
+}
+
+static int
+ps_write(FILE *fp, struct ida_image *img)
+{
+ unsigned int width,height,xoff,yoff;
+ unsigned int iwidth,iheight;
+ unsigned int x,y;
+ unsigned char *p;
+
+ if (ps.ori == PORTRAIT) {
+ iwidth = img->i.width;
+ iheight = img->i.height;
+ width = ps.width;
+ height = ps.height;
+ xoff = ps.xcenter - ps.width/2;
+ yoff = (ps.pheight - ps.ycenter) - ps.height/2;
+ } else{
+ iwidth = img->i.height;
+ iheight = img->i.width;
+ width = ps.height;
+ height = ps.width;
+ xoff = ps.ycenter - ps.height/2;
+ yoff = ps.xcenter - ps.width/2;
+ }
+
+ /* PS header */
+ fprintf(fp,header, /* includes bbox */
+ xoff,yoff,xoff+width,yoff+height);
+ fprintf(fp,"\n"
+ "/pix %d string def\n"
+ "/grays %d string def\n"
+ "/npixls 0 def\n"
+ "/rgbindx 0 def\n"
+ "\n",
+ img->i.width*3,img->i.width);
+ fwrite(ColorImage,strlen(ColorImage),1,fp);
+
+ fprintf(fp,"%d %d translate\n",xoff,yoff);
+ fprintf(fp,"%d %d scale\n",width,height);
+
+ fprintf(fp,"\n"
+ "%d %d 8\n"
+ "[%d 0 0 -%d 0 %d]\n"
+ "{currentfile pix readhexstring pop}\n"
+ "false 3 colorimage\n",
+ iwidth,iheight,iwidth,iheight,iheight);
+
+ /* image data + ps footer */
+ if (ps.ori == PORTRAIT) {
+ p = img->data;
+ for (y = 0; y < img->i.height; y++) {
+ for (x = 0; x < img->i.width; x++) {
+ if (0 == (x % 10))
+ fprintf(fp,"\n");
+ fprintf(fp,"%02x%02x%02x ",p[0],p[1],p[2]);
+ p += 3;
+ }
+ fprintf(fp,"\n");
+ }
+ } else {
+ for (x = img->i.width-1; x != -1; x--) {
+ p = img->data + 3*x;
+ for (y = 0; y < img->i.height; y++) {
+ if (0 == (y % 10))
+ fprintf(fp,"\n");
+ fprintf(fp,"%02x%02x%02x ",p[0],p[1],p[2]);
+ p += img->i.width*3;
+ }
+ fprintf(fp,"\n");
+ }
+ }
+ fprintf(fp,footer);
+ return 0;
+}
+
+struct ida_writer ps_writer = {
+ label: "PostScript",
+ ext: { "ps", "eps", NULL},
+ write: ps_write,
+ conf: ps_conf,
+};
+
+static void __init init_wr(void)
+{
+ write_register(&ps_writer);
+}
+
diff --git a/wr/write-tiff.c b/wr/write-tiff.c
new file mode 100644
index 0000000..9d4910e
--- /dev/null
+++ b/wr/write-tiff.c
@@ -0,0 +1,60 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <tiffio.h>
+
+#include "readers.h"
+#include "writers.h"
+#include "viewer.h"
+
+/* ---------------------------------------------------------------------- */
+/* save */
+
+static int
+tiff_write(FILE *fp, struct ida_image *img)
+{
+ TIFF *TiffHndl;
+ tdata_t buf;
+ unsigned int y;
+
+ TiffHndl = TIFFFdOpen(fileno(fp),"42.tiff","w");
+ if (TiffHndl == NULL)
+ return -1;
+ TIFFSetField(TiffHndl, TIFFTAG_IMAGEWIDTH, img->i.width);
+ TIFFSetField(TiffHndl, TIFFTAG_IMAGELENGTH, img->i.height);
+ TIFFSetField(TiffHndl, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
+ TIFFSetField(TiffHndl, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
+ TIFFSetField(TiffHndl, TIFFTAG_BITSPERSAMPLE, 8);
+ TIFFSetField(TiffHndl, TIFFTAG_SAMPLESPERPIXEL, 3);
+ TIFFSetField(TiffHndl, TIFFTAG_ROWSPERSTRIP, 2);
+ TIFFSetField(TiffHndl, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
+#if 0 /* fixme: make this configureable */
+ TIFFSetField(TiffHndl, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
+ TIFFSetField(TiffHndl, TIFFTAG_PREDICTOR, 2);
+#endif
+ if (img->i.dpi) {
+ float dpi = img->i.dpi;
+ TIFFSetField(TiffHndl, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
+ TIFFSetField(TiffHndl, TIFFTAG_XRESOLUTION, dpi);
+ TIFFSetField(TiffHndl, TIFFTAG_YRESOLUTION, dpi);
+ }
+
+ for (y = 0; y < img->i.height; y++) {
+ buf = img->data + 3*img->i.width*y;
+ TIFFWriteScanline(TiffHndl, buf, y, 0);
+ }
+ TIFFClose(TiffHndl);
+ return 0;
+}
+
+static struct ida_writer tiff_writer = {
+ label: "TIFF",
+ ext: { "tif", "tiff", NULL},
+ write: tiff_write,
+};
+
+static void __init init_wr(void)
+{
+ write_register(&tiff_writer);
+}
diff --git a/writers.c b/writers.c
new file mode 100644
index 0000000..18a1991
--- /dev/null
+++ b/writers.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "readers.h"
+#include "writers.h"
+
+/* ----------------------------------------------------------------------- */
+
+LIST_HEAD(writers);
+
+void write_register(struct ida_writer *writer)
+{
+ list_add_tail(&writer->list, &writers);
+}
diff --git a/writers.h b/writers.h
new file mode 100644
index 0000000..bd4a2b3
--- /dev/null
+++ b/writers.h
@@ -0,0 +1,14 @@
+#include "list.h"
+#include <X11/Intrinsic.h>
+
+/* save image files */
+struct ida_writer {
+ char *label;
+ char *ext[8];
+ int (*write)(FILE *fp, struct ida_image *img);
+ int (*conf)(Widget widget, struct ida_image *img);
+ struct list_head list;
+};
+
+extern struct list_head writers;
+void write_register(struct ida_writer *writer);
diff --git a/x11.c b/x11.c
new file mode 100644
index 0000000..5aaa1d0
--- /dev/null
+++ b/x11.c
@@ -0,0 +1,456 @@
+/*
+ * some X11 ximage / pixmaps rotines
+ *
+ * (c) 1996 Gerd Knorr <kraxel@goldbach.in-berlin.de>
+ *
+ * basic usage:
+ * 1) call x11_color_init()
+ * this does all the visual checking/colormap handling stuff and returns
+ * TRUECOLOR or PSEUDOCOLOR
+ * 2) create/load the image
+ * 3) call x11_create_pixmaps()
+ * For TRUECOLOR: It expects the data in one long (4 byte) per pixel.
+ * To create the long, run the rgb-values throuth the
+ * x11_lut_[red|green|blue] tables and or the results
+ * For PSEUDOCOLOR: The data is expected to be one byte per pixel,
+ * containing the results from dither_line (see dither.c)
+ * Not required to call init_dither, this is done by
+ * x11_color_init
+ * returns a pixmap.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+
+#include <X11/Xlib.h>
+#include <X11/Intrinsic.h>
+#include <X11/extensions/XShm.h>
+
+#include "x11.h"
+#include "dither.h"
+
+extern Display *dpy;
+
+#define PERROR(str) fprintf(stderr,"%s:%d: %s: %s\n",__FILE__,__LINE__,str,strerror(errno))
+
+/* ------------------------------------------------------------------------ */
+
+int display_type = 0;
+int display_depth = 0;
+XVisualInfo *info;
+
+/* PseudoColor: ditherresult => colormap-entry */
+int x11_colors;
+int x11_grays;
+unsigned long *x11_map;
+unsigned long x11_map_color[256];
+unsigned long x11_map_gray[64];
+
+unsigned long x11_red;
+unsigned long x11_green;
+unsigned long x11_blue;
+
+int have_shmem = 0;
+
+/*
+ * - xv uses 4:8:4 for truecolor images.
+ * - The GIMP 0.99.9 uses 6:6:4, but the 6 intervals for red+green are
+ * choosen somehow wired :-(
+ * - ImageMagick tries to optimize the palette for each image individual
+ */
+static int try_red[] = {4, 6, 6, 5, 4};
+static int try_green[] = {8, 6, 6, 5, 4};
+static int try_blue[] = {4, 6, 4, 5, 4};
+
+/* TrueColor: r,g,b => X11-color */
+unsigned long x11_lut_red[256];
+unsigned long x11_lut_green[256];
+unsigned long x11_lut_blue[256];
+unsigned long x11_lut_gray[256];
+
+static int
+x11_alloc_grays(Display * dpy, Colormap cmap, unsigned long *colors, int gray)
+{
+ XColor akt_color;
+ int i;
+
+ for (i = 0; i < gray; i++) {
+ akt_color.red = i * 65535 / (gray - 1);
+ akt_color.green = i * 65535 / (gray - 1);
+ akt_color.blue = i * 65535 / (gray - 1);
+
+ if (!XAllocColor(dpy, cmap, &akt_color)) {
+ /* failed, free them */
+ XFreeColors(dpy, cmap, colors, i, 0);
+ return 1;
+ }
+ colors[i] = akt_color.pixel;
+#if 0
+ fprintf(stderr, "%2lx: %04x %04x %04x\n",
+ akt_color.pixel,akt_color.red,akt_color.green,akt_color.red);
+#endif
+ }
+ return 0;
+}
+
+static int
+x11_alloc_colorcube(Display * dpy, Colormap cmap, unsigned long *colors,
+ int red, int green, int blue)
+{
+ XColor akt_color;
+ int i;
+
+ for (i = 0; i < red * green * blue; i++) {
+ akt_color.red = ((i / (green * blue)) % red) * 65535 / (red - 1);
+ akt_color.green = ((i / blue) % green) * 65535 / (green - 1);
+ akt_color.blue = (i % blue) * 65535 / (blue - 1);
+#if 0
+ fprintf(stderr, "%04x %04x %04x\n",
+ akt_color.red, akt_color.green, akt_color.red);
+#endif
+
+ if (!XAllocColor(dpy, cmap, &akt_color)) {
+ /* failed, free them */
+ XFreeColors(dpy, cmap, colors, i, 0);
+ return 1;
+ }
+ colors[i] = akt_color.pixel;
+ }
+ return 0;
+}
+
+static unsigned long
+x11_alloc_color(Display * dpy, Colormap cmap, int red, int green, int blue)
+{
+ XColor akt_color;
+
+ akt_color.red = red;
+ akt_color.green = green;
+ akt_color.blue = blue;
+
+ XAllocColor(dpy, cmap, &akt_color);
+ return akt_color.pixel;
+}
+
+static void
+x11_create_lut(unsigned long red_mask,
+ unsigned long green_mask,
+ unsigned long blue_mask)
+{
+ int rgb_red_bits = 0;
+ int rgb_red_shift = 0;
+ int rgb_green_bits = 0;
+ int rgb_green_shift = 0;
+ int rgb_blue_bits = 0;
+ int rgb_blue_shift = 0;
+ int i;
+ unsigned long mask;
+
+ for (i = 0; i < 24; i++) {
+ mask = (1 << i);
+ if (red_mask & mask)
+ rgb_red_bits++;
+ else if (!rgb_red_bits)
+ rgb_red_shift++;
+ if (green_mask & mask)
+ rgb_green_bits++;
+ else if (!rgb_green_bits)
+ rgb_green_shift++;
+ if (blue_mask & mask)
+ rgb_blue_bits++;
+ else if (!rgb_blue_bits)
+ rgb_blue_shift++;
+ }
+#if 0
+ printf("color: bits shift\n");
+ printf("red : %04i %05i\n", rgb_red_bits, rgb_red_shift);
+ printf("green: %04i %05i\n", rgb_green_bits, rgb_green_shift);
+ printf("blue : %04i %05i\n", rgb_blue_bits, rgb_blue_shift);
+#endif
+
+ for (i = 0; i < 256; i++) {
+ x11_lut_red[i] = (i >> (8 - rgb_red_bits)) << rgb_red_shift;
+ x11_lut_green[i] = (i >> (8 - rgb_green_bits)) << rgb_green_shift;
+ x11_lut_blue[i] = (i >> (8 - rgb_blue_bits)) << rgb_blue_shift;
+ x11_lut_gray[i] =
+ x11_lut_red[i] | x11_lut_green[i] | x11_lut_blue[i];
+ }
+}
+
+int
+x11_color_init(Widget shell, int *gray)
+{
+ Screen *scr;
+ Colormap cmap;
+ XVisualInfo template;
+ unsigned int found, i;
+
+ scr = XtScreen(shell);
+ cmap = DefaultColormapOfScreen(scr);
+ if (0 == x11_grays)
+ x11_grays = 8;
+
+ /* Ask for visual type */
+ template.screen = XDefaultScreen(dpy);
+ template.visualid =
+ XVisualIDFromVisual(DefaultVisualOfScreen(scr));
+ info = XGetVisualInfo(dpy, VisualIDMask | VisualScreenMask, &template,
+ &found);
+ if (XShmQueryExtension(dpy)) {
+ have_shmem = 1;
+ }
+
+ /* display_depth = (info->depth+7)/8; */
+ if (info->class == TrueColor) {
+ /* TrueColor */
+ *gray = 0; /* XXX testing... */
+ display_depth = 4;
+ display_type = TRUECOLOR;
+ x11_create_lut(info->red_mask, info->green_mask, info->blue_mask);
+ x11_black = x11_alloc_color(dpy, cmap, 0, 0, 0);
+ x11_gray = x11_alloc_color(dpy, cmap, 0xc400, 0xc400, 0xc400);
+ x11_lightgray = x11_alloc_color(dpy, cmap, 0xe000, 0xe000, 0xe000);
+ x11_white = x11_alloc_color(dpy, cmap, 0xffff, 0xffff, 0xffff);
+ } else if (info->class == PseudoColor && info->depth == 8) {
+ /* 8bit PseudoColor */
+ display_depth = 1;
+ display_type = PSEUDOCOLOR;
+ if (0 != x11_alloc_grays(dpy, cmap, x11_map_gray, x11_grays)) {
+ fprintf(stderr, "sorry, can't allocate %d grays\n", x11_grays);
+ exit(1);
+ }
+ if (!*gray) {
+ for (i = 0; i < sizeof(try_red) / sizeof(int); i++) {
+ if (0 == x11_alloc_colorcube
+ (dpy, cmap, x11_map_color,
+ try_red[i], try_green[i], try_blue[i])) {
+ x11_colors = try_red[i] * try_green[i] * try_blue[i];
+ init_dither(try_red[i], try_green[i], try_blue[i], x11_grays);
+ break;
+ }
+ }
+ if (i == sizeof(try_red) / sizeof(int)) {
+ *gray = 1;
+ fprintf(stderr, "failed to allocate enouth colors, "
+ "using grayscaled\n");
+ }
+ }
+ if (*gray)
+ init_dither(2, 2, 2, x11_grays);
+ } else if (info->class == StaticGray || info->class == GrayScale) {
+ /* Grayscale */
+ display_depth = 1;
+ display_type = PSEUDOCOLOR;
+ x11_grays = 64;
+ *gray = 1;
+ init_dither(2, 2, 2, x11_grays);
+ if (0 != x11_alloc_grays(dpy, cmap, x11_map_gray, x11_grays)) {
+ fprintf(stderr, "sorry, can't allocate %d grays\n", x11_grays);
+ exit(1);
+ }
+ } else {
+ fprintf(stderr, "sorry, can't handle visual\n");
+ exit(1);
+ }
+
+ /* some common colors */
+ x11_red = x11_alloc_color(dpy, cmap, 65535, 0, 0);
+ x11_green = x11_alloc_color(dpy, cmap, 0, 65535, 0);
+ x11_blue = x11_alloc_color(dpy, cmap, 0, 0, 65535);
+
+ if (*gray) {
+ x11_map = x11_map_gray;
+ dither_line = dither_line_gray;
+ } else {
+ x11_map = x11_map_color;
+ dither_line = dither_line_color;
+ }
+
+ return display_type;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int mitshm_bang = 0;
+
+static int
+x11_error_dev_null(Display * dpy, XErrorEvent * event)
+{
+ mitshm_bang = 1;
+ return 0;
+}
+
+XImage*
+x11_create_ximage(Widget shell, int width, int height, void **shm)
+{
+ XImage *ximage = NULL;
+ unsigned char *ximage_data;
+ XShmSegmentInfo *shminfo = NULL;
+ XtPointer old_handler;
+ Screen *scr = XtScreen(shell);
+
+ if (have_shmem) {
+ old_handler = XSetErrorHandler(x11_error_dev_null);
+ (*shm) = shminfo = malloc(sizeof(XShmSegmentInfo));
+ memset(shminfo, 0, sizeof(XShmSegmentInfo));
+ ximage = XShmCreateImage(dpy,
+ DefaultVisualOfScreen(scr),
+ DefaultDepthOfScreen(scr),
+ ZPixmap, NULL,
+ shminfo, width, height);
+ if (ximage) {
+ shminfo->shmid = shmget(IPC_PRIVATE,
+ ximage->bytes_per_line * ximage->height,
+ IPC_CREAT | 0777);
+ if (-1 == shminfo->shmid) {
+ fprintf(stderr,"shmget(%dMB): %s\n",
+ ximage->bytes_per_line * ximage->height / 1024 / 1024,
+ strerror(errno));
+ goto oom;
+ }
+ shminfo->shmaddr = (char *) shmat(shminfo->shmid, 0, 0);
+ if ((void *) -1 == shminfo->shmaddr) {
+ perror("shmat");
+ goto oom;
+ }
+ ximage->data = shminfo->shmaddr;
+ shminfo->readOnly = False;
+
+ XShmAttach(dpy, shminfo);
+ XSync(dpy, False);
+ shmctl(shminfo->shmid, IPC_RMID, 0);
+ if (mitshm_bang) {
+ have_shmem = 0;
+ shmdt(shminfo->shmaddr);
+ free(shminfo);
+ shminfo = *shm = NULL;
+ XDestroyImage(ximage);
+ ximage = NULL;
+ }
+ } else {
+ have_shmem = 0;
+ free(shminfo);
+ shminfo = *shm = NULL;
+ }
+ XSetErrorHandler(old_handler);
+ }
+
+ if (ximage == NULL) {
+ (*shm) = NULL;
+ if (NULL == (ximage_data = malloc(width * height * display_depth))) {
+ fprintf(stderr,"Oops: out of memory\n");
+ goto oom;
+ }
+ ximage = XCreateImage(dpy,
+ DefaultVisualOfScreen(scr),
+ DefaultDepthOfScreen(scr),
+ ZPixmap, 0, ximage_data,
+ width, height,
+ 8, 0);
+ }
+ memset(ximage->data, 0, ximage->bytes_per_line * ximage->height);
+
+ return ximage;
+
+ oom:
+ if (shminfo) {
+ if (shminfo->shmid && shminfo->shmid != -1)
+ shmctl(shminfo->shmid, IPC_RMID, 0);
+ free(shminfo);
+ }
+ if (ximage)
+ XDestroyImage(ximage);
+ return NULL;
+}
+
+void
+x11_destroy_ximage(Widget shell, XImage * ximage, void *shm)
+{
+ XShmSegmentInfo *shminfo = shm;
+
+ if (shminfo) {
+ XShmDetach(dpy, shminfo);
+ XDestroyImage(ximage);
+ shmdt(shminfo->shmaddr);
+ free(shminfo);
+ } else
+ XDestroyImage(ximage);
+}
+
+Pixmap
+x11_create_pixmap(Widget shell, unsigned char *byte_data,
+ int width, int height, int gray)
+{
+ Pixmap pixmap;
+ XImage *ximage;
+ XGCValues values;
+ GC gc;
+ unsigned long *long_data = (unsigned long *) byte_data;
+ int x, y;
+ void *shm;
+ unsigned long *map = gray ? x11_map_gray : x11_map;
+
+ Screen *scr = XtScreen(shell);
+
+ pixmap = XCreatePixmap(dpy,
+ RootWindowOfScreen(scr),
+ width, height,
+ DefaultDepthOfScreen(scr));
+ gc = XCreateGC(dpy, pixmap, 0, &values);
+
+ if (NULL == (ximage = x11_create_ximage(shell, width, height, &shm))) {
+ XFreePixmap(dpy, pixmap);
+ XFreeGC(dpy, gc);
+ return 0;
+ }
+ for (y = 0; y < height; y++)
+ if (display_type == TRUECOLOR)
+ for (x = 0; x < width; x++)
+ XPutPixel(ximage, x, y, *(long_data++));
+ else
+ for (x = 0; x < width; x++)
+ XPutPixel(ximage, x, y, map[(int) (*(byte_data++))]);
+
+ XPUTIMAGE(dpy, pixmap, gc, ximage, 0, 0, 0, 0, width, height);
+
+ x11_destroy_ximage(shell, ximage, shm);
+ XFreeGC(dpy, gc);
+ return pixmap;
+}
+
+void
+x11_data_to_ximage(unsigned char *data, unsigned char *ximage,
+ int x, int y, int sy, int gray)
+{
+ unsigned long *d;
+ int i, n;
+
+ if (display_type == PSEUDOCOLOR) {
+ if (gray) {
+ for (i = 0; i < y; i++)
+ dither_line_gray(data + x * i, ximage + x * i, i + sy, x);
+ } else {
+ for (i = 0; i < y; i++)
+ dither_line(data + 3 * x * i, ximage + x * i, i + sy, x);
+ }
+ } else {
+ d = (unsigned long *) ximage;
+ if (gray) {
+ n = x * y;
+ for (i = 0; i < n; i++)
+ *(d++) = x11_lut_gray[data[i]];
+ } else {
+ n = 3 * x * y;
+ for (i = 0; i < n; i += 3)
+ *(d++) = x11_lut_red[data[i]] |
+ x11_lut_green[data[i + 1]] |
+ x11_lut_blue[data[i + 2]];
+ }
+ }
+}
diff --git a/x11.h b/x11.h
new file mode 100644
index 0000000..bbc28ea
--- /dev/null
+++ b/x11.h
@@ -0,0 +1,42 @@
+#define PSEUDOCOLOR 1
+#define TRUECOLOR 2
+
+extern int display_type;
+extern int display_depth;
+extern XVisualInfo *info;
+
+extern int x11_grays;
+extern unsigned long *x11_map;
+
+extern unsigned long x11_lut_red[256];
+extern unsigned long x11_lut_green[256];
+extern unsigned long x11_lut_blue[256];
+extern unsigned long x11_lut_gray[256];
+extern unsigned long x11_map_color[256];
+extern unsigned long x11_map_gray[64];
+
+#define x11_black x11_map_gray[0]
+#define x11_gray x11_map_gray[47*x11_grays/64]
+#define x11_lightgray x11_map_gray[55*x11_grays/64]
+#define x11_white x11_map_gray[63*x11_grays/64]
+
+extern unsigned long x11_red;
+extern unsigned long x11_green;
+extern unsigned long x11_blue;
+
+extern int have_shmem;
+
+int x11_color_init(Widget shell, int *gray);
+
+void x11_data_to_ximage(unsigned char *rgb, unsigned char *ximage,
+ int x, int y, int sy, int gray);
+XImage *x11_create_ximage(Widget shell, int width, int height, void **shm);
+void x11_destroy_ximage(Widget shell, XImage * ximage, void *shm);
+Pixmap x11_create_pixmap(Widget shell, unsigned char *data,
+ int width, int height, int gray);
+
+#define XPUTIMAGE(dpy,dr,gc,xi,a,b,c,d,w,h) \
+ if (have_shmem) \
+ XShmPutImage(dpy,dr,gc,xi,a,b,c,d,w,h,True); \
+ else \
+ XPutImage(dpy,dr,gc,xi,a,b,c,d,w,h)
diff --git a/xdnd.c b/xdnd.c
new file mode 100644
index 0000000..978d7ae
--- /dev/null
+++ b/xdnd.c
@@ -0,0 +1,321 @@
+/*
+ * basic Xdnd support for Motif 2.x
+ *
+ * Receive drops only. Works fine in parallel with Motif DnD.
+ *
+ * Initiate drags is probably hard do do without breaking Motif DnD or
+ * heavily mucking with the Motif internals. Highlighting seems to be
+ * non-trivial too.
+ *
+ * Usage:
+ * (1) register XdndAction as "Xdnd"
+ * (2) register Widgets using XdndDropSink (acts like XmeDropSink
+ * for Motif DnD)
+ * (3) the transfer callback functions have to call
+ * XdndDropFinished() when they are done (i.e. after calling
+ * XmTransferDone)
+ *
+ * Data transfer is done using the usual Motif 2.x way, using UTM.
+ * Read: XmNdestinationCallback will be called, with
+ * XmDestinationCallbackStruct->selection set to XdndSelection.
+ *
+ * It is up to the application to handle the Xdnd MIME targets
+ * correctly, i.e. accept both "TEXT" and "text/plain" targets for
+ * example. Otherwise Xdnd support shouldn't need much work if Motif
+ * DnD support is present already.
+ *
+ * Known problems:
+ * - Not working with KDE 2.x as they don't provide a TARGETS
+ * target (which IMO is illegal, see ICCCM specs).
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <X11/Intrinsic.h>
+#include <X11/StringDefs.h>
+#include <Xm/Xm.h>
+#include <Xm/Transfer.h>
+#include <Xm/TransferP.h>
+
+#include "xdnd.h"
+
+/* ---------------------------------------------------------------------- */
+
+int xdnd_debug = 0;
+
+static Atom XdndAware;
+static Atom XdndTypeList;
+static Atom XdndSelection;
+
+static Atom XdndEnter;
+static Atom XdndPosition;
+static Atom XdndStatus;
+static Atom XdndLeave;
+static Atom XdndDrop;
+static Atom XdndFinished;
+
+static Atom XdndActionCopy;
+static Atom XdndActionMove;
+static Atom XdndActionLink;
+static Atom XdndActionAsk;
+static Atom XdndActionPrivate;
+
+/* ---------------------------------------------------------------------- */
+
+static void XdndInit(Display *dpy)
+{
+ if (XdndAware)
+ return;
+
+ XdndAware = XInternAtom(dpy, "XdndAware", False);
+ XdndTypeList = XInternAtom(dpy, "XdndTypeList", False);
+ XdndSelection = XInternAtom(dpy, "XdndSelection", False);
+
+ /* client messages */
+ XdndEnter = XInternAtom(dpy, "XdndEnter", False);
+ XdndPosition = XInternAtom(dpy, "XdndPosition", False);
+ XdndStatus = XInternAtom(dpy, "XdndStatus", False);
+ XdndLeave = XInternAtom(dpy, "XdndLeave", False);
+ XdndDrop = XInternAtom(dpy, "XdndDrop", False);
+ XdndFinished = XInternAtom(dpy, "XdndFinished", False);
+
+ /* actions */
+ XdndActionCopy = XInternAtom(dpy, "XdndActionCopy", False);
+ XdndActionMove = XInternAtom(dpy, "XdndActionMove", False);
+ XdndActionLink = XInternAtom(dpy, "XdndActionLink", False);
+ XdndActionAsk = XInternAtom(dpy, "XdndActionAsk", False);
+ XdndActionPrivate = XInternAtom(dpy, "XdndActionPrivate", False);
+}
+
+static void
+init_window(Widget widget)
+{
+ int version = 4;
+
+ /* window */
+ XChangeProperty(XtDisplay(widget),XtWindow(widget),
+ XdndAware, XA_ATOM, 32, PropModeReplace,
+ (XtPointer)&version, 1);
+
+ /* shell */
+ while (!XtIsShell(widget))
+ widget = XtParent(widget);
+
+ XChangeProperty(XtDisplay(widget),XtWindow(widget),
+ XdndAware, XA_ATOM, 32, PropModeReplace,
+ (XtPointer)&version, 1);
+ XtOverrideTranslations(widget, XtParseTranslationTable
+ ("<Message>XdndEnter: Xdnd()\n"
+ "<Message>XdndPosition: Xdnd()\n"
+ "<Message>XdndLeave: Xdnd()\n"
+ "<Message>XdndDrop: Xdnd()"));
+}
+
+static Widget
+find_window(Widget widget, int wx, int wy, int rx, int ry)
+{
+ WidgetList children;
+ Cardinal nchildren;
+ Dimension x,y,w,h;
+ Widget found = NULL;
+ int i;
+
+ nchildren = 0;
+ XtVaGetValues(widget,XtNchildren,&children,
+ XtNnumChildren,&nchildren,NULL);
+ fprintf(stderr,"findwindow %s\n",XtName(widget));
+ for (i = nchildren-1; i >= 0; i--) {
+ XtVaGetValues(children[i],XtNx,&x,XtNy,&y,
+ XtNwidth,&w,XtNheight,&h,NULL);
+ if (!XtIsManaged(children[i]))
+ continue;
+ if (XtIsSubclass(children[i],xmGadgetClass))
+ continue;
+ if (rx < wx+x || rx > wx+x+w)
+ continue;
+ if (ry < wy+y || ry > wy+y+h)
+ continue;
+ found = children[i];
+ break;
+ }
+ if (found) {
+ fprintf(stderr," more: %s\n",XtName(found));
+ return find_window(found,wx+x,wy+y,rx,ry);
+ }
+ fprintf(stderr," done: %s\n",XtName(widget));
+ return widget;
+}
+
+static int
+check_window(Widget widget)
+{
+ Atom type;
+ int format,rc;
+ unsigned long nitems,rest;
+ unsigned long *ldata;
+
+ rc = XGetWindowProperty(XtDisplay(widget),XtWindow(widget),
+ XdndAware,0,64,False,AnyPropertyType,
+ &type,&format,&nitems,&rest,
+ (XtPointer)&ldata);
+ XFree(ldata);
+ return rc == Success && nitems > 0;
+}
+
+static XtEnum get_operation(Atom action)
+{
+ if (XdndActionCopy == action)
+ return XmCOPY;
+ if (XdndActionLink == action)
+ return XmLINK;
+ if (XdndActionMove == action)
+ return XmMOVE;
+ return 0;
+}
+
+static Atom get_action(XtEnum operation)
+{
+ if (XmCOPY == operation)
+ return XdndActionCopy;
+ if (XmLINK == operation)
+ return XdndActionLink;
+ if (XmMOVE == operation)
+ return XdndActionMove;
+ return None;
+}
+
+static void
+XdndEvent(Widget widget, XtPointer clientdata, XEvent *event, Boolean *cont)
+{
+ switch(event->type) {
+ case MapNotify:
+ init_window(widget);
+ break;
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * not very nice this way, but as you can hardly have two drags at the
+ * same time with one pointer only it should be fine ...
+ */
+static Widget target;
+static int target_ok,drop_ok;
+static Window source;
+static XtEnum operation;
+
+void
+XdndAction(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params)
+{
+ char *name;
+ XEvent reply;
+
+ if (NULL == event)
+ return;
+ if (ClientMessage != event->type)
+ return;
+
+ if (XdndEnter == event->xclient.message_type) {
+ if (xdnd_debug)
+ fprintf(stderr,"Xdnd: Enter: win=0x%lx ver=%ld more=%s\n",
+ event->xclient.data.l[0],
+ event->xclient.data.l[1] >> 24,
+ (event->xclient.data.l[1] & 1) ? "yes" : "no");
+ }
+
+ if (XdndPosition == event->xclient.message_type) {
+ source = event->xclient.data.l[0];
+ target = find_window(widget,0,0,
+ event->xclient.data.l[2] >> 16,
+ event->xclient.data.l[2] & 0xffff);
+ target_ok = check_window(target);
+ if (target_ok) {
+ operation = get_operation(event->xclient.data.l[4]);
+ operation = XmCOPY; /* FIXME */
+ drop_ok = 1;
+ } else {
+ operation = 0;
+ drop_ok = 0;
+ }
+ if (xdnd_debug) {
+ name = NULL;
+ if (event->xclient.data.l[4])
+ name=XGetAtomName(XtDisplay(widget),event->xclient.data.l[4]);
+ fprintf(stderr,"Xdnd: Position: win=0x%lx pos=+%ld+%ld ts=%ld "
+ "ac=%s op=%d widget=%s drop=%s\n",
+ event->xclient.data.l[0],
+ event->xclient.data.l[2] >> 16,
+ event->xclient.data.l[2] & 0xffff,
+ event->xclient.data.l[3],
+ name,operation,
+ XtName(target),target_ok ? "yes" : "no");
+ if (name)
+ XFree(name);
+ }
+ memset(&reply,0,sizeof(reply));
+ reply.xany.type = ClientMessage;
+ reply.xany.display = XtDisplay(widget);
+ reply.xclient.window = event->xclient.data.l[0];
+ reply.xclient.message_type = XdndStatus;
+ reply.xclient.format = 32;
+ reply.xclient.data.l[0] = XtWindow(widget);
+ reply.xclient.data.l[1] = drop_ok ? 1 : 0;
+ reply.xclient.data.l[4] = get_action(operation);
+ XSendEvent(XtDisplay(widget),reply.xclient.window,0,0,&reply);
+ }
+
+ if (XdndDrop == event->xclient.message_type) {
+ source = event->xclient.data.l[0];
+ if (xdnd_debug)
+ fprintf(stderr,"Xdnd: Drop: win=0x%lx ts=%ld\n",
+ event->xclient.data.l[0],
+ event->xclient.data.l[2]);
+ XmeNamedSink(target,XdndSelection,XmCOPY,NULL,
+ XtLastTimestampProcessed(XtDisplay(widget)));
+ }
+
+ if (XdndLeave == event->xclient.message_type) {
+ source = 0;
+ if (xdnd_debug)
+ fprintf(stderr,"Xdnd: Leave: win=0x%lx\n",
+ event->xclient.data.l[0]);
+ }
+}
+
+void XdndDropFinished(Widget widget, XmSelectionCallbackStruct *scs)
+{
+ XEvent reply;
+
+ if (XdndSelection != scs->selection)
+ return;
+ if (0 == source)
+ return;
+
+ if (xdnd_debug)
+ fprintf(stderr,"Xdnd: sending Finished (0x%lx)\n",source);
+ memset(&reply,0,sizeof(reply));
+ reply.xany.type = ClientMessage;
+ reply.xany.display = XtDisplay(widget);
+ reply.xclient.window = source;
+ reply.xclient.message_type = XdndFinished;
+ reply.xclient.format = 32;
+ while (!XtIsShell(widget))
+ widget = XtParent(widget);
+ reply.xclient.data.l[0] = XtWindow(widget);
+ XSendEvent(XtDisplay(widget),reply.xclient.window,0,0,&reply);
+ source = 0;
+}
+
+void XdndDropSink(Widget widget)
+{
+ XdndInit(XtDisplay(widget));
+
+ if (XtWindow(widget))
+ init_window(widget);
+ XtAddEventHandler(widget,StructureNotifyMask,True,XdndEvent,NULL);
+}
diff --git a/xdnd.h b/xdnd.h
new file mode 100644
index 0000000..1e656a5
--- /dev/null
+++ b/xdnd.h
@@ -0,0 +1,5 @@
+extern int xdnd_debug;
+void XdndDropSink(Widget widget);
+void XdndAction(Widget widget, XEvent *event,
+ String *params, Cardinal *num_params);
+void XdndDropFinished(Widget widget, XmSelectionCallbackStruct *scs);
diff --git a/xpm/ccw.xpm b/xpm/ccw.xpm
new file mode 100644
index 0000000..c10a68f
--- /dev/null
+++ b/xpm/ccw.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * ccw_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #868682828686",
+" ",
+" ",
+" . ...... ",
+" .......... ",
+" ...XXXXXX.. ",
+" .... .. ",
+" ..... ..X ",
+" XXXXX ..X ",
+" ..X ",
+" ..X ",
+" . ...X ",
+" .. ...XX ",
+" ........XX ",
+" ......XX ",
+" XXXXXX ",
+" "};
diff --git a/xpm/cw.xpm b/xpm/cw.xpm
new file mode 100644
index 0000000..3942890
--- /dev/null
+++ b/xpm/cw.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * cw_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #868682828686",
+" ",
+" ",
+" ...... . ",
+" ..........X ",
+" ..XXXXXX...X ",
+" ..XX ....X ",
+" ..X .....X ",
+" ..X XXXXX ",
+" ..X ",
+" ..X ",
+" ... . ",
+" ... ..X ",
+" ........XX ",
+" ......XX ",
+" XXXXXX ",
+" "};
diff --git a/xpm/dir.xpm b/xpm/dir.xpm
new file mode 100644
index 0000000..9295ca3
--- /dev/null
+++ b/xpm/dir.xpm
@@ -0,0 +1,41 @@
+/* XPM */
+static char * dir_xpm[] = {
+"32 32 6 1",
+" c none",
+". c #000000000000",
+"X c #FFFFFFFFC71B",
+"o c #FFFFFFFFFFFF",
+"O c #FFFFFFFF0000",
+"+ c #861782078617",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ...... ",
+" .XXXXXX. ",
+" .XXXXXXXX. ",
+" ................... ",
+" .ooooooooooooooooooo. ",
+" .oXXXXXXXXXXXXXXXXXXXO. ",
+" .oXXXXXXXXXXXXXXXXXXXO.+ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .oXXXXXXXXXXXXXXXXXXXO.++ ",
+" .OOOOOOOOOOOOOOOOOOO..++ ",
+" ....................+++ ",
+" +++++++++++++++++++++ ",
+" +++++++++++++++++++ ",
+" ",
+" ",
+" "};
diff --git a/xpm/exit.xpm b/xpm/exit.xpm
new file mode 100644
index 0000000..53eee1a
--- /dev/null
+++ b/xpm/exit.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * exit_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #861782078617",
+" ",
+" ",
+" .. ",
+" ..X ",
+" . ..X . ",
+" ..X ..X .. ",
+" ..X ..X .. ",
+" ..X ..X ..X ",
+" ..X ..X ..X ",
+" ..X XX ..X ",
+" ..X ..X ",
+" .. ..XX ",
+" ........XX ",
+" ......XX ",
+" XXXXXX ",
+" "};
diff --git a/xpm/file.xpm b/xpm/file.xpm
new file mode 100644
index 0000000..deb89ef
--- /dev/null
+++ b/xpm/file.xpm
@@ -0,0 +1,39 @@
+/* XPM */
+static char * file_xpm[] = {
+"32 32 4 1",
+" c none",
+". c #000000000000",
+"X c #861782078617",
+"o c #FFFFFFFFFFFF",
+" ",
+" ",
+" ",
+" ......... ",
+" ..........X ",
+" ...Xooooo..XX ",
+" ..o.Xooooo..XX ",
+" ..oo.Xooooo..XX ",
+" ..ooo.Xooooo..XX ",
+" ..oooo.Xooooo..XX ",
+" ..ooooo.Xooooo..XX ",
+" .........Xooooo..XX ",
+" ..XXXXXXXXooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooooooooooo..XX ",
+" .................XX ",
+" ................XXX ",
+" XXXXXXXXXXXXXXXXXX ",
+" XXXXXXXXXXXXXXXXX ",
+" ",
+" ",
+" "};
diff --git a/xpm/fliph.xpm b/xpm/fliph.xpm
new file mode 100644
index 0000000..0ef2dc0
--- /dev/null
+++ b/xpm/fliph.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * fliph_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #868682828686",
+" . . ",
+" ..X .X ",
+" ...X .X ",
+"............. ",
+".............X ",
+" ...XXX.XXXXXX ",
+" ..X .X ",
+" .X .X . ",
+" X .X .. ",
+" .X ... ",
+" ............. ",
+" .............X",
+" .XXX...XX",
+" .X ..XX ",
+" .X .XX ",
+" X X "};
diff --git a/xpm/flipv.xpm b/xpm/flipv.xpm
new file mode 100644
index 0000000..e296b28
--- /dev/null
+++ b/xpm/flipv.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * flipv_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #861782078617",
+" .. ",
+" .... ",
+" ...... .. ",
+"........ ..X ",
+" XX..XXXX ..X ",
+" ..X ..X ",
+" ..X ..X ",
+"............... ",
+" XX..XXXXX..XXXX",
+" ..X ..X ",
+" ..X ..X ",
+" ..X ........ ",
+" ..X ......XX",
+" XX ....XX ",
+" ..XX ",
+" XX "};
diff --git a/xpm/next.xpm b/xpm/next.xpm
new file mode 100644
index 0000000..b5729e6
--- /dev/null
+++ b/xpm/next.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * next_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #868682828686",
+" ",
+" ",
+" . ",
+" .. ",
+" ... ",
+" .... ",
+" ........... ",
+" ............ ",
+" ...........XX ",
+" XXXXX....XX ",
+" ...XX ",
+" ..XX ",
+" .XX ",
+" X ",
+" ",
+" "};
diff --git a/xpm/prev.xpm b/xpm/prev.xpm
new file mode 100644
index 0000000..b4ae0af
--- /dev/null
+++ b/xpm/prev.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * prev_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #868682828686",
+" ",
+" ",
+" . ",
+" ..X ",
+" ...X ",
+" ....X ",
+" ........... ",
+" ............X ",
+" ...........X ",
+" ....XXXXXXX ",
+" ...X ",
+" ..X ",
+" .X ",
+" X ",
+" ",
+" "};
diff --git a/xpm/question.xpm b/xpm/question.xpm
new file mode 100644
index 0000000..662e74e
--- /dev/null
+++ b/xpm/question.xpm
@@ -0,0 +1,38 @@
+/* XPM */
+static char * question_xpm[] = {
+"32 32 3 1",
+" c none",
+". c #000000000000",
+"X c #861782078617",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" ...... ",
+" ......... ",
+" ....XX.... ",
+" ....XX X... ",
+" ...XX ...X ",
+" ...X ....X ",
+" XXX ...XX ",
+" ....X ",
+" ....XX ",
+" ...XX ",
+" ...XX ",
+" ...X ",
+" ...X ",
+" ...X ",
+" XXX ",
+" ",
+" ... ",
+" ...X ",
+" ...X ",
+" XXX ",
+" ",
+" ",
+" ",
+" ",
+" ",
+" "};
diff --git a/xpm/unknown.xpm b/xpm/unknown.xpm
new file mode 100644
index 0000000..89a6e4e
--- /dev/null
+++ b/xpm/unknown.xpm
@@ -0,0 +1,39 @@
+/* XPM */
+static char * unknown_xpm[] = {
+"32 32 4 1",
+" c none",
+". c #000000000000",
+"X c #861782078617",
+"o c #FFFFFFFFFFFF",
+" ",
+" ",
+" ",
+" ......... ",
+" ..........X ",
+" ...Xooooo..XX ",
+" ..o.Xooooo..XX ",
+" ..oo.Xooooo..XX ",
+" ..ooo.Xooooo..XX ",
+" ..oooo.Xooooo..XX ",
+" ..ooooo.Xooooo..XX ",
+" .........Xooooo..XX ",
+" ..XXXXXXXXooooo..XX ",
+" ..ooooooooooooo..XX ",
+" ..ooooo...ooooo..XX ",
+" ..oooo.XXX.oooo..XX ",
+" ..oooo.Xoo.Xooo..XX ",
+" ..oooooXoo.Xooo..XX ",
+" ..ooooooo.XXooo..XX ",
+" ..oooooo.XXoooo..XX ",
+" ..oooooo.Xooooo..XX ",
+" ..oooooooXooooo..XX ",
+" ..oooooo.oooooo..XX ",
+" ..oooooooXooooo..XX ",
+" ..ooooooooooooo..XX ",
+" .................XX ",
+" ................XXX ",
+" XXXXXXXXXXXXXXXXXX ",
+" XXXXXXXXXXXXXXXXX ",
+" ",
+" ",
+" "};
diff --git a/xpm/zoomin.xpm b/xpm/zoomin.xpm
new file mode 100644
index 0000000..99c82a0
--- /dev/null
+++ b/xpm/zoomin.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * zoomin_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #868682828686",
+" ",
+" ..... ",
+" .XXXXX. ",
+" .XX . ",
+" .XX . . ",
+" .X .X .X ",
+" .X ..... .X ",
+" .X X.XXX .X ",
+" .X .X .X ",
+" . X .XX ",
+" . ... ",
+" .....X... ",
+" XXXXX ... ",
+" ..X ",
+" XX ",
+" "};
diff --git a/xpm/zoomout.xpm b/xpm/zoomout.xpm
new file mode 100644
index 0000000..cce23e7
--- /dev/null
+++ b/xpm/zoomout.xpm
@@ -0,0 +1,22 @@
+/* XPM */
+static char * zoomout_xpm[] = {
+"16 16 3 1",
+" c none",
+". c #000000000000",
+"X c #868682828686",
+" ",
+" ..... ",
+" .XXXXX. ",
+" .XX . ",
+" .XX . ",
+" .X .X ",
+" .X ..... .X ",
+" .X XXXXX .X ",
+" .X .X ",
+" . .XX ",
+" . ... ",
+" .....X... ",
+" XXXXX ... ",
+" ..X ",
+" XX ",
+" "};
diff --git a/xwd.h b/xwd.h
new file mode 100644
index 0000000..9e7eddf
--- /dev/null
+++ b/xwd.h
@@ -0,0 +1 @@
+void parse_ximage(struct ida_image *dest, XImage *src);