From e9e9684117719204929821028ba9dbb7915ea119 Mon Sep 17 00:00:00 2001 From: kraxel Date: Sun, 28 Mar 2004 11:31:57 +0000 Subject: Initial revision --- COPYING | 339 +++++++++ Changes | 180 +++++ GNUmakefile | 215 ++++++ INSTALL | 59 ++ Ida.ad | 655 ++++++++++++++++++ README | 47 ++ RegEdit.c | 1795 ++++++++++++++++++++++++++++++++++++++++++++++++ RegEdit.h | 65 ++ RegEditI.h | 368 ++++++++++ TODO | 22 + VERSION | 1 + backup/Ida-de | 173 +++++ backup/Ida-default | 178 +++++ backup/Ida-fixed | 439 ++++++++++++ browser.c | 571 +++++++++++++++ browser.h | 3 + color.c | 513 ++++++++++++++ color.h | 1 + config.h | 0 curl.c | 348 ++++++++++ curl.h | 5 + desktop.c | 276 ++++++++ desktop.h | 7 + desktop/ida.desktop | 9 + dither.c | 193 ++++++ dither.h | 6 + exiftran.c | 262 +++++++ exiftran.man | 106 +++ fallback.pl | 26 + fb-gui.c | 189 +++++ fb-gui.h | 11 + fbgs | 69 ++ fbgs.man | 20 + fbi.c | 1552 +++++++++++++++++++++++++++++++++++++++++ fbi.man | 160 +++++ fbtools.c | 523 ++++++++++++++ fbtools.h | 23 + filebutton.c | 934 +++++++++++++++++++++++++ filebutton.h | 71 ++ filelist.c | 619 +++++++++++++++++ filelist.h | 3 + fileops.c | 100 +++ fileops.h | 1 + filter.c | 495 +++++++++++++ filter.h | 27 + fs.c | 502 ++++++++++++++ fs.h | 72 ++ genthumbnail.c | 219 ++++++ genthumbnail.h | 1 + hex.c | 141 ++++ hex.h | 1 + icons.c | 130 ++++ icons.h | 3 + ida.c | 1909 +++++++++++++++++++++++++++++++++++++++++++++++++++ ida.h | 26 + ida.man | 102 +++ idaconfig.c | 51 ++ idaconfig.h | 24 + jpeg/README | 2 + jpeg/jinclude.h | 91 +++ jpeg/jpegint.h | 392 +++++++++++ jpeg/jpeglib.h | 1096 +++++++++++++++++++++++++++++ jpeg/transupp.c | 928 +++++++++++++++++++++++++ jpeg/transupp.h | 135 ++++ jpegtools.c | 621 +++++++++++++++++ jpegtools.h | 28 + lirc.c | 60 ++ lirc.h | 2 + list.h | 168 +++++ logo.jpg | Bin 0 -> 16990 bytes lut.c | 97 +++ lut.h | 15 + man.c | 119 ++++ man.h | 4 + misc.h | 9 + mk/Autoconf.mk | 138 ++++ mk/Compile.mk | 88 +++ mk/Maintainer.mk | 12 + mk/Variables.mk | 46 ++ mk/utf8.tmp | 25 + op.c | 289 ++++++++ op.h | 7 + parseconfig.c | 857 +++++++++++++++++++++++ parseconfig.h | 68 ++ rd/Makefile | 2 + rd/magick.c | 85 +++ rd/read-bmp.c | 216 ++++++ rd/read-gif.c | 220 ++++++ rd/read-jpeg.c | 187 +++++ rd/read-pcd.c | 78 +++ rd/read-png.c | 164 +++++ rd/read-ppm.c | 110 +++ rd/read-tiff.c | 195 ++++++ rd/read-xpm.c | 287 ++++++++ rd/read-xwd.c | 357 ++++++++++ readers.c | 133 ++++ readers.h | 104 +++ sane.c | 324 +++++++++ sane.h | 1 + selections.c | 623 +++++++++++++++++ selections.h | 18 + thumbnail.cgi.c | 125 ++++ viewer.c | 962 ++++++++++++++++++++++++++ viewer.h | 96 +++ wr/Makefile | 2 + wr/write-jpeg.c | 103 +++ wr/write-png.c | 76 ++ wr/write-ppm.c | 34 + wr/write-ps.c | 475 +++++++++++++ wr/write-tiff.c | 60 ++ writers.c | 15 + writers.h | 14 + x11.c | 456 ++++++++++++ x11.h | 42 ++ xdnd.c | 321 +++++++++ xdnd.h | 5 + xpm/ccw.xpm | 22 + xpm/cw.xpm | 22 + xpm/dir.xpm | 41 ++ xpm/exit.xpm | 22 + xpm/file.xpm | 39 ++ xpm/fliph.xpm | 22 + xpm/flipv.xpm | 22 + xpm/next.xpm | 22 + xpm/prev.xpm | 22 + xpm/question.xpm | 38 + xpm/unknown.xpm | 39 ++ xpm/zoomin.xpm | 22 + xpm/zoomout.xpm | 22 + xwd.h | 1 + 130 files changed, 26383 insertions(+) create mode 100644 COPYING create mode 100644 Changes create mode 100644 GNUmakefile create mode 100644 INSTALL create mode 100644 Ida.ad create mode 100644 README create mode 100644 RegEdit.c create mode 100644 RegEdit.h create mode 100644 RegEditI.h create mode 100644 TODO create mode 100644 VERSION create mode 100644 backup/Ida-de create mode 100644 backup/Ida-default create mode 100644 backup/Ida-fixed create mode 100644 browser.c create mode 100644 browser.h create mode 100644 color.c create mode 100644 color.h create mode 100644 config.h create mode 100644 curl.c create mode 100644 curl.h create mode 100644 desktop.c create mode 100644 desktop.h create mode 100644 desktop/ida.desktop create mode 100644 dither.c create mode 100644 dither.h create mode 100644 exiftran.c create mode 100644 exiftran.man create mode 100755 fallback.pl create mode 100644 fb-gui.c create mode 100644 fb-gui.h create mode 100755 fbgs create mode 100644 fbgs.man create mode 100644 fbi.c create mode 100644 fbi.man create mode 100644 fbtools.c create mode 100644 fbtools.h create mode 100644 filebutton.c create mode 100644 filebutton.h create mode 100644 filelist.c create mode 100644 filelist.h create mode 100644 fileops.c create mode 100644 fileops.h create mode 100644 filter.c create mode 100644 filter.h create mode 100644 fs.c create mode 100644 fs.h create mode 100644 genthumbnail.c create mode 100644 genthumbnail.h create mode 100644 hex.c create mode 100644 hex.h create mode 100644 icons.c create mode 100644 icons.h create mode 100644 ida.c create mode 100644 ida.h create mode 100644 ida.man create mode 100644 idaconfig.c create mode 100644 idaconfig.h create mode 100644 jpeg/README create mode 100644 jpeg/jinclude.h create mode 100644 jpeg/jpegint.h create mode 100644 jpeg/jpeglib.h create mode 100644 jpeg/transupp.c create mode 100644 jpeg/transupp.h create mode 100644 jpegtools.c create mode 100644 jpegtools.h create mode 100644 lirc.c create mode 100644 lirc.h create mode 100644 list.h create mode 100644 logo.jpg create mode 100644 lut.c create mode 100644 lut.h create mode 100644 man.c create mode 100644 man.h create mode 100644 misc.h create mode 100644 mk/Autoconf.mk create mode 100644 mk/Compile.mk create mode 100644 mk/Maintainer.mk create mode 100644 mk/Variables.mk create mode 100644 mk/utf8.tmp create mode 100644 op.c create mode 100644 op.h create mode 100644 parseconfig.c create mode 100644 parseconfig.h create mode 100644 rd/Makefile create mode 100644 rd/magick.c create mode 100644 rd/read-bmp.c create mode 100644 rd/read-gif.c create mode 100644 rd/read-jpeg.c create mode 100644 rd/read-pcd.c create mode 100644 rd/read-png.c create mode 100644 rd/read-ppm.c create mode 100644 rd/read-tiff.c create mode 100644 rd/read-xpm.c create mode 100644 rd/read-xwd.c create mode 100644 readers.c create mode 100644 readers.h create mode 100644 sane.c create mode 100644 sane.h create mode 100644 selections.c create mode 100644 selections.h create mode 100644 thumbnail.cgi.c create mode 100644 viewer.c create mode 100644 viewer.h create mode 100644 wr/Makefile create mode 100644 wr/write-jpeg.c create mode 100644 wr/write-png.c create mode 100644 wr/write-ppm.c create mode 100644 wr/write-ps.c create mode 100644 wr/write-tiff.c create mode 100644 writers.c create mode 100644 writers.h create mode 100644 x11.c create mode 100644 x11.h create mode 100644 xdnd.c create mode 100644 xdnd.h create mode 100644 xpm/ccw.xpm create mode 100644 xpm/cw.xpm create mode 100644 xpm/dir.xpm create mode 100644 xpm/exit.xpm create mode 100644 xpm/file.xpm create mode 100644 xpm/fliph.xpm create mode 100644 xpm/flipv.xpm create mode 100644 xpm/next.xpm create mode 100644 xpm/prev.xpm create mode 100644 xpm/question.xpm create mode 100644 xpm/unknown.xpm create mode 100644 xpm/zoomin.xpm create mode 100644 xpm/zoomout.xpm create mode 100644 xwd.h 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. + + + Copyright (C) 19yy + + 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. + + , 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 ). + * 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 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 \ + space: Next() \n\ + osfDelete: Prev() \n\ + osfBackSpace: Prev() \n\ + Page_Down: NextPage() \n\ + Page_Up: PrevPage() \n\ + N: NextPage() \n\ + ~CtrlP: PrevPage() \n\ + KP_Add: Zoom(inc) \n\ + KP_Subtract: Zoom(dec) \n\ + : Ipc(drag) \n\ + : Popup(control) \n\ + \ + G: Gamma() \n\ + F: Browser() \n\ + L: Filelist() \n\ + CtrlV: Ipc(paste) \n\ + AltV: Ipc(paste) \n\ + CtrlC: Ipc(copy) \n\ + AltC: Ipc(copy) \n\ + \ + CtrlP: Print() \n\ + CtrlL: Load() \n\ + CtrlS: Save() \n\ + AltS: Sharpe() \n\ + ~Alt ~CtrlS: Resize() \n\ + plus: Zoom(inc) \n\ + minus: Zoom(dec) \n\ + U: Undo() \n\ + ~Alt ~CtrlC: Filter(crop) \n\ + ~Alt ~CtrlV: Filter(flip-vert) \n\ + H: Filter(flip-horz) \n\ + ~Alt ~ShiftT: Filter(rotate-cw) \n\ + ShiftT: Filter(rotate-ccw) \n\ + AltT: Rotate() \n\ + I: Filter(invert) \n\ + ~AltE: Color() \n\ + AltE: F3x3(-1,-1,-1,-1,8,-1,-1,-1,-1) \n\ + AltB: F3x3(1,1,1,1,1,1,1,1,1, 1,9) \n\ + AltM: F3x3(1,0,0,0,0,0,0,0,-1, 0,0,128) \n\ + osfHelp: Man(ida) \n\ + Q: Exit() + +Ida.view.VertScrollBar.accelerators: #override \ + : IncrementUpOrLeft(Up) \n\ + : IncrementDownOrRight(Down)\n\ + ~CtrlosfUp: IncrementUpOrLeft(Up) \n\ + ~CtrlosfDown: IncrementDownOrRight(Down)\n\ + CtrlosfUp: PageUpOrLeft(Up) \n\ + CtrlosfDown: PageDownOrRight(Down) +Ida.view.HorScrollBar.accelerators: #override \ + ~CtrlosfLeft: IncrementUpOrLeft(Left) \n\ + ~CtrlosfRight: IncrementDownOrRight(Right)\n\ + CtrlosfLeft: PageUpOrLeft(Left) \n\ + CtrlosfRight: 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 + +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 \ + space: Next() \n\ + osfDelete: Prev() \n\ + osfBackSpace: Prev() \n\ + KP_Add: Zoom(inc) \n\ + 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: CtrlL +ctrl*bar*save.labelString: Save image ... +ctrl*bar*save.mnemonic: S +ctrl*bar*save.acceleratorText: Ctrl+S +ctrl*bar*save.accelerator: CtrlS +ctrl*bar*browse.labelString: File browser ... +ctrl*bar*browse.acceleratorText: F +ctrl*bar*browse.accelerator: F +ctrl*bar*filelist.labelString: File list ... +ctrl*bar*filelist.acceleratorText: L +ctrl*bar*filelist.accelerator: 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: CtrlP +ctrl*bar*quit.labelString: Quit +ctrl*bar*quit.mnemonic: Q +ctrl*bar*quit.acceleratorText: Q +ctrl*bar*quit.accelerator: 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: U +ctrl*bar*copy.labelString: Copy +ctrl*bar*copy.acceleratorText: Ctrl+C +ctrl*bar*copy.accelerator: CtrlC +ctrl*bar*paste.labelString: Paste +ctrl*bar*paste.acceleratorText: Ctrl+V +ctrl*bar*paste.accelerator: CtrlV +ctrl*bar*flipv.labelString: Flip vertical +ctrl*bar*flipv.mnemonic: v +ctrl*bar*flipv.acceleratorText: V +ctrl*bar*flipv.accelerator: V +ctrl*bar*fliph.labelString: Flip horizontal +ctrl*bar*fliph.mnemonic: h +ctrl*bar*fliph.acceleratorText: H +ctrl*bar*fliph.accelerator: H +ctrl*bar*rotcw.labelString: Turn clockwise +ctrl*bar*rotcw.mnemonic: T +ctrl*bar*rotcw.acceleratorText: T +ctrl*bar*rotcw.accelerator: ~Meta ~ShiftT +ctrl*bar*rotccw.labelString: Turn counter clockwise +ctrl*bar*rotccw.acceleratorText: Shift+T +ctrl*bar*rotccw.accelerator: ShiftT +ctrl*bar*invert.labelString: Invert +ctrl*bar*invert.mnemonic: I +ctrl*bar*invert.acceleratorText: I +ctrl*bar*invert.accelerator: I +ctrl*bar*crop.labelString: Crop +ctrl*bar*crop.mnemonic: C +ctrl*bar*crop.acceleratorText: C +ctrl*bar*crop.accelerator: 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: ~CtrlS +ctrl*bar*rotany.labelString: Rotate ... +ctrl*bar*rotany.acceleratorText: Alt+T +ctrl*bar*rotany.accelerator: AltT + +! 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: 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: ~AltE +ctrl*bar*gray.labelString: Grayscale +ctrl*bar*blur.labelString: Blur +ctrl*bar*blur.acceleratorText: Alt+B +ctrl*bar*blur.accelerator: AltB +ctrl*bar*sharpe.labelString: Sharpe ... +ctrl*bar*sharpe.acceleratorText: Alt+S +ctrl*bar*sharpe.accelerator: AltS +ctrl*bar*edge.labelString: Edge detect +ctrl*bar*edge.acceleratorText: Alt+E +ctrl*bar*edge.accelerator: AltE +ctrl*bar*emboss.labelString: Emboss +ctrl*bar*emboss.acceleratorText: Alt+M +ctrl*bar*emboss.accelerator: Altm + +! 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: Backspace +ctrl*bar*next.labelString: Next file +ctrl*bar*next.acceleratorText: space +!ctrl*bar*next.accelerator: space +ctrl*bar*prevpage.labelString: Previous page +ctrl*bar*prevpage.acceleratorText: PageUp +!ctrl*bar*prevpage.accelerator: osfPageUp +ctrl*bar*nextpage.labelString: Next page +ctrl*bar*nextpage.acceleratorText: PageDown +!ctrl*bar*nextpage.accelerator: osfPageDown +ctrl*bar*zoomin.labelString: Zoom in +ctrl*bar*zoomin.acceleratorText: plus +ctrl*bar*zoomin.accelerator: plus +ctrl*bar*zoomout.labelString: Zoom out +ctrl*bar*zoomout.acceleratorText: minus +ctrl*bar*zoomout.accelerator: 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: 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: Q + +! edit menu +*cbar.edit.labelString: Edit +*cbar.edit.mnemonic: E +*cbar*copy.labelString: Copy +*cbar*copy.acceleratorText: Ctrl+C +*cbar*copy.accelerator: CtrlC +*cbar*paste.labelString: Paste +*cbar*paste.acceleratorText: Ctrl+V +*cbar*paste.accelerator: CtrlV +*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: 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: 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 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 +#include +#include +#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 + +/* 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 + 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 +#include + +#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 + +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 + +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 \ + space: Next() \n\ + osfDelete: Prev() \n\ + osfBackSpace: Prev() \n\ + Page_Down: NextPage() \n\ + Page_Up: PrevPage() \n\ + KP_Add: Zoom(inc) \n\ + KP_Subtract: Zoom(dec) \n\ + : Ipc(drag) \n\ + : Popup(control) \n\ + \ + G: Gamma() \n\ + F: Browser() \n\ + L: Filelist() \n\ + CtrlV: Ipc(paste) \n\ + AltV: Ipc(paste) \n\ + CtrlC: Ipc(copy) \n\ + AltC: Ipc(copy) \n\ + \ + CtrlP: Print() \n\ + CtrlL: Load() \n\ + CtrlS: Save() \n\ + AltS: Sharpe() \n\ + ~Alt ~CtrlS: Resize() \n\ + plus: Zoom(inc) \n\ + minus: Zoom(dec) \n\ + U: Undo() \n\ + ~Alt ~CtrlC: Filter(crop) \n\ + ~Alt ~CtrlV: Filter(flip-vert) \n\ + H: Filter(flip-horz) \n\ + ~Alt ~ShiftT: Filter(rotate-cw) \n\ + ShiftT: Filter(rotate-ccw) \n\ + AltT: Rotate() \n\ + I: Filter(invert) \n\ + ~AltE: Color() \n\ + AltE: F3x3(-1,-1,-1,-1,8,-1,-1,-1,-1) \n\ + AltB: F3x3(1,1,1,1,1,1,1,1,1, 1,9) \n\ + AltM: F3x3(1,0,0,0,0,0,0,0,-1, 0,0,128) \n\ + osfHelp: Man(ida) \n\ + Q: Exit() + +Ida.view.VertScrollBar.accelerators: #override \ + : IncrementUpOrLeft(Up) \n\ + : IncrementDownOrRight(Down)\n\ + ~CtrlosfUp: IncrementUpOrLeft(Up) \n\ + ~CtrlosfDown: IncrementDownOrRight(Down)\n\ + CtrlosfUp: PageUpOrLeft(Up) \n\ + CtrlosfDown: PageDownOrRight(Down) +Ida.view.HorScrollBar.accelerators: #override \ + ~CtrlosfLeft: IncrementUpOrLeft(Left) \n\ + ~CtrlosfRight: IncrementDownOrRight(Right)\n\ + CtrlosfLeft: PageUpOrLeft(Left) \n\ + CtrlosfRight: 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 \ + space: Next() \n\ + osfDelete: Prev() \n\ + osfBackSpace: Prev() \n\ + KP_Add: Zoom(inc) \n\ + KP_Subtract: Zoom(dec) + +ctrl*bar*load.acceleratorText: Ctrl+L +ctrl*bar*load.accelerator: CtrlL +ctrl*bar*save.acceleratorText: Ctrl+S +ctrl*bar*save.accelerator: CtrlS +ctrl*bar*browse.acceleratorText: F +ctrl*bar*browse.accelerator: F +ctrl*bar*filelist.acceleratorText: L +ctrl*bar*filelist.accelerator: L +ctrl*bar*print.acceleratorText: Ctrl+P +ctrl*bar*print.accelerator: CtrlP +ctrl*bar*quit.acceleratorText: Q +ctrl*bar*quit.accelerator: Q + +ctrl*bar*undo.acceleratorText: U +ctrl*bar*undo.accelerator: U +ctrl*bar*copy.acceleratorText: Ctrl+C +ctrl*bar*copy.accelerator: CtrlC +ctrl*bar*paste.acceleratorText: Ctrl+V +ctrl*bar*paste.accelerator: CtrlV +ctrl*bar*flipv.acceleratorText: V +ctrl*bar*flipv.accelerator: V +ctrl*bar*fliph.acceleratorText: H +ctrl*bar*fliph.accelerator: H +ctrl*bar*rotcw.acceleratorText: T +ctrl*bar*rotcw.accelerator: ~Meta ~ShiftT +ctrl*bar*rotccw.acceleratorText: Shift+T +ctrl*bar*rotccw.accelerator: ShiftT +ctrl*bar*invert.acceleratorText: I +ctrl*bar*invert.accelerator: I +ctrl*bar*crop.acceleratorText: C +ctrl*bar*crop.accelerator: C +ctrl*bar*scale.acceleratorText: S +ctrl*bar*scale.accelerator: ~CtrlS +ctrl*bar*rotany.acceleratorText: Alt+T +ctrl*bar*rotany.accelerator: AltT + +ctrl*bar*gamma.acceleratorText: G +ctrl*bar*gamma.accelerator: G +ctrl*bar*color.acceleratorText: E +ctrl*bar*color.accelerator: ~AltE +ctrl*bar*blur.acceleratorText: Alt+B +ctrl*bar*blur.accelerator: AltB +ctrl*bar*sharpe.acceleratorText: Alt+S +ctrl*bar*sharpe.accelerator: AltS +ctrl*bar*edge.acceleratorText: Alt+E +ctrl*bar*edge.accelerator: AltE +ctrl*bar*emboss.acceleratorText: Alt+M +ctrl*bar*emboss.accelerator: Altm + +ctrl*bar*prev.acceleratorText: backspace +!ctrl*bar*prev.accelerator: Backspace +ctrl*bar*next.acceleratorText: space +!ctrl*bar*next.accelerator: space +ctrl*bar*prevpage.acceleratorText: PageUp +!ctrl*bar*prevpage.accelerator: osfPageUp +ctrl*bar*nextpage.acceleratorText: PageDown +!ctrl*bar*nextpage.accelerator: osfPageDown +ctrl*bar*zoomin.acceleratorText: plus +ctrl*bar*zoomin.accelerator: plus +ctrl*bar*zoomout.acceleratorText: minus +ctrl*bar*zoomout.accelerator: 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: 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: Q +*cbar*copy.acceleratorText: Ctrl+C +*cbar*copy.accelerator: CtrlC +*cbar*paste.acceleratorText: Ctrl+V +*cbar*paste.accelerator: CtrlV +*cbar*filter.acceleratorText: F +*cbar*filter.accelerator: 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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,¶m); +} + +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,¶m); + } 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 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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 + * + */ +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include + +#include + +#include +#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 create/update comment\n" + " -g (re)generate thumbnail\n" + " -o 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 [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 )\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 +Set jpeg comment tag to . +.TP +.B -g +(re)generate EXIF thumbnail. +.TP +.B -o +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 +.SH COPYRIGHT +Copyright (C) 2002 Gerd Knorr +.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 +#include +#include +#include + +#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 if your PDF file requires password. +.SH SEE ALSO +fbi(1), gs(1) +.SH AUTHOR +Gerd Knorr 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 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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 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) +g jump to image # +.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 +.SH COPYRIGHT +Copyright (C) 1999-2000 Gerd Knorr +.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#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 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + +#include +#include + +#include "list.h" +#include "ida.h" +#include "readers.h" +#include "filebutton.h" +#include "fileops.h" + +#include +#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 +#include +#include +#include + +#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 + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#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 + +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 +#include +#include +#include +#include +#include + +#include +#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 + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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,¶m); + } else { + gamma_val = value; + viewer_start_op(ida,&desc_map,¶m); + } +} + +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,¶m); + } else { + bright_val = value; + viewer_start_op(ida,&desc_map,¶m); + } +} + +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,¶m); + } else { + contrast_val = value; + viewer_start_op(ida,&desc_map,¶m); + } +} + +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,¶m); + 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 +.SH COPYRIGHT +Copyright (C) 2002 Gerd Knorr +.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 +#include +#include +#include +#include + +#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 . + * Otherwise, we get them from or ; we may have to + * pull in as well. + * Note that the core JPEG library does not require ; + * 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 . + */ + +#ifdef HAVE_STDDEF_H +#include +#endif + +#ifdef HAVE_STDLIB_H +#include +#endif + +#ifdef NEED_SYS_TYPES_H +#include +#endif + +#include + +/* + * We need memory copying and zeroing functions, plus strncpy(). + * ANSI and System V implementations declare these in . + * BSD doesn't have the mem() functions, but it does have bcopy()/bzero(). + * Some systems may declare memset and memcpy in . + * + * 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 +#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 +#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 , 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "jpeg/transupp.h" /* Support routines for jpegtran */ +#include "jpegtools.h" + +#include "misc.h" + +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +#include +#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 Binary files /dev/null and b/logo.jpg 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 +#include +#include + +#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 + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +# +# 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 +# +# 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 +#include +#include + +#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 +#include +#include +#include +#include + +#include +#include + +#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(§ion->entries); + list_add_tail(§ion->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,§ion->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,§ion->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(§ion->next); + while (!list_empty(§ion->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 ",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,§ion->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 = §ion->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 = §ion->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 = §ion->entries; + if (item->next == §ion->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 == §ion->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 == §ion->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,§ion->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,§ion->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,§ion->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 +#include +#include + +#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 +#include +#include +#include +#ifdef HAVE_ENDIAN_H +# include +#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 +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +#include + +#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 +#include +#include +#include +#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 +#include +#include +#include +#include + +#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 +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 +#include +#include +#include + +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#ifdef HAVE_ENDIAN_H +# include +#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 +#include +#include + +#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 +#include +#include +#include +#include +#include + +#include "readers.h" +#include "viewer.h" +#include "sane.h" +#include "ida.h" + +#include +#include +#include +#include +#include + +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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +/* -------------------------------------------------------------------------- */ + +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 [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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 + +#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 +#include +#include +#include +#include +#include + +#include "readers.h" +#include "writers.h" +#include "misc.h" + +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include + +#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 +#include +#include +#include + +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include + +#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 +#include +#include + +#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 + +/* 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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 +#include +#include + +#include +#include +#include +#include +#include + +#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 + ("XdndEnter: Xdnd()\n" + "XdndPosition: Xdnd()\n" + "XdndLeave: Xdnd()\n" + "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); -- cgit